Spaces:
Sleeping
Sleeping
Update app.py
#1
by
Sivainti
- opened
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
|