new-site/api/src/routes/fcc-lookup.ts
justin bbfa6393fa Fall back to local DB when FCC name search is blocked
FCC/Akamai is blocking our server IP (403). Name search now falls back
to querying the local fcc_499_filers table (20K+ records) when the
live FCC search fails.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-22 00:41:47 -05:00

1493 lines
63 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { Router } from "express";
import { pool } from "../db.js";
const router = Router();
interface CoresData {
frn: string;
entity_name: string | null;
address: string | null;
city: string | null;
state: string | null;
zip: string | null;
status: string | null;
red_light: boolean | null;
error: string | null;
}
interface RmdData {
found: boolean;
business_name: string | null;
frn: string | null;
rmd_number: string | null;
certification_date: string | null;
implementation_type: string | null;
contact_name: string | null;
contact_email: string | null;
removed: boolean;
removal_reason: string | null;
error: string | null;
}
interface ComplianceCheck {
id: string;
label: string;
status: "green" | "yellow" | "red" | "unknown";
detail: string;
action_url: string | null;
due_date: string | null;
}
/**
* FCC Compliance Lookup
*
* GET /api/v1/fcc/lookup?frn=0012345678
*
* Queries multiple public FCC data sources + our local DB to produce
* a unified compliance status report for a given FRN.
*/
router.get("/api/v1/fcc/lookup", async (req, res) => {
const frn = (req.query.frn as string || "").replace(/\D/g, "").padStart(10, "0");
const quickMode = req.query.quick === "1"; // Skip slow checks (corp status, RMD audit)
if (!frn || frn.length !== 10 || frn === "0000000000") {
res.status(400).json({ error: "Please provide a valid 10-digit FRN." });
return;
}
const startMs = Date.now();
try {
// Phase 1: Fast local DB lookups first (~10ms)
const [localRmdP, localRemovedP, local499P] = await Promise.allSettled([
fetchLocalRmd(frn),
fetchLocalRemoved(frn),
fetchLocal499Filer(frn),
]);
const localRmdResult = localRmdP.status === "fulfilled" ? localRmdP.value : null;
const local499Result = local499P.status === "fulfilled" ? local499P.value : null;
const filerId = local499Result?.filer_id || null;
// Phase 2: External API calls — only fetch what we DON'T have locally.
// For carriers in our DB (most of them), skip the slow CORES + RMD scrapes entirely.
const hasLocalData = !!(localRmdResult || local499Result);
const phase2: Record<string, Promise<unknown>> = {};
if (!hasLocalData) {
// Unknown carrier — need live CORES + RMD lookups
phase2.cores = fetchCoresData(frn);
phase2.rmd = fetchRmdData(frn);
} else {
// Known carrier — use local data, skip slow external scrapes
phase2.cores = Promise.resolve(null);
phase2.rmd = Promise.resolve(null);
}
if (filerId) {
phase2.filerDetail = fetch499Detail(filerId);
phase2.cpniResult = fetchCpniStatus(filerId);
}
// Corp status
const corpEntityName = localRmdResult?.business_name || local499Result?.legal_name;
const corpPhysicalState = local499Result?.state;
if (!quickMode && corpEntityName && corpPhysicalState && !(localRmdResult?.foreign_voice_provider === true)) {
phase2.corpStatus = (async () => {
try {
const { lookupCorpStatus } = await import("./corp-status.js");
// Check physical state for foreign entity redirect
const localMatch = await pool.query(
`SELECT entity_type, formation_state FROM entity_cache
WHERE state = $1 AND LOWER(entity_name) = LOWER($2) LIMIT 1`,
[corpPhysicalState, corpEntityName],
).catch(() => ({ rows: [] }));
if (localMatch.rows.length > 0) {
const m = localMatch.rows[0] as Record<string, unknown>;
const eType = ((m.entity_type as string) || "").toUpperCase();
const formSt = (m.formation_state as string) || "";
if (eType.includes("FOREIGN") && formSt && formSt !== corpPhysicalState) {
return lookupCorpStatus(corpEntityName, formSt);
}
}
return lookupCorpStatus(corpEntityName, corpPhysicalState!);
} catch { return null; }
})();
}
// Wait for all phase 2 in parallel
const phase2Keys = Object.keys(phase2);
const phase2Results = await Promise.allSettled(Object.values(phase2));
const p2: Record<string, unknown> = {};
phase2Keys.forEach((k, i) => {
p2[k] = phase2Results[i].status === "fulfilled" ? (phase2Results[i] as PromiseFulfilledResult<unknown>).value : null;
});
let filerDetail = (p2.filerDetail as { current_as_of: string | null; comm_type: string | null; contributor: boolean | null; hq_address: string | null; hq_city: string | null; hq_state: string | null; hq_zip: string | null; error: string | null }) || null;
let cpniResult = (p2.cpniResult as { filed: boolean; cert_year: number | null; date_filed: string | null; error: string | null }) || null;
const coresResult = (p2.cores as CoresData) || { frn, entity_name: null, address: null, city: null, state: null, zip: null, status: null, red_light: null, error: "CORES lookup failed" } as CoresData;
const rmdResult = (p2.rmd as RmdData) || { found: false, business_name: null, frn: null, rmd_number: null, certification_date: null, implementation_type: null, contact_name: null, contact_email: null, removed: false, removal_reason: null, error: "RMD lookup failed" } as RmdData;
const localRemovedResult = localRemovedP.status === "fulfilled" ? localRemovedP.value : null;
// Prefer local 499 filer data over CORES scrape for entity info
if (!coresResult.entity_name && local499Result) {
coresResult.entity_name = local499Result.legal_name;
coresResult.state = local499Result.state;
coresResult.status = "found_local";
coresResult.error = null;
}
// Merge RMD data: prefer live API, enrich from local DB
if (localRmdResult) {
if (!rmdResult.found) {
rmdResult.found = true;
rmdResult.business_name = localRmdResult.business_name;
rmdResult.rmd_number = localRmdResult.rmd_number;
rmdResult.contact_name = localRmdResult.contact_name || localRmdResult.robocall_contact_name;
rmdResult.contact_email = localRmdResult.contact_email || localRmdResult.robocall_contact_email;
rmdResult.implementation_type = localRmdResult.implementation;
}
// Use best available date: last_recertified > last_updated > csv_imported_at
// Normalize all dates to YYYY-MM-DD strings
if (!rmdResult.certification_date) {
const rawDate = localRmdResult.last_recertified || localRmdResult.last_updated || localRmdResult.csv_imported_at;
if (rawDate) {
const d = rawDate instanceof Date ? rawDate : new Date(rawDate);
rmdResult.certification_date = d.toISOString().slice(0, 10);
}
}
// Check if the provider has been removed from RMD
if (localRmdResult.removed_from_rmd) {
rmdResult.removed = true;
rmdResult.removal_reason = `Removed from RMD registry on ${localRmdResult.removed_at ? new Date(localRmdResult.removed_at).toISOString().slice(0, 10) : "unknown date"}. Provider was previously registered but no longer appears in the FCC Robocall Mitigation Database.`;
}
}
// Build compliance checks
const checks: ComplianceCheck[] = [];
// 1. FCC CORES Registration — prefer local data (RMD/499 filer), fall back to live scrape
const localEntityName = localRmdResult?.business_name || local499Result?.legal_name;
const coresName = coresResult.entity_name || localEntityName;
const coresSource = coresResult.entity_name ? "CORES" : (localRmdResult?.business_name ? "RMD registry" : (local499Result?.legal_name ? "499 filer database" : null));
// Red light: check local DB first (populated by CORES Playwright scraper), then CORES scrape
const localRedLight = localRmdResult?.red_light_status;
const isRedLight = localRedLight === "red" || coresResult.red_light === true;
const isGreenLight = localRedLight === "green" || coresResult.red_light === false;
const redLightKnown = localRedLight != null || coresResult.red_light != null;
let coresStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let coresDetail = "";
if (coresName) {
if (isRedLight) {
coresStatus = "red";
coresDetail = `${coresName} — RED LIGHT (outstanding delinquent debts)`;
} else if (isGreenLight) {
coresStatus = "green";
coresDetail = `${coresName} — GREEN (no delinquent debts)`;
} else {
coresStatus = "green";
coresDetail = `${coresName} — Active`;
}
if (coresSource && coresSource !== "CORES") coresDetail += ` (via ${coresSource})`;
} else {
coresDetail = "FRN not found in local registry. Verify directly at FCC CORES.";
}
checks.push({
id: "cores_registration",
label: "FCC CORES Registration",
status: coresStatus,
detail: coresDetail,
action_url: null,
due_date: null,
});
// 2. RMD Filing
const rmdRemoved = localRemovedResult !== null;
let rmdStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let rmdDetail = "";
if (rmdRemoved) {
rmdStatus = "red";
rmdDetail = `REMOVED from RMD — ${localRemovedResult.removal_reason || "See FCC order"}. Immediate action required.`;
} else if (rmdResult.removed) {
rmdStatus = "red";
rmdDetail = rmdResult.removal_reason || "Removed from Robocall Mitigation Database. Immediate action required.";
} else if (rmdResult.found) {
const implLabel = rmdResult.implementation_type || "compliant";
if (rmdResult.certification_date) {
const certDate = new Date(rmdResult.certification_date);
const monthsSince = (Date.now() - certDate.getTime()) / (1000 * 60 * 60 * 24 * 30);
if (monthsSince > 13) {
rmdStatus = "red";
rmdDetail = `Certification expired — last updated ${rmdResult.certification_date}. Annual recertification required by March 1.`;
} else if (monthsSince > 10) {
rmdStatus = "yellow";
rmdDetail = `Certification expiring soon — last updated ${rmdResult.certification_date}. Recertification due March 1.`;
} else {
rmdStatus = "green";
rmdDetail = `${implLabel} — last updated ${rmdResult.certification_date}`;
}
} else {
// Present in RMD CSV but no date available — still means they're registered
rmdStatus = "green";
rmdDetail = `${implLabel} — registered in RMD`;
if (rmdResult.rmd_number) rmdDetail += ` (${rmdResult.rmd_number})`;
}
} else {
rmdStatus = "red";
rmdDetail = "Not found in Robocall Mitigation Database. Registration is required for all voice service providers. If you are a data-only provider, RMD does not apply.";
}
checks.push({
id: "rmd_filing",
label: "Robocall Mitigation Database (RMD)",
status: rmdStatus,
detail: rmdDetail,
action_url: null,
due_date: "March 1 (annual recertification)",
});
// 2b. RMD Filing Quality — real-time structured analysis of the filing
// Checks for 2026 compliance: STIR/SHAKEN consistency, provider classification,
// missing contact info, and logical combinations.
if (rmdResult.found && !rmdRemoved && !quickMode && localRmdResult) {
try {
const rmdIssues: Array<{ label: string; severity: string }> = [];
// Check: Provider classification — at least one must be selected
const isVSP = localRmdResult.voice_service_provider === true;
const isGW = localRmdResult.gateway_provider === true;
const isInter = localRmdResult.intermediate_provider === true;
if (!isVSP && !isGW && !isInter) {
rmdIssues.push({ label: "No provider classification selected", severity: "critical" });
}
// Check: STIR/SHAKEN consistency
const impl = (localRmdResult.implementation || "").toLowerCase();
if (isVSP && !isGW && !isInter) {
if (impl.includes("robocall mitigation") && !impl.includes("partial") && !impl.includes("complete")) {
rmdIssues.push({ label: "VSP without STIR/SHAKEN (robocall mitigation only)", severity: "major" });
}
}
if (isInter && !isVSP && !isGW) {
if (impl.includes("complete") && !impl.includes("partial")) {
rmdIssues.push({ label: "Intermediate provider claims Complete STIR/SHAKEN", severity: "major" });
}
}
// Note: contact_email/name are often NULL in our local DB because the RMD CSV
// doesn't include them (requires separate scrape). Not a filing deficiency.
// Check: 2026 requirements — if we have the PDF audit cached, include those
try {
const auditRow = await pool.query(
`SELECT total_deficiencies, severity, structured_checks, pdf_checks
FROM fcc_rmd_audit_results
WHERE frn = $1 AND audited_at > NOW() - INTERVAL '30 days'
LIMIT 1`,
[frn],
);
if (auditRow.rows.length > 0) {
const audit = auditRow.rows[0] as Record<string, unknown>;
// Merge both structured and PDF audit checks
const pChecks = (audit.pdf_checks as Array<{ id?: string; label: string; severity: string }>) || [];
const sChecks = (audit.structured_checks as Array<{ id?: string; label: string; severity: string }>) || [];
for (const pc of [...sChecks, ...pChecks]) {
if (pc.severity === "minor") continue; // only show major/critical
if (pc.label && !rmdIssues.some(i => i.label === pc.label)) {
rmdIssues.push(pc);
}
}
}
} catch { /* audit table may not exist */ }
if (rmdIssues.length > 0) {
const worstSev = rmdIssues.some(i => i.severity === "critical") ? "critical"
: rmdIssues.some(i => i.severity === "major") ? "major" : "minor";
const labels = rmdIssues.map(i => i.label).slice(0, 5);
checks.push({
id: "rmd_quality",
label: "RMD Filing Quality (2026)",
status: worstSev === "critical" ? "red" : "yellow",
detail: `${rmdIssues.length} issue(s) found: ${labels.join("; ")}${labels.length < rmdIssues.length ? ` (+${rmdIssues.length - labels.length} more)` : ""}. Your filing does not meet all 2026 FCC requirements. We recommend refiling your RMD certification with the missing sections included.`,
action_url: null,
due_date: null,
});
}
} catch {
// Silently skip on error
}
}
// 3. STIR/SHAKEN
//
// Data source: FCC Robocall Mitigation Database (RMD) — this is the
// carrier's SELF-REPORTED implementation status. It is NOT verified
// against the STI-PA (iConectiv) certificate authority database.
// A carrier can claim "Complete STIR/SHAKEN" in RMD without actually
// holding an active STI certificate from iConectiv.
//
// The authoritative source for certificate verification is the STI-PA
// at authenticate.iconectiv.com, which tracks actual certificate
// issuance by OCN. We flag the self-reported status and recommend
// the carrier confirm their cert is active with their STI-CA.
let ssStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let ssDetail = "";
if (rmdResult.found && rmdResult.implementation_type) {
const impl = rmdResult.implementation_type.toLowerCase();
const certDate = rmdResult.certification_date || "";
if (impl.includes("complete") || impl.includes("full")) {
ssStatus = "green";
ssDetail =
`Full STIR/SHAKEN implementation self-reported in RMD` +
(certDate ? ` (certified ${certDate})` : "") +
`. Note: this is the carrier's filing with the FCC, not a ` +
`verification from the STI-PA (iConectiv). Confirm your STI ` +
`certificate is active with your Certificate Authority and ` +
`that your SPC token has not expired.`;
} else if (impl.includes("partial") || impl.includes("robocall mitigation")) {
ssStatus = "yellow";
ssDetail =
`Partial implementation — robocall mitigation plan filed ` +
`but full STIR/SHAKEN not certified in RMD` +
(certDate ? ` (last updated ${certDate})` : "") +
`. Full implementation requires an STI certificate from an ` +
`approved CA (e.g. Sievert Larsen, Neustar, TransNexus) and ` +
`registration with the STI-PA at authenticate.iconectiv.com.`;
} else {
ssStatus = "yellow";
ssDetail = `Implementation type: ${rmdResult.implementation_type}. Verify certificate status with your STI-CA.`;
}
} else if (rmdResult.found) {
ssStatus = "red";
ssDetail =
`RMD filing exists but no STIR/SHAKEN implementation type ` +
`reported. If you are a voice service provider, you must ` +
`either implement STIR/SHAKEN (obtain an STI certificate ` +
`from an approved CA) or file a robocall mitigation plan.`;
} else {
ssDetail = "Cannot determine — no RMD filing found for this FRN. Verify STIR/SHAKEN status directly.";
}
// STIR/SHAKEN — computed but NOT shown on the compliance checker.
// The self-reported RMD status doesn't reliably indicate compliance,
// and showing it creates confusion with OCN bundling on the order page.
// Kept computed for internal use / future STI-PA verification.
// checks.push({
// id: "stir_shaken",
// label: "STIR/SHAKEN Call Authentication",
// status: ssStatus,
// detail: ssDetail,
// action_url: null,
// due_date: null,
// });
// 4. CPNI Annual Certification
//
// CPNI rule: by March 1 each year, every carrier must certify its
// compliance for the *prior* calendar year (47 CFR § 64.2009(e)).
// So on any date in year Y:
// - The "required cert year" is Y-1 (e.g. in 2026 you're certifying
// your 2025 compliance).
// - That cert's deadline is March 1 of year Y.
// - After March 1 of year Y with no filing → PAST DUE for Y-1;
// the missed deadline is the one we surface (NOT bumped a year
// forward — that would make a late filer look on-time).
// - Once the required cert is on file, the NEXT deadline is
// March 1 of year Y+1 for certifying year Y revenue.
const now = new Date();
const has499Filer = local499Result !== null;
const isForeignProvider = localRmdResult?.foreign_voice_provider === true;
// Required cert year = prior calendar year.
const requiredCertYear = now.getFullYear() - 1;
// Deadline for the required cert = March 1 of the current year.
const requiredCertDeadline = new Date(now.getFullYear(), 2, 1);
// Next annual deadline (only meaningful once the required cert is filed).
const nextAnnualDeadline = new Date(now.getFullYear() + 1, 2, 1);
const daysTo = (d: Date) =>
Math.ceil((d.getTime() - now.getTime()) / (1000 * 60 * 60 * 24));
const requiredDeadlineIso = requiredCertDeadline.toISOString().slice(0, 10);
const nextDeadlineIso = nextAnnualDeadline.toISOString().slice(0, 10);
let cpniStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let cpniDetail = "";
let cpniDueDate = requiredDeadlineIso;
if (has499Filer && cpniResult?.filed) {
if (cpniResult.cert_year! >= requiredCertYear) {
// They've certified for the required year or later. Next deadline
// is the following March 1.
cpniStatus = "green";
cpniDueDate = nextDeadlineIso;
cpniDetail =
`Filer ID: ${filerId}. Filed for ${cpniResult.cert_year} ` +
`on ${cpniResult.date_filed}. Next due ${nextDeadlineIso}.`;
} else {
// Older cert on record — they're past due for the most recent year.
cpniStatus = "red";
cpniDueDate = requiredDeadlineIso;
cpniDetail =
`Filer ID: ${filerId}. Past due — last filed for ` +
`${cpniResult.cert_year} (${cpniResult.date_filed}). ` +
`${requiredCertYear} certification was required by ${requiredDeadlineIso}.`;
}
} else if (has499Filer) {
// No filing on record. If the required deadline is behind us, it's
// past due; otherwise it's upcoming.
if (requiredCertDeadline < now) {
const daysOverdue = -daysTo(requiredCertDeadline);
cpniStatus = "red";
cpniDueDate = requiredDeadlineIso;
cpniDetail =
`Filer ID: ${filerId}. PAST DUE — ${requiredCertYear} ` +
`certification was required by ${requiredDeadlineIso} ` +
`(${daysOverdue} days overdue). File immediately to reduce ` +
`47 CFR § 1.80 forfeiture exposure.`;
} else {
const daysUntil = daysTo(requiredCertDeadline);
if (daysUntil <= 30) {
cpniStatus = "yellow";
cpniDetail =
`Filer ID: ${filerId}. Due in ${daysUntil} days ` +
`(${requiredDeadlineIso}). Certifying ${requiredCertYear} compliance.`;
} else {
cpniStatus = "red";
cpniDetail =
`Filer ID: ${filerId}. No CPNI filing found. ` +
`${requiredCertYear} certification required by ${requiredDeadlineIso}.`;
}
}
} else {
if (isForeignProvider) {
cpniStatus = "green";
cpniDetail = "Not required — foreign voice providers operating outside the US are generally exempt from CPNI certification requirements.";
} else {
cpniDetail = `No 499 Filer ID found. If you are a US-based voice carrier, you may need to register with USAC and file CPNI certification by ${requiredDeadlineIso}. Foreign providers are typically exempt.`;
}
}
checks.push({
id: "cpni_certification",
label: "CPNI Annual Certification",
status: cpniStatus,
detail: cpniDetail,
action_url: null,
due_date: cpniDueDate,
});
// 5. Form 499-A
//
// Annual 499-A is due April 1 each year, reporting the PRIOR calendar
// year's revenue (47 CFR § 54.711). Same past-due pattern as CPNI:
// surface the MISSED deadline when no registration is on file past
// April 1, not a year-bumped future date.
const requiredFilingYear = now.getFullYear() - 1; // reporting year
const requiredFilingDue = new Date(now.getFullYear(), 3, 1); // Apr 1
const nextAnnualFilingDue = new Date(now.getFullYear() + 1, 3, 1);
const requiredFilingIso = requiredFilingDue.toISOString().slice(0, 10);
const nextFilingIso = nextAnnualFilingDue.toISOString().slice(0, 10);
let f499Status: "green" | "yellow" | "red" | "unknown" = "unknown";
let f499Detail = "";
let f499DueDate = requiredFilingIso;
const currentAsOf = filerDetail?.current_as_of || null;
if (has499Filer) {
if (currentAsOf) {
// Parse "Registration Current as of" (M/D/YYYY). If the date is
// on/after this year's April 1 (or after the reporting year's
// April 1 before we've hit April), the filing is current.
const regDate = new Date(currentAsOf);
if (regDate >= requiredFilingDue) {
f499Status = "green";
f499DueDate = nextFilingIso;
f499Detail =
`Filer ID: ${filerId}. Registration current as of ` +
`${currentAsOf}. Next due ${nextFilingIso}.`;
} else {
// Registration date is before the current filing deadline.
// They filed for a prior year but haven't filed the current one.
// Compute which year the last filing likely covered:
// a filing on 4/1/2025 covers CY2024 revenue.
const lastFiledYear = regDate.getFullYear() - 1;
const daysOverdue = requiredFilingDue < now
? Math.ceil((now.getTime() - requiredFilingDue.getTime()) / (1000 * 60 * 60 * 24))
: 0;
f499Status = requiredFilingDue < now ? "red" : "yellow";
f499DueDate = requiredFilingIso;
f499Detail = requiredFilingDue < now
? `Filer ID: ${filerId}. PAST DUE — last filing covered CY${lastFiledYear} ` +
`(filed ${currentAsOf}). The CY${requiredFilingYear} annual 499-A was due ` +
`${requiredFilingIso} (${daysOverdue} days overdue).`
: `Filer ID: ${filerId}. Last filing covered CY${lastFiledYear} ` +
`(filed ${currentAsOf}). CY${requiredFilingYear} 499-A due ${requiredFilingIso}.`;
}
} else if (requiredFilingDue < now) {
// No "current as of" date + deadline passed → past due.
const daysOverdue = Math.ceil(
(now.getTime() - requiredFilingDue.getTime()) / (1000 * 60 * 60 * 24),
);
f499Status = "red";
f499DueDate = requiredFilingIso;
f499Detail =
`Filer ID: ${filerId}. PAST DUE — ${requiredFilingYear} annual ` +
`499-A required by ${requiredFilingIso} (${daysOverdue} days overdue). ` +
`Could not verify filing status; file immediately.`;
} else {
const daysUntil = Math.ceil(
(requiredFilingDue.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
if (daysUntil <= 30) {
f499Status = "yellow";
f499DueDate = requiredFilingIso;
f499Detail =
`Due in ${daysUntil} days (${requiredFilingIso}). Filer ID: ${filerId}.`;
} else {
f499Status = "unknown";
f499DueDate = requiredFilingIso;
f499Detail =
`Filer ID: ${filerId}. Could not verify filing status. ` +
`Next due ${requiredFilingIso}.`;
}
}
} else {
if (isForeignProvider) {
f499Status = "green";
f499Detail = "Not required — foreign voice providers are generally exempt from FCC Form 499 filing and USF contribution obligations.";
} else {
f499Detail = `No 499 Filer ID found for this FRN. If you are a US-based carrier, you must register with USAC and file Form 499-A annually (next due ${requiredFilingIso}). Foreign providers are typically exempt.`;
}
}
checks.push({
id: "form_499a",
label: "FCC Form 499-A (Annual)",
status: f499Status,
detail: f499Detail,
action_url: null,
due_date: f499DueDate,
});
// 6. Form 499-Q (quarterly)
//
// Quarterly projections are due Feb 1 / May 1 / Aug 1 / Nov 1 each
// year. Required for USF contributors (waived for de-minimis filers).
// We don't track per-quarter filings in our DB, so we surface BOTH the
// most recently passed deadline (so the customer can self-check past
// due) and the next upcoming deadline.
const quarterDues = [
new Date(now.getFullYear() - 1, 10, 1), // prior Nov 1 (for JanFeb window)
new Date(now.getFullYear(), 1, 1), // Feb 1
new Date(now.getFullYear(), 4, 1), // May 1
new Date(now.getFullYear(), 7, 1), // Aug 1
new Date(now.getFullYear(), 10, 1), // Nov 1
new Date(now.getFullYear() + 1, 1, 1), // next-year Feb 1
];
const next499Q = quarterDues.find(d => d > now)
?? new Date(now.getFullYear() + 1, 1, 1);
const last499Q = [...quarterDues].reverse().find(d => d <= now);
const days499QUntil = Math.ceil(
(next499Q.getTime() - now.getTime()) / (1000 * 60 * 60 * 24),
);
const next499QIso = next499Q.toISOString().slice(0, 10);
const last499QIso = last499Q ? last499Q.toISOString().slice(0, 10) : null;
const isContributor = filerDetail?.contributor;
let q499Status: "green" | "yellow" | "red" | "unknown" = "unknown";
let q499Detail = "";
let q499DueDate = next499QIso;
// De minimis explainer — appended to 499-Q detail when relevant.
// The nuance most carriers miss: de minimis filers are EXEMPT from
// collecting and remitting USF to USAC, but they still OWE USF to
// their upstream vendors (the vendor charges it on their bill and
// remits it to USAC themselves). So de minimis doesn't mean "no USF
// cost" — it means you pay USF embedded in your vendor invoices
// instead of collecting a line-item surcharge from your own customers.
const DE_MINIMIS_NOTE =
`De minimis filers (estimated annual USF contribution < $10,000) ` +
`are exempt from filing 499-Q and from collecting USF surcharges ` +
`from end users. However, de minimis does NOT eliminate your USF ` +
`cost — your upstream carriers (IXCs, CLECs, VoIP providers) still ` +
`charge you USF on their invoices and remit it to USAC on your ` +
`behalf. The trade-off: filing as a full contributor lets you ` +
`pass the USF charge through to your customers as a line-item ` +
`surcharge (revenue-neutral); filing de minimis means you absorb ` +
`the cost yourself.`;
if (isForeignProvider && !has499Filer) {
q499Status = "green";
q499Detail = "Not required — foreign voice providers are generally exempt from USF quarterly projections.";
} else if (!has499Filer) {
// No 499 Filer ID — can't have a quarterly due if not registered with USAC
q499Status = "unknown";
q499Detail = "No 499 Filer ID found. 499-Q is only required for registered USF contributors. If you file 499-A as de minimis, 499-Q is waived.";
} else if (isContributor === false) {
// USAC records show this filer as a non-contributor.
q499Status = "green";
q499Detail =
`Not currently a USF contributor — 499-Q not required. ` +
`Next deadline ${next499QIso} if contribution status changes. ` +
DE_MINIMIS_NOTE;
} else if (f499Status === "red") {
// Annual 499-A is past due; quarterly is almost certainly overdue too.
q499Status = "red";
q499DueDate = last499QIso ?? next499QIso;
q499Detail =
`PAST DUE — 499-A is delinquent, so quarterly projections ` +
`(last deadline ${last499QIso}) are almost certainly overdue. ` +
`Next deadline ${next499QIso}.`;
} else if (days499QUntil <= 30) {
q499Status = "yellow";
q499Detail =
`Due in ${days499QUntil} days (${next499QIso}). ` +
`Not required if you elected de minimis on your 499-A. ` +
DE_MINIMIS_NOTE +
(last499QIso
? ` Confirm the ${last499QIso} quarterly was filed on time.`
: "");
} else {
q499Status = "unknown";
q499Detail =
`Next due ${next499QIso}. ` +
`Required for USF contributors; waived for de minimis filers. ` +
DE_MINIMIS_NOTE +
(last499QIso
? ` Most recent deadline was ${last499QIso} — if you're a ` +
`contributor, confirm that quarterly was filed.`
: "");
}
checks.push({
id: "form_499q",
label: "FCC Form 499-Q (Quarterly)",
status: q499Status,
detail: q499Detail,
action_url: null,
due_date: q499DueDate,
});
// 7. Broadband Data Collection (BDC / Form 477)
// Auto-flag likely broadband providers by comm type; show unknown for VoIP-only
const commType = (filerDetail?.comm_type || "").toLowerCase();
const likelyBroadband = [
"cellular", "pcs", "smr", "cable", "local exchange",
"ilec", "clec", "broadband", "isp", "internet",
"fiber", "fixed wireless", "satellite",
].some(t => commType.includes(t));
let bdcStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let bdcDetail = "";
if (isForeignProvider && !has499Filer) {
bdcStatus = "green";
bdcDetail = "Not required — BDC filing applies only to providers offering broadband or voice service within the United States.";
} else if (likelyBroadband) {
bdcStatus = "yellow";
bdcDetail = `Comm type "${filerDetail?.comm_type}" suggests broadband service. BDC filing likely required — confirm below.`;
} else if (has499Filer) {
bdcDetail = `Comm type "${filerDetail?.comm_type || "unknown"}". BDC filing is required if you provide broadband internet access OR retail voice service to end users in the United States.`;
} else {
bdcDetail = "BDC filing is required if you provide broadband internet access OR retail voice service to end users in the United States.";
}
checks.push({
id: "bdc_filing",
label: "BDC Filing (Broadband + Voice Subscription Data)",
status: bdcStatus,
detail: bdcDetail,
action_url: null,
due_date: null,
});
// 8. Foreign provider recommendation (skip for Canadian carriers — they're already in North America)
const providerCountry = (localRmdResult?.country || "").trim().toLowerCase();
if (isForeignProvider && providerCountry !== "canada") {
const country = localRmdResult?.country || "your country";
checks.push({
id: "foreign_carrier_recommendation",
label: "Expand Into North America",
status: "yellow",
detail:
`Your carrier is registered as a foreign voice provider (${country}). ` +
`Establishing a North American carrier entity can give you direct vendor relationships, ` +
`simplified billing with US/Canadian partners, and local number access. ` +
`We can help you launch a Canadian CRTC-licensed carrier (BC corporation + full CRTC registration) ` +
`or a US FCC-authorized carrier (state formation + CORES/FRN + RMD + 499-A + CPNI — everything included).`,
action_url: null,
due_date: null,
});
}
// 9. Corporation Good Standing (result already fetched in parallel phase 2)
const corpResult = p2.corpStatus as { found: boolean; entity_name: string; entity_type: string; status: string; state: string; years_behind: number; breakdown: string; principal_address: string } | null;
if (corpResult?.found) {
const cStatus = (corpResult.status || "").toUpperCase();
const corpSt = corpResult.state || "";
let corpCheckStatus: "green" | "yellow" | "red" | "unknown" = "unknown";
let corpDetail = "";
const stateAddr = corpResult.principal_address || "";
const coresAddr = coresResult.address || "";
let addrNote = "";
if (stateAddr && coresAddr) {
const normState = stateAddr.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 30);
const normCores = coresAddr.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 30);
if (normState && normCores && normState !== normCores) {
addrNote = ` Note: state filing address (${stateAddr}) differs from FCC CORES address (${coresAddr}) — verify which is current.`;
}
} else if (stateAddr) {
addrNote = ` Address on file: ${stateAddr}.`;
}
if (cStatus === "ACTIVE") {
corpCheckStatus = "green";
corpDetail = `${corpResult.entity_name} (${corpResult.entity_type || "entity"}) in ${corpSt} — Active / Good Standing.${addrNote}`;
} else if (cStatus === "DELINQUENT" || cStatus === "SUSPENDED") {
corpCheckStatus = "red";
corpDetail = `${corpResult.entity_name} in ${corpSt} shows status: ${cStatus}. ` +
(corpResult.years_behind > 0
? `You are ${corpResult.years_behind} year${corpResult.years_behind > 1 ? "s" : ""} behind on annual reports. ${corpResult.breakdown}`
: `Annual report may be overdue. Contact us for a status review.`) +
addrNote;
} else if (cStatus === "DISSOLVED" || cStatus === "INACTIVE") {
corpCheckStatus = "red";
corpDetail = `${corpResult.entity_name} in ${corpSt} shows status: ${cStatus}. ` +
`Reinstatement required to restore good standing. ` +
(corpResult.breakdown || "Contact us for a reinstatement quote.") +
addrNote;
} else {
corpDetail = `${corpResult.entity_name} in ${corpSt} — status: ${cStatus}.${addrNote}`;
}
checks.push({
id: "corporation_status",
label: "Corporation Good Standing",
status: corpCheckStatus,
detail: corpDetail,
action_url: null,
due_date: null,
});
}
// Entity info (best available name — local sources preferred over CORES scrape)
const entityName = local499Result?.legal_name
|| coresResult.entity_name
|| rmdResult.business_name
|| localRmdResult?.business_name
|| null;
res.json({
frn,
entity_name: entityName,
cores: {
entity_name: coresResult.entity_name,
address: coresResult.address || filerDetail?.hq_address || null,
city: coresResult.city || filerDetail?.hq_city || null,
state: coresResult.state || filerDetail?.hq_state || null,
zip: coresResult.zip || filerDetail?.hq_zip || null,
red_light: coresResult.red_light,
error: coresResult.error,
},
rmd: {
found: rmdResult.found,
removed: rmdRemoved,
removal_reason: localRemovedResult?.removal_reason || null,
rmd_number: rmdResult.rmd_number || localRmdResult?.rmd_number || null,
certification_date: rmdResult.certification_date || localRmdResult?.cert_date || null,
implementation_type: rmdResult.implementation_type || localRmdResult?.implementation_type || null,
business_address: localRmdResult?.business_address || null,
contact_name: rmdResult.contact_name || localRmdResult?.robocall_contact_name || null,
other_dba_names: localRmdResult?.other_dba_names || null,
previous_dba_names: localRmdResult?.previous_dba_names || null,
other_frns: localRmdResult?.other_frns || null,
foreign_voice_provider: localRmdResult?.foreign_voice_provider ?? null,
provider_types: [
localRmdResult?.voice_service_provider ? "Voice Service Provider" : null,
localRmdResult?.gateway_provider ? "Gateway Provider" : null,
localRmdResult?.intermediate_provider ? "Intermediate Provider" : null,
].filter(Boolean),
error: rmdResult.error,
},
filer_499: local499Result ? {
filer_id: local499Result.filer_id,
legal_name: local499Result.legal_name,
trade_name: local499Result.trade_name,
state: local499Result.state,
service_type: local499Result.service_type,
current_as_of: filerDetail?.current_as_of || null,
comm_type: filerDetail?.comm_type || null,
usf_contributor: filerDetail?.contributor ?? null,
cpni_cert_year: cpniResult?.cert_year || null,
cpni_date_filed: cpniResult?.date_filed || null,
} : null,
checks,
checked_at: new Date().toISOString(),
});
// Log the check for analytics (non-blocking)
try {
const issueCount = checks?.filter((c: any) => c.status === "red" || c.status === "yellow").length || 0;
const worstSeverity = checks?.some((c: any) => c.status === "red") ? "critical"
: checks?.some((c: any) => c.status === "yellow") ? "major"
: checks?.some((c: any) => c.status === "green") ? "clean" : "clean";
const flaggedSlugs = checks
?.filter((c: any) => c.status === "red" || c.status === "yellow")
.map((c: any) => c.id || c.slug || "")
.filter(Boolean) || [];
const elapsed = Date.now() - startMs;
pool.query(
`INSERT INTO compliance_check_log (frn, entity_name, ip_address, user_agent, referrer, total_checks, issues_found, severity, check_slugs, response_ms)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
[
frn,
entityName || null,
(req as any).clientIp || req.ip || null,
req.headers["user-agent"]?.slice(0, 200) || null,
req.headers.referer?.slice(0, 200) || null,
checks?.length || 0,
issueCount,
worstSeverity,
flaggedSlugs.length > 0 ? flaggedSlugs : null,
elapsed,
],
).catch(() => {}); // non-blocking, don't fail the response
} catch { /* ignore logging errors */ }
} catch (err) {
console.error("[fcc-lookup] Error:", err);
res.status(500).json({ error: "FCC lookup failed. Please try again." });
}
});
// ── Data fetchers ────────────────────────────────────────────────────────
async function fetchCoresData(frn: string): Promise<CoresData> {
// FCC CORES public search detail page — returns entity info without login
const url = `https://apps.fcc.gov/cores/searchDetail.do?frn=${frn}`;
try {
const resp = await fetch(url, {
signal: AbortSignal.timeout(10000),
headers: {
"Accept": "text/html,application/xhtml+xml",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36",
},
redirect: "follow",
});
if (!resp.ok) {
return { frn, entity_name: null, address: null, city: null, state: null, zip: null, status: null, red_light: null, error: `CORES returned ${resp.status}` };
}
const html = await resp.text();
// Parse th/td pairs from the CORES detail page.
// Format: <th>Entity Name:</th> \n <td>Falcon Broadband, LLC</td>
const fields: Record<string, string> = {};
const pairRegex = /<th[^>]*>([^<]+)<\/th>\s*<td[^>]*>([\s\S]*?)<\/td>/gi;
let match;
while ((match = pairRegex.exec(html)) !== null) {
const key = match[1].replace(/[:\s]+$/g, "").trim().toLowerCase();
const val = match[2].replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
if (val && val.length > 1 && val.length < 200) {
fields[key] = val;
}
}
const entityName = fields["entity name"] || fields["registration name"] || null;
// Parse address from "Contact Address" field.
// Raw HTML has <br> separating lines: "12920 SE 38th Street<br>Bellevue, WA 98006<br>United States"
// We need to split on <br> BEFORE stripping tags to preserve line structure.
let address: string | null = null;
let city: string | null = null;
let state: string | null = null;
let zip: string | null = null;
// Re-extract the raw contact address (with <br> tags intact) from HTML
const addrMatch = html.match(/<th[^>]*>Contact Address:<\/th>\s*<td[^>]*>([\s\S]*?)<\/td>/i);
if (addrMatch) {
// Split on <br> tags to get individual lines
const lines = addrMatch[1]
.split(/<br\s*\/?>/i)
.map(l => l.replace(/<[^>]+>/g, "").replace(/\s+/g, " ").trim())
.filter(l => l.length > 0 && l !== "United States" && l !== "US");
if (lines.length >= 2) {
// First line(s) = street, last line with city/state/zip pattern
for (let i = lines.length - 1; i >= 0; i--) {
const csz = lines[i].match(/^(.+),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)$/);
if (csz) {
city = csz[1].trim();
state = csz[2];
zip = csz[3];
// Everything before this line is the street address
address = lines.slice(0, i).join(", ") || null;
break;
}
}
// Fallback: if no city/state/zip pattern found, use first line as address
if (!address && lines.length > 0) {
address = lines[0];
}
} else if (lines.length === 1) {
// Single line — try to parse city/state/zip from it
const csz = lines[0].match(/^(.+?)\s+([A-Za-z\s]+),\s*([A-Z]{2})\s+(\d{5}(?:-\d{4})?)$/);
if (csz) {
address = csz[1].trim();
city = csz[2].trim();
state = csz[3];
zip = csz[4];
} else {
address = lines[0];
}
}
} else if (fields["contact address"]) {
// Fallback to the already-stripped version
address = fields["contact address"];
}
// Red light is not available on the public page — needs authenticated check
// We'll get it from the Playwright-based red light scraper separately
const redLight = null;
if (!entityName && html.includes("password")) {
return { frn, entity_name: null, address: null, city: null, state: null, zip: null, status: "login_required", red_light: null, error: "CORES returned a login page for this FRN" };
}
return {
frn,
entity_name: entityName,
address,
city,
state,
zip,
status: entityName ? "found" : "not_found",
red_light: redLight,
error: null,
};
} catch (err: any) {
return { frn, entity_name: null, address: null, city: null, state: null, zip: null, status: null, red_light: null, error: err.message || "CORES unreachable" };
}
}
async function fetchRmdData(frn: string): Promise<RmdData> {
// FCC RMD — the ServiceNow API requires auth, so we primarily rely on our local DB.
// Try the public-facing RMD search page as a fallback to check if the FRN is listed.
// The RMD public table API: https://fccprod.servicenowservices.com/api/now/table/x_g_fmc_rmd_robocall_mitigation_database
const urls = [
`https://fccprod.servicenowservices.com/api/now/table/x_g_fmc_rmd_robocall_mitigation_database?sysparm_query=u_frn=${frn}&sysparm_limit=1&sysparm_fields=u_business_name,u_frn,number,u_certification_date,u_implementation_type,u_robocall_contact_name,u_robocall_contact_email`,
`https://fccprod.servicenowservices.com/api/x_g_fmc_rmd/rmd?frn=${frn}`,
];
for (const url of urls) {
try {
const resp = await fetch(url, {
signal: AbortSignal.timeout(10000),
headers: { "Accept": "application/json", "User-Agent": "Mozilla/5.0 (compatible; PerformanceWest/1.0)" },
});
if (!resp.ok) continue;
const data = await resp.json() as { result?: any[] };
const records = data.result || [];
if (records.length === 0) continue;
const rec = records[0];
return {
found: true,
business_name: rec.u_business_name || rec.business_name || rec.name || null,
frn: rec.u_frn || rec.frn || frn,
rmd_number: rec.number || rec.rmd_number || null,
certification_date: rec.u_certification_date || rec.certification_date || null,
implementation_type: rec.u_implementation_type || rec.implementation_type || null,
contact_name: rec.u_robocall_contact_name || null,
contact_email: rec.u_robocall_contact_email || null,
removed: false,
removal_reason: null,
error: null,
};
} catch {
continue;
}
}
// All API attempts failed — return not found (local DB will fill in if we have it)
return { found: false, business_name: null, frn: null, rmd_number: null, certification_date: null, implementation_type: null, contact_name: null, contact_email: null, removed: false, removal_reason: null, error: "RMD public API unavailable — checked local database" };
}
async function fetchLocalRmd(frn: string): Promise<any | null> {
try {
const result = await pool.query(
"SELECT * FROM fcc_rmd WHERE frn = $1 LIMIT 1",
[frn],
);
return result.rows[0] || null;
} catch {
return null;
}
}
async function fetchLocal499Filer(frn: string): Promise<any | null> {
try {
const result = await pool.query(
"SELECT * FROM fcc_499_filers WHERE frn = $1 LIMIT 1",
[frn],
);
return result.rows[0] || null;
} catch {
return null;
}
}
async function fetch499Detail(filerId: string): Promise<{
current_as_of: string | null; comm_type: string | null; contributor: boolean | null;
hq_address: string | null; hq_city: string | null; hq_state: string | null; hq_zip: string | null;
error: string | null;
}> {
// Scrape the FCC 499 filer detail page for filing status + address
const url = `https://apps.fcc.gov/cgb/form499/499detail.cfm?FilerNum=${filerId}`;
try {
const resp = await fetch(url, {
signal: AbortSignal.timeout(10000),
headers: { "Accept": "text/html", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" },
});
if (!resp.ok) return { current_as_of: null, comm_type: null, contributor: null, hq_address: null, hq_city: null, hq_state: null, hq_zip: null, error: `499 detail returned ${resp.status}` };
const html = await resp.text();
const currentMatch = html.match(/Registration Current as of:\s*<b>([^<]+)<\/b>/i);
const current_as_of = currentMatch ? currentMatch[1].trim() : null;
const commMatch = html.match(/Principal Communications Type:\s*<b>([^<]+)<\/b>/i);
const comm_type = commMatch ? commMatch[1].trim() || null : null;
const contribMatch = html.match(/Universal Service Fund Contributor:\s*<b>([^<]+)<\/b>/i);
const contributor = contribMatch ? contribMatch[1].trim().toLowerCase() === "yes" : null;
// Extract headquarters address (more reliable than CORES for some FRNs)
const addrMatch = html.match(/Headquarters Address:\s*<b>([^<]*)<\/b>/i);
const cityMatch = html.match(/Headquarters Address:[\s\S]*?City:\s*<b>([^<]*)<\/b>/i);
const stateMatch = html.match(/Headquarters Address:[\s\S]*?State:\s*<b>([^<]*)<\/b>/i);
const zipMatch = html.match(/Headquarters Address:[\s\S]*?ZIP Code:\s*<b>([^<]*)<\/b>/i);
return {
current_as_of, comm_type, contributor,
hq_address: addrMatch ? addrMatch[1].trim() || null : null,
hq_city: cityMatch ? cityMatch[1].trim() || null : null,
hq_state: stateMatch ? stateMatch[1].trim() || null : null,
hq_zip: zipMatch ? zipMatch[1].trim() || null : null,
error: null,
};
} catch (err: any) {
return { current_as_of: null, comm_type: null, contributor: null, hq_address: null, hq_city: null, hq_state: null, hq_zip: null, error: err.message };
}
}
async function fetchCpniStatus(filerId: string): Promise<{ filed: boolean; cert_year: number | null; date_filed: string | null; error: string | null }> {
// Check FCC CPNI portal for filing status
const url = "https://apps.fcc.gov/eb/CPNI/search_results.cfm";
try {
const resp = await fetch(url, {
method: "POST",
signal: AbortSignal.timeout(10000),
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36",
},
body: `Form499FilerID=${filerId}&BeginDate=&EndDate=&ConfirmationNumbers=&SubmitForm=Find+Submissions`,
});
if (!resp.ok) return { filed: false, cert_year: null, date_filed: null, error: `CPNI returned ${resp.status}` };
const html = await resp.text();
// Parse rows: <td>Feb 28 2025 8:30PM</td> ... <td>2024</td>
// The HTML has tabs/newlines between td elements
const rowRegex = /<td>([A-Z][a-z]{2}\s+\d{1,2}\s+\d{4}[^<]*)<\/td>[\s\S]*?<td>(\d{4})<\/td>/gi;
let latestYear = 0;
let latestDate = "";
let match;
while ((match = rowRegex.exec(html)) !== null) {
const year = parseInt(match[2]);
if (year > latestYear) {
latestYear = year;
latestDate = match[1].trim();
}
}
if (latestYear > 0) {
return { filed: true, cert_year: latestYear, date_filed: latestDate, error: null };
}
// Check if there were no results
if (html.includes("No matching records")) {
return { filed: false, cert_year: null, date_filed: null, error: null };
}
return { filed: false, cert_year: null, date_filed: null, error: null };
} catch (err: any) {
return { filed: false, cert_year: null, date_filed: null, error: err.message };
}
}
async function fetchLocalRemoved(frn: string): Promise<any | null> {
try {
const result = await pool.query(
"SELECT * FROM fcc_rmd_removed WHERE frn = $1 LIMIT 1",
[frn],
);
return result.rows[0] || null;
} catch {
return null;
}
}
/**
* Search local FCC databases by FRN, 499 Filer ID, or entity name.
*
* GET /api/v1/fcc/search?q=Falcon+Broadband
* GET /api/v1/fcc/search?frn=0027160886
* GET /api/v1/fcc/search?filer_id=812345
*
* Returns matching records from fcc_rmd, fcc_499_filers, and fcc_rmd_removed.
*/
/**
* Parse city + state from an RMD business_address string.
* The address is multi-line, with the last line typically:
* "Sheridan WY 82801" — City ST ZIP
* "UPSALA MN 56384" — City ST ZIP
* "Las Vegas NV 89104" — City ST ZIP
* "Quezon City, Philippines" — international (no US state)
*/
function addLocationFromAddress(row: Record<string, unknown>): Record<string, unknown> {
const addr = String(row.business_address || "");
if (!addr) return row;
// Split on newlines and take the last non-empty line
const lines = addr.split(/\n/).map(l => l.trim()).filter(Boolean);
const lastLine = lines[lines.length - 1] || "";
// Match "City ST ZIP" or "City ST" where ST is exactly 2 uppercase letters
const m = lastLine.match(/^(.+?)\s+([A-Z]{2})\s*(\d{5}(?:-\d{4})?)?$/);
if (m) {
row.city = m[1].replace(/,\s*$/, "").trim();
row.state = m[2];
}
return row;
}
router.get("/api/v1/fcc/search", async (req, res) => {
const q = (req.query.q as string || "").trim();
const frn = (req.query.frn as string || "").trim();
const filerId = (req.query.filer_id as string || "").trim();
if (!q && !frn && !filerId) {
res.status(400).json({ error: "Provide q (name search), frn, or filer_id." });
return;
}
try {
const results: any[] = [];
if (frn) {
// Exact FRN search across all tables
const rmd = await pool.query(
"SELECT rmd_number, frn, business_name, business_address, implementation, removed_from_rmd, removed_at, last_recertified, 'rmd' as source FROM fcc_rmd WHERE frn = $1 LIMIT 10",
[frn.padStart(10, "0")],
);
results.push(...rmd.rows.map(addLocationFromAddress));
const filers = await pool.query(
"SELECT filer_id, frn, legal_name as business_name, state, service_type, 'filer_499' as source FROM fcc_499_filers WHERE frn = $1 LIMIT 10",
[frn.padStart(10, "0")],
);
results.push(...filers.rows);
}
if (filerId) {
// Search by 499 Filer ID
const filers = await pool.query(
"SELECT filer_id, frn, legal_name as business_name, state, service_type, 'filer_499' as source FROM fcc_499_filers WHERE filer_id = $1 LIMIT 10",
[filerId],
);
results.push(...filers.rows);
}
if (q && q.length >= 2) {
// Free-text name search across both tables
const searchTerm = `%${q}%`;
const rmd = await pool.query(
`SELECT rmd_number, frn, business_name, business_address, implementation, removed_from_rmd, removed_at, 'rmd' as source
FROM fcc_rmd WHERE business_name ILIKE $1 ORDER BY business_name LIMIT 20`,
[searchTerm],
);
results.push(...rmd.rows.map(addLocationFromAddress));
const filers = await pool.query(
`SELECT filer_id, frn, legal_name as business_name, state, service_type, 'filer_499' as source
FROM fcc_499_filers WHERE legal_name ILIKE $1 OR trade_name ILIKE $1 ORDER BY legal_name LIMIT 20`,
[searchTerm],
);
results.push(...filers.rows);
}
// Deduplicate by FRN
const seen = new Set<string>();
const deduped = results.filter(r => {
const key = r.frn || r.rmd_number || r.filer_id || JSON.stringify(r);
if (seen.has(key)) return false;
seen.add(key);
return true;
});
res.json({
results: deduped,
count: deduped.length,
query: { q: q || undefined, frn: frn || undefined, filer_id: filerId || undefined },
});
} catch (err) {
console.error("[fcc-search] Error:", err);
res.status(500).json({ error: "Search failed." });
}
});
/**
* Search CORES / FCC 499 database by business name.
* Queries the FCC 499 filer database directly for entities matching the name.
*
* GET /api/v1/fcc/cores-search?name=Carrier+One
*/
router.get("/api/v1/fcc/cores-search", async (req, res) => {
const name = (req.query.name as string || "").trim();
if (!name || name.length < 2) {
res.status(400).json({ error: "Provide a business name (at least 2 characters)." });
return;
}
try {
// Query FCC 499 filer database with wildcard search
// The FCC search engine can't handle & in names (treats as URL separator even when encoded)
// Strip & so "AT&T" becomes "ATT" which still matches their records
const sanitizedName = name.replace(/&/g, "");
const url = `https://apps.fcc.gov/cgb/form499/499results.cfm?FilerID=&frn=&LegalName=${encodeURIComponent(sanitizedName)}&state=Any+State&operational=&comm_type=Any+Type&R1=and`;
const resp = await fetch(url, {
signal: AbortSignal.timeout(15000),
headers: { "Accept": "text/html", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36" },
});
if (!resp.ok) {
res.status(502).json({ error: `The FCC search system is temporarily unavailable (HTTP ${resp.status}). Try searching by FRN instead, or try again in a few minutes.` });
return;
}
const html = await resp.text();
// FCC sometimes returns 200 but with a maintenance/error page
if (html.includes("Service Unavailable") || (html.includes("503") && !html.includes("499results"))) {
res.status(502).json({ error: "The FCC search system is temporarily unavailable. Try searching by FRN instead, or try again in a few minutes." });
return;
}
// Parse the results table rows: <tr><th scope="row">834314</th> ... <td><A HREF="499detail.cfm?FilerNum=834314">Carrier One Inc.</a></td><td>DBA</td></tr>
const rowRegex = /<tr><th scope="row">(\d+)<\/th>\s*\n?\s*<td><A HREF="499detail\.cfm\?FilerNum=\d+">([^<]+)<\/a><\/td><td>([^<]*)<\/td><\/tr>/gi;
const results: { filer_id: string; legal_name: string; dba: string }[] = [];
let match;
while ((match = rowRegex.exec(html)) !== null) {
results.push({
filer_id: match[1].trim(),
legal_name: match[2].trim(),
dba: match[3].trim(),
});
}
// Also extract record count
const countMatch = html.match(/(\d+)\s+Record(?:s)?\s+Found/i);
const totalCount = countMatch ? parseInt(countMatch[1]) : results.length;
// For each result, look up the FRN from our local database
const enriched = await Promise.all(results.map(async (r) => {
try {
const local = await pool.query(
"SELECT frn FROM fcc_499_filers WHERE filer_id = $1 LIMIT 1",
[r.filer_id],
);
return { ...r, frn: local.rows[0]?.frn || null };
} catch {
return { ...r, frn: null };
}
}));
res.json({
results: enriched,
count: enriched.length,
total_found: totalCount,
query: name,
source: "FCC Form 499 Filer Database",
});
} catch (err: any) {
// FCC is down or blocking us — fall back to local database
console.warn("[fcc-cores-search] FCC search failed, falling back to local DB:", err.message);
try {
const localResults = await pool.query(
`SELECT filer_id, legal_name, trade_name AS dba, frn
FROM fcc_499_filers
WHERE legal_name ILIKE $1 OR trade_name ILIKE $1
ORDER BY legal_name
LIMIT 50`,
[`%${name}%`],
);
const enriched = localResults.rows.map((r: any) => ({
filer_id: r.filer_id,
legal_name: r.legal_name,
dba: r.dba || "",
frn: r.frn || null,
}));
res.json({
results: enriched,
count: enriched.length,
total_found: enriched.length,
query: name,
source: "Local database (FCC search temporarily unavailable)",
});
} catch (dbErr: any) {
console.error("[fcc-cores-search] Local fallback also failed:", dbErr);
res.status(500).json({ error: "Search is temporarily unavailable. Please try again later or search by FRN." });
}
}
});
/**
* Upload FCC Form 499 Filer Database Excel dump.
*
* POST /api/v1/fcc/upload-filer-db
* Content-Type: multipart/form-data (file field: "file")
*
* Accepts the Excel dump from https://apps.fcc.gov/cgb/form499/499a.cfm
* and upserts all records into fcc_499_filers table.
*/
router.post("/api/v1/fcc/upload-filer-db", async (req, res) => {
// Accept raw body as the file content (simple approach — no multer needed)
// Called via: curl -X POST -F "file=@499_dump.xlsx" .../upload-filer-db
// For now, accept JSON with CSV-parsed rows from the client side
const { rows } = req.body ?? {};
if (!rows || !Array.isArray(rows) || rows.length === 0) {
res.status(400).json({
error: "Send JSON body with 'rows' array. Each row: {filer_id, frn, legal_name, state, service_type}",
hint: "Use the CLI importer: python3 -m scripts.workers.fcc_499_filer_import /path/to/499_dump.xlsx",
});
return;
}
try {
let count = 0;
for (const row of rows) {
const filerId = String(row.filer_id || row.FilerID || "").trim();
const legalName = String(row.legal_name || row.LegalName || row.Company || "").trim();
if (!filerId || !legalName) continue;
await pool.query(
`INSERT INTO fcc_499_filers (filer_id, frn, legal_name, trade_name, state, service_type, status, last_scraped_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW(), NOW())
ON CONFLICT (filer_id) DO UPDATE SET
frn = COALESCE(EXCLUDED.frn, fcc_499_filers.frn),
legal_name = EXCLUDED.legal_name,
trade_name = COALESCE(EXCLUDED.trade_name, fcc_499_filers.trade_name),
state = COALESCE(EXCLUDED.state, fcc_499_filers.state),
service_type = COALESCE(EXCLUDED.service_type, fcc_499_filers.service_type),
last_scraped_at = NOW(), updated_at = NOW()`,
[
filerId,
String(row.frn || row.FRN || row.CORESID || "").trim() || null,
legalName,
String(row.trade_name || row.TradeName || "").trim() || null,
String(row.state || row.State || "").trim().substring(0, 2) || null,
String(row.service_type || row.ServiceType || row.PrincipalCommunicationsType || "").trim() || null,
String(row.status || row.OperationalStatus || "Active").trim(),
],
);
count++;
}
const totalResult = await pool.query("SELECT COUNT(*) FROM fcc_499_filers");
res.json({
success: true,
imported: count,
total_in_db: parseInt(totalResult.rows[0].count),
});
} catch (err) {
console.error("[fcc-upload] Error:", err);
res.status(500).json({ error: "Import failed." });
}
});
/**
* Import 499-A data from USAC E-File
*
* POST /api/v1/fcc/import-499a
* Body: { filer_id: "812345" }
*
* Dispatches a worker job to scrape USAC E-File for the filer's
* most recent 499-A data. Returns the extracted entity info,
* service categories, and revenue lines.
*/
router.post("/api/v1/fcc/import-499a", async (req, res) => {
const { filer_id } = req.body ?? {};
if (!filer_id || typeof filer_id !== "string" || filer_id.length < 3) {
res.status(400).json({ error: "Please provide a valid USAC 499 Filer ID." });
return;
}
const WORKER_URL = process.env.WORKER_URL || "http://workers:8090";
try {
// Dispatch to worker
const workerResp = await fetch(`${WORKER_URL}/jobs`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
action: "import_499a",
filer_id: filer_id.trim(),
}),
signal: AbortSignal.timeout(60000),
});
if (!workerResp.ok) {
const err = await workerResp.text();
console.error("[fcc-import] Worker error:", err);
res.status(502).json({
success: false,
error: "Import service unavailable. Please try again or enter data manually.",
});
return;
}
const result = await workerResp.json();
res.json(result);
} catch (err: any) {
console.error("[fcc-import] Error:", err.message);
// If worker is unavailable, return a helpful message
if (err.message?.includes("fetch failed") || err.message?.includes("ECONNREFUSED")) {
res.status(503).json({
success: false,
error: "Import service is not running. Please enter your data manually or contact us for assistance.",
manual_entry_required: true,
});
} else {
res.status(500).json({
success: false,
error: "Import failed. Please try again or enter data manually.",
});
}
}
});
export default router;