← back to AbramsOS
lib/claims-autopilot.js
108 lines
// lib/claims-autopilot.js — hands-off settlement-claim AUTO-FILL.
//
// Steve's directive (2026-08-18): "only do auto fill out ... i will never interject."
// So this removes the two manual bottlenecks that let five-figure claims LAPSE:
// (1) claims sat 'unreviewed' because eligibility was flipped by hand,
// (2) nothing auto-generated the fill brief.
// For every OPEN claim (deadline in the future) this auto-marks it pursued and
// auto-stages a completed fill brief (his identity mapped onto the form).
//
// HARD LEGAL LINE (unchanged): this NEVER submits and NEVER checks the
// penalty-of-perjury "I certify I am a class member" box. The final attest+submit
// is Steve's alone — surfaced early by claims-alert so it never lapses. Auto-filling
// commits nothing; it just has the paperwork ready.
//
// Claims that need NO form ("Automatic" / "No Claim Required") are marked 'automatic'
// (nothing to file — you're paid if eligible), not staged.
const fs = require('fs');
const path = require('path');
const db = require('./db');
const filler = require('./openclaw-claim-filler');
const LOCAL_Q = filler.LOCAL_Q; // AbramsOS/data/claim-fill-queue (dashboard reads this)
const USER = process.env.ABRAMSOS_USER_ID || 'user_steve';
// "No form to fill — you get paid automatically if eligible."
const AUTOMATIC_RE = /\b(automatic|no claim (form )?required|no claim form|payments? (are )?automatic)\b/i;
// Parse a US street address out of the free-text person.notes.
function extractAddress(notes) {
if (!notes) return null;
const m = notes.match(/\d+\s+[\w. ]+,\s*[\w. ]+,\s*[A-Z]{2}\s*\d{5}(-\d{4})?/);
return m ? m[0].trim() : null;
}
async function loadProfile(userId = USER) {
const r = await db.query(
`SELECT full_name, email, phone, notes FROM person WHERE user_id=$1 AND relation='self' LIMIT 1`,
[userId]
).catch(() => ({ rows: [] }));
const p = r.rows[0] || {};
return {
full_name: p.full_name || null,
email: p.email || null,
phone: p.phone || null,
address: extractAddress(p.notes),
};
}
// Stage the fill brief LOCAL-ONLY (dashboard queue) — deliberately NOT into
// ~/.claude/yolo-queue/pending-approval (the alert digest + dashboard are the surface;
// we don't re-clutter the approval queue).
function stageLocalOnly(claim, profile) {
const brief = filler.buildBrief(claim, profile || {});
fs.mkdirSync(LOCAL_Q, { recursive: true });
fs.writeFileSync(path.join(LOCAL_Q, `${claim.id}.json`), JSON.stringify(brief, null, 2));
return brief;
}
// One autopilot pass. Idempotent — safe to run on every scheduler tick.
async function autoProcess(userId = USER) {
const profile = await loadProfile(userId);
const missing = ['full_name', 'email', 'address'].filter((k) => !profile[k]);
const { rows } = await db.query(
`SELECT * FROM settlement_claim
WHERE user_id=$1 AND (deadline IS NULL OR deadline >= current_date)
ORDER BY deadline NULLS LAST`,
[userId]
);
const out = { staged: [], automatic: [], already: [], profile_missing: missing };
for (const c of rows) {
// No-form / automatic settlements: nothing to file.
const noForm = c.no_claim_required === true || AUTOMATIC_RE.test(c.payout_text || '') || AUTOMATIC_RE.test(c.name || '');
if (noForm) {
if (c.eligibility_state !== 'automatic') {
await db.query(`UPDATE settlement_claim SET eligibility_state='automatic', updated_at=now() WHERE id=$1`, [c.id]);
}
out.automatic.push({ name: c.name, payout: c.payout_text, deadline: c.deadline });
continue;
}
// Already prefilled/queued — leave it (don't re-stage).
if (c.fill_state === 'queued' || c.fill_state === 'prefilled_awaiting_submit') {
out.already.push({ name: c.name, payout: c.payout_text, deadline: c.deadline, fill_state: c.fill_state });
continue;
}
// Auto-pursue + auto-stage the fill brief. eligibility_state='eligible' here means
// "auto-pursue / fill the paperwork" — NOT a certification of class membership,
// which Steve makes only at submit time.
await db.query(`UPDATE settlement_claim SET eligibility_state='eligible', updated_at=now() WHERE id=$1`, [c.id]);
c.eligibility_state = 'eligible';
try {
stageLocalOnly(c, profile);
await db.query(`UPDATE settlement_claim SET fill_state='queued', updated_at=now() WHERE id=$1`, [c.id]);
out.staged.push({ name: c.name, payout: c.payout_text, deadline: c.deadline, mode_url: c.mode_url });
} catch (e) {
out.staged.push({ name: c.name, error: e.message });
}
}
return out;
}
module.exports = { autoProcess, loadProfile, extractAddress, USER };