From 23bdfc1585b615f21b4f754d3fd9d58a92b0b029 Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Sat, 18 Apr 2026 00:11:08 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20/api/hype-cycle/analysis=20endpoint=20?= =?UTF-8?q?=E2=80=94=20DB-backed=20Bass-fitted=20results=20from=20hype=5Fc?= =?UTF-8?q?ycle=5Fanalysis=20table?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_PENDING.md | 2 ++ packages/api/src/routes/hype-cycle.ts | 32 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/CHANGELOG_PENDING.md b/CHANGELOG_PENDING.md index 9c3397b..d62947f 100644 --- a/CHANGELOG_PENDING.md +++ b/CHANGELOG_PENDING.md @@ -159,3 +159,5 @@ Types: FEAT · FIX · UI · DATA · AI · INFRA {"d":"2026-03-30","t":"DATA","m":"v0.1.0: 5,018 transceivers, 351 vendors seeded from 23 initial scrapers"} {"d":"2026-04-17","t":"DATA","m":"Vendor cleanup: pruned 242 irrelevant OEM/manufacturer vendors with no transceiver or switch data — 348→106 vendors"} {"d":"2026-04-18","t":"FEAT","m":"Mouser Electronics API scraper: OEM reference prices for Juniper/Cisco/Arista PIDs — scheduled daily 03:00, MOUSER_API_KEY env var required"} +{"d":"2026-04-18","t":"FEAT","m":"Hype Cycle Engine: Norton-Bass diffusion model fitted to 6 tech generations (10G/100G/400G-QSFP-DD/800G-OSFP/400G-ZR/1.6T). Bass params via grid search, Gartner phase detection, ASP log-linear projection. Seeded market_metrics + hype_cycle_analysis table. Scheduled daily 04:30. API: GET /api/hype-cycle/analysis"} +{"d":"2026-04-18","t":"DATA","m":"migration 039: hype_cycle_analysis table (Bass p/q/M params, phase, score, projected share 1y/3y, ASP current + decline %). market_metrics CHECK extended with hype_score type"} diff --git a/packages/api/src/routes/hype-cycle.ts b/packages/api/src/routes/hype-cycle.ts index 44e7fe5..567ea84 100644 --- a/packages/api/src/routes/hype-cycle.ts +++ b/packages/api/src/routes/hype-cycle.ts @@ -165,6 +165,38 @@ hypeCycleRouter.get("/regional/:tech", (req: Request, res: Response) => { }); }); +// GET /api/hype-cycle/analysis — Bass-fitted results from DB (hype_cycle_analysis table) +hypeCycleRouter.get("/analysis", async (_req: Request, res: Response) => { + try { + const { pool } = await import("../db/client"); + const { rows } = await pool.query(` + SELECT DISTINCT ON (technology) + technology, computed_at, + bass_p, bass_q, bass_m, + t_peak_year, current_t, + ROUND(current_share * 100, 1) AS share_pct, + ROUND(projected_share_1y * 100, 1) AS share_1y_pct, + ROUND(projected_share_3y * 100, 1) AS share_3y_pct, + hype_phase, hype_score, + phase_since_year, years_to_next_phase, + asp_current_usd, asp_decline_pct_3y, + r_squared, data_points, notes + FROM hype_cycle_analysis + ORDER BY technology, computed_at DESC + `); + + if (rows.length === 0) { + res.json({ success: true, source: "db", data: [], message: "No analysis yet — run compute:hype-cycle job" }); + return; + } + + res.json({ success: true, source: "db", computed_at: rows[0]?.computed_at, data: rows }); + } catch (err) { + console.error("Hype cycle analysis DB error:", err); + res.status(500).json({ success: false, error: "Failed to fetch analysis" }); + } +}); + // GET /api/hype-cycle/:tech — Specific technology detail (must be last!) hypeCycleRouter.get("/:tech", (req: Request, res: Response) => { const techQuery = String(req.params.tech);