⟨/⟩ 50_Domain/Cards/Template/PageDetectionProfile.cs

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

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 PageDetectionProfile
    {
        /// <summary>Идентификатор профиля.</summary>
        public Guid Id { get; init; }

        /// <summary>Рамка-якорь таблицы бланка (границы, внутри которых считаются линии), % страницы.</summary>
        [Relation(Kind = RelationKind.Composition)]
        public SourceArea AnchorFrame { get; init; } = null!;

        /// <summary>Ожидаемое число горизонтальных линий сетки по всей рамке — грубый признак (сторона листа).</summary>
        public int GlobalHLines { get; init; }

        /// <summary>Ожидаемое число вертикальных линий сетки по всей рамке.</summary>
        public int GlobalVLines { get; init; }

        /// <summary>Зоны-различия — места, где редакции бланка расходятся; тонкий признак (какая форма).</summary>
        [Relation(Kind = RelationKind.Composition)]
        public IReadOnlyList<DiscriminatorZone> Zones { get; init; } = new DiscriminatorZone[0];
    }

    /// <summary>Зона-различие бланка: место, где счётчик линий разводит редакции формы.</summary>
    [DomainEntity(DomainEntityKind.ValueObject, Origin = DomainDataOrigin.Reference)]
    public class DiscriminatorZone
    {
        /// <summary>Имя зоны, как её назвал аналитик при разметке (напр. «Обрез»).</summary>
        public string Name { get; init; } = string.Empty;

        /// <summary>Прямоугольник зоны, % страницы (та же шкала, что у области поля).</summary>
        [Relation(Kind = RelationKind.Composition)]
        public SourceArea Area { get; init; } = null!;

        /// <summary>Ожидаемое число горизонтальных линий в зоне у этой формы.</summary>
        public int ExpectedHLines { get; init; }

        /// <summary>Ожидаемое число вертикальных линий в зоне.</summary>
        public int ExpectedVLines { get; init; }
    }

    /// <summary>Описание доменной сущности <see cref="PageDetectionProfile"/>.</summary>
    public class PageDetectionProfileSpec : IDomainEntityDocument
    {
        public static string _refName = "профиль автоопознания страницы";

        public Type Entity => typeof(PageDetectionProfile);

        public string Name => "Профиль автоопознания страницы";

        public string Description =>
            @"Эталон, по которому Сервис относит кадр к форме и её странице: рамка-якорь таблицы, ожидаемые числа линий сетки и зоны-различия с их счётчиками линий.";

        public string Version => "0.1";
        public string Status => "draft";

        public string[] Comments => new[]
        {
            "2026-08-28. Заведено под решение об опознании формы и страницы кадра; данные снимаются на стенде распознавания.",
        };

        public static string S1_Purpose = """
            ## Назначение

            Прежде чем читать поля, Сервис должен опознать, какой это бланк и какая его сторона —
            без этого неизвестны ни состав полей, ни их места. Профиль — эталон для этого опознания:
            он несёт признаки раскладки, а не сами поля.

            Признаков два. **Грубый** — число линий сетки по всей рамке: у оборота их заметно больше,
            чем у лицевой стороны, поэтому счётчик разводит стороны. **Тонкий** — число линий в зонах,
            где редакции одного бланка расходятся составом колонок; он разводит формы между собой.

            Профиль привязан к странице формы: у каждой формы каждая сторона — свой профиль.
            Опознание сравнивает признаки кадра с профилями и выбирает ближайший.
            """;

        public static string S2_NotFieldGeometry = """
            ## Профиль — не геометрия полей

            Профиль отвечает только за опознание раскладки. Где на странице какое поле читать — знание
            поля формы (его размеченная область), а не профиля. Так классификация кадра и чтение значений
            разведены: профиль выбирает форму, дальше поля формы задают, что и откуда извлекать.

            Разметка профиля — работа аналитика: он размечает рамку, зоны и снимает счётчики на эталонном
            скане. Пока форма не снабжена профилем, кадр этой формы автоматически не опознаётся.
            """;

        public static string S3_Coordinates = """
            ## Координаты

            Рамка-якорь и зоны задаются в **процентах страницы** — той же шкале, что область поля
            и область-источник значения, поэтому не зависят от разрешения снимка. Кадр перед опознанием
            выравнивается по вертикали; счётчики линий берутся внутри рамки-якоря.
            """;

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

            - {nameof(Ban.Sdaid.Icd.Arch.Adr.ADR_015_FormPageDetection)} — решение об опознании формы и страницы кадра, для которого профиль служит эталоном.
            - {nameof(Ban.Sdaid.Icd.Epics.RecognitionStand.EPIC_RecognitionStand)} — стенд, на котором профиль размечается и обкатывается.
            - {nameof(Ban.Sdaid.Icd.Domain.Cards.Template.FormField)} — поле формы: геометрия чтения значений (в отличие от профиля, отвечающего за опознание).
            """;
    }

    /// <summary>Описание доменной сущности <see cref="DiscriminatorZone"/>.</summary>
    public class DiscriminatorZoneSpec : IDomainEntityDocument
    {
        public Type Entity => typeof(DiscriminatorZone);

        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 = """
            ## Назначение

            Одной стороны мало, чтобы отличить редакции бланка: у них совпадает общая раскладка,
            а расходится состав колонок в отдельных местах. Зона отмечает такое место, а её счётчики
            линий — то, чем редакции в нём различаются (например, разное число колонок в графе «Обрез»).

            Зоны размечаются в системе координат страницы, чтобы одно и то же место сопоставлялось
            у всех форм независимо от того, как кадр лёг под съёмкой.
            """;
    }
}