using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd.Backend.Recognition;
namespace Ban.Sdaid.Icd.Arch.Adr
{
/// <summary>
/// Решение: как Сервис обращается к модели распознавания за значениями полей —
/// один синхронный вызов на страницу по OpenAI-совместимому протоколу, схема ответа
/// принуждается параметром запроса.
/// </summary>
public class ADR_009_RecognitionModelIntegration : IAdrDocument, IHasStructuralLinks
{
public static string _refName = "ADR-009 «Обращение к модели распознавания»";
public string Name => "ADR-009. Обращение к модели распознавания";
public string Description =>
@"Как формируется запрос к модели, как он передаётся и как ответ превращается в распознанные поля. Первая редакция намеренно минимальна: цель — получать отклики и на них докручивать качество.";
public string Version => "0.1";
public string Status => "proposed";
public string[] Comments => new[]
{
"2026-08-18. Заведён при проработке распознавания пакета ввода.",
};
public Type? Supersedes => null;
public SdaidLink[] Links => new[]
{
Rel.Uses<FieldExtractionPrompt>(),
};
public static string S1_Context = $"""
## Контекст
По {nameof(ADR_007_InputWithoutPlan)} распознавание стало обязательным звеном: пользователь
больше не сообщает Сервису ничего, кроме кадров, и всё остальное Сервис добывает сам.
Значит нужен способ обратиться к модели за значениями полей и вернуть результат
в проверяемом виде — со значением, уверенностью и указанием, откуда прочитано
({nameof(ADR_005_FieldValueSourceBinding)}).
Что уже есть: структура запроса, инструкция модели, каталог полей страницы 1 формы
«Издания из фондов БАН» и схема ответа — {nameof(FieldExtractionPrompt)}.
**Чего мы пока не знаем.** Ни одна модель на наших картах не проверена. Неизвестно,
читается ли рукописный текст приемлемо, различает ли модель заполненную клетку матрицы
от пустой, попадает ли она в область на изображении. Пока это неизвестно, любое сложное
решение об интеграции будет обоснованием придуманных, а не наблюдаемых трудностей.
Отсюда предмет решения: **минимальный работающий путь до отклика модели**.
### За рамками
Опознание вида ИК, выбор формы и распределение кадров по страницам решаются отдельно.
Здесь считаем, что к моменту вызова форма известна, кадр отнесён к странице формы,
и каталог полей этой страницы собран.
""";
public static string S2_Decision = $"""
## Решение
**Один синхронный вызов на страницу.** Сервис отправляет модели изображение страницы,
инструкцию, каталог полей этой страницы и схему ответа, дожидается ответа и разбирает его.
Страницы пакета обрабатываются последовательно, независимо друг от друга: каждая — свой
вызов со своим каталогом.
**Протокол — OpenAI-совместимый** `POST /v1/chat/completions`: изображение передаётся
как `image_url` с data:-URI, схема ответа — параметром запроса (`response_format`
с JSON Schema), а не текстом в промте. Такой контракт поддерживают и облачные провайдеры,
и локальные серверы (llama.cpp, vLLM), поэтому смена модели не меняет код.
**Модель — локальная, в контуре библиотеки.** Работаем с развёрнутой на своём
оборудовании Qwen; изображения карт наружу не передаются. Адрес, имя модели, температура
и таймаут задаются настройкой, а не запросом: пользователь модель не выбирает,
в домене её нет. Тот же контракт принимают облачные провайдеры — это оставляет путь
к сравнению качества, но прод-вариантом облако не рассматривается.
**Область на изображении берём такой, какую вернёт модель.** Своей локализации значения
по изображению Сервис не делает. Если окажется, что модель в область не попадает,
это будет отдельное решение — с фактами на руках.
**Ответ сохраняется целиком.** Вместе с распознанными полями пакет хранит сырой ответ
модели, имя модели и версию промта. Это цена одного текстового поля и единственный способ
потом понять, почему разбор вышел таким.
**Сбой не разбирается автоматически.** Таймаут, недоступность модели, ответ не по схеме
после одного повтора — пакет переводится в состояние «ошибка» и остаётся в очереди.
Повторную обработку запускает пользователь командой, как и при любой другой правке пакета.
### Чего в первой редакции нет
Сознательно не делаем — до появления фактов, требующих обратного:
| Не делаем | Почему отложено |
|---|---|
| Кэш разбора по содержимому | повторов пока единицы; кэш прячет как раз то, что мы хотим наблюдать |
| Разделение OCR и извлечения | сначала надо увидеть, справляется ли одна модель |
| Переспрос более сильной моделью при низкой уверенности | нечем задать порог: распределение уверенности неизвестно |
| Своя дообученная модель | дообучение требует эталонного набора, которого нет |
| Пакетная отправка нескольких страниц одним вызовом | экономия неощутима при последовательной обработке пакетов |
""";
public static string S3_Rationale = """
## Обоснование
Решение выбрано по критерию «сколько времени до первого наблюдаемого отклика»,
а не «насколько хорошо оно масштабируется». Пока не увидим, как модель читает наши карты,
все прочие критерии опираются на догадки.
Отсюда синхронный вызов и последовательные страницы: это самый прямой путь, и он не мешает
позже сделать иначе — обработка пакета уже асинхронна относительно пользователя, он
получает результат из очереди, а не ждёт у экрана.
Принуждение схемой — единственное усложнение, на которое идём сразу. Оно ничего не стоит
(параметр запроса) и снимает целый класс работы: разбор свободного текста, ретраи
на «модель ответила прозой», рассинхрон каталога и ответа.
Сырой ответ храним по той же причине, по которой вообще затеян минимальный вариант:
качество будем докручивать по наблюдениям, а наблюдать не за чем, если ответ выброшен
сразу после разбора.
""";
public static string S4_Consequences = """
## Следствия
**Время обработки пакета складывается из страниц.** Две страницы — два вызова подряд,
десятки секунд. Для очереди это приемлемо, для интерактивной подсказки при вводе — нет;
если такая подсказка понадобится, потребуется другое решение.
**Пропускная способность упирается в своё оборудование.** Раз модель локальная, скорость
обработки задаётся видеопамятью и числом одновременных запросов, а не тарифом провайдера.
Соседство с другими нагрузками на той же карте — вопрос размещения, а не этого решения.
**Появляется, что версионировать.** Промт и модель становятся частью результата: одна
и та же карта, разобранная разными версиями, даёт разные значения. Пока это только
хранится; правила «пересчитать всё при смене промта» нет и не нужно.
**Правки пользователя и повторная обработка ещё не примирены.** Решение фиксирует, что
повтор запускается командой, но не то, как исправленное значение переживает повтор.
Это отдельный вопрос, он поднят в эпике обработки пакета.
""";
public static string S5_Alternatives = """
## Рассмотренные альтернативы
**Свой сервис с дообученной моделью.** В соседнем проекте библиотеки распознавание сделано
так: дообученная модель под конкретную задачу, поднятая своим сервисом с собственным API.
Отвергнуто как первый шаг: дообучение требует размеченного эталонного набора, которого
у нас нет и который можно собрать только после того, как заработает базовый разбор.
Как второй шаг остаётся открытым — и тем более достижимым, что контракт вызова
от модели не зависит.
**Разделить OCR и извлечение полей.** Локальная модель читает текст со словными
координатами, вторая модель извлекает из текста значения; область поля тогда вычисляется
точно, сопоставлением со словами, а не доверием к модели. Отвергнуто для первой редакции:
два звена вместо одного, и оба надо отлаживать одновременно. Это первый кандидат
на усложнение, если окажется, что области смещены.
**Отправлять все страницы карты одним вызовом.** Модель видит карту целиком и может
связать данные разных страниц. Отвергнуто: усложняет каталог и схему ответа
(поля надо приписывать страницам), а выигрыш неочевиден — страницы наших форм
самостоятельны.
""";
}
}