Year Variables Analysis
Year & Variable Analysis - Forecasting System
Section titled “Year & Variable Analysis - Forecasting System”This document explains how years and variables work throughout the forecasting system, from database storage to frontend calculations.
Table of Contents
Section titled “Table of Contents”- Overview
- Database Layer
- Frontend Calculation Layer
- Variable Categories
- Year-Dependent Mechanisms
- Data Flow Diagrams
- Quick Reference
Overview
Section titled “Overview”The forecasting system uses a parameter-based approach:
- Database stores single-value parameters (base values, growth rates)
- Frontend generates 20-year projection arrays from these parameters
- Calculations use year indices to look up time-varying values
Key Constant: PROJECTION_DATA_LENGTH = 20 (base year + 19 projection years)
Database Layer
Section titled “Database Layer”Source: public.statistics table
Section titled “Source: public.statistics table”| Column | Type | Description |
|---|---|---|
geo | varchar | Geography (USA, EU5, Japan, China) |
indication | varchar | Disease/indication name |
line | varchar | Therapy line (null for indication-level, “line2”, “met1”, etc.) |
name | varchar | Variable name |
value | varchar | Variable value |
year | integer | Reference year (2023 or 2026) |
Variables by Reference Year
Section titled “Variables by Reference Year”Year 2023 - Base/Historical Values
Section titled “Year 2023 - Base/Historical Values”| Variable | Scope | Example Value | Description |
|---|---|---|---|
incidenceBase | Indication | 116285 | Starting patient count |
popBase | Geography | 1426 (millions) | Population base |
healthcareAccess | Indication | 95 | % with healthcare access |
earlyStagePercent | Indication | 70 | % early stage (solid tumors) |
metStagePercent | Indication | 30 | % metastatic (solid tumors) |
earlyToEarlyRelapse | Indication | 15 | % early→early relapse |
earlyToMetRelapse | Indication | 10 | % early→met relapse |
transitionRate | Line | 49 | % transitioning to next line |
treatmentRate | Line | 90 | % receiving treatment |
Year 2026 - Growth Rates & Configuration
Section titled “Year 2026 - Growth Rates & Configuration”| Variable | Scope | Example Value | Description |
|---|---|---|---|
incidenceGrowth | Indication | 0.025 | Annual growth rate (percentage, 0.025 = 0.025%) |
popGrowth | Geography | 0.01 | Population growth rate (percentage, 0.01 = 0.01%) |
category | Line | "metastatic" | Line category |
displayName | Line | "1st Line" | Display name |
type | Indication | "Solid Tumor" | Indication type |
Important Notes
Section titled “Important Notes”- No multi-year arrays in DB - Each variable has ONE year per geo/indication
- Year is metadata - Indicates data vintage, not “which year to calculate”
- Growth rates are percentages -
0.025= 0.025% annual growth (no conversion needed)
Frontend Calculation Layer
Section titled “Frontend Calculation Layer”Growth Rate Usage
Section titled “Growth Rate Usage”File: atoms.ts (deriveDiseaseConfig)
// Database stores percentage directly (0.025 = 0.025%), no conversion neededconst incidenceGrowth = getValue<number>("", "incidenceGrowth");const growthRate = incidenceGrowth ?? DEFAULT_INCIDENCE_GROWTH_RATE; // 0.5 (0.5%)Incidence Array Generation
Section titled “Incidence Array Generation”File: atoms.ts (uses buildIncidenceEvolution from IncidenceEvolution/computations.ts)
const incidenceValues = Array.from({ length: PROJECTION_DATA_LENGTH }, (_, i) => Math.round(baseIncidence * (1 + growthRate / 100) ** i),);Formula: incidence[year] = baseIncidence × (1 + growthRate/100)^year
Example: Base=100,000, Growth=0.5%
- Year 0: 100,000
- Year 1: 100,500
- Year 5: 102,525
- Year 10: 105,114
- Year 21: 111,071
Net Price Change
Section titled “Net Price Change”File: atoms.ts (net price evolution initialization)
// Initialized as zeros - user can edit year-by-yearnewRows.push({ name: "netPriceEvolution", value: new Array(PROJECTION_DATA_LENGTH).fill(0),});Applied in: computations.ts (calculateNetPriceFromAssumptions)
// Each year compounds: price[i] = price[i-1] × (1 + evolution[i]/100)let price = launchPrice;for (let i = 0; i <= yearIndex; i++) { price = price * (1 + netPriceEvolution[i] / 100);}Variable Categories
Section titled “Variable Categories”1. STATIC Variables (Single Value, All Years)
Section titled “1. STATIC Variables (Single Value, All Years)”These values do NOT change across the 20-year projection:
| Variable | Source | File Location |
|---|---|---|
launchPricePerMonth | Assumptions | atoms.ts Configuration section |
peakShare | Line config | Computed or override |
speedToPeak | Line config | Uptake curve selector |
monthsOfTherapy | Line config | Treatment duration |
compliance | Line config | Patient compliance % |
classShare | Line config | % of class captured |
retreatment | Indication | 0 or 1 |
moleculeType | Assumptions | ”Small Molecule” or “Biologic” |
yearOfFirstLaunch | Assumptions | Calendar year |
marketExclusivityYears | Assumptions | Years until LOE |
| Stage mix values | Population | earlyStagePercent, metStagePercent, etc. |
2. TIME-VARYING Arrays (22 Values)
Section titled “2. TIME-VARYING Arrays (22 Values)”These are stored as arrays, one value per projection year:
| Variable | Storage Location | How It Changes |
|---|---|---|
incidenceEvolution | IncidenceEvolution/atoms.ts | Compound growth from base |
netPriceEvolution | IncidenceEvolution/atoms.ts | User-editable % change per year |
erosionRates | IncidenceEvolution/atoms.ts | Post-LOE share erosion curve |
3. COMPUTED Per-Year (Derived During Calculation)
Section titled “3. COMPUTED Per-Year (Derived During Calculation)”These are calculated fresh for each year during projection:
| Variable | Depends On | Computation |
|---|---|---|
marketShare | launch, uptake, LOE, erosion | calculateMarketShare in computations.ts |
netPrice | launchPrice, priceEvolution | calculateNetPriceFromAssumptions in computations.ts |
newPatients | incidence, share, filters | calculateLinePatients in computations.ts |
lineSales | patients, price, compliance, cohort distribution | calculateLineSales in computations.ts |
4. CHANGEABLE Variables (Monte Carlo Only)
Section titled “4. CHANGEABLE Variables (Monte Carlo Only)”Variables with optional startYear for time-varying simulation:
| Variable | Config Fields | Used In |
|---|---|---|
healthcareAccess | value, min, max, startYear | Monte Carlo simulations |
treatmentRate | value, min, max, startYear | Monte Carlo simulations |
transitionRate | value, min, max, startYear | Monte Carlo simulations |
| Stage percentages | value, min, max, startYear | Monte Carlo simulations |
| Custom variables | value, min, max, startYear | Monte Carlo simulations |
Formula (applyChangeableValue in computations.ts):
if (year < startYear) return baseValue;// After startYear: compound growth capped at maxreturn Math.min(baseValue * (1 + min/100) ** (year - startYear), max);Year-Dependent Mechanisms
Section titled “Year-Dependent Mechanisms”1. Launch Year Logic
Section titled “1. Launch Year Logic”File: computations.ts (calculateMarketShare)
if (year < launchYear) return 0; // Line not activeconst yearOffset = year - launchYear; // Position on uptake curve- Lines have zero market share before their launch year
yearOffsetdetermines position on uptake curve
2. Uptake Curves
Section titled “2. Uptake Curves”File: constants.ts (UPTAKE_CURVE)
Predefined curves map years-since-launch to % of peak share:
| Curve | Year 1 | Year 2 | Year 3 | Year 4 | Year 5+ |
|---|---|---|---|---|---|
| 1 Year | 100% | 100% | 100% | 100% | 100% |
| 3 Year Slow | 10% | 50% | 100% | 100% | 100% |
| 5 Year Fast | 28% | 64% | 82% | 91% | 100% |
3. Loss of Exclusivity (LOE) Erosion
Section titled “3. Loss of Exclusivity (LOE) Erosion”File: computations.ts (erosion logic within calculateMarketShare)
const loeDateObj = new Date(`${loeDate}T00:00:00Z`);const loeYear = loeDateObj.getUTCFullYear();if (year >= loeYear && erosionRates.length > 0) { const loeMonth = loeDateObj.getUTCMonth() + 1; const erosionOffset = year - loeYear; const preRate = (erosionRates[erosionOffset] / 100) * (13 - loeMonth) / 12; const postRate = (erosionRates[erosionOffset + 1] / 100) * (loeMonth - 1) / 12; erosion = 1 - (preRate + postRate);}- No erosion before LOE year
- After LOE: month-weighted blending of adjacent erosion rates (matches
calculateLoEImpact)
4. Cohort Aggregation (Multi-Year Therapy)
Section titled “4. Cohort Aggregation (Multi-Year Therapy)”File: computations.ts (cohort logic within calculateLineSales)
// COHORT_YEARS = 5// Aggregates patients starting treatment across 5 yearsconst salesValues = Array.from({ length: COHORT_YEARS }, (_, yearOffset) => calculateSale(line, year, yearOffset, ...),);For therapies >12 months, sales include contributions from:
- Patients starting this year
- Patients who started 1-4 years ago still on therapy
Data Flow Diagrams
Section titled “Data Flow Diagrams”Database → Frontend → Calculation
Section titled “Database → Frontend → Calculation”┌─────────────────────────────────────────────────────────────────────────────┐│ DATABASE (statistics table) │├─────────────────────────────────────────────────────────────────────────────┤│ incidenceBase (2023): 116285 ││ incidenceGrowth (2026): 0.025 (percentage, 0.025 = 0.025%) ││ healthcareAccess (2023): 95 ││ transitionRate (2023): 49 │└───────────────────────────────────┬─────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────────────┐│ FRONTEND INITIALIZATION (atoms.ts) │├─────────────────────────────────────────────────────────────────────────────┤│ deriveDiseaseConfig(): ││ growthRate = 0.025 (used directly, no conversion) ││ ││ initIndicationAtom(): ││ incidenceEvolution = [116285, 116314, 116343, ..., 116893] (22 values) ││ netPriceEvolution = [0, 0, 0, ..., 0] (22 zeros, user-editable) │└───────────────────────────────────┬─────────────────────────────────────────┘ │ ▼┌─────────────────────────────────────────────────────────────────────────────┐│ CALCULATION LOOP (SalesChart/Model) │├─────────────────────────────────────────────────────────────────────────────┤│ for yearIndex = 0 to 21: ││ year = selectedYear + yearIndex ││ yearIncidence = incidenceEvolution[yearIndex] ││ ││ for each line: ││ marketShare = f(launch, uptake[yearOffset], erosion[year-loeYear]) ││ netPrice = launchPrice × Π(1 + priceEvolution[i]/100) ││ patients = yearIncidence × stageMix × filters × marketShare ││ sales = patients × netPrice × compliance × months / 1M │└─────────────────────────────────────────────────────────────────────────────┘Variable Scope Diagram
Section titled “Variable Scope Diagram”┌─────────────────────────────────────────────────────────────────────────────┐│ GEOGRAPHY LEVEL ││ popBase, popGrowth │├─────────────────────────────────────────────────────────────────────────────┤│ INDICATION LEVEL ││ incidenceBase, incidenceGrowth, healthcareAccess, type ││ earlyStagePercent, metStagePercent, earlyToEarlyRelapse, earlyToMetRelapse ││ incidenceEvolution[22], netPriceEvolution[22], erosionRates[21] ││ retreatment, moleculeType, yearOfFirstLaunch, loeDate │├─────────────────────────────────────────────────────────────────────────────┤│ LINE LEVEL ││ transitionRate, treatmentRate, category, displayName ││ launch, peakShare, speedToPeak, classShare, monthsOfTherapy, compliance │└─────────────────────────────────────────────────────────────────────────────┘Quick Reference
Section titled “Quick Reference”Key Files
Section titled “Key Files”| File | Purpose |
|---|---|
constants.ts | PROJECTION_DATA_LENGTH = 20, COHORT_YEARS = 5, DEFAULT_INCIDENCE_GROWTH_RATE = 0.5 |
constants.ts | UPTAKE_CURVE (uptake curves), MOLECULE_SHARE_EROSION, BIOLOGICS_SHARE_EROSION |
atoms.ts | deriveDiseaseConfig() — growth rate conversion |
atoms.ts | Incidence array generation (via buildIncidenceEvolution) |
computations.ts | calculateMarketShare() — launch/uptake/LOE |
computations.ts | calculateNetPriceFromAssumptions() |
computations.ts | calculateLinePatients() |
computations.ts | calculateLineSales() |
computations.ts | applyChangeableValue() — Monte Carlo time-varying |
IncidenceEvolution/computations.ts | computeGrowthRates(), applyGrowthRateChange() |
Formulas
Section titled “Formulas”| Calculation | Formula |
|---|---|
| Incidence evolution | base × (1 + growthRate/100)^year |
| Growth rate from data | ((curr - prev) / prev) × 100 |
| Net Price Change | price × Π(1 + evolution[i]/100) |
| Market share | uptake × peakShare × classShare × (1 - erosion) |
| Changeable value | value × (1 + min/100)^(year - startYear), capped at max |
| Line sales | patients × price × compliance × months / 1,000,000 |
Year Index vs Calendar Year
Section titled “Year Index vs Calendar Year”const yearIndex = 0 to 21; // Array positionconst year = selectedYear + yearIndex; // Calendar year (e.g., 2025, 2026...)const yearOffset = year - launchYear; // Years since line launchconst erosionOffset = year - loeYear; // Years since LOEMulti-Year Data Handling (Current Behavior)
Section titled “Multi-Year Data Handling (Current Behavior)”Current Query Logic
Section titled “Current Query Logic”File: atoms.ts (getReferenceValue)
export function getReferenceValue(rows, query, name): ReferenceValue | null { const filtered = getReferenceRows(rows, { ...query, name }); return filtered.length === 1 ? filtered[0].value : null; // ← KEY LINE}ReferenceQuery Does NOT Include Year
Section titled “ReferenceQuery Does NOT Include Year”File: atoms.ts (ReferenceQuery interface)
interface ReferenceQuery { geo?: string; indication?: string; line?: string; name?: string; // NO year field - year is NOT used in filtering!}What Happens with Duplicate Years
Section titled “What Happens with Duplicate Years”| Scenario | filtered.length | Result |
|---|---|---|
| 1 row (current state) | 1 | Returns the value ✓ |
| 0 rows (not found) | 0 | Returns null → uses default |
| 2+ rows (multiple years) | >1 | Returns null → uses default |
Critical Finding
Section titled “Critical Finding”The code does NOT take the latest year - if multiple rows exist for the same geo/indication/name with different years:
- Query matches ALL of them (year not in filter)
filtered.length > 1- Returns
null - Falls back to
DEFAULT_*constants
Current Database State
Section titled “Current Database State”-- Verified: No duplicates existSELECT ... GROUP BY name, geo, indication, line HAVING COUNT(*) > 1-- Result: 0 rowsEach variable has exactly ONE row per geo/indication/line combination. The year column is purely metadata.
If Multi-Year Support is Needed
Section titled “If Multi-Year Support is Needed”Would require modifying:
ReferenceQuery- addyear?: numberfieldmatchesReferenceQuery()- filter by year- OR: Sort by year descending and take first (latest)
- OR: Store year-specific values in arrays instead of separate rows
Summary
Section titled “Summary”- Database stores PARAMETERS (single values with reference year metadata)
- Frontend GENERATES arrays (20-year projections from parameters)
- Growth rates apply to INCIDENCE only (compound growth formula)
- Other time-varying behavior uses:
- Year-indexed arrays (price evolution, erosion)
- Launch year logic (line activation, uptake curves)
- LOE year logic (erosion start)
- Changeable configs (Monte Carlo what-if scenarios)
- Static values (line configs, assumptions) are constant across all years
- Multi-year duplicates return null - current design assumes 1 row per variable