[object Object]

← 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

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 →