"""FCC Form 499-Q Quarterly Filing Handler. After the client submits quarterly revenue via /order/fcc-499q, this handler: 1. Generates a 499-Q prep summary 2. Checks auto-filing toggle (same as 499-A) 3. If auto-filing enabled: submits to USAC E-File via Playwright 4. Captures confirmation number, sends client confirmation email 5. Records filing in the database The 499-Q is simpler than the 499-A — just 4 revenue categories projected for the quarter, determining the quarterly USF contribution. """ from __future__ import annotations import json import logging import os import tempfile from datetime import datetime from .base_handler import BaseServiceHandler from .telecom.auto_filing import check_auto_filing, request_admin_review logger = logging.getLogger("workers.services.form_499q") USAC_EFILE_URL = "https://forms.universalservice.org" class Form499QHandler(BaseServiceHandler): SERVICE_SLUG = "fcc-499q" SERVICE_NAME = "FCC Form 499-Q Quarterly Filing" def _create_admin_todo(self, order_number: str, description: str) -> None: try: from scripts.workers.erpnext_client import ERPNextClient ERPNextClient().create_resource("ToDo", { "description": f"[{self.SERVICE_SLUG}] {order_number}\n\n{description}", "priority": "High", "role": "Accounting Advisor", }) except Exception as exc: logger.error("Could not create admin ToDo: %s", exc) async def process(self, order_data: dict) -> dict | None: order_number = order_data.get("order_number", "") entity = order_data.get("entity", {}) intake_data = order_data.get("intake_data", {}) if not intake_data.get("intake_completed"): logger.info("Form499QHandler: %s intake not completed — waiting", order_number) return [] quarter = intake_data.get("quarter", "?") revenue = intake_data.get("revenue", {}) filer_id = intake_data.get("filer_id_499") or entity.get("filer_id_499", "") frn = intake_data.get("frn") or entity.get("frn", "") legal_name = entity.get("legal_name") or intake_data.get("entity_name", "") logger.info( "Form499QHandler: %s %s for %s (total: $%.2f)", order_number, quarter, legal_name, revenue.get("total", 0), ) # ── Auto-filing check ────────────────────────────────────────── decision = check_auto_filing(order_data) if not decision.may_submit: logger.info("Form499QHandler: staging for admin review (order=%s)", order_number) request_admin_review( order_number=order_number, service_slug=self.SERVICE_SLUG, service_name=self.SERVICE_NAME, entity_name=legal_name, frn=frn, packet_minio_paths=[], admin_email=decision.admin_email, summary=( f"499-Q {quarter} ready for {legal_name}. " f"Filer ID: {filer_id}. Total revenue: ${revenue.get('total', 0):.2f}. " f"Due: {intake_data.get('due_date', '?')}. " f"CC Inter: ${revenue.get('carriers_carrier_interstate', 0):.2f}, " f"CC Intra: ${revenue.get('carriers_carrier_intrastate', 0):.2f}, " f"EU Inter: ${revenue.get('end_user_interstate', 0):.2f}, " f"EU Intra: ${revenue.get('end_user_intrastate', 0):.2f}. " f"Submit via USAC E-File at {USAC_EFILE_URL}." ), ) # Send client confirmation that we received their data self._send_received_email( to=entity.get("contact_email") or order_data.get("customer_email", ""), entity_name=legal_name, order_number=order_number, quarter=quarter, due_date=intake_data.get("due_date", ""), ) return [] # no files to upload — admin will file manually # ── USAC E-File submission via Playwright ────────────────────── work_dir = tempfile.mkdtemp(prefix=f"499q_{order_number}_") confirmation_number = "" try: confirmation_number = await self._submit_to_usac( order_number=order_number, entity=entity, intake_data=intake_data, revenue=revenue, work_dir=work_dir, ) except Exception as exc: logger.error("Form499QHandler: USAC submission failed: %s", exc) self._create_admin_todo( order_number, f"499-Q {quarter} AUTO-FILING FAILED for {legal_name}\n\n" f"Error: {exc}\n\n" f"File manually at {USAC_EFILE_URL}\n" f"Filer ID: {filer_id}, FRN: {frn}\n" f"Revenue total: ${revenue.get('total', 0):.2f}", ) # ── Confirmation ─────────────────────────────────────────────── if confirmation_number: logger.info("Form499QHandler: %s filed — confirmation %s", order_number, confirmation_number) # Record filing try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) with conn.cursor() as cur: cur.execute( """UPDATE compliance_orders SET intake_data = intake_data || %s::jsonb, updated_at = now() WHERE order_number = %s""", (json.dumps({"confirmation_number": confirmation_number, "filed_at": datetime.utcnow().isoformat()}), order_number), ) conn.commit() conn.close() except Exception as exc: logger.warning("Could not record confirmation: %s", exc) self._send_confirmation_email( to=entity.get("contact_email") or order_data.get("customer_email", ""), entity_name=legal_name, order_number=order_number, quarter=quarter, confirmation=confirmation_number, ) else: # No confirmation — send "received" email, admin will file manually self._send_received_email( to=entity.get("contact_email") or order_data.get("customer_email", ""), entity_name=legal_name, order_number=order_number, quarter=quarter, due_date=intake_data.get("due_date", ""), ) return [] # confirmation PDF already uploaded to MinIO in _submit_to_usac async def _submit_to_usac( self, order_number: str, entity: dict, intake_data: dict, revenue: dict, work_dir: str, ) -> str: """Submit 499-Q to USAC E-File via Playwright. Returns confirmation number.""" from scripts.workers.services.telecom.human_delay import human_delay frn = intake_data.get("frn") or entity.get("frn", "") filer_id = intake_data.get("filer_id_499") or entity.get("filer_id_499", "") quarter = intake_data.get("quarter", "") USAC_STORAGE_STATE = os.environ.get( "USAC_STORAGE_STATE", "/app/data/usac_session.json", ) if not os.path.exists(USAC_STORAGE_STATE): self._create_admin_todo( order_number, f"499-Q {quarter}: No USAC E-File session found. " f"Log in at {USAC_EFILE_URL}, export session to {USAC_STORAGE_STATE}, " f"then re-dispatch.", ) return "" try: from playwright.async_api import async_playwright except ImportError: logger.warning("Playwright not available — creating admin todo") self._create_admin_todo( order_number, f"499-Q {quarter}: Playwright not installed. File manually at {USAC_EFILE_URL}.", ) return "" confirmation_number = "" async with async_playwright() as p: browser = await p.chromium.launch(headless=True) context = await browser.new_context(storage_state=USAC_STORAGE_STATE) page = await context.new_page() try: await page.goto(USAC_EFILE_URL, timeout=30000) await human_delay() # Navigate to Form 499-Q await page.click('text="Form 499-Q"') await human_delay() # Fill FRN / Filer ID await page.fill('input[name="frn"]', frn) await page.fill('input[name="filer_id"]', filer_id) await human_delay() # Revenue fields — the 499-Q has simplified revenue blocks # Carrier's carrier revenue cc_inter = revenue.get("carriers_carrier_interstate", 0) cc_intra = revenue.get("carriers_carrier_intrastate", 0) eu_inter = revenue.get("end_user_interstate", 0) eu_intra = revenue.get("end_user_intrastate", 0) # Fill revenue fields (exact selectors TBD from USAC form recon) for selector, value in [ ('input[name*="cc_interstate"], input[name*="carriers_carrier_inter"]', cc_inter), ('input[name*="cc_intrastate"], input[name*="carriers_carrier_intra"]', cc_intra), ('input[name*="eu_interstate"], input[name*="end_user_inter"]', eu_inter), ('input[name*="eu_intrastate"], input[name*="end_user_intra"]', eu_intra), ]: try: # Try each selector variant for sel in selector.split(", "): el = page.locator(sel) if await el.count() > 0: await el.fill(str(int(value * 100))) # cents break except Exception: pass await human_delay(0.3, 0.8) # Submit await human_delay(1.0, 2.0) await page.click('button:has-text("Review")') await page.wait_for_selector("text=Review", timeout=30000) await page.click('button:has-text("Submit")') await page.wait_for_selector("text=Confirmation", timeout=60000) # Capture confirmation body = await page.locator("body").inner_text() for line in body.splitlines(): if "Confirmation" in line or "Filing ID" in line: parts = line.split(":", 1) if len(parts) == 2 and parts[1].strip(): confirmation_number = parts[1].strip() break # Save confirmation PDF conf_path = os.path.join(work_dir, f"499q_{quarter}_confirmation.pdf") await page.pdf(path=conf_path, format="Letter") # Upload to MinIO try: from scripts.workers.minio_client import upload_file upload_file(conf_path, f"compliance/{order_number}/499q_{quarter}_confirmation.pdf") except Exception: pass except Exception as exc: logger.error("USAC 499-Q Playwright error: %s", exc) # Screenshot for debugging try: ss_path = os.path.join(work_dir, "usac_error.png") await page.screenshot(path=ss_path, full_page=True) except Exception: pass raise finally: await browser.close() return confirmation_number def _send_received_email(self, to: str, entity_name: str, order_number: str, quarter: str, due_date: str) -> None: if not to: return self._send_html_email( to=to, subject=f"499-Q {quarter} Data Received — {entity_name}", html=f"""

Form 499-Q {quarter} — Data Received

Your quarterly revenue data for {entity_name} ({quarter}, due {due_date}) has been received.

We'll file this with USAC and send you a confirmation email with your filing reference number once complete.

Order: {order_number}
Questions? Contact ops@performancewest.net.

""", ) def _send_confirmation_email(self, to: str, entity_name: str, order_number: str, quarter: str, confirmation: str) -> None: if not to: return self._send_html_email( to=to, subject=f"499-Q {quarter} Filed — Confirmation {confirmation} — {entity_name}", html=f"""

Form 499-Q {quarter} — Filed Successfully

Your FCC Form 499-Q quarterly filing for {entity_name} ({quarter}) has been successfully submitted to USAC.

Confirmation Number

{confirmation}

A confirmation PDF has been saved to your account. USAC will calculate your quarterly USF contribution based on the revenue data submitted.

Order: {order_number}
Questions? Contact ops@performancewest.net.

""", ) def _send_html_email(self, to: str, subject: str, html: str) -> None: try: import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText msg = MIMEMultipart("alternative") msg["From"] = os.environ.get("SMTP_FROM", "Performance West ") msg["To"] = to msg["Subject"] = subject msg.attach(MIMEText(html, "html")) with smtplib.SMTP( os.environ.get("SMTP_HOST", "co.carrierone.com"), int(os.environ.get("SMTP_PORT", "587")), timeout=30, ) as s: s.starttls() s.login(os.environ.get("SMTP_USER", ""), os.environ.get("SMTP_PASS", "")) s.send_message(msg) except Exception as exc: logger.warning("Email send failed: %s", exc)