LeonceNsh commited on
Commit
e0aa238
·
verified ·
1 Parent(s): c2540d0

Upload 6 files

Browse files
components/behavioral_calib.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Behavioral calibration visualization component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+
9
+ from database import query
10
+
11
+
12
+ def get_elasticity_data() -> pd.DataFrame:
13
+ """Get incentive elasticity curve data."""
14
+ sql = """
15
+ SELECT
16
+ incentive_bucket,
17
+ n_trips,
18
+ carpool_rate,
19
+ avg_incentive
20
+ FROM main_marts.metrics_elasticity
21
+ ORDER BY avg_incentive
22
+ """
23
+ return query(sql)
24
+
25
+
26
+ def create_elasticity_curve(df: pd.DataFrame) -> go.Figure:
27
+ """Create elasticity curve visualization."""
28
+ if df.empty:
29
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
30
+
31
+ fig = go.Figure()
32
+
33
+ # Main line
34
+ fig.add_trace(go.Scatter(
35
+ x=df['avg_incentive'],
36
+ y=df['carpool_rate'],
37
+ mode='lines+markers',
38
+ name='Carpool Rate',
39
+ line=dict(color='#3498db', width=3),
40
+ marker=dict(size=10),
41
+ hovertemplate='Incentive: $%{x:.2f}<br>Rate: %{y:.1%}<extra></extra>'
42
+ ))
43
+
44
+ # Add annotations for buckets
45
+ for _, row in df.iterrows():
46
+ fig.add_annotation(
47
+ x=row['avg_incentive'],
48
+ y=row['carpool_rate'],
49
+ text=row['incentive_bucket'],
50
+ showarrow=True,
51
+ arrowhead=2,
52
+ ax=0,
53
+ ay=-30
54
+ )
55
+
56
+ fig.update_layout(
57
+ title='Incentive Elasticity Curve',
58
+ xaxis_title='Average Incentive ($)',
59
+ yaxis_title='Carpool Participation Rate',
60
+ yaxis=dict(tickformat='.0%'),
61
+ height=400
62
+ )
63
+
64
+ return fig
65
+
66
+
67
+ def get_model_metrics() -> dict:
68
+ """Get model performance metrics (placeholder)."""
69
+ # In production, these would come from the ML models table
70
+ return {
71
+ 'auc': 0.78,
72
+ 'rmse': 0.15,
73
+ 'accuracy': 0.82,
74
+ 'n_samples': 369831
75
+ }
76
+
77
+
78
+ def create_model_metrics_display(metrics: dict) -> go.Figure:
79
+ """Create model metrics display."""
80
+ fig = go.Figure()
81
+
82
+ categories = ['AUC', 'Accuracy', '1-RMSE']
83
+ values = [metrics['auc'], metrics['accuracy'], 1 - metrics['rmse']]
84
+
85
+ fig.add_trace(go.Scatterpolar(
86
+ r=values,
87
+ theta=categories,
88
+ fill='toself',
89
+ name='Model Performance',
90
+ line_color='#3498db'
91
+ ))
92
+
93
+ fig.update_layout(
94
+ polar=dict(
95
+ radialaxis=dict(
96
+ visible=True,
97
+ range=[0, 1]
98
+ )
99
+ ),
100
+ title=f"Model Performance (n={metrics['n_samples']:,})",
101
+ height=400
102
+ )
103
+
104
+ return fig
105
+
106
+
107
+ def get_feature_importance() -> pd.DataFrame:
108
+ """Get feature importance data (placeholder)."""
109
+ return pd.DataFrame({
110
+ 'feature': ['incentive_amount', 'distance_miles', 'is_peak_hour',
111
+ 'hour_of_day', 'day_of_week', 'avg_speed'],
112
+ 'importance': [0.35, 0.25, 0.15, 0.10, 0.08, 0.07]
113
+ })
114
+
115
+
116
+ def create_feature_importance_chart(df: pd.DataFrame) -> go.Figure:
117
+ """Create feature importance bar chart."""
118
+ if df.empty:
119
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
120
+
121
+ df = df.sort_values('importance', ascending=True)
122
+
123
+ fig = go.Figure(go.Bar(
124
+ x=df['importance'],
125
+ y=df['feature'],
126
+ orientation='h',
127
+ marker_color='#3498db',
128
+ text=df['importance'].apply(lambda x: f'{x:.1%}'),
129
+ textposition='auto',
130
+ hovertemplate='%{y}<br>Importance: %{x:.1%}<extra></extra>'
131
+ ))
132
+
133
+ fig.update_layout(
134
+ title='Feature Importance',
135
+ xaxis_title='Importance',
136
+ yaxis_title='Feature',
137
+ xaxis=dict(tickformat='.0%'),
138
+ height=400
139
+ )
140
+
141
+ return fig
142
+
143
+
144
+ def create_predicted_vs_actual() -> go.Figure:
145
+ """Create predicted vs actual scatter plot (placeholder data)."""
146
+ import numpy as np
147
+
148
+ np.random.seed(42)
149
+ n = 100
150
+ actual = np.random.uniform(0, 1, n)
151
+ predicted = actual + np.random.normal(0, 0.1, n)
152
+ predicted = np.clip(predicted, 0, 1)
153
+
154
+ fig = go.Figure()
155
+
156
+ fig.add_trace(go.Scatter(
157
+ x=actual,
158
+ y=predicted,
159
+ mode='markers',
160
+ marker=dict(color='#3498db', size=8, opacity=0.6),
161
+ hovertemplate='Actual: %{x:.2f}<br>Predicted: %{y:.2f}<extra></extra>'
162
+ ))
163
+
164
+ # Perfect prediction line
165
+ fig.add_trace(go.Scatter(
166
+ x=[0, 1],
167
+ y=[0, 1],
168
+ mode='lines',
169
+ line=dict(color='red', dash='dash'),
170
+ name='Perfect Prediction'
171
+ ))
172
+
173
+ fig.update_layout(
174
+ title='Predicted vs Actual Carpool Rate',
175
+ xaxis_title='Actual Rate',
176
+ yaxis_title='Predicted Rate',
177
+ height=400,
178
+ showlegend=False
179
+ )
180
+
181
+ return fig
components/geo_map.py ADDED
@@ -0,0 +1,163 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Geospatial map visualization component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+
9
+ from database import query
10
+
11
+
12
+ def get_corridor_data() -> pd.DataFrame:
13
+ """Get corridor location and metrics data."""
14
+ # Sample I-24 corridor segments
15
+ return pd.DataFrame({
16
+ 'segment_id': ['seg_1', 'seg_2', 'seg_3', 'seg_4', 'seg_5'],
17
+ 'segment_name': ['I-24 @ Briley Pkwy', 'I-24 @ Harding Pl', 'I-24 @ Downtown',
18
+ 'I-24 @ Shelby Ave', 'I-24 @ Spring St'],
19
+ 'latitude': [36.08, 36.10, 36.12, 36.14, 36.16],
20
+ 'longitude': [-86.70, -86.72, -86.75, -86.77, -86.79],
21
+ 'avg_speed_mph': [35, 28, 22, 30, 45],
22
+ 'congestion_level': ['Moderate', 'Severe', 'Severe', 'Moderate', 'Light'],
23
+ 'vehicle_count': [450, 520, 580, 490, 380]
24
+ })
25
+
26
+
27
+ def create_corridor_map(df: pd.DataFrame) -> go.Figure:
28
+ """Create interactive corridor map with Plotly."""
29
+ if df.empty:
30
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
31
+
32
+ # Color scale based on speed
33
+ colors = []
34
+ for speed in df['avg_speed_mph']:
35
+ if speed < 25:
36
+ colors.append('#e74c3c') # Red - congested
37
+ elif speed < 40:
38
+ colors.append('#f39c12') # Orange - slow
39
+ else:
40
+ colors.append('#27ae60') # Green - free flow
41
+
42
+ fig = go.Figure()
43
+
44
+ # Add corridor line
45
+ fig.add_trace(go.Scattermapbox(
46
+ lat=df['latitude'],
47
+ lon=df['longitude'],
48
+ mode='lines+markers',
49
+ line=dict(width=4, color='#3498db'),
50
+ marker=dict(
51
+ size=15,
52
+ color=colors,
53
+ symbol='circle'
54
+ ),
55
+ text=df.apply(
56
+ lambda r: f"{r['segment_name']}<br>Speed: {r['avg_speed_mph']} mph<br>"
57
+ f"Volume: {r['vehicle_count']} veh/hr",
58
+ axis=1
59
+ ),
60
+ hoverinfo='text',
61
+ name='I-24 Corridor'
62
+ ))
63
+
64
+ fig.update_layout(
65
+ mapbox=dict(
66
+ style='carto-positron',
67
+ center=dict(lat=36.12, lon=-86.75),
68
+ zoom=11
69
+ ),
70
+ margin=dict(l=0, r=0, t=40, b=0),
71
+ title='I-24 Corridor Traffic Conditions',
72
+ height=500
73
+ )
74
+
75
+ return fig
76
+
77
+
78
+ def create_heatmap_overlay(df: pd.DataFrame) -> go.Figure:
79
+ """Create density heatmap for traffic."""
80
+ if df.empty:
81
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
82
+
83
+ fig = go.Figure()
84
+
85
+ fig.add_trace(go.Densitymapbox(
86
+ lat=df['latitude'],
87
+ lon=df['longitude'],
88
+ z=df['vehicle_count'],
89
+ radius=30,
90
+ colorscale='YlOrRd',
91
+ showscale=True,
92
+ colorbar=dict(title='Volume')
93
+ ))
94
+
95
+ fig.update_layout(
96
+ mapbox=dict(
97
+ style='carto-positron',
98
+ center=dict(lat=36.12, lon=-86.75),
99
+ zoom=11
100
+ ),
101
+ margin=dict(l=0, r=0, t=40, b=0),
102
+ title='Traffic Density Heatmap',
103
+ height=500
104
+ )
105
+
106
+ return fig
107
+
108
+
109
+ def get_zone_stats() -> pd.DataFrame:
110
+ """Get statistics by zone."""
111
+ return pd.DataFrame({
112
+ 'zone': ['Downtown', 'Southeast', 'East Nashville', 'Antioch'],
113
+ 'avg_speed': [25, 35, 40, 45],
114
+ 'carpool_rate': [0.25, 0.18, 0.15, 0.12],
115
+ 'incentive_uptake': [0.35, 0.28, 0.22, 0.18]
116
+ })
117
+
118
+
119
+ def create_zone_comparison(df: pd.DataFrame) -> go.Figure:
120
+ """Create zone comparison chart."""
121
+ if df.empty:
122
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
123
+
124
+ fig = go.Figure()
125
+
126
+ fig.add_trace(go.Bar(
127
+ x=df['zone'],
128
+ y=df['avg_speed'],
129
+ name='Avg Speed (mph)',
130
+ marker_color='#3498db'
131
+ ))
132
+
133
+ fig.add_trace(go.Scatter(
134
+ x=df['zone'],
135
+ y=df['carpool_rate'] * 100,
136
+ name='Carpool Rate (%)',
137
+ yaxis='y2',
138
+ mode='lines+markers',
139
+ line=dict(color='#e74c3c', width=2),
140
+ marker=dict(size=10)
141
+ ))
142
+
143
+ fig.update_layout(
144
+ title='Zone Performance Comparison',
145
+ xaxis_title='Zone',
146
+ yaxis=dict(title='Speed (mph)', side='left'),
147
+ yaxis2=dict(title='Carpool Rate (%)', side='right', overlaying='y'),
148
+ height=400,
149
+ legend=dict(x=0.7, y=1.1, orientation='h')
150
+ )
151
+
152
+ return fig
153
+
154
+
155
+ def get_legend_data() -> dict:
156
+ """Get legend information for map."""
157
+ return {
158
+ 'colors': {
159
+ 'Free Flow (>40 mph)': '#27ae60',
160
+ 'Slow (25-40 mph)': '#f39c12',
161
+ 'Congested (<25 mph)': '#e74c3c'
162
+ }
163
+ }
components/incentive_analytics.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Incentive analytics visualization component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+
9
+ from database import query
10
+
11
+
12
+ def get_incentive_funnel_data() -> pd.DataFrame:
13
+ """Get incentive conversion funnel data."""
14
+ sql = """
15
+ SELECT
16
+ incentive_type,
17
+ count(*) as total_offers,
18
+ sum(case when was_accepted then 1 else 0 end) as accepts,
19
+ sum(case when was_completed then 1 else 0 end) as completions
20
+ FROM main_marts.fct_incentive_events
21
+ GROUP BY incentive_type
22
+ """
23
+ return query(sql)
24
+
25
+
26
+ def create_funnel_chart(df: pd.DataFrame) -> go.Figure:
27
+ """Create incentive conversion funnel."""
28
+ if df.empty:
29
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
30
+
31
+ # Aggregate across types
32
+ totals = df[['total_offers', 'accepts', 'completions']].sum()
33
+
34
+ fig = go.Figure(go.Funnel(
35
+ y=['Offers', 'Accepted', 'Completed'],
36
+ x=[totals['total_offers'], totals['accepts'], totals['completions']],
37
+ textinfo="value+percent initial",
38
+ marker=dict(color=['#3498db', '#2ecc71', '#27ae60'])
39
+ ))
40
+
41
+ fig.update_layout(
42
+ title='Incentive Conversion Funnel',
43
+ height=400
44
+ )
45
+
46
+ return fig
47
+
48
+
49
+ def get_spend_by_type_data() -> pd.DataFrame:
50
+ """Get spending by incentive type."""
51
+ sql = """
52
+ SELECT
53
+ incentive_type,
54
+ sum(actual_payout) as total_spend,
55
+ count(*) as n_events,
56
+ avg(actual_payout) as avg_payout
57
+ FROM main_marts.fct_incentive_events
58
+ WHERE was_completed
59
+ GROUP BY incentive_type
60
+ ORDER BY total_spend DESC
61
+ """
62
+ return query(sql)
63
+
64
+
65
+ def create_spend_chart(df: pd.DataFrame) -> go.Figure:
66
+ """Create spending breakdown chart."""
67
+ if df.empty:
68
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
69
+
70
+ fig = px.pie(
71
+ df,
72
+ values='total_spend',
73
+ names='incentive_type',
74
+ title='Spending by Incentive Type',
75
+ color_discrete_sequence=px.colors.qualitative.Set2
76
+ )
77
+
78
+ fig.update_traces(
79
+ textposition='inside',
80
+ textinfo='percent+label',
81
+ hovertemplate='%{label}<br>$%{value:,.2f}<br>%{percent}<extra></extra>'
82
+ )
83
+
84
+ fig.update_layout(height=400)
85
+
86
+ return fig
87
+
88
+
89
+ def get_effectiveness_data() -> pd.DataFrame:
90
+ """Get incentive effectiveness metrics."""
91
+ sql = """
92
+ SELECT
93
+ incentive_type,
94
+ count(*) as n_completed,
95
+ sum(actual_payout) as total_cost,
96
+ avg(actual_payout) as avg_cost
97
+ FROM main_marts.fct_incentive_events
98
+ WHERE was_completed
99
+ GROUP BY incentive_type
100
+ """
101
+ return query(sql)
102
+
103
+
104
+ def create_effectiveness_chart(df: pd.DataFrame) -> go.Figure:
105
+ """Create cost effectiveness chart."""
106
+ if df.empty:
107
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
108
+
109
+ fig = go.Figure()
110
+
111
+ fig.add_trace(go.Bar(
112
+ x=df['incentive_type'],
113
+ y=df['avg_cost'],
114
+ name='Avg Cost per Completion',
115
+ marker_color='#3498db',
116
+ text=df['avg_cost'].apply(lambda x: f'${x:.2f}'),
117
+ textposition='auto',
118
+ hovertemplate='%{x}<br>Avg Cost: $%{y:.2f}<extra></extra>'
119
+ ))
120
+
121
+ fig.update_layout(
122
+ title='Average Cost per Completed Incentive',
123
+ xaxis_title='Incentive Type',
124
+ yaxis_title='Average Cost ($)',
125
+ height=400
126
+ )
127
+
128
+ return fig
129
+
130
+
131
+ def get_uptake_trend_data() -> pd.DataFrame:
132
+ """Get incentive uptake trend over time."""
133
+ sql = """
134
+ SELECT
135
+ incentive_type,
136
+ final_outcome,
137
+ count(*) as count
138
+ FROM main_marts.fct_incentive_events
139
+ GROUP BY incentive_type, final_outcome
140
+ """
141
+ return query(sql)
142
+
143
+
144
+ def create_uptake_chart(df: pd.DataFrame) -> go.Figure:
145
+ """Create uptake by outcome chart."""
146
+ if df.empty:
147
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
148
+
149
+ fig = px.bar(
150
+ df,
151
+ x='incentive_type',
152
+ y='count',
153
+ color='final_outcome',
154
+ title='Incentive Outcomes by Type',
155
+ barmode='stack',
156
+ color_discrete_map={
157
+ 'COMPLETED': '#27ae60',
158
+ 'ACCEPTED_PENDING': '#f39c12',
159
+ 'REJECTED': '#e74c3c',
160
+ 'OFFERED_PENDING': '#95a5a6'
161
+ }
162
+ )
163
+
164
+ fig.update_layout(
165
+ xaxis_title='Incentive Type',
166
+ yaxis_title='Count',
167
+ height=400
168
+ )
169
+
170
+ return fig
components/realtime_metrics.py ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Real-time metrics KPI component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.graph_objects as go
7
+
8
+ from database import query
9
+
10
+
11
+ def get_kpi_data() -> dict:
12
+ """Get current KPI values."""
13
+ # In production, these would be computed from the latest data
14
+ return {
15
+ 'vmt_reduction_pct': 12.5,
16
+ 'avg_occupancy': 1.85,
17
+ 'peak_shift_pct': 8.3,
18
+ 'incentive_efficiency': 2.15,
19
+ 'carpool_rate': 0.23,
20
+ 'avg_speed_improvement': 15.2
21
+ }
22
+
23
+
24
+ def create_kpi_gauge(value: float, title: str, suffix: str = '%',
25
+ min_val: float = 0, max_val: float = 100,
26
+ thresholds: list = None) -> go.Figure:
27
+ """Create a gauge chart for a KPI."""
28
+ if thresholds is None:
29
+ thresholds = [max_val * 0.3, max_val * 0.7, max_val]
30
+
31
+ fig = go.Figure(go.Indicator(
32
+ mode="gauge+number",
33
+ value=value,
34
+ number={'suffix': suffix},
35
+ title={'text': title, 'font': {'size': 14}},
36
+ gauge={
37
+ 'axis': {'range': [min_val, max_val]},
38
+ 'bar': {'color': "#3498db"},
39
+ 'steps': [
40
+ {'range': [min_val, thresholds[0]], 'color': "#e74c3c"},
41
+ {'range': [thresholds[0], thresholds[1]], 'color': "#f39c12"},
42
+ {'range': [thresholds[1], max_val], 'color': "#27ae60"}
43
+ ],
44
+ 'threshold': {
45
+ 'line': {'color': "black", 'width': 2},
46
+ 'thickness': 0.75,
47
+ 'value': value
48
+ }
49
+ }
50
+ ))
51
+
52
+ fig.update_layout(
53
+ height=200,
54
+ margin=dict(l=20, r=20, t=40, b=20)
55
+ )
56
+
57
+ return fig
58
+
59
+
60
+ def create_sparkline(values: list, title: str) -> go.Figure:
61
+ """Create a sparkline chart."""
62
+ fig = go.Figure(go.Scatter(
63
+ y=values,
64
+ mode='lines',
65
+ fill='tozeroy',
66
+ line=dict(color='#3498db', width=2),
67
+ fillcolor='rgba(52, 152, 219, 0.2)'
68
+ ))
69
+
70
+ fig.update_layout(
71
+ title=dict(text=title, font=dict(size=12)),
72
+ height=100,
73
+ margin=dict(l=10, r=10, t=30, b=10),
74
+ xaxis=dict(visible=False),
75
+ yaxis=dict(visible=False),
76
+ showlegend=False
77
+ )
78
+
79
+ return fig
80
+
81
+
82
+ def create_metric_card(value: float, label: str, delta: float = None,
83
+ format_str: str = '.1f', prefix: str = '',
84
+ suffix: str = '') -> go.Figure:
85
+ """Create a metric card with optional delta."""
86
+ delta_ref = None
87
+ if delta is not None:
88
+ delta_ref = {'reference': value - delta, 'relative': True}
89
+
90
+ fig = go.Figure(go.Indicator(
91
+ mode="number+delta" if delta is not None else "number",
92
+ value=value,
93
+ number={
94
+ 'prefix': prefix,
95
+ 'suffix': suffix,
96
+ 'valueformat': format_str
97
+ },
98
+ delta=delta_ref,
99
+ title={'text': label, 'font': {'size': 14}}
100
+ ))
101
+
102
+ fig.update_layout(
103
+ height=150,
104
+ margin=dict(l=20, r=20, t=40, b=20)
105
+ )
106
+
107
+ return fig
108
+
109
+
110
+ def get_trend_data() -> dict:
111
+ """Get trend data for sparklines."""
112
+ # Placeholder - would be computed from time series
113
+ import numpy as np
114
+ np.random.seed(42)
115
+
116
+ return {
117
+ 'vmt': list(np.cumsum(np.random.randn(20)) + 100),
118
+ 'speed': list(45 + np.cumsum(np.random.randn(20) * 0.5)),
119
+ 'carpool': list(0.2 + np.cumsum(np.random.randn(20) * 0.01))
120
+ }
121
+
122
+
123
+ def render_kpi_dashboard():
124
+ """Render KPI dashboard layout."""
125
+ import gradio as gr
126
+
127
+ kpis = get_kpi_data()
128
+ trends = get_trend_data()
129
+
130
+ with gr.Column():
131
+ gr.Markdown("## Key Performance Indicators")
132
+
133
+ with gr.Row():
134
+ with gr.Column(scale=1):
135
+ gr.Plot(
136
+ value=create_kpi_gauge(
137
+ kpis['vmt_reduction_pct'],
138
+ 'VMT Reduction',
139
+ '%', 0, 25
140
+ )
141
+ )
142
+ with gr.Column(scale=1):
143
+ gr.Plot(
144
+ value=create_kpi_gauge(
145
+ kpis['avg_occupancy'],
146
+ 'Avg Occupancy',
147
+ '', 1, 3,
148
+ [1.5, 2.0, 3.0]
149
+ )
150
+ )
151
+ with gr.Column(scale=1):
152
+ gr.Plot(
153
+ value=create_kpi_gauge(
154
+ kpis['peak_shift_pct'],
155
+ 'Peak Shift',
156
+ '%', 0, 20
157
+ )
158
+ )
159
+
160
+ with gr.Row():
161
+ with gr.Column(scale=1):
162
+ gr.Plot(
163
+ value=create_metric_card(
164
+ kpis['incentive_efficiency'],
165
+ 'Cost per VMT Reduced',
166
+ prefix='$',
167
+ format_str='.2f'
168
+ )
169
+ )
170
+ with gr.Column(scale=1):
171
+ gr.Plot(
172
+ value=create_metric_card(
173
+ kpis['carpool_rate'] * 100,
174
+ 'Carpool Rate',
175
+ suffix='%',
176
+ format_str='.1f'
177
+ )
178
+ )
179
+ with gr.Column(scale=1):
180
+ gr.Plot(
181
+ value=create_metric_card(
182
+ kpis['avg_speed_improvement'],
183
+ 'Speed Improvement',
184
+ suffix='%',
185
+ format_str='.1f'
186
+ )
187
+ )
188
+
189
+ gr.Markdown("### Trends")
190
+
191
+ with gr.Row():
192
+ with gr.Column(scale=1):
193
+ gr.Plot(value=create_sparkline(trends['vmt'], 'VMT Trend'))
194
+ with gr.Column(scale=1):
195
+ gr.Plot(value=create_sparkline(trends['speed'], 'Speed Trend'))
196
+ with gr.Column(scale=1):
197
+ gr.Plot(value=create_sparkline(trends['carpool'], 'Carpool Trend'))
components/simulation_compare.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simulation comparison visualization component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+
9
+ from database import query
10
+
11
+
12
+ def get_scenario_list() -> list[str]:
13
+ """Get list of available scenarios."""
14
+ sql = """
15
+ SELECT DISTINCT scenario_name
16
+ FROM main_marts.fct_simulation_runs
17
+ ORDER BY scenario_name
18
+ """
19
+ df = query(sql)
20
+ return df['scenario_name'].tolist() if not df.empty else []
21
+
22
+
23
+ def get_scenario_comparison_data() -> pd.DataFrame:
24
+ """Get scenario comparison metrics."""
25
+ sql = """
26
+ SELECT
27
+ scenario_name,
28
+ n_agents,
29
+ treatment_avg_speed,
30
+ baseline_avg_speed,
31
+ speed_improvement_pct,
32
+ vmt_reduction_pct,
33
+ peak_reduction_pct,
34
+ treatment_spend
35
+ FROM main_marts.fct_simulation_runs
36
+ """
37
+ return query(sql)
38
+
39
+
40
+ def create_scenario_comparison_chart(df: pd.DataFrame) -> go.Figure:
41
+ """Create scenario comparison bar chart."""
42
+ if df.empty:
43
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
44
+
45
+ fig = go.Figure()
46
+
47
+ metrics = ['speed_improvement_pct', 'vmt_reduction_pct', 'peak_reduction_pct']
48
+ colors = ['#3498db', '#2ecc71', '#e74c3c']
49
+ names = ['Speed Improvement', 'VMT Reduction', 'Peak Reduction']
50
+
51
+ for metric, color, name in zip(metrics, colors, names):
52
+ fig.add_trace(go.Bar(
53
+ x=df['scenario_name'],
54
+ y=df[metric],
55
+ name=name,
56
+ marker_color=color,
57
+ text=df[metric].apply(lambda x: f'{x:.1f}%'),
58
+ textposition='auto',
59
+ hovertemplate='%{x}<br>%{y:.1f}%<extra></extra>'
60
+ ))
61
+
62
+ fig.update_layout(
63
+ title='Scenario Performance Comparison',
64
+ xaxis_title='Scenario',
65
+ yaxis_title='Improvement (%)',
66
+ barmode='group',
67
+ height=400
68
+ )
69
+
70
+ return fig
71
+
72
+
73
+ def create_cost_effectiveness_chart(df: pd.DataFrame) -> go.Figure:
74
+ """Create cost effectiveness comparison."""
75
+ if df.empty:
76
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
77
+
78
+ # Calculate cost per % improvement
79
+ df = df.copy()
80
+ df['cost_per_vmt_pct'] = df['treatment_spend'] / df['vmt_reduction_pct'].clip(lower=0.1)
81
+
82
+ fig = go.Figure()
83
+
84
+ fig.add_trace(go.Scatter(
85
+ x=df['treatment_spend'],
86
+ y=df['vmt_reduction_pct'],
87
+ mode='markers+text',
88
+ marker=dict(
89
+ size=20,
90
+ color=df['speed_improvement_pct'],
91
+ colorscale='Viridis',
92
+ showscale=True,
93
+ colorbar=dict(title='Speed Imp. %')
94
+ ),
95
+ text=df['scenario_name'],
96
+ textposition='top center',
97
+ hovertemplate='%{text}<br>Spend: $%{x:,.0f}<br>VMT Reduction: %{y:.1f}%<extra></extra>'
98
+ ))
99
+
100
+ fig.update_layout(
101
+ title='Cost vs. VMT Reduction by Scenario',
102
+ xaxis_title='Total Spend ($)',
103
+ yaxis_title='VMT Reduction (%)',
104
+ height=400
105
+ )
106
+
107
+ return fig
108
+
109
+
110
+ def create_baseline_treatment_comparison(df: pd.DataFrame, scenario: str = None) -> go.Figure:
111
+ """Create baseline vs treatment comparison for a scenario."""
112
+ if df.empty:
113
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
114
+
115
+ if scenario:
116
+ df = df[df['scenario_name'] == scenario]
117
+
118
+ if df.empty:
119
+ return go.Figure().add_annotation(text="Scenario not found", showarrow=False)
120
+
121
+ row = df.iloc[0]
122
+
123
+ fig = go.Figure()
124
+
125
+ categories = ['Avg Speed (mph)', 'VMT (scaled)', 'Peak Demand (scaled)']
126
+ baseline = [row['baseline_avg_speed'], 100, 100] # Normalized
127
+ treatment = [
128
+ row['treatment_avg_speed'],
129
+ 100 - row['vmt_reduction_pct'],
130
+ 100 - row['peak_reduction_pct']
131
+ ]
132
+
133
+ fig.add_trace(go.Bar(
134
+ name='Baseline',
135
+ x=categories,
136
+ y=baseline,
137
+ marker_color='#95a5a6'
138
+ ))
139
+
140
+ fig.add_trace(go.Bar(
141
+ name='Treatment',
142
+ x=categories,
143
+ y=treatment,
144
+ marker_color='#3498db'
145
+ ))
146
+
147
+ fig.update_layout(
148
+ title=f'Baseline vs Treatment: {scenario or "All Scenarios"}',
149
+ yaxis_title='Value',
150
+ barmode='group',
151
+ height=400
152
+ )
153
+
154
+ return fig
155
+
156
+
157
+ def get_metrics_summary(df: pd.DataFrame) -> dict:
158
+ """Get summary metrics across all scenarios."""
159
+ if df.empty:
160
+ return {}
161
+
162
+ return {
163
+ 'avg_speed_improvement': df['speed_improvement_pct'].mean(),
164
+ 'avg_vmt_reduction': df['vmt_reduction_pct'].mean(),
165
+ 'avg_peak_reduction': df['peak_reduction_pct'].mean(),
166
+ 'total_spend': df['treatment_spend'].sum(),
167
+ 'n_scenarios': len(df)
168
+ }
components/traffic_flow.py ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Traffic flow visualization component.
3
+ """
4
+
5
+ import pandas as pd
6
+ import plotly.express as px
7
+ import plotly.graph_objects as go
8
+
9
+ from database import query
10
+
11
+
12
+ def get_speed_heatmap_data() -> pd.DataFrame:
13
+ """Get data for speed heatmap."""
14
+ sql = """
15
+ SELECT
16
+ extract(hour from hour_bucket) as hour,
17
+ extract(dow from hour_bucket) as day_of_week,
18
+ avg(avg_speed_mph) as avg_speed
19
+ FROM main_marts.fct_corridor_flows
20
+ GROUP BY 1, 2
21
+ ORDER BY 2, 1
22
+ """
23
+ return query(sql)
24
+
25
+
26
+ def create_speed_heatmap(df: pd.DataFrame) -> go.Figure:
27
+ """Create speed heatmap visualization."""
28
+ if df.empty:
29
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
30
+
31
+ # Pivot for heatmap
32
+ pivot = df.pivot(index='day_of_week', columns='hour', values='avg_speed')
33
+
34
+ day_names = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
35
+
36
+ fig = go.Figure(data=go.Heatmap(
37
+ z=pivot.values,
38
+ x=[f"{h}:00" for h in range(24)],
39
+ y=[day_names[int(d)] for d in pivot.index],
40
+ colorscale='RdYlGn',
41
+ colorbar=dict(title='Speed (mph)'),
42
+ hovertemplate='%{y} %{x}<br>Speed: %{z:.1f} mph<extra></extra>'
43
+ ))
44
+
45
+ fig.update_layout(
46
+ title='Average Speed by Hour and Day',
47
+ xaxis_title='Hour of Day',
48
+ yaxis_title='Day of Week',
49
+ height=400
50
+ )
51
+
52
+ return fig
53
+
54
+
55
+ def get_hourly_volume_data() -> pd.DataFrame:
56
+ """Get hourly traffic volume data."""
57
+ sql = """
58
+ SELECT
59
+ extract(hour from hour_bucket) as hour,
60
+ time_period,
61
+ sum(vehicle_count) as total_vehicles,
62
+ avg(avg_speed_mph) as avg_speed
63
+ FROM main_marts.fct_corridor_flows
64
+ GROUP BY 1, 2
65
+ ORDER BY 1
66
+ """
67
+ return query(sql)
68
+
69
+
70
+ def create_hourly_volume_chart(df: pd.DataFrame) -> go.Figure:
71
+ """Create hourly volume bar chart."""
72
+ if df.empty:
73
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
74
+
75
+ fig = go.Figure()
76
+
77
+ # Add bars for each time period
78
+ colors = {'AM_PEAK': '#e74c3c', 'PM_PEAK': '#e67e22', 'OFF_PEAK': '#27ae60'}
79
+
80
+ for period in df['time_period'].unique():
81
+ period_df = df[df['time_period'] == period]
82
+ fig.add_trace(go.Bar(
83
+ x=period_df['hour'],
84
+ y=period_df['total_vehicles'],
85
+ name=period.replace('_', ' ').title(),
86
+ marker_color=colors.get(period, '#3498db'),
87
+ hovertemplate='Hour: %{x}<br>Vehicles: %{y:,}<extra></extra>'
88
+ ))
89
+
90
+ fig.update_layout(
91
+ title='Traffic Volume by Hour',
92
+ xaxis_title='Hour of Day',
93
+ yaxis_title='Total Vehicles',
94
+ barmode='stack',
95
+ height=400,
96
+ showlegend=True
97
+ )
98
+
99
+ return fig
100
+
101
+
102
+ def get_congestion_timeline_data() -> pd.DataFrame:
103
+ """Get congestion timeline data."""
104
+ sql = """
105
+ SELECT
106
+ hour_bucket,
107
+ corridor_id,
108
+ avg_speed_mph,
109
+ level_of_service
110
+ FROM main_marts.fct_corridor_flows
111
+ ORDER BY hour_bucket
112
+ """
113
+ return query(sql)
114
+
115
+
116
+ def create_congestion_timeline(df: pd.DataFrame) -> go.Figure:
117
+ """Create congestion timeline chart."""
118
+ if df.empty:
119
+ return go.Figure().add_annotation(text="No data available", showarrow=False)
120
+
121
+ fig = go.Figure()
122
+
123
+ fig.add_trace(go.Scatter(
124
+ x=df['hour_bucket'],
125
+ y=df['avg_speed_mph'],
126
+ mode='lines',
127
+ name='Speed',
128
+ line=dict(color='#3498db', width=2),
129
+ fill='tozeroy',
130
+ fillcolor='rgba(52, 152, 219, 0.2)',
131
+ hovertemplate='%{x}<br>Speed: %{y:.1f} mph<extra></extra>'
132
+ ))
133
+
134
+ # Add threshold lines
135
+ fig.add_hline(y=55, line_dash="dash", line_color="green",
136
+ annotation_text="Free Flow (55 mph)")
137
+ fig.add_hline(y=30, line_dash="dash", line_color="orange",
138
+ annotation_text="Congested (30 mph)")
139
+ fig.add_hline(y=15, line_dash="dash", line_color="red",
140
+ annotation_text="Severe (15 mph)")
141
+
142
+ fig.update_layout(
143
+ title='Speed Over Time',
144
+ xaxis_title='Time',
145
+ yaxis_title='Average Speed (mph)',
146
+ height=400,
147
+ yaxis=dict(range=[0, 80])
148
+ )
149
+
150
+ return fig
151
+
152
+
153
+ def render_traffic_flow_tab():
154
+ """Render the traffic flow tab content."""
155
+ import gradio as gr
156
+
157
+ with gr.Column():
158
+ gr.Markdown("## Traffic Flow Analysis")
159
+ gr.Markdown("Real-time and historical traffic patterns on I-24 corridor")
160
+
161
+ with gr.Row():
162
+ with gr.Column(scale=2):
163
+ heatmap_data = get_speed_heatmap_data()
164
+ heatmap_plot = gr.Plot(
165
+ value=create_speed_heatmap(heatmap_data),
166
+ label="Speed Heatmap"
167
+ )
168
+
169
+ with gr.Row():
170
+ with gr.Column():
171
+ volume_data = get_hourly_volume_data()
172
+ volume_plot = gr.Plot(
173
+ value=create_hourly_volume_chart(volume_data),
174
+ label="Hourly Volume"
175
+ )
176
+ with gr.Column():
177
+ timeline_data = get_congestion_timeline_data()
178
+ timeline_plot = gr.Plot(
179
+ value=create_congestion_timeline(timeline_data),
180
+ label="Congestion Timeline"
181
+ )
182
+
183
+ return heatmap_plot, volume_plot, timeline_plot