← 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); });