← back to Ventura Corridor
iter 165: /api/magazine/duplicates + nav pill — surfaces qwen3 headline-templating (5 groups affecting 14 features today)
680bcb7fb215540650fa47f3ca6b5cddeafd1b5b · 2026-05-06 19:51:07 -0700 · SteveStudio2
Files touched
M public/today.htmlM src/server/index.ts
Diff
commit 680bcb7fb215540650fa47f3ca6b5cddeafd1b5b
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 6 19:51:07 2026 -0700
iter 165: /api/magazine/duplicates + nav pill — surfaces qwen3 headline-templating (5 groups affecting 14 features today)
---
public/today.html | 20 ++++++++++++++++++++
src/server/index.ts | 30 ++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/public/today.html b/public/today.html
index e733c4d..16cc0f7 100644
--- a/public/today.html
+++ b/public/today.html
@@ -127,6 +127,7 @@
<a href="/coverage.html">▰ coverage</a>
<a href="/magazine.html?quality=1" id="nav-quality" style="color:var(--ink-mute)">🔧 quality</a>
<a href="/health.html">♥ health</a>
+ <a href="/api/magazine/duplicates" id="nav-dupes" target="_blank" style="color:var(--ink-mute)">⊕ dupes</a>
<a href="/crawl-derby.html">🏇 derby</a>
<a href="/today.html" class="active">today</a>
<span class="theme-toggle-host" style="display:inline-flex;align-items:center;margin-left:6px"></span>
@@ -783,6 +784,25 @@ async function loadGenQueue() {
loadGenQueue();
setInterval(loadGenQueue, 5 * 60_000);
+async function loadDupesPill() {
+ try {
+ const d = await fetch('/api/magazine/duplicates').then(r => r.json());
+ const a = document.getElementById('nav-dupes');
+ if (!a) return;
+ if (d.groups > 0) {
+ a.style.color = 'var(--metal-glow)';
+ a.innerHTML = `⊕ dupes <span style="background:var(--metal);color:#0a0a0c;padding:0 6px;margin-left:4px;font-size:8px;letter-spacing:.1em;font-weight:600">${d.groups}</span>`;
+ a.title = `${d.groups} duplicate-headline groups affecting ${d.total_affected} features — qwen3 templating signal`;
+ } else {
+ a.style.color = 'var(--green)';
+ a.textContent = '⊕ dupes ✓';
+ a.title = 'every headline is unique';
+ }
+ } catch {}
+}
+loadDupesPill();
+setInterval(loadDupesPill, 5 * 60_000);
+
async function loadDraftPick() {
try {
const d = await fetch('/api/magazine/draft-pick-of-the-night').then(r => r.json());
diff --git a/src/server/index.ts b/src/server/index.ts
index f4ee494..f458566 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1932,6 +1932,36 @@ app.get('/api/magazine/random', async (req, res) => {
}
});
+// Duplicate-headline detector — when qwen3 converges to the same headline pattern
+// (e.g. "Precision Care in Encino's Heart" 3×), the editorial assistant is
+// templating. Surfacing duplicates lets Steve regen the runner-ups for variety.
+app.get('/api/magazine/duplicates', async (_req, res) => {
+ try {
+ const r = await query(`
+ SELECT mf.headline,
+ count(*) AS dup_count,
+ array_agg(json_build_object(
+ 'id', mf.id,
+ 'biz_name', b.name,
+ 'category_tag', mf.category_tag,
+ 'status', mf.status,
+ 'editorial_len', length(mf.editorial),
+ 'generated_at', mf.generated_at
+ ) ORDER BY mf.generated_at DESC) AS features
+ FROM magazine_features mf
+ JOIN businesses b ON b.id = mf.business_id
+ WHERE mf.headline IS NOT NULL
+ GROUP BY mf.headline
+ HAVING count(*) > 1
+ ORDER BY count(*) DESC, mf.headline ASC
+ `);
+ const totalAffected = r.rows.reduce((acc: number, row: any) => acc + Number(row.dup_count), 0);
+ res.json({ groups: r.rowCount, total_affected: totalAffected, rows: r.rows });
+ } catch (e: any) {
+ res.status(500).json({ error: e.message });
+ }
+});
+
// 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.
← 48b3342 iter 164: morning_feature_email now includes 'Suggested next
·
back to Ventura Corridor
·
iter 166: /duplicates.html admin viewer — keeps oldest, clic 1efb250 →