Docs/Skills and code/src/
src/series-viewer/server.js
What the file says about itself
Series-analysis viewer: browse the videos and channels found for each series, open a video to see its title, thumbnail, description, transcript and teardown, and generate a teardown with a button if none exists.
Mentioned on: page 12
// Series-analysis viewer: browse the videos and channels found for each series, open a video to see its
// title, thumbnail, description, transcript and teardown, and generate a teardown with a button if none exists.
// npm run series:viewer (http://localhost:3100, override with SERIES_VIEWER_PORT)
import express from "express";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { openStore } from "../series-analysis/store.js";
import { findTranscriptFile, parseTranscriptFile, readTeardown, jobFor, startTeardown } from "./teardown-job.js";
const here = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(here, "..", "..");
const PORT = Number(process.env.SERIES_VIEWER_PORT ?? 3100);
const db = openStore();
const app = express();
const specs = () => {
const dir = path.join(ROOT, "series");
if (!fs.existsSync(dir)) return [];
return fs.readdirSync(dir).filter(f => f.endsWith(".json")).map(f => {
try { return JSON.parse(fs.readFileSync(path.join(dir, f), "utf8")); } catch { return null; }
}).filter(Boolean);
};
const latestRun = series => db.prepare("SELECT run_id FROM series_rankings WHERE series_id=? ORDER BY run_id DESC LIMIT 1").get(series)?.run_id ?? null;
/** Video ids that already have a teardown on disk (batch run or viewer-generated). */
function teardownIds(series) {
const base = path.join(ROOT, "transcripts", "series-analysis", series, "teardown");
const ids = new Set();
try {
const manifest = JSON.parse(fs.readFileSync(path.join(base, "manifest.json"), "utf8"));
for (const c of manifest.cases) if (fs.existsSync(path.join(base, "analyses", `${c.cid}.json`))) ids.add(c.video_id);
} catch { /* no batch run */ }
const single = path.join(base, "single");
if (fs.existsSync(single)) for (const id of fs.readdirSync(single)) if (fs.existsSync(path.join(single, id, "analysis.json")) && jobFor(series, id)?.status !== "running") ids.add(id);
return ids;
}
function transcriptIds(series) {
const ids = new Set(db.prepare("SELECT video_id FROM series_transcripts WHERE series_id=? AND status='ok'").all(series).map(r => r.video_id));
const base = path.join(ROOT, "transcripts", "series-analysis", series);
for (const group of ["cohort-new", "cohort-established", "viewer"]) {
const dir = path.join(base, group);
if (!fs.existsSync(dir)) continue;
for (const f of fs.readdirSync(dir)) { const m = f.match(/([\w-]{11})\.txt$/); if (m) ids.add(m[1]); }
}
return ids;
}
app.get("/api/series", (_req, res) => {
const out = specs().map(s => {
const c = db.prepare("SELECT COUNT(*) videos, COUNT(DISTINCT COALESCE(channel_id, channel_url, channel_name)) channels FROM series_videos WHERE series_id=?").get(s.id);
return { id: s.id, name: s.name, premise: s.premise, ecosystem: s.ecosystem, videos: c.videos, channels: c.channels, teardowns: teardownIds(s.id).size, transcripts: transcriptIds(s.id).size };
});
res.json(out);
});
app.get("/api/series/:s/videos", (req, res) => {
const S = req.params.s;
const run = latestRun(S);
const rows = db.prepare(`
SELECT v.video_id, v.title, v.channel_name, COALESCE(v.channel_id, v.channel_url, v.channel_name) AS channel_key,
COALESCE(v.views, v.search_views) AS views, v.likes, v.published_at, v.search_published_text,
COALESCE(v.duration_seconds, v.search_duration_seconds) AS duration_seconds, v.info_status,
r.rank, r.selected, r.tier, r.learn_score, r.fit, r.performance, r.learnability, r.excluded, r.detail_json,
f.fit AS llm_fit, f.subject AS llm_subject
FROM series_videos v
LEFT JOIN series_rankings r ON r.series_id=v.series_id AND r.video_id=v.video_id AND r.run_id=?
LEFT JOIN series_llm_fit f ON f.series_id=v.series_id AND f.video_id=v.video_id
WHERE v.series_id=?`).all(run, S);
const td = teardownIds(S), tr = transcriptIds(S);
res.json(rows.map(r => {
const d = r.detail_json ? JSON.parse(r.detail_json) : {};
const { detail_json, ...rest } = r;
return { ...rest, subject: d.subject ?? r.llm_subject ?? null, outlier_ratio: d.outlier_ratio ?? null, subscribers: d.subscribers ?? null, views_per_day: d.views_per_day ?? null,
age_days: d.age_days ?? null, fit_heuristic: d.fit ?? null, has_teardown: td.has(r.video_id), has_transcript: tr.has(r.video_id), job: jobFor(S, r.video_id)?.status ?? null };
}));
});
app.get("/api/series/:s/channels", (req, res) => {
const S = req.params.s;
const rows = db.prepare(`
SELECT COALESCE(v.channel_id, v.channel_url, v.channel_name) AS channel_key, MAX(v.channel_name) AS name, MAX(v.channel_url) AS channel_url,
COUNT(*) AS videos, MAX(COALESCE(v.views, v.search_views)) AS best_views, SUM(r.selected) AS selected
FROM series_videos v
LEFT JOIN series_rankings r ON r.series_id=v.series_id AND r.video_id=v.video_id AND r.run_id=?
WHERE v.series_id=? GROUP BY channel_key`).all(latestRun(S), S);
const info = new Map(db.prepare("SELECT * FROM series_channels WHERE series_id=?").all(S).map(c => [c.channel_key, c]));
const td = teardownIds(S);
const tdByChannel = new Map();
for (const v of db.prepare("SELECT video_id, COALESCE(channel_id, channel_url, channel_name) AS k FROM series_videos WHERE series_id=?").all(S))
if (td.has(v.video_id)) tdByChannel.set(v.k, (tdByChannel.get(v.k) ?? 0) + 1);
res.json(rows.map(r => {
const c = info.get(r.channel_key) ?? {};
return { ...r, name: c.name ?? r.name, handle: c.handle ?? null, description: c.description ?? null, country: c.country ?? null, subscribers: c.subscriber_count ?? null,
total_videos: c.video_count ?? null, total_views: c.total_view_count ?? null, joined: c.joined_date ?? null, verified: c.is_verified ?? null,
baseline_median_views: c.baseline_median_views ?? null, baseline_p75_views: c.baseline_p75_views ?? null, enriched: c.status === "ok", teardowns: tdByChannel.get(r.channel_key) ?? 0 };
}));
});
app.get("/api/series/:s/videos/:id", (req, res) => {
const { s: S, id } = req.params;
const v = db.prepare("SELECT * FROM series_videos WHERE series_id=? AND video_id=?").get(S, id);
if (!v) return res.status(404).json({ error: "Unknown video" });
const run = latestRun(S);
const rk = run && db.prepare("SELECT * FROM series_rankings WHERE series_id=? AND run_id=? AND video_id=?").get(S, run, id);
const detail = rk?.detail_json ? JSON.parse(rk.detail_json) : null;
const channel = db.prepare("SELECT * FROM series_channels WHERE series_id=? AND channel_key=?").get(S, v.channel_id ?? v.channel_url ?? v.channel_name);
const queries = db.prepare("SELECT query, position FROM series_hits WHERE series_id=? AND video_id=? ORDER BY position LIMIT 15").all(S, id);
const llm = db.prepare("SELECT fit, subject, packaging, note FROM series_llm_fit WHERE series_id=? AND video_id=?").get(S, id) ?? null;
const tFile = findTranscriptFile(S, id, db);
const transcript = tFile ? parseTranscriptFile(tFile) : null;
const { detail_json, ...ranking } = rk ?? {};
res.json({ video: v, ranking: rk ? { ...ranking, detail } : null, channel: channel ?? null, queries, llm, transcript, teardown: readTeardown(S, id), job: jobFor(S, id) });
});
app.post("/api/series/:s/videos/:id/teardown", (req, res) => {
const { s: S, id } = req.params;
const v = db.prepare("SELECT * FROM series_videos WHERE series_id=? AND video_id=?").get(S, id);
if (!v) return res.status(404).json({ error: "Unknown video" });
const rk = db.prepare("SELECT detail_json FROM series_rankings WHERE series_id=? AND video_id=? ORDER BY run_id DESC LIMIT 1").get(S, id);
const job = startTeardown(S, id, { ...v, views: v.views ?? v.search_views, detail: rk?.detail_json ? JSON.parse(rk.detail_json) : null });
res.status(202).json(job);
});
app.get("/api/series/:s/videos/:id/teardown", (req, res) => {
const { s: S, id } = req.params;
res.json({ job: jobFor(S, id), teardown: readTeardown(S, id) });
});
app.use(express.static(path.join(here, "public")));
// Every page is served by the same shell; the client reads the path.
app.get(/^\/(s|series)\/.*/, (_req, res) => res.sendFile(path.join(here, "public", "index.html")));
app.listen(PORT, () => console.log(`[series-viewer] http://localhost:${PORT}`));
Shown as it is in the repository at build time (2026-11-03); the home directory appears as <home>. If this page and the file differ, the file is right. Back to the catalogue.