From b90d44366756b3cca4bc421725840270f5a51ef9 Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 30 May 2026 18:58:14 -0500 Subject: [PATCH] add submit_filing(): 3x web retry then fax fallback, fix success detection, shared attestation helper --- scripts/workers/fax_sender.py | 186 ++++++++++++++---- .../workers/services/fmcsa_web_submitter.py | 13 +- 2 files changed, 162 insertions(+), 37 deletions(-) diff --git a/scripts/workers/fax_sender.py b/scripts/workers/fax_sender.py index 5799f2e..d0f4b1b 100644 --- a/scripts/workers/fax_sender.py +++ b/scripts/workers/fax_sender.py @@ -174,6 +174,141 @@ async def poll_fax_status(log_id: str | int, timeout: int = 300, interval: int = } +async def submit_filing( + original_pdf_path: str, + pdf_url: str = "", + photo_id_path: str | None = None, + order_number: str = "", + dot_number: str = "", + mc_number: str = "", + entity_name: str = "", + document_type: str = "MCS-150 Biennial Update", + recipient_name: str = "Federal Motor Carrier Safety Administration (FMCSA)", + web_retries: int = 3, + web_retry_interval: int = 600, +) -> dict: + """Submit a filing to FMCSA: try web submission first, fall back to fax. + + Attempts electronic submission via ask.fmcsa.dot.gov up to `web_retries` + times, waiting `web_retry_interval` seconds between attempts. If all + web attempts fail, falls back to faxing to FMCSA at 202-366-3477. + + After successful submission (either method), generates an attestation + cover page with digital signature and prepends it to the original PDF. + + Returns: + dict with keys: success, method, attested_pdf, submitted_at, + screenshot_path, fax_log_id, error + """ + from scripts.workers.services.fmcsa_web_submitter import submit_mcs150 + + # ── Phase 1: Try web submission (up to 3 attempts, 10 min apart) ── + for attempt in range(1, web_retries + 1): + LOG.info("[filing] Web submission attempt %d/%d for DOT %s", + attempt, web_retries, dot_number) + + web_result = await submit_mcs150( + pdf_path=original_pdf_path, + photo_id_path=photo_id_path, + dot_number=dot_number, + mc_number=mc_number, + entity_name=entity_name, + ) + + if web_result["success"]: + LOG.info("[filing] Web submission succeeded for DOT %s on attempt %d", + dot_number, attempt) + # Generate attestation + attested = await _generate_attestation( + original_pdf_path=original_pdf_path, + order_number=order_number, + dot_number=dot_number, + entity_name=entity_name, + document_type=document_type, + recipient_name=recipient_name, + submitted_at=datetime.fromisoformat(web_result["submitted_at"]), + ) + return { + "success": True, + "method": "web", + "attested_pdf": attested, + "submitted_at": web_result["submitted_at"], + "screenshot_path": web_result.get("screenshot_path") + or web_result.get("pre_submit_screenshot"), + "fax_log_id": None, + "error": None, + } + + LOG.warning("[filing] Web attempt %d failed: %s", attempt, web_result["error"]) + if attempt < web_retries: + LOG.info("[filing] Waiting %ds before retry...", web_retry_interval) + await asyncio.sleep(web_retry_interval) + + # ── Phase 2: Fall back to fax ── + LOG.info("[filing] All %d web attempts failed, falling back to fax for DOT %s", + web_retries, dot_number) + + if not pdf_url: + return { + "success": False, + "method": "none", + "attested_pdf": None, + "submitted_at": None, + "screenshot_path": None, + "fax_log_id": None, + "error": "Web submission failed and no PDF URL provided for fax fallback", + } + + fax_result = await send_and_attest( + pdf_url=pdf_url, + original_pdf_path=original_pdf_path, + order_number=order_number, + dot_number=dot_number, + entity_name=entity_name, + document_type=document_type, + recipient_name=recipient_name, + ) + + return { + "success": fax_result["success"], + "method": "fax" if fax_result["success"] else "none", + "attested_pdf": fax_result.get("attested_pdf"), + "submitted_at": fax_result.get("submitted_at"), + "screenshot_path": None, + "fax_log_id": fax_result.get("fax_log_id"), + "error": fax_result.get("error"), + } + + +async def _generate_attestation( + original_pdf_path: str, + order_number: str, + dot_number: str, + entity_name: str, + document_type: str, + recipient_name: str, + submitted_at: datetime, +) -> str | None: + """Generate attestation cover page and prepend to original PDF.""" + try: + from scripts.document_gen.templates.filing_attestation import ( + generate_attestation_page, + prepend_attestation, + ) + attest_pdf = generate_attestation_page( + order_number=order_number, + dot_number=dot_number, + entity_name=entity_name, + document_type=document_type, + submitted_at=submitted_at, + recipient_name=recipient_name, + ) + return prepend_attestation(attest_pdf, original_pdf_path) + except Exception as exc: + LOG.error("[filing] Attestation generation failed: %s", exc) + return None + + async def send_and_attest( pdf_url: str, original_pdf_path: str, @@ -216,39 +351,20 @@ async def send_and_attest( # 3. Fax delivered — generate attestation submitted_at = datetime.now(timezone.utc) + attested = await _generate_attestation( + original_pdf_path=original_pdf_path, + order_number=order_number, + dot_number=dot_number, + entity_name=entity_name, + document_type=document_type, + recipient_name=recipient_name, + submitted_at=submitted_at, + ) - try: - from scripts.document_gen.templates.filing_attestation import ( - generate_attestation_page, - prepend_attestation, - ) - - attest_pdf = generate_attestation_page( - order_number=order_number, - dot_number=dot_number, - entity_name=entity_name, - document_type=document_type, - submitted_at=submitted_at, - recipient_name=recipient_name, - ) - - combined_pdf = prepend_attestation(attest_pdf, original_pdf_path) - - LOG.info("[fax] Attested copy generated: %s", combined_pdf) - - return { - "success": True, - "attested_pdf": combined_pdf, - "fax_log_id": log_id, - "submitted_at": submitted_at.isoformat(), - "error": None, - } - except Exception as exc: - LOG.error("[fax] Attestation generation failed: %s", exc) - return { - "success": True, # Fax still succeeded - "attested_pdf": None, - "fax_log_id": log_id, - "submitted_at": submitted_at.isoformat(), - "error": f"Fax sent but attestation failed: {exc}", - } + return { + "success": True, + "attested_pdf": attested, + "fax_log_id": log_id, + "submitted_at": submitted_at.isoformat(), + "error": None if attested else "Fax sent but attestation generation failed", + } diff --git a/scripts/workers/services/fmcsa_web_submitter.py b/scripts/workers/services/fmcsa_web_submitter.py index b845229..0b8cd7a 100644 --- a/scripts/workers/services/fmcsa_web_submitter.py +++ b/scripts/workers/services/fmcsa_web_submitter.py @@ -229,10 +229,19 @@ async def submit_mcs150( confirmation_text = await page.inner_text("body") # Check for success indicators - success = any(kw in confirmation_text.lower() for kw in [ + # Note: after successful submission, FMCSA redirects to www.fmcsa.dot.gov + # which may return 403 (Akamai WAF). The submission still went through — + # FMCSA sends a confirmation email to the address we provided. + # So a redirect away from ask.fmcsa.dot.gov IS a success signal. + redirected_away = "ask.fmcsa.dot.gov" not in page.url + has_confirmation_keywords = any(kw in confirmation_text.lower() for kw in [ "thank you", "submitted", "received", "confirmation", - "reference number", "ticket", "case" + "reference number", "ticket", "case", ]) + has_error_keywords = any(kw in confirmation_text.lower() for kw in [ + "please select an item", "required field", "invalid", + ]) + success = (redirected_away or has_confirmation_keywords) and not has_error_keywords LOG.info("[fmcsa] Submission %s for DOT %s", "succeeded" if success else "may have failed", dot_number)