VaneshDev commited on
Commit
cc1122d
Β·
verified Β·
1 Parent(s): a35dc13

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +149 -400
app.py CHANGED
@@ -17,7 +17,6 @@ import locale
17
  try:
18
  locale.setlocale(locale.LC_ALL, 'en_IN.UTF-8')
19
  except locale.Error:
20
- # fallback if locale not supported on system
21
  locale.setlocale(locale.LC_ALL, '')
22
 
23
  # Configure logging
@@ -54,427 +53,177 @@ equipment_choices = [
54
  "Bulldozer", "Crane", "Excavator", "Loader", "Forklift",
55
  "Backhoe", "Grader", "Scraper", "Dump Truck", "Roller"
56
  ]
57
-
58
  project_choices = [
59
  "Project Alpha", "Project Beta", "Project Gamma", "Project Delta", "Project Epsilon",
60
  "Project Zeta", "Project Theta", "Project Sigma", "Project Omega", "Project Phoenix"
61
  ]
62
-
63
  ai_suggestion_choices = ["Move", "Pause Rent", "Repair", "Replace"]
64
 
65
- # Generate PDF report
66
- def generate_pdf_report(record_id, data_dict):
67
- report_id = str(uuid.uuid4())[:8]
68
- report_filename = f"report_{report_id}.pdf"
69
- report_path = Path(f"static/reports/{report_filename}")
70
- report_path.parent.mkdir(parents=True, exist_ok=True)
71
-
72
- c = canvas.Canvas(str(report_path), pagesize=letter)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  c.setFont("Helvetica", 12)
74
- c.drawString(100, 750, f"Equipment Utilization Report")
75
- c.drawString(100, 735, f"Salesforce Record ID: {record_id}")
76
-
77
  y = 710
78
- for key, val in data_dict.items():
79
- c.drawString(100, y, f"{key}: {val}")
80
- y -= 20
81
-
82
  c.save()
83
-
84
- return str(report_path)
85
-
86
- # Generate CSV report
87
- def generate_csv_report(record_id, data_dict):
88
- report_id = str(uuid.uuid4())[:8]
89
- csv_filename = f"report_{report_id}.csv"
90
- csv_path = Path(f"static/reports/{csv_filename}")
91
- csv_path.parent.mkdir(parents=True, exist_ok=True)
92
-
93
- with open(csv_path, mode='w', newline='') as f:
94
- writer = csv.writer(f)
95
- writer.writerow(["Field", "Value"])
96
- writer.writerow(["Salesforce Record ID", record_id])
97
- for k, v in data_dict.items():
98
- writer.writerow([k, v])
99
-
100
- return str(csv_path)
101
-
102
- # Upload PDF to Salesforce and get the URL
103
- def upload_pdf_to_salesforce(pdf_file, record_id=None):
104
- try:
105
- with open(pdf_file, "rb") as f:
106
- file_data = f.read()
107
-
108
- encoded_file_data = base64.b64encode(file_data).decode('utf-8')
109
- logger.debug(f"Uploading PDF file for record ID: {record_id}")
110
-
111
- content_version_data = {
112
- "Title": f"Equipment Utilization Report",
113
- "PathOnClient": f"report_{record_id}.pdf",
114
- "VersionData": encoded_file_data,
115
- }
116
-
117
- if record_id:
118
- content_version_data["FirstPublishLocationId"] = record_id
119
-
120
- content_version = sf.ContentVersion.create(content_version_data)
121
- content_version_id = content_version["id"]
122
- logger.info(f"PDF uploaded to Salesforce with ContentVersion ID: {content_version_id}")
123
-
124
- # Construct download URL (may need org domain adjustment)
125
- file_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{content_version_id}"
126
- logger.debug(f"Generated PDF URL: {file_url}")
127
- return file_url
128
- except Exception as e:
129
- logger.error(f"Error uploading PDF to Salesforce: {str(e)}", exc_info=True)
130
- return None
131
-
132
- def call_ai_model(usage_hours, idle_hours, movement_frequency, cost_per_hour, last_maintenance_str):
133
- try:
134
- total_time = usage_hours + idle_hours
135
- utilization_ratio = usage_hours / total_time if total_time > 0 else 0
136
-
137
- if utilization_ratio < 0.3:
138
- suggestion = "Pause Rent"
139
- elif utilization_ratio < 0.6:
140
- suggestion = "Move"
141
- elif utilization_ratio < 0.8:
142
- suggestion = "Repair"
143
- else:
144
- suggestion = "Replace"
145
-
146
- confidence = min(1.0, utilization_ratio + 0.1)
147
- utilization_score = utilization_ratio * 100
148
-
149
- logger.info(f"AI Model Prediction: suggestion={suggestion}, confidence={confidence:.2f}, utilization_score={utilization_score:.2f}")
150
-
151
- return suggestion, confidence, utilization_score
152
-
153
- except Exception as e:
154
- logger.error(f"Error in AI model prediction: {e}")
155
- return "No Action", 0.0, 0.0
156
-
157
- def process_equipment_utilization(equipment_name, project_name, usage_hours, idle_hours,
158
- movement_frequency, cost_per_hour, last_maintenance, ai_suggestion):
159
-
160
- if not ai_suggestion:
161
- last_maintenance_str = last_maintenance.strftime('%Y-%m-%d') if last_maintenance else ""
162
- ai_suggestion, suggestion_confidence, utilization_score = call_ai_model(
163
- usage_hours, idle_hours, movement_frequency, cost_per_hour, last_maintenance_str
164
- )
165
- else:
166
- suggestion_confidence = 0.9
167
- utilization_score = 85.0
168
-
169
- summary_data = {
170
- "Equipment Name": equipment_name,
171
- "Project": project_name,
172
- "Usage Hours": usage_hours,
173
- "Idle Hours": idle_hours,
174
- "Suggestion": ai_suggestion,
175
- "Confidence": suggestion_confidence,
176
- "Utilization Score": utilization_score,
177
- "Cost per Hour": cost_per_hour,
178
- "Last Maintenance": last_maintenance.strftime('%Y-%m-%d') if last_maintenance else "N/A"
179
- }
180
-
181
- try:
182
- record_data = {
183
- "Equipment_Name__c": equipment_name,
184
- "Project_Name__c": project_name,
185
- "Usage_Hours__c": usage_hours,
186
- "Idle_Hours__c": idle_hours,
187
- "AI_Suggestion__c": ai_suggestion,
188
- "Suggestion_Confidence__c": suggestion_confidence * 100, # Convert 0–1 to 0–100
189
- "Utilization_Score__c": utilization_score,
190
- "Cost_per_Hour__c": cost_per_hour,
191
- "Report_Link__c": "Pending",
192
- "Last_Maintenance__c": last_maintenance.strftime('%Y-%m-%d') if last_maintenance else None,
193
- "Dashboard_Flag__c": False
194
- }
195
- logger.info(f"Creating Salesforce record with data: {record_data}")
196
-
197
- response = sf.Equipment_Utilization_Record__c.create(record_data)
198
- record_id = response.get("id")
199
- logger.info(f"Successfully created Salesforce record with ID: {record_id}")
200
-
201
- # Generate PDF and CSV reports and upload the PDF to Salesforce
202
- pdf_path = generate_pdf_report(record_id, summary_data)
203
- pdf_url = upload_pdf_to_salesforce(pdf_path, record_id)
204
-
205
- # Update the Salesforce record with the PDF URL
206
- sf.Equipment_Utilization_Record__c.update(record_id, {"Report_Link__c": pdf_url})
207
- logger.info(f"Updated Salesforce record {record_id} with Report_Link__c: {pdf_url}")
208
-
209
- return {
210
- "Salesforce_Record_Id": record_id,
211
- "status": "Success",
212
- "AI_Suggestion": ai_suggestion,
213
- "Suggestion_Confidence": suggestion_confidence,
214
- "Utilization_Score": utilization_score,
215
- "Report_Link": pdf_url,
216
- "CSV_Report_Link": generate_csv_report(record_id, summary_data),
217
- "Summary": summary_data,
218
- "Report_File_Path": pdf_path
219
- }
220
- except Exception as e:
221
- logger.error(f"Error creating or updating Salesforce record: {e}")
222
- return {"error": str(e)}
223
-
224
- def process_csv_upload(csv_file_path):
225
- try:
226
- expected_columns = [
227
- "equipment_name", "project_name", "usage_hours", "idle_hours",
228
- "movement_frequency", "cost_per_hour", "last_maintenance", "ai_suggestion"
229
- ]
230
-
231
- df = pd.read_csv(csv_file_path)
232
- df.columns = df.columns.str.strip()
233
-
234
- if not df.columns.tolist() or all(col == '' for col in df.columns):
235
- return {"error": "CSV file has no header row. Required columns: " + ", ".join(expected_columns)}
236
-
237
- if not all(col in df.columns for col in expected_columns):
238
- missing_cols = [col for col in expected_columns if col not in df.columns]
239
- sample_row = ",".join(expected_columns)
240
- return {"error": f"Missing columns: {', '.join(missing_cols)}. Expected: {', '.join(expected_columns)}. Sample row: {sample_row}"}
241
-
242
- results = []
243
- for index, row in df.iterrows():
244
- try:
245
- equipment_name = str(row["equipment_name"]).strip()
246
- project_name = str(row["project_name"]).strip()
247
-
248
- if equipment_name not in equipment_choices:
249
- results.append({"row": index + 1, "status": "Failed", "error": f"Invalid equipment_name: {equipment_name}. Must be one of: {', '.join(equipment_choices)}"})
250
- continue
251
- if project_name not in project_choices:
252
- results.append({"row": index + 1, "status": "Failed", "error": f"Invalid project_name: {project_name}. Must be one of: {', '.join(project_choices)}"})
253
- continue
254
-
255
- usage_hours = float(row["usage_hours"])
256
- idle_hours = float(row["idle_hours"])
257
- movement_frequency = float(row["movement_frequency"])
258
- cost_per_hour = float(row["cost_per_hour"])
259
-
260
- last_maintenance = str(row["last_maintenance"]).strip()
261
- if last_maintenance.lower() in ["", "nan", "none"]:
262
- last_maintenance = None
263
- else:
264
- last_maintenance = datetime.datetime.strptime(last_maintenance, "%Y-%m-%d")
265
-
266
- ai_suggestion = str(row["ai_suggestion"]).strip()
267
- if ai_suggestion.lower() in ["", "nan", "none"]:
268
- ai_suggestion = ""
269
- else:
270
- if ai_suggestion not in ai_suggestion_choices:
271
- results.append({"row": index + 1, "status": "Failed", "error": f"Invalid ai_suggestion: {ai_suggestion}. Must be one of: {', '.join(ai_suggestion_choices)}"})
272
- continue
273
-
274
- result = process_equipment_utilization(
275
- equipment_name=equipment_name,
276
- project_name=project_name,
277
- usage_hours=usage_hours,
278
- idle_hours=idle_hours,
279
- movement_frequency=movement_frequency,
280
- cost_per_hour=cost_per_hour,
281
- last_maintenance=last_maintenance,
282
- ai_suggestion=ai_suggestion
283
- )
284
-
285
- if "error" in result:
286
- results.append({"row": index + 1, "status": "Failed", "error": result["error"]})
287
- else:
288
- results.append({"row": index + 1, "status": "Success", "record_id": result["Salesforce_Record_Id"]})
289
-
290
- except Exception as e:
291
- logger.error(f"Error processing row {index + 1}: {str(e)}")
292
- results.append({"row": index + 1, "status": "Failed", "error": str(e)})
293
-
294
- return {"results": results, "total_rows": len(df), "processed": len(results)}
295
-
296
- except Exception as e:
297
- logger.error(f"Error processing CSV file: {str(e)}")
298
- return {"error": f"Failed to process CSV file: {str(e)}"}
299
-
300
- def format_output_for_display(result):
301
- if "error" in result:
302
- return f"❌ Error: {result['error']}"
303
-
304
  summary = result.get("Summary", {})
305
-
306
- # Format cost per hour as Indian currency if present
307
- cost_per_hour = summary.get("Cost per Hour", 0)
308
  try:
309
- cost_per_hour_formatted = locale.currency(cost_per_hour, grouping=True)
310
- except Exception:
311
- cost_per_hour_formatted = f"β‚Ή{cost_per_hour:,.2f}"
312
-
313
- confidence_percent = result.get('Suggestion_Confidence', 0) * 100
314
- utilization_score = result.get('Utilization_Score', 0)
315
- # Handle utilization_score: if decimal <= 1, convert to percentage
316
- if utilization_score <= 1:
317
- utilization_score_percent = utilization_score * 100
318
- else:
319
- utilization_score_percent = utilization_score
320
-
321
  lines = [
322
  "πŸ“‹ **Equipment Utilization Record :**",
323
- f"β€’ AI Suggestion: {result.get('AI_Suggestion', 'N/A')}",
324
- f"β€’ Suggestion Confidence: {confidence_percent:.2f}%",
325
- f"β€’ Utilization Score: {utilization_score_percent:.2f}%",
326
  f"β€’ Report Link: {result.get('Report_Link', 'N/A')}",
327
- f"β€’ CSV Report Link: {result.get('CSV_Report_Link', 'N/A')}",
328
  "",
329
- "πŸ”Ή "
330
- f" β€’ Equipment Name: {summary.get('Equipment Name', 'N/A')}",
331
- f" β€’ Project: {summary.get('Project', 'N/A')}",
332
  f" β€’ Usage Hours: {summary.get('Usage Hours', 0):.2f}",
333
  f" β€’ Idle Hours: {summary.get('Idle Hours', 0):.2f}",
334
- f" β€’ Cost per Hour: {cost_per_hour_formatted}",
335
- f" β€’ Last Maintenance: {summary.get('Last Maintenance', 'N/A')}"
336
  ]
337
-
338
  return "\n".join(lines)
339
 
340
- def gradio_upload_process(equipment_name, project_name, usage_hours, idle_hours,
341
- movement_frequency, cost_per_hour, last_maintenance, ai_suggestion):
342
- try:
343
- usage_hours = float(usage_hours)
344
- idle_hours = float(idle_hours)
345
- movement_frequency = float(movement_frequency)
346
- cost_per_hour = float(cost_per_hour)
347
- except Exception as e:
348
- return {"error": f"Invalid numeric input: {str(e)}"}, None
349
-
350
- try:
351
- last_maintenance_dt = datetime.datetime.strptime(last_maintenance, "%Y-%m-%d") if last_maintenance else None
352
- except Exception as e:
353
- return {"error": f"Invalid date format for Last Maintenance (expected YYYY-MM-DD): {str(e)}"}, None
354
-
355
- result = process_equipment_utilization(
356
- equipment_name,
357
- project_name,
358
- usage_hours,
359
- idle_hours,
360
- movement_frequency,
361
- cost_per_hour,
362
- last_maintenance_dt,
363
- ai_suggestion
364
- )
365
-
366
- formatted_output = format_output_for_display(result)
367
- # Return the formatted output and generated PDF file path
368
- report_file_path = result.get("Report_File_Path")
369
- return formatted_output, report_file_path
370
-
371
- # Gradio Interface
372
- with gr.Blocks() as app:
373
- gr.Markdown("## πŸ“‹ Equipment Utilization Record Uploader")
374
- gr.Markdown("Fill in the details below to generate AI suggestions and save them to Salesforce.")
375
-
376
- with gr.Group():
377
- with gr.Row():
378
- equipment_dropdown = gr.Dropdown(choices=equipment_choices, label="πŸ”§ Equipment Name", interactive=True)
379
- project_dropdown = gr.Dropdown(choices=project_choices, label="🏍️ Project Name", interactive=True)
380
- ai_suggestion_dropdown = gr.Dropdown(choices=[""] + ai_suggestion_choices, label="🧐 AI Suggestion", interactive=True)
381
-
382
- gr.Markdown("---")
383
-
384
- with gr.Row():
385
- usage_hours = gr.Number(label="⏱️ Usage Hours", value=0, minimum=0)
386
- idle_hours = gr.Number(label="πŸ•’ Idle Hours", value=0, minimum=0)
387
-
388
- with gr.Row():
389
- movement_frequency = gr.Number(label="πŸ“ˆ Movement Frequency", value=0, minimum=0)
390
- cost_per_hour = gr.Number(label="πŸ’° Cost per Hour", value=0, minimum=0)
391
-
392
- with gr.Row():
393
- last_maintenance = gr.Textbox(label="πŸ› οΈ Last Maintenance Date (YYYY-MM-DD)", placeholder="Optional")
394
-
395
- gr.Markdown("---")
396
-
397
- with gr.Row():
398
- submit_button = gr.Button("πŸš€ Submit", variant="primary")
399
- clear_button = gr.Button("🧹 Clear")
400
-
401
- # Add a headline separate from results box
402
- result_headline = gr.Markdown("### πŸ“ Equipment Record Details", elem_id="result-headline")
403
-
404
- # Result box with border lines
405
- output = gr.Markdown(label="πŸ“„ Equipment Record Data", elem_id="result-box")
406
-
407
- report_file_output = gr.File(label="πŸ“ƒ Download PDF Report", interactive=False)
408
-
409
- submit_button.click(
410
- fn=gradio_upload_process,
411
- inputs=[
412
- equipment_dropdown, project_dropdown,
413
- usage_hours, idle_hours,
414
- movement_frequency, cost_per_hour,
415
- last_maintenance, ai_suggestion_dropdown
416
- ],
417
- outputs=[output, report_file_output]
418
- )
419
- clear_button.click(
420
- fn=lambda: ("", None),
421
- inputs=[],
422
- outputs=[output, report_file_output]
423
- )
424
-
425
- # CSV Upload
426
- csv_upload = gr.File(label="πŸ“‚ Upload CSV file", file_types=[".csv"])
427
- csv_output = gr.JSON(label="πŸ“„ Batch Upload Results")
428
-
429
- csv_upload.change(
430
- fn=lambda file: process_csv_upload(file.name) if file else {},
431
- inputs=csv_upload,
432
- outputs=csv_output
433
  )
 
 
434
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  app.css = """
436
- .gradio-container {
437
- background-color: #000000 !important;
438
- padding: 20px;
439
- color: #ffffff !important;
440
- }
441
- .gr-button-primary {
442
- background-color: #1e90ff !important;
443
- color: white !important;
444
- }
445
- .gr-button-primary:hover {
446
- background-color: #0d6efd !important;
447
- }
448
- #result-headline {
449
- margin-top: 25px;
450
- margin-bottom: 8px;
451
- color: #1e90ff;
452
- font-weight: 700;
453
- font-size: 22px;
454
- border-bottom: 2px solid #1e90ff;
455
- padding-bottom: 6px;
456
- }
457
- #result-box {
458
- font-size: 16px;
459
- font-weight: 600;
460
- color: #ffffff !important;
461
- border: 3px solid #1e90ff;
462
- border-radius: 10px;
463
- padding: 20px;
464
- background-color: #141414;
465
- white-space: pre-line;
466
- min-height: 200px;
467
- }
468
- label {
469
- font-weight: 500;
470
- color: #ffffff !important;
471
- }
472
- input, textarea, select {
473
- background-color: #1c1c1c !important;
474
- color: #ffffff !important;
475
- border: 1px solid #444 !important;
476
- }
477
- """
478
-
479
  if __name__ == "__main__":
480
  app.launch()
 
17
  try:
18
  locale.setlocale(locale.LC_ALL, 'en_IN.UTF-8')
19
  except locale.Error:
 
20
  locale.setlocale(locale.LC_ALL, '')
21
 
22
  # Configure logging
 
53
  "Bulldozer", "Crane", "Excavator", "Loader", "Forklift",
54
  "Backhoe", "Grader", "Scraper", "Dump Truck", "Roller"
55
  ]
 
56
  project_choices = [
57
  "Project Alpha", "Project Beta", "Project Gamma", "Project Delta", "Project Epsilon",
58
  "Project Zeta", "Project Theta", "Project Sigma", "Project Omega", "Project Phoenix"
59
  ]
 
60
  ai_suggestion_choices = ["Move", "Pause Rent", "Repair", "Replace"]
61
 
62
+ # AI suggestion logic
63
+ def call_ai_model(usage, idle, freq, cost, last):
64
+ total = usage + idle
65
+ ratio = usage / total if total > 0 else 0
66
+ if ratio < 0.3:
67
+ sug = "Pause Rent"
68
+ elif ratio < 0.6:
69
+ sug = "Move"
70
+ elif ratio < 0.8:
71
+ sug = "Repair"
72
+ else:
73
+ sug = "Replace"
74
+ conf = min(1.0, ratio + 0.1)
75
+ score = ratio * 100
76
+ return sug, conf, score
77
+
78
+ # Core processing
79
+ def process_equipment_utilization(equip, proj, use_h, idle_h, move_f, cost_h, last_maint, ai_sug):
80
+ if not ai_sug:
81
+ ai_sug, conf, score = call_ai_model(use_h, idle_h, move_f, cost_h, last_maint)
82
+ else:
83
+ conf, score = 0.9, 85.0
84
+ summary = {
85
+ "Equipment Name": equip,
86
+ "Project": proj,
87
+ "Usage Hours": use_h,
88
+ "Idle Hours": idle_h,
89
+ "Suggestion": ai_sug,
90
+ "Confidence": conf,
91
+ "Utilization Score": score,
92
+ "Cost per Hour": cost_h,
93
+ "Last Maintenance": last_maint or "N/A"
94
+ }
95
+ record_data = {
96
+ "Equipment_Name__c": equip,
97
+ "Project_Name__c": proj,
98
+ "Usage_Hours__c": use_h,
99
+ "Idle_Hours__c": idle_h,
100
+ "AI_Suggestion__c": ai_sug,
101
+ "Suggestion_Confidence__c": conf * 100,
102
+ "Utilization_Score__c": score,
103
+ "Cost_per_Hour__c": cost_h,
104
+ "Report_Link__c": "Pending",
105
+ "Last_Maintenance__c": last_maint if last_maint != "N/A" else None,
106
+ "Dashboard_Flag__c": False
107
+ }
108
+ resp = sf.Equipment_Utilization_Record__c.create(record_data)
109
+ rec_id = resp.get("id")
110
+ # Prepare paths
111
+ uid = uuid.uuid4().hex[:8]
112
+ pdf_path = Path(f"static/reports/report_{uid}.pdf")
113
+ csv_path = Path(f"static/reports/report_{uid}.csv")
114
+ pdf_path.parent.mkdir(parents=True, exist_ok=True)
115
+ # Generate PDF
116
+ c = canvas.Canvas(str(pdf_path), pagesize=letter)
117
  c.setFont("Helvetica", 12)
118
+ c.drawString(100, 750, "Equipment Utilization Report")
119
+ c.drawString(100, 735, f"Record ID: {rec_id}")
 
120
  y = 710
121
+ for k, v in summary.items(): c.drawString(100, y, f"{k}: {v}"); y -= 20
 
 
 
122
  c.save()
123
+ # Generate CSV
124
+ with open(csv_path, "w", newline="") as f:
125
+ w = csv.writer(f)
126
+ w.writerow(["Field", "Value"])
127
+ w.writerow(["Record ID", rec_id])
128
+ [w.writerow([k, v]) for k, v in summary.items()]
129
+ # Upload PDF
130
+ encoded = base64.b64encode(pdf_path.read_bytes()).decode()
131
+ cv = sf.ContentVersion.create({
132
+ "Title": "UtilReport",
133
+ "PathOnClient": os.path.basename(str(pdf_path)),
134
+ "VersionData": encoded,
135
+ "FirstPublishLocationId": rec_id
136
+ })
137
+ pdf_url = f"https://{sf.sf_instance}/sfc/servlet.shepherd/version/download/{cv['id']}"
138
+ sf.Equipment_Utilization_Record__c.update(rec_id, {"Report_Link__c": pdf_url})
139
+ return {"Salesforce_Record_Id": rec_id, "Summary": summary, "Report_Link": pdf_url, "CSV_Report_Link": str(csv_path)}
140
+
141
+ # Format output
142
+ def format_output(result):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  summary = result.get("Summary", {})
144
+ # Cost formatting
145
+ cost_val = summary.get("Cost per Hour", 0)
 
146
  try:
147
+ cost_str = locale.currency(cost_val, grouping=True)
148
+ except:
149
+ cost_str = f"β‚Ή{cost_val:,.2f}"
150
+ conf_pct = summary.get("Confidence", 0) * 100
151
+ util_score = summary.get("Utilization Score", 0)
152
+ if util_score <= 1: util_score *= 100
 
 
 
 
 
 
153
  lines = [
154
  "πŸ“‹ **Equipment Utilization Record :**",
155
+ f"β€’ AI Suggestion: {summary.get('Suggestion', 'N/A')}",
156
+ f"β€’ Suggestion Confidence: {conf_pct:.2f}%",
157
+ f"β€’ Utilization Score: {util_score:.2f}%",
158
  f"β€’ Report Link: {result.get('Report_Link', 'N/A')}",
159
+ f"β€’ CSV Report Link: {result.get('CSV_REPORT_Link', 'N/A')}",
160
  "",
161
+ "πŸ”Ή β€’ Equipment Name: " + summary.get("Equipment Name", "N/A"),
162
+ " β€’ Project: " + summary.get("Project", "N/A"),
 
163
  f" β€’ Usage Hours: {summary.get('Usage Hours', 0):.2f}",
164
  f" β€’ Idle Hours: {summary.get('Idle Hours', 0):.2f}",
165
+ " β€’ Cost per Hour: " + cost_str,
166
+ " β€’ Last Maintenance: " + summary.get("Last Maintenance", "N/A")
167
  ]
 
168
  return "\n".join(lines)
169
 
170
+ # Gradio callbacks
171
+
172
+ def manual_input(equipment, project, usage, idle, freq, cost, last, ai_suggestion):
173
+ last_val = last or "N/A"
174
+ res = process_equipment_utilization(equipment, project, usage, idle, freq, cost, last_val, ai_suggestion)
175
+ formatted = format_output(res)
176
+ return formatted, res.get("Report_Link")
177
+
178
+ def batch_upload(csv_file):
179
+ if not csv_file: return {}
180
+ df = pd.read_csv(csv_file.name)
181
+ ids = []
182
+ for _, row in df.iterrows():
183
+ rec = process_equipment_utilization(
184
+ row['equipment_name'], row['project_name'],
185
+ float(row['usage_hours']), float(row['idle_hours']),
186
+ float(row['movement_frequency']), float(row['cost_per_hour']),
187
+ row.get('last_maintenance','N/A'), row.get('ai_suggestion','')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
188
  )
189
+ ids.append(rec['Salesforce_Record_Id'])
190
+ return {"records": ids}
191
 
192
+ # Interface
193
+ with gr.Blocks() as app:
194
+ gr.Markdown("## πŸ“‹ Equipment Utilization Record Uploader", elem_id="app-title")
195
+ with gr.Tabs():
196
+ with gr.TabItem("Manual Input"):
197
+ with gr.Group():
198
+ equipment_dropdown = gr.Dropdown(equipment_choices, label="πŸ”§ Equipment Name")
199
+ project_dropdown = gr.Dropdown(project_choices, label="🏍️ Project Name")
200
+ ai_dropdown = gr.Dropdown([""] + ai_suggestion_choices, label="🧐 AI Suggestion")
201
+ with gr.Group():
202
+ with gr.Row():
203
+ usage = gr.Number(label="⏱️ Usage Hours", value=0, minimum=0)
204
+ idle = gr.Number(label="πŸ•’ Idle Hours", value=0, minimum=0)
205
+ with gr.Row():
206
+ freq = gr.Number(label="πŸ“ˆ Movement Frequency", value=0, minimum=0)
207
+ cost = gr.Number(label="πŸ’° Cost per Hour", value=0, minimum=0)
208
+ last = gr.Textbox(label="πŸ› οΈ Last Maintenance Date (YYYY-MM-DD)", placeholder="Optional")
209
+ submit_btn = gr.Button("πŸš€ Submit", variant="primary")
210
+ result_txt = gr.Markdown(elem_id="result-box")
211
+ report_file = gr.File(label="πŸ“ƒ Download PDF Report")
212
+ submit_btn.click(
213
+ fn=manual_input,
214
+ inputs=[equipment_dropdown, project_dropdown, usage, idle, freq, cost, last, ai_dropdown],
215
+ outputs=[result_txt, report_file]
216
+ )
217
+ with gr.TabItem("CSV Upload"):
218
+ with gr.Group():
219
+ csv_file = gr.File(label="πŸ“‚ Upload CSV file", file_types=[".csv"])
220
+ csv_output = gr.JSON(label="πŸ“„ Batch Upload Results")
221
+ csv_file.change(fn=batch_upload, inputs=csv_file, outputs=csv_output)
222
  app.css = """
223
+ .gradio-container { background-color: #ffffff !important; }
224
+ #app-title { text-align: center !important; }
225
+ .gradio-container .gr-group { background-color: #d3d3d3 !important; padding: 20px; border: 3px solid #d3d3d3 !important; border-radius: 10px; }
226
+ #result-box { border: 3px solid #d3d3d3 !important; border-radius: 10px; padding: 10px; background: #f9f9f9; }
227
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  if __name__ == "__main__":
229
  app.launch()