""" Generate the FCC CPNI Annual Certification Letter. Produces the annual certification required by 47 CFR § 64.2009 certifying compliance with the Customer Proprietary Network Information (CPNI) rules (47 CFR §§ 64.2001-64.2011), including amendments from the 2023 Data Breach Notification Order (FCC 23-111). The letter is largely standard across carrier types. The only variation is wholesale-only carriers, whose CPNI obligations are limited to wholesale customer proprietary data rather than retail end-user CPNI. Usage: from scripts.document_gen.templates.cpni_cert_letter_generator import ( generate_cpni_cert_letter, ) path = generate_cpni_cert_letter( entity_name="Falcon Broadband LLC", frn="0027160886", filer_id_499="812345", reporting_year=2025, complaints_count=0, output_path="/tmp/cpni_cert.docx", ) """ from __future__ import annotations import logging from datetime import datetime from pathlib import Path from typing import Optional LOG = logging.getLogger("document_gen.cpni_cert") 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 except ImportError: LOG.warning("python-docx not installed — CPNI cert letter generation unavailable") Document = None # type: ignore[assignment,misc] # Navy blue used for section headings (RGB 0x1A, 0x27, 0x44) _NAVY = RGBColor(0x1A, 0x27, 0x44) if Document else None # Spacing constants (in twips; 1 pt = 20 twips) _AFTER_6PT = Pt(6) if Document else None def generate_cpni_cert_letter( # ── Entity identity ─────────────────────────────────────────── entity_name: str, frn: str = "", filer_id_499: str = "", # ── Address ─────────────────────────────────────────────────── address_street: str = "", address_city: str = "", address_state: str = "", address_zip: str = "", # ── Contact / officer ───────────────────────────────────────── officer_name: str = "", officer_title: str = "Chief Executive Officer", contact_email: str = "", contact_phone: str = "", # ── Reporting ───────────────────────────────────────────────── reporting_year: int = 0, complaints_count: int = 0, complaints_description: str = "", # ── Carrier flags ───────────────────────────────────────────── is_wholesale: bool = False, # ── Employee training ───────────────────────────────────────── employee_training_conducted: bool = True, # ── Disciplinary actions ────────────────────────────────────── disciplinary_actions_taken: bool = False, disciplinary_actions_description: str = "", # ── Data broker actions ─────────────────────────────────────── data_broker_actions: str = "", # ── Breaches (per FCC 23-111) ───────────────────────────────── breaches: list[dict] | None = None, # ── Marketing / CPNI usage ──────────────────────────────────── uses_cpni_for_marketing: bool = False, cpni_approval_method: str = "opt_in", # "opt_in" or "opt_out" # ── Pretexting safeguards ───────────────────────────────────── pretexting_safeguards: str = "", # ── Output ──────────────────────────────────────────────────── output_path: str = "/tmp/cpni_certification_letter.docx", ) -> Optional[str]: """ Generate a CPNI Annual Certification Letter as a DOCX file. Compliant with 47 CFR § 64.2009, including the 2023 Data Breach Notification Order (FCC 23-111). Returns the output file path on success, None on failure. """ if Document is None: LOG.error("python-docx not installed") return None if reporting_year == 0: reporting_year = datetime.now().year - 1 if breaches is None: breaches = [] doc = Document() # ── Page setup ──────────────────────────────────────────────── 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) today = datetime.now().strftime("%B %d, %Y") signer = officer_name or "Authorized Officer" title = officer_title or "Officer" cpni_scope = ( "wholesale customer proprietary data" if is_wholesale else "customer proprietary network information (CPNI)" ) # ── Helper functions ────────────────────────────────────────── def _set_spacing(paragraph, after_pt=6, before_pt=0): """Set paragraph spacing in points.""" pf = paragraph.paragraph_format pf.space_after = Pt(after_pt) if before_pt: pf.space_before = Pt(before_pt) def _heading(text: str, level: int = 1) -> None: """Add a navy blue section heading.""" p = doc.add_paragraph() run = p.add_run(text) run.font.size = Pt(12) run.bold = True run.font.color.rgb = _NAVY _set_spacing(p, after_pt=4, before_pt=8) def _body(text: str, bold: bool = False, size: int = 10) -> None: """Add body-text paragraph with 6pt spacing after.""" p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.LEFT run = p.add_run(text) run.font.size = Pt(size) run.bold = bold _set_spacing(p, after_pt=6) def _checkbox(label: str, checked: bool = True) -> None: """Add a checkbox-style line item.""" mark = "\u2611" if checked else "\u2610" p = doc.add_paragraph() p.alignment = WD_ALIGN_PARAGRAPH.LEFT run = p.add_run(f" {mark} {label}") run.font.size = Pt(10) _set_spacing(p, after_pt=3) def _spacer() -> None: p = doc.add_paragraph() _set_spacing(p, after_pt=0) # ── Page numbers ────────────────────────────────────────────── for section in doc.sections: footer = section.footer footer.is_linked_to_previous = False fp = footer.paragraphs[0] if footer.paragraphs else footer.add_paragraph() fp.alignment = WD_ALIGN_PARAGRAPH.CENTER # Insert PAGE field run = fp.add_run() run.font.size = Pt(8) run.font.color.rgb = RGBColor(0x80, 0x80, 0x80) fld_char_begin = run._element.makeelement(qn("w:fldChar"), {qn("w:fldCharType"): "begin"}) run._element.append(fld_char_begin) run2 = fp.add_run() run2.font.size = Pt(8) run2.font.color.rgb = RGBColor(0x80, 0x80, 0x80) instr = run2._element.makeelement(qn("w:instrText"), {}) instr.text = " PAGE " run2._element.append(instr) run3 = fp.add_run() run3.font.size = Pt(8) fld_char_end = run3._element.makeelement(qn("w:fldChar"), {qn("w:fldCharType"): "end"}) run3._element.append(fld_char_end) # ══════════════════════════════════════════════════════════════ # TITLE # ══════════════════════════════════════════════════════════════ title_p = doc.add_paragraph() title_p.alignment = WD_ALIGN_PARAGRAPH.CENTER title_run = title_p.add_run("CPNI Annual Certification Letter") title_run.font.size = Pt(14) title_run.bold = True title_run.font.color.rgb = _NAVY _set_spacing(title_p, after_pt=2) subtitle_p = doc.add_paragraph() subtitle_p.alignment = WD_ALIGN_PARAGRAPH.CENTER sub_run = subtitle_p.add_run( f"Pursuant to 47 CFR \u00a7 64.2009 \u2014 Calendar Year {reporting_year}" ) sub_run.font.size = Pt(10) sub_run.font.color.rgb = RGBColor(0x55, 0x55, 0x55) _set_spacing(subtitle_p, after_pt=6) # Horizontal rule rule_p = doc.add_paragraph() rule_p.alignment = WD_ALIGN_PARAGRAPH.CENTER rule_run = rule_p.add_run("\u2500" * 72) rule_run.font.size = Pt(6) rule_run.font.color.rgb = RGBColor(0xAA, 0xAA, 0xAA) _set_spacing(rule_p, after_pt=8) # ══════════════════════════════════════════════════════════════ # SECTION 1: Provider Information # ══════════════════════════════════════════════════════════════ _heading("1. Provider Information") info_lines = [f"Company Name: {entity_name}"] if frn: info_lines.append(f"FCC Registration Number (FRN): {frn}") if filer_id_499: info_lines.append(f"FCC Form 499 Filer ID: {filer_id_499}") addr = ", ".join(filter(None, [address_street, address_city])) if address_state or address_zip: addr += f", {address_state} {address_zip}".strip() if addr.strip(", "): info_lines.append(f"Address: {addr.strip(', ')}") if contact_phone: info_lines.append(f"Telephone: {contact_phone}") if contact_email: info_lines.append(f"Email: {contact_email}") info_lines.append(f"Certifying Officer: {signer}, {title}") info_lines.append(f"Date of Filing: {today}") info_lines.append( f"Filing Deadline: March 1, {reporting_year + 1}" ) _body("\n".join(info_lines)) # ══════════════════════════════════════════════════════════════ # SECTION 2: Certification of Compliance # ══════════════════════════════════════════════════════════════ _heading("2. Certification of Compliance") _body( f"Pursuant to 47 CFR \u00a7 64.2009(e), {entity_name} " f"({'FRN: ' + frn if frn else 'FRN pending'}" f"{', Filer ID: ' + filer_id_499 if filer_id_499 else ''}) " f"hereby submits its annual certification of compliance with the " f"Commission's Customer Proprietary Network Information (CPNI) rules " f"for calendar year {reporting_year}." ) _body( f"I, {signer}, {title} of {entity_name}, have personal knowledge " f"of, have reviewed, and am familiar with {entity_name}'s CPNI " f"compliance procedures and certify that the company has established " f"operating procedures that ensure compliance with the Commission's " f"CPNI rules set forth in 47 CFR \u00a7\u00a7 64.2001 through 64.2011. " f"{entity_name} has taken appropriate actions to protect the " f"confidentiality of {cpni_scope} and has limited access to and use " f"of such information in accordance with the Commission's rules." ) # ══════════════════════════════════════════════════════════════ # SECTION 3: Reporting Period # ══════════════════════════════════════════════════════════════ _heading("3. Reporting Period") _body( f"This certification covers the period from January 1, {reporting_year} " f"through December 31, {reporting_year}." ) # ══════════════════════════════════════════════════════════════ # SECTION 4: CPNI Safeguards # ══════════════════════════════════════════════════════════════ _heading("4. CPNI Safeguards") _body( f"{entity_name} has implemented the following safeguards to protect " f"{cpni_scope}:" ) # 4a - Customer authentication _body("(a) Customer Authentication and Password Procedures", bold=True) _checkbox( f"{entity_name} requires customer authentication through a password " f"or other secure credential before disclosing CPNI in response to " f"customer-initiated contacts, in accordance with 47 CFR \u00a7 64.2010.", checked=True, ) # 4b - Employee training _body("(b) Employee Training", bold=True) _checkbox( f"All employees with access to CPNI have been adequately trained on " f"the Commission's CPNI rules, including proper handling, disclosure " f"limitations, and breach notification procedures.", checked=employee_training_conducted, ) if not employee_training_conducted: _body( f"NOTE: {entity_name} is in the process of completing employee " f"training and anticipates full compliance within 30 days of this " f"filing." ) # 4c - Supervisory review _body("(c) Supervisory Review", bold=True) _checkbox( f"{entity_name} conducts regular supervisory reviews of CPNI access " f"and usage to ensure compliance with established procedures.", checked=True, ) # 4d - Pretexting safeguards _body("(d) Pretexting Safeguards", bold=True) if pretexting_safeguards: _checkbox(pretexting_safeguards, checked=True) else: _checkbox( f"{entity_name} has implemented safeguards to protect against " f"pretexting, including customer identity verification protocols, " f"employee awareness training on social engineering tactics, and " f"procedures to detect and report suspected pretexting attempts.", checked=True, ) # 4e - Notification of account changes _body("(e) Notification of Account Changes", bold=True) _checkbox( f"{entity_name} notifies customers of account changes, including " f"changes to passwords, address of record, or online account " f"credentials, through a communication to the customer's address " f"of record or established backup contact method, in accordance " f"with 47 CFR \u00a7 64.2010.", checked=True, ) # 4f - Record retention _body("(f) Record Retention", bold=True) _checkbox( f"{entity_name} maintains records of all CPNI access, disclosures, " f"customer complaints, and compliance actions for a minimum period " f"of five (5) years, as required by 47 CFR \u00a7 64.2009(e).", checked=True, ) # ══════════════════════════════════════════════════════════════ # SECTION 5: CPNI Complaints # ══════════════════════════════════════════════════════════════ _heading("5. CPNI Complaints") if complaints_count == 0: _body( f"During the reporting period, {entity_name} received no complaints " f"regarding unauthorized release or use of CPNI." ) else: desc = complaints_description or ( f"Each complaint was investigated and resolved in accordance with " f"{entity_name}'s CPNI compliance procedures." ) _body( f"During the reporting period, {entity_name} received " f"{complaints_count} complaint{'s' if complaints_count != 1 else ''} " f"regarding CPNI. {desc}" ) # ══════════════════════════════════════════════════════════════ # SECTION 6: Data Breaches # ══════════════════════════════════════════════════════════════ _heading("6. Data Breaches") if not breaches: _body( f"During the reporting period, {entity_name} experienced no data " f"breaches involving CPNI. No breach notifications were required " f"to be filed with the Commission, law enforcement, or affected " f"customers under 47 CFR \u00a7 64.2011." ) else: total_breaches = len(breaches) total_affected = sum(b.get("customers_affected", 0) for b in breaches) _body( f"During the reporting period, {entity_name} experienced " f"{total_breaches} data breach{'es' if total_breaches != 1 else ''} " f"involving CPNI, affecting a total of {total_affected:,} " f"customer{'s' if total_affected != 1 else ''}. Details of each " f"breach are provided below." ) # Breach detail table table = doc.add_table(rows=1, cols=5) table.style = "Table Grid" # Header row headers = [ "Breach #", "Date", "Customers\nAffected", "Description", "Response Actions", ] hdr_cells = table.rows[0].cells for i, header in enumerate(headers): hdr_cells[i].text = "" p = hdr_cells[i].paragraphs[0] run = p.add_run(header) run.bold = True run.font.size = Pt(9) run.font.color.rgb = RGBColor(0xFF, 0xFF, 0xFF) # Navy background shading = hdr_cells[i]._element.makeelement( qn("w:shd"), { qn("w:val"): "clear", qn("w:color"): "auto", qn("w:fill"): "1A2744", }, ) tc_pr = hdr_cells[i]._element.get_or_add_tcPr() tc_pr.append(shading) # Data rows for idx, breach in enumerate(breaches, start=1): row_cells = table.add_row().cells values = [ str(idx), str(breach.get("date", "N/A")), f"{breach.get('customers_affected', 0):,}", str(breach.get("description", "")), str(breach.get("response_actions", "")), ] for i, val in enumerate(values): row_cells[i].text = "" p = row_cells[i].paragraphs[0] run = p.add_run(val) run.font.size = Pt(9) _spacer() # ══════════════════════════════════════════════════════════════ # SECTION 7: Disciplinary Actions # ══════════════════════════════════════════════════════════════ _heading("7. Disciplinary Actions") if not disciplinary_actions_taken: _body( f"During the reporting period, {entity_name} did not take any " f"disciplinary action against employees for violations of the " f"Commission's CPNI rules." ) else: desc = disciplinary_actions_description or ( "Disciplinary action was taken in accordance with company policy." ) _body( f"During the reporting period, {entity_name} took disciplinary " f"action against one or more employees for violations of the " f"Commission's CPNI rules. {desc}" ) # ══════════════════════════════════════════════════════════════ # SECTION 8: Data Broker Actions # ══════════════════════════════════════════════════════════════ _heading("8. Actions Taken Against Data Brokers") if data_broker_actions: _body( f"During the reporting period, {entity_name} took the following " f"actions against data brokers: {data_broker_actions}" ) else: _body( f"During the reporting period, {entity_name} did not identify any " f"data brokers engaging in unauthorized access to or sale of CPNI, " f"and no actions against data brokers were required." ) # ══════════════════════════════════════════════════════════════ # SECTION 9: CPNI Marketing Usage # ══════════════════════════════════════════════════════════════ _heading("9. CPNI Marketing Usage") if uses_cpni_for_marketing: method_label = ( "opt-in" if cpni_approval_method == "opt_in" else "opt-out" ) _body( f"{entity_name} uses CPNI for marketing purposes. Customer " f"approval for such use is obtained through the {method_label} " f"method, in accordance with 47 CFR \u00a7 64.2007." ) else: _body( f"{entity_name} does not use CPNI for marketing purposes beyond " f"the scope of services to which the customer already subscribes. " f"No customer approval mechanism is required." ) # ══════════════════════════════════════════════════════════════ # SECTION 10: Breach Notification Compliance # ══════════════════════════════════════════════════════════════ _heading("10. Breach Notification Compliance") _body( f"{entity_name} certifies that its breach notification procedures " f"are compliant with 47 CFR \u00a7 64.2011, as amended by the 2023 " f"Data Breach Notification Order (FCC 23-111). These procedures " f"include:" ) _checkbox( "Notification to the FCC and, where applicable, the FBI and U.S. " "Secret Service, as soon as practicable and in no event later than " "30 days after reasonable determination of a breach.", checked=True, ) _checkbox( "Notification to affected customers as soon as practicable and in " "no event later than 30 days after notification to law enforcement " "(unless a delay is requested by law enforcement).", checked=True, ) _checkbox( "Breach notifications include the required content specified in " "\u00a7 64.2011, including a description of the breach, the categories " "of information compromised, and contact information for inquiries.", checked=True, ) # ══════════════════════════════════════════════════════════════ # SECTION 11: Officer Certification & Signature # ══════════════════════════════════════════════════════════════ _heading("11. Officer Certification and Signature") _body( f"I, {signer}, {title} of {entity_name}, certify under penalty of " f"perjury that the foregoing is true and correct. I have personal " f"knowledge of the facts stated herein, have reviewed {entity_name}'s " f"CPNI compliance procedures, and am satisfied that {entity_name} has " f"complied with the requirements of 47 CFR \u00a7\u00a7 64.2001 through " f"64.2011 during calendar year {reporting_year}." ) _spacer() _body("Respectfully submitted,") _spacer() _spacer() # Signature line sig_line = doc.add_paragraph() sig_run = sig_line.add_run("_" * 45) sig_run.font.size = Pt(10) _set_spacing(sig_line, after_pt=2) sig_name_p = doc.add_paragraph() name_run = sig_name_p.add_run(signer) name_run.font.size = Pt(10) name_run.bold = True _set_spacing(sig_name_p, after_pt=2) sig_title_p = doc.add_paragraph() sig_title_p.add_run(f"{title}, {entity_name}").font.size = Pt(10) _set_spacing(sig_title_p, after_pt=2) sig_date_p = doc.add_paragraph() sig_date_p.add_run(f"Date: {today}").font.size = Pt(10) _set_spacing(sig_date_p, after_pt=2) if contact_phone: sig_phone_p = doc.add_paragraph() sig_phone_p.add_run(f"Telephone: {contact_phone}").font.size = Pt(10) _set_spacing(sig_phone_p, after_pt=2) if contact_email: sig_email_p = doc.add_paragraph() sig_email_p.add_run(f"Email: {contact_email}").font.size = Pt(10) _set_spacing(sig_email_p, after_pt=2) # ── Save ────────────────────────────────────────────────────── output = Path(output_path) output.parent.mkdir(parents=True, exist_ok=True) doc.save(str(output)) LOG.info("CPNI certification letter generated: %s", output) return str(output)