⟨/⟩ Ntr/50_Domain/EnergyProfileModel/EnergyProfile.cs

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

using System;
using Rugrid.Sdaid.Notation.Documents;

using Building = Rugrid.Sdaid.Epas.Ntr.Domain.BuildingModel.Building;
using ConsumptionModelEntity = Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.ConsumptionModel;
using EnergyProfileCollectionTask = Rugrid.Sdaid.Epas.Ntr.Domain.EnergyProfileCollectionTaskModel.EnergyProfileCollectionTask;
using EnergyProfileStatus = Rugrid.Sdaid.Epas.Ntr.Domain.Reference.SysReference.EnergyProfileStatus;
using ModelConsistencyStatus = Rugrid.Sdaid.Epas.Ntr.Domain.Reference.SysReference.ModelConsistencyStatus;

namespace Rugrid.Sdaid.Epas.Ntr.Domain.EnergyProfileModel
{
    /// <summary>
    /// Доменная сущность. Перенесено дословно из ntr-01/domain/energy-profile-model/energy-profile.md.
    /// </summary>
    public class DOC_EnergyProfile : IDomainEntityDocument, IHasStructuralLinks, IFolder<NtrDomainFolder>
    {
        public SdaidLink[] Links => new[]
        {
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.BuildingModel.DOC_Building>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.ConsumptionModel.DOC_ConsumptionModel>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.EnergyProfileCollectionTaskModel.DOC_EnergyProfileCollectionTask>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.Reference.SysReference.DOC_EnergyProfileStatus>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Ntr.Domain.Reference.SysReference.DOC_ModelConsistencyStatus>(),
        };

        public static string _refName = "Энергопрофиль";

        public string Name => "Энергопрофиль";

        public string Description => "Алиасы: Энергопрофиль, Энергопрофиль здания. parents: [energy-profile-diagram].";

        public string Version => "1.0";
        public string Status => "accepted";
        public string[] Comments => new string[0];

                public Type Entity => typeof(EnergyProfile);

        public static string Body = """"

**Поведение:**

- Энергопрофиль создаётся только автоматически на основе задания на сбор профилей по зданиям орг. объектам за отчётный период. Создаётся один на одно здания на отчётный период. 
- При **сохранении** или по отдельной кнопке "**Проверить энергопрофиль**" должна выполняться проверка выполнения условий сохранения ЭП (см. ниже).
- Сохранение энергопрофиля в статусе "Сформирован" блокирует внесение изменений в модели энергопрофиля: модели нельзя редактировать, удалять, удалять из ЭП.

При сохранении Энергопрофиля необходимо выполнять `Проверить энергопрофиль`:

**Условия, обязательные для сохранения Энергопрофиля:

- **Если статус Энергопрофиля "[[../domain/energy-profile-model/energy-profile|Энергопрофиль]]. Статус" == `Формируется`:** сохранять в любом виде.

- **Если статус Энергопрофиля "[[../domain/energy-profile-model/energy-profile|Энергопрофиль]]. Статус" == `Сформирован`, то должны выполняться условия:**

- **Определены модели для всех видов расчётов.** Для каждого вида расчёта $k$ определена модель потребления $M^{k}$:
	
- **Все виды расчётов выполнены.** Статус расчёта каждой из моделей $M^{k}$ "[[../consumption-model/consumption-model|Модель потребления]]. Статус расчёта" == `Успешно выполнен`.
	
- **Несущий слой стен не менялся:**  не меняются параметры `InputModel`:
	
	- `Walls.CarrierLayerThermalConductivity`;
	- `Walls.CarrierLayerThickness`.
	
- **Несущий слой ВП не менялся:** не меняются параметры `InputModel`:
	
	- `Roof.FloorMaterialThermalConductivity`;
	- `Roof.FloorMaterialThickness`.
	
- **Оконные проёмы не меняются.** Для каждой модели $M^k$ вычисляется количество проёмов $N_{окон}^k(i,a)$ площади $a$ на стороне здания $i\in{[A,B,C,D,E,F,G,H]}$:
	
	$$N_{окон}^{k}(i, a) = \sum_{j,; a=a_{окон}^k(j)} {N_{окон}^k(i,j)}$$
	
	где:
	
	- $a_{окон}^{k}(j)$ - площадь проёма вида окон $j$ в модели $M^k$: `Windows[j].OpeningArea`;
	- $N_{окон}^k(i, j)$ - количество проёмов вида окон $j$ на стороне здания $i\in{[A,B,C,D,E,F,G,H]}$ в модели $M^k$: `Windows[j].OpeningsSideA`, ..., `Windows[j].OpeningsSideH`.
	
	Количества проёмов $N_{окон}^k(i,a)$ для всех видов расчётов $k$ должно быть одинаковым.
	

- **Воротные проёмы не меняются.** Для каждой модели $M^k$ вычисляется количество проёмов $N_{ворот}^{k}(i,a)$ площади $a$ на стороне здания $i\in{[A,B,C,D,E,F,G,H]}$:
    
    $$N_{ворот}^{k}(i, a) = \sum_{j,; a = a_{ворот}^{k}(j)} N_{ворот}^{k}(i, j)$$
    
    где:
    
    - $a_{ворот}^{k}(j)$ — площадь проёма вида ворот $j$ в модели $M^k$: `Doors[j].OpeningArea`;
    - $N_{ворот}^{k}(i, j)$ — количество проёмов вида ворот $j$ на стороне здания $i\in{A,B,C,D,E,F,G,H}$ в модели $M^k$: `Doors[j].OpeningsSideA`, ..., `Doors[j].OpeningsSideH`.
    
    Количества проёмов $N_{в}^{k}(i, a)$ для всех видов расчётов $k$ должно быть одинаковым.
	
- **Сопротивления теплопередаче всех элементов ограждающей конструкции здания не ухудшаются.** Приведённые сопротивления теплопередаче $R_{e}^{k}$  каждого элемента ограждающей конструкции здания  $e\in{[стен, окон, ворот, ВОК]}$ для различных видов расчётов $k$ удовлетворяют условиям:
	
	- $R_{e}^{Т} ≥ R_{e}^{Э} ≥ R_{e}^{Н}$. 
	
	На $R^{П}_e$ требование не распространяется.
	
- **Форма и габариты здания не менялись.** Для всех моделей $M^{k}$ в `InputModel` определены одинаковые параметры секции `Geometry`.
	
- **Функциональное назначение здания не меняется.** Для всех моделей $M^{k}$ определено одинаковое функциональное назначение здания: не меняются параметры `InputModel`, определяемые функциональным назначением:
	
	- `OperatingConditions.NormativeSpecificHeatConsumption`;
	- `OperatingConditions.AirConsumptionPerPerson`.
	 
- **Система отопления здания не меняется.** Для всех моделей $M^{k}$ определён один тип системы отопления: `OperatingConditions.HeatingType`.
	
- **Высота подвала не меняется.** Для всех моделей $M^{k}$ определена одинаковая высота подвала: `GroundContact.BasementHeight`

Если условия выполняются, сохранить ЭП, перейти в режим просмотра. Если нет, подсветить ошибку: сообщить об ошибке, предупредить, что при сохранении статус ЭП будет сброшен в `Формируется`, предложить: отменить, выполнить. 
"""";

    }

    /// <summary>
    /// Доменная сущность «Энергопрофиль».
    /// Класс — источник структуры: таблица свойств и диаграмма связей на странице документа
    /// строятся генератором по нему. Прозу и разбор ведёт документ DOC_EnergyProfile.
    /// </summary>
    [DomainEntity]
    public class EnergyProfile
    {
        /// <summary>Идентификатор записи</summary>
        [DomainField(Alias = "id", RequiredOnCreate = true, Immutable = true)]
        public Guid Id { get; set; }

        /// <summary>Задание на сбор энергопрофилей</summary>
        [Relation]
        [DomainField(Alias = "energy_profile_collection_task_id", RequiredOnCreate = true, Immutable = true)]
        public EnergyProfileCollectionTask? EnergyProfileCollectionTask { get; set; }

        /// <summary>Здание</summary>
        [Relation]
        [DomainField(Alias = "building_id", RequiredOnCreate = true, Immutable = true)]
        public Building? Building { get; set; }

        /// <summary>Наименование</summary>
        [DomainField(
            Alias = "name",
            Calculated = "Да, вычисляется при чтении / на UI; не хранится. Формула: \"{FK → Здание. Наименование} ({FK → Задание на сбор энергопрофилей. Год начала отчётного периода}-{Задание на сбор энергопрофилей. Год окончания отчётного периода})\" Пример: Гараж №1 (2026-2026)"
        )]
        public string? Name { get; set; }

        /// <summary>Модель нормативного расчёта</summary>
        [Relation]
        [DomainField(Alias = "normative_calculation_model_id", Range = "Если выбрана, то может быть только модель с `Вид расчёта` == `Нормативный`")]
        public ConsumptionModelEntity? NormativeCalculationModel { get; set; }

        /// <summary>Модель эталонного расчёта</summary>
        [Relation]
        [DomainField(Alias = "standard_calculation_model_id", Range = "Если выбрана, то может быть только модель с `Вид расчёта` == `Эталонный`")]
        public ConsumptionModelEntity? StandardCalculationModel { get; set; }

        /// <summary>Модель технологического расчёта</summary>
        [Relation]
        [DomainField(
            Alias = "technological_calculation_model_id",
            Range = "Если выбрана, то может быть только модель с `Вид расчёта` == `Технологический`"
        )]
        public ConsumptionModelEntity? TechnologicalCalculationModel { get; set; }

        /// <summary>Модель произвольного расчёта</summary>
        [Relation]
        [DomainField(Alias = "custom_calculation_model_id", Range = "Если выбрана, то может быть только модель с `Вид расчёта` == `Произвольный`")]
        public ConsumptionModelEntity? CustomCalculationModel { get; set; }

        /// <summary>Статус энергопрофиля</summary>
        [Relation]
        [DomainField(
            Alias = "status_id",
            RequiredOnCreate = true,
            Range = "{Формируется; Сформирован}",
            Default = "Формируется",
            Description = "Устанавливается пользователем"
        )]
        public EnergyProfileStatus? Status { get; set; }

        /// <summary>Статус выполнения условий расчётов</summary>
        [Relation]
        [DomainField(
            Alias = "model_consistency_status_id",
            RequiredOnCreate = true,
            Calculated = "Да",
            Range = "{Выполнены; Не выполнены}.",
            Default = "Не выполнены",
            Description = "Вычисляется автоматически"
        )]
        public ModelConsistencyStatus? ModelConsistencyStatus { get; set; }
    }

}