Slaiwala commited on
Commit
358cd1c
·
1 Parent(s): f42894a

Add spine_coder package (core + dataset + tests)

Browse files
spine_coder/spine_coder/__pycache__/__init__.cpython-312.pyc CHANGED
Binary files a/spine_coder/spine_coder/__pycache__/__init__.cpython-312.pyc and b/spine_coder/spine_coder/__pycache__/__init__.cpython-312.pyc differ
 
spine_coder/spine_coder/__pycache__/spine_coder_core.cpython-312.pyc CHANGED
Binary files a/spine_coder/spine_coder/__pycache__/spine_coder_core.cpython-312.pyc and b/spine_coder/spine_coder/__pycache__/spine_coder_core.cpython-312.pyc differ
 
spine_coder/spine_coder/spine_coder_core.py CHANGED
@@ -19,6 +19,7 @@ def _level_sort_key(lv: str) -> Tuple[int, int]:
19
 
20
  def _extract_levels(t: str) -> List[str]:
21
  levels = set()
 
22
  for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", t, flags=re.I):
23
  pfx1, n1, pfx2, n2 = m.group(1).upper(), int(m.group(2)), (m.group(3) or m.group(1)).upper(), int(m.group(4))
24
  if pfx1 != pfx2:
@@ -26,6 +27,7 @@ def _extract_levels(t: str) -> List[str]:
26
  else:
27
  lo, hi = sorted([n1, n2])
28
  for k in range(lo, hi+1): levels.add(f"{pfx1}{k}")
 
29
  for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\b", t, flags=re.I):
30
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
31
  return sorted(levels, key=_level_sort_key)
@@ -40,7 +42,9 @@ def _count_interspaces(levels: List[str]) -> int:
40
  for arr in by_band.values():
41
  if len(arr) >= 2:
42
  inters += sum(1 for i in range(1, len(arr)) if arr[i]-arr[i-1]==1)
 
43
  if "L5" in levels and "S1" in levels: inters += 1
 
44
  if inters == 0:
45
  for arr in by_band.values():
46
  if len(arr) >= 2: inters = 1; break
@@ -61,7 +65,7 @@ def _laterality_mods(t: str) -> List[str]:
61
  mods: List[str] = []
62
  if _has(t, r"\bleft\b|left-sided|left side|leftward|hemilam\w* left|foraminotomy left|\blt\b"):
63
  mods.append("LT")
64
- # tighten RIGHT: drop bare \brt\b to avoid accidental hits
65
  if _has(t, r"\bright\b|right-sided|right side|rightward|hemilam\w* right|foraminotomy right"):
66
  mods.append("RT")
67
  return list(sorted(set(mods)))
@@ -82,6 +86,17 @@ def _inst_code_by_span(span_segments: int, anterior: bool) -> str:
82
  else:
83
  return "22842" if span_segments <= 3 else ("22843" if span_segments <= 7 else "22844")
84
 
 
 
 
 
 
 
 
 
 
 
 
85
  # ---------- main ----------
86
  def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
87
  t = _norm(note)
@@ -93,13 +108,22 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
93
  span = _span_segments(levels)
94
  lat_mods = _laterality_mods(t)
95
 
96
- # ---------- Region ----------
97
  region = "unknown"
98
- if any(l.startswith("C") for l in levels) or _has(t, r"\bcervic"): region = "cervical"
99
- elif any(l.startswith("T") for l in levels) or _has(t, r"\bthorac"): region = "thoracic"
100
- elif any(l.startswith(("L","S")) for l in levels) or _has(t, r"\blumbar|sacrum"): region = "lumbar"
101
-
102
- # Helper flags
 
 
 
 
 
 
 
 
 
103
  mentions_plate_ant = _has(t, r"\b(anterior (plate|instrument(ation)?)|plate fixed|plating)\b") or _has(t, r"\bplate\b")
104
  mentions_pedicle = _has(t, r"\bpedicle screw|pedicle-screw|pedicle fixation|s2ai\b")
105
  mentions_rod = _has(t, r"\brod(s)?\b")
@@ -211,7 +235,7 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
211
  "Pulse generator implanted.", "neurostimulator", False, 0.84)
212
  avoid_decompression_for_stimulator = True
213
 
214
- # ---------- (7) Decompression (guarded: no history/corpectomy/revision/stimulator) ----------
215
  if (not no_decompression_due_to_revision) and (not avoid_decompression_for_stimulator):
216
  if _has(t, r"\bdecompression(s)?\b|laminectom(y|ies)|laminotom(y|ies)|foraminotom(y|ies)|foraminal decompression(s)?|neuroforamen|lateral recess|central stenosis") \
217
  and not _has(t, r"\b(prior|previous|history of|s/?p)\s+(decompression|laminectomy|laminotomy|foraminotomy)\b"):
@@ -244,7 +268,6 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
244
  if _has(t, r"\bcorpectomy\b") and (region == "cervical" or _has(t, r"\bcervic")):
245
  _add(suggestions, "63081", "Cervical corpectomy for decompression, first segment",
246
  "Cervical corpectomy documented.", "tumor/corpectomy", True, 0.86)
247
- # rough extra estimate
248
  if _has(t, r"additional (level|segment)|two levels|three levels|multi|c\d-?c\d"):
249
  extra = max(1, inters or 1)
250
  _add(suggestions, "63082", f"Each additional cervical segment ×{extra}",
@@ -270,7 +293,6 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
270
  _add(suggestions, base, "Posterior column osteotomy (Smith-Petersen), first level",
271
  "Deformity correction with SPO documented.", "deformity", True, 0.83)
272
  if _has(t, r"additional (level|segment)|two levels|three levels|multi"):
273
- # For simplicity keep base+1 add-on mapping
274
  _add(suggestions, str(int(base)+1), "Each additional vertebral segment ×1+",
275
  "Multi-level osteotomy.", "deformity add-on", False, 0.78)
276
 
@@ -336,7 +358,7 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
336
  for k, pat in mod_map.items():
337
  if _has(t, pat): case_modifiers.append({"modifier": k, "reason": reasons[k]})
338
 
339
- # EXPLICIT_BILATERAL_DECOMP: If 'bilateral' appears near decompression nouns, force 50 if not already set
340
  if not any(m["modifier"]=="50" for m in case_modifiers):
341
  if _has(t, r"\bbilateral\b") and _has(t, r"(foraminotom(y|ies)|facetectom(y|ies)|laminectom(y|ies)|laminotom(y|ies))"):
342
  case_modifiers.append({"modifier":"50","reason": reasons["50"]})
@@ -388,6 +410,19 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
388
  out.sort(key=lambda r: (not r.get("primary", False), -(r.get("score", r.get("confidence",0.0))), _cpt_num(r)))
389
  if isinstance(top_k, int) and top_k > 0: out = out[:top_k]
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  return {
392
  "payer": payer,
393
  "region": region,
@@ -395,5 +430,10 @@ def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10
395
  "interspaces_est": inters,
396
  "span_segments_est": span,
397
  "suggestions": out,
398
- "case_modifiers": case_modifiers
 
 
 
 
 
399
  }
 
19
 
20
  def _extract_levels(t: str) -> List[str]:
21
  levels = set()
22
+ # Ranges like C4–C7 (en/em dashes) or hyphen
23
  for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", t, flags=re.I):
24
  pfx1, n1, pfx2, n2 = m.group(1).upper(), int(m.group(2)), (m.group(3) or m.group(1)).upper(), int(m.group(4))
25
  if pfx1 != pfx2:
 
27
  else:
28
  lo, hi = sorted([n1, n2])
29
  for k in range(lo, hi+1): levels.add(f"{pfx1}{k}")
30
+ # Singles like C5, L4, S1
31
  for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\b", t, flags=re.I):
32
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
33
  return sorted(levels, key=_level_sort_key)
 
42
  for arr in by_band.values():
43
  if len(arr) >= 2:
44
  inters += sum(1 for i in range(1, len(arr)) if arr[i]-arr[i-1]==1)
45
+ # Special-case L5–S1 interspace if both present
46
  if "L5" in levels and "S1" in levels: inters += 1
47
+ # Fallback: if multiple segments but no explicit adjacency caught, assume at least 1 interspace
48
  if inters == 0:
49
  for arr in by_band.values():
50
  if len(arr) >= 2: inters = 1; break
 
65
  mods: List[str] = []
66
  if _has(t, r"\bleft\b|left-sided|left side|leftward|hemilam\w* left|foraminotomy left|\blt\b"):
67
  mods.append("LT")
68
+ # RIGHT: avoid bare \brt\b to reduce false positives
69
  if _has(t, r"\bright\b|right-sided|right side|rightward|hemilam\w* right|foraminotomy right"):
70
  mods.append("RT")
71
  return list(sorted(set(mods)))
 
86
  else:
87
  return "22842" if span_segments <= 3 else ("22843" if span_segments <= 7 else "22844")
88
 
89
+ def tech_flags(note: str) -> dict:
90
+ """Surface microscope, navigation, neuromonitoring, and fluoro flags."""
91
+ t = _norm(note)
92
+ flags = {
93
+ "microscope": _has(t, r"\bmicroscope\b|operating microscope"),
94
+ "nav": _has(t, r"\bnavigation\b|o-?arm|computer[- ]?assisted"),
95
+ "io_monitor": _has(t, r"\bneuromonitor\w*|\b(ssep|meps?)\b|intraoperative monitoring"),
96
+ "fluoro": _has(t, r"\bfluoro\w*|\bc-arm\b"),
97
+ }
98
+ return {"flags": flags}
99
+
100
  # ---------- main ----------
101
  def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
102
  t = _norm(note)
 
108
  span = _span_segments(levels)
109
  lat_mods = _laterality_mods(t)
110
 
111
+ # ---------- Region (with transitional cervicothoracic first) ----------
112
  region = "unknown"
113
+ lvlset = set(levels)
114
+ # Transitional zone FIRST: cervicothoracic (e.g., C7–T2)
115
+ if _has(t, r"\bcervico[- ]?thoracic\b") \
116
+ or _has(t, r"\bc7\s*[-–]\s*t[1-3]\b") \
117
+ or ("C7" in lvlset and any(x in lvlset for x in ("T1","T2","T3"))):
118
+ region = "cervicothoracic"
119
+ elif any(l.startswith("C") for l in levels) or _has(t, r"\bcervic"):
120
+ region = "cervical"
121
+ elif any(l.startswith("T") for l in levels) or _has(t, r"\bthorac"):
122
+ region = "thoracic"
123
+ elif any(l.startswith(("L","S")) for l in levels) or _has(t, r"\blumbar|sacrum"):
124
+ region = "lumbar"
125
+
126
+ # Helper flags for instrumentation
127
  mentions_plate_ant = _has(t, r"\b(anterior (plate|instrument(ation)?)|plate fixed|plating)\b") or _has(t, r"\bplate\b")
128
  mentions_pedicle = _has(t, r"\bpedicle screw|pedicle-screw|pedicle fixation|s2ai\b")
129
  mentions_rod = _has(t, r"\brod(s)?\b")
 
235
  "Pulse generator implanted.", "neurostimulator", False, 0.84)
236
  avoid_decompression_for_stimulator = True
237
 
238
+ # ---------- (7) Decompression (guarded) ----------
239
  if (not no_decompression_due_to_revision) and (not avoid_decompression_for_stimulator):
240
  if _has(t, r"\bdecompression(s)?\b|laminectom(y|ies)|laminotom(y|ies)|foraminotom(y|ies)|foraminal decompression(s)?|neuroforamen|lateral recess|central stenosis") \
241
  and not _has(t, r"\b(prior|previous|history of|s/?p)\s+(decompression|laminectomy|laminotomy|foraminotomy)\b"):
 
268
  if _has(t, r"\bcorpectomy\b") and (region == "cervical" or _has(t, r"\bcervic")):
269
  _add(suggestions, "63081", "Cervical corpectomy for decompression, first segment",
270
  "Cervical corpectomy documented.", "tumor/corpectomy", True, 0.86)
 
271
  if _has(t, r"additional (level|segment)|two levels|three levels|multi|c\d-?c\d"):
272
  extra = max(1, inters or 1)
273
  _add(suggestions, "63082", f"Each additional cervical segment ×{extra}",
 
293
  _add(suggestions, base, "Posterior column osteotomy (Smith-Petersen), first level",
294
  "Deformity correction with SPO documented.", "deformity", True, 0.83)
295
  if _has(t, r"additional (level|segment)|two levels|three levels|multi"):
 
296
  _add(suggestions, str(int(base)+1), "Each additional vertebral segment ×1+",
297
  "Multi-level osteotomy.", "deformity add-on", False, 0.78)
298
 
 
358
  for k, pat in mod_map.items():
359
  if _has(t, pat): case_modifiers.append({"modifier": k, "reason": reasons[k]})
360
 
361
+ # EXPLICIT_BILATERAL_DECOMP
362
  if not any(m["modifier"]=="50" for m in case_modifiers):
363
  if _has(t, r"\bbilateral\b") and _has(t, r"(foraminotom(y|ies)|facetectom(y|ies)|laminectom(y|ies)|laminotom(y|ies))"):
364
  case_modifiers.append({"modifier":"50","reason": reasons["50"]})
 
410
  out.sort(key=lambda r: (not r.get("primary", False), -(r.get("score", r.get("confidence",0.0))), _cpt_num(r)))
411
  if isinstance(top_k, int) and top_k > 0: out = out[:top_k]
412
 
413
+ # Tech flags
414
+ _tflags = tech_flags(note).get("flags", {})
415
+ _tflag_list = [k for k, v in _tflags.items() if v]
416
+
417
+ # Case-level laterality (meta)
418
+ case_laterality = "na"
419
+ if _has(t, r"\bbilateral\b|both sides"):
420
+ case_laterality = "bilateral"
421
+ elif _has(t, r"\bleft[- ]sided|\bleft\b"):
422
+ case_laterality = "left"
423
+ elif _has(t, r"\bright[- ]sided|\bright\b"):
424
+ case_laterality = "right"
425
+
426
  return {
427
  "payer": payer,
428
  "region": region,
 
430
  "interspaces_est": inters,
431
  "span_segments_est": span,
432
  "suggestions": out,
433
+ "case_modifiers": case_modifiers,
434
+ "flags": _tflag_list, # compact list for UI
435
+ "flags_map": _tflags, # full boolean map
436
+ "laterality": case_laterality,
437
+ "build": "FINAL-v2.0",
438
+ "mode": "standard",
439
  }
spine_coder/spine_coder/tests/__pycache__/conftest.cpython-312-pytest-8.4.2.pyc CHANGED
Binary files a/spine_coder/spine_coder/tests/__pycache__/conftest.cpython-312-pytest-8.4.2.pyc and b/spine_coder/spine_coder/tests/__pycache__/conftest.cpython-312-pytest-8.4.2.pyc differ
 
spine_coder/spine_coder/tests/__pycache__/test_conflicts.cpython-312-pytest-8.4.2.pyc CHANGED
Binary files a/spine_coder/spine_coder/tests/__pycache__/test_conflicts.cpython-312-pytest-8.4.2.pyc and b/spine_coder/spine_coder/tests/__pycache__/test_conflicts.cpython-312-pytest-8.4.2.pyc differ
 
spine_coder/spine_coder/tests/__pycache__/test_coverage_and_modifiers.cpython-312-pytest-8.4.2.pyc CHANGED
Binary files a/spine_coder/spine_coder/tests/__pycache__/test_coverage_and_modifiers.cpython-312-pytest-8.4.2.pyc and b/spine_coder/spine_coder/tests/__pycache__/test_coverage_and_modifiers.cpython-312-pytest-8.4.2.pyc differ
 
spine_coder/spine_coder/tests/__pycache__/test_dataset_basic.cpython-312-pytest-8.4.2.pyc CHANGED
Binary files a/spine_coder/spine_coder/tests/__pycache__/test_dataset_basic.cpython-312-pytest-8.4.2.pyc and b/spine_coder/spine_coder/tests/__pycache__/test_dataset_basic.cpython-312-pytest-8.4.2.pyc differ