SChodavarpu commited on
Commit
6260ddf
·
verified ·
1 Parent(s): b4359b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +81 -38
app.py CHANGED
@@ -7,7 +7,8 @@ import gradio as gr
7
  # ==========================================
8
  # CARPL Multi-Use-Case ROI Calculators (MMG / FFR-CT / MSK AI)
9
  # Shared UI/UX: Inter font, white cards, pill header, Overall + Tabs + Waterfall + Evidence + CTA
10
- # Extras: card-style use case chooser, MMG vendor presets, CSV export (fixed)
 
11
  # ==========================================
12
 
13
  USE_CASES = ["Mammography AI (MMG)", "FFR-CT AI", "MSK AI (ER/Trauma)"]
@@ -75,7 +76,6 @@ def write_csv(rows, title: str = "roi_results") -> str:
75
  with open(path, "w", encoding="utf-8") as f:
76
  f.write("Metric,Value\n")
77
  for lab, val in rows:
78
- # strip any HTML tags if present
79
  val = str(val).replace("<b>", "").replace("</b>", "")
80
  f.write(f"\"{lab}\",\"{val}\"\n")
81
  return str(path)
@@ -192,10 +192,10 @@ def compute_mmg(
192
  ("Effective capacity increase", pct(capacity_increase_pct)),
193
  ]
194
  },
195
- "waterfall": [("Incremental revenue", addl_followup_revenue_month), ("Incremental costs", - (vendor_cost_month + platform_cost_month + other_costs_month)), ("Operational value", ops_value_month)],
196
  "annual_card": {
197
- "incr_rev": addl_followup_revenue_month * 12.0,
198
- "incr_costs": (vendor_cost_month + platform_cost_month + other_costs_month) * 12.0,
199
  "ops_value": ops_value_month * 12.0,
200
  "net": net_impact_month * 12.0,
201
  "roi_pct": roi_pct_annual,
@@ -232,7 +232,6 @@ def compute_ffrct(
232
 
233
  pct_ai_qpa = max(0.0, min(1.0, pct_billed_ai_qpa/100.0))
234
  one_test_dx = max(0.0, min(1.0, one_test_dx_pct/100.0))
235
- need_addl_with_ai = 1.0 - one_test_dx
236
  dec_unnec_ica = max(0.0, min(1.0, dec_unnec_ica_pct/100.0 * max(0.0, min(1.0, sens_dec_unnec_ica_factor_pct/100.0))))
237
  more_likely_revasc = max(0.0, min(1.0, more_likely_revasc_pct/100.0))
238
  revasc_prev = max(0.0, min(1.0, revasc_prevalence_pct/100.0))
@@ -341,7 +340,7 @@ def compute_ffrct(
341
  "evidence": evidence,
342
  }
343
 
344
- # ---------- MSK ----------
345
  def compute_msk(
346
  scans_per_day: float,
347
  reading_time_min: float,
@@ -349,17 +348,28 @@ def compute_msk(
349
  radiologist_hourly_cost: float = 180.0,
350
  ):
351
  scans_per_month = clamp_nonneg(scans_per_day) * 30.0
352
- errors_reduced_per_month = int(scans_per_month * 0.05 * 0.20)
353
- discrepant_cases_flagged = int(scans_per_month * 0.05)
354
- hrs_saved_per_visit = max(0.0, er_time_to_treatment_min) * 0.50 / 60.0
355
- time_saved_per_scan_min = max(0.0, reading_time_min) * 0.30
356
- total_time_saved_hours = (scans_per_month * time_saved_per_scan_min) / 60.0
357
- value_time_saved_month = total_time_saved_hours * radiologist_hourly_cost
358
- radiologist_cost_savings = scans_per_month * 4.0
 
 
 
 
 
 
 
359
  incr_revenue_month = 0.0
360
  incr_costs_month = 0.0
361
- ops_value_month = value_time_saved_month + radiologist_cost_savings
362
- net_impact_month = ops_value_month
 
 
 
 
363
 
364
  evidence = """
365
  <ul class='evidence'>
@@ -369,33 +379,75 @@ def compute_msk(
369
  </ul>
370
  """
371
 
372
- return {
 
 
373
  "summary": f"For your ED with ~{int(scans_per_month):,} MSK scans/month, modeled net benefit is {usd(net_impact_month)} per month.",
 
374
  "financial": {
375
  "rows": [
376
- ("Value of radiologist time saved (mo)", usd(value_time_saved_month)),
377
- ("Radiologist cost proxy savings (mo)", usd(radiologist_cost_savings)),
378
  ("Net impact (mo)", f"<b>{usd(net_impact_month)}</b>"),
379
  ]
380
  },
 
381
  "clinical": {
382
  "rows": [
383
  ("Errors reduced (est.)", f"{errors_reduced_per_month} /mo"),
384
  ("Discrepant cases flagged (est.)", f"{discrepant_cases_flagged} /mo"),
385
  ("Hours saved per ED visit (modeled)", f"{hrs_saved_per_visit:.2f}"),
386
  ],
387
- "bars": [("Touch-time reduction", 0.30)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
388
  },
389
- "operational": {"rows": [("Radiologist hours saved / month", f"{total_time_saved_hours:.1f}")]},
390
- "waterfall": [("Incremental revenue", 0.0), ("Incremental costs", 0.0), ("Operational value", ops_value_month)],
391
- "annual_card": {"incr_rev": 0.0, "incr_costs": 0.0, "ops_value": ops_value_month * 12.0, "net": net_impact_month * 12.0, "roi_pct": float("nan"), "payback": float("nan")},
392
  "evidence": evidence,
393
  }
394
 
395
  # ---------- Card / HTML builders ----------
396
- def build_overall_card(title: str, summary_line: str, annual):
397
- roi_txt = "" if (isinstance(annual["roi_pct"], float) and math.isnan(annual["roi_pct"])) else f"{annual['roi_pct']*100:.1f}%"
398
- payback_txt = "∞" if (annual["payback"] == math.inf) else f"{annual['payback']:.1f}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  return f"""
400
  <div class='card'>
401
  <div style='display:flex;justify-content:space-between;align-items:center;margin-bottom:8px'>
@@ -403,14 +455,7 @@ def build_overall_card(title: str, summary_line: str, annual):
403
  <div class='pill'>Clinical · Financial · Operational</div>
404
  </div>
405
  <div style='margin-bottom:8px'>{summary_line}</div>
406
- <div class='kpi-grid'>
407
- <div>Incremental revenue (annual)</div><div><b>{usd(annual['incr_rev'])}</b></div>
408
- <div>Incremental costs (annual)</div><div class='neg'><b>{usd(annual['incr_costs'])}</b></div>
409
- <div>Operational value (annual)</div><div><b>{usd(annual['ops_value'])}</b></div>
410
- <div class='sep'>Net impact (annual)</div><div class='sep'><b>{usd(annual['net'])}</b></div>
411
- <div title="ROI = Net impact ÷ (annual platform + vendor per-case fees)">ROI %</div><div><b>{roi_txt}</b></div>
412
- <div title="(Annual AI program cost ÷ net impact per AI case) ÷ monthly AI cases">Months to payback</div><div><b>{payback_txt}</b></div>
413
- </div>
414
  </div>
415
  """
416
 
@@ -479,9 +524,7 @@ def build_ui():
479
  grid-template-columns: repeat(3, minmax(0,1fr));
480
  gap: 12px;
481
  }
482
- @media (max-width: 820px) {
483
- #uc [role="radiogroup"] { grid-template-columns: 1fr; }
484
- }
485
  #uc [role="radiogroup"] label {
486
  border: 1px solid #eef2f7;
487
  border-radius: 16px;
@@ -701,7 +744,7 @@ def build_ui():
701
  evidence = f"<div class='card'><div style='font-weight:700;margin-bottom:6px'>Evidence snapshot</div>{res['evidence']}<div class='small-note'>Neutral claims; update with site citations.</div></div>"
702
  cta = f"<div class='card cta'><div>Want to see this in your workflow?</div><a class='cta-btn' href='{CTA_URL}' target='_blank' rel='noopener'>{CTA_LABEL}</a></div>"
703
 
704
- # CSV export (flatten current rows) -> write to temp file
705
  fin_rows = [(lab, val) for lab, val in res["financial"]["rows"]]
706
  clin_rows = [(lab, val) for lab, val in res["clinical"]["rows"]]
707
  op_rows = [(lab, val) for lab, val in res["operational"]["rows"]]
 
7
  # ==========================================
8
  # CARPL Multi-Use-Case ROI Calculators (MMG / FFR-CT / MSK AI)
9
  # Shared UI/UX: Inter font, white cards, pill header, Overall + Tabs + Waterfall + Evidence + CTA
10
+ # Extras: card-style use case chooser, MMG vendor presets, CSV export
11
+ # MSK updates: Overall Impact shows Financial + Clinical + Operational; hides ROI/Payback if N/A
12
  # ==========================================
13
 
14
  USE_CASES = ["Mammography AI (MMG)", "FFR-CT AI", "MSK AI (ER/Trauma)"]
 
76
  with open(path, "w", encoding="utf-8") as f:
77
  f.write("Metric,Value\n")
78
  for lab, val in rows:
 
79
  val = str(val).replace("<b>", "").replace("</b>", "")
80
  f.write(f"\"{lab}\",\"{val}\"\n")
81
  return str(path)
 
192
  ("Effective capacity increase", pct(capacity_increase_pct)),
193
  ]
194
  },
195
+ "waterfall": [("Incremental revenue", incr_revenue_month), ("Incremental costs", -incr_costs_month), ("Operational value", ops_value_month)],
196
  "annual_card": {
197
+ "incr_rev": incr_revenue_month * 12.0,
198
+ "incr_costs": incr_costs_month * 12.0,
199
  "ops_value": ops_value_month * 12.0,
200
  "net": net_impact_month * 12.0,
201
  "roi_pct": roi_pct_annual,
 
232
 
233
  pct_ai_qpa = max(0.0, min(1.0, pct_billed_ai_qpa/100.0))
234
  one_test_dx = max(0.0, min(1.0, one_test_dx_pct/100.0))
 
235
  dec_unnec_ica = max(0.0, min(1.0, dec_unnec_ica_pct/100.0 * max(0.0, min(1.0, sens_dec_unnec_ica_factor_pct/100.0))))
236
  more_likely_revasc = max(0.0, min(1.0, more_likely_revasc_pct/100.0))
237
  revasc_prev = max(0.0, min(1.0, revasc_prevalence_pct/100.0))
 
340
  "evidence": evidence,
341
  }
342
 
343
+ # ---------- MSK (ER/Trauma) ----------
344
  def compute_msk(
345
  scans_per_day: float,
346
  reading_time_min: float,
 
348
  radiologist_hourly_cost: float = 180.0,
349
  ):
350
  scans_per_month = clamp_nonneg(scans_per_day) * 30.0
351
+
352
+ # Clinical volumes
353
+ errors_reduced_per_month = int(scans_per_month * 0.05 * 0.20) # example incidence × improvement
354
+ discrepant_cases_flagged = int(scans_per_month * 0.05)
355
+ hrs_saved_per_visit = max(0.0, er_time_to_treatment_min) * 0.50 / 60.0
356
+
357
+ # Operational value
358
+ time_saved_per_scan_min = max(0.0, reading_time_min) * 0.30
359
+ total_time_saved_hours = (scans_per_month * time_saved_per_scan_min) / 60.0
360
+ value_time_saved_month = total_time_saved_hours * radiologist_hourly_cost
361
+ radiologist_cost_savings = scans_per_month * 4.0 # proxy from earlier MSK model
362
+ ops_value_month = value_time_saved_month + radiologist_cost_savings
363
+
364
+ # Financial (FFS-style): no extra revenue/costs modeled here
365
  incr_revenue_month = 0.0
366
  incr_costs_month = 0.0
367
+
368
+ # Clinical value ($) — placeholder (0); wire a $/event later if desired
369
+ clinical_value_month = 0.0
370
+
371
+ # Net impact
372
+ net_impact_month = incr_revenue_month - incr_costs_month + ops_value_month + clinical_value_month
373
 
374
  evidence = """
375
  <ul class='evidence'>
 
379
  </ul>
380
  """
381
 
382
+ return:
383
+ # Summary line
384
+ {
385
  "summary": f"For your ED with ~{int(scans_per_month):,} MSK scans/month, modeled net benefit is {usd(net_impact_month)} per month.",
386
+ # Financial card (only what matters; no NaNs)
387
  "financial": {
388
  "rows": [
389
+ ("Incremental revenue (mo)", usd(incr_revenue_month)),
390
+ ("Incremental costs (mo)", usd(incr_costs_month)),
391
  ("Net impact (mo)", f"<b>{usd(net_impact_month)}</b>"),
392
  ]
393
  },
394
+ # Clinical card
395
  "clinical": {
396
  "rows": [
397
  ("Errors reduced (est.)", f"{errors_reduced_per_month} /mo"),
398
  ("Discrepant cases flagged (est.)", f"{discrepant_cases_flagged} /mo"),
399
  ("Hours saved per ED visit (modeled)", f"{hrs_saved_per_visit:.2f}"),
400
  ],
401
+ "bars": [("Touch-time reduction", 0.30)],
402
+ },
403
+ # Operational card
404
+ "operational": {
405
+ "rows": [
406
+ ("Radiologist hours saved / month", f"{total_time_saved_hours:.1f}"),
407
+ ("Value of radiologist time saved (mo)", usd(value_time_saved_month)),
408
+ ("Radiologist cost proxy savings (mo)", usd(radiologist_cost_savings)),
409
+ ]
410
+ },
411
+ # Waterfall
412
+ "waterfall": [
413
+ ("Incremental revenue", incr_revenue_month),
414
+ ("Incremental costs", -incr_costs_month),
415
+ ("Operational value", ops_value_month),
416
+ # If you later monetize clinical value, add:
417
+ # ("Clinical value", clinical_value_month),
418
+ ],
419
+ # Annual metrics for Overall Impact (no NaNs)
420
+ "annual_card": {
421
+ "incr_rev": incr_revenue_month * 12.0,
422
+ "incr_costs": incr_costs_month * 12.0,
423
+ "clinical_value": clinical_value_month * 12.0,
424
+ "ops_value": ops_value_month * 12.0,
425
+ "net": net_impact_month * 12.0,
426
+ "roi_pct": None, # hidden in Overall Impact
427
+ "payback": None, # hidden in Overall Impact
428
  },
 
 
 
429
  "evidence": evidence,
430
  }
431
 
432
  # ---------- Card / HTML builders ----------
433
+ def build_overall_card(title: str, summary_line: str, annual: dict):
434
+ """Conditional Overall card: shows Financial + Clinical + Operational; hides ROI/Payback if N/A."""
435
+ rows = []
436
+ if "incr_rev" in annual: rows.append(("Incremental revenue (annual)", f"<b>{usd(annual['incr_rev'])}</b>"))
437
+ if "incr_costs" in annual: rows.append(("Incremental costs (annual)", f"<b class='neg'>{usd(annual['incr_costs'])}</b>"))
438
+ if "clinical_value" in annual and isinstance(annual["clinical_value"], (int, float)) and annual["clinical_value"] != 0:
439
+ rows.append(("Clinical value (annual)", f"<b>{usd(annual['clinical_value'])}</b>"))
440
+ if "ops_value" in annual: rows.append(("Operational value (annual)", f"<b>{usd(annual['ops_value'])}</b>"))
441
+ if "net" in annual: rows.append(("Net impact (annual)", f"<b>{usd(annual['net'])}</b>"))
442
+
443
+ roi = annual.get("roi_pct", None)
444
+ if isinstance(roi, (int, float)) and not (math.isnan(roi) or math.isinf(roi)):
445
+ rows.append(("ROI %", f"<b>{roi*100:.1f}%</b>"))
446
+ payback = annual.get("payback", None)
447
+ if isinstance(payback, (int, float)) and not (math.isnan(payback) or math.isinf(payback)):
448
+ rows.append(("Months to payback", f"<b>{payback:.1f}</b>"))
449
+
450
+ items = "".join(f"<div>{lab}</div><div>{val}</div>" for lab, val in rows)
451
  return f"""
452
  <div class='card'>
453
  <div style='display:flex;justify-content:space-between;align-items:center;margin-bottom:8px'>
 
455
  <div class='pill'>Clinical · Financial · Operational</div>
456
  </div>
457
  <div style='margin-bottom:8px'>{summary_line}</div>
458
+ <div class='kpi-grid'>{items}</div>
 
 
 
 
 
 
 
459
  </div>
460
  """
461
 
 
524
  grid-template-columns: repeat(3, minmax(0,1fr));
525
  gap: 12px;
526
  }
527
+ @media (max-width: 820px) { #uc [role="radiogroup"] { grid-template-columns: 1fr; } }
 
 
528
  #uc [role="radiogroup"] label {
529
  border: 1px solid #eef2f7;
530
  border-radius: 16px;
 
744
  evidence = f"<div class='card'><div style='font-weight:700;margin-bottom:6px'>Evidence snapshot</div>{res['evidence']}<div class='small-note'>Neutral claims; update with site citations.</div></div>"
745
  cta = f"<div class='card cta'><div>Want to see this in your workflow?</div><a class='cta-btn' href='{CTA_URL}' target='_blank' rel='noopener'>{CTA_LABEL}</a></div>"
746
 
747
+ # CSV export -> write to temp file
748
  fin_rows = [(lab, val) for lab, val in res["financial"]["rows"]]
749
  clin_rows = [(lab, val) for lab, val in res["clinical"]["rows"]]
750
  op_rows = [(lab, val) for lab, val in res["operational"]["rows"]]