← back to AbramsOS
claims: George-backed backup-doc finder (home-sale/closing paperwork, receipts, breach notices) + /docs endpoint + surfaced in prep panel amber lane
451cbcf19a6976d6720c984f7959b0da62bcc155 · 2026-08-18 16:17:38 -0700 · Steve
Files touched
A lib/claim-docs.jsA public/claim-shots/claim_01kzvyhm9m9h86b92zqg40fbjj.pngA public/claim-shots/demo-kw6.pngM routes/settlements.jsM views/settlements.ejs
Diff
commit 451cbcf19a6976d6720c984f7959b0da62bcc155
Author: Steve <steve@designerwallcoverings.com>
Date: Tue Aug 18 16:17:38 2026 -0700
claims: George-backed backup-doc finder (home-sale/closing paperwork, receipts, breach notices) + /docs endpoint + surfaced in prep panel amber lane
---
lib/claim-docs.js | 54 +++++++++++++++++++++
.../claim_01kzvyhm9m9h86b92zqg40fbjj.png | Bin 0 -> 744042 bytes
public/claim-shots/demo-kw6.png | Bin 0 -> 181930 bytes
routes/settlements.js | 12 +++++
views/settlements.ejs | 20 ++++++--
5 files changed, 83 insertions(+), 3 deletions(-)
diff --git a/lib/claim-docs.js b/lib/claim-docs.js
new file mode 100644
index 0000000..d675647
--- /dev/null
+++ b/lib/claim-docs.js
@@ -0,0 +1,54 @@
+// 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 };
diff --git a/public/claim-shots/claim_01kzvyhm9m9h86b92zqg40fbjj.png b/public/claim-shots/claim_01kzvyhm9m9h86b92zqg40fbjj.png
new file mode 100644
index 0000000..f95795e
Binary files /dev/null and b/public/claim-shots/claim_01kzvyhm9m9h86b92zqg40fbjj.png differ
diff --git a/public/claim-shots/demo-kw6.png b/public/claim-shots/demo-kw6.png
new file mode 100644
index 0000000..bc1328f
Binary files /dev/null and b/public/claim-shots/demo-kw6.png differ
diff --git a/routes/settlements.js b/routes/settlements.js
index a1084e0..a44ff24 100644
--- a/routes/settlements.js
+++ b/routes/settlements.js
@@ -9,6 +9,7 @@ const filler = require('../lib/openclaw-claim-filler');
const { extractAddress } = require('../lib/claims-autopilot');
const { rankClaims } = require('../lib/settlement-score');
const { runFill } = require('../lib/claim-openclaw-run');
+const { findDocs } = require('../lib/claim-docs');
const router = express.Router();
const USER = 'user_steve';
@@ -143,4 +144,15 @@ router.post('/api/settlements/:id/run', async (req, res) => {
} catch (e) { res.status(503).json({ error: e.message }); }
});
+// Find the claim's SUPPORTING DOCS in Steve's email via George (home-sale/closing paperwork,
+// receipts, breach notices). Read-only. Needs George config; no-ops cleanly without it.
+router.get('/api/settlements/:id/docs', async (req, res) => {
+ try {
+ const r = await db.query(`SELECT * FROM settlement_claim WHERE id=$1 AND user_id=$2`, [req.params.id, USER]);
+ if (!r.rows.length) return res.status(404).json({ error: 'not found' });
+ const out = await findDocs(r.rows[0]);
+ res.json(out);
+ } catch (e) { res.status(503).json({ error: e.message }); }
+});
+
module.exports = router;
diff --git a/views/settlements.ejs b/views/settlements.ejs
index 16927f0..dd8eb4f 100644
--- a/views/settlements.ejs
+++ b/views/settlements.ejs
@@ -132,7 +132,8 @@
<p class="claim-meta" id="prep-note"></p>
<div class="lane green"><h4>🟢 We'll fill this — verify & paste</h4><div id="prep-green"></div></div>
<div class="lane amber"><h4>🟡 Needs you (once)</h4>
- <div class="claim-meta">If the form asks for a <strong>Claim ID / PIN</strong> it's on your mailed notice (many claims allow filing without it). Claim-specific facts (property address, dates) and any proof upload are entered by you on the portal.</div>
+ <div class="claim-meta">Claim ID / PIN (on your mailed notice — many claims file without it), claim-specific facts, and any proof upload go in on the portal.</div>
+ <div id="prep-docs" style="margin-top:8px"></div>
</div>
<div class="lane red"><h4>🔴 Locked — by law</h4>
<div class="claim-meta">The "under penalty of perjury I am a class member" attestation + Submit are <strong>yours</strong>. Nothing is submitted for you.</div>
@@ -170,18 +171,31 @@
document.getElementById('prep-open').style.display='none';
document.getElementById('prep-bd').style.display='flex';
}
+ function loadDocs(id){
+ var box=document.getElementById('prep-docs'); box.innerHTML='<span class="claim-meta">🔎 searching your email for backup docs…</span>';
+ fetch('/api/settlements/'+id+'/docs').then(function(r){return r.json();}).then(function(j){
+ if(!j.ok){ box.innerHTML='<span class="claim-meta">📎 doc search needs George configured (GEORGE_BASIC_AUTH).</span>'; return; }
+ var html='';
+ (j.groups||[]).forEach(function(g){ if(!g.count) return;
+ html+='<div style="margin:6px 0"><b style="font-size:12px">📎 '+g.label+' ('+g.count+')</b>';
+ g.docs.forEach(function(d){ html+='<div class="kv"><span class="v" style="font-weight:400">'+ (d.subject||'').replace(/[<>]/g,'') +'</span><a class="k" style="color:#2d5bff" href="https://mail.google.com/mail/u/0/#search/rfc822msgid" target="_blank">'+(d.date||'').slice(0,16)+'</a></div>'; });
+ html+='</div>';
+ });
+ box.innerHTML = html || '<span class="claim-meta">📎 no backup docs found in email for this claim.</span>';
+ }).catch(function(){ box.innerHTML='<span class="claim-meta">📎 doc search error.</span>'; });
+ }
document.querySelectorAll('.go').forEach(function(btn){ btn.onclick=function(){
var id=btn.dataset.id, name=btn.dataset.name||'Claim';
btn.disabled=true; btn.textContent='🤖 auto-filling in Chrome…';
// 1) live openclaw auto-fill (zero-click)
fetch('/api/settlements/'+id+'/run',{method:'POST'}).then(function(r){return r.json();}).then(function(j){
if(j && j.ok){ btn.disabled=false; btn.textContent='✅ Filled — review in Chrome';
- showPrep(name, 'Auto-filled '+((j.filled||[]).length)+' fields in your Chrome ('+(j.title||'')+'). Switch to Chrome, verify, check the attestation & submit. Nothing was submitted for you.', null, j.screenshot); return; }
+ showPrep(name, 'Auto-filled '+((j.filled||[]).length)+' fields in your Chrome ('+(j.title||'')+'). Switch to Chrome, verify, check the attestation & submit. Nothing was submitted for you.', null, j.screenshot); loadDocs(id); return; }
// 2) fallback: openclaw not up -> paste card
fetch('/api/settlements/'+id+'/claim-and-go',{method:'POST'}).then(function(r){return r.json();}).then(function(k){
btn.disabled=false; btn.textContent='✅ Claim it & Go';
if(k.error){ alert(k.error); return; }
- showPrep(name, k.note||'', k.fields, null);
+ showPrep(name, k.note||'', k.fields, null); loadDocs(id);
var op=document.getElementById('prep-open'); if(k.mode_url){ op.href=k.mode_url; op.style.display=''; window.open(k.mode_url,'_blank','noopener'); }
});
}).catch(function(){ btn.disabled=false; btn.textContent='✅ Claim it & Go'; alert('error'); });
← 9676eb8 claim-fill: combined First/Middle/Last name field -> full na
·
back to AbramsOS
·
auto-data-snapshot: 2026-08-18T16:40:41 (2 data files) — pub 00f2109 →