feat(chart): agentic AI market size forecasts
This commit is contained in:
177
src/charts/market_forecasts.py
Normal file
177
src/charts/market_forecasts.py
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
"""Agent Market Forecasts Chart"""
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
import matplotlib.lines as mlines
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
from src.data.agent_adoption import agent_market_forecasts
|
||||||
|
from src.utils.styling import get_theme, EXPORT_DPI, AGENT_GROWTH, AI_SPEND, GRAY_DARK
|
||||||
|
|
||||||
|
|
||||||
|
def _interpolate_forecast(forecast: dict) -> dict:
|
||||||
|
"""Interpolate yearly values from start/end using the stated CAGR."""
|
||||||
|
start_val = forecast["year_2025_billions"]
|
||||||
|
cagr = forecast["cagr_percent"]
|
||||||
|
|
||||||
|
# Determine end year key
|
||||||
|
if "year_2033_billions" in forecast:
|
||||||
|
end_key = "year_2033_billions"
|
||||||
|
end_year = 2033
|
||||||
|
else:
|
||||||
|
end_key = "year_2030_billions"
|
||||||
|
end_year = 2030
|
||||||
|
|
||||||
|
end_val = forecast[end_key]
|
||||||
|
|
||||||
|
# Interpolate yearly values using CAGR
|
||||||
|
values = {}
|
||||||
|
for year in range(2025, 2035):
|
||||||
|
if year <= end_year:
|
||||||
|
values[year] = start_val * ((1 + cagr / 100) ** (year - 2025))
|
||||||
|
else:
|
||||||
|
# No forecast beyond end year — leave as None
|
||||||
|
values[year] = None
|
||||||
|
|
||||||
|
return {
|
||||||
|
"source": forecast["source"],
|
||||||
|
"category": forecast.get("category", ""),
|
||||||
|
"cagr": cagr,
|
||||||
|
"start": start_val,
|
||||||
|
"end_val": end_val,
|
||||||
|
"end_year": end_year,
|
||||||
|
"values": values,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def plot_market_forecasts() -> str:
|
||||||
|
plt.rcParams.update(get_theme())
|
||||||
|
fig, ax = plt.subplots(figsize=(14, 8))
|
||||||
|
|
||||||
|
# Define colors per source
|
||||||
|
colors = {
|
||||||
|
"Omdia": "#e74c3c",
|
||||||
|
"BCC Research": "#2980b9",
|
||||||
|
"MarketsandMarkets": "#27ae60",
|
||||||
|
"Grand View Research": "#8e44ad",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Process forecasts
|
||||||
|
processed = [_interpolate_forecast(f) for f in agent_market_forecasts]
|
||||||
|
|
||||||
|
all_years = list(range(2025, 2035))
|
||||||
|
|
||||||
|
# Collect per-year min/max for shaded band (only where forecasts exist)
|
||||||
|
min_vals = []
|
||||||
|
max_vals = []
|
||||||
|
for year in all_years:
|
||||||
|
vals = [p["values"][year] for p in processed if p["values"][year] is not None]
|
||||||
|
if vals:
|
||||||
|
min_vals.append(min(vals))
|
||||||
|
max_vals.append(max(vals))
|
||||||
|
else:
|
||||||
|
min_vals.append(None)
|
||||||
|
max_vals.append(None)
|
||||||
|
|
||||||
|
# Plot each forecast line
|
||||||
|
handles = []
|
||||||
|
labels = []
|
||||||
|
for p in processed:
|
||||||
|
pts_x = []
|
||||||
|
pts_y = []
|
||||||
|
for year in all_years:
|
||||||
|
v = p["values"][year]
|
||||||
|
if v is not None:
|
||||||
|
pts_x.append(year)
|
||||||
|
pts_y.append(v)
|
||||||
|
|
||||||
|
if pts_x:
|
||||||
|
label = f'{p["source"]} ({p["cagr"]}% CAGR)'
|
||||||
|
color = colors.get(p["source"], AI_SPEND)
|
||||||
|
line, = ax.plot(
|
||||||
|
pts_x, pts_y,
|
||||||
|
color=color,
|
||||||
|
linewidth=2.5,
|
||||||
|
label=label,
|
||||||
|
marker="o",
|
||||||
|
markersize=5,
|
||||||
|
)
|
||||||
|
handles.append(line)
|
||||||
|
labels.append(label)
|
||||||
|
|
||||||
|
# Annotate endpoint
|
||||||
|
ax.annotate(
|
||||||
|
f"${p['end_val']:.1f}B",
|
||||||
|
xy=(pts_x[-1], pts_y[-1]),
|
||||||
|
xytext=(5, 8),
|
||||||
|
textcoords="offset points",
|
||||||
|
fontsize=9,
|
||||||
|
fontweight="bold",
|
||||||
|
color=color,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shaded confidence band between min and max
|
||||||
|
band_x = []
|
||||||
|
band_min = []
|
||||||
|
band_max = []
|
||||||
|
for i, year in enumerate(all_years):
|
||||||
|
if min_vals[i] is not None:
|
||||||
|
band_x.append(year)
|
||||||
|
band_min.append(min_vals[i])
|
||||||
|
band_max.append(max_vals[i])
|
||||||
|
|
||||||
|
if band_x:
|
||||||
|
ax.fill_between(
|
||||||
|
band_x, band_min, band_max,
|
||||||
|
alpha=0.12,
|
||||||
|
color=AGENT_GROWTH,
|
||||||
|
label="Forecast Range",
|
||||||
|
)
|
||||||
|
handles.append(
|
||||||
|
mlines.Line2D([], [], color=AGENT_GROWTH, alpha=0.3, linewidth=2)
|
||||||
|
)
|
||||||
|
labels.append("Forecast Range")
|
||||||
|
|
||||||
|
# Axes configuration
|
||||||
|
ax.set_yscale("log")
|
||||||
|
ax.set_ylim(0.8, 250)
|
||||||
|
ax.set_xlim(2024.5, 2034.5)
|
||||||
|
ax.set_xticks(all_years)
|
||||||
|
ax.set_xlabel("Year", fontsize=12)
|
||||||
|
ax.set_ylabel("Market Size ($ Billions, log scale)", fontsize=12)
|
||||||
|
ax.set_title(
|
||||||
|
"Agentic AI Market Size Forecasts",
|
||||||
|
fontsize=16,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Subtitle
|
||||||
|
fig.text(
|
||||||
|
0.5,
|
||||||
|
0.93,
|
||||||
|
"Multiple analyst projections 2025\u20132034",
|
||||||
|
fontsize=11,
|
||||||
|
ha="center",
|
||||||
|
style="italic",
|
||||||
|
color=GRAY_DARK,
|
||||||
|
)
|
||||||
|
|
||||||
|
ax.legend(handles=handles, labels=labels, loc="upper left", fontsize=9)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
fig.savefig(
|
||||||
|
"output/charts/11_agent_market_forecasts.png",
|
||||||
|
dpi=EXPORT_DPI,
|
||||||
|
facecolor=fig.get_facecolor(),
|
||||||
|
edgecolor="none",
|
||||||
|
)
|
||||||
|
plt.close(fig)
|
||||||
|
return "output/charts/11_agent_market_forecasts.png"
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
path = plot_market_forecasts()
|
||||||
|
print(f"Chart saved: {path}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user