Slaiwala commited on
Commit
b772f7f
Β·
verified Β·
1 Parent(s): 8ebc3f0

Update spine_coder/spine_coder_core.py

Browse files
Files changed (1) hide show
  1. spine_coder/spine_coder_core.py +281 -187
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/5)
3
  # Core utilities, text normalization, level parsing, region classification
4
 
5
  import re, json
@@ -21,13 +21,13 @@ def _has(text: str, pat: str) -> bool:
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)
@@ -44,8 +44,7 @@ def _expand_across_regions(p1: str, n1: int, p2: str, n2: int) -> List[str]:
44
  num += 1
45
  else:
46
  r += 1
47
- if r >= len(_ORDER):
48
- break
49
  num = 1
50
  return out
51
 
@@ -57,11 +56,9 @@ def _extract_levels(t: str) -> List[str]:
57
  p2 = (m.group(3) or p1).upper()
58
  n2 = int(m.group(4))
59
  if p1 == p2:
60
- for k in range(min(n1, n2), max(n1, n2) + 1):
61
- levels.add(f"{p1}{k}")
62
  else:
63
- for lv in _expand_across_regions(p1, n1, p2, n2):
64
- levels.add(lv)
65
  for m in _SINGLE.finditer(t):
66
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
67
  return sorted(levels, key=_level_sort_key)
@@ -85,11 +82,13 @@ def _classify_region(levels: List[str]) -> str:
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"
94
  INSTR_KW = r"\b(pedicle\s+screws?|lateral\s+mass\s+screws?|rods?|set\s+screws?|instrument(?:ed|ation))\b"
95
  NAV_KW = r"\b(navigation|navigated|o-?arm|ziehm|3d\s+spin|stealth|7d)\b"
@@ -97,9 +96,17 @@ ALLO_KW = r"\b(allograft|dbm|demineralized\s+bone\s+matrix)\b"
97
  AUTO_LOCAL_KW = r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\s+retained|morselized\s+autograft)\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, ACDF, TLIF/PLIF, ALIF, fusion, instrumentation, grafts, nav, removal, I&D, hematoma)
 
 
 
 
 
 
 
 
103
 
104
  def _inst_code_by_span(span: int, anterior: bool) -> str:
105
  if span <= 1: return ""
@@ -107,22 +114,6 @@ def _inst_code_by_span(span: int, anterior: bool) -> str:
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
- """Depth for I&D: 11044 bone, 11043 fascia/muscle, 11042 skin/subcut/unspecified."""
112
- if _has(t, r"\b(bone|osteomyelitis|to bone|down to bone)\b"): return "11044"
113
- if _has(t, r"\b(fascia|fascial|muscle|muscular|deep fascial)\b"): return "11043"
114
- return "11042"
115
-
116
- def _id_addon_units(t: str) -> int:
117
- """Rough heuristic for add-on debridement units (11045–11047)."""
118
- hits = 0
119
- if _has(t, r"\b(additional (area|areas)|second site|multiple (areas|sites)|extended debridement)\b"):
120
- hits += 1
121
- return hits
122
-
123
- def _is_neck_case(t: str) -> bool:
124
- return _has(t, r"\bcervic(?:al|o)|neck\b|acdf|smith[- ]?robinson")
125
-
126
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
127
  t = _norm(note)
128
  out: List[Dict[str, Any]] = []
@@ -130,41 +121,13 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
130
  span = max(inters + 1, 2)
131
 
132
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
133
- out.append({
134
- "cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
135
- "confidence": round(conf, 2), "primary": primary
136
- })
137
-
138
- # High-level guards
139
- washout_present = _has(t, r"\b(washout|irrigation and debridement|i\&d|incision and debridement|debridement)\b")
140
- washout_only = washout_present and not _has(t, FUSION_KW) and not _has(t, INSTR_KW)
141
-
142
- # (0) I&D / Washout CPTs (always code when present)
143
- if washout_present:
144
- id_code = _infer_id_depth_code(t)
145
- add(id_code, "Irrigation & Debridement (depth-based)",
146
- "Infection/washout documented.", "washout", 0.87, True)
147
- addons = _id_addon_units(t)
148
- if addons > 0:
149
- addon_map = {"11042":"11045", "11043":"11046", "11044":"11047"}
150
- add(addon_map[id_code], f"Debridement add-on Γ—{addons}",
151
- "Additional areas/sites debrided.", "washout add-on", 0.83)
152
-
153
- # (1) Post-op wound hematoma evacuation (non-I&D pathway)
154
- if _has(t, r"\b(hematoma|seroma)\b.*\b(evacuat|washout|drainage|incision)\b"):
155
- if _is_neck_case(t) or region.startswith("cervical"):
156
- add("21501", "Incision and drainage, deep neck (abscess/hematoma)",
157
- "Cervical/neck wound hematoma evacuation.", "evacuation", 0.84, True)
158
- else:
159
- add("10140", "Incision and drainage of hematoma/seroma, superficial",
160
- "Wound hematoma evacuation.", "evacuation", 0.82, True)
161
 
162
- # Short circuit on washout-only days for fusion/instrumentation/TLIF/ALIF
163
- if washout_only:
164
- # navigation, assistants, modifiers are still handled at case level; stop here
165
- return out
166
 
167
- # (2) Decompression
168
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
169
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
170
  base = base_map.get(region, "63047")
@@ -174,46 +137,14 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
174
  add("63048", f"Each additional level Γ—{inters}",
175
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
176
 
177
- # (3) ACDF (first interspace 22551 + additional 22552; plate β†’ 22845/6/7)
178
- acdf_hit = (
179
- _has(t, r"\bacdf\b|anterior cervical discectomy(?: and)? fusion|smith[- ]?robinson")
180
- or (region in {"cervical","cervicothoracic"} and _has(t, r"\bdiscectom\w+\b.*\b(arthrodesis|interbody|cage|plate)\b"))
181
- )
182
- if acdf_hit:
183
- # Count cervical interspaces from detected C-levels; fallback 1 if ambiguous
184
- c_levels = [lv for lv in levels if lv.startswith("C")]
185
- # naive interspace estimate: contiguous gaps among cervical levels
186
- c_inters = 0
187
- if len(c_levels) >= 2:
188
- nums = sorted(int(re.sub(r"\D","", x)) for x in c_levels)
189
- c_inters = sum(1 for i in range(1, len(nums)) if nums[i] - nums[i-1] == 1)
190
- if c_inters == 0: c_inters = 1
191
- else:
192
- c_inters = 1
193
- add("22551", "ACDF, first interspace (includes discectomy)",
194
- "Anterior cervical fusion pattern detected.", "ACDF", 0.95, True)
195
- if c_inters > 1:
196
- add("22552", f"ACDF, each additional interspace Γ—{c_inters-1}",
197
- "Multi-level ACDF.", "ACDF add-on", 0.90, False)
198
-
199
- if _has(t, r"\b(anterior (plate|plating)|plate fixed)\b|\bplate\b"):
200
- # plate span = interspaces + 1 segments (min 2)
201
- est_span = max(2, c_inters + 1)
202
- code = _inst_code_by_span(est_span, anterior=True)
203
- if code:
204
- desc = {"22845":"Anterior instrumentation (2–3 segments)",
205
- "22846":"Anterior instrumentation (4–7 segments)",
206
- "22847":"Anterior instrumentation (8+ segments)"}[code]
207
- add(code, desc, "Anterior plate present; span estimated from cervical levels.", "instrumentation", 0.82)
208
-
209
- # (4) TLIF/PLIF (explicit or implicit)
210
  tlif_like = (
211
- _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion") or
212
- (_has(t, r"\bfacetectom(y|ies)\b|complete facetectomy|transforaminal") and
213
- _has(t, r"\b(interbody (cage|device|spacer)|peek (cage|spacer)|titanium (cage|spacer)|allograft spacer)\b") and
214
- _has(t, r"\bpedicle\s+screws?\b"))
215
  )
216
- if tlif_like and region in {"lumbar", "thoracic"}:
217
  add("22633", "Posterior/posterolateral + posterior interbody, single level",
218
  "TLIF/PLIF pattern: interbody device + pedicle screws.", "TLIF/PLIF", 0.92, True)
219
  if inters > 0:
@@ -226,10 +157,10 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
226
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
227
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.83)
228
 
229
- # (5) ALIF
230
- if _has(t, r"\balif\b|anterior lumbar interbody fusion"):
231
  add("22558", "Anterior lumbar interbody fusion, single interspace",
232
- "ALIF detected.", "ALIF", 0.90, True)
233
  if inters > 0:
234
  add("22585", f"ALIF each additional interspace Γ—{inters}",
235
  "Multi-level ALIF.", "ALIF add-on", 0.86)
@@ -239,11 +170,11 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
239
  desc = {"22845":"Anterior instrumentation (2–3 segments)",
240
  "22846":"Anterior instrumentation (4–7 segments)",
241
  "22847":"Anterior instrumentation (8+ segments)"}[code]
242
- add(code, desc, "Anterior plate present; span estimated from levels.", "instrumentation", 0.80)
243
 
244
- # (6) Posterolateral/posterior fusion (no interbody)
245
  if _has(t, r"posterolateral\b.*\bfusion|posterior\b.*\bfusion|in situ\b.*\bfusion") \
246
- and not _has(t, r"\btlif\b|\bplif\b|posterior interbody"):
247
  base_map = {"cervical":"22600", "thoracic":"22610", "lumbar":"22612", "lumbosacral":"22612", "cervicothoracic":"22600"}
248
  base = base_map.get(region, "22612")
249
  add(base, f"Posterolateral/posterior fusion, first level ({region})",
@@ -257,10 +188,10 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
257
  desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
258
  "22843":"Posterior segmental instrumentation (4–7 segments)",
259
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
260
- add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.80)
261
 
262
- # (7) Instrumentation without explicit fusion (posterior)
263
- if _has(t, INSTR_KW):
264
  code = _inst_code_by_span(span, anterior=False)
265
  if code and not any(r["category"] == "instrumentation" for r in out):
266
  desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
@@ -268,20 +199,20 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
268
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
269
  add(code, desc, "Posterior instrumentation documented.", "instrumentation", 0.82)
270
 
271
- # (8) Navigation
272
  if _has(t, NAV_KW):
273
  add("61783", "Intraoperative navigation (image-guided)",
274
  "Navigation terms detected (O-arm/3D spin/Stealth/7D).", "navigation", 0.82)
275
 
276
- # (9) Bone grafts
277
  if _has(t, AUTO_SEP_KW):
278
- add("20937", "Autograft (separate incision)", "Iliac crest or separate-site autograft.", "graft", 0.80)
279
  elif _has(t, AUTO_LOCAL_KW):
280
- add("20936", "Autograft, local (same incision)", "Local autograft retained.", "graft", 0.80)
281
  if _has(t, ALLO_KW):
282
- add("20930", "Allograft, morselized / DBM", "Allograft/DBM used.", "graft", 0.80)
283
 
284
- # (10) Hardware removal
285
  if _has(t, r"(remov(ed|al)|explant).*(instrument|hardware|plate|rod|screw)"):
286
  if _has(t, r"\banterior\b|acdf|plate"):
287
  add("22855", "Removal of anterior instrumentation", "Anterior hardware removal.", "hardware_removal", 0.84, True)
@@ -290,21 +221,18 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
290
 
291
  return out
292
 
293
- # spine_coder_core_pro.py
294
- # Vertebro FINAL-v2.2-PRO+ (Block 3/5)
295
- # Specialty packs layered onto core CPT logic
296
-
297
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
298
  t = _norm(note)
299
  extra: List[Dict[str, Any]] = []
300
 
301
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
302
- extra.append({
303
- "cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
304
- "confidence": round(conf, 2), "primary": primary
305
- })
306
 
307
- # Tumor / corpectomy
308
  if _has(t, r"corpectomy|tumou?r|metastatic|metastasis|en bloc"):
309
  base = "63081" if region.startswith("cervical") or "cervico" in region else "63085"
310
  add(base, "Vertebral corpectomy, first segment", "Corpectomy/tumor resection.", "tumor/corpectomy", 0.88, True)
@@ -312,40 +240,71 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
312
  addon = "63082" if base == "63081" else "63086"
313
  add(addon, f"Each additional segment Γ—{inters}", "Multi-segment corpectomy.", "tumor/corpectomy add-on", 0.83)
314
 
315
- # Deformity / SPO
316
  if _has(t, r"\bspo\b|smith[- ]?petersen|posterior column osteotomy|osteotomy"):
317
  base = {"cervical": "22210", "thoracic": "22214", "lumbar": "22206"}.get(region, "22206")
318
  add(base, "Posterior column osteotomy, first level", "Deformity correction with SPO.", "deformity", 0.84, True)
319
  if inters > 0:
320
- add(str(int(base) + 1), f"Each additional level Γ—{inters}", "Multi-level osteotomy.", "deformity add-on", 0.80)
321
 
322
- # Stimulator
323
  if _has(t, r"stimulator|paddle lead|scs"):
324
  add("63655", "Laminectomy for implantation of neurostimulator paddle", "Paddle lead placement.", "stimulator", 0.86, True)
325
  if _has(t, r"\bipg\b|pulse generator|battery"):
326
  add("63685", "Insertion or replacement of IPG", "Pulse generator placed.", "stimulator add-on", 0.82)
327
 
328
- # Kyphoplasty / Vertebral augmentation
329
  if _has(t, r"kyphoplasty|vertebroplasty|cement"):
330
  base_map = {"cervical": "22510", "thoracic": "22513", "lumbar": "22514"}
331
  base = base_map.get(region, "22514")
332
  add(base, "Percutaneous vertebral augmentation, first level", "Kypho/vertebroplasty terms found.", "augmentation", 0.84, True)
333
  if inters > 0:
334
- add("22515", f"Each additional vertebral body Γ—{inters}", "Multi-level augmentation.", "augmentation add-on", 0.80)
335
 
336
- # Revision instrumentation
337
  if _has(t, r"revision of instrumentation|reinsertion|remove and replace|re-?insert"):
338
  add("22849", "Revision/reinsertion of spinal instrumentation", "Instrumentation revised/reinserted.", "revision", 0.84, True)
339
 
340
- # Pelvic fixation
341
  if _has(t, r"pelvic fixation|iliac bolt|iliac screw|s2ai"):
342
  add("22848", "Pelvic fixation (attach instrumentation to pelvis)", "Iliac/S2AI fixation.", "pelvic_fixation", 0.83)
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  return extra
345
 
346
- # spine_coder_core_pro.py
347
- # Vertebro FINAL-v2.2-PRO+ (Block 4/5)
348
- # Case-level modifiers & complications detector
349
 
350
  def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str, str]], Dict[str, bool], List[str]]:
351
  t = _norm(note)
@@ -353,7 +312,6 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
353
  complications_map: Dict[str, bool] = {}
354
  complications_list: List[str] = []
355
 
356
- # Modifiers (-50 / -59 intentionally suppressed for spine)
357
  mod_patterns = [
358
  ("22", r"(complex|technically (difficult|demanding)|difficult dissection|extensive adhesiolysis|"
359
  r"severe deformity|morbid obesity|revision (case|exposure)|re-?operative field|"
@@ -368,21 +326,20 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
368
  "Repeat procedure/service by the same physician."),
369
  ("77", r"\brepeat(ed)? procedure\b.*\b(another|different) (surgeon|physician)\b",
370
  "Repeat procedure by another physician."),
371
- ("78", r"(\btake[- ]?back\b|\bunplanned return\b).*\b(operating|op)\s*room\b"
372
- r"|(\breturn to (the )?(operating|op)\s*room\b.*\b(postop|post[- ]?operative|global period)\b)",
373
  "Unplanned return to OR during postoperative period."),
374
- ("79", r"\bunrelated procedure\b.*\b(postop|post[- ]?operative|global period)\b",
375
  "Unrelated procedure during postoperative period."),
376
  ]
377
  for code, pat, reason in mod_patterns:
378
  if _has(t, pat):
379
  modifiers.append({"modifier": code, "reason": reason})
380
 
381
- # Assistant logic: AS vs -80; -82 supersedes -80; PA/NP beats -80
382
  has_pa_np = _has(t, r"\b(pa[- ]?c|physician assistant|pa-c|nurse practitioner|np|advanced practice provider|app)\b")
383
  has_md_do = _has(t, r"\b(dr\.?|m\.?d\.?|d\.?o\.?)\b") or _has(t, r"\bassistant surgeon\b")
384
- if _has(t, r"assistant\(s\):") or _has(t, r"\bassistant(s)?[:\-]"):
385
- has_md_do = True
386
  if has_pa_np:
387
  modifiers.append({"modifier": "AS", "reason": "Non-physician assistant at surgery."})
388
  elif has_md_do:
@@ -394,11 +351,6 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
394
  if any(m["modifier"] == "53" for m in modifiers):
395
  modifiers = [m for m in modifiers if m["modifier"] != "52"]
396
 
397
- # Corpectomy-specific rule: suppress -52 if β€œpartial decompression” within corpectomy
398
- if any(m["modifier"] == "52" for m in modifiers):
399
- if _has(t, r"\bcorpectomy\b") and _has(t, r"\bpartial\b.*\b(decompression|laminectomy|facetectomy)\b"):
400
- modifiers = [m for m in modifiers if m["modifier"] != "52"]
401
-
402
  # Complications
403
  comp_rules = [
404
  ("dural_tear_or_durotomy", r"\b(dural tear|durotom(y|ies)|csf leak|cerebrospinal fluid leak)\b"),
@@ -413,24 +365,21 @@ def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str,
413
  ("positioning_injury", r"\b(pressure (injury|ulcer)|brachial plexus|ulnar neuropathy|peroneal neuropathy)\b"),
414
  ("transfusion", r"\btransfus(ed|ion)|prbc\b|\bcell saver\b"),
415
  ("massive_blood_loss", r"\bebl\b.*\b(> ?800\s?ml|>\s?1(\.|,)?0?00\s?ml|> ?1\s?l|> ?1000\s?cc)\b"),
416
- ("unplanned_return_to_or", r"\b(take[- ]?back|unplanned return)\b.*\b(operating|op)\s*room\b"),
417
  ("wound_dehiscence", r"\bwound dehiscence\b|\bdehisced\b"),
418
  ]
419
  for key, pat in comp_rules:
420
  hit = _has(t, pat)
421
  complications_map[key] = hit
422
- if hit:
423
- complications_list.append(key.replace("_", " ").title())
424
 
425
- # Negation in IONM (β€œno significant changes”) β†’ clear flag
426
  if _has(t, r"no significant changes in (motor|sensory) evoked potentials|neuromonitoring.*no (significant )?changes"):
427
  complications_map["neuromonitoring_change"] = False
428
  complications_list = [c for c in complications_list if c != "Neuromonitoring Change"]
429
 
430
  return modifiers, complications_map, complications_list
431
 
432
- # spine_coder_core_pro.py
433
- # Vertebro FINAL-v2.2-PRO+ (Block 5/5)
434
  # Output builder & main entrypoint with top_k + backward-compat alias
435
 
436
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
@@ -438,7 +387,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
438
  t = _norm(note)
439
  levels = _extract_levels(t)
440
  region = _classify_region(levels)
441
- inters = _count_interspaces(levels)
442
  laterality, mods_stub = _laterality_and_modifiers(t)
443
 
444
  # Inference
@@ -446,7 +395,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
446
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
447
  rows = base_rows + extra_rows
448
 
449
- # De-dup: keep max confidence, concat rationale
450
  merged: Dict[tuple, Dict[str, Any]] = {}
451
  for r in rows:
452
  key = (r["cpt"], r["category"])
@@ -456,6 +405,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
456
  merged[key]["confidence"] = round(max(merged[key]["confidence"], r["confidence"]), 2)
457
  merged[key]["rationale"] = (merged[key]["rationale"] + " / " + r["rationale"]).strip()
458
  rows = list(merged.values())
 
459
 
460
  # Tech flags
461
  flags_map = {
@@ -466,19 +416,18 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
466
  }
467
  tech_flags = [k for k, v in flags_map.items() if v]
468
 
 
 
 
 
 
 
 
469
  # Case-level modifiers & complications
470
  case_modifiers, complications_map, complications_list = _detect_case_modifiers_and_complications(t)
471
  if not case_modifiers and mods_stub:
472
  case_modifiers = [{"modifier": m, "reason": "Laterality-derived modifier."} for m in mods_stub]
473
 
474
- # Attach tech info & complications summary to the first primary row
475
- comp_txt = f" | Complications: {', '.join(complications_list)}" if complications_list else ""
476
- tech_txt = f" (Tech: {', '.join(tech_flags)})" if tech_flags else ""
477
- for r in rows:
478
- if r.get("primary", False):
479
- r["rationale"] = (r.get("rationale", "") + tech_txt + comp_txt).strip()
480
- break
481
-
482
  # Propagate -53 only to primary rows
483
  if any(m["modifier"] == "53" for m in case_modifiers):
484
  for r in rows:
@@ -490,23 +439,10 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
490
  if r.get("modifiers"):
491
  r["modifiers"] = [m for m in r["modifiers"] if m != "53"]
492
 
493
- # Category-weighted stable sort (primary first)
494
- cat_weight = {
495
- "ACDF": 10, "ALIF": 10, "TLIF/PLIF": 10, "posterior_fusion": 9, "decompression": 8,
496
- "ACDF add-on": 7, "ALIF add-on": 7, "TLIF/PLIF add-on": 7, "posterior_fusion add-on": 7, "decompression add-on": 7,
497
- "instrumentation": 6, "graft": 5, "navigation": 4, "hardware_removal": 4,
498
- "washout": 4, "washout add-on": 4, "evacuation": 4,
499
- "deformity": 3, "deformity add-on": 3, "tumor/corpectomy": 3, "tumor/corpectomy add-on": 3,
500
- "augmentation": 2, "augmentation add-on": 2, "pelvic_fixation": 2, "revision": 2
501
- }
502
- def _w(row): return (-1 if row.get("primary", False) else 0, -(cat_weight.get(row.get("category",""),1)), -row.get("confidence",0), row["cpt"])
503
- rows.sort(key=_w)
504
-
505
- # Top-K cap (HF UI)
506
  try:
507
  k = int(top_k)
508
- if k > 0:
509
- rows = rows[:k]
510
  except Exception:
511
  pass
512
 
@@ -522,21 +458,179 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
522
  "complications_flags": complications_map,
523
  "complications": complications_list,
524
  "suggestions": rows,
525
- "build": "FINAL-v2.2-PRO+",
526
  "mode": "standard",
527
  }
528
 
529
  # Backward compatibility alias
530
  suggest_with_cpt_billing = vertebro_infer
531
 
532
- # Quick CLI test
533
  if __name__ == "__main__":
534
  sample = """
535
- Assistant(s): PA-C at bedside.
536
- Preop Dx: C4–C6 radiculopathy with stenosis.
537
- Procedure: ACDF C4–C6 with anterior plating across C4–C6.
538
- Technique: Navigation, fluoroscopy; neuromonitoring with no significant changes.
539
- Hematoma evacuation not performed. No washout.
540
- Graft: Structural allograft interbody + DBM.
541
  """
542
  print(json.dumps(vertebro_infer(sample, top_k=15), indent=2))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # spine_coder_core_pro_elite.py
2
+ # Vertebro FINAL-v2.3-PRO-Elite (Block 1/4)
3
  # Core utilities, text normalization, level parsing, region classification
4
 
5
  import re, json
 
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)
 
44
  num += 1
45
  else:
46
  r += 1
47
+ if r >= len(_ORDER): break
 
48
  num = 1
49
  return out
50
 
 
56
  p2 = (m.group(3) or p1).upper()
57
  n2 = int(m.group(4))
58
  if p1 == p2:
59
+ for k in range(min(n1, n2), max(n1, n2) + 1): levels.add(f"{p1}{k}")
 
60
  else:
61
+ for lv in _expand_across_regions(p1, n1, p2, n2): levels.add(lv)
 
62
  for m in _SINGLE.finditer(t):
63
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
64
  return sorted(levels, key=_level_sort_key)
 
82
  if s and not (c or t or l): return "sacral"
83
  return "mixed"
84
 
85
+ # Laterality placeholder (case-level modifiers add real ones)
86
  def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
87
  return "midline", []
88
 
89
+ # ─────────────────────────────────────────────
90
+ # KEYWORD GROUPS (v2.3 adds VCR/CDA/Exposure terms)
91
+ # ─────────────────────────────────────────────
92
  FUSION_KW = r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
93
  INSTR_KW = r"\b(pedicle\s+screws?|lateral\s+mass\s+screws?|rods?|set\s+screws?|instrument(?:ed|ation))\b"
94
  NAV_KW = r"\b(navigation|navigated|o-?arm|ziehm|3d\s+spin|stealth|7d)\b"
 
96
  AUTO_LOCAL_KW = r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\s+retained|morselized\s+autograft)\b"
97
  AUTO_SEP_KW = r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|iliac crest bone|icbg)\b"
98
 
99
+ # New v2.3 detector phrases
100
+ ANTERIOR_KW = r"\banterior (approach|exposure|cervical|lumbar|thoracic)\b|\bacdf\b|\balif\b|\bsmith[- ]?robinson\b"
101
+ POSTERIOR_KW = r"\bposterior (approach|exposure)\b|\btlif\b|\bplif\b|\bposterolateral fusion\b|\bpedicle screws?\b|\brods?\b"
102
+ EXPOSURE_ONLY_KW= r"\b(exposure|approach)\b.*\b(no|without)\b.*\b(fusion|interbody|instrument|implant|cage|plate|screw)\b"
103
+ VCR_KW = r"\bvertebral column resection\b|\bVCR\b|\b3[- ]column resection\b"
104
+ INTRADURAL_KW = r"\bintradural\b|(intra[- ]?dural).*tumou?r|\bmeningioma\b|\bschwannoma\b|\bneurinoma\b|\bfilum\b"
105
+ CERVICAL_ARTHRO = r"\b(cervical (arthroplasty|disc replacement|artificial disc)|\bcda\b)\b"
106
+ LUMBAR_ARTHRO = r"\b(lumbar (arthroplasty|disc replacement)|\btdr\b)\b"
107
+
108
+ # Vertebro FINAL-v2.3-PRO-Elite (Block 2/4)
109
+ # CPT reasoning engine + specialty packs (extended)
110
 
111
  def _inst_code_by_span(span: int, anterior: bool) -> str:
112
  if span <= 1: return ""
 
114
  return "22845" if span <= 3 else ("22846" if span <= 7 else "22847")
115
  return "22842" if span <= 3 else ("22843" if span <= 7 else "22844")
116
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
118
  t = _norm(note)
119
  out: List[Dict[str, Any]] = []
 
121
  span = max(inters + 1, 2)
122
 
123
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
124
+ out.append({"cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
125
+ "confidence": round(conf, 2), "primary": primary})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
 
127
+ # Washout-only guard
128
+ washout_only = _has(t, r"(washout|irrigation and debridement|i\&d)") and not _has(t, FUSION_KW)
 
 
129
 
130
+ # 1) Decompression
131
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
132
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
133
  base = base_map.get(region, "63047")
 
137
  add("63048", f"Each additional level Γ—{inters}",
138
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
139
 
140
+ # 2) TLIF/PLIF (explicit or implicit)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  tlif_like = (
142
+ _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion")
143
+ or (_has(t, r"\bfacetectom(y|ies)\b|complete facetectomy|transforaminal")
144
+ and _has(t, r"\b(interbody (cage|device|spacer)|peek (cage|spacer)|titanium (cage|spacer)|allograft spacer)\b")
145
+ and _has(t, r"\bpedicle\s+screws?\b"))
146
  )
147
+ if tlif_like and not washout_only and region in {"lumbar", "thoracic"}:
148
  add("22633", "Posterior/posterolateral + posterior interbody, single level",
149
  "TLIF/PLIF pattern: interbody device + pedicle screws.", "TLIF/PLIF", 0.92, True)
150
  if inters > 0:
 
157
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
158
  add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.83)
159
 
160
+ # 3) ALIF (+ optional plate)
161
+ if _has(t, r"\balif\b|anterior lumbar interbody fusion") and not washout_only:
162
  add("22558", "Anterior lumbar interbody fusion, single interspace",
163
+ "ALIF detected.", "ALIF", 0.9, True)
164
  if inters > 0:
165
  add("22585", f"ALIF each additional interspace Γ—{inters}",
166
  "Multi-level ALIF.", "ALIF add-on", 0.86)
 
170
  desc = {"22845":"Anterior instrumentation (2–3 segments)",
171
  "22846":"Anterior instrumentation (4–7 segments)",
172
  "22847":"Anterior instrumentation (8+ segments)"}[code]
173
+ add(code, desc, "Anterior plate present; span estimated from levels.", "instrumentation", 0.8)
174
 
175
+ # 4) Posterolateral/posterior fusion (no interbody)
176
  if _has(t, r"posterolateral\b.*\bfusion|posterior\b.*\bfusion|in situ\b.*\bfusion") \
177
+ and not _has(t, r"\btlif\b|\bplif\b|posterior interbody") and not washout_only:
178
  base_map = {"cervical":"22600", "thoracic":"22610", "lumbar":"22612", "lumbosacral":"22612", "cervicothoracic":"22600"}
179
  base = base_map.get(region, "22612")
180
  add(base, f"Posterolateral/posterior fusion, first level ({region})",
 
188
  desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
189
  "22843":"Posterior segmental instrumentation (4–7 segments)",
190
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
191
+ add(code, desc, "Posterior instrumentation detected.", "instrumentation", 0.8)
192
 
193
+ # 5) Instrumentation w/o explicit fusion (posterior)
194
+ if _has(t, INSTR_KW) and not washout_only:
195
  code = _inst_code_by_span(span, anterior=False)
196
  if code and not any(r["category"] == "instrumentation" for r in out):
197
  desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
 
199
  "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
200
  add(code, desc, "Posterior instrumentation documented.", "instrumentation", 0.82)
201
 
202
+ # 6) Navigation
203
  if _has(t, NAV_KW):
204
  add("61783", "Intraoperative navigation (image-guided)",
205
  "Navigation terms detected (O-arm/3D spin/Stealth/7D).", "navigation", 0.82)
206
 
207
+ # 7) Bone grafts
208
  if _has(t, AUTO_SEP_KW):
209
+ add("20937", "Autograft (separate incision)", "Iliac crest or separate-site autograft.", "graft", 0.8)
210
  elif _has(t, AUTO_LOCAL_KW):
211
+ add("20936", "Autograft, local (same incision)", "Local autograft retained.", "graft", 0.8)
212
  if _has(t, ALLO_KW):
213
+ add("20930", "Allograft, morselized / DBM", "Allograft/DBM used.", "graft", 0.8)
214
 
215
+ # 8) Hardware removal
216
  if _has(t, r"(remov(ed|al)|explant).*(instrument|hardware|plate|rod|screw)"):
217
  if _has(t, r"\banterior\b|acdf|plate"):
218
  add("22855", "Removal of anterior instrumentation", "Anterior hardware removal.", "hardware_removal", 0.84, True)
 
221
 
222
  return out
223
 
224
+ # ─────────────────────────────────────────────
225
+ # SPECIALTY PACKS (adds 360Β°, VCR, Intradural, Exposure-only)
226
+ # ─────────────────────────────────────────────
 
227
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
228
  t = _norm(note)
229
  extra: List[Dict[str, Any]] = []
230
 
231
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
232
+ extra.append({"cpt": cpt, "desc": desc, "rationale": rationale, "category": cat,
233
+ "confidence": round(conf, 2), "primary": primary})
 
 
234
 
235
+ # Tumor / corpectomy (kept from v2.2)
236
  if _has(t, r"corpectomy|tumou?r|metastatic|metastasis|en bloc"):
237
  base = "63081" if region.startswith("cervical") or "cervico" in region else "63085"
238
  add(base, "Vertebral corpectomy, first segment", "Corpectomy/tumor resection.", "tumor/corpectomy", 0.88, True)
 
240
  addon = "63082" if base == "63081" else "63086"
241
  add(addon, f"Each additional segment Γ—{inters}", "Multi-segment corpectomy.", "tumor/corpectomy add-on", 0.83)
242
 
243
+ # Deformity / SPO (kept)
244
  if _has(t, r"\bspo\b|smith[- ]?petersen|posterior column osteotomy|osteotomy"):
245
  base = {"cervical": "22210", "thoracic": "22214", "lumbar": "22206"}.get(region, "22206")
246
  add(base, "Posterior column osteotomy, first level", "Deformity correction with SPO.", "deformity", 0.84, True)
247
  if inters > 0:
248
+ add(str(int(base) + 1), f"Each additional level Γ—{inters}", "Multi-level osteotomy.", "deformity add-on", 0.8)
249
 
250
+ # Stimulator (kept)
251
  if _has(t, r"stimulator|paddle lead|scs"):
252
  add("63655", "Laminectomy for implantation of neurostimulator paddle", "Paddle lead placement.", "stimulator", 0.86, True)
253
  if _has(t, r"\bipg\b|pulse generator|battery"):
254
  add("63685", "Insertion or replacement of IPG", "Pulse generator placed.", "stimulator add-on", 0.82)
255
 
256
+ # Kyphoplasty / Vertebral augmentation (kept)
257
  if _has(t, r"kyphoplasty|vertebroplasty|cement"):
258
  base_map = {"cervical": "22510", "thoracic": "22513", "lumbar": "22514"}
259
  base = base_map.get(region, "22514")
260
  add(base, "Percutaneous vertebral augmentation, first level", "Kypho/vertebroplasty terms found.", "augmentation", 0.84, True)
261
  if inters > 0:
262
+ add("22515", f"Each additional vertebral body Γ—{inters}", "Multi-level augmentation.", "augmentation add-on", 0.8)
263
 
264
+ # Revision instrumentation (kept)
265
  if _has(t, r"revision of instrumentation|reinsertion|remove and replace|re-?insert"):
266
  add("22849", "Revision/reinsertion of spinal instrumentation", "Instrumentation revised/reinserted.", "revision", 0.84, True)
267
 
268
+ # Pelvic fixation (kept)
269
  if _has(t, r"pelvic fixation|iliac bolt|iliac screw|s2ai"):
270
  add("22848", "Pelvic fixation (attach instrumentation to pelvis)", "Iliac/S2AI fixation.", "pelvic_fixation", 0.83)
271
 
272
+ # NEW: 360Β° fusion bundler (anterior + posterior in same episode)
273
+ if _has(t, ANTERIOR_KW) and _has(t, POSTERIOR_KW):
274
+ add("BUNDLE-360", "360Β° (circumferential) construct detected",
275
+ "Anterior + posterior fusion/instrumentation in same setting; verify distinct CPT families applied correctly.",
276
+ "bundle", 0.9, False)
277
+
278
+ # NEW: Vertebral Column Resection (flag for coder review)
279
+ if _has(t, VCR_KW):
280
+ add("REVIEW-VCR", "Vertebral Column Resection (3-column)",
281
+ "VCR language detected; confirm exact CPT family and approach-level specifics before final billing.",
282
+ "deformity_vcr_review", 0.95, True)
283
+
284
+ # NEW: Intradural tumor resection (flag for coder review)
285
+ if _has(t, INTRADURAL_KW):
286
+ add("REVIEW-INTRADURAL", "Intradural tumor resection suspected",
287
+ "Intradural tumor/excision terms detected; map to exact intradural CPT by level and approach.",
288
+ "intradural_review", 0.93, True)
289
+
290
+ # NEW: Exposure-only (guard produces explicit output row)
291
+ if _has(t, EXPOSURE_ONLY_KW) and not _has(t, FUSION_KW) and not _has(t, INSTR_KW):
292
+ add("EXPOSURE-ONLY", "Approach/exposure only (no fusion/instrumentation performed)",
293
+ "Explicit documentation of exposure without implantation or arthrodesis.",
294
+ "exposure_only", 0.9, True)
295
+
296
+ # Arthroplasty (kept but using the new keywords)
297
+ if _has(t, CERVICAL_ARTHRO):
298
+ add("22856", "Cervical disc arthroplasty, single level",
299
+ "Cervical disc arthroplasty documented.", "arthroplasty", 0.87, True)
300
+ if _has(t, LUMBAR_ARTHRO):
301
+ add("22857", "Lumbar disc arthroplasty, single level",
302
+ "Lumbar disc arthroplasty documented.", "arthroplasty", 0.86, True)
303
+
304
  return extra
305
 
306
+ # Vertebro FINAL-v2.3-PRO-Elite (Block 3/4)
307
+ # Case-level modifiers & complications detector (same as your v2.2, labeled v2.3)
 
308
 
309
  def _detect_case_modifiers_and_complications(note: str) -> Tuple[List[Dict[str, str]], Dict[str, bool], List[str]]:
310
  t = _norm(note)
 
312
  complications_map: Dict[str, bool] = {}
313
  complications_list: List[str] = []
314
 
 
315
  mod_patterns = [
316
  ("22", r"(complex|technically (difficult|demanding)|difficult dissection|extensive adhesiolysis|"
317
  r"severe deformity|morbid obesity|revision (case|exposure)|re-?operative field|"
 
326
  "Repeat procedure/service by the same physician."),
327
  ("77", r"\brepeat(ed)? procedure\b.*\b(another|different) (surgeon|physician)\b",
328
  "Repeat procedure by another physician."),
329
+ ("78", r"(\bunplanned return\b.*\b(operating|op)\s*room\b)|"
330
+ r"(\breturn to (the )?(operating|op)\s*room\b.*\b(postoperative|global period)\b)",
331
  "Unplanned return to OR during postoperative period."),
332
+ ("79", r"\bunrelated procedure\b.*\b(postoperative|global period)\b",
333
  "Unrelated procedure during postoperative period."),
334
  ]
335
  for code, pat, reason in mod_patterns:
336
  if _has(t, pat):
337
  modifiers.append({"modifier": code, "reason": reason})
338
 
339
+ # Assistant: AS vs -80 (mutually exclusive); -82 supersedes -80
340
  has_pa_np = _has(t, r"\b(pa[- ]?c|physician assistant|pa-c|nurse practitioner|np|advanced practice provider|app)\b")
341
  has_md_do = _has(t, r"\b(dr\.?|m\.?d\.?|d\.?o\.?)\b") or _has(t, r"\bassistant surgeon\b")
342
+ if _has(t, r"assistant\(s\):") or _has(t, r"\bassistant(s)?[:\-]"): has_md_do = True
 
343
  if has_pa_np:
344
  modifiers.append({"modifier": "AS", "reason": "Non-physician assistant at surgery."})
345
  elif has_md_do:
 
351
  if any(m["modifier"] == "53" for m in modifiers):
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"),
 
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"\bunplanned 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:
372
  hit = _has(t, pat)
373
  complications_map[key] = hit
374
+ if hit: complications_list.append(key.replace("_", " ").title())
 
375
 
 
376
  if _has(t, r"no significant changes in (motor|sensory) evoked potentials|neuromonitoring.*no (significant )?changes"):
377
  complications_map["neuromonitoring_change"] = False
378
  complications_list = [c for c in complications_list if c != "Neuromonitoring Change"]
379
 
380
  return modifiers, complications_map, complications_list
381
 
382
+ # Vertebro FINAL-v2.3-PRO-Elite (Block 4/4)
 
383
  # Output builder & main entrypoint with top_k + backward-compat alias
384
 
385
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
 
387
  t = _norm(note)
388
  levels = _extract_levels(t)
389
  region = _classify_region(levels)
390
+ inters = _count_interspaces(levels)
391
  laterality, mods_stub = _laterality_and_modifiers(t)
392
 
393
  # Inference
 
395
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
396
  rows = base_rows + extra_rows
397
 
398
+ # Merge duplicates (max confidence, concat rationale)
399
  merged: Dict[tuple, Dict[str, Any]] = {}
400
  for r in rows:
401
  key = (r["cpt"], r["category"])
 
405
  merged[key]["confidence"] = round(max(merged[key]["confidence"], r["confidence"]), 2)
406
  merged[key]["rationale"] = (merged[key]["rationale"] + " / " + r["rationale"]).strip()
407
  rows = list(merged.values())
408
+ rows.sort(key=lambda r: (-r.get("confidence", 0.0), r.get("primary", False) is False, r["cpt"]))
409
 
410
  # Tech flags
411
  flags_map = {
 
416
  }
417
  tech_flags = [k for k, v in flags_map.items() if v]
418
 
419
+ # Attach tech info to primary rows
420
+ if tech_flags:
421
+ tech_txt = f" (Tech: {', '.join(tech_flags)})"
422
+ for r in rows:
423
+ if r.get("primary", False):
424
+ r["rationale"] = (r.get("rationale", "") + tech_txt).strip()
425
+
426
  # Case-level modifiers & complications
427
  case_modifiers, complications_map, complications_list = _detect_case_modifiers_and_complications(t)
428
  if not case_modifiers and mods_stub:
429
  case_modifiers = [{"modifier": m, "reason": "Laterality-derived modifier."} for m in mods_stub]
430
 
 
 
 
 
 
 
 
 
431
  # Propagate -53 only to primary rows
432
  if any(m["modifier"] == "53" for m in case_modifiers):
433
  for r in rows:
 
439
  if r.get("modifiers"):
440
  r["modifiers"] = [m for m in r["modifiers"] if m != "53"]
441
 
442
+ # Top-K cap
 
 
 
 
 
 
 
 
 
 
 
 
443
  try:
444
  k = int(top_k)
445
+ if k > 0: rows = rows[:k]
 
446
  except Exception:
447
  pass
448
 
 
458
  "complications_flags": complications_map,
459
  "complications": complications_list,
460
  "suggestions": rows,
461
+ "build": "FINAL-v2.3-PRO-Elite",
462
  "mode": "standard",
463
  }
464
 
465
  # Backward compatibility alias
466
  suggest_with_cpt_billing = vertebro_infer
467
 
468
+ # CLI smoke test
469
  if __name__ == "__main__":
470
  sample = """
471
+ 360 fusion example: ALIF L4–L5 with structural allograft and plate;
472
+ posterior pedicle screws L4–L5 with posterolateral fusion; fluoroscopy + O-arm.
 
 
 
 
473
  """
474
  print(json.dumps(vertebro_infer(sample, top_k=15), indent=2))
475
+
476
+ # Vertebro FINAL-v2.3-PRO-Elite (Block 5/5)
477
+ # Test harness, batch helper, and lightweight Space shim (optional)
478
+
479
+ from typing import Iterable
480
+
481
+ # ─────────────────────────────────────────────
482
+ # Batch helper
483
+ # ─────────────────────────────────────────────
484
+ def batch_infer(notes: Iterable[str], payer: str = "Medicare", top_k: int = 10):
485
+ """Run vertebro_infer over an iterable of op notes. Returns list of dicts."""
486
+ results = []
487
+ for i, note in enumerate(notes, 1):
488
+ try:
489
+ out = vertebro_infer(note, payer=payer, top_k=top_k)
490
+ results.append({
491
+ "idx": i,
492
+ "region": out.get("region"),
493
+ "levels": out.get("levels"),
494
+ "interspaces": out.get("interspaces_est"),
495
+ "flags": out.get("flags"),
496
+ "case_modifiers": out.get("case_modifiers"),
497
+ "complications": out.get("complications"),
498
+ "primary_codes": [
499
+ {"cpt": r["cpt"], "desc": r["desc"], "cat": r["category"], "conf": r.get("confidence")}
500
+ for r in out.get("suggestions", []) if r.get("primary")
501
+ ],
502
+ "all_codes": [
503
+ {"cpt": r["cpt"], "desc": r["desc"], "cat": r["category"], "conf": r.get("confidence")}
504
+ for r in out.get("suggestions", [])
505
+ ],
506
+ "raw": out,
507
+ })
508
+ except Exception as e:
509
+ results.append({"idx": i, "error": str(e)})
510
+ return results
511
+
512
+
513
+ # ─────────────────────────────────────────────
514
+ # Pretty printer for CLI debugging
515
+ # ─────────────────────────────────────────────
516
+ def _pp_row(row: dict):
517
+ if "error" in row:
518
+ print(f"[{row['idx']}] ERROR: {row['error']}")
519
+ return
520
+ print(f"\n[{row['idx']}] Region={row['region']} Levels={row['levels']} Interspaces={row['interspaces']}")
521
+ if row.get("flags"):
522
+ print(f" Tech: {', '.join(row['flags'])}")
523
+ if row.get("case_modifiers"):
524
+ mods = ', '.join([m['modifier'] for m in row["case_modifiers"]])
525
+ print(f" Case Modifiers: {mods}")
526
+ if row.get("complications"):
527
+ print(f" Complications: {', '.join(row['complications'])}")
528
+
529
+ prims = row.get("primary_codes", [])
530
+ print(" Primary:")
531
+ if not prims:
532
+ print(" (none)")
533
+ for p in prims:
534
+ print(f" {p['cpt']} {p['cat']} conf={p['conf']} β€” {p['desc']}")
535
+ allc = row.get("all_codes", [])
536
+ extra = [a for a in allc if a not in prims]
537
+ if extra:
538
+ print(" Add-ons / Extras:")
539
+ for e in extra:
540
+ print(f" {e['cpt']} {e['cat']} conf={e['conf']} β€” {e['desc']}")
541
+
542
+
543
+ # ─────────────────────────────────────────────
544
+ # Smoke test set (Jason-style)
545
+ # ─────────────────────────────────────────────
546
+ def _smoke_notes():
547
+ return [
548
+ # 1) Cervicothoracic posterior fusion + decomp + nav + grafts
549
+ """Preop: Cervical stenosis with myelopathy.
550
+ Proc: C2–T2 posterolateral arthrodesis; posterior instrumentation C2–T2;
551
+ C3–C6 laminectomy with bilateral medial facetectomies/foraminotomies;
552
+ navigation (Ziehm 3D spin) and fluoroscopy; local autograft + DBM.
553
+ Assistant(s): Dr. Amber Parker.""",
554
+
555
+ # 2) ACDF C4–C6 with plate
556
+ """ACDF C4–C6 with structural allograft; anterior plate spanning C4–C6; nav + fluoro; PA-C assisting.""",
557
+
558
+ # 3) TLIF L4–L5 with screws/rods
559
+ """Right facetectomy and TLIF L4–L5 with PEEK cage; posterior pedicle screws L4–L5 with rods;
560
+ posterolateral arthrodesis; microscope + fluoro; PA-C assistant.""",
561
+
562
+ # 4) ALIF L4–S1 with plate
563
+ """ALIF L4–S1 with structural allograft spacers and anterior plating; vascular exposure; PA-C present; fluoroscopy.""",
564
+
565
+ # 5) Deformity PSO + pelvic fixation
566
+ """L3 pedicle subtraction osteotomy; posterior segmental instrumentation L2–S1 with S2AI pelvic fixation;
567
+ posterolateral fusion L2–S1; O-arm navigation; EBL 1200 mL; MD assistant.""",
568
+
569
+ # 6) Tumor corpectomy T7 + long construct
570
+ """T7 corpectomy for metastatic tumor with expandable cage reconstruction; posterior instrumentation T5–T9;
571
+ laminectomy T7; navigation + fluoroscopy; MD assistant.""",
572
+
573
+ # 7) Postop epidural hematoma evacuation (take-back)
574
+ """Unplanned return to OR POD#1: L4–L5 laminectomy for evacuation of epidural hematoma; Hemovac placed; PA-C assist; fluoroscopy.""",
575
+
576
+ # 8) I&D depth to bone (infection)
577
+ """Irrigation and debridement of lumbar wound to bone with pulse-lavage; VAC applied; PA-C assistant.""",
578
+
579
+ # 9) Exposure-only (guard)
580
+ """Left retroperitoneal exposure L4–S1 by vascular surgeon only; no interbody, no fusion, no screws/plate/cage placed; closed; to be staged later.""",
581
+
582
+ # 10) Cervical arthroplasty
583
+ """C5–C6 cervical disc arthroplasty (CDA); microscope, Ziehm 3D spin, fluoroscopy; PA-C assistant.""",
584
+
585
+ # 11) Lumbar TDR
586
+ """L5–S1 total disc replacement via anterior approach; vascular exposure; fluoroscopy; PA-C present.""",
587
+
588
+ # 12) SCS paddle + IPG
589
+ """T9–T10 laminectomy for paddle lead; IPG in right gluteal pocket; fluoroscopy; PA-C assistant.""",
590
+
591
+ # 13) NEW: 360Β° fusion bundler case
592
+ """ALIF L4–L5 with structural allograft and anterior plate; in same setting posterior approach with bilateral pedicle screws L4–L5 and posterolateral fusion; O-arm nav; fluoroscopy.""",
593
+
594
+ # 14) NEW: VCR review case
595
+ """Rigid kyphosis: Vertebral Column Resection (VCR) at T8 (3-column), cage reconstruction, posterior segmental instrumentation T6–T10; neuromonitoring; fluoroscopy; navigation.""",
596
+
597
+ # 15) NEW: Intradural tumor review case
598
+ """C6–C7 intradural extramedullary tumor (likely meningioma). Microscope; intradural tumor resection with dural repair; IONM; fluoroscopy.""",
599
+ ]
600
+
601
+
602
+ # ─────────────────────────────────────────────
603
+ # Public smoke runner
604
+ # ─────────────────────────────────────────────
605
+ def run_smoke_tests(top_k: int = 12):
606
+ """Run a fixed set of diverse Jason-style notes through the engine and print."""
607
+ notes = _smoke_notes()
608
+ results = batch_infer(notes, top_k=top_k)
609
+ for row in results:
610
+ _pp_row(row)
611
+ return results
612
+
613
+
614
+ # ─────────────────────────────────────────────
615
+ # Minimal Space shim (no hard dependency on gradio)
616
+ # Provides a callable `infer_for_space(note, payer, top_k)` used by app.py
617
+ # ─────────────────────────────────────────────
618
+ def infer_for_space(note: str, payer: str = "Medicare", top_k: int = 10) -> str:
619
+ """
620
+ Stable text output for UI: returns pretty JSON string.
621
+ Import this in app.py and wire to your textbox -> JSON panel.
622
+ """
623
+ res = vertebro_infer(note, payer=payer, top_k=top_k)
624
+ try:
625
+ return json.dumps(res, indent=2)
626
+ except Exception:
627
+ # Fallback minimal serializer
628
+ return str(res)
629
+
630
+
631
+ # ─────────────────────────────────────────────
632
+ # When run directly, execute smoke tests
633
+ # ─────────────────────────────────────────────
634
+ if __name__ == "__main__":
635
+ print("Running Vertebro v2.3-PRO-Elite smoke tests…")
636
+ _ = run_smoke_tests(top_k=15)