Per-biomarker UI компонент в bloodgpt-frontend (.NET stack, legacy track). Описывает как тренд представлен как первоклассная сущность внутри параметра — отдельный объект с двумя ребёнками: raw points + AI-generated prose. Это шаблон, который variant feat/actuality-mvp-dirty пока полностью не повторил для patient-scoped пути (carry-over Артура от 2026-05-18); описание ниже — снимок legacy дизайна, на который имеет смысл опираться при проектировании нового API shape (normal-biomarker-pipeline-coverage).
Data shape (data model)
В legacy DTO ([packages/sdk/generated/api/index.ts]1) тренд — поле trend?: TrendAnalysisDto внутри ParameterInfoDto:
interface ParameterInfoDto {
// identity + текущее значение
id, name, resultValue, units, normalMin/Max, borderlineMin/Max,
isAbnormal, isCritical, isHealthy, status,
description, // base описание биомаркера (per-биомаркер, static)
// trending metadata
trendingGroupId, // ключ группировки исторических LOINC-ренеймов
trendCount, // сколько исторических значений существует
trend?: TrendAnalysisDto {
parameterName,
trendingGroupId,
parameterV2Id,
trendAnalysis, // AI-generated markdown prose
trendData: {
"2023-01-15": TrendDataPointDto { value, isAbnormal, ... },
"2024-03-20": TrendDataPointDto { value, isAbnormal, ... },
...
}
}
}Ключевая структура — trend это самостоятельный объект на одном уровне детализации с параметром. Series (trendData, key=ISO-date) и prose (trendAnalysis, markdown) живут в одном месте, передаются вместе. trendingGroupId дублируется на уровне параметра — для определения «нужно ли вообще тренд-секцию показывать» без необходимости заглядывать внутрь trend. См. trending-groups про сам механизм группировки.
В .NET stack данные тренда лежат в Postgres (LoincParameters.TrendingGroupId колонка), prose trendAnalysis сохраняется отдельной mutation’ой и редактируется. FHIR в этом stack’е не используется.
UI panel — что отображает
Компонент ParamPanelTrendText.tsx2 (491 строка) рендерит TEXT часть тренда:
┌─ Trend Analysis ────────────────────────────────┐ ◄ Edit pencil (doctor only)
│ [trendAnalysis as markdown — AI prose 3-5 │
│ предложений про изменение значений per │
│ биомаркер; рендерится через MarkdownRenderer]│
├─────────────────────────────────────────────────┤
│ [description — base biomarker description, │
│ что вообще значит этот биомаркер] │
├─ Read more / Show less ───────── Learn more ────┤
└─────────────────────────────────────────────────┘
Chart (точки на графике) — отдельный компонент ResultsTrendChartJs.tsx3, рендерится в той же ParamPanel.tsx выше или рядом. Текст и chart не дублируют данные — chart использует trend.trendData, текст использует trend.trendAnalysis.
Режимы рендера
| Сценарий | Что показывается |
|---|---|
| trendCount ≤ 1 (один тест) | Только description + Learn More кнопка. Trend секция полностью скрывается через флаг hideTrendAnalysis=true |
| trendCount > 1, hasTrendData | Header «Trend Analysis» + markdown trendAnalysis + border + description. Read more / Show less переключение если текст overflow’ит 120px (COLLAPSED_TEXT_HEIGHT_PX) |
| trendLoading | Spinner + «Loading trend analytics…» |
| trendError | Info icon + «Trends require multiple tests» |
| !hasTrendData | Не рендерится (return null) |
| Doctor editing | Edit pencil → Textarea-режим для обоих полей (trendAnalysis + description) → Save/Cancel buttons, сохраняется через useTrendsUpdateTrendAnalysis mutation |
| PDF mode | Всегда expanded, без toggle buttons, более компактные размеры (text-[13px] leading-[1.45]) |
| Comparison variant | Side-by-side рендер для сравнения двух тестов |
«Single-value mode» — единственный путь, где description (base описание биомаркера) показывается без trend-блока. В multi-value mode они идут вместе, разделённые border.
Lazy fetch
Тренд не тянется eagerly для всех биомаркеров на странице — это было бы дорого при типичных 20-50 параметрах на тест. Вместо этого ParamPanel.tsx4 использует useLazyTrendAnalysis hook:
const canFetchTrend =
!isSingleValueMode &&
!trendsPending &&
Boolean(paramData.trendingGroupId);
const { trendData, trendLoading, trendError } = useLazyTrendAnalysis({
enabled: canFetchTrend,
initialTrendData: data.trend ?? null, // pre-loaded из ParameterInfoDto
trendingGroupId: paramData.trendingGroupId,
refreshKey: trendRefreshNonce,
});enabled гейт основан на:
!isSingleValueMode— для одного теста запрос бессмыслен!trendsPending— pending-флаг с уровня страницы (когда analysis ещё бежит)trendingGroupId !== null— нужен ключ группировки
initialTrendData — если ParameterInfoDto.trend уже пришёл в начальной выборке, hook стартует с этим значением без fetch’а. Это позволяет сервер-side оптимально pre-fetch’ить тренды для важных биомаркеров (или всех) и доставлять inline.
refreshKey — invalidation token, увеличивается когда юзер кликнул «Refresh analysis» или сохранил edit prose — заставляет re-fetch.
Editing affordance — doctor/org-admin only
Edit pencil показывается если canShowEditButton === true, что зависит от role guard. Когда юзер кликает:
- Switch текстовые блоки на
<Textarea>(рисует draftsdescriptionDraft/trendAnalysisDraft) - Show Save / Cancel buttons
- На Save — mutation
useTrendsUpdateTrendAnalysisобновляет prose в backend’е - Optimistic update / refresh
Это значит trendAnalysis не immutable AI-output, а editable artifact — врач может corrigировать AI-prose, и эти правки сохраняются в legacy backend per биомаркер. Это важная особенность legacy track’а — separation между «AI первый draft» и «human-validated final».
Открытые вопросы
- FHIR equivalent для editable trendAnalysis. В legacy DB поле
LoincParameters.TrendingGroupIdхранит ID, отдельная таблица хранит editable prose. В FHIR-стэке editable AI-content concept ещё не зафиксирован — открытый вопрос пересекается с fhir-modeling-ai-content и fhir-resource-origin-and-lifecycle (надо ли стampитьmeta.tagorigin=human-editedпосле правки врача). - Где живёт AI trend prose в новом patient-scoped pipeline. Сегодня — нигде надёжно: Reasoner получает history плохо (поле даты пропало после Python→TS миграции), Personalizer имеет пустой
formatHistory(hist)слот. Артур взял carry-over на пересмотр (2026-05-18 созвон). См. biomarker-analysis-pipeline. - Cross-biomarker trends в Insights секции. Per-биомаркер тренд — это микро-tour; макро-обзор («3 биомаркера ухудшаются», «холестерин стабильно растёт два года») — это уже уровень Insights, не парам-панели. См. Phase 3 в health-report-pipeline (5 Insight секций, не реализованы).
- Lazy fetch vs eager в FHIR-стэке. В legacy lazy через
trendingGroupId. В FHIR’е raw series тянется черезObservation?subject=X&code=LOINChistory запрос — может быть медленнее, может потребоваться precomputation в response shape для/api/biomarkersчтобы избежать N+1 fetch’ей в UI. - Single-value mode triggering.
trendCount ≤ 1сегодня скрывает trend секцию полностью. В новой архитектуре с patient-scoped views (агрегация всех тестов пациента)trendCountвсегда >1 для повторно сданного биомаркера — single-value mode может стать deprecated кроме first-upload UX.
Связано
- trending-groups — механизм группировки LOINC-кодов одного analyte; источник
trendingGroupIdключа - biomarker — entity-уровень
- loinc — base codification поверх которого живут trending groups
- biomarker-analysis-pipeline — где AI trend prose должна генериться в новом стэке (Reasoner + Personalizer, сегодня carry-over)
- normal-biomarker-pipeline-coverage — variant B миграция; trend pass-through через Reasoner — отдельный workstream
- health-report-pipeline — Phase 3 Insight секции включают cross-biomarker «Trends» как одна из 5 секций
- fhir-modeling-ai-content — общий концепт mapping AI-output в FHIR; editable trendAnalysis — частный случай
- fhir-resource-origin-and-lifecycle —
meta.tagстратегия для human-edited AI prose
Сноски
-
packages/sdk/generated/api/index.ts—TrendAnalysisDto,ParameterInfoDto.trend?field, generated from .NET backend OpenAPI schema, accessed 2026-05-18, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/packages/sdk/generated/api/index.ts. ↩ -
apps/dashboard/components/features/Results/ParamPanel/ParamPanelTrendText.tsx— UI компонент trend prose рендера, 491 строка, accessed 2026-05-18, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/apps/dashboard/components/features/Results/ParamPanel/ParamPanelTrendText.tsx. ↩ -
apps/dashboard/components/charts/ResultsTrendChartJs.tsx— chart-side рендер дляtrendDatapoints, accessed 2026-05-18, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/apps/dashboard/components/charts/ResultsTrendChartJs.tsx. ↩ -
apps/dashboard/components/features/Results/ParamPanel/ParamPanel.tsx— orchestrator, используетuseLazyTrendAnalysis(стр. 139-152),useTrendsUpdateTrendAnalysismutation для edit (стр. 121), accessed 2026-05-18, https://github.com/Realai-plus/bloodgpt-frontend/blob/main/apps/dashboard/components/features/Results/ParamPanel/ParamPanel.tsx. ↩