irp: attach signed POA + census-enrich address; fix date JSON crash

- send_irp_submission now REQUIRES and ATTACHES the signed Power of Attorney PDF
  (downloaded from MinIO) — the state won't act on a third-party filing without
  it, and 'on file, available on request' stalls the request. If the POA isn't
  available we don't email and fall back to a manual todo.
- Backfill missing legal_name + registered address from the FMCSA census so the
  submission isn't sent with a blank address (root cause of the empty
  'Legal/registered address: , ,' line). Customer-supplied values win.
- state_trucking passes signed_auth_key through to the IRP submitter.
- Fix 'Object of type date is not JSON serializable' when creating the admin
  todo (json.dumps(..., default=str)) — broke the intrastate (bash-fee) path.
This commit is contained in:
justin 2026-06-16 05:18:23 -05:00
parent 1d6693adb9
commit a74516a255
2 changed files with 88 additions and 15 deletions

View file

@ -83,15 +83,73 @@ def state_irp_contact(base_state: str) -> dict | None:
return IRP_STATE_CONTACTS.get((base_state or "").upper())
def _enrich_address_from_census(dot_number: str, intake: dict) -> dict:
"""Fill missing legal_name / address fields from the FMCSA census so the IRP
submission isn't sent with a blank address. Customer-supplied values win."""
out = dict(intake or {})
need = any(not out.get(k) for k in ("legal_name", "address_street", "address_city",
"address_state", "address_zip"))
if not need or not dot_number:
return out
try:
from scripts.workers.services.mcs150_update import MCS150UpdateHandler
census = MCS150UpdateHandler.__new__(MCS150UpdateHandler)._fetch_carrier_record(dot_number)
for k in ("legal_name", "address_street", "address_city", "address_state",
"address_zip", "phone"):
if not out.get(k) and census.get(k):
out[k] = census[k]
except Exception as exc: # noqa: BLE001
LOG.warning("[irp] census enrich failed for DOT %s: %s", dot_number, exc)
return out
def _download_minio(key: str) -> bytes | None:
"""Fetch an object's bytes from MinIO (the signed POA PDF). None on failure."""
if not key:
return None
try:
from minio import Minio
mc = Minio(
f"{os.getenv('MINIO_ENDPOINT', 'minio')}:{os.getenv('MINIO_PORT', '9000')}",
access_key=os.getenv("MINIO_ACCESS_KEY", ""),
secret_key=os.getenv("MINIO_SECRET_KEY", ""),
secure=os.getenv("MINIO_SECURE", "false").lower() == "true",
)
bucket = os.getenv("MINIO_BUCKET", "performancewest")
resp = mc.get_object(bucket, key)
data = resp.read()
resp.close()
resp.release_conn()
return data
except Exception as exc: # noqa: BLE001
LOG.warning("[irp] could not download POA %s: %s", key, exc)
return None
def send_irp_submission(order_number: str, entity_name: str, dot_number: str,
base_state: str, intake: dict, signed_auth_note: str = "") -> bool:
base_state: str, intake: dict, signed_auth_key: str = "") -> bool:
"""Email the base-state IRP unit a Schedule A/B submission for this carrier,
tagged for reply matching. Returns True if sent."""
with the signed POA attached, tagged for reply matching. Returns True if sent.
The state will not act on a third-party filing without the signed Power of
Attorney, so we REQUIRE the POA PDF and attach it; if it's missing we do not
send (the caller falls back to a manual todo)."""
contact = state_irp_contact(base_state)
if not contact or not contact.get("email"):
LOG.warning("[%s] No IRP submission contact for base state %s", order_number, base_state)
return False
# Attach the signed POA — required by the state to act on our behalf.
poa_bytes = _download_minio(signed_auth_key)
if not poa_bytes:
LOG.warning("[%s] No signed POA available (key=%s) — not emailing IRP office",
order_number, signed_auth_key or "(none)")
return False
# Backfill legal name + address from the FMCSA census if intake lacks them.
intake = _enrich_address_from_census(dot_number, intake)
entity_name = entity_name or intake.get("legal_name", "")
op_states = intake.get("operating_states") or []
if isinstance(op_states, str):
try:
@ -99,12 +157,19 @@ def send_irp_submission(order_number: str, entity_name: str, dot_number: str,
except Exception:
op_states = [s.strip() for s in op_states.split(",") if s.strip()]
addr = ", ".join(p for p in [
intake.get("address_street", ""),
intake.get("address_city", ""),
f"{intake.get('address_state','')} {intake.get('address_zip','')}".strip(),
] if p.strip())
subject = f"IRP Apportioned Registration Request — {entity_name} (USDOT {dot_number}) [{SUBJECT_TAG} {order_number}]"
body = (
f"To {contact['agency']},\n\n"
f"On behalf of our client (signed Power of Attorney on file), we request "
f"IRP apportioned registration for the following carrier and ask that you "
f"reply with the computed apportioned fee invoice so we can remit payment.\n\n"
f"On behalf of our client, and under the signed Power of Attorney attached "
f"to this email, we request IRP apportioned registration for the following "
f"carrier. Please reply with the computed apportioned fee invoice so we can "
f"remit payment.\n\n"
f"Carrier: {entity_name}\n"
f"USDOT: {dot_number}\n"
f"MC/MX/FF: {intake.get('mc_number','')}\n"
@ -112,10 +177,9 @@ def send_irp_submission(order_number: str, entity_name: str, dot_number: str,
f"Power units: {intake.get('power_units','')}\n"
f"Registered weight bracket: {intake.get('gross_weight_bracket','')}\n"
f"Operating jurisdictions: {', '.join(op_states) if op_states else base_state}\n"
f"Legal/registered address: {intake.get('address_street','')}, "
f"{intake.get('address_city','')}, {intake.get('address_state','')} "
f"{intake.get('address_zip','')}\n\n"
f"{signed_auth_note or 'A signed Power of Attorney authorizing Performance West Inc. to file on the carrier behalf is available on request.'}\n\n"
f"Legal/registered address: {addr or '(see attached)'}\n\n"
f"Attached: signed Power of Attorney authorizing Performance West Inc. to "
f"file and remit fees on the carrier's behalf.\n\n"
f"Please reply to {FILINGS_FROM} with the apportioned fee total (including any "
f"processing fees) and any required Schedule A/B forms. Keep the subject "
f"reference [{SUBJECT_TAG} {order_number}] so we can match your reply.\n\n"
@ -124,20 +188,26 @@ def send_irp_submission(order_number: str, entity_name: str, dot_number: str,
f"(888) 411-0383 · {FILINGS_FROM}\n"
)
try:
from email.mime.application import MIMEApplication
msg = MIMEMultipart()
msg["From"] = SMTP_USER
msg["To"] = contact["email"]
msg["Reply-To"] = FILINGS_FROM
msg["Subject"] = subject
msg.attach(MIMEText(body, "plain"))
poa = MIMEApplication(poa_bytes, _subtype="pdf")
poa.add_header("Content-Disposition", "attachment",
filename=f"POA_{entity_name.replace(' ','_')}_{dot_number}.pdf")
msg.attach(poa)
with smtplib.SMTP(SMTP_HOST, SMTP_PORT, timeout=30) as s:
s.starttls()
if SMTP_USER and SMTP_PASS:
s.login(SMTP_USER, SMTP_PASS)
s.sendmail(SMTP_USER, [contact["email"], FILINGS_FROM], msg.as_string())
LOG.info("[%s] IRP submission emailed to %s (%s)", order_number, contact["email"], base_state)
LOG.info("[%s] IRP submission emailed to %s (%s) with POA attached",
order_number, contact["email"], base_state)
send_telegram(
f"📤 IRP submission sent\n{entity_name} (DOT {dot_number})\n"
f"📤 IRP submission sent (POA attached)\n{entity_name} (DOT {dot_number})\n"
f"Base state: {base_state}{contact['email']}\n"
f"Order: {order_number}\nAwaiting the state's apportioned-fee invoice."
)

View file

@ -311,7 +311,8 @@ class StateTruckingHandler:
and not order_data.get("gov_fee_paid")
and not self._gov_fee_settled(order_number)):
if self._request_gov_fee_payment(order_number, service_slug, service_name,
entity_name, customer_email, customer_phone, intake):
entity_name, customer_email, customer_phone,
intake, signed_auth_key):
self._set_fulfillment_status(order_number, FULFILLMENT_AWAITING_FEE_APPROVAL)
LOG.info("[%s] Gov fee quoted — held pending customer payment", order_number)
return []
@ -390,7 +391,7 @@ class StateTruckingHandler:
order_number,
service_slug,
todo_description,
json.dumps(todo_data),
json.dumps(todo_data, default=str),
))
conn.commit()
notify_fulfillment_todo(
@ -713,7 +714,8 @@ class StateTruckingHandler:
return False
def _request_gov_fee_payment(self, order_number, service_slug, service_name,
entity_name, customer_email, customer_phone, intake) -> bool:
entity_name, customer_email, customer_phone, intake,
signed_auth_key=None) -> bool:
"""Set up government-fee collection after authorization.
IRP: the fee is unknown until the base state computes it, so we EMAIL the
@ -731,7 +733,8 @@ class StateTruckingHandler:
return False
base_state = (intake.get("base_state") or intake.get("address_state") or "").upper()
sent = send_irp_submission(order_number, entity_name,
intake.get("dot_number", ""), base_state, intake)
intake.get("dot_number", ""), base_state, intake,
signed_auth_key=signed_auth_key)
if sent:
try:
notify_fulfillment_todo(