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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
256
scripts/workers/services/foreign_carrier_affiliation.py
Normal file
256
scripts/workers/services/foreign_carrier_affiliation.py
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
"""Foreign Carrier Affiliation Notification handler (47 CFR § 63.11).
|
||||
|
||||
Section 63.11 requires U.S. carriers affiliated with a foreign carrier
|
||||
serving the same route to/from the United States to file a notification
|
||||
with the FCC's International Bureau. The notification goes to ECFS /
|
||||
IBFS — scaffolding here uses the ECFS Express upload path (same as CPNI
|
||||
and a few other proceedings).
|
||||
|
||||
Intake fields (intake_data.foreign_carrier):
|
||||
foreign_carrier_legal_name: str
|
||||
country: str (ISO-2)
|
||||
ownership_pct: float
|
||||
affected_routes: list[str] # ISO-2 country codes
|
||||
affiliation_date: str # YYYY-MM-DD
|
||||
notification_type: str # "pre-consummation" | "post-closing"
|
||||
|
||||
Rare-enough filing that we generate the notification letter + auto-submit
|
||||
when the auto-filing toggle is on, but we prefer admin review on this one
|
||||
because a miscategorized affiliation is costly.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .base_handler import BaseServiceHandler
|
||||
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__)
|
||||
|
||||
ECFS_UPLOAD_URL = os.environ.get(
|
||||
"FCC_ECFS_UPLOAD_URL", "https://www.fcc.gov/ecfs/upload/express",
|
||||
)
|
||||
# IB Docket 99-217 is the historical home; current filings often use IB
|
||||
# Docket 04-47 (Section 63.10/63.11 proceedings). Override via env for
|
||||
# deployment-specific needs.
|
||||
FCC_63_11_DOCKET = os.environ.get("FCC_63_11_DOCKET", "04-47")
|
||||
|
||||
|
||||
class ForeignCarrierAffiliationHandler(BaseServiceHandler):
|
||||
SERVICE_SLUG = "fcc-63-11-notification"
|
||||
SERVICE_NAME = "Foreign Carrier Affiliation Notification (47 CFR § 63.11)"
|
||||
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", {})
|
||||
intake = order_data.get("intake_data") or {}
|
||||
fc_intake = intake.get("foreign_carrier") or {}
|
||||
entity_id = entity.get("id")
|
||||
|
||||
generated: list[str] = []
|
||||
|
||||
required = ["foreign_carrier_legal_name", "country",
|
||||
"ownership_pct", "affected_routes", "affiliation_date"]
|
||||
missing = [k for k in required if not fc_intake.get(k)]
|
||||
if missing:
|
||||
self._create_admin_todo(
|
||||
order_number,
|
||||
f"63.11 notification requires intake_data.foreign_carrier to "
|
||||
f"carry: {missing}. Ask the customer to complete intake.",
|
||||
)
|
||||
return generated
|
||||
|
||||
# Generate the notification letter
|
||||
letter_path = self._write_letter(
|
||||
order_number=order_number, entity=entity,
|
||||
fc_intake=fc_intake, work_dir=work_dir,
|
||||
)
|
||||
if letter_path:
|
||||
generated.append(letter_path)
|
||||
try:
|
||||
generated.append(self._convert_to_pdf(letter_path))
|
||||
except Exception as exc:
|
||||
logger.warning("63.11 letter PDF conversion failed: %s", exc)
|
||||
|
||||
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=[f"compliance/{order_number}/{os.path.basename(p)}" for p in generated],
|
||||
admin_email=decision.admin_email,
|
||||
summary=(
|
||||
f"47 CFR § 63.11 affiliation notification.\n"
|
||||
f"Foreign carrier: {fc_intake['foreign_carrier_legal_name']} "
|
||||
f"({fc_intake['country']}).\n"
|
||||
f"Ownership: {fc_intake['ownership_pct']}%.\n"
|
||||
f"Affected routes: {', '.join(fc_intake.get('affected_routes', []))}.\n"
|
||||
f"Affiliation date: {fc_intake['affiliation_date']}."
|
||||
),
|
||||
)
|
||||
return generated
|
||||
|
||||
# Auto-submit via ECFS
|
||||
conf_path, conf_num = await self._submit_to_ecfs(
|
||||
order_number=order_number, entity=entity, letter_pdf=next(
|
||||
(p for p in generated if p.endswith(".pdf")), letter_path,
|
||||
), work_dir=work_dir,
|
||||
)
|
||||
if conf_path:
|
||||
generated.append(conf_path)
|
||||
|
||||
if entity_id and conf_num:
|
||||
self._persist_affiliation(entity_id, fc_intake, conf_num)
|
||||
|
||||
return generated
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def _write_letter(
|
||||
self, *, order_number: str, entity: dict, fc_intake: dict, work_dir: str,
|
||||
) -> str:
|
||||
from docx import Document
|
||||
doc = Document()
|
||||
doc.add_heading(
|
||||
"Notification of Foreign Carrier Affiliation "
|
||||
"(47 CFR § 63.11)", level=1,
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Filed: {datetime.now().strftime('%B %d, %Y')}"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Filing party: {entity.get('legal_name', '')} "
|
||||
f"(FRN {entity.get('frn', 'N/A')})"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"To: Federal Communications Commission — International Bureau"
|
||||
)
|
||||
doc.add_paragraph("")
|
||||
doc.add_paragraph(
|
||||
f"Pursuant to 47 CFR § 63.11, {entity.get('legal_name', '')} "
|
||||
f"notifies the Commission of an affiliation with a foreign "
|
||||
f"carrier as follows:"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Foreign carrier legal name: {fc_intake['foreign_carrier_legal_name']}"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Country / jurisdiction of foreign carrier: {fc_intake['country']}"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Ownership interest: {fc_intake['ownership_pct']}%"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Affected route(s): {', '.join(fc_intake.get('affected_routes', []))}"
|
||||
)
|
||||
doc.add_paragraph(
|
||||
f"Affiliation date: {fc_intake['affiliation_date']} "
|
||||
f"({fc_intake.get('notification_type', 'post-closing')})"
|
||||
)
|
||||
doc.add_paragraph("")
|
||||
doc.add_paragraph(
|
||||
"This notification is submitted in accordance with the "
|
||||
"requirements and timing specified in 47 CFR § 63.11(a) "
|
||||
"and 63.11(b). The filing party certifies that the "
|
||||
"information provided is true and correct to the best of its "
|
||||
"knowledge."
|
||||
)
|
||||
for _ in range(2):
|
||||
doc.add_paragraph("")
|
||||
doc.add_paragraph("_" * 45)
|
||||
doc.add_paragraph(entity.get("ceo_name") or entity.get("contact_name", ""))
|
||||
doc.add_paragraph(entity.get("ceo_title", "Chief Executive Officer"))
|
||||
doc.add_paragraph(entity.get("legal_name", ""))
|
||||
|
||||
out = os.path.join(work_dir, f"fcc_63_11_letter_{order_number}.docx")
|
||||
doc.save(out)
|
||||
return out
|
||||
|
||||
async def _submit_to_ecfs(
|
||||
self, *, order_number: str, entity: dict,
|
||||
letter_pdf: str, work_dir: str,
|
||||
) -> tuple[str | None, str]:
|
||||
conf_path = os.path.join(work_dir, f"ecfs_63_11_confirmation_{order_number}.pdf")
|
||||
confirmation = ""
|
||||
try:
|
||||
async with undetected_browser(headless=True) as (ctx, page):
|
||||
await page.goto(ECFS_UPLOAD_URL, wait_until="domcontentloaded")
|
||||
await human_delay(1.5, 3.0)
|
||||
await type_slowly(page, 'input[name="proceedings"]', FCC_63_11_DOCKET)
|
||||
await page.wait_for_selector(f'li:has-text("{FCC_63_11_DOCKET}")', timeout=10000)
|
||||
await page.click(f'li:has-text("{FCC_63_11_DOCKET}")')
|
||||
await human_delay()
|
||||
await type_slowly(page, 'input[name="name_of_filer"]', entity.get("legal_name", ""))
|
||||
await type_slowly(page, 'input[name="filer_email"]', entity.get("contact_email", ""))
|
||||
await page.select_option('select[name="type_of_filing"]', label="Notification")
|
||||
await page.set_input_files('input[type="file"]', letter_pdf)
|
||||
await human_delay(1.0, 2.0)
|
||||
await page.click('button:has-text("Continue")')
|
||||
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)
|
||||
body = await page.locator("body").inner_text()
|
||||
for line in body.splitlines():
|
||||
if "Filing ID" in line or "Confirmation" in line:
|
||||
parts = line.split(":", 1)
|
||||
if len(parts) == 2 and parts[1].strip():
|
||||
confirmation = parts[1].strip()
|
||||
break
|
||||
await page.pdf(path=conf_path, format="Letter")
|
||||
return conf_path, confirmation
|
||||
except Exception as exc:
|
||||
logger.exception("63.11 ECFS submission failed: %s", exc)
|
||||
self._create_admin_todo(
|
||||
order_number,
|
||||
f"63.11 ECFS submission raised: {exc}. Packet is in MinIO; "
|
||||
f"file manually at https://www.fcc.gov/ecfs/ under docket {FCC_63_11_DOCKET}.",
|
||||
)
|
||||
return None, ""
|
||||
|
||||
def _persist_affiliation(self, entity_id: int, fc_intake: dict,
|
||||
confirmation: str) -> None:
|
||||
try:
|
||||
import json
|
||||
import psycopg2
|
||||
conn = psycopg2.connect(os.environ.get("DATABASE_URL", ""))
|
||||
record = dict(fc_intake)
|
||||
record["filed_at"] = datetime.utcnow().isoformat()
|
||||
record["ecfs_confirmation"] = confirmation
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
UPDATE telecom_entities
|
||||
SET foreign_affiliations = COALESCE(foreign_affiliations, '[]'::jsonb)
|
||||
|| %s::jsonb
|
||||
WHERE id = %s
|
||||
""",
|
||||
(json.dumps([record]), entity_id),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
except Exception as exc:
|
||||
logger.warning("Could not persist affiliation on %s: %s", entity_id, 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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue