Improve compliance checker UX + add search logging
- Loading message: shows estimated time (30-90 seconds) + rotating status updates (RMD, CPNI, USAC, BDC, STIR/SHAKEN, compiling report) - Timeout increased to 90s (was 60s) - Error messages: "try again in a few minutes" (not "moments" or "check internet") - New compliance_check_log table: logs every FCC lookup with FRN, entity, IP, user agent, referrer, issue count, severity, response time - Enables conversion funnel analysis and follow-up Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
0085e2b33e
commit
c127cdd908
3 changed files with 84 additions and 4 deletions
26
api/migrations/077_compliance_check_log.sql
Normal file
26
api/migrations/077_compliance_check_log.sql
Normal file
|
|
@ -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);
|
||||
|
|
@ -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." });
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ import Base from "../../layouts/Base.astro";
|
|||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"></path>
|
||||
</svg>
|
||||
<p class="text-gray-500 text-sm">Checking FCC databases…</p>
|
||||
<p class="text-gray-700 text-sm font-medium" id="loading-status">Checking FCC databases…</p>
|
||||
<p class="text-gray-400 text-xs mt-1">This usually takes 30–90 seconds — we're querying multiple FCC systems in real time.</p>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue