Rene Fichtmueller eb875f37d2 feat: Phase 3 — Norton-Bass Hype Cycle Engine
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
2026-03-27 23:35:57 +13:00

469 lines
16 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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.30.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; // 0100 on the hype cycle curve
readonly adoptionPct: number; // Current market adoption %
readonly compositeScore: number; // 0100
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; // 01
readonly aspDeclineRate: number; // % per year
readonly standardsMaturity: number; // 0100
readonly interopLevel: number; // 0100
readonly vendorCount: number;
readonly vendorTrend: "increasing" | "stable" | "decreasing";
readonly mediaHypeIndex: number; // 0100
}
// ============================================================
// Known technology generations (seed data, March 2026)
// ============================================================
export const TECH_GENERATIONS: ReadonlyArray<TechGeneration> = [
{
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<TechGeneration> = [
{
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 (0100)
// ============================================================
const PHASE_POSITIONS: Record<HypeCyclePhase, [number, number]> = {
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<HypeCyclePhase, string> = {
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<PhaseMetrics>,
): 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 01 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<HypeCycleResult> {
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())
);
}