// FCC Form 499-A shared utilities (TypeScript / API side) // // Mirrors scripts/workers/services/telecom/fcc_499_utils.py — same logic // is available to both the API (for /validate dry-run) and the worker // handler (for actual form submission). Keep in sync. // // Authority: 2026 Form 499-A Instructions. import { pool } from "../db.js"; // ── Line 105 box-tick derivation ────────────────────────────────────────── export const LINE_105_BOX_NUMBERS: Record = { voip_interconnected: 1, voip_non_interconnected: 2, clec: 3, ilec: 4, local_reseller: 5, toll_reseller: 6, ixc: 7, wireless: 8, mvno: 9, prepaid_calling_card: 10, private_line: 11, satellite: 12, payphone: 13, osp: 14, shared_tenant: 15, audio_bridging: 16, toll_free: 17, paging: 18, smr: 19, fixed_wireless: 20, mobile_satellite: 21, other: 22, }; export interface Line105Entry { id: string; rank: number; infra_type?: "facilities" | "reseller" | "mvno"; is_tdm_service?: boolean; } export function derivedLine105Boxes( categoryId: string, infraType: string | undefined, ): number[] { const boxes: number[] = []; if (infraType === "reseller") { if (categoryId === "clec") boxes.push(5); if (categoryId === "ixc") boxes.push(6); } if (infraType === "mvno" && categoryId === "wireless") boxes.push(9); return boxes; } export function allLine105BoxesToTick(categories: Line105Entry[]): number[] { const boxes = new Set(); for (const cat of categories ?? []) { if (cat.id && LINE_105_BOX_NUMBERS[cat.id] !== undefined) { boxes.add(LINE_105_BOX_NUMBERS[cat.id]); } for (const b of derivedLine105Boxes(cat.id, cat.infra_type)) { boxes.add(b); } } return Array.from(boxes).sort((a, b) => a - b); } // ── Safe harbor lookup ──────────────────────────────────────────────────── export const SAFE_HARBOR_DISALLOWED = new Set(["voip_non_interconnected"]); export function safeHarborAllowed(categoryId: string): boolean { return !SAFE_HARBOR_DISALLOWED.has(categoryId); } export async function loadSafeHarborPct( formYear: number, categoryId: string, ): Promise { if (SAFE_HARBOR_DISALLOWED.has(categoryId)) return null; const r = await pool.query( `SELECT interstate_pct FROM fcc_safe_harbor_percentages WHERE form_year = $1 AND line_105_category = $2`, [formYear, categoryId], ); return r.rows[0] ? Number(r.rows[0].interstate_pct) : null; } // ── De minimis calculator (Appendix A) ─────────────────────────────────── export interface DeMinimisWorksheet { form_year: number; line_1_filer_interstate_cents: number; line_2_filer_intl_cents: number; line_3_affiliates_interstate_cents: number; line_4_affiliates_intl_cents: number; line_5_consolidated_interstate_cents: number; line_6_consolidated_total_cents: number; line_7_interstate_pct: number; line_8_lire_exempt: boolean; line_9_contribution_base_cents: number; line_10_factor: number; line_11_estimated_contrib_cents: number; is_de_minimis: boolean; threshold_usd: number; notes: string[]; } export interface AffiliateRevenue { total_revenue_cents: number; interstate_pct: number; international_pct: number; } export async function loadDeMinimisFactor(formYear: number): Promise { const r = await pool.query( `SELECT factor FROM fcc_deminimis_factors WHERE form_year = $1`, [formYear], ); if (!r.rows[0]) { throw new Error(`No de minimis factor configured for form year ${formYear}`); } return Number(r.rows[0].factor); } export async function calculateDeMinimis(opts: { form_year: number; filer_total_revenue_cents: number; filer_interstate_pct: number; filer_international_pct: number; affiliates?: AffiliateRevenue[]; }): Promise { const affiliates = opts.affiliates ?? []; const notes: string[] = []; const line_1 = Math.round( opts.filer_total_revenue_cents * (opts.filer_interstate_pct / 100), ); const line_2 = Math.round( opts.filer_total_revenue_cents * (opts.filer_international_pct / 100), ); let line_3 = 0; let line_4 = 0; for (const a of affiliates) { line_3 += Math.round(a.total_revenue_cents * a.interstate_pct / 100); line_4 += Math.round(a.total_revenue_cents * a.international_pct / 100); } const line_5 = line_1 + line_3; const intl_total = line_2 + line_4; const line_6 = line_5 + intl_total; const line_7 = line_6 > 0 ? Math.round(100 * line_5 / line_6 * 10000) / 10000 : 0; const line_8 = line_7 <= 12.0; const line_9 = line_5 + (line_8 ? 0 : intl_total); const line_10 = await loadDeMinimisFactor(opts.form_year); const line_11 = Math.round(line_9 * line_10); const threshold_usd = 10000; const is_de_minimis = line_11 < threshold_usd * 100; if (is_de_minimis) { notes.push( `De minimis: estimated contribution $${(line_11 / 100).toFixed(2)} < ` + `$${threshold_usd.toLocaleString()} threshold.`, ); } else { notes.push( `NOT de minimis: estimated contribution $${(line_11 / 100).toFixed(2)} ` + `≥ $${threshold_usd.toLocaleString()} threshold.`, ); } if (line_8) { notes.push( `LIRE exempt: interstate (${line_7.toFixed(2)}%) ≤ 12% of combined` + ` interstate+intl — international revenue excluded.`, ); } return { form_year: opts.form_year, line_1_filer_interstate_cents: line_1, line_2_filer_intl_cents: line_2, line_3_affiliates_interstate_cents: line_3, line_4_affiliates_intl_cents: line_4, line_5_consolidated_interstate_cents: line_5, line_6_consolidated_total_cents: line_6, line_7_interstate_pct: line_7, line_8_lire_exempt: line_8, line_9_contribution_base_cents: line_9, line_10_factor: line_10, line_11_estimated_contrib_cents: line_11, is_de_minimis, threshold_usd, notes, }; } // ── Line 612 filing type ───────────────────────────────────────────────── export type FilingType = | "original_april_1" | "registration_new_filer" | "revised_registration" | "revised_revenue"; export function detectFilingType(opts: { entity: { filer_id_499?: string | null }; current_year_filing_exists?: boolean; revised_reason?: "registration" | "revenue" | null; }): FilingType { if (!opts.entity.filer_id_499) return "registration_new_filer"; if (opts.current_year_filing_exists) { if (opts.revised_reason === "registration") return "revised_registration"; if (opts.revised_reason === "revenue") return "revised_revenue"; } return "original_april_1"; } // ── TRS contribution base (Lines 512-514) ──────────────────────────────── export const TRS_BASE_LINE_KEYS = [ "line_403", "line_404", "line_404_1", "line_404_3", "line_405", "line_406", "line_407", "line_408", "line_409", "line_410", "line_411", "line_412", "line_413", "line_414_1", "line_414_2", "line_415", "line_416", "line_417", "line_418_4", ] as const; export function computeTrsContributionBase( revenueLines: Record, ): { line_512: number; line_513: number; line_514: number } { const sum = TRS_BASE_LINE_KEYS.reduce( (acc, k) => acc + (Number(revenueLines[k]) || 0), 0, ); const line_512 = sum - (Number(revenueLines.line_511) || 0); const line_513 = Number(revenueLines.line_513) || 0; const line_514 = line_512 - line_513; return { line_512, line_513, line_514 }; }