⟨/⟩ Ntr/35_ChangeRequests/DOC_CR_2026_08_28_InternalHeatGain.cs

215 строк · в начало

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_28_InternalHeatGain : IChangeRequestDocument, IHasStructuralLinks, IFolder<NtrChangeRequestsFolder>
    {
        public SdaidLink[] Links => new[]
        {
            Rel.Uses<DOC_TtrCalc>(),
            Rel.Uses<DOC_EeCalc>(),
            Rel.Uses<DOC_CalcGraph>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_LightingModel>(),
        };

        public static string _refName = "CR 2026-08-28: удельные бытовые теплопоступления";

        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 = """"
            ## Источник (как есть)

            Показатель Энергия бытовых тепловыделений за отопительный период, тыс. кВт*ч надо переименовать в
            Удельные бытовые теплопоступления, Вт/м2. Меняется единица измерения и формула расчёта. Новая формула:

            $$Q_{быт}=((90·n_p + 10+1000·P_{light})/A_h)·45 / 168, Вт/м^2$$

            где:

            - $P_{light}$ - установленная мощность осветительных приборов модели  освещения, $кВт$.

            дальнейшее использование показателя остаётся прежним, ничего не меняется
            """";

        public static string Analysis = $$""""
            ## Разбор

            Меняется секция методики {{SdaidAnchors.RefTo<TeInternalHeatGainEnergy>("te.internal_heat_gain_energy")}}
            целиком: наименование показателя, единица измерения, формула и состав исходных данных.

            **Обозначение меняется с $Q_{быт}$ на $q_{быт}$.** Показатель перестал быть энергией и стал
            удельной величиной, а в методике заглавная $Q$ закреплена за энергией (ср. $Q_{рад}$ — энергия
            теплопоступлений от солнечной радиации), строчная $q$ — за удельными характеристиками
            ($q_{норм}$, $q_{от}^{р}$). Обозначение $q_{быт}$ в методике свободно; с $k_{быт}$ оно не
            путается — разные буквы и разные единицы измерения (Вт/м² против Вт/(м³·°C)).

            **Что уходит из формулы.** Продолжительность отопительного периода $z_{ht}$ и среднее время
            работы в сутки $t_{работы}$ (справочное значение справочника «Коэффициенты ТТР»). Показатель
            перестаёт быть величиной «за отопительный период» и становится удельной мощностью, поэтому
            правило валидации $z_{ht} \in [1; 365]$ в этой секции больше не нужно — сама характеристика
            используется и валидируется в других секциях.

            **Что приходит в формулу.** Установленная мощность осветительных приборов $P_{light}$, кВт —
            расчётный параметр секции {{SdaidAnchors.RefTo<EeLightingModelTotals>("ee.lighting_model_totals")}}
            раздела {{SdaidAnchors.RefTo<DOC_EeCalc>()}}; в домене это атрибут
            «Установленная мощность, кВт» сущности
            {{SdaidAnchors.RefTo<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_LightingModel>()}}.

            **Это новая связь между разделами расчёта.** Сейчас ТЭ и ЭЭ считаются независимо: фасад расчёта
            вызывает их по отдельности, и ни один параметр из ЭЭ в ТЭ не передаётся. Новая формула делает
            раздел ТЭ зависимым от результата раздела ЭЭ — самое существенное последствие запроса.

            **Следствие: граф расчёта становится общим.** До этого CR граф — {{SdaidAnchors.RefTo<DOC_CalcGraph>()}} —
            покрывал только раздел ТЭ; у раздела ЭЭ графа не было. Межразделовая связь в методике при
            этом уже есть: секция {{SdaidAnchors.RefTo<SummaryEeConsumptionKwh>("summary.ee_consumption_kwh")}}
            берёт расчётные параметры и из ТЭ, и из ЭЭ — но записана она прозой и на графе не видна.

            До сих пор раздельные графы были допустимы, потому что связь лежала в хвосте: ТЭ и ЭЭ — два
            независимых поддерева, сходящиеся в своде. Новая формула вставляет дугу из ЭЭ в **середину**
            графа ТЭ: у секции {{SdaidAnchors.RefTo<TeInternalHeatGainEnergy>("te.internal_heat_gain_energy")}}
            появляется входящая дуга от узла, которого в графе ТЭ нет. Раздельный граф перестаёт быть
            корректным описанием порядка расчёта.

            Цикла не возникает: ни одна секция раздела ЭЭ не использует расчётные параметры ТЭ. Порядок
            остаётся однозначным — ЭЭ, затем ТЭ, затем свод.

            **Дальнейшее использование не меняется — это подтверждается размерностью.** Потребитель у
            показателя один: {{SdaidAnchors.RefTo<TeSpecInternalHeatGainChar>("te.spec_internal_heat_gain_char")}},
            $k_{быт} = Q_{быт}·A_h / (V_h·(t_{int} - t_{ext}))$. С новой единицей измерения $Вт/м^2$
            произведение $Q_{быт}·A_h$ даёт Вт, а результат — $Вт/(м^3·°C)$, что совпадает с объявленной
            единицей измерения $k_{быт}$. При старой единице ($тыс.кВт·ч$) размерность потребителя не
            сходилась — смена показателя её как раз исправляет.

            ### Принятые решения

            **Передача $P_{light}$ в раздел ТЭ — не открытый вопрос.** С общим графом расчёта дуга
            $P_{light}$ ничем не отличается от любой другой межсекционной дуги: узел считается после
            своих предшественников, значение передаётся на вход. Граница «ТЭ / ЭЭ» — это структура
            реализации (два сервиса, два входных раздела), а не факт методики; методика знает только
            секции и связи между ними. Поэтому в спецификации решать нечего, а в реализации решение
            прямое: фасад расчёта считает ЭЭ до ТЭ и передаёт $P_{light}$ на вход секции.

            **Граф расчёта достраивается до общего** (закрывает вопрос об объёме работы по графу).
            Узлы разделов ЭЭ и свода добавлены в существующий документ, он переименован в
            {{SdaidAnchors.RefTo<DOC_CalcGraph>()}}. Отдельный документ верхнего уровня с сохранением
            графов по разделам отвергнут: один и тот же факт — дуга между секциями — оказался бы
            записан в двух местах, что нарушает правило одного факта. Попутно снят висячий след:
            в графе стояла пометка, что узлы ЭЭ перенесены в отдельный граф расчёта потребления
            электроэнергии, — такого документа никогда не существовало.

            **Ранее посчитанные модели не пересчитываются автоматически.** Значение показателя
            изменится, пересчёт выполняет пользователь по своей инициативе. Массовая операция
            пересчёта не разрабатывается.

            **Формула переносится дословно, как дана заказчиком.** При разборе были заданы два вопроса,
            оба закрыты подтверждением исходной формулировки:

            - слагаемое `+ 10` в числителе. В прежней формуле площадь входила слагаемым $0{,}01·A_h$ —
              10 Вт на квадратный метр; в новой числитель делится на $A_h$, и по этой логике слагаемое
              ожидалось бы как $10·A_h$. Принята запись из запроса: `+ 10`;
            - множители `45 / 168` — константы методики, в справочник «Коэффициенты ТТР» не выносятся.
              Справочное значение $t_{работы}$ (среднее время работы в сутки) из формулы уходит.

            **Поведение при отсутствии предшественника — тоже общее правило.** Если предшественник не
            посчитан, узел не считается, и дальше по графу — то же самое. Это уже работающая семантика
            расчёта (гранулярность отказа по узлам), новых правил доработка не вводит.

            ### Открытые вопросы

            Нет.

            ### Смежная находка

            **Гранулярность отказа на границе разделов грубее, чем на графе.** Внутри раздела отказ
            локален: не посчитанный узел не мешает соседним ветвям. Но на стыке разделов результат
            обнуляется целиком — если в разделе ЭЭ есть хотя бы одна ошибка, весь его результат
            отбрасывается. До сих пор это ни на что не влияло, потому что ТЭ ничего у ЭЭ не спрашивал.
            После доработки ошибка в любой части ЭЭ (например, в видах токоприёмников, не имеющих к
            освещению отношения) лишит раздел ТЭ значения $P_{light}$ и оборвёт ветвь $q_{быт}$ →
            $k_{быт}$ → $q_{от}^{р}$ вплоть до класса энергоэффективности. Правильное поведение —
            брать $P_{light}$ по узлу графа, а не по разделу целиком; это правка фасада расчёта,
            не методики.
            """";

        public static string BlastRadius = $$""""
            ## Blast radius

            **Спецификация:**

            | Документ | Что меняется |
            |---|---|
            | {{SdaidAnchors.RefTo<TeInternalHeatGainEnergy>("te.internal_heat_gain_energy")}} | наименование секции, формула, исходные данные, правила валидации |
            | {{SdaidAnchors.RefTo<TeSpecInternalHeatGainChar>("te.spec_internal_heat_gain_char")}} | описание параметра $Q_{быт}$: наименование и единица измерения |
            | {{SdaidAnchors.RefTo<TtrSistemaOboznacheniy>("Система обозначений")}} | строка $Q_{быт}$ в перечне обозначений: обозначение, наименование, единица измерения |
            | {{SdaidAnchors.RefTo<TtrIshodnyeDannyeDlyaRascheta>("Исходные данные для расчёта")}} | строка $t_{работы}$ (уходит из формулы, см. OQ-2) и строка «Коэффициенты в формуле $Q_{быт}$: 0,09; 0,01» |
            | {{SdaidAnchors.RefTo<DOC_CalcGraph>()}} | становится общим графом расчёта: узлы разделов ЭЭ и свода, новая дуга $P_{light}$ из ЭЭ в ТЭ, подпись связи $Q_{быт}$ |

            Переименование обозначения затрагивает те же документы — отдельной строкой не выделяется.

            **Реализация (`src/Ntr.Calc`):** `InternalHeatGainEnergyInput` (состав параметров),
            `InternalHeatGainEnergyCalculator` (формула и hint журнала расчёта),
            `InternalHeatGainEnergyValidator` (правила), `TtrService` (передача $P_{light}$),
            `CalcService` (порядок разделов), XML-doc `TEResult.InternalHeatGainEnergy` — единица измерения.

            **Чего доработка НЕ касается.** Показатель $Q_{быт}$ наружу модуля расчёта не выходит: в
            read-модель, API и на UI уходит только $k_{быт}$ (`SpecificInternalHeatGainsWtPerM3c`).
            Поэтому миграций БД, правок контрактов API, swagger и интерфейса не требуется — доработка
            укладывается в модуль расчёта. Единственное наблюдаемое пользователем изменение, помимо новых
            значений показателей, — строка в журнале расчёта.
            """";

        public static string Plan = """"
            ## План

            1. ✔ Граф расчёта сведён в общий: {{SdaidAnchors.RefTo<DOC_CalcGraph>()}} — добавлены узлы
               разделов ЭЭ и свода.
            2. ✔ Правки спецификации по таблице blast radius; в изменённой секции проставлены `Status`
               и `Comments` со ссылкой на этот CR.
            3. Реализация в `Ntr.Calc` (включая порядок разделов и передачу $P_{light}$ по узлу графа).
            4. Контрольный пример в `src/Ntr.Calc.Tests`: контрольные значения даёт заказчик или
               пользователь — расчёт их тем же алгоритмом, который проверяется, ничего не доказывает.
            5. Зафиксировать «Итог».
            """";

        public static string Result = """"
            ## Итог

            | Пункт | Статус |
            |---|---|
            | Общий граф расчёта | выполнено |
            | Правки методики (наименование, обозначение, единица измерения, формула, исходные данные) | выполнено |
            | Реализация в `Ntr.Calc` | выполнено |
            | Контрольные примеры | ожидают контрольных значений заказчика |

            Пересчёт ранее посчитанных моделей не выполняется — по решению заказчика пользователь
            пересчитывает модели сам.

            **Реализация.** Секция `te.internal_heat_gain_energy` пересчитана по новой формуле; из входа
            секции ушла продолжительность отопительного периода, добавилась $P_{light}$. Фасад расчёта
            считает раздел ЭЭ до раздела ТЭ и передаёт узловой результат `ee.lighting_model_totals`,
            а не результат раздела: сводка освещения теперь вычисляется независимо от исхода ветви
            токоприёмников (см. смежную находку). Тот же порядок восстановлен во втором месте, где
            раздел ТЭ считается отдельно, — в правиле доступности модели при проверке энергопаспорта.

            **Контрольные примеры.** Значения ниже по графу от $q_{быт}$ изменились, и подтвердить их
            расчётом того же алгоритма нельзя. Ассерты, ставшие недействительными (k_быт, q_от^р, δq,
            класс энергоэффективности, потребление ТЭ), отключены с пометкой `TODO (CR 2026-08-28)` до
            получения контрольных значений от заказчика. Для контрольного примера «Гараж №1»
            $k_{быт}$ изменился с 0,0736 до 0,0022 Вт/(м³·°C) — примерно в 33 раза меньше, что
            смещает расчётную удельную характеристику и класс энергоэффективности.
            """";
    }
}