From 6c5eb49b152a579e127c7e668bb11b9a39372324 Mon Sep 17 00:00:00 2001 From: Orchestrator Date: Thu, 4 Jun 2026 17:54:15 -0500 Subject: [PATCH] feat(chart): agentic AI market size forecasts --- src/charts/market_forecasts.py | 177 +++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 src/charts/market_forecasts.py diff --git a/src/charts/market_forecasts.py b/src/charts/market_forecasts.py new file mode 100644 index 0000000..f690fb0 --- /dev/null +++ b/src/charts/market_forecasts.py @@ -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()