From 3df3a08221a1a65b2b13129d7124b12d28c9cb95 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 16 Jun 2026 02:46:10 -0500 Subject: [PATCH] mcs150 handler: derive admin-assisted intake from census; gate ready_to_file Admin-assisted DOT services (UCR, BOC-3) routed to this handler were marked ready_to_file with whatever intake existed -- e.g. a UCR with only a DOT number, missing legal name / state / fleet-size bracket (which sets the UCR fee tier). That made the admin 'ready to file' status dishonest and unfileable. Now, for ADMIN_ASSISTED_REQUIRED services we first enrich intake from the FMCSA census (legal_name, address_state, power_units) + the order email, and derive the UCR fleet_size_bracket from power units (UCR_FLEET_BRACKETS). If every required field is then present we persist it and mark intake validated (falls through to the admin review gate -> ready_to_file). If anything is still missing, we persist what we have, set fulfillment_status=awaiting_intake, and email the customer to complete intake -- instead of falsely showing ready_to_file. --- scripts/workers/services/mcs150_update.py | 126 ++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/scripts/workers/services/mcs150_update.py b/scripts/workers/services/mcs150_update.py index 2bce48f..2d8ef4a 100644 --- a/scripts/workers/services/mcs150_update.py +++ b/scripts/workers/services/mcs150_update.py @@ -69,6 +69,34 @@ class MCS150UpdateHandler: "dot-full-compliance", }) + # Admin-assisted DOT services routed to this handler that do NOT produce an + # MCS-150 form, but still need a minimum set of intake fields before a human + # can actually file them. Without this, a paid order would be marked + # ready_to_file with empty intake (e.g. UCR with only a DOT number -- no + # legal name / state / fleet size, which determine the UCR fee bracket and + # the filing itself). Most of these can be DERIVED from the FMCSA census + + # the order email (see _enrich_admin_assisted_intake); we only hold the order + # at awaiting_intake for whatever genuinely can't be filled in. Keys are + # service slugs; values are the required intake_data fields. Mirrors + # REQUIRED_FIELDS in api/src/routes/compliance-orders.ts. + ADMIN_ASSISTED_REQUIRED = { + "ucr-registration": ["dot_number", "legal_name", "address_state", "email", "fleet_size_bracket"], + "boc3-filing": ["dot_number", "legal_name", "email"], + } + + # UCR fee tiers by total power units (FMCSA 2025 schedule). Used to derive + # fleet_size_bracket from the carrier's census power-unit count so we don't + # have to ask the customer for something the public record already states. + # (low, high_inclusive, bracket_label) + UCR_FLEET_BRACKETS = [ + (0, 2, "0-2"), + (3, 5, "3-5"), + (6, 20, "6-20"), + (21, 100, "21-100"), + (101, 1000, "101-1000"), + (1001, 10**9, "1001+"), + ] + async def process(self, order_data: dict) -> list[str]: """Entry point called by job_server. Delegates to handle().""" order_number = order_data.get("order_number", order_data.get("name", "")) @@ -158,6 +186,34 @@ class MCS150UpdateHandler: order_number, missing) return [] + # INTAKE-COMPLETENESS GATE for admin-assisted (non-MCS-150) services. + # These don't produce a form, but we still must not mark them + # ready_to_file with empty intake (e.g. a UCR with only a DOT number). + # First DERIVE everything we can from the FMCSA census + the order email, + # then -- if any required field is still missing -- email the customer to + # complete intake and hold the order at awaiting_intake. When all + # required fields are present (derived or supplied) we fall through to the + # admin review gate, which marks it ready_to_file for a human to file. + if slug in self.ADMIN_ASSISTED_REQUIRED: + intake = self._enrich_admin_assisted_intake(slug, intake, customer_email) + order_data["intake_data"] = intake # persist derived values downstream + missing = [f for f in self.ADMIN_ASSISTED_REQUIRED[slug] + if intake.get(f) in (None, "", [], {})] + if missing: + self._persist_intake(order_number, intake) + self._set_fulfillment_status(order_number, "awaiting_intake") + self._request_intake_completion( + order_number, entity_name, customer_email, dot_number, missing) + LOG.info("[%s] Admin-assisted %s held for intake; missing=%s", + order_number, slug, missing) + return [] + # All required fields present -- persist the census-derived values so + # the admin (and validation) sees a complete intake. + self._persist_intake(order_number, intake) + self._mark_intake_validated(order_number) + LOG.info("[%s] Admin-assisted %s intake complete (derived from census)", + order_number, slug) + # Step 1: Fill the official MCS-150 PDF. Only services that actually file # an MCS-150 produce the form; the other admin-assisted DOT services # routed to this handler (UCR, MC authority, audit prep, ETA, name @@ -701,6 +757,76 @@ class MCS150UpdateHandler: except Exception as exc: LOG.warning("[%s] Failed to set fulfillment_status=%s: %s", order_number, status, exc) + def _enrich_admin_assisted_intake(self, slug: str, intake: dict, customer_email: str) -> dict: + """Fill an admin-assisted service's required intake from data we already + have: the FMCSA census (legal_name, address_state, power_units) and the + order's customer_email. Customer-supplied values always win. Returns a + new merged dict; never raises. + """ + out = dict(intake or {}) + dot = out.get("dot_number", "") + try: + census = self._fetch_carrier_record(dot) if dot else {} + except Exception as exc: # noqa: BLE001 + LOG.warning("[enrich] census fetch failed for DOT %s: %s", dot, exc) + census = {} + # Census fills only what the customer didn't already provide. + for k in ("legal_name", "address_state", "address_city", "address_street", + "address_zip", "power_units", "ein", "phone"): + if not out.get(k) and census.get(k): + out[k] = census[k] + # Contact email: fall back to the order's customer email. + if not out.get("email") and customer_email: + out["email"] = customer_email + # UCR fee bracket: derive from power units when not supplied. + if slug == "ucr-registration" and not out.get("fleet_size_bracket"): + pu = out.get("power_units") or census.get("power_units") + try: + n = int(str(pu).strip()) + except (TypeError, ValueError): + n = None + if n is not None: + for lo, hi, label in self.UCR_FLEET_BRACKETS: + if lo <= n <= hi: + out["fleet_size_bracket"] = label + break + return out + + def _persist_intake(self, order_number: str, intake: dict): + """Write the (possibly census-enriched) intake_data back to the order so + the admin view, validation, and any later re-dispatch see it.""" + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + with conn.cursor() as cur: + cur.execute( + "UPDATE compliance_orders SET intake_data=%s, updated_at=now() " + "WHERE order_number=%s", + (json.dumps(intake), order_number), + ) + conn.commit() + conn.close() + except Exception as exc: # noqa: BLE001 + LOG.warning("[%s] Failed to persist intake_data: %s", order_number, exc) + + def _mark_intake_validated(self, order_number: str): + """Flag intake as complete so the order stops getting reminder nudges and + the admin sees a green 'intake complete'. Only call once every required + field is present (derived or supplied).""" + try: + import psycopg2 + conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) + with conn.cursor() as cur: + cur.execute( + "UPDATE compliance_orders SET intake_data_validated=TRUE, " + "validation_errors=NULL, updated_at=now() WHERE order_number=%s", + (order_number,), + ) + conn.commit() + conn.close() + except Exception as exc: # noqa: BLE001 + LOG.warning("[%s] Failed to mark intake validated: %s", order_number, exc) + def _create_admin_review_todo(self, order_number, entity_name, dot_number, slug, minio_path, customer_email, client_signed): """High-priority admin todo: verify the prepared filing BEFORE submission.