"""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)