Единый админский инструмент для контроля качества данных в пайплайне обработки анализов. Сейчас (апрель 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 prefix | Criteria |
|---|---|
analyze-parameter | single_parameter_analysis |
test-overview | test_overview |
follow-up-recommendation | followup_generation |
panel-overview | panel_overview |
trend-analysis | trend_analysis |
- Запускает LLM judge (по умолчанию
gpt-4.1, требует logprobs — text-parsing fallback убран в коде явно) - Пишет результат в
EvaluationScore - Включён per-organization через WorkOS feature flag
LLM_JUDGE— постепенный rollout - Fair quota:
BATCH_SIZE = 10распределяется пропорционально между орг’ами по числу непросчитанных тестов _skippedsentinel-строка для тестов без подходящих 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_JUDGEfeature 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-notify | Trend conflicts, stuck tests, balance reconciliation, GUID renormalization | сквозное / Analysis |
#bg-crosscheck | CrossCheck Bot per test (verified / unverified counts) | Recognition |
#normalization_service_notifications | LOINC Daily Digest, Sentry errors | Normalization |
#langfuse-notifications | LangFuse alerts | сквозное |
#monitoring-prod | ArgoCD health, infra alerts | infra |
| Прямые SQL-запросы | Unknown-параметры, флаги NeedToNormalization, IsCrosscheckVerified | Normalization / 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_JUDGEflag включён. На 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 | Содержимое | Статус |
|---|---|---|
| Metrics | Crosscheck consensus %, кол-во применённых трансформаций (μ→u, /1→/l, super-scripts, en-dash, normal→norm, +→positive) | crosscheck считается, в org-dashboard виден avgVerifiedParams / totalDiscrepancies; трансформации не считаются |
| View | PDF страница, 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 | Содержимое | Статус |
|---|---|---|
| Metrics | Cache hit %, Unknown count (-2), Error (-1), API retries, fallback на «Additional» | LangFuse metadata из ParameterNormalizationService:164; в DQD не агрегировано |
| View | Что вернул normalization API, альтернативные маппинги, история | LangFuse |
| Correct | Remap на правильный ParameterV2 → обновление справочника | не реализовано в DQD |
LLM-judge для unit conversion готов upstream — unit_converter.yaml (deterministic-style, threshold 0.9, проверка точности формул mmol/L ↔ mg/dL). Не портирован.
LOINC Normalization (Python/FastAPI, март 2026)
| Level | Содержимое | Статус |
|---|---|---|
| Metrics | Items processed/day, healthy / unhealthy, error count, failed mappings | Daily Digest в #normalization_service_notifications |
| View | LOINC mapping, failed mappings, alternative codes | UI прототип loinc_harmonization_service/ui/ (standalone) |
| Correct | Remap LOINC → mapping table | в прототипе UI, не интегрировано в DQD |
См. loinc-harmonization-service, loinc-harmonization-pipeline.
Analysis — реализованный слой
| Level | Содержимое | Реализация |
|---|---|---|
| Metrics | Pass rate per critère (5 штук), overall pass rate | EvaluationScore aggregated в org-dashboard «Generation Quality» |
| View | LLM judge reason, score, threshold, criteriaVersion | Per-test Quality column → drill-down (LangFuse trace по langfuseTraceId) |
| Correct | Inline edit narrative / patterns / considerations / recommended steps | editable-overview.tsx на странице validation |
См. reference-ranges.
End-to-End
| Метрика | Реализация |
|---|---|
| Overall quality score | Org-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 time | Range 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.
Связанные решения
- dqd-implementation-approach — Hybrid-вариант (active, реализован через BG-1196)
Открытые вопросы
- Подключить уже-готовые 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 слой покрыт
Связано
- llm-judges — методология и scoring judge’ей, формат YAML, порт работы Никиты
- recognition — Recognition layer
- normalization-service — классическая нормализация
- loinc-harmonization-service — LOINC сервис + Dictionary admin UI прототип
- loinc-harmonization-pipeline — LOINC pipeline
- bounding-box-validation — смежный контроль распознавания
- reference-ranges — Analysis layer
- parameter-triage-codes —
-1Error /-2Unknown коды