2026 04 08 Hematology Comparison Summary Cards
Hematology Comparison Summary Cards Implementation Plan
Section titled “Hematology Comparison Summary Cards Implementation Plan”For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Fix comparison page summary cards to properly handle hematology diseases — hide stage-specific fields, fix the therapy txEligible data bug, and adapt DOCX export.
Architecture: Thread diseaseType from geoDiseaseConfigAtom through ComparisonScenarioData into ComparisonSummaryCard and buildComparisonReport. Fix therapy-group eligible count capture in collectComparisonData.
Tech Stack: React 19, TypeScript, Jotai, docx library
Spec: docs/superpowers/specs/2026-04-08-hematology-comparison-summary-cards-design.md
Task 1: Add diseaseType to ComparisonScenarioData type
Section titled “Task 1: Add diseaseType to ComparisonScenarioData type”Files:
-
Modify:
src/features/reports/forecasting/sections/Comparison/types.ts:5-21 -
Step 1: Add
diseaseTypefield to the interface
In src/features/reports/forecasting/sections/Comparison/types.ts, add diseaseType after the selectedYear field (line 9):
export interface ComparisonScenarioData { meta: TabMeta; geo: string; indication: string | null; selectedYear: number; diseaseType: "Solid Tumor" | "Hematology";
// Summary card incidence: number; // ... rest unchanged- Step 2: Verify no type errors
Run: pnpm lint
Expected: Type errors in collectComparisonData.ts (missing diseaseType in return object). This is expected — Task 2 fixes it.
- Step 3: Commit
git add src/features/reports/forecasting/sections/Comparison/types.tsgit commit -m "feat(comparison): add diseaseType field to ComparisonScenarioData"Task 2: Populate diseaseType and fix therapy txEligible bug in collectComparisonData
Section titled “Task 2: Populate diseaseType and fix therapy txEligible bug in collectComparisonData”Files:
-
Modify:
src/features/reports/forecasting/sections/Comparison/collectComparisonData.ts:46,97-103,215-237 -
Step 1: Fix therapy group eligible count capture
In src/features/reports/forecasting/sections/Comparison/collectComparisonData.ts, find the eligible count capture block (lines 97-103):
// First line's eligibleCount = L1 tx-eligible for that stage if (group.stageCategory === "early") { txEligibleEarly = items[0]?.eligibleCount ?? 0; } else if (group.stageCategory === "metastatic") { txEligibleMetL1 = items[0]?.eligibleCount ?? 0; }Replace with:
// First line's eligibleCount = L1 tx-eligible for that stage if (group.stageCategory === "early") { txEligibleEarly = items[0]?.eligibleCount ?? 0; } else if (group.stageCategory === "metastatic") { txEligibleMetL1 = items[0]?.eligibleCount ?? 0; } else if (group.stageCategory === "therapy") { txEligibleMetL1 = items[0]?.eligibleCount ?? 0; }This fixes the data bug where hematology therapy lines were never captured (metL1 was always 0). Since hematology has no metastatic lines, the metL1 slot is reused for the first therapy line’s eligible count.
- Step 2: Add
diseaseTypeto return object
In the same file, find the return object (line 215 onwards). Add diseaseType after selectedYear:
return { meta, geo, indication, selectedYear, diseaseType: diseaseConfig?.config.type ?? "Solid Tumor",
incidence: baseIncidence, // ... rest unchangeddiseaseConfig is already read at line 46 (store.get(geoDiseaseConfigAtom)), so no new atom reads needed.
- Step 3: Verify lint passes
Run: pnpm lint
Expected: PASS (type error from Task 1 is now resolved)
- Step 4: Commit
git add src/features/reports/forecasting/sections/Comparison/collectComparisonData.tsgit commit -m "fix(comparison): populate diseaseType and fix therapy txEligible bug"Task 3: Conditional rendering in ComparisonSummaryCard
Section titled “Task 3: Conditional rendering in ComparisonSummaryCard”Files:
-
Modify:
src/features/reports/forecasting/sections/Comparison/ComparisonSummaryCard.tsx:11-92 -
Step 1: Add
isSolidTumorderivation
In src/features/reports/forecasting/sections/Comparison/ComparisonSummaryCard.tsx, inside the ComparisonSummaryCard function body (after line 13, the color const), add:
const isSolidTumor = data.diseaseType === "Solid Tumor";- Step 2: Conditionally render the Incidence section heading
Find line 51-52:
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1.5"> Incidence & Stage </div>Replace with:
<div className="text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1.5"> {isSolidTumor ? "Incidence & Stage" : "Incidence"} </div>Note: Since we’re now using a JSX expression, & becomes & inside the string literal.
- Step 3: Conditionally render stage distribution rows
Find lines 59-70 (the Early Stage and Metastatic rows inside the “Incidence & Stage” section):
<div className="flex justify-between text-sm"> <span className="text-muted-foreground">Early Stage</span> <span className="font-medium tabular-nums"> {data.stageDistribution.earlyPercent.toFixed(1)}% </span> </div> <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Metastatic</span> <span className="font-medium tabular-nums"> {data.stageDistribution.metPercent.toFixed(1)}% </span> </div>Replace with:
{isSolidTumor && ( <> <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Early Stage</span> <span className="font-medium tabular-nums"> {data.stageDistribution.earlyPercent.toFixed(1)}% </span> </div> <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Metastatic</span> <span className="font-medium tabular-nums"> {data.stageDistribution.metPercent.toFixed(1)}% </span> </div> </> )}- Step 4: Conditionally render Treatment Eligible section
Find lines 79-92 (the Treatment Eligible section):
<div className="space-y-0.5"> <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Early Stage</span> <span className="font-semibold tabular-nums" style={{ color }}> {Math.round(data.txEligible.early).toLocaleString()} </span> </div> <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Metastatic Line 1</span> <span className="font-semibold tabular-nums" style={{ color }}> {Math.round(data.txEligible.metL1).toLocaleString()} </span> </div> </div>Replace with:
<div className="space-y-0.5"> {isSolidTumor && ( <div className="flex justify-between text-sm"> <span className="text-muted-foreground">Early Stage</span> <span className="font-semibold tabular-nums" style={{ color }}> {Math.round(data.txEligible.early).toLocaleString()} </span> </div> )} <div className="flex justify-between text-sm"> <span className="text-muted-foreground"> {isSolidTumor ? "Metastatic Line 1" : "Therapy Line 1"} </span> <span className="font-semibold tabular-nums" style={{ color }}> {Math.round(data.txEligible.metL1).toLocaleString()} </span> </div> </div>- Step 5: Verify lint passes
Run: pnpm lint
Expected: PASS
- Step 6: Commit
git add src/features/reports/forecasting/sections/Comparison/ComparisonSummaryCard.tsxgit commit -m "feat(comparison): conditional summary card rendering for hematology diseases"Task 4: Adapt DOCX summary table for hematology
Section titled “Task 4: Adapt DOCX summary table for hematology”Files:
-
Modify:
src/features/reports/forecasting/sections/Comparison/reporting/buildComparisonReport.ts:33-51 -
Step 1: Update
buildSummaryTableto check disease type
In src/features/reports/forecasting/sections/Comparison/reporting/buildComparisonReport.ts, find the buildSummaryTable function (lines 33-51):
function buildSummaryTable(input: ComparisonReportInput): Table { const headers = [ "Scenario", "Geography", "Indication", "Incidence", "Early Tx Eligible", "Met L1 Tx Eligible", ]; const rows = input.data.map((s) => [ s.meta.label, s.geo, s.indication ?? "—", String(Math.round(s.incidence)), String(Math.round(s.txEligible.early)), String(Math.round(s.txEligible.metL1)), ]); return buildTable(headers, rows);}Replace with:
function buildSummaryTable(input: ComparisonReportInput): Table { const allHematology = input.data.every((s) => s.diseaseType === "Hematology");
const headers = allHematology ? ["Scenario", "Geography", "Indication", "Incidence", "Therapy L1 Tx Eligible"] : ["Scenario", "Geography", "Indication", "Incidence", "Early Tx Eligible", "Met L1 Tx Eligible"];
const rows = input.data.map((s) => allHematology ? [ s.meta.label, s.geo, s.indication ?? "—", String(Math.round(s.incidence)), String(Math.round(s.txEligible.metL1)), ] : [ s.meta.label, s.geo, s.indication ?? "—", String(Math.round(s.incidence)), String(Math.round(s.txEligible.early)), String(Math.round(s.txEligible.metL1)), ], ); return buildTable(headers, rows);}- Step 2: Verify lint passes
Run: pnpm lint
Expected: PASS
- Step 3: Commit
git add src/features/reports/forecasting/sections/Comparison/reporting/buildComparisonReport.tsgit commit -m "feat(comparison): adapt DOCX summary table headers for hematology"Task 5: Final verification
Section titled “Task 5: Final verification”- Step 1: Full lint check
Run: pnpm lint
Expected: PASS with no errors
- Step 2: Build check
Run: pnpm build
Expected: PASS — no type errors, no build failures
- Step 3: Manual verification
Open the app and verify:
- Select a hematology disease (e.g., CLL), create 2+ scenarios, open the Comparison page
- Summary cards show: Context (Geo + Indication), Incidence (no stage rows), Treatment Eligible with “Therapy Line 1” label and correct non-zero value
- Select a solid tumour disease, create 2+ scenarios, open the Comparison page
- Summary cards show the full layout: Context, Incidence & Stage (with Early % and Met %), Treatment Eligible with both “Early Stage” and “Metastatic Line 1”
- Export a comparison DOCX report for a hematology disease
- Summary table has 5 columns (no “Early Tx Eligible”), “Therapy L1 Tx Eligible” header