Единый админский инструмент для контроля качества данных в пайплайне обработки анализов. Сейчас (апрель 2026) — частично реализован в b2b-platform под именами Admin / Org Monitoring Dashboard и блоком Generation Quality. Покрывает Analysis-слой через LLM-judges (порт 5 из 11 критериев Никиты, G-Eval с logprobs) и таблицу EvaluationScore; Recognition / Normalization слои пока остаются на старом фрагментированном мониторинге. Спецификация — RFC-022 (черновик февраль 2026 + март-обновление).

Текущее состояние реализации (BG-1196, апрель 2026)

Реализовано через серию коммитов BG-1196 в bloodgpt-for-business. Пошли по Hybrid-варианту (свой UI + LangFuse как trace-store), не по «чистому LangFuse-first» как изначально рекомендовал RFC. См. dqd-implementation-approach.

Inngest cron evaluation-judge (analysis-worker)

apps/analysis-worker/src/inngest/functions/evaluation-judge.function.ts:

  • Расписание: "0 * * * *" — каждый час
  • На каждом тике: выбирает BloodTest со status=completed, у которых нет ещё EvaluationScore для какого-то из критериев (или есть _skipped-sentinel старше 24 часов)
  • Фетчит observations из Langfuse по traceId = testId, тип GENERATION, limit 50
  • По имени observation определяет, какой критерий применять:
Observation prefixCriteria
analyze-parametersingle_parameter_analysis
test-overviewtest_overview
follow-up-recommendationfollowup_generation
panel-overviewpanel_overview
trend-analysistrend_analysis
  • Запускает LLM judge (по умолчанию gpt-4.1, требует logprobs — text-parsing fallback убран в коде явно)
  • Пишет результат в EvaluationScore
  • Включён per-organization через WorkOS feature flag LLM_JUDGE — постепенный rollout
  • Fair quota: BATCH_SIZE = 10 распределяется пропорционально между орг’ами по числу непросчитанных тестов
  • _skipped sentinel-строка для тестов без подходящих observations — чтобы не запрашивать заново раньше 24 часов

Prisma модель EvaluationScore

packages/database/prisma/schema.prisma:

bloodTestId, promptName, langfuseTraceId, langfuseObservationId,
score (0-1), passed (bool), reason, judgeModel, threshold,
criteriaVersion (sha256(criteria)[:8]), trigger ("cron" | "manual"),
createdAt
unique (bloodTestId, promptName, langfuseObservationId)

criteriaVersion — хэш YAML-критерия в момент оценки. Позволяет пересчитать после изменения критерия и сравнить версии.

promptName = "_skipped" + langfuseObservationId = "none" — sentinel для пропущенных тестов.

YAML-критерии

5 критериев в packages/evaluation/src/criteria/: single_parameter_analysis, test_overview, followup_generation, panel_overview, trend_analysis. Перенос с eval-репо Никиты (11 judges, 2000+ тестов). Методология, scoring через logprobs, формат YAML, оставшиеся 6 critères, BG-1216 для composite scoring path — см. отдельную страницу llm-judges.

Admin Monitoring Dashboard

apps/b2b-platform/src/app/admin/monitoring/ + src/components/admin/monitoring-dashboard.tsx. Глобальный (по всем орг’ам):

  • Summary: totalRequests, completed, errorCount, successRate, processingCount, staleCount
  • Latency: p50 / p95 / p99 / avg / sampleSize
  • Crosscheck: avgVerifiedParams, totalDiscrepancies, totalResolved, testsWithCrosscheck
  • Timeline (bucket: completed / failed / processing / stale + avgLatencyMs)
  • Status / biomarker / domain-frequency distributions
  • ErrorLog, staleTests
  • Quality Score card (BG-1196) — сводный pass rate

Org Monitoring Dashboard

apps/b2b-platform/src/app/dashboard/[id]/monitoring/page.tsx + org-monitoring-dashboard.tsx. Те же метрики в scope организации, плюс Generation Quality block:

  • Шесть карточек (5 критериев + overall)
  • Per-criterion pass count / total evaluated
  • Линки на отфильтрованный список тестов: «View evaluated tests», «View failed tests»
  • Показывается только при LLM_JUDGE feature flag для этой org

Per-test Quality column

В таблице тестов dashboard/[id]/tests добавлен столбец Quality (BG-1196), фильтрация ?evaluated=true, ?evaluationPassed=false.

Validation editing (Correct-уровень для Analysis)

apps/b2b-platform/src/app/dashboard/[id]/validation/[testId]/editable-overview.tsx — inline-редактирование narrative summary / patterns / considerations / recommended steps. Это первая часть Correct-функциональности на Analysis-слое; remap для Normalization и исправление Recognition пока не реализованы.

Standalone evaluation CLI

apps/evaluation/ — отдельное приложение (Node CLI + web server, sqlite storage, comparator/analyzer/cli-formatter). Используется для batch-прогонов критериев off-line (compare reports, drill-down). Отдельный путь от продакшен-cron.

Что отличается от RFC-022

RFC-022Реальная реализация
3 layer × 3 level (Recognition / Normalization / Analysis)Реализован только Analysis-слой; Recognition и Normalization — на старом мониторинге
Рекомендация: LangFuse-first → HybridСразу Hybrid (свой EvaluationScore + Langfuse traceId для drill-down)
LangFuse Annotation Queues для reviewНе использован — review идёт через editable-overview страницу
11 YAML критериев (из eval-репо Никиты)Пока 5 критериев
Включение для всехPer-org feature flag LLM_JUDGE — постепенный rollout
Cron «раз в час, оценить через 2-4 недели → решить про Hybrid»Cron "0 * * * *" зашит сразу; оценка-эксперимент пропущена

Где живёт остальной контроль качества (фрагментированно)

DQD пока не покрыл:

ИсточникЧто покрываетLayer
#bg-notifyTrend conflicts, stuck tests, balance reconciliation, GUID renormalizationсквозное / Analysis
#bg-crosscheckCrossCheck Bot per test (verified / unverified counts)Recognition
#normalization_service_notificationsLOINC Daily Digest, Sentry errorsNormalization
#langfuse-notificationsLangFuse alertsсквозное
#monitoring-prodArgoCD health, infra alertsinfra
Прямые SQL-запросыUnknown-параметры, флаги NeedToNormalization, IsCrosscheckVerifiedNormalization / Recognition

«Тихие» трансформации (SanitizeForPostgres, μ→u, /1→/l, en-dash → дефис, +→positive) по-прежнему не отслеживаются.

Архитектура: 3 layer × 3 level

Целевая концепция RFC-022 — каждый этап пайплайна имеет три возможности:

                Metrics (что система считает автоматически)
                  │
Layer  ─────────► View (drill-down в детали)
                  │
                Correct (исправление → score → метрика ошибки)

Два уровня админки — кто что может на Correct-уровне

DQD физически — два разных интерфейса с разным разрешённым scope:

  • Super-admin (внутренняя команда)apps/b2b-platform/src/app/admin/.... Полный доступ: видит все организации, все тесты, все ошибки. Может всё на Correct: править narrative, remap параметры, изменять reference ranges, помечать judge-ошибки. Это наш инструмент контроля качества.
  • Org-admin (B2B-клиент)apps/b2b-platform/src/app/dashboard/[id]/.... Видит только свою организацию. Generation Quality block виден если LLM_JUDGE flag включён. На Correct — сильно ограничен: вряд ли стоит давать партнёру править narrative или менять mapping справочника, иначе мы теряем consistency данных и запутываем self-interpretation. Реалистичный scope клиента — feedback («этот результат выглядит неверно», «параметр распознан неправильно»), который попадает в очередь к super-admin’у; прямого write-доступа в производственные данные нет.

Эта разделённость не зашита явно — сейчас обе странички дашборда живут в одном b2b-platform, и единственный фактический gate — WorkOS RBAC + feature flag. Что именно показывать клиенту в Generation Quality / Per-test Quality column / validation editing — ещё открытый дизайн-вопрос.

Layers:

Recognition  →  Normalization  →  Analysis  =  E2E score
(crosscheck)    (LOINC mapping)    (LLM)       (weighted)

Recognition

LevelСодержимоеСтатус
MetricsCrosscheck consensus %, кол-во применённых трансформаций (μ→u, /1→/l, super-scripts, en-dash, normal→norm, +→positive)crosscheck считается, в org-dashboard виден avgVerifiedParams / totalDiscrepancies; трансформации не считаются
ViewPDF страница, 3 попытки распознавания, различияLangFuse traces
CorrectВыбрать правильное значение → score recognition_errorне реализовано

LLM-judge для Recognition готов upstream — у Никиты есть recognize_gpt.yaml (data preservation integrity, translation rules, completeness, document classification) и recognize_medical_context.yaml. Не портированы и не подключены к нашему OBSERVATION_TO_CRITERIA маппингу. Включение даст Recognition Quality block в org-dashboard через тот же cron. См. llm-judges.

См. recognition, bounding-box-validation.

Normalization (.NET ParameterNormalizationService)

LevelСодержимоеСтатус
MetricsCache hit %, Unknown count (-2), Error (-1), API retries, fallback на «Additional»LangFuse metadata из ParameterNormalizationService:164; в DQD не агрегировано
ViewЧто вернул normalization API, альтернативные маппинги, историяLangFuse
CorrectRemap на правильный ParameterV2 → обновление справочникане реализовано в DQD

LLM-judge для unit conversion готов upstream — unit_converter.yaml (deterministic-style, threshold 0.9, проверка точности формул mmol/L ↔ mg/dL). Не портирован.

См. normalization-service.

LOINC Normalization (Python/FastAPI, март 2026)

LevelСодержимоеСтатус
MetricsItems processed/day, healthy / unhealthy, error count, failed mappingsDaily Digest в #normalization_service_notifications
ViewLOINC mapping, failed mappings, alternative codesUI прототип loinc_harmonization_service/ui/ (standalone)
CorrectRemap LOINC → mapping tableв прототипе UI, не интегрировано в DQD

См. loinc-harmonization-service, loinc-harmonization-pipeline.

Analysis — реализованный слой

LevelСодержимоеРеализация
MetricsPass rate per critère (5 штук), overall pass rateEvaluationScore aggregated в org-dashboard «Generation Quality»
ViewLLM judge reason, score, threshold, criteriaVersionPer-test Quality column → drill-down (LangFuse trace по langfuseTraceId)
CorrectInline edit narrative / patterns / considerations / recommended stepseditable-overview.tsx на странице validation

См. reference-ranges.

End-to-End

МетрикаРеализация
Overall quality scoreOrg-dashboard: суммарный pass rate по 5 критериям analysis-слоя
Weighted score (RFC: recognition × 0.4 + normalization × 0.3 + analysis × 0.3)Не реализован — только analysis-часть посчитана
By organizationЧерез org-dashboard и admin-dashboard (отдельный view per org)
By timeRange filter (24h / 30d / all) в обоих дашбордах
By lab / sourceЧерез domainFrequency / biomarkerDistribution; LLM-judge breakdown по lab — нет

Feedback loop — частично

Half-loop работает: judge оценивает completed test → score появляется в дашборде → видно «German labs: X% pass rate» (через org-фильтр). Закрытая часть петли (correction человеком → score = 0 на recognition_error → метрика ошибки) не реализованаeditable-overview правит narrative analysis, но не возвращает correction-сигнал в evaluation-таблицу. Для recognition / normalization correction-вход вообще нет.

Pipeline shift (март 2026): нужна абстракция

Параллельно идёт переход с multi-step pipeline (recognition → normalization → analysis отдельными шагами) на FHIR single-pass recognition — LLM выдаёт сразу FHIR Bundle (Observation/DiagnosticReport/Patient/Practitioner/…) одним проходом, без промежуточных narrative-структур. Подробнее: narrative-to-fhir, hl7-input-pipeline. Это важно для DQD потому, что текущая привязка judge-ов к именам observations (analyze-parameter, panel-overview, …) переживёт переход только если эти имена остаются. Если single-pass даёт другие observation-имена, маппинг OBSERVATION_TO_CRITERIA потребует пересмотра.

Клиентский мониторинг — отдельный трек

Параллельно Артём делал pilot mode + базовые метрики для B2B клиентов (success rate, latency, tier breakdown). Это встроилось в org-dashboard (status / latency / crosscheck / timeline / errorLog) — там же, где и DQD-функциональность Generation Quality. Физически — одна страница, две аудитории: B2B-клиент видит operational-метрики (success rate / latency / errors) всегда; «Generation Quality» (DQD judge-scores, разбор completed-тестов по criteria) показывается только когда у org включён flag LLM_JUDGE. Флаг — gate видимости одного блока на той же странице, не отдельный продукт.

Связь с LOINC Dictionary admin UI

Прототип Dictionary admin UI (loinc_harmonization_service/ui/, разрабатывался параллельно в марте) — View + Correct над LOINC mapping table. Физически standalone-приложение; Mar 3 архитектурное решение было «сначала standalone, потом объединить в DQD». По текущему направлению — этот UI принадлежит superadmin scope (внутренний инструмент для curators), не B2B-клиентскому DQD. То есть «Dictionary admin» и «Generation Quality для B2B-clients» — разные surface’ы, объединять их не надо. См. loinc-harmonization-service.

Связь с RFC-012 OCR Validation

RFC-012 описывает workflow валидации распознавания — приоритизированная очередь, контекст для решения, batch-валидация похожих, обратная связь в Langfuse через scores. DQD задумывался как расширение этой идеи на весь pipeline. В реальной реализации Recognition correction вообще не сделан — ни в DQD, ни как отдельный сервис; упор пошёл целиком в Analysis. Recognition остаётся отдельным сложным кейсом — vision-LLM, ошибки структурные а не семантические, нужны другие validation-инструменты — и в scope DQD сейчас не входит. RFC-012: /home/i/JOBS/BloodGPT/specs/RFC/012-ocr-validation.md.

Граница с RFC-021 FHIR Data Quality

RFC-021 — спецификация data-quality requirements для входящих FHIR-данных от B2B-клиник (InterSystems, Epic, Cerner, лабораторных партнёров): какие FHIR-поля обязательны на импорте, как валидировать соответствие профайлу при приёме, чеклист для партнёров перед подключением. То есть это «контракт качества на входе», а не «измерение качества внутри нашего pipeline» — RFC-021 описывает что мы от партнёра ожидаем получить, DQD описывает как мы свои AI-выходы оцениваем. Эти два контекста путать не нужно. RFC-021: /home/i/JOBS/BloodGPT/specs/RFC/021-fhir-data-quality-requirements.md.

Связанные решения

Открытые вопросы

  • Подключить уже-готовые upstream критерии Никиты для Recognition (recognize_gpt, recognize_medical_context) и Normalization (unit_converter) — это самый дешёвый путь расширить DQD на эти слои, см. llm-judges
  • Как переживёт переход на FHIR single-pass — observation-имена изменятся, маппинг OBSERVATION_TO_CRITERIA сломается
  • Закрытие feedback loop: correction в editable-overview → возврат в EvaluationScore как «judge ошибся, человек исправил»
  • Перенос оставшихся 6 YAML-критериев из eval-репо Никиты (5 из 11 портированы) — panel_description, parameter_range_gpt плюс 3 для Recognition / Normalization выше
  • Включение LLM_JUDGE для остальных орг’ов — критерии для go/no-go не заданы
  • Объединение standalone Dictionary admin UI с org-monitoring-dashboard
  • Weighted E2E score (0.4 / 0.3 / 0.3) — пока не считается, так как только Analysis слой покрыт

Связано

Источники

Источники: 1 2.

Сноски

  1. Сессия ildar/c8967f27, 2026-03-02 — (Mar 2-3.

  2. Сессия ildar/920d5967, 2026-03-26 — (Mar 30-31.