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>
This commit is contained in:
commit
f8cd37ac8c
1823 changed files with 145167 additions and 0 deletions
244
api/src/lib/fcc_499_utils.ts
Normal file
244
api/src/lib/fcc_499_utils.ts
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
// 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 };
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue