heor-agent-mcp 0.2.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +44 -0
  2. package/dist/formatters/comparisonMarkdown.d.ts +3 -0
  3. package/dist/formatters/comparisonMarkdown.d.ts.map +1 -0
  4. package/dist/formatters/comparisonMarkdown.js +68 -0
  5. package/dist/formatters/comparisonMarkdown.js.map +1 -0
  6. package/dist/models/evppi.d.ts +33 -0
  7. package/dist/models/evppi.d.ts.map +1 -0
  8. package/dist/models/evppi.js +75 -0
  9. package/dist/models/evppi.js.map +1 -0
  10. package/dist/models/modelUtils.d.ts +7 -2
  11. package/dist/models/modelUtils.d.ts.map +1 -1
  12. package/dist/models/modelUtils.js +49 -31
  13. package/dist/models/modelUtils.js.map +1 -1
  14. package/dist/models/psa.d.ts +4 -0
  15. package/dist/models/psa.d.ts.map +1 -1
  16. package/dist/models/psa.js +29 -2
  17. package/dist/models/psa.js.map +1 -1
  18. package/dist/models/survivalFitting.d.ts +47 -0
  19. package/dist/models/survivalFitting.d.ts.map +1 -0
  20. package/dist/models/survivalFitting.js +342 -0
  21. package/dist/models/survivalFitting.js.map +1 -0
  22. package/dist/network/bucher.d.ts +29 -0
  23. package/dist/network/bucher.d.ts.map +1 -0
  24. package/dist/network/bucher.js +125 -0
  25. package/dist/network/bucher.js.map +1 -0
  26. package/dist/network/frequentistNma.d.ts +12 -0
  27. package/dist/network/frequentistNma.d.ts.map +1 -0
  28. package/dist/network/frequentistNma.js +230 -0
  29. package/dist/network/frequentistNma.js.map +1 -0
  30. package/dist/network/index.d.ts +4 -1
  31. package/dist/network/index.d.ts.map +1 -1
  32. package/dist/network/index.js +3 -0
  33. package/dist/network/index.js.map +1 -1
  34. package/dist/network/pathfinder.d.ts +14 -0
  35. package/dist/network/pathfinder.d.ts.map +1 -0
  36. package/dist/network/pathfinder.js +93 -0
  37. package/dist/network/pathfinder.js.map +1 -0
  38. package/dist/network/types.d.ts +45 -0
  39. package/dist/network/types.d.ts.map +1 -1
  40. package/dist/providers/direct/index.d.ts.map +1 -1
  41. package/dist/providers/direct/index.js +82 -27
  42. package/dist/providers/direct/index.js.map +1 -1
  43. package/dist/providers/direct/iqwig.js +1 -1
  44. package/dist/providers/direct/iqwig.js.map +1 -1
  45. package/dist/providers/direct/niceTa.d.ts +4 -0
  46. package/dist/providers/direct/niceTa.d.ts.map +1 -1
  47. package/dist/providers/direct/niceTa.js +76 -25
  48. package/dist/providers/direct/niceTa.js.map +1 -1
  49. package/dist/providers/direct/tlv.js +1 -1
  50. package/dist/providers/direct/tlv.js.map +1 -1
  51. package/dist/providers/types.d.ts +6 -0
  52. package/dist/providers/types.d.ts.map +1 -1
  53. package/dist/server.js +89 -9
  54. package/dist/server.js.map +1 -1
  55. package/dist/tools/budgetImpactModel.d.ts +116 -0
  56. package/dist/tools/budgetImpactModel.d.ts.map +1 -0
  57. package/dist/tools/budgetImpactModel.js +257 -0
  58. package/dist/tools/budgetImpactModel.js.map +1 -0
  59. package/dist/tools/costEffectivenessModel.d.ts +18 -0
  60. package/dist/tools/costEffectivenessModel.d.ts.map +1 -1
  61. package/dist/tools/costEffectivenessModel.js +155 -27
  62. package/dist/tools/costEffectivenessModel.js.map +1 -1
  63. package/dist/tools/htaDossierPrep.d.ts.map +1 -1
  64. package/dist/tools/htaDossierPrep.js +119 -0
  65. package/dist/tools/htaDossierPrep.js.map +1 -1
  66. package/dist/tools/indirectComparison.d.ts +73 -0
  67. package/dist/tools/indirectComparison.d.ts.map +1 -0
  68. package/dist/tools/indirectComparison.js +191 -0
  69. package/dist/tools/indirectComparison.js.map +1 -0
  70. package/dist/tools/knowledgeWrite.d.ts.map +1 -1
  71. package/dist/tools/knowledgeWrite.js +17 -3
  72. package/dist/tools/knowledgeWrite.js.map +1 -1
  73. package/dist/tools/literatureSearch.d.ts +4 -0
  74. package/dist/tools/literatureSearch.d.ts.map +1 -1
  75. package/dist/tools/literatureSearch.js +43 -1
  76. package/dist/tools/literatureSearch.js.map +1 -1
  77. package/dist/tools/populationAdjustedComparison.d.ts +145 -0
  78. package/dist/tools/populationAdjustedComparison.d.ts.map +1 -0
  79. package/dist/tools/populationAdjustedComparison.js +431 -0
  80. package/dist/tools/populationAdjustedComparison.js.map +1 -0
  81. package/dist/tools/screenAbstracts.d.ts +99 -0
  82. package/dist/tools/screenAbstracts.d.ts.map +1 -0
  83. package/dist/tools/screenAbstracts.js +442 -0
  84. package/dist/tools/screenAbstracts.js.map +1 -0
  85. package/dist/tools/survivalFitting.d.ts +56 -0
  86. package/dist/tools/survivalFitting.d.ts.map +1 -0
  87. package/dist/tools/survivalFitting.js +147 -0
  88. package/dist/tools/survivalFitting.js.map +1 -0
  89. package/dist/tools/validateLinks.d.ts +24 -0
  90. package/dist/tools/validateLinks.d.ts.map +1 -0
  91. package/dist/tools/validateLinks.js +171 -0
  92. package/dist/tools/validateLinks.js.map +1 -0
  93. package/package.json +4 -2
@@ -0,0 +1,257 @@
1
+ import { z } from "zod";
2
+ import { createAuditRecord, addAssumption, setMethodology, } from "../audit/builder.js";
3
+ import { auditToMarkdown } from "../formatters/markdown.js";
4
+ import { contentToDocx } from "../formatters/docx.js";
5
+ import { saveReport } from "../knowledge/index.js";
6
+ const BudgetImpactSchema = z.object({
7
+ intervention: z.string().min(1),
8
+ comparator: z.string().min(1),
9
+ indication: z.string().min(1),
10
+ perspective: z.enum(["nhs", "us_payer", "societal"]),
11
+ time_horizon_years: z.number().int().min(1).max(10).default(5),
12
+ eligible_population: z.number().positive(),
13
+ population_growth_rate: z.number().min(-0.1).max(0.2).default(0.0),
14
+ market_share: z.object({
15
+ year_1: z.number().min(0).max(1),
16
+ year_2: z.number().min(0).max(1).optional(),
17
+ year_3: z.number().min(0).max(1).optional(),
18
+ year_4: z.number().min(0).max(1).optional(),
19
+ year_5: z.number().min(0).max(1).optional(),
20
+ }),
21
+ drug_cost_annual: z.number().nonnegative(),
22
+ comparator_cost_annual: z.number().nonnegative(),
23
+ admin_cost_annual: z.number().nonnegative().default(0),
24
+ monitoring_cost_annual: z.number().nonnegative().default(0),
25
+ ae_cost_annual: z.number().nonnegative().default(0),
26
+ comparator_ae_cost_annual: z.number().nonnegative().default(0),
27
+ displacement: z
28
+ .array(z.object({
29
+ treatment: z.string(),
30
+ share: z.number().min(0).max(1),
31
+ cost_annual: z.number().nonnegative(),
32
+ }))
33
+ .optional(),
34
+ output_format: z.enum(["text", "json", "docx"]).optional(),
35
+ project: z.string().optional(),
36
+ });
37
+ const CURRENCY = {
38
+ nhs: { symbol: "\u00A3", name: "GBP" },
39
+ us_payer: { symbol: "$", name: "USD" },
40
+ societal: { symbol: "$", name: "USD" },
41
+ };
42
+ function computeBudgetImpact(params) {
43
+ const years = params.time_horizon_years;
44
+ const shares = [
45
+ params.market_share.year_1,
46
+ params.market_share.year_2,
47
+ params.market_share.year_3,
48
+ params.market_share.year_4,
49
+ params.market_share.year_5,
50
+ ];
51
+ // Interpolate/extrapolate market share for missing years
52
+ let lastDefinedIdx = 0;
53
+ for (let i = 0; i < shares.length; i++) {
54
+ if (shares[i] !== undefined)
55
+ lastDefinedIdx = i;
56
+ }
57
+ for (let i = 1; i < years; i++) {
58
+ if (shares[i] === undefined) {
59
+ shares[i] = shares[lastDefinedIdx];
60
+ }
61
+ }
62
+ const interventionAnnualCost = params.drug_cost_annual +
63
+ params.admin_cost_annual +
64
+ params.monitoring_cost_annual +
65
+ params.ae_cost_annual;
66
+ const comparatorAnnualCost = params.comparator_cost_annual +
67
+ params.admin_cost_annual +
68
+ params.comparator_ae_cost_annual;
69
+ // Displaced treatments: cost saved by switching patients from existing therapies
70
+ const displacementCostPerPatient = (params.displacement ?? []).reduce((sum, d) => sum + d.share * d.cost_annual, 0);
71
+ const results = [];
72
+ for (let y = 0; y < years; y++) {
73
+ const pop = Math.round(params.eligible_population *
74
+ Math.pow(1 + params.population_growth_rate, y));
75
+ const share = shares[y] ?? shares[lastDefinedIdx] ?? 0;
76
+ const treated = Math.round(pop * share);
77
+ const untreated = pop - treated;
78
+ const intCost = treated * interventionAnnualCost;
79
+ const compCost = untreated * comparatorAnnualCost;
80
+ const displacedSaved = treated * displacementCostPerPatient;
81
+ // Net = new world cost - old world cost
82
+ // Old world: everyone on comparator
83
+ const oldWorldCost = pop * comparatorAnnualCost;
84
+ const newWorldCost = intCost + compCost;
85
+ const net = newWorldCost - oldWorldCost - displacedSaved;
86
+ results.push({
87
+ year: y + 1,
88
+ eligible_population: pop,
89
+ treated_population: treated,
90
+ intervention_cost: intCost,
91
+ comparator_cost: compCost,
92
+ displaced_cost_saved: displacedSaved,
93
+ net_budget_impact: net,
94
+ per_patient_cost: treated > 0 ? net / treated : 0,
95
+ });
96
+ }
97
+ return results;
98
+ }
99
+ export async function handleBudgetImpactModel(rawParams) {
100
+ const params = BudgetImpactSchema.parse(rawParams);
101
+ const outputFormat = params.output_format ?? "text";
102
+ const { symbol } = CURRENCY[params.perspective];
103
+ let audit = createAuditRecord("budget_impact_model", params, outputFormat);
104
+ audit = setMethodology(audit, "ISPOR Budget Impact Analysis good practice (Mauskopf 2007, Sullivan 2014)");
105
+ audit = addAssumption(audit, `Perspective: ${params.perspective}`);
106
+ audit = addAssumption(audit, `Time horizon: ${params.time_horizon_years} years`);
107
+ audit = addAssumption(audit, `Eligible population: ${params.eligible_population.toLocaleString()} (growth: ${(params.population_growth_rate * 100).toFixed(1)}%/yr)`);
108
+ const results = computeBudgetImpact(params);
109
+ const totalNetImpact = results.reduce((s, r) => s + r.net_budget_impact, 0);
110
+ const totalTreated = results.reduce((s, r) => s + r.treated_population, 0);
111
+ if (outputFormat === "json") {
112
+ return {
113
+ content: {
114
+ years: results,
115
+ total_net_budget_impact: totalNetImpact,
116
+ total_patients_treated: totalTreated,
117
+ currency: CURRENCY[params.perspective].name,
118
+ },
119
+ audit,
120
+ };
121
+ }
122
+ const fmt = (n) => `${symbol}${Math.round(n).toLocaleString()}`;
123
+ const lines = [
124
+ `## Budget Impact Analysis: ${params.intervention} vs ${params.comparator}`,
125
+ `**Indication:** ${params.indication} | **Perspective:** ${params.perspective.toUpperCase()} | **Horizon:** ${params.time_horizon_years} years`,
126
+ ``,
127
+ `### Year-by-Year Budget Impact`,
128
+ `| Year | Eligible Pop. | Treated | Market Share | Net Budget Impact | Per Patient |`,
129
+ `|------|--------------|---------|-------------|-------------------|-------------|`,
130
+ ...results.map((r) => `| ${r.year} | ${r.eligible_population.toLocaleString()} | ${r.treated_population.toLocaleString()} | ${((r.treated_population / r.eligible_population) * 100).toFixed(1)}% | ${fmt(r.net_budget_impact)} | ${fmt(r.per_patient_cost)} |`),
131
+ `| **Total** | | **${totalTreated.toLocaleString()}** | | **${fmt(totalNetImpact)}** | |`,
132
+ ``,
133
+ `### Summary`,
134
+ `- **Total net budget impact** over ${params.time_horizon_years} years: **${fmt(totalNetImpact)}**`,
135
+ `- **Average per-patient annual cost difference:** ${fmt(totalNetImpact / (totalTreated || 1))}`,
136
+ totalNetImpact > 0
137
+ ? `- The intervention **increases** total budget by ${fmt(totalNetImpact)} over ${params.time_horizon_years} years`
138
+ : `- The intervention **saves** ${fmt(Math.abs(totalNetImpact))} over ${params.time_horizon_years} years`,
139
+ ``,
140
+ params.displacement && params.displacement.length > 0
141
+ ? `### Treatment Displacement\n${params.displacement.map((d) => `- ${d.treatment}: ${(d.share * 100).toFixed(0)}% displaced (${fmt(d.cost_annual)}/yr)`).join("\n")}\n`
142
+ : "",
143
+ `---`,
144
+ `> **Disclaimer:** This is a preliminary budget impact estimate for orientation purposes only. Results require validation by a qualified health economist before use in any HTA submission or payer negotiation.`,
145
+ ``,
146
+ auditToMarkdown(audit),
147
+ ]
148
+ .filter(Boolean)
149
+ .join("\n");
150
+ if (outputFormat === "docx") {
151
+ const base64 = await contentToDocx("Budget Impact Analysis Report", lines, audit);
152
+ const filenameStem = `bia-${params.intervention.slice(0, 30)}-vs-${params.comparator.slice(0, 30)}`;
153
+ const savedPath = await saveReport(base64, filenameStem, params.project);
154
+ const sizeKb = Math.round(base64.length / 1024);
155
+ return {
156
+ content: `## DOCX Report Generated\n\n**File:** \`${savedPath}\`\n**Size:** ${sizeKb} KB\n\nOpen with: \`open "${savedPath}"\``,
157
+ audit,
158
+ };
159
+ }
160
+ return { content: lines, audit };
161
+ }
162
+ export const budgetImpactModelToolSchema = {
163
+ name: "budget_impact_model",
164
+ description: "Estimate the total budget impact of adopting a new intervention over 1-5 years. Follows ISPOR Budget Impact Analysis good practice guidelines (Mauskopf 2007, Sullivan 2014). Computes year-by-year net cost to payer, including market share uptake, treatment displacement, and population growth.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ intervention: { type: "string", description: "New drug or treatment" },
169
+ comparator: {
170
+ type: "string",
171
+ description: "Current standard of care",
172
+ },
173
+ indication: { type: "string", description: "Disease or condition" },
174
+ perspective: { type: "string", enum: ["nhs", "us_payer", "societal"] },
175
+ time_horizon_years: {
176
+ type: "number",
177
+ description: "Budget horizon in years (1-10, default 5)",
178
+ },
179
+ eligible_population: {
180
+ type: "number",
181
+ description: "Number of eligible patients in Year 1",
182
+ },
183
+ population_growth_rate: {
184
+ type: "number",
185
+ description: "Annual population growth rate (e.g., 0.02 for 2%). Default 0.",
186
+ },
187
+ market_share: {
188
+ type: "object",
189
+ description: "Expected market share of intervention by year (0-1). Missing years extrapolate from last defined.",
190
+ properties: {
191
+ year_1: { type: "number" },
192
+ year_2: { type: "number" },
193
+ year_3: { type: "number" },
194
+ year_4: { type: "number" },
195
+ year_5: { type: "number" },
196
+ },
197
+ required: ["year_1"],
198
+ },
199
+ drug_cost_annual: {
200
+ type: "number",
201
+ description: "Annual drug acquisition cost for intervention",
202
+ },
203
+ comparator_cost_annual: {
204
+ type: "number",
205
+ description: "Annual drug cost for comparator",
206
+ },
207
+ admin_cost_annual: {
208
+ type: "number",
209
+ description: "Annual administration cost (applies to both arms). Default 0.",
210
+ },
211
+ monitoring_cost_annual: {
212
+ type: "number",
213
+ description: "Annual monitoring cost for intervention. Default 0.",
214
+ },
215
+ ae_cost_annual: {
216
+ type: "number",
217
+ description: "Annual adverse event cost for intervention. Default 0.",
218
+ },
219
+ comparator_ae_cost_annual: {
220
+ type: "number",
221
+ description: "Annual adverse event cost for comparator. Default 0.",
222
+ },
223
+ displacement: {
224
+ type: "array",
225
+ description: "Existing treatments displaced by intervention (share of patients switching, cost saved)",
226
+ items: {
227
+ type: "object",
228
+ properties: {
229
+ treatment: { type: "string" },
230
+ share: {
231
+ type: "number",
232
+ description: "Fraction of treated patients displaced (0-1)",
233
+ },
234
+ cost_annual: {
235
+ type: "number",
236
+ description: "Annual cost of displaced treatment",
237
+ },
238
+ },
239
+ required: ["treatment", "share", "cost_annual"],
240
+ },
241
+ },
242
+ output_format: { type: "string", enum: ["text", "json", "docx"] },
243
+ project: { type: "string", description: "Project ID for persistence" },
244
+ },
245
+ required: [
246
+ "intervention",
247
+ "comparator",
248
+ "indication",
249
+ "perspective",
250
+ "eligible_population",
251
+ "market_share",
252
+ "drug_cost_annual",
253
+ "comparator_cost_annual",
254
+ ],
255
+ },
256
+ };
257
+ //# sourceMappingURL=budgetImpactModel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budgetImpactModel.js","sourceRoot":"","sources":["../../src/tools/budgetImpactModel.ts"],"names":[],"mappings":"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"}
@@ -83,6 +83,24 @@ export declare const costEffectivenessModelToolSchema: {
83
83
  type: string;
84
84
  description: string;
85
85
  };
86
+ scenarios: {
87
+ type: string;
88
+ description: string;
89
+ items: {
90
+ type: string;
91
+ properties: {
92
+ name: {
93
+ type: string;
94
+ description: string;
95
+ };
96
+ overrides: {
97
+ type: string;
98
+ description: string;
99
+ };
100
+ };
101
+ required: string[];
102
+ };
103
+ };
86
104
  output_format: {
87
105
  type: string;
88
106
  enum: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"costEffectivenessModel.d.ts","sourceRoot":"","sources":["../../src/tools/costEffectivenessModel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,UAAU,EAKX,MAAM,uBAAuB,CAAC;AA6F/B,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC,CAuTrB;AAED,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2E5C,CAAC"}
1
+ {"version":3,"file":"costEffectivenessModel.d.ts","sourceRoot":"","sources":["../../src/tools/costEffectivenessModel.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,UAAU,EAKX,MAAM,uBAAuB,CAAC;AAoN/B,wBAAsB,4BAA4B,CAChD,SAAS,EAAE,OAAO,GACjB,OAAO,CAAC,UAAU,CAAC,CAuVrB;AAED,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+F5C,CAAC"}
@@ -29,13 +29,43 @@ const CEModelSchema = z.object({
29
29
  run_psa: z.boolean().optional(),
30
30
  psa_iterations: z.number().int().min(1).max(10000).optional(),
31
31
  run_owsa: z.boolean().optional(),
32
+ scenarios: z
33
+ .array(z.object({
34
+ name: z.string().min(1),
35
+ overrides: z.object({
36
+ time_horizon: z
37
+ .union([z.enum(["lifetime", "5yr", "10yr"]), z.number().positive()])
38
+ .optional(),
39
+ perspective: z.enum(["nhs", "us_payer", "societal"]).optional(),
40
+ clinical_inputs: z
41
+ .object({
42
+ efficacy_delta: z.number().min(0).max(1).optional(),
43
+ mortality_reduction: z.number().min(0).max(1).optional(),
44
+ })
45
+ .optional(),
46
+ cost_inputs: z
47
+ .object({
48
+ drug_cost_annual: z.number().nonnegative().optional(),
49
+ comparator_cost_annual: z.number().nonnegative().optional(),
50
+ })
51
+ .optional(),
52
+ utility_inputs: z
53
+ .object({
54
+ qaly_on_treatment: z.number().min(0).max(1).optional(),
55
+ qaly_comparator: z.number().min(0).max(1).optional(),
56
+ })
57
+ .optional(),
58
+ }),
59
+ }))
60
+ .max(10)
61
+ .optional(),
32
62
  output_format: z.enum(["text", "json", "docx"]).optional(),
33
63
  project: z.string().optional(),
34
64
  });
35
65
  import { runPartSA } from "../models/partsa.js";
36
66
  import { runPSA } from "../models/psa.js";
37
67
  import { runOWSA, buildDefaultOWSAParameters } from "../models/owsa.js";
38
- import { runMarkovAndComputeICER, } from "../models/modelUtils.js";
68
+ import { runMarkovAndComputeICER, getTimeHorizonYears, } from "../models/modelUtils.js";
39
69
  import { createAuditRecord, addAssumption, setMethodology, } from "../audit/builder.js";
40
70
  import { auditToMarkdown } from "../formatters/markdown.js";
41
71
  import { contentToDocx } from "../formatters/docx.js";
@@ -47,17 +77,69 @@ const WTP_THRESHOLDS = {
47
77
  societal: { low: 50000, high: 100000, currency: "USD", symbol: "$" },
48
78
  };
49
79
  const DISCOUNT_RATE = 0.035;
50
- function getTimeHorizonYears(horizon) {
51
- if (horizon === "lifetime")
52
- return 40;
53
- if (horizon === "5yr")
54
- return 5;
55
- if (horizon === "10yr")
56
- return 10;
57
- return Number(horizon);
80
+ /**
81
+ * Run scenario analysis: re-run the CE model with parameter overrides.
82
+ * Returns markdown table comparing base case to each scenario.
83
+ */
84
+ function buildScenarioSection(params, symbol) {
85
+ if (!params.scenarios || params.scenarios.length === 0)
86
+ return [];
87
+ const lines = [
88
+ `### Scenario Analysis`,
89
+ `| Scenario | ICER | Delta Cost | Delta QALY | Verdict |`,
90
+ `|----------|------|-----------|------------|---------|`,
91
+ ];
92
+ // Base case row
93
+ const baseResult = runMarkovAndComputeICER(params);
94
+ const baseIcer = isFinite(baseResult.icer)
95
+ ? `${symbol}${Math.round(baseResult.icer).toLocaleString()}`
96
+ : baseResult.delta_cost <= 0 && baseResult.delta_qaly >= 0
97
+ ? "Dominant"
98
+ : "Dominated";
99
+ lines.push(`| **Base case** | ${baseIcer} | ${symbol}${Math.round(baseResult.delta_cost).toLocaleString()} | ${baseResult.delta_qaly.toFixed(3)} | — |`);
100
+ for (const scenario of params.scenarios) {
101
+ // Deep merge overrides into base params
102
+ const merged = {
103
+ ...params,
104
+ ...scenario.overrides,
105
+ clinical_inputs: {
106
+ ...params.clinical_inputs,
107
+ ...(scenario.overrides.clinical_inputs ??
108
+ {}),
109
+ },
110
+ cost_inputs: {
111
+ ...params.cost_inputs,
112
+ ...(scenario.overrides.cost_inputs ?? {}),
113
+ },
114
+ utility_inputs: params.utility_inputs
115
+ ? {
116
+ ...params.utility_inputs,
117
+ ...(scenario.overrides.utility_inputs ?? {}),
118
+ }
119
+ : undefined,
120
+ };
121
+ // Remove scenarios from merged to avoid recursion
122
+ delete merged.scenarios;
123
+ const result = runMarkovAndComputeICER(merged);
124
+ const perspective = scenario.overrides.perspective ?? params.perspective;
125
+ const threshold = WTP_THRESHOLDS[perspective];
126
+ const verdict = wtpVerdict(result.icer, threshold, result.delta_cost, result.delta_qaly);
127
+ const icerStr = isFinite(result.icer)
128
+ ? `${symbol}${Math.round(result.icer).toLocaleString()}`
129
+ : result.delta_cost <= 0 && result.delta_qaly >= 0
130
+ ? "Dominant"
131
+ : "Dominated";
132
+ lines.push(`| ${scenario.name} | ${icerStr} | ${symbol}${Math.round(result.delta_cost).toLocaleString()} | ${result.delta_qaly.toFixed(3)} | ${verdict} |`);
133
+ }
134
+ lines.push(``);
135
+ return lines;
58
136
  }
59
- function wtpVerdict(icer, threshold) {
60
- if (!isFinite(icer) && icer < 0)
137
+ function wtpVerdict(icer, threshold, delta_cost, delta_qaly) {
138
+ // Dominant: lower cost AND higher QALYs — always cost-effective
139
+ if (delta_cost <= 0 && delta_qaly >= 0 && (delta_cost < 0 || delta_qaly > 0))
140
+ return "cost_effective";
141
+ // Dominated: higher cost AND lower QALYs — never cost-effective
142
+ if (delta_cost >= 0 && delta_qaly <= 0 && (delta_cost > 0 || delta_qaly < 0))
61
143
  return "dominated";
62
144
  if (!isFinite(icer))
63
145
  return "not_cost_effective";
@@ -67,14 +149,14 @@ function wtpVerdict(icer, threshold) {
67
149
  return "borderline";
68
150
  return "not_cost_effective";
69
151
  }
70
- function buildWTPAssessment(icer, perspective) {
152
+ function buildWTPAssessment(icer, perspective, delta_cost, delta_qaly) {
71
153
  const threshold = WTP_THRESHOLDS[perspective];
72
154
  return {
73
155
  threshold_low: threshold.low,
74
156
  threshold_high: threshold.high,
75
157
  currency: threshold.currency,
76
158
  symbol: threshold.symbol,
77
- verdict: wtpVerdict(icer, threshold),
159
+ verdict: wtpVerdict(icer, threshold, delta_cost, delta_qaly),
78
160
  };
79
161
  }
80
162
  export async function handleCostEffectivenessModel(rawParams) {
@@ -161,7 +243,7 @@ export async function handleCostEffectivenessModel(rawParams) {
161
243
  comparator_qaly = markovResult.comparator_qaly;
162
244
  intervention_lys = markovResult.intervention_lys;
163
245
  comparator_lys = markovResult.comparator_lys;
164
- stateNames = params.states ?? ["On-Treatment", "Off-Treatment"];
246
+ stateNames = params.states ?? ["On-Treatment", "Off-Treatment", "Dead"];
165
247
  nCycles = years;
166
248
  }
167
249
  const incremental_lys = intervention_lys - comparator_lys;
@@ -173,6 +255,7 @@ export async function handleCostEffectivenessModel(rawParams) {
173
255
  base_params: params,
174
256
  n_iterations,
175
257
  seed: 12345,
258
+ evpi_lambda: threshold.low,
176
259
  });
177
260
  psaSummary = {
178
261
  iterations: n_iterations,
@@ -182,6 +265,11 @@ export async function handleCostEffectivenessModel(rawParams) {
182
265
  prob_cost_effective: psaResult.prob_cost_effective,
183
266
  ceac: psaResult.ceac,
184
267
  evpi: psaResult.evpi,
268
+ evppi: psaResult.evppi.map((e) => ({
269
+ parameter: e.parameter,
270
+ evppi: e.evppi,
271
+ evppi_proportion: e.evppi_proportion,
272
+ })),
185
273
  scatter: psaResult.scatter_sample.map((it) => ({
186
274
  delta_cost: it.delta_cost,
187
275
  delta_qaly: it.delta_qaly,
@@ -204,9 +292,9 @@ export async function handleCostEffectivenessModel(rawParams) {
204
292
  }
205
293
  // --- WTP Analysis ---
206
294
  const wtp_analysis = {
207
- nhs: buildWTPAssessment(icer, "nhs"),
208
- us_payer: buildWTPAssessment(icer, "us_payer"),
209
- societal: buildWTPAssessment(icer, "societal"),
295
+ nhs: buildWTPAssessment(icer, "nhs", delta_cost, delta_qaly),
296
+ us_payer: buildWTPAssessment(icer, "us_payer", delta_cost, delta_qaly),
297
+ societal: buildWTPAssessment(icer, "societal", delta_cost, delta_qaly),
210
298
  };
211
299
  // --- Build result ---
212
300
  const modelResult = {
@@ -238,17 +326,27 @@ export async function handleCostEffectivenessModel(rawParams) {
238
326
  return { content: modelResult, audit };
239
327
  }
240
328
  // --- Text output (backwards-compatible) ---
241
- const icerFormatted = isFinite(icer)
242
- ? Math.round(icer).toLocaleString()
243
- : "Dominated";
329
+ const isDominant = delta_cost <= 0 && delta_qaly >= 0 && (delta_cost < 0 || delta_qaly > 0);
330
+ const isDominated = delta_cost >= 0 && delta_qaly <= 0 && (delta_cost > 0 || delta_qaly < 0);
331
+ const icerFormatted = isDominant
332
+ ? "Dominant"
333
+ : isDominated
334
+ ? "Dominated"
335
+ : isFinite(icer)
336
+ ? Math.round(icer).toLocaleString()
337
+ : "N/A";
244
338
  const perspectiveVerdict = wtp_analysis[params.perspective];
245
- const interpretation = isFinite(icer) && icer >= 0
246
- ? icer < threshold.low
247
- ? `${symbol}${icerFormatted}/QALY — likely cost-effective (below NICE threshold of ${symbol}${threshold.low.toLocaleString()})`
248
- : icer < threshold.high
249
- ? `${symbol}${icerFormatted}/QALY borderline cost-effective (within ${symbol}${threshold.low.toLocaleString()}–${symbol}${threshold.high.toLocaleString()} threshold range)`
250
- : `${symbol}${icerFormatted}/QALY not cost-effective at standard threshold`
251
- : "Dominatedintervention costs more and provides less benefit than comparator";
339
+ const interpretation = isDominant
340
+ ? "Dominant intervention is less costly and more effective than comparator"
341
+ : isDominated
342
+ ? "Dominated intervention costs more and provides less benefit than comparator"
343
+ : isFinite(icer) && icer >= 0
344
+ ? icer < threshold.low
345
+ ? `${symbol}${icerFormatted}/QALYlikely cost-effective (below NICE threshold of ${symbol}${threshold.low.toLocaleString()})`
346
+ : icer < threshold.high
347
+ ? `${symbol}${icerFormatted}/QALY — borderline cost-effective (within ${symbol}${threshold.low.toLocaleString()}–${symbol}${threshold.high.toLocaleString()} threshold range)`
348
+ : `${symbol}${icerFormatted}/QALY — not cost-effective at standard threshold`
349
+ : "ICER could not be computed";
252
350
  // PSA section
253
351
  const psaSection = [];
254
352
  if (psaSummary) {
@@ -257,6 +355,17 @@ export async function handleCostEffectivenessModel(rawParams) {
257
355
  : (psaSummary.prob_cost_effective["us_payer_low"] ?? 0);
258
356
  const pcePercent = Math.round(pcePerspective * 100);
259
357
  psaSection.push(`### Probabilistic Sensitivity Analysis (PSA)`, `Based on ${psaSummary.iterations.toLocaleString()} Monte Carlo iterations:`, `- **Mean ICER:** ${symbol}${Math.round(psaSummary.mean_icer).toLocaleString()}/QALY`, `- **95% CI:** ${symbol}${Math.round(psaSummary.ci_icer_lower).toLocaleString()} – ${symbol}${Math.round(psaSummary.ci_icer_upper).toLocaleString()}/QALY`, `- **Probability cost-effective** at ${params.perspective} threshold: **${pcePercent}%**`, `- **EVPI:** ${symbol}${Math.round(psaSummary.evpi).toLocaleString()} (expected value of perfect information)`, ``);
358
+ // EVPPI section
359
+ if (psaSummary.evppi && psaSummary.evppi.length > 0) {
360
+ psaSection.push(`#### EVPPI (Partial Value of Information)`);
361
+ psaSection.push(`Which parameters are worth further research:`);
362
+ psaSection.push(`| Parameter | EVPPI | % of EVPI |`);
363
+ psaSection.push(`|-----------|-------|-----------|`);
364
+ for (const ev of psaSummary.evppi.slice(0, 5)) {
365
+ psaSection.push(`| ${ev.parameter} | ${symbol}${Math.round(ev.evppi).toLocaleString()} | ${(ev.evppi_proportion * 100).toFixed(1)}% |`);
366
+ }
367
+ psaSection.push(``);
368
+ }
260
369
  }
261
370
  // OWSA section (top 5)
262
371
  const owsaSection = [];
@@ -297,6 +406,7 @@ export async function handleCostEffectivenessModel(rawParams) {
297
406
  ``,
298
407
  ...psaSection,
299
408
  ...owsaSection,
409
+ ...buildScenarioSection(params, symbol),
300
410
  `### Model Structure`,
301
411
  `Multi-state Markov model with half-cycle correction. Discounted at ${DISCOUNT_RATE * 100}% per NICE reference case.`,
302
412
  `States: ${stateNames.join(", ")} | Cycles: ${nCycles} annual cycles`,
@@ -388,6 +498,24 @@ export const costEffectivenessModelToolSchema = {
388
498
  type: "number",
389
499
  description: "PSA iterations (default: 1000, max: 10000)",
390
500
  },
501
+ scenarios: {
502
+ type: "array",
503
+ description: "Optional scenario analysis: array of named parameter overrides. Each scenario re-runs the model with the specified changes. Max 10 scenarios.",
504
+ items: {
505
+ type: "object",
506
+ properties: {
507
+ name: {
508
+ type: "string",
509
+ description: "Scenario name (e.g., '20% price reduction')",
510
+ },
511
+ overrides: {
512
+ type: "object",
513
+ description: "Parameter overrides (time_horizon, perspective, clinical_inputs, cost_inputs, utility_inputs)",
514
+ },
515
+ },
516
+ required: ["name", "overrides"],
517
+ },
518
+ },
391
519
  output_format: { type: "string", enum: ["text", "json", "docx"] },
392
520
  project: {
393
521
  type: "string",