N1 Healthcare — CHR Workflow Architecture
N1 Healthcare — CHR Workflow Architecture
Overview
Section titled “Overview”The workflow-health-summary** generates a 1-page patient health summary via a 6-stage integrative medicine cascade.
It analyzes biomarker data through deterministic scoring, enrichment, and derived marker computation, then uses two LLM calls to
identify clinical patterns and produce structured clinical summaries. The final output is deterministically rendered to HTML via
chr-html.
Pipeline Stages
LLM Calls
Forge Libraries
Pydantic ModelsPhilosophy: This repo is pure analysis logic. No infrastructure, no templates, no API clients. All shared infra comes from forge-sentinel libs.
Run Modes
Section titled “Run Modes”Local Mode
Section titled “Local Mode”JSON file → cascade → HTML. No credentials required.
INPUT_FILE=tests/fixtures/patient-small.json uv run python bin/run.py
Forge Mode
Section titled “Forge Mode”API fetch → cascade → HTML + PDF, with billing and cloud upload.
Requires USER_ID, CHR_ID, LiteLLM and N1 API credentials.
Pipeline Flowchart
Section titled “Pipeline Flowchart”3 deterministic stages + 2 LLM calls + 1 render. The LLM receives the complete enriched picture and reasons about patterns across markers (integrative medicine approach).
S0Pre-filterDeterministic
chr-data (reference) • Pure scoring: deviation, trend, recency, density
S1Context AssemblyDeterministic
chr-data (reference: optimal ranges, body systems, meds, unit conversion)
S2Derived MarkersDeterministic
chr-data (DERIVED_REGISTRY, unit conversion)
S3Pattern RecognitionLLM
chr-llm (allm_json_call, load_prompt) • chr-langfuse (tracing)
S4Clinical SummaryLLM
chr-llm (allm_json_call, load_prompt) • chr-langfuse (tracing)
S5RenderDeterministic
chr-html (html_document, build_report) • chr-styles (CSS) • chr-pdf (WeasyPrint)Stage Details
Section titled “Stage Details”Pre-filter — Deterministic Scoring
Section titled “Pre-filter — Deterministic Scoring”Groups biomarkers by biomarker ID, scores each group, then selects top 50 unhealthy + top 50 healthy biomarker groups (100 total). Each selected group carries its full measurement history.
Input`dict[str, BiomarkerInput]`
Output`Stage0Output`Unhealthy Group Scoring (max 80)
Section titled “Unhealthy Group Scoring (max 80)”- worst_deviation (30): Highest deviation across all measurements vs reference midpoint
- trend (20): Linear regression slope across full history (3+ data points required)
- recency (10): ≤2yr = 10, ≤5yr = 5, older = 0
- data_richness (10): ≥5 pts = 10, ≥3 = 7, 2 = 4, 1 = 1
- oor_consistency (10): Percentage of measurements that are abnormal
Healthy Group Scoring (max 80)
Section titled “Healthy Group Scoring (max 80)”- clinical_significance (30): High-value markers (glucose, HbA1c, TSH, lipids, etc.) = 30; others = 10
- stability (20): Fraction of measurements that are normal × 20
- recency (10): Same as unhealthy
- data_richness (10): Same as unhealthy
- system_coverage (10): Bonus for underrepresented body systems
Hard Rules
Section titled “Hard Rules”- Severely abnormal groups (worst_deviation ≥ 20) always included
- Qualitative biomarkers excluded
- If one pool has fewer than 50, overflow fills from the other
chr-dataContext Assembly — Functional Enrichment
Section titled “Context Assembly — Functional Enrichment”Enriches scored biomarkers with functional/optimal ranges, body system classification, and medication interaction context. All lookups come from chr-data’s reference module.
Input`Stage0Output + PatientContext?`
Output`ContextEnrichedInput`Functional Classification
Section titled “Functional Classification”- optimal: Within both lab range and functional/optimal range
- within_lab: Within lab range, no optimal range defined
- outside_optimal: Within lab range but outside functional range
- outside_lab: Outside standard lab reference range
- unknown: Non-numeric value
Key Operations
Section titled “Key Operations”- Unit conversion to conventional units via
convert_to_conventional() - Optimal range lookup via
get_optimal_range(canonical) - Body system mapping via
get_body_systems(canonical) - Medication interaction detection via
get_affected_markers(med_name) - Data quality assessment (recency, trend availability, system coverage)
chr-dataDerived Markers — Computed Ratios & Indices
Section titled “Derived Markers — Computed Ratios & Indices”Computes clinically meaningful derived markers from available biomarkers. Uses DERIVED_REGISTRY from chr-data which contains formulas, parameter mappings, and interpretation logic.
Input`ContextEnrichedInput + PatientContext?`
Output`DerivedMarkersOutput`Example Derived Markers
Section titled “Example Derived Markers”- TG/HDL Ratio: Triglycerides / HDL — insulin resistance proxy
- HOMA-IR: (Glucose × Insulin) / 405 — insulin resistance
- eGFR: CKD-EPI formula (requires age + sex) — kidney function
- NLR: Neutrophils / Lymphocytes — inflammation marker
Key Operations
Section titled “Key Operations”- Builds biomarker_id → numeric_value lookup (highest prefilter score wins)
- All values converted to conventional units before computation
- Demographics-dependent markers (eGFR) require age and sex from PatientContext
- Skipped markers tracked separately for transparency
chr-dataPattern Recognition — LLM Call #1
Section titled “Pattern Recognition — LLM Call #1”LLM identifies 2-6 clinical patterns across enriched biomarkers, providing root cause hypotheses, gap analysis, and cross-system connections. Thinks like a functional medicine doctor.
Input`ContextEnrichedInput + DerivedMarkersOutput`
Output`PatternAnalysisOutput`Prompt Template Variables
Section titled “Prompt Template Variables”- {{enriched_biomarkers_json}} — Full enriched biomarker data
- {{derived_markers_json}} — Computed derived markers
- {{context_section}} — Patient context (optional)
- {{data_quality_json}} — Data quality summary
Post-LLM Validation
Section titled “Post-LLM Validation”- Filter evidence IDs to only those present in input data
- Skip patterns with zero valid supporting evidence
- Downgrade confidence from “high” to “medium” when all evidence is stale (>2 years)
- Enforce 1-6 pattern count (fallback pattern if none survive validation)
- Severity floor: pattern can’t be “monitor” if supporting biomarker has high deviation (≥60 score + outside_lab)
- Force trajectory to “insufficient_data” when no longitudinal data exists
chr-llmchr-langfuseClinical Summary — LLM Call #2
Section titled “Clinical Summary — LLM Call #2”LLM produces the final structured summary: headline, key findings, recommendations, gap recommendations, and overall status. This is the content that gets rendered into the report.
Input`PatternAnalysisOutput + ContextEnrichedInput + DerivedMarkersOutput`
Output`ClinicalSummaryOutput`Prompt Template Variables
Section titled “Prompt Template Variables”- {{patterns_json}} — Clinical patterns from S3
- {{enriched_biomarkers_json}} — Enriched biomarkers (compact)
- {{derived_markers_json}} — Derived markers
- {{context_section}} — Patient context (optional)
Post-LLM Validation
Section titled “Post-LLM Validation”- Filter findings to valid measurement_ids only
- Force trend to “insufficient_data” when no longitudinal data
- Ensure critical pattern biomarkers appear in findings
- Cap findings: min 3, max 10 (pad with highest-scored biomarkers if needed)
- Cap recommendations: min 2, max 5 (add generic fallbacks if needed)
- Deterministic override: overall_status based on pattern severities (critical → attention_needed)
- Medication dedup: flag recommendations that suggest starting a medication the patient already takes
chr-llmchr-langfuseRender — Deterministic HTML
Section titled “Render — Deterministic HTML”Converts ClinicalSummaryOutput to a complete HTML document using chr-html’s component library and chr-styles’s CSS system. No LLM-generated HTML — the presentation layer is fully deterministic.
Input`ClinicalSummaryOutput`
Output`HTML string → PDF via chr-pdf`Render Components
Section titled “Render Components”- Header: Patient name, date, overall status badge
- Headline: Key finding + trajectory summary
- Key Findings: Severity dots, trend arrows, functional notes
- Clinical Patterns: Pattern cards with severity borders and root cause
- Recommendations: Category-tagged action items (excluding “test” category)
- Recommended Tests: Gap recommendations (test-specific)
- Footer: AI disclaimer + confidentiality notice
CSS Pipeline
Section titled “CSS Pipeline”html_document(body, report_type="single-page", override="health-summary")- Base CSS + single-page type CSS + health-summary override CSS layered
- PDF rendering via WeasyPrint through
chr-pdf
chr-htmlchr-styleschr-pdfchr-document-managerFull Prompts
Section titled “Full Prompts”Both prompts use a system/human split loaded via chr-llm’s load_prompt(). The system message is everything above the --- separator, the human template is everything below. Template variables are highlighted in amber.
pattern_recognition.md — Stage 3 Prompt (78 lines)S3 • LLM
**CopyYou are an integrative medicine physician analyzing biomarker data. Your task is to identify clinical patterns — connections across biomarkers that reveal underlying health stories. Think like a functional medicine doctor: look at the whole picture, ask “why”, and identify what’s missing.
Always respond with valid JSON only. No markdown, no explanation, just JSON.
Section titled “Always respond with valid JSON only. No markdown, no explanation, just JSON.”Clinical Pattern Analysis
Section titled “Clinical Pattern Analysis”You are analyzing biomarker data for a patient health summary. The biomarkers have been enriched with functional/optimal ranges (tighter than standard lab ranges) and organized by body system. Derived markers (ratios, indices) have been computed where data permits.
How to Think About This
Section titled “How to Think About This”- Look for clusters, not isolated values. A single mildly elevated marker means little. Three markers in the same direction across two systems tells a story.
- Use functional ranges first. A glucose of 95 is “normal” by lab standards but already suboptimal functionally. When both lab AND functional ranges are abnormal, that’s more significant.
- Consider root causes. Elevated glucose + elevated triglycerides + low HDL isn’t three separate problems — it’s insulin resistance. Name the root cause.
- Note what’s missing. If you see elevated inflammatory markers but no thyroid panel, that’s a gap worth noting.
- Assess trajectory. A value that’s been worsening over 3 data points matters more than a single snapshot.
- Cross-system connections matter. Thyroid dysfunction affects cholesterol. Insulin resistance drives inflammation. Connect the dots.
Input Data
Section titled “Input Data”Enriched Biomarkers (with functional ranges and body systems)
Section titled “Enriched Biomarkers (with functional ranges and body systems)”{{enriched_biomarkers_json}}
Derived Markers (computed ratios and indices)
Section titled “Derived Markers (computed ratios and indices)”{{derived_markers_json}}
{{context_section}}
Data Quality Summary
Section titled “Data Quality Summary”{{data_quality_json}}
- Identify 2-6 clinical patterns. Each pattern must reference at least 1 biomarker by measurement_id.
- For each pattern, provide:
- A root cause hypothesis (what’s driving this pattern?)
- Supporting evidence (measurement_ids that support this pattern)
- Contradicting evidence (measurement_ids that argue against, if any)
- Trajectory assessment
- Affected body systems
- Include gap analysis: what tests are missing that would confirm or refute your hypotheses?
- Note cross-system connections when they exist.
- Use ONLY measurement_ids that exist in the input data. Do not invent biomarker IDs.
- Severity should reflect the clinical significance of the entire pattern, not just individual values.
- When derived markers are available (HOMA-IR, TG/HDL ratio, etc.), use them to strengthen or modify your pattern assessment.
- When medication context is available, consider whether abnormal values are expected given the medication (e.g., B12 depletion on metformin is expected, not a new finding).
Output Format
Section titled “Output Format”Return a JSON object:
{ "patterns": [```json{"name": "<pattern name, 3-8 words>","severity": "critical" | "significant" | "monitor","confidence": "high" | "medium" | "low","narrative": "<clinical explanation, max 80 words>","supporting_evidence": ["<measurement_id>", ...],"contradicting_evidence": ["<measurement_id>", ...],"root_cause_hypothesis": "<possible root cause, max 30 words>","body_systems": ["<system>", ...],"trajectory": "worsening" | "improving" | "stable" | "insufficient_data"}], “gap_analysis”: [
{"test_name": "<name of missing test>","reason": "<why valuable, max 25 words>","related_pattern": "<pattern name>","priority": "high" | "medium" | "low"}], “cross_system_connections”: [
"<brief description of a cross-system relationship>"] }
```textclinical_summary.md — Stage 4 Prompt (98 lines)S4 • LLM
CopyYou are writing the final content for a 1-page patient health summary. You have clinical patterns, enriched biomarker data, and derived markers. Now produce the summary content that will be rendered into the report.
Always respond with valid JSON only. No markdown, no explanation, just JSON.---## Clinical Summary Generation
You are producing the final structured output for a patient health summary report. You have:1. Clinical patterns identified by an integrative medicine analysis2. Enriched biomarkers with functional/optimal ranges3. Derived markers (computed ratios and indices)
### Input Data
#### Clinical Patterns{{patterns_json}}
#### Enriched Biomarkers{{enriched_biomarkers_json}}
#### Derived Markers{{derived_markers_json}}
{{context_section}}
### Rules
#### Headline- Single sentence, max 15 words.- Reference the most clinically significant pattern, not just one biomarker.- Tone: direct, informative, not alarmist.- When patient context is available, acknowledge known diagnoses if they relate to the most significant findings.
#### Key Findings (3-10)- Prioritize: critical first, then significant, then monitor.- Each finding needs: biomarker name, value with unit, severity, trend, and context (max 15 words).- Include a functional_note when the value is within lab range but outside functional/optimal range (e.g., "Outside optimal: <90 mg/dL").- Reference the related pattern when applicable.- When medications are known, include medication context (e.g., "LDL 145 on atorvastatin — above goal").- Maximum 10 findings.
#### Recommendations (2-5)- Categorize each: "test" (order a test), "lifestyle" (diet/exercise/sleep), "discuss" (talk to provider), "monitor" (retest/track), "urgent" (immediate action).- Be specific. Not "improve diet" but "increase omega-3 intake to improve TG/HDL ratio."- Include a brief rationale for each recommendation.- CRITICAL: Do NOT suggest starting a medication the patient is already taking.- Reference the pattern that drives this recommendation.
#### Gap Recommendations- Tests that should be ordered to fill data gaps identified in the pattern analysis.- Each with a reason and priority.
#### Overall Status- "attention_needed": any critical pattern- "monitoring_recommended": significant patterns but no critical- "generally_healthy": only monitor-level patterns
#### Trajectory Summary- One sentence summarizing the overall health trajectory based on pattern trajectories.
### Output Format
Return a JSON object:{ “headline”: “<max 15 words>”, “key_findings”: [
{"measurement_id": "<from input>","display_name": "<clean biomarker name>","value": "<value with unit>","severity": "critical" | "significant" | "monitor","trend": "worsening" | "improving" | "stable" | "insufficient_data","context": "<1-line explanation, max 15 words>","functional_note": "<functional range note or empty string>","pattern_ref": "<pattern name if applicable, else null>"}], “recommendations”: [
{"priority": 1-5,"category": "test" | "lifestyle" | "discuss" | "monitor" | "urgent","action": "<specific recommendation, max 30 words>","rationale": "<brief rationale, max 20 words>","pattern_ref": "<related pattern name>"}], “gap_recommendations”: [
{"test_name": "<test name>","reason": "<why recommended, max 25 words>","priority": "high" | "medium" | "low"}], “overall_status”: “attention_needed” | “monitoring_recommended” | “generally_healthy”, “trajectory_summary”: “<overall trajectory, max 20 words>” }
## Data Contracts
All Pydantic models live in `cascade/models.py`. Models are grouped by the stage that produces them.
### Input Models
BiomarkerInput — Raw biomarker data
```textCopyclass BiomarkerInput(BaseModel):
"""Biomarker data matching workflow-functional's BiomarkerInsight schema."""
test_name: strmeasurement_id: strvalue: strstatus: strreference_range: strunit: str | None = Nonetest_date: str | None = Noneall_measurements: list[dict[str, Any]] | None = Nonefile_name: str | None = Nonebiomarker_id: str | None = Nonerecord_id: str | None = NoneCascadeInput — Top-level pipeline input
Copyclass CascadeInput(BaseModel):```text"""Top-level input to the cascade pipeline."""
patient_name: str = Field(description="Display name for the report header")report_date: str = Field(description="Report date in human-readable format")biomarkers: dict[str, BiomarkerInput] = Field(description="measurement_id -> BiomarkerInput")context: PatientContext | None = Field(default=None) PatientContext — Non-biomarker clinical data
```textCopyclass PatientContext(BaseModel):```text"""Non-biomarker patient data passed to LLM stages for clinical context."""
patient_name: str = "Patient"age: int | None = Nonegender: str | None = Nonediagnoses: list[dict[str, Any]] = Field(default_factory=list)medications: list[dict[str, Any]] = Field(default_factory=list)procedures: list[dict[str, Any]] = Field(default_factory=list)genetics: list[dict[str, Any]] = Field(default_factory=list)report_date: str = ""enabled_types: set[str] = Field(default_factory=lambda: set(ALL_CONTEXT_TYPES))### Stage 0 Output
Stage0Output, ScoredBiomarker, ScoredCanonicalGroup
```textCopyclass ScoredBiomarker(BaseModel):```text"""Biomarker with deterministic relevance score."""biomarker: BiomarkerInputtotal_score: floatcomponents: dict[str, float] = Field(default_factory=dict)class ScoredCanonicalGroup(BaseModel):
"""Patient biomarker group with all measurements and group-level scoring."""biomarker_id: strrepresentative: BiomarkerInputall_members: list[BiomarkerInput]pool: Literal["healthy", "unhealthy"]total_score: floatcomponents: dict[str, float] = Field(default_factory=dict)class Stage0Output(BaseModel):
"""Output of the deterministic pre-filter."""scored_biomarkers: list[ScoredBiomarker]canonical_groups: list[ScoredCanonicalGroup]excluded_count: intinput_count: intunhealthy_count: int = 0healthy_count: int = 0### Stage 1 Output
ContextEnrichedInput, EnrichedBiomarker, DataQuality, OptimalRange, MedicationInteraction
```textCopyclass OptimalRange(BaseModel):```textlow: float | None = Nonehigh: float | None = Nonesource: str = ""class MedicationInteraction(BaseModel):
medication_name: strinteraction_note: str = ""class EnrichedBiomarker(BaseModel):
"""Biomarker enriched with functional ranges, body systems, medication context."""biomarker: BiomarkerInputprefilter_score: floatnumeric_value: float | None = Nonelab_range_low: float | None = Nonelab_range_high: float | None = Noneoptimal_range: OptimalRange | None = Nonefunctional_status: str = "unknown" # optimal|within_lab|outside_optimal|outside_lab|unknownbody_systems: list[str] = Field(default_factory=list)medication_interactions: list[MedicationInteraction] = Field(default_factory=list)class DataQuality(BaseModel):
total_biomarkers: intbiomarkers_with_trends: int = 0biomarkers_recent: int = 0biomarkers_stale: int = 0systems_covered: list[str] = Field(default_factory=list)systems_missing: list[str] = Field(default_factory=list)has_context: bool = Falsehas_medications: bool = Falseclass ContextEnrichedInput(BaseModel):
"""Output of Stage 1: Context Assembly."""enriched_biomarkers: list[EnrichedBiomarker]by_system: dict[str, list[str]]data_quality: DataQualitypatient_context: PatientContext | None = None### Stage 2 Output
DerivedMarkersOutput, DerivedMarker
```textCopyclass DerivedMarker(BaseModel):```text"""A computed marker derived from multiple biomarkers."""name: strvalue: floatunit: strstatus: str # optimal|borderline|elevated|lowoptimal_range_str: strinterpretation: strsource_biomarker_ids: list[str]class DerivedMarkersOutput(BaseModel):
"""Output of Stage 2: Derived Markers."""computed: list[DerivedMarker] = Field(default_factory=list)skipped: list[str] = Field(default_factory=list)### Stage 3 Output
PatternAnalysisOutput, ClinicalPattern, GapAnalysisItem
```textCopyclass ClinicalPattern(BaseModel):```text"""A clinical pattern identified across multiple biomarkers."""name: strseverity: Literal["critical", "significant", "monitor"]confidence: Literal["high", "medium", "low"]narrative: str # max 80 wordssupporting_evidence: list[str] # measurement_ids, min 1contradicting_evidence: list[str]root_cause_hypothesis: str # max 30 wordsbody_systems: list[str]trajectory: Literal["worsening", "improving", "stable", "insufficient_data"]class GapAnalysisItem(BaseModel):
"""A missing lab test that would inform clinical reasoning."""test_name: strreason: str # max 25 wordsrelated_pattern: strpriority: Literal["high", "medium", "low"]class PatternAnalysisOutput(BaseModel):
"""Output of Stage 3: Pattern Recognition."""patterns: list[ClinicalPattern] # 1-6gap_analysis: list[GapAnalysisItem]cross_system_connections: list[str]### Stage 4 Output
ClinicalSummaryOutput, SummaryFinding, Recommendation, GapRecommendation
```textCopyclass SummaryFinding(BaseModel):```text"""A key finding for the clinical summary."""measurement_id: strdisplay_name: strvalue: strseverity: Literal["critical", "significant", "monitor"]trend: Literal["worsening", "improving", "stable", "insufficient_data"]context: str # max 15 wordsfunctional_note: strpattern_ref: str | Noneclass Recommendation(BaseModel):
"""A prioritized recommendation."""priority: int # 1-5category: Literal["test", "lifestyle", "discuss", "monitor", "urgent"]action: str # max 30 wordsrationale: str # max 20 wordspattern_ref: strclass GapRecommendation(BaseModel):
"""A recommended test to fill a data gap."""test_name: strreason: str # max 25 wordspriority: Literal["high", "medium", "low"]class ClinicalSummaryOutput(BaseModel):
"""Output of Stage 4 — everything needed to render the report."""headline: str # max 15 wordskey_findings: list[SummaryFinding] # 1-10recommendations: list[Recommendation] # 2-5gap_recommendations: list[GapRecommendation]overall_status: Literal["attention_needed", "monitoring_recommended", "generally_healthy"]trajectory_summary: str # max 20 wordspatterns: list[ClinicalPattern]html: str = "" # populated by render.py, not LLM## Library Dependency Map
Forge-sentinel libraries used at each stage. = deterministic stage, = LLM stage.
```textLibraryS0S1S2S3S4S5
chr-core
chr-data
chr-llm
chr-html
chr-styles
chr-frontend
chr-logging
chr-document-manager
chr-pdf
chr-langfuseLibrary Details
Section titled “Library Details”chr-coreException hierarchy, CHRBaseSettings, PII sanitization. Transitive dependency via chr-llm.
chr-dataAsync N1 API fetching, pagination, typed models, biomarker reference data (optimal ranges, body systems, medication markers, derived formulas, unit conversion).
chr-llmLLM calls with billing, JSON parsing, token tracking. `allm_json_call()` for stages 3-4, `load_prompt()` for .md prompt files.
chr-htmlHTML fragment/document builders. `html_document()`, severity dots, trend arrows, escaping utilities.
chr-stylesCSS loader (base + type + override). `get_report_css("single-page", "health-summary")`.
chr-frontendProgress tracking, UI phases, user error messages. `ProgressReporter`, `UIPhase`, `BaseProgressTracker`.
chr-loggingStructured JSON logging, PII sanitization, context vars. `configure_logging()`, `bind_request_context()`.
chr-document-managerS3/GCS upload, signed URLs, publish pipeline. `publish_report()` for HTML write + PDF render + cloud upload.
chr-pdfHTML→PDF rendering, compression. `WeasyPrintRenderer` via `publish_report()`.
chr-langfuseLangfuse LLM tracing (spans, cost, prompt visibility). `traced_stage()` wraps stages 3-4 LLM calls.Not Yet Integrated
Section titled “Not Yet Integrated”chr-verifyHallucination detection (name matching, timeline validation). No verification of LLM output before render.
chr-checkpointFile-based stage checkpointing, crash recovery. Restarts currently re-run all 6 stages.
chr-resilienceRetry decorators, error classification, OTEL setup. Not yet integrated.
chr-chartsBiomarker chart generation (Chart.js, matplotlib). No visualizations in reports currently.Configuration
Section titled “Configuration”Settings model from config.py using pydantic-settings. All settings can be overridden via environment variables.
Settings Model
Copyclass Settings(BaseSettings):
# LLMllm_model: str = "gemini-2.5-pro"llm_temperature: float = 0.1llm_max_retries: int = 2llm_timeout: int = 180
# Service identity (for billing metadata)service_name: str = "workflow-health-summary"
# Cascade tuningprefilter_max_output: int = 100
# Progress reporting (forge mode)n1_api_base_url: str = ""n1_api_key: str = ""sync_with_cloud: bool = True
# Storage (cloud upload via chr-document-manager)bucket_name: str = ""storage_provider: str = "s3"aws_region: str = "us-east-2"skip_uploads: bool = False
model_config = {"env_prefix": "", "env_file": ".env", "extra": "ignore"}Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`LLM_MODEL``gemini-2.5-pro`LLM model for stages 3-4 (via LiteLLM)
`LLM_TEMPERATURE``0.1`Low temperature for consistent clinical output
`LLM_MAX_RETRIES``2`Retry count for failed LLM calls
`LLM_TIMEOUT``180`Timeout in seconds per LLM call
`SERVICE_NAME``workflow-health-summary`Service identity for billing metadata
`PREFILTER_MAX_OUTPUT``100`Max biomarker groups selected (50 unhealthy + 50 healthy)
`N1_API_BASE_URL`*empty*N1 API base URL (forge mode)
`N1_API_KEY`*empty*N1 API key (forge mode)
`SYNC_WITH_CLOUD``true`Enable cloud progress sync
`BUCKET_NAME`*empty*S3/GCS bucket for report upload
`STORAGE_PROVIDER``s3`Cloud storage provider (s3 or gcs)
`AWS_REGION``us-east-2`AWS region for S3
`SKIP_UPLOADS``false`Skip cloud upload (local mode)
`INPUT_FILE`*none*Path to JSON patient file (local mode)
`USER_ID`*none*Patient user ID (forge mode)
`CHR_ID`*none*CHR session ID (forge mode)Overview
Section titled “Overview”The workflow-data-analysis** generates multi-page biomarker-focused Comprehensive Health Reports (CHR) — data visualizations with AI-powered summaries but no clinical recommendations. It processes patient biomarker groups through 8 analysis stages: chart discovery, chart rendering, clinical clustering, biomarker summaries, SBAR narrative, pattern & follow-up analysis, timeline analysis, and deterministic HTML assembly.
Analysis Stages
7+LLM Call Types
Forge Libraries
20+Feature FlagsPhilosophy: This repo is a thin orchestrator. Chart utilities, HTML builders, retry logic, data fetching, and styling all live in forge-sentinel shared libraries. WDA only does: LLM calls, chart generation, data mapping, and pipeline orchestration.
Run Modes
Section titled “Run Modes”Local Mode
Section titled “Local Mode”CSV fixtures → charts → HTML. No API credentials required.
uv run python -m src.reportgen.main with USE_LOCAL_DATA=true
Staging / Production
Section titled “Staging / Production”N1 API fetch → charts + LLM summaries → HTML + PDF upload.
Requires USER_ID, CHR_ID, N1_API_KEY, OPENAI_API_KEY
Pipeline Flowchart
Section titled “Pipeline Flowchart”3 deterministic + 5 LLM-powered stages. Deterministic data processing bookends the LLM calls. All LLM features have deterministic fallbacks for graceful degradation.
S0Chart DiscoveryDeterministic
chr-data (EntityFetcher) • chr-charts (classification, resolve_category)
S1Chart RenderingLLM + Fallback
chr-charts (validate_biomarker_value) • Chart.js config generation or PNG download
S2Smart ClusteringDeterministic
chr-charts (apply_smart_clustering, subgroups) • Clinical priority ordering
S3Biomarker SummariesLLM
chr-llm (billing) • Per-chart trend analysis + per-category chapter summaries
S4SBAR Clinical NarrativeLLM
chr-llm • Situation, Background, Assessment, Recommendations
S5Pattern & Follow-Up AnalysisLLM
chr-llm • Cross-biomarker patterns + follow-up test recommendations
S6Timeline AnalysisDeterministic + LLM
Inflection point detection • LLM narrative generation • Hallucination validation
S7RenderDeterministic
chr-html (component builders) • chr-styles (CSS) • chr-core (PII sanitization)Stage Details
Section titled “Stage Details”Chart Discovery — Fetch, Filter & Classify
Section titled “Chart Discovery — Fetch, Filter & Classify”Fetches patient biomarker groups from the N1 API, filters by data richness, optionally classifies uncategorized biomarkers via LLM, and resolves each into a health category. Returns a prioritized list of chartable biomarkers.
Input`biomarkers[] + biomarkers_df`
Output`List[ChartMetadata]`Filtering
Section titled “Filtering”- Keep only biomarker groups with
member_count ≥ 2(minimum for trend line) - Filter out invalid biomarker_id values (NaN, None, empty)
- Validate biologically plausible values via
validate_biomarker_value() - When count exceeds
MAX_BIOMARKERS(1000), prioritize by: member_count DESC → health_areas count DESC → name ASC
Category Resolution (fallback chain)
Section titled “Category Resolution (fallback chain)”-
- API
group_namefrom patient biomarker metadata
- API
-
- LLM classification via
classify_biomarkers_with_llm()(optional, uses fast model)
- LLM classification via
-
- Infer from
health_areasfield
- Infer from
-
- Heuristic name matching (
infer_category_from_name())
- Heuristic name matching (
-
- Fallback: “Uncategorized”
LLM Classification (Optional)
Section titled “LLM Classification (Optional)”- Model:
classification_model(default: gemini-3-flash-preview) - Purpose: Batch-classify uncategorized biomarker names into health categories
- Returns
ClassificationResultwith cache + token usage - Cached across calls to avoid re-classifying known biomarkers
chr-datachr-chartschr-llmChart Rendering — PNG or Chart.js
Section titled “Chart Rendering — PNG or Chart.js”Generates visual chart representations for each biomarker. Two rendering modes: download PNGs from the N1 chart API, or generate Chart.js configurations via LLM (with deterministic fallback). Chart.js mode produces interactive browser-rendered charts.
Input`ChartMetadata[] + biomarkers_df`
Output`chart_paths{} (PNG) or chart_configs{} (Chart.js)`PNG Mode (default)
Section titled “PNG Mode (default)”- Download chart images from N1 chart API
- Skipped entirely when Chart.js mode is enabled
- Batch download with error handling per chart
Chart.js Mode (LLM-powered)
Section titled “Chart.js Mode (LLM-powered)”- LLM generates complete Chart.js v4 JSON configs with reference range annotations
- Deterministic fallback builds config from data when LLM unavailable or disabled
- Batched with semaphore (max 5 concurrent LLM calls)
- Deduplicates same-date readings by averaging (
_deduplicate_rows_by_date()) - Validates values via
validate_biomarker_value()— filters biologically implausible data - Fallback reference range parsing from row data when API returns null (
_extract_reference_range_from_rows())
Chart.js Output Format
Section titled “Chart.js Output Format”- Line chart with date labels (Mon YYYY format)
- Green points (#22c55e) for normal values, red (#ef4444) for out-of-range
- Reference range shown as shaded annotation band
- Responsive with hover tooltips
chr-chartschr-llmchr-resilienceSmart Clustering — Clinical Priority Ordering
Section titled “Smart Clustering — Clinical Priority Ordering”Groups charts by health category, orders categories by clinical relevance (most abnormal biomarkers first), and assigns biomarkers to clinical sub-groups within each category. Ensures the most clinically significant data appears first in the report.
Input`ChartMetadata[] + biomarkers_df`
Output`charts_by_category{} (ordered) + category_order[]`Patient Profile Analysis
Section titled “Patient Profile Analysis”analyze_patient_profile(biomarkers_df)— counts abnormal biomarkers per category- Categories with more out-of-range values ranked higher
- Ensures most clinically relevant sections seen first
Category Ordering
Section titled “Category Ordering”smart_category_order()— sort by abnormal count descendingsort_charts_within_category()— prioritize within each categorysort_singletons_within_category()— sort table rows for singleton biomarkers
Sub-Group Assignment
Section titled “Sub-Group Assignment”- 7 category definitions with keyword-matched sub-groups (e.g., Cardiovascular → Lipid Panel, Cardiac Markers, Blood Pressure)
get_subgroup_for_biomarker(name, category)— case-insensitive partial matchgroup_charts_by_subgroup()— ordered by clinical priority within category- Unmapped biomarkers fall to “Uncategorized” sub-group
chr-chartsBiomarker Summaries — Per-Chart & Per-Category
Section titled “Biomarker Summaries — Per-Chart & Per-Category”Generates per-biomarker trend summaries and per-category chapter summaries via LLM. Chart summaries provide individual biomarker analysis while chapter summaries give a holistic view of each health category. All calls are independently toggleable and run with semaphore-based batched concurrency (max 5 concurrent).
Input`biomarkers_df + charts_by_category{}`
Output`chart_summaries{} + chapter_summaries{}`Chart Summaries (enable_chart_summaries)
Section titled “Chart Summaries (enable_chart_summaries)”- Per-biomarker trend analysis: description, measurements, clinical meaning, action
- Extracts
[CONCLUSION:positive|neutral|negative]sentiment tag - Deterministic fallback if LLM fails
- Batched with semaphore (max 5 concurrent)
Chapter Summaries (enable_chapter_summaries)
Section titled “Chapter Summaries (enable_chapter_summaries)”- 3-4 sentence analysis per health category
- Covers: overall picture, key findings, patterns, clinical context
- Constrained to only reference biomarkers in the category
chr-llmchr-resilienceSBAR Clinical Narrative
Section titled “SBAR Clinical Narrative”Generates a structured SBAR (Situation, Background, Assessment, Recommendations) clinical narrative from the patient’s biomarker data. Uses the medical communication standard adapted for health reports. All biomarker references validated against input data.
Input`biomarkers_df + charts_by_category{}`
Output`SBARSummary (situation, background, assessment, recommendations)`SBAR Framework
Section titled “SBAR Framework”- Medical communication standard adapted for health reports
Structure
Section titled “Structure”- Situation: 1-2 sentences on current health status
- Background: 3-5 bullets with key vitals/labs (actual values)
- Assessment: 1-2 sentence interpretation of findings
- Recommendations: 3-5 action items starting with bold verbs (Discuss, Monitor, Schedule, Review)
Safety
Section titled “Safety”- All biomarker references validated against input data
- No clinical diagnoses — observations only
- JSON response format with structured parsing
chr-llmchr-resiliencePattern & Follow-Up Analysis
Section titled “Pattern & Follow-Up Analysis”Discovers cross-biomarker patterns and generates follow-up test recommendations. Identifies multi-biomarker interactions, temporal trends, and cross-system connections. All biomarker names validated against input data to prevent hallucination.
Input`biomarkers_df + charts_by_category{}`
Output`KeyPattern[] + FollowUpTest[]`Key Patterns (enable_key_patterns)
Section titled “Key Patterns (enable_key_patterns)”- Cross-biomarker pattern discovery (max 10 patterns)
- Each pattern: name, description, connected biomarkers, causes, effects, actions, priority (high/medium/low), type (concern/healthy)
- All biomarker names validated against input data (hallucination prevention)
Category Patterns (enable_category_patterns)
Section titled “Category Patterns (enable_category_patterns)”- Per-category pattern subsets for inline display within category sections
Follow-Up Tests (enable_follow_up_tests)
Section titled “Follow-Up Tests (enable_follow_up_tests)”- 3-5 recommended tests with urgency: priority / recommended / routine
- Related biomarkers must exist in patient data
- Each test: name, reason, related_biomarkers, urgency
chr-llmchr-resilienceTimeline Analysis — Inflection Detection + Narrative
Section titled “Timeline Analysis — Inflection Detection + Narrative”Detects significant biomarker changes (inflection points) over time using deterministic algorithms, then generates an LLM-powered timeline narrative. Three-layer hallucination prevention validates all LLM output against actual patient data.
Input`biomarkers_df`
Output`HealthEvent[] + timeline_narrative: str`Inflection Point Detection (Deterministic)
Section titled “Inflection Point Detection (Deterministic)”- Status changes (normal ↔ abnormal): Require ≥ 25% value change to avoid minor fluctuations
- Significant value changes (same status): Require > 40% change
- Values validated via
validate_biomarker_value()before analysis - Signed
change_pct: positive = increase, negative = decrease - Events classified: improvement, deterioration, or significant_change
Event Selection
Section titled “Event Selection”- Sort by absolute change_pct descending (most significant first)
- Take top N events (default 5, configurable via
timeline_max_events) - Re-sort chronologically for narrative coherence
LLM Narrative Generation
Section titled “LLM Narrative Generation”- Bullet-point format: overall trajectory + individual event descriptions
- Bold biomarker names and significant changes
Hallucination Prevention (3 layers)
Section titled “Hallucination Prevention (3 layers)”_validate_timeline_narrative(): All biomarker name mentions must exist in actual data_validate_timeline_values(): All percentage claims checked against real data (±0.5% tolerance)_sanitize_percentages(): Replace hallucinated percentages with correct values from events
chr-chartschr-llmRender — Deterministic HTML
Section titled “Render — Deterministic HTML”Converts all generated content into a complete HTML report using chr-html’s component library and chr-styles’s CSS system. No LLM-generated HTML — the presentation layer is fully deterministic. Charts embedded as base64 data URIs (PNG) or inline Chart.js canvases. All LLM output PII-sanitized before rendering.
Input`All S0-S6 outputs + demographics`
Output`HTML string → file`Render Components (from chr-html)
Section titled “Render Components (from chr-html)”html_document(): Full HTML wrapper with head, body, report-type CSSreport_header_card(): Title, patient age/gender, report datecategory_section(): Health area headers with category iconschart_block()/chartjs_block(): Chart + summary textchart_grid(): Multi-column chart layoutsbar_grid(): SBAR display (Situation, Background, Assessment, Recommendations)pattern_card(): Key pattern cards with priority/type badgesfollow_up_tests_grid(): Follow-up test recommendation cardstimeline(): Timeline events and narrativecritical_findings_strip(): Out-of-range biomarker summary banner
CSS Pipeline
Section titled “CSS Pipeline”html_document(body, report_type="multi-page")- Base CSS + multi-page type CSS layered via
chr-styles - PII sanitization via
chr-core.pii.sanitize_report_content()
chr-htmlchr-styleschr-coreFull Prompts
Section titled “Full Prompts”Chart Summary Prompt Per Chart • S3
**CopyAnalyze the following biomarker data and provide a concise clinical summary.
Description: Brief technical definition of what this biomarker measures Measurements: Trend (direction + brief explanation) followed by all data points Clinical Meaning: 1-2 brief points about what the values indicate Action: One specific sentence (action required OR “No action required”) [Sample Source]: Blood/Urine/Saliva/etc [CONCLUSION:positive|neutral|negative] One-sentence summary
CRITICAL: Only reference biomarkers provided in the data below. Do NOT hallucinate values, dates, or biomarker names.
Chapter Summary Prompt Per Category • S3
CopyAnalyze the following biomarker data for the {category} health area.
Write a comprehensive summary (3-4 sentences, max {max_words} words) covering:1. Overall health picture for this category2. Key findings (abnormal, borderline, or optimal values)3. Patterns and multi-biomarker interactions4. Clinical context and what the results suggest
CRITICAL: Only reference biomarkers listed in the data below.SBAR Summary Prompt Global • S4
CopyGenerate a clinical SBAR summary from the biomarker data:
- SITUATION: 1-2 concise sentences on current health status- BACKGROUND: 3-5 bullets with key vitals/labs (include actual values)- ASSESSMENT: 1-2 sentence interpretation of findings- RECOMMENDATIONS: 3-5 action bullets starting with **bold verb** (Examples: **Discuss**, **Monitor**, **Schedule**, **Review**)
Return ONLY valid JSON. No markdown wrapping.Key Pattern Discovery Prompt Global • S5
CopyAnalyze biomarker data to discover KEY PATTERNS a physician should know:- Multi-biomarker interactions and correlations- Temporal trends across measurements- Cross-system connections
CRITICAL: ONLY reference biomarkers present in the patient's actual report.
Return JSON array of patterns:[{ "pattern_name": "3-8 word name", "pattern_discovered": "detailed description", "connected_biomarkers": ["Exact Biomarker Name", ...], "potential_causes": ["cause1", ...], "potential_effects": ["effect1", ...], "possible_actions": ["action1", ...], "priority": "high|medium|low", "pattern_type": "concern|healthy"}]Follow-Up Tests Prompt Global • S5
CopySuggest 3-5 follow-up tests based on biomarker patterns.
CRITICAL: ONLY reference biomarkers from the patient's actual data.
For each test:{ "test_name": "Test Name", "reason": "Grounded in patient's specific findings", "related_biomarkers": ["Exact Biomarker Name"], "urgency": "priority|recommended|routine"}
Return ONLY valid JSON array.Timeline Narrative Prompt Global • S6
CopyCRITICAL: Response MUST be ONLY bullet points. No paragraphs.
Analyze chronological biomarker events and create bullet-point summary:1. EVERY LINE must start with "- " (markdown bullet)2. Highlight most significant health changes3. Identify overall trajectory (improving/stable/declining/mixed)4. Group related biomarker changes
FORMAT RULES:- EVERY line MUST start with "- "- NO paragraphs allowed- Use **bold** for biomarker names or important changes- Start with overall trajectory as first bullet
Chronological events: {events_text}Data Contracts
Section titled “Data Contracts”Core Models
Section titled “Core Models”ChartMetadata
Copyclass ChartMetadata:```textid: str # biomarker_idcanonical_name: strdescription: strbiomarker: strchart_type: str = "line"data_points: int = 0units: str | None = Nonecategory: str = "Uncategorized" # resolved health categoryhealth_areas: list[str] = []member_count: int = 0 # data points in biomarker groupreference_range_min: float | None = Nonereference_range_max: float | None = None HealthEvent (Timeline)
```textCopyclass HealthEvent:```textdate: str # "YYYY-MM" formatbiomarker_name: strbiomarker_id: strevent_type: str # "improvement" | "deterioration" | "significant_change"old_value: strnew_value: strold_status: str # "normal" | "abnormal"new_status: strchange_pct: float | None # Signed: positive = increase, negative = decreasereference_range: strunit: str Summary Dataclasses
```textCopyclass ChartSummary:```textbiomarker_id: strcanonical_name: strsummary_text: strconclusion: str # "positive" | "neutral" | "negative"is_fallback: bool = Falseclass ChapterSummary:
category: strsummary_text: strbiomarker_count: intabnormal_count: intclass SBARSummary:
situation: strbackground: list[str]assessment: strrecommendations: list[str]class KeyPattern:
pattern_name: strpattern_discovered: strconnected_biomarkers: list[str]potential_causes: list[str]potential_effects: list[str]possible_actions: list[str]priority: str # "high" | "medium" | "low"pattern_type: str # "concern" | "healthy"class FollowUpTest:
test_name: strreason: strrelated_biomarkers: list[str]urgency: str # "priority" | "recommended" | "routine"class ChartJSConfig(BaseModel):
biomarker_id: strcanonical_name: strconfig_json: str # Complete Chart.js config as JSONis_fallback: bool = False## Library Map
### Dependency Grid
```textLibraryS0S1S2S3S4S5S6S7
chr-data
chr-charts
chr-llm
chr-resilience
chr-core
chr-html
chr-styles
chr-frontendLibrary Details
Section titled “Library Details”chr-coreException hierarchy (`RetryableError`, `NonRetryableError`), `CHRBaseSettings`, PII sanitization for report content.
chr-dataAsync N1 API fetching via `EntityFetcher`. Typed models: `Biomarker`, `Biomarker`, `UserProfile`. Local CSV loader for development.
chr-chartsLLM classification (`classify_biomarkers_with_llm`), smart clustering (`apply_smart_clustering`), sub-group definitions, value validation (`validate_biomarker_value`), reference range formatting.
chr-llmLLM billing headers via `get_billing_headers()`, cost tracking (`CostTracker`), token usage recording, model pricing reference.
chr-resilienceRetry decorators (`@retry_api`, `@retry_llm`), error classification, progress helpers (`compute_adjusted_progress`, `trace_progress_stage`).
chr-htmlHTML component builders: `html_document`, `chart_block`, `chartjs_block`, `sbar_grid`, `pattern_card`, `timeline`, `category_section`, `follow_up_tests_grid`.
chr-stylesCSS loader with report_type + override layering. Design tokens, responsive layouts, N1 brand colors.
chr-loggingStructured JSON logging, PII sanitization in logs, `update_log_stage()` for stage-based log context.Configuration
Section titled “Configuration”Settings model from config.py using CHRBaseSettings (forge). Over 20 feature flags control which analysis stages run and what appears in the report.
Settings Model (key fields)
Copyclass CloudSettings(CHRBaseSettings):
# Charts (S0-S1)enable_charts: bool = Trueenable_tables: bool = Falseenable_llm_charts: bool = Falseuse_deterministic_charts: bool = Truemax_biomarkers: int = 1000max_llm_chart_biomarkers: int = 15
# LLM Summaries (S3-S5)enable_chart_summaries: bool = Trueenable_chapter_summaries: bool = Trueenable_key_patterns: bool = Trueenable_category_patterns: bool = Trueenable_sbar: bool = Trueenable_follow_up_tests: bool = True
# Timeline (S6)enable_timeline_summary: bool = True
# Modelssummary_model: str = "gemini-2.5-pro"classification_model: str = "gemini-3-flash-preview"temperature: float = 0.2
# Safetyenable_pii_sanitization: bool = Trueenable_llm_classification: bool = True
# Retry (per K8s pod, ×3 with restarts)retry_llm_max_attempts: int = 20retry_api_max_attempts: int = 10Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`SUMMARY_MODEL``gemini-2.5-pro`LLM model for all summary generation (S3-S6)
`CLASSIFICATION_MODEL``gemini-3-flash-preview`Fast model for biomarker categorization (S0)
`TEMPERATURE``0.2`LLM sampling temperature (lower = more deterministic)
`MAX_BIOMARKERS``1000`Maximum charts to generate (0 = unlimited)
`ENABLE_LLM_CHARTS``false`Use Chart.js instead of PNG charts (S1)
`ENABLE_CHARTS``true`Include biomarker chart images in report
`ENABLE_SBAR``true`Generate SBAR clinical summary (S4)
`ENABLE_KEY_PATTERNS``true`Cross-biomarker pattern discovery (S5)
`ENABLE_FOLLOW_UP_TESTS``true`Follow-up test recommendations (S5)
`ENABLE_TIMELINE_SUMMARY``true`Timeline narrative from inflection points (S6)
`DEV_MODE``false`Show patient biomarker IDs on charts
`USE_LOCAL_DATA``false`Use local CSV files instead of N1 APIFunctional Workflow
Section titled “Functional Workflow”📋Coming Soon
Section titled “Coming Soon”Clinical report with recommendations. Python, LangGraph, LiteLLM. PDF output via Typst.Generative Sequential Workflow
Section titled “Generative Sequential Workflow”📄Coming Soon
Section titled “Coming Soon”Detailed narrative report with sequential LLM stages. Python + LiteLLM, PDF via LaTeX.
Generative Langroid Workflow
Section titled “Generative Langroid Workflow”🤖Coming Soon
Section titled “Coming Soon”Agent-based workflow with lifestyle guides. Python, Langroid, LiteLLM.
Claude Code Workflow
Section titled “Claude Code Workflow”💻Coming Soon
Section titled “Coming Soon”CLI-driven report generation via Claude Code agent. Python.
Overview
Section titled “Overview”The workflow-supplements-optimization** generates a 1-page supplement protocol via a 7-stage cascade.
It analyzes biomarker data through deterministic pre-filtering, context enrichment, derived marker computation, and
nutrient gap matching, then uses two LLM calls to select supplements with specific dosing and build a daily protocol.
The final output is deterministically rendered to HTML via chr-html.
Pipeline Stages
LLM Calls
Forge Libraries
20+Pydantic ModelsPhilosophy: Deterministic first, LLM second. Safety screening (drug-supplement interactions, contraindications) is always deterministic — never delegated to an LLM. The LLM only selects from pre-validated candidates and generates patient-friendly text.
Run Modes
Section titled “Run Modes”Local Mode
Section titled “Local Mode”JSON file → cascade → HTML. No credentials required.
INPUT_FILE=tests/fixtures/patient.json uv run python bin/run.py
Forge Mode
Section titled “Forge Mode”API fetch → cascade → HTML + PDF, with billing and cloud upload.
Requires USER_ID, CHR_ID, LiteLLM and N1 API credentials.
Pipeline Flowchart
Section titled “Pipeline Flowchart”4 deterministic stages + 2 LLM calls + 1 render. Stages 0–2.5 are fully deterministic (scoring, enrichment, derived markers, nutrient gap matching). Stages 3–4 use LLM for supplement selection and protocol assembly. Stage 5 renders deterministically.
S0Pre-filterDeterministic
Scoring: deviation, trend, recency, density, OOR consistency • Top 50 unhealthy + 50 healthy
S1Context AssemblyDeterministic
chr-data (optimal ranges, body systems, medication interactions, unit conversion)
S2Derived MarkersDeterministic
chr-data (DERIVED_REGISTRY) • HOMA-IR, eGFR, LDL:HDL, TG:HDL ratios
S2.5Nutrient Gap MatchingDeterministic
41 biomarker→nutrient mappings • ~30 drug-supplement + supplement-supplement interaction rules
S3Supplement AnalysisLLM
chr-llm (allm_json_call, load_prompt) • Selects 3-8 from pre-validated candidates • Post-LLM validation + hallucination detection
S4Protocol AssemblyLLM
chr-llm (allm_json_call, load_prompt) • Daily schedule (4 slots) • Safety notes • Retest timeline
S5RenderDeterministic
chr-html (html_document) • chr-styles (single-page + supplements override) • chr-pdf (WeasyPrint)Stage Details
Section titled “Stage Details”Pre-filter — Deterministic Scoring
Section titled “Pre-filter — Deterministic Scoring”Groups biomarkers by biomarker ID, scores each group with 5 components (max 80 pts), then selects top 50 unhealthy + top 50 healthy biomarker groups. Severely abnormal groups are always included. Each selected group carries its full measurement history.
Input`dict[str, BiomarkerInput]`
Output`Stage0Output`Unhealthy Group Scoring (max 80)
Section titled “Unhealthy Group Scoring (max 80)”- worst_deviation (0-30): Highest deviation across all measurements vs reference midpoint
- temporal_trend (0-20): Linear regression slope across full history (3+ data points required)
- recency (0-10): ≤2yr = 10, ≤5yr = 5, older = 0
- data_richness (0-10): ≥5 pts = 10, ≥3 = 7, 2 = 4, 1 = 1
- oor_consistency (0-10): Percentage of measurements that are out-of-range
Healthy Group Scoring (max 80)
Section titled “Healthy Group Scoring (max 80)”- clinical_significance (0-30): High-value markers (glucose, HbA1c, TSH, lipids) = 30; others = 10
- stability (0-20): Fraction of measurements that are normal × 20
- recency (0-10): Same as unhealthy
- data_richness (0-10): Same as unhealthy
- system_coverage (0-10): Bonus for underrepresented body systems
Hard Rules
Section titled “Hard Rules”- Severely abnormal groups (worst_deviation ≥ 20) always included
- Qualitative biomarkers excluded
- If one pool has fewer than 50, overflow fills from the other
Key References
Section titled “Key References”_HIGH_SIGNIFICANCE_MARKERS— clinically important markers list_TEST_NAME_ALIASES— ~50 biomarker ID normalization rulesget_body_systems()— body system classification from chr-data
chr-dataContext Assembly — Functional Enrichment
Section titled “Context Assembly — Functional Enrichment”Enriches scored biomarkers with functional/optimal ranges, body system classification, medication interaction context, and data quality assessment. All lookups come from chr-data’s reference module.
Input`Stage0Output + PatientContext?`
Output`ContextEnrichedInput`Functional Classification
Section titled “Functional Classification”- outside_lab: Outside standard lab reference range (highest priority)
- outside_optimal: Within lab range but outside functional/optimal range
- within_optimal: Within both lab and functional ranges
Key Operations
Section titled “Key Operations”- Optimal range lookup via
get_optimal_range(canonical) - Unit conversion to conventional units via
convert_to_conventional() - Medication interaction detection via
get_affected_markers(med_name) - Body system mapping via
get_body_systems(canonical) - Data quality assessment (coverage, staleness, body systems)
chr-dataDerived Markers — Computed Ratios & Indices
Section titled “Derived Markers — Computed Ratios & Indices”Computes clinically meaningful derived markers from available biomarkers. Uses DERIVED_REGISTRY from chr-data which contains formulas, parameter mappings, and interpretation logic.
Input`ContextEnrichedInput + PatientContext?`
Output`DerivedMarkersOutput`Example Derived Markers
Section titled “Example Derived Markers”- HOMA-IR: (Glucose × Insulin) / 405 — insulin resistance
- eGFR: CKD-EPI formula (requires age + sex) — kidney function
- LDL:HDL Ratio: LDL / HDL — cardiovascular risk proxy
- TG:HDL Ratio: Triglycerides / HDL — insulin resistance proxy
Key Operations
Section titled “Key Operations”- Builds biomarker_id → numeric_value lookup (highest prefilter score wins)
- All values converted to conventional units before computation
- Demographics-dependent markers (eGFR) require age and sex from PatientContext
- Skipped markers tracked separately for transparency
chr-data
2.5Nutrient Gap Matching — Deterministic Candidate Selection
Section titled “Nutrient Gap Matching — Deterministic Candidate Selection”Maps enriched biomarkers to supplement candidates using 41 biomarker-to-nutrient rules, then screens all candidates against drug-supplement and supplement-supplement interaction databases. Contraindicated supplements are blocked before any LLM call.
Input`ContextEnrichedInput + DerivedMarkersOutput + PatientContext`
Output`list[NutrientCandidate] + list[InteractionFlag]`Biomarker → Nutrient Mapping
Section titled “Biomarker → Nutrient Mapping”- 41 mappings in
BIOMARKER_NUTRIENT_MAP - Direct markers (vitamin D, B12, ferritin) map 1:1
- Indirect markers (homocysteine, CRP, TSH) map to multiple nutrients with relevance + evidence scores
Severity Classification
Section titled “Severity Classification”- severe: ≥30% deviation from optimal midpoint
- moderate: 10-30% deviation
- mild: <10% deviation
Interaction Screening
Section titled “Interaction Screening”- ~30 drug-supplement rules: Levothyroxine+Iron, Metformin+B12, Warfarin+Omega-3, etc.
- Supplement-supplement rules: Iron+Calcium absorption, Zinc+Copper competition, etc.
- Blocking logic:
contraindicated→ blocked, diagnosis contraindications → blocked - Caution flags: flagged with spacing/timing instructions, not blocked
reference/nutrient_gaps.pyreference/supplement_specs.pyreference/interactions.pySupplement Analysis — LLM Call #1
Section titled “Supplement Analysis — LLM Call #1”LLM selects 3-8 supplements from pre-validated candidates, assigns specific doses, and writes clinical rationales. Extensive post-LLM validation catches hallucinated supplements, enforces dose caps, and ensures safety.
Input`NutrientCandidates + ContextEnrichedInput + DerivedMarkersOutput + PatientContext`
Output`SupplementGapAnalysisOutput`LLM Selection
Section titled “LLM Selection”- Selects 3-8 supplements from pre-validated candidates that passed S2.5 screening
- Assigns specific doses based on biomarker severity, patient context, and supplement specs
- Writes per-supplement clinical rationale
Post-LLM Validation
Section titled “Post-LLM Validation”- Name resolution: exact match → fuzzy match → alias lookup against SUPPLEMENT_SPECS
- Blocking check: re-verify against interaction database (LLM may hallucinate cleared supplements)
- Deduplication: merge duplicate supplement entries
- Dose capping: if LLM proposes dose above max_daily, cap to max_daily
- Timing sanitization: normalize timing values to known slots
- Hallucination detection: verify supplement names against ground truth in SUPPLEMENT_SPECS
Fallback
Section titled “Fallback”- If post-validation produces 0 valid supplements: pick top candidate from highest-severity gap deterministically
chr-llmchr-langfusechr-verifyProtocol Assembly — LLM Call #2
Section titled “Protocol Assembly — LLM Call #2”LLM builds the daily protocol: a 4-slot schedule, patient-friendly safety notes, retest timeline, and headline + summary. Extensive post-LLM validation backfills missing data and resolves timing conflicts deterministically.
Input`SupplementGapAnalysisOutput + InteractionFlags + PatientContext`
Output`SupplementProtocolOutput`Daily Schedule
Section titled “Daily Schedule”- 4 time slots: morning, afternoon, evening, bedtime
- Per-slot instructions (e.g. “take with fat for D3 absorption”)
Post-LLM Validation
Section titled “Post-LLM Validation”- Auto-assign: supplements missing from schedule are assigned to a slot based on supplement_specs timing
- Conflict resolution: Iron + Calcium never in same slot (absorption interference)
- Safety note backfill: interaction flags from S2.5 are added as safety notes if LLM omitted them
- Retest backfill: severe-gap biomarkers get retest entries if LLM omitted them
- Headline hallucination check: validated against supplement data; deterministic fallback if hallucinated
Overall Status (Deterministic)
Section titled “Overall Status (Deterministic)”- actionable_gaps: any severe gaps detected
- minor_optimization: moderate gaps only
- no_significant_gaps: no deficiencies found
- Status is always determined deterministically from gap data, never by LLM
chr-llmchr-langfusechr-verifyRender — Deterministic HTML
Section titled “Render — Deterministic HTML”Converts SupplementProtocolOutput to a complete HTML document using chr-html’s component library and chr-styles’s CSS system. No LLM-generated HTML — the presentation layer is fully deterministic.
Input`SupplementProtocolOutput + patient_name + report_date`
Output`HTML string → PDF via chr-pdf`HTML Sections
Section titled “HTML Sections”- Header: Patient name, date, overall status badge
- Headline + summary: Protocol overview from S4
- Daily schedule (hero): 4 time-slot cards (morning/afternoon/evening/bedtime) with per-slot instructions
- Supplement theme cards: Individual supplement cards with dose, form, rationale, retest
- Interaction warnings: Amber callouts for flagged interactions
- Safety notes: base .callout (amber for important, neutral for informational)
- Retest timeline: base .data-table with biomarker, weeks, reason
- Disclaimer + footer: base .report-disclaimer + .report-footer
CSS Pipeline
Section titled “CSS Pipeline”html_document(body, report_type="single-page", override="supplements")- Base CSS + single-page type CSS + supplements override CSS layered
- PDF rendering via WeasyPrint through
chr-pdf
Edge Case: No Gaps
Section titled “Edge Case: No Gaps”- When pipeline finds no deficiencies: renders a simplified “No Significant Gaps” variant
chr-htmlchr-styleschr-pdfchr-document-managerFull Prompts
Section titled “Full Prompts”Both prompts use a system/human split loaded via chr-llm’s load_prompt(). The system message is everything above the --- separator, the human template is everything below.
Stage 3 — Supplement Gap Analysis
Section titled “Stage 3 — Supplement Gap Analysis”Instructs the LLM to select 3-8 supplements from pre-validated candidates, assign specific doses, and write clinical rationales for each selection.
Prompt template: src/supplements_optimization/prompts/supplement_gap_analysis.md
Stage 4 — Protocol Assembly
Section titled “Stage 4 — Protocol Assembly”Instructs the LLM to build a daily schedule (4 time slots), write patient-friendly safety notes, set a retest timeline, and compose the headline and summary.
Prompt template: src/supplements_optimization/prompts/protocol_assembly.md
Data Contracts
Section titled “Data Contracts”All Pydantic v2 models live in src/supplements_optimization/cascade/models.py. Models are grouped by the stage that produces them.
ModelStagePurposeKey Fields
`BiomarkerInput`InputRaw biomarker measurementtest_name, value, unit, reference_range, biomarker_id`PatientContext`InputPatient demographics + medicationspatient_name, age, gender, medications, diagnoses`ScoredBiomarkerGroup`S0Biomarker group with scoringbiomarker_id, pool, total_score, components`Stage0Output`S0Pre-filter resultscanonical_groups, excluded_count, unhealthy_count, healthy_count`EnrichedBiomarker`S1Biomarker with functional contextfunctional_status, optimal_range, body_systems, medication_interactions`ContextEnrichedInput`S1Full enriched datasetenriched_biomarkers, by_system, data_quality`DerivedMarker`S2Computed ratio/indexname, value, unit, status, interpretation, source_biomarker_ids`DerivedMarkersOutput`S2All derived markerscomputed, skipped`NutrientGapMatch`S2.5Biomarker → nutrient matchbiomarker_id, nutrient, severity, deviation_pct`NutrientCandidate`S2.5Pre-validated supplement candidatesupplement_name, gap_matches, priority_score, safety_status`InteractionFlag`S2.5Drug/supplement interactionsupplement, interacts_with, severity, mechanism`PrioritizedSupplement`S3LLM-selected supplementrank, supplement_name, specific_dose, form, rationale, retest_weeks`SupplementGapAnalysisOutput`S3Analysis resultspriority_supplements, validation_notes`DailyScheduleSlot`S4Time slot in scheduletime_of_day, supplements, instructions`SafetyNote`S4Patient safety notecategory, severity, note`RetestItem`S4Retest recommendationbiomarker_name, retest_weeks, reason`SupplementProtocolOutput`S4Final protocol (render input)headline, protocol_summary, overall_status, daily_schedule, safety_notes, retest_timelineLibrary Dependency Map
Section titled “Library Dependency Map”Forge-sentinel libraries used at each stage. = deterministic stage, = LLM stage.
LibraryS0S1S2S2.5S3S4S5
chr-core
chr-data
chr-llm
chr-html
chr-styles
chr-frontend
chr-logging
chr-document-manager
chr-pdf
chr-langfuse
chr-verify
chr-resilienceLibrary Details
Section titled “Library Details”chr-coreException hierarchy, CHRBaseSettings, PII sanitization. Transitive dependency via chr-llm.
chr-dataAsync N1 API fetching, pagination, typed models, biomarker reference data (optimal ranges, body systems, medication markers, derived formulas, unit conversion).
chr-llmLLM calls with billing, JSON parsing, token tracking. `allm_json_call()` for stages 3-4, `load_prompt()` for .md prompt files.
chr-htmlHTML fragment/document builders. `html_document()`, supplement theme cards, schedule slots, escaping utilities.
chr-stylesCSS loader (base + type + override). `get_report_css("single-page", "supplements")`.
chr-frontendProgress tracking, UI phases, user error messages. `ProgressReporter`, `UIPhase`, `BaseProgressTracker`.
chr-loggingStructured JSON logging, PII sanitization, context vars. `configure_logging()`, `bind_request_context()`.
chr-document-managerS3/GCS upload, signed URLs, publish pipeline. `publish_report()` for HTML write + PDF render + cloud upload.
chr-pdfHTML→PDF rendering, compression. `WeasyPrintRenderer` via `publish_report()`.
chr-langfuseLangfuse LLM tracing (spans, cost, prompt visibility). `traced_stage()` wraps stages 3-4 LLM calls.
chr-verifyHallucination detection (name matching, supplement validation). Post-LLM verification in stages 3-4.
chr-resilienceRetry decorators, error classification, OTEL setup. LLM call retries in stages 3-4.Configuration
Section titled “Configuration”Settings model from config.py using pydantic-settings. All settings can be overridden via environment variables.
Settings Model
**Copyclass Settings(BaseSettings):
# LLMllm_model: str = "gemini-2.5-pro"llm_temperature: float = 0.1llm_max_retries: int = 2llm_timeout: int = 180
# Service identity (for billing metadata)service_name: str = "workflow-supplements-optimization"
# Cascade tuningprefilter_max_output: int = 100max_supplements: int = 8deidentify_pii: bool = True
# Progress reporting (forge mode)n1_api_base_url: str = ""n1_api_key: str = ""sync_with_cloud: bool = True
# Storage (cloud upload via chr-document-manager)bucket_name: str = ""storage_provider: str = "s3"aws_region: str = "us-east-2"skip_uploads: bool = False
model_config = {"env_prefix": "", "env_file": ".env", "extra": "ignore"}Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`LLM_MODEL``gemini-2.5-pro`LLM model for stages 3-4 (via LiteLLM)
`LLM_TEMPERATURE``0.1`Low temperature for consistent dosing
`LLM_MAX_RETRIES``2`Retry count for failed LLM calls
`LLM_TIMEOUT``180`Timeout in seconds per LLM call
`SERVICE_NAME``workflow-supplements-optimization`Service identity for billing metadata
`PREFILTER_MAX_OUTPUT``100`Max biomarker groups selected (50 unhealthy + 50 healthy)
`MAX_SUPPLEMENTS``8`Maximum supplements in final protocol (3-8 range)
`DEIDENTIFY_PII``true`Sanitize PII before LLM calls
`N1_API_BASE_URL`*empty*N1 API base URL (forge mode)
`N1_API_KEY`*empty*N1 API key (forge mode)
`SYNC_WITH_CLOUD``true`Enable cloud progress sync
`BUCKET_NAME`*empty*S3/GCS bucket for report upload
`STORAGE_PROVIDER``s3`Cloud storage provider (s3 or gcs)
`AWS_REGION``us-east-2`AWS region for S3
`SKIP_UPLOADS``false`Skip cloud upload (local mode)
`INPUT_FILE`*none*Path to JSON patient file (local mode)
`USER_ID`*none*Patient user ID (forge mode)
`CHR_ID`*none*CHR session ID (forge mode)Overview
Section titled “Overview”The workflow-predictive-aging** generates a multi-page predictive health report via a 7-stage integrative medicine cascade with 10/20/30-year biomarker projections. It extends the health-summary pipeline with deterministic trend analysis (Stage 2.5), predictive LLM prompts, and projection chart generation (Stage 4.5). The final output is a multi-page HTML/PDF with inline projection charts.
Pipeline Stages
LLM Calls
Forge Libraries
25+Pydantic ModelsPhilosophy: Same as health-summary — pure analysis logic. The key differentiator is Stage 2.5 (deterministic projections via linear regression + population norm blending) and Stage 4.5 (chart generation via chr-charts). Dual-mode: legacy prompts (single snapshot) or predictive prompts (trajectory-focused).
Run Modes
Section titled “Run Modes”Local Mode
Section titled “Local Mode”JSON file → cascade → HTML + PDF. No credentials required.
INPUT_FILE=tests/fixtures/patient-small.json uv run python bin/run.py
Forge Mode
Section titled “Forge Mode”API fetch → cascade → HTML + PDF, with billing, PII de-identification, and cloud upload.
Requires USER_ID, CHR_ID, LiteLLM and N1 API credentials.
Pipeline Flowchart
Section titled “Pipeline Flowchart”4 deterministic stages + 2 LLM calls + 1 chart generation + 1 render. Stages 2.5 and 4.5 are unique to predictive-aging (not present in health-summary). The LLM receives projections and reasons about aging trajectories.
S0Pre-filterDeterministic
chr-data (reference) • Pure scoring: deviation, trend, recency, density
S1Context AssemblyDeterministic
chr-data (reference: optimal ranges, body systems, meds, unit conversion)
S2Derived MarkersDeterministic
chr-data (DERIVED_REGISTRY, unit conversion)
S2.5ProjectionsDeterministic
Linear regression + population norm blending (NHANES/Framingham/CKD-EPI) • 16 clinical thresholds
S3Pattern RecognitionLLM
chr-llm (allm_json_call, load_prompt) • chr-langfuse (tracing) • chr-resilience (retry) • chr-verify
S4Clinical SummaryLLM
chr-llm (allm_json_call, load_prompt) • chr-langfuse (tracing) • chr-resilience (retry) • chr-verify
S4.5Chart GenerationDeterministic
chr-charts (matplotlib → base64 PNG) • Priority scoring: threshold crossings × 10 + severity
S5RenderDeterministic
chr-html (multi-page) • chr-styles (CSS) • chr-pdf (WeasyPrint)Stage Details
Section titled “Stage Details”Pre-filter — Deterministic Scoring
Section titled “Pre-filter — Deterministic Scoring”Groups biomarkers by biomarker ID, scores each group, then selects top 50 unhealthy + top 50 healthy biomarker groups (100 total). Same logic as health-summary.
Input`dict[str, BiomarkerInput]`
Output`Stage0Output`Scoring (same as health-summary)
Section titled “Scoring (same as health-summary)”- worst_deviation (30): Highest deviation across all measurements vs reference midpoint
- trend (20): Linear regression slope across full history (3+ data points required)
- recency (10): ≤2yr = 10, ≤5yr = 5, older = 0
- data_richness (10): ≥5 pts = 10, ≥3 = 7, 2 = 4, 1 = 1
- oor_consistency (10): Percentage of measurements that are abnormal
chr-dataContext Assembly — Functional Enrichment
Section titled “Context Assembly — Functional Enrichment”Enriches scored biomarkers with functional/optimal ranges, body system classification, and medication interaction context. Same logic as health-summary.
Input`Stage0Output + PatientContext?`
Output`ContextEnrichedInput`Key Operations
Section titled “Key Operations”- Unit conversion to conventional units via
convert_to_conventional() - Optimal range lookup via
get_optimal_range(canonical) - Body system mapping via
get_body_systems(canonical) - Medication interaction detection via
get_affected_markers(med_name)
chr-dataDerived Markers — Computed Ratios & Indices
Section titled “Derived Markers — Computed Ratios & Indices”Computes clinically meaningful derived markers (HOMA-IR, TG/HDL, eGFR, NLR) from available biomarkers using DERIVED_REGISTRY. Same logic as health-summary.
Input`ContextEnrichedInput + PatientContext?`
Output`DerivedMarkersOutput`Example Derived Markers
Section titled “Example Derived Markers”- HOMA-IR: (Glucose × Insulin) / 405 — insulin resistance
- TG/HDL Ratio: Triglycerides / HDL — insulin resistance proxy
- eGFR: CKD-EPI formula (requires age + sex) — kidney function
- NLR: Neutrophils / Lymphocytes — inflammation marker
chr-data
2.5Projections — Trend Analysis & 10/20/30-Year Forecasting
Section titled “Projections — Trend Analysis & 10/20/30-Year Forecasting”Fully deterministic stage unique to predictive-aging. Computes blended trends (patient history + population norms) and projects each biomarker 10, 20, and 30 years into the future. Checks 16 clinical thresholds for disease-onset crossings.
Input`ContextEnrichedInput + DerivedMarkersOutput`
Output`ProjectionOutput`Blending Weights (Patient vs Population)
Section titled “Blending Weights (Patient vs Population)”- 5+ data points: 90% patient / 10% population
- 3-4 data points: 70% patient / 30% population
- 2 data points: 50% patient / 50% population
- 1 data point: 20% patient / 80% population
- 0 data points: 0% patient / 100% population norm
Population Norm Sources
Section titled “Population Norm Sources”- NHANES age-stratified means (general biomarkers)
- Framingham equations (cardiovascular markers)
- CKD-EPI age-related decline (kidney function)
Clinical Thresholds (16 rules)
Section titled “Clinical Thresholds (16 rules)”- Glucose: ≥100 pre-diabetic, ≥126 diabetic
- HbA1c: ≥5.7% pre-diabetic, ≥6.5% diabetic
- eGFR: <60 CKD Stage 3, <30 CKD Stage 4
- LDL: ≥160 high, ≥190 very high
- Triglycerides: ≥200 high, ≥500 very high
- TSH: >4.5 hypothyroid, <0.4 hyperthyroid
- ALT: >56 elevated
- Creatinine: >1.3 elevated
- Hemoglobin: <12 anemia
Key Operations
Section titled “Key Operations”- Linear regression on patient measurement history (slope units/year, r²)
- Blended slope = (patient_weight × patient_slope) + (pop_weight × pop_slope)
- projected_value = current + blended_slope × years
- Threshold crossing detection: current below threshold → projected above (or vice versa)
- System-level aggregation: trajectory = “declining” if risk worsens 10y→30y
- Max 50 biomarkers projected (configurable via
projection_max_biomarkers)
chr-dataPattern Recognition — LLM Call #1
Section titled “Pattern Recognition — LLM Call #1”LLM identifies clinical patterns. In predictive mode, it uses projections to assess aging acceleration, threshold crossings, and projected severity at 10/20/30 years. Outputs PredictivePatternOutput with AgingRiskAssessment per pattern and an aging_acceleration_score (0–100). In legacy mode, it produces a standard single-snapshot PatternAnalysisOutput.
Input (predictive)`ContextEnrichedInput + DerivedMarkersOutput + ProjectionOutput`
Output (predictive)`PredictivePatternOutput`Predictive Mode Additions
Section titled “Predictive Mode Additions”- Per-pattern
AgingRiskAssessment: risk narratives at 10y, 20y, 30y + acceleration/deceleration factors - Projected severity at each horizon (critical/significant/monitor)
- Focus on trajectories over snapshots: where is each biomarker heading?
- Cross-system compounding: multiplicative, not additive risks
aging_acceleration_score(0 = protective, 50 = on track, 100 = accelerated)
Post-LLM Validation
Section titled “Post-LLM Validation”- Filter evidence IDs to only those present in input data
- Skip patterns with zero valid supporting evidence
- Enforce 1–4 pattern count
- Hallucination verification via
chr-verify
chr-llmchr-langfusechr-resiliencechr-verifyClinical Summary — LLM Call #2
Section titled “Clinical Summary — LLM Call #2”LLM produces the final structured summary. In predictive mode, findings include projected values at 10/20/30 years, threshold alerts, and recommendations carry time_sensitivity. Outputs PredictiveSummaryOutput with aging_outlook (accelerated/on_track/favorable). In legacy mode, produces standard ClinicalSummaryOutput.
Input (predictive)`PredictivePatternOutput + ContextEnrichedInput + DerivedMarkersOutput + ProjectionOutput`
Output (predictive)`PredictiveSummaryOutput`Predictive Mode Additions
Section titled “Predictive Mode Additions”aging_outlook: accelerated | on_track | favorable- Findings (1–8) include
projected_10y,projected_20y,projected_30yvalues threshold_alertwhen clinical threshold crossing is projected- Recommendations (1–5) include
time_sensitivity: immediate | within_1y | within_5y | long_term impact_descriptionper recommendation (how it affects trajectory)aging_acceleration_score(0–100) echoed from S3- Headline focuses on projected trajectory, not current snapshot
Post-LLM Validation
Section titled “Post-LLM Validation”- Filter findings to valid measurement_ids only
- Cap findings: min 1, max 8
- Cap recommendations: min 1, max 5
- Hallucination verification via
chr-verify - Deterministic override: overall_status based on threshold crossings within 10y → attention_needed
chr-llmchr-langfusechr-resiliencechr-verify
4.5Chart Generation — Projection Visualizations
Section titled “Chart Generation — Projection Visualizations”Fully deterministic stage unique to predictive-aging. Selects key biomarkers for visualization via priority scoring, then renders projection charts using chr-charts (matplotlib → base64 PNG). Two chart types: finding charts (Page 1) and system charts (Page 2).
Input`PredictiveSummaryOutput + ProjectionOutput + ContextEnrichedInput`
Output`list[ProjectionChart]`Finding Charts (Page 1) — max 3
Section titled “Finding Charts (Page 1) — max 3”- Priority: findings with
threshold_alertor severity ≥ significant - Score = threshold_crossings × 10 + severity_weight
- Shows: historical data points + trend line + projected curve + threshold lines
System Charts (Page 2) — max 4
Section titled “System Charts (Page 2) — max 4”- One chart per declining system (trajectory = “declining”)
- Picks worst biomarker per system: most threshold crossings, then highest risk at 30y
- Shows: historical data + projection + lab/optimal range bands + threshold lines
Render Pipeline
Section titled “Render Pipeline”- Patient measurement history → (dates_as_fractional_years, values)
- chr-charts matplotlib renderer → base64 PNG
- Output:
ProjectionChart(biomarker_id, display_name, base64_png)
chr-chartschr-dataRender — Multi-Page HTML
Section titled “Render — Multi-Page HTML”Converts PredictiveSummaryOutput + charts to a multi-page HTML document using chr-html and chr-styles. No LLM-generated HTML — the presentation layer is fully deterministic.
Input`PredictiveSummaryOutput + list[ProjectionChart]`
Output`HTML string → PDF via chr-pdf`Page Layout
Section titled “Page Layout”- Page 1 (Executive): Headline, aging outlook, aging acceleration score, key findings with projected values, up to 3 finding charts
- Page 2 (System Projections): System-level trajectory summaries, up to 4 system charts, threshold crossing alerts
- Page 3+ (Details): Clinical patterns with aging risk assessments, recommendations with time sensitivity, gap recommendations
CSS Pipeline
Section titled “CSS Pipeline”html_document(body, report_type="multi-page", override="predictive-aging")- Base CSS + multi-page type CSS + predictive-aging override CSS layered
- Inline chart images (base64 PNG — no external dependencies)
- PDF rendering via WeasyPrint through
chr-pdf
chr-htmlchr-styleschr-pdfchr-document-managerFull Prompts
Section titled “Full Prompts”Four prompts in dual-mode configuration: predictive (with projections) and legacy (snapshot only). Loaded via chr-llm’s load_prompt(). System message above ---, human template below. Template variables in amber.
predictive_patterns.md — Stage 3 Predictive Mode (aging trajectories)S3 • LLM
**CopyYou are an integrative medicine physician analyzing biomarker data with a focus on aging acceleration. Your task is to identify clinical patterns through the lens of trajectories, threshold crossings, and projected health outcomes at 10, 20, and 30 years.
Always respond with valid JSON only. No markdown, no explanation, just JSON.
Section titled “Always respond with valid JSON only. No markdown, no explanation, just JSON.”Predictive Pattern Analysis
Section titled “Predictive Pattern Analysis”You are analyzing biomarker data enriched with projections for a predictive aging report. The biomarkers have been enriched with functional/optimal ranges, organized by body system, and projected forward using trend analysis. Derived markers and clinical thresholds have been pre-computed.
How to Think About This
Section titled “How to Think About This”- Focus on trajectories, not snapshots. A glucose of 95 matters less than a glucose trending from 85 to 95 over 5 years with a projected crossing of 100 mg/dL in 8 years.
- Use projections as the anchor. Where is each biomarker heading? Which clinical thresholds will be crossed?
- Identify aging acceleration. Which patterns make biological aging faster than chronological aging?
- Consider threshold crossings. A projected crossing from “normal” to “pre-diabetic” in 10 years is clinically actionable NOW.
- Assess risk at each horizon. Current severity may be “monitor” but projected 20-year severity may be “critical”.
- Cross-system compounding. Insulin resistance + declining kidney function + rising inflammation is multiplicative, not additive.
Input Data
Section titled “Input Data”Enriched Biomarkers (with functional ranges and body systems)
Section titled “Enriched Biomarkers (with functional ranges and body systems)”{{enriched_biomarkers_json}}
Derived Markers (computed ratios and indices)
Section titled “Derived Markers (computed ratios and indices)”{{derived_markers_json}}
Biomarker Projections (10/20/30-year trend analysis)
Section titled “Biomarker Projections (10/20/30-year trend analysis)”{{projections_json}}
System Projection Summaries
Section titled “System Projection Summaries”{{system_projections_json}}
{{context_section}}
Data Quality Summary
Section titled “Data Quality Summary”{{data_quality_json}}
- Identify 1-4 clinical patterns focused on aging acceleration.
- For each pattern, provide:
- Root cause hypothesis (max 30 words)
- Supporting evidence (measurement_ids from input)
- Current trajectory AND projected severity at 10y, 20y, 30y
- Aging risk assessment: risk narratives at each horizon, acceleration and deceleration factors
- Affected body systems
- Include gap analysis: what tests would confirm or refute projections?
- Note cross-system connections with compounding risk.
- Use ONLY measurement_ids from input data.
- Provide an aging_acceleration_score (0-100): 0 = protective, 50 = on track for age, 100 = maximally accelerated.
Output Format
Section titled “Output Format”Return a JSON object:
{ "patterns": [```json{"name": "<3-8 words>","severity": "critical" | "significant" | "monitor","confidence": "high" | "medium" | "low","narrative": "<max 80 words, trajectory-focused>","supporting_evidence": ["<measurement_id>", ...],"contradicting_evidence": ["<measurement_id>", ...],"root_cause_hypothesis": "<max 30 words>","body_systems": ["<system>", ...],"trajectory": "worsening" | "improving" | "stable" | "insufficient_data","projected_severity_10y": "critical" | "significant" | "monitor","projected_severity_20y": "critical" | "significant" | "monitor","projected_severity_30y": "critical" | "significant" | "monitor","aging_risk": {"risk_10y": "<max 30 words>","risk_20y": "<max 30 words>","risk_30y": "<max 30 words>","acceleration_factors": ["<factor>", ...],"deceleration_factors": ["<factor>", ...]}}], “gap_analysis”: [
{"test_name": "<name>","reason": "<max 25 words>","related_pattern": "<pattern name>","priority": "high" | "medium" | "low"}],
“cross_system_connections”: [“
```textpredictive_summary.md — Stage 4 Predictive Mode (multi-page, projected values)S4 • LLM
CopyYou are writing the final content for a multi-page predictive aging health report. You have clinical patterns with aging risk assessments, enriched biomarkers, derived markers, and 10/20/30-year projections. Produce the summary content for a report focused on projected health trajectory.
Always respond with valid JSON only. No markdown, no explanation, just JSON.---## Predictive Summary Generation
You are producing the final structured output for a predictive aging report. This report spans multiple pages and focuses on WHERE the patient's health is heading, not just where it is now.
### Input Data
#### Clinical Patterns (with aging risk assessments){{patterns_json}}
#### Enriched Biomarkers{{enriched_biomarkers_json}}
#### Derived Markers{{derived_markers_json}}
#### Biomarker Projections{{projections_json}}
{{context_section}}
### Rules
#### Headline- Max 15 words. Focus on PROJECTED trajectory.- Example: "Metabolic markers project diabetes risk within 10 years without intervention"
#### Aging Outlook- "accelerated": aging faster than chronological age (score > 65)- "on_track": aging at expected rate (score 35-65)- "favorable": aging slower than expected (score < 35)
#### Key Findings (1-8)- Include current value AND projected values at 10y, 20y, 30y (from deterministic projections)- Include threshold_alert when a clinical threshold crossing is projected- Do NOT invent projected values — use only from projections input- Prioritize findings with threshold crossings and high severity
#### Recommendations (1-5)- Include time_sensitivity: "immediate" | "within_1y" | "within_5y" | "long_term"- Include impact_description (max 25 words): how this affects the aging trajectory- CRITICAL: Do NOT suggest medications the patient is already taking
#### Healthy Highlights (0-4)- Biomarkers with favorable projected trajectories
#### Overall Status- "attention_needed": threshold crossings within 10y- "monitoring_recommended": threshold crossings within 20y- "generally_healthy": no threshold crossings or only 30y+
#### Trajectory Summary- Max 25 words. Overall aging trajectory summary.
### Output Format
Return a JSON object:{ “headline”: “<max 15 words, trajectory-focused>”, “aging_outlook”: “accelerated” | “on_track” | “favorable”, “aging_acceleration_score”: 0-100, “key_findings”: [
{"measurement_id": "<from input>","display_name": "<biomarker name>","value": "<current value with unit>","severity": "critical" | "significant" | "monitor","trend": "worsening" | "improving" | "stable" | "insufficient_data","context": "<max 15 words>","functional_note": "<or empty>","pattern_ref": "<pattern name or null>","projected_10y": "<projected value with unit>","projected_20y": "<projected value with unit>","projected_30y": "<projected value with unit>","threshold_alert": "<threshold label or empty>"}], “healthy_highlights”: [
{"display_name": "<biomarker name>","value": "<value with unit>","note": "<max 12 words>"}], “recommendations”: [
{"priority": 1-5,"category": "test" | "lifestyle" | "discuss" | "monitor" | "urgent","action": "<max 30 words>","rationale": "<max 20 words>","pattern_ref": "<related pattern>","time_sensitivity": "immediate" | "within_1y" | "within_5y" | "long_term","impact_description": "<max 25 words>"}], “gap_recommendations”: [
{"test_name": "<test>","reason": "<max 25 words>","priority": "high" | "medium" | "low"}], “overall_status”: “attention_needed” | “monitoring_recommended” | “generally_healthy”, “trajectory_summary”: “<max 25 words>” }
pattern_recognition.md — Stage 3 Legacy Mode (single snapshot, no projections)S3 • LLM
CopyYou are an integrative medicine physician analyzing biomarker data. Your task is to identify clinical patterns — connections across biomarkers that reveal underlying health stories. Think like a functional medicine doctor: look at the whole picture, ask "why", and identify what's missing.
Always respond with valid JSON only. No markdown, no explanation, just JSON.---## Clinical Pattern Analysis
You are analyzing biomarker data for a patient health summary. The biomarkers have been enriched with functional/optimal ranges (tighter than standard lab ranges) and organized by body system. Derived markers (ratios, indices) have been computed where data permits.
### How to Think About This
1. **Look for clusters, not isolated values.** Three markers in the same direction across two systems tells a story.2. **Use functional ranges first.** A glucose of 95 is "normal" by lab standards but already suboptimal functionally.3. **Consider root causes.** Elevated glucose + elevated triglycerides + low HDL isn't three problems — it's insulin resistance.4. **Note what's missing.** Elevated inflammatory markers but no thyroid panel is a gap worth noting.5. **Assess trajectory.** A value worsening over 3 data points matters more than a single snapshot.
### Input Data
#### Enriched Biomarkers{{enriched_biomarkers_json}}
#### Derived Markers{{derived_markers_json}}
{{context_section}}
#### Data Quality Summary{{data_quality_json}}
### Rules
1. Identify 1-4 clinical patterns. Each must reference ≥1 biomarker by measurement_id.2. Provide root cause hypothesis, supporting/contradicting evidence, trajectory, body systems.3. Include gap analysis and cross-system connections.4. Use ONLY measurement_ids from input data.
### Output Format
Return JSON: `{ "patterns": [...], "gap_analysis": [...], "cross_system_connections": [...] }`clinical_summary.md — Stage 4 Legacy Mode (single page, 6 findings max)S4 • LLM
CopyYou are writing the final content for a 1-page patient health summary. You have clinical patterns, enriched biomarker data, and derived markers. Produce the summary content that will be rendered into the report.
Always respond with valid JSON only. No markdown, no explanation, just JSON.---## Clinical Summary Generation
You are producing the final structured output for a patient health summary report. This is a single-page report — content must fit on one A4 page.
### Input Data
#### Clinical Patterns{{patterns_json}}
#### Enriched Biomarkers{{enriched_biomarkers_json}}
#### Derived Markers{{derived_markers_json}}
{{context_section}}
### Rules
- **Headline** (max 15 words): most clinically significant finding- **Key Findings** (1-6): severity, trend, context, functional_note, pattern_ref- **Healthy Highlights** (0-4): biomarkers in good health- **Recommendations** (1-3): category, rationale, pattern_ref. Do NOT suggest meds already being taken.- **Gap Recommendations** (0-5): missing tests- **Overall Status**: attention_needed | monitoring_recommended | generally_healthy- **Trajectory Summary** (max 20 words)
### Output Format
Return JSON: `{ "headline": "...", "key_findings": [...], "healthy_highlights": [...], "recommendations": [...], "gap_recommendations": [...], "overall_status": "...", "trajectory_summary": "..." }`Data Contracts
Section titled “Data Contracts”All Pydantic models live in cascade/models.py. Models grouped by the stage that produces them. Shared models (Input, S0-S2) are identical to health-summary; predictive-specific models highlighted below.
Input Models (shared)
Section titled “Input Models (shared)”BiomarkerInput, CascadeInput, PatientContext — same as health-summary
Copyclass BiomarkerInput(BaseModel):
test_name: strmeasurement_id: strvalue: strstatus: strreference_range: strunit: str | None = Nonetest_date: str | None = Noneall_measurements: list[dict[str, Any]] | None = Nonefile_name: str | None = Nonebiomarker_id: str | None = Nonerecord_id: str | None = Noneadditional_data: dict[str, Any] = {}unit_mismatch: bool = Falseclass PatientContext(BaseModel):
patient_name: str = "Patient"age: int | None = Nonegender: str | None = Nonediagnoses: list[dict[str, Any]] = []medications: list[dict[str, Any]] = []procedures: list[dict[str, Any]] = []genetics: list[dict[str, Any]] = []report_date: str = ""enabled_types: set[str]Stage 2.5 Output — Projections
Section titled “Stage 2.5 Output — Projections”TrendAnalysis, TimeHorizonProjection, BiomarkerProjection, SystemProjectionSummary, ProjectionOutput
Copyclass TrendAnalysis(BaseModel):```text"""Blended trend from patient data + population norms."""slope: float # annual rate of change in native unitsdirection: Literal["increasing", "decreasing", "stable"] = "stable"confidence: Literal["high", "medium", "low"] = "low"source: Literal["patient_data", "population_norm", "blended"] = "population_norm"r_squared: float | None = Nonedata_points: int = 0class TimeHorizonProjection(BaseModel):
"""Single time-horizon projection for a biomarker."""years: int # 10, 20, or 30projected_value: floatprojected_status: Literal["optimal", "within_lab", "outside_optimal", "outside_lab", "unknown"] = "unknown"risk_level: Literal["low", "moderate", "high", "critical"] = "low"threshold_crossing: str = "" # clinical threshold label if crossed, else ""class BiomarkerProjection(BaseModel):
"""Full projection for a single biomarker across all horizons."""biomarker_id: strdisplay_name: strcurrent_value: floatunit: strtrend: TrendAnalysisprojections: list[TimeHorizonProjection] = [] # at 10, 20, 30 yearsclinical_significance: str = ""class SystemProjectionSummary(BaseModel):
"""System-level aggregation of biomarker projections."""system_name: strtrajectory: Literal["declining", "stable", "improving"] = "stable"risk_10y: Literal["low", "moderate", "high", "critical"] = "low"risk_20y: Literal["low", "moderate", "high", "critical"] = "low"risk_30y: Literal["low", "moderate", "high", "critical"] = "low"threshold_crossings: list[str] = []biomarker_ids: list[str] = []class ProjectionOutput(BaseModel):
"""Output of Stage 2.5: Projections."""biomarker_projections: list[BiomarkerProjection] = []system_summaries: list[SystemProjectionSummary] = []projection_horizons: list[int] = [10, 20, 30]patient_age: int | None = Nonedata_source_summary: dict[str, int] = {} # count by source type### Stage 3 Output — Predictive Patterns
AgingRiskAssessment, PredictivePattern, PredictivePatternOutput
```textCopyclass AgingRiskAssessment(BaseModel):```text"""Per-pattern aging risk narratives at each projection horizon."""risk_10y: str = "" # max 30 wordsrisk_20y: str = "" # max 30 wordsrisk_30y: str = "" # max 30 wordsacceleration_factors: list[str] = []deceleration_factors: list[str] = []class PredictivePattern(ClinicalPattern):
"""Extends ClinicalPattern with aging risk projections."""aging_risk: AgingRiskAssessment = AgingRiskAssessment()projected_severity_10y: Literal["critical", "significant", "monitor"] = "monitor"projected_severity_20y: Literal["critical", "significant", "monitor"] = "monitor"projected_severity_30y: Literal["critical", "significant", "monitor"] = "monitor"class PredictivePatternOutput(BaseModel):
"""Output of Stage 3 in predictive mode."""patterns: list[PredictivePattern] # 1-4gap_analysis: list[GapAnalysisItem] = []cross_system_connections: list[str] = []aging_acceleration_score: int = 50 # 0-100### Stage 4 Output — Predictive Summary
PredictiveFinding, PredictiveRecommendation, PredictiveSummaryOutput
```textCopyclass PredictiveFinding(SummaryFinding):```text"""Extends SummaryFinding with projected values."""projected_10y: str = ""projected_20y: str = ""projected_30y: str = ""threshold_alert: str = ""class PredictiveRecommendation(Recommendation):
"""Extends Recommendation with time sensitivity."""time_sensitivity: Literal["immediate", "within_1y", "within_5y", "long_term"] = "long_term"impact_description: str = "" # max 25 wordsclass PredictiveSummaryOutput(BaseModel):
"""Output of Stage 4 in predictive mode — everything for multi-page render."""headline: str # max 15 wordsaging_outlook: Literal["accelerated", "on_track", "favorable"] = "on_track"key_findings: list[PredictiveFinding] # 1-8healthy_highlights: list[HealthyHighlight] = [] # up to 4recommendations: list[PredictiveRecommendation] # 1-5gap_recommendations: list[GapRecommendation] = [] # 0-5overall_status: Literal["attention_needed", "monitoring_recommended", "generally_healthy"]trajectory_summary: str = "" # max 25 wordspatterns: list[PredictivePattern] = []aging_acceleration_score: int = 50 # 0-100html: str = "" # populated by render, not LLM### Stage 4.5 Output — Charts
ProjectionChart (NamedTuple)
```textCopyclass ProjectionChart(NamedTuple):```text"""A rendered projection chart for a single biomarker."""biomarker_id: strdisplay_name: strbase64_png: str # full data URI: data:image/png;base64,...## Library Dependency Map
Forge-sentinel libraries used at each stage. = deterministic stage, = LLM stage, = predictive-aging unique stage.
```textLibraryS0S1S2S2.5S3S4S4.5S5
chr-core
chr-data
chr-llm
chr-charts
chr-html
chr-styles
chr-pdf
chr-verify
chr-resilience
chr-langfuse
chr-frontend
chr-logging
chr-document-managerLibrary Details
Section titled “Library Details”chr-coreException hierarchy (RetryableError, NonRetryableError), CHRBaseSettings, PII sanitization. Transitive dependency via chr-llm.
chr-dataAsync N1 API fetching, transforms, biomarker reference data (optimal ranges, body systems, medication markers, derived formulas, unit conversion). Used in S0-S2, S2.5 (population norms), S4.5 (historical data lookup).
chr-llmLLM calls with billing, JSON parsing, token tracking. `allm_json_call()` for S3-S4, `load_prompt()` for .md prompt files. Auto-prefix bare model names with `openai/` when proxy enabled.
chr-chartsChart generation via matplotlib. Renders projection charts with historical data points, trend lines, projected curves, lab/optimal range bands, and clinical threshold lines. Output: base64 PNG.
chr-htmlHTML fragment/document builders. `html_document()` with multi-page support for predictive-aging.
chr-stylesCSS loader. `get_report_css("multi-page", "predictive-aging")`.
chr-pdfHTML→PDF rendering, compression. `WeasyPrintRenderer` via `publish_report()`.
chr-verifyHallucination detection: name matching, timeline validation, projected value verification. Integrated at S3-S4 post-LLM validation.
chr-resilienceRetry decorators (`@retry_llm`, `@retry_api`) on LLM calls in S3-S4. RetryBudget for cascading failure protection.
chr-langfuseLangfuse LLM tracing (spans, cost, prompt visibility). `traced_stage()` wraps S3-S4 LLM calls.
chr-frontendProgress tracking, UI phases. `ProgressReporter`, `UIPhase`. Used across all stages.
chr-document-managerS3/GCS upload, signed URLs, publish pipeline. `publish_report()` for HTML + PDF + cloud upload.Configuration
Section titled “Configuration”Pydantic Settings in src/predictive_aging/config.py. All settings overridable via environment variables. Predictive-aging-specific fields highlighted.
Settings Model
Copyclass Settings(BaseSettings):
# LLMllm_model: str = "gemini-2.5-pro"llm_temperature: float = 0.1llm_timeout: int = 180
# Service identityservice_name: str = "workflow-predictive-aging"
# Cascade tuningprefilter_max_output: int = 100
# Projection settings (predictive-aging specific)projection_horizons: list[int] = [10, 20, 30]projection_max_biomarkers: int = 50
# PII de-identification (predictive-aging specific)deidentify_pii: bool = True
# Progress reporting (forge mode)n1_api_base_url: str = ""n1_api_key: str = ""sync_with_cloud: bool = True
# Storage (cloud upload)bucket_name: str = ""storage_provider: str = "s3"aws_region: str = "us-east-2"skip_uploads: bool = False
model_config = {"env_prefix": "", "env_file": ".env", "extra": "ignore"}Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`LLM_MODEL``gemini-2.5-pro`LLM model for stages 3-4 (via LiteLLM)
`LLM_TEMPERATURE``0.1`Low temperature for consistent clinical output
`LLM_TIMEOUT``180`Timeout in seconds per LLM call
`SERVICE_NAME``workflow-predictive-aging`Service identity for billing metadata
`PREFILTER_MAX_OUTPUT``100`Max biomarker groups selected (50 unhealthy + 50 healthy)
`PROJECTION_HORIZONS``[10, 20, 30]`Years to project forward (predictive-aging specific)
`PROJECTION_MAX_BIOMARKERS``50`Max biomarkers to project (predictive-aging specific)
`DEIDENTIFY_PII``true`Replace patient names with pseudonyms (predictive-aging specific)
`N1_API_BASE_URL`*empty*N1 API base URL (forge mode)
`N1_API_KEY`*empty*N1 API key (forge mode)
`SYNC_WITH_CLOUD``true`Enable cloud progress sync
`BUCKET_NAME`*empty*S3/GCS bucket for report upload
`STORAGE_PROVIDER``s3`Cloud storage provider (s3 or gcs)
`AWS_REGION``us-east-2`AWS region for S3
`SKIP_UPLOADS``false`Skip cloud upload (local mode)
`INPUT_FILE`*none*Path to JSON patient file (local mode)
`USER_ID`*none*Patient user ID (forge mode)
`CHR_ID`*none*CHR session ID (forge mode)Overview
Section titled “Overview”Easy CHR** generates an interactive, multi-section health report from raw medical PDFs (lab results, imaging, clinical notes) via a 7-phase agentic pipeline. Four phases use iterative LLM tool-calling loops (where the model explores data, forms hypotheses, and builds output incrementally), one phase uses a single LLM call, and the final rendering is fully deterministic (Nunjucks templates — no LLM involvement). The output is a self-contained HTML report with Chart.js visualisations and a 3D body-twin viewer.
Pipeline Phases
Agentic Phases
32+LLM Tools
25+TypeScript InterfacesPhilosophy: This is a deep agentic reasoning system, not a simple prompt→response pipeline. Each phase uses an iterative tool-calling loop where the LLM decides which tool to call, receives results, reasons about them, and iterates — often for 25–50 cycles. External state (Maps, Sets) preserves work product through chat compression, so the agent can lose conversation memory but never its output.
Run Modes
Section titled “Run Modes”Local Mode
Section titled “Local Mode”Upload PDFs via React UI → SSE-streamed pipeline → HTML report.
POST /api/realm with multipart files. Requires GEMINI_API_KEY.
Forge Mode (K8s Job)
Section titled “Forge Mode (K8s Job)”N1 API data fetch → pipeline → HTML + S3 upload + signed URL.
Requires USER_ID, CHR_ID, N1 API + S3 credentials.
Three Entry Points
Section titled “Three Entry Points”execute()
Section titled “execute()”Full pipeline: Raw PDFs → OCR extraction → all 7 phases. Used by HTTP upload path.
executeWithExtractedContent()
Section titled “executeWithExtractedContent()”Skips Phase 1: Pre-extracted markdown from N1 API → Phases 2–7. Used by job runner.
Pipeline Flowchart
Section titled “Pipeline Flowchart”4 agentic phases (iterative LLM tool-calling loops) + 1 single-shot LLM call + 1 OCR extraction + 1 deterministic render. Each agentic phase uses external state that survives chat compression — the agent can lose conversation history but never its work product.
P1Document ExtractionDeterministic + OCR
Gemini Vision • PDF → Markdown with page numbers
P2Agentic Medical AnalysisAgentic • 50 iter
8+ tools • Explore → Hypothesise → Cross-reference → Synthesise
P3Agentic Research & ValidationAgentic • 50 iter
5 tools • Web search → Fetch primary sources → Record verdicts
P4Agentic Data StructuringAgentic • 25 iter
8 tools • Builds structured_data.json incrementally (SOURCE OF TRUTH)
P5Agentic ValidationAgentic • 15 iter
11+ tools • Verification-focused: verify_value_exists, compare_date_ranges
P6Organ InsightsSingle LLM Call
Per-organ markdown • Bridge between CHR and 3D body twin
P7Deterministic HTML RenderDeterministic
Nunjucks + Tailwind (inline) + Chart.js + Cytoscape.js • Milliseconds, 100% fidelityHow Each Agentic Phase Works
Section titled “How Each Agentic Phase Works”Phases 2–5 all follow the same iterative tool-calling pattern. The LLM is given tools, decides which to call, receives results, and iterates until it calls a completion tool.
The Agentic Loop (shared pattern)
Section titled “The Agentic Loop (shared pattern)”- 1. Send history + tools to LLM via
generateContentStreaming()(keeps LiteLLM proxy alive) - 2. LLM responds with tool calls (e.g.
search_data("platelets")) or text - 3. Execute each tool against external state (Maps, Sets); add results to conversation
- 4. Check completion — did LLM call the
complete_*tool? If yes, validate and return - 5. Check compression — if tokens > 50% of 1M limit, compress oldest 70%, keep newest 30%
- 6. Repeat until completion or max iterations reached
External State (survives compression)
Section titled “External State (survives compression)”- Medical Analyst:
currentAnalysis: Map<string, string>— exploration notes and synthesis sections - Research Agent:
findings: Map,searchesPerformed: Set,documentsRead: Set - Data Structurer:
currentJson: Map<string, unknown>— the JSON being built section-by-section - Validator:
validationIssues: Array— accumulated issues
Why Streaming?
Section titled “Why Streaming?”- Gemini 3 Pro thinking takes 2–3 minutes on complex function calls
- LiteLLM gateway has a ~5 minute TTFT (time-to-first-token) timeout
generateContentStreaming()sends first token in seconds, preventing timeout cascade
Stage Details
Section titled “Stage Details”Document Extraction — PDF to Markdown
Section titled “Document Extraction — PDF to Markdown”Converts raw PDFs into a single combined markdown file. Each document gets a ## [filename.pdf] header. For N1 API sources, pre-extracted markdown is fetched directly (skipping OCR).
Input`PDF/TXT/CSV files *or* N1 API record IDs`
Output`extracted.md (~460KB combined)`How It Works
Section titled “How It Works”- PDFs: Gemini Vision OCR (
gemini-2.5-flash) processes each page, adds page numbers - Text files: Direct read with
## [FileName]section markers - N1 API: Pre-extracted markdown via
/records/{id}/markdown(fast path, avoids OCR cost) - Mixed mode: Combine pre-extracted + OCR for records with partial extraction
- All content combined into one
extracted.mdfile
Challenges
Section titled “Challenges”- Multi-language content (English, Chinese, Malay) in same table
- Inconsistent units across labs (
g/dLvsg/L,mmol/Lvsmg/dL) - OCR artefacts and formatting inconsistencies
- Reference ranges vary by lab provider
@google/genaigemini-2.5-flashAgentic Medical Analysis — Explore, Hypothesise, Synthesise
Section titled “Agentic Medical Analysis — Explore, Hypothesise, Synthesise”An Integrative Systems Physician agent iteratively explores 17+ years of medical records. It reads documents oldest→newest, forms hypotheses, cross-references findings across body systems, and produces a comprehensive medical narrative.
Input`extracted.md (via tools) + patient question`
Output`analysis.md (medical narrative)`How the Agent Reasons (Two Modes)
Section titled “How the Agent Reasons (Two Modes)”- MODE A — Exploration (Phases 1–4 internally): Read documents oldest→newest via
read_document() - Write descriptive exploration notes (e.g. “OAT Panel Jul 2025”, “Cross-System Pattern: Methylation → Coagulation”)
- For every marker with ≥2 occurrences: call
get_value_history()to track trends - Perform ≥10 searches, write ≥8 exploration notes
**MODE B — Synthesis (Phase 5 internally):**- Write final synthesis sections with
replace=true - Required: Executive Summary, System-by-System Analysis, Timeline, Root Cause Hypothesis, Causal Chain, Keystone Findings, Recommendations, Missing Data
Available Tools (8+)
Section titled “Available Tools (8+)”list_documents()— inventory of all source docsread_document(name)— full text of one document sectionsearch_data(query)— case-insensitive substring search with ±2 line contextget_value_history(marker)— all historical values, chronologically sorted (for trend detection)get_analysis()/get_section(name)— read current progressupdate_analysis(section, content, replace?)— write to external state Mapget_date_range()/list_documents_by_year()/extract_timeline_events()— temporal awarenesscomplete_analysis(summary, confidence)— validates synthesis sections exist, signals done
Data Fidelity Rule
Section titled “Data Fidelity Rule”- Every extracted value must include: exact number + unit + reference range + flag
- Format:
TSH: 2.3 mIU/L (ref 0.4-4.0) *L
gemini-3-pro-preview50 max iterationschat compressionAgentic Research & Web Validation — Evidence Grounding
Section titled “Agentic Research & Web Validation — Evidence Grounding”An Agentic Medical Research Specialist searches the web to ground claims from the analysis with authoritative external evidence. It searches iteratively, fetches and reads primary sources, and records honest verdicts (supported / unsupported / contested).
Input`analysis.md`
Output`research.json (claims + sources)`How the Agent Reasons
Section titled “How the Agent Reasons”- Survey: Identify 12–20 claim candidates from analysis, prioritise by centrality and actionability
- Search: Multiple searches per claim with different formulations (mechanism, evidence quality, guideline position)
- Read: Fetch primary sources (papers, guidelines, institution pages) via
fetch_document()— up to 8KB each - Record:
record_finding(claim, verdict, confidence, evidence, sources)with honest verdicts - Audit:
get_research_state()to check coverage before completing - “Depth over speed” —
contestedandunsupportedverdicts are valuable findings, not failures
Available Tools (5)
Section titled “Available Tools (5)”search_web(query)— Gemini built-in web search; resolves Google redirect URLsfetch_document(url)— reads up to 8KB of content, strips HTMLrecord_finding(claim, verdict, confidence, evidence, sources)— stores in external Findings Mapget_research_state()— audit progress (claims covered, searches done, findings count)complete_research(summary)— validates substantive summary, minimum coverage
Source Classification
Section titled “Source Classification”journal: pubmed, ncbi, nejm, lancetguideline: who.int, cdc.gov, nih.govinstitution: mayoclinic, clevelandcliniceducation: uptodate, medscape
gemini-3-pro-preview50 max iterationsweb searchAgentic Data Structuring — Building the Source of Truth
Section titled “Agentic Data Structuring — Building the Source of Truth”A Data Extraction Specialist agent transforms the medical narrative and research findings into a single, precisely structured JSON object (structured_data.json). This JSON drives the deterministic HTML renderer — every field must be accurate and complete.
Input`analysis.md (inline) + research.json (inline) + extracted.md (tools)`
Output`structured_data.json (25+ fields, Zod-validated)`Source Priority (conflict resolution)
Section titled “Source Priority (conflict resolution)”- Priority 1 — Analysis (inline): Primary for clinical reasoning, interpretations, causal chains
- Priority 2 — Research (inline): Citations, validated claims
- Priority 3 — Raw Source (via tools): Original values, dates, reference ranges
How JSON is Built Incrementally
Section titled “How JSON is Built Incrementally”- Agent reads analysis + research in context window
- Calls
update_json_section("executive_summary", '{...}')for object sections - Calls
append_to_section("data_gaps", '[{...}, {...}]')for arrays (3–5 items/batch) - Calls
search_source()andget_value_history()to verify values from raw source - Periodically calls
get_json_draft()to check completeness - Calls
complete_structuring()when all required sections present
Mandatory Tool Calls for detailed_findings
Section titled “Mandatory Tool Calls for detailed_findings”- For EVERY body system:
search_source("[system] diagnosis")+search_source("[system] biomarker") - For EVERY biomarker trend: MUST call
get_value_history("[marker]")— never write trend data from memory - If
get_value_history()returns <2 data points → exclude frombiomarker_trends[](charts need ≥2)
Available Tools (8)
Section titled “Available Tools (8)”search_source(query)— search extracted.mdget_value_history(marker)— all historical values for trend buildingget_date_range()/list_source_documents()— orientationupdate_json_section(section, data)— set object fields in external Mapappend_to_section(section, items)— add to array fields (1–5 per call)get_json_draft()— review progresscomplete_structuring(summary)— validates required sections: executiveSummary, criticalFindings, timeline, diagnoses, systemsHealth
Post-Processing: Zod Validation
Section titled “Post-Processing: Zod Validation”- Output validated through
report.zod.tswith intelligent coercion normalizeKeysToSnakeCase()— camelCase → snake_case recursivelyz.coerce.number()— string “6.5” → number 6.5.catch("neutral")— invalid enum values default instead of failingflatEventsToEras()— flat event arrays → hierarchical era/group/findings- Reference filtering — strips entries without
https://URIs
gemini-3-pro-preview25 max iterationszodAgentic Validation — Verification-Focused QA
Section titled “Agentic Validation — Verification-Focused QA”A QA Specialist agent verifies that structured_data.json accurately reflects the source data. Unlike the analyst which explores, the validator verifies. Tools return summaries and verification results (not full content) to prevent payload bloat.
Input`structured_data.json + extracted.md + analysis.md`
Output`validation.md + corrected JSON (if needed)`Verification Workflow (7 Steps)
Section titled “Verification Workflow (7 Steps)”- 1. Orientation:
list_documents()+get_json_overview()+compare_date_ranges() - 2. Timeline check:
find_missing_timeline_years()→ flag gaps as critical issues - 3. Numeric check: For each document →
get_document_summary()→ for each abnormal value →verify_value_exists() - 4. Array check: Verify counts in criticalFindings, diagnoses, trends
- 5. Qualitative check: Search for medications, supplements, symptoms
- 6. PII check: Any full name, DOB, SSN in JSON → critical issue
- 7. Complete:
complete_validation(status)— pass / pass_with_warnings / needs_revision
Primary Verification Tool
Section titled “Primary Verification Tool”verify_value_exists(marker, expected_value?)returns:{ existsInSource, existsInJson, sourceValue, jsonValue, match }- Combined check: is value in extracted.md AND in structured_data.json? Do they agree?
Correction Mechanism
Section titled “Correction Mechanism”- If
needs_revision: surgical JSON patches viadeepMergeJsonPatch() - Faster and more precise than full regeneration
gemini-3-pro-preview15 max iterations11+ toolsOrgan Insights — Bridge to 3D Body Twin
Section titled “Organ Insights — Bridge to 3D Body Twin”Single LLM call that generates per-organ markdown from structured_data.json. This is the bridge between the HTML report and the 3D body twin visualisation.
Input`structured_data.json`
Output`organ_insights.md → body-twin.json`Per-Organ Sections
Section titled “Per-Organ Sections”## Organ Nameheaders with Status (critical/warning/stable/optimal), Confidence, Markers table- Clinical findings, cross-organ connections, clinical implications
BodyTwinTransformerparses markdown into typedBodyTwinDatafor Three.js viewer- 14 body systems, per-organ health scores, cross-organ connection edges
gemini-3-pro-previewsingle callDeterministic HTML Render — Nunjucks Templates
Section titled “Deterministic HTML Render — Nunjucks Templates”Pure data transformation — no LLM calls. Takes validated JSON, computes a section manifest (which sections have enough data to render), renders Nunjucks templates, and compiles Tailwind CSS inline. Output is a self-contained HTML file with no external CDN dependencies.
Input`StructuredReportParsed (Zod-validated) + organ_insights.md`
Output`index.html (self-contained, interactive)`Rendering Pipeline
Section titled “Rendering Pipeline”- 1. Compute Section Manifest: Boolean map of which sections render (threshold-based rules)
- 2. Nunjucks Templates:
base.html.njk+ 13 section includes gated by manifest - 3. Custom Filters:
format_bold,format_citations,paragraphs,trend_chart_data,score_level - 4. Tailwind CSS: PostCSS compiles used classes into inline
<style>— no CDN dependency - 5. Charts: Chart.js initialised client-side from embedded
data-trend-chartJSON attributes
Section Manifest Thresholds
Section titled “Section Manifest Thresholds”conditions: total_count ≥ 1timeline: eras.length ≥ 2biological_story: graph_edges ≥ 1 AND graph_nodes ≥ 2- Treatment sub-tabs: each independently gated by array length > 0
- If manifest value is
false: section completely absent (no heading, no empty state)
Report Sections (13 templates)
Section titled “Report Sections (13 templates)”- Executive Summary (SBAR hero) • Key Metrics (system scores) • Conditions (priority groups)
- Key Visualisations (radar, gauge, donut, heatmap) • Timeline (era-based) • Biological Story (Cytoscape.js)
- Detailed Findings (tabbed per-system with sub-tabs) • Treatment Plan • Prognosis
- Long-Term Management • Organ Insights • Data Gaps • References
nunjuckstailwindcsspostcsschart.jscytoscape.jsHow Data Flows: Concrete Example
Section titled “How Data Flows: Concrete Example”A single platelet value flowing through all 7 phases illustrates how the system reasons about data, verifies it, and renders it.
Platelet Value: End-to-End Journey Through 7 PhasesExample
**Copy═══ PHASE 1: Extraction ═══════════════════════════════════════════════ Source PDF (20070912.pdf) is OCR’d by Gemini Vision:
[20070912.pdf]
Section titled “[20070912.pdf]”| Platelets | 159 | x10^9/L | ...
═══ PHASE 2: Medical Analysis (Agentic) ══════════════════════════════ Agent calls tools iteratively:
read_document(“20070912.pdf”) → sees platelets: 159 get_value_history(“Platelets”) → returns 8 values across 17 years update_analysis(“Hematologic Observations”, “Platelets: 159 x10^9/L (2007)…”)
Agent writes synthesis: “Chronic mild thrombocytopenia (140-160 range) since 2007”
═══ PHASE 3: Research (Agentic) ══════════════════════════════════════ Agent searches web and reads primary sources:
search_web(“chronic mild thrombocytopenia benign ethnic”) fetch_document(“https://pubmed.ncbi.nlm.nih.gov/…”) → reads abstract record_finding(“Chronic mild thrombocytopenia”, “supported”, “high”, …)
═══ PHASE 4: Data Structuring (Agentic) ══════════════════════════════ Agent reads analysis inline, then verifies via tools:
search_source(“platelets diagnosis”) → Finds: “mild thrombocytopenia” in PBF reports from 2011, 2014, 2017
get_value_history(“Platelet”) → Returns 8 data points: 159(2007), 176.9(2009), 163(2010), 148(2011), 172(2013), 149(2014), 141(2017), 138(2025)
update_json_section(“detailed_findings”, ’{ “systems”: [{
"name": "Hematologic", "score": 42, "level": "warning","biomarker_trends": [{"name": "Platelets","data_points": [{"date":"2007-09-12","value":159,"unit":"x10^9/L","status":"Low"},...8 total...],"arrow": "↓", "trend": "Gradual decline over 17 years"}]}] }’)
═══ PHASE 5: Validation (Agentic) ════════════════════════════════════
verify_value_exists(“Platelets”, “159”) → { existsInSource: true, existsInJson: true, match: true }
═══ PHASE 6: Organ Insights ══════════════════════════════════════════ LLM generates per-organ markdown including:
Bone Marrow
Section titled “Bone Marrow”- Status: warning
- Markers: Platelets 138 x10^9/L (↓ trending)
- Cross-organ: Affects clotting cascade, possible splenic sequestration
═══ PHASE 7: Deterministic Render ════════════════════════════════════ Nunjucks template reads JSON, filter serialises for Chart.js:
{% for bt in sys.biomarker_trends | filter_min_points(2) %} {% endfor %}
→ Client-side JS initialises Chart.js line chart with reference range band
How Contradictions are HandledReasoning
CopyThe pipeline handles contradictions at three levels:
── Level 1: Source-level (Phase 2) ─────────────────────────────────────Same biomarker in different units across labs: Lab A: Hemoglobin 14.6 g/dL Lab B: Hemoglobin 146 g/L (same value, different unit)
The analyst agent uses search_data() and get_value_history() to seeALL values. The SKILL.md instructs: "Same biomarker reported in differentunits across labs — always include the unit and reference range."
── Level 2: Analysis vs Source (Phase 4) ───────────────────────────────Priority chain resolves conflicts: Priority 1: Analysis (trusted clinical interpretation) Priority 2: Research (validated citations) Priority 3: Raw Source (exact values via tools)
If analysis says "Platelets 140" but raw source says "Platelets 138":→ Agent calls search_source("platelets") to verify→ Uses the tool result (raw source) for exact value→ Trusts analysis for clinical interpretation
── Level 3: JSON vs Source (Phase 5) ───────────────────────────────────Validator cross-checks every value: verify_value_exists("Platelets", "140") → { existsInSource: true, sourceValue: "138",```textexistsInJson: true, jsonValue: "140",match: false }→ report_issue(“wrong_value”, “warning”, “Platelets: JSON says 140, source says 138”) → Correction applied via deepMergeJsonPatch()
```textHow Chat Compression Preserves WorkInfrastructure
CopyWhen conversation exceeds 50% of 1M token limit:
1. Split history at a safe boundary (after user message, not mid-function)2. Compress oldest ~70% with phase-specific prompt3. LLM outputs a <state_snapshot> summarising progress4. New history = [system prompt] + [snapshot] + [kept 30%]5. Safety: reject if compressed ≥ original size
CRITICAL: External state is NEVER compressed:
Phase 2 currentAnalysis: Map<string, string> ✅ survives Phase 3 findings: Map, searches: Set ✅ survives Phase 4 currentJson: Map<string, unknown> ✅ survives Phase 5 validationIssues: Array ✅ survives
The agent can lose the memory of HOW it decided something,but never the decision itself.
Phase-specific compression prompts: analyst → Key findings, documents explored, analysis state researcher → Claims researched, searches performed structurer → JSON sections written, source lookups validator → Verification results, issues foundData Contracts
Section titled “Data Contracts”All TypeScript interfaces live in server/src/schemas/report.schema.ts. Zod validation in report.zod.ts. Section visibility in section-manifest.ts.
Root Schema
Section titled “Root Schema”StructuredReport — Top-level report structure (25+ fields)
Copyinterface StructuredReport { executive_summary: ExecutiveSummary; // SBAR format — always required key_metrics: KeyMetricsDashboard; // system scores — always required detailed_findings: DetailedFindings; // per-system drilldown — always required conditions: IdentifiedConditions | null; // condition groups timeline: MedicalTimeline | null; // era-based timeline biological_story: BiologicalStory | null; // Cytoscape.js graph treatment_plan: TreatmentPlan | null; // actions, supplements, lifestyle prognosis: Prognosis | null; // outcome scenarios long_term_management: LongTermManagement | null; // checkpoints, monitoring data_gaps: DataGap[] | null; // missing tests references: Reference[] | null; // online sources only organ_insights: string | null; // injected by Phase 6 }
Key Nested Types
Section titled “Key Nested Types”ExecutiveSummary — SBAR clinical communication
Copyinterface ExecutiveSummary { central_theme: string; // single sentence, overall trajectory key_statistics: KeyStatistics; // counts: conditions, diagnoses, biomarkers, date range situation: SBARBullet[]; // 3-5 bullets, ≤15 words each background: SBARBullet[]; // 3-5 bullets assessment: SBARBullet[]; // 3-5 bullets recommendation: SBARBullet[]; // 3-5 bullets narrative_summary: string; // full narrative (multiple paragraphs) top_priority: string; // single most urgent action}
interface KeyStatistics { total_conditions: number; data_span_years: string; total_diagnoses: number; total_biomarkers: number; total_biomarkers_out_of_range: number; most_recent_assessment: string;}DetailedFindings — Per-body-system drilldown (most complex section)
Copyinterface DetailedFindings { systems: BodySystem[];}
interface BodySystem { name: string; // "Hematologic", "Cardiovascular", etc. score: number; // 0-100 health score status: string; // "Concerning", "Fair", "Good" level: Level; // "critical" | "warning" | "neutral" | "positive" diagnoses: SystemDiagnosis[]; // diagnosis name, date, status, citation biomarkers: SystemBiomarker[]; // most recent value per marker biomarker_trends: BiomarkerTrend[]; // full history for Chart.js (≥2 points) clinical_interpretation: string; // prose with **bold** and [citations]}
interface BiomarkerTrend { name: string; data_points: TrendDataPoint[]; // [{date, value, unit, reference, status, citation}] status: string; // "Slowly declining", "Stable", etc. arrow: TrendArrow; // "→" | "~" | "↑" | "↑↑" | "↓" | "↓↓" level: Level; trend: string; // narrative description of trend}BiologicalStory — Cytoscape.js disease cascade graph
Copyinterface BiologicalStory { central_pathophysiology: string; graph_nodes: GraphNode[]; // tier 0 = root driver, tier 5 = end-stage graph_edges: GraphEdge[]; // directed relationships with mechanism text}
interface GraphNode { id: string; label: string; tier: 0 | 1 | 2 | 3 | 4 | 5; node_type: "root" | "critical" | "warning" | "neutral";}
interface GraphEdge { source: string; // node id target: string; // node id affect: "Drives" | "Worsens" | "Allows" | "Impairs" | "Creates"; mechanism: string; // "Insulin resistance → Increased hepatic VLDL" bidirectional: boolean;}SectionManifest — Deterministic rendering visibility
Copyinterface SectionManifest { // Always present executive_summary: true; key_metrics: true; detailed_findings: true;
// Conditional (threshold-based) conditions: boolean; // total_count ≥ 1 key_visualizations: boolean; // any systems data exists timeline: boolean; // eras.length ≥ 2 biological_story: boolean; // edges ≥ 1 AND nodes ≥ 2
// Treatment plan sub-tabs (each independent) treatment_plan: boolean; treatment_plan_immediate: boolean; treatment_plan_supplements: boolean; treatment_plan_lifestyle: boolean; treatment_plan_monitoring: boolean; treatment_plan_doctor_questions: boolean;
// Long-term management sub-tabs long_term_management: boolean; long_term_checkpoints: boolean; long_term_monitoring: boolean; long_term_vaccinations: boolean; long_term_screenings: boolean;
prognosis: boolean; organ_insights: boolean; data_gaps: boolean; references: boolean;}Technology & Dependency Map
Section titled “Technology & Dependency Map”Dependencies used at each phase. = deterministic, = LLM-powered.
TechnologyP1P2P3P4P5P6P7
@google/genai
genai-factory (streaming)
chat-compression
zod (report.zod.ts)
nunjucks
tailwindcss + postcss
section-manifest
langfuse
fastify
S3 / local storageKey Technology Roles
Section titled “Key Technology Roles”@google/genaiGemini AI SDK for function calling and streaming. All LLM interactions go through this.
genai-factory.ts`generateContentStreaming()` keeps LiteLLM proxy alive during 2-3 minute thinking. `createGoogleGenAI()` injects billing headers.
chat-compressionPhase-specific history compression. Preserves external state (Maps, Sets), compresses oldest 70% of conversation when token threshold exceeded.
zod + report.zod.tsRuntime schema validation with intelligent coercion (string→number, camelCase→snake_case, flat events→eras). Graceful degradation via `.catch()` defaults.
nunjucksTemplate engine for deterministic HTML rendering. 13 section templates with 10+ custom filters (format_bold, format_citations, trend_chart_data, etc.).
tailwindcss + postcssCompiles CSS inline from used utility classes. Zero CDN dependency — output is fully self-contained.
section-manifest.tsSingle source of truth for which sections render. Threshold-based boolean map prevents empty sections from appearing.
langfuseLLM observability: trace spans per phase, generation-level token tracking, cost attribution per user via billing headers.
fastifyHTTP server for local mode. SSE streaming of pipeline events to React frontend.
@aws-sdk/client-s3Production storage for reports. Two-tier: scoped storage (pipeline intermediates) + base storage (final HTML with signed URLs).Configuration
Section titled “Configuration”Settings from server/src/config.ts. Uses a defaults-only policy** — environment variable model overrides are intentionally ignored for consistency.
REALM_CONFIG — Full Configuration
**Copyconst REALM_CONFIG = { models: {
markdown: 'gemini-2.5-flash', // Phase 1: OCR extractionintermediate: 'gemini-3-pro-preview', // Phases 2-6: agentic reasoninghtml: 'gemini-3-flash-preview', // (legacy, not used in Phase 7)doctor: 'gemini-3-pro-preview', // Primary model for all agents}, agenticLoop: {
maxIterations: 10, // Default (overridden per-phase: 25-50)enableWebSearch: true, // Phase 3 toggle}, retry: {
llm: { maxRetries: 8, baseMultiplier: 10, maxWait: 600 }, // secondsapi: { maxRetries: 3, baseMultiplier: 5, maxWait: 120 },vision: { maxRetries: 5, baseMultiplier: 5, maxWait: 180 },}, throttle: {
pdfExtraction: { maxConcurrent: 4, delayMs: 100 },webSearch: { maxConcurrent: 3, delayMs: 250 },llm: { maxConcurrent: 3, delayMs: 150 },}, compression: {
threshold: 0.5, // Trigger at 50% of token limitpreserveFraction: 0.3, // Keep newest 30%tokenLimit: 1048576, // 1M tokens (gemini-3-pro-preview)}, };
Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`GEMINI_API_KEY`*required*Gemini API key (or LiteLLM gateway key)
`GOOGLE_GEMINI_BASE_URL`*empty*Custom base URL for LiteLLM gateway routing
`MAX_AGENTIC_ITERATIONS``10`Default max iterations for agentic loops (overridden per-phase)
`ENABLE_WEB_SEARCH``true`Toggle Phase 3 (research) on/off
`COMPRESSION_THRESHOLD``0.5`Trigger chat compression at this fraction of token limit
`COMPRESSION_PRESERVE``0.3`Fraction of recent history to preserve during compression
`LLM_RETRY_MAX_WAIT_SECONDS``600`Maximum wait time for LLM retry backoff
`REGEN_HTML``false`Skip entire pipeline, re-render from existing structured_data.json
`OBSERVABILITY_ENABLED``false`Enable Langfuse tracing for LLM calls
`USER_ID`*none*Patient user ID (forge/job-runner mode)
`CHR_ID`*none*CHR session ID (forge/job-runner mode)
`N1_API_BASE_URL`*empty*N1 API base URL for data fetching
`BUCKET_NAME`*empty*S3 bucket for report upload (production)Infrastructure
Section titled “Infrastructure”Deployment
Section titled “Deployment”Docker → Kubernetes ephemeral job. Multi-arch builds (native ARM64 + AMD64). Separate ECR repos for prod (`n1-prod/`) and staging (`n1-staging/`).Storage
Section titled “Storage”Two-tier: scopedStorage (pipeline intermediates) + baseStorage (final HTML with signed URLs). S3 for production, local filesystem for development.
Overview
Section titled “Overview”workflow-abc** generates interactive, multi-section Comprehensive Health Reports via an 8-stage multi-agent pipeline built on LangGraph and DeepAgents. Five stages use iterative LLM tool-calling loops with specialized DeepAgents (explore, timeline, analyze, assess, synthesize), one stage uses LLM-based structured extraction, and two stages are deterministic (data preparation and reference linking). The output is a self-contained HTML report with Chart.js visualisations, Cytoscape.js disease-cascade graphs, and interactive tooltips.
Pipeline Stages
Specialized Agents
Shared Subagents
10+Report SectionsPhilosophy: This is an orchestrator-driven multi-agent system using the evaluator–optimizer pattern. Each agent operates in an iterative tool-calling loop with dedicated subagents for data exploration and medical research. Agents communicate via a guarded virtual filesystem — each writes markdown reports that downstream agents read, ensuring clean separation of concerns and full auditability. The analyze stage uses a fan-out/fan-in pattern where per-condition workers run in parallel.
Agent Architecture Patterns
Section titled “Agent Architecture Patterns”Evaluator–Optimizer Loop
Section titled “Evaluator–Optimizer Loop”Each agent’s output is validated by a dedicated Validator agent (Claude Opus). If validation fails, the agent receives structured feedback and retries. Configurable iterations (default: 1).
Fan-Out / Fan-In (Analyze)
Section titled “Fan-Out / Fan-In (Analyze)”The analyze stage spawns parallel workers via LangGraph’s Send() API — one worker per identified condition (up to 15). Workers run concurrently with configurable max concurrency (default: 4). Partial failures are tracked separately.
Output Report Sections
Section titled “Output Report Sections”10 Interactive Sections
Section titled “10 Interactive Sections”Executive Summary • Key Metrics Dashboard • Identified Conditions • Medical Timeline • The Biological Story (Cytoscape.js graph) • Detailed Findings by System • Treatment Plan (8 tabs) • Prognosis • Long-Term Outlook • References
Interactive Features
Section titled “Interactive Features”Abbreviation tooltips • Citation hover cards • Expandable condition cards • Tabbed treatment plan • System health score bars • Biomarker trend charts (Chart.js) • Disease cascade network graph (Cytoscape.js) • Print-optimized layout
Pipeline Flowchart
Section titled “Pipeline Flowchart”5 agentic stages (iterative LLM tool-calling with DeepAgents) + 1 LLM-based extraction + 2 deterministic stages. The analyze and assess stages run in parallel after explore, then synthesize merges all outputs.
S1Data PreparationDeterministic
N1 API → Clean → Categorise → Markdown + JSON files
S2TimelineAgentic • Claude Sonnet
Pre-generated template → Agent edits Section 1 in-place • data_explorer + research subagents
S3ExploreAgentic • Claude Sonnet
Data exploration → Topic identification → Severity/actionability ranking
S4aAnalyzeOrchestrator–Worker • Claude Sonnet
Fan-out: Per-condition deep-dive • Up to 15 parallel workers
S4bAssessAgentic • Claude Sonnet
Per-body-system scoring • Biomarker trends • Clinical interpretation
S5SynthesizeAgentic • Claude Sonnet
Cross-condition synthesis • Medication reconciliation • Cascade effects • Treatment plan
S6Render HTMLLLM Extraction + Deterministic
Parse report → LLM-based structured extraction (9 schemas) → Jinja2 template render
S7Update ReferencesDeterministic
Refresh record URLs → Append citation tables → Re-render HTML with reference tooltipsHow Each Agent Works (DeepAgents Pattern)
Section titled “How Each Agent Works (DeepAgents Pattern)”All agentic stages (S2–S5) follow the DeepAgents iterative tool-calling pattern, with shared middleware for patient context injection, retry handling, truncation recovery, and tool output limits.
The Agent Loop (shared pattern)
Section titled “The Agent Loop (shared pattern)”- 1. Inject patient context via PatientContextMiddleware (name, DOB, age, gender, data file paths)
- 2. Send task + tools to LLM with extended thinking enabled (budget: 4K–8K tokens)
- 3. LLM responds with tool calls (e.g.
search_biomarkers("platelets")) or text - 4. Execute tools against guarded filesystem; results added to conversation
- 5. Delegate to subagents when needed (data_explorer for file navigation, research for web search)
- 6. Check completion — agent writes final report to workspace filesystem
- 7. Validate (optional) — Validator agent (Claude Opus) checks output quality; re-run on failure
- 8. Extract structured output — Pydantic schema extraction from agent messages
Middleware Stack (per agent)
Section titled “Middleware Stack (per agent)”- PatientContextMiddleware: Injects patient demographics + data file overview into first message
- RetryMiddleware: Exponential backoff with jitter (2s initial, 60s max, 4× multiplier, 20 retries) for transient errors
- TruncationHandlerMiddleware: Detects
max_tokenstruncation, clears incomplete tool calls, injects iterative writing guidance - ToolOutputLimitMiddleware: Prevents context explosion from large tool outputs
Two Subagent Types
Section titled “Two Subagent Types”- Data Explorer (Claude Haiku): Lightweight dict-spec subagent. Navigates patient files, extracts specific values, writes summaries to files for context preservation.
- Research (Claude Haiku): Lightweight dict-spec subagent. Web search + URL fetch with automatic citation tracking ([w1], [w2], etc.). Source hierarchy: Guidelines > Peer-reviewed > Clinical references.
Stage Details
Section titled “Stage Details”Data Preparation — API Fetch & Categorisation
Section titled “Data Preparation — API Fetch & Categorisation”Fetches patient data from the N1 API, runs data cleaners, generates a clinical index, and writes structured markdown + JSON files to the workspace. This is the only stage that contacts external APIs for patient data.
Input`USER_ID + CHR_ID → N1 API (profiles, diagnoses, procedures, biomarkers)`
Output`profile.md, diagnoses.md, procedures.md, biomarkers.md, metadata.yaml`Pipeline
Section titled “Pipeline”- Fetch patient records from N1 API (authenticated)
- Run data cleaners for validation and normalisation
- Generate clinical index with categorical groupings
- Create markdown and JSON output files in workspace
- Initialise PatientContext (name, DOB, age, gender)
- Populate DataFiles with all generated file paths
Timeline — Medical History Chronology
Section titled “Timeline — Medical History Chronology”Builds a comprehensive medical timeline. Unique pattern: sections 2–5 are programmatically pre-generated from structured data, then the agent reads the full template and edits Section 1 (narrative overview) in-place.
Input`Patient data files (diagnoses, biomarkers, procedures, profile)`
Output`/timeline/report.md`Key Characteristics
Section titled “Key Characteristics”- Model: Claude Sonnet (extended thinking: 6K budget)
- Subagents: data_explorer, research
- Middleware: ToolOutputLimitMiddleware, RetryMiddleware
- Validation: Optional evaluator–optimizer loop
- Report: 5 sections (1 agent-authored, 4 programmatic)
Explore — Data Exploration & Topic Discovery
Section titled “Explore — Data Exploration & Topic Discovery”Explores all patient data to identify condition groups, ranked by severity and actionability. This stage determines the topics that the analyze stage will deep-dive into. Focuses on discovery and organisation, not treatment recommendations.
Input`Timeline report + raw patient data files`
Output`/explore/summary.md + TopicIdentification[] (structured)`Key Characteristics
Section titled “Key Characteristics”- Model: Claude Sonnet (extended thinking: 6K budget)
- Subagents: data_explorer, research
- Middleware: ToolOutputLimitMiddleware, TruncationHandlerMiddleware, RetryMiddleware
- Validation: Optional evaluator–optimizer loop with structured extraction
- Structured output: TopicIdentification with topic_name, severity, actionability, conditions_included, key_biomarkers
4aAnalyze — Per-Condition Deep Analysis (Orchestrator–Worker)
Section titled “Analyze — Per-Condition Deep Analysis (Orchestrator–Worker)”Uses the orchestrator–worker pattern with LangGraph’s Send() API. The orchestrator receives identified topics from explore, then spawns isolated workers — one per condition — running in parallel with configurable concurrency.
Input`TopicIdentification[] + timeline + explore reports`
Output`/analyze/{topic_slug}/report.md (per condition)`Key Characteristics
Section titled “Key Characteristics”- Model: Claude Sonnet (extended thinking: 6K budget)
- Pattern: Fan-out via
Send()→ parallel workers → fan-in with report collection - Max workers: Configurable (default: 4 concurrent, up to 15 conditions)
- Error handling: Partial failures tracked separately; other workers continue
- Per-worker: Isolated state + optional validation loop
- Subagents: data_explorer, research (per worker)
4bAssess — System Health Scoring (runs parallel with Analyze)
Section titled “Assess — System Health Scoring (runs parallel with Analyze)”Scores each body system (0–100) with evidence-based assessment, biomarker trend analysis, and clinical interpretation. Runs in parallel with analyze after explore completes.
Input`Timeline report + explore report + raw patient data`
Output`/assess/report.md`Key Characteristics
Section titled “Key Characteristics”- Model: Claude Sonnet (extended thinking: 6K budget)
- Subagents: data_explorer, research
- Tools: Direct biomarker/diagnosis lookups, clinical guideline verification
- Middleware: ToolOutputLimitMiddleware, TruncationHandlerMiddleware, RetryMiddleware
- Validation: Optional evaluator–optimizer loop
- Output: Per-system health scores with evidence and clinical interpretation
Synthesize — Unified Cross-Condition Report
Section titled “Synthesize — Unified Cross-Condition Report”Merges all upstream outputs into a single comprehensive health report. Waits for both analyze and assess to complete. Produces the executive summary, medication reconciliation, polypharmacy assessment, cascade effects analysis, and treatment plan.
Input`Explore + assess + analyze reports (all upstream outputs)`
Output`/synthesize/report.md`Key Characteristics
Section titled “Key Characteristics”- Model: Claude Sonnet (extended thinking: 6K budget)
- Subagents: data_explorer, research
- Content: Executive summary, medication reconciliation, polypharmacy assessment, cascade effects, treatment plan, prognosis
- Middleware: ToolOutputLimitMiddleware, TruncationHandlerMiddleware, RetryMiddleware
- Validation: Optional evaluator–optimizer loop
Render HTML — Structured Extraction & Template Rendering
Section titled “Render HTML — Structured Extraction & Template Rendering”Two-phase rendering: first, an LLM extracts structured data from the synthesize report using 9 Pydantic schemas; then, a Jinja2 template renders the final interactive HTML report deterministically from the extracted JSON.
Input`Synthesize report + explore + assess reports`
Output`/render/synthesize.html + /render/extract/*.json`Extraction Schemas (9 total)
Section titled “Extraction Schemas (9 total)”- AbbreviationList: Medical abbreviation definitions for interactive tooltips
- Header: Report title, patient info, generation date
- Sections 1–8: Timeline overview, system health scores, identified conditions, condition analysis, medication reconciliation, treatment plan, prognosis, health recommendations
Rendering Pipeline
Section titled “Rendering Pipeline”- Parse synthesize report markdown into sections (remapped to final numbering)
- Extract additional context from explore and assess reports
- Run LLM-based extraction per section (Gemini Flash) → save JSON to
/render/extract/*.json - Apply abbreviation postprocessor for interactive tooltips
- Render Jinja2 HTML template with extracted data
Update References — Citation Linking & URL Refresh
Section titled “Update References — Citation Linking & URL Refresh”Refreshes N1 API record URLs (preventing expiry), appends source-linked reference tables to all markdown reports, and re-renders the HTML with reference tooltips and citation data.
Input`All upstream reports + rendered HTML`
Output`Enhanced reports with § References + re-rendered HTML with tooltips`Pipeline
Section titled “Pipeline”- Refresh record URLs from N1 API (ensure signed URLs not expired)
- Scan all report files (timeline, explore, analyze, assess, synthesize)
- Append source-linked reference tables to each report
- Re-render HTML with reference tables and tooltip data
How Data Flows Between Agents
Section titled “How Data Flows Between Agents”Agents communicate via a guarded virtual filesystem. Each agent writes markdown reports to its dedicated directory; downstream agents read from upstream directories via the data_explorer subagent.
File-Based Inter-Agent CommunicationArchitecture
Copy═══ WORKSPACE FILESYSTEM LAYOUT ═══════════════════════════════════════ agent_environment/{user_id}/{YYYY-MM-DD_HH-MM-SS}/
/data/ profile.md # Patient demographics diagnoses.md # All diagnoses with IDs (d1-dN) procedures.md # All procedures with IDs (p1-pN) biomarkers.md # Biomarker groups with IDs (cb1-cbN) metadata.yaml # Clinical index & categorical groupings
/timeline/ report.md # 5-section medical timeline
/explore/ summary.md # Topic identification & severity ranking
/analyze/ {topic-slug-1}/report.md # Per-condition deep analysis {topic-slug-2}/report.md …up to 15 conditions
/assess/ report.md # Per-body-system health scores
/synthesize/ report.md # Unified cross-condition report
/render/ extract/ # Per-section JSON extraction
abbreviations.jsonheader.jsonsection_1.json ... section_8.jsonsynthesize.html # Final interactive report
Inter-Agent Reading PatternsData Flow
Copy═══ WHO READS WHAT ════════════════════════════════════════════════════
timeline reads: /data/* # Raw patient data onlyexplore reads: /data/* + /timeline/report.md # Data + timeline contextanalyze reads: /data/* + /timeline/ + /explore/ # All upstream (per worker)assess reads: /data/* + /timeline/ + /explore/ # Parallel with analyzesynthesize reads: /explore/ + /assess/ + /analyze/* # All agent outputsrender_htmlreads: /synthesize/ + /explore/ + /assess/ # For extraction
═══ STATE CHANNELS (LangGraph Reducers) ═══════════════════════════════
# Shared fields (take-last reducer):workspace_root: strpatient_context: PatientContext # user_id, name, dob, age, genderdata_files: DataFiles # Paths to all input data files
# Per-agent message channels (add-messages reducer):explore_messages: list[BaseMessage]timeline_messages: list[BaseMessage]assess_messages: list[BaseMessage]synthesize_messages: list[BaseMessage]
# Output paths:timeline_report_path: str | Noneexplore_report_path: str | Noneidentified_topics: list[TopicIdentification] | Noneanalyze_report_paths: list[str] # add reducer (accumulates)analyze_errors: list[dict] # add reducer (tracks failures)assess_report_path: str | Nonesynthesize_report_path: str | Nonerender_html_path: str | None # Final outputBiomarker Value: End-to-End Journey Through AgentsExample
Copy═══ STAGE 1: Data Preparation ═════════════════════════════════════════N1 API returns patient biomarker data:
biomarkers.md: cb42 | Platelets | Hematologic```textb97: 2024-01-15 → 138 x10^9/L (Low) ref: 150-400b64: 2023-06-22 → 141 x10^9/L (Low) ref: 150-400b33: 2021-11-10 → 149 x10^9/L (Low) ref: 150-400═══ STAGE 2: Timeline ═════════════════════════════════════════════════ Agent reads pre-generated sections 2-5, edits section 1 in-place:
Section 1 (agent-authored): “Chronic mild thrombocytopenia (138-149 range) present since earliest records, with gradual downward trend…”
═══ STAGE 3: Explore ══════════════════════════════════════════════════ Agent calls patient data tools:
search_biomarkers(category=“Hematologic”, out_of_range_only=True) → Returns platelets, WBC, and other out-of-range hematologic markers
Agent identifies topic: TopicIdentification( topic_name=“Hematologic Dysfunction”, severity=“Moderate”, actionability=“High”, conditions_included=[“Thrombocytopenia”, “Anemia”], key_biomarkers=[“cb42”, “cb15”] )
═══ STAGE 4a: Analyze (Worker) ════════════════════════════════════════ Worker spawned for “hematologic-dysfunction” topic:
search_biomarkers(ids=[“cb42”], include_details=True) → Full platelet history with reference ranges and dates
Research subagent: web_search(“chronic mild thrombocytopenia differential diagnosis”) → Returns [w1]: UpToDate, [w2]: Blood journal review
Writes: /analyze/hematologic-dysfunction/report.md
═══ STAGE 4b: Assess (parallel) ═══════════════════════════════════════ Agent scores Hematologic system:
search_biomarkers(category=“Hematologic”) search_diagnoses(system=“Hematologic”)
Score: 42/100 | Level: warning | Status: “Concerning” Biomarker trends: Platelets ↓ (gradual decline over 3 years)
═══ STAGE 5: Synthesize ═══════════════════════════════════════════════ Agent reads all upstream reports via data_explorer:
data_explorer: read /assess/report.md → gets Hematologic score: 42 data_explorer: read /analyze/hematologic-dysfunction/report.md → gets deep analysis
Produces unified section with cross-condition context: “Chronic thrombocytopenia (platelets trending ↓ from 149 to 138) interacts with metabolic dysfunction via…”
═══ STAGE 6: Render HTML ══════════════════════════════════════════════ LLM extracts structured data from synthesize report:
section_2.json (Key Metrics Dashboard): { systems: [{ name: “Hematologic”, score: 42, level: “warning”, … }] }
Jinja2 renders Chart.js trend chart + score bar + tooltip
═══ STAGE 7: Update References ════════════════════════════════════════ Appends to all reports:
References
Section titled “References”| ID | Type | Name | Link |
|---|---|---|---|
| cb42 | Biomarker | Platelets | View Record |
| [w1] | Web | UpToDate | View Source |
## Data Contracts
All data contracts are defined as Pydantic models in Python. Key schemas are in `src/healthcare_agent/workflow/subgraphs/*/schemas.py` and `src/healthcare_agent/workflow/state.py`.
### Core State
HealthcareGraphState — Main workflow state (TypedDict)
```textCopyclass HealthcareGraphState(TypedDict):
# Shared fields (take-last reducer)workspace_root: strpatient_context: PatientContextdata_files: DataFiles
# Per-agent message channels (add-messages reducer)explore_messages: list[BaseMessage]timeline_messages: list[BaseMessage]assess_messages: list[BaseMessage]synthesize_messages: list[BaseMessage]
# Stage outputstimeline_report_path: str | Noneexplore_report_path: str | Noneidentified_topics: list[TopicIdentification] | Noneanalyze_report_paths: list[str] # add reduceranalyze_errors: list[dict] # add reducerassess_report_path: str | Nonesynthesize_report_path: str | Nonerender_html_path: str | None # Final outputKey Domain Models
Section titled “Key Domain Models”TopicIdentification — Explore output schema
Copyclass TopicIdentification(BaseModel):```texttopic_name: str # "Metabolic Syndrome"topic_slug: str # "metabolic-syndrome"conditions_included: list[str] # ["Type 2 Diabetes", "Dyslipidemia"]severity: Literal["High", "Moderate", "Low"]actionability: Literal["High", "Moderate", "Low"]rationale: str # With citations [d5], [cb42]key_biomarkers: list[str] # ["cb42", "cb15"] PatientContext & DataFiles — Shared context injected into all agents
```textCopyclass PatientContext(TypedDict):```textuser_id: strpatient_name: strdob: strage: intgender: strtoday_date: strclass DataFiles(TypedDict):
profile: str # /data/profile.mddiagnoses: str # /data/diagnoses.mdprocedures: str # /data/procedures.mdbiomarkers: str # /data/biomarkers.mdmetadata: str # /data/metadata.yaml HTML Extraction Schemas — 9 Pydantic models for structured rendering
```textCopy# 9 extraction schemas used in Stage 6 (Render HTML):class AbbreviationList(BaseModel): # Interactive tooltip definitionsclass Header(BaseModel): # Title, patient info, dateclass Section1(BaseModel): # Timeline overviewclass Section2(BaseModel): # System health scores (from assess)class Section3(BaseModel): # Identified conditionsclass Section4(BaseModel): # Condition analysis detailclass Section5(BaseModel): # Biological story (Cytoscape graph)class Section6(BaseModel): # Medication reconciliationclass Section7(BaseModel): # Treatment planclass Section8(BaseModel): # Prognosis + long-term outlookTechnology & Dependency Map
Section titled “Technology & Dependency Map”Dependencies used at each stage. = deterministic, = LLM-powered.
TechnologyS1S2S3S4S5S6S7
LangGraph
DeepAgents
LangChain
Anthropic Claude (Sonnet)
Anthropic Claude (Opus)
Anthropic Claude (Haiku)
Gemini (extraction)
Pydantic
Jinja2
N1 API Client
Langfuse
S3 / local storageKey Technology Roles
Section titled “Key Technology Roles”LangGraphState graph orchestration. Manages workflow node execution, fan-out/fan-in parallelisation via `Send()`, and custom reducers for state merging.
DeepAgentsMulti-turn agent framework with tool calling and subagent delegation. Provides the iterative loop pattern and middleware injection points.
LangChainModel abstraction layer (`ChatAnthropic`, `ChatOpenAI`, `ChatDeepSeek`). Provides `BaseTool` interface and message types for agent communication.
Anthropic ClaudePrimary LLM family. Opus for validation (8K thinking budget), Sonnet for main agents (6K thinking budget), Haiku for subagents (4K thinking budget). All use extended thinking with interleaved-thinking beta.
Gemini (via LiteLLM)Used for structured data extraction in render_html stage. Flash variant for fast extraction, Pro variant for categorisation with thinking.
PydanticSchema validation for all structured outputs (TopicIdentification, HTML extraction schemas, settings). `BaseSettings` for environment configuration.
Jinja2Deterministic HTML template rendering. Converts extracted JSON into interactive report with Chart.js, Cytoscape.js, and tooltip data.
structlogStructured logging for all pipeline stages. Machine-readable log events with context injection.
LangfuseLLM observability: trace spans per stage, generation-level token tracking, cost attribution. Disabled by default.
N1 API ClientAuthenticated access to patient records (profiles, diagnoses, procedures, biomarkers). Used in data_preparation and update_references stages.Agent ↔ Model Assignment
Section titled “Agent ↔ Model Assignment”AgentModelThinking BudgetPurpose
`explore`Claude Sonnet6,000 tokensData exploration & topic discovery
`timeline`Claude Sonnet6,000 tokensMedical history chronology
`analyze`Claude Sonnet6,000 tokensPer-condition deep analysis
`assess`Claude Sonnet6,000 tokensSystem health scoring
`synthesize`Claude Sonnet6,000 tokensCross-condition synthesis
`validator`Claude Opus8,000 tokensQuality validation (evaluator)
`data_explorer`Claude Haiku4,000 tokensFile navigation subagent
`research`Claude Haiku4,000 tokensWeb research subagent
`extraction`Gemini Flash—Structured data extractionConfiguration
Section titled “Configuration”All settings from src/settings.py via Pydantic BaseSettings. Agent-level config in src/healthcare_agent/config.py.
SharedConfig — Agent Pipeline Configuration
Copyclass SharedConfig:
workspace_root: Path # agent_environment/{user_id}/{timestamp}/workspace_override: str | None # Reuse existing workspace on rerunsrequire_validation: bool = True # Enable validator agentmax_validation_iterations: int = 1 # Retry failed validation N timesmax_analyze_workers: int = 4 # Parallel analyze workers (fan-out)max_conditions: int = 15 # Limit conditions analysedmodel_overrides: dict[str, str] # {"explore": "claude-opus", ...}Features:
Section titled “Features:”- Lazy initialisation: workspace created on first access
Section titled “- Lazy initialisation: workspace created on first access”- Lazy model caching: models created on demand, cached per-agent
Section titled “- Lazy model caching: models created on demand, cached per-agent”- Guarded file backend: virtual filesystem with guardrails
Section titled “- Guarded file backend: virtual filesystem with guardrails”- Per-agent model override support via model_overrides dict
Section titled “- Per-agent model override support via model_overrides dict”Environment Variables
Section titled “Environment Variables”VariableDefaultDescription
`USER_ID`*required*Patient user ID for API data fetch
`CHR_ID`*required*CHR session ID for progress tracking
`OPENAI_API_KEY`*required*LiteLLM proxy API key (routes to Claude/Gemini)
`OPENAI_BASE_URL`*required*LiteLLM proxy base URL
`N1_API_KEY`*required*N1 API authentication key
`N1_API_BASE_URL`*required*N1 API base URL for data fetching
`BUCKET_NAME`*empty*S3 bucket for report upload (production)
`REQUIRE_VALIDATION``true`Enable/disable validator agent
`MAX_VALIDATION_ITERATIONS``1`Max validation retry loops per agent
`MAX_ANALYZE_WORKERS``4`Max concurrent analyze workers (fan-out)
`MAX_CONDITIONS``15`Limit conditions for analyze stage
`MODEL_CLAUDE_OPUS``claude-4.6-opus`Claude Opus model version
`MODEL_CLAUDE_SONNET``claude-4.6-sonnet`Claude Sonnet model version
`MODEL_CLAUDE_HAIKU``claude-4.5-haiku`Claude Haiku model version
`MODEL_GEMINI_PRO``gemini-3-pro-preview`Gemini Pro model version
`MODEL_GEMINI_FLASH``gemini-3-flash-preview`Gemini Flash model version
`CHR_FILENAME``HEALTH_REPORT.html`Output HTML filename
`IS_DEVELOPMENT``false`Development mode flag
`LANGFUSE_ENABLED``false`Enable Langfuse LLM observabilityInfrastructure
Section titled “Infrastructure”Deployment
Section titled “Deployment”Docker → Kubernetes ephemeral job. Python 3.11+ with `uv` package manager. LangGraph Studio for local development (`uv run langgraph dev`).Storage
Section titled “Storage”Guarded virtual filesystem (in-memory or disk) for pipeline intermediates. S3 for production report storage with signed URLs. Workspace path: agent_environment/{user_id}/{timestamp}/
Retry & Resilience
Section titled “Retry & Resilience”Exponential backoff: 2s initial, 60s max, 4× multiplier, full jitter. 20 retries for transient errors. Retryable status codes: 429, 500, 502, 503, 504, 524, 529.
Observability
Section titled “Observability”Structured logging (structlog) + optional Langfuse tracing. Progress tracking via N1 API stages: DATA_PREP, TIMELINE, EXPLORE, ANALYZE, ASSESS, SYNTHESIZE, RENDER, REFERENCES.
