⟨/⟩ Nts/55_Backend/Calc/InputModel.cs

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

using System;
using System.Collections.Generic;
using Rugrid.Sdaid.Notation.Documents;
using Rugrid.Sdaid.Epas.Nts.Domain;

namespace Rugrid.Sdaid.Epas.Nts.Backend.Calc
{
    /// <summary>
    /// Модель входных данных расчёта нормативного расхода топлива. Класс — единственный источник:
    /// таблица исходных данных методики, соответствие «доменный атрибут → параметр расчёта»
    /// и контракт входной модели строятся по нему. Состав — по ветке лёгкового автомобиля;
    /// по мере разбора следующих веток состав расширяется.
    /// </summary>
    public class DOC_InputModel : IDomainEntityDocument, IHasStructuralLinks, IFolder<NtsBackendFolder>
    {
        public SdaidLink[] Links => new[]
        {
            Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_Vehicle>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_SelectedModifications>(),
            Rel.Uses<Rugrid.Sdaid.Epas.Nts.Domain.DOC_VehicleModification>(),
        };

        public static string _refName = "Модель входных данных расчёта ЭНТС";

        public string Name => "Модель входных данных расчёта";

        public string Description =>
            @"Исходные данные расчёта нормативного расхода топлива: обозначение в формулах, единица измерения, диапазон и источник значения.";

        public string Version => "0.2";
        public string Status => "draft";

        public string[] Comments => new[]
        {
            "Исходные данные описаны классом, а не таблицей: в ЭНЗС тот же состав ведётся тремя рукописными таблицами (исходные данные методики, контракт InputModel, маппинг), и они расходятся. Источник значения задан [CalcSource] через typeof/nameof, поэтому переименование доменного атрибута ломает сборку.",
            "Шесть надбавок (PopulationAllowance, TechStopAllowance, LowSpeedAllowance, SpecialVehicleAllowance, EmergencyAllowance, ForcedIdleAllowance) — decimal (не nullable) здесь, а на Vehicle — decimal? (nullable). Это осознанно: на Vehicle отсутствие значения — «не задано», во входной модели отсутствие уже заменено на 0 при маппинге (поле на Vehicle — «по умолчанию 0»). CalcSource не проверяет совместимость типов, несовпадение nullable — не повод исправлять в одну из сторон.",
            "`CalcSourceAttribute` выражает только один шаг до одного скалярного поля одной связи — не цепочку из нескольких шагов (VehicleKind, FuelType), не коллекцию строк (BaseConsumptionRates, ReferenceGroupConsumptionRates), не фильтр по условию (ReferenceGroupConsumptionRates). Где реальный путь шире, атрибут указывает на ближайшее однозначное приближение, а точный путь — в Description самого поля; это ограничение самого атрибута, не повторяется в каждом Description заново.",
        };

        public Type Entity => typeof(InputModel);

        public static string Reading = """"
        ## Как читать таблицу

        - **Обозначение** — как величина названа в формулах методики. Пусто — величина в формулы
          не входит, а выбирает ветку расчёта.
        - **Источник** — доменный атрибут, из которого заполняется параметр. Пусто — значения нет
          в АИС: оно вводится пользователем в формах модуля, берётся из справочника или принимается
          по умолчанию согласно методике. Чем именно — сказано в описании параметра.
        """";
    }

    /// <summary>
    /// Исходные данные расчёта нормативного расхода топлива для одного транспортного средства
    /// за период (ветка лёгкового автомобиля).
    /// </summary>
    [DomainEntity]
    public class InputModel
    {
        /// <summary>Вид ТС</summary>
        [DomainField(RequiredForCalc = "Да",
            Description = "Выбирает формулу расчёта: лёгковой, автобус, грузовой бортовой и тягач, самосвал, фургон, специальный. " +
                "Реальный путь: `Vehicle.VehicleKind.Code` — значение, с которым сравнивает методика " +
                "(см. `fuel.category_by_kind`), не наименование; `CalcSource` указывает на саму связь `Vehicle.VehicleKind` (см. ограничение атрибута — Comments)")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.VehicleKind))]
        public string? VehicleKind { get; set; }

        /// <summary>Вид топлива</summary>
        [DomainField(RequiredForCalc = "Да", Range = "{бензин, дизельное топливо}",
            Description = "Для лёгкового расход не зависит от топлива; используется в ветках грузовых и самосвалов. " +
                "Реальный путь: `Vehicle.FuelKindPrimary.Name` — фактическое значение, с которым сравнивает методика " +
                "(см. `fuel.h_transport`/`fuel.h_trailer`); `CalcSource` указывает на саму связь (см. ограничение атрибута — Comments)")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.FuelKindPrimary))]
        public string? FuelType { get; set; }

        /// <summary>Базовые нормы отобранных модификаций, л/100 км</summary>
        [DomainField(Unit = "л/100 км", Range = "список, ≥ 1 элемента", RequiredForCalc = "Да",
            Description = "Базовые нормы отобранных для ТС модификаций справочника «Марка, модель и модификация ТС»; по ним секция `fuel.base_norm` вычисляет минимальную и максимальную базовые нормы и саму базовую норму H_s. " +
                "Реальный путь: `VehicleModification.NormPerMileage` каждой из строк `SelectedModifications`, отобранных " +
                "для данного ТС (коллекция, не одна строка); `CalcSource` указывает на связь одной строки отбора — " +
                "`SelectedModifications.VehicleModification` (см. ограничение атрибута — Comments)")]
        [CalcSource(typeof(SelectedModifications), nameof(SelectedModifications.VehicleModification))]
        public IReadOnlyList<decimal>? BaseConsumptionRates { get; set; }

        /// <summary>Базовые нормы всей базовой группы (вид ТС + вид топлива), л/100 км</summary>
        [DomainField(Unit = "л/100 км", Range = "список, ≥ 1 элемента", RequiredForCalc = "Да",
            Description = "Базовые нормы ВСЕХ модификаций базовой группы ТС (вид ТС + вид топлива, мягко суженной по топливу), не только отобранных для ТС; по ним секция `fuel.reference_norm` вычисляет эталонную норму H_эт = min. Пустая группа — та же ошибка, что у `BaseConsumptionRates` («Базовая норма не определена»). " +
                "Реальный путь: `VehicleModification.NormPerMileage` у ВСЕХ строк справочника, отобранных процедурой " +
                "подбора базовой группы по виду ТС и виду топлива (см. Nts.Backend.ModificationSelection.DOC_ModificationGroupSelection, " +
                "раздел «Базовая группа») — фильтр по условию, не связь одной строки, как у `BaseConsumptionRates`; " +
                "`CalcSource` указывает на само поле `VehicleModification.NormPerMileage` (см. ограничение атрибута — Comments)")]
        [CalcSource(typeof(Rugrid.Sdaid.Epas.Nts.Domain.VehicleModification), nameof(Rugrid.Sdaid.Epas.Nts.Domain.VehicleModification.NormPerMileage))]
        public IReadOnlyList<decimal>? ReferenceGroupConsumptionRates { get; set; }

        /// <summary>Пробег за период, км</summary>
        [DomainField(Symbol = "S", Unit = "км", Range = "вещественное, > 0 или не передано",
            Description = "Задаётся вызывающей стороной расчёта, не только картой ТС — например, " +
                "формированием отчёта реальным фактическим пробегом за период. Не передано (`null`) " +
                "— секция `fuel.mileage` сама подставляет эталон (удельный расход) — 100 км; передано, " +
                "но ≤ 0 — ошибка валидации. Карточка ТС это поле не задаёт и всегда получает эталон")]
        public decimal? Mileage { get; set; }

        /// <summary>Грузоподъёмность, т</summary>
        [DomainField(Symbol = "q", Unit = "т", Range = "вещественное, ≥ 0",
            Description = "Для грузовых: задаёт массу груза (секция `fuel.g_cargo`)")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.CargoCapacity))]
        public decimal? CargoCapacity { get; set; }

        /// <summary>Собственная масса прицепа (полуприцепа), т</summary>
        [DomainField(Symbol = "G_{пр}", Unit = "т", Range = "вещественное, ≥ 0",
            Description = "Для грузовых; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.TrailerMass))]
        public decimal? TrailerMass { get; set; }

        /// <summary>Количество поездок с грузом за смену, шт</summary>
        [DomainField(Symbol = "Z", Unit = "шт.", Range = "целое, ≥ 0",
            Description = "Для самосвалов; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.TripsWithCargoPerShift))]
        public int? TripsWithCargoPerShift { get; set; }

        /// <summary>Норма расхода топлива на транспортную работу автомобиля-самосвала, л/100 т·км</summary>
        [DomainField(Symbol = "H_w", Unit = "л/100 т·км", Range = "вещественное, ≥ 0",
            Description = "Для самосвалов; вводится в модуле")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.DumpTruckTransportWorkConsumptionRate))]
        public decimal? DumpTruckTransportWorkConsumptionRate { get; set; }

        /// <summary>Норма расхода топлива на работу отопителя, л/ч</summary>
        [DomainField(Symbol = "H_{от}", Unit = "л/ч", Range = "вещественное, ≥ 0",
            Description = "Для автобусов; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.ConsumptionRateForUsingHeater))]
        public decimal? ConsumptionRateForUsingHeater { get; set; }

        /// <summary>Время работы автомобиля с включённым отопителем, ч</summary>
        [DomainField(Symbol = "T", Unit = "ч", Range = "вещественное, ≥ 0",
            Description = "Для автобусов; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.TimeWhenUsingHeater))]
        public decimal? HeaterTime { get; set; }

        /// <summary>Норма расхода топлива на час работы, летняя, л/ч</summary>
        [DomainField(Symbol = "H_т^{лето}", Unit = "л/ч", Range = "вещественное, ≥ 0",
            Description = "Для специальных автомобилей; из АИС ОИА ПЭО")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.ConsumptionRatePerHourSummer))]
        public decimal? ConsumptionRatePerHourSummer { get; set; }

        /// <summary>Норма расхода топлива на час работы, зимняя, л/ч</summary>
        [DomainField(Symbol = "H_т^{зима}", Unit = "л/ч", Range = "вещественное, ≥ 0",
            Description = "Для специальных автомобилей; из АИС ОИА ПЭО")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.ConsumptionRatePerHourWinter))]
        public decimal? ConsumptionRatePerHourWinter { get; set; }

        /// <summary>Дата начала эксплуатации</summary>
        [DomainField(Symbol = "StartDate", Range = "дата", RequiredForCalc = "Да",
            Description = "Вместе с отчётной датой задаёт срок эксплуатации для надбавки D_{эксп}")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.StartDate))]
        public DateTime? StartDate { get; set; }

        /// <summary>Отчётная дата</summary>
        [DomainField(Symbol = "ReportDate", Range = "дата", RequiredForCalc = "Да",
            Description = "Дата расчёта; для расчёта берётся текущая дата")]
        public DateTime? ReportDate { get; set; }

        /// <summary>Предельная величина зимних надбавок не более, %</summary>
        [DomainField(Symbol = "D_{район}", Unit = "%", Range = "вещественное, ≥ 0", RequiredForCalc = "Да",
            Description = "Предельная величина зимней надбавки для субъекта РФ, в котором эксплуатируется ТС; равна поправочному коэффициенту на климатический район D_{район}. Выбирается из справочника «Предельные значения зимних надбавок» по субъекту РФ эксплуатации ТС")]
        public decimal? ClimateRegionAllowance { get; set; }

        /// <summary>Поправочный коэффициент на численность населения</summary>
        [DomainField(Symbol = "D_{нас}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент на численность населения, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.PopulationAllowance))]
        public decimal PopulationAllowance { get; set; }

        /// <summary>Поправочный коэффициент на частые технологические остановки</summary>
        [DomainField(Symbol = "D_{ост}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент на частые технологические остановки, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.TechStopAllowance))]
        public decimal TechStopAllowance { get; set; }

        /// <summary>Поправочный коэффициент при движении с пониженной скоростью</summary>
        [DomainField(Symbol = "D_{пон}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент при движении с пониженной скоростью, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.LowSpeedAllowance))]
        public decimal LowSpeedAllowance { get; set; }

        /// <summary>Поправочный коэффициент при работе специальных автомобилей</summary>
        [DomainField(Symbol = "D_{спец}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент при работе специальных автомобилей, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.SpecialVehicleAllowance))]
        public decimal SpecialVehicleAllowance { get; set; }

        /// <summary>Поправочный коэффициент при работе в условиях ЧС</summary>
        [DomainField(Symbol = "D_{чс}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент при работе в условиях ЧС, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.EmergencyAllowance))]
        public decimal EmergencyAllowance { get; set; }

        /// <summary>Поправочный коэффициент при вынужденном простое</summary>
        [DomainField(Symbol = "D_{прос}", Unit = "%", Range = "вещественное, ≥ 0",
            Description = "Поправочный коэффициент при вынужденном простое, %; вводится в модуле, по умолчанию 0")]
        [CalcSource(typeof(Vehicle), nameof(Vehicle.ForcedIdleAllowance))]
        public decimal ForcedIdleAllowance { get; set; }
    }
}