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 | Нужно ли при заведении вида копировать состав атрибутов с существующего вида, чтобы не набивать сотни записей заново |
""";
}
}