⟨/⟩ 40_Arch/ADR/ADR_006_CardDataStructure.cs

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

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

namespace Ban.Sdaid.Icd.Arch.Adr
{
    /// <summary>
    /// Решение о структуре данных информационной карты: как хранить состав полей, различающийся
    /// от вида к виду ИК и от редакции к редакции бланка. Принят вариант 5.
    /// </summary>
    public class ADR_006_CardDataStructure : IAdrDocument
    {
        public static string _refName = "ADR-006 «Структура данных информационной карты»";

        public string Name => "ADR-006. Структура данных информационной карты";

        public string Description =>
            @"Как хранить содержимое информационной карты: фиксированными свойствами, иерархией видов или шаблоном вида ИК.";

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

        public string[] Comments => new[]
        {
            "2026-08-11. Заведён как разбор вариантов.",
            "2026-08-12. Принят вариант 4: состав и форма описываются раздельно.",
            "2026-08-19. Рабочим принят вариант 5: вид документа вместо конфигурации, дерево видов, атрибут документа карты. Применён к модели данных.",
            "2026-08-27. Общий словарь атрибутов упразднён: атрибут принадлежит виду документа и несёт код, наименование и тип значения. Переиспользование обеспечивает именованный тип значения.",
            "2026-08-27. Числовой вид значения разделён на целое и вещественное; значения справочных наборов хранятся в ограничениях типа.",
            "2026-08-31. Экземпляр фонда убран из модели: реестр экземпляров и опознание экземпляра отнесены к перспективам развития.",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст

            Информационная карта фиксирует результаты обработки экземпляра фонда. Её содержимое —
            это поля бумажной формы, и вопрос в том, чем они являются для модели данных: свойствами
            сущности, известными на этапе проектирования, или данными, состав которых задаётся
            извне и меняется без правки кода.

            Что известно из образцов заказчика (см. «Образцы бумажных информационных карт»):

            - **Состав полей различается по видам ИК.** У формы на издание два листа, у формы на
              рукопись — пять. Пересечение между ними невелико: шифр, дата, ФИО составителя, вид
              документа. Всё остальное расходится — «Переписчик рукописи», «Основание атрибуции»,
              «Филиграни», «Задание по хранению» есть только у рукописи; «Конволют», «Камерный
              каталог», «Первоначальный академический штамп» — только у издания.
            - **У одного вида ИК несколько редакций бланка.** По изданиям видно три. Состав полей
              у них почти совпадает, различаются заголовки и подписи полей: `Кол-во стр.` против
              `Количество страниц`, `Ил. в тексте` против `Иллюстрации в тексте`. Лист сохранности
              тоже существует в разных вёрстках.
            - **Состав полей известен не для всех видов.** Форма на графику передана в текстовом
              формате, образцов на листовой документ, свиток и вложение нет вовсе. Состав раздела
              «Мероприятия стабилизации» не определён (OQ-V-2).
            - **Большинство полей — отметки в клетках**, а не текст: «Задано / Выполнено»,
              «Дерево / Картон / Бумага», степень повреждения по шкале.
            - **Незаполненные поля — норма.** Встречаются карты, где заполнены только шифр
              и инвентарный номер.

            Ключевое ограничение: Сервис оцифровывает **уже существующий** архив. Любая схема,
            которая не даёт внести карту, лежащую в фонде, неприемлема — исправить бумагу нельзя.
            """;

        public static string S2_Options = """
            ## Рассматриваемые варианты

            ### Вариант 1. Фиксированные свойства сущности

            Каждое поле формы — свойство класса информационной карты, с собственным типом.

            За: типизация и валидация на уровне модели, поиск и фильтрация по полю средствами
            хранилища, ошибка в имени поля ловится компилятором.

            Против: класс становится объединением полей всех видов ИК, где для конкретной карты
            большая часть свойств заведомо пуста и неизвестно, какая именно. Каждая новая редакция
            бланка и каждый новый вид документа требуют правки кода и миграции. Различие в подписи
            одного и того же поля между редакциями выразить нечем — придётся выбрать одно написание
            и потерять остальные.

            ### Вариант 2. Иерархия видов ИК (наследование классов)

            Базовый класс с общей частью, наследники по видам: ИК издания, ИК рукописи и далее.

            За: расхождения между видами выражены явно, общая часть описана один раз, тип карты
            известен статически.

            Против: по образцам общая часть — четыре поля, а расхождения — десятки и сотни полей.
            Получается почти пустой родитель и не пересекающиеся между собой наследники, то есть
            наследование без переиспользования. Редакция бланка иерархией не выражается вовсе:
            это версия формы, а не подвид карты, и добавлять по классу на редакцию бессмысленно.
            Наследование в модели данных дополнительно упирается в отображение на хранилище.

            ### Вариант 3. Плоский список полей (как сейчас)

            Карта хранит список значений «раздел + наименование поля + значение».

            За: любой вид ИК и любая редакция укладываются без изменения схемы; экраны проверки
            и выгрузка пишутся один раз на все виды.

            Против: состав полей ничем не задан и не контролируется — ничто не мешает записать
            `Заглавие` и `заглавие` как разные поля; нельзя ответить на вопрос, какие поля вид ИК
            обязан иметь; типизации нет, дата и число хранятся строками.

            ### Вариант 4. Раздельное описание электронного документа и бумажной формы

            Состав полей описывается данными, а не кодом. Ключевое отличие от вариантов 1–3:
            **что хранится** и **как это напечатано** — два разных описания, а не одно.

            #### Электронный документ: что хранится

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

            **Атрибут** — поле как понятие: код, каноническое наименование, тип значения. Живёт
            в общем словаре и переиспользуется всеми конфигурациями.

            **Тип значения** — правило, которому подчиняется значение: вид значения и ограничения.

            ```
            вид = string;  min = 1;   max = 500      → длина значения
            вид = number;  min = 0;   max = 14       → диапазон значения
            вид = date;    min = 900; max = 2026     → диапазон значения
            вид = bool;                                  → отметка в клетке: есть или нет
            вид = choice [A: «Слепое»; B: «Золотое»; C: «Другие виды»];  min = 0; max = 3
            вид = ref → справочник «Материалы»;                          min = 0; max = 1
            ```

            Пара `min`/`max` есть у любого вида, но означает осмысленное именно для него: у строки —
            длину, у числа и даты — границы, у выбора — **количество выбранных значений**. Этим же
            задаётся кратность: `max = 1` — выбор одного (степень повреждения), `max = N` —
            множественный (материалы основы, украшения).

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

            `choice` перечисляет значения прямо в типе, `ref` ссылается на справочник. Критерий:
            если один и тот же набор значений нужен двум и более атрибутам — это справочник.

            **Документ** — экземпляр: `docId` равен собственному идентификатору, `parentId` пуст
            у корня; ссылка на конфигурацию; значения атрибутов. Документ может быть составным:
            у вложенного документа `parentId` указывает на непосредственного родителя, а `docId` —
            на корень, поэтому весь документ достаётся одним запросом.

            **Корневой документ — агрегат:** части адресуются только через него.

            **Вложенный документ** — эксперты, замеры pH, авторы, переписчики, владельцы, биотесты.
            Это состав, а не вёрстка: список существует в данных, даже если на бланке под него
            отведена одна строка.

            **Значение атрибута** — ссылка на атрибут и одно или несколько значений.

            #### Бумажный документ: как это напечатано

            **Форма документа** — описание бланка целиком; ссылается на конфигурацию документа.
            У одной конфигурации может быть несколько форм — по числу редакций бланка в обращении.

            **Страница формы** — номер, заголовок, роль-заполнитель.

            **Раздел** — заголовок, порядок; разделы вкладываются друг в друга.

            **Размещение атрибута** — атрибут, подпись именно на этом бланке, место на листе.
            Область поля на изображении относится к форме, а не к конфигурации: конфигурация
            описывает состав, а не бумагу.

            Версий у формы нет: изменился бланк — это другая форма. Версионирование не нужно потому,
            что значение опознаётся кодом атрибута и от места печати не зависит.

            Страницы, разделы, подписи и координаты к электронному документу отношения не имеют.

            #### Связь двух описаний

            Форма ссылается на конфигурацию. В форму попадают не все атрибуты состава — служебные
            данные Сервиса на бумаге отсутствуют, — но атрибута, которого нет в составе, в форме
            быть не может. Обязательность — свойство состава, а не формы.

            То, с какой формы оцифрована конкретная карта, — **свойство пакета ввода**: это факт
            ввода, а не свойство самой карты. Без него нельзя объяснить, почему поле распозналось
            так, а не иначе.

            #### Ядро карты: типизированные свойства поверх состава

            Информационная карта остаётся **отдельной сущностью**, а не растворяется в общем
            документе. У неё есть то, чего у абстрактного документа быть не может: связь с документом
            фонда, устанавливаемая на проверке экземпляров; связь с пакетом-первоисточником; дата
            карты как часть идентификации; факт выгрузки в АБИС; собственный жизненный цикл —
            карта создаётся командой пользователя по итогам проверки. Загнать это в атрибуты нельзя:
            «выгружена ли карта» не может быть строкой в общем хранилище значений.

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

            **Критерий отбора в ядро — функциональный, а не структурный.** В ядро идёт не то, что
            встречается на всех бланках, а то, **по чему Сервис работает сам**: ищет карту, строит
            реестр, отбирает выборку, подбирает экземпляр фонда, формирует выгрузку. Структурный
            критерий «общее для всех конфигураций» здесь не работает: у ИК издания одно поле
            «Заглавие», у ИК рукописи их три — унифицированное, полное и перевод.

            **Ядро — проекция, а не второй источник истины.** Источник истины — значения атрибутов;
            свойства ядра заполняются из них и пересчитываются при правке значений. Какой атрибут
            даёт какое свойство ядра, задаёт **конфигурация**: у ИК рукописи заглавие берётся
            из унифицированного заглавия, и оно же, по-видимому, соответствует полю «Заглавие»
            на форме на издание.

            Что это даёт: поиск и отбор по ядру идут по типизированным колонкам, то есть недостаток
            варианта 4 — «поиск через связь значения с атрибутом» — снимается для тех полей, по
            которым действительно ищут. Вопрос остаётся только для поиска **за пределами ядра**
            (OQ-A6-4).

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

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

            - **Матрица «Задано / Выполнено» — два атрибута**, а не один составной: коды вида
              `restoration_assigned` и `restoration_done`. Словарь длиннее, но тип значения остаётся
              простым, а на бумаге это две независимые клетки, заполняемые в разное время
              и разными людьми.
            - **Повторяющиеся колонки — это разные атрибуты.** «Материалы основы» с колонками 1 и 2
              на форме на рукопись — не одно поле, заполняемое дважды, а два: основа переплёта
              и основа футляра. Тот же принцип дробности.
            - **Наследование конфигураций не вводится.** Переиспользование обеспечивает словарь:
              общее поле изданий и рукописей — один и тот же атрибут в двух конфигурациях.
            - **Повторяющиеся группы — вложенные документы.** Эксперты, замеры pH, авторы,
              переписчики, владельцы, биотесты. Таблицы с заранее известными строками —
              «Владельческие пометы», «Оформление экземпляра» — вложенными документами **не**
              являются: набор строк задан бланком, это просто пары атрибутов.
            - **Тип значения хранится как вид плюс JSON-ограничения.** Вид (`kind`) — отдельным
              полем, чтобы отвечать на вопрос «все атрибуты-даты» без разбора JSON в каждой записи;
              остальные параметры — одним JSON-полем, без отдельной таблицы на каждый вид значения.

            #### Соглашения по переносу с бумаги

            - **Одно поле бланка против списка в составе.** Если в составе список (авторы, замеры pH),
              а на бланке под него одно поле, распознанное значение **добавляется** в список.
              Соглашение временное: принято до проработки задачи распознавания и будет пересмотрено,
              когда станет понятно, что умеет модель.
            - **Рукописный текст вне клеток** сохраняется как примечание к документу с областью
              на изображении. При проверке пользователю предлагается отнести его к атрибуту, то есть
              примечание может стать значением. В образцах такое есть: `5,83 перед форзац` в поле
              величины pH, приписка «нет» рядом со шкалой степени повреждения.

            #### За

            - новая редакция бланка — новая форма из тех же атрибутов, без правки кода и миграции;
              карты разных редакций сравнимы, потому что опознаются по коду атрибута, а не по подписи;
            - выгрузка в АБИС отображается с кодов атрибутов — одно правило на все виды и редакции;
            - тип значения даёт и контроль ввода, и рамки распознавателю: `date 900–2026` отсекает
              заведомый мусор в рукописном годе, `choice` сводит задачу к выбору из списка;
            - различие «одна отметка» и «несколько отметок» выражено явно, поэтому две отметки там,
              где допустима одна, — видимая ошибка, а не молча принятые данные;
            - разделение состава и формы снимает версионирование: бланк можно менять, не задевая
              созданные карты;
            - состав документа перестаёт быть произвольным: значение без атрибута в конфигурации
              невозможно.

            #### Против

            - сущностей становится вдвое больше, чем в вариантах 1–3, и у них свой жизненный цикл
              плюс интерфейсы ведения; до наполнения словаря, конфигураций и форм Сервис бесполезен;
            - поиск по полю за пределами ядра идёт через связь значения с атрибутом, а не по колонке
              таблицы; для полей ядра это снято проекцией, для остальных цена зависит от того, нужен
              ли такой поиск вообще (OQ-A6-4);
            - ядро карты дублирует значения атрибутов и требует пересчёта при их правке —
              рассинхронизация ядра и состава возможна и заметна не сразу;
            - словарь атрибутов надо чем-то наполнить: по образцам это сотни записей на вид ИК —
              отдельная работа до начала оцифровки;
            - дерево документов и дерево разделов существуют параллельно и легко путаются: первое
              про данные, второе про бумагу. Ошибка в том, куда положить понятие, обнаружится поздно.
            """;

        public static string S25_Variant5 = """
            ## Вариант 5. Вид документа и атрибут документа карты

            Раскладка, предложенная после принятия варианта 4. Отличается от него тремя вещами:
            видом документа вместо конфигурации, деревом **видов** наряду с деревом экземпляров
            и отдельной сущностью «атрибут документа карты» между составом и значением.

            ```mermaid
            classDiagram
              direction LR

              class AttributeValueType["Тип значения"] {
                <<Entity>>
                +Guid Id
                +String Code
                +String Name
                +ValueKind Kind
                +String Constraints
              }

              class DocumentKind["Вид документа"] {
                <<Entity>>
                +Guid DocId
                +Guid ParentId
                +String DocKind
                +String Name
                +IReadOnlyList~DocumentKind~ NestedKinds
                +IReadOnlyList~DocumentKindAttribute~ Attributes
              }

              class DocumentKindAttribute["Атрибут вида документа"] {
                <<Entity>>
                +DocumentKind DocumentKind
                +String Code
                +String Name
                +AttributeValueType ValueType
                +Boolean IsRequired
                +Int32 Order
                +CardCoreField CoreField
              }

              class InformationCard["Информационная карта"] {
                <<Entity>>
                +Guid Id
                +DocumentKind Kind
                +DateOnly Date
                +IReadOnlyList~CardDocument~ Documents
              }

              class CardDocument["Документ информационной карты"] {
                <<Entity>>
                +Guid DocId
                +Guid ParentId
                +DocumentKind Kind
                +IReadOnlyList~CardDocument~ NestedDocuments
                +IReadOnlyList~CardDocumentAttribute~ Attributes
              }

              class CardDocumentAttribute["Атрибут документа карты"] {
                <<Entity>>
                +CardDocument Document
                +DocumentKindAttribute KindAttribute
                +IReadOnlyList~String~ Values
                +ValueOrigin Origin
                +Guid FilledByUserId
                +DateTime FilledAt
              }

              class DocumentForm["Форма документа"] {
                <<Entity>>
                +Guid Id
                +DocumentKind DocumentKind
                +String Name
                +IReadOnlyList~FormPage~ Pages
              }

              class FormPage["Страница формы"] {
                <<Entity>>
                +Int32 Number
                +String Title
                +IReadOnlyList~FormSection~ Sections
              }

              class FormSection["Раздел страницы"] {
                <<Entity>>
                +String Title
                +Int32 Order
                +IReadOnlyList~FormSection~ NestedSections
                +IReadOnlyList~FormField~ Fields
              }

              class FormField["Поле формы"] {
                <<Entity>>
                +DocumentKindAttribute KindAttribute
                +String Caption
                +Int32 Order
                +SourceArea Area
              }

              class SourceArea["Область на кадре"] {
                <<ValueObject>>
                +Double X
                +Double Y
                +Double Width
                +Double Height
              }

              class InputPacket["Пакет ввода"] {
                <<Entity>>
                +Guid Id
                +String Number
                +DocumentForm DocumentForm
                +DateTime QueuedAt
                +Guid CreatedByUserId
                +String RecognitionRequestJson
                +String RecognitionResultJson
                +DateTime RecognizedAt
                +IReadOnlyList~ImageFrame~ Frames
                +IReadOnlyList~PacketFieldValue~ Values
              }

              class ImageFrame["Кадр"] {
                <<Entity>>
                +Guid Id
                +Int32 Order
                +String StorageKey
                +ImageSource Source
                +FormPage FormPage
              }

              class PacketFieldValue["Значение поля пакета"] {
                <<Entity>>
                +DocumentKindAttribute KindAttribute
                +FormField FormField
                +IReadOnlyList~String~ Values
                +IReadOnlyList~String~ RecognizedValues
                +ValueOrigin Origin
                +Double Confidence
                +ImageFrame ImageFrame
                +SourceArea Area
              }

              DocumentKind "1" *-- "0..*" DocumentKind : ParentId
              DocumentKind "1" *-- "0..*" DocumentKindAttribute : Attributes
              DocumentKindAttribute --> "1" AttributeValueType : ValueType

              DocumentForm --> "1" DocumentKind : DocumentKind
              DocumentForm "1" *-- "1..*" FormPage : Pages
              FormPage "1" *-- "0..*" FormSection : Sections
              FormSection "1" *-- "0..*" FormSection : Sections
              FormSection "1" *-- "0..*" FormField : Fields
              FormField --> "1" DocumentKindAttribute : KindAttribute


              InputPacket --> "1" DocumentForm : DocumentForm
              InputPacket "1" *-- "1..*" ImageFrame : Frames
              InputPacket "1" *-- "0..*" PacketFieldValue : Values
              ImageFrame --> "0..1" FormPage : FormPage
              PacketFieldValue --> "1" DocumentKindAttribute : KindAttribute
              PacketFieldValue --> "0..1" FormField : FormField
              PacketFieldValue --> "0..1" ImageFrame : ImageFrame
              PacketFieldValue "1" *-- "0..1" SourceArea : Area
              FormField "1" *-- "0..1" SourceArea : Area

              InformationCard --> "1" InputPacket : SourcePacket
              InformationCard --> "1" DocumentKind : Kind
              InformationCard "1" *-- "1..*" CardDocument : Documents

              CardDocument --> "1" DocumentKind : Kind
              CardDocument "1" *-- "0..*" CardDocument : ParentId
              CardDocument "1" *-- "0..*" CardDocumentAttribute : Attributes

              CardDocumentAttribute --> "1" DocumentKindAttribute : KindAttribute

              style DocumentKind fill:#d6e4f7,stroke:#2f5f96
              style DocumentKindAttribute fill:#d6e4f7,stroke:#2f5f96
              style DocumentForm fill:#dcefdc,stroke:#3f7f3f
              style FormPage fill:#dcefdc,stroke:#3f7f3f
              style FormSection fill:#dcefdc,stroke:#3f7f3f
              style FormField fill:#dcefdc,stroke:#3f7f3f
              style InputPacket fill:#fbe6cd,stroke:#b5762a
              style ImageFrame fill:#fbe6cd,stroke:#b5762a
              style PacketFieldValue fill:#fbe6cd,stroke:#b5762a
              style InformationCard fill:#f7dfe4,stroke:#a34457
              style CardDocument fill:#f7dfe4,stroke:#a34457
              style CardDocumentAttribute fill:#f7dfe4,stroke:#a34457
              style AttributeValueType fill:#f2f2f2,stroke:#8c8c8c
              style SourceArea fill:#f2f2f2,stroke:#8c8c8c
            ```

            Цветом выделены четыре агрегата: **вид документа** — голубой, **форма документа** —
            зелёный, **пакет ввода** — оранжевый, **информационная карта** — розовый. Серым —
            то, что вне агрегатов: типы значений и область на кадре.

            ### Как читается цепочка

            Состав задаётся дважды параллельными парами. **Вид документа** и его **атрибут вида** —
            описание того, что бывает; **документ карты** и его **атрибут документа** — то, что
            заполнено на конкретной карте. Каждый уровень экземпляра ссылается на свой уровень
            описания: документ карты знает свой вид, атрибут документа карты знает атрибут вида.

            Значение — **поле** атрибута документа карты, а не отдельная сущность: сначала «в этом
            документе есть такое поле», затем «у поля такие значения».

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

            Множественные значения (материалы основы, украшения, физико-механические повреждения
            отмечаются несколькими клетками сразу) хранятся списком в том же поле. Цена: запрос
            «во всех картах, где среди материалов есть картон» становится поиском по содержимому
            списка, а не соединением таблиц, — и требует индекса соответствующего вида. Это
            приемлемо, потому что поиск за пределами ядра решено не поддерживать.

            ### Бумажная форма

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

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

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

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

            ### Ввод и распознавание

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

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

            Заполнил пользователь значение при формировании пакета или при проверке — разницы нет,
            это одно и то же значение одного и того же атрибута; различается только происхождение.

            **Кадр относится к странице формы**, а не к «роли изображения»: страница 3 формы
            на рукопись точнее, чем абстрактная роль. Ссылка необязательна: кадр, который обработка
            ни к одной странице не отнесла, считается лишним и не распознаётся.

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

            Ключом в JSON служит **код атрибута**, а не подпись поля: подпись меняется от бланка
            к бланку, и по ней результат обратно не разложить. Форма задаёт структуру запроса
            и порядок, состав вида — имена.

            **Исправление пользователя не затирает распознанное.** У значения два набора: текущий
            и то, что вернула модель. Причина не в аудите — пары «что модель прочла / что там
            на самом деле» и есть материал для дообучения, который Сервис накапливает попутно.
            Если исправление затирает распознанное, материал теряется навсегда.

            Источник значения — **свойства самого значения**: кадр и область, оба необязательные.
            У введённого вручную источника нет, у распознанного из клетки есть и кадр, и область,
            у отнесённого к странице целиком — кадр без области. Отдельная сущность понадобилась бы,
            если бы одно значение собиралось из нескольких мест; на бланке такого не бывает.

            ### Атрибут вида документа и связующие сущности

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

            Ещё две сущности существуют ради связи, и вся их ценность — в том, что на этой связи
            можно хранить. Без реквизитов каждая вырождается в лишнюю таблицу.

            | Связующая сущность | Что связывает | Что несёт |
            |---|---|---|
            | Поле формы | раздел страницы ↔ атрибут вида документа | подпись на этом бланке, порядок в разделе, область на странице |
            | Атрибут документа карты | документ карты ↔ атрибут вида документа | значения, происхождение (распознано или введено), кем и когда заполнено |

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

            ### Почему словаря атрибутов нет

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

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

            Цена: код атрибута уникален внутри вида, а не на всю систему. Сравнимость карт разных
            видов от этого не страдает — её обеспечивает ядро карты, где шифр издания и шифр
            рукописи сводятся в одну колонку. Выгрузка в АБИС тоже задаётся по видам: поля формата
            для издания и рукописи разные, одного правила на все виды всё равно не выходило.

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

            Это и есть главный выигрыш варианта 5 против варианта 4: там значение ссылалось прямо
            на атрибут, и вешать на него «кем заполнено» было некуда — пришлось бы либо раздувать
            само значение, либо хранить сведения о заполнении на документе целиком, теряя привязку
            к полю.

            ### Отличия от варианта 4

            | Вариант 4 | Вариант 5 |
            |---|---|
            | Конфигурация документа | Вид документа с признаком `DocKind` |
            | Виды документов не связаны между собой | Вид документа входит в состав вида — дерево описаний |
            | Значение ссылается прямо на атрибут | Между ними появился атрибут документа карты; значение — его поле |
            | Карта содержит один корневой документ | Карта содержит документы карты, каждый своего вида |
            | Экземпляр фонда | Экземпляр фонда |
            | Дата карты — часть ядра | Дата вынесена как обязательный реквизит карты: дата передачи экземпляра в обработку |

            ### Что даёт отдельный атрибут документа карты

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

            Цена — лишний уровень косвенности: чтобы добраться от документа до значения, нужно пройти
            через две сущности вместо одной, и это же удваивает число записей при хранении.

            ### Что даёт дерево видов

            Вид документа, входящий в состав вида, позволяет описать вложение **на уровне описания**:
            «в ИК издания входит эксперт, а в эксперта — ничего». В варианте 4 то же выражено списком
            допустимых вложенных конфигураций с кратностью.

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

            ### Вопросы к варианту

            | № | Вопрос |
            |---|---|
            | 1 | Карта «является документом вида» и при этом содержит документы карты. Карта — корневой документ дерева или отдельная сущность **над** деревом? От этого зависит, есть ли у неё собственные атрибуты |
            | 2 | Кратность вложения: где хранится «сколько экземпляров вложенного вида допустимо», если дерево видов её не несёт |
            | 3 | Нужен ли `DocKind` при наличии дерева видов — что он различает сверх самого вида |
            | 4 | Экземпляр фонда вместо экземпляра фонда: это переименование или другая сущность (экземпляр против издания) |
            | 5 | Дата карты — дата передачи экземпляра в обработку. На бумажной форме в поле «Дата» пишут именно её или дату заполнения карты |
            | 6 | Что происходит с исправлениями пользователя при повторной обработке: машина меняет только незатронутые значения или переписывает все |
            | 7 | Может ли форма размещать поля вложенных видов — например, печатать эксперта прямо на листе карты, — или у каждого вида своя форма |
            """;

        public static string S3_Decision = """
            ## Решение

            Принят **вариант 5**. Состав информационной карты описывается данными, причём **что
            хранится** и **как это напечатано** — два раздельных описания; между описанием
            и значением стоит факт заполнения.

            - **Вид документа** задаёт состав: атрибуты вида и виды документов, входящие в него.
              Виды образуют дерево описаний, парное дереву заполненных документов.
            - **Атрибут вида документа** — узловая сущность и есть поле в составе вида: код,
              каноническое наименование, тип значения, обязательность, порядок и отображение
              в ядро карты. На него ссылаются и поле формы, и значение в пакете, и атрибут
              документа карты.
            - **Форма документа** описывает бланк: страницы, разделы, поля с подписями и местом
              на листе. У одного вида может быть несколько форм, версий у формы нет.
            - **Общего словаря атрибутов нет**: атрибут принадлежит виду, и код уникален внутри
              вида. **Тип значения** — самостоятельная именованная сущность, которую выбирают
              при описании атрибута; переиспользование обеспечивает она. Хранится как вид
              значения плюс JSON-ограничения.
            - **Документ карты** — дерево заполненных документов; **атрибут документа карты**
              несёт значения, происхождение, кем и когда заполнено.
            - **Информационная карта** стоит над деревом: вид, дата передачи экземпляра в обработку,
              документы карты, типизированное ядро и собственные связи. Собственных атрибутов у неё
              нет — всё, что на бланке, живёт в документах.
            - **Значения пакета ввода** ссылаются на атрибут вида обязательно, на поле формы —
              если атрибут напечатан. Отдельной сущности «идентификатор пакета» нет: шифр,
              инвентарный номер, место хранения и дата — обычные атрибуты вида.

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

            Варианты 1–3 отклонены. Фиксированные свойства и наследование классов не выдерживают
            расхождения видов ИК и редакций бланка: по образцам заказчика формы на издание и на
            рукопись почти не пересекаются по составу, а редакций бланка на издание уже три. Плоский
            список полей допускает произвольный состав и не даёт ни контроля, ни типов.

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

        public static string S35_Notes = """
            ## Рукописный текст вне клеток

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

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

            Кадр и область при этом не нужны примечанию как таковому: это свойства **значения
            в пакете ввода**, где у распознанного текста уже есть и кадр, и прямоугольник. Модель
            распознавания возвращает такие куски отдельным списком (`notes` в ответе), и разбор
            превращает каждый в примечание.

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

            Решение помечено как предложенное: обсуждается вместе с составом видов документов.
            """;

        public static string S4_Consequences = """
            ## Следствия

            ### Что придётся сделать до запуска

            Наполнить виды документов с их атрибутами, типы значений и формы: по образцам это
            сотни записей на вид ИК. Работа выполняется **до** начала оцифровки — без видов
            и форм Сервис неработоспособен. Это самостоятельный объём, который стоит спланировать
            отдельно от разработки.

            Атрибуты, совпадающие у издания и рукописи, заводятся дважды: словаря, из которого их
            можно было бы взять готовыми, больше нет. По образцам совпадений немного — шифр, дата,
            ФИО составителя, — но при заведении новых видов объём стоит держать в уме (OQ-A6-14).

            ### Что становится дешевле

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

            ### За чем следить

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

            **Путаница двух деревьев.** Дерево документов описывает данные, дерево разделов —
            бумагу. Понятие, положенное не в то дерево, обнаружится поздно и будет стоить дорого.

            **Соглашение о списках временное.** Правило «значение из единственного поля бланка
            добавляется в список» принято до проработки распознавания и подлежит пересмотру.

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

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

            ### Что решение не закрывает

            Отнесение аллигат — к карте или к экземпляру фонда — остаётся открытым (см. ниже).
            """;

        public static string S5_Questions = """
            ## Вопросы, закрытые решением

            | Вопрос | Ответ |
            |---|---|
            | Различать ли редакции бланка одного вида ИК | Различать нечего: версий нет, другая редакция — просто другая форма документа |
            | Нужен ли отдельный тип значения под отметку в клетке | Да, добавлен вид `bool`; выражать отметку выбором из набора в одно значение было бы менее наглядно |
            | Кто ведёт виды документов, типы значений и формы | Администратор Сервиса; на первом этапе фактически разработчик |
            | Как не дать правке ради одного вида сломать другой | Атрибут принадлежит виду; общим остаётся только тип значения, и он выбирается осознанно |
            | Чем обеспечена сравнимость карт разных видов без общего словаря | Ядром карты: шифр, инвентарный номер, заглавие, автор и год сводятся в типизированные колонки |
            | Где хранятся значения справочных наборов — тиснение, материалы покрытия, степень повреждения | В ограничениях типа значения, парами «код — значение». Отдельное хранилище понадобится только для больших наборов: первый кандидат — язык издания, 589 значений |
            | Нужен ли поиск по значению за пределами ядра | Нет. Понадобился поиск по полю — поле переносится в ядро |
            | Как версионируются виды документов | Не версионируются. Изменение вида на созданные документы не влияет: значения опознаются кодом атрибута |
            | Хранить ли в виде документа область поля на изображении | Нет. Вид описывает состав; область относится к форме документа |
            | Как выразить «экспертов сколько угодно» | Самим вложением: вид «эксперт» входит в состав вида ИК, и число вложенных документов этого вида не ограничено. Отдельная кратность не нужна |
            | Как называется единица фонда | Экземпляр фонда. Слово «документ» занято абстрактным документом модели, и оставлять «документ фонда» рядом с «документом карты» нельзя |

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

            | № | Вопрос |
            |---|---|
            | OQ-A6-1 | Аллигаты — часть информационной карты или часть экземпляра фонда; от этого зависит, в какое дерево они попадают |
            | OQ-A6-11 | Нужен ли код вида (`DocKind`) при наличии дерева видов — что он различает сверх самого вида |
            | OQ-A6-12 | Дата карты определена как дата передачи экземпляра в обработку. Проверить по бумаге: в поле «Дата» пишут её или дату заполнения карты |
            | OQ-A6-13 | Может ли форма размещать поля вложенных видов — печатать эксперта прямо на листе карты — или у каждого вида своя форма |
            | OQ-A6-14 | Нужно ли при заведении вида копировать состав атрибутов с существующего вида, чтобы не набивать сотни записей заново |
            """;
    }
}