""" 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 asyncio import json import logging import os from datetime import datetime from scripts.workers.telegram_notify import notify_fulfillment_todo LOG = logging.getLogger("workers.services.boc3_filing") # Process agent partner: Registered Agents Inc / Process Agent LLC # ProcessAgent.com is a subsidiary of Registered Agents Inc. # They own commercial offices in every US state (blanket agent). # Cost: $25/year per carrier. No API — file via Playwright on their site. # Phone: (406) 300-4044 PROCESS_AGENT_PARTNER = { "name": "Process Agent LLC (Registered Agents Inc)", "website": "https://www.processagent.com", "phone": "(406) 300-4044", "cost_cents": 2500, # $25/year "automation": "playwright", # No API — automate via browser } 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. Tries Playwright, falls back to handle().""" order_number = order_data.get("order_number", order_data.get("name", "")) intake = order_data.get("intake_data") or {} if isinstance(intake, str): intake = json.loads(intake) dot_number = intake.get("dot_number", "") customer_email = order_data.get("customer_email", "") # Try Playwright automation if credentials are configured if dot_number and os.environ.get("PW_CARD_NUMBER") and os.environ.get("BOC3_ACCOUNT_PASSWORD"): try: from .boc3_playwright import BOC3ProcessAgent adapter = BOC3ProcessAgent() result = await adapter.file_boc3({ "dot_number": dot_number, "docket_number": intake.get("docket_number", ""), "legal_name": intake.get("entity_name", order_data.get("customer_name", "")), "entity_type": intake.get("entity_type", "carrier"), "contact": { "first_name": (order_data.get("customer_name") or "").split()[0] if order_data.get("customer_name") else "", "last_name": " ".join((order_data.get("customer_name") or "").split()[1:]), "phone": intake.get("phone", ""), "street": intake.get("address_street", ""), "city": intake.get("address_city", ""), "state": intake.get("address_state", ""), "zip": intake.get("address_zip", ""), }, "email": customer_email, }) if result.success: LOG.info("[%s] BOC-3 filed via Playwright! Order: %s", order_number, result.order_id) self._send_confirmation_email( order_number, intake.get("entity_name", order_data.get("customer_name", "")), dot_number, customer_email, ) return [] elif result.captcha_hit: LOG.warning("[%s] CAPTCHA on processagent.com — falling back to admin todo", order_number) else: LOG.warning("[%s] Playwright filing failed: %s — admin todo", order_number, result.error) except Exception as exc: LOG.warning("[%s] Playwright error: %s — admin todo", order_number, exc) # Fall back to manual admin todo 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 authority/BOC-3 status (structured) and branch on it. auth = self._get_authority_state(dot_number) boc3_status = auth["summary"] branch = auth["branch"] # Branch-specific follow-ups. These are surfaced for upsell-approve on the # order timeline (customer/admin confirms + pays) — NEVER auto-charged. recommended_followups: list[dict] = [] branch_steps: list[str] = [] if branch == "active": # Default behavior: just file/refresh the BOC-3. branch_steps = ["Authority is ACTIVE — file/refresh BOC-3 only."] elif branch == "pending": branch_steps = [ "Authority is PENDING — BOC-3 can be filed now (parallel OK).", "Authority will NOT activate until active insurance (BMC-91/BMC-34) is on file" " AND the ~21-day vetting/protest window passes.", ] recommended_followups.append({ "type": "insurance_reminder", "title": "Confirm active insurance is on file", "reason": "Pending authority needs insurance + the 21-day vetting " "window before it activates.", "service_slug": None, }) elif branch == "revoked": branch_steps = [ "Authority is REVOKED/INACTIVE — BOC-3 alone does NOT reinstate.", "Recommend reinstatement (OP-1 reinstatement + $80 gov fee).", ] recommended_followups.append({ "type": "upsell", "title": "Reinstate operating authority", "reason": "Authority is revoked; a BOC-3 cannot activate revoked " "authority. Reinstatement (OP-1 + $80 FMCSA fee) is required.", "service_slug": "mc-authority", }) elif branch == "none": branch_steps = [ "NO operating authority on file (USDOT only) — BOC-3 has nothing to attach to.", "MC operating authority is likely needed FIRST; do NOT file BOC-3 in isolation.", ] recommended_followups.append({ "type": "upsell", "title": "Apply for MC operating authority first", "reason": "A BOC-3 designates process agents for an operating " "authority. With USDOT only, authority must be obtained first.", "service_slug": "mc-authority", }) else: branch_steps = [ f"Authority status UNKNOWN ({boc3_status}) — verify manually before filing.", ] # 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(), } # Create admin todo for manual filing (Playwright attempt already made in process()) todo_data = { "order_number": order_number, "service": self.SERVICE_NAME, "designation": designation, "current_boc3_status": boc3_status, "authority_state": auth, "recommended_followups": recommended_followups, "steps": branch_steps + [ "1. Go to https://www.processagent.com/order", "2. Submit BOC-3 order ($25) with carrier's DOT#, MC#, legal name, address", f" Partner: {PROCESS_AGENT_PARTNER['name']}", "3. Process Agent LLC files BOC-3 electronically with FMCSA (1-5 business days)", "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", "")) todo_title = f"BOC-3 Filing — {entity_name} (DOT {dot_number})" todo_description = ( 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"Authority status: {boc3_status}\n" f"Customer: {customer_email}\n\n" + ("Recommended follow-ups (upsell-approve, not auto-charged):\n" + "\n".join(f" - {f['title']}: {f['reason']}" for f in recommended_followups) + "\n\n" if recommended_followups else "") + f"Submit to process agent partner for electronic filing with FMCSA." ) 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') """, ( todo_title, "filing", "high", order_number, self.SERVICE_SLUG, todo_description, json.dumps(todo_data), )) conn.commit() notify_fulfillment_todo( title=todo_title, order_number=order_number, service_slug=self.SERVICE_SLUG, priority="high", description=todo_description, ) 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: """Human-readable summary string (kept for backward compat / emails).""" auth = self._get_authority_state(dot_number) return auth.get("summary", "Could not determine authority status") def _get_authority_state(self, dot_number: str) -> dict: """ Return structured authority state from FMCSA QC API. Keys: common/contract/broker (raw status codes A/I/P/N/None), any_active (bool), any_pending (bool), any_revoked (bool), has_any_authority (bool), branch (str), summary (str). Branch is one of: active | pending | revoked | none | unknown. """ result = { "common": None, "contract": None, "broker": None, "any_active": False, "any_pending": False, "any_revoked": False, "has_any_authority": False, "branch": "unknown", "summary": "Could not determine authority status", } try: import urllib.request api_key = os.environ.get("FMCSA_API_KEY", "") if not api_key: result["summary"] = "API key not configured" return result 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", {}) common = carrier.get("commonAuthorityStatus") contract = carrier.get("contractAuthorityStatus") broker = carrier.get("brokerAuthorityStatus") statuses = [s for s in (common, contract, broker) if s] result.update(common=common, contract=contract, broker=broker) # FMCSA codes: A=active, I=inactive, P=pending, N=none/not authorized. result["any_active"] = any(s == "A" for s in statuses) result["any_pending"] = any(s == "P" for s in statuses) result["any_revoked"] = any(s == "I" for s in statuses) result["has_any_authority"] = any(s in ("A", "I", "P") for s in statuses) if result["any_active"]: result["branch"] = "active" result["summary"] = "Authority active (BOC-3 likely on file)" elif result["any_pending"]: result["branch"] = "pending" result["summary"] = "Authority pending (needs BOC-3 + insurance to activate)" elif result["any_revoked"]: result["branch"] = "revoked" result["summary"] = "Authority revoked/inactive (reinstatement likely needed)" else: result["branch"] = "none" result["summary"] = "No operating authority on file (USDOT only)" except Exception as exc: result["summary"] = f"Could not check: {exc}" return result 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 import os as _smtp_os with smtplib.SMTP(_smtp_os.getenv("SMTP_HOST", "co.carrierone.com"), int(_smtp_os.getenv("SMTP_PORT", "587")), timeout=30) as s: s.starttls() _u, _p = _smtp_os.getenv("SMTP_USER", ""), _smtp_os.getenv("SMTP_PASS", "") if _u and _p: s.login(_u, _p) 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 import os as _smtp_os with smtplib.SMTP(_smtp_os.getenv("SMTP_HOST", "co.carrierone.com"), int(_smtp_os.getenv("SMTP_PORT", "587")), timeout=30) as s: s.starttls() _u, _p = _smtp_os.getenv("SMTP_USER", ""), _smtp_os.getenv("SMTP_PASS", "") if _u and _p: s.login(_u, _p) 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)