Спецификация реализации отчёта NTR-R01
Карта реализации отчёта NTR-R01 в src/Ntr.Reports: раскладка папок, контракты, конвейер сборки, Excel-шаблон.
1 Спецификация реализацииImplSpecifikaciyaRealizacii⟨/⟩
1.1 Расположение и структура папокImplRaspolozhenieIStrukturaPapok⟨/⟩
Отчёт реализуется в проекте src/Ntr.Reports/ в папке NTR-R01/. В корне папки лежит только один основной файл-оркестратор NtrR01ReportGenerator.cs; всё остальное разнесено по тематическим подпапкам.
NTR-R01/
├── NtrR01ReportGenerator.cs ← оркестратор сборки отчёта (единственный файл в корне папки)
├── readme.md
│
├── Abstractions/ публичные контракты модуля
│ ├── INtrR01ReportGenerator.cs
│ ├── INtrR01ReportDataProvider.cs
│ ├── INtrR01SectionBuilder.cs
│ └── NtrR01ReportGenerationResult.cs
│
├── Params/ входные параметры отчёта
│ └── GenerateNtrR01ReportParams.cs параметры запуска отчёта (NavId, BuildingId?,
│ CollectionTaskId, EnergyProfileStatusIds,
│ EnergyProfileConditionsStatusIds, DateOfCreation)
│
├── Model/ модель готового отчёта (immutable records)
│ ├── NtrR01Report.cs корень: ReportParams + Sections
│ ├── SectionKind.cs enum секций данных: ElectricalEnergyConsumption /
│ │ HeatEnergyConsumption /
│ │ EnergyEfficiencyClass / Walls / Roofs
│ ├── SectionMetadata.cs заголовки и имена листов Excel
│ ├── CalcKind.cs enum видов расчёта: Normative / Standard /
│ │ Technological / Custom
│ ├── RowLevel.cs enum уровней строки секции:
│ │ Company / ExecutiveApparatus / Mes / Pmes /
│ │ Substation / Building
│ ├── SectionRow.cs узел дерева строк: RowLevel, Children,
│ │ путь иерархии, per-Building поля,
│ │ 4 слота по CalcKind с per-секционными колонками
│ └── Sections/ per-секционные колонки слотов
│ ├── ElectricalEnergyConsumptionSlot.cs
│ ├── HeatEnergyConsumptionSlot.cs
│ ├── EnergyEfficiencyClassSlot.cs
│ ├── WallsSlot.cs
│ └── RoofsSlot.cs
│
├── DataProvider/ выборка сырых данных из NtrReadDbContext
│ ├── NtrR01ReportDataProvider.cs
│ ├── NtrR01SourceData.cs агрегат сырых данных
│ └── Sources/ сырые DTO с подтянутой иерархией ИА/МЭС/ПМЭС/ПС
│ ├── ConsumptionModelSource.cs
│ ├── BuildingSource.cs
│ ├── EnergyProfileSource.cs
│ └── EnergyProfileCollectionTaskSource.cs
│
├── Sections/ по одному builder'у на каждую секцию
│ ├── ElectricalEnergyConsumptionSectionBuilder.cs
│ ├── HeatEnergyConsumptionSectionBuilder.cs
│ ├── EnergyEfficiencyClassSectionBuilder.cs
│ ├── WallsSectionBuilder.cs
│ └── RoofsSectionBuilder.cs
│
├── Hierarchy/ построение дерева орг-структуры из выбранных зданий
│ └── OrgHierarchyTreeBuilder.cs строит дерево по фактически присутствующим зданиям
│
└── Excel/
├── INtrR01ReportExcelRenderer.cs
├── NtrR01ReportExcelRenderer.cs
└── Templates/
└── ntr-r01-template.xlsx встроенный ресурс
1.2 Архитектурные решенияImplArhitekturnyeResheniya⟨/⟩
1.2.1 Объект отчёта `NtrR01Report`ImplObektOtchetaNtrr01report⟨/⟩
Отчёт содержит секции и параметры. Все модели — record с init-свойствами.
NtrR01Report:
ReportParams : GenerateNtrR01ReportParams
ElectricalEnergyConsumption: Section<ElectricalEnergyConsumptionSlot>
HeatEnergyConsumption : Section<HeatEnergyConsumptionSlot>
EnergyEfficiencyClass : Section<EnergyEfficiencyClassSlot>
Walls : Section<WallsSlot>
Roofs : Section<RoofsSlot> // ВОК
Section<TSlot>:
Header : string // из SectionMetadata
TabName : string // имя листа в Excel
Roots : List<SectionRow<TSlot>> // корни дерева строк секции
Cекций данных — пять (SectionKind). Шестой пункт спецификации, «Параметры отчёта», самостоятельной секцией не является — его значения хранятся прямо в NtrR01Report.ReportParams и при рендере выводятся на отдельный лист Excel.
1.2.2 Параметры отчётаImplParametryOtcheta⟨/⟩
GenerateNtrR01ReportParams:
DateOfCreation : DateTime
NavId : Guid // обязателен; орг-объект (Компания/ИА/МЭС/ПМЭС/ПС)
BuildingId : Guid? // опционален; если задан — отчёт по одному Зданию
CollectionTaskId : Guid // выбранное Задание на сбор энергопрофилей
EnergyProfileStatusIds : IReadOnlyList<Guid> // соответствуют EpEnergyProfileStatus.Id; пусто = «все»
EnergyProfileConditionsStatusIds : IReadOnlyList<Guid> // соответствуют EpModelConsistencyStatus.Id; пусто = «все»
В блоке навигации пользователь выбирает узел в едином иерархическом дереве, в котором присутствуют и орг-объекты, и здания. В отчёт прилетает пара (NavId, BuildingId?):
BuildingIdзадан → отчёт по выбранному Зданию (его Энергопрофиль в выбранном Задании).NavIdпри этом содержит идентификатор орг-объекта, к которому привязано Здание (используется для построения цепочки иерархии в строках отчёта).BuildingIdне задан → отчёт по всем Зданиям, находящимся в узлеNavIdили ниже.
Задание на сбор энергопрофилей (CollectionTaskId) обязательно. По умолчанию UI подставляет последнее созданное задание.
1.2.3 Обёртка результатаImplObertkaRezultata⟨/⟩
NtrR01ReportGenerationResult:
Report : NtrR01Report?
Warnings : IReadOnlyList<string> // мягкие проблемы (расхождения per-Building полей и т.п.)
Errors : IReadOnlyList<string> // блокирующие ошибки
IsSuccess : bool // Report != null && Errors.Count == 0
1.2.4 Конвейер сборкиImplKonveyerSborki⟨/⟩
INtrR01ReportDataProvider одним проходом тянет сырьё из NtrReadDbContext с join'ами на Navs/NavIa/NavMeses/NavPmeses/NavSubstations/RegBuildings и справочники → OrgHierarchyTreeBuilder строит дерево орг-структуры по фактически присутствующим в выборке зданиям → набор INtrR01SectionBuilder (по одному на секцию) раскладывает данные по узлам дерева и per-CalcKind слотам → NtrR01ReportGenerator собирает корневой NtrR01Report → INtrR01ReportExcelRenderer обходит дерево preorder'ом, выводит строки в xlsx поверх шаблона и попутно формирует Excel-формулы агрегации.
DI. Один INtrR01ReportGenerator / INtrR01ReportDataProvider / INtrR01ReportExcelRenderer + N INtrR01SectionBuilder через IEnumerable<>. Регистрация — в NtrReportsDiExtension.
1.2.5 Состав секцийImplSostavSekciy⟨/⟩
Cекций данных — пять, по одной на каждый SectionKind:
SectionKind |
Заголовок (краткое наименование) | Колонки слота (per-CalcKind) |
|---|---|---|
ElectricalEnergyConsumption |
Расход ЭЭ | Общее потребление ЭЭ зданием, кВт·ч |
HeatEnergyConsumption |
Расход ТЭ | Общее потребление ТЭ зданием, Гкал |
EnergyEfficiencyClass |
Класс | Удельная характеристика расхода ТЭ (расч.) Вт/(м³·℃); Удельная характеристика расхода ТЭ (норм.) Вт/(м³·℃); Отклонение расчётной от нормативной, %; Класс энергоэффективности |
Walls |
Стены | По 3 колонки (материал / теплопроводность Вт/(м·К) / толщина мм) для несущего слоя, основного утеплителя и дополнительного утеплителя — 9 колонок |
Roofs |
ВОК | По 3 колонки для несущего слоя верхнего перекрытия, утеплителя верхнего перекрытия и утеплителя перекрытия крыши — 9 колонок |
1.2.6 Структура строки секцииImplStrukturaStrokiSekcii⟨/⟩
Одна листовая строка секции = одно Здание. Каждая листовая строка содержит четыре набора per-секционных колонок (слотов) — по одному на CalcKind: «Характеристики нормативной модели», «Характеристики эталонной модели», «Характеристики технологической модели», «Характеристики произвольной модели». Каждый слот заполняется значениями соответствующей Модели потребления из Энергопрофиля Здания. Если модели для какого-то вида расчёта в Энергопрофиле здания нет — соответствующий слот пуст.
Кроме листовых, в секции присутствуют строки-агрегаты уровней Компания / ИА / МЭС / ПМЭС / Подстанция (см. иерархию ниже).
Строки секции образуют дерево: узлы агрегатов содержат дочерние узлы; листья — здания. Рендер обходит дерево preorder'ом.
SectionRow<TSlot> (узел дерева, общая часть для всех секций):
RowLevel : Company | ExecutiveApparatus | Mes | Pmes | Substation | Building
Children : List<SectionRow<TSlot>> // у Building пуст
Hierarchy : имена Компании / ИА / МЭС / ПМЭС / Подстанции / Здания
(повторяются на потомках строки)
PerBuildingFields : Адрес, Климатический район, Функциональное назначение,
Тип системы отопления, Объём отапливаемый м³, Этажность,
Статус энергопрофиля, Статус выполнения условий расчётов
// заполняются ТОЛЬКО на строках уровня Building
Slots : Dictionary<CalcKind, TSlot?> // 4 ключа
// на Building заполняются из соответствующей модели Энергопрофиля
// на агрегатах остаются null; суммируемые колонки рендер
// заполняет формулой SUM (см. «Агрегация по орг-иерархии»)
1.2.7 Иерархия и состав строк секцииImplIerarhiyaISostavStrokSekcii⟨/⟩
RowLevel: Company → ExecutiveApparatus (ИА) → Mes → Pmes → Substation → Building. Состав прямых детей по уровням, правила образования и наполнения дерева — см. функциональную часть выше (раздел «Состав агрегатных записей по уровням»).
Здание принадлежит ровно одному орг-объекту (ИА / МЭС / ПМЭС / Подстанция) — это гарантировано доменом. Дерево строится только по фактически присутствующим в выборке зданиям; пустых веток не создаётся. Порядок строк — preorder обхода дерева (агрегат, ниже его потомки).
1.2.8 Источники значений в листовой строкеImplIstochnikiZnacheniyVListovoyStroke⟨/⟩
Общая часть листовой строки (имена орг-иерархии и per-Building поля, включая статус энергопрофиля и статус выполнения условий расчётов) — состав колонок и источники см. в функциональной части выше (таблица «Общие для всех секций параметры записей секции»). Значения, источник которых — Модель потребления (Функциональное назначение, Тип системы отопления, Объём отапливаемый, Этажность), берутся из Нормативной модели Энергопрофиля Здания; при расхождении этих полей между 4 моделями фиксируется предупреждение в NtrR01ReportGenerationResult.Warnings.
Per-CalcKind слоты по секциям (маппинг на свойства доменных сущностей для реализации)
В строке здания на каждый CalcKind заполняется свой слот, значения берутся из соответствующей Модели потребления:
- Расход ЭЭ —
ConsumptionModel.TotalElectricalEnergyConsumptionKwh. - Расход ТЭ —
ConsumptionModel.TotalHeatEnergyConsumptionGcal. - Класс —
ConsumptionModel.CalculatedHeatLossCoeff,ConsumptionModel.NormativeHeatLossCoeff,ConsumptionModel.DeviationPercent,ConsumptionModel → RefEnergyEfficiencyClass.Name. - Стены —
ConsumptionModel → WallsModel:- Несущий слой:
→ RefMaterial.Name,→ RefMaterial.ThermalConductivity,LoadBearingThicknessMm. - Основной утеплитель:
→ RefInsulationMaterial.Name,→ RefInsulationMaterial.ThermalConductivity,MainInsulationThicknessMm. - Дополнительный утеплитель: аналогично, поля
AdditionalInsulation*.
- Несущий слой:
- ВОК —
ConsumptionModel → RoofModel:- Верхнее перекрытие, несущий слой:
→ RefMaterial.Name,→ RefMaterial.ThermalConductivity,UpperFloorLoadBearingThicknessMm. - Верхнее перекрытие, утеплитель:
→ RefInsulationMaterial.Name,→ RefInsulationMaterial.ThermalConductivity,UpperFloorInsulationThicknessMm. - Перекрытие крыши, утеплитель: аналогично, поля
RoofInsulation*.
- Верхнее перекрытие, несущий слой:
Конкретные имена свойств уточняются по Ntr.Domain.Modules.Consumption.Entities при реализации.
1.2.9 Агрегация по орг-иерархииImplAgregaciyaPoOrgIerarhii⟨/⟩
В дополнение к листовым строкам в каждой секции присутствуют строки-агрегаты по орг-иерархии (см. таблицу выше). На этих строках заполняется только имя соответствующего уровня иерархии; per-Building поля и большинство per-секционных колонок остаются пустыми.
Агрегируются (суммой по листовым потомкам, отдельно по каждому слоту CalcKind) только показатели потребления:
- секция «Расход ЭЭ» — «Общее потребление ЭЭ зданием, кВт·ч»;
- секция «Расход ТЭ» — «Общее потребление ТЭ зданием, Гкал».
Остальные показатели (удельные характеристики, отклонение, класс энергоэффективности, материалы / теплопроводности / толщины стен и ВОК) по орг-иерархии не агрегируются — ячейки остаются пустыми.
Расчёт агрегатов выполняется Excel-формулами в самом файле отчёта. В NtrR01Report суммируемые поля на агрегатных строках имеют значение null; INtrR01ReportExcelRenderer при выводе строки-агрегата подставляет в эти ячейки формулу SUM(...), диапазон которой собирается из Excel-номеров строк листовых потомков (рендер ведёт SectionRow → ExcelRowNumber при preorder-обходе дерева). Это даёт пользователю прозрачность (формула видна в ячейке) и снимает с генератора арифметику.
1.2.10 Выборка моделей потребленияImplVyborkaModeleyPotrebleniya⟨/⟩
- Задание на сбор энергопрофилей создаётся на конкретный отчётный период. В рамках одного Задания на каждое Здание создаётся один Энергопрофиль; Энергопрофиль ссылается на 4 Модели потребления — по одной на каждый
CalcKind(Нормативный/Эталонный/Технологический/Произвольный; «Базовый» в Энергопрофиль не входит). - У одного Здания могут существовать Энергопрофили в разных Заданиях — за разные отчётные периоды, в разных Заданиях. Выбор
CollectionTaskIdв параметрах отчёта однозначно выбирает один Энергопрофиль на Здание из этого Задания. - Определение слота модели. Принадлежность Модели потребления к слоту
CalcKindопределяется по тому, какой именно ссылкой Энергопрофиля (NormativeModelId/StandardModelId/TechnologicalModelId/CustomModelId) она указана. ПолеConsumptionModel.CalcKindIdдля отчёта не используется — слот задаётся именно ссылкой из Энергопрофиля. - Сужение по
BuildingId. ЕслиGenerateNtrR01ReportParams.BuildingIdзадан — выборка сужается до Энергопрофиля этого Здания в выбранном Задании. Иначе берутся все Здания под узломNavIdи их Энергопрофили в выбранном Задании. - Фильтр
EnergyProfileStatusIds(«Включать энергопрофили в статусах»): если список не пуст — фильтруемEpEnergyProfile.StatusIdпо этим Id напрямую; если пуст — фильтр не применяется (включаются все статусы). - Фильтр
EnergyProfileConditionsStatusIds(«Включать энергопрофили со статусом выполнения условий расчётов»): если список не пуст — фильтруемEpEnergyProfile.ModelConsistencyStatusIdпо этим Id напрямую; если пуст — фильтр не применяется. - Неизвестные Id (не соответствующие ни одной известной доменной константе) валидацией не отсекаются — они просто не пройдут как фильтр по БД (ни одного профиля с таким Id не найдётся).
- Порядок слотов в шапке таблиц секций — по справочнику
CalcKind: Нормативный → Эталонный → Технологический → Произвольный.
1.2.11 Валидация параметровImplValidaciyaParametrov⟨/⟩
NavIdобязателен (Guid.Emptyнедопустим).CollectionTaskIdобязателен.BuildingIdопционален; если задан — проверяется, что Здание принадлежит узлуNavIdили его потомкам.- При нарушении —
NtrR01ReportGenerationResult.Errorsзаполняется,Report=null,IsSuccess= false.
1.2.12 Пустая выборкаImplPustayaVyborka⟨/⟩
Если по выбранному Заданию и фильтрам не нашлось ни одного Энергопрофиля:
NtrR01Reportсобирается с пустыми секциями (Roots = []) и заполненнымReportParams;- в
Warningsдобавляется сообщение «По выбранному заданию и фильтрам не найдено энергопрофилей»; IsSuccess= true (это не ошибка).
1.2.13 Excel-шаблонImplExcelShablon⟨/⟩
Templates/ntr-r01-template.xlsx — встроенный ресурс. 6 листов: 5 листов секций (имена = Section.TabName из SectionMetadata) + лист «Параметры отчёта». На каждом листе данных — sheet-scoped именованная ячейка DataAnchor, с которой начинается вывод строк. Форматы чисел и дат заданы прямо в шаблоне на колонках (в каждой колонке однотипные значения — формат привязан к колонке шаблона). На листе «Параметры отчёта» — именованные ячейки Param_DateOfCreation, Param_NavObject, Param_BuildingObject, Param_CollectionTask, Param_StatusFilter, Param_ConditionsStatusFilter. Рендер обходит дерево SectionRow preorder'ом, по ходу записи запоминает Excel-номер строки каждого узла и для агрегатов формирует Excel-формулы SUM по Excel-номерам своих листовых потомков.
1.2.14 Read-контекстImplReadKontekst⟨/⟩
Используется существующий NtrReadDbContext (src/Infrastructure.Read/). Основные DbSet:
CmConsumptionModels,CmOperatingConditions,CmShapeAndDimensions,CmWallsModels,CmRoofModels,CmCalcKinds,CmCalcStatuses;EpEnergyProfiles,EpEnergyProfileCollectionTasks,EpEnergyProfileStatuses,EpEnergyProfileCollectionTaskStatuses;RegBuildings;Navs,NavCompanies,NavIa,NavMeses,NavPmeses,NavSubstations;- справочники:
RefClimateRegion,RefBuildingPurpose,RefHeatingType,RefMaterial,RefInsulationMaterial,RefEnergyEfficiencyClass.
Структурные связи
Использует
- Прикладной сервис Эталоны и нормативы потребления ТЭР зданиями