new-site/scripts/workers/services/form_499_initial.py
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
Includes: API (Express/TypeScript), Astro site, Python workers,
document generators, FCC compliance tools, Canada CRTC formation,
Ansible infrastructure, and deployment scripts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-27 06:54:22 -05:00

320 lines
14 KiB
Python

"""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)