Slaiwala commited on
Commit
f15b975
·
verified ·
1 Parent(s): 3b016a1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -22
app.py CHANGED
@@ -43,6 +43,10 @@ def _sha256(path: str) -> str:
43
  return "unknown"
44
 
45
  def _load_core_from_file(path: str):
 
 
 
 
46
  path = __os.path.abspath(path)
47
  if not __os.path.exists(path):
48
  return None
@@ -55,8 +59,10 @@ def _load_core_from_file(path: str):
55
  return mod
56
 
57
  def _force_import_core():
 
58
  _purge_spine_modules()
59
 
 
60
  forced = __os.environ.get("SPINE_CORE_PATH")
61
  if forced and __os.path.exists(forced):
62
  mod = _load_core_from_file(forced)
@@ -64,6 +70,7 @@ def _force_import_core():
64
  print("[CORE] forced path:", forced, "sha:", _sha256(forced))
65
  return mod
66
 
 
67
  for rel in [
68
  "spine_coder_core.py",
69
  "spine_coder/spine_coder_core.py",
@@ -76,6 +83,7 @@ def _force_import_core():
76
  print("[CORE] loaded file:", p, "sha:", _sha256(p))
77
  return mod
78
 
 
79
  for modname in [
80
  "spine_coder.spine_coder.spine_coder_core",
81
  "spine_coder.spine_coder_core",
@@ -100,7 +108,7 @@ try:
100
  except Exception:
101
  _active_src = "unknown"
102
 
103
- # ---- One-time startup probe ---------------------------------------------------
104
  try:
105
  _probe_note = "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7."
106
  _probe = suggest_with_cpt_billing(_probe_note, payer="Medicare", top_k=5)
@@ -219,6 +227,7 @@ def _core_path() -> str:
219
  except Exception:
220
  return "unknown"
221
 
 
222
  SUGG_COLS = [
223
  "CPT", "Description", "Rationale",
224
  "Confidence", "Primary", "Category", "Laterality", "Units"
@@ -227,6 +236,7 @@ EMPTY_SUGG_DF = pd.DataFrame(columns=SUGG_COLS)
227
  EMPTY_MODS_DF = pd.DataFrame([{"modifier": "—", "reason": ""}])
228
 
229
  def _coalesce_rows(result: Dict[str, Any]) -> Tuple[pd.DataFrame, Dict[str, Any]]:
 
230
  if not isinstance(result, dict):
231
  return EMPTY_SUGG_DF, {}
232
 
@@ -239,6 +249,7 @@ def _coalesce_rows(result: Dict[str, Any]) -> Tuple[pd.DataFrame, Dict[str, Any]
239
  continue
240
  mods = s.get("modifiers", []) or []
241
 
 
242
  row_lat = (s.get("laterality") or "").strip().lower()
243
  if not row_lat:
244
  if isinstance(mods, list) and "LT" in mods:
@@ -262,7 +273,7 @@ def _coalesce_rows(result: Dict[str, Any]) -> Tuple[pd.DataFrame, Dict[str, Any]
262
  "Units": s.get("units", 1),
263
  })
264
 
265
- # Levels
266
  segs: List[str] = []
267
  inters_list: List[str] = []
268
  lvl_lat = ""
@@ -274,7 +285,7 @@ def _coalesce_rows(result: Dict[str, Any]) -> Tuple[pd.DataFrame, Dict[str, Any]
274
  elif isinstance(levels_obj, list):
275
  segs = [str(x) for x in levels_obj]
276
 
277
- # Flags
278
  flags_obj = result.get("flags")
279
  if isinstance(flags_obj, list):
280
  flags_list = [str(x) for x in flags_obj]
@@ -346,7 +357,7 @@ def run_inference(note: str, payer: str, top_k: int, session_id: str):
346
  try:
347
  result = suggest_with_cpt_billing(note=note, payer=payer, top_k=top_k)
348
  if DEBUG:
349
- print("[DEBUG] build/region/laterality/flags:",
350
  result.get("build"), result.get("region"),
351
  result.get("laterality"), result.get("flags"))
352
  except Exception as e:
@@ -406,7 +417,7 @@ def run_probe(session_id: str) -> Tuple[str, str]:
406
  tests = [
407
  ("Implicit TLIF",
408
  "Left facetectomy L4–L5 with PEEK interbody cage and pedicle screws; rods secured.",
409
- ["22633"],
410
  ),
411
  ("ACDF chain w/ plate",
412
  "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7.",
@@ -417,10 +428,12 @@ def run_probe(session_id: str) -> Tuple[str, str]:
417
  ["00000"],
418
  ),
419
  ]
 
420
  probe = suggest_with_cpt_billing(tests[1][1], payer="Medicare", top_k=10)
421
  build = probe.get("build","")
422
  info_lines.append(f"**Core path:** `{core_path}` \n**Build:** `{build}`")
423
 
 
424
  for label, note, expect_codes in tests:
425
  res = suggest_with_cpt_billing(note, payer="Medicare", top_k=10)
426
  codes = [s.get("cpt") for s in (res.get("suggestions") or [])]
@@ -442,6 +455,7 @@ EXAMPLES = [
442
  ["Posterior cervical foraminotomy right C6–C7; no fusion or instrumentation.", "Medicare", 10],
443
  ["ALIF L5–S1 with structural allograft; non-segmental instrumentation placed.", "Medicare", 10],
444
  ["Removal of posterior segmental instrumentation T10–L2; no new hardware placed.", "Medicare", 10],
 
445
  ["TLIF L4–L5 was initiated but aborted midway due to neuromonitoring changes.", "Medicare", 10], # -53
446
  ["Bilateral decompression and foraminotomy at L4–L5 and L5–S1.", "Medicare", 10], # -50
447
  ["Assistant surgeon present; resident not available.", "Medicare", 10], # -82
@@ -464,27 +478,77 @@ THEME = gr.themes.Soft(
464
  CUSTOM_CSS = """
465
  :root { --radius-lg: 16px; }
466
  .gradio-container { font-family: ui-sans-serif, system-ui, -apple-system; }
467
- .header-card { border-radius: 18px; padding: 18px; border: 1px solid #1f2937;
468
- background: linear-gradient(180deg,#0f172a,#0b1220); color: #e5e7eb; }
469
- .badge-row code { margin-right: 8px; border-radius: 12px; padding: 2px 8px;
470
- background: #111827; color: #e5e7eb; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  .table-wrap { max-height: 520px; overflow: auto; }
472
- #suggestions_table .dataframe { font-size: 15px; width: 100% !important;
473
- table-layout: auto !important; border-collapse: collapse; }
474
- #suggestions_table .dataframe th, #suggestions_table .dataframe td {
475
- white-space: normal; word-wrap: break-word; text-align: left;
476
- vertical-align: top; padding: 10px 12px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
477
  #suggestions_table .dataframe th:nth-child(1),
478
  #suggestions_table .dataframe td:nth-child(1) {
479
- min-width: 120px; max-width: 140px; white-space: nowrap !important;
 
 
480
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
481
- text-align: center; }
 
 
 
482
  #suggestions_table .dataframe th:nth-child(2),
483
- #suggestions_table .dataframe td:nth-child(2) { min-width: 360px; max-width: 560px; }
 
 
 
 
 
484
  #suggestions_table .dataframe th:nth-child(3),
485
- #suggestions_table .dataframe td:nth-child(3) { min-width: 320px; max-width: 520px; }
 
 
 
 
 
486
  #suggestions_table .dataframe th:nth-child(6),
487
- #suggestions_table .dataframe td:nth-child(6) { min-width: 180px; }
 
 
 
 
488
  #suggestions_table .dataframe th:nth-child(4),
489
  #suggestions_table .dataframe td:nth-child(4),
490
  #suggestions_table .dataframe th:nth-child(5),
@@ -492,7 +556,10 @@ CUSTOM_CSS = """
492
  #suggestions_table .dataframe th:nth-child(7),
493
  #suggestions_table .dataframe td:nth-child(7),
494
  #suggestions_table .dataframe th:nth-child(8),
495
- #suggestions_table .dataframe td:nth-child(8) { min-width: 90px; }
 
 
 
496
  .footer-note { color:#94a3b8; font-size:12px; }
497
  """
498
 
@@ -510,6 +577,7 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
510
  )
511
 
512
  with gr.Row():
 
513
  with gr.Column(scale=5):
514
  note_in = gr.Textbox(
515
  label="Operative Note",
@@ -525,7 +593,11 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
525
  clear_btn = gr.Button("Clear")
526
 
527
  with gr.Accordion("Quick Examples", open=False):
528
- gr.Examples(examples=EXAMPLES, inputs=[note_in, payer_dd, topk], label="Click a row to load an example")
 
 
 
 
529
 
530
  with gr.Accordion("Feedback", open=False):
531
  fb_choice = gr.Radio(choices=["👍", "👎"], label="Was this helpful?")
@@ -542,10 +614,12 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
542
  probe_btn = gr.Button("Run Probe (core path + 3 tests)")
543
  probe_md = gr.Markdown("")
544
 
 
545
  with gr.Column(scale=7):
546
  gr.Markdown("#### Results")
547
  summary_md = gr.Markdown("—", elem_classes=["badge-row"])
548
 
 
549
  table = gr.Dataframe(
550
  value=EMPTY_SUGG_DF,
551
  label="CPT Suggestions",
@@ -556,6 +630,7 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
556
  elem_id="suggestions_table",
557
  )
558
 
 
559
  gr.Markdown("### Case Modifiers (visit-level)")
560
  case_mods_table = gr.Dataframe(
561
  value=EMPTY_MODS_DF.copy(),
@@ -582,7 +657,10 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
582
  run_btn.click(run_inference, inputs=run_inputs, outputs=run_outputs)
583
  note_in.submit(run_inference, inputs=run_inputs, outputs=run_outputs)
584
 
585
- clear_btn.click(on_clear, outputs=[note_in, payer_dd, topk, table, case_mods_table, summary_md, json_out, warn_md, session_id])
 
 
 
586
 
587
  fb_submit.click(record_feedback, inputs=[session_id, fb_choice, fb_text], outputs=fb_status)
588
  export_btn.click(do_export, inputs=[session_id], outputs=[export_file])
@@ -590,4 +668,5 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
590
  probe_btn.click(run_probe, inputs=[session_id], outputs=[probe_md, warn_md])
591
 
592
  if __name__ == "__main__":
 
593
  demo.launch()
 
43
  return "unknown"
44
 
45
  def _load_core_from_file(path: str):
46
+ """
47
+ Load spine_coder_core.py directly from a given path (bypass module cache).
48
+ Register in sys.modules BEFORE exec to enable imports inside the core.
49
+ """
50
  path = __os.path.abspath(path)
51
  if not __os.path.exists(path):
52
  return None
 
59
  return mod
60
 
61
  def _force_import_core():
62
+ """Order: purge caches → SPINE_CORE_PATH → local files → package modules."""
63
  _purge_spine_modules()
64
 
65
+ # 1) Explicit override
66
  forced = __os.environ.get("SPINE_CORE_PATH")
67
  if forced and __os.path.exists(forced):
68
  mod = _load_core_from_file(forced)
 
70
  print("[CORE] forced path:", forced, "sha:", _sha256(forced))
71
  return mod
72
 
73
+ # 2) Likely file paths near app
74
  for rel in [
75
  "spine_coder_core.py",
76
  "spine_coder/spine_coder_core.py",
 
83
  print("[CORE] loaded file:", p, "sha:", _sha256(p))
84
  return mod
85
 
86
+ # 3) Package modules
87
  for modname in [
88
  "spine_coder.spine_coder.spine_coder_core",
89
  "spine_coder.spine_coder_core",
 
108
  except Exception:
109
  _active_src = "unknown"
110
 
111
+ # ---- One-time startup probe (prints to Space logs) ----------------------------
112
  try:
113
  _probe_note = "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7."
114
  _probe = suggest_with_cpt_billing(_probe_note, payer="Medicare", top_k=5)
 
227
  except Exception:
228
  return "unknown"
229
 
230
+ # Display columns (per-row modifiers are deliberately not shown here)
231
  SUGG_COLS = [
232
  "CPT", "Description", "Rationale",
233
  "Confidence", "Primary", "Category", "Laterality", "Units"
 
236
  EMPTY_MODS_DF = pd.DataFrame([{"modifier": "—", "reason": ""}])
237
 
238
  def _coalesce_rows(result: Dict[str, Any]) -> Tuple[pd.DataFrame, Dict[str, Any]]:
239
+ """Build the suggestions table (no modifier columns) and meta badges."""
240
  if not isinstance(result, dict):
241
  return EMPTY_SUGG_DF, {}
242
 
 
249
  continue
250
  mods = s.get("modifiers", []) or []
251
 
252
+ # Derive row laterality
253
  row_lat = (s.get("laterality") or "").strip().lower()
254
  if not row_lat:
255
  if isinstance(mods, list) and "LT" in mods:
 
273
  "Units": s.get("units", 1),
274
  })
275
 
276
+ # Normalize levels
277
  segs: List[str] = []
278
  inters_list: List[str] = []
279
  lvl_lat = ""
 
285
  elif isinstance(levels_obj, list):
286
  segs = [str(x) for x in levels_obj]
287
 
288
+ # Normalize flags
289
  flags_obj = result.get("flags")
290
  if isinstance(flags_obj, list):
291
  flags_list = [str(x) for x in flags_obj]
 
357
  try:
358
  result = suggest_with_cpt_billing(note=note, payer=payer, top_k=top_k)
359
  if DEBUG:
360
+ print("[DEBUG] build/region/laterality/flags]:",
361
  result.get("build"), result.get("region"),
362
  result.get("laterality"), result.get("flags"))
363
  except Exception as e:
 
417
  tests = [
418
  ("Implicit TLIF",
419
  "Left facetectomy L4–L5 with PEEK interbody cage and pedicle screws; rods secured.",
420
+ ["22633"], # expect
421
  ),
422
  ("ACDF chain w/ plate",
423
  "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7.",
 
428
  ["00000"],
429
  ),
430
  ]
431
+ # Run once to get build
432
  probe = suggest_with_cpt_billing(tests[1][1], payer="Medicare", top_k=10)
433
  build = probe.get("build","")
434
  info_lines.append(f"**Core path:** `{core_path}` \n**Build:** `{build}`")
435
 
436
+ # Execute each test
437
  for label, note, expect_codes in tests:
438
  res = suggest_with_cpt_billing(note, payer="Medicare", top_k=10)
439
  codes = [s.get("cpt") for s in (res.get("suggestions") or [])]
 
455
  ["Posterior cervical foraminotomy right C6–C7; no fusion or instrumentation.", "Medicare", 10],
456
  ["ALIF L5–S1 with structural allograft; non-segmental instrumentation placed.", "Medicare", 10],
457
  ["Removal of posterior segmental instrumentation T10–L2; no new hardware placed.", "Medicare", 10],
458
+ # Case-modifier smoke tests:
459
  ["TLIF L4–L5 was initiated but aborted midway due to neuromonitoring changes.", "Medicare", 10], # -53
460
  ["Bilateral decompression and foraminotomy at L4–L5 and L5–S1.", "Medicare", 10], # -50
461
  ["Assistant surgeon present; resident not available.", "Medicare", 10], # -82
 
478
  CUSTOM_CSS = """
479
  :root { --radius-lg: 16px; }
480
  .gradio-container { font-family: ui-sans-serif, system-ui, -apple-system; }
481
+
482
+ /* Header card */
483
+ .header-card {
484
+ border-radius: 18px;
485
+ padding: 18px;
486
+ border: 1px solid #1f2937;
487
+ background: linear-gradient(180deg,#0f172a,#0b1220);
488
+ color: #e5e7eb;
489
+ }
490
+ .badge-row code {
491
+ margin-right: 8px;
492
+ border-radius: 12px;
493
+ padding: 2px 8px;
494
+ background: #111827;
495
+ color: #e5e7eb;
496
+ }
497
+
498
+ /* Table container */
499
  .table-wrap { max-height: 520px; overflow: auto; }
500
+
501
+ /* Suggestions table target */
502
+ #suggestions_table .dataframe {
503
+ font-size: 15px;
504
+ width: 100% !important;
505
+ table-layout: auto !important;
506
+ border-collapse: collapse;
507
+ }
508
+ #suggestions_table .dataframe th,
509
+ #suggestions_table .dataframe td {
510
+ white-space: normal;
511
+ word-wrap: break-word;
512
+ text-align: left;
513
+ vertical-align: top;
514
+ padding: 10px 12px;
515
+ }
516
+
517
+ /* Column sizing (1-indexed):
518
+ 1=CPT, 2=Description, 3=Rationale, 4=Confidence,
519
+ 5=Primary, 6=Category, 7=Laterality, 8=Units */
520
+
521
+ /* CPT — wider & no wrap, monospace, centered */
522
  #suggestions_table .dataframe th:nth-child(1),
523
  #suggestions_table .dataframe td:nth-child(1) {
524
+ min-width: 120px;
525
+ max-width: 140px;
526
+ white-space: nowrap !important;
527
  font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
528
+ text-align: center;
529
+ }
530
+
531
+ /* Description — roomy */
532
  #suggestions_table .dataframe th:nth-child(2),
533
+ #suggestions_table .dataframe td:nth-child(2) {
534
+ min-width: 360px;
535
+ max-width: 560px;
536
+ }
537
+
538
+ /* Rationale — roomy */
539
  #suggestions_table .dataframe th:nth-child(3),
540
+ #suggestions_table .dataframe td:nth-child(3) {
541
+ min-width: 320px;
542
+ max-width: 520px;
543
+ }
544
+
545
+ /* Category — a bit wider */
546
  #suggestions_table .dataframe th:nth-child(6),
547
+ #suggestions_table .dataframe td:nth-child(6) {
548
+ min-width: 180px;
549
+ }
550
+
551
+ /* Keep tiny columns compact */
552
  #suggestions_table .dataframe th:nth-child(4),
553
  #suggestions_table .dataframe td:nth-child(4),
554
  #suggestions_table .dataframe th:nth-child(5),
 
556
  #suggestions_table .dataframe th:nth-child(7),
557
  #suggestions_table .dataframe td:nth-child(7),
558
  #suggestions_table .dataframe th:nth-child(8),
559
+ #suggestions_table .dataframe td:nth-child(8) {
560
+ min-width: 90px;
561
+ }
562
+
563
  .footer-note { color:#94a3b8; font-size:12px; }
564
  """
565
 
 
577
  )
578
 
579
  with gr.Row():
580
+ # Inputs
581
  with gr.Column(scale=5):
582
  note_in = gr.Textbox(
583
  label="Operative Note",
 
593
  clear_btn = gr.Button("Clear")
594
 
595
  with gr.Accordion("Quick Examples", open=False):
596
+ gr.Examples(
597
+ examples=EXAMPLES,
598
+ inputs=[note_in, payer_dd, topk],
599
+ label="Click a row to load an example"
600
+ )
601
 
602
  with gr.Accordion("Feedback", open=False):
603
  fb_choice = gr.Radio(choices=["👍", "👎"], label="Was this helpful?")
 
614
  probe_btn = gr.Button("Run Probe (core path + 3 tests)")
615
  probe_md = gr.Markdown("")
616
 
617
+ # Results
618
  with gr.Column(scale=7):
619
  gr.Markdown("#### Results")
620
  summary_md = gr.Markdown("—", elem_classes=["badge-row"])
621
 
622
+ # Suggestions table (no modifier columns)
623
  table = gr.Dataframe(
624
  value=EMPTY_SUGG_DF,
625
  label="CPT Suggestions",
 
630
  elem_id="suggestions_table",
631
  )
632
 
633
+ # Case-level modifiers table
634
  gr.Markdown("### Case Modifiers (visit-level)")
635
  case_mods_table = gr.Dataframe(
636
  value=EMPTY_MODS_DF.copy(),
 
657
  run_btn.click(run_inference, inputs=run_inputs, outputs=run_outputs)
658
  note_in.submit(run_inference, inputs=run_inputs, outputs=run_outputs)
659
 
660
+ clear_btn.click(
661
+ on_clear,
662
+ outputs=[note_in, payer_dd, topk, table, case_mods_table, summary_md, json_out, warn_md, session_id]
663
+ )
664
 
665
  fb_submit.click(record_feedback, inputs=[session_id, fb_choice, fb_text], outputs=fb_status)
666
  export_btn.click(do_export, inputs=[session_id], outputs=[export_file])
 
668
  probe_btn.click(run_probe, inputs=[session_id], outputs=[probe_md, warn_md])
669
 
670
  if __name__ == "__main__":
671
+ # If running locally: set server_name to 0.0.0.0 for external access; Space ignores.
672
  demo.launch()