Spaces:
Sleeping
Sleeping
Update spine_coder/spine_coder_core.py
Browse files- 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 |
-
#
|
|
|
|
|
|
|
| 5 |
def _norm(s: str) -> str:
|
| 6 |
-
if not s:
|
| 7 |
-
|
| 8 |
-
s =
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
|
| 11 |
def _has(text: str, pat: str) -> bool:
|
| 12 |
return re.search(pat, text, flags=re.I) is not None
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
else:
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
levels.add(f"{m.group(1).upper()}{int(m.group(2))}")
|
| 32 |
-
return sorted(levels,
|
| 33 |
|
| 34 |
-
def _count_interspaces(levels:
|
| 35 |
if not levels: return 0
|
| 36 |
-
|
| 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
|
| 56 |
-
|
| 57 |
-
by_band: Dict[str, List[int]] = {}
|
| 58 |
for lv in levels:
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
if anterior:
|
| 88 |
-
return "22845" if
|
| 89 |
else:
|
| 90 |
-
return "22842" if
|
| 91 |
-
|
| 92 |
-
#
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
#
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
#
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
| 140 |
-
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
#
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 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,
|
| 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":
|
| 542 |
-
"region":
|
| 543 |
-
"levels":
|
| 544 |
-
"interspaces_est":
|
| 545 |
-
"
|
| 546 |
-
"
|
| 547 |
-
"
|
| 548 |
-
"
|
| 549 |
-
"
|
| 550 |
-
"
|
| 551 |
-
"
|
| 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))
|