346 lines
13 KiB
TypeScript
346 lines
13 KiB
TypeScript
/**
|
|
* 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;
|