# Remove series with only 0 values 
y_cl4 = y_cl4.filter(pl.col('unique_id') != '22028504_US01_8_1108389')

# Display 200 rows in Polars output    
pl.Config.set_tbl_rows(200)

# Function to perform the grouping, counting, and sorting of lengths          
def group_count_sort(y_cl4, length_threshold=None):
    lengths = y_cl4.groupby('unique_id').agg(pl.count().alias('length'))
    if length_threshold:
        lengths = lengths.filter(pl.col('length') > length_threshold)
    counts = lengths.groupby('length').agg(pl.count().alias('count')).sort('length')
    return lengths, counts

# Lengths for all series
all_lengths, all_counts = group_count_sort(y_cl4)
print(all_counts)

# Function to filter y_cl4 based on lengths
def filter_and_sort(y_cl4, lengths):
    y_cl4_filtered = y_cl4.join(
        lengths.select(pl.col('unique_id')),
        on='unique_id',
        how='semi'  
    )
    return y_cl4_filtered.sort('ds')

# Lengths greater than 31
lengths_over_31, counts_for_over_31 = group_count_sort(y_cl4, 31)
y_cl4_over_31 = filter_and_sort(y_cl4, lengths_over_31)
print(counts_for_over_31)

# Lengths greater than 15
lengths_over_15, counts_for_over_15 = group_count_sort(y_cl4, 15)
y_cl4_over_15 = filter_and_sort(y_cl4, lengths_over_15)
print(counts_for_over_15)
 this was used on an ensemble model, # Lengths greater than 29
lengths_over_29, counts_for_over_29 = group_count_sort(y_cl4, 29)
y_cl4_over_29 = filter_and_sort(y_cl4, lengths_over_29)
print(counts_for_over_29)

# Lengths greater than 9
lengths_over_9, counts_for_over_9 = group_count_sort(y_cl4, 9)
y_cl4_over_9 = filter_and_sort(y_cl4, lengths_over_9)
print(counts_for_over_9) this was sused for ensemble model but different datasets from statsforecast import StatsForecast
from statsforecast.models import AutoARIMA, AutoETS, DynamicOptimizedTheta, IMAPA
from statsforecast.utils import ConformalIntervals
import numpy as np
import polars as pl

# Polars option to display all rows
pl.Config.set_tbl_rows(None)

# Initialize the models
models = [
    AutoARIMA(season_length=12),
    AutoETS(damped=True, season_length=12),
    DynamicOptimizedTheta(season_length=12),
    IMAPA()
]

# Initialize the StatsForecast model
sf = StatsForecast(models=models, freq='1w', n_jobs=-1)

# Perform cross-validation with a step size of 1 to mimic an expanding window
crossvalidation_df = sf.cross_validation(df=y_cl4_over_29, h=1, step_size=1, n_windows=8, sort_df=True)

# Calculate the ensemble mean
ensemble = crossvalidation_df[['AutoARIMA', 'AutoETS', 'DynamicOptimizedTheta', 'IMAPA']].mean(axis=1)

# Create a Series for the ensemble mean
ensemble_series = pl.Series('Ensemble', ensemble)

# Add the ensemble mean as a new column to the DataFrame
crossvalidation_df = crossvalidation_df.with_columns(ensemble_series)

def wmape(y_true, y_pred):
    return np.abs(y_true - y_pred).sum() / np.abs(y_true).sum()

# Calculate the WMAPE for the ensemble model
wmape_value = wmape(crossvalidation_df['y'], crossvalidation_df['Ensemble'])
print('Average WMAPE for Ensemble: ', round(wmape_value, 4))

# Calculate the errors for the ensemble model
errors = crossvalidation_df['y'] - crossvalidation_df['Ensemble']

# For an individual forecast
individual_accuracy = 1 - (abs(crossvalidation_df['y'] - crossvalidation_df['Ensemble']) / crossvalidation_df['y'])
individual_bias = (crossvalidation_df['Ensemble'] / crossvalidation_df['y']) - 1

# Add these calculations as new columns to DataFrame
crossvalidation_df = crossvalidation_df.with_columns([
    individual_accuracy.alias("individual_accuracy"),
    individual_bias.alias("individual_bias")
])

# Print the individual accuracy and bias for each week
for row in crossvalidation_df.to_dicts():
    id = row['unique_id']
    date = row['ds'] 
    accuracy = row['individual_accuracy']
    bias = row['individual_bias']
    print(f"{id}, {date}, Individual Accuracy: {accuracy:.4f}, Individual Bias: {bias:.4f}")

# For groups of forecasts
group_accuracy = 1 - (errors.abs().sum() / crossvalidation_df['y'].sum())
group_bias = (crossvalidation_df['Ensemble'].sum() / crossvalidation_df['y'].sum()) - 1

# Print the average group accuracy and group bias over all folds for the ensemble model
print('Average Group Accuracy: ', round(group_accuracy, 4))
print('Average Group Bias: ', round(group_bias, 4))


# Fit the models on the entire dataset
sf.fit(y_cl4_over_9)

# Instantiate the ConformalIntervals class
prediction_intervals = ConformalIntervals()

# Generate 24 months forecasts
forecasts_df = sf.forecast(h=52*2, prediction_intervals=prediction_intervals, level=[95], id_col='unique_id', sort_df=True)

# Apply the non-negative constraint to the forecasts of individual models
forecasts_df = forecasts_df.with_columns([
    pl.when(pl.col('AutoARIMA') < 0).then(0).otherwise(pl.col('AutoARIMA')).alias('AutoARIMA'),
    pl.when(pl.col('AutoETS') < 0).then(0).otherwise(pl.col('AutoETS')).alias('AutoETS'),
    pl.when(pl.col('DynamicOptimizedTheta') < 0).then(0).otherwise(pl.col('DynamicOptimizedTheta')).alias('DynamicOptimizedTheta'),
    pl.when(pl.col('IMAPA') < 0).then(0).otherwise(pl.col('IMAPA')).alias('IMAPA'),

    pl.when(pl.col('AutoARIMA-lo-95') < 0).then(0).otherwise(pl.col('AutoARIMA-lo-95')).alias('AutoARIMA-lo-95'),
    pl.when(pl.col('AutoETS-lo-95') < 0).then(0).otherwise(pl.col('AutoETS-lo-95')).alias('AutoETS-lo-95'),
    pl.when(pl.col('DynamicOptimizedTheta-lo-95') < 0).then(0).otherwise(pl.col('DynamicOptimizedTheta-lo-95')).alias('DynamicOptimizedTheta-lo-95'),
    pl.when(pl.col('IMAPA-lo-95') < 0).then(0).otherwise(pl.col('IMAPA-lo-95')).alias('IMAPA-lo-95')
])

# Calculate the ensemble forecast
ensemble_forecast = forecasts_df[['AutoARIMA', 'AutoETS', 'DynamicOptimizedTheta', 'IMAPA']].mean(axis=1)

# Calculate the lower and upper prediction intervals for the ensemble forecast
ensemble_lo_95 = forecasts_df[['AutoARIMA-lo-95', 'AutoETS-lo-95', 'DynamicOptimizedTheta-lo-95', 'IMAPA-lo-95']].mean(axis=1)
ensemble_hi_95 = forecasts_df[['AutoARIMA-hi-95', 'AutoETS-hi-95', 'DynamicOptimizedTheta-hi-95', 'IMAPA-hi-95']].mean(axis=1)

# Create Series for the ensemble forecast and its prediction intervals
ensemble_forecast_series = pl.Series('EnsembleForecast', ensemble_forecast)
ensemble_lo_95_series = pl.Series('Ensemble-lo-95', ensemble_lo_95)
ensemble_hi_95_series = pl.Series('Ensemble-hi-95', ensemble_hi_95)

# Add the ensemble forecast and its prediction intervals as new columns to the DataFrame
forecasts_df = forecasts_df.with_columns([ensemble_forecast_series, ensemble_lo_95_series, ensemble_hi_95_series])

# Round the ensemble forecast and prediction intervals and convert to integer
forecasts_df = forecasts_df.with_columns([
    pl.col("EnsembleForecast").round().cast(pl.Int32),
    pl.col("Ensemble-lo-95").round().cast(pl.Int32),
    pl.col("Ensemble-hi-95").round().cast(pl.Int32)
])

# Split the unique_id concat into the original columns
def split_unique_id(unique_id):
    parts = unique_id.split('_')
    return parts if len(parts) >= 4 else (parts + [None] * (4 - len(parts)))

forecasts_df = (
    forecasts_df
    .with_columns([
        pl.col('unique_id').apply(lambda uid: split_unique_id(uid)[0]).alias('MaterialID'),
        pl.col('unique_id').apply(lambda uid: split_unique_id(uid)[1]).alias('SalesOrg'),
        pl.col('unique_id').apply(lambda uid: split_unique_id(uid)[2]).alias('DistrChan'),
        pl.col('unique_id').apply(lambda uid: split_unique_id(uid)[3]).alias('CL4'),
    ])
    .drop('unique_id')
)

# Rename ‘ds’ to ‘WeekDate’
forecasts_df = forecasts_df.rename({'ds': 'WeekDate'})

# Reorder the columns
forecasts_df = forecasts_df.select([
    "MaterialID",
    "SalesOrg",
    "DistrChan", 
    "CL4",
    "WeekDate",
    "EnsembleForecast", 
    "Ensemble-lo-95", 
    "Ensemble-hi-95",
    "AutoARIMA", 
    "AutoARIMA-lo-95", 
    "AutoARIMA-hi-95", 
    "AutoETS", 
    "AutoETS-lo-95", 
    "AutoETS-hi-95", 
    "DynamicOptimizedTheta", 
    "DynamicOptimizedTheta-lo-95", 
    "DynamicOptimizedTheta-hi-95",
    "IMAPA", 
    "IMAPA-lo-95", 
    "IMAPA-hi-95"
]) 

# Create an empty list
forecasts_list = []

# Append each row to the list
for row in forecasts_df.to_dicts():
    forecasts_list.append(row)

# Print the list
for forecast in forecasts_list:
    print(forecast) {'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 6, 2, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 6, 9, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 6, 16, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 6, 23, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 6, 30, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 7, 7, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 7, 14, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 7, 21, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 7, 28, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 8, 4, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 8, 11, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 8, 18, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 8, 25, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 9, 1, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 9, 8, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 9, 15, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 9, 22, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 9, 29, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 10, 6, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 10, 13, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 10, 20, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 10, 27, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 11, 3, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 11, 10, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta-hi-95': 0.0, 'IMAPA': 0.0, 'IMAPA-lo-95': 0.0, 'IMAPA-hi-95': 0.0}
{'MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389', 'WeekDate': datetime.datetime(2025, 11, 17, 0, 0), 'EnsembleForecast': 0, 'Ensemble-lo-95': 0, 'Ensemble-hi-95': 0, 'AutoARIMA': 0.0, 'AutoARIMA-lo-95': 0.0, 'AutoARIMA-hi-95': 0.0, 'AutoETS': 0.0, 'AutoETS-lo-95': 0.0, 'AutoETS-hi-95': 0.0, 'DynamicOptimizedTheta': 0.0, 'DynamicOptimizedTheta-lo-95': 0.0, 'DynamicOptimizedTheta- why is MaterialID': '22027032', 'SalesOrg': 'US01', 'DistrChan': '8', 'CL4': '1108389' still showing up even though it was removed in the first block?