feat(sp6): 4-field weighted scoring module

This commit is contained in:
Tyler
2026-06-20 18:30:05 -06:00
parent f4910fd94d
commit 3432c76561
2 changed files with 221 additions and 0 deletions
+96
View File
@@ -0,0 +1,96 @@
"""Transparent 4-field weighted scoring for Inbox candidate pairs.
SP6 T5.
Weights (sum to 100):
- patient control number match (exact, normalized) 40
- service date proximity (±3 days linear decay) 25
- charge amount proximity (±10% linear decay) 20
- provider NPI exact match 15
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import date
from decimal import Decimal
from typing import Protocol
class _ScorableClaim(Protocol):
patient_control_number: str
service_date_from: date | None
charge_amount: Decimal
provider_npi: str | None
class _ScorableRemit(Protocol):
patient_control_number: str
service_date: date | None
charge_amount: Decimal
rendering_provider_npi: str | None
@dataclass(frozen=True)
class ScoreBreakdown:
patient: float
date: float
amount: float
provider: float
@property
def total(self) -> int:
return round(
self.patient * 40
+ self.date * 25
+ self.amount * 20
+ self.provider * 15
)
@property
def tier(self) -> str:
if self.total >= 75:
return "strong"
if self.total >= 50:
return "weak"
return "hidden"
def _norm_pcn(s: str) -> str:
return (s or "").strip().lower().lstrip("0")
def _date_score(claim_date: date | None, remit_date: date | None) -> float:
if claim_date is None or remit_date is None:
return 0.0
delta = abs((claim_date - remit_date).days)
if delta > 3:
return 0.0
return 1.0 - (delta / 3.0)
def _amount_score(claim_amt: Decimal, remit_amt: Decimal) -> float:
if claim_amt <= 0:
return 0.0
diff = abs(claim_amt - remit_amt)
threshold = claim_amt * Decimal("0.10")
if diff > threshold:
return 0.0
# Normalize diff within threshold to 0..1
return float(Decimal("1.0") - (diff / threshold))
def _provider_score(claim_npi: str | None, remit_npi: str | None) -> float:
if not claim_npi or not remit_npi:
return 0.0
return 1.0 if claim_npi.strip() == remit_npi.strip() else 0.0
def score_pair(claim: _ScorableClaim, remit: _ScorableRemit) -> ScoreBreakdown:
claim_pcn = _norm_pcn(claim.patient_control_number)
remit_pcn = _norm_pcn(remit.patient_control_number)
return ScoreBreakdown(
patient=1.0 if claim_pcn and claim_pcn == remit_pcn else 0.0,
date=_date_score(claim.service_date_from, remit.service_date),
amount=_amount_score(claim.charge_amount, remit.charge_amount),
provider=_provider_score(claim.provider_npi, remit.rendering_provider_npi),
)