"""Payment-history risk scorer. Pure-Python: takes payment history (list of payment events) + average days-late and returns a risk score 0-100. Higher = more risky.""" from dataclasses import dataclass @dataclass class PartnerRiskScore: score: int band: str drivers: list[str] def score_partner(*, total_invoices: int = 0, paid_late_count: int = 0, avg_days_late: float = 0.0, longest_overdue_days: int = 0, open_overdue_amount: float = 0.0, average_invoice_amount: float = 1000.0) -> PartnerRiskScore: """Compute a 0-100 risk score from payment-history primitives. Heuristic weights: - 30% : late-payment ratio (paid_late_count / total_invoices) - 25% : avg days late (capped at 60 days) - 25% : longest current overdue (capped at 120 days) - 20% : open overdue amount as multiple of average invoice """ drivers: list[str] = [] score = 0.0 if total_invoices > 0: late_ratio = paid_late_count / total_invoices score += min(late_ratio * 100, 100) * 0.30 if late_ratio > 0.5: drivers.append(f"{paid_late_count}/{total_invoices} invoices paid late") score += min(avg_days_late / 60, 1) * 100 * 0.25 if avg_days_late > 14: drivers.append(f"Avg {avg_days_late:.1f} days late on payment") score += min(longest_overdue_days / 120, 1) * 100 * 0.25 if longest_overdue_days > 30: drivers.append(f"Longest currently overdue: {longest_overdue_days} days") if average_invoice_amount > 0: ratio = open_overdue_amount / average_invoice_amount score += min(ratio / 5, 1) * 100 * 0.20 if ratio > 1.5: drivers.append(f"Open overdue ${open_overdue_amount:,.2f} ({ratio:.1f}x avg invoice)") final = int(round(score)) if final >= 80: band = 'critical' elif final >= 60: band = 'high' elif final >= 30: band = 'medium' else: band = 'low' return PartnerRiskScore(score=final, band=band, drivers=drivers)