using Rugrid.Sdaid.Notation.Documents;
using Rugrid.Sdaid.Epas.Ntr.Backend.Calc;
namespace Rugrid.Sdaid.Epas.Ntr.ChangeRequests
{
/// <summary>
/// Доработка методики второго этапа: расчёт площади верхних ограждающих конструкций
/// для четырёхскатных крыш (вальмовой и шатровой) при прямоугольной форме здания.
/// Требует нового атрибута «Длина конька» — сквозная правка от БД до интерфейса.
/// </summary>
public class DOC_CR_2026_08_31_UpperEnclosuresHipRoof : IChangeRequestDocument, IHasStructuralLinks, IFolder<NtrChangeRequestsFolder>
{
public SdaidLink[] Links => new[]
{
Rel.Uses<DOC_TtrCalc>(),
Rel.Uses<DOC_CalcModel>(),
Rel.Uses<DOC_TtrCalcInputMapping>(),
Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_RoofModel>(),
Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.Reference.DOC_RoofType>(),
Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Infrastructure.AisOiaPeo.DOC_AisDataImport>(),
Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Ui.Componetns.DOC_ModelCardRoofModel>(),
};
public static string _refName = "CR 2026-08-31: ВОК для четырёхскатной крыши";
public string Name => "Расчёт площади ВОК для четырёхскатных крыш";
public string Description =>
@"Реализовать расчёт площади верхних ограждающих конструкций для типов крыши «Четырёхскатная вальмовая» и «Четырёхскатная шатровая» при прямоугольной форме здания. Формулы зафиксированы в методике; для вальмовой крыши требуется новый исходный параметр «Длина конька», которого нет ни в домене, ни в БД, ни на интерфейсе.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
public static string Source = """"
## Источник (как есть)
Одна из задач второго этапа: доработка методики расчёта: реализация расчёта площади верхних
ограждающих конструкций для типов крыши: Односкатная, Четырехскатная вальмовая,
Четырехскатная шатровая для прямоугольной форрмы здания.
Задача разбита на две: **A — односкатная** и **B — четырёхскатные** (вальмовая и шатровая,
этот CR).
"""";
public static string Analysis = $$""""
## Разбор
Формулы для обоих типов крыши уже есть в методике — они были вписаны заранее и перенесены
в SDAID вместе с остальным текстом. Реализации нет: `MapRoofType` в `CalcInputModelFactory`
на этих типах кидает исключение, а диспетчеры секций
{{SdaidAnchors.RefTo<TeAtticWallArea>("te.attic_wall_area")}} и
{{SdaidAnchors.RefTo<TeRoofCoverArea>("te.roof_cover_area")}} знают только плоскую,
двускатную и односкатную крышу.
### Что считаем
Площадь стен чердака по методике для обоих типов равна нулю: у вальмовой и шатровой крыши
скаты сходятся со всех сторон, вертикальных фронтонов нет.
| Тип | $A_{НП.Черд}$ | $A_{ПК}$ |
|---|---|---|
| Четырёхскатная вальмовая (`hipped_rect`) | 0 | $(L_A + L_{конька})·h_{трап} + L_B·h_{треуг}$ |
| Четырёхскатная шатровая (`pyramidal_rect`) | 0 | $L_A·h^A_{треуг} + L_B·h^B_{треуг}$ |
где $h_{трап} = \sqrt{(H-z_{ВП})^2 + (L_B/2)^2}$,
$h_{треуг} = \sqrt{(H-z_{ВП})^2 + ((L_A-L_{конька})/2)^2}$,
$h^A_{треуг} = \sqrt{(H-z_{ВП})^2 + (L_B/2)^2}$,
$h^B_{треуг} = \sqrt{(H-z_{ВП})^2 + (L_A/2)^2}$.
**Шатровая — вырожденный случай вальмовой** при $L_{конька} = 0$: подставив ноль в формулу
вальмовой, получаем формулу шатровой. Отдельными ветками расчёта они остаются — тип задан
справочником, — но это объясняет, почему незаполненный конёк у вальмовой крыши опасен:
расчёт молча даст площадь шатровой — отсюда правило обязательности ниже.
### Как различаются вальмовая и шатровая
В загруженном из АИС ОИА ПЭО справочнике типов крыш для них заведены отдельные записи:
`hipped` — четырёхскатная вальмовая, `pyramidal` — четырёхскатная шатровая. Тип расчёта
определяется кодом записи напрямую, как для плоской, двускатной и односкатной крыши.
Длина конька — исходный параметр формулы вальмовой крыши, а не способ определить тип.
### Новый параметр «Длина конька»
Параметр не ведёся в АИС ОИА ПЭО. Его надо добавить в характеристики верхних ограждающих конструкций,
в сущность {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_RoofModel>()}}:
{{SdaidCode.OfMember<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.RoofModel>(nameof(Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.RoofModel.RidgeLengthM))}}
Он будет заполняться пользователем в Сервисе, импорт из АИС его не должен затирать.
Атрибут добавлен в спецификацию сущности этим CR. Как он ложится на структуру БД,
read-модель и контракты — в разделе Blast radius.
### Правило валидации длины конька
Правило одно, внесено в методику — раздел «Правила валидации» секции
{{SdaidAnchors.RefTo<TeRoofCoverArea>("te.roof_cover_area")}}: для вальмовой крыши
$0 < L_{конька} < L_A$. Место выбрано по использованию: длина конька входит только в эту
секцию, смежная {{SdaidAnchors.RefTo<TeAtticWallArea>("te.attic_wall_area")}} для
четырёхскатных крыш даёт ноль.
Обоснование границ: верхняя — из наклонной высоты торцевого ската
$h_{треуг}=\sqrt{(H-z_{ВП})^2 + ((L_A-L_{конька})/2)^2}$, при $L_{конька} = L_A$ торцевые
скаты вырождаются в линию. Сторона сравнения — $L_A$, а не $\max(L_A, L_B)$: конёк входит
в формулу как слагаемое и вычитаемое именно к $L_A$. Нижняя граница строгая, поэтому
незаполненное значение нельзя подставлять нулём: при нулевом коньке формула вальмовой
крыши вырождается в формулу шатровой и вернула бы площадь крыши другого типа.
### Открытые вопросы
- **OQ-1. Контрольные примеры** — по одному на вальмовую и шатровую крышу. Значения
предоставляет заказчик: расчёт контрольных чисел тем же алгоритмом, который проверяется,
ничего не доказывает.
Прежняя запись справочника «Четырехскатная» (`tr300`), если она осталась и на неё ссылаются
модели, остаётся типом крыши, не поддержанным методикой, — как и `tr400` «Двускатная без
нижнего перекрытия». Ни то, ни другое в объём этого CR не входит.
"""";
public static string BlastRadius = $$""""
## Blast radius
В отличие от CR по односкатной крыше, здесь появляется новый исходный параметр, поэтому
правка сквозная — от структуры БД до формы ввода. Ниже — фактическая цепочка,
прослеженная по коду на примере соседнего атрибута «Уровень верхнего перекрытия»
(`UpperFloorLevelM`), который проходит ровно те же слои.
### Спецификация
| Документ | Правка |
|---|---|
| {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_RoofModel>()}} | новый атрибут «Длина конька, м» в классе сущности — таблица свойств и диаграмма связей строятся по нему |
| {{SdaidAnchors.RefTo<TeRoofCoverArea>("te.roof_cover_area")}} | правила валидации длины конька — внесены; источник данных для $L_{конька}$ — атрибут сущности ВОК |
| {{SdaidAnchors.RefTo<TeAtticWallArea>("te.attic_wall_area")}} | правила валидации: перечень типов крыши |
| {{SdaidAnchors.RefTo<DOC_CalcModel>()}} | блок `roof` модели входных данных: новое поле `ridgeLengthM` — без него параметр не доедет до расчёта |
| {{SdaidAnchors.RefTo<DOC_TtrCalcInputMapping>()}} | соответствие `Roof.RidgeLength` ← «ВОК. Длина конька, м» в секции `TeInputSection.Roof` |
| {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Ntr.Domain.Reference.DOC_RoofType>()}} | состав справочника: записи `hipped` и `pyramidal` загружены из АИС; правок не требует, фиксируется как факт |
| {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Ntr.Ui.Componetns.DOC_ModelCardRoofModel>()}} | поле «Длина конька, м» в описании полей вкладки и в макете экрана — видимое только для вальмовой крыши |
### База данных
- `CmRoofModelEntity` — свойство `RidgeLengthM` (`decimal?`) и сеттер по образцу
`SetUpperFloorLevelM`; копирование в методе клонирования модели.
- `CmRoofModelEntityConfiguration` — `builder.Property(...).HasComment(...)`.
- **Миграция EF** в `Ntr.Infrastructure/Migrations`: колонка `ridge_length_m` типа
`numeric`, nullable, в таблице `ntr.cm_roof_models`. Данных не переносим: у существующих
моделей конёк не заполнен.
- `Infrastructure.Read/NtrModels/CmRoofModel` + конфигурация read-контекста —
read-модель ведётся отдельно от доменной.
### API и контракты
- `RoofModelDetails` (модель ответа) и `RoofModelDetailsDto` в `ConsumptionModelDtos` —
новое поле.
- `GetConsumptionModelForEditQueryHandler` — проекция поля в ответ.
- `CreateConsumptionModelCommandHandler`, `ApplyConsumptionModelEditCommandHandler`,
`ConsumptionModelService` — запись значения в сущность.
- `AisToNtrImportService` — **правок не требует**: в выгрузке АИС длины конька нет,
импорт поле не трогает.
- Swagger-контракт меняется ⇒ требуется перегенерация клиента фронтенда.
### Расчёт
Цепочка передачи параметра до формулы: атрибут сущности ВОК → колонка БД → DTO ответа
и команды → `InputModel.roof.ridgeLengthM` → `RoofTeInput.RidgeLength` → калькулятор
секции `te.roof_cover_area`. Разрыв в любом звене оставит расчёт с пустым коньком,
то есть с ошибкой валидации.
- `RoofTeInput` — поле `RidgeLength`; сборка входной модели в `CalcInputModelFactory`.
- `CalcInputModelFactory` — проброс длины конька и сопоставление кодов `hipped`
и `pyramidal` с типами расчёта.
- `RoofType` — новые значения перечисления.
- `RoofCoverAreaCalculator` — ветки `hipped_rect` и `pyramidal_rect`;
`AtticWallAreaCalculator` — обе ветки дают ноль.
- Валидаторы обеих секций — допуск новых типов крыши и правило по $L_{конька}$.
### Интерфейс
- `roof.util.ts` — контрол, заполнение при загрузке модели, дескриптор поля формы,
сборка payload. Поле показывается только для четырёхскатной крыши — по образцу
полей, зависящих от «Наличия чердака».
- Сгенерированный клиент `roofModelDetails.ts` — после перегенерации по swagger.
- Отображение атрибута в карточке модели в режиме просмотра.
### Тесты
- Контрольные примеры на вальмовую и шатровую крышу в `src/Ntr.Calc.Tests`
(значения — OQ-1).
"""";
public static string Plan = """"
## План
1. Правки спецификации по таблице blast radius; в изменённых секциях проставить `Status`
и `Comments` со ссылкой на этот CR.
2. Домен и БД: атрибут, конфигурация, миграция, read-модель.
3. API: контракты, обработчики команд и запросов; перегенерация клиента.
4. UI: поле на форме и в карточке модели.
5. Расчёт: типы крыши, ветки калькуляторов, валидаторы, сопоставление кода справочника.
6. Контрольные примеры (значения предоставляет заказчик).
7. Зафиксировать «Итог».
Шаги 2–5 разумно вести снизу вверх (домен → API → UI), чтобы контракт менялся один раз.
"""";
public static string Acceptance = """"
## Acceptance criteria
- **AC-1.** Контрольные примеры на вальмовую и шатровую крышу в `src/Ntr.Calc.Tests`
проходят: расчёт завершается со статусом `Success`, значения $A_{НП.Черд}$, $A_{ПК}$
и $A_{ВОК}$ совпадают с контрольными.
- **AC-2.** В карточке модели на вкладке верхних ограждающих конструкций можно ввести
длину конька; значение сохраняется и после перезагрузки страницы отображается прежним.
- **AC-3.** Модель потребления с четырёхскатной крышей рассчитывается из интерфейса
и не падает с ошибкой «Необрабатываемый тип крыши».
- **AC-4.** В журнале расчёта виден выбранный вариант методики — `hipped_rect`
или `pyramidal_rect`.
- **AC-5.** Повторный импорт данных из АИС ОИА ПЭО не затирает введённую длину конька.
- **AC-6.** Поведение моделей с плоской, двускатной и односкатной крышей не изменилось:
контрольные примеры 1–4 продолжают проходить.
"""";
public static string Result = """"
## Итог
—
"""";
}
}