Skip to content

Master Dag

Scope: Calculation dependency graph. For technical architecture (Jotai state, React Query, component patterns), see ARCHITECTURE.md.

Complete system in a single diagram - verified against source code.

flowchart TB
    subgraph INPUTS["📥 INPUT VARIABLES"]
        direction TB

        subgraph INC_INPUTS["Incidence Inputs"]
            BASE_INC[Base Incidence]
            POP_BASE[Pop Base<br/>per-capita fallback source]
            GEO[Geography<br/>USA/EU5/Japan]
            INDICATION[Indication<br/>Disease name]
            INC_EVO[Incidence Evolution<br/>growth rates array]
        end

        subgraph STAGE_INPUTS["Stage Mix Inputs"]
            EARLY_PCT[Early Stage %]
            MET_PCT[Metastatic %]
            E2E_RELAPSE[Early→Early Relapse %]
            E2M_RELAPSE[Early→Met Relapse %]
            HC_ACCESS[Healthcare Access %]
        end

        subgraph PATIENT_INPUTS["Patient Flow Inputs"]
            TREAT_RATE[Treatment Rate %]
            TRANS_RATE[Transition Rate %]
            RETREAT[Retreatment Factor]
            CUSTOM_VARS[Custom Variables<br/>with growth rates]
        end

        subgraph PEAK_INPUTS["Peak Share Inputs"]
            LAUNCH_ORD[Launch Order<br/>1-10]
            BEST_CLASS[Best in Class<br/>true/false]
            DELAY_COMP[Delay vs Competition<br/>quarters]
            NUM_COMP[Num Competitors<br/>1-10]
            CLASS_SHARE[Class Share %<br/>default 100]
        end

        subgraph MARKET_INPUTS["Market Inputs"]
            LAUNCH_DT[Launch Date<br/>YYYY-MM-DD]
            SPEED_PEAK[Speed to Peak<br/>e.g. 3 Year Medium]
            LOE_DATE[LoE Date]
            MOL_TYPE[Molecule Type<br/>Biologic/Small Mol]
            EVENTS[Market Events<br/>impactPercent + startDate]
        end

        subgraph PRICE_INPUTS["Pricing Inputs"]
            YEAR_FIRST_LAUNCH[Year of First Launch]
            LAUNCH_PRICE[Launch Price<br/>$/month]
            ANN_PRICE_CHG[Annual Price Change %]
            NET_PRICE_EVO[Net Price Change<br/>custom array]
            MKT_EXCL_YRS[Market Exclusivity Years]
        end

        subgraph SALES_INPUTS["Sales Inputs"]
            MONTHS_THER[Months of Therapy<br/>can exceed 12]
            COMPLIANCE[Compliance %]
        end

        subgraph MC_INPUTS["Monte Carlo Inputs"]
            MC_VARS[Variable Ranges<br/>min/mode/max]
            MC_DIST[Distribution Type<br/>triangular/normal/uniform]
            MC_ITERS[Iterations<br/>default 1000]
        end
    end

    subgraph REFERENCE["📚 REFERENCE DATA"]
        LAUNCH_MATRIX[LAUNCH_ORDERS<br/>10x10 Matrix]
        BIC_ARRAY[BEST_IN_CLASS<br/>Array by competitors]
        UPTAKE_CURVES[UPTAKE_CURVE<br/>30+ curves]
        MOL_EROSION[MOLECULE_SHARE_EROSION<br/>Small molecule rates]
        BIO_EROSION[BIOLOGICS_SHARE_EROSION<br/>Biologic rates]
        COHORT_YRS[COHORT_YEARS = 5<br/>Multi-year aggregation]
    end

    subgraph CALC_INC["🧮 INCIDENCE CALCULATION"]
        YEAR_INC[Year Incidence<br/>= Base × 1+GrowthRate^n]
        INC_GROWTH[Apply Growth Rate<br/>from evolution array]
    end

    BASE_INC --> YEAR_INC
    GEO --> BASE_INC
    POP_BASE -.per-capita fallback.-> BASE_INC
    INC_EVO --> INC_GROWTH
    YEAR_INC --> INC_GROWTH

    subgraph CALC_ADDR["🧮 ADDRESSABLE POPULATION"]
        direction TB

        subgraph EARLY_CALC["Early Stage Path"]
            EARLY_BASE[Incidence × Early%]
            EARLY_REL[× 1+E2E%]
            EARLY_HC[× HC%]
            EARLY_ADDR[Early Addressable]
        end

        subgraph MET_CALC["Metastatic Path"]
            MET_DENOVO[Incidence × Met%]
            MET_REL[+ Incidence × Early% × E2M%]
            MET_SUM[Sum]
            MET_HC[× HC%]
            MET_ADDR[Met Addressable]
        end

        subgraph THER_CALC["Therapy Path - Hematology"]
            THER_ADDR[Therapy Addressable<br/>= Incidence × HC%]
        end

        subgraph CUSTOM_CALC["Custom Variables"]
            CUSTOM_MULT[Custom Multiplier<br/>product of all custom vars]
            FINAL_ADDR[Final Addressable<br/>× customMultiplier]
        end
    end

    INC_GROWTH --> EARLY_BASE
    EARLY_PCT --> EARLY_BASE
    EARLY_BASE --> EARLY_REL
    E2E_RELAPSE --> EARLY_REL
    EARLY_REL --> EARLY_HC
    HC_ACCESS --> EARLY_HC
    EARLY_HC --> EARLY_ADDR

    INC_GROWTH --> MET_DENOVO
    MET_PCT --> MET_DENOVO
    INC_GROWTH --> MET_REL
    EARLY_PCT --> MET_REL
    E2M_RELAPSE --> MET_REL
    MET_DENOVO --> MET_SUM
    MET_REL --> MET_SUM
    MET_SUM --> MET_HC
    HC_ACCESS --> MET_HC
    MET_HC --> MET_ADDR

    INC_GROWTH --> THER_ADDR
    HC_ACCESS --> THER_ADDR

    CUSTOM_VARS --> CUSTOM_MULT
    EARLY_ADDR --> FINAL_ADDR
    MET_ADDR --> FINAL_ADDR
    THER_ADDR --> FINAL_ADDR
    CUSTOM_MULT --> FINAL_ADDR

    subgraph CALC_PEAK["🧮 PEAK SHARE CALCULATION"]
        BASE_SHARE[Base Share<br/>from LAUNCH_ORDERS matrix]
        BIC_BONUS[BIC Bonus<br/>from BEST_IN_CLASS array]
        DELAY_PEN[Delay Penalty<br/>= delay > 3 ? delay × 0.5 : 0]
        PEAK_SHARE[Peak Share %<br/>= clamp 0-100: Base + BIC - Delay]
        EFF_PEAK[Effective Peak Share<br/>= Peak × ClassShare / 100]
    end

    LAUNCH_ORD --> BASE_SHARE
    NUM_COMP --> BASE_SHARE
    LAUNCH_MATRIX --> BASE_SHARE
    BEST_CLASS --> BIC_BONUS
    NUM_COMP --> BIC_BONUS
    BIC_ARRAY --> BIC_BONUS
    DELAY_COMP --> DELAY_PEN
    BASE_SHARE --> PEAK_SHARE
    BIC_BONUS --> PEAK_SHARE
    DELAY_PEN --> PEAK_SHARE
    PEAK_SHARE --> EFF_PEAK
    CLASS_SHARE --> EFF_PEAK

    subgraph CALC_MKS["🧮 MARKET SHARE OVER TIME"]
        YEAR_OFFSET[Year Offset<br/>= Year - LaunchYear]
        UPTAKE_VAL[Uptake Value<br/>from UPTAKE_CURVE by speed and offset]
        PREV_UPTAKE[Previous Uptake<br/>from UPTAKE_CURVE by speed and offset]
        WEIGHTED_SHARE[Weighted Share<br/>month-adjusted blend]
        EVENT_IMPACT[Event Impact<br/>Σ event.impactPercent × uptake]
        BASE_MKS[Base Market Share<br/>= Weighted × Peak%]
        EROSION_LOOKUP[Erosion Rate Lookup<br/>by molecule type]
        LOE_IMPACT[LoE Impact<br/>month-weighted erosion]
        MKT_SHARE[Final Market Share %<br/>= Base + Events × LoE]
    end

    LAUNCH_DT --> YEAR_OFFSET
    YEAR_OFFSET --> UPTAKE_VAL
    SPEED_PEAK --> UPTAKE_VAL
    UPTAKE_CURVES --> UPTAKE_VAL
    UPTAKE_VAL --> WEIGHTED_SHARE
    YEAR_OFFSET --> PREV_UPTAKE
    PREV_UPTAKE --> WEIGHTED_SHARE
    LAUNCH_DT --> WEIGHTED_SHARE
    WEIGHTED_SHARE --> BASE_MKS
    EFF_PEAK --> BASE_MKS
    EVENTS --> EVENT_IMPACT
    UPTAKE_VAL --> EVENT_IMPACT
    LOE_DATE --> EROSION_LOOKUP
    MOL_TYPE --> EROSION_LOOKUP
    MOL_EROSION --> EROSION_LOOKUP
    BIO_EROSION --> EROSION_LOOKUP
    EROSION_LOOKUP --> LOE_IMPACT
    LOE_DATE --> LOE_IMPACT
    BASE_MKS --> MKT_SHARE
    EVENT_IMPACT --> MKT_SHARE
    LOE_IMPACT --> MKT_SHARE

    subgraph CALC_PAT["🧮 PATIENT FLOW BY LINE"]
        direction TB

        subgraph LINE_1["1L First Line"]
            L1_ELIG[1L Eligible<br/>= Addressable × TreatRate%]
            L1_NEW[1L New Patients<br/>= Eligible × MktShare%]
        end

        subgraph LINE_2["2L Second Line"]
            L2_POOL[2L Pool<br/>= 1L_Elig - 1L_New × Retreat]
            L2_TRANS[× Transition%]
            L2_ELIG[2L Eligible<br/>× TreatRate%]
            L2_NEW[2L New Patients<br/>= Eligible × MktShare%]
        end

        subgraph LINE_3["3L+ Third Line+"]
            L3_POOL[3L Pool<br/>= 2L_Elig - 2L_New × Retreat]
            L3_ELIG[3L+ Eligible<br/>= Pool × Transition%<br/>NO TreatRate]
            L3_NEW[3L+ New Patients<br/>= Eligible × MktShare%]
        end
    end

    FINAL_ADDR --> L1_ELIG
    TREAT_RATE --> L1_ELIG
    L1_ELIG --> L1_NEW
    MKT_SHARE --> L1_NEW

    L1_ELIG --> L2_POOL
    L1_NEW --> L2_POOL
    RETREAT --> L2_POOL
    L2_POOL --> L2_TRANS
    TRANS_RATE --> L2_TRANS
    L2_TRANS --> L2_ELIG
    TREAT_RATE --> L2_ELIG
    L2_ELIG --> L2_NEW
    MKT_SHARE --> L2_NEW

    L2_ELIG --> L3_POOL
    L2_NEW --> L3_POOL
    RETREAT --> L3_POOL
    L3_POOL --> L3_ELIG
    TRANS_RATE --> L3_ELIG
    L3_ELIG --> L3_NEW
    MKT_SHARE --> L3_NEW

    subgraph CALC_PRICE["🧮 NET PRICE CALCULATION"]
        YEARS_FROM_LAUNCH[Years from Launch<br/>= Year - YearOfFirstLaunch]
        HAS_CUSTOM{Has Custom<br/>Evolution?}
        DURATION_CALC[Duration Calculation<br/>capped near LoE by<br/>marketExclusivityYears]
        COMPOUND_PRICE[Compound Price<br/>= Launch × 1+APC% ^ duration]
        CUSTOM_PRICE[Custom Price<br/>cumulative year changes]
        NET_PRICE[Net Price $/month]
    end

    YEAR_FIRST_LAUNCH --> YEARS_FROM_LAUNCH
    NET_PRICE_EVO --> HAS_CUSTOM
    HAS_CUSTOM -->|No| COMPOUND_PRICE
    HAS_CUSTOM -->|Yes| CUSTOM_PRICE
    LAUNCH_PRICE --> COMPOUND_PRICE
    ANN_PRICE_CHG --> COMPOUND_PRICE
    YEARS_FROM_LAUNCH --> DURATION_CALC
    LOE_DATE --> DURATION_CALC
    MKT_EXCL_YRS --> DURATION_CALC
    DURATION_CALC --> COMPOUND_PRICE
    LAUNCH_PRICE --> CUSTOM_PRICE
    NET_PRICE_EVO --> CUSTOM_PRICE
    COMPOUND_PRICE --> NET_PRICE
    CUSTOM_PRICE --> NET_PRICE

    subgraph CALC_SALES["🧮 LINE SALES CALCULATION"]
        direction TB

        subgraph COHORT_AGG["Multi-Year Cohort Aggregation"]
            COHORT_LOOP[For each of 5 cohort years]
            COHORT_PATIENTS[Get patients from<br/>year - offset]
            COHORT_MONTHS[Calculate months<br/>for this cohort]
        end

        subgraph SALES_1["1L Sales"]
            S1_MULT[NewPatients × NetPrice]
            S1_COMP[× Compliance%]
            S1_MOT[× months for cohort]
            S1_DIV[÷ 1,000,000]
            L1_SALES[1L Sales $M<br/>sum of all cohorts]
        end

        subgraph SALES_2["2L Sales"]
            L2_SALES[2L Sales $M]
        end

        subgraph SALES_3["3L+ Sales"]
            L3_SALES[3L+ Sales $M]
        end
    end

    COHORT_YRS --> COHORT_LOOP
    COHORT_LOOP --> COHORT_PATIENTS
    COHORT_PATIENTS --> COHORT_MONTHS
    MONTHS_THER --> COHORT_MONTHS

    L1_NEW --> S1_MULT
    NET_PRICE --> S1_MULT
    S1_MULT --> S1_COMP
    COMPLIANCE --> S1_COMP
    S1_COMP --> S1_MOT
    COHORT_MONTHS --> S1_MOT
    S1_MOT --> S1_DIV
    S1_DIV --> L1_SALES

    L2_NEW --> L2_SALES
    NET_PRICE --> L2_SALES
    COMPLIANCE --> L2_SALES
    COHORT_MONTHS --> L2_SALES

    L3_NEW --> L3_SALES
    NET_PRICE --> L3_SALES
    COMPLIANCE --> L3_SALES
    COHORT_MONTHS --> L3_SALES

    subgraph CALC_TOTAL["🧮 TOTAL SALES"]
        SALES_SUM[Sum All Lines]
        TOTAL_SALES[Total Sales $M]
    end

    L1_SALES --> SALES_SUM
    L2_SALES --> SALES_SUM
    L3_SALES --> SALES_SUM
    SALES_SUM --> TOTAL_SALES

    subgraph CALC_MC["🎲 MONTE CARLO SIMULATION"]
        direction TB

        subgraph MC_SAMPLE["Sampling - per iteration"]
            SAMPLE_HC[Sample HC%]
            SAMPLE_TR[Sample TreatRate%]
            SAMPLE_STAGE[Sample Stage Mix<br/>Early/Met/E2E/E2M]
            SAMPLE_PS[Sample PeakShare]
            SAMPLE_MOT[Sample MOT]
            SAMPLE_LP[Sample LaunchPrice]
            SAMPLE_CUSTOM[Sample Custom Vars]
        end

        subgraph MC_ITER["Iteration Loop - 1000x"]
            CLONE_STATE[Clone Base State<br/>shallow copy]
            APPLY_SAMPLES[Apply Sampled Values<br/>to cloned state]
            RUN_CALC[Run Full Calculation<br/>using forecasting/math/forecasting.ts]
            COLLECT_RESULT[Collect Year Sales]
        end

        subgraph MC_ANALYZE["Statistical Analysis"]
            ALL_RESULTS[All Results<br/>1000 × numYears]
            CALC_P10[P10 - quantile 0.1]
            CALC_P50[P50 - quantile 0.5]
            CALC_P90[P90 - quantile 0.9]
            CALC_MEAN[Mean]
        end

        subgraph MC_TORNADO["Tornado Analysis"]
            HOLD_BASE[Hold all at mostLikely]
            VAR_MIN[Run with var at min]
            VAR_MAX[Run with var at max]
            IMPACT[Impact = abs max - min]
            RANK[Sort by total impact<br/>ascending]
        end
    end

    MC_VARS --> SAMPLE_HC
    MC_VARS --> SAMPLE_TR
    MC_VARS --> SAMPLE_STAGE
    MC_VARS --> SAMPLE_PS
    MC_VARS --> SAMPLE_MOT
    MC_VARS --> SAMPLE_LP
    MC_VARS --> SAMPLE_CUSTOM
    MC_DIST --> SAMPLE_HC

    SAMPLE_HC --> APPLY_SAMPLES
    SAMPLE_TR --> APPLY_SAMPLES
    SAMPLE_STAGE --> APPLY_SAMPLES
    SAMPLE_PS --> APPLY_SAMPLES
    SAMPLE_MOT --> APPLY_SAMPLES
    SAMPLE_LP --> APPLY_SAMPLES
    SAMPLE_CUSTOM --> APPLY_SAMPLES

    CLONE_STATE --> APPLY_SAMPLES
    APPLY_SAMPLES --> RUN_CALC
    RUN_CALC --> COLLECT_RESULT
    COLLECT_RESULT --> ALL_RESULTS

    ALL_RESULTS --> CALC_P10
    ALL_RESULTS --> CALC_P50
    ALL_RESULTS --> CALC_P90
    ALL_RESULTS --> CALC_MEAN

    MC_VARS --> HOLD_BASE
    HOLD_BASE --> VAR_MIN
    HOLD_BASE --> VAR_MAX
    VAR_MIN --> IMPACT
    VAR_MAX --> IMPACT
    IMPACT --> RANK

    subgraph OUTPUTS["📤 OUTPUTS"]
        direction TB

        subgraph MODEL_OUT["Model Outputs"]
            OUT_PAT_LINE[Patients by Line/Year]
            OUT_MKS_LINE[Market Share by Line/Year]
            OUT_SALES_LINE[Sales by Line/Year]
            OUT_TOTAL[Total Sales by Year]
        end

        subgraph CHART_OUT["Chart Outputs"]
            SALES_CHART[Stacked Bar Chart<br/>Sales by Line]
        end

        subgraph MC_OUT["Monte Carlo Outputs"]
            P10_FORECAST[P10 Forecast<br/>Pessimistic]
            P50_FORECAST[P50 Forecast<br/>Median]
            P90_FORECAST[P90 Forecast<br/>Optimistic]
            TORNADO_CHART[Tornado Chart<br/>Sensitivity ranked]
        end
    end

    L1_NEW --> OUT_PAT_LINE
    L2_NEW --> OUT_PAT_LINE
    L3_NEW --> OUT_PAT_LINE
    MKT_SHARE --> OUT_MKS_LINE
    L1_SALES --> OUT_SALES_LINE
    L2_SALES --> OUT_SALES_LINE
    L3_SALES --> OUT_SALES_LINE
    TOTAL_SALES --> OUT_TOTAL

    OUT_SALES_LINE --> SALES_CHART

    CALC_P10 --> P10_FORECAST
    CALC_P50 --> P50_FORECAST
    CALC_P90 --> P90_FORECAST
    RANK --> TORNADO_CHART

    %% Container Styling per Legend - Dark colors for contrast
    style INPUTS fill:#1565c0,stroke:#0d47a1,color:#fff
    style REFERENCE fill:#e65100,stroke:#bf360c,color:#fff
    style CALC_INC fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_ADDR fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_PEAK fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_MKS fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_PAT fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_PRICE fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_SALES fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_TOTAL fill:#2e7d32,stroke:#1b5e20,color:#fff
    style CALC_MC fill:#ad1457,stroke:#880e4f,color:#fff
    style OUTPUTS fill:#6a1b9a,stroke:#4a148c,color:#fff
ContainerColorHexMeaning
INPUTSBlue#1565c0Input Variables
REFERENCEOrange#e65100Reference Data / Lookup Tables
CALC_*Green#2e7d32Calculated Values
CALC_MCMagenta#ad1457Monte Carlo Simulation
OUTPUTSPurple#6a1b9aOutput Values

Source: src/features/forecasting/math/forecasting.ts (incidence computed via buildIncidenceEvolution in src/features/forecasting/math/incidence-evolution.ts)

incidence[0] = round(BaseIncidence)
incidence[i] = round(incidence[i-1] × (1 + GrowthRate / 100))
YearIncidence = incidence[yearIndex]

Source: src/features/forecasting/math/forecasting.ts (calculateLineFunnelForDisplay)

EarlyAddressable = Incidence × (EarlyStage% / 100) × (1 + EarlyToEarlyRelapse% / 100) × (HC% / 100)
MetAddressable = (Incidence × MetStage% / 100 + Incidence × EarlyStage% / 100 × EarlyToMetRelapse% / 100) × (HC% / 100)

Source: src/features/forecasting/math/forecasting.ts (hematology path in calculateLineFunnelForDisplay)

TherapyAddressable = Incidence × (HC% / 100)

Source: src/features/forecasting/math/forecasting.tsgetCustomMultiplier (display) or getCustomMultiplierForYear(vars, year) (live Model/SalesChart, Drug Treated Patients)

customMultiplier = Π(customVar.value / 100) for all custom variables
FinalAddressable = Addressable × customMultiplier
// For variables with `changeable && startYear !== null`, the per-year value
// replaces customVar.value via applyChangeableValue(baseValue, year, growth,
// max, startYear) — see Section 2 "Growth Projection" in FORECASTING_MODEL.md.

Source: src/features/forecasting/math/forecasting.ts (calculatePeakShare)

order = clamp(launchOrder, 1, 10)
competitors = clamp(numCompetitors, 1, 10)
baseShare = LAUNCH_ORDERS[order - 1][competitors - 1]
bicBonus = bestInClass ? BEST_IN_CLASS[competitors - 1] : 0
delayPenalty = delayVsCompetition > 3 ? delayVsCompetition × 0.5 : 0
PeakShare (within class) = clamp(0, 100, baseShare + bicBonus - delayPenalty)
EffectivePeakShare = PeakShare × (classShare / 100)

The EffectivePeakShare is used in market share calculations.

Source: src/features/forecasting/math/forecasting.ts (calculateMarketShare) — used by Model, SalesChart, Comparison, and the DOCX export.

yearOffset = year - launchYear
uptake = UPTAKE_CURVE[speedToPeak][yearOffset + 1]
prevUptake = yearOffset > 0 ? UPTAKE_CURVE[speedToPeak][yearOffset] : 0
launchMonth = launch.getUTCMonth() + 1
weightedShare = (uptake × (13 - launchMonth) + prevUptake × (launchMonth - 1)) / 12
baseShare = weightedShare × (peakShare / 100)
eventImpact = Σ over events with startDate ≤ year of (event.impactPercent × monthWeightedUptake)
// same speedToPeak uptake curve as the line; events ramp via the line's curve
erosionOffset = year - loeYear
loeMonth = loeDate.getUTCMonth() + 1
lastIdx = erosionRates.length - 1
preRate = (erosionRates[min(erosionOffset, lastIdx)] / 100) × (loeMonth - 1) / 12 // pre-LoE segment of the year
postRate = (erosionRates[min(erosionOffset + 1, lastIdx)] / 100) × (13 - loeMonth) / 12 // post-LoE segment of the year
erosion = year >= loeYear ? 1 - (preRate + postRate) : 1
MarketShare = clamp(0, 100, (baseShare + eventImpact) × erosion)

See FORECASTING_MODEL.md §6.4 — Market events (additive impacts) for the additive-vs-multiplicative rationale and event stacking semantics.

Source: src/features/forecasting/math/forecasting.ts (calculateMarketShareA, used by Monte Carlo and Tornado via calculateLineSalesTotal).

marketShare = calculateMarketShareInternal(line, year, lines) // weightedShare × peakShare
eventImpact = calculateEventImpact(line, year, lines)
loeImpact = calculateLoEImpact(year, erosionData, assumptions)
MarketShareA = clamp(0, 100, (marketShare + eventImpact) × loeImpact)

Algebraically identical to the deterministic path above — same uptake curve, same additive event impact, same month-weighted LoE erosion. The simulation path keeps the LoE step factored into a separate helper (calculateLoEImpact) so MC/Tornado inner loops can hoist it; the deterministic path inlines the same math.

Source: src/features/forecasting/math/forecasting.ts (calculateLoEImpact, plus the inline equivalent in calculateMarketShare).

if year < loeYear: return 1
if erosionData.length === 0: return 1
offset = year - loeYear
lastIdx = erosionData.length - 1
preErosion = (erosionData[min(offset, lastIdx)] / 100) × (loeMonth - 1) / 12 // pre-LoE segment
postErosion = (erosionData[min(offset + 1, lastIdx)] / 100) × (13 - loeMonth) / 12 // post-LoE segment
LoEImpact = 1 - (preErosion + postErosion)

Source: src/features/forecasting/math/forecasting.ts (calculateLinePatients)

1L_Eligible = Addressable × (TreatmentRate / 100)
1L_NewPatients = 1L_Eligible × (MarketShare / 100)

Source: src/features/forecasting/math/forecasting.ts (calculateLinePatients, 2L case)

2L_Pool = 1L_Eligible - (1L_NewPatients × Retreatment)
2L_Eligible = (2L_Pool × TransitionRate / 100) × (TreatmentRate / 100)
2L_NewPatients = 2L_Eligible × (MarketShare / 100)

Source: src/features/forecasting/math/forecasting.ts (calculateLinePatients, 3L+ case)

3L_Pool = 2L_Eligible - (2L_NewPatients × Retreatment)
3L_Eligible = 3L_Pool × (TransitionRate / 100) // NO TreatmentRate
3L_NewPatients = 3L_Eligible × (MarketShare / 100)

Source: src/features/forecasting/math/forecasting.ts (calculateNetPriceFromAssumptions)

if year <= launchYear: return launchPrice
yearsFromLaunch = year - launchYear
NetPrice = launchPrice × (1 + annualPriceChange / 100) ^ yearsFromLaunch

Source: src/features/forecasting/math/forecasting.ts (calculateNetPriceFromAssumptions, duration cap logic)

loeYear = parseInt(loeDate.split("-")[0])
duration = year >= loeYear - 1 ? marketExclusivityYears - 1 : year - yearOfFirstLaunch
NetPrice = launchPrice × (1 + priceChange / 100) ^ duration

Both the Model/SalesChart path (calculateLineSales) and Monte Carlo/Tornado path (calculateLineSalesTotal) use cohort-based distribution. MonthsOfTherapy is spread across calendar years, each contributing up to 12 months.

Source: src/features/forecasting/math/forecasting.ts (calculateLineSales, calculateLineSalesTotal)

COHORT_YEARS = 5
For yearOffset in 0..4:
treatmentMonthThreshold = yearOffset × 12
if treatmentMonths <= treatmentMonthThreshold: break
months = yearOffset == 0
? min(treatmentMonths, 12)
: treatmentMonths > (threshold + 12) ? 12 : treatmentMonths - threshold
patients = getCohortPatients(line, year - yearOffset, ...)
sale = (patients × netPrice × compliance × months) / 1,000,000
LineSales = Σ(sale for all cohort years)

When MonthsOfTherapy ≤ 12, only yearOffset=0 iterates and the formula reduces to the simple form.

TotalSales = Σ(LineSales[i]) for all therapy lines

Source: src/features/forecasting/math/distributions.ts (triangularDistribution, normalDistribution, uniformDistribution; each accepts an optional rng: () => number = Math.random to support seeded simulations — the worker constructs a pure-rand xoroshiro128+ generator via xoroshiro128plus(seed) when seed is supplied through the WorkerMessage contract, mirrored verbatim by src/test/helpers/seededRng.ts so fixtures stay in lockstep with the worker)

Triangular:

c = (mostLikely - min) / (max - min)
u = random()
if u <= c:
value = min + sqrt(u × (max - min) × (mostLikely - min))
else:
value = max - sqrt((1 - u) × (max - min) × (max - mostLikely))

Normal (Box-Muller):

mean = (min + max) / 2
stdDev = (max - min) / 6 // NORMAL_DISTRIBUTION_SIGMAS = 6
z = sqrt(-2 × ln(u1)) × cos(2π × u2)
value = z × stdDev + mean

Uniform:

value = min + random() × (max - min)

Per FORECASTING_MODEL.md and code analysis:

  • Transition Rate
  • Compliance
  • Annual Price Change

These are held constant during Monte Carlo simulation.

Incidence Evolution in Monte Carlo/Tornado

Section titled “Incidence Evolution in Monte Carlo/Tornado”

Monte Carlo and Tornado analyses use getYearIncidence(year) for each simulated year, which applies the growth rates from the Incidence Evolution table (Custom Inputs). This ensures simulations properly reflect year-over-year incidence changes rather than using a static base incidence value.


CalculationFileFunction
Custom Multipliersrc/features/forecasting/math/forecasting.tsgetCustomMultiplier, getCustomMultiplierForYear
Line Funnel Displaysrc/features/forecasting/math/forecasting.tscalculateLineFunnelForDisplay
Market Sharesrc/features/forecasting/math/forecasting.tscalculateMarketShare
Net Pricesrc/features/forecasting/math/forecasting.tscalculateNetPriceFromAssumptions
Line Patientssrc/features/forecasting/math/forecasting.tscalculateLinePatients
Line Salessrc/features/forecasting/math/forecasting.tscalculateLineSales
Changeable Valuessrc/features/forecasting/math/forecasting.tsapplyChangeableValue
Drug Treated Patients (MC)src/features/forecasting/math/forecasting.tscalculateTreatmentEligiblePatients
Line Sales Total (MC)src/features/forecasting/math/forecasting.tscalculateLineSalesTotal
Peak Sharesrc/features/forecasting/math/forecasting.tscalculatePeakShare
LoE Datesrc/features/forecasting/math/forecasting.tscomputeLoeDate
Distributionssrc/features/forecasting/math/distributions.tstriangularDistribution, normalDistribution, uniformDistribution, getRandomValueByDistribution
Growth Ratessrc/features/forecasting/math/incidence-evolution.tscomputeGrowthRates
Incidence Evolutionsrc/features/forecasting/math/incidence-evolution.tsbuildIncidenceEvolution
Growth Rate Changesrc/features/forecasting/math/incidence-evolution.tsapplyGrowthRateChange
Monte Carlo Enginesrc/features/forecasting/math/monte-carlo.tsrunMonteCarloSimulation
Tornado Analysissrc/features/forecasting/math/tornado.tsrunTornadoAnalysis