/** * State PUC/PSC Registration Requirements API * * GET /api/v1/puc/requirements?state=TX * Returns PUC requirements for a single state. * * GET /api/v1/puc/requirements/all * Returns all states for the multi-state picker. * * POST /api/v1/puc/quote * Accepts { states: ['TX','NY'], type: 'voip'|'broadband'|'clec' } * Returns per-state fee breakdown + bond requirements + service fee. */ import { Router, type Request, type Response } from "express"; import { pool } from "../db.js"; const router = Router(); // Our service fee per state const PUC_SERVICE_FEE_CENTS = 39900; // $399/state // ── GET /api/v1/puc/requirements?state=TX ──────────────────────────────────── router.get("/api/v1/puc/requirements", async (req: Request, res: Response) => { try { const state = (req.query.state as string || "").toUpperCase().trim(); if (!state || state.length !== 2) { res.status(400).json({ error: "state query param required (2-letter code)" }); return; } const { rows } = await pool.query( `SELECT * FROM state_puc_requirements WHERE state_code = $1`, [state] ); if (rows.length === 0) { res.status(404).json({ error: `No PUC data for state: ${state}` }); return; } const r = rows[0]; res.json({ state_code: r.state_code, agency_name: r.agency_name, agency_url: r.agency_url, voip: { registration_required: r.voip_registration_required, registration_type: r.voip_registration_type, registration_fee_cents: r.voip_registration_fee_cents, annual_fee_cents: r.voip_annual_fee_cents, bond_required: r.voip_bond_required, bond_amount_cents: r.voip_bond_amount_cents, bond_type: r.voip_bond_type, reseller_exempt: r.voip_reseller_exempt, reseller_bond_cents: r.voip_reseller_bond_cents, reseller_notes: r.voip_reseller_notes, ott_exempt: r.voip_ott_exempt, notes: r.voip_notes, }, broadband: { registration_required: r.broadband_registration_required, registration_type: r.broadband_registration_type, registration_fee_cents: r.broadband_registration_fee_cents, annual_fee_cents: r.broadband_annual_fee_cents, notes: r.broadband_notes, }, clec: { certification_required: r.clec_certification_required, certification_fee_cents: r.clec_certification_fee_cents, bond_required: r.clec_bond_required, bond_amount_cents: r.clec_bond_amount_cents, }, ongoing: { annual_report_required: r.annual_report_required, annual_report_due: r.annual_report_due, usf_surcharge_required: r.usf_surcharge_required, usf_surcharge_description: r.usf_surcharge_description, }, notes: r.notes, last_verified_date: r.last_verified_date, service_fee_cents: PUC_SERVICE_FEE_CENTS, }); } catch (err) { console.error("[puc] requirements error:", err); res.status(500).json({ error: "Internal server error" }); } }); // ── GET /api/v1/puc/requirements/all ───────────────────────────────────────── router.get("/api/v1/puc/requirements/all", async (_req: Request, res: Response) => { try { const { rows } = await pool.query(` SELECT r.state_code, j.name AS state_name, r.agency_name, r.agency_url, r.voip_registration_required, r.voip_registration_type, r.voip_registration_fee_cents, r.voip_bond_required, r.voip_bond_amount_cents, r.voip_reseller_exempt, r.voip_reseller_bond_cents, r.voip_reseller_notes, r.voip_ott_exempt, r.broadband_registration_required, r.broadband_registration_fee_cents, r.clec_certification_required, r.clec_certification_fee_cents, r.clec_bond_required, r.clec_bond_amount_cents, r.annual_report_required, r.usf_surcharge_required, r.notes FROM state_puc_requirements r JOIN jurisdictions j ON j.code = r.state_code WHERE j.country = 'US' ORDER BY j.name `); res.json({ states: rows.map(r => ({ state_code: r.state_code, state_name: r.state_name, agency_name: r.agency_name, agency_url: r.agency_url, voip_required: r.voip_registration_required, voip_type: r.voip_registration_type, voip_fee_cents: r.voip_registration_fee_cents, voip_bond_required: r.voip_bond_required, voip_bond_cents: r.voip_bond_amount_cents, voip_reseller_exempt: r.voip_reseller_exempt, voip_reseller_bond_cents: r.voip_reseller_bond_cents, voip_reseller_notes: r.voip_reseller_notes, voip_ott_exempt: r.voip_ott_exempt, broadband_required: r.broadband_registration_required, broadband_fee_cents: r.broadband_registration_fee_cents, clec_required: r.clec_certification_required, clec_fee_cents: r.clec_certification_fee_cents, clec_bond_required: r.clec_bond_required, clec_bond_cents: r.clec_bond_amount_cents, annual_report: r.annual_report_required, usf_surcharge: r.usf_surcharge_required, notes: r.notes, })), service_fee_cents: PUC_SERVICE_FEE_CENTS, count: rows.length, }); } catch (err) { console.error("[puc] requirements/all error:", err); res.status(500).json({ error: "Internal server error" }); } }); // ── POST /api/v1/puc/quote ─────────────────────────────────────────────────── router.post("/api/v1/puc/quote", async (req: Request, res: Response) => { try { const { states, type } = req.body as { states?: string[]; type?: string }; if (!states || !Array.isArray(states) || states.length === 0) { res.status(400).json({ error: "states array required" }); return; } const regType = type || "voip"; if (!["voip", "broadband", "clec", "bundle"].includes(regType)) { res.status(400).json({ error: "type must be voip, broadband, clec, or bundle" }); return; } const codes = states.map(s => s.toUpperCase().trim()).filter(s => s.length === 2); if (codes.length === 0) { res.status(400).json({ error: "No valid 2-letter state codes provided" }); return; } const { rows } = await pool.query( `SELECT r.*, j.name AS state_name FROM state_puc_requirements r JOIN jurisdictions j ON j.code = r.state_code WHERE r.state_code = ANY($1) ORDER BY j.name`, [codes] ); const breakdown = rows.map(r => { let fee_cents = 0; let bond_cents = 0; let required = false; if (regType === "voip" || regType === "bundle") { fee_cents += r.voip_registration_fee_cents; bond_cents += r.voip_bond_amount_cents; required = r.voip_registration_required; } if (regType === "broadband" || regType === "bundle") { fee_cents += r.broadband_registration_fee_cents; required = required || r.broadband_registration_required; } if (regType === "clec" || regType === "bundle") { fee_cents += r.clec_certification_fee_cents; bond_cents = Math.max(bond_cents, r.clec_bond_amount_cents); required = required || r.clec_certification_required; } return { state_code: r.state_code, state_name: r.state_name, registration_required: required, state_fee_cents: required ? fee_cents : 0, bond_required: required && bond_cents > 0, bond_amount_cents: required ? bond_cents : 0, service_fee_cents: required ? PUC_SERVICE_FEE_CENTS : 0, total_cents: required ? (PUC_SERVICE_FEE_CENTS + fee_cents) : 0, exempt: !required, notes: r.voip_notes || r.notes, }; }); const requiredStates = breakdown.filter(b => b.registration_required); const exemptStates = breakdown.filter(b => !b.registration_required); const totalServiceFees = requiredStates.reduce((sum, b) => sum + b.service_fee_cents, 0); const totalStateFees = requiredStates.reduce((sum, b) => sum + b.state_fee_cents, 0); const totalBondAmount = requiredStates.reduce((sum, b) => sum + b.bond_amount_cents, 0); res.json({ type: regType, breakdown, summary: { total_states: codes.length, required_states: requiredStates.length, exempt_states: exemptStates.length, service_fee_total_cents: totalServiceFees, state_fee_total_cents: totalStateFees, bond_total_cents: totalBondAmount, grand_total_cents: totalServiceFees + totalStateFees, bond_note: totalBondAmount > 0 ? "Bond amounts shown are typical ranges. Exact bond requirement depends on provider size and type. Bond procurement is coordinated separately." : null, }, }); } catch (err) { console.error("[puc] quote error:", err); res.status(500).json({ error: "Internal server error" }); } }); export default router;