using System;
using Ban.Sdaid.Notation.Documents;
namespace Ban.Sdaid.Icd.Arch.Adr
{
/// <summary>
/// Решение: как Сервис опознаёт форму и страницу кадра — детерминированным геометрическим
/// детектором по пикселям (выравнивание → рамка-якорь → счёт линий по зонам-различиям → скоринг
/// с отрывом), а не моделью. Заполняет шаг «опознание вида» фоновой очереди (ADR-014).
/// </summary>
public class ADR_015_FormPageDetection : IAdrDocument
{
public static string _refName = "ADR-015 «Опознание формы и страницы кадра»";
public string Name => "ADR-015. Опознание формы и страницы кадра";
public string Description =>
@"Как Сервис относит кадр к форме и её странице до извлечения полей: геометрический детектор по пикселям (deskew, рамка-якорь, счёт линий сетки в зонах-различиях, скоринг с отрывом-уверенностью), эталонные данные — справочник при форме, низкая уверенность уводит пакет в «ошибка». Механизм отработан на стенде распознавания.";
public string Version => "0.1";
public string Status => "proposed";
public string[] Comments => new[]
{
"2026-08-27. Заведён по итогам обкатки детектора на стенде (src-tools/card-extraction-stand): deskew + рамка-якорь (frameYbyLines) + счёт линий по зонам + скоринг; закрывает OQ-RS-1 стенда и слот «опознание вида», который ADR-014 держал открытым.",
};
public Type? Supersedes => null;
public static string S1_Context = $"""
## Контекст
{nameof(Ban.Sdaid.Icd.Requirements.FR_003_AttributeExtraction)} требует: прежде чем читать
поля, Сервис **определяет форму бланка и распределяет кадры по её страницам** — пока форма
не опознана, состав страниц и полей неизвестен, а кадр, не отнесённый ни к одной странице,
считается лишним.
{nameof(ADR_014_PacketProcessingQueue)} поставил **опознание вида** шагом фоновой очереди
(воркер прогоняет нормализацию и опознание вида; неуспех уводит пакет в «ошибка»), но
**механизм не задал** и сослался на {nameof(ADR_009_RecognitionModelIntegration)}. А тот
в разделе «За рамками» опознание формы/страницы явно **исключает** — считает, что
к моменту вызова модели форма уже известна, кадр отнесён к странице, каталог полей собран.
Отсюда пробел, который закрывает это решение: **чем именно кадр относится к форме и странице**.
Что уже есть. Механизм отработан на стенде — {nameof(Ban.Sdaid.Icd.Epics.RecognitionStand.EPIC_RecognitionStand)}:
выравнивание кадра, детекция рамки-якоря таблицы, счёт линий сетки, разведение вариантов
бланка по зонам-различиям, скоринг с отрывом. Геометрия полей для последующего чтения
уже смоделирована — область поля {nameof(Ban.Sdaid.Icd.Domain.Cards.Template.FormField)}
в процентах страницы.
### За рамками
Чтение значений полей известной формы ({nameof(ADR_009_RecognitionModelIntegration)}),
нормализация кадра ({nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_005_ImageNormalizationOrthocover)})
и семантический вид карты (`card_kind` модель возвращает **в ходе** извлечения,
{nameof(Ban.Sdaid.Icd.Requirements.FR_003_AttributeExtraction)}) — не здесь. Это решение
про геометрическое опознание раскладки, дающее конкретную форму и страницу.
""";
public static string S2_Decision = $"""
## Решение
**Опознание геометрическое и детерминированное, не модельное.** Форму и страницу кадра
Сервис определяет алгоритмом по пикселям, а не обращением к VLM. Причина в порядке:
опознание нужно **до** извлечения — выбрать форму, чтобы собрать каталог полей страницы
и включить офлайн-чтение отметок; на этом шаге модель избыточна, недетерминирована
и дороже, а признак — чисто геометрический (структура линий бланка).
**Два признака, грубый и тонкий.** Кадр выравнивается по вертикали (deskew), у таблицы
детектируется рамка-якорь (границы по краевым линиям сетки). Дальше:
- **страница** (сторона листа, {nameof(Ban.Sdaid.Icd.Domain.Cards.Template.FormPage)}) —
по грубому признаку: число горизонтальных линий по всей рамке разводит стороны;
- **форма** (редакция бланка, {nameof(Ban.Sdaid.Icd.Domain.Cards.Template.DocumentForm)}) —
по тонкому: в заранее размеченных **зонах-различиях** сравнивается число линий.
**Скоринг с отрывом.** Кандидату считается взвешенная сумма модулей расхождений
(грубый признак весомее, зоны уточняют); выбирается минимум. **Отрыв от второго места —
уверенность**: мал — «вид не опознан».
**Стыковка с очередью.** Это и есть шаг «опознание вида» воркера {nameof(ADR_014_PacketProcessingQueue)}:
уверенное опознание даёт пару «форма + страница» каждому кадру, пакет идёт дальше по конвейеру;
низкая уверенность или отсутствие подходящей формы = «вид не опознан» → пакет в состояние
**«ошибка»**, оператор разбирается командой (ручной повтор, отклонение вида, выбор формы вручную).
**Ручной выбор оператора авторитетнее детектора** — детектор снимает рутину, а не решает окончательно.
**Эталонные данные — справочные, при форме.** Детектору нужен эталон на каждую форму/страницу:
рамка-якорь, линии сетки, зоны-различия и ожидаемые счётчики линий. Это отдельная
**reference-сущность профиля детектирования** при форме и странице (вводится следующим
артефактом). Разметка эталона — работа аналитика на стенде.
**Координаты — доли выровненного кадра, сводятся к процентам страницы** — той же шкале,
что область поля {nameof(Ban.Sdaid.Icd.Domain.Cards.Template.FormField)} и источник значения
({nameof(ADR_005_FieldValueSourceBinding)}); шкала не зависит от разрешения снимка.
**Обработка образа — локальная, управляемой библиотекой.** Пиксельные профили и счёт линий
выполняются в процессе Сервиса CPU-библиотекой обработки изображений (конкретная —
в пункте реализации); кадры наружу не передаются, как и при обращении к модели.
### Чего в первой редакции нет
Сознательно откладываем до фактов:
| Не делаем | Почему |
|---|---|
| Модельное (VLM) опознание формы | геометрия дешевле, детерминирована и её достаточно на размеченных бланках |
| Обучаемый классификатор кадров | требует эталонного набора, которого пока нет |
| Автопостроение эталона формы из скана | разметку ведёт аналитик; автоподсказку добавим по мере надобности |
| Порог уверенности как настройка калибровки | распределение уверенности на проде неизвестно; пока порог грубый, спорное уводится в «ошибка» |
""";
public static string S3_Rationale = """
## Обоснование
Критерий выбора — «сколько до первого воспроизводимого опознания на наших бланках», а не
«насколько универсально». Структура линий бланка — устойчивый признак: он не зависит от
рукописного наполнения и берётся арифметикой по пикселям, поэтому опознание бесплатно
и повторяемо, а его ошибки объяснимы (видно, какая зона не совпала), в отличие от вердикта
модели.
Это прямое продолжение принципа стенда «математика вперёд, модель — только на текст»:
детерминированное опознание раскладки открывает офлайн-чтение отметок и адресную
сегментацию, а модель остаётся там, где без неё нельзя, — на рукописном тексте.
Геометрия и модель не конкурируют, а дополняют: детектор даёт конкретную форму/страницу
(редакцию и сторону), а семантический `card_kind` модель возвращает в ходе извлечения —
это независимая перекрёстная проверка, а не источник геометрии.
""";
public static string S4_Consequences = $"""
## Следствия
**Каждой форме нужен размеченный эталон.** Без профиля детектирования форма не опознаётся —
появляется работа аналитика по разметке (её и ведёт стенд, {nameof(Ban.Sdaid.Icd.Epics.RecognitionStand.EPIC_RecognitionStand)}),
и справочник форм versionируется вместе с профилями.
**В бэкенде появляется обработка изображений.** Раньше Сервис лишь звал внешнюю нормализацию
({nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_005_ImageNormalizationOrthocover)}); теперь он сам
считает пиксельные профили — новая библиотека-зависимость под своим пунктом реализации.
**Порог уверенности живёт без калибровки.** Пока набора для настройки нет, граница «опознано /
не опознано» грубая; спорные кадры уходят в «ошибка» {nameof(ADR_014_PacketProcessingQueue)},
где решает оператор. Это осознанно строгая сторона: лучше спросить человека, чем сесть не на ту форму.
**Потолок — разрешение снимка.** Счёт линий шумит на мелком фото; на этом упирается и точность
опознания, и последующее офлайн-чтение (открытый вопрос стенда про разрешение).
""";
public static string S5_Alternatives = """
## Рассмотренные альтернативы
**Опознавать форму моделью (VLM).** Отправлять кадр модели и просить назвать вид/форму.
Отвергнуто как первый шаг: недетерминировано, дороже и позже (сетевой вызов на каждый кадр
до конвейера), а признак раскладки чисто геометрический — модель тут не нужна. Семантический
`card_kind` модель и так вернёт при извлечении; это оставляет перекрёстную сверку.
**Обучаемый классификатор кадров.** Свёрточная сеть на «front_v1 / back_v2 / …». Отвергнуто:
требует размеченного набора кадров, которого нет и который собирать только после того, как
заработает базовое опознание; эвристика по линиям даёт результат сразу и объяснимо.
**Только ручной выбор формы оператором.** Оператор указывает форму каждому кадру. Отвергнуто
как единственный путь — это рутина, ради устранения которой всё и затеяно; но сохранено как
**фолбэк и высшая инстанция**: детектор предлагает, при низкой уверенности спрашивает,
исправление оператора не переспрашивается.
""";
}
}