"""Form 499-A Initial (New Filer) Registration handler. Per USAC rules, new telecommunications providers must register with the Universal Service Administrative Company within 30 days of offering service. The "New Filer Registration" path at forms.universalservice.org creates a USAC Filer ID (812xxx) that every subsequent 499-A / 499-Q filing references. Distinct from our annual ``Form499AHandler`` — new filers have no revenue to report yet; we submit Blocks 1, 2, and 6 (identifying info, contacts, officer certification) and skip revenue. Flow: 1. Intake-driven Playwright session against https://forms.universalservice.org → "Register as New Filer" 2. Submit Blocks 1, 2-A, 2-B, 2-C, 6 3. Capture Filer ID from confirmation 4. Persist filer_id_499 on the entity 5. Schedule next April 1 in compliance_calendar """ from __future__ import annotations import logging import os from datetime import date, datetime from typing import Optional from .base_handler import BaseServiceHandler from .telecom import filing_state from .telecom.auto_filing import check_auto_filing, request_admin_review from .telecom.undetected_browser import undetected_browser, human_delay, type_slowly logger = logging.getLogger(__name__) USAC_EFILE_URL = os.environ.get( "USAC_EFILE_URL", "https://forms.universalservice.org/", ) class Form499InitialHandler(BaseServiceHandler): SERVICE_SLUG = "fcc-499-initial" SERVICE_NAME = "Form 499 Initial Registration" REQUIRES_LLM = False async def process(self, order_data: dict) -> list[str]: work_dir = self._make_work_dir() order_number = order_data["name"] entity = order_data.get("entity", {}) entity_id = entity.get("id") if not entity.get("frn"): self._create_admin_todo( order_number, "Form 499 Initial Registration requires an FRN. Order a " "cores-frn-registration first, then re-dispatch this.", ) return [] if entity.get("filer_id_499"): logger.info( "Form499InitialHandler: entity %s already has filer_id_499 %s — " "skipping", entity_id, entity.get("filer_id_499"), ) return [] decision = check_auto_filing(order_data) if not decision.may_submit: request_admin_review( order_number=order_number, service_slug=self.SERVICE_SLUG, service_name=self.SERVICE_NAME, entity_name=entity.get("legal_name", ""), frn=entity.get("frn", ""), packet_minio_paths=[], admin_email=decision.admin_email, summary=( "New-filer registration at USAC E-File. Blocks 1+2+6 only " "(no revenue to report). Will assign Filer ID on success." ), ) return [] filer_id, confirmation_path = await self._submit_new_filer( order_number=order_number, entity=entity, work_dir=work_dir, ) generated: list[str] = [] if confirmation_path: generated.append(confirmation_path) if filer_id and entity_id: self._persist_filer_id(entity_id, filer_id) self._schedule_first_499a(order_number, entity) return generated # ------------------------------------------------------------------ # # USAC E-File flow — New Filer Registration # ------------------------------------------------------------------ # async def _submit_new_filer( self, *, order_number: str, entity: dict, work_dir: str, ) -> tuple[str, Optional[str]]: confirmation_path = os.path.join( work_dir, f"usac_new_filer_confirmation_{order_number}.pdf", ) try: async with undetected_browser(headless=True) as (ctx, page): await page.goto(USAC_EFILE_URL, wait_until="domcontentloaded") await human_delay(1.5, 3.0) # Click into New Filer Registration await page.click("text=Register as New Filer") await human_delay() # Block 1 — identifying info await type_slowly(page, 'input[name="company_name"]', entity.get("legal_name", "")) await page.fill('input[name="frn"]', entity.get("frn", "")) if entity.get("dba_name"): await page.fill('input[name="dba_name"]', entity.get("dba_name", "")) if entity.get("ein"): await page.fill('input[name="ein"]', entity.get("ein", "").replace("-", "")) if entity.get("affiliated_filer_name"): await page.fill('input[name="affiliated_filer_name"]', entity["affiliated_filer_name"]) await page.fill('input[name="affiliated_filer_ein"]', entity.get("affiliated_filer_ein", "")) for i, tn in enumerate((entity.get("trade_names") or [])[:10]): try: await page.fill(f'input[name="trade_name_{i}"]', tn) except Exception: pass # Line 105 — multi-select ranked categories from .telecom.fcc_499_utils import all_line_105_boxes_to_tick categories = entity.get("line_105_categories") or [] if not categories and entity.get("carrier_category"): # Legacy single-category fallback categories = [{"id": entity["carrier_category"], "rank": 1, "infra_type": entity.get("infra_type", "facilities")}] for box_num in all_line_105_boxes_to_tick(categories): try: await page.check(f'input[name="line_105_box_{box_num}"]') except Exception: pass # Block 2-A — regulatory contact (entity overrides, else PW defaults) await page.fill('input[name="regulatory_contact_name"]', entity.get("regulatory_contact_name") or entity.get("contact_name", "")) await page.fill('input[name="regulatory_contact_email"]', entity.get("regulatory_contact_email") or entity.get("contact_email", "")) await page.fill('input[name="regulatory_contact_phone"]', entity.get("regulatory_contact_phone") or entity.get("contact_phone", "")) # Block 2-B — D.C. Agent # Source priority: intake_data.dc_agent → entity columns → NWRA default intake = order_data.get("intake_data", {}) or {} dc = intake.get("dc_agent", {}) or {} await page.fill('input[name="dc_agent_company"]', dc.get("company") or entity.get("dc_agent_company", "Northwest Registered Agent Service Inc.")) await page.fill('input[name="dc_agent_street"]', dc.get("street") or entity.get("dc_agent_street", "1717 N Street NW STE 1")) await page.fill('input[name="dc_agent_city"]', dc.get("city") or entity.get("dc_agent_city", "Washington")) await page.fill('input[name="dc_agent_state"]', dc.get("state") or entity.get("dc_agent_state", "DC")) await page.fill('input[name="dc_agent_zip"]', dc.get("zip") or entity.get("dc_agent_zip", "20036")) # Block 2-C — Officers (up to 3, with business addresses) for i in (1, 2, 3): name = entity.get(f"officer_{i}_name") if i > 1 else ( entity.get("officer_1_name") or entity.get("ceo_name") ) title = entity.get(f"officer_{i}_title") if i > 1 else ( entity.get("officer_1_title") or entity.get("ceo_title") ) if not name: continue await page.fill(f'input[name="officer_{i}_name"]', name) await page.fill(f'input[name="officer_{i}_title"]', title or "") await page.fill(f'input[name="officer_{i}_street"]', entity.get(f"officer_{i}_street", "")) await page.fill(f'input[name="officer_{i}_city"]', entity.get(f"officer_{i}_city", "")) await page.fill(f'input[name="officer_{i}_state"]', entity.get(f"officer_{i}_state", "")) await page.fill(f'input[name="officer_{i}_zip"]', entity.get(f"officer_{i}_zip", "")) # Line 227: jurisdictions multi-select for state in entity.get("jurisdictions_served") or []: try: await page.check(f'input[name="line_227_state_{state}"]') except Exception: pass # Line 228: first-service date (or pre-1999 checkbox) if entity.get("first_telecom_service_pre_1999"): try: await page.check('input[name="line_228_pre_1999"]') except Exception: pass else: if entity.get("first_telecom_service_year"): await page.fill('input[name="line_228_year"]', str(entity["first_telecom_service_year"])) if entity.get("first_telecom_service_month"): await page.fill('input[name="line_228_month"]', str(entity["first_telecom_service_month"])) # Block 6 — certification signature sig_name = entity.get("officer_1_name") or entity.get("ceo_name") \ or entity.get("contact_name", "") await page.fill('input[name="officer_signature_name"]', sig_name) await page.check('input[name="officer_certification"]') await human_delay(1.5, 3.0) await page.click('button:has-text("Submit")') await page.wait_for_selector("text=Filer ID", timeout=90000) body = await page.locator("body").inner_text() filer_id = "" import re m = re.search(r"\bFiler ID[:\s]*(\d{6,8})\b", body) if m: filer_id = m.group(1) await page.pdf(path=confirmation_path, format="Letter") if not filer_id: self._create_admin_todo( order_number, "USAC new-filer submission completed but Filer ID could " "not be extracted. Check the confirmation PDF in MinIO; " "update telecom_entities.filer_id_499 manually.", ) return "", confirmation_path logger.info( "Form499InitialHandler: assigned Filer ID %s for %s", filer_id, entity.get("legal_name", ""), ) return filer_id, confirmation_path except Exception as exc: logger.exception("Form499InitialHandler: USAC flow failed: %s", exc) self._create_admin_todo( order_number, f"USAC new-filer registration raised: {exc}. Complete manually " f"at {USAC_EFILE_URL} and update telecom_entities.filer_id_499.", ) return "", None # ------------------------------------------------------------------ # # Persistence + calendar scheduling # ------------------------------------------------------------------ # def _persist_filer_id(self, entity_id: int, filer_id: str) -> None: try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) with conn.cursor() as cur: cur.execute( "UPDATE telecom_entities SET filer_id_499=%s WHERE id=%s", (filer_id, entity_id), ) conn.commit() conn.close() except Exception as exc: logger.warning("Could not persist filer_id_499 on %s: %s", entity_id, exc) def _schedule_first_499a(self, order_number: str, entity: dict) -> None: """Write the next April 1 deadline into ERPNext Compliance Calendar.""" try: from scripts.workers.erpnext_client import ERPNextClient year = datetime.utcnow().year + ( 0 if datetime.utcnow().month < 4 else 1 ) due = date(year, 4, 1).strftime("%Y-%m-%d") ERPNextClient().create_resource( "Compliance Calendar", { "entity_name": entity.get("legal_name", ""), "order_reference": order_number, "compliance_type": "FCC Form 499-A", "description": ( f"First annual 499-A filing for " f"{entity.get('legal_name', '')} (Filer ID pending). " f"Due April 1 following the reporting year." ), "due_date": due, "recurring": 1, "recurrence_period":"Yearly", "status": "Upcoming", }, ) logger.info( "Form499InitialHandler: scheduled first 499-A for %s on %s", entity.get("legal_name", ""), due, ) except Exception as exc: logger.warning("Could not schedule first 499-A calendar entry: %s", exc) 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)