[object Object]

← back to Ventura Corridor

iter 163: /api/magazine/draft-pick-of-the-night + 'Publish next' rail on today.html (heuristic-scored)

280b67995545dd52f7ee49584335373046c32cf0 · 2026-05-06 19:45:36 -0700 · SteveStudio2

Files touched

Diff

commit 280b67995545dd52f7ee49584335373046c32cf0
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 6 19:45:36 2026 -0700

    iter 163: /api/magazine/draft-pick-of-the-night + 'Publish next' rail on today.html (heuristic-scored)
---
 public/today.html   | 22 ++++++++++++++++++++++
 src/server/index.ts | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 58 insertions(+)

diff --git a/public/today.html b/public/today.html
index 390daa1..e733c4d 100644
--- a/public/today.html
+++ b/public/today.html
@@ -143,6 +143,12 @@
   <span id="gen-queue-strip" style="font-family:var(--mono);font-size:11px;color:var(--ink-mute)">…</span>
 </section>
 
+<section style="padding:14px 32px;border-bottom:1px solid var(--rule);background:rgba(212,182,131,0.05);display:none" id="draft-pick-rail">
+  <span style="font-family:var(--serif);font-style:italic;color:var(--metal-glow);font-size:14px;margin-right:14px">Publish next:</span>
+  <a id="draft-pick-link" href="#" style="font-family:var(--serif);font-style:italic;color:var(--ink);font-size:15px;text-decoration:none">…</a>
+  <span id="draft-pick-meta" style="font-family:var(--mono);font-size:10px;color:var(--ink-mute);margin-left:14px">…</span>
+</section>
+
 <section class="day-hero">
   <div class="date" id="today-date">—</div>
   <h2>Three things, <em>in order</em>.</h2>
@@ -777,6 +783,22 @@ async function loadGenQueue() {
 loadGenQueue();
 setInterval(loadGenQueue, 5 * 60_000);
 
+async function loadDraftPick() {
+  try {
+    const d = await fetch('/api/magazine/draft-pick-of-the-night').then(r => r.json());
+    const rail = document.getElementById('draft-pick-rail');
+    if (!d.pick) { rail.style.display = 'none'; return; }
+    rail.style.display = 'block';
+    const link = document.getElementById('draft-pick-link');
+    link.textContent = d.pick.headline || d.pick.biz_name;
+    link.href = '/magazine/' + d.pick.id;
+    const meta = document.getElementById('draft-pick-meta');
+    meta.textContent = `${d.pick.biz_name} · ${d.pick.editorial_len}c · ${d.pick.category_tag || '—'} · score ${d.pick.score}`;
+  } catch {}
+}
+loadDraftPick();
+setInterval(loadDraftPick, 5 * 60_000);
+
 async function updateFeedbackPill() {
   try {
     const d = await fetch('/api/feedback').then(r => r.json());
diff --git a/src/server/index.ts b/src/server/index.ts
index 288b08a..f4ee494 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1932,6 +1932,42 @@ app.get('/api/magazine/random', async (req, res) => {
   }
 });
 
+// Draft-pick-of-the-night — strongest unpublished draft from the last 24h,
+// scored by editorial length × pull-quote-presence × subhead × ad-signal × random tiebreak.
+// Used to seed the "Publish next" pill on /today.html and the morning email.
+app.get('/api/magazine/draft-pick-of-the-night', async (_req, res) => {
+  try {
+    const r = await query(`
+      SELECT mf.id, mf.headline, mf.subhead, mf.editorial, mf.pull_quote, mf.category_tag,
+             mf.generated_at, length(mf.editorial) AS editorial_len,
+             b.name AS biz_name, b.address AS biz_address, b.city,
+             COALESCE((be.ad_signals->>'paid_ads_count')::int, 0) AS paid_ads_count,
+             (length(mf.editorial) +
+              CASE WHEN mf.pull_quote IS NOT NULL THEN 100 ELSE 0 END +
+              CASE WHEN mf.subhead    IS NOT NULL THEN  50 ELSE 0 END +
+              COALESCE((be.ad_signals->>'paid_ads_count')::int, 0) * 30) AS score
+      FROM magazine_features mf
+      JOIN businesses b ON b.id = mf.business_id
+      LEFT JOIN business_enrichment be ON be.business_id = mf.business_id
+      WHERE mf.status = 'draft'
+        AND mf.generated_at > NOW() - INTERVAL '24 hours'
+        AND length(mf.editorial) > 240
+      ORDER BY score DESC, mf.generated_at DESC
+      LIMIT 1
+    `);
+    if (r.rowCount === 0) return res.json({ pick: null });
+    const f = r.rows[0];
+    res.json({
+      pick: f,
+      permalink: `/magazine/${f.id}`,
+      ad_pack_url: `/magazine/${f.id}/ad-pack.html`,
+      review_url: `/magazine.html?status=draft#${f.id}`,
+    });
+  } catch (e: any) {
+    res.status(500).json({ error: e.message });
+  }
+});
+
 // Quality-flag audit — surfaces features with fixable issues for the curation queue.
 // Each flag is independent (a feature can have multiple). Used by /magazine.html
 // for a "🔧 quality" chip + by the standup script to count cleanup opportunities.

← fcadf64 iter 162: /api/magazine/:id/social-pack.json — programmatic  ·  back to Ventura Corridor  ·  iter 164: morning_feature_email now includes 'Suggested next 48b3342 →