new-site/scripts/workers/services/mcs150_update.py
justin 33da00fd89 50-state trucking compliance: services, checker, order page, CA landing
- Migration 079: state_trucking_requirements table seeded for all 51 jurisdictions
  (IRP, IFTA, weight-distance taxes, MCP/CARB, intrastate authority, state DOT)
- Migration 080: carrier_operating_states tracking table
- 13 new state trucking services in catalog ($99-$599)
- StateTruckingHandler with state-specific admin todos
- DOT compliance checker: 7 new state-level checks (IRP, IFTA, weight tax,
  MCP/CARB, emissions, intrastate authority, state DOT number)
- New API endpoint: GET /api/v1/dot/state-requirements
- DOT order page: state compliance service cards with auto-preselect
- California trucking landing page (MCP + CARB + IRP + IFTA)
- Fix: DOT checker nav missing Trucking/DOT section
- Fix: All 8 DOT intake pages missing style block (dangling text)
- Fix: DOT confirmation email now says "Order Confirmed" not "Action Required"
- Fix: MCS150/BOC3/StateTrucking handlers missing async process() method
- Fix: StateTruckingHandler connection leak + slug resolution

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-29 12:46:33 -05:00

203 lines
7.9 KiB
Python

"""
MCS-150 Biennial Update Service Handler.
The MCS-150 is filed through the FMCSA Portal (portal.fmcsa.dot.gov) using
Login.gov credentials + MFA. Since we can't automate through MFA, this is
an admin-assisted service:
1. Client orders MCS-150 update
2. We collect their updated info via intake form
3. Admin logs into FMCSA Portal with client's credentials (provided by client)
OR we prepare the data and walk the client through filing via screen share
4. We verify the update was accepted
5. We send confirmation with updated company snapshot
Service slug: mcs150-update
Price: $79
Gov fee: $0
Intake data needed:
- DOT number
- Legal name (confirm/update)
- DBA name (confirm/update)
- Principal business address
- Mailing address
- Phone number
- Email address
- Number of power units
- Number of drivers
- Operation type (interstate/intrastate)
- Carrier operation (authorized for hire, exempt for hire, private)
- Cargo types
- Hazmat (Y/N)
- Annual mileage + year
- FMCSA Portal login credentials (Login.gov email + password)
OR "I need help creating my Login.gov account"
Filing approach:
Option A: Client provides Login.gov credentials → admin files directly
Option B: Guided filing via screen share (Zoom/Teams) → $29 upcharge
Option C: We prepare a pre-filled PDF → client uploads themselves (cheapest)
"""
from __future__ import annotations
import json
import logging
import os
from datetime import datetime
LOG = logging.getLogger("workers.services.mcs150_update")
class MCS150UpdateHandler:
"""Handle MCS-150 biennial update orders."""
SERVICE_SLUG = "mcs150-update"
SERVICE_NAME = "MCS-150 Biennial Update"
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", ""))
return self.handle(order_data, order_number)
def handle(self, order_data: dict, order_number: str) -> list[str]:
"""
Process an MCS-150 update order.
Since FMCSA Portal requires Login.gov MFA, this creates an admin
todo rather than automating the filing directly.
"""
LOG.info("[%s] Processing MCS-150 update order", order_number)
intake = order_data.get("intake_data") or {}
if isinstance(intake, str):
intake = json.loads(intake)
dot_number = intake.get("dot_number", "")
entity_name = intake.get("entity_name", order_data.get("customer_name", ""))
customer_email = order_data.get("customer_email", "")
# Validate required fields
if not dot_number:
LOG.error("[%s] Missing DOT number in intake data", order_number)
return []
# Check current MCS-150 status via FMCSA API
mcs150_status = self._check_current_status(dot_number)
# Create admin todo with all the info needed to file
todo_data = {
"order_number": order_number,
"service": self.SERVICE_NAME,
"dot_number": dot_number,
"entity_name": entity_name,
"customer_email": customer_email,
"current_status": mcs150_status,
"intake_data": intake,
"filing_url": "https://portal.fmcsa.dot.gov/login",
"steps": [
"1. Log into FMCSA Portal with client's Login.gov credentials",
"2. Navigate to Registration > MCS-150",
"3. Update fields with intake data provided",
"4. Verify all information is correct",
"5. Submit the update",
"6. Take screenshot of confirmation",
"7. Download updated company snapshot from SAFER",
"8. Email confirmation + snapshot to client",
],
}
# Create admin todo
try:
import psycopg2
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
with conn.cursor() as cur:
cur.execute("""
INSERT INTO admin_todos (
title, category, priority, order_number, service_slug,
description, data, status
) VALUES (%s, %s, %s, %s, %s, %s, %s, 'pending')
""", (
f"MCS-150 Update — {entity_name} (DOT {dot_number})",
"filing",
"normal",
order_number,
self.SERVICE_SLUG,
f"File MCS-150 biennial update for {entity_name}.\n"
f"DOT: {dot_number}\n"
f"Customer: {customer_email}\n"
f"Current MCS-150 status: {mcs150_status}\n\n"
f"Client intake data attached. Log into FMCSA Portal and update.",
json.dumps(todo_data),
))
conn.commit()
conn.close()
LOG.info("[%s] Admin todo created for MCS-150 update", order_number)
except Exception as exc:
LOG.error("[%s] Failed to create admin todo: %s", order_number, exc)
# Send client a status email
self._send_status_email(order_number, entity_name, dot_number, customer_email)
return [] # No generated files — admin handles the filing
def _check_current_status(self, dot_number: str) -> str:
"""Check current MCS-150 status via FMCSA API."""
try:
import urllib.request
api_key = os.environ.get("FMCSA_API_KEY", "")
if not api_key:
return "API key not configured"
url = f"https://mobile.fmcsa.dot.gov/qc/services/carriers/{dot_number}?webKey={api_key}"
req = urllib.request.Request(url, headers={"Accept": "application/json"})
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
carrier = data.get("content", {}).get("carrier", {})
outdated = carrier.get("mcs150Outdated", "?")
status = carrier.get("statusCode", "?")
allowed = carrier.get("allowedToOperate", "?")
return (
f"Status: {status}, Allowed: {allowed}, "
f"MCS-150 Outdated: {outdated}"
)
except Exception as exc:
return f"Could not check: {exc}"
def _send_status_email(self, order_number, entity_name, dot_number, customer_email):
"""Send client an email that we're working on their update."""
if not customer_email:
return
try:
import smtplib
from email.mime.text import MIMEText
body = (
f"Hi,\n\n"
f"We've received your MCS-150 biennial update order for "
f"{entity_name} (DOT# {dot_number}).\n\n"
f"Order: {order_number}\n\n"
f"Our team will review your intake information and complete "
f"the filing within 1-2 business days. We'll send you a "
f"confirmation with your updated company snapshot once it's done.\n\n"
f"If we need your FMCSA Portal login credentials, we'll reach "
f"out via a separate secure email.\n\n"
f"Questions? Reply to this email or call (888) 411-0383.\n\n"
f"Performance West Inc.\n"
f"DOT Compliance Services\n"
)
msg = MIMEText(body)
msg["Subject"] = f"MCS-150 Update In Progress — {entity_name} (DOT {dot_number})"
msg["From"] = "noreply@performancewest.net"
msg["To"] = customer_email
with smtplib.SMTP("localhost", 25) as s:
s.sendmail(msg["From"], [customer_email], msg.as_string())
LOG.info("[%s] Status email sent to %s", order_number, customer_email)
except Exception as exc:
LOG.warning("[%s] Failed to send status email: %s", order_number, exc)