diff --git a/package.json b/package.json index c7ab914..46d7282 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bulb-project-api", - "version": "0.5.0", + "version": "0.7.0", "description": "API Service for Bulb Project", "author": "uStudio Front-End Department", "license": "MIT", diff --git a/src/do/algorithms/categories/31110000-0.ts b/src/do/algorithms/categories/31110000-0.ts index c7b1e94..2a75bad 100644 --- a/src/do/algorithms/categories/31110000-0.ts +++ b/src/do/algorithms/categories/31110000-0.ts @@ -115,7 +115,7 @@ export class ElectricMotors extends AlgorithmEngine { }) ); - const modeOfUse = this.tryGetModeOfUse(requestedNeed.requirementResponses, '03'); + const modeOfUse = ElectricMotors.tryGetModeOfUse(requestedNeed.requirementResponses, '03'); if (modeOfUse) { const { hoursInDay, daysInWeek } = modeOfUse; @@ -125,7 +125,7 @@ export class ElectricMotors extends AlgorithmEngine { requestedPower.split('-').length : +requestedPower; - const tariff = this.tryGetTariff(requestedNeed.requirementResponses, '04'); + const tariff = ElectricMotors.tryGetTariff(requestedNeed.requirementResponses, '04'); const ei1YearEnergyProduction = ((availableVariants.find(({ relatedItem }) => relatedItem === Variants.IE1)?.metrics[0].observations[0] .measure as number) || 0) * diff --git a/src/do/algorithms/categories/31500000-1.ts b/src/do/algorithms/categories/31500000-1.ts index 71908e8..6d35af6 100644 --- a/src/do/algorithms/categories/31500000-1.ts +++ b/src/do/algorithms/categories/31500000-1.ts @@ -23,6 +23,15 @@ import type { SpecificationResponse } from '../../entity/specification'; import { DocxGeneratorService } from '../../services/docx-generator'; import { CsvService } from '../../services/csv'; +enum Criteria { + TypeOfNeed = '01', + TypeOfBase = '02', + BulbType = '03', + LightFlowType = '04', + ModeOfUse = '05', + Tariff = '06', +} + enum Variants { Incandescent = '31519100-8', Halogen = '31512000-8', @@ -71,32 +80,50 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { super(); } - private static calculateEnergyEfficiencyClass(eei: number): string { - if (eei <= 0.13) { - return 'A++'; - } + private static reduceEnergyEfficiencyFormulas( + eei: number, + configuration: Array<[energyEfficiencyClass: string, upperRange?: number]> + ): string { + /* eslint-disable immutable/no-let */ + let [finalEnergyEfficiencyClass] = configuration[0]; - if (eei > 0.13 && eei <= 0.18) { - return 'A+'; - } + for (let i = 0; i < configuration.length; i += 1) { + const [energyEfficiencyClass, upperRange = eei] = configuration[i]; + const [, lowerRange = 0] = configuration[i - 1] || []; - if (eei > 0.18 && eei <= 0.4) { - return 'A'; - } + if (eei > lowerRange && eei <= upperRange) { + finalEnergyEfficiencyClass = energyEfficiencyClass; - if (eei > 0.4 && eei <= 0.95) { - return 'B'; + break; + } } - if (eei > 0.95 && eei <= 1.2) { - return 'C'; - } + return finalEnergyEfficiencyClass; + } - if (eei > 1.2 && eei <= 1.75) { - return 'D'; + private static calculateEnergyEfficiencyClass(eei: number, lightFlowType: LightFlowType): string { + if (lightFlowType === LightFlowType.Directional) { + return LightingEquipmentAndElectricLamps.reduceEnergyEfficiencyFormulas(eei, [ + ['A++', 0.13], + ['A+', 0.18], + ['A', 0.4], + ['B', 0.95], + ['C', 1.2], + ['D', 1.75], + ['E'], + ]); } - return 'E'; + // LightFlowType.NonDirectional + return LightingEquipmentAndElectricLamps.reduceEnergyEfficiencyFormulas(eei, [ + ['A++', 0.11], + ['A+', 0.17], + ['A', 0.24], + ['B', 0.6], + ['C', 0.8], + ['D', 0.95], + ['E'], + ]); } private static getValueFromResponses(responses: RequirementResponse[], requirementId: string): unknown { @@ -303,6 +330,16 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { ]; } + private static getLightFlowType(requirementResponses: RequirementResponse[], criterionNumber: string): LightFlowType { + const [lightFlowTypeResponse] = this.getResponsesForCriterion(requirementResponses, criterionNumber); + + if (!lightFlowTypeResponse) { + return LightFlowType.NonDirectional; + } + + return lightFlowTypeResponse.value as LightFlowType; + } + // eslint-disable-next-line sonarjs/cognitive-complexity public async getCalculation({ category: { items, criteria, conversions }, @@ -359,9 +396,10 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }; }, {} as Calculation); - const selectedBulbType = requirementResponses.find(({ requirement }) => requirement.id.startsWith('03'))?.value as - | Variants - | undefined; + const selectedBulbType = LightingEquipmentAndElectricLamps.getResponsesForCriterion( + requirementResponses, + Criteria.BulbType + )[0]?.value as Variants | undefined; if (selectedBulbType === undefined || !Object.values(Variants).includes(selectedBulbType)) { throw new BadRequestException(`Incorrect lamp type was provided.`); @@ -374,7 +412,10 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { } // 1) Type of need - const typeOfNeedResponses = requirementResponses.filter(({ requirement }) => requirement.id.startsWith('01')); + const typeOfNeedResponses = LightingEquipmentAndElectricLamps.getResponsesForCriterion( + requirementResponses, + Criteria.TypeOfNeed + ); const typeOfNeedResponsesIsConsistent = typeOfNeedResponses.every(({ requirement }) => { return typeOfNeedResponses[0].requirement.id.slice(2, 4) === requirement.id.slice(2, 4); @@ -665,11 +706,11 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { } // 3) Bulb lifetime - const modeOfUse = this.tryGetModeOfUse(requirementResponses, '04'); + const modeOfUse = LightingEquipmentAndElectricLamps.tryGetModeOfUse(requirementResponses, Criteria.ModeOfUse); if (modeOfUse) { const { hoursInDay, daysInWeek } = modeOfUse; - const tariff = this.tryGetTariff(requirementResponses, '05'); + const tariff = LightingEquipmentAndElectricLamps.tryGetTariff(requirementResponses, Criteria.Tariff); (Object.keys(availableBulbTypes) as Variants[]).forEach((bulbType) => { const { quantity, power } = availableBulbTypes[bulbType]; @@ -725,7 +766,8 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { (Object.keys(availableBulbTypes) as Variants[]).forEach((bulbType) => { availableBulbTypes[bulbType].eeClass = LightingEquipmentAndElectricLamps.calculateEnergyEfficiencyClass( - availableBulbTypes[bulbType].eei + availableBulbTypes[bulbType].eei, + LightingEquipmentAndElectricLamps.getLightFlowType(requirementResponses, Criteria.LightFlowType) ); }); @@ -840,15 +882,15 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }; enum Functionality { - RatedLifetime = 'Rated lifetime', - LumenMaintenance = 'Lumen maintenance', - SwitchingCycle = 'Switching cycle', - StartingTime = 'Starting time', - WarmUp = 'Warm up', - PrematureFailure = 'Premature failure rate', - PowerFactor = 'Power factor', - ColourRendering = 'Colour rendering (Ra)', - SurvivalFactor = 'Survival factor', + RatedLifetime = 'Номінальний термін служби ламп', + LumenMaintenance = 'Збереження світловіддачі', + SwitchingCycle = 'Кількість циклів вимикання перед виходом з ладу', + StartingTime = 'Час запуску', + WarmUp = 'Час розігрівання лампи', + PrematureFailure = 'Відсоток передчасного виходу з ладу', + PowerFactor = 'Коефіцієнт потужності', + ColourRendering = 'Кольоропередача', + SurvivalFactor = 'Коефіцієнт довговічності лампи', } switch (relatedItem) { @@ -863,11 +905,11 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 2000, unit: { id: '', - name: 'h', + name: 'год', }, }, { - title: `${Functionality.LumenMaintenance} at 75% of rated average lifetime`, + title: `${Functionality.LumenMaintenance} при 75% номінального середнього терміна служби ламп`, dataType: 'integer', minValue: 85, unit: { @@ -886,20 +928,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: 0.2, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'number', maxValue: 1, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 200 h`, + title: `${Functionality.PrematureFailure} при 200 год`, dataType: 'number', maxValue: 5, unit: { @@ -928,11 +970,11 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 2000, unit: { id: '', - name: 'h', + name: 'год', }, }, { - title: `${Functionality.LumenMaintenance} at 75% of rated average lifetime`, + title: `${Functionality.LumenMaintenance} при 75% номінального середнього терміна служби ламп`, dataType: 'integer', minValue: 80, unit: { @@ -951,20 +993,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: 0.2, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'number', maxValue: 1, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 200 h`, + title: `${Functionality.PrematureFailure} при 200 год`, dataType: 'number', maxValue: 5, unit: { @@ -996,11 +1038,11 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 2000, unit: { id: '', - name: 'h', + name: 'год', }, }, { - title: `${Functionality.LumenMaintenance} at 75% of rated average lifetime`, + title: `${Functionality.LumenMaintenance} при 75% номінального середнього терміна служби ламп`, dataType: 'integer', minValue: 85, unit: { @@ -1019,20 +1061,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: 0.2, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'number', maxValue: 1, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 200 h`, + title: `${Functionality.PrematureFailure} при 200 год`, dataType: 'number', maxValue: 5, unit: { @@ -1061,11 +1103,11 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 2000, unit: { id: '', - name: 'h', + name: 'год', }, }, { - title: `${Functionality.LumenMaintenance} at 75% of rated average lifetime`, + title: `${Functionality.LumenMaintenance} при 75% номінального середнього терміна служби ламп`, dataType: 'integer', minValue: 80, unit: { @@ -1084,20 +1126,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: 0.2, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'number', maxValue: 1, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 200 h`, + title: `${Functionality.PrematureFailure} при 200 год`, dataType: 'number', maxValue: 5, unit: { @@ -1124,12 +1166,12 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { functionalityRequirementGroup.requirements.push( ...([ { - title: `${Functionality.SurvivalFactor} at 6000 h`, + title: `${Functionality.SurvivalFactor} при 6000 год`, dataType: 'number', minValue: 0.7, }, { - title: `${Functionality.LumenMaintenance} at 2000 h`, + title: `${Functionality.LumenMaintenance} при 2000 год`, dataType: 'integer', minValue: 88, unit: { @@ -1138,7 +1180,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }, }, { - title: `${Functionality.LumenMaintenance} at 6000 h`, + title: `${Functionality.LumenMaintenance} при 6000 год`, dataType: 'integer', minValue: 70, unit: { @@ -1152,7 +1194,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 30000, unit: { id: '', - name: 'h', + name: 'год', }, }, { @@ -1161,20 +1203,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: power >= 10 ? 1 : 1.5, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'integer', maxValue: 40, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 400 h`, + title: `${Functionality.PrematureFailure} при 400 год`, dataType: 'integer', maxValue: 2, unit: { @@ -1183,7 +1225,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }, }, { - title: 'UVA + UVB radiation', + title: 'Випромінення UVA + UVB', dataType: 'integer', maxValue: 2, unit: { @@ -1192,7 +1234,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }, }, { - title: 'UVC radiation', + title: 'Випромінення UVC', dataType: 'number', maxValue: 0.01, unit: { @@ -1225,12 +1267,12 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { functionalityRequirementGroup.requirements.push( ...([ { - title: `${Functionality.SurvivalFactor} at 6000 h`, + title: `${Functionality.SurvivalFactor} при 6000 год`, dataType: 'number', minValue: 0.7, }, { - title: `${Functionality.LumenMaintenance} at 2000 h`, + title: `${Functionality.LumenMaintenance} при 2000 год`, dataType: 'integer', minValue: 83, unit: { @@ -1239,7 +1281,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { }, }, { - title: `${Functionality.LumenMaintenance} at 6000 h`, + title: `${Functionality.LumenMaintenance} при 6000 год`, dataType: 'integer', minValue: 70, unit: { @@ -1253,7 +1295,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: 30000, unit: { id: '', - name: 'h', + name: 'год', }, }, { @@ -1262,20 +1304,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: power >= 10 ? 1 : 1.5, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 60% of lumenus flux`, + title: `${Functionality.WarmUp} до 60% Ф`, dataType: 'integer', maxValue: 40, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 1000 h`, + title: `${Functionality.PrematureFailure} при 1000 год`, dataType: 'integer', maxValue: 5, unit: { @@ -1310,12 +1352,12 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { functionalityRequirementGroup.requirements.push( ...([ { - title: `${Functionality.SurvivalFactor} at 6000 h`, + title: `${Functionality.SurvivalFactor} при 6000 год`, dataType: 'number', minValue: 0.9, }, { - title: `${Functionality.LumenMaintenance} at 6000 h`, + title: `${Functionality.LumenMaintenance} при 6000 год`, dataType: 'integer', minValue: 80, unit: { @@ -1329,7 +1371,7 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { minValue: techChars[Variants.LED].timeRate / 2, unit: { id: '', - name: 'h', + name: 'год', }, }, { @@ -1338,20 +1380,20 @@ export class LightingEquipmentAndElectricLamps extends AlgorithmEngine { maxValue: 0.5, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.WarmUp} to 95% of lumenus flux`, + title: `${Functionality.WarmUp} до 95% Ф`, dataType: 'number', maxValue: 2, unit: { id: '', - name: 's', + name: 'сек', }, }, { - title: `${Functionality.PrematureFailure} at 1000 h`, + title: `${Functionality.PrematureFailure} при 1000 год`, dataType: 'integer', maxValue: 5, unit: { diff --git a/src/do/entity/algorithm-engine.ts b/src/do/entity/algorithm-engine.ts index 508175b..2a78ecb 100644 --- a/src/do/entity/algorithm-engine.ts +++ b/src/do/entity/algorithm-engine.ts @@ -12,35 +12,20 @@ interface ModeOfUse { export abstract class AlgorithmEngine { public readonly categoryId: string; - protected tryGetTariff(requirementResponses: RequirementResponse[], criterionNumber: string): number | void { - const tariffResponses = requirementResponses.filter(({ requirement }) => { + protected static getResponsesForCriterion( + requirementResponses: RequirementResponse[], + criterionNumber: string + ): RequirementResponse[] { + return requirementResponses.filter(({ requirement }) => { return requirement.id.startsWith(criterionNumber); }); - - if (tariffResponses.length !== 1) { - throw new BadRequestException(`Incorrect tariffs information provided.`); - } - - const tariffValue = tariffResponses[0].value; - const requirementReferenceId = tariffResponses[0].requirement.id; - - if ( - !['number', 'boolean'].includes(typeof tariffValue) || - (requirementReferenceId === '0401010000' && (typeof tariffValue !== 'number' || tariffValue <= 0)) || - (requirementReferenceId === '0402010000' && (typeof tariffValue !== 'boolean' || !tariffValue)) - ) { - throw new BadRequestException(`Requirement responses for tariffs are not valid.`); - } - - if (typeof tariffValue === 'number') { - return tariffValue; - } } - protected tryGetModeOfUse(requirementResponses: RequirementResponse[], criterionNumber: string): ModeOfUse | void { - const modeOfUseResponses = requirementResponses.filter(({ requirement }) => { - return requirement.id.startsWith(criterionNumber); - }); + protected static tryGetModeOfUse( + requirementResponses: RequirementResponse[], + criterionNumber: string + ): ModeOfUse | void { + const modeOfUseResponses = this.getResponsesForCriterion(requirementResponses, criterionNumber); if (modeOfUseResponses.length === 0) { throw new BadRequestException(`Mode of use responses must be provided.`); @@ -72,6 +57,30 @@ export abstract class AlgorithmEngine { } } + protected static tryGetTariff(requirementResponses: RequirementResponse[], criterionNumber: string): number | void { + const tariffResponses = this.getResponsesForCriterion(requirementResponses, criterionNumber); + + if (tariffResponses.length !== 1) { + throw new BadRequestException(`Incorrect tariffs information provided.`); + } + + const tariffValue = tariffResponses[0].value; + const requirementReferenceId = tariffResponses[0].requirement.id; + + if ( + !['number', 'boolean'].includes(typeof tariffValue) || + (requirementReferenceId === `${criterionNumber}01010000` && + (typeof tariffValue !== 'number' || tariffValue <= 0)) || + (requirementReferenceId === `${criterionNumber}02010000` && (typeof tariffValue !== 'boolean' || !tariffValue)) + ) { + throw new BadRequestException(`Requirement responses for tariffs are not valid.`); + } + + if (typeof tariffValue === 'number') { + return tariffValue; + } + } + public abstract getCalculation(payload: CalculationPayload): Promise; public abstract getSpecification(payload: SpecificationPayload): SpecificationResponse;