using System;
using System.Collections.Generic;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd.Domain.Cards.Template;
namespace Ban.Sdaid.Icd.Domain.Capture
{
/// <summary>Состояние пакета ввода в конвейере обработки.</summary>
public enum PacketState
{
/// <summary>В очереди: ждёт обработки или повторной обработки.</summary>
Queued,
/// <summary>Проверка: обработан и взят пользователем в работу.</summary>
Checking,
/// <summary>Завершён: по пакету создана информационная карта.</summary>
Completed,
/// <summary>Ошибка: вид ИК не опознан и не указан пользователем.</summary>
Failed,
}
/// <summary>Пакет ввода — кадры страниц одной бумажной ИК, поставленные в очередь обработки.</summary>
[DomainEntity]
public class InputPacket
{
/// <summary>Идентификатор пакета.</summary>
public Guid Id { get; init; }
/// <summary>Номер пакета вида П-ГГГГММДД-NNN, присваивается при постановке в очередь.</summary>
public string Number { get; init; }
/// <summary>Состояние пакета.</summary>
[Relation]
public PacketState State { get; init; }
/// <summary>Момент постановки в очередь.</summary>
public DateTime QueuedAt { get; init; }
/// <summary>Кадры пакета.</summary>
[Relation(Kind = RelationKind.Composition, Min = 1)]
public IReadOnlyList<ImageFrame> Frames { get; init; }
/// <summary>
/// Форма документа, опознанная при обработке; null — вид ИК не определён.
/// Задаёт состав страниц, по которым распределяются кадры.
/// </summary>
[Relation]
public DocumentForm? Form { get; init; }
/// <summary>Уверенность определения вида ИК, 0..1; null — обработка не выполнялась.</summary>
public double? FormConfidence { get; init; }
/// <summary>
/// Виды ИК, отклонённые пользователем, и указанный им верный вид — контекст
/// повторной обработки.
/// </summary>
[Relation(Kind = RelationKind.Composition)]
public IReadOnlyList<FormRejection> Rejections { get; init; }
/// <summary>Значения полей пакета: распознанные и введённые пользователем.</summary>
[Relation(Kind = RelationKind.Composition)]
public IReadOnlyList<PacketFieldValue> Values { get; init; } = new PacketFieldValue[0];
/// <summary>Запрос к модели распознавания в том виде, в каком он отправлен.</summary>
public string? RecognitionRequestJson { get; init; }
/// <summary>Ответ модели распознавания целиком, как он получен.</summary>
public string? RecognitionResultJson { get; init; }
/// <summary>Момент последней обработки; null — пакет ещё не обрабатывался.</summary>
public DateTime? RecognizedAt { get; init; }
/// <summary>Оператор, сформировавший пакет (факт истории, не меняется).</summary>
public Guid CreatedByUserId { get; init; }
/// <summary>Оператор, взявший пакет в проверку; null — пакет свободен.</summary>
public Guid? AssignedToUserId { get; init; }
/// <summary>
/// Пакет помечен удалённым (мягкое удаление): скрыт из очереди, но запись и файлы сохранены — обратимо.
/// Жёсткое удаление стирает запись и файлы физически и этим флагом не пользуется.
/// </summary>
public bool Deleted { get; set; }
}
/// <summary>Отклонение опознанного вида ИК: что отвергнуто и что считать верным.</summary>
[DomainEntity(DomainEntityKind.ValueObject)]
public class FormRejection
{
/// <summary>Отклонённая форма документа.</summary>
[Relation]
public DocumentForm? Rejected { get; init; }
/// <summary>Форма, указанная пользователем как верная; null — пользователь её не знает.</summary>
[Relation]
public DocumentForm? Suggested { get; init; }
/// <summary>Момент отклонения.</summary>
public DateTime RejectedAt { get; init; }
/// <summary>Пользователь, отклонивший вид.</summary>
public Guid RejectedByUserId { get; init; }
}
/// <summary>Описание доменной сущности <see cref="InputPacket"/>.</summary>
public class InputPacketSpec : IDomainEntityDocument
{
public Type Entity => typeof(InputPacket);
/// <summary>Display-текст для типизированных ссылок <c>SdaidAnchors.RefTo<InputPacketSpec>()</c>.</summary>
public static string _refName = "пакет ввода";
public string Name => "Пакет ввода";
public string Description =>
@"Агрегат модуля ввода: кадры страниц одной бумажной ИК, опознанная форма, значения полей и состояние в конвейере обработки.";
public string Version => "0.3";
public string Status => "draft";
public string[] Comments => new[]
{
"2026-08-19. По варианту 5 решения о структуре данных: значения полей пакета вместо распознанных полей и идентификаторов, запрос и ответ модели сохраняются в пакете.",
};
public static string S1_Purpose = """
## Назначение
Единица передачи данных из ввода в обработку. Всё, что пользователь снял по одной
информационной карте, уходит в очередь одним пакетом.
Пакет собирается **без плана**: пользователь снимает страницы подряд, а какой это вид ИК
и по какой форме сделан бланк, определяет Сервис при обработке.
""";
public static string S2_States = $"""
## Состояния
| Состояние | Когда наступает |
|---|---|
| В очереди | пакет поставлен на обработку или возвращён на повторную |
| Проверка | обработан и взят пользователем в работу |
| Завершён | по пакету создана информационная карта |
| Ошибка | вид ИК не опознан и не указан пользователем |
Состояния заменили прежнюю модель, где статус выводился из последнего подтверждённого
этапа обработки: этапов было пять, теперь у проверки две вкладки — исходные данные
и проверка карты, — и выводить статус стало не из чего.
По этим же состояниям фильтруется очередь.
""";
public static string S3_FormRecognition = $"""
## Опознание формы
Форма документа проставляется обработкой вместе с уверенностью её определения. Пока форма
не опознана, разобрать значения невозможно: состав страниц и полей берётся из неё.
Если пользователь считает, что вид определён неверно, он отклоняет его и может указать
верный. Отклонение сохраняется в пакете и уходит в повторную обработку как контекст:
какая форма отвергнута и какую считать верной. Пакет, у которого форма не опознана
и не указана, переходит в состояние ошибки.
Решение и его цена — в {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_007_InputWithoutPlan)}.
""";
public static string S35_Recognition = $"""
## Обмен с моделью
Пакет сериализуется в JSON **по структуре формы** и отправляется модели вместе с кадрами;
ответ сохраняется в пакете целиком и разбирается в значения полей. Интерфейс рендерит
значения обратно на структуру формы — страницы, разделы, подписи полей.
Ключом в JSON служит **код атрибута**, а не подпись поля: подпись меняется от бланка
к бланку, и по ней результат обратно не разложить. Форма задаёт структуру запроса
и порядок, состав вида — имена.
Запрос и ответ хранятся полями пакета, а не отдельными сущностями: у них нет
собственной жизни, они читаются только вместе с пакетом. Повторная обработка переписывает
результат прежней — история прогонов пока не нужна.
Устройство самого запроса — {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_009_RecognitionModelIntegration)}.
""";
public static string S4_Invariants = """
## Инварианты
- Пакет не создаётся пустым: как минимум один кадр.
- Число кадров не превышает предела, заданного настройкой; по умолчанию пять.
- Номер присваивается один раз и не меняется.
- Автор ввода и исполнитель проверки — разные атрибуты.
- Правка состава или разметки кадров возвращает пакет в очередь: результаты
распознавания перестают соответствовать исходным данным.
- Форма фиксируется у пакета, а не у карты: это обстоятельство ввода, а не свойство
готового документа.
- Значения полей принадлежат пакету, а не карте: уверенность, распознанный набор,
кадр и область — данные о распознавании, а не о результате.
- Удаление пакета — двух видов: **мягкое** (флаг `Deleted`, пакет скрыт из очереди,
запись и файлы сохранены — обратимо) и **жёсткое** (окончательное: запись удаляется
каскадом вместе с кадрами и значениями, файлы кадров стираются из хранилища —
необратимо). По умолчанию пользователю доступно мягкое; жёсткое — только по праву
(claim; модель доступа — ADR-011).
""";
public static string S99_Related = $"""
## Связанные артефакты
- {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_007_InputWithoutPlan)} — решение о вводе без плана
- {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_004_CreatorVsAssignee)} — решение о разделении «кто ввёл» и «кто взял в работу»
- {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_011_AbacAuthorization)} — доступ к жёсткому удалению определяется правом (claim)
""";
}
/// <summary>Описание доменной сущности <see cref="FormRejection"/>.</summary>
public class FormRejectionSpec : IDomainEntityDocument
{
public Type Entity => typeof(FormRejection);
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_Invariants = """
## Инварианты
- Отклонения накапливаются: каждая попытка сохраняется, прежние не затираются.
По ним видно, сколько раз пакет возвращался и что уже отвергнуто.
- Отклонённая и предложенная формы различаются: предложить отвергнутую нельзя.
""";
}
}