"""Figure for the Cycle 26 register. Run with /work/venv-plot/bin/python."""
import csv, os, numpy as np, matplotlib
matplotlib.use('Agg'); import matplotlib.pyplot as plt
from matplotlib.lines import Line2D
HERE = os.path.dirname(os.path.abspath(__file__))
BLUE, ORANGE, AQUA, YELLOW = '#2a78d6', '#eb6834', '#1baf7a', '#eda100'
INK, MUTED, GRID = '#1f2328', '#57606a', '#d0d7de'
plt.rcParams.update({'font.size': 10, 'axes.edgecolor': MUTED, 'axes.labelcolor': INK, 'xtick.color': MUTED, 'ytick.color': MUTED,
                     'axes.spines.top': False, 'axes.spines.right': False, 'axes.grid': True, 'grid.color': GRID,
                     'grid.linewidth': 0.6, 'axes.axisbelow': True})
R = [r for r in csv.DictReader(open(os.path.join(HERE, 'c26_register.csv'), encoding='utf-8')) if r['amp']]
def dy(s):
    p = s.split('-'); return int(p[0]) + ((int(p[1]) if len(p) > 1 else 6) - 0.5) / 12
fig, ax = plt.subplots(figsize=(9, 5))
ax.axhspan(118.8, 234.2, color=GRID, alpha=0.35, lw=0)
ax.text(2017.6, 229, 'climatology 68% range (cycles 1-25), centre 178', color=MUTED, fontsize=8, va='top')
ax.axhline(160.9, color=INK, lw=1, ls='--'); ax.text(2017.6, 163.5, 'Cycle 25 maximum 160.9', color=INK, fontsize=8)
phys = {'polar_field_precursor', 'flux_transport_or_dynamo'}
for r in R:
    x = dy(r['issued']); v = float(r['amp']); c = AQUA if r['method_class'] in phys else (ORANGE if r['method_class'] == 'ml_neural' else BLUE)
    if r['low'] and r['high']:
        ax.errorbar(x, v, yerr=[[v - float(r['low'])], [float(r['high']) - v]], fmt='o', ms=5, color=c, mec='white', mew=0.6, elinewidth=1.2, capsize=0, zorder=3)
    else:
        ax.plot(x, v, 'o', ms=5, mfc='white', mec=c, mew=1.4, zorder=3)
ax.errorbar(2026.85, 155.2, yerr=50.2, fmt='s', ms=6, color=YELLOW, mec='white', elinewidth=2, capsize=0, zorder=4)
ax.text(2027.0, 100, 'declining-phase\nbaseline\n155 ± 50', color=INK, fontsize=8)
h = [Line2D([], [], marker='o', color=AQUA, ls='', label='polar field / flux transport / dynamo'),
     Line2D([], [], marker='o', color=ORANGE, ls='', label='ML / neural network'),
     Line2D([], [], marker='o', color=BLUE, ls='', label='other statistical / spectral'),
     Line2D([], [], marker='o', mfc='white', mec=MUTED, ls='', label='open marker: no interval stated')]
ax.legend(handles=h, loc='lower left', frameon=False, fontsize=8)
ax.set_xlim(2017.5, 2028.2); ax.set_ylim(0, 290)
ax.set_xlabel('issue date'); ax.set_ylabel('predicted Cycle 26 maximum (as stated; mostly SN v2 smoothed)')
ax.set_title('29 numeric Cycle 26 forecasts (as of October 2026) and the baselines they must beat', color=INK, fontsize=11, loc='left')
fig.tight_layout(); fig.savefig(os.path.join(HERE, 'fig_c26_register.png'), dpi=150); print('ok')
