AjaykumarPilla commited on
Commit
14faef4
·
verified ·
1 Parent(s): 27af5df

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +173 -77
app.py CHANGED
@@ -4,7 +4,7 @@ import pandas as pd
4
  import matplotlib.pyplot as plt
5
  import os
6
  from datetime import datetime
7
- from model import predict_delay
8
  from utils import validate_inputs, generate_heatmap
9
  from reportlab.lib.pagesizes import letter
10
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
@@ -15,6 +15,7 @@ from simple_salesforce import Salesforce
15
  import base64
16
  import logging
17
  import json
 
18
 
19
  # Configure logging
20
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -41,6 +42,10 @@ except Exception as e:
41
  logger.error(f"Salesforce connection failed: {str(e)}")
42
  sf = None
43
 
 
 
 
 
44
  # Title
45
  st.title("Project Delay Predictor 🚀")
46
 
@@ -51,11 +56,67 @@ task_options = {
51
  "Construction": ["Foundation Work", "Structural Build", "Utility Installation"]
52
  }
53
 
54
- # Initialize session state for phase and task
55
  if 'phase' not in st.session_state:
56
  st.session_state.phase = ""
57
  if 'task' not in st.session_state:
58
  st.session_state.task = ""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  # Function to format high_risk_phases with flag and alert
61
  def format_high_risk_phases(high_risk_phases):
@@ -89,8 +150,10 @@ def generate_pdf(input_data, prediction, heatmap_fig):
89
  f"Workforce Gap: {input_data['workforce_gap']}%",
90
  f"Workforce Skill Level: {input_data['workforce_skill_level']}",
91
  f"Workforce Shift Hours: {input_data['workforce_shift_hours']}",
 
92
  f"Weather Condition: {input_data['weather_condition']}",
93
- f"Weather Forecast Date: {input_data['weather_forecast_date']}"
 
94
  ]
95
  for field in input_fields:
96
  story.append(Paragraph(field, styles['Normal']))
@@ -137,8 +200,10 @@ def save_to_salesforce(input_data, prediction, pdf_buffer):
137
  "Workforce_Gap__c": input_data["workforce_gap"],
138
  "Workforce_Skill_Level__c": input_data["workforce_skill_level"],
139
  "Workforce_Shift_Hours__c": input_data["workforce_shift_hours"],
 
140
  "Weather_Condition__c": input_data["weather_condition"],
141
  "Weather_Forecast_Date__c": input_data["weather_forecast_date"],
 
142
  "Delay_Probability__c": prediction["delay_probability"],
143
  "AI_Insights__c": prediction["ai_insights"],
144
  "High_Risk_Phases__c": "; ".join(format_high_risk_phases(prediction["high_risk_phases"]))
@@ -158,7 +223,7 @@ def save_to_salesforce(input_data, prediction, pdf_buffer):
158
  "Title": f"Delay_Prediction_Report_{input_data['project_name']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
159
  "PathOnClient": "project_delay_report.pdf",
160
  "VersionData": pdf_base64,
161
- "FirstPublishLocationId": record_id # Link to the Delay_Predictor__c record
162
  }
163
  cv_result = sf.ContentVersion.create(content_version)
164
  if not cv_result["success"]:
@@ -181,7 +246,7 @@ def save_to_salesforce(input_data, prediction, pdf_buffer):
181
 
182
  # Update the Delay_Predictor__c record with the PDF URL
183
  update_result = sf.Delay_Predictor__c.update(record_id, {"PDF_Report__c": pdf_url})
184
- if update_result != 204: # 204 indicates success for updates
185
  logger.error(f"Failed to update PDF_Report__c with URL: {pdf_url}")
186
  return f"Failed to update PDF_Report__c field: {update_result}"
187
 
@@ -212,11 +277,10 @@ with st.form("project_form"):
212
  workforce_skill_level = st.selectbox("Workforce Skill Level", ["", "Low", "Medium", "High"], index=0)
213
  workforce_shift_hours = st.number_input("Workforce Shift Hours", min_value=0, step=1, value=0)
214
  st.write(f"**Selected Shift Hours**: {workforce_shift_hours}")
215
- weather_condition = st.selectbox("Weather Condition", ["", "Sunny", "Partly Cloudy", "Cloudy", "Light Rain", "Heavy Rain", "Severe Storm"], index=0)
216
- st.write(f"**Selected Weather Condition**: {weather_condition}")
217
  weather_forecast_date = st.date_input("Weather Forecast Date", min_value=datetime(2025, 1, 1), value=None)
218
 
219
- submit_button = st.form_submit_button("Predict Delay")
220
 
221
  # Process form submission
222
  if submit_button:
@@ -231,82 +295,114 @@ if submit_button:
231
  "workforce_gap": workforce_gap,
232
  "workforce_skill_level": workforce_skill_level,
233
  "workforce_shift_hours": workforce_shift_hours,
234
- "weather_condition": weather_condition,
235
- "weather_forecast_date": weather_forecast_date.strftime("%Y-%m-%d") if weather_forecast_date else ""
 
 
236
  }
237
 
 
238
  error = validate_inputs(input_data)
239
- if error:
240
  st.error(error)
241
  logger.error(f"Validation error: {error}")
242
  else:
243
- with st.spinner("Generating predictions and AI insights..."):
244
- try:
245
- prediction = predict_delay(input_data)
246
- except Exception as e:
247
- st.error(f"Prediction failed: {str(e)}")
248
- logger.error(f"Prediction failed: {str(e)}")
249
- prediction = {"error": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
- if "error" in prediction:
252
- st.error(prediction["error"])
 
 
 
253
  else:
254
- st.subheader("Prediction Results")
255
- st.write(f"**Delay Probability**: {prediction['delay_probability']:.2f}%")
256
- st.write("**High Risk Phases**:")
257
- for line in format_high_risk_phases(prediction['high_risk_phases']):
258
- st.write(line)
259
- st.write(f"**AI Insights**: {prediction['ai_insights']}")
260
- st.write(f"**Weather Condition**: {prediction['weather_condition']}")
261
 
262
- # Generate Chart.js heatmap
263
- chart_config = generate_heatmap(prediction['delay_probability'], f"{phase}: {task}")
264
- chart_id = f"chart-{hash(str(chart_config))}"
265
- chart_html = f"""
266
- <canvas id="{chart_id}" style="max-height: 200px; max-width: 600px;"></canvas>
267
- <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
268
- <script>
269
- try {{
270
- const ctx = document.getElementById('{chart_id}').getContext('2d');
271
- new Chart(ctx, {json.dumps(chart_config)});
272
- }} catch (e) {{
273
- console.error('Chart.js failed: ' + e);
274
- }}
275
- </script>
276
- """
277
- try:
278
- components.html(chart_html, height=250)
279
- logger.info("Chart.js heatmap rendered")
280
- except Exception as e:
281
- logger.error(f"Chart.js rendering failed: {str(e)}")
282
- st.error("Failed to render heatmap; please check your browser settings.")
283
-
284
- # Generate matplotlib figure for PDF
285
- fig, ax = plt.subplots(figsize=(8, 2))
286
- color = 'red' if prediction['delay_probability'] > 75 else 'yellow' if prediction['delay_probability'] > 50 else 'green'
287
- ax.barh([f"{phase}: {task}"], [prediction['delay_probability']], color=color, edgecolor='black')
288
- ax.set_xlim(0, 100)
289
- ax.set_xlabel("Delay Probability (%)")
290
- ax.set_title("Delay Risk Heatmap")
291
- plt.tight_layout()
292
-
293
- pdf_buffer = generate_pdf(input_data, prediction, fig)
294
- plt.close(fig)
295
- st.download_button(
296
- label="Download Prediction Report (PDF)",
297
- data=pdf_buffer,
298
- file_name="project_delay_report.pdf",
299
- mime="application/pdf"
300
- )
301
-
302
- # Save to Salesforce, including PDF
303
- sf_error = save_to_salesforce(input_data, prediction, pdf_buffer)
304
- if sf_error:
305
- st.error(sf_error)
306
- logger.error(f"Salesforce error: {sf_error}")
307
  else:
308
- st.success("Prediction data and PDF successfully saved to Salesforce!")
309
- logger.info("Data and PDF saved to Salesforce")
310
-
311
- st.session_state.prediction = prediction
312
- st.session_state.input_data = input_data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import matplotlib.pyplot as plt
5
  import os
6
  from datetime import datetime
7
+ from model import predict_delay, get_weather_condition
8
  from utils import validate_inputs, generate_heatmap
9
  from reportlab.lib.pagesizes import letter
10
  from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Image
 
15
  import base64
16
  import logging
17
  import json
18
+ import requests
19
 
20
  # Configure logging
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
42
  logger.error(f"Salesforce connection failed: {str(e)}")
43
  sf = None
44
 
45
+ # Weather API configuration
46
+ WEATHER_API_KEY = os.environ.get("WEATHER_API_KEY")
47
+ WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/forecast"
48
+
49
  # Title
50
  st.title("Project Delay Predictor 🚀")
51
 
 
56
  "Construction": ["Foundation Work", "Structural Build", "Utility Installation"]
57
  }
58
 
59
+ # Initialize session state
60
  if 'phase' not in st.session_state:
61
  st.session_state.phase = ""
62
  if 'task' not in st.session_state:
63
  st.session_state.task = ""
64
+ if 'weather_data' not in st.session_state:
65
+ st.session_state.weather_data = None
66
+
67
+ # Function to fetch weather data
68
+ def fetch_weather_data(project_location, date):
69
+ if not WEATHER_API_KEY:
70
+ logger.error("WEATHER_API_KEY not set")
71
+ return None, "Weather API key not set. Please provide a valid API key."
72
+ try:
73
+ params = {
74
+ "q": project_location,
75
+ "appid": WEATHER_API_KEY,
76
+ "units": "metric"
77
+ }
78
+ response = requests.get(WEATHER_API_URL, params=params)
79
+ response.raise_for_status()
80
+ data = response.json()
81
+
82
+ # Find the closest forecast to the specified date
83
+ target_date = datetime.strptime(date, "%Y-%m-%d")
84
+ closest_forecast = None
85
+ min_time_diff = float('inf')
86
+
87
+ for forecast in data['list']:
88
+ forecast_time = datetime.fromtimestamp(forecast['dt'])
89
+ time_diff = abs((forecast_time - target_date).total_seconds())
90
+ if time_diff < min_time_diff:
91
+ min_time_diff = time_diff
92
+ closest_forecast = forecast
93
+
94
+ if not closest_forecast:
95
+ return None, "No forecast available for the specified date."
96
+
97
+ # Map weather conditions to impact score
98
+ weather_main = closest_forecast['weather'][0]['main'].lower()
99
+ if 'clear' in weather_main:
100
+ impact_score = 10
101
+ elif 'clouds' in weather_main:
102
+ impact_score = 30 if closest_forecast['clouds']['all'] < 50 else 50
103
+ elif 'rain' in weather_main:
104
+ impact_score = 70 if closest_forecast['rain'].get('3h', 0) < 2.5 else 85
105
+ elif 'storm' in weather_main or 'thunderstorm' in weather_main:
106
+ impact_score = 90
107
+ else:
108
+ impact_score = 50 # Default for other conditions (e.g., fog, snow)
109
+
110
+ weather_condition = get_weather_condition(impact_score)
111
+ return {
112
+ "weather_impact_score": impact_score,
113
+ "weather_condition": weather_condition,
114
+ "temperature": closest_forecast['main']['temp'],
115
+ "humidity": closest_forecast['main']['humidity']
116
+ }, None
117
+ except Exception as e:
118
+ logger.error(f"Failed to fetch weather data: {str(e)}")
119
+ return None, f"Failed to fetch weather data for {project_location}: {str(e)}"
120
 
121
  # Function to format high_risk_phases with flag and alert
122
  def format_high_risk_phases(high_risk_phases):
 
150
  f"Workforce Gap: {input_data['workforce_gap']}%",
151
  f"Workforce Skill Level: {input_data['workforce_skill_level']}",
152
  f"Workforce Shift Hours: {input_data['workforce_shift_hours']}",
153
+ f"Weather Impact Score: {input_data['weather_impact_score']}",
154
  f"Weather Condition: {input_data['weather_condition']}",
155
+ f"Weather Forecast Date: {input_data['weather_forecast_date']}",
156
+ f"Project Location: {input_data['project_location']}"
157
  ]
158
  for field in input_fields:
159
  story.append(Paragraph(field, styles['Normal']))
 
200
  "Workforce_Gap__c": input_data["workforce_gap"],
201
  "Workforce_Skill_Level__c": input_data["workforce_skill_level"],
202
  "Workforce_Shift_Hours__c": input_data["workforce_shift_hours"],
203
+ "Weather_Impact_Score__c": input_data["weather_impact_score"],
204
  "Weather_Condition__c": input_data["weather_condition"],
205
  "Weather_Forecast_Date__c": input_data["weather_forecast_date"],
206
+ "Project_Location__c": input_data["project_location"],
207
  "Delay_Probability__c": prediction["delay_probability"],
208
  "AI_Insights__c": prediction["ai_insights"],
209
  "High_Risk_Phases__c": "; ".join(format_high_risk_phases(prediction["high_risk_phases"]))
 
223
  "Title": f"Delay_Prediction_Report_{input_data['project_name']}_{datetime.now().strftime('%Y%m%d_%H%M%S')}",
224
  "PathOnClient": "project_delay_report.pdf",
225
  "VersionData": pdf_base64,
226
+ "FirstPublishLocationId": record_id
227
  }
228
  cv_result = sf.ContentVersion.create(content_version)
229
  if not cv_result["success"]:
 
246
 
247
  # Update the Delay_Predictor__c record with the PDF URL
248
  update_result = sf.Delay_Predictor__c.update(record_id, {"PDF_Report__c": pdf_url})
249
+ if update_result != 204:
250
  logger.error(f"Failed to update PDF_Report__c with URL: {pdf_url}")
251
  return f"Failed to update PDF_Report__c field: {update_result}"
252
 
 
277
  workforce_skill_level = st.selectbox("Workforce Skill Level", ["", "Low", "Medium", "High"], index=0)
278
  workforce_shift_hours = st.number_input("Workforce Shift Hours", min_value=0, step=1, value=0)
279
  st.write(f"**Selected Shift Hours**: {workforce_shift_hours}")
280
+ project_location = st.text_input("Project Location (City)", placeholder="e.g., New York")
 
281
  weather_forecast_date = st.date_input("Weather Forecast Date", min_value=datetime(2025, 1, 1), value=None)
282
 
283
+ submit_button = st.form_submit_button("Fetch Weather and Predict Delay")
284
 
285
  # Process form submission
286
  if submit_button:
 
295
  "workforce_gap": workforce_gap,
296
  "workforce_skill_level": workforce_skill_level,
297
  "workforce_shift_hours": workforce_shift_hours,
298
+ "weather_impact_score": 0, # Placeholder, to be updated
299
+ "weather_condition": "", # Placeholder, to be updated
300
+ "weather_forecast_date": weather_forecast_date.strftime("%Y-%m-%d") if weather_forecast_date else "",
301
+ "project_location": project_location
302
  }
303
 
304
+ # Validate inputs (excluding weather fields initially)
305
  error = validate_inputs(input_data)
306
+ if error and not error.startswith("Please select or fill in weather"):
307
  st.error(error)
308
  logger.error(f"Validation error: {error}")
309
  else:
310
+ # Fetch weather data
311
+ if project_location and weather_forecast_date:
312
+ weather_data, weather_error = fetch_weather_data(project_location, input_data["weather_forecast_date"])
313
+ if weather_error:
314
+ st.error(weather_error)
315
+ logger.error(weather_error)
316
+ input_data["weather_impact_score"] = 50 # Fallback value
317
+ input_data["weather_condition"] = "Unknown"
318
+ else:
319
+ input_data["weather_impact_score"] = weather_data["weather_impact_score"]
320
+ input_data["weather_condition"] = weather_data["weather_condition"]
321
+ st.write(f"**Weather Data for {project_location} on {input_data['weather_forecast_date']}**:")
322
+ st.write(f"- Condition: {weather_data['weather_condition']}")
323
+ st.write(f"- Impact Score: {weather_data['weather_impact_score']}")
324
+ st.write(f"- Temperature: {weather_data['temperature']}°C")
325
+ st.write(f"- Humidity: {weather_data['humidity']}%")
326
+ st.session_state.weather_data = weather_data
327
+ else:
328
+ st.error("Please provide a project location and weather forecast date.")
329
+ logger.error("Project location or weather forecast date missing")
330
+ input_data["weather_impact_score"] = 50 # Fallback value
331
+ input_data["weather_condition"] = "Unknown"
332
 
333
+ # Re-validate with weather data
334
+ error = validate_inputs(input_data)
335
+ if error:
336
+ st.error(error)
337
+ logger.error(f"Validation error: {error}")
338
  else:
339
+ with st.spinner("Generating predictions and AI insights..."):
340
+ try:
341
+ prediction = predict_delay(input_data)
342
+ except Exception as e:
343
+ st.error(f"Prediction failed: {str(e)}")
344
+ logger.error(f"Prediction failed: {str(e)}")
345
+ prediction = {"error": str(e)}
346
 
347
+ if "error" in prediction:
348
+ st.error(prediction["error"])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
349
  else:
350
+ st.subheader("Prediction Results")
351
+ st.write(f"**Delay Probability**: {prediction['delay_probability']:.2f}%")
352
+ st.write("**High Risk Phases**:")
353
+ for line in format_high_risk_phases(prediction['high_risk_phases']):
354
+ st.write(line)
355
+ st.write(f"**AI Insights**: {prediction['ai_insights']}")
356
+ st.write(f"**Weather Condition**: {prediction['weather_condition']}")
357
+
358
+ # Generate Chart.js heatmap
359
+ chart_config = generate_heatmap(prediction['delay_probability'], f"{phase}: {task}")
360
+ chart_id = f"chart-{hash(str(chart_config))}"
361
+ chart_html = f"""
362
+ <canvas id="{chart_id}" style="max-height: 200px; max-width: 600px;"></canvas>
363
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
364
+ <script>
365
+ try {{
366
+ const ctx = document.getElementById('{chart_id}').getContext('2d');
367
+ new Chart(ctx, {json.dumps(chart_config)});
368
+ }} catch (e) {{
369
+ console.error('Chart.js failed: ' + e);
370
+ }}
371
+ </script>
372
+ """
373
+ try:
374
+ components.html(chart_html, height=250)
375
+ logger.info("Chart.js heatmap rendered")
376
+ except Exception as e:
377
+ logger.error(f"Chart.js rendering failed: {str(e)}")
378
+ st.error("Failed to render heatmap; please check your browser settings.")
379
+
380
+ # Generate matplotlib figure for PDF
381
+ fig, ax = plt.subplots(figsize=(8, 2))
382
+ color = 'red' if prediction['delay_probability'] > 75 else 'yellow' if prediction['delay_probability'] > 50 else 'green'
383
+ ax.barh([f"{phase}: {task}"], [prediction['delay_probability']], color=color, edgecolor='black')
384
+ ax.set_xlim(0, 100)
385
+ ax.set_xlabel("Delay Probability (%)")
386
+ ax.set_title("Delay Risk Heatmap")
387
+ plt.tight_layout()
388
+
389
+ pdf_buffer = generate_pdf(input_data, prediction, fig)
390
+ plt.close(fig)
391
+ st.download_button(
392
+ label="Download Prediction Report (PDF)",
393
+ data=pdf_buffer,
394
+ file_name="project_delay_report.pdf",
395
+ mime="application/pdf"
396
+ )
397
+
398
+ # Save to Salesforce, including PDF
399
+ sf_error = save_to_salesforce(input_data, prediction, pdf_buffer)
400
+ if sf_error:
401
+ st.error(sf_error)
402
+ logger.error(f"Salesforce error: {sf_error}")
403
+ else:
404
+ st.success("Prediction data and PDF successfully saved to Salesforce!")
405
+ logger.info("Data and PDF saved to Salesforce")
406
+
407
+ st.session_state.prediction = prediction
408
+ st.session_state.input_data = input_data