diff --git a/api/migrations/077_compliance_check_log.sql b/api/migrations/077_compliance_check_log.sql
new file mode 100644
index 0000000..cf72747
--- /dev/null
+++ b/api/migrations/077_compliance_check_log.sql
@@ -0,0 +1,26 @@
+-- 077: Log every FCC compliance check for analytics and follow-up.
+-- Tracks who runs checks, what results they see, and whether they convert.
+
+CREATE TABLE IF NOT EXISTS compliance_check_log (
+ id SERIAL PRIMARY KEY,
+ frn TEXT NOT NULL,
+ entity_name TEXT,
+ ip_address TEXT,
+ user_agent TEXT,
+ referrer TEXT,
+ -- Results summary
+ total_checks INTEGER DEFAULT 0,
+ issues_found INTEGER DEFAULT 0,
+ severity TEXT, -- critical, major, minor, clean
+ check_slugs TEXT[], -- which services were flagged
+ -- Timing
+ response_ms INTEGER, -- how long the API call took
+ -- Conversion tracking
+ clicked_order BOOLEAN DEFAULT FALSE,
+ order_number TEXT, -- if they eventually ordered
+ -- Metadata
+ created_at TIMESTAMPTZ DEFAULT NOW()
+);
+
+CREATE INDEX IF NOT EXISTS idx_check_log_frn ON compliance_check_log(frn);
+CREATE INDEX IF NOT EXISTS idx_check_log_created ON compliance_check_log(created_at);
diff --git a/api/src/routes/fcc-lookup.ts b/api/src/routes/fcc-lookup.ts
index 16e59a3..cca1a12 100644
--- a/api/src/routes/fcc-lookup.ts
+++ b/api/src/routes/fcc-lookup.ts
@@ -55,6 +55,7 @@ router.get("/api/v1/fcc/lookup", async (req, res) => {
return;
}
+ const startMs = Date.now();
try {
// Phase 1: Fast local DB lookups first (~10ms)
const [localRmdP, localRemovedP, local499P] = await Promise.allSettled([
@@ -824,6 +825,36 @@ router.get("/api/v1/fcc/lookup", async (req, res) => {
checks,
checked_at: new Date().toISOString(),
});
+
+ // Log the check for analytics (non-blocking)
+ try {
+ const issueCount = checks?.filter((c: any) => c.severity === "critical" || c.severity === "major").length || 0;
+ const worstSeverity = checks?.some((c: any) => c.severity === "critical") ? "critical"
+ : checks?.some((c: any) => c.severity === "major") ? "major"
+ : checks?.some((c: any) => c.severity === "minor") ? "minor" : "clean";
+ const flaggedSlugs = checks
+ ?.filter((c: any) => c.severity === "critical" || c.severity === "major")
+ .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." });
diff --git a/site/src/pages/tools/fcc-compliance-check.astro b/site/src/pages/tools/fcc-compliance-check.astro
index 3f24683..c04c7e1 100644
--- a/site/src/pages/tools/fcc-compliance-check.astro
+++ b/site/src/pages/tools/fcc-compliance-check.astro
@@ -84,7 +84,8 @@ import Base from "../../layouts/Base.astro";
Checking FCC databases…
+Checking FCC databases…
+This usually takes 30–90 seconds — we're querying multiple FCC systems in real time.
@@ -381,16 +382,36 @@ import Base from "../../layouts/Base.astro"; errorBox.classList.add("hidden"); loadingEl.classList.remove("hidden"); + // Progress status updates while waiting + const statusEl = document.getElementById("loading-status"); + const statusMsgs = [ + "Checking FCC databases\u2026", + "Querying Robocall Mitigation Database\u2026", + "Checking CPNI certification status\u2026", + "Looking up USAC 499 filing history\u2026", + "Verifying BDC / Form 477 records\u2026", + "Checking STIR/SHAKEN implementation\u2026", + "Compiling your compliance report\u2026", + ]; + let msgIdx = 0; + const statusTimer = setInterval(() => { + msgIdx++; + if (statusEl && msgIdx < statusMsgs.length) { + statusEl.textContent = statusMsgs[msgIdx]; + } + }, 8000); + // Cancel any prior in-flight request if (currentController) currentController.abort(); const controller = new AbortController(); currentController = controller; try { - const timeout = setTimeout(() => controller.abort(), 60000); + const timeout = setTimeout(() => controller.abort(), 90000); const res = await fetch(`${API}/api/v1/fcc/lookup?frn=${frn}`, { signal: controller.signal }); clearTimeout(timeout); + clearInterval(statusTimer); // Ignore if a newer request superseded this one if (currentController !== controller) return; @@ -409,12 +430,14 @@ import Base from "../../layouts/Base.astro"; } renderResults(data); } catch (err) { + clearInterval(statusTimer); if (err.name === "AbortError") { - showError("The FCC databases are taking too long to respond. Please try again in a few moments."); + showError("The FCC databases are taking too long to respond. Please try again in a few minutes."); } else { - showError(err.message || "Could not reach the FCC databases. Please check your internet connection and try again."); + showError(err.message || "Could not reach the FCC databases right now. Please try again in a few minutes."); } } finally { + clearInterval(statusTimer); loadingEl.classList.add("hidden"); } }