Add USAC Filer ID Deactivation Letter template

Formal letter addressed to USAC Contributor Operations requesting
deactivation of a 499 Filer ID. Covers:
- Entity identification (name, Filer ID, FRN, EIN)
- Reason for deactivation + termination date
- Final 499-A status (zero-revenue included OR filed separately)
- Successor entity info (if applicable)
- Outstanding balance acknowledgment
- Related filings confirmation (RMD, CPNI, BDC)
- Officer signature block
- Entity summary box

Handler updated to:
- Generate the letter via document_gen template
- Upload DOCX to MinIO (compliance/{order_number}/)
- Reference the letter in admin todo

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
justin 2026-05-03 06:15:26 -05:00
parent eee2aa497b
commit a404cb1b57
2 changed files with 341 additions and 3 deletions

View file

@ -0,0 +1,295 @@
"""
Generate USAC Filer ID Deactivation Request Letter.
Produces a formal letter requesting USAC to deactivate a 499 Filer ID
pursuant to the FCC Form 499-A filing obligations. The letter is
submitted to USAC Contributor Operations (Form499@usac.org) along
with a final 499-A filing.
Per 2026 FCC Form 499-A Instructions:
- Filers that cease providing telecommunications must deactivate
their Filer ID with USAC by submitting a letter with termination
date and successor entity information
- Must be submitted within 30 days of ceasing service
- Processing takes 60-90 days
Usage:
from scripts.document_gen.templates.form_499a_discontinuance_letter_generator import (
generate_discontinuance_letter,
)
path = generate_discontinuance_letter(
entity_name="Acme Telecom LLC",
filer_id="829999",
frn="0015341902",
...
)
"""
from __future__ import annotations
import logging
from datetime import datetime
from pathlib import Path
from typing import Optional
LOG = logging.getLogger("document_gen.discontinuance_letter")
try:
from docx import Document
from docx.shared import Pt, Inches, RGBColor
from docx.enum.text import WD_ALIGN_PARAGRAPH
from docx.oxml.ns import qn
from docx.oxml import OxmlElement
except ImportError:
LOG.warning("python-docx not installed — discontinuance letter generation unavailable")
Document = None # type: ignore
NAVY = RGBColor(0x1A, 0x27, 0x44) if Document else None
BODY_SIZE = Pt(10) if Document else None
HEADING_SIZE = Pt(12) if Document else None
def _add_body(doc, text: str, bold: bool = False, italic: bool = False,
size=None, color=None, alignment=None) -> None:
p = doc.add_paragraph()
p.paragraph_format.space_after = Pt(6)
if alignment:
p.alignment = alignment
run = p.add_run(text)
run.font.size = size or BODY_SIZE
run.bold = bold
run.italic = italic
if color:
run.font.color.rgb = color
def _add_horizontal_rule(doc) -> None:
p = doc.add_paragraph()
pPr = p._p.get_or_add_pPr()
pBdr = OxmlElement("w:pBdr")
bottom = OxmlElement("w:bottom")
bottom.set(qn("w:val"), "single")
bottom.set(qn("w:sz"), "6")
bottom.set(qn("w:space"), "1")
bottom.set(qn("w:color"), "1A2744")
pBdr.append(bottom)
pPr.append(pBdr)
def generate_discontinuance_letter(
# Entity
entity_name: str,
filer_id: str = "",
frn: str = "",
ein: str = "",
address: str = "",
# Contact / Officer
officer_name: str = "",
officer_title: str = "",
officer_email: str = "",
officer_phone: str = "",
# Discontinuance details
termination_date: str = "",
discontinuance_reason: str = "Company is no longer providing telecommunications services",
successor_entity: str = "",
successor_filer_id: str = "",
last_filing_year: int = 0,
# Flags
includes_final_zero_filing: bool = True,
outstanding_balances: bool = False,
# Output
output_path: str = "/tmp/discontinuance_letter.docx",
) -> Optional[str]:
"""Generate the USAC Filer ID Deactivation Request Letter as a DOCX file."""
if Document is None:
LOG.error("python-docx not installed")
return None
today = datetime.now()
today_str = today.strftime("%B %d, %Y")
term_date = termination_date or today_str
filing_year = last_filing_year or (today.year - 1)
doc = Document()
for section in doc.sections:
section.top_margin = Inches(1)
section.bottom_margin = Inches(1)
section.left_margin = Inches(1.25)
section.right_margin = Inches(1.25)
# ── Date ──
_add_body(doc, today_str, alignment=WD_ALIGN_PARAGRAPH.RIGHT)
# ── Addressee ──
_add_body(doc, "USAC Contributor Operations", bold=True)
_add_body(doc, "Universal Service Administrative Company")
_add_body(doc, "700 12th Street NW, Suite 900")
_add_body(doc, "Washington, DC 20005")
_add_body(doc, "Email: Form499@usac.org")
_add_body(doc, "Phone: (888) 641-8722")
_add_horizontal_rule(doc)
# ── Subject line ──
_add_body(doc, "", size=Pt(4)) # spacer
_add_body(
doc,
f"Re: Request for Deactivation of 499 Filer ID — {entity_name}",
bold=True, size=HEADING_SIZE, color=NAVY,
)
if filer_id:
_add_body(doc, f"Filer ID: {filer_id}", bold=True)
if frn:
_add_body(doc, f"FCC Registration Number (FRN): {frn}", bold=True)
_add_body(doc, "", size=Pt(4)) # spacer
# ── Body ──
_add_body(doc, "Dear USAC Contributor Operations:")
# Paragraph 1: Purpose
_add_body(
doc,
f"On behalf of {entity_name}"
f"{' (Filer ID: ' + filer_id + ')' if filer_id else ''}"
f"{' (FRN: ' + frn + ')' if frn else ''}, "
f"this letter serves as a formal request to deactivate the above-referenced "
f"499 Filer ID in accordance with the FCC Form 499-A filing instructions "
f"and USAC's Filer ID management procedures."
)
# Paragraph 2: Reason
_add_body(
doc,
f"Reason for deactivation: {discontinuance_reason}. "
f"The company ceased providing telecommunications services "
f"as of {term_date}."
)
# Paragraph 3: Final filing
if includes_final_zero_filing:
_add_body(
doc,
f"A final FCC Form 499-A has been filed for the {filing_year} reporting year "
f"with zero telecommunications revenue, reflecting the cessation of all "
f"regulated services. Line 603 exemptions (TRS, NANPA, LNP) have been "
f"claimed with the notation 'Not in business as of {term_date}' on the "
f"explanation line."
)
else:
_add_body(
doc,
f"A final FCC Form 499-A for the {filing_year} reporting year has been "
f"filed separately, reporting actual telecommunications revenue for the "
f"period during which the company was in service. Line 603 exemptions "
f"(TRS, NANPA, LNP) have been claimed with the notation 'Not in business "
f"as of {term_date}' on the explanation line."
)
# Paragraph 4: Successor entity (if applicable)
if successor_entity:
_add_body(
doc,
f"Successor entity information: {successor_entity}"
f"{' (Filer ID: ' + successor_filer_id + ')' if successor_filer_id else ''}. "
f"The successor entity has assumed responsibility for any outstanding "
f"universal service contribution obligations, true-up payments, and "
f"continuation of regulated services, if any."
)
else:
_add_body(
doc,
f"There is no successor entity. {entity_name} has permanently ceased "
f"all telecommunications operations and does not intend to resume service."
)
# Paragraph 5: Outstanding balances
if outstanding_balances:
_add_body(
doc,
f"{entity_name} acknowledges that any outstanding USF contribution "
f"obligations, true-up payments, or refunds will be settled through "
f"the normal USAC invoicing process prior to final account closure."
)
else:
_add_body(
doc,
f"{entity_name} believes there are no outstanding USF contribution "
f"obligations or unpaid invoices. If any final true-up is required, "
f"please contact the undersigned for prompt resolution."
)
# Paragraph 6: Related filings
_add_body(
doc,
f"In connection with this deactivation, {entity_name} confirms that "
f"all related FCC filing obligations are being addressed, including "
f"Robocall Mitigation Database (RMD) certification, CPNI annual "
f"certification, and Broadband Data Collection (BDC) filings, as "
f"applicable."
)
# Paragraph 7: CORES update
_add_body(
doc,
f"{entity_name} will update its FCC CORES registration to reflect "
f"inactive status following confirmation of this deactivation."
)
# Paragraph 8: Request
_add_body(
doc,
f"We respectfully request that USAC process this deactivation and "
f"confirm in writing when the Filer ID has been deactivated and "
f"removed from future invoicing cycles. Please direct any questions "
f"or requests for additional documentation to the contact below."
)
_add_body(doc, "", size=Pt(8)) # spacer
# ── Closing ──
_add_body(doc, "Respectfully submitted,")
_add_body(doc, "", size=Pt(20)) # signature space
_add_body(doc, "_" * 40)
if officer_name:
_add_body(doc, officer_name, bold=True)
if officer_title:
_add_body(doc, officer_title)
_add_body(doc, entity_name)
if officer_email:
_add_body(doc, f"Email: {officer_email}")
if officer_phone:
_add_body(doc, f"Phone: {officer_phone}")
_add_horizontal_rule(doc)
# ── Entity summary box ──
_add_body(doc, "Entity Summary", bold=True, size=HEADING_SIZE, color=NAVY)
summary_lines = [
f"Legal Name: {entity_name}",
f"Filer ID: {filer_id or 'N/A'}",
f"FRN: {frn or 'N/A'}",
f"EIN: {ein or 'N/A'}",
f"Address: {address or 'N/A'}",
f"Termination Date: {term_date}",
f"Reason: {discontinuance_reason}",
f"Successor Entity: {successor_entity or 'None'}",
f"Final 499-A: {'Zero-revenue filing included' if includes_final_zero_filing else 'Filed separately with actual revenue'}",
]
for line in summary_lines:
_add_body(doc, line, size=Pt(9), color=RGBColor(0x66, 0x66, 0x66))
# ── Footer ──
_add_body(doc, "", size=Pt(6))
_add_body(
doc,
f"Prepared by Performance West Inc. on behalf of {entity_name}. "
f"Generated {today_str}.",
italic=True, size=Pt(8), color=RGBColor(0x99, 0x99, 0x99),
)
# Save
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
doc.save(output_path)
LOG.info("Discontinuance letter generated: %s", output_path)
return output_path

View file

@ -40,11 +40,53 @@ class Form499ADiscontinuanceHandler(BaseComplianceHandler):
discontinuance_reason = intake_data.get("discontinuance_reason", "Ceased providing telecommunications services")
last_service_date = intake_data.get("last_service_date", "")
# If ordered as standalone discontinuance ($299), includes a zero-revenue
# final filing. If ordered with a full 499-A ($499+$299), the 499-A handler
# files the revenue report separately — we just handle the deactivation.
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)
# 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)
@ -79,6 +121,7 @@ class Form499ADiscontinuanceHandler(BaseComplianceHandler):
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', '')}",
)