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