import dash
from dash import dcc, html
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc
import plotly.express as px
import pandas as pd
import numpy as np
from sklearn.linear_model import LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import PolynomialFeatures
from sklearn.pipeline import make_pipeline
import plotly.graph_objects as go
from functools import lru_cache
import warnings
warnings.filterwarnings('ignore')
# Removed ARIMA import and ARIMA_AVAILABLE flag
# ------------------------------------------------------------------------------
# 1. Constants and Configuration
# ------------------------------------------------------------------------------
ACCURACY_THRESHOLDS = {'EXCELLENT': 85, 'GOOD': 60, 'NEEDS_IMPROVEMENT': 0}
FEEDBACK_MESSAGES = {
'EXCELLENT': "✅ Accuracy: {score}/100. Excellent!",
'GOOD': "🔶 Accuracy: {score}/100. Good attempt.",
'NEEDS_IMPROVEMENT': "❌ Accuracy: {score}/100. Needs improvement. Model: {model_val:,.0f}"
}
# ------------------------------------------------------------------------------
# 2. Dataset Loading
# ------------------------------------------------------------------------------
try:
df = (pd.read_csv("global.1751_2021.csv")
.rename(columns={
'Total carbon emissions from fossil fuel consumption and cement production (million metric tons of C)': 'Total_Emissions',
'Carbon emissions from solid fuel consumption': 'Solid_Consumption',
'Carbon emissions from liquid fuel consumption': 'Liquid_Consumption',
'Carbon emissions from gas fuel consumption': 'Gas_Consumption',
'Carbon emissions from cement production': 'Cement_Production',
'Carbon emissions from gas flaring': 'Gas_Flaring',
'Per capita carbon emissions (metric tons of carbon; after 1949 only)': 'PerCapita_Emissions'
}))
df['Year'] = pd.to_numeric(df['Year'], errors='coerce')
df = df.dropna(subset=['Year']).sort_values(by='Year').reset_index(drop=True)
start_year, end_year = int(df['Year'].min()), int(df['Year'].max())
emission_columns = ['Total_Emissions', 'Solid_Consumption', 'Liquid_Consumption', 'Gas_Consumption', 'Cement_Production', 'Gas_Flaring']
for col in emission_columns:
df[col] = pd.to_numeric(df[col], errors='coerce').fillna(0)
df = df.dropna(subset=['Total_Emissions'])
except FileNotFoundError:
print("Error: 'global.1751_2021.csv' not found. Generating sample data.")
start_year, end_year = 1900, 2020
years = np.arange(start_year, end_year + 1)
num_records = len(years)
total_emissions = np.linspace(100, 10000, num_records) + np.random.normal(0, 500, num_records)
total_emissions = np.maximum(0, total_emissions).astype(int)
df = pd.DataFrame({
'Year': years,
'Total_Emissions': total_emissions,
'Solid_Consumption': (total_emissions * 0.4).astype(int),
'Liquid_Consumption': (total_emissions * 0.3).astype(int),
'Gas_Consumption': (total_emissions * 0.15).astype(int),
'Cement_Production': (total_emissions * 0.1).astype(int),
'Gas_Flaring': (total_emissions * 0.05).astype(int),
'PerCapita_Emissions': np.linspace(1, 20, num_records)
})
emission_columns = ['Total_Emissions', 'Solid_Consumption', 'Liquid_Consumption', 'Gas_Consumption', 'Cement_Production', 'Gas_Flaring']
display_names = {'Total_Emissions': 'Total Emissions', 'Solid_Consumption': 'Solid Fuel', 'Liquid_Consumption': 'Liquid Fuel',
'Gas_Consumption': 'Gas Fuel', 'Cement_Production': 'Cement Production', 'Gas_Flaring': 'Gas Flaring'}
# ------------------------------------------------------------------------------
# 3. Enhanced Model Functions with Caching
# ------------------------------------------------------------------------------
def get_model(model_type):
"""Get model instance based on type"""
models = {
'Linear': LinearRegression(),
'Polynomial (Degree 2)': make_pipeline(PolynomialFeatures(2), LinearRegression()),
'Random Forest': RandomForestRegressor(n_estimators=50, random_state=42),
# Removed ARIMA
}
return models.get(model_type, LinearRegression())
@lru_cache(maxsize=128)
def cached_model_prediction(model_type, target_column, year):
"""Cached model predictions for performance"""
return get_model_and_prediction_with_bounds(model_type, target_column, np.array([[year]]))
def get_model_and_prediction_with_bounds(model_type, target_column, years_to_predict_arr):
"""Enhanced model training with multiple algorithms"""
valid_indices = df[target_column].notna()
X_train = df.loc[valid_indices, ['Year']]
y_train = df.loc[valid_indices, target_column]
if len(X_train) < 3:
return np.zeros_like(years_to_predict_arr).flatten(), np.zeros_like(years_to_predict_arr).flatten(), np.zeros_like(years_to_predict_arr).flatten()
try:
# Removed ARIMA specific logic
model = get_model(model_type)
model.fit(X_train, y_train)
predictions = model.predict(years_to_predict_arr)
residuals = y_train - model.predict(X_train)
except Exception as e:
print(f"Error training/predicting with {model_type} for {target_column}: {e}. Falling back to Linear Regression.")
model = LinearRegression()
model.fit(X_train, y_train)
predictions = model.predict(years_to_predict_arr)
residuals = y_train - model.predict(X_train)
rmse = np.sqrt(np.mean(residuals**2)) if len(residuals) > 0 else 0
time_span = end_year - start_year
bounds = []
for year_val in years_to_predict_arr.flatten():
distance = max(0, year_val - end_year)
uncertainty_factor = 1 + (distance / (time_span / 5)) * 1.5
error_margin = rmse * 1.96 * uncertainty_factor
pred_val = predictions[0] if len(predictions) == 1 else model.predict(np.array([[year_val]]))[0]
bounds.append((max(0, pred_val - error_margin), max(0, pred_val + error_margin)))
lower_bounds, upper_bounds = zip(*bounds)
return predictions, np.array(lower_bounds), np.array(upper_bounds)
def compare_all_models(target_column, year):
"""Compare predictions from all available models"""
models = ['Linear', 'Polynomial (Degree 2)', 'Random Forest']
# Removed ARIMA from this list
results = {}
for model_type in models:
try:
pred, lower, upper = get_model_and_prediction_with_bounds(model_type, target_column, np.array([[year]]))
results[model_type] = {'prediction': pred[0], 'lower': lower[0], 'upper': upper[0]}
except Exception as e:
print(f"Error comparing model {model_type} for {target_column} at {year}: {e}")
results[model_type] = {'prediction': 0, 'lower': 0, 'upper': 0}
return results
# ------------------------------------------------------------------------------
# 4. Helper Functions
# ------------------------------------------------------------------------------
def validate_inputs(year, user_total, *other_predictions):
"""Validate user inputs, specifically requiring Total_Emissions for overall score"""
errors = []
if year is None:
errors.append("Year must be provided.")
elif year <= end_year:
errors.append(f"Year must be greater than current data range ({end_year}).")
# Check if Total_Emissions is provided, if not, add a warning
if user_total is None:
errors.append("To get an overall score, you must provide a prediction for 'Total Emissions'.")
elif user_total < 0:
errors.append("'Total Emissions' prediction cannot be negative.")
# Check other predictions for negativity
if any(p is not None and p < 0 for p in other_predictions):
errors.append("Other predictions cannot be negative.")
return errors
def calculate_accuracy_score(user_val, model_val, reference_max):
"""Calculate accuracy score with improved logic"""
if user_val is None or reference_max == 0:
return 0
difference = abs(user_val - model_val)
scale_factor = max(reference_max, abs(model_val), 1)
score = 100 - (difference / scale_factor) * 100
return max(0, int(score))
def get_feedback_message(score, model_val):
"""Get feedback message based on score"""
if score > ACCURACY_THRESHOLDS['EXCELLENT']:
return FEEDBACK_MESSAGES['EXCELLENT'].format(score=score), "text-success"
elif score > ACCURACY_THRESHOLDS['GOOD']:
return FEEDBACK_MESSAGES['GOOD'].format(score=score), "text-warning"
else:
return FEEDBACK_MESSAGES['NEEDS_IMPROVEMENT'].format(score=score, model_val=model_val), "text-danger"
# ------------------------------------------------------------------------------
# 5. UI Components
# ------------------------------------------------------------------------------
def create_prediction_card(id_suffix, label_text, placeholder_text):
"""Create prediction input card with validation"""
return dbc.Col(
dbc.Card(
dbc.CardBody([
html.H6(label_text, className="card-title"),
dbc.Input(
id=f'user-prediction-{id_suffix}',
type='number',
placeholder=placeholder_text,
min=0,
className="mb-2",
valid=False,
invalid=False
),
html.Div(id=f'feedback-{id_suffix}', className="text-muted small")
]),
className="mb-3 shadow-sm"
),
md=4, lg=3
)
def create_loading_component(component_id, children):
"""Create loading wrapper for components"""
return dcc.Loading(
id=f"loading-{component_id}",
type="default",
children=children
)
# ------------------------------------------------------------------------------
# 6. App Layout
# ------------------------------------------------------------------------------
SECTIONS = {
'historical': {'title': '1. Historical Trends', 'description': 'Explore the evolution of carbon emissions over time.', 'icon': '📊'},
'prediction': {'title': '2. Make Your Prediction', 'description': 'Forecast emissions and challenge AI models!', 'icon': '✍️'},
'results': {'title': '3. Your Results & Analysis', 'description': 'Check your accuracy and improve your streak.', 'icon': '🏆'},
'comparison': {'title': '4. Multi-Model Comparison', 'description': 'Compare your prediction against ALL AI models simultaneously.', 'icon': '⚖️'}
}
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.SUPERHERO],
meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1.0'}])
app.title = "Carbon Emissions Prediction Challenge"
server = app.server
app.layout = dbc.Container(fluid=True, className="p-4", children=[
dbc.Row(dbc.Col(html.H1("🚀 Carbon Emissions AI Challenge!", className="text-center my-4 text-primary display-4"))),
dbc.Row(dbc.Col(html.P("Beat AI models at predicting the future of carbon emissions!", className="text-center text-light lead mb-5"))),
dcc.Store(id='gamification-store', data={'predictions': [], 'best_total_score': 0, 'streak': 0}),
dcc.Store(id='last-submission-data', data={}),
dbc.Row([
dbc.Col(md=3, className="mb-4", style={'overflowY': 'auto', 'maxHeight': 'calc(100vh - 100px)'}, children=[
dbc.Card([
dbc.CardHeader(html.H5("AI Model Selection", className="card-title text-info")),
dbc.CardBody([
html.P("Choose which AI model to compete against:", className="text-muted small"),
dbc.Select(
id='model-selector',
options=[
{'label': '🔵 Linear Regression', 'value': 'Linear'},
{'label': '🟣 Polynomial AI (Degree 2)', 'value': 'Polynomial (Degree 2)'},
{'label': '🟢 Random Forest AI', 'value': 'Random Forest'},
], # Removed ARIMA option
value='Random Forest',
className="mb-3"
)
])
], className="shadow-lg mb-4 border-info"),
html.H4("🎯 Challenge Sections", className="mb-3 text-secondary"),
*[dbc.Card(
dbc.CardBody([
html.H5(f"{section_data['icon']} {section_data['title']}", className="card-title"),
html.P(section_data['description'], className="card-text text-muted small"),
# dbc.Button(f"Go to {section_data['title'].split('.')[0]}", id=f'nav-button-{section_id}',
# color="primary", className="w-100 mt-2")
dbc.Button(f"Explore Historical Data" if section_id == 'historical'
else f"Start Predicting" if section_id == 'prediction'
else f"View My Results" if section_id == 'results'
else f"Compare Models",
id=f'nav-button-{section_id}',
color="primary", className="w-100 mt-2")
]), className="mb-3 shadow-sm border-primary"
) for section_id, section_data in SECTIONS.items()],
]),
dbc.Col(md=9, children=[
# Historical Section (Initial state: display 'block')
html.Div(id='content-historical', className="mb-4", style={'display': 'block'}, children=[
dbc.Card([
dbc.CardHeader(html.H3("📊 Historical Emissions Analysis", className="card-title text-primary")),
dbc.CardBody([
html.P("Study historical patterns to make better predictions.", className="text-muted mb-4"),
dbc.Label("Categories to Display:", html_for="historical-emission-selector"),
dcc.Dropdown(
id='historical-emission-selector',
options=[{'label': display_names[col], 'value': col} for col in emission_columns],
value=['Total_Emissions'], multi=True, className="mb-3"
),
create_loading_component('historical', dcc.Graph(id='historical-emissions-graph'))
])
], className="shadow-lg")
]),
# Prediction Section (Initial state: display 'none')
html.Div(id='content-prediction', className="mb-4", style={'display': 'none'}, children=[
dbc.Card([
dbc.CardHeader(html.H3("🎯 Make Your Prediction!", className="card-title text-success")),
dbc.CardBody([
# WARNING MESSAGE ADDED HERE
dbc.Alert("💡 Hint: To get an overall score, you must provide a prediction for 'Total Emissions'!", color="info", className="mb-4"),
dbc.Row([
dbc.Col([
dbc.Label("Year to Predict:", html_for="prediction-year-input"),
dbc.Input(id='prediction-year-input', type='number', value=end_year + 5,
min=end_year + 1, step=1, className="mb-3")
], md=6),
dbc.Col([
html.Div(id='input-validation-feedback', className="text-center")
], md=6)
]),
html.H4("Your Estimates (million metric tons of C)", className="my-3 text-secondary"),
dbc.Row([
create_prediction_card('total', display_names['Total_Emissions'], 'Ex: 12000'),
create_prediction_card('solid', display_names['Solid_Consumption'], 'Ex: 5000'),
create_prediction_card('liquid', display_names['Liquid_Consumption'], 'Ex: 4000'),
create_prediction_card('gas', display_names['Gas_Consumption'], 'Ex: 2000'),
create_prediction_card('cement', display_names['Cement_Production'], 'Ex: 1000'),
create_prediction_card('flaring', display_names['Gas_Flaring'], 'Ex: 500'),
]),
dbc.Button('🚀 Challenge the AI Models!', id='submit-prediction-button', n_clicks=0,
color="success", className="w-100 mt-4", size="lg"),
html.Div(id='prediction-status-message', className="mt-3 text-center")
])
], className="shadow-lg")
]),
# Results Section (Initial state: display 'none')
html.Div(id='content-results', className="mb-4", style={'display': 'none'}, children=[
dbc.Card([
dbc.CardHeader(html.H3("🏆 Your Performance Dashboard", className="card-title text-info")),
dbc.CardBody([
dbc.Row(className="mb-4 text-center", children=[
dbc.Col(dbc.Card(dbc.CardBody([
html.H5("Latest Score", className="card-title"),
html.H3(id='current-total-accuracy-display', className="text-success")
]), className="shadow-sm border-success"), md=4),
dbc.Col(dbc.Card(dbc.CardBody([
html.H5("Personal Best", className="card-title"),
html.H3(id='best-total-score-display', className="text-info")
]), className="shadow-sm border-info"), md=4),
dbc.Col(dbc.Card(dbc.CardBody([
html.H5("Winning Streak", className="card-title"),
html.H3(id='streak-display', className="text-warning")
]), className="shadow-sm border-warning"), md=4),
]),
create_loading_component('scorecard', dcc.Graph(id='individual-accuracy-scorecard')),
html.Div(id='prediction-history-table-container',
style={'overflowX': 'auto', 'maxHeight': '300px'}, className="mb-4"),
])
], className="shadow-lg")
]),
# Multi-Model Comparison Section (Initial state: display 'none')
html.Div(id='content-comparison', className="mb-4", style={'display': 'none'}, children=[
dbc.Card([
dbc.CardHeader(html.H3("⚖️ Multi-Model AI Showdown", className="card-title text-info")),
dbc.CardBody([
html.P("See how you performed against ALL AI models at once!", className="text-muted mb-4"),
dbc.Label("Select Category:", html_for="comparison-emission-selector"),
dcc.Dropdown(
id='comparison-emission-selector',
options=[{'label': display_names[col], 'value': col} for col in emission_columns],
value='Total_Emissions', clearable=False, className="mb-4"
),
create_loading_component('comparison', dcc.Graph(id='prediction-comparison-graph')),
])
], className="shadow-lg")
])
])
])
])
# ------------------------------------------------------------------------------
# 7. Streamlined Callbacks
# ------------------------------------------------------------------------------
# Callback para la navegación entre secciones
@app.callback(
[Output('content-historical', 'style'),
Output('content-prediction', 'style'),
Output('content-results', 'style'),
Output('content-comparison', 'style')],
[Input('nav-button-historical', 'n_clicks'),
Input('nav-button-prediction', 'n_clicks'),
Input('nav-button-results', 'n_clicks'),
Input('nav-button-comparison', 'n_clicks')]
)
def navigate_sections(n_hist, n_pred, n_res, n_comp):
ctx = dash.callback_context
if not ctx.triggered:
# Initial load: display historical, hide others
return {'display': 'block'}, {'display': 'none'}, {'display': 'none'}, {'display': 'none'}
button_id = ctx.triggered[0]['prop_id'].split('.')[0]
styles = {
'content-historical': {'display': 'none'},
'content-prediction': {'display': 'none'},
'content-results': {'display': 'none'},
'content-comparison': {'display': 'none'}
}
if button_id == 'nav-button-historical':
styles['content-historical'] = {'display': 'block'}
elif button_id == 'nav-button-prediction':
styles['content-prediction'] = {'display': 'block'}
elif button_id == 'nav-button-results':
styles['content-results'] = {'display': 'block'}
elif button_id == 'nav-button-comparison':
styles['content-comparison'] = {'display': 'block'}
return styles['content-historical'], styles['content-prediction'], \
styles['content-results'], styles['content-comparison']
# Callback para el gráfico histórico
@app.callback(
Output('historical-emissions-graph', 'figure'),
Input('historical-emission-selector', 'value')
)
def update_historical_graph(selected_types):
if not selected_types:
selected_types = ['Total_Emissions']
fig = px.area(df, x='Year', y=selected_types,
title='Historical Carbon Emissions by Category',
labels={col: display_names[col] for col in selected_types},
template='plotly_dark')
fig.update_layout(hovermode="x unified",
xaxis_title="Year",
yaxis_title="Emissions (million metric tons of C)",
transition_duration=500)
return fig
# Callback principal para procesar predicciones y actualizar resultados
@app.callback(
[Output('input-validation-feedback', 'children'),
Output('prediction-status-message', 'children'),
Output('feedback-total', 'children'),
Output('feedback-solid', 'children'),
Output('feedback-liquid', 'children'),
Output('feedback-gas', 'children'),
Output('feedback-cement', 'children'),
Output('feedback-flaring', 'children'),
Output('gamification-store', 'data'),
Output('last-submission-data', 'data'),
Output('current-total-accuracy-display', 'children'),
Output('best-total-score-display', 'children'),
Output('streak-display', 'children'),
Output('individual-accuracy-scorecard', 'figure'),
Output('prediction-history-table-container', 'children')
],
[Input('submit-prediction-button', 'n_clicks'),
Input('prediction-year-input', 'value'),
Input('user-prediction-total', 'value'),
Input('user-prediction-solid', 'value'),
Input('user-prediction-liquid', 'value'),
Input('user-prediction-gas', 'value'),
Input('user-prediction-cement', 'value'),
Input('user-prediction-flaring', 'value'),
Input('model-selector', 'value')
],
[State('gamification-store', 'data'),
State('last-submission-data', 'data')]
)
def process_prediction_and_update_results(
n_clicks, year, user_total, user_solid, user_liquid, user_gas, user_cement, user_flaring,
selected_model, gamification_data, last_submission_data_state
):
ctx = dash.callback_context
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
# Initialize all outputs
initial_feedback_messages = {col: "" for col in emission_columns}
initial_validation_feedback = ""
initial_prediction_status = ""
initial_gamification_data = gamification_data
initial_last_submission_data = last_submission_data_state
initial_current_score = "N/A"
initial_best_score = gamification_data['best_total_score']
initial_streak = gamification_data['streak']
initial_scorecard_fig = go.Figure()
initial_history_table = html.Div()
# --- Handling initial load or non-submit button triggers (e.g., model selector change) ---
if triggered_id != 'submit-prediction-button' or n_clicks == 0:
if last_submission_data_state:
initial_current_score = f"{last_submission_data_state.get('latest_score', 0)}%"
current_total_score, scorecard_fig, history_table = \
_generate_results_outputs(last_submission_data_state, gamification_data)
return (initial_validation_feedback, initial_prediction_status,
initial_feedback_messages['Total_Emissions'], initial_feedback_messages['Solid_Consumption'],
initial_feedback_messages['Liquid_Consumption'], initial_feedback_messages['Gas_Consumption'],
initial_feedback_messages['Cement_Production'], initial_feedback_messages['Gas_Flaring'],
initial_gamification_data, initial_last_submission_data,
current_total_score, initial_best_score, initial_streak,
scorecard_fig, history_table)
return (initial_validation_feedback, initial_prediction_status,
initial_feedback_messages['Total_Emissions'], initial_feedback_messages['Solid_Consumption'],
initial_feedback_messages['Liquid_Consumption'], initial_feedback_messages['Gas_Consumption'],
initial_feedback_messages['Cement_Production'], initial_feedback_messages['Gas_Flaring'],
initial_gamification_data, initial_last_submission_data,
initial_current_score, initial_best_score, initial_streak,
initial_scorecard_fig, initial_history_table)
# --- If submit button was clicked, proceed with prediction logic ---
user_predictions = {
'Total_Emissions': user_total, 'Solid_Consumption': user_solid, 'Liquid_Consumption': user_liquid,
'Gas_Consumption': user_gas, 'Cement_Production': user_cement, 'Gas_Flaring': user_flaring
}
# Pass user_total explicitly for validation
errors = validate_inputs(year, user_total, user_solid, user_liquid, user_gas, user_cement, user_flaring)
if errors:
return (
html.Div([html.P(e, className="text-danger") for e in errors]),
"",
*["" for _ in emission_columns],
gamification_data,
last_submission_data_state,
initial_current_score, initial_best_score, initial_streak,
initial_scorecard_fig, initial_history_table
)
total_accuracy_score = 0
feedback_children = {col: "" for col in emission_columns}
model_predictions = {}
current_reference_max = df['Total_Emissions'].max()
# The competing model for score calculation is always the one selected in the dropdown
competing_model_for_score = selected_model
for emission_type in emission_columns:
user_val = user_predictions[emission_type]
if user_val is not None:
model_pred, _, _ = cached_model_prediction(competing_model_for_score, emission_type, year)
model_val = model_pred[0]
score = calculate_accuracy_score(user_val, model_val, current_reference_max)
feedback_msg, feedback_class = get_feedback_message(score, model_val)
feedback_children[emission_type] = html.Span(feedback_msg, className=feedback_class)
# Only Total_Emissions score counts for the main score
if emission_type == 'Total_Emissions':
total_accuracy_score = score
# Always store model_predictions for the comparison graph (ALL models)
all_model_comparison_results = compare_all_models(emission_type, year)
model_predictions[emission_type] = {
'year': year,
'user_prediction': user_val,
'model_predictions': all_model_comparison_results,
'selected_competing_model': competing_model_for_score,
'accuracy_score': score,
'model_value_for_feedback': model_val
}
else:
feedback_children[emission_type] = ""
# Update gamification data
gamification_data['predictions'].append({
'year': year,
'user_predictions': {k: v for k, v in user_predictions.items() if v is not None},
'model_predictions_summary': {k: v['model_value_for_feedback'] for k, v in model_predictions.items() if v['user_prediction'] is not None},
'scores': {k: v['accuracy_score'] for k, v in model_predictions.items() if v['user_prediction'] is not None},
'overall_score': total_accuracy_score,
'competing_model_used': competing_model_for_score, # Store the model used for THIS score
'timestamp': pd.Timestamp.now().strftime('%Y-%m-%d %H:%M:%S')
})
# Update streak and best score
prev_streak = gamification_data['streak']
if total_accuracy_score >= ACCURACY_THRESHOLDS['GOOD']:
gamification_data['streak'] = prev_streak + 1
else:
gamification_data['streak'] = 0
if total_accuracy_score > gamification_data['best_total_score']:
gamification_data['best_total_score'] = total_accuracy_score
# Store last successful submission data
updated_last_submission_data = {
'year': year,
'user_predictions': user_predictions,
'model_predictions_detailed': model_predictions,
'latest_score': total_accuracy_score,
'display_names': display_names,
'competing_model_used': competing_model_for_score,
}
# Generate results outputs
current_total_score_display, scorecard_fig, history_table = \
_generate_results_outputs(updated_last_submission_data, gamification_data)
prediction_status_message = html.Div([
html.P(f"Prediction for {year} submitted!", className="text-success")
])
return (
"",
prediction_status_message,
feedback_children['Total_Emissions'],
feedback_children['Solid_Consumption'],
feedback_children['Liquid_Consumption'],
feedback_children['Gas_Consumption'],
feedback_children['Cement_Production'],
feedback_children['Gas_Flaring'],
gamification_data,
updated_last_submission_data,
current_total_score_display,
f"{gamification_data['best_total_score']}%",
f"{gamification_data['streak']}",
scorecard_fig,
history_table
)
def _generate_results_outputs(last_submission_data, gamification_data):
"""Helper function to generate score card and history table"""
year = last_submission_data['year']
user_predictions = last_submission_data['user_predictions']
model_predictions_detailed = last_submission_data['model_predictions_detailed']
display_names = last_submission_data['display_names']
latest_score = last_submission_data['latest_score']
current_total_score_display = f"{latest_score}%"
scores_data = []
for col, pred_info in model_predictions_detailed.items():
if user_predictions.get(col) is not None:
scores_data.append({
'Category': display_names[col],
'Score': pred_info['accuracy_score'],
'User Value': user_predictions[col],
'Model Value': pred_info['model_value_for_feedback']
})
scorecard_df = pd.DataFrame(scores_data)
scorecard_fig = go.Figure()
if not scorecard_df.empty:
scorecard_fig = px.bar(scorecard_df, x='Category', y='Score',
title=f'Accuracy for each Emission Category (Year {year})',
color='Score',
color_continuous_scale=px.colors.sequential.Plotly3,
text_auto=True,
hover_data={'User Value': True, 'Model Value': True, 'Score': ':.0f'})
scorecard_fig.update_layout(yaxis_range=[0, 100], template='plotly_dark')
history_records = gamification_data['predictions']
history_table_rows = []
if history_records:
tbody_rows = []
for entry in reversed(history_records):
comp_model_display = entry.get('competing_model_used', 'N/A')
for category, user_val in entry['user_predictions'].items():
if category in entry['model_predictions_summary']:
ai_pred_val = entry['model_predictions_summary'][category]
score = entry['scores'].get(category, 0)
tbody_rows.append(html.Tr([
html.Td(entry['timestamp']),
html.Td(entry['year']),
html.Td(display_names[category]),
html.Td(f"{user_val:,.0f}"),
html.Td(f"{ai_pred_val:,.0f}"),
html.Td(comp_model_display), # AI Used Column
html.Td(f"{score}%")
]))
history_table = dbc.Table(tbody_rows, bordered=True, hover=True, striped=True,
color="dark", className="mt-3")
history_table.children.insert(0, html.Thead(html.Tr([
html.Th("Timestamp"), html.Th("Year"), html.Th("Category"),
html.Th("Your Prediction"), html.Th("AI Prediction"), html.Th("AI Used"), html.Th("Score")
])))
else:
history_table = html.P("No prediction history yet.", className="text-muted text-center mt-3")
return current_total_score_display, scorecard_fig, history_table
# Callback para actualizar el gráfico de comparación (separado del submit)
@app.callback(
Output('prediction-comparison-graph', 'figure'),
[Input('comparison-emission-selector', 'value')],
[State('last-submission-data', 'data')]
)
def update_comparison_graph_from_store(selected_emission, last_submission_data):
fig = go.Figure()
if not last_submission_data:
fig.add_annotation(text="Submit a prediction first to see comparison results.",
xref="paper", yref="paper", showarrow=False,
font=dict(size=16, color="white"))
fig.update_layout(template='plotly_dark',
xaxis={'visible': False}, yaxis={'visible': False})
return fig
year = last_submission_data['year']
user_predictions = last_submission_data['user_predictions']
model_predictions_detailed = last_submission_data['model_predictions_detailed']
display_names = last_submission_data['display_names']
user_val = user_predictions.get(selected_emission)
if selected_emission not in model_predictions_detailed:
fig.add_annotation(text=f"No prediction data available for {display_names.get(selected_emission, selected_emission)}.",
xref="paper", yref="paper", showarrow=False,
font=dict(size=16, color="white"))
fig.update_layout(template='plotly_dark',
xaxis={'visible': False}, yaxis={'visible': False})
return fig
all_model_preds_for_emission = model_predictions_detailed[selected_emission]['model_predictions']
categories = []
values = []
upper_bounds = []
lower_bounds = []
if user_val is not None:
categories.append("Your Prediction")
values.append(user_val)
upper_bounds.append(user_val)
lower_bounds.append(user_val)
for model_name, data in all_model_preds_for_emission.items():
categories.append(f"{model_name} AI")
values.append(data['prediction'])
lower_bounds.append(data['lower'])
upper_bounds.append(data['upper'])
if not categories:
fig.add_annotation(text=f"No data to display for {display_names.get(selected_emission, selected_emission)}.",
xref="paper", yref="paper", showarrow=False,
font=dict(size=16, color="white"))
fig.update_layout(template='plotly_dark',
xaxis={'visible': False}, yaxis={'visible': False})
return fig
fig.add_trace(go.Bar(
x=categories,
y=values,
name='Predicted Value',
marker_color=['lightblue'] + ['lightcoral'] * (len(categories) - 1),
text=[f'{v:,.0f}' for v in values],
textposition='outside'
))
error_y_values = [0]
error_y_minus = [0]
for i, model_name in enumerate(all_model_preds_for_emission.keys()):
error_val = (upper_bounds[i+1] - lower_bounds[i+1]) / 2
error_y_values.append(error_val)
error_y_minus.append(error_val)
fig.update_traces(error_y=dict(type='data', array=error_y_values, arrayminus=error_y_minus, visible=True))
fig.update_layout(
title=f'Comparison for {display_names.get(selected_emission, selected_emission)} in Year {year}',
xaxis_title="Entity",
yaxis_title="Emissions (million metric tons of C)",
template='plotly_dark',
showlegend=False,
hovermode="x unified"
)
return fig