""" 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) # Step 1: Fill the official MCS-150 PDF pdf_path = None try: from scripts.document_gen.templates.mcs150_pdf_filler import fill_mcs150 pdf_path = fill_mcs150(intake, order_number=order_number) LOG.info("[%s] Filled MCS-150 PDF: %s", order_number, pdf_path) except Exception as exc: LOG.error("[%s] PDF fill failed: %s", order_number, exc) # Step 2: Upload PDF to MinIO for storage minio_path = None pdf_url = None if pdf_path: try: from minio import Minio mc = Minio( f"{os.environ.get('MINIO_ENDPOINT', 'minio')}:{os.environ.get('MINIO_PORT', '9000')}", access_key=os.environ.get("MINIO_ACCESS_KEY", ""), secret_key=os.environ.get("MINIO_SECRET_KEY", ""), secure=False, ) bucket = os.environ.get("MINIO_BUCKET", "performancewest") minio_path = f"filings/mcs150/{order_number}/{os.path.basename(pdf_path)}" mc.fobj_put(bucket, minio_path, open(pdf_path, "rb"), length=os.path.getsize(pdf_path), content_type="application/pdf") # Generate presigned URL for fax/web submission from datetime import timedelta pdf_url = mc.presigned_get_object(bucket, minio_path, expires=timedelta(hours=2)) LOG.info("[%s] PDF uploaded to MinIO: %s", order_number, minio_path) except Exception as exc: LOG.error("[%s] MinIO upload failed: %s", order_number, exc) # Step 3: Check for photo ID photo_id_path = None if intake.get("photo_id_uploaded"): try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) cur = conn.cursor() cur.execute( "SELECT minio_paths FROM id_upload_tokens WHERE order_number = %s AND front_uploaded = TRUE ORDER BY created_at DESC LIMIT 1", (order_number,), ) row = cur.fetchone() conn.close() if row and row[0]: paths = json.loads(row[0]) if isinstance(row[0], str) else row[0] if paths.get("front"): photo_id_path = paths["front"] LOG.info("[%s] Photo ID found: %s", order_number, photo_id_path) except Exception as exc: LOG.warning("[%s] Could not retrieve photo ID: %s", order_number, exc) # Step 4: Create e-sign record for perjury declaration # Customer must sign before we submit to FMCSA if pdf_path and minio_path: try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) cur = conn.cursor() cur.execute(""" INSERT INTO esign_records ( order_number, document_type, document_title, entity_name, document_minio_key, document_metadata, requires_perjury, status, expires_at ) VALUES (%s, %s, %s, %s, %s, %s, TRUE, 'pending', NOW() + interval '7 days') ON CONFLICT (order_number, document_type) WHERE status IN ('pending', 'signed') DO NOTHING """, ( order_number, "mcs150", "MCS-150 Biennial Update — Certification Under Penalty of Perjury", entity_name, minio_path, json.dumps({ "dot_number": dot_number, "form_type": "mcs150", "perjury_text": ( "I hereby certify under penalty of perjury that the information " "contained in this MCS-150 form is true and correct to the best " "of my knowledge and belief. I understand that making a false " "statement is punishable under 18 U.S.C. § 1001." ), }), )) conn.commit() conn.close() LOG.info("[%s] E-sign record created for MCS-150 perjury declaration", order_number) # Send e-sign link to customer self._send_esign_email(order_number, entity_name, dot_number, customer_email) # NOTE: The filing will be triggered by the esign_completed handler # after the customer signs. For now, also proceed with submission # since many orders may not have esign set up yet. except Exception as exc: LOG.error("[%s] E-sign setup failed (proceeding anyway): %s", order_number, exc) # Step 5: Submit electronically (3x web → fax fallback) filing_result = None if pdf_path: try: import asyncio from scripts.workers.fax_sender import submit_filing loop = asyncio.new_event_loop() filing_result = loop.run_until_complete(submit_filing( original_pdf_path=pdf_path, pdf_url=pdf_url or "", photo_id_path=photo_id_path, order_number=order_number, dot_number=dot_number, mc_number=intake.get("mc_number", ""), entity_name=entity_name, document_type="MCS-150 Biennial Update", web_retries=3, web_retry_interval=600, )) loop.close() if filing_result and filing_result.get("success"): LOG.info("[%s] Filing submitted via %s", order_number, filing_result.get("method")) else: LOG.warning("[%s] Electronic filing failed: %s", order_number, filing_result.get("error") if filing_result else "unknown") except Exception as exc: LOG.error("[%s] Filing submission error: %s", order_number, exc) # Step 5: Update order status in database try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) cur = conn.cursor() status_data = { "mcs150_status": mcs150_status, "pdf_minio_path": minio_path, "filing_method": filing_result.get("method") if filing_result else None, "filing_success": filing_result.get("success") if filing_result else False, "fax_log_id": filing_result.get("fax_log_id") if filing_result else None, "screenshot_path": filing_result.get("screenshot_path") if filing_result else None, "submitted_at": filing_result.get("submitted_at") if filing_result else None, "attested_pdf": filing_result.get("attested_pdf") if filing_result else None, } cur.execute(""" UPDATE compliance_orders SET intake_data = jsonb_set( COALESCE(intake_data, '{}'::jsonb), '{filing_status}', %s::jsonb ) WHERE order_number = %s """, (json.dumps(status_data), order_number)) conn.commit() conn.close() except Exception as exc: LOG.error("[%s] DB update failed: %s", order_number, exc) # Step 6: Create admin todo (for manual verification + customer delivery) try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) filed_method = filing_result.get("method", "pending") if filing_result else "pending" filed_ok = filing_result.get("success", False) if filing_result else False 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 {'Filed' if filed_ok else 'Review'} — {entity_name} (DOT {dot_number})", "filing", "low" if filed_ok else "normal", order_number, self.SERVICE_SLUG, f"MCS-150 for {entity_name} (DOT {dot_number}).\n" f"Filing method: {filed_method}\n" f"Status: {'SUBMITTED — verify in 5-10 days' if filed_ok else 'NEEDS MANUAL FILING'}\n" f"Customer: {customer_email}\n" f"PDF: {minio_path or 'not generated'}", json.dumps({ "order_number": order_number, "dot_number": dot_number, "entity_name": entity_name, "filing_result": filing_result, }), )) conn.commit() conn.close() except Exception as exc: LOG.error("[%s] Failed to create admin todo: %s", order_number, exc) # Step 7: Send client status email self._send_status_email(order_number, entity_name, dot_number, customer_email) return [minio_path] if minio_path else [] 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_esign_email(self, order_number, entity_name, dot_number, customer_email): """Send e-sign link for MCS-150 perjury declaration.""" if not customer_email: return try: import smtplib import jwt from email.mime.text import MIMEText secret = os.environ.get("JWT_SECRET", os.environ.get("ADMIN_JWT_SECRET", "")) token = jwt.encode( {"order_id": order_number, "order_type": "mcs150", "email": customer_email}, secret, algorithm="HS256", ) domain = os.environ.get("DOMAIN", "performancewest.net") esign_url = f"https://{domain}/portal/esign?token={token}" body = ( f"Hi,\n\n" f"Your MCS-150 Biennial Update for {entity_name} (DOT# {dot_number}) " f"has been prepared and is ready for your signature.\n\n" f"Federal law requires your certification under penalty of perjury " f"before we can submit this form to FMCSA.\n\n" f"Please review and sign here:\n{esign_url}\n\n" f"This link expires in 7 days.\n\n" f"Once you sign, we will submit the form to FMCSA electronically " f"and provide you with a Certificate of Filing.\n\n" f"Questions? Call (888) 411-0383.\n\n" f"Performance West Inc.\n" ) msg = MIMEText(body) msg["Subject"] = f"Action Required: Sign Your MCS-150 — {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] E-sign email sent to %s", order_number, customer_email) except Exception as exc: LOG.warning("[%s] Failed to send e-sign email: %s", order_number, 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)