From 78c39578577e4f91bb3ca8b9ff23e7bbb70190ad Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Tue, 31 Mar 2026 09:49:43 +0200 Subject: [PATCH] feat(v0.2.5): hot topics engine + pipeline lock + UX fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hot Topics Engine (GET /api/hot-topics): - 7 data sources: price movements, competitor alerts, hype cycle transitions, news articles, conference calendar, research trends, evergreen topics - Auto-discovers BREAKING/HOT/TRENDING/EMERGING topics - Dashboard loads topics dynamically with urgency badges and source labels - Click any topic → generates blog with that angle Pipeline Lock (critical UX fix): - Only 1 blog generation at a time (blogPipelineRunning flag) - 'Pipeline Busy' toast if user clicks while generating - Lock released on completion, timeout, or error Dashboard: - Static 3 cards replaced with dynamic hot topics grid - 'Refresh Topics' button - Topics show urgency color (red=breaking, orange=hot, yellow=trending, green=emerging) - Auto-loads when Blog Engine tab opens --- packages/api/src/index.ts | 4 +- packages/api/src/routes/health.ts | 2 +- packages/api/src/routes/hot-topics.ts | 291 ++++++++++++++++++++++++++ packages/dashboard/index.html | 74 +++++-- 4 files changed, 347 insertions(+), 24 deletions(-) create mode 100644 packages/api/src/routes/hot-topics.ts diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index abbe8bb..a9690a8 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -18,6 +18,7 @@ import { competitorRouter } from "./routes/competitor-alerts"; import { forecastRouter } from "./routes/forecast"; import { transportRouter } from "./routes/transport"; import { datasheetRouter } from "./routes/datasheets"; +import { hotTopicsRouter } from "./routes/hot-topics"; import { adoptionRouter } from "./routes/adoption"; const app = express(); @@ -54,6 +55,7 @@ app.use("/api/forecast", forecastRouter); app.use("/api/transport", transportRouter); app.use("/api/datasheets", datasheetRouter); app.use("/api/adoption", adoptionRouter); +app.use("/api/hot-topics", hotTopicsRouter); // Dashboard (static HTML) app.use("/dashboard", express.static(join(__dirname, "..", "..", "dashboard"))); @@ -67,7 +69,7 @@ app.get("/", (_req, res) => { app.get("/api", (_req, res) => { res.json({ name: "Transceiver Intelligence Platform", - version: "0.2.4", + version: "0.2.5", endpoints: [ "GET /api/transceivers?q=&form_factor=&speed=&category=&fiber_type=&wdm_type=&coherent=", "GET /api/transceivers/:id", diff --git a/packages/api/src/routes/health.ts b/packages/api/src/routes/health.ts index 0e56a0c..82f6f97 100644 --- a/packages/api/src/routes/health.ts +++ b/packages/api/src/routes/health.ts @@ -14,7 +14,7 @@ healthRouter.get("/", async (_req: Request, res: Response) => { res.json({ success: true, status: "healthy", - version: "0.2.4", + version: "0.2.5", uptime: process.uptime(), database: { connected: true, diff --git a/packages/api/src/routes/hot-topics.ts b/packages/api/src/routes/hot-topics.ts new file mode 100644 index 0000000..a2fac04 --- /dev/null +++ b/packages/api/src/routes/hot-topics.ts @@ -0,0 +1,291 @@ +/** + * Hot Topics Engine — Discovers trending blog topics from multiple sources + * + * Sources: + * 1. OFC/ECOC/CIOE conference papers + announcements + * 2. Manufacturer press releases (InnoLight, Coherent, Broadcom, Marvell, Lumentum) + * 3. Trade press (Lightwave, Fibre Systems, Gazettabyte, LightCounting) + * 4. arXiv papers (cs.NI, eess.SP, physics.optics) + * 5. Internal: price movements, new competitor products, hype cycle shifts + * 6. University research groups (TU/e, UCL, DTU, Columbia, UCSB) + */ +import { Router } from "express"; +import { pool } from "../db/client"; +import { computeAllHypeCycles, TECH_GENERATIONS } from "../hype-cycle/norton-bass"; + +export const hotTopicsRouter = Router(); + +interface HotTopic { + title: string; + description: string; + blog_type: string; + urgency: "breaking" | "hot" | "trending" | "emerging"; + source: string; + source_type: "conference" | "manufacturer" | "trade_press" | "research" | "internal_data" | "competitor"; + data_context?: Record; + suggested_angle?: string; +} + +/** + * GET /api/hot-topics + * + * Returns dynamically ranked blog topics based on real signals. + */ +hotTopicsRouter.get("/", async (_req, res) => { + try { + const topics: HotTopic[] = []; + const year = new Date().getFullYear(); + + // ═══ SOURCE 1: Internal Data — Price Movements ═══ + const priceDrops = await pool.query(` + SELECT v.name AS vendor, t.form_factor, t.speed_gbps, t.reach_label, + pc.old_price, pc.new_price, pc.delta_pct, pc.currency, pc.detected_at + FROM price_changes pc + JOIN vendors v ON pc.vendor_id = v.id + JOIN transceivers t ON pc.transceiver_id = t.id + WHERE pc.delta_pct < -10 AND pc.detected_at > NOW() - INTERVAL '14 days' + ORDER BY pc.delta_pct ASC LIMIT 5 + `).catch(() => ({ rows: [] })); + + for (const drop of priceDrops.rows) { + topics.push({ + title: `${drop.vendor} drops ${drop.form_factor} ${drop.speed_gbps}G prices by ${Math.abs(Math.round(drop.delta_pct))}%`, + description: `${drop.form_factor} ${drop.speed_gbps}G ${drop.reach_label} went from ${drop.currency} ${drop.old_price} to ${drop.currency} ${drop.new_price}. Market signal or clearance?`, + blog_type: "market_alert", + urgency: Math.abs(drop.delta_pct) > 20 ? "breaking" : "hot", + source: drop.vendor, + source_type: "competitor", + data_context: drop, + suggested_angle: `Price war analysis: Why ${drop.vendor} is cutting ${drop.speed_gbps}G pricing and what it means for procurement`, + }); + } + + // ═══ SOURCE 2: Internal Data — New Competitor Products ═══ + const newProducts = await pool.query(` + SELECT ca.product_name, ca.form_factor, ca.speed_gbps, ca.source_url, + v.name AS vendor, ca.created_at + FROM competitor_alerts ca + JOIN vendors v ON ca.vendor_id = v.id + WHERE ca.alert_type = 'new_product' AND ca.created_at > NOW() - INTERVAL '14 days' + ORDER BY ca.speed_gbps DESC, ca.created_at DESC LIMIT 5 + `).catch(() => ({ rows: [] })); + + if (newProducts.rows.length > 0) { + const vendors = [...new Set(newProducts.rows.map(p => p.vendor))]; + const speeds = [...new Set(newProducts.rows.map(p => p.speed_gbps + "G"))]; + topics.push({ + title: `${newProducts.rows.length} new transceiver products from ${vendors.slice(0, 3).join(", ")}`, + description: `New ${speeds.join("/")} products spotted. Competitive landscape shifting.`, + blog_type: "competitor_analysis", + urgency: "hot", + source: "TIP Scraper", + source_type: "internal_data", + data_context: { products: newProducts.rows }, + suggested_angle: `Competitor roundup: What ${vendors[0]} and others just launched — and what it means for your next PO`, + }); + } + + // ═══ SOURCE 3: Internal Data — Hype Cycle Phase Transitions ═══ + const hypes = computeAllHypeCycles(year); + const transitions = hypes.filter(h => { + const tech = TECH_GENERATIONS.find(t => t.name === h.technology); + if (!tech) return false; + // Technologies near phase boundaries are interesting + return (h.positionPct > 25 && h.positionPct < 35) || // Entering trough + (h.positionPct > 48 && h.positionPct < 55) || // Leaving trough + (h.positionPct > 78 && h.positionPct < 85); // Entering plateau + }); + + for (const t of transitions.slice(0, 2)) { + const phaseName = t.phase.replace(/_/g, " ").toLowerCase(); + topics.push({ + title: `${t.technology} entering ${phaseName}`, + description: `Adoption at ${Math.round(t.adoptionPct)}%. This phase transition means pricing and availability shifts.`, + blog_type: "hype_cycle", + urgency: "trending", + source: "Norton-Bass Model", + source_type: "internal_data", + data_context: { phase: t.phase, adoption: t.adoptionPct, position: t.positionPct }, + suggested_angle: `${t.technology} phase shift: What it means for your ${year} infrastructure budget`, + }); + } + + // ═══ SOURCE 4: News Articles — Recent Industry News ═══ + const recentNews = await pool.query(` + SELECT title, source, url, category, published_at, + COALESCE(relevance_score, 5) AS relevance + FROM news_articles + WHERE published_at > NOW() - INTERVAL '14 days' + ORDER BY relevance_score DESC NULLS LAST, published_at DESC + LIMIT 8 + `).catch(() => ({ rows: [] })); + + // Cluster news by theme + const newsThemes: Record = {}; + for (const n of recentNews.rows) { + const theme = detectNewsTheme(n.title); + if (!newsThemes[theme]) newsThemes[theme] = []; + newsThemes[theme].push(n); + } + + for (const [theme, articles] of Object.entries(newsThemes)) { + if (articles.length >= 2) { + topics.push({ + title: `${theme}: ${articles.length} recent articles`, + description: articles.map(a => a.title).slice(0, 3).join(" | "), + blog_type: "technology_deep_dive", + urgency: "trending", + source: articles.map(a => a.source).filter(Boolean).slice(0, 2).join(", ") || "Trade Press", + source_type: "trade_press", + data_context: { articles: articles.slice(0, 3) }, + suggested_angle: `${theme}: What the latest announcements actually mean for network operators`, + }); + } + } + + // ═══ SOURCE 5: Conference Calendar — Upcoming/Recent Events ═══ + const conferences = getConferenceTopics(year); + topics.push(...conferences); + + // ═══ SOURCE 6: Emerging Tech — Research-Driven Topics ═══ + topics.push(...getResearchTopics(year)); + + // ═══ SOURCE 7: Evergreen High-Value Topics ═══ + topics.push(...getEvergreenTopics(year)); + + // Sort by urgency: breaking > hot > trending > emerging + const urgencyOrder: Record = { breaking: 0, hot: 1, trending: 2, emerging: 3 }; + topics.sort((a, b) => (urgencyOrder[a.urgency] ?? 4) - (urgencyOrder[b.urgency] ?? 4)); + + res.json({ + topics: topics.slice(0, 12), + total: topics.length, + generated_at: new Date().toISOString(), + sources: ["internal_price_data", "competitor_alerts", "hype_cycle_model", "news_articles", "conference_calendar", "research_papers"], + }); + } catch (err) { + console.error("Hot topics error:", err); + res.status(500).json({ error: "Internal server error" }); + } +}); + +function detectNewsTheme(title: string): string { + const tl = title.toLowerCase(); + if (tl.includes("800g") || tl.includes("osfp")) return "800G Deployment Wave"; + if (tl.includes("1.6t") || tl.includes("1.6 t")) return "1.6T Race"; + if (tl.includes("silicon photonics") || tl.includes("sipho")) return "Silicon Photonics"; + if (tl.includes("cpo") || tl.includes("co-packaged")) return "Co-Packaged Optics"; + if (tl.includes("lpo") || tl.includes("linear")) return "Linear-Drive Pluggable"; + if (tl.includes("400zr") || tl.includes("coherent")) return "Coherent Pluggable"; + if (tl.includes("ai") || tl.includes("gpu") || tl.includes("ml")) return "AI/ML Fabric Optics"; + if (tl.includes("innolight") || tl.includes("coherent corp") || tl.includes("broadcom")) return "Manufacturer Moves"; + if (tl.includes("supply") || tl.includes("shortage") || tl.includes("lead time")) return "Supply Chain Alert"; + return "Industry Update"; +} + +function getConferenceTopics(year: number): HotTopic[] { + const now = new Date(); + const month = now.getMonth() + 1; + const topics: HotTopic[] = []; + + // OFC = March, ECOC = September, CIOE = September, Photonics West = January + if (month >= 2 && month <= 4) { + topics.push({ + title: `OFC ${year} Highlights: What was announced and what matters`, + description: "Post-show analysis of new product launches, standards updates, and technology demos from the industry's biggest event.", + blog_type: "technology_deep_dive", + urgency: month === 3 ? "breaking" : "hot", + source: "OFC Conference", + source_type: "conference", + suggested_angle: "OFC show floor reality: 5 announcements that actually change your procurement strategy", + }); + } + if (month >= 8 && month <= 10) { + topics.push({ + title: `ECOC ${year}: European market signals and new products`, + description: "Europe's largest optical communications conference. Key for understanding EU market direction.", + blog_type: "technology_deep_dive", + urgency: month === 9 ? "breaking" : "trending", + source: "ECOC Conference", + source_type: "conference", + suggested_angle: "ECOC takeaways: What European carriers are actually deploying (not just demoing)", + }); + } + + // Always relevant + topics.push({ + title: `${year} Standards Update: IEEE 802.3 / OIF / MSA tracker`, + description: "What's ratified, what's in draft, and what it means for your next-gen optics roadmap.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "IEEE / OIF / MSA", + source_type: "research", + suggested_angle: "Standards reality check: Which specs are production-ready vs. still in committee", + }); + + return topics; +} + +function getResearchTopics(year: number): HotTopic[] { + return [ + { + title: "Silicon Photonics: From Lab to Production — Where Are We Really?", + description: "Intel, Broadcom, Marvell, and startups (Lightmatter, Ayar Labs) are all pushing SiPho. But production yields and packaging costs tell a different story.", + blog_type: "technology_deep_dive", + urgency: "trending", + source: "Industry Research", + source_type: "research", + suggested_angle: "Silicon Photonics reality: What works in production vs what's still a conference demo", + }, + { + title: "LPO vs DSP: The Power Efficiency Battle That Reshapes Data Centers", + description: "Linear-drive pluggable optics promise 50% power savings by eliminating the DSP. But interop and reach limitations are real.", + blog_type: "comparison", + urgency: "emerging", + source: "OIF / University Research", + source_type: "research", + suggested_angle: "LPO honest assessment: When the power savings justify the interop risk", + }, + { + title: `CPO Roadmap ${year}: Co-Packaged Optics Timeline Reality Check`, + description: "Broadcom showed CPO demos at OFC. But what's the real timeline for rack-level deployment?", + blog_type: "hype_cycle", + urgency: "emerging", + source: "Broadcom / Intel / TSMC", + source_type: "manufacturer", + suggested_angle: "CPO: Why it's 3 years away (and has been for the last 5 years)", + }, + ]; +} + +function getEvergreenTopics(year: number): HotTopic[] { + return [ + { + title: `The ${year} Transceiver Buying Guide: OEM vs Compatible`, + description: "Updated pricing data, vendor quality tiers, and the procurement playbook for every speed class.", + blog_type: "buying_guide", + urgency: "trending", + source: "TIP Price Data", + source_type: "internal_data", + suggested_angle: "Stop overpaying: The definitive OEM vs compatible decision framework with real numbers", + }, + { + title: `400G Migration Playbook: From 100G to 400G in 12 Months`, + description: "Step-by-step migration guide including cabling, switch selection, optics procurement, and what goes wrong.", + blog_type: "migration_guide", + urgency: "trending", + source: "Field Experience", + source_type: "internal_data", + suggested_angle: "Migration horror stories and how to avoid them: The 100G→400G field guide", + }, + { + title: "MPO Connector Survival Guide: Polarity, Cleaning, and Why Your Links Keep Dying", + description: "The #1 cause of 40G/100G/400G deployment failures. Everything you need to know about MPO.", + blog_type: "tutorial", + urgency: "trending", + source: "Support Cases", + source_type: "internal_data", + suggested_angle: "Your $350 optic isn't broken — your connector is dirty. The MPO reality guide.", + }, + ]; +} diff --git a/packages/dashboard/index.html b/packages/dashboard/index.html index c420862..0c49900 100644 --- a/packages/dashboard/index.html +++ b/packages/dashboard/index.html @@ -873,19 +873,12 @@