/** * Hype Cycle API routes * * GET /api/hype-cycle — All technologies with current phase * GET /api/hype-cycle/enriched — All technologies with data-driven metrics * GET /api/hype-cycle/lifecycle — Revenue lifecycle predictions for all speeds * GET /api/hype-cycle/regional/:tech — Regional adoption model for a technology * GET /api/hype-cycle/:tech — Specific technology with 5-year forecast */ import { Router, Request, Response } from "express"; import { computeAllHypeCycles, computeHypeCycle, findTechnology, TECH_GENERATIONS, SPECIAL_TECHS, } from "../hype-cycle/norton-bass"; import { getDataDrivenOverrides, getSpeedClassMetrics, computeRevenueLifecycle, computeRegionalAdoption, } from "../hype-cycle/data-enrichment"; export const hypeCycleRouter = Router(); const q = (p: string, req: Request): string | undefined => req.query[p] ? String(req.query[p]) : undefined; // GET /api/hype-cycle — All technologies (model-only, fast) hypeCycleRouter.get("/", (_req: Request, res: Response) => { const yearParam = q("year", _req); const year = yearParam ? parseInt(yearParam) : new Date().getFullYear(); const results = computeAllHypeCycles(year); const sorted = [...results].sort((a, b) => a.positionPct - b.positionPct); res.json({ success: true, year, model: "Norton-Bass Multigenerational Diffusion", technologies: sorted.map((r) => ({ technology: r.technology, phase: r.phaseLabel, positionPct: r.positionPct, adoptionPct: r.adoptionPct, compositeScore: r.compositeScore, peakYear: r.forecast.peakShipmentYear, yearsToPlateauFromNow: r.forecast.yearsToPlateauFromNow, })), }); }); // GET /api/hype-cycle/enriched — Data-driven enrichment from scraped data hypeCycleRouter.get("/enriched", async (_req: Request, res: Response) => { try { const yearParam = q("year", _req); const year = yearParam ? parseInt(yearParam) : new Date().getFullYear(); const overridesMap = await getDataDrivenOverrides(); const allTechs = [...TECH_GENERATIONS, ...SPECIAL_TECHS]; const results = allTechs.map((tech) => { const overrides = overridesMap.get(tech.speedGbps); return computeHypeCycle(tech, year, overrides); }); const sorted = [...results].sort((a, b) => a.positionPct - b.positionPct); // Also include raw metrics for transparency const speedMetrics = await getSpeedClassMetrics(); res.json({ success: true, year, model: "Norton-Bass + Data-Driven Enrichment", dataSource: { totalTransceivers: speedMetrics.reduce((s, m) => s + m.skuCount, 0), totalPricePoints: speedMetrics.reduce((s, m) => s + m.priceCount, 0), speedClasses: speedMetrics.length, }, technologies: sorted.map((r) => ({ technology: r.technology, phase: r.phaseLabel, positionPct: r.positionPct, adoptionPct: r.adoptionPct, compositeScore: r.compositeScore, peakYear: r.forecast.peakShipmentYear, yearsToPlateauFromNow: r.forecast.yearsToPlateauFromNow, metrics: r.metrics, fiveYearForecast: r.forecast.fiveYearProjection, })), rawSpeedMetrics: speedMetrics.map((m) => ({ speedGbps: m.speedGbps, vendorCount: m.vendorCount, skuCount: m.skuCount, avgPrice: m.avgPrice ? Math.round(m.avgPrice * 100) / 100 : null, minPrice: m.minPrice ? Math.round(m.minPrice * 100) / 100 : null, maxPrice: m.maxPrice ? Math.round(m.maxPrice * 100) / 100 : null, formFactors: m.formFactors, reachVariants: m.reachVariants, })), }); } catch (err) { console.error("Enriched hype cycle error:", err); res.status(500).json({ success: false, error: "Failed to compute enriched hype cycle" }); } }); // GET /api/hype-cycle/lifecycle — Revenue lifecycle predictions hypeCycleRouter.get("/lifecycle", async (_req: Request, res: Response) => { try { const currentYear = new Date().getFullYear(); const speedMetrics = await getSpeedClassMetrics(); const priceMap = new Map(speedMetrics.map((m) => [m.speedGbps, m.avgPrice])); const allTechs = [...TECH_GENERATIONS, ...SPECIAL_TECHS]; const lifecycles = allTechs.map((tech) => computeRevenueLifecycle( tech.speedGbps, tech.name, tech.introYear, tech.peakYear, currentYear, priceMap.get(tech.speedGbps), ) ); // Sort by revenue index (highest current revenue first) const sorted = [...lifecycles].sort((a, b) => b.revenueIndex - a.revenueIndex); res.json({ success: true, currentYear, lifecycles: sorted, }); } catch (err) { console.error("Lifecycle error:", err); res.status(500).json({ success: false, error: "Failed to compute lifecycles" }); } }); // GET /api/hype-cycle/regional/:tech — Regional adoption by technology hypeCycleRouter.get("/regional/:tech", (req: Request, res: Response) => { const techQuery = req.params.tech; const currentYear = new Date().getFullYear(); const tech = findTechnology(techQuery); if (!tech) { res.status(404).json({ success: false, error: `Technology "${techQuery}" not found. Available: 1G, 10G, 25G, 40G, 100G, 400G, 800G, 1.6T, CPO, LPO, 400ZR`, }); return; } const regions = computeRegionalAdoption(tech.peakYear, currentYear, tech.name); res.json({ success: true, technology: tech.name, speedGbps: tech.speedGbps, globalPeakYear: tech.peakYear, regions, }); }); // GET /api/hype-cycle/:tech — Specific technology detail (must be last!) hypeCycleRouter.get("/:tech", (req: Request, res: Response) => { const techQuery = req.params.tech; const yearParam = q("year", req); const year = yearParam ? parseInt(yearParam) : new Date().getFullYear(); const tech = findTechnology(techQuery); if (!tech) { res.status(404).json({ success: false, error: `Technology "${techQuery}" not found. Available: 1G, 10G, 25G, 40G, 100G, 400G, 800G, 1.6T, CPO, LPO, 400ZR`, }); return; } const result = computeHypeCycle(tech, year); // Add regional data const regions = computeRegionalAdoption(tech.peakYear, year, tech.name); // Add revenue lifecycle const lifecycle = computeRevenueLifecycle( tech.speedGbps, tech.name, tech.introYear, tech.peakYear, year, ); res.json({ success: true, ...result, regionalAdoption: regions, revenueLifecycle: lifecycle, }); });