Skip to content

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.

  1. Overview
  2. Database Layer
  3. Frontend Calculation Layer
  4. Variable Categories
  5. Year-Dependent Mechanisms
  6. Data Flow Diagrams
  7. Quick Reference

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)


ColumnTypeDescription
geovarcharGeography (USA, EU5, Japan, China)
indicationvarcharDisease/indication name
linevarcharTherapy line (null for indication-level, “line2”, “met1”, etc.)
namevarcharVariable name
valuevarcharVariable value
yearintegerReference year (2023 or 2026)
VariableScopeExample ValueDescription
incidenceBaseIndication116285Starting patient count
popBaseGeography1426 (millions)Population base
healthcareAccessIndication95% with healthcare access
earlyStagePercentIndication70% early stage (solid tumors)
metStagePercentIndication30% metastatic (solid tumors)
earlyToEarlyRelapseIndication15% early→early relapse
earlyToMetRelapseIndication10% early→met relapse
transitionRateLine49% transitioning to next line
treatmentRateLine90% receiving treatment
VariableScopeExample ValueDescription
incidenceGrowthIndication0.025Annual growth rate (percentage, 0.025 = 0.025%)
popGrowthGeography0.01Population growth rate (percentage, 0.01 = 0.01%)
categoryLine"metastatic"Line category
displayNameLine"1st Line"Display name
typeIndication"Solid Tumor"Indication type
  1. No multi-year arrays in DB - Each variable has ONE year per geo/indication
  2. Year is metadata - Indicates data vintage, not “which year to calculate”
  3. Growth rates are percentages - 0.025 = 0.025% annual growth (no conversion needed)

File: atoms.ts (deriveDiseaseConfig)

// Database stores percentage directly (0.025 = 0.025%), no conversion needed
const incidenceGrowth = getValue<number>("", "incidenceGrowth");
const growthRate = incidenceGrowth ?? DEFAULT_INCIDENCE_GROWTH_RATE; // 0.5 (0.5%)

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

File: atoms.ts (net price evolution initialization)

// Initialized as zeros - user can edit year-by-year
newRows.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);
}

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:

VariableSourceFile Location
launchPricePerMonthAssumptionsatoms.ts Configuration section
peakShareLine configComputed or override
speedToPeakLine configUptake curve selector
monthsOfTherapyLine configTreatment duration
complianceLine configPatient compliance %
classShareLine config% of class captured
retreatmentIndication0 or 1
moleculeTypeAssumptions”Small Molecule” or “Biologic”
yearOfFirstLaunchAssumptionsCalendar year
marketExclusivityYearsAssumptionsYears until LOE
Stage mix valuesPopulationearlyStagePercent, metStagePercent, etc.

These are stored as arrays, one value per projection year:

VariableStorage LocationHow It Changes
incidenceEvolutionIncidenceEvolution/atoms.tsCompound growth from base
netPriceEvolutionIncidenceEvolution/atoms.tsUser-editable % change per year
erosionRatesIncidenceEvolution/atoms.tsPost-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:

VariableDepends OnComputation
marketSharelaunch, uptake, LOE, erosioncalculateMarketShare in computations.ts
netPricelaunchPrice, priceEvolutioncalculateNetPriceFromAssumptions in computations.ts
newPatientsincidence, share, filterscalculateLinePatients in computations.ts
lineSalespatients, price, compliance, cohort distributioncalculateLineSales 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:

VariableConfig FieldsUsed In
healthcareAccessvalue, min, max, startYearMonte Carlo simulations
treatmentRatevalue, min, max, startYearMonte Carlo simulations
transitionRatevalue, min, max, startYearMonte Carlo simulations
Stage percentagesvalue, min, max, startYearMonte Carlo simulations
Custom variablesvalue, min, max, startYearMonte Carlo simulations

Formula (applyChangeableValue in computations.ts):

if (year < startYear) return baseValue;
// After startYear: compound growth capped at max
return Math.min(baseValue * (1 + min/100) ** (year - startYear), max);

File: computations.ts (calculateMarketShare)

if (year < launchYear) return 0; // Line not active
const yearOffset = year - launchYear; // Position on uptake curve
  • Lines have zero market share before their launch year
  • yearOffset determines position on uptake curve

File: constants.ts (UPTAKE_CURVE)

Predefined curves map years-since-launch to % of peak share:

CurveYear 1Year 2Year 3Year 4Year 5+
1 Year100%100%100%100%100%
3 Year Slow10%50%100%100%100%
5 Year Fast28%64%82%91%100%

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 years
const 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

┌─────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────────────────────┘

FilePurpose
constants.tsPROJECTION_DATA_LENGTH = 20, COHORT_YEARS = 5, DEFAULT_INCIDENCE_GROWTH_RATE = 0.5
constants.tsUPTAKE_CURVE (uptake curves), MOLECULE_SHARE_EROSION, BIOLOGICS_SHARE_EROSION
atoms.tsderiveDiseaseConfig() — growth rate conversion
atoms.tsIncidence array generation (via buildIncidenceEvolution)
computations.tscalculateMarketShare() — launch/uptake/LOE
computations.tscalculateNetPriceFromAssumptions()
computations.tscalculateLinePatients()
computations.tscalculateLineSales()
computations.tsapplyChangeableValue() — Monte Carlo time-varying
IncidenceEvolution/computations.tscomputeGrowthRates(), applyGrowthRateChange()
CalculationFormula
Incidence evolutionbase × (1 + growthRate/100)^year
Growth rate from data((curr - prev) / prev) × 100
Net Price Changeprice × Π(1 + evolution[i]/100)
Market shareuptake × peakShare × classShare × (1 - erosion)
Changeable valuevalue × (1 + min/100)^(year - startYear), capped at max
Line salespatients × price × compliance × months / 1,000,000
const yearIndex = 0 to 21; // Array position
const year = selectedYear + yearIndex; // Calendar year (e.g., 2025, 2026...)
const yearOffset = year - launchYear; // Years since line launch
const erosionOffset = year - loeYear; // Years since LOE

Multi-Year Data Handling (Current Behavior)

Section titled “Multi-Year Data Handling (Current Behavior)”

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
}

File: atoms.ts (ReferenceQuery interface)

interface ReferenceQuery {
geo?: string;
indication?: string;
line?: string;
name?: string;
// NO year field - year is NOT used in filtering!
}
Scenariofiltered.lengthResult
1 row (current state)1Returns the value ✓
0 rows (not found)0Returns null → uses default
2+ rows (multiple years)>1Returns null → uses default

The code does NOT take the latest year - if multiple rows exist for the same geo/indication/name with different years:

  1. Query matches ALL of them (year not in filter)
  2. filtered.length > 1
  3. Returns null
  4. Falls back to DEFAULT_* constants
-- Verified: No duplicates exist
SELECT ... GROUP BY name, geo, indication, line HAVING COUNT(*) > 1
-- Result: 0 rows

Each variable has exactly ONE row per geo/indication/line combination. The year column is purely metadata.

Would require modifying:

  1. ReferenceQuery - add year?: number field
  2. matchesReferenceQuery() - filter by year
  3. OR: Sort by year descending and take first (latest)
  4. OR: Store year-specific values in arrays instead of separate rows

  1. Database stores PARAMETERS (single values with reference year metadata)
  2. Frontend GENERATES arrays (20-year projections from parameters)
  3. Growth rates apply to INCIDENCE only (compound growth formula)
  4. 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)
  5. Static values (line configs, assumptions) are constant across all years
  6. Multi-year duplicates return null - current design assumes 1 row per variable