Slaiwala commited on
Commit
e520e38
Β·
verified Β·
1 Parent(s): 2b488b7

Update spine_coder/spine_coder_core.py

Browse files
Files changed (1) hide show
  1. spine_coder/spine_coder_core.py +189 -533
spine_coder/spine_coder_core.py CHANGED
@@ -1,553 +1,209 @@
 
 
 
1
  import re, json
2
  from typing import Dict, Any, List, Tuple
3
 
4
- # ---------- helpers ----------
 
 
5
  def _norm(s: str) -> str:
6
- if not s: return ""
7
- s = s.replace("\u2013","-").replace("\u2014","-").replace("β€”","-").replace("–","-").replace("\u00A0"," ")
8
- s = re.sub(r"\s+", " ", s.strip().lower())
9
- return s
 
 
10
 
11
  def _has(text: str, pat: str) -> bool:
12
  return re.search(pat, text, flags=re.I) is not None
13
 
14
- def _level_sort_key(lv: str) -> Tuple[int, int]:
15
- band = {"C":100, "T":200, "L":300, "S":400}.get(lv[0], 999)
16
- num = int(re.sub(r"\D", "", lv) or 0)
17
- return (band, num)
18
-
19
- def _extract_levels(t: str) -> List[str]:
20
- levels = set()
21
- # Ranges (e.g., L4-5, C4–C7). If prefix changes (C->T), capture both endpoints.
22
- for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", t, flags=re.I):
23
- pfx1, n1, pfx2, n2 = m.group(1).upper(), int(m.group(2)), (m.group(3) or m.group(1)).upper(), int(m.group(4))
24
- if pfx1 != pfx2:
25
- levels.add(f"{pfx1}{n1}"); levels.add(f"{pfx2}{n2}")
 
 
 
 
 
 
 
 
 
26
  else:
27
- lo, hi = sorted([n1, n2])
28
- for k in range(lo, hi+1): levels.add(f"{pfx1}{k}")
29
- # Singles (e.g., L4, T10)
30
- for m in re.finditer(r"\b([CTLS])\s?(\d{1,2})\b", t, flags=re.I):
 
 
 
 
 
 
 
 
 
 
 
31
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
32
- return sorted(levels, key=_level_sort_key)
33
 
34
- def _count_interspaces(levels: List[str]) -> int:
35
  if not levels: return 0
36
- by_band: Dict[str, List[int]] = {}
37
- for lv in levels:
38
- by_band.setdefault(lv[0], []).append(int(re.sub(r"\D", "", lv)))
39
- for b in list(by_band):
40
- by_band[b] = sorted(set(by_band[b]))
41
- inters = 0
42
- for arr in by_band.values():
43
- if len(arr) >= 2:
44
- inters += sum(1 for i in range(1, len(arr)) if arr[i]-arr[i-1]==1)
45
- # L5–S1 interspace
46
- if "L5" in levels and "S1" in levels: inters += 1
47
- # Conservative fallback for multi-level with gaps
48
- if inters == 0:
49
- for arr in by_band.values():
50
- if len(arr) >= 2:
51
- inters = 1
52
- break
53
- return inters
54
 
55
- def _span_segments(levels: List[str]) -> int:
56
- if not levels: return 0
57
- by_band: Dict[str, List[int]] = {}
58
  for lv in levels:
59
- by_band.setdefault(lv[0], []).append(int(re.sub(r"\D", "", lv)))
60
- spans = []
61
- for arr in by_band.values():
62
- arr = sorted(set(arr))
63
- spans.append(arr[-1]-arr[0]+1 if len(arr)>=2 else 1)
64
- # If multiple bands, take max; ensure at least 2 for instrumentation estimation in mixed cases
65
- return (max(spans) if spans else 0) if len(spans)==1 else max(spans + [2])
66
-
67
- def _laterality_mods(t: str) -> List[str]:
68
- mods: List[str] = []
69
- if _has(t, r"\bleft\b|left-sided|left side|leftward|hemilam\w* left|foraminotomy left|\blt\b"):
70
- mods.append("LT")
71
- # Avoid bare \brt\b to reduce false matches
72
- if _has(t, r"\bright\b|right-sided|right side|rightward|hemilam\w* right|foraminotomy right"):
73
- mods.append("RT")
74
- return list(sorted(set(mods)))
75
-
76
- def _add(suggestions: List[Dict[str, Any]], cpt: str, desc: str, rationale: str,
77
- category: str, primary: bool, confidence: float = 0.8, units: int = 1,
78
- mods: List[str] = None, score: float = None):
79
- suggestions.append({
80
- "cpt": cpt, "desc": desc, "rationale": rationale, "category": category,
81
- "primary": primary, "confidence": confidence, "units": units,
82
- "modifiers": mods or [], "score": score if score is not None else confidence
83
- })
84
-
85
- def _inst_code_by_span(span_segments: int, anterior: bool) -> str:
86
- if span_segments <= 1: return ""
 
 
 
 
 
 
 
 
 
 
 
 
87
  if anterior:
88
- return "22845" if span_segments <= 3 else ("22846" if span_segments <= 7 else "22847")
89
  else:
90
- return "22842" if span_segments <= 3 else ("22843" if span_segments <= 7 else "22844")
91
-
92
- # ---------- main ----------
93
- def suggest_with_cpt_billing(note: str, payer: str = "Medicare", top_k: int = 10) -> Dict[str, Any]:
94
- t = _norm(note)
95
- suggestions: List[Dict[str, Any]] = []
96
- case_modifiers: List[Dict[str, str]] = []
97
-
98
- # --- Early guard: exposure-only / access-only -----------------------------
99
- # Flexible: catches "anterior exposure of L4–S1 performed by vascular surgeon ... No fusion performed"
100
- exposure_only = (
101
- _has(t, r"\bexpos(e|ed|ure)\b|\bapproach\b") and
102
- _has(t, r"\banterior\b|retroperitoneal|vascular (surgeon|exposure)|access\b") and
103
- _has(t, r"\bno (fusion|arthrodesis|interbody|implants?|cage|plate|screw|instrumentation)\b|without (fusion|interbody|instrumentation)") and
104
- not _has(t, r"\bfusion|arthrodesis|discectom\w+|interbody|tlif|plif|alif|acdf|arthroplasty|stimulator|instrument|hardware|screw|rod|plate|cage")
105
- )
106
- if exposure_only:
107
- return {
108
- "payer": payer,
109
- "region": "unknown",
110
- "levels": [],
111
- "interspaces_est": 0,
112
- "span_segments_est": 0,
113
- "suggestions": [{
114
- "cpt": "00000",
115
- "desc": "No recognizable spine CPT pattern found",
116
- "rationale": "Exposure-only access without decompression/fusion/instrumentation.",
117
- "confidence": 0.0, "category": "none", "primary": True, "modifiers": [], "units": 1, "score": 0.0
118
- }],
119
- "case_modifiers": [],
120
- "flags": [],
121
- "flags_map": {},
122
- "laterality": "na",
123
- "build": "FINAL-v2.1",
124
- "mode": "standard",
125
- }
126
-
127
- # --- Levels / spans / laterality -----------------------------------------
128
- levels = _extract_levels(t)
129
- inters = _count_interspaces(levels)
130
- span = _span_segments(levels)
131
- lat_mods = _laterality_mods(t)
132
-
133
- # ---------- Region ----------
134
- region = "unknown"
135
- if any(l.startswith("C") for l in levels) or _has(t, r"\bcervic"):
136
- region = "cervical"
137
- elif any(l.startswith("T") for l in levels) or _has(t, r"\bthorac"):
138
- region = "thoracic"
139
- elif any(l.startswith(("L","S")) for l in levels) or _has(t, r"\blumbar|sacrum"):
140
- region = "lumbar"
141
-
142
- # Transitional cervicothoracic override
143
- has_c7 = any(l.upper() == "C7" for l in levels) or _has(t, r"\bc7\b")
144
- has_t1_3 = any(l.upper() in {"T1","T2","T3"} for l in levels) or _has(t, r"\bt[1-3]\b")
145
- if has_c7 and has_t1_3:
146
- region = "cervicothoracic"
147
-
148
- # Helper flags
149
- mentions_plate_ant = _has(t, r"\b(anterior (plate|instrument(ation)?)|plate fixed|plating)\b") or _has(t, r"\bplate\b")
150
- mentions_pedicle = _has(t, r"\bpedicle screw|pedicle-screw|pedicle fixation|s2ai\b")
151
- mentions_rod = _has(t, r"\brod(s)?\b")
152
- mentions_inst_post = mentions_pedicle or mentions_rod or _has(t, r"\binstrument(ation)?\b")
153
-
154
- did_corpectomy = False
155
- no_decompression_due_to_revision = False
156
- avoid_decompression_for_stimulator = False
157
-
158
- # ---------- (1) Hardware removal (support both word orders) ----------
159
- rem_pat_either = (
160
- r"(?:\b(hardware|instrumentation|implant|plate|rod|screw)\b.{0,40}\b(remov\w+|explant\w+|taken out))"
161
- r"|(?:\bremov\w+\b.{0,40}\b(hardware|instrumentation|implant|plate|rod|screw)\b)"
162
- )
163
- if _has(t, rem_pat_either):
164
- if _has(t, r"\banterior\b|acdf|alif|anterior plate|smith[- ]?robinson"):
165
- _add(suggestions, "22855", "Removal of anterior instrumentation",
166
- "Anterior hardware removal documented.", "hardware_removal", True, 0.85, mods=lat_mods)
167
- elif _has(t, r"non[- ]?segmental|hook|wire") and not _has(t, r"segmental|pedicle"):
168
- _add(suggestions, "22850", "Removal of posterior non-segmental instrumentation",
169
- "Posterior non-segmental removal documented.", "hardware_removal", True, 0.82, mods=lat_mods)
170
- else:
171
- _add(suggestions, "22852", "Removal of posterior segmental instrumentation",
172
- "Posterior segmental hardware removal documented.", "hardware_removal", True, 0.85, mods=lat_mods)
173
-
174
- # If remove + replace/reinsert/revision language β†’ add 22849 and avoid decompression auto-fires
175
- if _has(t, r"\b(reinsertion|remove and replace|revision of instrumentation|hardware revision)\b"):
176
- _add(suggestions, "22849", "Reinsertion/revision of spinal instrumentation",
177
- "Revision/reinsertion documented.", "revision", True, 0.85)
178
- no_decompression_due_to_revision = True
179
-
180
- # ---------- (2b) New posterior instrumentation WITHOUT explicit fusion ----------
181
- if (
182
- not _has(t, r"\btlif\b|\bplif\b|posterolateral\b.*\bfusion|posterior\b.*\bfusion")
183
- and (mentions_inst_post or _has(t, r"\b(new|placed|inserted|reinserted)\b.{0,20}\b(screw|rod|construct|instrumentation)\b"))
184
- and region in {"cervical","thoracic","lumbar"}
185
- ):
186
- est_segments = span or max(2, inters + 1)
187
- code = _inst_code_by_span(est_segments, anterior=False)
188
- if code:
189
- desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
190
- "22843":"Posterior segmental instrumentation (4–7 segments)",
191
- "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
192
- _add(suggestions, code, desc, "New posterior instrumentation documented.", "instrumentation", False, 0.82)
193
-
194
- # ---------- (2) ACDF with guards (history/removal) + broadened trigger ----------
195
- found_removal = _has(t, rem_pat_either)
196
- acdf_history = _has(t, r"\b(prior|previous|history of|s/?p)\s*acdf\b")
197
- acdf_current = (
198
- _has(t, r"\banterior cervical discectomy|anterior cervical fusion|smith[- ]?robinson\b")
199
- or (
200
- (region in {"cervical","cervicothoracic"})
201
- and _has(t, r"\bdiscectom\w+\b")
202
- and (_has(t, r"\b(interbody|cage|arthrodesis|plate)\b") or mentions_plate_ant)
203
- )
204
- or _has(t, r"\bacdf\b")
205
- )
206
-
207
- if acdf_current and not (found_removal and not _has(t, r"\bacdf\b")) and not exposure_only:
208
- n = max(1, inters or 1)
209
- _add(suggestions, "22551", "ACDF, first interspace (includes discectomy)",
210
- "Anterior cervical fusion pattern detected.", "ACDF", True, 0.95, mods=lat_mods)
211
- if n > 1:
212
- _add(suggestions, "22552", f"ACDF, each additional interspace Γ—{n-1}",
213
- "Multi-level ACDF.", "ACDF add-on", False, 0.9, units=(n-1), mods=lat_mods)
214
-
215
- # Plate span inference: "spanning C4–C7" or "span C4–C7"
216
- m_span = re.search(r"\bspan\w*\s*(c\d)\s*[-–—]\s*(c\d)\b", t, flags=re.I)
217
- plate_span_est = None
218
- if m_span:
219
- try:
220
- c_lo = int(re.sub(r"\D","", m_span.group(1)))
221
- c_hi = int(re.sub(r"\D","", m_span.group(2)))
222
- if c_hi >= c_lo:
223
- plate_span_est = c_hi - c_lo + 1
224
- except Exception:
225
- plate_span_est = None
226
-
227
- if mentions_plate_ant:
228
- est_span = (plate_span_est if plate_span_est and plate_span_est >= 2
229
- else (span if (span and span >= 2) else (n + 1)))
230
- code = _inst_code_by_span(est_span, anterior=True)
231
- if code:
232
- desc = {"22845":"Anterior instrumentation (2–3 segments)",
233
- "22846":"Anterior instrumentation (4–7 segments)",
234
- "22847":"Anterior instrumentation (8+ segments)"}[code]
235
- _add(suggestions, code, desc, "Anterior plate present; span estimated from levels/plate span.", "instrumentation", False, 0.8)
236
-
237
- # ---------- (3a) Implicit TLIF when keywords imply the construct ----------
238
- if (
239
- not _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion")
240
- and _has(t, r"\bfacetectom(y|ies)\b|complete facetectomy|hemifacetectomy|transforaminal")
241
- and _has(t, r"\binterbody (cage|device|spacer)\b|peek (cage|spacer)|titanium (cage|spacer)|allograft spacer")
242
- and (mentions_pedicle or _has(t, r"\bpedicle screw(s)?\b"))
243
- and region in {"lumbar", "thoracic"}
244
- ):
245
- n = max(1, inters or 1)
246
- _add(suggestions, "22633", "Posterior/posterolateral + posterior interbody, single level",
247
- "Implicit TLIF/PLIF: facetectomy + interbody device + pedicle screws.", "TLIF/PLIF", True, 0.93, mods=lat_mods)
248
- if n > 1:
249
- _add(suggestions, "22634", f"Posterior interbody each additional interspace Γ—{n-1}",
250
- "Multi-level implicit TLIF/PLIF.", "TLIF/PLIF add-on", False, 0.89, units=(n-1), mods=lat_mods)
251
- code = _inst_code_by_span(span or (n+1), anterior=False)
252
- if code:
253
- desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
254
- "22843":"Posterior segmental instrumentation (4–7 segments)",
255
- "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
256
- _add(suggestions, code, desc, "Posterior instrumentation detected (implicit TLIF).", "instrumentation", False, 0.83)
257
-
258
- # ---------- (3) TLIF / PLIF (beats posterior fusion) ----------
259
- if _has(t, r"\btlif\b|\bplif\b|posterior interbody fusion"):
260
- n = max(1, inters or 1)
261
- _add(suggestions, "22633", "Posterior/posterolateral + posterior interbody, single level",
262
- "TLIF/PLIF pattern: posterior approach + cage + screws.", "TLIF/PLIF", True, 0.92, mods=lat_mods)
263
- if n > 1:
264
- _add(suggestions, "22634", f"Posterior interbody each additional interspace Γ—{n-1}",
265
- "Multi-level TLIF/PLIF.", "TLIF/PLIF add-on", False, 0.88, units=(n-1), mods=lat_mods)
266
- if mentions_inst_post:
267
- code = _inst_code_by_span(span or (n+1), anterior=False)
268
- if code:
269
- desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
270
- "22843":"Posterior segmental instrumentation (4–7 segments)",
271
- "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
272
- _add(suggestions, code, desc, "Posterior instrumentation detected.", "instrumentation", False, 0.82)
273
-
274
- # ---------- (4) ALIF ----------
275
- if _has(t, r"\balif\b|anterior lumbar interbody fusion"):
276
- n = max(1, inters or 1)
277
- _add(suggestions, "22558", "Anterior lumbar interbody fusion, single interspace",
278
- "ALIF detected.", "ALIF", True, 0.9)
279
- if n > 1:
280
- _add(suggestions, "22585", f"ALIF each additional interspace Γ—{n-1}",
281
- "Multi-level ALIF.", "ALIF add-on", False, 0.86, units=(n-1))
282
- if mentions_plate_ant:
283
- est_span = span if (span and span >= 2) else (n + 1)
284
- code = _inst_code_by_span(est_span, anterior=True)
285
- if code:
286
- desc = {"22845":"Anterior instrumentation (2–3 segments)",
287
- "22846":"Anterior instrumentation (4–7 segments)",
288
- "22847":"Anterior instrumentation (8+ segments)"}[code]
289
- _add(suggestions, code, desc, "Anterior plate present; span estimated from levels.", "instrumentation", False, 0.8)
290
-
291
- # ---------- (5) Posterolateral/posterior fusion WITHOUT interbody ----------
292
- if (_has(t, r"posterolateral\b.*\bfusion|post[- ]?lat\b.*\bfusion|posterior\b.*\bfusion|in situ\b.*\bfusion")
293
- and not _has(t, r"\btlif\b|\bplif\b|posterior interbody")):
294
- base_map = {"cervical":"22600", "thoracic":"22610", "lumbar":"22612"}
295
- base = base_map.get(region, "22612")
296
- _add(suggestions, base, f"Posterolateral/posterior fusion, first level ({region})",
297
- "Posterior fusion without interbody.", "posterior_fusion", True, 0.78, mods=lat_mods)
298
- add_units = max(0, inters)
299
- if add_units:
300
- _add(suggestions, "22614", f"Posterior fusion each additional segment Γ—{add_units}",
301
- "Multi-level posterior fusion.", "posterior_fusion add-on", False, 0.72, units=add_units, mods=lat_mods)
302
- if mentions_inst_post:
303
- n_seg = span or max(2, (inters + 1))
304
- code = _inst_code_by_span(n_seg, anterior=False)
305
- if code:
306
- desc = {"22842":"Posterior segmental instrumentation (2–3 segments)",
307
- "22843":"Posterior segmental instrumentation (4–7 segments)",
308
- "22844":"Posterior segmental instrumentation (8+ segments)"}[code]
309
- _add(suggestions, code, desc, "Posterior instrumentation detected.", "instrumentation", False, 0.8)
310
-
311
- # ---------- (6) Neurostimulator paddle + IPG ----------
312
- if _has(t, r"\bstimulator\b|paddle lead|spinal cord stimulator|scs") and _has(t, r"\blaminectomy\b|laminotomy"):
313
- _add(suggestions, "63655", "Laminectomy for implantation of neurostimulator paddle electrodes",
314
- "Paddle lead placed via laminectomy.", "neurostimulator", True, 0.86)
315
- if _has(t, r"\bpulse generator\b|ipg|implantable pulse generator|battery\b"):
316
- _add(suggestions, "63685", "Insertion or replacement of IPG",
317
- "Pulse generator implanted.", "neurostimulator", False, 0.84)
318
- avoid_decompression_for_stimulator = True
319
-
320
- # ---------- (7) Decompression (guarded: no history/corpectomy/revision/stimulator) ----------
321
- if (not no_decompression_due_to_revision) and (not avoid_decompression_for_stimulator):
322
- if _has(t, r"\bdecompression(s)?\b|laminectom(y|ies)|laminotom(y|ies)|foraminotom(y|ies)|foraminal decompression(s)?|neuroforamen|lateral recess|central stenosis") \
323
- and not _has(t, r"\b(prior|previous|history of|s/?p)\s+(decompression|laminectomy|laminotomy|foraminotomy)\b"):
324
- base_map = {"cervical":"63045", "thoracic":"63046", "lumbar":"63047"}
325
- base = base_map.get(region, "63047")
326
- _add(suggestions, base, "Decompression, first level", "Decompression terms detected.", "decompression", True, 0.82, mods=lat_mods)
327
- if _has(t, r"additional level|two levels|three levels|multi|levels|l\d-?l\d|c\d-?c\d|t\d-?t\d"):
328
- extra = max(1, inters)
329
- _add(suggestions, "63048", f"Decompression, each additional level Γ—{extra}",
330
- "Multi-level decompression inferred.", "decompression add-on", False, 0.78, units=extra, mods=lat_mods)
331
-
332
- # ---------- (8) Interspinous/interlaminar device ----------
333
- if _has(t, r"\bcoflex\b|interspinous|interlaminar device|ipd"):
334
- _add(suggestions, "22868", "Insertion of interspinous/interlaminar device",
335
- "Coflex/IPD insertion documented.", "interspinous", True, 0.86, mods=lat_mods)
336
-
337
- # ---------- (9) Cervical disc arthroplasty ----------
338
- if _has(t, r"cervical (arthroplasty|artificial disc|disc replacement)|\bcda\b") or (
339
- (region == "cervical") and _has(t, r"\barthroplasty\b|artificial disc|disc replacement")
340
- ):
341
- n = max(1, inters or 1)
342
- _add(suggestions, "22856", "Cervical disc arthroplasty, single level",
343
- "Cervical disc arthroplasty documented.", "arthroplasty", True, 0.87, mods=lat_mods)
344
- if n > 1:
345
- _add(suggestions, "22858", f"Cervical disc arthroplasty, each additional level Γ—{n-1}",
346
- "Multi-level arthroplasty.", "arthroplasty add-on", False, 0.84, units=(n-1), mods=lat_mods)
347
-
348
- # ---------- (10) Tumor / corpectomy ----------
349
- # Cervical corpectomy
350
- if _has(t, r"\bcorpectomy\b") and (region == "cervical" or _has(t, r"\bcervic")):
351
- _add(suggestions, "63081", "Cervical corpectomy for decompression, first segment",
352
- "Cervical corpectomy documented.", "tumor/corpectomy", True, 0.86)
353
- if _has(t, r"additional (level|segment)|two levels|three levels|multi|c\d-?c\d"):
354
- extra = max(1, inters or 1)
355
- _add(suggestions, "63082", f"Each additional cervical segment Γ—{extra}",
356
- "Multi-segment cervical corpectomy.", "tumor/corpectomy add-on", False, 0.82, units=extra)
357
- did_corpectomy = True
358
- # Thoracic/Lumbar corpectomy (non-cervical)
359
- if _has(t, r"\btumou?r\b|metastatic|metastasis|en bloc") or (_has(t, r"\bcorpectomy\b") and region in {"thoracic","lumbar"}):
360
- _add(suggestions, "63085", "Vertebral corpectomy for decompression (thoracic/lumbar), first segment",
361
- "Tumor/corpectomy documented.", "tumor/corpectomy", True, 0.84)
362
- if _has(t, r"additional (level|segment)|two levels|three levels|multi|t\d-?t\d|l\d-?l\d"):
363
- _add(suggestions, "63086", "Each additional segment (thoracic/lumbar) Γ—1+",
364
- "Multi-segment corpectomy.", "tumor/corpectomy add-on", False, 0.8)
365
- did_corpectomy = True
366
-
367
- # If corpectomy present, suppress decompression rows
368
- if did_corpectomy:
369
- suggestions = [r for r in suggestions if r["category"] != "decompression" and r["cpt"] not in {"63045","63046","63047","63048"}]
370
-
371
- # ---------- (11) Deformity / Smith-Petersen osteotomy ----------
372
- if _has(t, r"\bspo\b|smith[- ]?petersen|posterior column osteotomy|osteotomy"):
373
- base_map = {"cervical":"22210", "thoracic":"22214", "lumbar":"22206"}
374
- base = base_map.get(region, "22206")
375
- _add(suggestions, base, "Posterior column osteotomy (Smith-Petersen), first level",
376
- "Deformity correction with SPO documented.", "deformity", True, 0.83)
377
- if _has(t, r"additional (level|segment)|two levels|three levels|multi"):
378
- _add(suggestions, str(int(base)+1), "Each additional vertebral segment Γ—1+",
379
- "Multi-level osteotomy.", "deformity add-on", False, 0.78)
380
-
381
- # ---------- (12) Kyphoplasty / vertebral augmentation ----------
382
- if _has(t, r"\bkyphoplasty\b|vertebroplasty|cement (augmentation|injection)"):
383
- base_map = {"cervical":"22510", "thoracic":"22513", "lumbar":"22514"}
384
- base = base_map.get(region, "22514")
385
- _add(suggestions, base, "Percutaneous vertebral augmentation, first level",
386
- "Vertebral augmentation terms present.", "percutaneous", True, 0.82)
387
- if _has(t, r"additional (level|segment)|two levels|three levels|multi"):
388
- _add(suggestions, "22515", "Each additional vertebral body Γ—1+",
389
- "Multi-level augmentation.", "percutaneous add-on", False, 0.8)
390
-
391
- # ---------- (13) Pelvic fixation ----------
392
- if _has(t, r"\bpelvic fixation\b|iliac bolt|iliac screw|s2ai|to pelvis\b"):
393
- _add(suggestions, "22848", "Pelvic fixation (attach instrumentation to pelvis)",
394
- "Iliac/pelvic fixation documented.", "pelvic_fixation", False, 0.84)
395
-
396
- # ---------- (14) Bone grafts (prefer structural 20931 over 20930) ----------
397
- structural_like = _has(t, r"\b(structural allograft|fibular strut|strut graft|allograft spacer(s)?|interbody allograft spacer(s)?)\b")
398
- any_allograft = _has(t, r"\ballograft\b|dbm|demineralized|osteo.?promotive|morselized local bone|local autograft")
399
-
400
- if _has(t, r"\bautograft\b|iliac crest harvest|icbg|iliac crest bone"):
401
- _add(suggestions, "20937", "Autograft (separate incision)",
402
- "Autograft harvest documented.", "bone_graft", False, 0.8)
403
-
404
- if structural_like:
405
- _add(suggestions, "20931", "Structural allograft",
406
- "Structural allograft documented.", "bone_graft", False, 0.8)
407
- elif any_allograft:
408
- _add(suggestions, "20930", "Allograft, morselized / osteopromotive material",
409
- "Allograft/DBM documented.", "bone_graft", False, 0.8)
410
-
411
- if _has(t, r"\bbmp\b|biologic|recombinant growth factor|rhbmp"):
412
- _add(suggestions, "20939", "Osteopromotive biologic (e.g., BMP)",
413
- "BMP/biologic documented.", "bone_graft", False, 0.8)
414
-
415
- # ---------- Case Modifiers (case-level) ----------
416
- mod_map = {
417
- "22": r"\b(complex|difficult|prolonged|adhesiolysis|severe deformity|morbid obesity)\b",
418
- "50": r"\b(both sides|bilaterally|bilateral(?!\s*(pedicle|screw|screws|rod|rods|instrumentation)))\b",
419
- "52": r"\bpartial|limited|reduced service\b",
420
- "53": r"\baborted|terminated|discontinued\b",
421
- "59": r"\bseparate (site|incision)|distinct|different level\b",
422
- "62": r"\bco[- ]?surgeon|two surgeons|co-surgeons\b",
423
- "78": r"\breturn to or\b|\bunplanned return\b",
424
- "79": r"\bunrelated procedure\b",
425
- "80": r"\bassistant surgeon|assistant present\b",
426
- "82": r"\bresident not available\b"
427
- }
428
- reasons = {
429
- "22": "Increased procedural service (complexity).",
430
- "50": "Bilateral procedure documented.",
431
- "52": "Reduced service (limited portion performed).",
432
- "53": "Procedure discontinued for safety.",
433
- "59": "Distinct procedural service (separate site/level).",
434
- "62": "Two surgeons (co-surgeons) documented.",
435
- "78": "Unplanned return to OR during global period.",
436
- "79": "Unrelated procedure during postoperative period.",
437
- "80": "Assistant surgeon documented.",
438
- "82": "Assistant surgeon; resident not available."
439
  }
440
- for k, pat in mod_map.items():
441
- if _has(t, pat): case_modifiers.append({"modifier": k, "reason": reasons[k]})
442
-
443
- # Force -50 if 'bilateral' appears near decompression nouns and not already set
444
- if not any(m["modifier"]=="50" for m in case_modifiers):
445
- if _has(t, r"\bbilateral\b") and _has(t, r"(foraminotom(y|ies)|facetectom(y|ies)|laminectom(y|ies)|laminotom(y|ies))"):
446
- case_modifiers.append({"modifier":"50","reason": reasons["50"]})
447
-
448
- # --- Modifier sanity rules ---
449
- if any(m["modifier"]=="82" for m in case_modifiers):
450
- case_modifiers = [m for m in case_modifiers if m["modifier"]!="80"]
451
- if any(m["modifier"]=="53" for m in case_modifiers):
452
- case_modifiers = [m for m in case_modifiers if m["modifier"]!="52"]
453
-
454
- # ---------- Default guard ----------
455
- if not suggestions:
456
- suggestions = [{
457
- "cpt": "00000",
458
- "desc": "No recognizable spine CPT pattern found",
459
- "rationale": "No fusion/decompression pattern detected.",
460
- "confidence": 0.0, "category": "none", "primary": True, "modifiers": [], "units": 1, "score": 0.0
461
- }]
462
-
463
- # ---------- De-dup ----------
464
- merged: Dict[Tuple[str, str, str, str], Dict[str, Any]] = {}
465
- for r in suggestions:
466
- key = (r["cpt"], r["desc"], r["category"], "|".join(sorted(r.get("modifiers", []))))
467
- if key not in merged:
468
- merged[key] = r.copy()
469
- else:
470
- merged[key]["units"] = merged[key].get("units", 1) + r.get("units", 1)
471
- merged[key]["confidence"] = max(merged[key]["confidence"], r.get("confidence", 0.0))
472
- merged[key]["score"] = max(merged[key]["score"], r.get("score", 0.0))
473
- merged[key]["primary"] = merged[key]["primary"] or r.get("primary", False)
474
-
475
- out = list(merged.values())
476
-
477
- # --- Propagate case-level -53 (discontinued) onto primary rows only ---
478
- has_53 = any(m["modifier"] == "53" for m in case_modifiers)
479
- if has_53:
480
- for r in out:
481
- if r.get("primary", False):
482
- r.setdefault("modifiers", [])
483
- if "53" not in r["modifiers"]:
484
- r["modifiers"].append("53")
485
- # ensure add-ons don't inherit -53
486
- for r in out:
487
- if not r.get("primary", False) and r.get("modifiers"):
488
- r["modifiers"] = [m for m in r["modifiers"] if m != "53"]
489
-
490
- # --- Apply laterality to rows; strip when case-level bilateral (-50) present ---
491
- has_50 = any(m["modifier"] == "50" for m in case_modifiers)
492
- lat_row_mods = [] if has_50 else lat_mods
493
- for row in out:
494
- if row["category"] in {"decompression","decompression add-on","TLIF/PLIF","posterior_fusion","posterior_fusion add-on"}:
495
- if lat_row_mods and not row.get("modifiers"):
496
- row["modifiers"] = lat_row_mods[:]
497
- if has_50:
498
- for row in out:
499
- if row.get("modifiers"):
500
- row["modifiers"] = [m for m in row["modifiers"] if m not in ("LT","RT")]
501
-
502
- # ---------- Order ----------
503
- def _cpt_num(x: Dict[str, Any]) -> int:
504
- try: return int(re.sub(r"\D","", x.get("cpt","0")) or 0)
505
- except: return 0
506
- out.sort(key=lambda r: (not r.get("primary", False), -(r.get("score", r.get("confidence",0.0))), _cpt_num(r)))
507
- if isinstance(top_k, int) and top_k > 0:
508
- out = out[:top_k]
509
-
510
- # ---------- Tech flags (microscope / navigation / neuromonitoring / fluoro) ----------
511
- flags_map = {
512
- "microscope": _has(t, r"\bmicroscope\b|microdissection"),
513
- "nav": _has(t, r"\bnavigation\b|o-?arm|stealth|mazor|7d|image[- ]?guided"),
514
- "io_monitor": _has(t, r"\bneuromonitor\w*|ssep|tcem|tcme|emg\b|\bintra[- ]?op(erative)? monitoring\b"),
515
- "fluoro": _has(t, r"\bfluoro\w*|c[- ]?arm\b|fluoroscop\w*"),
516
- }
517
- flags_list = [k for k, v in flags_map.items() if v]
518
-
519
- # ---------- Case-level laterality (meta) ----------
520
- bilateral_procedure_pat = (
521
- r"\bboth sides\b"
522
- r"|"
523
- r"\bbilateral\b(?!\s*(pedicle|screw|screws|rod|rods|instrument|instrumentation|hardware|construct))"
524
- )
525
- case_laterality = "na"
526
- if _has(t, r"\bleft[- ]sided|\bleft\b"):
527
- case_laterality = "left"
528
- elif _has(t, r"\bright[- ]sided|\bright\b"):
529
- case_laterality = "right"
530
- elif _has(t, bilateral_procedure_pat):
531
- case_laterality = "bilateral"
532
-
533
- # ---------- OPTIONAL: surface tech flags in primary rationale ----------
534
- if flags_list:
535
- for r in out:
536
- if r.get("primary", False):
537
- r["rationale"] = (r.get("rationale","") + f" (Tech: {', '.join(flags_list)})").strip()
538
- break
539
 
540
  return {
541
- "payer": payer,
542
- "region": region,
543
- "levels": levels,
544
- "interspaces_est": inters,
545
- "span_segments_est": span,
546
- "suggestions": out,
547
- "case_modifiers": case_modifiers,
548
- "flags": flags_list,
549
- "flags_map": flags_map,
550
- "laterality": case_laterality,
551
- "build": "FINAL-v2.1",
552
- "mode": "standard",
553
  }
 
 
 
 
 
 
 
 
 
 
 
1
+ # spine_coder_core.py
2
+ # Vertebro FINAL-v2.2 β€” Production Hybrid Engine
3
+
4
  import re, json
5
  from typing import Dict, Any, List, Tuple
6
 
7
+ # ─────────────────────────────────────────────
8
+ # NORMALIZATION UTILITIES
9
+ # ─────────────────────────────────────────────
10
  def _norm(s: str) -> str:
11
+ if not s:
12
+ return ""
13
+ s = (s.replace("\u2013", "-").replace("\u2014", "-")
14
+ .replace("β€”", "-").replace("–", "-")
15
+ .replace("\u00A0", " "))
16
+ return re.sub(r"\s+", " ", s.strip().lower())
17
 
18
  def _has(text: str, pat: str) -> bool:
19
  return re.search(pat, text, flags=re.I) is not None
20
 
21
+ # ─────────────────────────────────────────────
22
+ # LEVEL / 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
+ rank={"C":100,"T":200,"L":300,"S":400}
29
+ return (rank.get(lv[0].upper(),999), int(re.sub(r"\D","",lv) or 0))
30
+
31
+ _SPAN = re.compile(r"\b([CTLS])\s?(\d{1,2})\s*[-–]\s*([CTLS])?\s?(\d{1,2})\b", re.I)
32
+ _SINGLE = re.compile(r"\b([CTLS])\s?(\d{1,2})\b", re.I)
33
+
34
+ def _expand_across_regions(p1:str,n1:int,p2:str,n2:int)->List[str]:
35
+ p1,p2=p1.upper(),p2.upper()
36
+ out=[]; i=_ORDER.index(p1); r=i; num=n1
37
+ while True:
38
+ out.append(f"{_ORDER[r]}{num}")
39
+ if _ORDER[r]==p2 and num==n2: break
40
+ if num<_MAXNUM[_ORDER[r]]:
41
+ num+=1
42
  else:
43
+ r+=1
44
+ if r>=len(_ORDER): break
45
+ num=1
46
+ return out
47
+
48
+ def _extract_levels(t:str)->List[str]:
49
+ t=_norm(t); levels=set()
50
+ for m in _SPAN.finditer(t):
51
+ p1,n1=m.group(1).upper(),int(m.group(2))
52
+ p2=(m.group(3) or p1).upper(); n2=int(m.group(4))
53
+ if p1==p2:
54
+ for k in range(min(n1,n2),max(n1,n2)+1): levels.add(f"{p1}{k}")
55
+ else:
56
+ for lv in _expand_across_regions(p1,n1,p2,n2): levels.add(lv)
57
+ for m in _SINGLE.finditer(t):
58
  levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
59
+ return sorted(levels,key=_level_sort_key)
60
 
61
+ def _count_interspaces(levels:List[str])->int:
62
  if not levels: return 0
63
+ return max(0,len(sorted(set(levels),key=_level_sort_key))-1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
+ def _classify_region(levels:List[str])->str:
66
+ b={"C":False,"T":False,"L":False,"S":False}
 
67
  for lv in levels:
68
+ if lv: b[lv[0].upper()]=True
69
+ c,t,l,s=b["C"],b["T"],b["L"],b["S"]
70
+ if c and t and not (l or s): return "cervicothoracic"
71
+ if t and l and not (c or s): return "thoracolumbar"
72
+ if l and s and not (c or t): return "lumbosacral"
73
+ if c and not (t or l or s): return "cervical"
74
+ if t and not (c or l or s): return "thoracic"
75
+ if l and not (c or t or s): return "lumbar"
76
+ if s and not (c or t or l): return "sacral"
77
+ return "mixed"
78
+
79
+ # ─────────────────────────────────────────────
80
+ # LATERALITY / MODIFIERS
81
+ # ─────────────────────────────────────────────
82
+ def _laterality_and_modifiers(note:str)->Tuple[str,List[str]]:
83
+ t=_norm(note)
84
+ midline_kw=[
85
+ r"bilateral medial facetectom",r"bilateral foraminotom",
86
+ r"bilateral decompression",r"bilateral pedicle screws",
87
+ r"bilateral rods?",r"left hemilaminectomy",r"right hemilaminectomy"
88
+ ]
89
+ if any(_has(t,p) for p in midline_kw):
90
+ return "midline",[]
91
+ return "midline",[]
92
+
93
+ # ─────────────────────────────────────────────
94
+ # FLAG PATTERNS
95
+ # ─────────────────────────────────────────────
96
+ FUSION_KW=r"\b(arthrodesis|fusion|t?lif|alif|plif|xlif|interbody\s+cage|peek\s+cage|structural\s+cage)\b"
97
+ INSTR_KW=r"\b(pedicle\s+screws?|lateral\s+mass\s+screws?|rods?|set\s+screws?|instrument(?:ed|ation))\b"
98
+ NAV_KW=r"\b(navigation|navigated|o-?arm|ziehm|3d\s+spin)\b"
99
+ ALLO_KW=r"\b(allograft|dbm|demineralized\s+bone\s+matrix)\b"
100
+ AUTO_LOCAL_KW=r"\b(local\s+autograft|spinous\s+process\s+bone|lamina\s+bone\s+retained|morselized\s+autograft)\b"
101
+ AUTO_SEP_KW=r"\b(iliac\s+crest|separate\s+incision|rib\s+graft|harvested\s+from\s+iliac)\b"
102
+
103
+ # ─────────────────────────────────────────────
104
+ # INSTRUMENTATION SPAN HELPER
105
+ # ─────────────────────────────────────────────
106
+ def _inst_code_by_span(span:int, anterior:bool)->str:
107
+ if span<=1: return ""
108
  if anterior:
109
+ return "22845" if span<=3 else ("22846" if span<=7 else "22847")
110
  else:
111
+ return "22842" if span<=3 else ("22843" if span<=7 else "22844")
112
+
113
+ # ─────────────────────────────────────────────
114
+ # PRIMARY CPT LOGIC (streamlined from v2.1)
115
+ # ─────────────────────────────────────────────
116
+ def _infer_cpts(note:str, region:str, levels:List[str])->List[str]:
117
+ t=_norm(note); cpts=[]
118
+ inters=_count_interspaces(levels)
119
+
120
+ # Decompression
121
+ if re.search(r"laminectomy|decompression|facetectomy|foraminotomy",t):
122
+ base={"cervical":"63045","thoracic":"63046","lumbar":"63047"}.get(region,"63047")
123
+ extras=max(0,inters-1)
124
+ cpts.append(base)
125
+ if extras>0: cpts.extend(["63048"]*extras)
126
+
127
+ # Fusions (posterior/interbody)
128
+ if re.search(FUSION_KW,t):
129
+ if region.startswith("cervico"): base="22600"
130
+ elif region.startswith("thoraco"): base="22610"
131
+ elif region.startswith("lumbo"): base="22612"
132
+ elif region=="lumbar": base="22630"
133
+ else: base="22612"
134
+ extras=max(0,inters)
135
+ cpts.append(base)
136
+ if extras>0: cpts.extend(["22614"]*extras)
137
+
138
+ # Instrumentation
139
+ if re.search(INSTR_KW,t):
140
+ segs=len(levels)
141
+ if segs<=2: cpts.append("22840")
142
+ elif segs<=6: cpts.append("22842")
143
+ elif segs<=12: cpts.append("22843")
144
+ else: cpts.append("22844")
145
+
146
+ # Navigation
147
+ if re.search(NAV_KW,t): cpts.append("61783")
148
+
149
+ # Grafts
150
+ if re.search(AUTO_SEP_KW,t): cpts.append("20937")
151
+ elif re.search(AUTO_LOCAL_KW,t): cpts.append("20936")
152
+ if re.search(ALLO_KW,t): cpts.append("20930")
153
+
154
+ # Hardware removal
155
+ if re.search(r"remov(ed|al).*instrument",t): cpts.append("22852")
156
+
157
+ return sorted(set(cpts))
158
+
159
+ # ─────────────────────────────────────────────
160
+ # MAIN WRAPPER
161
+ # ─────────────────────────────────────────────
162
+ def vertebro_infer(note:str, payer:str="Medicare")->Dict[str,Any]:
163
+ t=_norm(note)
164
+ levels=_extract_levels(t)
165
+ region=_classify_region(levels)
166
+ inter=_count_interspaces(levels)
167
+ laterality,mods=_laterality_and_modifiers(t)
168
+
169
+ # Flag summary
170
+ flags=[]
171
+ for pat,label in [(FUSION_KW,"arthrodesis"),(INSTR_KW,"instrumentation"),
172
+ (NAV_KW,"nav"),(ALLO_KW,"allograft"),
173
+ (AUTO_LOCAL_KW,"autograft"),(AUTO_SEP_KW,"autograft_sep")]:
174
+ if re.search(pat,t): flags.append(label)
175
+
176
+ cpts=_infer_cpts(t,region,levels)
177
+
178
+ # Tech flags
179
+ flags_map={
180
+ "microscope":_has(t,r"\bmicroscope\b|microdissection"),
181
+ "nav":_has(t,r"\bnavigation\b|o-?arm|stealth|7d|image[- ]?guided"),
182
+ "io_monitor":_has(t,r"\bneuromonitor|ssep|emg|monitoring\b"),
183
+ "fluoro":_has(t,r"\bfluoro|c[- ]?arm|fluoroscop"),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
184
  }
185
+ tech_flags=[k for k,v in flags_map.items() if v]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
 
187
  return {
188
+ "payer":payer,
189
+ "region":region,
190
+ "levels":levels,
191
+ "interspaces_est":inter,
192
+ "laterality":laterality,
193
+ "case_modifiers":mods,
194
+ "flags":list(set(flags+tech_flags)),
195
+ "flags_map":flags_map,
196
+ "suggested_cpts":cpts,
197
+ "build":"FINAL-v2.2",
198
+ "mode":"standard"
 
199
  }
200
+
201
+ # ─────────────────────────────────────────────
202
+ # CLI TEST
203
+ # ─────────────────────────────────────────────
204
+ if __name__=="__main__":
205
+ sample = """Preop Dx: C3–C6 stenosis with myelopathy.
206
+ Procedure: C3–C6 laminectomy and C2–T1 posterior fusion with instrumentation.
207
+ Navigation used; local autograft and allograft placed."""
208
+ result = vertebro_infer(sample)
209
+ print(json.dumps(result, indent=2))