2. Модель расчёта CalcModel

Документ Версия: 1.2 changed

Контракт модели расчёта: InputModel (единая модель ТЭ + ЭЭ), OutputModel (расчётные параметры МОП), CalcLog.

⟨/⟩ Исходник
CalcModel {
  context: {
    idMap: [
      { refId: string, serviceId: uuid }
    ]
  },
  inputModel: InputModel,
  outputModel: OutputModel,
  calcLog: CalcLog
}

context.idMap — единообразный массив пар { refId, serviceId }, связывающий внутренние идентификаторы модуля расчёта с идентификаторами сущностей сервиса в domain-data.json.

Синглтоны (одна сущность на модель, refId — фиксированное well-known имя):

refId Сущность сервиса Коллекция в domain-data.json
consumptionModel МодельОбъектаПотребления consumptionModels
building Здание buildings
geometry Форма и геометрия buildingGeometry
walls Стены walls
windowModel Модель окон windowModels
doorModel Модель ворот doorModels
roof ВОК roofs
groundContact ЗКГ groundContacts
ventilation Вентиляция ventilations
operatingConditions Условия эксплуатации operatingConditions
heatingPeriod Условия ОП heatingPeriodConditions
lightingModel Модель освещения ЭЭ lightingModels
receiverModel Модель токоприёмников ЭЭ receiverModels

Коллекции (несколько элементов на модель, refId генерируется в PrepareInputModel):

refId (шаблон) Сущность сервиса Коллекция в domain-data.json
windowKind-N Вид остекления проёмов windows
doorKind-N Вид воротных проёмов doors
lightingKind-N Вид освещения buildingLighting
receiverKind-N Вид токоприёмника buildingEEConsumers

2.1 InputModel (единая модель ТЭ + ЭЭ)

Единое правило для PrepareInputModel: если в расчёт подставляется значение из справочника, передаются:

InputModel {
  te: TeInputSection,
  ee: EeInputSection
}

InputModel не содержит сервисных идентификаторов. Сущности внутри InputModel идентифицируются через refId (см. §2).

2.1.1 te: TeInputSection

TeInputSection {
  geometry: {
    buildingFormCode: string,
    orientationSideACode: string,
    buildingHeightM: ValueObject,
    buildingFloorCount: ValueObject,
    upperFloorLevelM: ValueObject,
    heatedVolumeM3: ValueObject,
    heatedAreaM2: ValueObject,
    dimensionsM: {
      A: ValueObject,
      B: ValueObject,
      C: ValueObject,
      D: ValueObject,
      E: ValueObject,
      F: ValueObject,
      G: ValueObject,
      H: ValueObject
    }
  },
  walls: {
    material: {
      materialCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    },
    mainInsulation: {
      insulationCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    },
    additionalInsulation: {
      insulationCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    }
  },
  roof: {
    roofTypeCode: string,
    hasAttic: boolean,
    ridgeLengthM: ValueObject | null,
    customRoofAreaM2: ValueObject | null,
    customAtticWallAreaM2: ValueObject | null,
    floorLayer: {
      materialCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    },
    floorInsulation: {
      insulationCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    },
    roofInsulation: {
      insulationCode: string,
      thicknessMm: ValueObject,
      thermalConductivity: ValueObject
    }
  },
  groundContact: {
    hasBasement: boolean,
    basementCeilingHeightM: ValueObject | null,
    basementFloorLevelM: ValueObject | null,
    customGroundContactAreaM2: ValueObject | null
  },
  windows: {
    items: [
      {
        refId: string,
        openingAreaM2: ValueObject,
        openingsBySide: {
          A: ValueObject,
          B: ValueObject,
          C: ValueObject,
          D: ValueObject,
          E: ValueObject,
          F: ValueObject,
          G: ValueObject,
          H: ValueObject
        },
        thermalResistance: ValueObject,
        shadingCoefficient: ValueObject,
        solarRadiationCoefficient: ValueObject,
        airPermeabilityAt10Pa: ValueObject
      }
    ]
  },
  doors: {
    items: [
      {
        refId: string,
        openingAreaM2: ValueObject,
        openingsBySide: {
          A: ValueObject,
          B: ValueObject,
          C: ValueObject,
          D: ValueObject,
          E: ValueObject,
          F: ValueObject,
          G: ValueObject,
          H: ValueObject
        },
        thermalResistance: ValueObject,
        airPermeabilityAt10Pa: ValueObject
      }
    ]
  },
  ventilation: {
    hasMechanicalVentilation: boolean,
    hasRecuperator: boolean,
    recuperatorEfficiency: ValueObject | null
  },
  operating: {
    workplacesCount: ValueObject,
    internalTemperatureC: ValueObject,
    buildingPurposeCode: string,
    airConsumptionM3PerPersonHour: ValueObject,
    heatingRegulationTypeCode: string,
    kReg: ValueObject,
    energySupplyTypeCode: string
  },
  heatingPeriod: {
    durationDays: ValueObject,
    externalTemperatureC: ValueObject,
    windSpeedMps: ValueObject,
    latitudeDeg: ValueObject
  },
  solarRadiation: {
    items: [
      {
        directionCode: string,
        monthNumber: number,
        radiation: ValueObject
      }
    ]
  },
  normative: {
    specificHeatConsumptionNorm: {
      recordCode: string,
      valueObject: ValueObject
    },
    energyEfficiencyClasses: [
      {
        code: string,
        deviationMinPercent: ValueObject,
        deviationMaxPercent: ValueObject
      }
    ]
  },
  coefficients: {
    surfaceThermalResistance: {
      code: string,
      valueObject: ValueObject
    },
    airSpecificHeat: {
      code: string,
      valueObject: ValueObject
    },
    reductionBuildingsAirVolume: {
      code: string,
      valueObject: ValueObject
    },
    workingTimePerDayH: {
      code: string,
      valueObject: ValueObject
    },
    basementZonesThermalResistance: {
      I: {
        code: string,
        valueObject: ValueObject
      },
      II: {
        code: string,
        valueObject: ValueObject
      },
      III: {
        code: string,
        valueObject: ValueObject
      },
      IV: {
        code: string,
        valueObject: ValueObject
      }
    }
  }
}

Число в te.solarRadiation.items[].radiation берётся только из поля value записи справочника (solarRadiationData / solarRadiationRecord в domain-schema.json). В корректном наполнении справочника однозначно задаётся кортеж (сторона света, нижняя граница широтного диапазона в градусах северной широты, номер месяца); в массиве items для выбранного диапазона широты здания не ожидается больше одного элемента с одной и той же парой (directionCode, monthNumber).

2.1.2 ee: EeInputSection

EeInputSection {
  lighting: {
    items: [
      {
        refId: string,
        fixturesCount: ValueObject,
        lampsPerFixture: ValueObject,
        lampPowerW: ValueObject,
        hoursPerDay: ValueObject,
        daysPerYear: ValueObject
      }
    ]
  },
  receivers: {
    items: [
      {
        refId: string,
        totalPowerKw: ValueObject,
        hoursPerDay: ValueObject,
        daysPerYear: ValueObject
      }
    ]
  }
}

2.2 OutputModel (расчётные параметры МОП)

OutputModel {
  te: TeOutputSection,
  ee: EeOutputSection,
  totals: CommonTotals
}

2.2.1 te: TeOutputSection

Все расчётные поля ТЭ задаются в формате ValueObject.

TeOutputSection {
  energyEfficiencyClass: {
    classCode: string,
    normativeSpecificCharacteristic: ValueObject,
    calcSpecificCharacteristic: ValueObject,
    deviationPercent: ValueObject
  },
  keBuilding: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject
  },
  keWalls: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    overlapThicknessBySideM: {
      A: ValueObject, B: ValueObject, C: ValueObject, D: ValueObject,
      E: ValueObject, F: ValueObject, G: ValueObject, H: ValueObject
    },
    innerPerimeterM: ValueObject,
    innerSurfaceAreaM2: ValueObject
  },
  keWindows: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    airPermeabilityResistance: ValueObject,
    branch: { formMethodSectionId: string, orientationMethodSectionId: string },
    areaBySide: { A: ValueObject, B: ValueObject, C: ValueObject, D: ValueObject, E: ValueObject, F: ValueObject, G: ValueObject, H: ValueObject },
    tempSidesS1ToS4: { s1: ValueObject, s2: ValueObject, s3: ValueObject, s4: ValueObject },
    areaByDirection: { north: ValueObject, east: ValueObject, south: ValueObject, west: ValueObject },
    solarRadiation: {
      byDirection: [
        { directionCode: string, value: ValueObject }
      ]
    },
    items: [{
      refId: string,
      specHeatProtectChar: ValueObject,
      areaM2: ValueObject,
      thermalResistance: ValueObject,
      heatLossEnergyThousandKwh: ValueObject,
      heatLossPowerKw: ValueObject,
      airPermeabilityResistance: ValueObject,
      infiltrationAirKgH: ValueObject
    }]
  },
  keDoors: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    airPermeabilityResistance: ValueObject,
    branch: { formMethodSectionId: string, orientationMethodSectionId: string },
    areaBySide: { A: ValueObject, B: ValueObject, C: ValueObject, D: ValueObject, E: ValueObject, F: ValueObject, G: ValueObject, H: ValueObject },
    tempSidesS1ToS4: { s1: ValueObject, s2: ValueObject, s3: ValueObject, s4: ValueObject },
    areaByDirection: { north: ValueObject, east: ValueObject, south: ValueObject, west: ValueObject },
    items: [{
      refId: string,
      specHeatProtectChar: ValueObject,
      areaM2: ValueObject,
      thermalResistance: ValueObject,
      heatLossEnergyThousandKwh: ValueObject,
      heatLossPowerKw: ValueObject,
      airPermeabilityResistance: ValueObject,
      infiltrationAirKgH: ValueObject
    }]
  },
  keUpperEnclosures: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    atticWallAreaM2: ValueObject,
    roofCoverAreaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    branch: { atticWallMethodSectionId: string, roofCoverMethodSectionId: string }
  },
  keGroundContact: {
    specHeatProtectChar: ValueObject,
    areaM2: ValueObject,
    basementWallAreaM2: ValueObject,
    floorAreaM2: ValueObject,
    thermalResistance: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    branch: { basementWallMethodSectionId: string, floorAreaMethodSectionId: string, areaMethodSectionId: string, zonesFormMethodSectionId: string },
    zonesAreaM2: { I: ValueObject, II: ValueObject, III: ValueObject, IV: ValueObject },
    zoneSideLengths: object
  },
  solarRadiation: {
    byDirection: [
      { directionCode: string, value: ValueObject }
    ]
  },
  ventilation: {
    specVentChar: ValueObject,
    heatLossEnergyThousandKwh: ValueObject,
    heatLossPowerKw: ValueObject,
    infiltrationAirMassTotal: ValueObject,
    supplyAirFlowM3H: ValueObject,
    ventilationHours: ValueObject,
    recuperatorEfficiency: ValueObject,
    infiltrationAirKgH: ValueObject,
    infiltrationHours: ValueObject,
    pressureDifferencePa: ValueObject,
    airWeightOutside: ValueObject,
    airWeightInside: ValueObject,
    branch: { ventHoursMethodSectionId: string, recuperatorMethodSectionId: string, infiltrationHoursMethodSectionId: string }
  },
  heatGains: {
    specHeatGainsChar: ValueObject,
    heatGainsEnergyTotal: ValueObject,
    heatGainsPowerTotal: ValueObject,
    specDomesticHeatGainsChar: ValueObject,
    domesticHeatGainsEnergy: ValueObject,
    domesticHeatGainsPower: ValueObject,
    specSolarHeatGainsChar: ValueObject,
    solarHeatGainsEnergy: ValueObject,
    solarHeatGainsPower: ValueObject,
    coefHeatGainsEfficiency: ValueObject
  },
  heatingPeriod: {
    degreeDays: ValueObject
  }
}

2.2.2 ee: EeOutputSection

EeOutputSection {
  lighting: {
    items: [{ refId: string, installedPowerKw: ValueObject, annualConsumptionThousandKwh: ValueObject }],
    installedPowerKw: ValueObject,
    annualConsumptionThousandKwh: ValueObject
  },
  receivers: {
    items: [{ refId: string, installedPowerKw: ValueObject, annualConsumptionThousandKwh: ValueObject }],
    installedPowerKw: ValueObject,
    annualConsumptionThousandKwh: ValueObject
  },
  subtotal: {
    installedPowerKw: ValueObject,
    annualConsumptionThousandKwh: ValueObject
  }
}

2.2.3 totals: CommonTotals

CommonTotals {
  eeWithoutHeating: {
    installedPowerKw: ValueObject,
    annualConsumptionThousandKwh: ValueObject
  },
  electricHeating: {
    enabled: boolean,
    annualConsumptionThousandKwh: ValueObject
  },
  eeTotal: {
    annualConsumptionThousandKwh: ValueObject
  },
  teTotal: {
    annualConsumptionThousandKwh: ValueObject
  }
}

Правила агрегации итогов не дублируются в этом документе и задаются только в методиках:

2.3 CalcLog

2.3.1 Контракт CalcLog и записей CalcLogEntry

Оркестратор ModelCalc формирует журнал шагов расчёта. Каждый шаг — одна запись CalcLogEntry на вызов одной функции расчёта (functionId).

CalcLog {
  startedAt: string,
  finishedAt: string,
  records: CalcLogEntry[],
  errorsCount: ValueObject,
  warningsCount: ValueObject
}

Структура CalcLogEntry одинакова для любого статуса:

CalcLogEntry {
  functionId: string,
  methodSectionId: string,
  status: "ok" | "missing_input" | "error" | "not_found",
  inputs: object,
  outputs: object,
  formulaTemplate: string,
  formulaSubstitution: object,
  missingInputs: string[]
}

Правила формирования formulaSubstitution:

Правила при status = missing_input:

2.3.2 Подсказка к значению расчётного показателя (UI)

Для отображаемого в интерфейсе значения поля OutputModel с путём outputPath:

  1. Найти запись журнала: выбрать CalcLogEntry из records, в которой в outputs присутствует расчётный результат для outputPath (или согласованное с оркестратором правило «последняя запись, записавшая этот путь»). Если таких несколько (агрегация), UI следует правилам продукта, зафиксированным в отдельном требовании к экрану.
  2. Собрать текст подсказки:
    • взять formulaTemplate;
    • подставить значения из formulaSubstitution вместо символов (включая отображение "-" для пропусков);
    • при необходимости добавить methodSectionId / ссылку на фрагмент методики (ТЭ или ЭЭ).
  3. При missing_input: показать, что значение не получено, и вывести missingInputs (через запятую или списком), без выдумывания чисел в UI.