Slaiwala commited on
Commit
867bd56
Β·
verified Β·
1 Parent(s): 5ee81f3

Update spine_coder/spine_coder_core.py

Browse files
Files changed (1) hide show
  1. spine_coder/spine_coder_core.py +83 -47
spine_coder/spine_coder_core.py CHANGED
@@ -1,5 +1,5 @@
1
  # spine_coder_core_pro.py
2
- # Vertebro FINAL-v2.2-PRO (Block 1/4)
3
  # Core utilities, text normalization, level parsing, region classification
4
 
5
  import re, json
@@ -18,17 +18,16 @@ def _norm(s: str) -> str:
18
  def _has(text: str, pat: str) -> bool:
19
  return re.search(pat, text, flags=re.I) is not None
20
 
21
-
22
  # ─────────────────────────────────────────────
23
  # LEVEL AND REGION LOGIC
24
  # ─────────────────────────────────────────────
25
- _ORDER = ["C", "T", "L", "S"]
26
  _MAXNUM = {"C": 7, "T": 12, "L": 5, "S": 5}
27
 
28
  def _level_sort_key(lv: str) -> Tuple[int, int]:
29
  band_rank = {"C": 100, "T": 200, "L": 300, "S": 400}
30
  band = band_rank.get(lv[0].upper(), 999)
31
- num = int(re.sub(r"\D", "", lv) or 0)
32
  return (band, num)
33
 
34
  _SPAN = re.compile(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", re.I)
@@ -36,7 +35,7 @@ _SINGLE = re.compile(r"\b([CTLS])\s?(\d{1,2})\b", re.I)
36
 
37
  def _expand_across_regions(p1: str, n1: int, p2: str, n2: int) -> List[str]:
38
  p1, p2 = p1.upper(), p2.upper()
39
- out, i, j, r, num = [], _ORDER.index(p1), _ORDER.index(p2), _ORDER.index(p1), n1
40
  while True:
41
  out.append(f"{_ORDER[r]}{num}")
42
  if _ORDER[r] == p2 and num == n2:
@@ -68,8 +67,7 @@ def _extract_levels(t: str) -> List[str]:
68
  return sorted(levels, key=_level_sort_key)
69
 
70
  def _count_interspaces(levels: List[str]) -> int:
71
- if not levels:
72
- return 0
73
  lv_sorted = sorted(set(levels), key=_level_sort_key)
74
  return max(0, len(lv_sorted) - 1)
75
 
@@ -87,9 +85,9 @@ def _classify_region(levels: List[str]) -> str:
87
  if s and not (c or t or l): return "sacral"
88
  return "mixed"
89
 
90
- # laterality/modifiers stub (we handle real modifiers later at case level)
91
  def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
92
- return "midline", [] # spine work is midline; real modifiers added by detector
93
 
94
  # Base keyword groups
95
  FUSION_KW = r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
@@ -100,8 +98,8 @@ AUTO_LOCAL_KW = r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\
100
  AUTO_SEP_KW = r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|iliac crest bone|icbg)\b"
101
 
102
  # spine_coder_core_pro.py
103
- # Vertebro FINAL-v2.2-PRO (Block 2/4)
104
- # CPT reasoning engine + specialty packs
105
 
106
  def _inst_code_by_span(span: int, anterior: bool) -> str:
107
  if span <= 1: return ""
@@ -109,6 +107,17 @@ def _inst_code_by_span(span: int, anterior: bool) -> str:
109
  return "22845" if span <= 3 else ("22846" if span <= 7 else "22847")
110
  return "22842" if span <= 3 else ("22843" if span <= 7 else "22844")
111
 
 
 
 
 
 
 
 
 
 
 
 
112
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
113
  t = _norm(note)
114
  out: List[Dict[str, Any]] = []
@@ -116,13 +125,21 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
116
  span = max(inters + 1, 2)
117
 
118
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
119
- out.append({"cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
120
- "confidence": round(conf, 2), "primary": primary})
 
 
121
 
122
- # Washout/I&D guard β€” prevents false fusion/instrumentation
123
  washout_only = _has(t, r"(washout|irrigation and debridement|i\&d)") and not _has(t, FUSION_KW)
124
 
125
- # 1) Decompression
 
 
 
 
 
 
126
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
127
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
128
  base = base_map.get(region, "63047")
@@ -132,12 +149,12 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
132
  add("63048", f"Each additional level Γ—{inters}",
133
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
134
 
135
- # 2) TLIF/PLIF (explicit or implicit via facetectomy + cage + screws)
136
  tlif_like = (
137
- _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion")
138
- or (_has(t, r"\bfacetectom(y|ies)\b|complete facetectomy|transforaminal")
139
- and _has(t, r"\b(interbody (cage|device|spacer)|peek (cage|spacer)|titanium (cage|spacer)|allograft spacer)\b")
140
- and _has(t, r"\bpedicle\s+screws?\b"))
141
  )
142
  if tlif_like and not washout_only and region in {"lumbar", "thoracic"}:
143
  add("22633", "Posterior/posterolateral + posterior interbody, single level",
@@ -152,7 +169,7 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
152
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
153
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.83)
154
 
155
- # 3) ALIF
156
  if _has(t, r"\balif\b|anterior lumbar interbody fusion") and not washout_only:
157
  add("22558", "Anterior lumbar interbody fusion, single interspace",
158
  "ALIF detected.", "ALIF", 0.9, True)
@@ -167,7 +184,7 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
167
  "22847":"Anterior instrumentation (8+ segments)"}[code]
168
  add(code, desc, "Anterior plate present; span estimated from levels.", "instrumentation", 0.8)
169
 
170
- # 4) Posterolateral/posterior fusion (no interbody)
171
  if _has(t, r"posterolateral\b.*\bfusion|posterior\b.*\bfusion|in situ\b.*\bfusion") \
172
  and not _has(t, r"\btlif\b|\bplif\b|posterior interbody") and not washout_only:
173
  base_map = {"cervical":"22600", "thoracic":"22610", "lumbar":"22612", "lumbosacral":"22612", "cervicothoracic":"22600"}
@@ -185,7 +202,7 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
185
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
186
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.8)
187
 
188
- # 5) Instrumentation w/o explicit fusion (posterior)
189
  if _has(t, INSTR_KW) and not washout_only:
190
  code = _inst_code_by_span(span, anterior=False)
191
  if code and not any(r["category"] == "instrumentation" for r in out):
@@ -194,12 +211,12 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
194
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
195
  add(code, desc, "Posterior instrumentation documented.", "instrumentation", 0.82)
196
 
197
- # 6) Navigation (if not separately captured by facility)
198
  if _has(t, NAV_KW):
199
  add("61783", "Intraoperative navigation (image-guided)",
200
  "Navigation terms detected (O-arm/3D spin/Stealth/7D).", "navigation", 0.82)
201
 
202
- # 7) Bone grafts
203
  if _has(t, AUTO_SEP_KW):
204
  add("20937", "Autograft (separate incision)", "Iliac crest or separate-site autograft.", "graft", 0.8)
205
  elif _has(t, AUTO_LOCAL_KW):
@@ -207,7 +224,7 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
207
  if _has(t, ALLO_KW):
208
  add("20930", "Allograft, morselized / DBM", "Allograft/DBM used.", "graft", 0.8)
209
 
210
- # 8) Hardware removal
211
  if _has(t, r"(remov(ed|al)|explant).*(instrument|hardware|plate|rod|screw)"):
212
  if _has(t, r"\banterior\b|acdf|plate"):
213
  add("22855", "Removal of anterior instrumentation", "Anterior hardware removal.", "hardware_removal", 0.84, True)
@@ -216,13 +233,19 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
216
 
217
  return out
218
 
 
 
 
 
219
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
220
  t = _norm(note)
221
  extra: List[Dict[str, Any]] = []
222
 
223
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
224
- extra.append({"cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
225
- "confidence": round(conf, 2), "primary": primary})
 
 
226
 
227
  # Tumor / corpectomy
228
  if _has(t, r"corpectomy|tumou?r|metastatic|metastasis|en bloc"):
@@ -264,7 +287,7 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
264
  return extra
265
 
266
  # spine_coder_core_pro.py
267
- # Vertebro FINAL-v2.2-PRO (Block 3/4)
268
  # Case-level modifiers & complications detector
269
 
270
  def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str, str]], Dict[str, bool], List[str]]:
@@ -273,36 +296,44 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
273
  complications_map: Dict[str, bool] = {}
274
  complications_list: List[str] = []
275
 
276
- # β€”β€”β€” Modifiers (-50 and -59 intentionally suppressed for spine billing) β€”β€”β€”
277
  mod_patterns = [
278
  ("22", r"(complex|technically (difficult|demanding)|difficult dissection|extensive adhesiolysis|"
279
  r"severe deformity|morbid obesity|revision (case|exposure)|re-?operative field|"
280
  r"dense scar tissue|prolonged (exposure|procedure)|ossified p(?:l|ll)\b)",
281
  "Increased procedural service (complexity)."),
282
- ("52", r"\b(partial|limited|reduced (service|extent))\b", "Reduced service performed."),
 
 
 
 
283
  ("53", r"\b(aborted|terminated|discontinued|stopped prior to completion)\b",
284
  "Procedure discontinued for patient safety."),
 
285
  ("62", r"\b(co[- ]?surgeon|two surgeons|co-surgeons)\b",
286
  "Two surgeons (co-surgeons) documented."),
 
287
  ("76", r"\brepeat(ed)? procedure\b.*\b(same (surgeon|physician))\b",
288
  "Repeat procedure/service by the same physician."),
289
  ("77", r"\brepeat(ed)? procedure\b.*\b(another|different) (surgeon|physician)\b",
290
  "Repeat procedure by another physician."),
291
- ("78", r"(\bunplanned return\b.*\b(operating|op)\s*room\b)"
292
- r"|(\breturn to (the )?(operating|op)\s*room\b.*\b(postoperative|global period)\b)",
 
 
293
  "Unplanned return to OR during postoperative period."),
294
- ("79", r"\bunrelated procedure\b.*\b(postoperative|global period)\b",
 
295
  "Unrelated procedure during postoperative period."),
296
- # -50, -59 suppressed to avoid noise in spine
297
  ]
298
  for code, pat, reason in mod_patterns:
299
  if _has(t, pat):
300
  modifiers.append({"modifier": code, "reason": reason})
301
 
302
- # Assistant logic: AS vs -80 (mutually exclusive); -82 supersedes -80
303
  has_pa_np = _has(t, r"\b(pa[- ]?c|physician assistant|pa-c|nurse practitioner|np|advanced practice provider|app)\b")
304
  has_md_do = _has(t, r"\b(dr\.?|m\.?d\.?|d\.?o\.?)\b") or _has(t, r"\bassistant surgeon\b")
305
- if _has(t, r"assistant\(s\):") or _has(t, r"\bassistant(s)?[:\-]"):
306
  has_md_do = True
307
  if has_pa_np:
308
  modifiers.append({"modifier": "AS", "reason": "Non-physician assistant at surgery."})
@@ -315,7 +346,12 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
315
  if any(m["modifier"] == "53" for m in modifiers):
316
  modifiers = [m for m in modifiers if m["modifier"] != "52"]
317
 
318
- # β€”β€”β€” Complications β€”β€”β€”
 
 
 
 
 
319
  comp_rules = [
320
  ("dural_tear_or_durotomy", r"\b(dural tear|durotom(y|ies)|csf leak|cerebrospinal fluid leak)\b"),
321
  ("neuromonitoring_change", r"(loss|significant (decrease|change)).*(ssep|mep|meps|tcem|neuromonitor)"),
@@ -329,7 +365,7 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
329
  ("positioning_injury", r"\b(pressure (injury|ulcer)|brachial plexus|ulnar neuropathy|peroneal neuropathy)\b"),
330
  ("transfusion", r"\btransfus(ed|ion)|prbc\b|\bcell saver\b"),
331
  ("massive_blood_loss", r"\bebl\b.*\b(> ?800\s?ml|>\s?1(\.|,)?0?00\s?ml|> ?1\s?l|> ?1000\s?cc)\b"),
332
- ("unplanned_return_to_or", r"\bunplanned return\b.*\b(operating|op)\s*room\b"),
333
  ("wound_dehiscence", r"\bwound dehiscence\b|\bdehisced\b"),
334
  ]
335
  for key, pat in comp_rules:
@@ -338,7 +374,7 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
338
  if hit:
339
  complications_list.append(key.replace("_", " ").title())
340
 
341
- # Negation in IONM lines
342
  if _has(t, r"no significant changes in (motor|sensory) evoked potentials|neuromonitoring.*no (significant )?changes"):
343
  complications_map["neuromonitoring_change"] = False
344
  complications_list = [c for c in complications_list if c != "Neuromonitoring Change"]
@@ -346,7 +382,7 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
346
  return modifiers, complications_map, complications_list
347
 
348
  # spine_coder_core_pro.py
349
- # Vertebro FINAL-v2.2-PRO (Block 4/4)
350
  # Output builder & main entrypoint with top_k + backward-compat alias
351
 
352
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
@@ -354,7 +390,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
354
  t = _norm(note)
355
  levels = _extract_levels(t)
356
  region = _classify_region(levels)
357
- inters = _count_interspaces(levels)
358
  laterality, mods_stub = _laterality_and_modifiers(t)
359
 
360
  # Inference
@@ -362,7 +398,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
362
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
363
  rows = base_rows + extra_rows
364
 
365
- # Merge duplicates (max confidence, concat rationale)
366
  merged: Dict[tuple, Dict[str, Any]] = {}
367
  for r in rows:
368
  key = (r["cpt"], r["category"])
@@ -383,7 +419,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
383
  }
384
  tech_flags = [k for k, v in flags_map.items() if v]
385
 
386
- # Attach tech info to primary rows
387
  if tech_flags:
388
  tech_txt = f" (Tech: {', '.join(tech_flags)})"
389
  for r in rows:
@@ -406,7 +442,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
406
  if r.get("modifiers"):
407
  r["modifiers"] = [m for m in r["modifiers"] if m != "53"]
408
 
409
- # Top-K cap
410
  try:
411
  k = int(top_k)
412
  if k > 0:
@@ -436,12 +472,12 @@ suggest_with_cpt_billing = vertebro_infer
436
  # Quick CLI test
437
  if __name__ == "__main__":
438
  sample = """
439
- Assistant(s): Dr. Amber Parker
440
  Preop Dx: C3–C6 stenosis with myelopathy.
441
  Procedure:
442
  1. C3–C6 laminectomy and medial facetectomies.
443
  2. C2–T1 posterolateral arthrodesis; posterior instrumentation C2–T1.
444
  3. Navigation and fluoroscopy utilized; neuromonitoring with no significant changes.
445
- EBL 1200 mL. Dural tear identified and repaired primarily.
446
  """
447
  print(json.dumps(vertebro_infer(sample, top_k=12), indent=2))
 
1
  # spine_coder_core_pro.py
2
+ # Vertebro FINAL-v2.2-PRO (Block 1/5)
3
  # Core utilities, text normalization, level parsing, region classification
4
 
5
  import re, json
 
18
  def _has(text: str, pat: str) -> bool:
19
  return re.search(pat, text, flags=re.I) is not None
20
 
 
21
  # ─────────────────────────────────────────────
22
  # LEVEL AND REGION LOGIC
23
  # ─────────────────────────────────────────────
24
+ _ORDER = ["C", "T", "L", "S"]
25
  _MAXNUM = {"C": 7, "T": 12, "L": 5, "S": 5}
26
 
27
  def _level_sort_key(lv: str) -> Tuple[int, int]:
28
  band_rank = {"C": 100, "T": 200, "L": 300, "S": 400}
29
  band = band_rank.get(lv[0].upper(), 999)
30
+ num = int(re.sub(r"\D", "", lv) or 0)
31
  return (band, num)
32
 
33
  _SPAN = re.compile(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", re.I)
 
35
 
36
  def _expand_across_regions(p1: str, n1: int, p2: str, n2: int) -> List[str]:
37
  p1, p2 = p1.upper(), p2.upper()
38
+ out, r, num = [], _ORDER.index(p1), n1
39
  while True:
40
  out.append(f"{_ORDER[r]}{num}")
41
  if _ORDER[r] == p2 and num == n2:
 
67
  return sorted(levels, key=_level_sort_key)
68
 
69
  def _count_interspaces(levels: List[str]) -> int:
70
+ if not levels: return 0
 
71
  lv_sorted = sorted(set(levels), key=_level_sort_key)
72
  return max(0, len(lv_sorted) - 1)
73
 
 
85
  if s and not (c or t or l): return "sacral"
86
  return "mixed"
87
 
88
+ # laterality/modifiers stub (real modifiers are detected case-level)
89
  def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
90
+ return "midline", []
91
 
92
  # Base keyword groups
93
  FUSION_KW = r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
 
98
  AUTO_SEP_KW = r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|iliac crest bone|icbg)\b"
99
 
100
  # spine_coder_core_pro.py
101
+ # Vertebro FINAL-v2.2-PRO (Block 2/5)
102
+ # CPT reasoning engine (decompression, TLIF/PLIF, ALIF, fusion, instrumentation, grafts, nav, removal)
103
 
104
  def _inst_code_by_span(span: int, anterior: bool) -> str:
105
  if span <= 1: return ""
 
107
  return "22845" if span <= 3 else ("22846" if span <= 7 else "22847")
108
  return "22842" if span <= 3 else ("22843" if span <= 7 else "22844")
109
 
110
+ def _infer_id_depth_code(t: str) -> str:
111
+ """
112
+ Heuristic for I&D depth CPT:
113
+ - 11044: bone
114
+ - 11043: fascia/muscle
115
+ - 11042: skin/subcut or unspecified
116
+ """
117
+ if _has(t, r"\b(bone|osteomyelitis|to bone|down to bone)\b"): return "11044"
118
+ if _has(t, r"\b(fascia|fascial|muscle|muscular|deep fascial)\b"): return "11043"
119
+ return "11042"
120
+
121
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
122
  t = _norm(note)
123
  out: List[Dict[str, Any]] = []
 
125
  span = max(inters + 1, 2)
126
 
127
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
128
+ out.append({
129
+ "cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
130
+ "confidence": round(conf, 2), "primary": primary
131
+ })
132
 
133
+ # Washout/I&D guard β€” prevents false fusion/instrumentation on washout only
134
  washout_only = _has(t, r"(washout|irrigation and debridement|i\&d)") and not _has(t, FUSION_KW)
135
 
136
+ # (A) I&D / Washout CPTs (always add if present)
137
+ if _has(t, r"\b(washout|irrigation and debridement|i\&d|incision and debridement|debridement)\b"):
138
+ id_code = _infer_id_depth_code(t)
139
+ add(id_code, "Irrigation & Debridement (depth-based)",
140
+ "Infection/washout documented.", "washout", 0.86, True)
141
+
142
+ # (B) Decompression
143
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
144
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
145
  base = base_map.get(region, "63047")
 
149
  add("63048", f"Each additional level Γ—{inters}",
150
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
151
 
152
+ # (C) TLIF/PLIF (explicit or implicit)
153
  tlif_like = (
154
+ _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion") or
155
+ (_has(t, r"\bfacetectom(y|ies)\b|complete facetectomy|transforaminal") and
156
+ _has(t, r"\b(interbody (cage|device|spacer)|peek (cage|spacer)|titanium (cage|spacer)|allograft spacer)\b") and
157
+ _has(t, r"\bpedicle\s+screws?\b"))
158
  )
159
  if tlif_like and not washout_only and region in {"lumbar", "thoracic"}:
160
  add("22633", "Posterior/posterolateral + posterior interbody, single level",
 
169
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
170
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.83)
171
 
172
+ # (D) ALIF
173
  if _has(t, r"\balif\b|anterior lumbar interbody fusion") and not washout_only:
174
  add("22558", "Anterior lumbar interbody fusion, single interspace",
175
  "ALIF detected.", "ALIF", 0.9, True)
 
184
  "22847":"Anterior instrumentation (8+ segments)"}[code]
185
  add(code, desc, "Anterior plate present; span estimated from levels.", "instrumentation", 0.8)
186
 
187
+ # (E) Posterolateral/posterior fusion (no interbody)
188
  if _has(t, r"posterolateral\b.*\bfusion|posterior\b.*\bfusion|in situ\b.*\bfusion") \
189
  and not _has(t, r"\btlif\b|\bplif\b|posterior interbody") and not washout_only:
190
  base_map = {"cervical":"22600", "thoracic":"22610", "lumbar":"22612", "lumbosacral":"22612", "cervicothoracic":"22600"}
 
202
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
203
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.8)
204
 
205
+ # (F) Instrumentation without explicit fusion (posterior)
206
  if _has(t, INSTR_KW) and not washout_only:
207
  code = _inst_code_by_span(span, anterior=False)
208
  if code and not any(r["category"] == "instrumentation" for r in out):
 
211
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
212
  add(code, desc, "Posterior instrumentation documented.", "instrumentation", 0.82)
213
 
214
+ # (G) Navigation
215
  if _has(t, NAV_KW):
216
  add("61783", "Intraoperative navigation (image-guided)",
217
  "Navigation terms detected (O-arm/3D spin/Stealth/7D).", "navigation", 0.82)
218
 
219
+ # (H) Bone grafts
220
  if _has(t, AUTO_SEP_KW):
221
  add("20937", "Autograft (separate incision)", "Iliac crest or separate-site autograft.", "graft", 0.8)
222
  elif _has(t, AUTO_LOCAL_KW):
 
224
  if _has(t, ALLO_KW):
225
  add("20930", "Allograft, morselized / DBM", "Allograft/DBM used.", "graft", 0.8)
226
 
227
+ # (I) Hardware removal
228
  if _has(t, r"(remov(ed|al)|explant).*(instrument|hardware|plate|rod|screw)"):
229
  if _has(t, r"\banterior\b|acdf|plate"):
230
  add("22855", "Removal of anterior instrumentation", "Anterior hardware removal.", "hardware_removal", 0.84, True)
 
233
 
234
  return out
235
 
236
+ # spine_coder_core_pro.py
237
+ # Vertebro FINAL-v2.2-PRO (Block 3/5)
238
+ # Specialty packs layered onto core CPT logic
239
+
240
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
241
  t = _norm(note)
242
  extra: List[Dict[str, Any]] = []
243
 
244
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
245
+ extra.append({
246
+ "cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
247
+ "confidence": round(conf, 2), "primary": primary
248
+ })
249
 
250
  # Tumor / corpectomy
251
  if _has(t, r"corpectomy|tumou?r|metastatic|metastasis|en bloc"):
 
287
  return extra
288
 
289
  # spine_coder_core_pro.py
290
+ # Vertebro FINAL-v2.2-PRO (Block 4/5)
291
  # Case-level modifiers & complications detector
292
 
293
  def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str, str]], Dict[str, bool], List[str]]:
 
296
  complications_map: Dict[str, bool] = {}
297
  complications_list: List[str] = []
298
 
299
+ # Modifiers (-50 / -59 intentionally suppressed for spine)
300
  mod_patterns = [
301
  ("22", r"(complex|technically (difficult|demanding)|difficult dissection|extensive adhesiolysis|"
302
  r"severe deformity|morbid obesity|revision (case|exposure)|re-?operative field|"
303
  r"dense scar tissue|prolonged (exposure|procedure)|ossified p(?:l|ll)\b)",
304
  "Increased procedural service (complexity)."),
305
+
306
+ # 52 β€” reduced service (guarded)
307
+ ("52", r"\b(partial|limited|reduced (service|extent))\b",
308
+ "Reduced service performed."),
309
+
310
  ("53", r"\b(aborted|terminated|discontinued|stopped prior to completion)\b",
311
  "Procedure discontinued for patient safety."),
312
+
313
  ("62", r"\b(co[- ]?surgeon|two surgeons|co-surgeons)\b",
314
  "Two surgeons (co-surgeons) documented."),
315
+
316
  ("76", r"\brepeat(ed)? procedure\b.*\b(same (surgeon|physician))\b",
317
  "Repeat procedure/service by the same physician."),
318
  ("77", r"\brepeat(ed)? procedure\b.*\b(another|different) (surgeon|physician)\b",
319
  "Repeat procedure by another physician."),
320
+
321
+ # 78 β€” broadened phrasing for Jason-style notes
322
+ ("78", r"(\btake[- ]?back\b|\bunplanned return\b).*\b(operating|op)\s*room\b"
323
+ r"|(\breturn to (the )?(operating|op)\s*room\b.*\b(postop|post[- ]?operative|global period)\b)",
324
  "Unplanned return to OR during postoperative period."),
325
+
326
+ ("79", r"\bunrelated procedure\b.*\b(postop|post[- ]?operative|global period)\b",
327
  "Unrelated procedure during postoperative period."),
 
328
  ]
329
  for code, pat, reason in mod_patterns:
330
  if _has(t, pat):
331
  modifiers.append({"modifier": code, "reason": reason})
332
 
333
+ # Assistant logic: AS vs -80 (mutually exclusive); -82 supersedes -80; PA/NP beats -80
334
  has_pa_np = _has(t, r"\b(pa[- ]?c|physician assistant|pa-c|nurse practitioner|np|advanced practice provider|app)\b")
335
  has_md_do = _has(t, r"\b(dr\.?|m\.?d\.?|d\.?o\.?)\b") or _has(t, r"\bassistant surgeon\b")
336
+ if _has(t, r"assistant\(s\):") or _has(t, r"\bassistant(s)?[:\-]"): # generic assistant section β†’ likely MD
337
  has_md_do = True
338
  if has_pa_np:
339
  modifiers.append({"modifier": "AS", "reason": "Non-physician assistant at surgery."})
 
346
  if any(m["modifier"] == "53" for m in modifiers):
347
  modifiers = [m for m in modifiers if m["modifier"] != "52"]
348
 
349
+ # Corpectomy-specific rule: suppress -52 if β€œpartial decompression” inside a corpectomy case
350
+ if any(m["modifier"] == "52" for m in modifiers):
351
+ if _has(t, r"\bcorpectomy\b") and _has(t, r"\bpartial\b.*\b(decompression|laminectomy|facetectomy)\b"):
352
+ modifiers = [m for m in modifiers if m["modifier"] != "52"]
353
+
354
+ # Complications
355
  comp_rules = [
356
  ("dural_tear_or_durotomy", r"\b(dural tear|durotom(y|ies)|csf leak|cerebrospinal fluid leak)\b"),
357
  ("neuromonitoring_change", r"(loss|significant (decrease|change)).*(ssep|mep|meps|tcem|neuromonitor)"),
 
365
  ("positioning_injury", r"\b(pressure (injury|ulcer)|brachial plexus|ulnar neuropathy|peroneal neuropathy)\b"),
366
  ("transfusion", r"\btransfus(ed|ion)|prbc\b|\bcell saver\b"),
367
  ("massive_blood_loss", r"\bebl\b.*\b(> ?800\s?ml|>\s?1(\.|,)?0?00\s?ml|> ?1\s?l|> ?1000\s?cc)\b"),
368
+ ("unplanned_return_to_or", r"\b(take[- ]?back|unplanned return)\b.*\b(operating|op)\s*room\b"),
369
  ("wound_dehiscence", r"\bwound dehiscence\b|\bdehisced\b"),
370
  ]
371
  for key, pat in comp_rules:
 
374
  if hit:
375
  complications_list.append(key.replace("_", " ").title())
376
 
377
+ # Negation in IONM (β€œno significant changes”) β†’ clear flag
378
  if _has(t, r"no significant changes in (motor|sensory) evoked potentials|neuromonitoring.*no (significant )?changes"):
379
  complications_map["neuromonitoring_change"] = False
380
  complications_list = [c for c in complications_list if c != "Neuromonitoring Change"]
 
382
  return modifiers, complications_map, complications_list
383
 
384
  # spine_coder_core_pro.py
385
+ # Vertebro FINAL-v2.2-PRO (Block 5/5)
386
  # Output builder & main entrypoint with top_k + backward-compat alias
387
 
388
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
 
390
  t = _norm(note)
391
  levels = _extract_levels(t)
392
  region = _classify_region(levels)
393
+ inters = _count_interspaces(levels)
394
  laterality, mods_stub = _laterality_and_modifiers(t)
395
 
396
  # Inference
 
398
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
399
  rows = base_rows + extra_rows
400
 
401
+ # De-dup: keep max confidence, concat rationale
402
  merged: Dict[tuple, Dict[str, Any]] = {}
403
  for r in rows:
404
  key = (r["cpt"], r["category"])
 
419
  }
420
  tech_flags = [k for k, v in flags_map.items() if v]
421
 
422
+ # Attach tech info to primary row(s)
423
  if tech_flags:
424
  tech_txt = f" (Tech: {', '.join(tech_flags)})"
425
  for r in rows:
 
442
  if r.get("modifiers"):
443
  r["modifiers"] = [m for m in r["modifiers"] if m != "53"]
444
 
445
+ # Top-K cap (HF UI)
446
  try:
447
  k = int(top_k)
448
  if k > 0:
 
472
  # Quick CLI test
473
  if __name__ == "__main__":
474
  sample = """
475
+ Assistant(s): Dr. Amber Parker, PA-C
476
  Preop Dx: C3–C6 stenosis with myelopathy.
477
  Procedure:
478
  1. C3–C6 laminectomy and medial facetectomies.
479
  2. C2–T1 posterolateral arthrodesis; posterior instrumentation C2–T1.
480
  3. Navigation and fluoroscopy utilized; neuromonitoring with no significant changes.
481
+ EBL 1200 mL. Dural tear identified and repaired primarily. Unplanned return to operating room noted.
482
  """
483
  print(json.dumps(vertebro_infer(sample, top_k=12), indent=2))