Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -67,11 +67,11 @@ ai_suggestion_choices = ["Move", "Pause Rent", "Repair", "Replace"]
|
|
| 67 |
# AI suggestion logic
|
| 68 |
def call_ai_model(usage, idle, freq, cost, last):
|
| 69 |
try:
|
| 70 |
-
# Ensure inputs are valid numbers
|
| 71 |
-
usage = float(usage) if usage is not None else 0.0
|
| 72 |
-
idle = float(idle) if idle is not None else 0.0
|
| 73 |
-
freq = float(freq) if freq is not None else 0.0
|
| 74 |
-
cost = float(cost) if cost is not None else 0.0
|
| 75 |
|
| 76 |
total = usage + idle
|
| 77 |
ratio = usage / total if total > 0 else 0
|
|
@@ -126,7 +126,7 @@ def process_equipment_utilization(equip, proj, use_h, idle_h, move_f, cost_h, la
|
|
| 126 |
record_data = {
|
| 127 |
"Equipment_Name__c": equip,
|
| 128 |
"Project_Name__c": proj,
|
| 129 |
-
"Usage_Hours__c": float(use_h),
|
| 130 |
"Idle_Hours__c": float(idle_h),
|
| 131 |
"AI_Suggestion__c": ai_sug,
|
| 132 |
"Suggestion_Confidence__c": float(conf * 100),
|
|
@@ -137,6 +137,11 @@ def process_equipment_utilization(equip, proj, use_h, idle_h, move_f, cost_h, la
|
|
| 137 |
"Dashboard_Flag__c": False
|
| 138 |
}
|
| 139 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
# Log the record data for debugging
|
| 141 |
logger.info(f"Sending record data to Salesforce: {record_data}")
|
| 142 |
|
|
@@ -211,7 +216,6 @@ def process_equipment_utilization(equip, proj, use_h, idle_h, move_f, cost_h, la
|
|
| 211 |
# Format output
|
| 212 |
def format_output(result):
|
| 213 |
summary = result.get("Summary", {})
|
| 214 |
-
# Cost formatting
|
| 215 |
cost_val = summary.get("Cost per Hour", 0)
|
| 216 |
try:
|
| 217 |
cost_str = locale.currency(cost_val, grouping=True)
|
|
@@ -231,7 +235,9 @@ def format_output(result):
|
|
| 231 |
f" β’ Project: {summary.get('Project', 'N/A')}",
|
| 232 |
f" β’ Usage Hours: {summary.get('Usage Hours', 0):.2f}",
|
| 233 |
f" β’ Idle Hours: {summary.get('Idle Hours', 0):.2f}",
|
| 234 |
-
f" β’ Cost per
|
|
|
|
|
|
|
| 235 |
f" β’ Last Maintenance: {summary.get('Last Maintenance', 'N/A')}"
|
| 236 |
]
|
| 237 |
return "\n".join(lines)
|
|
@@ -239,61 +245,49 @@ def format_output(result):
|
|
| 239 |
# Gradio callbacks
|
| 240 |
def manual_input(equipment, project, usage, idle, freq, cost, last, ai_suggestion):
|
| 241 |
try:
|
| 242 |
-
# Validate inputs
|
| 243 |
if not equipment or equipment not in equipment_choices:
|
| 244 |
raise ValueError("Please select a valid Equipment Name.")
|
| 245 |
if not project or project not in project_choices:
|
| 246 |
raise ValueError("Please select a valid Project Name.")
|
| 247 |
-
if usage is None or usage < 0:
|
| 248 |
raise ValueError("Usage Hours must be a non-negative number.")
|
| 249 |
-
if idle is None or idle < 0:
|
| 250 |
raise ValueError("Idle Hours must be a non-negative number.")
|
| 251 |
-
if freq is None or
|
| 252 |
-
|
| 253 |
-
if cost is None or cost < 0:
|
| 254 |
-
raise ValueError("Cost per Hour must be a non-negative number.")
|
| 255 |
-
|
| 256 |
-
last_val = last or "N/A"
|
| 257 |
-
res = process_equipment_utilization(equipment, project, usage, idle, freq, cost, last_val, ai_suggestion)
|
| 258 |
-
formatted = format_output(res)
|
| 259 |
-
return formatted, res.get("Report_File_Path")
|
| 260 |
-
except Exception as e:
|
| 261 |
-
logger.error(f"Error in manual_input: {e}")
|
| 262 |
-
return f"Error: {str(e)}", None
|
| 263 |
-
|
| 264 |
-
def batch_upload(csv_file):
|
| 265 |
-
try:
|
| 266 |
-
if not csv_file:
|
| 267 |
-
raise ValueError("Please upload a CSV file.")
|
| 268 |
-
df = pd.read_csv(csv_file.name)
|
| 269 |
-
required_columns = ['equipment_name', 'project_name', 'usage_hours', 'idle_hours', 'movement_frequency', 'cost_per_hour']
|
| 270 |
-
missing_columns = [col for col in required_columns if col not in df.columns]
|
| 271 |
-
if missing_columns:
|
| 272 |
-
raise ValueError(f"CSV file is missing required columns: {', '.join(missing_columns)}")
|
| 273 |
-
|
| 274 |
-
# Validate numeric columns for NaN or invalid values
|
| 275 |
-
numeric_columns = ['usage_hours', 'idle_hours', 'movement_frequency', 'cost_per_hour']
|
| 276 |
-
for col in numeric_columns:
|
| 277 |
if df[col].isna().any():
|
| 278 |
-
raise ValueError(f"
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
|
|
|
| 283 |
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
row['usage_hours'], row['idle_hours'],
|
| 289 |
-
row['movement_frequency'], row['cost_per_hour'],
|
| 290 |
-
row.get('last_maintenance', 'N/A'), row.get('ai_suggestion', '')
|
| 291 |
)
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
except Exception as e:
|
| 295 |
logger.error(f"Error in batch_upload: {e}")
|
| 296 |
-
return {"error": str(e)}
|
| 297 |
|
| 298 |
# Interface
|
| 299 |
with gr.Blocks() as app:
|
|
@@ -316,17 +310,19 @@ with gr.Blocks() as app:
|
|
| 316 |
clear_btn = gr.Button("π§Ή Clear")
|
| 317 |
result_txt = gr.Markdown(elem_id="result-box")
|
| 318 |
report_file = gr.File(label="π Download PDF Report")
|
|
|
|
| 319 |
submit_btn.click(
|
| 320 |
fn=manual_input,
|
| 321 |
inputs=[equipment_dropdown, project_dropdown, usage, idle, freq, cost, last, ai_dropdown],
|
| 322 |
-
outputs=[result_txt, report_file]
|
| 323 |
)
|
| 324 |
-
clear_btn.click(lambda: ("", None), None, [result_txt, report_file])
|
| 325 |
with gr.TabItem("CSV Upload"):
|
| 326 |
with gr.Group():
|
| 327 |
csv_file = gr.File(label="π Upload CSV file", file_types=[".csv"])
|
| 328 |
csv_output = gr.JSON(label="π Batch Upload Results")
|
| 329 |
-
|
|
|
|
| 330 |
app.css = """
|
| 331 |
.gradio-container { background-color: #ffffff !important; }
|
| 332 |
#app-title { text-align: center !important; }
|
|
|
|
| 67 |
# AI suggestion logic
|
| 68 |
def call_ai_model(usage, idle, freq, cost, last):
|
| 69 |
try:
|
| 70 |
+
# Ensure inputs are valid numbers and not NaN
|
| 71 |
+
usage = float(usage) if usage is not None and not np.isnan(usage) else 0.0
|
| 72 |
+
idle = float(idle) if idle is not None and not np.isnan(idle) else 0.0
|
| 73 |
+
freq = float(freq) if freq is not None and not np.isnan(freq) else 0.0
|
| 74 |
+
cost = float(cost) if cost is not None and not np.isnan(cost) else 0.0
|
| 75 |
|
| 76 |
total = usage + idle
|
| 77 |
ratio = usage / total if total > 0 else 0
|
|
|
|
| 126 |
record_data = {
|
| 127 |
"Equipment_Name__c": equip,
|
| 128 |
"Project_Name__c": proj,
|
| 129 |
+
"Usage_Hours__c": float(use_h),
|
| 130 |
"Idle_Hours__c": float(idle_h),
|
| 131 |
"AI_Suggestion__c": ai_sug,
|
| 132 |
"Suggestion_Confidence__c": float(conf * 100),
|
|
|
|
| 137 |
"Dashboard_Flag__c": False
|
| 138 |
}
|
| 139 |
|
| 140 |
+
# Final check for NaN in record_data
|
| 141 |
+
for key, value in record_data.items():
|
| 142 |
+
if isinstance(value, float) and np.isnan(value):
|
| 143 |
+
raise ValueError(f"Field {key} contains NaN, which is not allowed in Salesforce requests.")
|
| 144 |
+
|
| 145 |
# Log the record data for debugging
|
| 146 |
logger.info(f"Sending record data to Salesforce: {record_data}")
|
| 147 |
|
|
|
|
| 216 |
# Format output
|
| 217 |
def format_output(result):
|
| 218 |
summary = result.get("Summary", {})
|
|
|
|
| 219 |
cost_val = summary.get("Cost per Hour", 0)
|
| 220 |
try:
|
| 221 |
cost_str = locale.currency(cost_val, grouping=True)
|
|
|
|
| 235 |
f" β’ Project: {summary.get('Project', 'N/A')}",
|
| 236 |
f" β’ Usage Hours: {summary.get('Usage Hours', 0):.2f}",
|
| 237 |
f" β’ Idle Hours: {summary.get('Idle Hours', 0):.2f}",
|
| 238 |
+
f" β’ Cost per
|
| 239 |
+
|
| 240 |
+
Hour: {cost_str}",
|
| 241 |
f" β’ Last Maintenance: {summary.get('Last Maintenance', 'N/A')}"
|
| 242 |
]
|
| 243 |
return "\n".join(lines)
|
|
|
|
| 245 |
# Gradio callbacks
|
| 246 |
def manual_input(equipment, project, usage, idle, freq, cost, last, ai_suggestion):
|
| 247 |
try:
|
|
|
|
| 248 |
if not equipment or equipment not in equipment_choices:
|
| 249 |
raise ValueError("Please select a valid Equipment Name.")
|
| 250 |
if not project or project not in project_choices:
|
| 251 |
raise ValueError("Please select a valid Project Name.")
|
| 252 |
+
if usage is None or usage < 0 or np.isnan(usage):
|
| 253 |
raise ValueError("Usage Hours must be a non-negative number.")
|
| 254 |
+
if idle is None or idle < 0 or np.isnan(idle):
|
| 255 |
raise ValueError("Idle Hours must be a non-negative number.")
|
| 256 |
+
if freq is None or freqNUMERIC_FIELDS = ['usage_hours', 'idle_hours', 'movement_frequency', 'cost_per_hour']
|
| 257 |
+
for col in NUMERIC_FIELDS:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 258 |
if df[col].isna().any():
|
| 259 |
+
raise ValueError(f"ColumnACHINERY_FIELDS = ['equipment_name', 'project_name']
|
| 260 |
+
for col in MACHINERY_FIELDS:
|
| 261 |
+
if col not in df.columns:
|
| 262 |
+
raise ValueError(f"Missing required column: {col}")
|
| 263 |
+
if df[col].isna().any():
|
| 264 |
+
raise ValueError(f"Column '{col}' contains missing values. Please ensure all rows have valid equipment and project names.")
|
| 265 |
|
| 266 |
+
# Validate ai_suggestion if provided
|
| 267 |
+
if 'ai_suggestion' in df.columns:
|
| 268 |
+
df['ai_suggestion'] = df['ai_suggestion'].apply(
|
| 269 |
+
lambda x: x if x in ai_suggestion_choices or pd.isna(x) or x == '' else None
|
|
|
|
|
|
|
|
|
|
| 270 |
)
|
| 271 |
+
|
| 272 |
+
ids = []
|
| 273 |
+
csv_files = []
|
| 274 |
+
for idx, row in df.iterrows():
|
| 275 |
+
try:
|
| 276 |
+
rec = process_equipment_utilization(
|
| 277 |
+
row['equipment_name'], row['project_name'],
|
| 278 |
+
row['usage_hours'], row['idle_hours'],
|
| 279 |
+
row['movement_frequency'], row['cost_per_hour'],
|
| 280 |
+
row.get('last_maintenance', 'N/A'), row.get('ai_suggestion', '')
|
| 281 |
+
)
|
| 282 |
+
ids.append(rec['Salesforce_Record_Id'])
|
| 283 |
+
csv_files.append(rec['CSV_Report_Link'])
|
| 284 |
+
except Exception as e:
|
| 285 |
+
logger.error(f"Error processing row {idx + 2}: {e}")
|
| 286 |
+
raise ValueError(f"Error processing row {idx + 2}: {str(e)}")
|
| 287 |
+
return {"records": ids}, csv_files
|
| 288 |
except Exception as e:
|
| 289 |
logger.error(f"Error in batch_upload: {e}")
|
| 290 |
+
return {"error": str(e)}, None
|
| 291 |
|
| 292 |
# Interface
|
| 293 |
with gr.Blocks() as app:
|
|
|
|
| 310 |
clear_btn = gr.Button("π§Ή Clear")
|
| 311 |
result_txt = gr.Markdown(elem_id="result-box")
|
| 312 |
report_file = gr.File(label="π Download PDF Report")
|
| 313 |
+
csv_file = gr.File(label="π Download CSV Report")
|
| 314 |
submit_btn.click(
|
| 315 |
fn=manual_input,
|
| 316 |
inputs=[equipment_dropdown, project_dropdown, usage, idle, freq, cost, last, ai_dropdown],
|
| 317 |
+
outputs=[result_txt, report_file, csv_file]
|
| 318 |
)
|
| 319 |
+
clear_btn.click(lambda: ("", None, None), None, [result_txt, report_file, csv_file])
|
| 320 |
with gr.TabItem("CSV Upload"):
|
| 321 |
with gr.Group():
|
| 322 |
csv_file = gr.File(label="π Upload CSV file", file_types=[".csv"])
|
| 323 |
csv_output = gr.JSON(label="π Batch Upload Results")
|
| 324 |
+
csv_downloads = gr.File(label="π₯ Download Generated CSV Reports")
|
| 325 |
+
csv_file.change(fn=batch_upload, inputs=csv_file, outputs=[csv_output, csv_downloads])
|
| 326 |
app.css = """
|
| 327 |
.gradio-container { background-color: #ffffff !important; }
|
| 328 |
#app-title { text-align: center !important; }
|