Group normalized shipment rows into deduped shipments and gradeable coverage lines (patient -> device -> shipment), the structural fix for the row-keyed worklist bug (six months of orders no longer = six worklist lines). - Locked dedup key (patient_id, device_type, date_of_service); order_number is display-only. Monthly resupplies stay separate, not duplicates. - Client-mapped plan_type carried through, never guessed from payer name; conflicting plan types in a group route to "plan type needed". - Quantity merge: sum across distinct component configs, max within a config (duplicate guard) with a warning breadcrumb on disagreement. - CGM gradeable this phase; pumps preserved/displayed but not graded. Additive only: not yet wired into the live pipeline, grading unchanged. 30 tests green (full suite 46), code-auditor reviewed (M1/M2/L1/L2/L3 fixed). Design: docs/dedup-design-2026-06-24.md, docs/readiness-model-brief-2026-06-23.md Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
250 lines
9.5 KiB
Python
250 lines
9.5 KiB
Python
"""
|
|
dedup.py
|
|
Signal — STTIL Solutions
|
|
|
|
Groups normalized shipment rows into deduped shipments and gradeable coverage
|
|
lines. This is the structural foundation of the readiness model: it turns a
|
|
flat list of CSV-derived rows into the patient -> device -> shipment hierarchy
|
|
the worklist needs, BEFORE any grading happens.
|
|
|
|
Design: docs/dedup-design-2026-06-24.md (Pi, locked by Kisa 2026-06-24)
|
|
docs/readiness-model-brief-2026-06-23.md sections 3, 5, 7
|
|
|
|
LOCKED DEDUP KEY: (patient_id, device_type, date_of_service)
|
|
order_number is a displayed attribute, NEVER part of the dedup key.
|
|
Monthly resupplies are separate dates of service, NOT duplicates.
|
|
|
|
PLAN TYPE: carried through verbatim from the client-mapped field. It is NEVER
|
|
derived from the payer name. None means "plan type needed" -> keyed as
|
|
'unknown' so the line still forms but cannot be graded green later.
|
|
|
|
PHI CONTRACT: operates only on patient_id, device_type, shipment_date,
|
|
quantity, payer (display), plan_type, component, order_number, hcpcs, and
|
|
the csv_* doc-status fields. No names, SSNs, DOBs, or contact fields.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import logging
|
|
from collections import defaultdict
|
|
from dataclasses import dataclass, field
|
|
from datetime import date, datetime
|
|
from typing import Optional
|
|
|
|
from core.coverage_calculator import ShipmentRecord, CoverageFlag
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def _hash_pid(patient_id: str) -> str:
|
|
"""Short hashed patient_id for log lines — raw patient_id is never logged."""
|
|
return hashlib.sha256(patient_id.encode()).hexdigest()[:12]
|
|
|
|
|
|
def _as_date(value) -> date:
|
|
"""Coerce a datetime to a pure date so same-day timestamps group together."""
|
|
return value.date() if isinstance(value, datetime) else value
|
|
|
|
# CGM device types graded this phase. Pumps and other DMEPOS categories are
|
|
# preserved and displayed but not graded (gradeable=False). Mirrors the CGM
|
|
# devices in payer_rules.json; moves into addressable config in a later step.
|
|
CGM_DEVICE_TYPES = frozenset(
|
|
{"dexcom_g6", "dexcom_g7", "freestyle_libre_2", "freestyle_libre_3"}
|
|
)
|
|
|
|
UNKNOWN_PLAN_TYPE = "unknown"
|
|
|
|
|
|
def is_cgm_device(device_type: str) -> bool:
|
|
"""True only for CGM device types graded in the current phase."""
|
|
return device_type in CGM_DEVICE_TYPES
|
|
|
|
|
|
@dataclass
|
|
class MergedShipment:
|
|
"""One deduped shipment: all rows sharing (patient_id, device_type, DOS)."""
|
|
patient_id: str
|
|
device_type: str
|
|
shipment_date: date
|
|
total_quantity: int
|
|
components: list[str] # combined, deduped, first-seen order
|
|
order_numbers: list[str] # collected, deduped — display only
|
|
hcpcs_codes: list[str] # collected, deduped — display only
|
|
payer: str # raw display NAME, latest non-null
|
|
plan_type: Optional[str] # client-mapped; NEVER from payer name
|
|
# csv_* doc fields — latest non-null wins across the group
|
|
csv_visit_date: Optional[date] = None
|
|
csv_swo_status: Optional[str] = None
|
|
csv_pecos_verified: Optional[str] = None
|
|
csv_pa_status: Optional[str] = None
|
|
csv_diagnosis_on_file: Optional[str] = None
|
|
csv_transfer_from: Optional[str] = None
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CoverageLineKey:
|
|
patient_id: str
|
|
device_type: str
|
|
plan_type_id: str
|
|
|
|
|
|
@dataclass
|
|
class CoverageLine:
|
|
"""A gradeable unit: one patient's one device under one plan type.
|
|
|
|
line_status and timing_flag are computed later by the grading step
|
|
(gradeLine); a freshly assigned line is intentionally ungraded.
|
|
"""
|
|
patient_id: str
|
|
device_type: str
|
|
plan_type: str
|
|
gradeable: bool
|
|
current_shipment: MergedShipment
|
|
prior_shipments: list[MergedShipment] = field(default_factory=list)
|
|
line_status: Optional[str] = None
|
|
timing_flag: Optional[CoverageFlag] = None
|
|
|
|
|
|
def _qty(record: ShipmentRecord) -> int:
|
|
"""A row without a quantity is still a shipment -> default to 1."""
|
|
return 1 if record.quantity is None else record.quantity
|
|
|
|
|
|
def _merge_quantities(rows: list[ShipmentRecord]) -> int:
|
|
"""Sum quantities across DISTINCT component configs, max within a config.
|
|
|
|
Within one component config, identical rows are likely data-entry
|
|
duplicates, so take the MAX (guards against doubling). Across distinct
|
|
configs (a real component split, e.g. sensor + transmitter), SUM.
|
|
|
|
A disagreement within one config (e.g. qty 3 and qty 100 for the same
|
|
component) collapses to the max and leaves a warning, so silently dropping
|
|
quantity always leaves an audit breadcrumb.
|
|
"""
|
|
by_config: dict[frozenset, list[int]] = defaultdict(list)
|
|
for row in rows:
|
|
by_config[frozenset({row.component})].append(_qty(row))
|
|
|
|
total = 0
|
|
for config, qtys in by_config.items():
|
|
if len(set(qtys)) > 1:
|
|
logger.warning(
|
|
"Quantity disagreement in dedup group patient=%s device=%s "
|
|
"component=%s qtys=%s -> taking max",
|
|
_hash_pid(rows[0].patient_id), rows[0].device_type,
|
|
set(config), qtys,
|
|
)
|
|
total += max(qtys)
|
|
return total
|
|
|
|
|
|
def _merge_plan_type(rows: list[ShipmentRecord]) -> Optional[str]:
|
|
"""Plan type is grading-critical and must NEVER be guessed.
|
|
|
|
If a dedup group carries more than one distinct non-null plan type, the data
|
|
is ambiguous, so return None ('plan type needed') rather than pick one.
|
|
"""
|
|
distinct = {r.plan_type for r in rows if r.plan_type}
|
|
if len(distinct) > 1:
|
|
logger.warning(
|
|
"Conflicting plan_type in dedup group patient=%s device=%s: %s "
|
|
"-> routing to 'plan type needed'",
|
|
_hash_pid(rows[0].patient_id), rows[0].device_type, sorted(distinct),
|
|
)
|
|
return None
|
|
return next(iter(distinct)) if distinct else None
|
|
|
|
|
|
def _dedup_preserve_order(values: list[Optional[str]]) -> list[str]:
|
|
"""Collect non-empty values, dedup, preserve first-seen order."""
|
|
seen: dict[str, None] = {}
|
|
for v in values:
|
|
if v:
|
|
seen.setdefault(v, None)
|
|
return list(seen.keys())
|
|
|
|
|
|
def _latest_non_null(rows: list[ShipmentRecord], attr: str):
|
|
"""Last meaningful value for an attribute, in ingestion order.
|
|
|
|
Empty / whitespace-only strings are treated as null so a blank later row
|
|
never clobbers an earlier real value (does not rely on upstream nulling).
|
|
"""
|
|
result = None
|
|
for row in rows:
|
|
value = getattr(row, attr)
|
|
if value is None:
|
|
continue
|
|
if isinstance(value, str) and value.strip() == "":
|
|
continue
|
|
result = value
|
|
return result
|
|
|
|
|
|
def dedup(records: list[ShipmentRecord]) -> list[MergedShipment]:
|
|
"""Group rows by (patient_id, device_type, shipment_date) into MergedShipments."""
|
|
groups: dict[tuple, list[ShipmentRecord]] = {}
|
|
for record in records:
|
|
key = (record.patient_id, record.device_type, _as_date(record.shipment_date))
|
|
groups.setdefault(key, []).append(record)
|
|
|
|
merged: list[MergedShipment] = []
|
|
for (patient_id, device_type, shipment_date), rows in groups.items():
|
|
merged.append(
|
|
MergedShipment(
|
|
patient_id=patient_id,
|
|
device_type=device_type,
|
|
shipment_date=shipment_date,
|
|
total_quantity=_merge_quantities(rows),
|
|
components=_dedup_preserve_order([r.component for r in rows]),
|
|
order_numbers=_dedup_preserve_order([r.order_number for r in rows]),
|
|
hcpcs_codes=_dedup_preserve_order([r.hcpcs for r in rows]),
|
|
payer=_latest_non_null(rows, "payer") or "",
|
|
plan_type=_merge_plan_type(rows),
|
|
csv_visit_date=_latest_non_null(rows, "csv_visit_date"),
|
|
csv_swo_status=_latest_non_null(rows, "csv_swo_status"),
|
|
csv_pecos_verified=_latest_non_null(rows, "csv_pecos_verified"),
|
|
csv_pa_status=_latest_non_null(rows, "csv_pa_status"),
|
|
csv_diagnosis_on_file=_latest_non_null(rows, "csv_diagnosis_on_file"),
|
|
csv_transfer_from=_latest_non_null(rows, "csv_transfer_from"),
|
|
)
|
|
)
|
|
|
|
merged.sort(key=lambda m: (m.patient_id, m.device_type, m.shipment_date))
|
|
return merged
|
|
|
|
|
|
def assign_to_coverage_lines(
|
|
merged_shipments: list[MergedShipment],
|
|
) -> dict[CoverageLineKey, CoverageLine]:
|
|
"""Group MergedShipments into CoverageLines by (patient_id, device_type, plan_type).
|
|
|
|
plan_type is the client-mapped field. None is keyed as 'unknown' (plan type
|
|
needed) so the line still forms but cannot be graded green later. The latest
|
|
DOS in each line is the current shipment; earlier ones are collapsed history.
|
|
"""
|
|
by_key: dict[CoverageLineKey, list[MergedShipment]] = {}
|
|
for shipment in merged_shipments:
|
|
key = CoverageLineKey(
|
|
patient_id=shipment.patient_id,
|
|
device_type=shipment.device_type,
|
|
plan_type_id=shipment.plan_type or UNKNOWN_PLAN_TYPE,
|
|
)
|
|
by_key.setdefault(key, []).append(shipment)
|
|
|
|
lines: dict[CoverageLineKey, CoverageLine] = {}
|
|
for key, shipments in by_key.items():
|
|
shipments.sort(key=lambda s: s.shipment_date, reverse=True)
|
|
lines[key] = CoverageLine(
|
|
patient_id=key.patient_id,
|
|
device_type=key.device_type,
|
|
plan_type=key.plan_type_id,
|
|
gradeable=is_cgm_device(key.device_type),
|
|
current_shipment=shipments[0],
|
|
prior_shipments=shipments[1:],
|
|
line_status=None,
|
|
timing_flag=None,
|
|
)
|
|
|
|
return lines
|