using System;
using System.Collections.Generic;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd.Domain.Processing;
namespace Ban.Sdaid.Icd.Domain.Cards.Template
{
/// <summary>Форма документа — описание бумажного бланка: страницы, разделы, поля.</summary>
[DomainEntity(Origin = DomainDataOrigin.Reference)]
public class DocumentForm
{
/// <summary>Идентификатор формы.</summary>
public Guid Id { get; init; }
/// <summary>Вид документа, который представлен этой формой.</summary>
[Relation]
public DocumentKind DocumentKind { get; init; } = null!;
/// <summary>Наименование бланка, как оно напечатано в заголовке формы.</summary>
public string Name { get; init; } = string.Empty;
/// <summary>Доступна ли форма для выбора при вводе.</summary>
public bool IsActive { get; init; }
/// <summary>Страницы бланка.</summary>
[Relation(Kind = RelationKind.Composition, Min = 1)]
public IReadOnlyList<FormPage> Pages { get; init; } = new FormPage[0];
}
/// <summary>Страница бумажного бланка.</summary>
[DomainEntity(DomainEntityKind.ValueObject, Origin = DomainDataOrigin.Reference)]
public class FormPage
{
/// <summary>Номер страницы, начиная с первой.</summary>
public int Number { get; init; }
/// <summary>Заголовок страницы, как он напечатан на бланке.</summary>
public string Title { get; init; } = string.Empty;
/// <summary>Роль, заполняющая страницу, если она указана на бланке.</summary>
public string? FilledBy { get; init; }
/// <summary>Разделы верхнего уровня на странице.</summary>
[Relation(Kind = RelationKind.Composition)]
public IReadOnlyList<FormSection> Sections { get; init; } = new FormSection[0];
/// <summary>Профиль автоопознания страницы: эталон рамки, линий и зон-различий; null, если не размечен.</summary>
[Relation(Kind = RelationKind.Composition)]
public PageDetectionProfile? Detection { get; init; }
}
/// <summary>Раздел страницы: озаглавленная группа полей. Разделы вкладываются друг в друга.</summary>
[DomainEntity(DomainEntityKind.ValueObject, Origin = DomainDataOrigin.Reference)]
public class FormSection
{
/// <summary>Заголовок раздела, как он напечатан на бланке.</summary>
public string Title { get; init; } = string.Empty;
/// <summary>Порядок раздела среди соседних.</summary>
public int Order { get; init; }
/// <summary>Вложенные разделы.</summary>
[Relation(Kind = RelationKind.Composition)]
public IReadOnlyList<FormSection> Sections { get; init; } = new FormSection[0];
/// <summary>Поля, размещённые непосредственно в этом разделе.</summary>
[Relation(Kind = RelationKind.Composition)]
public IReadOnlyList<FormField> Fields { get; init; } = new FormField[0];
}
/// <summary>Поле формы: место на бланке, где заполняется атрибут вида документа.</summary>
[DomainEntity(Origin = DomainDataOrigin.Reference)]
public class FormField
{
/// <summary>Идентификатор поля.</summary>
public Guid Id { get; init; }
/// <summary>Атрибут вида документа, который заполняется в этой позиции.</summary>
[Relation]
public DocumentKindAttribute KindAttribute { get; init; } = null!;
/// <summary>Подпись поля, как она напечатана на этом бланке.</summary>
public string Caption { get; init; } = string.Empty;
/// <summary>Порядок поля в разделе.</summary>
public int Order { get; init; }
/// <summary>Область поля на странице, если она размечена; служит подсказкой сегментации.</summary>
[Relation(Kind = RelationKind.Composition)]
public SourceArea? Area { get; init; }
}
/// <summary>Описание доменной сущности <see cref="DocumentForm"/>.</summary>
public class DocumentFormSpec : IDomainEntityDocument
{
public Type Entity => typeof(DocumentForm);
public string Name => "Форма документа";
public string Description =>
@"Описание бумажного бланка: страницы, разделы и размещённые в них поля с подписями и местом на листе.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
public static string S1_Purpose = """
## Назначение
Форма — бумажный бланк, описанный данными. Она отвечает на вопрос, как выглядит лист:
какие поля где напечатаны и как подписаны. Что при этом хранится и обязательно ли поле —
знание вида документа.
Одному виду документа соответствует несколько форм — по числу редакций бланка в обращении.
По образцам заказчика только у ИК издания их три: они отличаются заголовками и подписями
полей, а состав почти совпадает.
Форма работает в обе стороны: описывает и печать документа, и разметку бумажного бланка
при распознавании — где какое поле искать.
""";
public static string S2_NoVersions = """
## Почему у формы нет версий
Значение опознаётся кодом атрибута и не зависит от того, где поле напечатано. Поэтому
изменение бланка не задевает созданные документы, и версионировать форму не нужно:
другой бланк — просто другая форма.
Это следствие раздельного описания состава и формы. Пока состав задавался бланком,
версии были обязательны — иначе правка задним числом меняла бы смысл готовых документов.
""";
public static string S3_Composition = """
## Что попадает в форму
В форму входят не все атрибуты состава: служебные данные Сервиса на бумаге отсутствуют.
Обратное невозможно, причём не по договорённости, а по устройству модели: поле ссылается
на атрибут вида документа, поэтому атрибута, которого в составе вида нет, в форме
быть не может.
Явная связь формы с видом при этом не лишняя, хотя вид выводится и из полей. Она
отвечает на вопрос «какие формы есть у этого вида» без обхода всех полей, позволяет
завести форму до наполнения её полями и делает проверяемым инвариант: все поля формы
ссылаются на атрибуты того же вида, что указан у формы.
Порядок страниц и разделов воспроизводит бумажную форму: пользователь проверяет документ,
сверяясь с изображением, и расхождение порядка сбивает работу.
Формы без страниц не бывает: бланк — это лист, и описание бланка без единого листа
не описывает ничего. Завести форму до наполнения её полями можно, до наполнения
страницами — нет.
""";
public static string S99_Related = $"""
## Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_006_CardDataStructure)} — решение о структуре данных информационной карты
""";
}
/// <summary>Описание доменной сущности <see cref="FormPage"/>.</summary>
public class FormPageSpec : IDomainEntityDocument
{
public Type Entity => typeof(FormPage);
public string Name => "Страница формы";
public string Description =>
@"Страница бумажного бланка: номер, заголовок и роль, которая её заполняет.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
public static string S1_Purpose = """
## Назначение
Страница — единица, с которой работает пользователь при вводе: каждой странице
соответствует свой кадр изображения. Заголовок и указание роли берутся с бланка:
у формы на рукопись страницы прямо подписаны «Хранитель (л. 1)», «Материальная основа.
Хранитель–Консерватор (л. 3)», «Задание по консервации. Консерватор (л. 5)».
Число страниц зависит от бланка: у формы на издание их две, у формы на рукопись — пять.
""";
}
/// <summary>Описание доменной сущности <see cref="FormSection"/>.</summary>
public class FormSectionSpec : IDomainEntityDocument
{
public Type Entity => typeof(FormSection);
public string Name => "Раздел формы";
public string Description =>
@"Озаглавленная группа полей на странице бланка. Разделы вкладываются друг в друга и воспроизводят разметку бумаги.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new string[0];
public static string S1_Purpose = """
## Назначение
Раздел нужен для показа: он воспроизводит разметку бланка, чтобы пользователь сверял
документ с изображением, не отыскивая поля глазами.
В опознании значения разделы не участвуют — значение опознаётся кодом атрибута.
Поэтому одинаковые по смыслу позиции в разных разделах — «Повреждён» у переплёта
и у блока — это разные атрибуты, а не один атрибут в двух местах.
""";
}
/// <summary>Описание доменной сущности <see cref="FormField"/>.</summary>
public class FormFieldSpec : IDomainEntityDocument
{
public static string _refName = "поле формы";
public Type Entity => typeof(FormField);
public string Name => "Поле формы";
public string Description =>
@"Позиция на бланке, где заполняется атрибут вида документа: подпись, порядок в разделе и область на странице.";
public string Version => "0.1";
public string Status => "draft";
public string[] Comments => new[]
{
"2026-08-19. Заведено по варианту 5 решения о структуре данных; заместило размещение атрибута.",
};
public static string S1_Purpose = """
## Назначение
Хранит то, что зависит от бланка: подпись и место. Подпись живёт здесь, а не на атрибуте,
потому что различается между редакциями: `Кол-во стр.` и `Количество страниц` —
два поля одного атрибута в разных формах.
Поле ссылается на **атрибут вида документа** — то есть на поле в составе конкретного
вида, а не на понятие вообще: сослаться на атрибут, которого в составе этого вида нет,
поле не может.
""";
public static string S2_Area = """
## Область на странице
Если область поля размечена, она служит подсказкой сегментации: распознавателю не нужно
искать поле по всей странице, достаточно прочитать заданный прямоугольник. Разметка
необязательна — без неё поле ищется общим алгоритмом.
Область задаётся в процентах от размера страницы, как и у распознанного значения,
поэтому не зависит от разрешения снимка.
""";
}
}