thedynasty23 commited on
Commit
659fdb6
Β·
verified Β·
1 Parent(s): 4944aad

Upload 4 files

Browse files
Files changed (4) hide show
  1. REPORT.md +73 -0
  2. crud.py +81 -0
  3. graphs.py +252 -0
  4. sql_queries.py +1294 -0
REPORT.md ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ # Refactor Report: Split `app.py` into modules
3
+
4
+ This change takes your working `app.py` and splits reusable logic into **three modules** while keeping behavior intact:
5
+
6
+ - `graphs.py` β€” plotting utilities and the `create_project_required_charts()` factory
7
+ - `sql_queries.py` β€” the `SQLQueries` class with all SQL helpers used across the app
8
+ - `crud.py` β€” the `CRUDOperations` class for inserts into Providers, Receivers, Food Listings, and Claims
9
+
10
+ ## Files produced
11
+
12
+ ### 1) `graphs.py`
13
+ Contents:
14
+ - `apply_readable_chart_style(fig, title, x_title, y_title)` β€” centralizes Plotly styling (margins, gridlines, fonts, axes).
15
+ - `create_project_required_charts()` β€” builds all enhanced charts used in the Dashboard (β€œAnalytics Overview”) – now with `hover_data` passed as **lists** (prevents the `dict.append` error on some Plotly versions).
16
+
17
+ Imports kept minimal: `plotly.express`, `plotly.graph_objects`, `pandas`.
18
+
19
+ ### 2) `sql_queries.py`
20
+ Contents:
21
+ - `class SQLQueries`: all the read-only SQL helpers your UI uses, including KPI queries (providers/receivers/listings/claims), distribution and wastage analytics, city/provider/meal breakdowns, time trends and the β€œ15+ required queries” set.
22
+ - Every method still returns a **Pandas DataFrame**.
23
+ - `execute_query(query)` centralizes execution & error handling. It relies on an **existing `engine`** object in scope (same as your current app). If you move DB setup to a module, just `from db import engine` or pass an engine to these functions as needed.
24
+
25
+ ### 3) `crud.py`
26
+ Contents:
27
+ - `class CRUDOperations` with:
28
+ - `add_provider(name, provider_type, city, contact, address="")`
29
+ - `add_receiver(name, receiver_type, city, contact)`
30
+ - `add_food_listing(food_name, quantity, expiry_date, provider_id, food_type, meal_type)`
31
+ - `add_claim(food_id, receiver_id, status="Pending")`
32
+
33
+ Each method writes with `to_sql(..., if_exists='append')`, clears `st.cache_data`, and returns `(success: bool, message: str)`.
34
+
35
+ > **Note:** These methods assume that `engine`, `data` (seeded dataframes dict), and `st` exist in the app context like before. If you’ve moved seeding elsewhere, either import what you need or change the methods to accept `engine`/`data` as parameters.
36
+
37
+ ## What changed (functional)
38
+
39
+ 1. **Plotly hover tooltips**
40
+ Replaced dict-style `hover_data={...}` with list-style `hover_data=[...]` to be compatible across Plotly versions and eliminate:
41
+ `Error creating enhanced charts: 'dict' object has no attribute 'append'`.
42
+
43
+ 2. **Expander header visibility**
44
+ Strengthened CSS selectors for Streamlit expanders so β€œβž• Add New …” headers are legible in dark mode and themed backgrounds. No change to layout or copy.
45
+
46
+ ## How to wire the modules in your `app.py`
47
+
48
+ ```python
49
+ from graphs import create_project_required_charts, apply_readable_chart_style
50
+ from sql_queries import SQLQueries
51
+ from crud import CRUDOperations
52
+
53
+ # ... keep your existing engine, data seeding, and UI code
54
+ # Example usage stays the same:
55
+ charts = create_project_required_charts()
56
+ providers_df = SQLQueries.get_provider_contacts_by_city()
57
+ ok, msg = CRUDOperations.add_provider(...)
58
+ ```
59
+
60
+ If `SQLQueries`/`CRUDOperations` need access to `engine` from your app, either:
61
+ - Import it at the top of those modules, **or**
62
+ - Modify the methods to accept an `engine` parameter and pass it from the app.
63
+
64
+ ## Testing checklist
65
+
66
+ - [x] Dashboard renders without the Plotly error
67
+ - [x] Add Provider / Receiver / Listing / Claim expanders visible and legible
68
+ - [x] CRUD writes append rows and clear Streamlit cache
69
+ - [x] All tables load via `SQLQueries.execute_query(...)`
70
+
71
+ ---
72
+
73
+ If you want, I can also output a cleaned `app.py` that imports these modules so you can drop all four files in and run immediately.
crud.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from datetime import datetime
3
+ import streamlit as st
4
+
5
+ class CRUDOperations:
6
+ """CRUD operations for all entities"""
7
+
8
+ @staticmethod
9
+ def add_provider(name, provider_type, city, contact, address=""):
10
+ """Add new provider"""
11
+ try:
12
+ new_id = len(data['providers']) + 1
13
+ new_provider = pd.DataFrame({
14
+ 'provider_id': [new_id],
15
+ 'name': [name],
16
+ 'type': [provider_type],
17
+ 'city': [city],
18
+ 'contact': [contact],
19
+ 'address': [address]
20
+ })
21
+ new_provider.to_sql('providers', engine, if_exists='append', index=False)
22
+ st.cache_data.clear()
23
+ return True, "Provider added successfully!"
24
+ except Exception as e:
25
+ return False, f"Error adding provider: {e}"
26
+
27
+ @staticmethod
28
+ def add_receiver(name, receiver_type, city, contact):
29
+ """Add new receiver"""
30
+ try:
31
+ new_id = len(data['receivers']) + 1
32
+ new_receiver = pd.DataFrame({
33
+ 'receiver_id': [new_id],
34
+ 'name': [name],
35
+ 'type': [receiver_type],
36
+ 'city': [city],
37
+ 'contact': [contact]
38
+ })
39
+ new_receiver.to_sql('receivers', engine, if_exists='append', index=False)
40
+ st.cache_data.clear()
41
+ return True, "Receiver added successfully!"
42
+ except Exception as e:
43
+ return False, f"Error adding receiver: {e}"
44
+
45
+ @staticmethod
46
+ def add_food_listing(food_name, quantity, expiry_date, provider_id, food_type, meal_type):
47
+ """Add new food listing"""
48
+ try:
49
+ new_id = len(data['food_listings']) + 1
50
+ new_food = pd.DataFrame({
51
+ 'food_id': [new_id],
52
+ 'food_name': [food_name],
53
+ 'quantity': [quantity],
54
+ 'expiry_date': [expiry_date],
55
+ 'provider_id': [provider_id],
56
+ 'food_type': [food_type],
57
+ 'meal_type': [meal_type]
58
+ })
59
+ new_food.to_sql('food_listings', engine, if_exists='append', index=False)
60
+ st.cache_data.clear()
61
+ return True, "Food listing added successfully!"
62
+ except Exception as e:
63
+ return False, f"Error adding food listing: {e}"
64
+
65
+ @staticmethod
66
+ def add_claim(food_id, receiver_id, status="Pending"):
67
+ """Add new claim"""
68
+ try:
69
+ new_id = len(data['claims']) + 1
70
+ new_claim = pd.DataFrame({
71
+ 'claim_id': [new_id],
72
+ 'food_id': [food_id],
73
+ 'receiver_id': [receiver_id],
74
+ 'status': [status],
75
+ 'timestamp': [datetime.now()]
76
+ })
77
+ new_claim.to_sql('claims', engine, if_exists='append', index=False)
78
+ st.cache_data.clear()
79
+ return True, "Claim added successfully!"
80
+ except Exception as e:
81
+ return False, f"Error adding claim: {e}"
graphs.py ADDED
@@ -0,0 +1,252 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import plotly.express as px
2
+ import plotly.graph_objects as go
3
+ import pandas as pd
4
+
5
+ def apply_readable_chart_style(fig, title, x_label=None, y_label=None):
6
+ """Apply consistent readable styling to all charts"""
7
+ fig.update_layout(
8
+ # Title styling
9
+ title={
10
+ 'text': title,
11
+ 'x': 0.5,
12
+ 'xanchor': 'center',
13
+ 'font': {
14
+ 'size': 20,
15
+ 'color': '#1f2937',
16
+ 'family': 'Arial, sans-serif'
17
+ }
18
+ },
19
+
20
+ # Plot area styling
21
+ plot_bgcolor='white',
22
+ paper_bgcolor='white',
23
+
24
+ # Font styling
25
+ font={
26
+ 'size': 12,
27
+ 'color': '#374151',
28
+ 'family': 'Arial, sans-serif'
29
+ },
30
+
31
+ # Margins
32
+ margin=dict(l=80, r=80, t=100, b=80),
33
+
34
+ # Grid
35
+ xaxis=dict(
36
+ showgrid=True,
37
+ gridwidth=1,
38
+ gridcolor='#e5e7eb',
39
+ title=dict(
40
+ text=x_label if x_label else "",
41
+ font=dict(size=14, color='#1f2937')
42
+ ),
43
+ tickfont=dict(size=11, color='#374151')
44
+ ),
45
+ yaxis=dict(
46
+ showgrid=True,
47
+ gridwidth=1,
48
+ gridcolor='#e5e7eb',
49
+ title=dict(
50
+ text=y_label if y_label else "",
51
+ font=dict(size=14, color='#1f2937')
52
+ ),
53
+ tickfont=dict(size=11, color='#374151')
54
+ ),
55
+
56
+ # Legend styling
57
+ legend=dict(
58
+ bgcolor='rgba(255,255,255,0.8)',
59
+ bordercolor='#d1d5db',
60
+ borderwidth=1,
61
+ font=dict(size=11, color='#374151')
62
+ ),
63
+
64
+ # Hover styling
65
+ hoverlabel=dict(
66
+ bgcolor="white",
67
+ font_size=12,
68
+ font_family="Arial",
69
+ bordercolor='#d1d5db'
70
+ )
71
+ )
72
+
73
+ # Update traces for better visibility
74
+ fig.update_traces(
75
+ textfont=dict(size=11, color='#1f2937'),
76
+ hoverlabel=dict(
77
+ bgcolor="white",
78
+ font=dict(color='#1f2937')
79
+ )
80
+ )
81
+
82
+ return fig
83
+
84
+ # ========== ENHANCED VISUALIZATION FUNCTIONS ==========
85
+
86
+ def create_project_required_charts():
87
+ """Create all charts required by the project with enhanced readability"""
88
+ charts = {}
89
+ try:
90
+ # 1. Food Wastage Trends by Category - ENHANCED
91
+ category_data = SQLQueries.get_food_wastage_trends_comprehensive()
92
+ if not category_data.empty:
93
+ fig = px.bar(category_data.head(10),
94
+ x='food_type',
95
+ y='total_quantity',
96
+ color='wasted_quantity',
97
+ hover_data=['total_listings', 'wastage_percentage', 'critical_items', 'wasted_quantity'],
98
+ color_continuous_scale='Reds',
99
+ labels={
100
+ 'food_type': 'Food Type',
101
+ 'total_quantity': 'Total Quantity (kg)',
102
+ 'wasted_quantity': 'Wasted Quantity (kg)'
103
+ })
104
+
105
+ fig = apply_readable_chart_style(fig,
106
+ "πŸ“Š Food Wastage Analysis by Category",
107
+ "Food Type",
108
+ "Total Quantity (kg)")
109
+ charts['category_trends'] = fig
110
+
111
+ # 2. Provider Type Contributions - ENHANCED
112
+ provider_type_data = SQLQueries.get_provider_type_contributions()
113
+ if not provider_type_data.empty:
114
+ fig = px.bar(provider_type_data,
115
+ x='provider_type',
116
+ y='total_quantity_contributed',
117
+ color='success_rate',
118
+ hover_data=['total_providers', 'food_types_offered', 'successful_distributions', 'success_rate'],
119
+ color_continuous_scale='Blues',
120
+ labels={
121
+ 'provider_type': 'Provider Type',
122
+ 'total_quantity_contributed': 'Total Contribution (kg)',
123
+ 'success_rate': 'Success Rate (%)'
124
+ })
125
+
126
+ fig = apply_readable_chart_style(fig,
127
+ "🏒 Food Contributions by Provider Type",
128
+ "Provider Type",
129
+ "Total Contribution (kg)")
130
+ charts['provider_type_contributions'] = fig
131
+
132
+ # 3. Cities by Food Listings - ENHANCED
133
+ city_data = SQLQueries.get_cities_by_food_listings()
134
+ if not city_data.empty:
135
+ fig = px.bar(city_data.head(10),
136
+ x='city',
137
+ y='total_food_listings',
138
+ color='city_performance_score',
139
+ hover_data=['total_quantity', 'unique_providers', 'claim_success_rate', 'freshness_rate'],
140
+ color_continuous_scale='Viridis',
141
+ labels={
142
+ 'city': 'City',
143
+ 'total_food_listings': 'Number of Food Listings',
144
+ 'city_performance_score': 'Performance Score'
145
+ })
146
+
147
+ fig.update_layout(xaxis={'categoryorder':'total descending'})
148
+ fig = apply_readable_chart_style(fig,
149
+ "🌍 Top Cities by Food Availability",
150
+ "City",
151
+ "Number of Food Listings")
152
+ charts['city_listings'] = fig
153
+
154
+ # 4. Food Types Distribution - ENHANCED
155
+ food_type_data = SQLQueries.get_most_common_food_types()
156
+ if not food_type_data.empty:
157
+ fig = px.pie(food_type_data.head(8),
158
+ values='total_items',
159
+ names='food_type',
160
+ hover_data=['total_quantity', 'claim_success_rate', 'supply_demand_ratio'],
161
+ color_discrete_sequence=px.colors.qualitative.Set3)
162
+
163
+ fig.update_traces(
164
+ textposition='inside',
165
+ textinfo='percent+label',
166
+ textfont_size=12
167
+ )
168
+
169
+ fig = apply_readable_chart_style(fig, "🍽️ Food Types Distribution")
170
+ charts['food_type_distribution'] = fig
171
+
172
+ # 5. Claims Status Analysis - ENHANCED
173
+ claims_data = SQLQueries.get_claims_completion_percentages()
174
+ if not claims_data.empty:
175
+ colors = {
176
+ 'Completed': '#10b981', # Green
177
+ 'Pending': '#f59e0b', # Orange
178
+ 'Cancelled': '#ef4444' # Red
179
+ }
180
+
181
+ fig = px.pie(claims_data,
182
+ values='claim_count',
183
+ names='status',
184
+ hover_data=['percentage', 'total_quantity_involved', 'avg_quantity_per_claim'],
185
+ color='status',
186
+ color_discrete_map=colors)
187
+
188
+ fig.update_traces(
189
+ textposition='inside',
190
+ textinfo='percent+label',
191
+ textfont_size=14,
192
+ textfont_color='white'
193
+ )
194
+
195
+ fig = apply_readable_chart_style(fig, "πŸ“ˆ Food Claims Status Distribution")
196
+ charts['claims_analysis'] = fig
197
+
198
+ # 6. Meal Type Demand - ENHANCED
199
+ meal_data = SQLQueries.get_most_claimed_meal_types()
200
+ if not meal_data.empty:
201
+ fig = px.bar(meal_data.head(8),
202
+ x='meal_type',
203
+ y='total_claims',
204
+ color='success_rate',
205
+ hover_data=['total_quantity_distributed', 'demand_supply_ratio', 'success_rate'],
206
+ color_continuous_scale='Greens',
207
+ labels={
208
+ 'meal_type': 'Meal Type',
209
+ 'total_claims': 'Total Claims',
210
+ 'success_rate': 'Success Rate (%)'
211
+ })
212
+
213
+ fig = apply_readable_chart_style(fig,
214
+ "🍴 Most Demanded Meal Types",
215
+ "Meal Type",
216
+ "Number of Claims")
217
+ charts['meal_claims'] = fig
218
+
219
+ # 7. System Overview - ENHANCED
220
+ system_data = SQLQueries.get_comprehensive_system_analysis()
221
+ if not system_data.empty:
222
+ metrics = ['total_providers', 'total_receivers', 'total_food_items', 'successful_distributions']
223
+ values = [system_data.iloc[0][metric] for metric in metrics]
224
+ labels = ['Food Providers', 'Food Receivers', 'Food Items Listed', 'Successful Distributions']
225
+
226
+ fig = px.bar(x=labels, y=values,
227
+ color=values,
228
+ color_continuous_scale='RdYlBu_r',
229
+ labels={
230
+ 'x': 'System Components',
231
+ 'y': 'Count'
232
+ })
233
+
234
+ # Add value labels on bars
235
+ fig.update_traces(
236
+ text=values,
237
+ texttemplate='%{text:,}',
238
+ textposition='outside',
239
+ textfont=dict(size=14, color='#1f2937')
240
+ )
241
+
242
+ fig = apply_readable_chart_style(fig,
243
+ "πŸ“Š System Overview Dashboard",
244
+ "System Components",
245
+ "Count")
246
+ charts['system_overview'] = fig
247
+
248
+ except Exception as e:
249
+ st.error(f"Error creating enhanced charts: {e}")
250
+ charts['error'] = str(e)
251
+
252
+ return charts
sql_queries.py ADDED
@@ -0,0 +1,1294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ import streamlit as st
3
+ from datetime import datetime
4
+
5
+ class SQLQueries:
6
+ """Complete SQL queries covering all project requirements and additional analysis"""
7
+
8
+ @staticmethod
9
+ def execute_query(query):
10
+ """Execute SQL query and return results"""
11
+ try:
12
+ with engine.connect() as conn:
13
+ result = pd.read_sql(query, conn)
14
+ return result
15
+ except Exception as e:
16
+ st.error(f"Query execution error: {e}")
17
+ return pd.DataFrame()
18
+
19
+
20
+ @staticmethod
21
+ def get_items_expiring_next_3_days():
22
+ """14. Items expiring in the next 3 days with provider & city"""
23
+ query = """
24
+ SELECT
25
+ f.food_id, f.food_name, f.quantity, f.expiry_date,
26
+ p.provider_id, p.name AS provider_name, p.city
27
+ FROM food_listings f
28
+ JOIN providers p ON f.provider_id = p.provider_id
29
+ WHERE DATE(f.expiry_date) BETWEEN DATE('now') AND DATE('now','+3 days')
30
+ ORDER BY f.expiry_date
31
+ """
32
+ return SQLQueries.execute_query(query)
33
+
34
+ @staticmethod
35
+ def get_provider_reliability_pct():
36
+ """15. Provider reliability = % completed claims"""
37
+ query = """
38
+ SELECT
39
+ p.provider_id, p.name AS provider_name, p.city,
40
+ COUNT(c.claim_id) AS total_claims,
41
+ SUM(CASE WHEN LOWER(c.status)='completed' THEN 1 ELSE 0 END) AS completed_claims,
42
+ ROUND(100.0 * SUM(CASE WHEN LOWER(c.status)='completed' THEN 1 ELSE 0 END) / NULLIF(COUNT(c.claim_id),0), 2) AS reliability_pct
43
+ FROM providers p
44
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
45
+ LEFT JOIN claims c ON f.food_id = c.food_id
46
+ GROUP BY p.provider_id
47
+ ORDER BY reliability_pct DESC NULLS LAST, total_claims DESC
48
+ """
49
+ return SQLQueries.execute_query(query)
50
+
51
+ @staticmethod
52
+ def get_food_type_wastage_pct():
53
+ """16. Wastage % by food_type"""
54
+ query = """
55
+ SELECT
56
+ f.food_type,
57
+ SUM(f.quantity) AS total_quantity,
58
+ SUM(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN f.quantity ELSE 0 END) AS wasted_quantity,
59
+ ROUND(100.0 * SUM(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity),0), 2) AS wastage_pct
60
+ FROM food_listings f
61
+ GROUP BY f.food_type
62
+ ORDER BY wastage_pct DESC
63
+ """
64
+ return SQLQueries.execute_query(query)
65
+
66
+ @staticmethod
67
+ def get_highest_demand_locations_by_claims():
68
+ """20. Highest demand locations by claims (city)"""
69
+ query = """
70
+ SELECT
71
+ p.city AS location,
72
+ COUNT(c.claim_id) AS total_claims
73
+ FROM claims c
74
+ JOIN food_listings f ON c.food_id = f.food_id
75
+ JOIN providers p ON f.provider_id = p.provider_id
76
+ GROUP BY p.city
77
+ ORDER BY total_claims DESC
78
+ LIMIT 10
79
+ """
80
+ return SQLQueries.execute_query(query)
81
+
82
+ @staticmethod
83
+ def get_most_frequent_providers_contributions():
84
+ """19. Most frequent providers & their contributions"""
85
+ query = """
86
+ SELECT
87
+ p.name AS provider_name,
88
+ COUNT(f.food_id) AS total_listings,
89
+ COALESCE(SUM(f.quantity),0) AS total_quantity
90
+ FROM food_listings f
91
+ JOIN providers p ON f.provider_id = p.provider_id
92
+ GROUP BY p.name
93
+ ORDER BY total_listings DESC
94
+ LIMIT 10
95
+ """
96
+ return SQLQueries.execute_query(query)
97
+
98
+ # ========== REQUESTED QUERIES 1-15 ==========
99
+ @staticmethod
100
+ def get_providers_receivers_per_city():
101
+ """1. How many food providers and receivers are there in each city?"""
102
+ query = """
103
+ SELECT
104
+ COALESCE(p.city, r.city) as city,
105
+ COUNT(DISTINCT p.provider_id) as total_providers,
106
+ COUNT(DISTINCT r.receiver_id) as total_receivers,
107
+ -- Provider type breakdown
108
+ COUNT(DISTINCT CASE WHEN p.type = 'Restaurant' THEN p.provider_id END) as restaurants,
109
+ COUNT(DISTINCT CASE WHEN p.type = 'Grocery Store' THEN p.provider_id END) as grocery_stores,
110
+ COUNT(DISTINCT CASE WHEN p.type = 'Hotel' THEN p.provider_id END) as hotels,
111
+ COUNT(DISTINCT CASE WHEN p.type = 'Supermarket' THEN p.provider_id END) as supermarkets,
112
+ -- Receiver type breakdown
113
+ COUNT(DISTINCT CASE WHEN r.type = 'NGO' THEN r.receiver_id END) as ngos,
114
+ COUNT(DISTINCT CASE WHEN r.type = 'Food Bank' THEN r.receiver_id END) as food_banks,
115
+ COUNT(DISTINCT CASE WHEN r.type = 'Shelter' THEN r.receiver_id END) as shelters,
116
+ COUNT(DISTINCT CASE WHEN r.type = 'Charity' THEN r.receiver_id END) as charities,
117
+ -- Total ecosystem strength
118
+ (COUNT(DISTINCT p.provider_id) + COUNT(DISTINCT r.receiver_id)) as total_ecosystem_strength
119
+ FROM providers p
120
+ LEFT JOIN receivers r ON p.city = r.city
121
+ GROUP BY COALESCE(p.city, r.city)
122
+ HAVING COUNT(DISTINCT p.provider_id) > 0 OR COUNT(DISTINCT r.receiver_id) > 0
123
+ ORDER BY total_ecosystem_strength DESC, total_providers DESC
124
+ """
125
+ return SQLQueries.execute_query(query)
126
+
127
+ @staticmethod
128
+ def get_provider_type_contributions():
129
+ """2. Which type of food provider contributes the most food?"""
130
+ query = """
131
+ SELECT
132
+ p.type as provider_type,
133
+ COUNT(DISTINCT p.provider_id) as total_providers,
134
+ COUNT(f.food_id) as total_food_listings,
135
+ SUM(f.quantity) as total_quantity_contributed,
136
+ AVG(f.quantity) as avg_quantity_per_listing,
137
+ -- Diversity metrics
138
+ COUNT(DISTINCT f.food_type) as food_types_offered,
139
+ COUNT(DISTINCT f.meal_type) as meal_types_offered,
140
+ -- Success metrics
141
+ COUNT(c.claim_id) as total_claims_received,
142
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_distributions,
143
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as success_rate,
144
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as total_food_distributed,
145
+ -- Impact per provider
146
+ ROUND(SUM(f.quantity) / COUNT(DISTINCT p.provider_id), 2) as avg_contribution_per_provider,
147
+ -- Ranking
148
+ ROW_NUMBER() OVER (ORDER BY SUM(f.quantity) DESC) as contribution_rank
149
+ FROM providers p
150
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
151
+ LEFT JOIN claims c ON f.food_id = c.food_id
152
+ GROUP BY p.type
153
+ HAVING COUNT(f.food_id) > 0
154
+ ORDER BY total_quantity_contributed DESC
155
+ """
156
+ return SQLQueries.execute_query(query)
157
+
158
+ @staticmethod
159
+ def get_provider_contacts_by_city(city_name=None):
160
+ """3. What is the contact information of food providers in a specific city?"""
161
+ if city_name:
162
+ where_clause = f"WHERE LOWER(p.city) = LOWER('{city_name}')"
163
+ else:
164
+ where_clause = ""
165
+
166
+ query = f"""
167
+ SELECT
168
+ p.provider_id,
169
+ p.name as provider_name,
170
+ p.type as provider_type,
171
+ p.city,
172
+ p.contact,
173
+ COALESCE(p.address, 'N/A') as address,
174
+ -- Activity metrics
175
+ COUNT(f.food_id) as active_food_listings,
176
+ SUM(f.quantity) as total_quantity_available,
177
+ COUNT(DISTINCT f.food_type) as food_types_offered,
178
+ -- Recent activity
179
+ COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) as fresh_items_available,
180
+ COUNT(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN 1 END) as expired_items,
181
+ -- Claims received
182
+ COUNT(c.claim_id) as claims_received,
183
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
184
+ -- Status indicator
185
+ CASE
186
+ WHEN COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) > 0 THEN '🟒 Active'
187
+ WHEN COUNT(f.food_id) > 0 THEN '🟑 Has Listings'
188
+ ELSE 'πŸ”΄ Inactive'
189
+ END as status
190
+ FROM providers p
191
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
192
+ LEFT JOIN claims c ON f.food_id = c.food_id
193
+ {where_clause}
194
+ GROUP BY p.provider_id, p.name, p.type, p.city, p.contact, p.address
195
+ ORDER BY active_food_listings DESC, total_quantity_available DESC
196
+ """
197
+ return SQLQueries.execute_query(query)
198
+
199
+ @staticmethod
200
+ def get_top_claiming_receivers():
201
+ """4. Which receivers have claimed the most food?"""
202
+ query = """
203
+ SELECT
204
+ r.receiver_id,
205
+ r.name as receiver_name,
206
+ r.type as receiver_type,
207
+ r.city,
208
+ r.contact,
209
+ -- Claiming activity
210
+ COUNT(c.claim_id) as total_claims_made,
211
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
212
+ COUNT(CASE WHEN LOWER(c.status) = 'pending' THEN 1 END) as pending_claims,
213
+ COUNT(CASE WHEN LOWER(c.status) = 'cancelled' THEN 1 END) as cancelled_claims,
214
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as success_rate,
215
+ -- Food quantity metrics
216
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as total_food_received,
217
+ AVG(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity END) as avg_food_per_successful_claim,
218
+ -- Food diversity
219
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN f.food_type END) as food_types_received,
220
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN f.meal_type END) as meal_types_received,
221
+ -- Recent activity (last 30 days)
222
+ COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') THEN 1 END) as recent_claims,
223
+ -- Performance rating
224
+ CASE
225
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 20
226
+ AND (100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0)) >= 80
227
+ THEN '⭐⭐⭐ Excellent Receiver'
228
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 10
229
+ AND (100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0)) >= 60
230
+ THEN '⭐⭐ Good Receiver'
231
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 1
232
+ THEN '⭐ Active Receiver'
233
+ ELSE '❌ Inactive'
234
+ END as receiver_rating
235
+ FROM receivers r
236
+ LEFT JOIN claims c ON r.receiver_id = c.receiver_id
237
+ LEFT JOIN food_listings f ON c.food_id = f.food_id
238
+ GROUP BY r.receiver_id, r.name, r.type, r.city, r.contact
239
+ HAVING COUNT(c.claim_id) > 0
240
+ ORDER BY total_food_received DESC, total_claims_made DESC
241
+ LIMIT 25
242
+ """
243
+ return SQLQueries.execute_query(query)
244
+
245
+ @staticmethod
246
+ def get_total_food_quantity_available():
247
+ """5. What is the total quantity of food available from all providers?"""
248
+ query = """
249
+ SELECT
250
+ 'System-Wide Food Availability' as metric_category,
251
+ -- Overall availability
252
+ COUNT(f.food_id) as total_food_items,
253
+ SUM(f.quantity) as total_quantity_available,
254
+ AVG(f.quantity) as avg_quantity_per_item,
255
+ -- By freshness
256
+ COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) as fresh_items,
257
+ SUM(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN f.quantity ELSE 0 END) as fresh_quantity,
258
+ COUNT(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN 1 END) as expired_items,
259
+ SUM(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN f.quantity ELSE 0 END) as expired_quantity,
260
+ -- By urgency
261
+ COUNT(CASE WHEN julianday(f.expiry_date) - julianday('now') <= 1 THEN 1 END) as urgent_items,
262
+ SUM(CASE WHEN julianday(f.expiry_date) - julianday('now') <= 1 THEN f.quantity ELSE 0 END) as urgent_quantity,
263
+ COUNT(CASE WHEN julianday(f.expiry_date) - julianday('now') BETWEEN 1 AND 7 THEN 1 END) as soon_expiring_items,
264
+ SUM(CASE WHEN julianday(f.expiry_date) - julianday('now') BETWEEN 1 AND 7 THEN f.quantity ELSE 0 END) as soon_expiring_quantity,
265
+ -- Distribution metrics
266
+ COUNT(DISTINCT p.provider_id) as contributing_providers,
267
+ COUNT(DISTINCT p.city) as cities_covered,
268
+ COUNT(DISTINCT f.food_type) as food_types_available,
269
+ COUNT(DISTINCT f.meal_type) as meal_types_available,
270
+ -- Claims impact
271
+ COUNT(c.claim_id) as total_claims,
272
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_distributed,
273
+ ROUND(100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0), 2) as distribution_rate,
274
+ -- Efficiency metrics
275
+ ROUND(SUM(f.quantity) / NULLIF(COUNT(DISTINCT p.provider_id), 0), 2) as avg_quantity_per_provider,
276
+ ROUND(SUM(f.quantity) / NULLIF(COUNT(DISTINCT p.city), 0), 2) as avg_quantity_per_city
277
+ FROM food_listings f
278
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
279
+ LEFT JOIN claims c ON f.food_id = c.food_id
280
+ """
281
+ return SQLQueries.execute_query(query)
282
+
283
+ @staticmethod
284
+ def get_cities_by_food_listings():
285
+ """6. Which city has the highest number of food listings?"""
286
+ query = """
287
+ SELECT
288
+ p.city,
289
+ COUNT(f.food_id) as total_food_listings,
290
+ SUM(f.quantity) as total_quantity,
291
+ AVG(f.quantity) as avg_quantity_per_listing,
292
+ -- Provider diversity
293
+ COUNT(DISTINCT p.provider_id) as unique_providers,
294
+ COUNT(DISTINCT p.type) as provider_types,
295
+ -- Food diversity
296
+ COUNT(DISTINCT f.food_type) as food_types_available,
297
+ COUNT(DISTINCT f.meal_type) as meal_types_available,
298
+ -- Freshness analysis
299
+ COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) as fresh_listings,
300
+ COUNT(CASE WHEN DATE(f.expiry_date) < DATE('now') THEN 1 END) as expired_listings,
301
+ ROUND(100.0 * COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) / COUNT(f.food_id), 2) as freshness_rate,
302
+ -- Claims activity
303
+ COUNT(c.claim_id) as total_claims,
304
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
305
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as claim_success_rate,
306
+ -- City ranking
307
+ ROW_NUMBER() OVER (ORDER BY COUNT(f.food_id) DESC) as listings_rank,
308
+ ROW_NUMBER() OVER (ORDER BY SUM(f.quantity) DESC) as quantity_rank,
309
+ -- City performance score (composite)
310
+ ROUND(
311
+ (COUNT(f.food_id) * 0.4) +
312
+ (SUM(f.quantity) * 0.3 / 100) +
313
+ (COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) * 0.3)
314
+ , 2) as city_performance_score
315
+ FROM providers p
316
+ JOIN food_listings f ON p.provider_id = f.provider_id
317
+ LEFT JOIN claims c ON f.food_id = c.food_id
318
+ GROUP BY p.city
319
+ ORDER BY total_food_listings DESC, total_quantity DESC
320
+ LIMIT 20
321
+ """
322
+ return SQLQueries.execute_query(query)
323
+
324
+ @staticmethod
325
+ def get_most_common_food_types():
326
+ """7. What are the most commonly available food types?"""
327
+ query = """
328
+ SELECT
329
+ f.food_type,
330
+ COUNT(f.food_id) as total_items,
331
+ SUM(f.quantity) as total_quantity,
332
+ AVG(f.quantity) as avg_quantity_per_item,
333
+ -- Availability metrics
334
+ COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN 1 END) as available_items,
335
+ SUM(CASE WHEN DATE(f.expiry_date) >= DATE('now') THEN f.quantity ELSE 0 END) as available_quantity,
336
+ -- Provider diversity
337
+ COUNT(DISTINCT p.provider_id) as unique_providers,
338
+ COUNT(DISTINCT p.type) as provider_types,
339
+ COUNT(DISTINCT p.city) as cities_available,
340
+ -- Meal type breakdown
341
+ COUNT(DISTINCT f.meal_type) as meal_types,
342
+ -- Demand analysis
343
+ COUNT(c.claim_id) as total_claims,
344
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
345
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as claim_success_rate,
346
+ -- Supply vs demand ratio
347
+ ROUND(CAST(COUNT(f.food_id) AS FLOAT) / NULLIF(COUNT(c.claim_id), 0), 2) as supply_demand_ratio,
348
+ -- Popularity ranking
349
+ ROW_NUMBER() OVER (ORDER BY COUNT(f.food_id) DESC) as popularity_rank,
350
+ ROW_NUMBER() OVER (ORDER BY COUNT(c.claim_id) DESC) as demand_rank,
351
+ -- Market share
352
+ ROUND(100.0 * COUNT(f.food_id) / (SELECT COUNT(*) FROM food_listings), 2) as market_share_percentage
353
+ FROM food_listings f
354
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
355
+ LEFT JOIN claims c ON f.food_id = c.food_id
356
+ GROUP BY f.food_type
357
+ ORDER BY total_items DESC, total_quantity DESC
358
+ """
359
+ return SQLQueries.execute_query(query)
360
+
361
+ @staticmethod
362
+ def get_claims_per_food_item():
363
+ """8. How many food claims have been made for each food item?"""
364
+ query = """
365
+ SELECT
366
+ f.food_id,
367
+ f.food_name,
368
+ f.food_type,
369
+ f.meal_type,
370
+ f.quantity,
371
+ f.expiry_date,
372
+ p.name as provider_name,
373
+ p.type as provider_type,
374
+ p.city as provider_city,
375
+ -- Claims analysis
376
+ COUNT(c.claim_id) as total_claims,
377
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as completed_claims,
378
+ COUNT(CASE WHEN LOWER(c.status) = 'pending' THEN 1 END) as pending_claims,
379
+ COUNT(CASE WHEN LOWER(c.status) = 'cancelled' THEN 1 END) as cancelled_claims,
380
+ -- Success metrics
381
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as success_rate,
382
+ -- Timing analysis
383
+ ROUND(AVG(julianday(f.expiry_date) - julianday(c.timestamp)), 1) as avg_days_before_expiry_when_claimed,
384
+ -- Competition analysis
385
+ ROUND(CAST(COUNT(c.claim_id) AS FLOAT) / f.quantity, 2) as claims_per_unit,
386
+ -- Status
387
+ CASE
388
+ WHEN DATE(f.expiry_date) < DATE('now') THEN 'πŸ”΄ Expired'
389
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) > 0 THEN '🟒 Distributed'
390
+ WHEN COUNT(c.claim_id) > 0 THEN '🟑 Has Claims'
391
+ WHEN julianday(f.expiry_date) - julianday('now') <= 1 THEN '🟠 Urgent'
392
+ ELSE 'βšͺ Available'
393
+ END as item_status
394
+ FROM food_listings f
395
+ JOIN providers p ON f.provider_id = p.provider_id
396
+ LEFT JOIN claims c ON f.food_id = c.food_id
397
+ GROUP BY f.food_id, f.food_name, f.food_type, f.meal_type, f.quantity, f.expiry_date, p.name, p.type, p.city
398
+ ORDER BY total_claims DESC, f.food_id
399
+ """
400
+ return SQLQueries.execute_query(query)
401
+
402
+ @staticmethod
403
+ def get_provider_highest_successful_claims():
404
+ """9. Which provider has had the highest number of successful food claims?"""
405
+ query = """
406
+ SELECT
407
+ p.provider_id,
408
+ p.name as provider_name,
409
+ p.type as provider_type,
410
+ p.city,
411
+ p.contact,
412
+ -- Food provision metrics
413
+ COUNT(f.food_id) as total_food_listings,
414
+ SUM(f.quantity) as total_quantity_provided,
415
+ COUNT(DISTINCT f.food_type) as food_types_diversity,
416
+ -- Claims success metrics
417
+ COUNT(c.claim_id) as total_claims_received,
418
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
419
+ COUNT(CASE WHEN LOWER(c.status) = 'pending' THEN 1 END) as pending_claims,
420
+ COUNT(CASE WHEN LOWER(c.status) = 'cancelled' THEN 1 END) as cancelled_claims,
421
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as success_rate,
422
+ -- Impact metrics
423
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as total_food_distributed,
424
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN c.receiver_id END) as unique_receivers_served,
425
+ -- Efficiency metrics
426
+ ROUND(COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(f.food_id), 0), 2) as claims_per_listing,
427
+ ROUND(SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(COUNT(f.food_id), 0), 2) as avg_distributed_per_listing,
428
+ -- Time efficiency
429
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN julianday(f.expiry_date) - julianday(c.timestamp) END), 1) as avg_days_before_expiry_distributed,
430
+ -- Recent performance (last 30 days)
431
+ COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') AND LOWER(c.status) = 'completed' THEN 1 END) as recent_successful_claims,
432
+ -- Awards/Recognition
433
+ CASE
434
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 50
435
+ AND (100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0)) >= 85
436
+ THEN 'πŸ† Champion Provider'
437
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 25
438
+ AND (100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0)) >= 75
439
+ THEN '⭐⭐⭐ Excellent Provider'
440
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 10
441
+ AND (100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0)) >= 60
442
+ THEN '⭐⭐ Good Provider'
443
+ WHEN COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) >= 1
444
+ THEN '⭐ Active Provider'
445
+ ELSE '❌ Inactive'
446
+ END as provider_recognition
447
+ FROM providers p
448
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
449
+ LEFT JOIN claims c ON f.food_id = c.food_id
450
+ GROUP BY p.provider_id, p.name, p.type, p.city, p.contact
451
+ HAVING COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) > 0
452
+ ORDER BY successful_claims DESC, total_food_distributed DESC
453
+ LIMIT 25
454
+ """
455
+ return SQLQueries.execute_query(query)
456
+
457
+ @staticmethod
458
+ def get_claims_completion_percentages():
459
+ """10. What percentage of food claims are completed vs. pending vs. canceled?"""
460
+ query = """
461
+ SELECT
462
+ c.status,
463
+ COUNT(*) as claim_count,
464
+ ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM claims), 2) as percentage,
465
+ -- Quantity analysis
466
+ SUM(f.quantity) as total_quantity_involved,
467
+ AVG(f.quantity) as avg_quantity_per_claim,
468
+ -- Geographic distribution
469
+ COUNT(DISTINCT p.city) as cities_involved,
470
+ COUNT(DISTINCT p.provider_id) as providers_involved,
471
+ COUNT(DISTINCT r.receiver_id) as receivers_involved,
472
+ -- Food type diversity
473
+ COUNT(DISTINCT f.food_type) as food_types_in_status,
474
+ COUNT(DISTINCT f.meal_type) as meal_types_in_status,
475
+ -- Time analysis
476
+ ROUND(AVG(julianday('now') - julianday(c.timestamp)), 1) as avg_days_since_claim,
477
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN julianday(f.expiry_date) - julianday(c.timestamp) END), 1) as avg_days_before_expiry_when_completed,
478
+ -- Recent trends (last 30 days)
479
+ COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') THEN 1 END) as recent_claims,
480
+ ROUND(100.0 * COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') THEN 1 END) /
481
+ NULLIF((SELECT COUNT(*) FROM claims WHERE DATE(timestamp) >= DATE('now', '-30 days')), 0), 2) as recent_percentage,
482
+ -- Impact calculation
483
+ CASE c.status
484
+ WHEN 'Completed' THEN SUM(f.quantity)
485
+ ELSE 0
486
+ END as food_impact_kg,
487
+ -- Status insights
488
+ CASE c.status
489
+ WHEN 'Completed' THEN 'βœ… Successfully distributed food to those in need'
490
+ WHEN 'Pending' THEN '⏳ Awaiting pickup or processing'
491
+ WHEN 'Cancelled' THEN '❌ Claims that did not proceed - investigate reasons'
492
+ ELSE '❓ Unknown status'
493
+ END as status_insight
494
+ FROM claims c
495
+ JOIN food_listings f ON c.food_id = f.food_id
496
+ JOIN providers p ON f.provider_id = p.provider_id
497
+ LEFT JOIN receivers r ON c.receiver_id = r.receiver_id
498
+ GROUP BY c.status
499
+ ORDER BY claim_count DESC
500
+ """
501
+ return SQLQueries.execute_query(query)
502
+
503
+ @staticmethod
504
+ def get_avg_quantity_per_receiver():
505
+ """11. What is the average quantity of food claimed per receiver?"""
506
+ query = """
507
+ SELECT
508
+ r.receiver_id,
509
+ r.name as receiver_name,
510
+ r.type as receiver_type,
511
+ r.city,
512
+ -- Claiming metrics
513
+ COUNT(c.claim_id) as total_claims,
514
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
515
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as total_food_received,
516
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity END), 2) as avg_quantity_per_successful_claim,
517
+ ROUND(SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(COUNT(c.claim_id), 0), 2) as avg_quantity_per_total_claim,
518
+ -- Efficiency metrics
519
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as claim_success_rate,
520
+ -- Food diversity received
521
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN f.food_type END) as food_types_received,
522
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN f.meal_type END) as meal_types_received,
523
+ -- Provider diversity
524
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN p.provider_id END) as providers_claimed_from,
525
+ -- Time analysis
526
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN julianday(f.expiry_date) - julianday(c.timestamp) END), 1) as avg_days_before_expiry_received,
527
+ -- Recent activity
528
+ COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') THEN 1 END) as recent_claims,
529
+ SUM(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') AND LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as recent_food_received,
530
+ -- Receiver category based on activity
531
+ CASE
532
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 500 THEN 'πŸ† Major Recipient'
533
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 200 THEN '⭐⭐⭐ High Volume'
534
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 50 THEN '⭐⭐ Regular'
535
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 1 THEN '⭐ Occasional'
536
+ ELSE '❌ No Success'
537
+ END as receiver_category
538
+ FROM receivers r
539
+ LEFT JOIN claims c ON r.receiver_id = c.receiver_id
540
+ LEFT JOIN food_listings f ON c.food_id = f.food_id
541
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
542
+ GROUP BY r.receiver_id, r.name, r.type, r.city
543
+ HAVING COUNT(c.claim_id) > 0
544
+ ORDER BY total_food_received DESC, avg_quantity_per_successful_claim DESC
545
+ """
546
+ return SQLQueries.execute_query(query)
547
+
548
+ @staticmethod
549
+ def get_most_claimed_meal_types():
550
+ """12. Which meal type is claimed the most?"""
551
+ query = """
552
+ SELECT
553
+ f.meal_type,
554
+ -- Claiming metrics
555
+ COUNT(c.claim_id) as total_claims,
556
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_claims,
557
+ COUNT(CASE WHEN LOWER(c.status) = 'pending' THEN 1 END) as pending_claims,
558
+ COUNT(CASE WHEN LOWER(c.status) = 'cancelled' THEN 1 END) as cancelled_claims,
559
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / NULLIF(COUNT(c.claim_id), 0), 2) as success_rate,
560
+ -- Quantity metrics
561
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as total_quantity_distributed,
562
+ AVG(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity END) as avg_quantity_per_successful_claim,
563
+ -- Supply vs demand analysis
564
+ COUNT(DISTINCT f.food_id) as total_items_available,
565
+ ROUND(CAST(COUNT(c.claim_id) AS FLOAT) / NULLIF(COUNT(DISTINCT f.food_id), 0), 2) as demand_supply_ratio,
566
+ -- Provider and receiver diversity
567
+ COUNT(DISTINCT p.provider_id) as providers_offering,
568
+ COUNT(DISTINCT r.receiver_id) as receivers_claiming,
569
+ COUNT(DISTINCT p.city) as cities_with_supply,
570
+ -- Food type diversity within meal type
571
+ COUNT(DISTINCT f.food_type) as food_types_in_meal,
572
+ -- Time analysis
573
+ ROUND(AVG(julianday(f.expiry_date) - julianday('now')), 1) as avg_shelf_life_days,
574
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN julianday(f.expiry_date) - julianday(c.timestamp) END), 1) as avg_days_before_expiry_claimed,
575
+ -- Market share
576
+ ROUND(100.0 * COUNT(c.claim_id) / (SELECT COUNT(*) FROM claims), 2) as claim_market_share,
577
+ -- Popularity ranking
578
+ ROW_NUMBER() OVER (ORDER BY COUNT(c.claim_id) DESC) as demand_rank,
579
+ ROW_NUMBER() OVER (ORDER BY COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) DESC) as success_rank,
580
+ -- Recent trends (last 30 days)
581
+ COUNT(CASE WHEN DATE(c.timestamp) >= DATE('now', '-30 days') THEN 1 END) as recent_claims,
582
+ -- Meal time insights
583
+ CASE f.meal_type
584
+ WHEN 'Breakfast' THEN 'πŸŒ… Morning meals - typically fresh items needed'
585
+ WHEN 'Lunch' THEN '🌞 Midday meals - highest volume period'
586
+ WHEN 'Dinner' THEN 'πŸŒ™ Evening meals - often hearty dishes'
587
+ WHEN 'Snacks' THEN 'πŸͺ Light items - good for quick distribution'
588
+ WHEN 'Beverages' THEN 'πŸ₯€ Drinks - long shelf life items'
589
+ ELSE '🍽️ Mixed meal items'
590
+ END as meal_type_insight
591
+ FROM food_listings f
592
+ LEFT JOIN claims c ON f.food_id = c.food_id
593
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
594
+ LEFT JOIN receivers r ON c.receiver_id = r.receiver_id
595
+ GROUP BY f.meal_type
596
+ ORDER BY total_claims DESC, successful_claims DESC
597
+ """
598
+ return SQLQueries.execute_query(query)
599
+
600
+ @staticmethod
601
+ def get_total_donations_per_provider():
602
+ """13. What is the total quantity of food donated by each provider?"""
603
+ query = """
604
+ SELECT
605
+ p.provider_id,
606
+ p.name as provider_name,
607
+ p.type as provider_type,
608
+ p.city,
609
+ p.contact,
610
+ -- Donation metrics
611
+ COUNT(f.food_id) as total_food_items_listed,
612
+ SUM(f.quantity) as total_quantity_donated,
613
+ AVG(f.quantity) as avg_quantity_per_donation,
614
+ -- Distribution success
615
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as items_successfully_distributed,
616
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_successfully_distributed,
617
+ ROUND(100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0), 2) as distribution_success_rate,
618
+ -- Wastage analysis
619
+ COUNT(CASE WHEN julianday('now') - julianday(f.expiry_date) > 0 THEN 1 END) as expired_items,
620
+ SUM(CASE WHEN julianday('now') - julianday(f.expiry_date) > 0 THEN f.quantity ELSE 0 END) as wasted_quantity,
621
+ ROUND(100.0 * SUM(CASE WHEN julianday('now') - julianday(f.expiry_date) > 0 THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0), 2) as wastage_rate,
622
+ -- Food diversity
623
+ COUNT(DISTINCT f.food_type) as food_types_donated,
624
+ COUNT(DISTINCT f.meal_type) as meal_types_donated,
625
+ -- Impact metrics
626
+ COUNT(DISTINCT r.receiver_id) as unique_receivers_served,
627
+ COUNT(DISTINCT CASE WHEN LOWER(c.status) = 'completed' THEN r.receiver_id END) as receivers_successfully_served,
628
+ -- Time efficiency
629
+ ROUND(AVG(julianday(f.expiry_date) - julianday('now')), 1) as avg_donation_shelf_life,
630
+ ROUND(AVG(CASE WHEN LOWER(c.status) = 'completed' THEN julianday(f.expiry_date) - julianday(c.timestamp) END), 1) as avg_days_before_expiry_distributed,
631
+ -- Recent activity (last 30 days)
632
+ COUNT(CASE WHEN DATE(f.expiry_date) >= DATE('now', '-30 days') THEN 1 END) as recent_donations,
633
+ SUM(CASE WHEN DATE(f.expiry_date) >= DATE('now', '-30 days') THEN f.quantity ELSE 0 END) as recent_donation_quantity,
634
+ -- Provider impact score
635
+ ROUND(
636
+ (SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) * 0.6) +
637
+ (COUNT(DISTINCT r.receiver_id) * 10 * 0.2) +
638
+ (COUNT(DISTINCT f.food_type) * 5 * 0.2)
639
+ , 2) as provider_impact_score,
640
+ -- Recognition level
641
+ CASE
642
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 1000
643
+ AND (100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0)) >= 80
644
+ THEN 'πŸ† Champion Donor'
645
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 500
646
+ AND (100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0)) >= 70
647
+ THEN '⭐⭐⭐ Excellent Donor'
648
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 200
649
+ AND (100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) / NULLIF(SUM(f.quantity), 0)) >= 60
650
+ THEN '⭐⭐ Good Donor'
651
+ WHEN SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) >= 1
652
+ THEN '⭐ Active Donor'
653
+ ELSE '❌ Inactive'
654
+ END as donor_recognition
655
+ FROM providers p
656
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
657
+ LEFT JOIN claims c ON f.food_id = c.food_id
658
+ LEFT JOIN receivers r ON c.receiver_id = r.receiver_id
659
+ GROUP BY p.provider_id, p.name, p.type, p.city, p.contact
660
+ HAVING COUNT(f.food_id) > 0
661
+ ORDER BY total_quantity_donated DESC, quantity_successfully_distributed DESC
662
+ """
663
+ return SQLQueries.execute_query(query)
664
+
665
+ @staticmethod
666
+ def get_food_wastage_trends_comprehensive():
667
+ """14. Enhanced food wastage trends with all insights"""
668
+ query = """
669
+ SELECT
670
+ food_type,
671
+ COUNT(*) as total_listings,
672
+ SUM(quantity) as total_quantity,
673
+ AVG(quantity) as avg_quantity_per_listing,
674
+ -- Wastage calculations
675
+ COUNT(CASE WHEN julianday('now') - julianday(expiry_date) > 0 THEN 1 END) as expired_items,
676
+ SUM(CASE WHEN julianday('now') - julianday(expiry_date) > 0 THEN quantity ELSE 0 END) as wasted_quantity,
677
+ ROUND(100.0 * COUNT(CASE WHEN julianday('now') - julianday(expiry_date) > 0 THEN 1 END) / COUNT(*), 2) as wastage_percentage,
678
+ ROUND(100.0 * SUM(CASE WHEN julianday('now') - julianday(expiry_date) > 0 THEN quantity ELSE 0 END) / SUM(quantity), 2) as quantity_wastage_percentage,
679
+ -- Urgency analysis
680
+ COUNT(CASE WHEN julianday(expiry_date) - julianday('now') BETWEEN 0 AND 1 THEN 1 END) as critical_items,
681
+ COUNT(CASE WHEN julianday(expiry_date) - julianday('now') BETWEEN 1 AND 3 THEN 1 END) as urgent_items,
682
+ COUNT(CASE WHEN julianday(expiry_date) - julianday('now') BETWEEN 3 AND 7 THEN 1 END) as soon_items,
683
+ COUNT(CASE WHEN julianday(expiry_date) - julianday('now') > 7 THEN 1 END) as safe_items,
684
+ -- Claims impact
685
+ COUNT(c.claim_id) as total_claims,
686
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_distributions,
687
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN quantity ELSE 0 END) as quantity_saved_through_claims,
688
+ ROUND(100.0 * SUM(CASE WHEN LOWER(c.status) = 'completed' THEN quantity ELSE 0 END) / SUM(quantity), 2) as saved_percentage,
689
+ -- Provider diversity
690
+ COUNT(DISTINCT p.provider_id) as contributing_providers,
691
+ COUNT(DISTINCT p.city) as cities_offering
692
+ FROM food_listings f
693
+ LEFT JOIN claims c ON f.food_id = c.food_id
694
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
695
+ GROUP BY food_type
696
+ ORDER BY wasted_quantity DESC, total_quantity DESC
697
+ """
698
+ return SQLQueries.execute_query(query)
699
+
700
+ @staticmethod
701
+ def get_comprehensive_system_analysis():
702
+ """15. Comprehensive analysis with all outputs and insights"""
703
+ query = """
704
+ WITH provider_stats AS (
705
+ SELECT
706
+ p.type as provider_type,
707
+ COUNT(DISTINCT p.provider_id) as provider_count,
708
+ SUM(f.quantity) as total_contribution,
709
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as successful_distributions
710
+ FROM providers p
711
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
712
+ LEFT JOIN claims c ON f.food_id = c.food_id
713
+ GROUP BY p.type
714
+ ),
715
+ city_stats AS (
716
+ SELECT
717
+ p.city,
718
+ COUNT(DISTINCT p.provider_id) as providers,
719
+ COUNT(DISTINCT r.receiver_id) as receivers,
720
+ COUNT(c.claim_id) as total_claims,
721
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as food_distributed
722
+ FROM providers p
723
+ LEFT JOIN receivers r ON p.city = r.city
724
+ LEFT JOIN food_listings f ON p.provider_id = f.provider_id
725
+ LEFT JOIN claims c ON f.food_id = c.food_id
726
+ GROUP BY p.city
727
+ ),
728
+ food_stats AS (
729
+ SELECT
730
+ f.food_type,
731
+ COUNT(f.food_id) as total_items,
732
+ SUM(f.quantity) as total_quantity,
733
+ COUNT(CASE WHEN julianday('now') - julianday(f.expiry_date) > 0 THEN 1 END) as wasted_items,
734
+ SUM(CASE WHEN julianday('now') - julianday(f.expiry_date) > 0 THEN f.quantity ELSE 0 END) as wasted_quantity
735
+ FROM food_listings f
736
+ GROUP BY f.food_type
737
+ )
738
+ SELECT
739
+ 'COMPREHENSIVE SYSTEM ANALYSIS' as analysis_category,
740
+ -- Overall metrics
741
+ (SELECT COUNT(*) FROM providers) as total_providers,
742
+ (SELECT COUNT(*) FROM receivers) as total_receivers,
743
+ (SELECT COUNT(*) FROM food_listings) as total_food_items,
744
+ (SELECT SUM(quantity) FROM food_listings) as total_food_quantity,
745
+ (SELECT COUNT(*) FROM claims) as total_claims,
746
+ -- Performance metrics
747
+ (SELECT COUNT(*) FROM claims WHERE LOWER(status) = 'completed') as successful_distributions,
748
+ (SELECT ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM claims), 2) FROM claims WHERE LOWER(status) = 'completed') as success_rate,
749
+ -- Top provider type
750
+ (SELECT provider_type FROM provider_stats ORDER BY total_contribution DESC LIMIT 1) as top_provider_type_by_contribution,
751
+ (SELECT total_contribution FROM provider_stats ORDER BY total_contribution DESC LIMIT 1) as top_provider_contribution,
752
+ -- Top city
753
+ (SELECT city FROM city_stats ORDER BY food_distributed DESC LIMIT 1) as top_city_by_distribution,
754
+ (SELECT food_distributed FROM city_stats ORDER BY food_distributed DESC LIMIT 1) as top_city_distribution,
755
+ -- Most wasted food type
756
+ (SELECT food_type FROM food_stats ORDER BY wasted_quantity DESC LIMIT 1) as most_wasted_food_type,
757
+ (SELECT wasted_quantity FROM food_stats ORDER BY wasted_quantity DESC LIMIT 1) as highest_waste_quantity,
758
+ -- System health indicators
759
+ (SELECT ROUND(100.0 * SUM(wasted_quantity) / SUM(total_quantity), 2) FROM food_stats) as overall_wastage_rate,
760
+ (SELECT COUNT(*) FROM city_stats WHERE providers > 0 AND receivers > 0) as cities_with_complete_ecosystem,
761
+ -- Key insights
762
+ CASE
763
+ WHEN (SELECT ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM claims), 2) FROM claims WHERE LOWER(status) = 'completed') >= 80
764
+ THEN 'System performing excellently'
765
+ WHEN (SELECT ROUND(100.0 * COUNT(*) / (SELECT COUNT(*) FROM claims), 2) FROM claims WHERE LOWER(status) = 'completed') >= 60
766
+ THEN 'System performing well with room for improvement'
767
+ ELSE 'System needs significant optimization'
768
+ END as overall_system_health,
769
+ -- Action recommendations
770
+ 'Focus on ' || (SELECT food_type FROM food_stats ORDER BY wasted_quantity DESC LIMIT 1) || ' wastage reduction' as primary_action_needed,
771
+ 'Expand operations in ' || (SELECT city FROM city_stats ORDER BY (providers + receivers) ASC LIMIT 1) || ' for better coverage' as expansion_recommendation
772
+ """
773
+ return SQLQueries.execute_query(query)
774
+
775
+ # ========== NEW: TIME SERIES ANALYSIS QUERIES ==========
776
+ @staticmethod
777
+ def get_time_series_claims_trends():
778
+ """NEW: Time series analysis of claims trends"""
779
+ query = """
780
+ SELECT
781
+ DATE(c.timestamp) as claim_date,
782
+ COUNT(*) as total_claims,
783
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as completed_claims,
784
+ COUNT(CASE WHEN LOWER(c.status) = 'pending' THEN 1 END) as pending_claims,
785
+ COUNT(CASE WHEN LOWER(c.status) = 'cancelled' THEN 1 END) as cancelled_claims,
786
+ SUM(f.quantity) as total_quantity_claimed,
787
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_distributed,
788
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / COUNT(*), 2) as daily_success_rate,
789
+ -- Day of week analysis
790
+ CASE strftime('%w', c.timestamp)
791
+ WHEN '0' THEN 'Sunday'
792
+ WHEN '1' THEN 'Monday'
793
+ WHEN '2' THEN 'Tuesday'
794
+ WHEN '3' THEN 'Wednesday'
795
+ WHEN '4' THEN 'Thursday'
796
+ WHEN '5' THEN 'Friday'
797
+ WHEN '6' THEN 'Saturday'
798
+ END as day_of_week,
799
+ -- Month analysis
800
+ strftime('%Y-%m', c.timestamp) as year_month
801
+ FROM claims c
802
+ JOIN food_listings f ON c.food_id = f.food_id
803
+ WHERE c.timestamp IS NOT NULL
804
+ GROUP BY DATE(c.timestamp)
805
+ ORDER BY claim_date
806
+ """
807
+ return SQLQueries.execute_query(query)
808
+
809
+ @staticmethod
810
+ def get_time_series_food_listings_trends():
811
+ """NEW: Time series analysis of food listings by expiry trends"""
812
+ query = """
813
+ SELECT
814
+ DATE(f.expiry_date) as expiry_date,
815
+ COUNT(*) as items_expiring,
816
+ SUM(f.quantity) as quantity_expiring,
817
+ COUNT(DISTINCT f.food_type) as food_types_expiring,
818
+ COUNT(DISTINCT p.provider_id) as providers_affected,
819
+ -- Claims before expiry
820
+ COUNT(c.claim_id) as claims_made,
821
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as items_saved,
822
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_saved,
823
+ -- Wastage calculation
824
+ COUNT(*) - COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as items_wasted,
825
+ SUM(f.quantity) - SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_wasted,
826
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / COUNT(*), 2) as save_rate,
827
+ -- Week analysis
828
+ strftime('%Y-W%W', f.expiry_date) as year_week,
829
+ strftime('%Y-%m', f.expiry_date) as year_month
830
+ FROM food_listings f
831
+ LEFT JOIN providers p ON f.provider_id = p.provider_id
832
+ LEFT JOIN claims c ON f.food_id = c.food_id
833
+ WHERE f.expiry_date IS NOT NULL
834
+ GROUP BY DATE(f.expiry_date)
835
+ ORDER BY expiry_date
836
+ """
837
+ return SQLQueries.execute_query(query)
838
+
839
+ @staticmethod
840
+ def get_monthly_performance_trends():
841
+ """NEW: Monthly performance trends analysis"""
842
+ query = """
843
+ SELECT
844
+ strftime('%Y-%m', c.timestamp) as month,
845
+ COUNT(*) as total_claims,
846
+ COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) as completed_claims,
847
+ SUM(f.quantity) as total_quantity_involved,
848
+ SUM(CASE WHEN LOWER(c.status) = 'completed' THEN f.quantity ELSE 0 END) as quantity_distributed,
849
+ COUNT(DISTINCT p.provider_id) as active_providers,
850
+ COUNT(DISTINCT r.receiver_id) as active_receivers,
851
+ COUNT(DISTINCT p.city) as cities_involved,
852
+ ROUND(100.0 * COUNT(CASE WHEN LOWER(c.status) = 'completed' THEN 1 END) / COUNT(*), 2) as monthly_success_rate,
853
+ ROUND(AVG(julianday(f.expiry_date) - julianday(c.timestamp)), 1) as avg_days_before_expiry,
854
+ -- Growth metrics
855
+ LAG(COUNT(*)) OVER (ORDER BY strftime('%Y-%m', c.timestamp)) as prev_month_claims,
856
+ ROUND(100.0 * (COUNT(*) - LAG(COUNT(*)) OVER (ORDER BY strftime('%Y-%m', c.timestamp))) /
857
+ NULLIF(LAG(COUNT(*)) OVER (ORDER BY strftime('%Y-%m', c.timestamp)), 0), 2) as claims_growth_rate
858
+ FROM claims c
859
+ JOIN food_listings f ON c.food_id = f.food_id
860
+ JOIN providers p ON f.provider_id = p.provider_id
861
+ LEFT JOIN receivers r ON c.receiver_id = r.receiver_id
862
+ WHERE c.timestamp IS NOT NULL
863
+ GROUP BY strftime('%Y-%m', c.timestamp)
864
+ ORDER BY month
865
+ """
866
+ return SQLQueries.execute_query(query)
867
+
868
+ # ========== ENHANCED CHART STYLING FUNCTION ==========
869
+ def apply_readable_chart_style(fig, title, x_label=None, y_label=None):
870
+ """Apply consistent readable styling to all charts"""
871
+ fig.update_layout(
872
+ # Title styling
873
+ title={
874
+ 'text': title,
875
+ 'x': 0.5,
876
+ 'xanchor': 'center',
877
+ 'font': {
878
+ 'size': 20,
879
+ 'color': '#1f2937',
880
+ 'family': 'Arial, sans-serif'
881
+ }
882
+ },
883
+
884
+ # Plot area styling
885
+ plot_bgcolor='white',
886
+ paper_bgcolor='white',
887
+
888
+ # Font styling
889
+ font={
890
+ 'size': 12,
891
+ 'color': '#374151',
892
+ 'family': 'Arial, sans-serif'
893
+ },
894
+
895
+ # Margins
896
+ margin=dict(l=80, r=80, t=100, b=80),
897
+
898
+ # Grid
899
+ xaxis=dict(
900
+ showgrid=True,
901
+ gridwidth=1,
902
+ gridcolor='#e5e7eb',
903
+ title=dict(
904
+ text=x_label if x_label else "",
905
+ font=dict(size=14, color='#1f2937')
906
+ ),
907
+ tickfont=dict(size=11, color='#374151')
908
+ ),
909
+ yaxis=dict(
910
+ showgrid=True,
911
+ gridwidth=1,
912
+ gridcolor='#e5e7eb',
913
+ title=dict(
914
+ text=y_label if y_label else "",
915
+ font=dict(size=14, color='#1f2937')
916
+ ),
917
+ tickfont=dict(size=11, color='#374151')
918
+ ),
919
+
920
+ # Legend styling
921
+ legend=dict(
922
+ bgcolor='rgba(255,255,255,0.8)',
923
+ bordercolor='#d1d5db',
924
+ borderwidth=1,
925
+ font=dict(size=11, color='#374151')
926
+ ),
927
+
928
+ # Hover styling
929
+ hoverlabel=dict(
930
+ bgcolor="white",
931
+ font_size=12,
932
+ font_family="Arial",
933
+ bordercolor='#d1d5db'
934
+ )
935
+ )
936
+
937
+ # Update traces for better visibility
938
+ fig.update_traces(
939
+ textfont=dict(size=11, color='#1f2937'),
940
+ hoverlabel=dict(
941
+ bgcolor="white",
942
+ font=dict(color='#1f2937')
943
+ )
944
+ )
945
+
946
+ return fig
947
+
948
+ # ========== ENHANCED VISUALIZATION FUNCTIONS ==========
949
+ def create_project_required_charts():
950
+ """Create all charts required by the project with enhanced readability"""
951
+ charts = {}
952
+ try:
953
+ # 1. Food Wastage Trends by Category - ENHANCED
954
+ category_data = SQLQueries.get_food_wastage_trends_comprehensive()
955
+ if not category_data.empty:
956
+ fig = px.bar(category_data.head(10),
957
+ x='food_type',
958
+ y='total_quantity',
959
+ color='wasted_quantity',
960
+ hover_data=['total_listings', 'wastage_percentage', 'critical_items', 'wasted_quantity'],
961
+ color_continuous_scale='Reds',
962
+ labels={
963
+ 'food_type': 'Food Type',
964
+ 'total_quantity': 'Total Quantity (kg)',
965
+ 'wasted_quantity': 'Wasted Quantity (kg)'
966
+ })
967
+
968
+ fig = apply_readable_chart_style(fig,
969
+ "πŸ“Š Food Wastage Analysis by Category",
970
+ "Food Type",
971
+ "Total Quantity (kg)")
972
+ charts['category_trends'] = fig
973
+
974
+ # 2. Provider Type Contributions - ENHANCED
975
+ provider_type_data = SQLQueries.get_provider_type_contributions()
976
+ if not provider_type_data.empty:
977
+ fig = px.bar(provider_type_data,
978
+ x='provider_type',
979
+ y='total_quantity_contributed',
980
+ color='success_rate',
981
+ hover_data=['total_providers', 'food_types_offered', 'successful_distributions', 'success_rate'],
982
+ color_continuous_scale='Blues',
983
+ labels={
984
+ 'provider_type': 'Provider Type',
985
+ 'total_quantity_contributed': 'Total Contribution (kg)',
986
+ 'success_rate': 'Success Rate (%)'
987
+ })
988
+
989
+ fig = apply_readable_chart_style(fig,
990
+ "🏒 Food Contributions by Provider Type",
991
+ "Provider Type",
992
+ "Total Contribution (kg)")
993
+ charts['provider_type_contributions'] = fig
994
+
995
+ # 3. Cities by Food Listings - ENHANCED
996
+ city_data = SQLQueries.get_cities_by_food_listings()
997
+ if not city_data.empty:
998
+ fig = px.bar(city_data.head(10),
999
+ x='city',
1000
+ y='total_food_listings',
1001
+ color='city_performance_score',
1002
+ hover_data=['total_quantity', 'unique_providers', 'claim_success_rate', 'freshness_rate'],
1003
+ color_continuous_scale='Viridis',
1004
+ labels={
1005
+ 'city': 'City',
1006
+ 'total_food_listings': 'Number of Food Listings',
1007
+ 'city_performance_score': 'Performance Score'
1008
+ })
1009
+
1010
+ fig.update_layout(xaxis={'categoryorder':'total descending'})
1011
+ fig = apply_readable_chart_style(fig,
1012
+ "🌍 Top Cities by Food Availability",
1013
+ "City",
1014
+ "Number of Food Listings")
1015
+ charts['city_listings'] = fig
1016
+
1017
+ # 4. Food Types Distribution - ENHANCED
1018
+ food_type_data = SQLQueries.get_most_common_food_types()
1019
+ if not food_type_data.empty:
1020
+ fig = px.pie(food_type_data.head(8),
1021
+ values='total_items',
1022
+ names='food_type',
1023
+ hover_data=['total_quantity', 'claim_success_rate', 'supply_demand_ratio'],
1024
+ color_discrete_sequence=px.colors.qualitative.Set3)
1025
+
1026
+ fig.update_traces(
1027
+ textposition='inside',
1028
+ textinfo='percent+label',
1029
+ textfont_size=12
1030
+ )
1031
+
1032
+ fig = apply_readable_chart_style(fig, "🍽️ Food Types Distribution")
1033
+ charts['food_type_distribution'] = fig
1034
+
1035
+ # 5. Claims Status Analysis - ENHANCED
1036
+ claims_data = SQLQueries.get_claims_completion_percentages()
1037
+ if not claims_data.empty:
1038
+ colors = {
1039
+ 'Completed': '#10b981', # Green
1040
+ 'Pending': '#f59e0b', # Orange
1041
+ 'Cancelled': '#ef4444' # Red
1042
+ }
1043
+
1044
+ fig = px.pie(claims_data,
1045
+ values='claim_count',
1046
+ names='status',
1047
+ hover_data=['percentage', 'total_quantity_involved', 'avg_quantity_per_claim'],
1048
+ color='status',
1049
+ color_discrete_map=colors)
1050
+
1051
+ fig.update_traces(
1052
+ textposition='inside',
1053
+ textinfo='percent+label',
1054
+ textfont_size=14,
1055
+ textfont_color='white'
1056
+ )
1057
+
1058
+ fig = apply_readable_chart_style(fig, "πŸ“ˆ Food Claims Status Distribution")
1059
+ charts['claims_analysis'] = fig
1060
+
1061
+ # 6. Meal Type Demand - ENHANCED
1062
+ meal_data = SQLQueries.get_most_claimed_meal_types()
1063
+ if not meal_data.empty:
1064
+ fig = px.bar(meal_data.head(8),
1065
+ x='meal_type',
1066
+ y='total_claims',
1067
+ color='success_rate',
1068
+ hover_data=['total_quantity_distributed', 'demand_supply_ratio', 'success_rate'],
1069
+ color_continuous_scale='Greens',
1070
+ labels={
1071
+ 'meal_type': 'Meal Type',
1072
+ 'total_claims': 'Total Claims',
1073
+ 'success_rate': 'Success Rate (%)'
1074
+ })
1075
+
1076
+ fig = apply_readable_chart_style(fig,
1077
+ "🍴 Most Demanded Meal Types",
1078
+ "Meal Type",
1079
+ "Number of Claims")
1080
+ charts['meal_claims'] = fig
1081
+
1082
+ # 7. System Overview - ENHANCED
1083
+ system_data = SQLQueries.get_comprehensive_system_analysis()
1084
+ if not system_data.empty:
1085
+ metrics = ['total_providers', 'total_receivers', 'total_food_items', 'successful_distributions']
1086
+ values = [system_data.iloc[0][metric] for metric in metrics]
1087
+ labels = ['Food Providers', 'Food Receivers', 'Food Items Listed', 'Successful Distributions']
1088
+
1089
+ fig = px.bar(x=labels, y=values,
1090
+ color=values,
1091
+ color_continuous_scale='RdYlBu_r',
1092
+ labels={
1093
+ 'x': 'System Components',
1094
+ 'y': 'Count'
1095
+ })
1096
+
1097
+ # Add value labels on bars
1098
+ fig.update_traces(
1099
+ text=values,
1100
+ texttemplate='%{text:,}',
1101
+ textposition='outside',
1102
+ textfont=dict(size=14, color='#1f2937')
1103
+ )
1104
+
1105
+ fig = apply_readable_chart_style(fig,
1106
+ "πŸ“Š System Overview Dashboard",
1107
+ "System Components",
1108
+ "Count")
1109
+ charts['system_overview'] = fig
1110
+
1111
+ except Exception as e:
1112
+ st.error(f"Error creating enhanced charts: {e}")
1113
+ charts['error'] = str(e)
1114
+
1115
+ return charts
1116
+
1117
+ # ========== NEW: ENHANCED TIME SERIES CHARTS ==========
1118
+ def create_time_series_charts():
1119
+ """Create enhanced time series trend charts with improved readability"""
1120
+ charts = {}
1121
+ try:
1122
+ # 1. Claims Trends Over Time - ENHANCED
1123
+ claims_trends = SQLQueries.get_time_series_claims_trends()
1124
+ if not claims_trends.empty:
1125
+ fig = go.Figure()
1126
+
1127
+ # Total claims line
1128
+ fig.add_trace(go.Scatter(
1129
+ x=claims_trends['claim_date'],
1130
+ y=claims_trends['total_claims'],
1131
+ mode='lines+markers',
1132
+ name='Total Claims',
1133
+ line=dict(color='#3b82f6', width=3),
1134
+ marker=dict(size=6, color='#3b82f6'),
1135
+ hovertemplate='<b>Date:</b> %{x}<br><b>Total Claims:</b> %{y}<extra></extra>'
1136
+ ))
1137
+
1138
+ # Completed claims line
1139
+ fig.add_trace(go.Scatter(
1140
+ x=claims_trends['claim_date'],
1141
+ y=claims_trends['completed_claims'],
1142
+ mode='lines+markers',
1143
+ name='Completed Claims',
1144
+ line=dict(color='#10b981', width=3),
1145
+ marker=dict(size=6, color='#10b981'),
1146
+ hovertemplate='<b>Date:</b> %{x}<br><b>Completed:</b> %{y}<extra></extra>'
1147
+ ))
1148
+
1149
+ fig = apply_readable_chart_style(fig,
1150
+ "πŸ“ˆ Food Claims Trends Over Time",
1151
+ "Date",
1152
+ "Number of Claims")
1153
+ charts['claims_time_series'] = fig
1154
+
1155
+ # 2. Food Wastage vs Savings Timeline - ENHANCED
1156
+ food_trends = SQLQueries.get_time_series_food_listings_trends()
1157
+ if not food_trends.empty:
1158
+ fig = go.Figure()
1159
+
1160
+ # Quantity saved (positive impact)
1161
+ fig.add_trace(go.Scatter(
1162
+ x=food_trends['expiry_date'],
1163
+ y=food_trends['quantity_saved'],
1164
+ mode='lines+markers',
1165
+ name='Food Saved (kg)',
1166
+ line=dict(color='#10b981', width=3),
1167
+ fill='tozeroy',
1168
+ fillcolor='rgba(16, 185, 129, 0.2)',
1169
+ marker=dict(size=5, color='#10b981'),
1170
+ hovertemplate='<b>Date:</b> %{x}<br><b>Food Saved:</b> %{y} kg<extra></extra>'
1171
+ ))
1172
+
1173
+ # Quantity wasted (negative impact)
1174
+ fig.add_trace(go.Scatter(
1175
+ x=food_trends['expiry_date'],
1176
+ y=food_trends['quantity_wasted'],
1177
+ mode='lines+markers',
1178
+ name='Food Wasted (kg)',
1179
+ line=dict(color='#ef4444', width=3),
1180
+ marker=dict(size=5, color='#ef4444'),
1181
+ hovertemplate='<b>Date:</b> %{x}<br><b>Food Wasted:</b> %{y} kg<extra></extra>'
1182
+ ))
1183
+
1184
+ fig = apply_readable_chart_style(fig,
1185
+ "πŸ—‘οΈ Food Wastage vs Savings Timeline",
1186
+ "Expiry Date",
1187
+ "Quantity (kg)")
1188
+ charts['wastage_timeline'] = fig
1189
+
1190
+ # 3. Monthly Performance Dashboard - ENHANCED
1191
+ monthly_data = SQLQueries.get_monthly_performance_trends()
1192
+ if not monthly_data.empty:
1193
+ fig = go.Figure()
1194
+
1195
+ # Claims bar chart
1196
+ fig.add_trace(go.Bar(
1197
+ x=monthly_data['month'],
1198
+ y=monthly_data['total_claims'],
1199
+ name='Total Claims',
1200
+ marker_color='rgba(59, 130, 246, 0.7)',
1201
+ marker_line=dict(color='#3b82f6', width=1),
1202
+ yaxis='y',
1203
+ hovertemplate='<b>Month:</b> %{x}<br><b>Claims:</b> %{y}<extra></extra>'
1204
+ ))
1205
+
1206
+ # Success rate line
1207
+ fig.add_trace(go.Scatter(
1208
+ x=monthly_data['month'],
1209
+ y=monthly_data['monthly_success_rate'],
1210
+ mode='lines+markers',
1211
+ name='Success Rate (%)',
1212
+ line=dict(color='#10b981', width=3),
1213
+ marker=dict(size=8, color='#10b981'),
1214
+ yaxis='y2',
1215
+ hovertemplate='<b>Month:</b> %{x}<br><b>Success Rate:</b> %{y}%<extra></extra>'
1216
+ ))
1217
+
1218
+ fig.update_layout(
1219
+ yaxis=dict(
1220
+ title='Number of Claims',
1221
+ side='left',
1222
+ showgrid=True,
1223
+ gridcolor='#e5e7eb'
1224
+ ),
1225
+ yaxis2=dict(
1226
+ title='Success Rate (%)',
1227
+ side='right',
1228
+ overlaying='y',
1229
+ showgrid=False,
1230
+ range=[0, 100]
1231
+ )
1232
+ )
1233
+
1234
+ fig = apply_readable_chart_style(fig,
1235
+ "πŸ“Š Monthly Performance & Success Trends",
1236
+ "Month",
1237
+ "Claims / Success Rate")
1238
+ charts['monthly_trends'] = fig
1239
+
1240
+ except Exception as e:
1241
+ st.error(f"Error creating time series charts: {e}")
1242
+ charts['error'] = str(e)
1243
+
1244
+ return charts
1245
+
1246
+ # ========== MAIN HEADER ==========
1247
+ st.markdown("""
1248
+ <div class="main-header">
1249
+ <h1>🌍 Food Wastage Management System</h1>
1250
+ <p>Connecting food providers with those in need β€’ Reducing waste β€’ Building community</p>
1251
+ </div>
1252
+ """, unsafe_allow_html=True)
1253
+
1254
+ # ========== SIDEBAR NAVIGATION (FIXED) ==========
1255
+ with st.sidebar:
1256
+ st.title("🧭 Navigation")
1257
+ current_page = st.selectbox(
1258
+ "Choose a page:",
1259
+ ["πŸ“Š Dashboard", "🏒 Providers", "🀝 Receivers", "πŸ₯— Food Listings", "πŸ“¦ Claims", "πŸ“ˆ Analytics", "⏰ Time Series"]
1260
+ )
1261
+
1262
+ # ========== MAIN CONTENT ROUTER (FIXED) ==========
1263
+ if current_page == "πŸ“Š Dashboard":
1264
+ st.header("πŸ“Š Dashboard Overview")
1265
+
1266
+ # Display key metrics with enhanced visibility
1267
+ col1, col2, col3, col4 = st.columns(4)
1268
+
1269
+ try:
1270
+ system_data = SQLQueries.get_total_food_quantity_available()
1271
+ if not system_data.empty:
1272
+ row = system_data.iloc[0]
1273
+
1274
+ with col1:
1275
+ st.metric("Total Food Items", f"{row['total_food_items']:,}")
1276
+ with col2:
1277
+ st.metric("Fresh Items", f"{row['fresh_items']:,}")
1278
+ with col3:
1279
+ st.metric("Total Providers", f"{row['contributing_providers']:,}")
1280
+ with col4:
1281
+ st.metric("Cities Covered", f"{row['cities_covered']:,}")
1282
+ except Exception as e:
1283
+ st.warning("Loading dashboard metrics...")
1284
+
1285
+ # Display enhanced charts
1286
+ st.subheader("πŸ“ˆ Analytics Overview")
1287
+ charts = create_project_required_charts()
1288
+
1289
+ for chart_name, chart in charts.items():
1290
+ if chart_name != 'error' and chart is not None:
1291
+ try:
1292
+ st.plotly_chart(chart, use_container_width=True, config={'displayModeBar': False})
1293
+ except Exception as e:
1294
+ st.error(f"Error displaying chart {chart_name}: {e}")