- 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>
272 lines
11 KiB
Python
272 lines
11 KiB
Python
"""
|
|
BOC-3 Process Agent Designation Service Handler.
|
|
|
|
The BOC-3 designates a process agent in every US state who can accept
|
|
legal documents on behalf of a motor carrier. The process agent (not
|
|
the carrier) files the form with FMCSA.
|
|
|
|
Model: Performance West partners with a blanket process agent service
|
|
(e.g., NWRA or similar) who covers all 48 contiguous states + DC.
|
|
We collect the carrier's info, submit the designation to our process
|
|
agent partner, they file the BOC-3 electronically with FMCSA.
|
|
|
|
Service slug: boc3-filing
|
|
Price: $149
|
|
Gov fee: $0
|
|
|
|
Intake data needed:
|
|
- DOT number
|
|
- MC/FF/MX number (docket number)
|
|
- Legal name
|
|
- DBA name (if any)
|
|
- Business address
|
|
- Phone number
|
|
- Email
|
|
- Entity type (carrier, broker, freight forwarder)
|
|
|
|
Filing flow:
|
|
1. Client orders BOC-3 filing
|
|
2. We collect intake data
|
|
3. We submit designation request to process agent partner
|
|
4. Process agent files BOC-3 electronically with FMCSA
|
|
5. We verify filing on FMCSA L&I system
|
|
6. We send confirmation to client
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import logging
|
|
import os
|
|
from datetime import datetime
|
|
|
|
LOG = logging.getLogger("workers.services.boc3_filing")
|
|
|
|
# Process agent partner details
|
|
# NWRA (Northwest Registered Agent) confirmed they no longer offer BOC-3.
|
|
# Registered Agents Inc is the replacement vendor (contract pending).
|
|
# Wholesale cost: ~$25 or less. Manual fulfillment until API is available.
|
|
PROCESS_AGENT_PARTNER = {
|
|
"name": "Registered Agents Inc",
|
|
"contact_email": "", # Update when contract is executed
|
|
"api_endpoint": None, # Will be set when RAI API is available
|
|
}
|
|
|
|
|
|
class BOC3FilingHandler:
|
|
"""Handle BOC-3 process agent designation orders."""
|
|
|
|
SERVICE_SLUG = "boc3-filing"
|
|
SERVICE_NAME = "BOC-3 Process Agent Filing"
|
|
|
|
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 a BOC-3 filing order.
|
|
|
|
Currently creates an admin todo. When process agent partner API
|
|
is available, this will automate the submission.
|
|
"""
|
|
LOG.info("[%s] Processing BOC-3 filing order", order_number)
|
|
|
|
intake = order_data.get("intake_data") or {}
|
|
if isinstance(intake, str):
|
|
intake = json.loads(intake)
|
|
|
|
dot_number = intake.get("dot_number", "")
|
|
docket_number = intake.get("docket_number", "") # MC-XXXXXX
|
|
entity_name = intake.get("entity_name", order_data.get("customer_name", ""))
|
|
customer_email = order_data.get("customer_email", "")
|
|
entity_type = intake.get("entity_type", "carrier") # carrier, broker, freight_forwarder
|
|
|
|
if not dot_number:
|
|
LOG.error("[%s] Missing DOT number", order_number)
|
|
return []
|
|
|
|
# Check current BOC-3 status
|
|
boc3_status = self._check_boc3_status(dot_number)
|
|
|
|
# Build the designation request
|
|
designation = {
|
|
"dot_number": dot_number,
|
|
"docket_number": docket_number,
|
|
"legal_name": entity_name,
|
|
"dba_name": intake.get("dba_name", ""),
|
|
"business_address": {
|
|
"street": intake.get("address_street", ""),
|
|
"city": intake.get("address_city", ""),
|
|
"state": intake.get("address_state", ""),
|
|
"zip": intake.get("address_zip", ""),
|
|
},
|
|
"phone": intake.get("phone", ""),
|
|
"email": customer_email,
|
|
"entity_type": entity_type,
|
|
"requested_at": datetime.utcnow().isoformat(),
|
|
}
|
|
|
|
# If we have a process agent API, submit directly
|
|
if PROCESS_AGENT_PARTNER.get("api_endpoint"):
|
|
success = self._submit_to_process_agent(designation)
|
|
if success:
|
|
self._send_confirmation_email(order_number, entity_name, dot_number, customer_email)
|
|
return []
|
|
|
|
# Otherwise create admin todo
|
|
todo_data = {
|
|
"order_number": order_number,
|
|
"service": self.SERVICE_NAME,
|
|
"designation": designation,
|
|
"current_boc3_status": boc3_status,
|
|
"steps": [
|
|
"1. Submit BOC-3 designation request to process agent partner",
|
|
f" Partner: {PROCESS_AGENT_PARTNER['name']}",
|
|
f" Email: {PROCESS_AGENT_PARTNER.get('contact_email', 'TBD')}",
|
|
"2. Include: DOT#, MC#, legal name, address for all 48 states + DC",
|
|
"3. Process agent files BOC-3 electronically with FMCSA",
|
|
"4. Verify filing at https://li-public.fmcsa.dot.gov/LIVIEW/pkg_carrquery.prc_carrlist",
|
|
"5. Send confirmation to client",
|
|
],
|
|
}
|
|
|
|
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"BOC-3 Filing — {entity_name} (DOT {dot_number})",
|
|
"filing",
|
|
"high",
|
|
order_number,
|
|
self.SERVICE_SLUG,
|
|
f"File BOC-3 process agent designation for {entity_name}.\n"
|
|
f"DOT: {dot_number}\n"
|
|
f"MC/Docket: {docket_number}\n"
|
|
f"Type: {entity_type}\n"
|
|
f"Customer: {customer_email}\n\n"
|
|
f"Submit to process agent partner for electronic filing with FMCSA.",
|
|
json.dumps(todo_data),
|
|
))
|
|
conn.commit()
|
|
conn.close()
|
|
LOG.info("[%s] Admin todo created for BOC-3 filing", order_number)
|
|
except Exception as exc:
|
|
LOG.error("[%s] Failed to create admin todo: %s", order_number, exc)
|
|
|
|
# Send status email
|
|
self._send_status_email(order_number, entity_name, dot_number, customer_email)
|
|
|
|
return []
|
|
|
|
def _check_boc3_status(self, dot_number: str) -> str:
|
|
"""Check if carrier has a BOC-3 on file 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/"
|
|
f"{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", {})
|
|
# BOC-3 status isn't directly in the API, but we can check
|
|
# if authority is active (requires BOC-3 + insurance on file)
|
|
common = carrier.get("commonAuthorityStatus", "N")
|
|
contract = carrier.get("contractAuthorityStatus", "N")
|
|
broker = carrier.get("brokerAuthorityStatus", "N")
|
|
|
|
if common == "A" or contract == "A" or broker == "A":
|
|
return "Authority active (BOC-3 likely on file)"
|
|
else:
|
|
return "No active authority (BOC-3 may be needed)"
|
|
except Exception as exc:
|
|
return f"Could not check: {exc}"
|
|
|
|
def _submit_to_process_agent(self, designation: dict) -> bool:
|
|
"""Submit designation to process agent partner via API."""
|
|
# TODO: Implement when partner API is available
|
|
LOG.warning("Process agent API not configured — falling back to admin todo")
|
|
return False
|
|
|
|
def _send_status_email(self, order_number, entity_name, dot_number, customer_email):
|
|
"""Send client an email that we're working on their BOC-3."""
|
|
if not customer_email:
|
|
return
|
|
try:
|
|
import smtplib
|
|
from email.mime.text import MIMEText
|
|
|
|
body = (
|
|
f"Hi,\n\n"
|
|
f"We've received your BOC-3 process agent designation order for "
|
|
f"{entity_name} (DOT# {dot_number}).\n\n"
|
|
f"Order: {order_number}\n\n"
|
|
f"We're submitting your designation to our blanket process agent "
|
|
f"who covers all 48 contiguous states plus DC. Once filed with "
|
|
f"FMCSA, your operating authority will reflect the active BOC-3.\n\n"
|
|
f"This is typically completed within 1-2 business days.\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"BOC-3 Filing 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)
|
|
|
|
def _send_confirmation_email(self, order_number, entity_name, dot_number, customer_email):
|
|
"""Send confirmation that BOC-3 has been filed."""
|
|
if not customer_email:
|
|
return
|
|
try:
|
|
import smtplib
|
|
from email.mime.text import MIMEText
|
|
|
|
body = (
|
|
f"Hi,\n\n"
|
|
f"Your BOC-3 process agent designation has been filed with FMCSA "
|
|
f"for {entity_name} (DOT# {dot_number}).\n\n"
|
|
f"Order: {order_number}\n\n"
|
|
f"Your process agent is now designated in all 48 contiguous states "
|
|
f"plus the District of Columbia. This designation remains active "
|
|
f"as long as your carrier account is maintained.\n\n"
|
|
f"You can verify your BOC-3 status at:\n"
|
|
f"https://li-public.fmcsa.dot.gov/LIVIEW/pkg_carrquery.prc_carrlist\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"BOC-3 Filed — {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] Confirmation email sent to %s", order_number, customer_email)
|
|
except Exception as exc:
|
|
LOG.warning("[%s] Failed to send confirmation email: %s", order_number, exc)
|