← back to Ventura Corridor
iter 146: auto-cover job — promotes top-tapped feature to cover when none set; nightly 2:42 AM
a4769a44751d8ac2b87a0e2a460632dd097d6d40 · 2026-05-06 19:12:37 -0700 · SteveStudio2
Files touched
M package.jsonA src/jobs/auto_cover.ts
Diff
commit a4769a44751d8ac2b87a0e2a460632dd097d6d40
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:12:37 2026 -0700
iter 146: auto-cover job — promotes top-tapped feature to cover when none set; nightly 2:42 AM
---
package.json | 3 ++-
src/jobs/auto_cover.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/package.json b/package.json
index ae138d5..43870d0 100644
--- a/package.json
+++ b/package.json
@@ -31,7 +31,8 @@
"magazine:auto-publish": "tsx src/jobs/auto_publish_top.ts",
"magazine:regen-thin": "tsx src/jobs/regen_thin.ts",
"magazine:ig-dryrun": "tsx src/jobs/ig_autopost_dryrun.ts",
- "magazine:standup": "tsx src/jobs/daily_standup_audio.ts"
+ "magazine:standup": "tsx src/jobs/daily_standup_audio.ts",
+ "magazine:auto-cover": "tsx src/jobs/auto_cover.ts"
},
"dependencies": {
"@types/multer": "^2.1.0",
diff --git a/src/jobs/auto_cover.ts b/src/jobs/auto_cover.ts
new file mode 100644
index 0000000..315f6e2
--- /dev/null
+++ b/src/jobs/auto_cover.ts
@@ -0,0 +1,62 @@
+// 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); });
← 3f9a762 iter 145: /issue/archive — back-issues index w/ cover-image
·
back to Ventura Corridor
·
iter 147: /api/magazine.json now accepts ?cat= and ?limit= f af14cb7 →