diff --git a/scripts/document_gen/templates/rmd_exhibit_a_generator.py b/scripts/document_gen/templates/rmd_exhibit_a_generator.py index 00480b4..4a71752 100644 --- a/scripts/document_gen/templates/rmd_exhibit_a_generator.py +++ b/scripts/document_gen/templates/rmd_exhibit_a_generator.py @@ -138,6 +138,25 @@ def _stir_shaken_paragraph( f"high-volume origination, or facilities-based IP origination " f"infrastructure." ) + elif option == "terminate_only": + paras.append( + f"{entity_name} operates as a terminating provider and does not " + f"originate calls onto the public switched telephone network. " + f"{entity_abbr} receives exclusively pre-authenticated traffic " + f"from originating and intermediate providers. As a " + f"terminating-only carrier, {entity_abbr} is not required to " + f"maintain its own STI certificate or SPC token for call signing." + ) + paras.append( + f"{entity_abbr} verifies STIR/SHAKEN attestation information on " + f"all inbound SIP INVITE messages using standard verification " + f"service (VS) functionality configured on its session border " + f"controllers. {entity_abbr} certifies partial STIR/SHAKEN " + f"implementation (Option 2 in the RMD) reflecting its " + f"verification-only posture. Per FCC rules, the obligation to " + f"sign calls falls on the originating provider, not the " + f"terminating provider." + ) elif option == "option3": paras.append( f"{entity_name} certifies no STIR/SHAKEN signing implementation " @@ -203,12 +222,18 @@ def _scope_paragraph( f"{entity_abbr} does not accept foreign-originated traffic and " f"operates solely with domestic U.S. NANP resources." ) - parts.append( - f"As a small provider without its own Class 4 switch or outbound " - f"origination platform, {entity_abbr} relies on trusted underlying " - f"carriers for DID origination, call termination, and STIR/SHAKEN " - f"attestation/signing where applicable." - ) + if is_wholesale or carrier_role in ("wholesale_domestic", "gateway", "facilities"): + parts.append( + f"{entity_abbr} operates its own network infrastructure for " + f"voice service delivery." + ) + else: + parts.append( + f"As a small provider without its own Class 4 switch or outbound " + f"origination platform, {entity_abbr} relies on trusted underlying " + f"carriers for DID origination, call termination, and STIR/SHAKEN " + f"attestation/signing where applicable." + ) parts.append( f"This Robocall Mitigation Plan outlines {entity_abbr}'s measures to " f"detect, prevent, and mitigate unlawful robocalls in compliance " @@ -382,12 +407,18 @@ def generate_exhibit_a( if ocn: _add_body(doc, f"OCN: {ocn}") else: - _add_body(doc, ( - f"{entity_abbr} does not possess an Operating Company Number " - f"(OCN). Per FCC guidance, no OCN is required for a small " - f"retail provider without local exchange carrier status; " - f"\"No\" is selected on the RMD form." - )) + if is_wholesale or carrier_role in ("wholesale_domestic", "gateway"): + _add_body(doc, ( + f"{entity_abbr} does not currently possess an Operating " + f"Company Number (OCN). \"No\" is selected on the RMD form." + )) + else: + _add_body(doc, ( + f"{entity_abbr} does not possess an Operating Company Number " + f"(OCN). Per FCC guidance, no OCN is required for a small " + f"retail provider without local exchange carrier status; " + f"\"No\" is selected on the RMD form." + )) _add_body(doc, "Principals, Affiliates, Subsidiaries, and Parent Companies:", bold=True) _add_bullets(doc, principals or [f"{contact_name or entity_name} (sole principal)"]) diff --git a/scripts/document_gen/templates/rmd_letter_generator.py b/scripts/document_gen/templates/rmd_letter_generator.py index 9dd16c0..b228924 100644 --- a/scripts/document_gen/templates/rmd_letter_generator.py +++ b/scripts/document_gen/templates/rmd_letter_generator.py @@ -463,6 +463,7 @@ def generate_rmd_letter( needs_exhibit_a = stir_shaken_status in ( "partial_implementation", + "terminate_only", "robocall_mitigation_only", "exempt_small_carrier", ) @@ -1071,6 +1072,24 @@ def _build_stir_shaken_section( italic=True, ) + elif stir_shaken_status == "terminate_only": + b.body( + f"{entity_name} operates exclusively as a terminating provider " + f"and does not originate calls onto the public switched telephone " + f"network. {entity_name} receives pre-authenticated traffic from " + f"originating and intermediate providers and verifies STIR/SHAKEN " + f"attestation information on all inbound SIP INVITE messages." + ) + b.body( + f"As a terminating-only carrier, {entity_name} is not required to " + f"maintain its own STI certificate or SPC token for call signing. " + f"Per FCC rules (47 CFR § 64.6301), the obligation to sign calls " + f"falls on the originating provider, not the terminating provider. " + f"{entity_name} certifies partial STIR/SHAKEN implementation " + f"(reflecting its verification-only posture) and has implemented " + f"a robocall mitigation program as described in Exhibit A." + ) + elif stir_shaken_status == "robocall_mitigation_only": b.body( f"{entity_name} has not implemented STIR/SHAKEN caller ID " diff --git a/scripts/workers/services/rmd_filing.py b/scripts/workers/services/rmd_filing.py index 898dfbc..9d5fb67 100644 --- a/scripts/workers/services/rmd_filing.py +++ b/scripts/workers/services/rmd_filing.py @@ -299,6 +299,7 @@ class RMDFilingHandler(BaseServiceHandler): stir_status = entity.get("stir_shaken_status", "complete_implementation") if stir_status in ( "partial_implementation", + "terminate_only", "robocall_mitigation_only", "exempt_small_carrier", ): @@ -317,12 +318,30 @@ class RMDFilingHandler(BaseServiceHandler): work_dir, f"robocall_mitigation_program_{order_number}_{date_str}.docx", ) + # Map stir_shaken_status to Exhibit A rmd_option + _STATUS_TO_OPTION = { + "complete_implementation": "option1", + "partial_implementation": "option2", + "terminate_only": "terminate_only", + "robocall_mitigation_only": "option3", + "exempt_small_carrier": "option3", + } exhibit = generate_exhibit_a( entity_name=entity.get("legal_name", ""), frn=entity.get("frn", ""), + ocn=entity.get("ocn", ""), carrier_role=role, carrier_metadata=entity.get("carrier_metadata", {}), upstream_provider_name=entity.get("upstream_provider_name", ""), + is_wholesale=entity.get("is_wholesale", False), + is_gateway=entity.get("is_gateway_provider", False), + foreign_traffic=entity.get("is_international_only", False), + rmd_option=_STATUS_TO_OPTION.get(stir_status, "option2"), + contact_name=entity.get("contact_name", ""), + contact_title=entity.get("contact_title", ""), + contact_email=entity.get("contact_email", ""), + contact_phone=entity.get("contact_phone", ""), + address=entity.get("address", ""), llm_generate=self._call_llm, output_path=exhibit_docx, ) @@ -422,8 +441,11 @@ class RMDFilingHandler(BaseServiceHandler): ) # STIR/SHAKEN implementation status — value must match the - # RMD form radio options. + # RMD form radio options. terminate_only maps to + # partial_implementation on the FCC form (verification-only). stir_status = entity.get("stir_shaken_status", "complete_implementation") + if stir_status == "terminate_only": + stir_status = "partial_implementation" await page.click(f'input[name="stir_shaken_status"][value="{stir_status}"]') await human_delay() diff --git a/site/src/components/intake/steps/STIRShakenStep.astro b/site/src/components/intake/steps/STIRShakenStep.astro index 49d347a..974d941 100644 --- a/site/src/components/intake/steps/STIRShakenStep.astro +++ b/site/src/components/intake/steps/STIRShakenStep.astro @@ -13,16 +13,18 @@ +
- + - + @@ -33,17 +35,49 @@ .pw-help { color: #64748b; font-size: 0.9rem; margin-bottom: 1rem; } .pw-field { display: block; font-weight: 600; margin: 0.8rem 0 0.2rem; font-size: 0.88rem; } .pw-input { width: 100%; padding: 0.5rem 0.7rem; border: 1px solid #cbd5e1; border-radius: 6px; font-size: 0.93rem; } + .pw-hint { color: #4b5563; font-size: 0.82rem; margin-top: 0.3rem; padding: 0.5rem 0.7rem; background: #f0f4f8; border-radius: 6px; line-height: 1.5; } .pw-err { color: #b91c1c; margin-top: 0.75rem; font-size: 0.9rem; }