new-site/api/src/lib/fcc_499_utils.ts
justin f8cd37ac8c Initial commit — Performance West telecom compliance platform
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>
2026-04-27 06:54:22 -05:00

244 lines
7.7 KiB
TypeScript

// 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<string, number> = {
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<number>();
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<number | null> {
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<number> {
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<DeMinimisWorksheet> {
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<string, number | undefined | null>,
): { 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 };
}