← back to AbramsOS
lib/claim-docs.js
55 lines
// lib/claim-docs.js — find the SUPPORTING DOCUMENTS a claim needs, in Steve's email, via George.
// The claim auto-fill covers identity+payment; the "🟡 needs you" lane often wants a PROOF upload
// (home-sale/closing paperwork for the real-estate antitrust claim, receipts for product claims,
// the breach notice / Claim ID for data-breach claims). This searches Gmail (through the George
// bridge) for those e-docs so they're one click from the claim. READ-ONLY — never sends, never files.
const GEORGE_URL = (process.env.GEORGE_URL || '').replace(/\/$/, '');
const GEORGE_BASIC_AUTH = process.env.GEORGE_BASIC_AUTH || '';
// which mailboxes to search for personal claim docs
const ACCOUNTS = (process.env.CLAIM_DOC_ACCOUNTS || 'info,steve-personal,steve-office').split(',').map((s) => s.trim());
// Map a claim to the doc-hunt query by its type. Each is a Gmail search string.
function queriesFor(claim) {
const name = (claim.name || '').toLowerCase();
const cat = (claim.category || '').toLowerCase();
const isRealEstate = /home ?buyer|real ?estate|keller williams|re\/?max|realtor|nar|commission|broker/.test(name + cat);
const isBreach = /data breach|breach|privacy|incident|health|imaging|medical/.test(name + cat);
const isProduct = /product|inhaler|magnesium|nutricost|pump|spa|paper towel/.test(name + cat);
const out = [];
if (isRealEstate) out.push({ label: 'home purchase / closing docs', q: 'has:attachment (closing statement OR "settlement statement" OR HUD-1 OR "ALTA settlement" OR escrow OR "purchase agreement" OR "final closing" OR deed OR "buyer\'s statement" OR "commission")' });
if (isBreach) out.push({ label: 'breach notice / Claim ID', q: '("notice of data breach" OR "data security incident" OR "class member" OR "claim number" OR "claimant id" OR "settlement notice") ' + (claim.name ? '"' + claim.name.split(' ')[0] + '"' : '') });
if (isProduct) out.push({ label: 'receipt / proof of purchase', q: 'has:attachment (receipt OR invoice OR "order confirmation" OR "proof of purchase")' });
// generic fallback: the settlement's own mailed notice
out.push({ label: 'settlement mailed notice', q: '("' + (claim.name || 'settlement').replace(/["]/g, '') + '" OR settlement OR "class action") (notice OR claim OR "ID" OR postcard)' });
return out;
}
async function george(account, q, max) {
const url = `${GEORGE_URL}/api/messages?account=${encodeURIComponent(account)}&maxResults=${max || 8}&q=${encodeURIComponent(q)}`;
const res = await fetch(url, { headers: { Authorization: 'Basic ' + GEORGE_BASIC_AUTH } });
if (!res.ok) throw new Error('George ' + res.status);
const j = await res.json();
return j.messages || j || [];
}
async function findDocs(claim) {
if (!GEORGE_URL || !GEORGE_BASIC_AUTH) return { ok: false, skipped: 'george-not-configured', groups: [] };
const groups = [];
for (const { label, q } of queriesFor(claim)) {
const hits = [];
for (const acct of ACCOUNTS) {
let msgs = [];
try { msgs = await george(acct, q, 6); } catch (e) { continue; }
for (const m of msgs) hits.push({ account: acct, id: m.id, subject: m.subject, from: m.from, date: m.date, snippet: (m.snippet || '').slice(0, 90) });
}
// newest first, dedup by id
const seen = new Set();
const uniq = hits.filter((h) => (seen.has(h.id) ? false : (seen.add(h.id), true))).sort((a, b) => (b.date || '').localeCompare(a.date || '')).slice(0, 6);
groups.push({ label, query: q, count: uniq.length, docs: uniq });
}
return { ok: true, groups };
}
module.exports = { findDocs, queriesFor };