⟨/⟩ 40_Arch/Tech/CSharp/ADR_SRV_005_ImageNormalizationOrthocover.cs

156 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;
using Ban.Sdaid.Icd;

namespace Ban.Sdaid.Icd.Arch.Tech.CSharp
{
    /// <summary>
    /// ADR-SRV-005: Нормализация изображений через внешний сервис orthocover. Перекошенное фото страницы/формы →
    /// выровненное «как со сканера» изображение для этапа «Проверка файлов». Интеграция за абстракцией
    /// `IImageNormalizer`; запуск — ручной; обработанные хранятся рядом с оригиналом.
    /// </summary>
    public class ADR_SRV_005_ImageNormalizationOrthocover : IAdrDocument, IFolder<ArchCSharpFolder>
    {
        public string Name => "ADR-SRV-005. Нормализация изображений (orthocover)";

        public string Description =>
            @"Кадры пакета — это фото форм ИК (под углом, с фоном, с изгибом). Для проверки и распознавания нужен
вид «как со сканера». Фиксируем: нормализацию выполняет внешний ML-сервис **orthocover** за абстракцией
`IImageNormalizer`; запуск — ручной (кнопка на предобработке); обработанное изображение хранится рядом с
оригиналом, а кадр получает ссылку на него. Тип страницы для сервиса выводится из роли кадра.";

        public string Version => "1.0";
        public string Status => "accepted";
        public string[] Comments => new[]
        {
            "2026-08-14. Принято при подключении orthocover (живой стенд) для этапа «Проверка файлов».",
        };

        public Type? Supersedes => null;

        public static string S1_Context = """
            ## Контекст и постановка задачи

            Оператор снимает форму ИК камерой рабочего места: кадр перекошен, с фоном и изгибом. Этап
            «Проверка файлов» показывает переключение «оригинал ↔ обработанное», а распознавание полей идёт по
            выровненному изображению. Значит нужен шаг **нормализации**: получить из фото чистое выровненное
            изображение «как со сканера».

            Писать свой пайплайн (сегментация/выравнивание/dewarp) — отдельный ML-проект. Есть готовый внешний
            сервис **orthocover**, который это делает и попутно детектит штрихкоды. Нужно зафиксировать, как мы
            его подключаем, когда запускаем и где храним результат, не завязывая прикладной код на конкретный
            сервис.
            """;

        public static string S2_DecisionDrivers = """
            ## Драйверы решения

            1. **Не строить свой ML** — переиспользовать готовый сервис нормализации.
            2. **Изоляция** — прикладной код не должен зависеть от конкретного сервиса (сменяемость).
            3. **Без фоновой инфраструктуры** — очереди/воркеров пока нет; запуск должен работать «здесь и сейчас».
            4. **Предсказуемое хранение** — обработанное лежит рядом с оригиналом, доступно по варианту.
            """;

        public static string S3_ConsideredOptions = """
            ## Рассмотренные варианты

            - **A (выбран).** Внешний **orthocover** по HTTP за абстракцией `IImageNormalizer`; ручной запуск;
              обработанное — в файловом хранилище, ссылка — на кадре.
            - **B.** Свой пайплайн нормализации (onnx/opencv) в процессе бэка. Тяжело, отдельный проект,
              дублирует готовый сервис.
            - **C.** Без нормализации, распознавать по оригиналу. Хуже качество, не соответствует этапу
              «Проверка файлов».
            """;

        public static string S4_DecisionOutcome = """
            ## Решение

            Выбран **Вариант A**.

            ### Абстракция и клиент

            - `IImageNormalizer` (в `Ban.Icd.Infrastructure`, домен-нейтральна) — вход: байты изображения + тип
              страницы; выход: обработанные изображения + метаданные (флаг качества/режим, штрихкоды).
            - `OrthocoverImageNormalizer` — реализация поверх типизированного `HttpClient`.

            ### Контракт сервиса (orthocover)

            - `POST {Orthocover:Url}/normalize` — **multipart/form-data**: `image` + `type` + `enhance`;
              заголовок `X-API-Key` (если сервис требует).
            - Ответ **multipart/mixed**: часть `meta` (JSON: `results[]` с `mode`/`flag`/`side`/`barcodes[]`) +
              по PNG-части на каждый результат.

            ### Тип страницы из роли кадра

            Роль штрихкода → `barcode` (только коды, без картинки); остальные роли → `text` (кроп листа + dewarp).

            ### Хранение результата

            Обработанный PNG — в файловом хранилище под ключом `<номер пакета>/<NN>_<роль>_norm.png`
            (схема ключей — ADR-002). Кадр получает ссылку на обработанное, флаг качества и время обработки.
            Содержимое отдаётся тем же endpoint выдачи кадра с параметром `variant=normalized`.

            ### Запуск — ручной

            Кнопка «повторить обработку» на предобработке: пер-кадровый вызов
            `POST /api/InputPacket/{id}/frames/{order}/normalize` (есть и пакетный `.../normalize`). Обработка
            синхронная (≈2–5 с/кадр). Сбой по кадру не валит остальные — фиксируется в сводке.

            ### Конфигурация

            `Orthocover:Url` — в `appsettings` (не секрет). `Orthocover:ApiKey` — секрет: локально в
            `appsettings.Local.json` (вне git) или переменной окружения; на стендах — через devops-генератор
            конфигов (ADR-SRV-004). Пустой `Url` = интеграция не сконфигурирована (клиент вернёт понятную ошибку).

            ### Что не делаем сейчас

            - Не запускаем нормализацию автоматически при сдаче пакета (нужна очередь — отложено).
            - Не сохраняем штрихкоды/атрибуты (извлечение идентификаторов — отдельный этап).
            - Не обрабатываем «разворот» (`spread`) — не встречается в ИК.
            """;

        public static string S5_PositiveConsequences = """
            ## Положительные следствия

            - Готовое качество нормализации без своего ML.
            - Прикладной код зависит от `IImageNormalizer`, а не от orthocover — сервис сменяем.
            - Обработанное предсказуемо лежит рядом с оригиналом и доступно по варианту.
            """;

        public static string S6_NegativeConsequences = """
            ## Отрицательные следствия и компромиссы

            - Синхронный ручной запуск блокирует запрос на ≈2–5 с/кадр — до введения очереди это осознанный
              компромисс.
            - Внешняя зависимость: недоступность сервиса → кадр не обработается (ошибка фиксируется, не падение).
            """;

        public static string S7_Validation = """
            ## Проверка

            - Живой прогон против стенда: сдача пакета → запуск обработки → обработанный PNG отдаётся по
              `variant=normalized`; у кадра выставлен признак «обработан».
            - При недоступном сервисе запрос не падает — в сводке по кадру фиксируется ошибка.
            """;

        public static string S8_OpenQuestions = """
            ## Открытые вопросы / отложено

            - **Автозапуск при сдаче** — вместе с фоновой очередью обработки.
            - **Штрихкоды/идентификаторы** — сохранение и авто-подстановка на этапе полей/экземпляров.
            - **Флаги качества** (`low_quality` → пересъёмка) — явный показ и действия оператору.
            - **Пер-пакетный прогресс** — при многокадровых пакетах.
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Api.Endpoints.InputPackets.NormalizePacket.DOC_NormalizePacket)} — контракт запуска обработки.
            - {nameof(Ban.Sdaid.Icd.Backend.Handlers.InputPackets.NormalizePacketHandler)} — сценарий обработки.
            - {nameof(Ban.Sdaid.Icd.Domain.Capture.ImageFrameSpec)} — поля обработанного изображения на кадре.
            - {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_002_PacketIdentityAndFileKeys)} — ключи файлов (в т.ч. обработанного).
            - {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_004_AppConfigAndCors)} — конфигурация/секреты по средам.
            """;
    }
}