feat(govfee): exact fees + agency processing fees; IRP email/invoice reconciliation

- gov_fee: add AGENCY_PROCESSING_FEE (per-service card/convenience fee passed
  through so the customer pays the true all-in cost); estimate_gov_fee now folds
  it into the billed total. IFTA/intrastate/UCR fees are published/near-exact.

- IRP fees can't be looked up — only the base state computes them. New
  irp_filing.py: emails the base-state IRP unit a Schedule A/B request (Reply-To
  the IRP filings mailbox, [PW-IRP CO-...] subject tag), and a 15-min cron
  (irp_invoice_poller) scans the mailbox for the state's invoice reply, parses
  the exact apportioned fee, Telegram-alerts you, and bills the customer the
  EXACT amount via a gov-fee child order + payment link. Then it proceeds to
  ready_to_file for your final approval.

- state_trucking gov-fee gate now routes IRP to the email/invoice path and
  IFTA/intrastate to immediate exact-fee billing.

- Mailbox is configurable (IRP_FILINGS_IMAP_* in app.env.j2); falls back to
  OPS_IMAP_* filtered by the [PW-IRP] tag until a dedicated mailbox exists.

Telegram alerts fire on IRP submission sent, invoice received (billed), and
un-parseable replies (so you can read + enter the fee manually).
This commit is contained in:
justin 2026-06-16 04:58:14 -05:00
parent 861f2fbfd4
commit ea695d6828
6 changed files with 406 additions and 3 deletions

View file

@ -66,6 +66,25 @@ IRP_WEIGHT_MULTIPLIERS = {
"_default": 1.0,
}
# Agency / government payment-processing (card convenience) fees. Many state
# portals add a card convenience fee on top of the statutory fee; we pass it
# through so the customer pays the true all-in cost. Modeled as a percentage of
# the government fee plus an optional flat add-on, per service. Refine per state
# as confirmed. (pct_of_gov_fee, flat_cents)
AGENCY_PROCESSING_FEE = {
"irp-registration": (0.0, 0), # SC IRP invoice states the all-in total; no extra
"ifta-application": (0.0, 0), # decals billed at cost; no card fee on a tiny amount
"intrastate-authority": (0.0, 0), # filing fee paid by check/ACH; no card fee
"ucr-registration": (0.0, 0), # ucr.gov includes no separate convenience fee
"_default": (0.0, 0),
}
def agency_processing_fee_cents(slug: str, gov_fee_cents: int) -> int:
"""Government/agency card-convenience fee passed through to the customer."""
pct, flat = AGENCY_PROCESSING_FEE.get(slug, AGENCY_PROCESSING_FEE["_default"])
return int(round(gov_fee_cents * pct / 100.0)) + flat
@dataclass
class GovFeeEstimate:
@ -83,7 +102,19 @@ def _int(v, default=0) -> int:
def estimate_gov_fee(slug: str, intake: dict) -> GovFeeEstimate:
"""Estimate the government/state fee for an at-cost trucking service."""
"""Estimate the all-in government fee (statutory fee + any agency card/
convenience processing fee) for an at-cost trucking service."""
est = _estimate_gov_fee_base(slug, intake)
proc = agency_processing_fee_cents(slug, est.cents)
if proc > 0:
est.breakdown.append(f"Agency processing fee: ${proc/100:.2f}")
est.cents += proc
est.label += f" (incl. ${proc/100:.2f} agency processing fee)"
return est
def _estimate_gov_fee_base(slug: str, intake: dict) -> GovFeeEstimate:
"""Statutory/base government fee before any agency processing surcharge."""
intake = intake or {}
power_units = max(_int(intake.get("power_units"), 1), 1)
base_state = (intake.get("base_state") or intake.get("address_state") or "").upper()