add blur detection + Ollama ID validation + corporate check for all carriers

- Client-side: Laplacian variance blur detection in photo quality check
  (very blurry / somewhat blurry / acceptable / good)
- Server-side: async Ollama vision model validates uploaded image is a
  real government ID (minicpm-v:8b), flags non-ID uploads
- Corporate check: sole proprietors now get yellow 'form an LLC' upsell,
  formal entities get annual report/RA reminder
This commit is contained in:
justin 2026-05-30 19:17:31 -05:00
parent e40f359693
commit e2313bcc5e
3 changed files with 150 additions and 2 deletions

View file

@ -431,7 +431,7 @@ router.get("/api/v1/dot/lookup", async (req, res) => {
}
}
// ── Check 15: Corporate / LLC Compliance ──
// ── Check 15: Corporate / Entity Compliance ──
{
const entityName = (name || "").toUpperCase();
const isLLC = /\bLLC\b|\bL\.L\.C\b/.test(entityName);
@ -439,10 +439,10 @@ router.get("/api/v1/dot/lookup", async (req, res) => {
const isLTD = /\bLTD\b|\bLTD\.\b|\bLIMITED\b/.test(entityName);
const isLP = /\bLP\b|\bL\.P\.\b|\bLLP\b/.test(entityName);
const isFormalEntity = isLLC || isCorp || isLTD || isLP;
const state = carrier?.phyState || census?.phy_state || "";
if (isFormalEntity) {
const entityType = isLLC ? "LLC" : isCorp ? "Corporation" : isLTD ? "Ltd" : "LP/LLP";
const state = carrier?.phyState || census?.phy_state || "";
checks.push({
id: "corporate_compliance",
label: "Corporate / Entity Compliance",
@ -452,6 +452,17 @@ router.get("/api/v1/dot/lookup", async (req, res) => {
+ `administrative dissolution and loss of liability protection. `
+ `Performance West can handle your annual filings and registered agent service.`,
});
} else {
// Sole proprietor / no formal entity — suggest forming one
checks.push({
id: "corporate_compliance",
label: "Business Entity & Liability Protection",
status: "yellow",
detail: `Operating as a sole proprietor in ${state || "your state"} means your personal assets `
+ `are not protected from business liabilities, lawsuits, or accidents. `
+ `Forming an LLC or Corporation separates your personal and business assets. `
+ `Performance West can form your LLC in as little as 24 hours and serve as your registered agent.`,
});
}
}

View file

@ -185,6 +185,9 @@ router.post("/api/v1/id-upload/:token", async (req: Request, res: Response) => {
thumbnailUrl = (await presign(minioPath, "GET", 3600)) || "";
}
// Kick off async Ollama ID validation (non-blocking)
validateIdWithOllama(base64Data, tokenData.order_number as string, token).catch(() => {});
res.json({
success: true,
uploaded: true,
@ -227,4 +230,93 @@ router.get("/api/v1/id-upload/:token/status", async (req: Request, res: Response
}
});
// ── Ollama Vision ID Validation ────────────────────────────────────
const OLLAMA_URL = process.env.OLLAMA_URL || "http://ollama:11434";
const OLLAMA_MODEL = process.env.OLLAMA_VISION_MODEL || "minicpm-v:8b";
async function validateIdWithOllama(
base64Image: string,
orderNumber: string,
token: string,
): Promise<void> {
try {
const prompt = `Analyze this image. Is this a government-issued photo ID (driver's license, passport, state ID, or military ID)?
Reply in this exact JSON format:
{"is_id": true/false, "id_type": "driver_license/passport/state_id/military_id/unknown", "readable": true/false, "issues": ["list of issues if any"], "confidence": 0.0-1.0}
Rules:
- "is_id" = true only if this clearly shows a government photo ID
- "readable" = true only if text on the ID appears legible
- List issues like "blurry", "too dark", "partial/cropped", "glare", "wrong document"
- Be strict: a photo of a credit card, insurance card, or random document is NOT an ID`;
const resp = await fetch(`${OLLAMA_URL}/api/generate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: OLLAMA_MODEL,
prompt,
images: [base64Image.substring(0, 500000)], // Cap at ~375KB decoded
stream: false,
}),
});
if (!resp.ok) {
console.warn("[id-validate] Ollama returned", resp.status);
return;
}
const data = (await resp.json()) as { response?: string };
const raw = data.response || "";
console.log("[id-validate] Ollama response:", raw.substring(0, 300));
// Parse JSON from response
const jsonMatch = raw.match(/\{[\s\S]*?\}/);
if (!jsonMatch) return;
let validation: Record<string, unknown>;
try {
validation = JSON.parse(jsonMatch[0]);
} catch {
return;
}
// Store validation result in the token record
await pool.query(
`UPDATE id_upload_tokens SET minio_paths = jsonb_set(
COALESCE(minio_paths, '{}'::jsonb),
'{validation}',
$1::jsonb
) WHERE token = $2`,
[JSON.stringify(validation), token],
).catch(() => {});
// If it's clearly not an ID, flag it in the order
if (validation.is_id === false) {
await pool.query(
`UPDATE compliance_orders SET intake_data = jsonb_set(
COALESCE(intake_data, '{}'::jsonb),
'{photo_id_validation}',
$1::jsonb
) WHERE order_number = $2`,
[JSON.stringify({
valid: false,
reason: "Uploaded image does not appear to be a government-issued photo ID",
issues: validation.issues || [],
checked_at: new Date().toISOString(),
}), orderNumber],
).catch(() => {});
console.warn(`[id-validate] Order ${orderNumber}: uploaded image is NOT a valid ID`);
} else {
console.log(`[id-validate] Order ${orderNumber}: valid ${validation.id_type}, readable=${validation.readable}`);
}
} catch (err) {
console.warn("[id-validate] Ollama validation failed:", err);
}
}
export default router;