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>
253 lines
8.8 KiB
TypeScript
253 lines
8.8 KiB
TypeScript
/**
|
|
* 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;
|