Add DOT/FMCSA compliance checker API
GET /api/v1/dot/lookup?dot=XXXXXX — live compliance check combining local census data + FMCSA QCMobile API. Checks: - Operating status (allowed to operate Y/N) - MCS-150 biennial update (overdue detection) - Insurance filing (BIPD, cargo, bond) - Safety rating (S/C/U) - Operating authority status - Out-of-service rates vs national average - Crash record GET /api/v1/dot/search?name=Acme — name search against local census Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
dfee4fc6c0
commit
46069b07a0
2 changed files with 348 additions and 0 deletions
|
|
@ -46,6 +46,7 @@ import portalRmdReviewRouter from "./routes/portal-rmd-review.js";
|
|||
import portalEsignGenericRouter from "./routes/portal-esign-generic.js";
|
||||
import pucRouter from "./routes/puc.js";
|
||||
import fccCarrierRegRouter from "./routes/fcc-carrier-registration.js";
|
||||
import dotLookupRouter from "./routes/dot-lookup.js";
|
||||
|
||||
const app = express();
|
||||
|
||||
|
|
@ -116,6 +117,7 @@ app.use(fccFilingsRouter);
|
|||
app.use(foreignQualRouter);
|
||||
app.use(pucRouter);
|
||||
app.use(fccCarrierRegRouter);
|
||||
app.use(dotLookupRouter);
|
||||
app.use(adminCryptoRouter);
|
||||
// Note: identityRouter mounted above express.json() for webhook route,
|
||||
// but also handles non-webhook routes (create-session, poll) which work fine with json()
|
||||
|
|
|
|||
346
api/src/routes/dot-lookup.ts
Normal file
346
api/src/routes/dot-lookup.ts
Normal file
|
|
@ -0,0 +1,346 @@
|
|||
/**
|
||||
* DOT / FMCSA motor carrier compliance lookup.
|
||||
*
|
||||
* GET /api/v1/dot/lookup?dot=1234567
|
||||
* GET /api/v1/dot/search?name=Acme+Trucking
|
||||
*
|
||||
* Combines local census data with live FMCSA QCMobile API for
|
||||
* real-time compliance status checks.
|
||||
*/
|
||||
|
||||
import { Router } from "express";
|
||||
import pool from "../db.js";
|
||||
|
||||
const router = Router();
|
||||
const FMCSA_API_KEY = process.env.FMCSA_API_KEY || "";
|
||||
const FMCSA_BASE = "https://mobile.fmcsa.dot.gov/qc/services/carriers";
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────
|
||||
|
||||
async function fmcsaFetch(path: string): Promise<any> {
|
||||
if (!FMCSA_API_KEY) return null;
|
||||
try {
|
||||
const url = `${FMCSA_BASE}/${path}${path.includes("?") ? "&" : "?"}webKey=${FMCSA_API_KEY}`;
|
||||
const resp = await fetch(url, {
|
||||
signal: AbortSignal.timeout(10000),
|
||||
headers: { "Accept": "application/json" },
|
||||
});
|
||||
if (!resp.ok) return null;
|
||||
return await resp.json();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
type CheckStatus = "green" | "yellow" | "red" | "unknown";
|
||||
|
||||
interface ComplianceCheck {
|
||||
id: string;
|
||||
label: string;
|
||||
status: CheckStatus;
|
||||
detail: string;
|
||||
action_url?: string | null;
|
||||
}
|
||||
|
||||
function twoYearsAgo(): Date {
|
||||
const d = new Date();
|
||||
d.setFullYear(d.getFullYear() - 2);
|
||||
return d;
|
||||
}
|
||||
|
||||
// ── DOT Lookup ──────────────────────────────────────────────────────
|
||||
|
||||
router.get("/api/v1/dot/lookup", async (req, res) => {
|
||||
const rawDot = (req.query.dot as string || "").trim().replace(/\D/g, "");
|
||||
if (!rawDot) {
|
||||
res.status(400).json({ error: "Provide a DOT number." });
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. Local census data
|
||||
const local = await pool.query(
|
||||
"SELECT * FROM fmcsa_carriers WHERE dot_number = $1",
|
||||
[rawDot],
|
||||
);
|
||||
const census = local.rows[0] || null;
|
||||
|
||||
// 2. Live FMCSA API
|
||||
const snapshot = await fmcsaFetch(rawDot);
|
||||
const carrier = snapshot?.content?.carrier || null;
|
||||
|
||||
// 3. Authority check
|
||||
const authorityData = await fmcsaFetch(`${rawDot}/authority`);
|
||||
const authorities = (authorityData?.content || []).map((a: any) => a.carrierAuthority);
|
||||
|
||||
if (!census && !carrier) {
|
||||
res.status(404).json({ error: `DOT# ${rawDot} not found.` });
|
||||
return;
|
||||
}
|
||||
|
||||
// Build compliance checks
|
||||
const checks: ComplianceCheck[] = [];
|
||||
const name = carrier?.legalName || census?.legal_name || "Unknown";
|
||||
const dba = carrier?.dbaName || census?.dba_name || "";
|
||||
|
||||
// ── Check 1: Operating Status ──
|
||||
if (carrier) {
|
||||
const allowed = carrier.allowedToOperate === "Y";
|
||||
const status = carrier.statusCode;
|
||||
checks.push({
|
||||
id: "operating_status",
|
||||
label: "Operating Status",
|
||||
status: allowed ? "green" : "red",
|
||||
detail: allowed
|
||||
? `Authorized to operate. Status: ${status === "A" ? "Active" : status}.`
|
||||
: `NOT authorized to operate. Status: ${status}. This carrier cannot legally operate.`,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Check 2: MCS-150 Biennial Update ──
|
||||
{
|
||||
const outdated = carrier?.mcs150Outdated === "Y";
|
||||
const mcs150Date = census?.mcs150_parsed || null;
|
||||
const isOverdue = mcs150Date ? new Date(mcs150Date) < twoYearsAgo() : null;
|
||||
|
||||
if (outdated || isOverdue) {
|
||||
checks.push({
|
||||
id: "mcs150",
|
||||
label: "MCS-150 Biennial Update",
|
||||
status: "red",
|
||||
detail: `OVERDUE — last filed ${mcs150Date || "unknown date"}. The MCS-150 must be updated every 2 years. `
|
||||
+ `Failure to update can result in USDOT deactivation and fines up to $1,000/day (capped at $10,000). `
|
||||
+ `File at: https://safer.fmcsa.dot.gov/CompanyUpdateSearch.aspx`,
|
||||
action_url: "https://safer.fmcsa.dot.gov/CompanyUpdateSearch.aspx",
|
||||
});
|
||||
} else if (mcs150Date) {
|
||||
checks.push({
|
||||
id: "mcs150",
|
||||
label: "MCS-150 Biennial Update",
|
||||
status: "green",
|
||||
detail: `Current — last filed ${mcs150Date}.`,
|
||||
});
|
||||
} else {
|
||||
checks.push({
|
||||
id: "mcs150",
|
||||
label: "MCS-150 Biennial Update",
|
||||
status: "unknown",
|
||||
detail: "Filing date not available. Verify at SAFER.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check 3: Insurance ──
|
||||
if (carrier) {
|
||||
const bipdRequired = carrier.bipdInsuranceRequired === "Y";
|
||||
const bipdOnFile = carrier.bipdInsuranceOnFile !== null && carrier.bipdInsuranceOnFile !== "0";
|
||||
const cargoRequired = carrier.cargoInsuranceRequired === "Y";
|
||||
const cargoOnFile = carrier.cargoInsuranceOnFile !== null && carrier.cargoInsuranceOnFile !== "0";
|
||||
const bondRequired = carrier.bondInsuranceRequired === "Y";
|
||||
const bondOnFile = carrier.bondInsuranceOnFile !== null && carrier.bondInsuranceOnFile !== "0";
|
||||
|
||||
const issues: string[] = [];
|
||||
if (bipdRequired && !bipdOnFile) issues.push("Liability (BIPD) insurance required but not on file");
|
||||
if (cargoRequired && !cargoOnFile) issues.push("Cargo insurance required but not on file");
|
||||
if (bondRequired && !bondOnFile) issues.push("Bond/trust fund required but not on file");
|
||||
|
||||
if (issues.length > 0) {
|
||||
checks.push({
|
||||
id: "insurance",
|
||||
label: "Insurance Filing",
|
||||
status: "red",
|
||||
detail: issues.join(". ") + ". Operating without required insurance can result in immediate out-of-service order and operating authority revocation.",
|
||||
});
|
||||
} else if (bipdRequired || cargoRequired) {
|
||||
checks.push({
|
||||
id: "insurance",
|
||||
label: "Insurance Filing",
|
||||
status: "green",
|
||||
detail: "Required insurance is on file with FMCSA.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check 4: Safety Rating ──
|
||||
if (carrier && carrier.safetyRating) {
|
||||
const rating = carrier.safetyRating;
|
||||
const ratingDate = carrier.safetyRatingDate || "";
|
||||
const ratingMap: Record<string, { status: CheckStatus; label: string }> = {
|
||||
S: { status: "green", label: "Satisfactory" },
|
||||
C: { status: "yellow", label: "Conditional" },
|
||||
U: { status: "red", label: "Unsatisfactory" },
|
||||
};
|
||||
const r = ratingMap[rating] || { status: "unknown", label: rating };
|
||||
checks.push({
|
||||
id: "safety_rating",
|
||||
label: "Safety Rating",
|
||||
status: r.status,
|
||||
detail: `${r.label}${ratingDate ? ` (rated ${ratingDate})` : ""}. `
|
||||
+ (rating === "U" ? "An Unsatisfactory rating may lead to an operations out-of-service order." : "")
|
||||
+ (rating === "C" ? "A Conditional rating indicates deficiencies that need correction." : ""),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Check 5: Operating Authority ──
|
||||
if (carrier) {
|
||||
const commonActive = carrier.commonAuthorityStatus === "A";
|
||||
const contractActive = carrier.contractAuthorityStatus === "A";
|
||||
const brokerActive = carrier.brokerAuthorityStatus === "A";
|
||||
const isForHire = census?.authorized_for_hire || false;
|
||||
|
||||
if (isForHire && !commonActive && !contractActive) {
|
||||
checks.push({
|
||||
id: "authority",
|
||||
label: "Operating Authority",
|
||||
status: "red",
|
||||
detail: "For-hire carrier without active operating authority. Common authority: "
|
||||
+ (carrier.commonAuthorityStatus || "None") + ", Contract authority: "
|
||||
+ (carrier.contractAuthorityStatus || "None")
|
||||
+ ". You cannot legally transport freight for compensation without active authority.",
|
||||
});
|
||||
} else if (commonActive || contractActive || brokerActive) {
|
||||
const types: string[] = [];
|
||||
if (commonActive) types.push("Common");
|
||||
if (contractActive) types.push("Contract");
|
||||
if (brokerActive) types.push("Broker");
|
||||
checks.push({
|
||||
id: "authority",
|
||||
label: "Operating Authority",
|
||||
status: "green",
|
||||
detail: `Active authority: ${types.join(", ")}.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check 6: Out-of-Service Rates ──
|
||||
if (carrier) {
|
||||
const driverOos = parseFloat(carrier.driverOosRate) || 0;
|
||||
const driverNat = parseFloat(carrier.driverOosRateNationalAverage) || 5.51;
|
||||
const vehicleOos = parseFloat(carrier.vehicleOosRate) || 0;
|
||||
const vehicleNat = parseFloat(carrier.vehicleOosRateNationalAverage) || 20.72;
|
||||
|
||||
if (driverOos > driverNat * 1.5 || vehicleOos > vehicleNat * 1.5) {
|
||||
checks.push({
|
||||
id: "oos_rate",
|
||||
label: "Out-of-Service Rate",
|
||||
status: "red",
|
||||
detail: `Driver OOS: ${driverOos.toFixed(1)}% (national avg: ${driverNat}%). `
|
||||
+ `Vehicle OOS: ${vehicleOos.toFixed(1)}% (national avg: ${vehicleNat}%). `
|
||||
+ `Significantly above national average — targeted for increased inspections.`,
|
||||
});
|
||||
} else if (driverOos > driverNat || vehicleOos > vehicleNat) {
|
||||
checks.push({
|
||||
id: "oos_rate",
|
||||
label: "Out-of-Service Rate",
|
||||
status: "yellow",
|
||||
detail: `Driver OOS: ${driverOos.toFixed(1)}% (national avg: ${driverNat}%). `
|
||||
+ `Vehicle OOS: ${vehicleOos.toFixed(1)}% (national avg: ${vehicleNat}%). `
|
||||
+ `Above national average.`,
|
||||
});
|
||||
} else if (carrier.vehicleInsp > 0 || carrier.driverInsp > 0) {
|
||||
checks.push({
|
||||
id: "oos_rate",
|
||||
label: "Out-of-Service Rate",
|
||||
status: "green",
|
||||
detail: `Driver OOS: ${driverOos.toFixed(1)}% (national avg: ${driverNat}%). `
|
||||
+ `Vehicle OOS: ${vehicleOos.toFixed(1)}% (national avg: ${vehicleNat}%). Within normal range.`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ── Check 7: Crash History ──
|
||||
if (carrier) {
|
||||
const crashes = carrier.crashTotal || 0;
|
||||
const fatal = carrier.fatalCrash || 0;
|
||||
const injury = carrier.injCrash || 0;
|
||||
if (fatal > 0) {
|
||||
checks.push({
|
||||
id: "crashes",
|
||||
label: "Crash Record",
|
||||
status: "red",
|
||||
detail: `${crashes} total crashes on record, including ${fatal} fatal and ${injury} injury crashes.`,
|
||||
});
|
||||
} else if (crashes > 0) {
|
||||
checks.push({
|
||||
id: "crashes",
|
||||
label: "Crash Record",
|
||||
status: "yellow",
|
||||
detail: `${crashes} crash(es) on record, ${injury} with injuries. No fatalities.`,
|
||||
});
|
||||
} else {
|
||||
checks.push({
|
||||
id: "crashes",
|
||||
label: "Crash Record",
|
||||
status: "green",
|
||||
detail: "No crashes on record.",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Build response
|
||||
const redCount = checks.filter(c => c.status === "red").length;
|
||||
const yellowCount = checks.filter(c => c.status === "yellow").length;
|
||||
|
||||
res.json({
|
||||
dot_number: rawDot,
|
||||
legal_name: name,
|
||||
dba_name: dba || null,
|
||||
phy_city: carrier?.phyCity || census?.phy_city || null,
|
||||
phy_state: carrier?.phyState || census?.phy_state || null,
|
||||
telephone: census?.telephone || null,
|
||||
email: census?.email_address || null,
|
||||
fleet: {
|
||||
power_units: carrier?.totalPowerUnits ?? census?.nbr_power_unit ?? null,
|
||||
drivers: carrier?.totalDrivers ?? census?.driver_total ?? null,
|
||||
},
|
||||
mcs150_date: census?.mcs150_parsed || null,
|
||||
carrier_type: carrier?.censusTypeId?.censusTypeDesc || census?.carrier_operation || null,
|
||||
for_hire: census?.authorized_for_hire || false,
|
||||
checks,
|
||||
summary: {
|
||||
red: redCount,
|
||||
yellow: yellowCount,
|
||||
green: checks.filter(c => c.status === "green").length,
|
||||
total: checks.length,
|
||||
},
|
||||
checked_at: new Date().toISOString(),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[dot-lookup] Error:", err);
|
||||
res.status(500).json({ error: "DOT lookup failed." });
|
||||
}
|
||||
});
|
||||
|
||||
// ── Name Search ─────────────────────────────────────────────────────
|
||||
|
||||
router.get("/api/v1/dot/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 {
|
||||
const result = await pool.query(
|
||||
`SELECT dot_number, legal_name, dba_name, phy_city, phy_state,
|
||||
nbr_power_unit, authorized_for_hire, mcs150_parsed
|
||||
FROM fmcsa_carriers
|
||||
WHERE legal_name ILIKE $1 OR dba_name ILIKE $1
|
||||
ORDER BY legal_name
|
||||
LIMIT 50`,
|
||||
[`%${name}%`],
|
||||
);
|
||||
|
||||
res.json({
|
||||
results: result.rows,
|
||||
count: result.rows.length,
|
||||
query: name,
|
||||
source: "FMCSA Motor Carrier Census",
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[dot-search] Error:", err);
|
||||
res.status(500).json({ error: "Search failed." });
|
||||
}
|
||||
});
|
||||
|
||||
export default router;
|
||||
Loading…
Add table
Add a link
Reference in a new issue