"""FCC Form 499-A Discontinuance Filing Handler. For carriers who no longer provide telecommunications services and need to close out their USAC 499-A filing obligations. Files a final 499-A with zero revenue and requests discontinuance status from USAC. This is typically for: - Pure broadband resale ISPs who were incorrectly filing 499-A - Carriers who have ceased operations - Companies that were acquired and the FRN is being retired """ from __future__ import annotations import logging import os from datetime import datetime from .base_handler import BaseServiceHandler logger = logging.getLogger("workers.services.form_499a_discontinuance") class Form499ADiscontinuanceHandler(BaseServiceHandler): SERVICE_SLUG = "fcc-499a-discontinuance" SERVICE_NAME = "Form 499-A Discontinuance 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", {}) 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( "Form499ADiscontinuanceHandler: %s for %s (FRN: %s, Filer ID: %s)", order_number, legal_name, frn, filer_id, ) discontinuance_reason = intake_data.get("discontinuance_reason", "Ceased providing telecommunications services") last_service_date = intake_data.get("last_service_date", "") includes_zero_filing = not intake_data.get("has_separate_499a", False) # ── Generate USAC deactivation letter ────────────────────────── letter_path = None try: from scripts.document_gen.templates.form_499a_discontinuance_letter_generator import ( generate_discontinuance_letter, ) import tempfile work_dir = tempfile.mkdtemp(prefix=f"disc_{order_number}_") date_str = datetime.now().strftime("%Y%m%d") docx_path = os.path.join( work_dir, f"usac_deactivation_letter_{order_number}_{date_str}.docx", ) letter_path = generate_discontinuance_letter( entity_name=legal_name, filer_id=filer_id, frn=frn, ein=entity.get("ein", ""), address=entity.get("address", intake_data.get("address", "")), officer_name=intake_data.get("officer_name") or entity.get("contact_name", ""), officer_title=intake_data.get("officer_title") or entity.get("contact_title", ""), officer_email=entity.get("contact_email") or order_data.get("customer_email", ""), officer_phone=entity.get("contact_phone") or order_data.get("customer_phone", ""), termination_date=last_service_date, discontinuance_reason=discontinuance_reason, successor_entity=intake_data.get("successor_entity", ""), successor_filer_id=intake_data.get("successor_filer_id", ""), last_filing_year=int(entity.get("last_filing_year") or 0), includes_final_zero_filing=includes_zero_filing, outstanding_balances=intake_data.get("outstanding_balances", False), output_path=docx_path, ) if letter_path: logger.info("Discontinuance letter generated: %s", letter_path) # Upload to MinIO try: from scripts.workers.minio_client import upload_file minio_key = f"compliance/{order_number}/usac_deactivation_letter_{date_str}.docx" upload_file(letter_path, minio_key) logger.info("Uploaded to MinIO: %s", minio_key) except Exception as exc: logger.warning("MinIO upload failed: %s", exc) except Exception as exc: logger.warning("Discontinuance letter generation failed: %s", exc) # ── Client eSign gate ────────────────────────────────────────── # Officer must sign the deactivation letter before we send to USAC. client_approved = order_data.get("client_approved", False) if not client_approved and letter_path: minio_key = f"compliance/{order_number}/usac_deactivation_letter_{datetime.now().strftime('%Y%m%d')}.docx" try: import psycopg2 conn = psycopg2.connect(os.environ.get("DATABASE_URL", "")) from scripts.workers.services.telecom.esign_helper import request_esign request_esign( conn=conn, order_number=order_number, document_type="discontinuance", document_title="USAC Filer ID Deactivation Letter", entity_name=legal_name, customer_email=entity.get("contact_email") or order_data.get("customer_email", ""), customer_name=order_data.get("customer_name") or entity.get("contact_name", ""), document_minio_key=minio_key, requires_perjury=False, metadata={"frn": frn, "filer_id": filer_id}, ) conn.close() except Exception as exc: logger.warning("Could not create discontinuance eSign record: %s", exc) logger.info("Form499ADiscontinuanceHandler: paused for client eSign — order %s", order_number) return [letter_path] if letter_path else [] # Per FCC 499-A Instructions: discontinuance requires TWO steps: # 1. File the final 499-A (may have actual revenue from the portion # of the year the company operated — NOT required to be zero) # 2. Submit a deactivation letter to USAC within 30 days of ceasing service # # Line 603: check TRS/LNP/NANPA exemption boxes, write # "Not in business as of filing date" on the explanation line self._create_admin_todo( order_number, f"FILE 499-A DISCONTINUANCE for {legal_name}\n\n" f"FRN: {frn}\n" f"Filer ID: {filer_id}\n" f"Reason: {discontinuance_reason}\n" f"Last service date: {last_service_date or 'Not specified'}\n\n" f"STEP 1 — File Final 499-A {'(ZERO REVENUE — included in this order)' if includes_zero_filing else '(filed separately via full 499-A order)'}:\n" f" Log in to USAC E-File (https://forms.universalservice.org/)\n" f" {'File a zero-revenue 499-A (all revenue lines $0).' if includes_zero_filing else 'The full 499-A with actual revenue is being filed under a separate order.'}\n" f" On Line 603, check all exemption boxes (TRS, LNP, NANPA)\n" f" and write 'Not in business as of {last_service_date or 'filing date'}'\n" f" on the explanation line.\n\n" f"STEP 2 — Submit USAC Deactivation Letter:\n" f" Send letter to USAC (Form499@usac.org) with:\n" f" - Company name: {legal_name}\n" f" - Filer ID: {filer_id}\n" f" - FRN: {frn}\n" f" - Termination date: {last_service_date or 'TBD'}\n" f" - Reason: {discontinuance_reason}\n" f" - Successor entity: {intake_data.get('successor_entity', 'None')}\n" f" Must be submitted within 30 days of ceasing service.\n" f" Processing takes up to 60-90 days.\n\n" f"STEP 3 — Update CORES:\n" f" Update FCC CORES registration to reflect inactive status.\n\n" f"STEP 4 — Related Filings:\n" f" Confirm CPNI, RMD, and BDC filings are also discontinued.\n\n" f"DEACTIVATION LETTER: {'Generated — check MinIO compliance/' + order_number + '/' if letter_path else 'GENERATION FAILED — draft manually'}\n\n" f"Client email: {entity.get('contact_email') or order_data.get('customer_email', '')}", ) # ── Auto-email deactivation letter to USAC ────────────────────── # On prod with auto-filing enabled, sends the letter directly. # On dev, sends to admin for review. usac_email = os.environ.get("USAC_DEACTIVATION_EMAIL", "Form499@usac.org") admin_email = os.environ.get("ADMIN_EMAIL", "ops@performancewest.net") # In dev/test mode, redirect USAC emails to admin if os.environ.get("NODE_ENV") == "development": usac_email = admin_email logger.info("Dev mode: redirecting USAC deactivation to %s", usac_email) if letter_path: try: import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from email.mime.base import MIMEBase from email import encoders msg = MIMEMultipart() msg["From"] = os.environ.get("SMTP_FROM", "Performance West ") msg["To"] = usac_email msg["Cc"] = admin_email msg["Subject"] = f"Filer ID Deactivation Request — {legal_name} (Filer ID: {filer_id})" body = ( f"Please find attached a formal request to deactivate the 499 Filer ID " f"for {legal_name} (Filer ID: {filer_id}, FRN: {frn}).\n\n" f"Termination date: {last_service_date or 'See attached letter'}\n" f"Reason: {discontinuance_reason}\n\n" f"Please confirm deactivation at your earliest convenience.\n\n" f"Submitted by Performance West Inc. on behalf of {legal_name}.\n" f"Contact: {admin_email}" ) msg.attach(MIMEText(body, "plain")) # Attach the letter with open(letter_path, "rb") as f: part = MIMEBase("application", "vnd.openxmlformats-officedocument.wordprocessingml.document") part.set_payload(f.read()) encoders.encode_base64(part) part.add_header("Content-Disposition", f'attachment; filename="{os.path.basename(letter_path)}"') msg.attach(part) 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) logger.info("Deactivation letter emailed to %s (cc: %s)", usac_email, admin_email) except Exception as exc: logger.warning("Failed to email deactivation letter: %s", exc) # Send confirmation to client self._send_confirmation( to=entity.get("contact_email") or order_data.get("customer_email", ""), entity_name=legal_name, order_number=order_number, filer_id=filer_id, ) # Return list of file paths for MinIO upload (letter already uploaded above) return [letter_path] if letter_path else [] def _send_confirmation( self, to: str, entity_name: str, order_number: str, filer_id: str, ) -> None: if not to: return try: import smtplib from email.mime.multipart import MIMEMultipart from email.mime.text import MIMEText from scripts._email_plaintext import html_to_text subject = f"Form 499-A Discontinuance Filed — {entity_name}" html = f"""

Form 499-A Discontinuance

We've received your request to discontinue the FCC Form 499-A filing obligation for {entity_name} (Filer ID: {filer_id}).

We will:

  1. File your final Form 499-A reporting revenue for the period you were in service (this may be zero or actual revenue for a partial year)
  2. Submit a deactivation letter to USAC requesting closure of your filer account
  3. Update your FCC CORES registration to reflect inactive status
  4. Confirm discontinuance of related obligations (CPNI, RMD, BDC)

USAC processing takes 60-90 days. You'll receive a confirmation email at each step. During this period, you won't receive new invoices for USF contributions.

Order: {order_number}
Questions? Reply to this email or contact ops@performancewest.net.

""" msg = MIMEMultipart("alternative") msg["From"] = os.environ.get("SMTP_FROM", "Performance West ") msg["To"] = to msg["Subject"] = subject msg.attach(MIMEText(html_to_text(html), "plain")) 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("Discontinuance confirmation email failed: %s", exc)