from __future__ import annotations from datetime import datetime from decimal import Decimal, InvalidOperation, ROUND_HALF_UP import pendulum def clean_money(value) -> Decimal: text = str(value or "").replace("$", "").replace(",", "").strip() if not text: return Decimal("0") try: return Decimal(text) except InvalidOperation: return Decimal("0") def clean_int(value) -> int: try: return max(0, int(str(value or "0").strip())) except ValueError: return 0 def money(value: Decimal) -> str: value = value.quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) return f"${value:,.2f}" def parse_date(value): text = str(value or "").strip() if not text: return None for fmt in ("YYYY-MM-DD", "MM/DD/YYYY", "MM-DD-YYYY"): try: return pendulum.from_format(text, fmt) except Exception: pass try: return pendulum.parse(text) except Exception: return None def format_date(value) -> str: if not value: return "" return value.format("MMMM Do, YYYY") def add_months(date_value, months: int): if not date_value: return None return date_value.add(months=months) def add_last_four_fields(data: dict) -> dict: # Existing old-template compatibility. for key, value in list(data.items()): if not key.endswith("Account"): continue base = key[:-len("Account")] digits = "".join(ch for ch in str(value or "") if ch.isdigit()) data[f"{base}AccLastFour"] = digits[-4:] if len(digits) >= 4 else digits ssn = "".join(ch for ch in str(data.get("SSN", "")) if ch.isdigit()) if ssn: data["SSNLastFour"] = ssn[-4:] ssn2 = "".join(ch for ch in str(data.get("client2SSN", "")) if ch.isdigit()) if ssn2: data["SSN2LastFour"] = ssn2[-4:] return data def add_case_filename_fields(data: dict) -> dict: plaintiff = str(data.get("casePlaintiff", "") or "") data["casePlaintiffFileName"] = plaintiff.replace(" ", "-").replace(",", "").replace(".", "") return data def add_settlement_schedule(data: dict, calculation: dict) -> dict: config = (calculation.get("outputsDynamic") or {}).get("settlementSchedule", {}) count_field = config.get("countField", "settlementInstallmentNo") count = clean_int(data.get(count_field)) # Safety cap. Old app used many fixed fields; 120 is plenty for MVP. count = min(count, int(config.get("maxCount", 120))) settlement_amount = clean_money(data.get("settlementAmount")) installment_amount = clean_money(data.get("settlementInstallmentAmount")) first_payment_date = parse_date(data.get("settlementFirstPaymentDate")) if count <= 0: return data if installment_amount == 0 and settlement_amount > 0: installment_amount = (settlement_amount / Decimal(count)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) remaining = settlement_amount for i in range(count): suffix = f"{i:02d}" payment_date = add_months(first_payment_date, i) payment = installment_amount if settlement_amount > 0: if i == count - 1: payment = remaining remaining = max(Decimal("0"), remaining - payment) data[f"settlementPaymentDate{suffix}"] = format_date(payment_date) data[f"settlementPaymentAmount{suffix}"] = money(payment) data[f"settlementRemaingBalance{suffix}"] = money(remaining) # Correct spelling alias for future templates. data[f"settlementRemainingBalance{suffix}"] = money(remaining) return data def apply_legacy_legal_calculations(data: dict, calculation: dict) -> dict: data = dict(data) data = add_last_four_fields(data) data = add_case_filename_fields(data) data = add_settlement_schedule(data, calculation) return data