From 08e80e11f96b0b2d7c6c6ef8dc349706effab8e1 Mon Sep 17 00:00:00 2001 From: justin Date: Sat, 30 May 2026 21:02:32 -0500 Subject: [PATCH] add POST /api/v1/dot/name-check: parallel SOS + FMCSA name availability check - Checks state SOS for entity name availability (via workers adapter) - Checks FMCSA census for exact + fuzzy name matches - Returns: sos_available, fmcsa_in_use, fmcsa_matches, combined 'available' flag - 20s timeout on SOS lookup, FMCSA query is instant (local DB) --- api/src/routes/dot-lookup.ts | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/api/src/routes/dot-lookup.ts b/api/src/routes/dot-lookup.ts index d571f73..e1797d3 100644 --- a/api/src/routes/dot-lookup.ts +++ b/api/src/routes/dot-lookup.ts @@ -669,6 +669,99 @@ router.get("/api/v1/dot/search", async (req, res) => { } }); +// ── Name Availability Check (SOS + FMCSA parallel) ─────────────── + +router.post("/api/v1/dot/name-check", async (req, res) => { + const { name, state } = req.body ?? {}; + if (!name || name.length < 2) { + res.status(400).json({ error: "name is required (at least 2 characters)." }); + return; + } + const stateCode = (state || "").toUpperCase().trim(); + + try { + // Run SOS check and FMCSA check in parallel + const [sosResult, fmcsaResult] = await Promise.allSettled([ + // 1. SOS name availability via workers /entity-status + stateCode && stateCode.length === 2 + ? fetch(`${WORKER_URL}/entity-status`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ entity_name: name, state_code: stateCode }), + signal: AbortSignal.timeout(20000), + }).then(r => r.json()) + : Promise.resolve({ skipped: true }), + + // 2. FMCSA census — exact and fuzzy name match + pool.query( + `SELECT dot_number, legal_name, dba_name, phy_state, carrier_operation + FROM fmcsa_carriers + WHERE legal_name ILIKE $1 OR dba_name ILIKE $1 + ORDER BY CASE WHEN UPPER(legal_name) = UPPER($2) THEN 0 ELSE 1 END, legal_name + LIMIT 20`, + [`%${name}%`, name], + ), + ]); + + // Parse SOS result + let sosAvailable: boolean | null = null; + let sosEntity: Record | null = null; + if (sosResult.status === "fulfilled") { + const sos = sosResult.value as Record; + if (sos.skipped) { + sosAvailable = null; // No state provided + } else if (sos.found) { + sosAvailable = false; // Name is taken + sosEntity = sos as Record; + } else { + sosAvailable = true; // Name is available + } + } + + // Parse FMCSA result + let fmcsaMatches: Record[] = []; + let fmcsaExactMatch = false; + if (fmcsaResult.status === "fulfilled") { + fmcsaMatches = fmcsaResult.value.rows || []; + fmcsaExactMatch = fmcsaMatches.some( + (r: Record) => + (r.legal_name as string || "").toUpperCase() === name.toUpperCase(), + ); + } + + res.json({ + name, + state: stateCode || null, + sos_available: sosAvailable, + sos_entity: sosEntity ? { + status: sosEntity.status, + entity_number: sosEntity.entity_number, + entity_type: sosEntity.entity_type, + } : null, + fmcsa_in_use: fmcsaExactMatch, + fmcsa_matches: fmcsaMatches.slice(0, 10).map((r: Record) => ({ + dot_number: r.dot_number, + legal_name: r.legal_name, + dba_name: r.dba_name, + state: r.phy_state, + })), + available: sosAvailable !== false && !fmcsaExactMatch, + message: fmcsaExactMatch + ? `"${name}" is already registered with FMCSA (DOT# ${(fmcsaMatches.find((r: Record) => (r.legal_name as string || "").toUpperCase() === name.toUpperCase()) as Record)?.dot_number}). Choose a different name or use your existing entity.` + : sosAvailable === false + ? `"${name}" is already registered in ${stateCode}. The name may still be usable if it's your entity.` + : sosAvailable === true + ? `"${name}" is available in ${stateCode} and not found in FMCSA records.` + : !stateCode + ? `"${name}" ${fmcsaExactMatch ? "is" : "is not"} found in FMCSA records. Provide a state to also check Secretary of State availability.` + : `Could not verify availability. Check with the ${stateCode} Secretary of State directly.`, + }); + } catch (err) { + console.error("[name-check] Error:", err); + res.status(500).json({ error: "Name check failed." }); + } +}); + // ── State Requirements Lookup ────────────────────────────────────── router.get("/api/v1/dot/state-requirements", async (req, res) => {