← back to Ventura Corridor
src/jobs/auto_cover.ts
63 lines
// Auto-pick this month's issue cover from top-tapped published features
// when no cover has been chosen manually. Runs nightly at 2:42 AM, just
// after auto-publish at 2:22 — that way the freshly-published top story
// can be promoted to cover.
//
// Heuristic:
// 1. If the current month already has a cover_feature_id, do nothing
// (Steve's manual pick wins).
// 2. Otherwise, pick the published feature that maximizes
// taps × 3 + views, restricted to features published this month.
// 3. Fallback: highest-views feature published this month.
// 4. Fallback: most-recently published feature this month.
//
// Run manually: npm run magazine:auto-cover
import { Pool } from 'pg';
const pool = new Pool({ host: '/tmp', database: 'ventura_corridor' });
async function main() {
const month = new Date().toISOString().slice(0, 7);
const cur = await pool.query(`SELECT cover_feature_id FROM magazine_issues WHERE issue_month = $1`, [month]);
if (cur.rows[0]?.cover_feature_id) {
console.log(`[auto-cover] ${month} already has cover_feature_id=${cur.rows[0].cover_feature_id} — skipping`);
process.exit(0);
}
const pick = await pool.query(`
SELECT mf.id, mf.headline, mf.subhead, COALESCE(mf.taps, 0) AS taps, COALESCE(mf.views, 0) AS views,
b.name AS biz_name
FROM magazine_features mf
JOIN businesses b ON b.id = mf.business_id
WHERE mf.status = 'published'
AND mf.published_at IS NOT NULL
AND length(mf.editorial) > 240
AND to_char(mf.published_at AT TIME ZONE 'America/Los_Angeles', 'YYYY-MM') = $1
ORDER BY (COALESCE(mf.taps, 0) * 3 + COALESCE(mf.views, 0)) DESC,
mf.published_at DESC
LIMIT 1
`, [month]);
if (pick.rowCount === 0) {
console.log(`[auto-cover] no qualifying features published in ${month} — skipping`);
process.exit(0);
}
const f = pick.rows[0];
await pool.query(`
INSERT INTO magazine_issues (issue_month, cover_feature_id, cover_kicker, cover_set_at, notes)
VALUES ($1, $2, $3, NOW(), $4)
ON CONFLICT (issue_month) DO UPDATE
SET cover_feature_id = EXCLUDED.cover_feature_id,
cover_kicker = COALESCE(magazine_issues.cover_kicker, EXCLUDED.cover_kicker),
cover_set_at = NOW(),
notes = COALESCE(magazine_issues.notes, '') || ' [auto:' || to_char(NOW(),'YYYY-MM-DD') || ']'
`, [month, f.id, 'On the cover this month', `auto-pick by taps×3+views, ${f.taps} taps + ${f.views} views`]);
console.log(`[auto-cover] ${month} → feature ${f.id} "${f.headline}" (${f.biz_name}) — ${f.taps} taps, ${f.views} views`);
await pool.end();
}
main().catch(e => { console.error('[auto-cover]', e); process.exit(1); });