N1 Healthcare — Biomarker Grouping Pipeline
N1 Healthcare — Biomarker Grouping Pipeline
Overview
Section titled “Overview”Historical reference.** This document describes the pre-Rosetta biomarker grouping pipeline (alias lookup + embedding matching). For the current BiomarkerDefinition engine that supersedes it, see Rosetta — Complete Architecture.
The enrichment-biomarkers refactor replaces the current full ML pipeline (embeddings + agglomerative clustering + LLM fixing for every biomarker) with a two-tier approach: a global alias lookup table handles ~95% of biomarkers instantly, while embedding matching handles the remaining ~5% edge cases. Grouping runs inline per record, enrichment is decoupled entirely.
Pipeline Steps
~95%Alias Lookup
~5%Embedding Fallback
LLM Calls at RuntimeCore change: The vast majority of biomarkers skip the ML pipeline completely. Cost no longer scales quadratically with user history. Concurrent processing is unblocked. Enrichment failures can’t take down grouping.
Prerequisite: Global Alias Table
Section titled “Prerequisite: Global Alias Table”~1,000–2,000 standardized biomarker names with ~5–6 aliases per name. Seeded by LLM, covering the vast majority of lab biomarkers. Simple direct non-LLM lookup with known aliases — used for name standardization. Grows automatically as new raw names are mapped. Includes expected unit families and default sample source per definition.
Current Problems
Section titled “Current Problems”1. Full ML for every biomarker
Section titled “1. Full ML for every biomarker”A routine “HGB” gets embeddings, agglomerative clustering, and multi-pass LLM fixing — the same as a rare specialized marker. ~95% are common lab tests that could be a simple name lookup.
2. Non-grouping work bundled in
Section titled “2. Non-grouping work bundled in”Systems assignment, unit conversion, and data validation are inside the grouper. A unit conversion failure blocks grouping from completing.
3. Single-user locking
Section titled “3. Single-user locking”One grouping batch at a time per user. Can’t group two records concurrently. Users with the most records wait the longest.
4. O(n²) cost scaling
Section titled “4. O(n²) cost scaling”Agglomerative clustering compares every biomarker against every other. 250 biomarkers across 5 records is drastically more expensive than 50 from 1 record. Most active users get worst performance.
Pipeline Flowchart
Section titled “Pipeline Flowchart”Per-record inline grouping. All records process concurrently. No user-level locks. The lookup table handles ~95% in sub-milliseconds. Embedding fallback only for the ~5% that miss.
S1Alias LookupDeterministic
Direct match raw name against global alias table • No LLM, no fuzzy • Sub-millisecond per biomarker
Match found in alias table?
~95% — Yes
Proceed to assign or create
~5% — No
S2Embedding MatchML
Compare against user's existing canonicals only (40–80 vectors, not all biomarkers)
S3Assign or CreateDeterministic
Check user's existing canonicals for name + sample source + unit compatibility
User has canonical with this name + same source?
**Yes, units convertible**Assign to existing canonical (conversion in enrichment)
**Yes, units incompatible**Create separate canonical with unit in name, e.g. "Vitamin D (IU/L)"
**Different source**Create separate canonical (clinically distinct, e.g. serum vs urine)
**No match at all**Create new canonical for user
S4DedupDeterministic
Quick query: merge duplicate canonicals for this user (same name, same source) • Handles concurrency races
User sees grouped data as each record finishes
After all records complete
E1EnrichmentLLM + Deterministic
Health areas / systems (LLM) • Unit standardization (deterministic) • Runs in parallel, separate processStage Details
Section titled “Stage Details”Alias Lookup — Direct Name Match
Section titled “Alias Lookup — Direct Name Match”Each biomarker’s raw name is matched directly against the global alias table. No fuzzy matching, no LLM. Exact string comparison against known aliases.
Input`biomarker.raw_name`
Output`canonical_name | null`How it works
Section titled “How it works”- Alias table: ~1,000–2,000 canonical names, ~5–6 aliases each (e.g. “HGB”, “Hemoglobin”, “Hgb”, “Haemoglobin” →
Hemoglobin) - Lookup: Case-insensitive exact match against all aliases
- Performance: Sub-millisecond per biomarker, handles ~95% of all biomarkers
- Growth: Table grows automatically as new raw names are mapped via embedding fallback
Table metadata per canonical
Section titled “Table metadata per canonical”- Expected unit families: Groups of convertible units (e.g. mg/dL, g/dL, mmol/L for same family)
- Default sample source: Most common source for this biomarker (e.g. “serum” for glucose)
alias-tableEmbedding Match — Lookup Miss Only
Section titled “Embedding Match — Lookup Miss Only”For the ~5% that miss the alias table. Generates an embedding for the raw biomarker name and compares against the user’s existing canonical embeddings only (typically 40–80 vectors). No agglomerative clustering across all biomarkers.
Input`biomarker.raw_name (unmatched)`
Output`closest_canonical | null`How it works
Section titled “How it works”- Generate embedding for the unmatched raw name
- Compare against user’s existing canonical embeddings (cosine distance)
- Match if: distance < threshold AND compatible sample source AND compatible units
- No match: Create new canonical for user
Key difference from current
Section titled “Key difference from current”- Current: O(n²) — every biomarker compared against every other biomarker
- Proposed: O(k) — one biomarker compared against ~40–80 existing canonicals
MedEmbedAssign or Create — User Canonical Check
Section titled “Assign or Create — User Canonical Check”Now we have a canonical name (from Step 1 or 2). Check whether the user already has a canonical with this name + matching sample source + compatible units.
Input`canonical_name + biomarker metadata`
Output`assigned_definition_id | new_canonical`Decision tree
Section titled “Decision tree”- Name + source match, units convertible: Assign to existing canonical. Unit conversion happens downstream in enrichment.
- Name + source match, units incompatible: Create separate canonical with unit appended to name (e.g. “Vitamin D (IU/L)”). Different assay methods produce fundamentally different measurements.
- Same name, different source: Create separate canonical. Serum vs urine glucose are clinically distinct measurements.
- Name + source not in user DB: Create new canonical for user.
- No match at all (S2 miss): Create new canonical.
Unit convertibility check
Section titled “Unit convertibility check”- Uses the alias table’s expected unit families per canonical
- Units in the same family (mg/dL ↔ mmol/L) = convertible
- Units in different families (IU/L vs ng/mL) = incompatible = separate canonical
alias-tableuser-canonicals-dbDedup — Concurrent Race Cleanup
Section titled “Dedup — Concurrent Race Cleanup”Quick query after each record completes. Finds duplicate canonicals for this user (same name, same source) created by concurrent record processing. Merges them by keeping the lowest ID, reassigning biomarkers, and deleting extras.
Input`user_id + newly created canonicals`
Output`merged canonicals (no duplicates)`Why this is needed
Section titled “Why this is needed”- Records process concurrently — two records can both create a “Hemoglobin” canonical at the same time
- The dedup query runs after each record, not in a batch — duplicates are cleaned up immediately
- This is a fast DB query + merge, not an expensive computation
Merge strategy
Section titled “Merge strategy”- Keep canonical with lowest ID (first created)
- Reassign all biomarkers from duplicate canonicals to the kept one
- Delete the duplicate canonical records
user-canonicals-db
E1Enrichment — Separate Process
Section titled “Enrichment — Separate Process”Runs after all records complete, as a separate parallel process. Completely decoupled from grouping — a failure here does not block biomarker visibility.
Input`new/updated canonicals`
Output`enriched canonicals`What it does
Section titled “What it does”- Health areas / systems assignment: LLM-powered classification of canonicals into body systems (cardiovascular, metabolic, etc.)
- Unit standardization: Convert biomarker values to the canonical standard unit where units differ but are convertible
- Data validation: Quality checks moved out of grouper
What this unblocks
Section titled “What this unblocks”- Graphing and trend visualization
- Health report summarization
- Cross-biomarker analysis
LLM (systems)unit-conversionSample Source & Unit Mismatch Handling
Section titled “Sample Source & Unit Mismatch Handling”Different sample source
Section titled “Different sample source”- Always creates separate canonicals, even with the same biomarker name
- Example: serum glucose vs urine glucose are clinically distinct measurements
- The canonical name stays the same, but they’re different entries for the user
Different units, same source
Section titled “Different units, same source”- Check the alias table’s expected unit families for that canonical
- Convertible (e.g. mg/dL ↔ mmol/L): same canonical, unit conversion happens in enrichment step
- Incompatible (e.g. IU/L vs ng/mL — different assay methods): separate canonical, append the incompatible unit to the canonical name to disambiguate (e.g. “Vitamin D (IU/L)”)
Unknown or missing sample source
Section titled “Unknown or missing sample source”- Default to the most common source for that canonical (from alias table metadata)
- Flag for review if ambiguous
Before vs After
Section titled “Before vs After”CurrentProposed
**Common biomarkers (~95%)**Full ML pipeline (embeddings + clustering + LLM)Direct alias lookup (sub-ms, no LLM)
**Rare biomarkers (~5%)**Full ML pipelineEmbedding match against user's canonicals only
**Cost scaling**O(n²) — every biomarker vs every biomarkerO(k) — one biomarker vs ~40–80 canonicals
**Systems / health areas**Inside grouperSeparate enrichment process
**Unit conversion**Inside grouperSeparate enrichment process
**Concurrency**Locked per user_id, sequentialNo locks, dedup handles races
**User sees grouped data**After entire pipeline completesAs each record finishes
**LLM calls at runtime**Multi-pass cluster fixing + name standardizationZero (enrichment is a separate step)What’s Removed from Grouping
Section titled “What’s Removed from Grouping”LLM name standardization at runtime LLM cluster fixing (multi-pass) Agglomerative clustering on all biomarkers Systems / health area assignment Unit conversion Data validation within grouper Per-record user_id lock and batching Idempotency keys
Systems assignment, unit conversion, and data validation are moved to the enrichment step — not deleted, just decoupled from the grouping critical path.
Current Pipeline
Section titled “Current Pipeline”Documentation for the current enrichment-biomarkers pipeline. This page documents the proposed refactored pipeline.
Current Flow
Section titled “Current Flow”S1Embedding GenerationML
MedEmbed embeddings for all biomarkers in user's history
S2Agglomerative ClusteringML
O(n²) — every biomarker compared against every other • Cosine distance < 0.05
S3LLM Cluster FixingLLM
Multi-pass LLM review of clusters • Name standardization • Merge/split decisions
S4Systems & EnrichmentLLM + Deterministic
Health areas, unit conversion, data validation — all bundled inside the grouper
User sees results after entire pipeline completesKey Issues
Section titled “Key Issues”Full ML for every biomarker
Section titled “Full ML for every biomarker”~95% of biomarkers are common lab tests that don’t need embeddings or LLM.
O(n²) cost
Section titled “O(n²) cost”Agglomerative clustering scales quadratically. Most active users are most expensive.
Enrichment bundled in
Section titled “Enrichment bundled in”Unit conversion or system assignment failure blocks grouping entirely.
Sequential processing
Section titled “Sequential processing”Per-user lock means records queue up. No concurrency.
