Slaiwala commited on
Commit
cd28972
·
verified ·
1 Parent(s): f2e05cd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +101 -47
app.py CHANGED
@@ -1,11 +1,12 @@
1
  # app.py — Spine Coder (Chatbot + Feedback + Session Logs) — Gradio 4.x
2
  # ------------------------------------------------------------------------------
3
  # FINAL-v2.1 UI build:
 
 
4
  # - Clean CPT table (no per-row modifier columns)
5
  # - Case Modifiers panel (-22, -50, -53, -80, -82, ...)
6
- # - Build tag + Core file path shown in UI chips and logged
7
- # - Force-import + reload of core to avoid stale cache
8
- # - Structured logs/export, graceful errors, modern Gradio usage
9
  # ------------------------------------------------------------------------------
10
 
11
  import os
@@ -19,49 +20,63 @@ from typing import Any, Dict, List, Tuple
19
  import pandas as pd
20
  import gradio as gr
21
 
22
- # ---- Core import --------------------------------------------------------------
23
- import os as _os, sys as _sys, inspect as _inspect
24
- _sys.path.insert(0, _os.path.abspath(".")) # ensure repo root is first on sys.path
25
 
26
- # Try normal imports (still useful for local dev)
27
- try:
28
- from spine_coder.spine_coder.spine_coder_core import suggest_with_cpt_billing as _maybe_suggest
29
- except Exception:
30
- try:
31
- from spine_coder.spine_coder_core import suggest_with_cpt_billing as _maybe_suggest
32
- except Exception:
33
- _maybe_suggest = None
34
 
35
- # --- Force-load the v2.1 core and avoid stale caches (HF Space sometimes caches modules) ---
36
- import importlib
 
 
 
 
 
 
 
 
 
 
37
 
38
  def _force_import_core():
39
- mod = None
40
- try:
41
- # Preferred package layout
42
- mod = importlib.import_module("spine_coder.spine_coder.spine_coder_core")
43
- except Exception:
44
- # Fallback to flat layout
45
- mod = importlib.import_module("spine_coder.spine_coder_core")
46
- # Hard reload to bust any stale cache
47
- mod = importlib.reload(mod)
48
- return mod
49
-
50
- try:
51
- _core = _force_import_core()
52
- suggest_with_cpt_billing = _core.suggest_with_cpt_billing
53
- except Exception:
54
- # Last resort: use whatever we imported earlier (if any)
55
- if _maybe_suggest is None:
56
- raise
57
- suggest_with_cpt_billing = _maybe_suggest
 
 
 
 
 
58
 
59
- # Prove which file is actually loaded (visible in Space logs)
60
  try:
61
- print("[CORE] loaded from:", _inspect.getsourcefile(suggest_with_cpt_billing))
62
  except Exception:
63
  pass
64
 
 
 
 
 
 
 
 
 
65
 
66
  # ==== Config ==================================================================
67
  os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
@@ -94,18 +109,15 @@ def export_session(session_id: str) -> str:
94
  json.dump(data, f, indent=2, ensure_ascii=False)
95
  return out_path
96
 
97
-
98
- # ==== Helpers =================================================================
99
 
100
  def _core_path() -> str:
101
- """Absolute path of the loaded core, for UI + logs."""
102
  try:
103
- p = _inspect.getsourcefile(suggest_with_cpt_billing) or "unknown"
104
- return p
105
  except Exception:
106
  return "unknown"
107
 
108
- # NOTE: Modifiers columns removed from the CPT table on purpose.
109
  SUGG_COLS = [
110
  "CPT", "Description", "Rationale",
111
  "Confidence", "Primary", "Category", "Laterality", "Units"
@@ -210,7 +222,6 @@ def _summary_md(meta: Dict[str, Any]) -> str:
210
  if meta.get("flags"): chips.append(f"`Flags: {meta['flags']}`")
211
  if meta.get("build"): chips.append(f"`Build: {meta['build']}`")
212
  if meta.get("mode"): chips.append(f"`Mode: {meta['mode']}`")
213
- # Show only the basename of the core file in UI chips (full path is in logs)
214
  try:
215
  core_base = os.path.basename(meta.get("core_path","")) if meta.get("core_path") else ""
216
  if core_base:
@@ -222,7 +233,6 @@ def _summary_md(meta: Dict[str, Any]) -> str:
222
  def new_session() -> str:
223
  return str(uuid.uuid4())[:8]
224
 
225
-
226
  # ==== Core actions ============================================================
227
 
228
  def run_inference(note: str, payer: str, top_k: int, session_id: str):
@@ -234,7 +244,6 @@ def run_inference(note: str, payer: str, top_k: int, session_id: str):
234
  )
235
 
236
  _append_log(session_id, {"event": "request", "payer": payer, "top_k": top_k, "note": note})
237
- warn_text = ""
238
  try:
239
  result = suggest_with_cpt_billing(note=note, payer=payer, top_k=top_k)
240
  if DEBUG:
@@ -262,7 +271,6 @@ def run_inference(note: str, payer: str, top_k: int, session_id: str):
262
  "event": "response",
263
  "meta": {
264
  **meta,
265
- # compact string for quick scan in logs
266
  "case_modifiers": ", ".join([f"-{m}" for m in [cm.get("modifier","") for cm in (result.get("case_modifiers") or [])] if m]) or ""
267
  },
268
  "rows_len": int(len(sugg_df) if hasattr(sugg_df, "__len__") else 0)
@@ -289,6 +297,46 @@ def on_clear():
289
  new_session()
290
  )
291
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
292
 
293
  # ==== Examples ================================================================
294
 
@@ -453,6 +501,10 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
453
  export_btn = gr.Button("Export Session as JSON")
454
  export_file = gr.File(label="Download", interactive=False)
455
 
 
 
 
 
456
  # Results
457
  with gr.Column(scale=7):
458
  gr.Markdown("#### Results")
@@ -504,6 +556,8 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="Spine Coder — CPT Billing")
504
  fb_submit.click(record_feedback, inputs=[session_id, fb_choice, fb_text], outputs=fb_status)
505
  export_btn.click(do_export, inputs=[session_id], outputs=[export_file])
506
 
 
 
507
  if __name__ == "__main__":
508
  # If running locally: set server_name to 0.0.0.0 for external access; Space ignores.
509
  demo.launch()
 
1
  # app.py — Spine Coder (Chatbot + Feedback + Session Logs) — Gradio 4.x
2
  # ------------------------------------------------------------------------------
3
  # FINAL-v2.1 UI build:
4
+ # - Bulletproof core loader (module + direct file path) to avoid stale caches
5
+ # - Startup PROBE print to logs + in-UI Diagnostics/Probe button
6
  # - Clean CPT table (no per-row modifier columns)
7
  # - Case Modifiers panel (-22, -50, -53, -80, -82, ...)
8
+ # - Build tag + Core file path in UI chips and logs
9
+ # - Structured logs/export, modern Gradio usage
 
10
  # ------------------------------------------------------------------------------
11
 
12
  import os
 
20
  import pandas as pd
21
  import gradio as gr
22
 
23
+ # ==== Bulletproof Core Import ==================================================
24
+ import importlib, importlib.util, importlib.machinery, sys as __sys, os as __os, inspect as __inspect
 
25
 
26
+ __sys.path.insert(0, __os.path.abspath(".")) # ensure repo root is on sys.path
 
 
 
 
 
 
 
27
 
28
+ def _load_core_from_file(path: str):
29
+ """Load spine_coder_core.py directly from a given path (bypass module cache)."""
30
+ if not __os.path.isabs(path):
31
+ path = __os.path.abspath(path)
32
+ if not __os.path.exists(path):
33
+ return None
34
+ name = "spine_coder_core_dynamic"
35
+ loader = importlib.machinery.SourceFileLoader(name, path)
36
+ spec = importlib.util.spec_from_loader(name, loader)
37
+ mod = importlib.util.module_from_spec(spec)
38
+ loader.exec_module(mod)
39
+ return importlib.reload(mod)
40
 
41
  def _force_import_core():
42
+ """Try package modules first; then known file paths. Reload to bust caches."""
43
+ # 1) Try module layouts
44
+ for modname in [
45
+ "spine_coder.spine_coder.spine_coder_core",
46
+ "spine_coder.spine_coder_core",
47
+ ]:
48
+ try:
49
+ mod = importlib.import_module(modname)
50
+ return importlib.reload(mod)
51
+ except Exception:
52
+ pass
53
+ # 2) Try likely file paths (repo variants)
54
+ for rel in [
55
+ "spine_coder/spine_coder/spine_coder_core.py",
56
+ "spine_coder/spine_coder_core.py",
57
+ "spine_coder_core.py",
58
+ ]:
59
+ mod = _load_core_from_file(rel)
60
+ if mod:
61
+ return mod
62
+ raise ImportError("Unable to locate spine_coder_core.py via modules or file paths.")
63
+
64
+ _core = _force_import_core()
65
+ suggest_with_cpt_billing = _core.suggest_with_cpt_billing
66
 
 
67
  try:
68
+ print("[CORE] loaded from:", __inspect.getsourcefile(suggest_with_cpt_billing))
69
  except Exception:
70
  pass
71
 
72
+ # ---- One-time startup probe (prints to Space logs) ----------------------------
73
+ try:
74
+ _probe_note = "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7."
75
+ _probe = suggest_with_cpt_billing(_probe_note, payer="Medicare", top_k=5)
76
+ print("[PROBE] build:", _probe.get("build"))
77
+ print("[PROBE] first_suggestion:", (_probe.get("suggestions") or [{}])[0])
78
+ except Exception as e:
79
+ print("[PROBE] failed:", e)
80
 
81
  # ==== Config ==================================================================
82
  os.environ.setdefault("GRADIO_ANALYTICS_ENABLED", "False")
 
109
  json.dump(data, f, indent=2, ensure_ascii=False)
110
  return out_path
111
 
112
+ # ==== UI Helpers ==============================================================
 
113
 
114
  def _core_path() -> str:
 
115
  try:
116
+ return __inspect.getsourcefile(suggest_with_cpt_billing) or "unknown"
 
117
  except Exception:
118
  return "unknown"
119
 
120
+ # NOTE: Per-row modifiers removed from the CPT table on purpose.
121
  SUGG_COLS = [
122
  "CPT", "Description", "Rationale",
123
  "Confidence", "Primary", "Category", "Laterality", "Units"
 
222
  if meta.get("flags"): chips.append(f"`Flags: {meta['flags']}`")
223
  if meta.get("build"): chips.append(f"`Build: {meta['build']}`")
224
  if meta.get("mode"): chips.append(f"`Mode: {meta['mode']}`")
 
225
  try:
226
  core_base = os.path.basename(meta.get("core_path","")) if meta.get("core_path") else ""
227
  if core_base:
 
233
  def new_session() -> str:
234
  return str(uuid.uuid4())[:8]
235
 
 
236
  # ==== Core actions ============================================================
237
 
238
  def run_inference(note: str, payer: str, top_k: int, session_id: str):
 
244
  )
245
 
246
  _append_log(session_id, {"event": "request", "payer": payer, "top_k": top_k, "note": note})
 
247
  try:
248
  result = suggest_with_cpt_billing(note=note, payer=payer, top_k=top_k)
249
  if DEBUG:
 
271
  "event": "response",
272
  "meta": {
273
  **meta,
 
274
  "case_modifiers": ", ".join([f"-{m}" for m in [cm.get("modifier","") for cm in (result.get("case_modifiers") or [])] if m]) or ""
275
  },
276
  "rows_len": int(len(sugg_df) if hasattr(sugg_df, "__len__") else 0)
 
297
  new_session()
298
  )
299
 
300
+ # ==== Diagnostics / Probe =====================================================
301
+
302
+ def run_probe(session_id: str) -> Tuple[str, str]:
303
+ """Run a live diagnostic: show core path & build and 3 smoke tests."""
304
+ info_lines = []
305
+ try:
306
+ core_path = _core_path()
307
+ # Three canonical tests
308
+ tests = [
309
+ ("Implicit TLIF",
310
+ "Left facetectomy L4–L5 with PEEK interbody cage and pedicle screws; rods secured.",
311
+ ["22633"], # expect
312
+ ),
313
+ ("ACDF chain w/ plate",
314
+ "Discectomies at C4–C5, C5–C6, and C6–C7. Interbody cages and anterior plate spanning C4–C7.",
315
+ ["22551","22552","22846"],
316
+ ),
317
+ ("Exposure-only",
318
+ "Anterior exposure of L4–S1 performed by vascular surgeon for access. No fusion performed.",
319
+ ["00000"],
320
+ ),
321
+ ]
322
+ # Run once to get build
323
+ probe = suggest_with_cpt_billing(tests[1][1], payer="Medicare", top_k=10)
324
+ build = probe.get("build","")
325
+ info_lines.append(f"**Core path:** `{core_path}` \n**Build:** `{build}`")
326
+
327
+ # Execute each test
328
+ for label, note, expect_codes in tests:
329
+ res = suggest_with_cpt_billing(note, payer="Medicare", top_k=10)
330
+ codes = [s.get("cpt") for s in (res.get("suggestions") or [])]
331
+ ok = all(any(ec == c for c in codes) for ec in expect_codes)
332
+ info_lines.append(f"- **{label}** → codes: `{', '.join(codes) or '∅'}` — **{'PASS' if ok else 'CHECK'}**")
333
+ md = "\n".join(info_lines)
334
+ _append_log(session_id, {"event":"probe","details": md})
335
+ return md, ""
336
+ except Exception as e:
337
+ tb = traceback.format_exc()
338
+ _append_log(session_id, {"event":"probe_error","error":repr(e),"traceback":tb})
339
+ return "", f"⚠️ Probe failed: {e}"
340
 
341
  # ==== Examples ================================================================
342
 
 
501
  export_btn = gr.Button("Export Session as JSON")
502
  export_file = gr.File(label="Download", interactive=False)
503
 
504
+ with gr.Accordion("Diagnostics", open=False):
505
+ probe_btn = gr.Button("Run Probe (core path + 3 tests)")
506
+ probe_md = gr.Markdown("")
507
+
508
  # Results
509
  with gr.Column(scale=7):
510
  gr.Markdown("#### Results")
 
556
  fb_submit.click(record_feedback, inputs=[session_id, fb_choice, fb_text], outputs=fb_status)
557
  export_btn.click(do_export, inputs=[session_id], outputs=[export_file])
558
 
559
+ probe_btn.click(run_probe, inputs=[session_id], outputs=[probe_md, warn_md])
560
+
561
  if __name__ == "__main__":
562
  # If running locally: set server_name to 0.0.0.0 for external access; Space ignores.
563
  demo.launch()