⟨/⟩ 50_Domain/Cards/Template/AttributeValueType.cs

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

using System;
using Ban.Sdaid.Notation.Documents;

namespace Ban.Sdaid.Icd.Domain.Cards.Template
{
    /// <summary>Вид типа значения — чем определяется допустимое значение атрибута.</summary>
    public enum ValueKind
    {
        /// <summary>Строка. Ограничения — длина значения.</summary>
        String,

        /// <summary>Целое число. Ограничения — диапазон значения.</summary>
        Integer,

        /// <summary>Вещественное число. Ограничения — диапазон значения и число знаков после запятой.</summary>
        Decimal,

        /// <summary>Дата. Ограничения — диапазон значения.</summary>
        Date,

        /// <summary>Отметка в клетке: заполнена или нет. Ограничений не имеет.</summary>
        Bool,

        /// <summary>Выбор из перечисления, заданного в самом типе. Ограничения — количество выбранных значений.</summary>
        Choice,

        /// <summary>Выбор из справочника. Ограничения — количество выбранных значений.</summary>
        Reference,
    }

    /// <summary>Тип значения атрибута: вид значения и ограничения, осмысленные для этого вида.</summary>
    [DomainEntity(Origin = DomainDataOrigin.Reference)]
    public class AttributeValueType
    {
        /// <summary>Идентификатор типа.</summary>
        public Guid Id { get; init; }

        /// <summary>Код типа, по которому на него ссылаются атрибуты.</summary>
        public string Code { get; init; } = string.Empty;

        /// <summary>Наименование типа.</summary>
        public string Name { get; init; } = string.Empty;

        /// <summary>Вид типа — отдельным полем, не внутри ограничений.</summary>
        [Relation]
        public ValueKind Kind { get; init; }

        /// <summary>
        /// Ограничения вида в виде JSON: границы, перечень вариантов, код справочника.
        /// Состав зависит от вида типа.
        /// </summary>
        public string? Constraints { get; init; }
    }

    /// <summary>Описание доменной сущности <see cref="AttributeValueType"/>.</summary>
    public class AttributeValueTypeSpec : IDomainEntityDocument
    {
        public Type Entity => typeof(AttributeValueType);

        public string Name => "Тип значения";

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

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

        public string[] Comments => new[]
        {
            "2026-08-27. После упразднения словаря атрибутов тип значения — единственное, что переиспользуется между видами документов.",
            "2026-08-27. Числовой вид значения разделён на целое и вещественное: разница существенна для распознавания.",
            "2026-08-27. Значения набора хранятся в ограничениях типа; вид «выбор из справочника» зарезервирован под большие наборы и пока не используется.",
        };

        public static string S1_Purpose = """
            ## Назначение

            Тип значения отвечает на два вопроса сразу: **что можно вписать** в поле и **сколько**
            значений допустимо. Второе не менее важно первого: на бумажной форме сосуществуют поля,
            где отмечается ровно одна клетка, и поля, где отмечается сколько угодно, — и без явного
            различия Сервис не отличит ошибку распознавания от верно прочитанной карты.

            Тип **именован и переиспользуется**: «степень повреждения» — один тип на шесть полей
            листа сохранности, «исполнитель» — на мониторинг и таблицу экспертов. После того как
            общий словарь атрибутов упразднён, тип значения — единственное, что виды документов
            делят между собой, и делят осознанно: его выбирают, описывая атрибут.

            Отсюда обязанность интерфейса ведения: показывать, **где тип используется**. Правка
            типа меняет поведение всех полей, которые на него ссылаются, — в этом его смысл,
            но вслепую такую правку делать нельзя.

            Вид значения (`kind`) — не справочник, а перечень возможностей системы: добавить
            седьмой вид значит написать разбор ограничений, отрисовку поля, проверку ввода
            и формат в запросе к модели. Администратор такого не заводит.
            """;

        public static string S2_Constraints = """
            ## Ограничения по видам типов

            Ограничения есть у любого вида, но означают то, что осмысленно именно для него:

            | Вид типа | Что означают ограничения | Откуда берутся значения | Пример |
            |---|---|---|---|
            | Строка | длина значения | ввод | заглавие: от 1 до 500 знаков |
            | Целое | диапазон значения | ввод | количество страниц: от 1 до 5000 |
            | Вещественное | диапазон значения, знаки после запятой | ввод | величина pH: от 0 до 14 |
            | Дата | диапазон значения | ввод | время создания рукописи: от 900 до текущего года |
            | Отметка | ограничений нет | ввод | «Реставрация задано» |
            | Выбор из перечисления | количество выбранных вариантов | перечисление в самом типе | тиснение: слепое, золотое, другие виды; не более трёх |
            | Выбор из справочника | количество выбранных значений | справочник | материалы основы: сколько угодно |

            Через количество выражается кратность поля: не более одного значения — выбор одного
            (степень повреждения); не менее и не более одного — обязательный выбор одного (тип
            знаковой информации); более одного — множественный выбор (материалы основы, украшения).

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

            Целое и вещественное разделены по той же причине, по которой отметка отделена от выбора:
            разница видна распознавателю. У количества страниц дробной части не бывает, и `5,83`
            в этом поле — ошибка чтения, а не значение; у величины pH, наоборот, запятая обязательна,
            и прочитанное `583` разумно понимать как `5,83`. Одним числовым видом это не выразить.
            """;

        public static string S3_Storage = """
            ## Хранение ограничений

            Вид типа хранится **отдельным полем**, ограничения — одним значением в формате JSON.
            Отдельной таблицы на каждый вид значения не заводим: разложение на «тип строки», «тип
            даты», «тип выбора» дало бы целостность ценой соединений и пяти таблиц вместо одной.

            Вид при этом наружу вынесен намеренно: без него нельзя ответить на вопрос «покажи все
            атрибуты-даты», не разбирая JSON в каждой записи, — а такие вопросы возникают при
            настройке распознавания.

            Цена решения: тип перестаёт переиспользоваться так, как переиспользовался бы справочник
            типов. Ограничения строк и дат у каждого поля всё равно свои, а общие наборы значений
            остаются справочниками, поэтому потеря невелика.
            """;

        public static string S4_ChoiceVsReference = """
            ## Где живут значения набора

            Значения задаются **в ограничениях самого типа**, парами «код — значение», в порядке,
            в каком они напечатаны на бланке:

            ```
            вид = choice; значения = [a: «Кожа»; b: «Пергамент»; c: «Ткань»; d: «Бумага»;
                                      e: «Другое»; 0: «Отсутствует»];  min = 0; max = 1
            ```

            **Код — часть данных, а не порядковый номер.** У заказчика это буква подполя ИРБИС,
            и в АБИС уходит именно она, поэтому код задаётся явно и не меняется. Один и тот же
            `a` означает «Кожа» в одном типе и «Слепое» в другом — код уникален внутри своего типа,
            и этого достаточно.

            Прежний критерий — «набор нужен двум и более атрибутам, значит справочник» — отпал
            вместе со словарём атрибутов. Атрибут теперь принадлежит виду и всегда один, так что
            по этой букве справочником было бы всё. Переиспользование обеспечивает сам тип:
            «степень повреждения» — один тип на шесть полей листа сохранности.

            Остался вопрос **объёма и ведения**. Набор из четырёх значений живёт в ограничениях
            без затей. Набор из пятисот — нет: его ищут, пополняют и правят по одному значению,
            а ограничения переписываются целиком. Для таких наборов оставлен вид `ref`: значения
            ведутся снаружи, тип на них ссылается. Пока он **не используется** — первым кандидатом
            станет язык издания, 589 значений; хранилище значений появится тогда же, когда
            понадобится.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_006_CardDataStructure)} — решение о структуре данных информационной карты
            """;
    }
}