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>
221 lines
8.8 KiB
Python
221 lines
8.8 KiB
Python
"""State PUC/PSC Registration handler.
|
|
|
|
Processes per-state PUC registrations for VoIP, broadband, and CLEC
|
|
providers. Fans out one `state_puc_registrations` row per selected
|
|
state and creates admin todos for manual filing.
|
|
|
|
States without VoIP registration requirements are skipped with a note.
|
|
States requiring a surety bond start at 'bond_pending' status.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from typing import Optional
|
|
|
|
import psycopg2
|
|
|
|
from .base_handler import BaseServiceHandler
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DATABASE_URL = os.environ.get("DATABASE_URL", "")
|
|
|
|
# Our service fee per state
|
|
PUC_SERVICE_FEE_CENTS = 39900 # $399/state
|
|
|
|
|
|
class StatePucFilingHandler(BaseServiceHandler):
|
|
SERVICE_SLUG = "state-puc"
|
|
SERVICE_NAME = "State PUC/PSC Registration"
|
|
REQUIRES_LLM = False
|
|
|
|
async def process(self, order_data: dict) -> list[str]:
|
|
order_number = order_data["name"]
|
|
entity = order_data.get("entity", {}) or {}
|
|
intake = order_data.get("intake_data", {}) or {}
|
|
|
|
# Target states from intake
|
|
target_states = intake.get("target_states") or []
|
|
if isinstance(target_states, str):
|
|
target_states = [s.strip().upper() for s in target_states.split(",") if s.strip()]
|
|
else:
|
|
target_states = [str(s).strip().upper() for s in target_states]
|
|
|
|
if not target_states:
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: no target states specified in intake_data. "
|
|
"Admin should add target_states and re-dispatch.",
|
|
)
|
|
return []
|
|
|
|
entity_name = (
|
|
intake.get("entity_legal_name")
|
|
or entity.get("legal_name")
|
|
or ""
|
|
)
|
|
if not entity_name:
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: missing entity legal name. "
|
|
"Admin should set entity_legal_name in intake_data.",
|
|
)
|
|
return []
|
|
|
|
reg_type = intake.get("registration_type", "voip")
|
|
# Normalize: empty string → None (DB CHECK constraint rejects "")
|
|
provider_type = intake.get("provider_type") or None
|
|
frn = intake.get("frn") or entity.get("frn")
|
|
|
|
conn = psycopg2.connect(DATABASE_URL)
|
|
try:
|
|
with conn.cursor() as cur:
|
|
for state_code in target_states:
|
|
# Look up state PUC requirements
|
|
cur.execute(
|
|
"""SELECT voip_registration_required, voip_registration_fee_cents,
|
|
voip_bond_required, voip_bond_amount_cents,
|
|
broadband_registration_required, broadband_registration_fee_cents,
|
|
clec_certification_required, clec_certification_fee_cents,
|
|
clec_bond_required, clec_bond_amount_cents,
|
|
agency_name
|
|
FROM state_puc_requirements
|
|
WHERE state_code = %s""",
|
|
(state_code,),
|
|
)
|
|
req = cur.fetchone()
|
|
if not req:
|
|
logger.warning("StatePucHandler: no PUC data for %s", state_code)
|
|
continue
|
|
|
|
(voip_req, voip_fee, voip_bond_req, voip_bond_amt,
|
|
bb_req, bb_fee, clec_req, clec_fee, clec_bond_req, clec_bond_amt,
|
|
agency_name) = req
|
|
|
|
# Calculate fees based on registration type
|
|
state_fee = 0
|
|
bond_amount = 0
|
|
required = False
|
|
|
|
if reg_type in ("voip", "bundle"):
|
|
if voip_req:
|
|
required = True
|
|
state_fee += voip_fee
|
|
if voip_bond_req:
|
|
bond_amount = max(bond_amount, voip_bond_amt)
|
|
if reg_type in ("broadband", "bundle"):
|
|
if bb_req:
|
|
required = True
|
|
state_fee += bb_fee
|
|
if reg_type in ("clec", "bundle"):
|
|
if clec_req:
|
|
required = True
|
|
state_fee += clec_fee
|
|
if clec_bond_req:
|
|
bond_amount = max(bond_amount, clec_bond_amt)
|
|
|
|
if not required:
|
|
logger.info(
|
|
"StatePucHandler: %s does not require %s registration — skipping",
|
|
state_code, reg_type,
|
|
)
|
|
continue
|
|
|
|
# Check for existing active registration
|
|
cur.execute(
|
|
"""SELECT id FROM state_puc_registrations
|
|
WHERE order_number = %s AND state_code = %s
|
|
AND status NOT IN ('cancelled','rejected')
|
|
LIMIT 1""",
|
|
(order_number, state_code),
|
|
)
|
|
if cur.fetchone():
|
|
logger.info(
|
|
"StatePucHandler: %s already has %s registration — skipping",
|
|
order_number, state_code,
|
|
)
|
|
continue
|
|
|
|
co_id = order_data.get("compliance_order_id") or order_data.get("id")
|
|
|
|
# Initial status: bond_pending if bond required, else filing
|
|
initial_status = "bond_pending" if bond_amount > 0 else "filing"
|
|
|
|
cur.execute(
|
|
"""INSERT INTO state_puc_registrations (
|
|
compliance_order_id, order_number,
|
|
telecom_entity_id, state_code,
|
|
registration_type, entity_legal_name, frn,
|
|
provider_type,
|
|
status,
|
|
state_fee_cents, bond_amount_cents,
|
|
service_fee_cents, retail_total_cents
|
|
) VALUES (
|
|
%s, %s, %s, %s, %s, %s, %s, %s, %s,
|
|
%s, %s, %s, %s
|
|
)""",
|
|
(
|
|
co_id, order_number,
|
|
entity.get("id"), state_code,
|
|
reg_type, entity_name, frn,
|
|
provider_type,
|
|
initial_status,
|
|
state_fee, bond_amount,
|
|
PUC_SERVICE_FEE_CENTS,
|
|
PUC_SERVICE_FEE_CENTS + state_fee,
|
|
),
|
|
)
|
|
|
|
# Create admin todo
|
|
bond_note = (
|
|
f" BOND REQUIRED: ${bond_amount / 100:,.0f} surety bond. "
|
|
"Coordinate with surety provider before filing."
|
|
if bond_amount > 0
|
|
else ""
|
|
)
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"PUC registration in {state_code} ({agency_name}): "
|
|
f"{entity_name} — {reg_type} registration. "
|
|
f"State fee: ${state_fee / 100:,.0f}.{bond_note} "
|
|
f"Provider type: {provider_type or 'not specified'}. "
|
|
f"FRN: {frn or 'N/A'}.",
|
|
)
|
|
|
|
conn.commit()
|
|
logger.info(
|
|
"StatePucHandler: created registration(s) for %s across %d state(s)",
|
|
order_number, len(target_states),
|
|
)
|
|
except Exception as exc:
|
|
logger.exception(
|
|
"StatePucHandler: DB error for %s: %s", order_number, exc,
|
|
)
|
|
conn.rollback()
|
|
self._create_admin_todo(
|
|
order_number,
|
|
f"{self.SERVICE_NAME}: failed to create registration rows — {exc}",
|
|
)
|
|
return []
|
|
finally:
|
|
conn.close()
|
|
|
|
return []
|
|
|
|
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": "Medium",
|
|
"role": "Accounting Advisor",
|
|
},
|
|
)
|
|
except Exception as exc:
|
|
logger.error("Could not create admin ToDo: %s", exc)
|