Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -90,11 +90,32 @@ def generate_pdf(record_data):
|
|
90 |
logger.debug("Generating PDF...")
|
91 |
pdf_file = BytesIO()
|
92 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
93 |
-
|
94 |
-
|
95 |
-
c.
|
96 |
-
c.drawString(100,
|
97 |
-
c.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
c.save()
|
99 |
pdf_file.seek(0)
|
100 |
logger.debug("PDF generated successfully.")
|
@@ -114,8 +135,8 @@ def upload_pdf_to_salesforce(pdf_file, project_title, record_id=None):
|
|
114 |
encoded_pdf_data = base64.b64encode(pdf_file.getvalue()).decode('utf-8')
|
115 |
logger.debug(f"Uploading PDF for project: {project_title}, record ID: {record_id}")
|
116 |
content_version_data = {
|
117 |
-
"Title": f"{project_title} -
|
118 |
-
"PathOnClient": f"{project_title}
|
119 |
"VersionData": encoded_pdf_data,
|
120 |
}
|
121 |
|
@@ -194,14 +215,14 @@ def send_to_salesforce(project_title, gantt_chart_url, ai_plan_score, estimated_
|
|
194 |
new_record_id = project_record['id']
|
195 |
logger.info(f"Created new Salesforce record with ID: {new_record_id}")
|
196 |
return new_record_id
|
197 |
-
|
198 |
except Exception as e:
|
199 |
logger.error(f"Error sending data to Salesforce: {str(e)}", exc_info=True)
|
200 |
if hasattr(e, 'content') and e.content:
|
201 |
logger.error(f"Salesforce API response: {e.content}")
|
202 |
return None
|
203 |
|
204 |
-
# Function to generate Gantt chart
|
205 |
def generate_project_timeline(boq_file, weather, workforce, location, project_title):
|
206 |
temp_dir = None
|
207 |
try:
|
@@ -210,36 +231,101 @@ def generate_project_timeline(boq_file, weather, workforce, location, project_ti
|
|
210 |
raise ValueError("No file uploaded")
|
211 |
|
212 |
temp_dir = tempfile.mkdtemp()
|
213 |
-
output_filename = f"gantt_chart_{project_title.replace(' ', '')}
|
214 |
output_path = os.path.join(temp_dir, output_filename)
|
215 |
logger.debug(f"Gantt chart will be saved to: {output_path}")
|
216 |
|
|
|
217 |
if isinstance(boq_file, str):
|
218 |
df = pd.read_csv(boq_file)
|
219 |
else:
|
220 |
df = pd.read_csv(boq_file.name)
|
221 |
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
-
|
233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
234 |
plt.close(fig)
|
235 |
|
236 |
-
|
237 |
-
f"{task} - {'High' if weather == 'rainy' and duration > 5 else 'Low'} Risk (Weather)"
|
238 |
-
for task, duration in zip(task_names, task_durations)
|
239 |
-
]
|
240 |
-
risk_tags_str = "\n".join(risk_tags)
|
241 |
-
logger.debug(f"Generated risk tags: {risk_tags_str}")
|
242 |
-
logger.info("Gantt chart and risk tags generated successfully.")
|
243 |
return output_path, risk_tags_str, temp_dir
|
244 |
except Exception as e:
|
245 |
logger.error(f"Error generating project timeline: {str(e)}", exc_info=True)
|
@@ -255,16 +341,22 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
255 |
if not boq_file:
|
256 |
return None, "Error: No BOQ file uploaded"
|
257 |
|
|
|
|
|
|
|
|
|
258 |
boq_file_path = boq_file.name if hasattr(boq_file, 'name') else boq_file
|
259 |
file_path, risk_tags, temp_dir = generate_project_timeline(boq_file_path, weather, workforce, location, project_title)
|
260 |
if not file_path:
|
261 |
return None, f"Error: Failed to generate timeline: {risk_tags}"
|
262 |
|
|
|
263 |
df = pd.read_csv(boq_file_path)
|
264 |
estimated_duration = sum(df["Duration"])
|
265 |
ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
|
266 |
logger.debug(f"Estimated duration: {estimated_duration}, AI plan score: {ai_plan_score}")
|
267 |
|
|
|
268 |
record_id = send_to_salesforce(
|
269 |
project_title=project_title,
|
270 |
gantt_chart_url="",
|
@@ -277,17 +369,22 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
277 |
)
|
278 |
|
279 |
if not record_id:
|
280 |
-
return None, f"Error: Failed to create Salesforce record - check logs for details\n\
|
281 |
|
|
|
282 |
work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
|
283 |
if not work_items_id:
|
284 |
logger.warning("Failed to upload BOQ file, but proceeding with record creation")
|
285 |
|
|
|
286 |
record_data = {
|
287 |
"project_title": project_title,
|
288 |
"estimated_duration": estimated_duration,
|
289 |
"ai_plan_score": ai_plan_score,
|
290 |
"status": "Draft",
|
|
|
|
|
|
|
291 |
"risk_tags": risk_tags,
|
292 |
}
|
293 |
pdf_file = generate_pdf(record_data)
|
@@ -300,6 +397,7 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
300 |
if not pdf_content_id:
|
301 |
logger.warning("Failed to upload PDF, but proceeding with record creation")
|
302 |
|
|
|
303 |
update_result = send_to_salesforce(
|
304 |
project_title=project_title,
|
305 |
gantt_chart_url=pdf_url if pdf_url else "",
|
@@ -314,7 +412,8 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
314 |
if not update_result:
|
315 |
logger.warning("Failed to update record with PDF URL, but record was created")
|
316 |
|
317 |
-
|
|
|
318 |
image_url = None
|
319 |
if image_content_id:
|
320 |
sf = get_salesforce_connection()
|
@@ -322,8 +421,22 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
322 |
image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
|
323 |
logger.debug(f"Generated image URL: {image_url}")
|
324 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
325 |
logger.info("Gradio interface completed successfully.")
|
326 |
-
return image_url if image_url else file_path,
|
327 |
except Exception as e:
|
328 |
logger.error(f"Error in Gradio interface: {str(e)}", exc_info=True)
|
329 |
return None, f"Error in Gradio interface: {str(e)}"
|
@@ -336,20 +449,32 @@ def gradio_interface(boq_file, weather, workforce, location, project_title):
|
|
336 |
demo = gr.Blocks(theme="default")
|
337 |
with demo:
|
338 |
gr.Markdown("## AI Civil Work Planner")
|
339 |
-
gr.Markdown("Generate a project timeline (Gantt chart) and risk
|
340 |
|
341 |
with gr.Row():
|
342 |
with gr.Column():
|
343 |
-
boq_file = gr.File(label="Upload BOQ Data (CSV format)")
|
344 |
-
weather = gr.Dropdown(label="Weather
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
349 |
|
350 |
with gr.Column():
|
351 |
-
output_image = gr.Image(label="Gantt Chart"
|
352 |
-
|
|
|
|
|
|
|
353 |
|
354 |
submit_btn.click(
|
355 |
fn=gradio_interface,
|
@@ -361,21 +486,21 @@ with demo:
|
|
361 |
app = FastAPI()
|
362 |
app.add_middleware(
|
363 |
CORSMiddleware,
|
364 |
-
allow_origins=["
|
365 |
allow_credentials=True,
|
366 |
allow_methods=["*"],
|
367 |
allow_headers=["*"],
|
368 |
)
|
369 |
|
370 |
-
# Mount directory for temporary files
|
371 |
app.mount("/static", StaticFiles(directory=tempfile.gettempdir()), name="static")
|
372 |
|
373 |
-
# Health check endpoint
|
374 |
@app.get("/health")
|
375 |
async def health_check():
|
376 |
return {"status": "healthy"}
|
377 |
|
378 |
-
# FastAPI endpoint for processing BOQ files
|
379 |
@app.post("/api/gradio_interface")
|
380 |
async def api_gradio_interface(
|
381 |
boq_file: UploadFile = File(...),
|
@@ -400,7 +525,6 @@ async def api_gradio_interface(
|
|
400 |
df = pd.read_csv(boq_file_path)
|
401 |
estimated_duration = sum(df["Duration"])
|
402 |
ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
|
403 |
-
logger.debug(f"Estimated duration: {estimated_duration}, AI plan score: {ai_plan_score}")
|
404 |
|
405 |
record_id = send_to_salesforce(
|
406 |
project_title=project_title,
|
@@ -415,30 +539,27 @@ async def api_gradio_interface(
|
|
415 |
|
416 |
if not record_id:
|
417 |
return JSONResponse({
|
418 |
-
"error":
|
419 |
-
"text": f"Risk
|
420 |
}, status_code=500)
|
421 |
|
422 |
work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
|
423 |
-
|
424 |
-
logger.warning("Failed to upload BOQ file, but proceeding with record creation")
|
425 |
-
|
426 |
record_data = {
|
427 |
"project_title": project_title,
|
428 |
"estimated_duration": estimated_duration,
|
429 |
"ai_plan_score": ai_plan_score,
|
430 |
"status": "Draft",
|
|
|
|
|
|
|
431 |
"risk_tags": risk_tags,
|
432 |
}
|
|
|
433 |
pdf_file = generate_pdf(record_data)
|
434 |
-
if not pdf_file:
|
435 |
-
logger.warning("Failed to generate PDF, but proceeding with record creation")
|
436 |
-
|
437 |
pdf_content_id, pdf_url = None, None
|
438 |
if pdf_file:
|
439 |
pdf_content_id, pdf_url = upload_pdf_to_salesforce(pdf_file, project_title, record_id)
|
440 |
-
if not pdf_content_id:
|
441 |
-
logger.warning("Failed to upload PDF, but proceeding with record creation")
|
442 |
|
443 |
update_result = send_to_salesforce(
|
444 |
project_title=project_title,
|
@@ -451,8 +572,6 @@ async def api_gradio_interface(
|
|
451 |
weather_type=weather,
|
452 |
work_items_id=work_items_id if work_items_id else ""
|
453 |
)
|
454 |
-
if not update_result:
|
455 |
-
logger.warning("Failed to update record with PDF URL, but record was created")
|
456 |
|
457 |
image_content_id = upload_file_to_salesforce(file_path, f"{project_title}_Gantt_Chart.png", record_id)
|
458 |
image_url = None
|
@@ -460,12 +579,23 @@ async def api_gradio_interface(
|
|
460 |
sf = get_salesforce_connection()
|
461 |
if sf:
|
462 |
image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
|
463 |
-
logger.debug(f"Generated image URL: {image_url}")
|
464 |
|
465 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
466 |
return JSONResponse({
|
467 |
"image": image_url if image_url else f"/static/{os.path.basename(file_path)}",
|
468 |
-
"text":
|
469 |
})
|
470 |
except Exception as e:
|
471 |
logger.error(f"Error in API gradio interface: {str(e)}", exc_info=True)
|
@@ -473,7 +603,6 @@ async def api_gradio_interface(
|
|
473 |
finally:
|
474 |
if temp_dir and os.path.exists(temp_dir):
|
475 |
shutil.rmtree(temp_dir)
|
476 |
-
logger.debug(f"Cleaned up temporary directory: {temp_dir}")
|
477 |
|
478 |
if __name__ == "__main__":
|
479 |
# Run Gradio UI
|
|
|
90 |
logger.debug("Generating PDF...")
|
91 |
pdf_file = BytesIO()
|
92 |
c = canvas.Canvas(pdf_file, pagesize=letter)
|
93 |
+
|
94 |
+
# Add project details
|
95 |
+
c.setFont("Helvetica-Bold", 14)
|
96 |
+
c.drawString(100, 750, "Project Summary Report")
|
97 |
+
c.setFont("Helvetica", 12)
|
98 |
+
|
99 |
+
y_position = 720
|
100 |
+
for key, value in record_data.items():
|
101 |
+
if key == "risk_tags":
|
102 |
+
continue # We'll handle risk tags separately
|
103 |
+
|
104 |
+
c.drawString(100, y_position, f"{key.replace('_', ' ').title()}: {value}")
|
105 |
+
y_position -= 20
|
106 |
+
|
107 |
+
# Add risk tags section
|
108 |
+
if "risk_tags" in record_data:
|
109 |
+
c.setFont("Helvetica-Bold", 12)
|
110 |
+
c.drawString(100, y_position - 20, "Risk Analysis:")
|
111 |
+
c.setFont("Helvetica", 10)
|
112 |
+
|
113 |
+
risk_tags = record_data["risk_tags"].split("\n")
|
114 |
+
for tag in risk_tags:
|
115 |
+
if tag.strip():
|
116 |
+
c.drawString(120, y_position - 40, tag)
|
117 |
+
y_position -= 15
|
118 |
+
|
119 |
c.save()
|
120 |
pdf_file.seek(0)
|
121 |
logger.debug("PDF generated successfully.")
|
|
|
135 |
encoded_pdf_data = base64.b64encode(pdf_file.getvalue()).decode('utf-8')
|
136 |
logger.debug(f"Uploading PDF for project: {project_title}, record ID: {record_id}")
|
137 |
content_version_data = {
|
138 |
+
"Title": f"{project_title} - Project Report",
|
139 |
+
"PathOnClient": f"{project_title}_Report.pdf",
|
140 |
"VersionData": encoded_pdf_data,
|
141 |
}
|
142 |
|
|
|
215 |
new_record_id = project_record['id']
|
216 |
logger.info(f"Created new Salesforce record with ID: {new_record_id}")
|
217 |
return new_record_id
|
218 |
+
|
219 |
except Exception as e:
|
220 |
logger.error(f"Error sending data to Salesforce: {str(e)}", exc_info=True)
|
221 |
if hasattr(e, 'content') and e.content:
|
222 |
logger.error(f"Salesforce API response: {e.content}")
|
223 |
return None
|
224 |
|
225 |
+
# Function to generate Gantt chart and risk analysis
|
226 |
def generate_project_timeline(boq_file, weather, workforce, location, project_title):
|
227 |
temp_dir = None
|
228 |
try:
|
|
|
231 |
raise ValueError("No file uploaded")
|
232 |
|
233 |
temp_dir = tempfile.mkdtemp()
|
234 |
+
output_filename = f"gantt_chart_{project_title.replace(' ', '_')}.png"
|
235 |
output_path = os.path.join(temp_dir, output_filename)
|
236 |
logger.debug(f"Gantt chart will be saved to: {output_path}")
|
237 |
|
238 |
+
# Read the BOQ file
|
239 |
if isinstance(boq_file, str):
|
240 |
df = pd.read_csv(boq_file)
|
241 |
else:
|
242 |
df = pd.read_csv(boq_file.name)
|
243 |
|
244 |
+
# Validate required columns
|
245 |
+
required_columns = ["Task Name", "Duration"]
|
246 |
+
missing_columns = [col for col in required_columns if col not in df.columns]
|
247 |
+
if missing_columns:
|
248 |
+
raise ValueError(f"CSV is missing required columns: {', '.join(missing_columns)}")
|
249 |
+
|
250 |
+
# Generate detailed risk analysis
|
251 |
+
risk_analysis = []
|
252 |
+
for _, row in df.iterrows():
|
253 |
+
task = row["Task Name"]
|
254 |
+
duration = row["Duration"]
|
255 |
+
|
256 |
+
# Weather risk assessment
|
257 |
+
if weather.lower() == "rainy":
|
258 |
+
weather_impact = "High" if duration > 3 else "Medium"
|
259 |
+
weather_reason = "Prolonged rain exposure" if duration > 3 else "Some rain impact expected"
|
260 |
+
elif weather.lower() == "sunny":
|
261 |
+
weather_impact = "Low"
|
262 |
+
weather_reason = "Favorable working conditions"
|
263 |
+
else: # cloudy
|
264 |
+
weather_impact = "Low"
|
265 |
+
weather_reason = "Mild weather impact"
|
266 |
+
|
267 |
+
# Workforce risk assessment
|
268 |
+
if workforce < 10 and duration > 5:
|
269 |
+
workforce_impact = "High"
|
270 |
+
workforce_reason = "Insufficient workforce for task duration"
|
271 |
+
elif workforce < 15 and duration > 10:
|
272 |
+
workforce_impact = "Medium"
|
273 |
+
workforce_reason = "Workforce may be stretched for this duration"
|
274 |
+
else:
|
275 |
+
workforce_impact = "Low"
|
276 |
+
workforce_reason = "Adequate workforce available"
|
277 |
+
|
278 |
+
# Overall risk assessment
|
279 |
+
overall_risk = "High" if "High" in [weather_impact, workforce_impact] else "Medium" if "Medium" in [weather_impact, workforce_impact] else "Low"
|
280 |
+
|
281 |
+
risk_analysis.append(
|
282 |
+
f"Task: {task}\n"
|
283 |
+
f"- Duration: {duration} days\n"
|
284 |
+
f"- Weather Impact: {weather_impact} ({weather_reason})\n"
|
285 |
+
f"- Workforce Impact: {workforce_impact} ({workforce_reason})\n"
|
286 |
+
f"- Overall Risk: {overall_risk}\n"
|
287 |
+
)
|
288 |
+
|
289 |
+
risk_tags_str = "\n".join(risk_analysis)
|
290 |
+
|
291 |
+
# Generate Gantt chart
|
292 |
+
plt.style.use('ggplot')
|
293 |
+
fig, ax = plt.subplots(figsize=(12, 6))
|
294 |
+
|
295 |
+
# Color tasks based on risk level
|
296 |
+
colors = []
|
297 |
+
for _, row in df.iterrows():
|
298 |
+
duration = row["Duration"]
|
299 |
+
if weather.lower() == "rainy" and duration > 3:
|
300 |
+
colors.append('#ff6b6b') # red for high risk
|
301 |
+
elif workforce < 10 and duration > 5:
|
302 |
+
colors.append('#ff6b6b') # red for high risk
|
303 |
+
elif (weather.lower() == "rainy" and duration > 1) or (workforce < 15 and duration > 7):
|
304 |
+
colors.append('#ffd166') # yellow for medium risk
|
305 |
+
else:
|
306 |
+
colors.append('#06d6a0') # green for low risk
|
307 |
+
|
308 |
+
ax.barh(df["Task Name"], df["Duration"], color=colors, edgecolor='black')
|
309 |
+
ax.set_xlabel("Duration (days)", fontweight='bold')
|
310 |
+
ax.set_ylabel("Tasks", fontweight='bold')
|
311 |
+
ax.set_title(f"Project Timeline: {project_title}\nLocation: {location} | Weather: {weather}", fontweight='bold')
|
312 |
+
|
313 |
+
# Add risk legend
|
314 |
+
ax.text(0.95, 0.15,
|
315 |
+
"Risk Levels:\n"
|
316 |
+
"Green = Low Risk\n"
|
317 |
+
"Yellow = Medium Risk\n"
|
318 |
+
"Red = High Risk",
|
319 |
+
transform=ax.transAxes,
|
320 |
+
bbox=dict(facecolor='white', alpha=0.8),
|
321 |
+
verticalalignment='top',
|
322 |
+
horizontalalignment='right')
|
323 |
+
|
324 |
+
plt.tight_layout()
|
325 |
+
fig.savefig(output_path, format="png", bbox_inches="tight", dpi=100)
|
326 |
plt.close(fig)
|
327 |
|
328 |
+
logger.info("Gantt chart and risk analysis generated successfully.")
|
|
|
|
|
|
|
|
|
|
|
|
|
329 |
return output_path, risk_tags_str, temp_dir
|
330 |
except Exception as e:
|
331 |
logger.error(f"Error generating project timeline: {str(e)}", exc_info=True)
|
|
|
341 |
if not boq_file:
|
342 |
return None, "Error: No BOQ file uploaded"
|
343 |
|
344 |
+
# Validate workforce input
|
345 |
+
if workforce <= 0:
|
346 |
+
return None, "Error: Workforce size must be greater than 0"
|
347 |
+
|
348 |
boq_file_path = boq_file.name if hasattr(boq_file, 'name') else boq_file
|
349 |
file_path, risk_tags, temp_dir = generate_project_timeline(boq_file_path, weather, workforce, location, project_title)
|
350 |
if not file_path:
|
351 |
return None, f"Error: Failed to generate timeline: {risk_tags}"
|
352 |
|
353 |
+
# Calculate project metrics
|
354 |
df = pd.read_csv(boq_file_path)
|
355 |
estimated_duration = sum(df["Duration"])
|
356 |
ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
|
357 |
logger.debug(f"Estimated duration: {estimated_duration}, AI plan score: {ai_plan_score}")
|
358 |
|
359 |
+
# Create Salesforce record
|
360 |
record_id = send_to_salesforce(
|
361 |
project_title=project_title,
|
362 |
gantt_chart_url="",
|
|
|
369 |
)
|
370 |
|
371 |
if not record_id:
|
372 |
+
return None, f"Error: Failed to create Salesforce record - check logs for details\n\n=== RISK ANALYSIS ===\n\n{risk_tags}"
|
373 |
|
374 |
+
# Upload BOQ file to Salesforce
|
375 |
work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
|
376 |
if not work_items_id:
|
377 |
logger.warning("Failed to upload BOQ file, but proceeding with record creation")
|
378 |
|
379 |
+
# Generate and upload PDF report
|
380 |
record_data = {
|
381 |
"project_title": project_title,
|
382 |
"estimated_duration": estimated_duration,
|
383 |
"ai_plan_score": ai_plan_score,
|
384 |
"status": "Draft",
|
385 |
+
"location": location,
|
386 |
+
"weather": weather,
|
387 |
+
"workforce_size": workforce,
|
388 |
"risk_tags": risk_tags,
|
389 |
}
|
390 |
pdf_file = generate_pdf(record_data)
|
|
|
397 |
if not pdf_content_id:
|
398 |
logger.warning("Failed to upload PDF, but proceeding with record creation")
|
399 |
|
400 |
+
# Update record with PDF URL
|
401 |
update_result = send_to_salesforce(
|
402 |
project_title=project_title,
|
403 |
gantt_chart_url=pdf_url if pdf_url else "",
|
|
|
412 |
if not update_result:
|
413 |
logger.warning("Failed to update record with PDF URL, but record was created")
|
414 |
|
415 |
+
# Upload Gantt chart image
|
416 |
+
image_content_id = upload_file_to_salesforce(file_path, f"{project_title}_Gantt_Chart.png", record_id)
|
417 |
image_url = None
|
418 |
if image_content_id:
|
419 |
sf = get_salesforce_connection()
|
|
|
421 |
image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
|
422 |
logger.debug(f"Generated image URL: {image_url}")
|
423 |
|
424 |
+
# Format output message
|
425 |
+
output_message = (
|
426 |
+
f"=== PROJECT SUMMARY ===\n\n"
|
427 |
+
f"Project: {project_title}\n"
|
428 |
+
f"Location: {location}\n"
|
429 |
+
f"Weather: {weather}\n"
|
430 |
+
f"Workforce Size: {workforce}\n"
|
431 |
+
f"Estimated Duration: {estimated_duration} days\n"
|
432 |
+
f"AI Plan Score: {ai_plan_score:.1f}%\n\n"
|
433 |
+
f"Salesforce Record ID: {record_id}\n\n"
|
434 |
+
f"=== RISK ANALYSIS ===\n\n"
|
435 |
+
f"{risk_tags}"
|
436 |
+
)
|
437 |
+
|
438 |
logger.info("Gradio interface completed successfully.")
|
439 |
+
return image_url if image_url else file_path, output_message
|
440 |
except Exception as e:
|
441 |
logger.error(f"Error in Gradio interface: {str(e)}", exc_info=True)
|
442 |
return None, f"Error in Gradio interface: {str(e)}"
|
|
|
449 |
demo = gr.Blocks(theme="default")
|
450 |
with demo:
|
451 |
gr.Markdown("## AI Civil Work Planner")
|
452 |
+
gr.Markdown("Generate a project timeline (Gantt chart) and risk analysis based on BOQ data and site parameters.")
|
453 |
|
454 |
with gr.Row():
|
455 |
with gr.Column():
|
456 |
+
boq_file = gr.File(label="Upload BOQ Data (CSV format)", file_types=[".csv"])
|
457 |
+
weather = gr.Dropdown(label="Weather Condition",
|
458 |
+
choices=["Sunny", "Rainy", "Cloudy"],
|
459 |
+
value="Sunny")
|
460 |
+
workforce = gr.Number(label="Workforce Size",
|
461 |
+
value=10,
|
462 |
+
precision=0,
|
463 |
+
minimum=1,
|
464 |
+
maximum=100,
|
465 |
+
step=1)
|
466 |
+
location = gr.Textbox(label="Location",
|
467 |
+
placeholder="Enter project location")
|
468 |
+
project_title = gr.Textbox(label="Project Title",
|
469 |
+
placeholder="Enter project title")
|
470 |
+
submit_btn = gr.Button("Generate Project Plan", variant="primary")
|
471 |
|
472 |
with gr.Column():
|
473 |
+
output_image = gr.Image(label="Gantt Chart",
|
474 |
+
type="filepath")
|
475 |
+
risk_tags = gr.Textbox(label="Project Summary and Risk Analysis",
|
476 |
+
lines=20,
|
477 |
+
max_lines=50)
|
478 |
|
479 |
submit_btn.click(
|
480 |
fn=gradio_interface,
|
|
|
486 |
app = FastAPI()
|
487 |
app.add_middleware(
|
488 |
CORSMiddleware,
|
489 |
+
allow_origins=["*"],
|
490 |
allow_credentials=True,
|
491 |
allow_methods=["*"],
|
492 |
allow_headers=["*"],
|
493 |
)
|
494 |
|
495 |
+
# Mount directory for temporary files
|
496 |
app.mount("/static", StaticFiles(directory=tempfile.gettempdir()), name="static")
|
497 |
|
498 |
+
# Health check endpoint
|
499 |
@app.get("/health")
|
500 |
async def health_check():
|
501 |
return {"status": "healthy"}
|
502 |
|
503 |
+
# FastAPI endpoint for processing BOQ files
|
504 |
@app.post("/api/gradio_interface")
|
505 |
async def api_gradio_interface(
|
506 |
boq_file: UploadFile = File(...),
|
|
|
525 |
df = pd.read_csv(boq_file_path)
|
526 |
estimated_duration = sum(df["Duration"])
|
527 |
ai_plan_score = min(100, max(0, 100 - (estimated_duration / 100)))
|
|
|
528 |
|
529 |
record_id = send_to_salesforce(
|
530 |
project_title=project_title,
|
|
|
539 |
|
540 |
if not record_id:
|
541 |
return JSONResponse({
|
542 |
+
"error": "Failed to create Salesforce record",
|
543 |
+
"text": f"Risk Analysis:\n\n{risk_tags}"
|
544 |
}, status_code=500)
|
545 |
|
546 |
work_items_id = upload_file_to_salesforce(boq_file_path, "Boq_data.csv", record_id)
|
547 |
+
|
|
|
|
|
548 |
record_data = {
|
549 |
"project_title": project_title,
|
550 |
"estimated_duration": estimated_duration,
|
551 |
"ai_plan_score": ai_plan_score,
|
552 |
"status": "Draft",
|
553 |
+
"location": location,
|
554 |
+
"weather": weather,
|
555 |
+
"workforce_size": workforce,
|
556 |
"risk_tags": risk_tags,
|
557 |
}
|
558 |
+
|
559 |
pdf_file = generate_pdf(record_data)
|
|
|
|
|
|
|
560 |
pdf_content_id, pdf_url = None, None
|
561 |
if pdf_file:
|
562 |
pdf_content_id, pdf_url = upload_pdf_to_salesforce(pdf_file, project_title, record_id)
|
|
|
|
|
563 |
|
564 |
update_result = send_to_salesforce(
|
565 |
project_title=project_title,
|
|
|
572 |
weather_type=weather,
|
573 |
work_items_id=work_items_id if work_items_id else ""
|
574 |
)
|
|
|
|
|
575 |
|
576 |
image_content_id = upload_file_to_salesforce(file_path, f"{project_title}_Gantt_Chart.png", record_id)
|
577 |
image_url = None
|
|
|
579 |
sf = get_salesforce_connection()
|
580 |
if sf:
|
581 |
image_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{image_content_id}"
|
|
|
582 |
|
583 |
+
output_message = (
|
584 |
+
f"=== PROJECT SUMMARY ===\n\n"
|
585 |
+
f"Project: {project_title}\n"
|
586 |
+
f"Location: {location}\n"
|
587 |
+
f"Weather: {weather}\n"
|
588 |
+
f"Workforce Size: {workforce}\n"
|
589 |
+
f"Estimated Duration: {estimated_duration} days\n"
|
590 |
+
f"AI Plan Score: {ai_plan_score:.1f}%\n\n"
|
591 |
+
f"Salesforce Record ID: {record_id}\n\n"
|
592 |
+
f"=== RISK ANALYSIS ===\n\n"
|
593 |
+
f"{risk_tags}"
|
594 |
+
)
|
595 |
+
|
596 |
return JSONResponse({
|
597 |
"image": image_url if image_url else f"/static/{os.path.basename(file_path)}",
|
598 |
+
"text": output_message
|
599 |
})
|
600 |
except Exception as e:
|
601 |
logger.error(f"Error in API gradio interface: {str(e)}", exc_info=True)
|
|
|
603 |
finally:
|
604 |
if temp_dir and os.path.exists(temp_dir):
|
605 |
shutil.rmtree(temp_dir)
|
|
|
606 |
|
607 |
if __name__ == "__main__":
|
608 |
# Run Gradio UI
|