Audit: ignore history already refunded and campaigns older than the hourly log; login daily fee capped at remaining budget

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
martbost
2026-09-15 05:45:57 -05:00
parent 84a9063bbe
commit e03dbbffcf
3 changed files with 10 additions and 4 deletions
+9 -3
View File
@@ -3,13 +3,18 @@
// checked against actual shows. Runs on demand from Admin > Reports and once a day; any issue alerts the admin.
// DB mode only (production); JSON mode reports "not checked".
const db = require('./db');
let R = null; // { notify(text) }
const fs = require('fs'); const path = require('path');
let R = null; // { notify(text), dataDir }
function init(refs) { R = refs; }
// credits already returned for past counting errors: subtract them so history that was made right does not keep flagging
function refunded() { const out = {}; try { for (const f of fs.readdirSync(R.dataDir)) if (/^refunds-.*\.json$/.test(f)) for (const i of (JSON.parse(fs.readFileSync(path.join(R.dataDir, f), 'utf8')).items || [])) out[i.id] = (out[i.id] || 0) + Number(i.credits || 0); } catch (e) {} return out; }
async function run() {
const out = { checkedAt: Date.now(), checks: [] };
if (!db.enabled()) { out.checks.push({ name: 'Counters', ok: true, detail: 'JSON mode: not checked', issues: [] }); return out; }
const camps = await db.q("SELECT id, name, type, status, imps, clicks, spent, accrued, budget, created, house FROM campaigns WHERE house=0 OR house IS NULL");
const back = refunded(); for (const c of camps) if (back[c.id]) c.spent = Math.max(0, Number(c.spent) - back[c.id]); // already made right
const logStart = (await db.q('SELECT MIN(day) d FROM camp_hours'))[0].d; const logStartMs = logStart ? Date.parse(logStart + 'T00:00:00Z') : 0;
const byType = t => camps.filter(c => c.type === t);
const add = (name, issues, detail) => out.checks.push({ name, ok: !issues.length, detail, issues });
// 1. per-view formats: campaign.imps must equal the rows in the log that paid for them
@@ -23,8 +28,9 @@ async function run() {
{
const rows = await db.q('SELECT campaign_id, SUM(n) n FROM camp_hours GROUP BY campaign_id'); const h = {}; for (const r of rows) h[r.campaign_id] = Number(r.n);
const issues = [];
for (const c of camps.filter(c => ['banner', 'text'].includes(c.type))) { const l = h[c.id] || 0; if (Math.abs(Number(c.imps) - l) > 2) issues.push('#' + c.id + ' ' + c.name.slice(0, 30) + ': ' + c.imps + ' views vs ' + l + ' in the hourly log'); }
add('banner/text views vs hourly log', issues, byType('banner').length + byType('text').length + ' campaigns compared');
// only campaigns that started after the hourly log did
for (const c of camps.filter(c => ['banner', 'text'].includes(c.type) && Number(c.created) >= logStartMs)) { const l = h[c.id] || 0; if (Math.abs(Number(c.imps) - l) > 2) issues.push('#' + c.id + ' ' + c.name.slice(0, 30) + ': ' + c.imps + ' views vs ' + l + ' in the hourly log'); }
add('banner/text views vs hourly log', issues, camps.filter(c => ['banner', 'text'].includes(c.type) && Number(c.created) >= logStartMs).length + ' campaigns compared (started since the hourly log began ' + (logStart || '') + ')');
}
// 3. login: days charged vs days shown (a day may only be charged after the ad was shown)
{