from datetime import datetime, timedelta

# Function to extract actuals for a given lag and cutoff date
def extract_actuals_for_lag(df, lag, cutoff_date):
    start_date = cutoff_date - timedelta(weeks=lag+4)
    end_date = cutoff_date - timedelta(weeks=lag)
    mask = (df['ds'] >= pl.lit(start_date)) & (df['ds'] < pl.lit(end_date))
    mask_df = df.filter(mask)  # Use df.filter() instead of boolean indexing
    return mask_df.select(['unique_id', 'y']).groupby('unique_id').agg(pl.col('y').list().alias('actuals'))

# Function to calculate group accuracy and bias for a given lag
def calculate_lag_accuracy_bias(actuals, forecasts, cutoff_date):
    lag_accuracy = {}
    lag_bias = {}

    forecasts_filtered = forecasts[(forecasts['WeekDate'] >= cutoff_date) & (forecasts['WeekDate'] < cutoff_date + timedelta(weeks=12))]
    forecasts_filtered = forecasts_filtered.with_columns([
        pl.concat_str([
            pl.col('MaterialID').cast(pl.Utf8),
            "_",
            pl.col('SalesOrg').cast(pl.Utf8),
            "_",
            pl.col('DistrChan').cast(pl.Utf8),
            "_",
            pl.col('CL4').cast(pl.Utf8)
        ]).alias('unique_id')
    ])

    for record in actuals.to_dicts():
        key = record['unique_id']
        actual_values = record['actuals']
        forecasts = forecasts_filtered[forecasts_filtered['unique_id'] == key, 'AutoARIMA'].to_lists()[0]
        abs_errors = [abs(forecast - actual) for forecast, actual in zip(forecasts, actual_values)]
        sum_abs_errors = sum(abs_errors)
        sum_actuals = sum(actual_values)
        sum_forecasts = sum(forecasts)
        if sum_actuals > 0:
            lag_accuracy[key] = 1 - (sum_abs_errors / sum_actuals)
            lag_bias[key] = (sum_forecasts / sum_actuals) - 1
        else:
            lag_accuracy[key] = 0  # Set accuracy to 0 if sum_actuals is 0
            lag_bias[key] = 0  # Set bias to 0 if sum_actuals is 0

    return lag_accuracy, lag_bias

# Set the cutoff date for lag 0 (e.g., first week of 2023)
cutoff_date = datetime(2023, 1, 2)

# Calculate accuracy and bias for each lag
lag_results = {}
for lag in range(12):
    actuals = extract_actuals_for_lag(crossvalidation_df, lag, cutoff_date)
    lag_accuracy, lag_bias = calculate_lag_accuracy_bias(actuals, forecasts_df, cutoff_date)
    lag_results[f'lag{lag}'] = {'accuracy': lag_accuracy, 'bias': lag_bias}
    cutoff_date += timedelta(weeks=1)

# Print the results along with the 12-week out forecasts
for lag, result in lag_results.items():
    print(f"{lag}:")
    for key, accuracy in result['accuracy'].items():
        forecasts = forecasts_df[forecasts_df['unique_id'] == key, 'AutoARIMA'].to_lists()[0]
        print(f"  {key}: Accuracy={accuracy:.4f}, Bias={result['bias'][key]:.4f}, 12-week out forecasts={forecasts}") ---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/Users/tungnguyen/Downloads/Nestle/Nestle CL4 Low Volume ARIMAX Ensemble.ipynb Cell 26 line 5
     50 lag_results = {}
     51 for lag in range(12):
---> 52     actuals = extract_actuals_for_lag(crossvalidation_df, lag, cutoff_date)
     53     lag_accuracy, lag_bias = calculate_lag_accuracy_bias(actuals, forecasts_df, cutoff_date)
     54     lag_results[f'lag{lag}'] = {'accuracy': lag_accuracy, 'bias': lag_bias}

/Users/tungnguyen/Downloads/Nestle/Nestle CL4 Low Volume ARIMAX Ensemble.ipynb Cell 26 line 9
      7 mask = (df['ds'] >= pl.lit(start_date)) & (df['ds'] < pl.lit(end_date))
      8 mask_df = df.filter(mask)  # Use df.filter() instead of boolean indexing
----> 9 return mask_df.select(['unique_id', 'y']).groupby('unique_id').agg(pl.col('y').list().alias('actuals'))

TypeError: 'ExprListNameSpace' object is not callable