medbillcodes-api / app /encounter_align.py
medbillcodes-deploy
Deploy cloud pilot API
5cceba0
Raw
History Blame Contribute Delete
9.46 kB
"""Align UI encounter types to specialty Schedule listings by care setting.
Specialty **26 (Paediatrics)** and **00 (Family Practice)** use different
Consultations and Visits sections. Encounter labels map to letter-prefix
families plus BM25 steering keywords for that specialty.
"""
from __future__ import annotations
from .specialty_registry import normalize_specialty_code
# Ordered (substring -> (prefixes, keywords)) rules. First match wins — more
# specific Paediatrics phrases must precede generic ones.
_PAEDS_RULES: list[tuple[tuple[str, ...], tuple[list[str], str]]] = [
# --- Newborn & delivery (H261 / H267 live in PAEDIATRICS (26)) ---
(
("maternal delivery", "attendance at maternal", "delivery attendance"),
(["H"], "attendance at maternal delivery H267 Paediatrics"),
),
(
("newborn",),
(["H"], "newborn care in hospital or home H261 Paediatrics"),
),
# --- Prenatal (before hospital so "Hospital - Prenatal …" keeps A+C) ---
# SoB PAEDIATRICS (26) lists A665 with the same-section alternatives below;
# keep them in-scope so limited/repeat/MSA can be recommended when prenatal
# elements are not met.
(
("prenatal",),
(
["A", "C"],
"prenatal consultation A665 C665 Paediatrics "
"limited consultation A565 repeat consultation A266 "
"medical specific assessment A263 A264 A661 "
"level 1 level 2 paediatric assessment A261 A262",
),
),
# --- Long-term care (must precede generic 'complex') ---
(
("long-term", "long term", "continuing care", "ltc"),
(
["W"],
"long-term care paediatric consultation subsequent visit "
"periodic health visit",
),
),
# --- Hospital in-patient (C-series Paediatrics mirrors) ---
# Before developmental so "Hospital - … Neurodevelopmental" stays C.
(
("subsequent", "concurrent", "daily round"),
(
["C"],
"hospital in-patient subsequent visit concurrent care "
"medical specific re-assessment",
),
),
(
("hospital", "admission", "inpatient", "in-patient", "mrp"),
(
["C"],
"hospital in-patient paediatric consultation medical specific "
"assessment concurrent care neurodevelopmental",
),
),
# --- Ambulatory consultations (before developmental; "neurodevelopmental
# consultation" is an A/C/W consult listing, not K developmental care) ---
(
("well-baby", "well baby", "18 month", "18-month", "enhanced 18"),
(
["A"],
"enhanced 18 month well baby visit A268 Paediatrics",
),
),
(
(
"special paediatric",
"extended special",
"neurodevelopmental consultation",
"special / extended",
"consultation / limited",
"specialist consultation",
"consultation",
),
(
["A"],
"paediatric consultation limited repeat special extended "
"neurodevelopmental A260 A265 A565 A266 A662 A667 A695",
),
),
(
("medical specific", "level 2", "general assessment"),
(
["A"],
"level 2 paediatric assessment medical specific assessment "
"re-assessment A262 A263 A264 A661",
),
),
(
("level 1", "partial", "recheck", "minor"),
(
["A"],
"level 1 paediatric assessment A261",
),
),
# --- Developmental / behavioural care (K119 / K122 / K123) ---
# Narrow needles so "neurodevelopmental consultation" stays on A/C/W.
(
(
"developmental -",
"behavioural care",
"behavioral care",
"developmental care",
"developmental assessment incentive",
"family developmental",
"individual developmental",
),
(
["K", "A"],
"paediatric developmental behavioural care K119 K122 K123 "
"neurodevelopmental",
),
),
# --- Interprofessional / case conference ---
(
(
"case conference",
"multi-disciplinary",
"multidisciplinary",
"inter-professional",
"interprofessional",
),
(
["K"],
"developmental behavioural care case conference "
"interprofessional special services",
),
),
# --- Virtual: audio/telephone before generic "virtual" ---
(
("audio", "telephone"),
(
["A"],
"office paediatric assessment consultation virtual telephone",
),
),
(
("virtual", "video"),
(["A"], "office paediatric assessment consultation virtual video"),
),
(
("ambulatory", "office", "outpatient"),
(
["A"],
"paediatric assessment consultation well baby office ambulatory",
),
),
# --- Emergency: Paediatrics (26) only lists newborn/delivery under H ---
(
("emergency",),
(
["H", "A"],
"paediatric assessment consultation emergency urgent",
),
),
]
# Family Practice (00) — A001/A007/A003 ladder + hospital/LTC/K017.
_FP_RULES: list[tuple[tuple[str, ...], tuple[list[str], str]]] = [
(
("periodic health", "annual health", "complete physical"),
(
["K", "A"],
"periodic health visit K017 Family Practice office",
),
),
(
("long-term", "long term", "continuing care", "ltc", "nursing home"),
(
["W"],
"long-term care subsequent visit periodic health W001 W002 W003 "
"W004 W109 Family Practice",
),
),
(
("subsequent", "concurrent", "supportive", "daily round"),
(
["C"],
"hospital in-patient subsequent visit concurrent care supportive "
"care C002 C007 C008 C010 Family Practice",
),
),
(
("hospital", "admission", "inpatient", "in-patient", "mrp"),
(
["C"],
"hospital in-patient general assessment consultation "
"C003 C004 C005 C006 Family Practice",
),
),
# Intermediate before well-baby so "Intermediate assessment / well baby care"
# steers to A007; pure enhanced-18 / well-baby labels still hit the next rule.
(
("intermediate",),
(
["A"],
"intermediate assessment A007 Family Practice",
),
),
(
("well-baby", "well baby", "18 month", "18-month", "enhanced 18"),
(
["A"],
"well baby care A007 enhanced 18 month well baby visit A002 "
"Family Practice",
),
),
# Ambulatory consult → office A only (hospital C mirrors stay on Hospital rules).
(
("repeat consultation", "repeat consult"),
(
["A"],
"repeat consultation A006 Family Practice office",
),
),
(
("consultation", "consult"),
(
["A"],
"consultation repeat consultation A006 Family Practice office",
),
),
(
("general assessment", "complete assessment", "comprehensive"),
(
["A"],
"general assessment A003 Family Practice",
),
),
(
("re-assessment", "reassessment", "general re"),
(
["A"],
"general re-assessment A004 Family Practice",
),
),
(
("mini",),
(
["A"],
"mini assessment A008 Family Practice",
),
),
(
("minor", "brief", "focused", "single complaint"),
(
["A"],
"minor assessment A001 Family Practice",
),
),
(
("audio", "telephone", "virtual", "video"),
(
["A"],
"office assessment Family Practice virtual visit A001 A007 A003",
),
),
(
("ambulatory", "office", "outpatient", "clinic"),
(
["A"],
"office assessment Family Practice A001 A007 A003 A004 A008",
),
),
(
("emergency", "urgent"),
(
["A", "H"],
"office assessment urgent Family Practice A001 A007",
),
),
]
def align_encounter(
encounter_type: str | None,
specialty_code: str | None = None,
) -> tuple[list[str], str]:
"""Return (preferred code prefixes, specialty steering keywords)."""
if not encounter_type:
return [], ""
et = encounter_type.lower()
code = normalize_specialty_code(specialty_code)
rules = _FP_RULES if code == "00" else _PAEDS_RULES
for needles, result in rules:
if any(n in et for n in needles):
return list(result[0]), result[1]
return [], ""
def prefix_filter(prefixes: list[str]) -> list[dict]:
"""OpenSearch filter clause restricting billing_code to the given prefixes."""
if not prefixes:
return []
return [
{
"bool": {
"should": [{"prefix": {"billing_code": p}} for p in prefixes],
"minimum_should_match": 1,
}
}
]