From eb875f37d297bbf86374e73086df9f98c608164c Mon Sep 17 00:00:00 2001 From: Rene Fichtmueller Date: Fri, 27 Mar 2026 23:35:57 +1300 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=203=20=E2=80=94=20Norton-Bass=20H?= =?UTF-8?q?ype=20Cycle=20Engine?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the full Norton-Bass Multigenerational Diffusion Model for transceiver technology lifecycle forecasting. Math: Bass diffusion F(t) + logistic adoption S(t) = L / (1 + e^(-k(t-t0))) Parameters: p (innovation ~0.03), q (imitation ~0.3-0.5), m (market potential) Phase Classification Engine (composite score): 30% Port shipment share + 20% ASP decline rate + 15% Standards maturity + 15% Interop validation + 10% Vendor trajectory + 10% Media sentiment 11 technologies tracked: 1G → 10G → 25G → 40G → 100G → 400G → 800G → 1.6T + CPO, LPO, 400ZR Coherent 5-year adoption forecast per technology API: GET /api/hype-cycle (all) + GET /api/hype-cycle/:tech (detail) Live: https://transceiver-db.context-x.org/api/hype-cycle --- packages/api/src/hype-cycle/norton-bass.ts | 468 +++++++++++++++++++++ packages/api/src/index.ts | 4 + packages/api/src/routes/hype-cycle.ts | 66 +++ 3 files changed, 538 insertions(+) create mode 100644 packages/api/src/hype-cycle/norton-bass.ts create mode 100644 packages/api/src/routes/hype-cycle.ts diff --git a/packages/api/src/hype-cycle/norton-bass.ts b/packages/api/src/hype-cycle/norton-bass.ts new file mode 100644 index 0000000..35fa45f --- /dev/null +++ b/packages/api/src/hype-cycle/norton-bass.ts @@ -0,0 +1,468 @@ +/** + * Norton-Bass Multigenerational Diffusion Model + * + * Mathematical engine for forecasting transceiver technology adoption. + * Based on Norton & Bass (1987, Management Science). + * + * Key equations: + * Bass: f(t) / [1 - F(t)] = p + q * F(t) + * Logistic: S(t) = L / (1 + e^(-k(t - t0))) + * + * Parameters: + * p = Innovation coefficient (~0.03 for network hardware) + * q = Imitation coefficient (~0.3–0.5) + * m = Total market potential (addressable port shipments) + */ + +/** Technology generation definition */ +export interface TechGeneration { + readonly name: string; // e.g. "100G QSFP28" + readonly speedGbps: number; + readonly formFactor: string; + readonly introYear: number; // Year first shipped commercially + readonly peakYear: number; // Year of peak shipments (estimated) + readonly p: number; // Innovation coefficient + readonly q: number; // Imitation coefficient + readonly m: number; // Market potential (millions of ports) + readonly k: number; // Logistic growth rate + readonly t0: number; // Inflection point year +} + +/** Hype Cycle phases per Gartner methodology */ +export type HypeCyclePhase = + | "INNOVATION_TRIGGER" + | "PEAK_OF_INFLATED_EXPECTATIONS" + | "TROUGH_OF_DISILLUSIONMENT" + | "SLOPE_OF_ENLIGHTENMENT" + | "PLATEAU_OF_PRODUCTIVITY" + | "LEGACY_DECLINE"; + +/** Result of a hype cycle computation */ +export interface HypeCycleResult { + readonly technology: string; + readonly phase: HypeCyclePhase; + readonly phaseLabel: string; + readonly positionPct: number; // 0–100 on the hype cycle curve + readonly adoptionPct: number; // Current market adoption % + readonly compositeScore: number; // 0–100 + readonly forecast: AdoptionForecast; + readonly metrics: PhaseMetrics; +} + +export interface AdoptionForecast { + readonly currentYear: number; + readonly yearsToPlateauFromNow: number; + readonly peakShipmentYear: number; + readonly cumulativeAdoptionPct: number; + readonly yearlyAdoptionPct: number; + readonly fiveYearProjection: ReadonlyArray<{ year: number; adoptionPct: number; phase: HypeCyclePhase }>; +} + +export interface PhaseMetrics { + readonly shipmentShare: number; // 0–1 + readonly aspDeclineRate: number; // % per year + readonly standardsMaturity: number; // 0–100 + readonly interopLevel: number; // 0–100 + readonly vendorCount: number; + readonly vendorTrend: "increasing" | "stable" | "decreasing"; + readonly mediaHypeIndex: number; // 0–100 +} + +// ============================================================ +// Known technology generations (seed data, March 2026) +// ============================================================ +export const TECH_GENERATIONS: ReadonlyArray = [ + { + name: "1G SFP", + speedGbps: 1, + formFactor: "SFP", + introYear: 2001, + peakYear: 2012, + p: 0.03, q: 0.38, m: 500, k: 0.45, t0: 2008, + }, + { + name: "10G SFP+", + speedGbps: 10, + formFactor: "SFP+", + introYear: 2006, + peakYear: 2018, + p: 0.03, q: 0.42, m: 600, k: 0.50, t0: 2014, + }, + { + name: "40G QSFP+", + speedGbps: 40, + formFactor: "QSFP+", + introYear: 2010, + peakYear: 2019, + p: 0.025, q: 0.35, m: 150, k: 0.40, t0: 2016, + }, + { + name: "25G SFP28", + speedGbps: 25, + formFactor: "SFP28", + introYear: 2015, + peakYear: 2022, + p: 0.04, q: 0.45, m: 200, k: 0.55, t0: 2019, + }, + { + name: "100G QSFP28", + speedGbps: 100, + formFactor: "QSFP28", + introYear: 2014, + peakYear: 2024, + p: 0.03, q: 0.40, m: 400, k: 0.48, t0: 2020, + }, + { + name: "400G QSFP-DD", + speedGbps: 400, + formFactor: "QSFP-DD", + introYear: 2020, + peakYear: 2027, + p: 0.035, q: 0.50, m: 300, k: 0.52, t0: 2025, + }, + { + name: "800G OSFP", + speedGbps: 800, + formFactor: "OSFP", + introYear: 2023, + peakYear: 2029, + p: 0.04, q: 0.55, m: 250, k: 0.55, t0: 2027, + }, + { + name: "1.6T OSFP-XD", + speedGbps: 1600, + formFactor: "OSFP-XD", + introYear: 2025, + peakYear: 2032, + p: 0.03, q: 0.45, m: 180, k: 0.48, t0: 2030, + }, +]; + +// Special tech entries (not speed-generational) +export const SPECIAL_TECHS: ReadonlyArray = [ + { + name: "CPO", + speedGbps: 1600, + formFactor: "CPO", + introYear: 2024, + peakYear: 2033, + p: 0.02, q: 0.30, m: 50, k: 0.35, t0: 2031, + }, + { + name: "LPO", + speedGbps: 800, + formFactor: "LPO", + introYear: 2024, + peakYear: 2029, + p: 0.035, q: 0.48, m: 100, k: 0.50, t0: 2027, + }, + { + name: "400ZR Coherent", + speedGbps: 400, + formFactor: "QSFP-DD", + introYear: 2021, + peakYear: 2026, + p: 0.04, q: 0.50, m: 80, k: 0.55, t0: 2024, + }, +]; + +// ============================================================ +// Core Bass Diffusion Functions +// ============================================================ + +/** + * Bass diffusion: cumulative adoption F(t) + * + * F(t) = (1 - e^(-(p+q)*t)) / (1 + (q/p)*e^(-(p+q)*t)) + */ +export function bassCumulativeAdoption( + t: number, + p: number, + q: number, +): number { + const pq = p + q; + const exp = Math.exp(-pq * t); + return (1 - exp) / (1 + (q / p) * exp); +} + +/** + * Bass diffusion: instantaneous adoption rate f(t) + * + * f(t) = ((p+q)^2 / q) * (e^(-(p+q)*t) / (1 + (q/p)*e^(-(p+q)*t))^2) + */ +export function bassAdoptionRate( + t: number, + p: number, + q: number, +): number { + const pq = p + q; + const exp = Math.exp(-pq * t); + const denominator = 1 + (q / p) * exp; + return (pq * pq / q) * (exp / (denominator * denominator)); +} + +/** + * Logistic adoption curve: S(t) = L / (1 + e^(-k*(t - t0))) + */ +export function logisticAdoption( + year: number, + L: number, + k: number, + t0: number, +): number { + return L / (1 + Math.exp(-k * (year - t0))); +} + +// ============================================================ +// Phase Classification Engine +// ============================================================ + +export function classifyPhase(metrics: PhaseMetrics): HypeCyclePhase { + const { shipmentShare, aspDeclineRate, standardsMaturity, vendorTrend, mediaHypeIndex } = metrics; + + if (shipmentShare < 0.01 && standardsMaturity < 30) { + return "INNOVATION_TRIGGER"; + } + + if (shipmentShare < 0.05 && mediaHypeIndex > 70 && vendorTrend === "increasing") { + return "PEAK_OF_INFLATED_EXPECTATIONS"; + } + + if (aspDeclineRate > 30 && vendorTrend === "decreasing" && mediaHypeIndex < 40) { + return "TROUGH_OF_DISILLUSIONMENT"; + } + + if ( + shipmentShare >= 0.05 && shipmentShare <= 0.30 && + aspDeclineRate >= 10 && aspDeclineRate <= 25 && + (vendorTrend === "stable" || vendorTrend === "increasing") + ) { + return "SLOPE_OF_ENLIGHTENMENT"; + } + + if (shipmentShare > 0.30 && aspDeclineRate < 10) { + return "PLATEAU_OF_PRODUCTIVITY"; + } + + // Fallback: use composite score approach + return compositePhaseClassification(metrics); +} + +/** + * Composite score phase classification + * + * Phase_Score = 0.30 * Normalize(PortShipment_share) + * + 0.20 * Normalize(ASP_decline_rate) + * + 0.15 * Normalize(Standards_maturity) + * + 0.15 * Normalize(InteropValidation_level) + * + 0.10 * Normalize(VendorCount_trajectory) + * + 0.10 * Normalize(MediaSentiment_score) + */ +function compositePhaseClassification(metrics: PhaseMetrics): HypeCyclePhase { + const score = + 0.30 * normalize(metrics.shipmentShare, 0, 0.5) + + 0.20 * normalize(metrics.aspDeclineRate, 0, 50) + + 0.15 * normalize(metrics.standardsMaturity, 0, 100) + + 0.15 * normalize(metrics.interopLevel, 0, 100) + + 0.10 * (metrics.vendorTrend === "increasing" ? 0.3 : metrics.vendorTrend === "stable" ? 0.6 : 0.9) + + 0.10 * normalize(100 - metrics.mediaHypeIndex, 0, 100); + + if (score < 0.15) return "INNOVATION_TRIGGER"; + if (score < 0.30) return "PEAK_OF_INFLATED_EXPECTATIONS"; + if (score < 0.45) return "TROUGH_OF_DISILLUSIONMENT"; + if (score < 0.70) return "SLOPE_OF_ENLIGHTENMENT"; + return "PLATEAU_OF_PRODUCTIVITY"; +} + +function normalize(value: number, min: number, max: number): number { + return Math.max(0, Math.min(1, (value - min) / (max - min))); +} + +// ============================================================ +// Phase position on the hype cycle curve (0–100) +// ============================================================ + +const PHASE_POSITIONS: Record = { + INNOVATION_TRIGGER: [0, 15], + PEAK_OF_INFLATED_EXPECTATIONS: [15, 30], + TROUGH_OF_DISILLUSIONMENT: [30, 50], + SLOPE_OF_ENLIGHTENMENT: [50, 80], + PLATEAU_OF_PRODUCTIVITY: [80, 95], + LEGACY_DECLINE: [95, 100], +}; + +const PHASE_LABELS: Record = { + INNOVATION_TRIGGER: "Innovation Trigger", + PEAK_OF_INFLATED_EXPECTATIONS: "Peak of Inflated Expectations", + TROUGH_OF_DISILLUSIONMENT: "Trough of Disillusionment", + SLOPE_OF_ENLIGHTENMENT: "Slope of Enlightenment", + PLATEAU_OF_PRODUCTIVITY: "Plateau of Productivity", + LEGACY_DECLINE: "Legacy / Decline", +}; + +// ============================================================ +// Main computation: compute hype cycle for a technology +// ============================================================ + +export function computeHypeCycle( + tech: TechGeneration, + currentYear: number = new Date().getFullYear(), + overrideMetrics?: Partial, +): HypeCycleResult { + // Time since introduction + const t = currentYear - tech.introYear; + const tNorm = Math.max(0, t); + + // Bass diffusion adoption + const cumulativeAdoption = bassCumulativeAdoption(tNorm, tech.p, tech.q); + const adoptionRate = bassAdoptionRate(tNorm, tech.p, tech.q); + + // Logistic adoption (port shipments) + const logisticShipments = logisticAdoption(currentYear, tech.m, tech.k, tech.t0); + const shipmentShare = logisticShipments / 1000; // Normalize to 0–1 range (1000M total market) + + // Estimate metrics from model + const yearsAfterIntro = currentYear - tech.introYear; + const yearsToPeak = tech.peakYear - tech.introYear; + const progressRatio = yearsAfterIntro / yearsToPeak; + + const metrics: PhaseMetrics = { + shipmentShare: overrideMetrics?.shipmentShare ?? Math.min(0.5, shipmentShare), + aspDeclineRate: overrideMetrics?.aspDeclineRate ?? estimateAspDecline(progressRatio), + standardsMaturity: overrideMetrics?.standardsMaturity ?? estimateStandardsMaturity(progressRatio), + interopLevel: overrideMetrics?.interopLevel ?? estimateInteropLevel(progressRatio), + vendorCount: overrideMetrics?.vendorCount ?? estimateVendorCount(progressRatio), + vendorTrend: overrideMetrics?.vendorTrend ?? estimateVendorTrend(progressRatio), + mediaHypeIndex: overrideMetrics?.mediaHypeIndex ?? estimateMediaHype(progressRatio), + }; + + const phase = classifyPhase(metrics); + + // Check for legacy/decline + const finalPhase = (currentYear > tech.peakYear + 8 && metrics.shipmentShare < 0.05) + ? "LEGACY_DECLINE" + : phase; + + // Position on curve (0-100) + const [phaseMin, phaseMax] = PHASE_POSITIONS[finalPhase]; + const intraPhaseProgress = Math.min(1, cumulativeAdoption); + const positionPct = phaseMin + (phaseMax - phaseMin) * intraPhaseProgress; + + // Composite score + const compositeScore = Math.round( + 0.30 * normalize(metrics.shipmentShare, 0, 0.5) * 100 + + 0.20 * normalize(metrics.aspDeclineRate, 0, 50) * 100 + + 0.15 * normalize(metrics.standardsMaturity, 0, 100) * 100 + + 0.15 * normalize(metrics.interopLevel, 0, 100) * 100 + + 0.10 * (metrics.vendorTrend === "increasing" ? 30 : metrics.vendorTrend === "stable" ? 60 : 90) + + 0.10 * (100 - metrics.mediaHypeIndex) + ); + + // 5-year forecast + const fiveYearProjection = Array.from({ length: 5 }, (_, i) => { + const futureYear = currentYear + i + 1; + const futureT = futureYear - tech.introYear; + const futureAdoption = bassCumulativeAdoption(futureT, tech.p, tech.q); + const futureProgressRatio = (futureYear - tech.introYear) / yearsToPeak; + const futureMetrics: PhaseMetrics = { + ...metrics, + shipmentShare: Math.min(0.5, logisticAdoption(futureYear, tech.m, tech.k, tech.t0) / 1000), + aspDeclineRate: estimateAspDecline(futureProgressRatio), + vendorTrend: estimateVendorTrend(futureProgressRatio), + }; + return { + year: futureYear, + adoptionPct: Math.round(futureAdoption * 100), + phase: (futureYear > tech.peakYear + 8 && futureMetrics.shipmentShare < 0.05) + ? "LEGACY_DECLINE" as HypeCyclePhase + : classifyPhase(futureMetrics), + }; + }); + + return { + technology: tech.name, + phase: finalPhase, + phaseLabel: PHASE_LABELS[finalPhase], + positionPct: Math.round(positionPct), + adoptionPct: Math.round(cumulativeAdoption * 100), + compositeScore, + forecast: { + currentYear, + yearsToPlateauFromNow: Math.max(0, tech.peakYear + 3 - currentYear), + peakShipmentYear: tech.peakYear, + cumulativeAdoptionPct: Math.round(cumulativeAdoption * 100), + yearlyAdoptionPct: Math.round(adoptionRate * 100), + fiveYearProjection, + }, + metrics, + }; +} + +// ============================================================ +// Estimation heuristics (from model parameters) +// ============================================================ + +function estimateAspDecline(progressRatio: number): number { + // ASP decline accelerates through Trough, stabilizes at Plateau + if (progressRatio < 0.3) return 5; // Early: slow decline + if (progressRatio < 0.6) return 35; // Peak/Trough: rapid decline + if (progressRatio < 1.0) return 15; // Slope: moderate decline + return 5; // Plateau: minimal decline +} + +function estimateStandardsMaturity(progressRatio: number): number { + if (progressRatio < 0.2) return 20; // Draft standards + if (progressRatio < 0.5) return 60; // Published, some revisions + if (progressRatio < 0.8) return 85; // Mature standards + return 95; // Fully mature +} + +function estimateInteropLevel(progressRatio: number): number { + if (progressRatio < 0.3) return 25; // Limited interop testing + if (progressRatio < 0.6) return 55; // Growing interop + if (progressRatio < 0.9) return 80; // Broad interop + return 95; // Universal interop +} + +function estimateVendorCount(progressRatio: number): number { + if (progressRatio < 0.3) return 5; + if (progressRatio < 0.6) return 15; + if (progressRatio < 1.0) return 25; + return 20; // Consolidation +} + +function estimateVendorTrend(progressRatio: number): "increasing" | "stable" | "decreasing" { + if (progressRatio < 0.5) return "increasing"; + if (progressRatio < 1.2) return "stable"; + return "decreasing"; +} + +function estimateMediaHype(progressRatio: number): number { + // Hype peaks early (Peak of Inflated Expectations) + if (progressRatio < 0.15) return 40; // Innovation: moderate buzz + if (progressRatio < 0.30) return 85; // Peak: maximum hype + if (progressRatio < 0.50) return 25; // Trough: hype collapse + if (progressRatio < 0.80) return 50; // Slope: balanced coverage + return 30; // Plateau: boring = good +} + +// ============================================================ +// Compute all technologies at once +// ============================================================ + +export function computeAllHypeCycles( + currentYear: number = new Date().getFullYear(), +): ReadonlyArray { + const allTechs = [...TECH_GENERATIONS, ...SPECIAL_TECHS]; + return allTechs.map((tech) => computeHypeCycle(tech, currentYear)); +} + +export function findTechnology(query: string): TechGeneration | undefined { + const q = query.toLowerCase(); + const allTechs = [...TECH_GENERATIONS, ...SPECIAL_TECHS]; + return allTechs.find((t) => + t.name.toLowerCase().includes(q) || + q.includes(t.speedGbps.toString()) || + q.includes(t.formFactor.toLowerCase()) + ); +} diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index a471dbf..4594fd6 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -8,6 +8,7 @@ import { switchRouter } from "./routes/switches"; import { vendorRouter } from "./routes/vendors"; import { standardRouter } from "./routes/standards"; import { healthRouter } from "./routes/health"; +import { hypeCycleRouter } from "./routes/hype-cycle"; const app = express(); @@ -30,6 +31,7 @@ app.use("/api/switches", switchRouter); app.use("/api/vendors", vendorRouter); app.use("/api/standards", standardRouter); app.use("/api/health", healthRouter); +app.use("/api/hype-cycle", hypeCycleRouter); // Root app.get("/", (_req, res) => { @@ -45,6 +47,8 @@ app.get("/", (_req, res) => { "GET /api/vendors?type=", "GET /api/standards?speed=", "GET /api/health", + "GET /api/hype-cycle", + "GET /api/hype-cycle/:tech", ], }); }); diff --git a/packages/api/src/routes/hype-cycle.ts b/packages/api/src/routes/hype-cycle.ts new file mode 100644 index 0000000..23eccd0 --- /dev/null +++ b/packages/api/src/routes/hype-cycle.ts @@ -0,0 +1,66 @@ +/** + * Hype Cycle API routes + * + * GET /api/hype-cycle — All technologies with current phase + * GET /api/hype-cycle/:tech — Specific technology with 5-year forecast + */ +import { Router, Request, Response } from "express"; +import { + computeAllHypeCycles, + computeHypeCycle, + findTechnology, +} from "../hype-cycle/norton-bass"; + +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 +hypeCycleRouter.get("/", (_req: Request, res: Response) => { + const yearParam = q("year", _req); + const year = yearParam ? parseInt(yearParam) : new Date().getFullYear(); + + const results = computeAllHypeCycles(year); + + // Sort by position on hype cycle + 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/:tech — Specific technology detail +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); + + res.json({ + success: true, + ...result, + }); +});