Slaiwala commited on
Commit
45e1cb1
Β·
verified Β·
1 Parent(s): 84d8bdb

Update spine_coder/spine_coder_core.py

Browse files
Files changed (1) hide show
  1. spine_coder/spine_coder_core.py +107 -99
spine_coder/spine_coder_core.py CHANGED
@@ -1,13 +1,11 @@
1
  # spine_coder_core_pro.py
2
- # Vertebro FINAL-v2.2-PRO (Block 1 / 3)
3
  # Core utilities, text normalization, level parsing, region classification
4
 
5
  import re, json
6
  from typing import Dict, Any, List, Tuple
7
 
8
- # ─────────────────────────────────────────────
9
- # TEXT NORMALIZATION
10
- # ─────────────────────────────────────────────
11
  def _norm(s: str) -> str:
12
  """Clean unicode dashes, normalize spaces, lowercase text."""
13
  if not s:
@@ -21,9 +19,7 @@ def _has(text: str, pat: str) -> bool:
21
  return re.search(pat, text, flags=re.I) is not None
22
 
23
 
24
- # ─────────────────────────────────────────────
25
- # LEVEL AND REGION LOGIC
26
- # ─────────────────────────────────────────────
27
  _ORDER = ["C", "T", "L", "S"]
28
  _MAXNUM = {"C": 7, "T": 12, "L": 5, "S": 5}
29
 
@@ -33,16 +29,14 @@ def _level_sort_key(lv: str) -> Tuple[int, int]:
33
  num = int(re.sub(r"\D", "", lv) or 0)
34
  return (band, num)
35
 
36
- _SPAN = re.compile(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", re.I)
37
  _SINGLE = re.compile(r"\b([CTLS])\s?(\d{1,2})\b", re.I)
38
 
39
  def _expand_across_regions(p1: str, n1: int, p2: str, n2: int) -> List[str]:
40
  """Inclusive sequence from (p1,n1) to (p2,n2), crossing C/T/L/S boundaries."""
41
  p1, p2 = p1.upper(), p2.upper()
42
  out = []
43
- i = _ORDER.index(p1)
44
- j = _ORDER.index(p2)
45
- r = i
46
  num = n1
47
  while True:
48
  out.append(f"{_ORDER[r]}{num}")
@@ -99,9 +93,7 @@ def _classify_region(levels: List[str]) -> str:
99
  return "mixed"
100
 
101
 
102
- # ─────────────────────────────────────────────
103
- # LATERALITY AND MODIFIERS
104
- # ─────────────────────────────────────────────
105
  def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
106
  """Force midline for spine work; suppress invalid -50."""
107
  t = _norm(note)
@@ -115,25 +107,23 @@ def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
115
  return "midline", []
116
 
117
 
118
- # ─────────────────────────────────────────────
119
- # BASE FLAG PATTERNS
120
- # ─────────────────────────────────────────────
121
- FUSION_KW = r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
122
- INSTR_KW = r"\b(pedicle\s+screws?|lateral\s+mass\s+screws?|rods?|set\s+screws?|instrument(?:ed|ation))\b"
123
- NAV_KW = r"\b(navigation|navigated|o-?arm|ziehm|3d\s+spin)\b"
124
- ALLO_KW = r"\b(allograft|dbm|demineralized\s+bone\s+matrix)\b"
125
- AUTO_LOCAL_KW = r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\s+retained|morselized\s+autograft)\b"
126
- AUTO_SEP_KW = r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|harvested\s+from\s+iliac)\b"
127
 
128
- ### --- END OF BLOCK 1 --- ###
 
 
129
 
130
  # spine_coder_core_pro.py
131
- # Vertebro FINAL-v2.2-PRO (Block 2 / 3)
132
  # CPT reasoning logic and inference tree
133
 
134
- # ─────────────────────────────────────────────
135
- # INSTRUMENTATION SPAN HELPER
136
- # ─────────────────────────────────────────────
137
  def _inst_code_by_span(span: int, anterior: bool) -> str:
138
  """Return instrumentation CPT code based on span length."""
139
  if span <= 1:
@@ -144,28 +134,21 @@ def _inst_code_by_span(span: int, anterior: bool) -> str:
144
  return "22842" if span <= 3 else ("22843" if span <= 7 else "22844")
145
 
146
 
147
- # ─────────────────────────────────────────────
148
- # CORE CPT INFERENCE ENGINE
149
- # ─────────────────────────────────────────────
150
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
151
  """Return CPT dictionary list with rationale and confidence."""
152
  t = _norm(note)
153
  out: List[Dict[str, Any]] = []
154
  inters = _count_interspaces(levels)
155
  span = max(inters + 1, 2)
156
- conf = 0.85
157
 
158
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
159
  out.append({
160
- "cpt": cpt,
161
- "desc": desc,
162
- "rationale": rationale,
163
- "category": cat,
164
- "confidence": round(conf, 2),
165
- "primary": primary
166
  })
167
 
168
- # ----- 1. Decompression -----
169
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
170
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
171
  base = base_map.get(region, "63047")
@@ -176,7 +159,7 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
176
  add("63048", f"Each additional level Γ—{inters}",
177
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
178
 
179
- # ----- 2. Fusion -----
180
  if _has(t, FUSION_KW):
181
  base_map = {"cervicothoracic": "22600", "cervical": "22600",
182
  "thoracic": "22610", "lumbar": "22630", "lumbosacral": "22612"}
@@ -187,7 +170,43 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
187
  add("22614", f"Each additional level Γ—{inters}",
188
  "Multi-level fusion extension.", "fusion add-on", 0.84)
189
 
190
- # ----- 3. Instrumentation -----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  if _has(t, INSTR_KW):
192
  code = _inst_code_by_span(span, anterior=False)
193
  desc = {
@@ -195,36 +214,46 @@ def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any
195
  "22843": "Posterior segmental instrumentation (4–7 segments)",
196
  "22844": "Posterior segmental instrumentation (8+ segments)"
197
  }.get(code, "Posterior instrumentation")
198
- add(code, desc, "Posterior instrumentation detected (pedicle screws/rods).",
199
- "instrumentation", 0.88)
 
200
 
201
- # ----- 4. Navigation -----
202
  if _has(t, NAV_KW):
203
  add("61783", "Intraoperative navigation (image-guided)",
204
  "Navigation terms detected (Ziehm/O-arm/3D spin).", "navigation", 0.82)
205
 
206
- # ----- 5. Bone grafts -----
207
  if _has(t, AUTO_SEP_KW):
208
  add("20937", "Autograft, separate incision",
209
- "Iliac crest or separate-site autograft documented.", "graft", 0.8)
210
  elif _has(t, AUTO_LOCAL_KW):
211
  add("20936", "Autograft, local (same incision)",
212
- "Local autograft material retained.", "graft", 0.8)
213
- if _has(t, ALLO_KW):
 
 
 
 
 
214
  add("20930", "Allograft, morselized / DBM",
215
- "Allograft or DBM usage detected.", "graft", 0.8)
216
 
217
- # ----- 6. Hardware removal -----
218
  if _has(t, r"remov(ed|al).*instrument"):
219
  add("22852", "Removal of posterior instrumentation",
220
  "Hardware removal/explantation documented.", "hardware_removal", 0.84, True)
221
 
 
 
 
 
222
  return out
223
 
 
 
 
224
 
225
- # ─────────────────────────────────────────────
226
- # SPECIALTY PACKS (Tumor / Deformity / Stimulator / Kyphoplasty / Revision)
227
- # ─────────────────────────────────────────────
228
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
229
  """Return CPTs from specialty packs layered onto base logic."""
230
  t = _norm(note)
@@ -232,12 +261,8 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
232
 
233
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
234
  extra.append({
235
- "cpt": cpt,
236
- "desc": desc,
237
- "rationale": rationale,
238
- "category": cat,
239
- "confidence": round(conf, 2),
240
- "primary": primary
241
  })
242
 
243
  # Tumor / corpectomy
@@ -251,13 +276,13 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
251
  "Multi-segment corpectomy.", "tumor/corpectomy add-on", 0.83)
252
 
253
  # Deformity / osteotomy
254
- if _has(t, r"spo\b|smith[- ]?petersen|posterior column osteotomy|osteotomy"):
255
  base = {"cervical": "22210", "thoracic": "22214", "lumbar": "22206"}.get(region, "22206")
256
  add(base, "Posterior column osteotomy, first level",
257
  "Deformity correction (SPO/osteotomy) documented.", "deformity", 0.84, True)
258
  if inters > 0:
259
  add(str(int(base) + 1), f"Each additional level Γ—{inters}",
260
- "Multi-level osteotomy.", "deformity add-on", 0.8)
261
 
262
  # Stimulator
263
  if _has(t, r"stimulator|paddle lead|scs"):
@@ -275,7 +300,7 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
275
  "Kyphoplasty/vertebroplasty terms found.", "augmentation", 0.84, True)
276
  if inters > 0:
277
  add("22515", f"Each additional level Γ—{inters}",
278
- "Multi-level augmentation.", "augmentation add-on", 0.8)
279
 
280
  # Revision instrumentation
281
  if _has(t, r"revision of instrumentation|reinsert|remove and replace"):
@@ -289,18 +314,10 @@ def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str
289
 
290
  return extra
291
 
292
- ### --- END OF BLOCK 2 --- ###
293
-
294
  # spine_coder_core_pro.py
295
- # Vertebro FINAL-v2.2-PRO (Block 3 / 3)
296
- # Output builder, rationale composer, and main entrypoint
297
-
298
- # ─────────────────────────────────────────────
299
- # MAIN OUTPUT ASSEMBLER
300
- # ─────────────────────────────────────────────
301
- # ─────────────────────────────────────────────
302
- # MAIN OUTPUT ASSEMBLER (v2.2-PRO with top_k + BC alias)
303
- # ─────────────────────────────────────────────
304
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
305
  """Master inference wrapper β€” merges base logic + specialty packs.
306
  `top_k` kept for HF UI/backward compatibility; rows are truncated to top_k after scoring.
@@ -311,12 +328,12 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
311
  inters = _count_interspaces(levels)
312
  laterality, mods = _laterality_and_modifiers(t)
313
 
314
- # --- run inference ---
315
- base_rows = _infer_cpts(t, region, levels)
316
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
317
  rows = base_rows + extra_rows
318
 
319
- # --- merge duplicates and compute confidence max + rationale concat ---
320
  merged: Dict[tuple, Dict[str, Any]] = {}
321
  for r in rows:
322
  key = (r["cpt"], r["category"])
@@ -324,28 +341,28 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
324
  merged[key] = r.copy()
325
  else:
326
  merged[key]["confidence"] = round(max(merged[key]["confidence"], r["confidence"]), 2)
327
- merged[key]["rationale"] = (merged[key]["rationale"] + " / " + r["rationale"]).strip()
328
 
329
  rows = list(merged.values())
330
  rows.sort(key=lambda r: (-r.get("confidence", 0.0), r.get("primary", False) is False, r["cpt"]))
331
 
332
- # --- flag map (tech) ---
333
  flags_map = {
334
  "microscope": _has(t, r"\bmicroscope\b|microdissection"),
335
- "nav": _has(t, r"\bnavigation\b|o-?arm|ziehm|3d\s+spin"),
336
  "io_monitor": _has(t, r"\bneuromonitor|ssep|emg|monitoring\b"),
337
- "fluoro": _has(t, r"\bfluoro|c[- ]?arm|fluoroscop"),
338
  }
339
  tech_flags = [k for k, v in flags_map.items() if v]
340
 
341
- # --- attach tech info to rationale for primary row(s) ---
342
  if tech_flags:
343
  tech_txt = f" (Tech: {', '.join(tech_flags)})"
344
  for r in rows:
345
  if r.get("primary", False):
346
  r["rationale"] = (r.get("rationale", "") + tech_txt).strip()
347
 
348
- # --- Top-K cap (HF UI slider compatibility) ---
349
  try:
350
  k = int(top_k)
351
  if k > 0:
@@ -353,7 +370,7 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
353
  except Exception:
354
  pass
355
 
356
- # --- build final JSON output ---
357
  return {
358
  "payer": payer,
359
  "region": region,
@@ -372,24 +389,15 @@ def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[
372
  suggest_with_cpt_billing = vertebro_infer
373
 
374
 
375
-
376
- # ─────────────────────────────────────────────
377
- # CLI / HF Space TEST ENTRYPOINT
378
- # ─────────────────────────────────────────────
379
  if __name__ == "__main__":
380
  sample = """
381
  Preoperative Diagnosis: C3–C6 stenosis with myelopathy.
382
  Procedure:
383
- 1. C3–C6 laminectomy and medial facetectomies.
384
- 2. C2–T1 posterolateral arthrodesis.
385
- 3. C2–T1 posterior instrumentation.
386
- 4. Use of navigation and intra-operative fluoroscopy.
387
- 5. Local autograft and allograft placed.
388
  """
389
- result = vertebro_infer(sample)
390
- print(json.dumps(result, indent=2))
391
-
392
-
393
- # Backward compatibility for old app versions
394
- suggest_with_cpt_billing = vertebro_infer
395
-
 
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
6
  from typing import Dict, Any, List, Tuple
7
 
8
+ # ── Text normalization ────────────────────────────────────────────────────────
 
 
9
  def _norm(s: str) -> str:
10
  """Clean unicode dashes, normalize spaces, lowercase text."""
11
  if not s:
 
19
  return re.search(pat, text, flags=re.I) is not None
20
 
21
 
22
+ # ── Levels & region ──────────────────────────────────────────────────────────
 
 
23
  _ORDER = ["C", "T", "L", "S"]
24
  _MAXNUM = {"C": 7, "T": 12, "L": 5, "S": 5}
25
 
 
29
  num = int(re.sub(r"\D", "", lv) or 0)
30
  return (band, num)
31
 
32
+ _SPAN = re.compile(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", re.I)
33
  _SINGLE = re.compile(r"\b([CTLS])\s?(\d{1,2})\b", re.I)
34
 
35
  def _expand_across_regions(p1: str, n1: int, p2: str, n2: int) -> List[str]:
36
  """Inclusive sequence from (p1,n1) to (p2,n2), crossing C/T/L/S boundaries."""
37
  p1, p2 = p1.upper(), p2.upper()
38
  out = []
39
+ r = _ORDER.index(p1)
 
 
40
  num = n1
41
  while True:
42
  out.append(f"{_ORDER[r]}{num}")
 
93
  return "mixed"
94
 
95
 
96
+ # ── Laterality & modifiers (spine midline default) ───────────────────────────
 
 
97
  def _laterality_and_modifiers(note: str) -> Tuple[str, List[str]]:
98
  """Force midline for spine work; suppress invalid -50."""
99
  t = _norm(note)
 
107
  return "midline", []
108
 
109
 
110
+ # ── Base flag patterns ───────────────────────────────────────────────────────
111
+ FUSION_KW = r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
112
+ INSTR_KW = r"\b(pedicle\s+screws?|lateral\s+mass\s+screws?|rods?|set\s+screws?|instrument(?:ed|ation))\b"
113
+ NAV_KW = r"\b(navigation|navigated|o-?arm|ziehm|3d\s+spin)\b"
114
+ ALLO_KW = r"\b(allograft|dbm|demineralized\s+bone\s+matrix)\b"
115
+ AUTO_LOCAL_KW = r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\s+retained|morselized\s+autograft)\b"
116
+ AUTO_SEP_KW = r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|harvested\s+from\s+iliac)\b"
 
 
117
 
118
+ # ACDF recognition
119
+ ACDF_KW = r"(anterior cervical discectomy|acdf|smith[- ]?robinson|anterior cervical fusion)"
120
+ ACDF_SUPPORT = r"(interbody|cage|peek|dbm|allograft|arthrodesis|plate|plating)"
121
 
122
  # spine_coder_core_pro.py
123
+ # Vertebro FINAL-v2.2-PRO (Block 2/4)
124
  # CPT reasoning logic and inference tree
125
 
126
+ # ── Instrumentation span helper ──────────────────────────────────────────────
 
 
127
  def _inst_code_by_span(span: int, anterior: bool) -> str:
128
  """Return instrumentation CPT code based on span length."""
129
  if span <= 1:
 
134
  return "22842" if span <= 3 else ("22843" if span <= 7 else "22844")
135
 
136
 
137
+ # ── Core CPT inference engine ────────────────────────────────────────────────
 
 
138
  def _infer_cpts(note: str, region: str, levels: List[str]) -> List[Dict[str, Any]]:
139
  """Return CPT dictionary list with rationale and confidence."""
140
  t = _norm(note)
141
  out: List[Dict[str, Any]] = []
142
  inters = _count_interspaces(levels)
143
  span = max(inters + 1, 2)
 
144
 
145
  def add(cpt: str, desc: str, rationale: str, cat: str, conf: float = 0.85, primary: bool = False):
146
  out.append({
147
+ "cpt": cpt, "desc": desc, "rationale": rationale,
148
+ "category": cat, "confidence": round(conf, 2), "primary": primary
 
 
 
 
149
  })
150
 
151
+ # 1) Decompression
152
  if _has(t, r"laminectomy|decompression|facetectomy|foraminotomy"):
153
  base_map = {"cervical": "63045", "thoracic": "63046", "lumbar": "63047"}
154
  base = base_map.get(region, "63047")
 
159
  add("63048", f"Each additional level Γ—{inters}",
160
  "Multi-level decompression inferred.", "decompression add-on", 0.82)
161
 
162
+ # 2) Generic posterior/posterolateral fusion (will be suppressed if TLIF/PLIF hits)
163
  if _has(t, FUSION_KW):
164
  base_map = {"cervicothoracic": "22600", "cervical": "22600",
165
  "thoracic": "22610", "lumbar": "22630", "lumbosacral": "22612"}
 
170
  add("22614", f"Each additional level Γ—{inters}",
171
  "Multi-level fusion extension.", "fusion add-on", 0.84)
172
 
173
+ # 2b) ACDF (22551/22552) + optional anterior instrumentation
174
+ if _has(t, ACDF_KW) and _has(t, ACDF_SUPPORT) and region in {"cervical", "cervicothoracic"}:
175
+ cerv_levels = [lv for lv in levels if lv.startswith("C")]
176
+ cerv_inters = max(1, len(sorted(set(cerv_levels), key=_level_sort_key)) - 1) if cerv_levels else max(1, inters or 1)
177
+ add("22551", "ACDF, first interspace (includes discectomy)",
178
+ "Anterior cervical discectomy and fusion pattern detected.", "ACDF", 0.95, True)
179
+ if cerv_inters > 1:
180
+ add("22552", f"ACDF, each additional interspace Γ—{cerv_inters-1}",
181
+ "Multi-level ACDF.", "ACDF add-on", 0.90)
182
+ if _has(t, r"\bplate|plating|anterior plate\b"):
183
+ span_est = max(2, cerv_inters + 1) # segments = interspaces + 1
184
+ a_code = _inst_code_by_span(span_est, anterior=True)
185
+ if a_code:
186
+ a_desc = {"22845":"Anterior instrumentation (2–3 segments)",
187
+ "22846":"Anterior instrumentation (4–7 segments)",
188
+ "22847":"Anterior instrumentation (8+ segments)"}[a_code]
189
+ add(a_code, a_desc, "Anterior plate present; span estimated from cervical levels.", "instrumentation", 0.84)
190
+
191
+ # 2c) TLIF/PLIF (22633/22634) with suppression of generic posterior fusion add-ons
192
+ tlif_plif_hit = (
193
+ _has(t, r"\b(tlif|plif|posterior interbody fusion)\b")
194
+ or (_has(t, r"facetectom(y|ies)|hemifacetectomy|transforaminal")
195
+ and _has(t, r"interbody (cage|device|spacer)|peek (cage|spacer)|titanium (cage|spacer)")
196
+ and _has(t, r"pedicle\s+screws?"))
197
+ )
198
+ if tlif_plif_hit and region in {"lumbar", "thoracic", "thoracolumbar", "lumbosacral"}:
199
+ n = max(1, inters or 1)
200
+ add("22633", "Posterior/posterolateral + posterior interbody fusion, single interspace",
201
+ "TLIF/PLIF construct detected (facetectomy + interbody device + pedicle screws).",
202
+ "TLIF/PLIF", 0.93, True)
203
+ if n > 1:
204
+ add("22634", f"Posterior interbody, each additional interspace Γ—{n-1}",
205
+ "Multi-level TLIF/PLIF.", "TLIF/PLIF add-on", 0.89)
206
+ # internal flag to suppress generic posterior fusion rows
207
+ out.append({"cpt":"__SUPPRESS_PF__", "desc":"", "rationale":"", "category":"internal", "confidence":1.0, "primary":False})
208
+
209
+ # 3) Posterior instrumentation (span-based)
210
  if _has(t, INSTR_KW):
211
  code = _inst_code_by_span(span, anterior=False)
212
  desc = {
 
214
  "22843": "Posterior segmental instrumentation (4–7 segments)",
215
  "22844": "Posterior segmental instrumentation (8+ segments)"
216
  }.get(code, "Posterior instrumentation")
217
+ if code:
218
+ add(code, desc, "Posterior instrumentation detected (pedicle screws/rods).",
219
+ "instrumentation", 0.88)
220
 
221
+ # 4) Navigation
222
  if _has(t, NAV_KW):
223
  add("61783", "Intraoperative navigation (image-guided)",
224
  "Navigation terms detected (Ziehm/O-arm/3D spin).", "navigation", 0.82)
225
 
226
+ # 5) Bone grafts (local vs separate incision; structural preference)
227
  if _has(t, AUTO_SEP_KW):
228
  add("20937", "Autograft, separate incision",
229
+ "Iliac crest or separate-site autograft documented.", "graft", 0.80)
230
  elif _has(t, AUTO_LOCAL_KW):
231
  add("20936", "Autograft, local (same incision)",
232
+ "Local autograft material retained.", "graft", 0.80)
233
+ if _has(t, r"structural allograft|fibular strut|strut graft|allograft spacer"):
234
+ # prefer 20931 over 20930 when structural is present
235
+ out = [r for r in out if r.get("cpt") != "20930"]
236
+ add("20931", "Structural allograft",
237
+ "Structural allograft (strut/cage) documented.", "graft", 0.82)
238
+ elif _has(t, ALLO_KW):
239
  add("20930", "Allograft, morselized / DBM",
240
+ "Allograft or DBM usage detected.", "graft", 0.80)
241
 
242
+ # 6) Hardware removal
243
  if _has(t, r"remov(ed|al).*instrument"):
244
  add("22852", "Removal of posterior instrumentation",
245
  "Hardware removal/explantation documented.", "hardware_removal", 0.84, True)
246
 
247
+ # TLIF guard: remove generic posterior fusion rows if TLIF/PLIF detected
248
+ if any(r.get("cpt") == "__SUPPRESS_PF__" for r in out):
249
+ out = [r for r in out if r["category"] not in {"fusion", "fusion add-on"} and r.get("cpt") != "__SUPPRESS_PF__"]
250
+
251
  return out
252
 
253
+ # spine_coder_core_pro.py
254
+ # Vertebro FINAL-v2.2-PRO (Block 3/4)
255
+ # Specialty packs
256
 
 
 
 
257
  def _apply_specialty_packs(note: str, region: str, inters: int, levels: List[str]) -> List[Dict[str, Any]]:
258
  """Return CPTs from specialty packs layered onto base logic."""
259
  t = _norm(note)
 
261
 
262
  def add(cpt, desc, rationale, cat, conf=0.84, primary=False):
263
  extra.append({
264
+ "cpt": cpt, "desc": desc, "rationale": rationale,
265
+ "category": cat, "confidence": round(conf, 2), "primary": primary
 
 
 
 
266
  })
267
 
268
  # Tumor / corpectomy
 
276
  "Multi-segment corpectomy.", "tumor/corpectomy add-on", 0.83)
277
 
278
  # Deformity / osteotomy
279
+ if _has(t, r"\bspo\b|smith[- ]?petersen|posterior column osteotomy|osteotomy"):
280
  base = {"cervical": "22210", "thoracic": "22214", "lumbar": "22206"}.get(region, "22206")
281
  add(base, "Posterior column osteotomy, first level",
282
  "Deformity correction (SPO/osteotomy) documented.", "deformity", 0.84, True)
283
  if inters > 0:
284
  add(str(int(base) + 1), f"Each additional level Γ—{inters}",
285
+ "Multi-level osteotomy.", "deformity add-on", 0.80)
286
 
287
  # Stimulator
288
  if _has(t, r"stimulator|paddle lead|scs"):
 
300
  "Kyphoplasty/vertebroplasty terms found.", "augmentation", 0.84, True)
301
  if inters > 0:
302
  add("22515", f"Each additional level Γ—{inters}",
303
+ "Multi-level augmentation.", "augmentation add-on", 0.80)
304
 
305
  # Revision instrumentation
306
  if _has(t, r"revision of instrumentation|reinsert|remove and replace"):
 
314
 
315
  return extra
316
 
 
 
317
  # spine_coder_core_pro.py
318
+ # Vertebro FINAL-v2.2-PRO (Block 4/4)
319
+ # Output builder & main entrypoint with top_k + backward-compat alias
320
+
 
 
 
 
 
 
321
  def vertebro_infer(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
322
  """Master inference wrapper β€” merges base logic + specialty packs.
323
  `top_k` kept for HF UI/backward compatibility; rows are truncated to top_k after scoring.
 
328
  inters = _count_interspaces(levels)
329
  laterality, mods = _laterality_and_modifiers(t)
330
 
331
+ # Inference
332
+ base_rows = _infer_cpts(t, region, levels)
333
  extra_rows = _apply_specialty_packs(t, region, inters, levels)
334
  rows = base_rows + extra_rows
335
 
336
+ # Merge duplicates (max confidence, concat rationale)
337
  merged: Dict[tuple, Dict[str, Any]] = {}
338
  for r in rows:
339
  key = (r["cpt"], r["category"])
 
341
  merged[key] = r.copy()
342
  else:
343
  merged[key]["confidence"] = round(max(merged[key]["confidence"], r["confidence"]), 2)
344
+ merged[key]["rationale"] = (merged[key]["rationale"] + " / " + r["rationale"]).strip()
345
 
346
  rows = list(merged.values())
347
  rows.sort(key=lambda r: (-r.get("confidence", 0.0), r.get("primary", False) is False, r["cpt"]))
348
 
349
+ # Tech flags
350
  flags_map = {
351
  "microscope": _has(t, r"\bmicroscope\b|microdissection"),
352
+ "nav": _has(t, r"\bnavigation\b|o-?arm|ziehm|3d\s+spin"),
353
  "io_monitor": _has(t, r"\bneuromonitor|ssep|emg|monitoring\b"),
354
+ "fluoro": _has(t, r"\bfluoro|c[- ]?arm|fluoroscop"),
355
  }
356
  tech_flags = [k for k, v in flags_map.items() if v]
357
 
358
+ # Attach tech info to primary rows
359
  if tech_flags:
360
  tech_txt = f" (Tech: {', '.join(tech_flags)})"
361
  for r in rows:
362
  if r.get("primary", False):
363
  r["rationale"] = (r.get("rationale", "") + tech_txt).strip()
364
 
365
+ # Top-K cap
366
  try:
367
  k = int(top_k)
368
  if k > 0:
 
370
  except Exception:
371
  pass
372
 
373
+ # Output
374
  return {
375
  "payer": payer,
376
  "region": region,
 
389
  suggest_with_cpt_billing = vertebro_infer
390
 
391
 
392
+ # CLI / HF Space quick test
 
 
 
393
  if __name__ == "__main__":
394
  sample = """
395
  Preoperative Diagnosis: C3–C6 stenosis with myelopathy.
396
  Procedure:
397
+ 1. C3–C6 laminectomy and medial facetectomies.
398
+ 2. C2–T1 posterolateral arthrodesis.
399
+ 3. C2–T1 posterior instrumentation with pedicle screws and rods.
400
+ 4. Use of navigation and intra-operative fluoroscopy.
401
+ 5. Local autograft and allograft placed.
402
  """
403
+ print(json.dumps(vertebro_infer(sample), indent=2))