"""
================================================================================
ACCURACY ANALYSIS - CONSOLIDATED REPORT
================================================================================
AstraZeneca Cash Flow Forecasting - Datathon 2025

Purpose: Calculate comprehensive accuracy metrics including:
- Direction Accuracy (correctly predicting inflow vs outflow)
- MAE, RMSE (absolute dollar errors)
- Confidence Interval Coverage
- Business-friendly interpretation

Key Insight: MAPE/SMAPE unreliable for cash flow (crosses zero)
             MAE and Direction Accuracy are better metrics
"""

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

from sklearn.metrics import mean_absolute_error, mean_squared_error
from prophet import Prophet
import os

# AstraZeneca Color Palette
AZ_COLORS = {
    'mulberry': '#830051',
    'lime_green': '#C4D600',
    'navy': '#003865',
    'graphite': '#3F4444',
    'light_blue': '#68D2DF',
    'magenta': '#D0006F',
    'purple': '#3C1053',
    'gold': '#F0AB00'
}

print("=" * 80)
print("ACCURACY ANALYSIS - CONSOLIDATED REPORT")
print("=" * 80)

# =============================================================================
# LOAD DATA
# =============================================================================
print("\n[1/6] Loading data...")

df = pd.read_csv('data_cleaned_weekly.csv')
df['Week_Start'] = pd.to_datetime(df['Week_Start'])
df = df.sort_values('Week_Start').reset_index(drop=True)

print(f"   Total weeks: {len(df)}")
print(f"   Date range: {df['Week_Start'].min().date()} to {df['Week_Start'].max().date()}")

# =============================================================================
# HELPER FUNCTIONS
# =============================================================================
def calculate_direction_accuracy(y_true, y_pred):
    """Calculate % of correct direction predictions (inflow vs outflow)"""
    true_direction = np.sign(y_true)
    pred_direction = np.sign(y_pred)
    correct = (true_direction == pred_direction).sum()
    return (correct / len(y_true)) * 100

def calculate_metrics(y_true, y_pred):
    """Calculate comprehensive metrics"""
    mae = mean_absolute_error(y_true, y_pred)
    rmse = np.sqrt(mean_squared_error(y_true, y_pred))
    
    # Direction accuracy
    direction_acc = calculate_direction_accuracy(y_true, y_pred)
    
    # SMAPE (for reference, but note it's unreliable)
    numerator = np.abs(y_true - y_pred)
    denominator = (np.abs(y_true) + np.abs(y_pred)) / 2
    mask = denominator != 0
    smape = np.mean(numerator[mask] / denominator[mask]) * 100 if mask.sum() > 0 else np.nan
    
    return {
        'MAE': mae,
        'RMSE': rmse,
        'Direction_Accuracy': direction_acc,
        'SMAPE': smape
    }

# =============================================================================
# VALIDATION 1: SIMPLE HOLDOUT (Weeks 40-44)
# =============================================================================
print("\n[2/6] Validation 1: Simple Holdout (Weeks 40-44)...")

train_size = 39
train_df = df.iloc[:train_size].copy()
test_df = df.iloc[train_size:].copy()

# Train Prophet
prophet_df = pd.DataFrame({
    'ds': train_df['Week_Start'],
    'y': train_df['Net_Cash_Flow_USD']
})

model = Prophet(
    yearly_seasonality=False,
    weekly_seasonality=False,
    daily_seasonality=False,
    seasonality_mode='additive',
    interval_width=0.95,
    changepoint_prior_scale=0.05
)
model.add_seasonality(name='monthly', period=30.5, fourier_order=5)
model.fit(prophet_df)

# Predict
future = pd.DataFrame({'ds': test_df['Week_Start']})
forecast = model.predict(future)

y_test = test_df['Net_Cash_Flow_USD'].values
y_pred = forecast['yhat'].values
y_lower = forecast['yhat_lower'].values
y_upper = forecast['yhat_upper'].values

# Calculate metrics
holdout_metrics = calculate_metrics(y_test, y_pred)

# CI Coverage
ci_coverage = np.mean((y_test >= y_lower) & (y_test <= y_upper)) * 100

print(f"\n   SIMPLE HOLDOUT RESULTS (5 weeks):")
print(f"   +------------------------+------------------+")
print(f"   | Metric                 | Value            |")
print(f"   +------------------------+------------------+")
print(f"   | MAE                    | ${holdout_metrics['MAE']/1e6:>10.2f}M    |")
print(f"   | RMSE                   | ${holdout_metrics['RMSE']/1e6:>10.2f}M    |")
print(f"   | Direction Accuracy     | {holdout_metrics['Direction_Accuracy']:>10.1f}%    |")
print(f"   | 95% CI Coverage        | {ci_coverage:>10.1f}%    |")
print(f"   | SMAPE (unreliable)     | {holdout_metrics['SMAPE']:>10.1f}%    |")
print(f"   +------------------------+------------------+")

# Detailed per-week analysis
print(f"\n   Week-by-Week Analysis:")
print(f"   +------+-------------+-------------+------------+-----------+")
print(f"   | Week | Actual      | Predicted   | Error      | Direction |")
print(f"   +------+-------------+-------------+------------+-----------+")
for i, (actual, pred) in enumerate(zip(y_test, y_pred)):
    error = actual - pred
    actual_dir = "IN" if actual > 0 else "OUT"
    pred_dir = "IN" if pred > 0 else "OUT"
    dir_match = "[OK]" if (actual > 0) == (pred > 0) else "[X]"
    print(f"   | {test_df['Week_Number'].iloc[i]:>4} | ${actual/1e6:>8.2f}M | ${pred/1e6:>8.2f}M | ${error/1e6:>7.2f}M | {actual_dir:>3} vs {pred_dir:<3} {dir_match} |")
print(f"   +------+-------------+-------------+------------+-----------+")

# =============================================================================
# VALIDATION 2: ROLLING WINDOW CROSS-VALIDATION
# =============================================================================
print("\n[3/6] Validation 2: Rolling Window Cross-Validation...")

rolling_results = []

# Test different horizons
horizons = [
    (28, 4, "1 Month"),
    (28, 8, "2 Months"),
    (28, 12, "3 Months"),
    (28, 16, "4 Months")
]

for train_end, test_weeks, horizon_name in horizons:
    train_data = df.iloc[:train_end].copy()
    test_data = df.iloc[train_end:train_end+test_weeks].copy()
    
    if len(test_data) < test_weeks:
        test_data = df.iloc[train_end:].copy()
    
    # Train Prophet
    prophet_df = pd.DataFrame({
        'ds': train_data['Week_Start'],
        'y': train_data['Net_Cash_Flow_USD']
    })
    
    model = Prophet(
        yearly_seasonality=False,
        weekly_seasonality=False,
        daily_seasonality=False,
        seasonality_mode='additive',
        interval_width=0.95,
        changepoint_prior_scale=0.05
    )
    model.add_seasonality(name='monthly', period=30.5, fourier_order=5)
    model.fit(prophet_df)
    
    # Predict
    future = pd.DataFrame({'ds': test_data['Week_Start']})
    forecast = model.predict(future)
    
    y_test_roll = test_data['Net_Cash_Flow_USD'].values
    y_pred_roll = forecast['yhat'].values
    y_lower_roll = forecast['yhat_lower'].values
    y_upper_roll = forecast['yhat_upper'].values
    
    # Calculate metrics
    metrics = calculate_metrics(y_test_roll, y_pred_roll)
    ci_cov = np.mean((y_test_roll >= y_lower_roll) & (y_test_roll <= y_upper_roll)) * 100
    
    rolling_results.append({
        'Horizon': horizon_name,
        'Test_Weeks': len(test_data),
        'MAE': metrics['MAE'],
        'RMSE': metrics['RMSE'],
        'Direction_Accuracy': metrics['Direction_Accuracy'],
        'CI_Coverage': ci_cov,
        'SMAPE': metrics['SMAPE']
    })

rolling_df = pd.DataFrame(rolling_results)

print(f"\n   ROLLING WINDOW RESULTS:")
print(f"   +----------+-------+-----------+-----------+------------+----------+")
print(f"   | Horizon  | Weeks | MAE       | RMSE      | Direction  | CI Cov   |")
print(f"   +----------+-------+-----------+-----------+------------+----------+")
for _, row in rolling_df.iterrows():
    print(f"   | {row['Horizon']:<8} | {row['Test_Weeks']:>5} | ${row['MAE']/1e6:>6.2f}M | ${row['RMSE']/1e6:>6.2f}M | {row['Direction_Accuracy']:>8.1f}%  | {row['CI_Coverage']:>6.1f}%  |")
print(f"   +----------+-------+-----------+-----------+------------+----------+")

# =============================================================================
# VALIDATION 3: LEAVE-ONE-OUT CROSS-VALIDATION
# =============================================================================
print("\n[4/6] Validation 3: Leave-One-Out Cross-Validation (last 10 weeks)...")

loo_results = []

# Test on last 10 weeks using expanding window
for test_week in range(35, 44):
    train_data = df.iloc[:test_week].copy()
    test_data = df.iloc[test_week:test_week+1].copy()
    
    # Train Prophet
    prophet_df = pd.DataFrame({
        'ds': train_data['Week_Start'],
        'y': train_data['Net_Cash_Flow_USD']
    })
    
    model = Prophet(
        yearly_seasonality=False,
        weekly_seasonality=False,
        daily_seasonality=False,
        seasonality_mode='additive',
        interval_width=0.95,
        changepoint_prior_scale=0.05
    )
    model.add_seasonality(name='monthly', period=30.5, fourier_order=5)
    model.fit(prophet_df)
    
    # Predict 1 week ahead
    future = pd.DataFrame({'ds': test_data['Week_Start']})
    forecast = model.predict(future)
    
    actual = test_data['Net_Cash_Flow_USD'].values[0]
    predicted = forecast['yhat'].values[0]
    lower = forecast['yhat_lower'].values[0]
    upper = forecast['yhat_upper'].values[0]
    
    loo_results.append({
        'Week': test_week + 1,
        'Actual': actual,
        'Predicted': predicted,
        'Error': actual - predicted,
        'Abs_Error': abs(actual - predicted),
        'Direction_Correct': (actual > 0) == (predicted > 0),
        'Within_CI': (actual >= lower) and (actual <= upper)
    })

loo_df = pd.DataFrame(loo_results)

# Summary statistics
avg_mae_loo = loo_df['Abs_Error'].mean()
direction_acc_loo = loo_df['Direction_Correct'].mean() * 100
ci_coverage_loo = loo_df['Within_CI'].mean() * 100

print(f"\n   LEAVE-ONE-OUT RESULTS (1-week ahead, 9 folds):")
print(f"   +------------------------+------------------+")
print(f"   | Metric                 | Value            |")
print(f"   +------------------------+------------------+")
print(f"   | Average MAE            | ${avg_mae_loo/1e6:>10.2f}M    |")
print(f"   | Direction Accuracy     | {direction_acc_loo:>10.1f}%    |")
print(f"   | 95% CI Coverage        | {ci_coverage_loo:>10.1f}%    |")
print(f"   +------------------------+------------------+")

# =============================================================================
# CREATE VISUALIZATION
# =============================================================================
print("\n[5/6] Creating visualizations...")

os.makedirs('dashboard', exist_ok=True)

fig = plt.figure(figsize=(20, 14))
fig.suptitle('ACCURACY ANALYSIS - CONSOLIDATED REPORT\nAstraZeneca Cash Flow Forecasting', 
             fontsize=18, fontweight='bold', color=AZ_COLORS['navy'], y=0.98)

# ------ Plot 1: Direction Accuracy Comparison ------
ax1 = fig.add_subplot(2, 3, 1)

methods = ['Holdout\n(5 weeks)', '1-Month\nRolling', '2-Month\nRolling', '3-Month\nRolling', '4-Month\nRolling', 'LOO\n(1-week)']
direction_accs = [
    holdout_metrics['Direction_Accuracy'],
    rolling_df.iloc[0]['Direction_Accuracy'],
    rolling_df.iloc[1]['Direction_Accuracy'],
    rolling_df.iloc[2]['Direction_Accuracy'],
    rolling_df.iloc[3]['Direction_Accuracy'],
    direction_acc_loo
]

colors = [AZ_COLORS['lime_green'] if acc >= 60 else AZ_COLORS['gold'] if acc >= 50 else AZ_COLORS['magenta'] for acc in direction_accs]
bars = ax1.bar(methods, direction_accs, color=colors, edgecolor=AZ_COLORS['navy'], linewidth=2)

ax1.axhline(y=50, color=AZ_COLORS['graphite'], linestyle='--', linewidth=2, label='Random Guess (50%)')
ax1.set_ylabel('Direction Accuracy (%)', fontweight='bold')
ax1.set_title('Direction Accuracy\n(Correctly Predicting Inflow vs Outflow)', fontweight='bold', fontsize=11)
ax1.set_ylim(0, 100)
ax1.legend()

for bar, acc in zip(bars, direction_accs):
    ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, f'{acc:.0f}%', 
             ha='center', fontweight='bold', fontsize=10)

# ------ Plot 2: MAE by Validation Method ------
ax2 = fig.add_subplot(2, 3, 2)

mae_values = [
    holdout_metrics['MAE']/1e6,
    rolling_df.iloc[0]['MAE']/1e6,
    rolling_df.iloc[1]['MAE']/1e6,
    rolling_df.iloc[2]['MAE']/1e6,
    rolling_df.iloc[3]['MAE']/1e6,
    avg_mae_loo/1e6
]

colors_mae = [AZ_COLORS['lime_green'] if mae < 2 else AZ_COLORS['gold'] if mae < 3 else AZ_COLORS['magenta'] for mae in mae_values]
bars = ax2.bar(methods, mae_values, color=colors_mae, edgecolor=AZ_COLORS['navy'], linewidth=2)

ax2.set_ylabel('MAE (Millions USD)', fontweight='bold')
ax2.set_title('Mean Absolute Error (MAE)\nLower is Better', fontweight='bold', fontsize=11)

for bar, mae in zip(bars, mae_values):
    ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.1, f'${mae:.2f}M', 
             ha='center', fontweight='bold', fontsize=9)

# ------ Plot 3: CI Coverage ------
ax3 = fig.add_subplot(2, 3, 3)

ci_values = [
    ci_coverage,
    rolling_df.iloc[0]['CI_Coverage'],
    rolling_df.iloc[1]['CI_Coverage'],
    rolling_df.iloc[2]['CI_Coverage'],
    rolling_df.iloc[3]['CI_Coverage'],
    ci_coverage_loo
]

colors_ci = [AZ_COLORS['lime_green'] if ci >= 90 else AZ_COLORS['gold'] if ci >= 80 else AZ_COLORS['magenta'] for ci in ci_values]
bars = ax3.bar(methods, ci_values, color=colors_ci, edgecolor=AZ_COLORS['navy'], linewidth=2)

ax3.axhline(y=95, color=AZ_COLORS['mulberry'], linestyle='--', linewidth=2, label='Target (95%)')
ax3.set_ylabel('CI Coverage (%)', fontweight='bold')
ax3.set_title('95% Confidence Interval Coverage\nActuals within predicted range', fontweight='bold', fontsize=11)
ax3.set_ylim(0, 100)
ax3.legend()

for bar, ci in zip(bars, ci_values):
    ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, f'{ci:.0f}%', 
             ha='center', fontweight='bold', fontsize=10)

# ------ Plot 4: Actual vs Predicted (Holdout) ------
ax4 = fig.add_subplot(2, 3, 4)

weeks = test_df['Week_Number'].values
ax4.scatter(y_test/1e6, y_pred/1e6, c=AZ_COLORS['navy'], s=150, zorder=5, edgecolors='white', linewidth=2)

# Perfect prediction line
min_val = min(min(y_test), min(y_pred)) / 1e6
max_val = max(max(y_test), max(y_pred)) / 1e6
ax4.plot([min_val, max_val], [min_val, max_val], 'k--', linewidth=2, label='Perfect Prediction')

# Add quadrant lines
ax4.axhline(y=0, color=AZ_COLORS['graphite'], linestyle='-', linewidth=1, alpha=0.5)
ax4.axvline(x=0, color=AZ_COLORS['graphite'], linestyle='-', linewidth=1, alpha=0.5)

# Add labels for each point
for i, (actual, pred, week) in enumerate(zip(y_test/1e6, y_pred/1e6, weeks)):
    ax4.annotate(f'W{week}', (actual, pred), textcoords="offset points", xytext=(5,5), fontsize=9)

ax4.set_xlabel('Actual Cash Flow (Millions USD)', fontweight='bold')
ax4.set_ylabel('Predicted Cash Flow (Millions USD)', fontweight='bold')
ax4.set_title('Actual vs Predicted (Holdout Test)\nPoints near diagonal = good predictions', fontweight='bold', fontsize=11)
ax4.legend()
ax4.grid(True, alpha=0.3)

# Add quadrant labels
ax4.text(0.95, 0.95, 'Correct\nInflow', transform=ax4.transAxes, ha='right', va='top', 
         fontsize=9, color=AZ_COLORS['lime_green'], fontweight='bold')
ax4.text(0.05, 0.05, 'Correct\nOutflow', transform=ax4.transAxes, ha='left', va='bottom', 
         fontsize=9, color=AZ_COLORS['lime_green'], fontweight='bold')
ax4.text(0.95, 0.05, 'Wrong\n(Pred IN, Act OUT)', transform=ax4.transAxes, ha='right', va='bottom', 
         fontsize=9, color=AZ_COLORS['magenta'], fontweight='bold')
ax4.text(0.05, 0.95, 'Wrong\n(Pred OUT, Act IN)', transform=ax4.transAxes, ha='left', va='top', 
         fontsize=9, color=AZ_COLORS['magenta'], fontweight='bold')

# ------ Plot 5: MAE Degradation by Horizon ------
ax5 = fig.add_subplot(2, 3, 5)

horizons_weeks = [4, 8, 12, 16]
mae_by_horizon = rolling_df['MAE'].values / 1e6
dir_by_horizon = rolling_df['Direction_Accuracy'].values

ax5_twin = ax5.twinx()

line1 = ax5.plot(horizons_weeks, mae_by_horizon, 'o-', color=AZ_COLORS['navy'], linewidth=3, 
                  markersize=12, label='MAE')
line2 = ax5_twin.plot(horizons_weeks, dir_by_horizon, 's--', color=AZ_COLORS['lime_green'], linewidth=3, 
                       markersize=12, label='Direction Acc.')

ax5.set_xlabel('Forecast Horizon (Weeks)', fontweight='bold')
ax5.set_ylabel('MAE (Millions USD)', fontweight='bold', color=AZ_COLORS['navy'])
ax5_twin.set_ylabel('Direction Accuracy (%)', fontweight='bold', color=AZ_COLORS['lime_green'])
ax5.set_title('Accuracy Degradation by Forecast Horizon', fontweight='bold', fontsize=11)

ax5.tick_params(axis='y', labelcolor=AZ_COLORS['navy'])
ax5_twin.tick_params(axis='y', labelcolor=AZ_COLORS['lime_green'])

lines = line1 + line2
labels = [l.get_label() for l in lines]
ax5.legend(lines, labels, loc='center right')
ax5.grid(True, alpha=0.3)

# ------ Plot 6: Summary Table ------
ax6 = fig.add_subplot(2, 3, 6)
ax6.axis('off')

# Calculate overall summary
avg_direction = np.mean(direction_accs)
avg_mae = np.mean(mae_values)
avg_ci = np.mean(ci_values)

summary_text = f"""
ACCURACY SUMMARY
================

KEY METRICS (Business-Friendly)
-------------------------------
Direction Accuracy:  {avg_direction:.0f}% average
  "Prophet correctly predicts inflow vs outflow
   {avg_direction:.0f}% of the time"

Mean Absolute Error: ${avg_mae:.2f}M average
  "On average, forecasts are off by ${avg_mae:.2f}M"

CI Coverage:         {avg_ci:.0f}% average
  "Actual values fall within predicted range
   {avg_ci:.0f}% of the time"


INTERPRETATION
--------------
Direction Accuracy > 50%: Better than random
Direction Accuracy > 60%: Good for cash flow
Direction Accuracy > 75%: Excellent

MAE < $2M: Good accuracy
MAE $2-3M: Acceptable for 6-month forecast
MAE > $3M: Consider model refinement


RECOMMENDATION
--------------
Prophet shows {avg_direction:.0f}% direction accuracy
with ${avg_mae:.2f}M average error.

This is {'GOOD' if avg_direction >= 60 else 'ACCEPTABLE'} for cash flow
forecasting where values cross zero.

The model is suitable for:
- Short-term planning (1-2 months)
- Scenario analysis (use CI bounds)
- Trend identification
"""

ax6.text(0.05, 0.95, summary_text, transform=ax6.transAxes, fontsize=9,
         verticalalignment='top', fontfamily='monospace',
         bbox=dict(boxstyle='round', facecolor=AZ_COLORS['lime_green'], alpha=0.2))

plt.tight_layout()
plt.savefig('dashboard/05_accuracy_analysis.png', dpi=300, bbox_inches='tight', facecolor='white')
plt.close()

print("   Saved: dashboard/05_accuracy_analysis.png")

# =============================================================================
# SAVE RESULTS
# =============================================================================
print("\n[6/6] Saving results...")

# Consolidated accuracy report
accuracy_summary = {
    'Validation_Method': ['Holdout (5w)', '1-Month Rolling', '2-Month Rolling', 
                          '3-Month Rolling', '4-Month Rolling', 'LOO (1-week)'],
    'MAE_USD': [holdout_metrics['MAE'], rolling_df.iloc[0]['MAE'], rolling_df.iloc[1]['MAE'],
                rolling_df.iloc[2]['MAE'], rolling_df.iloc[3]['MAE'], avg_mae_loo],
    'RMSE_USD': [holdout_metrics['RMSE'], rolling_df.iloc[0]['RMSE'], rolling_df.iloc[1]['RMSE'],
                 rolling_df.iloc[2]['RMSE'], rolling_df.iloc[3]['RMSE'], np.nan],
    'Direction_Accuracy_Pct': direction_accs,
    'CI_Coverage_Pct': ci_values,
    'SMAPE_Pct': [holdout_metrics['SMAPE'], rolling_df.iloc[0]['SMAPE'], rolling_df.iloc[1]['SMAPE'],
                  rolling_df.iloc[2]['SMAPE'], rolling_df.iloc[3]['SMAPE'], np.nan]
}

accuracy_df = pd.DataFrame(accuracy_summary)
accuracy_df.to_csv('accuracy_summary.csv', index=False)
print("   Saved: accuracy_summary.csv")

# =============================================================================
# FINAL SUMMARY
# =============================================================================
print("\n" + "=" * 80)
print("ACCURACY ANALYSIS COMPLETE")
print("=" * 80)

print(f"""
CONSOLIDATED ACCURACY RESULTS
=============================

DIRECTION ACCURACY (Correctly predicting Inflow vs Outflow):
   +----------------------+------------+----------------+
   | Validation Method    | Direction  | Interpretation |
   +----------------------+------------+----------------+
   | Holdout (5 weeks)    | {holdout_metrics['Direction_Accuracy']:>8.0f}%  | {'Good' if holdout_metrics['Direction_Accuracy'] >= 60 else 'Fair'}           |
   | 1-Month Rolling      | {rolling_df.iloc[0]['Direction_Accuracy']:>8.0f}%  | {'Good' if rolling_df.iloc[0]['Direction_Accuracy'] >= 60 else 'Fair'}           |
   | 2-Month Rolling      | {rolling_df.iloc[1]['Direction_Accuracy']:>8.0f}%  | {'Good' if rolling_df.iloc[1]['Direction_Accuracy'] >= 60 else 'Fair'}           |
   | 3-Month Rolling      | {rolling_df.iloc[2]['Direction_Accuracy']:>8.0f}%  | {'Good' if rolling_df.iloc[2]['Direction_Accuracy'] >= 60 else 'Fair'}           |
   | 4-Month Rolling      | {rolling_df.iloc[3]['Direction_Accuracy']:>8.0f}%  | {'Good' if rolling_df.iloc[3]['Direction_Accuracy'] >= 60 else 'Fair'}           |
   | LOO (1-week ahead)   | {direction_acc_loo:>8.0f}%  | {'Good' if direction_acc_loo >= 60 else 'Fair'}           |
   +----------------------+------------+----------------+
   | AVERAGE              | {avg_direction:>8.0f}%  | {'GOOD' if avg_direction >= 60 else 'FAIR'}           |
   +----------------------+------------+----------------+

MEAN ABSOLUTE ERROR (Dollar accuracy):
   +----------------------+------------+----------------+
   | Validation Method    | MAE        | Interpretation |
   +----------------------+------------+----------------+
   | Holdout (5 weeks)    | ${holdout_metrics['MAE']/1e6:>7.2f}M  | {'Good' if holdout_metrics['MAE']/1e6 < 2 else 'Fair'}           |
   | 1-Month Rolling      | ${rolling_df.iloc[0]['MAE']/1e6:>7.2f}M  | {'Good' if rolling_df.iloc[0]['MAE']/1e6 < 2 else 'Fair'}           |
   | 2-Month Rolling      | ${rolling_df.iloc[1]['MAE']/1e6:>7.2f}M  | {'Good' if rolling_df.iloc[1]['MAE']/1e6 < 2 else 'Fair'}           |
   | 3-Month Rolling      | ${rolling_df.iloc[2]['MAE']/1e6:>7.2f}M  | {'Good' if rolling_df.iloc[2]['MAE']/1e6 < 2 else 'Fair'}           |
   | 4-Month Rolling      | ${rolling_df.iloc[3]['MAE']/1e6:>7.2f}M  | {'Good' if rolling_df.iloc[3]['MAE']/1e6 < 2 else 'Fair'}           |
   | LOO (1-week ahead)   | ${avg_mae_loo/1e6:>7.2f}M  | {'Good' if avg_mae_loo/1e6 < 2 else 'Fair'}           |
   +----------------------+------------+----------------+
   | AVERAGE              | ${avg_mae:>7.2f}M  | {'GOOD' if avg_mae < 2 else 'FAIR'}           |
   +----------------------+------------+----------------+

CONFIDENCE INTERVAL COVERAGE:
   Average 95% CI Coverage: {avg_ci:.0f}%
   (Target: 95%, Actual values within predicted range)

KEY INSIGHTS:
   1. Direction Accuracy ({avg_direction:.0f}%) is {'above' if avg_direction > 50 else 'at'} random chance (50%)
   2. MAE (${avg_mae:.2f}M) is {'acceptable' if avg_mae < 3 else 'high'} for cash flow forecasting
   3. CI Coverage ({avg_ci:.0f}%) indicates {'well-calibrated' if avg_ci >= 80 else 'needs calibration'} uncertainty estimates

FILES CREATED:
   - dashboard/05_accuracy_analysis.png
   - accuracy_summary.csv

==> Model accuracy validated! Ready for business reporting.
""")

