Dineshpopuri commited on
Commit
d988691
·
verified ·
1 Parent(s): b11ca30

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +122 -198
app.py CHANGED
@@ -10,7 +10,6 @@ from retry import retry
10
  import time
11
  from datetime import datetime
12
  import base64
13
- import uuid
14
 
15
  # Print statement to confirm script initialization
16
  print("Starting Project Closure Readiness Evaluator app...")
@@ -65,10 +64,12 @@ else:
65
  def init_salesforce():
66
  if not SALESFORCE_AVAILABLE:
67
  logging.error("Salesforce library not available. Skipping connection.")
68
- return None, None, "Salesforce library not available"
69
 
70
  try:
71
- logging.info("Attempting to connect to Salesforce...")
 
 
72
  sf = Salesforce(
73
  username=SF_USERNAME,
74
  password=SF_PASSWORD,
@@ -76,55 +77,13 @@ def init_salesforce():
76
  instance_url=SF_INSTANCE_URL
77
  )
78
  logging.info("Salesforce connected successfully")
79
-
80
  # Test read access on Project_Closure_Handover__c
81
  test_query = sf.query("SELECT Id FROM Project_Closure_Handover__c LIMIT 1")
82
- logging.info(f"Test query result (read access on Project_Closure_Handover__c): {test_query}")
83
-
84
- # Describe Project_Closure_Handover__c to find the related object for Project_ID__c
85
  object_description = sf.Project_Closure_Handover__c.describe()
86
- project_id_field = None
87
- for field in object_description['fields']:
88
- if field['name'] == 'Project_ID__c':
89
- project_id_field = field
90
- break
91
-
92
- if not project_id_field:
93
- logging.error("Project_ID__c field not found on Project_Closure_Handover__c")
94
- return None, None, "Project_ID__c field not found on Project_Closure_Handover__c"
95
-
96
- # Check if Project_ID__c is a reference (Lookup) field
97
- if not project_id_field['referenceTo']:
98
- logging.error("Project_ID__c is not a reference field. It must be a Lookup or Master-Detail field.")
99
- return None, None, "Project_ID__c is not a reference field"
100
-
101
- related_object = project_id_field['referenceTo'][0] # e.g., 'Projects__c'
102
- logging.info(f"Project_ID__c references object: {related_object}")
103
-
104
- # Test read access on the related object
105
- related_query = sf.query(f"SELECT Id FROM {related_object} LIMIT 1")
106
- logging.info(f"{related_object} test query result (read access): {related_query}")
107
-
108
- # Describe the related object to log its fields (for debugging)
109
- related_object_description = getattr(sf, related_object).describe()
110
- related_fields = [field['name'] for field in related_object_description['fields']]
111
- logging.info(f"Fields on {related_object}: {related_fields}")
112
-
113
- # Log some sample data from the related object to confirm records exist
114
- sample_data_query = sf.query(f"SELECT Id, Name FROM {related_object} LIMIT 5")
115
- logging.info(f"Sample data from {related_object}: {sample_data_query['records']}")
116
-
117
- # Test object permissions for Project_Closure_Handover__c
118
- permissions = {
119
- "createable": object_description.get("createable", False),
120
- "updateable": object_description.get("updateable", False)
121
- }
122
- logging.info(f"Object permissions for Project_Closure_Handover__c: {permissions}")
123
- if not permissions["createable"]:
124
- logging.error("User lacks create permission on Project_Closure_Handover__c")
125
- return None, None, "User lacks create permission on Project_Closure_Handover__c"
126
-
127
- return sf, related_object, "Salesforce connected successfully"
128
  except SalesforceError as e:
129
  logging.error(f"Salesforce authentication failed: {str(e)}")
130
  logging.error("Possible issues: Incorrect credentials, IP restrictions, or insufficient permissions.")
@@ -134,37 +93,6 @@ def init_salesforce():
134
  logging.error("Check your Salesforce org settings, network restrictions, or API access.")
135
  raise
136
 
137
- # Function to get the Salesforce record ID for a project based on its custom identifier
138
- def get_project_record_id(sf, related_object, project_code):
139
- try:
140
- # Sanitize the project_code to prevent SOQL injection
141
- project_code = bleach.clean(project_code.strip())
142
-
143
- # Try multiple fields to match the project_code
144
- fields_to_try = ['Name', 'Project_Code__c', 'External_ID__c']
145
- record_id = None
146
- for field in fields_to_try:
147
- query = f"SELECT Id FROM {related_object} WHERE {field} = '{project_code}' LIMIT 1"
148
- logging.info(f"Trying query: {query}")
149
- result = sf.query(query)
150
- logging.debug(f"Query result for {field}: {result}")
151
- if result['totalSize'] > 0:
152
- record_id = result['records'][0]['Id']
153
- logging.info(f"Found project record ID: {record_id} for {field}: {project_code} in {related_object}")
154
- return record_id, "Success"
155
- else:
156
- logging.info(f"No project found with {field}: {project_code} in {related_object}")
157
-
158
- # If no record is found after trying all fields
159
- logging.error(f"No project found with {project_code} in {related_object} after trying fields: {fields_to_try}")
160
- return None, f"No project found with {project_code} in {related_object} after trying fields: {fields_to_try}"
161
- except SalesforceError as e:
162
- logging.error(f"Failed to query project record: {str(e)}")
163
- return None, f"Salesforce query error: {str(e)}"
164
- except Exception as e:
165
- logging.error(f"Error querying project record: {str(e)}")
166
- return None, f"Error querying project: {str(e)}"
167
-
168
  # Summarize text using Hugging Face Inference API
169
  def summarize_text(text, max_length=100, min_length=30):
170
  if not HF_AVAILABLE:
@@ -186,68 +114,48 @@ def summarize_text(text, max_length=100, min_length=30):
186
  return text # Fallback to original text
187
 
188
  # Create Salesforce record in custom object Project_Closure_Handover__c
189
- def create_salesforce_record(project_id, score, checklist_summary, missing_summary, status, escalated, logs, qa_report, punch_list_text, open_punch_items, pdf_path=None):
190
  if not SALESFORCE_AVAILABLE:
191
  logging.error("Salesforce library not available. Skipping record creation.")
192
  return "Salesforce library not available"
193
 
194
  try:
195
- sf, related_object, connection_message = init_salesforce()
196
- if not sf or not related_object:
197
  logging.error(f"Skipping Salesforce record creation due to connection failure: {connection_message}")
198
  return connection_message
199
 
200
- # Get the Salesforce record ID for the project
201
- project_record_id, project_query_message = get_project_record_id(sf, related_object, project_id)
202
- if not project_record_id:
203
- logging.error(f"Failed to get project record ID: {project_query_message}")
204
- return f"Error: {project_query_message}"
205
-
206
- # Summarize checklist_summary and missing_summary
207
  summarized_checklist = summarize_text(checklist_summary)
208
  summarized_missing = summarize_text(missing_summary)
209
 
210
  # Ensure inputs are properly formatted
211
- project_id = str(project_id)[:80] if project_id else "Unknown" # For display purposes
212
  score = float(score) if score is not None else 0.0
213
- checklist_summary = str(checklist_summary)[:131072] if checklist_summary else ""
214
- summarized_checklist = str(summarized_checklist)[:131072] if summarized_checklist else ""
215
- missing_summary = str(missing_summary)[:131072] if missing_summary else ""
216
- summarized_missing = str(summarized_missing)[:131072] if summarized_missing else ""
217
- status = str(status)[:255] if status else ""
218
- logs = str(logs)[:131072] if logs else ""
219
- qa_report = str(qa_report)[:131072] if qa_report else ""
220
- punch_list_text = str(punch_list_text)[:131072] if punch_list_text else ""
221
  missing_documents = len(missing_summary.split(", ")) if missing_summary and missing_summary != "None" else 0
222
  open_punch_items = int(open_punch_items) if open_punch_items is not None else 0
223
- evaluated_at = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")
224
- alert_sent = "true" if escalated else "false"
225
- escalation_flag = "true" if escalated else "false"
 
226
 
227
- # Validate required fields
228
- required_fields = {
229
- "Project_ID__c": project_record_id,
230
- "Readiness_Score__c": score,
231
- "Checklist_Summary__c": checklist_summary,
232
- "Status__c": status,
233
- "Evaluated_At__c": evaluated_at
234
- }
235
- for field, value in required_fields.items():
236
- if not value:
237
- logging.error(f"Required field {field} is missing or empty")
238
- return f"Error: Required field {field} is missing or empty"
239
-
240
- # Create the record
241
  record = {
242
- "Project_ID__c": project_record_id, # Use the Salesforce record ID
243
  "Readiness_Score__c": score,
244
  "Checklist_Summary__c": checklist_summary,
245
  "Missing_Documents__c": missing_documents,
246
  "Status__c": status,
247
  "Summarized_Missing_Items__c": summarized_missing,
248
  "Alert_Sent__c": alert_sent,
249
- "Client_PDF_Pack_URL__c": "",
250
- "Closure_Pack_URL__c": "",
251
  "Escalation_Flag__c": escalation_flag,
252
  "Evaluated_At__c": evaluated_at,
253
  "Logs__c": logs,
@@ -256,59 +164,57 @@ def create_salesforce_record(project_id, score, checklist_summary, missing_summa
256
  "QA_Report__c": qa_report
257
  }
258
 
259
- logging.debug(f"Creating Salesforce record with data: {record}")
260
  result = sf.Project_Closure_Handover__c.create(record)
 
261
  record_id = result.get('id')
262
- if not record_id:
263
- logging.error("Failed to retrieve record ID from Salesforce response")
264
- return "Error: Failed to create Salesforce record"
265
-
266
- logging.info(f"Successfully created Salesforce record: {record_id}")
267
 
268
- # Attach PDF if available
269
  pdf_download_url = ""
270
  if pdf_path and os.path.exists(pdf_path):
271
- try:
272
- logging.info(f"Attaching PDF to Salesforce record ID: {record_id}")
273
- with open(pdf_path, "rb") as pdf_file:
274
- pdf_content = pdf_file.read()
275
- pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
276
-
277
- attachment = {
278
- "ParentId": record_id,
279
- "Name": f"Readiness_Report_{record_id}.pdf",
280
- "Body": pdf_base64,
281
- "ContentType": "application/pdf"
282
- }
283
-
284
- attachment_result = sf.Attachment.create(attachment)
285
- attachment_id = attachment_result.get('id')
286
- if not attachment_id:
287
- logging.error("Failed to retrieve attachment ID from Salesforce response")
288
- return f"Record created (ID: {record_id}), but failed to attach PDF"
289
-
290
- logging.info(f"Successfully attached PDF: {attachment_id}")
291
- pdf_download_url = f"{SF_INSTANCE_URL}/servlet/servlet.FileDownload?file={attachment_id}"
292
-
293
- # Update record with PDF URL
294
- update_data = {
295
- "Client_PDF_Pack_URL__c": pdf_download_url
296
- }
297
- sf.Project_Closure_Handover__c.update(record_id, update_data)
298
- logging.info(f"Updated record {record_id} with PDF URL: {pdf_download_url}")
299
- except Exception as e:
300
- logging.error(f"Failed to attach PDF: {str(e)}")
301
- return f"Record created (ID: {record_id}), but failed to attach PDF: {str(e)}"
302
  else:
303
- logging.warning(f"No PDF file found at {pdf_path}. Skipping attachment.")
304
 
305
- return f"Record created successfully. Record ID: {record_id}. PDF URL: {pdf_download_url}"
306
  except SalesforceError as e:
307
- logging.error(f"Salesforce error: {str(e)}")
308
  logging.error("Possible issues: Object permissions, field-level security, validation rules, or required fields.")
 
 
 
 
 
309
  return f"Salesforce error: {str(e)}"
310
  except Exception as e:
311
- logging.error(f"Failed to create Salesforce record: {str(e)}")
312
  return f"Error creating record: {str(e)}"
313
 
314
  # Clean input to prevent injection attacks
@@ -318,23 +224,27 @@ def sanitize_input(text):
318
  return bleach.clean(text.strip())
319
 
320
  # Rule-based completeness engine with weighted scoring
321
- def evaluate_readiness(project_id, logs, qa_report, punch_list_text):
322
  try:
323
- logging.info(f"Evaluating readiness for Project ID: {project_id} - Logs: {logs}, QA Report: {qa_report}, Punch List: {punch_list_text}")
 
 
 
324
  score = 0
325
  missing_items = []
326
  checklist_details = []
327
 
328
- LOGS_WEIGHT = 30
329
- QA_WEIGHT = 40
330
- PUNCH_WEIGHT = 30
 
331
 
332
- project_id = sanitize_input(project_id)
333
  logs = sanitize_input(logs)
334
  qa_report = sanitize_input(qa_report)
335
  punch_list_text = sanitize_input(punch_list_text)
336
 
337
- # Process Project Logs
338
  log_keywords = r"complete|handover done|finished|closed|successful"
339
  negative_keywords = r"issue|pending|incomplete|problem|delay"
340
  logs_pass = logs and re.search(log_keywords, logs.lower())
@@ -343,15 +253,18 @@ def evaluate_readiness(project_id, logs, qa_report, punch_list_text):
343
  if logs_pass and not logs_negative:
344
  score += LOGS_WEIGHT
345
  checklist_details.append("Logs: Completed")
 
346
  elif logs_pass and logs_negative:
347
- score += LOGS_WEIGHT // 2
348
  missing_items.append("Issues detected in logs")
349
  checklist_details.append("Logs: Partially Completed (issues detected)")
 
350
  else:
351
  missing_items.append("Project Logs Incomplete")
352
  checklist_details.append("Logs: Pending")
 
353
 
354
- # Process QA Report
355
  qa_keywords = r"approved|passed|cleared"
356
  qa_pass = qa_report and re.search(qa_keywords, qa_report.lower())
357
  if qa_pass:
@@ -360,8 +273,9 @@ def evaluate_readiness(project_id, logs, qa_report, punch_list_text):
360
  else:
361
  missing_items.append("QA Approval Missing")
362
  checklist_details.append("QA Report: Pending")
 
363
 
364
- # Process Punch List
365
  punch_keywords = r"none|resolved|closed|no issues"
366
  punch_pass = punch_list_text and re.search(punch_keywords, punch_list_text.lower())
367
  if punch_pass:
@@ -370,16 +284,26 @@ def evaluate_readiness(project_id, logs, qa_report, punch_list_text):
370
  else:
371
  missing_items.append("Open Punch Points Detected")
372
  checklist_details.append("Punch List: Pending")
 
 
 
 
373
 
374
- open_punch_items = 0 if punch_pass else 1
375
  escalated = score < 70 or open_punch_items > 2
 
 
 
376
  status = "Escalated" if escalated else ("Completed" if not missing_items else "In Progress")
377
- checklist_status = "Escalated" if escalated else ("Completed" if not missing_items else "Pending")
378
 
 
379
  checklist_summary = "\n".join(checklist_details)
380
  missing_summary = "None" if not missing_items else ", ".join(missing_items)
381
 
 
382
  color_class = "red" if score < 70 else "yellow" if score <= 90 else "green"
 
383
  progress_bar = f"""
384
  <div class="progress-container">
385
  <div class="progress-bar {color_class}" style="width: {score}%;">
@@ -388,42 +312,44 @@ def evaluate_readiness(project_id, logs, qa_report, punch_list_text):
388
  </div>
389
  """
390
 
391
- logging.info(f"Readiness Score: {score}%, Status: {status}, Escalated: {escalated}")
392
- return (project_id, score, checklist_summary, missing_summary, status, progress_bar, escalated, logs, qa_report, punch_list_text, open_punch_items, checklist_status)
393
  except Exception as e:
394
  logging.error(f"Error in evaluate_readiness: {str(e)}")
395
  raise
396
 
397
  # Generate PDF report with signature slots
398
- def generate_pdf(project_id, score, checklist_summary, missing_summary, checklist_status, logs, qa_report, punch_list_text):
399
  try:
400
- project_id = str(project_id)[:80] if project_id else "Unknown"
 
401
  score = str(float(score)) if score is not None else "0"
402
- checklist_summary = str(checklist_summary)[:1000] if checklist_summary else ""
403
- checklist_status = str(checklist_status)[:255] if checklist_status else ""
404
 
 
405
  checklist_summary = checklist_summary.encode('ascii', 'ignore').decode('ascii')
406
  checklist_status = checklist_status.encode('ascii', 'ignore').decode('ascii')
407
 
408
- pdf_path = f"readiness_report_{uuid.uuid4()}.pdf"
409
- logging.info(f"Generating PDF at {pdf_path}")
 
410
 
 
411
  c = canvas.Canvas(pdf_path, pagesize=letter)
412
  c.setFont("Times-Bold", 16)
413
  c.drawString(50, 750, "Project Closure Readiness Report")
414
  c.setFont("Times-Roman", 12)
415
- c.drawString(50, 730, f"Project ID: {project_id}")
416
- c.drawString(50, 710, f"Readiness Score: {score}%")
417
- c.drawString(50, 690, f"Status: {checklist_status}")
418
- c.drawString(50, 670, "Checklist Summary:")
419
- y = 650
420
  BOTTOM_MARGIN = 50
421
  for line in checklist_summary.split("\n"):
422
  if y < BOTTOM_MARGIN:
423
  c.showPage()
424
  c.setFont("Times-Roman", 12)
425
  y = 750
426
- c.drawString(50, y, line[:100])
427
  y -= 20
428
  if y - 40 < BOTTOM_MARGIN:
429
  c.showPage()
@@ -437,6 +363,7 @@ def generate_pdf(project_id, score, checklist_summary, missing_summary, checklis
437
  c.drawString(50, y - 60, "Date: ____________________")
438
  c.save()
439
 
 
440
  if not os.path.exists(pdf_path):
441
  logging.error(f"PDF file {pdf_path} was not created")
442
  raise FileNotFoundError(f"PDF file {pdf_path} was not created")
@@ -447,7 +374,7 @@ def generate_pdf(project_id, score, checklist_summary, missing_summary, checklis
447
  logging.error(f"Error in generate_pdf: {str(e)}")
448
  raise
449
 
450
- # Gradio interface
451
  with gr.Blocks(css="""
452
  .progress-container { background-color: #f0f0f0; width: 100%; height: 20px; border-radius: 5px; overflow: hidden; position: relative; }
453
  .progress-bar { height: 100%; text-align: center; line-height: 20px; color: #000; font-size: 12px; }
@@ -463,7 +390,6 @@ with gr.Blocks(css="""
463
  )
464
  with gr.Row():
465
  with gr.Column(scale=2):
466
- project_id_input = gr.Textbox(label="Project ID", placeholder="Enter Project ID (e.g., PRJ-001)", value="PRJ-001")
467
  logs_input = gr.Textbox(label="Project Logs", lines=5, placeholder="Enter project logs (e.g., 'Project complete, handover done')")
468
  qa_input = gr.Dropdown(
469
  label="QA Report",
@@ -479,7 +405,6 @@ with gr.Blocks(css="""
479
  )
480
  submit_btn = gr.Button("Evaluate and Generate PDF")
481
  with gr.Column(scale=3):
482
- project_id_output = gr.Textbox(label="Project ID")
483
  score_output = gr.Number(label="Readiness Score (%)")
484
  progress_output = gr.HTML(label="Alert Indicator: Progress")
485
  gr.Markdown("Color-coded readiness: Red (<70%), Yellow (70-90%), Green (>90%)")
@@ -491,26 +416,25 @@ with gr.Blocks(css="""
491
  open_punch_items_output = gr.Number(label="Open Punch Items (Debug)")
492
  pdf_output = gr.File(label="Download PDF Report", type="filepath", interactive=False)
493
  pdf_debug = gr.Textbox(label="PDF Debug Output")
494
- salesforce_output = gr.Textbox(label="Salesforce Status")
495
 
 
496
  submit_btn.click(
497
  fn=evaluate_readiness,
498
- inputs=[project_id_input, logs_input, qa_input, punch_input],
499
  outputs=[
500
- project_id_output, score_output, checklist_output, missing_output, status_output, progress_output,
501
  gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, status_output
502
  ]
503
  ).then(
504
  fn=generate_pdf,
505
- inputs=[project_id_output, score_output, checklist_output, missing_output, status_output, gr.State(), gr.State(), gr.State()],
506
  outputs=[pdf_output, pdf_debug]
507
  ).then(
508
  fn=create_salesforce_record,
509
  inputs=[
510
- project_id_output, score_output, checklist_output, missing_output, status_output,
511
  gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, pdf_output
512
  ],
513
- outputs=salesforce_output
514
  )
515
-
516
- demo.launch()
 
10
  import time
11
  from datetime import datetime
12
  import base64
 
13
 
14
  # Print statement to confirm script initialization
15
  print("Starting Project Closure Readiness Evaluator app...")
 
64
  def init_salesforce():
65
  if not SALESFORCE_AVAILABLE:
66
  logging.error("Salesforce library not available. Skipping connection.")
67
+ return None, "Salesforce library not available"
68
 
69
  try:
70
+ logging.info("Attempting to connect to Salesforce with the following credentials:")
71
+ logging.info(f"Username: {SF_USERNAME}")
72
+ logging.info(f"Instance URL: {SF_INSTANCE_URL}")
73
  sf = Salesforce(
74
  username=SF_USERNAME,
75
  password=SF_PASSWORD,
 
77
  instance_url=SF_INSTANCE_URL
78
  )
79
  logging.info("Salesforce connected successfully")
 
80
  # Test read access on Project_Closure_Handover__c
81
  test_query = sf.query("SELECT Id FROM Project_Closure_Handover__c LIMIT 1")
82
+ logging.info(f"Test query result (read access): {test_query}")
83
+ # Test create access by attempting to describe the object and check permissions
 
84
  object_description = sf.Project_Closure_Handover__c.describe()
85
+ logging.info(f"Object description: {object_description}")
86
+ return sf, "Salesforce connected successfully"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  except SalesforceError as e:
88
  logging.error(f"Salesforce authentication failed: {str(e)}")
89
  logging.error("Possible issues: Incorrect credentials, IP restrictions, or insufficient permissions.")
 
93
  logging.error("Check your Salesforce org settings, network restrictions, or API access.")
94
  raise
95
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  # Summarize text using Hugging Face Inference API
97
  def summarize_text(text, max_length=100, min_length=30):
98
  if not HF_AVAILABLE:
 
114
  return text # Fallback to original text
115
 
116
  # Create Salesforce record in custom object Project_Closure_Handover__c
117
+ def create_salesforce_record(score, checklist_summary, missing_summary, status, escalated, logs, qa_report, punch_list_text, open_punch_items, pdf_path=None):
118
  if not SALESFORCE_AVAILABLE:
119
  logging.error("Salesforce library not available. Skipping record creation.")
120
  return "Salesforce library not available"
121
 
122
  try:
123
+ sf, connection_message = init_salesforce()
124
+ if not sf:
125
  logging.error(f"Skipping Salesforce record creation due to connection failure: {connection_message}")
126
  return connection_message
127
 
128
+ # Summarize checklist_summary and missing_summary using Hugging Face
 
 
 
 
 
 
129
  summarized_checklist = summarize_text(checklist_summary)
130
  summarized_missing = summarize_text(missing_summary)
131
 
132
  # Ensure inputs are properly formatted
 
133
  score = float(score) if score is not None else 0.0
134
+ checklist_summary = str(checklist_summary) if checklist_summary else ""
135
+ summarized_checklist = str(summarized_checklist) if summarized_checklist else ""
136
+ missing_summary = str(missing_summary) if missing_summary else ""
137
+ summarized_missing = str(summarized_missing) if summarized_missing else ""
138
+ status = str(status) if status else ""
139
+ logs = str(logs) if logs else ""
140
+ qa_report = str(qa_report) if qa_report else ""
141
+ punch_list_text = str(punch_list_text) if punch_list_text else ""
142
  missing_documents = len(missing_summary.split(", ")) if missing_summary and missing_summary != "None" else 0
143
  open_punch_items = int(open_punch_items) if open_punch_items is not None else 0
144
+ evaluated_at = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") # Salesforce Date/Time format
145
+ alert_sent = str(bool(escalated)).lower() # Converts True/False to "true"/"false"
146
+ logging.info(f"Setting Alert_Sent__c to: {alert_sent}")
147
+ escalation_flag = str(bool(escalated)).lower() # Ensure this is also a proper boolean string
148
 
149
+ # Create the record in Project_Closure_Handover__c without the PDF URL for now
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  record = {
 
151
  "Readiness_Score__c": score,
152
  "Checklist_Summary__c": checklist_summary,
153
  "Missing_Documents__c": missing_documents,
154
  "Status__c": status,
155
  "Summarized_Missing_Items__c": summarized_missing,
156
  "Alert_Sent__c": alert_sent,
157
+ "Client_PDF_Pack_URL__c": "", # Will be updated after attachment is created
158
+ "Closure_Pack_URL__c": "", # Placeholder; update if you have a closure pack URL
159
  "Escalation_Flag__c": escalation_flag,
160
  "Evaluated_At__c": evaluated_at,
161
  "Logs__c": logs,
 
164
  "QA_Report__c": qa_report
165
  }
166
 
167
+ logging.debug(f"Attempting to create Salesforce record in Project_Closure_Handover__c with data: {record}")
168
  result = sf.Project_Closure_Handover__c.create(record)
169
+ logging.info(f"Successfully created Salesforce record: {result}")
170
  record_id = result.get('id')
171
+ logging.info(f"Record ID: {record_id}")
 
 
 
 
172
 
173
+ # Attach the PDF to the record if pdf_path exists and update the URL
174
  pdf_download_url = ""
175
  if pdf_path and os.path.exists(pdf_path):
176
+ logging.info(f"Attaching PDF to Salesforce record ID: {record_id}")
177
+ with open(pdf_path, "rb") as pdf_file:
178
+ pdf_content = pdf_file.read()
179
+ pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
180
+
181
+ attachment = {
182
+ "ParentId": record_id,
183
+ "Name": "Readiness_Report.pdf",
184
+ "Body": pdf_base64,
185
+ "ContentType": "application/pdf"
186
+ }
187
+
188
+ attachment_result = sf.Attachment.create(attachment)
189
+ logging.info(f"Successfully attached PDF to record: {attachment_result}")
190
+ attachment_id = attachment_result.get('id')
191
+ logging.info(f"Attachment ID: {attachment_id}")
192
+
193
+ # Construct the direct download URL for the attachment
194
+ pdf_download_url = f"{SF_INSTANCE_URL}/servlet/servlet.FileDownload?file={attachment_id}"
195
+ logging.info(f"Generated PDF download URL: {pdf_download_url}")
196
+
197
+ # Update the Project_Closure_Handover__c record with the PDF download URL
198
+ update_data = {
199
+ "Client_PDF_Pack_URL__c": pdf_download_url
200
+ }
201
+ sf.Project_Closure_Handover__c.update(record_id, update_data)
202
+ logging.info(f"Updated record {record_id} with Client_PDF_Pack_URL__c: {pdf_download_url}")
 
 
 
 
203
  else:
204
+ logging.warning(f"No PDF file found at {pdf_path}. Skipping attachment and URL update.")
205
 
206
+ return f"Record created successfully. Record ID: {record_id}. PDF attached and URL set to: {pdf_download_url}"
207
  except SalesforceError as e:
208
+ logging.error(f"Salesforce error while creating Project_Closure_Handover__c record: {str(e)}")
209
  logging.error("Possible issues: Object permissions, field-level security, validation rules, or required fields.")
210
+ logging.error("Check the following in your Salesforce org:")
211
+ logging.error("- Ensure the user has Create and Edit permission on Project_Closure_Handover__c.")
212
+ logging.error("- Ensure the user has permission to create and read Attachments.")
213
+ logging.error("- Verify field-level security for all fields in the record.")
214
+ logging.error("- Check for validation rules or required fields that might be failing.")
215
  return f"Salesforce error: {str(e)}"
216
  except Exception as e:
217
+ logging.error(f"Failed to create Salesforce Project_Closure_Handover__c record: {str(e)}")
218
  return f"Error creating record: {str(e)}"
219
 
220
  # Clean input to prevent injection attacks
 
224
  return bleach.clean(text.strip())
225
 
226
  # Rule-based completeness engine with weighted scoring
227
+ def evaluate_readiness(logs, qa_report, punch_list_text):
228
  try:
229
+ # Log inputs for debugging
230
+ logging.info(f"Inputs - Logs: {logs}, QA Report: {qa_report}, Punch List: {punch_list_text}")
231
+
232
+ # Initialize score and lists for tracking
233
  score = 0
234
  missing_items = []
235
  checklist_details = []
236
 
237
+ # Define weights for scoring
238
+ LOGS_WEIGHT = 30 # 30% weight for logs
239
+ QA_WEIGHT = 40 # 40% weight for QA report
240
+ PUNCH_WEIGHT = 30 # 30% weight for punch list
241
 
242
+ # Sanitize inputs
243
  logs = sanitize_input(logs)
244
  qa_report = sanitize_input(qa_report)
245
  punch_list_text = sanitize_input(punch_list_text)
246
 
247
+ # Process Project Logs (30% weight)
248
  log_keywords = r"complete|handover done|finished|closed|successful"
249
  negative_keywords = r"issue|pending|incomplete|problem|delay"
250
  logs_pass = logs and re.search(log_keywords, logs.lower())
 
253
  if logs_pass and not logs_negative:
254
  score += LOGS_WEIGHT
255
  checklist_details.append("Logs: Completed")
256
+ logging.info(f"Logs Check: Pass (positive keywords found, no negative keywords), Score: {score}%")
257
  elif logs_pass and logs_negative:
258
+ score += LOGS_WEIGHT // 2 # Partial score for mixed signals
259
  missing_items.append("Issues detected in logs")
260
  checklist_details.append("Logs: Partially Completed (issues detected)")
261
+ logging.info(f"Logs Check: Partial Pass (positive and negative keywords found), Score: {score}%")
262
  else:
263
  missing_items.append("Project Logs Incomplete")
264
  checklist_details.append("Logs: Pending")
265
+ logging.info(f"Logs Check: Fail (no positive keywords or only negative keywords), Score: {score}%")
266
 
267
+ # Process QA Report (40% weight)
268
  qa_keywords = r"approved|passed|cleared"
269
  qa_pass = qa_report and re.search(qa_keywords, qa_report.lower())
270
  if qa_pass:
 
273
  else:
274
  missing_items.append("QA Approval Missing")
275
  checklist_details.append("QA Report: Pending")
276
+ logging.info(f"QA Check: {'Pass' if qa_pass else 'Fail'}, Score so far: {score}%")
277
 
278
+ # Process Punch List (30% weight)
279
  punch_keywords = r"none|resolved|closed|no issues"
280
  punch_pass = punch_list_text and re.search(punch_keywords, punch_list_text.lower())
281
  if punch_pass:
 
284
  else:
285
  missing_items.append("Open Punch Points Detected")
286
  checklist_details.append("Punch List: Pending")
287
+ logging.info(f"Punch List Check: {'Pass' if punch_pass else 'Fail'}, Final Score: {score}%")
288
+
289
+ # Calculate open punch items
290
+ open_punch_items = 0 if punch_pass else 1 # Simplified for dropdown input
291
 
292
+ # Enhanced escalation logic
293
  escalated = score < 70 or open_punch_items > 2
294
+ logging.info(f"Escalation Check: Score < 70 ({score < 70}), Open Punch Items > 2 ({open_punch_items > 2}), Escalated: {escalated}")
295
+
296
+ # Map "Pending" to "In Progress" for Salesforce Status__c picklist
297
  status = "Escalated" if escalated else ("Completed" if not missing_items else "In Progress")
298
+ checklist_status = "Escalated" if escalated else ("Completed" if not missing_items else "Pending") # For UI display
299
 
300
+ # Build summaries
301
  checklist_summary = "\n".join(checklist_details)
302
  missing_summary = "None" if not missing_items else ", ".join(missing_items)
303
 
304
+ # Generate progress bar HTML
305
  color_class = "red" if score < 70 else "yellow" if score <= 90 else "green"
306
+ logging.info(f"Readiness Score: {score}%, Color Class: {color_class}")
307
  progress_bar = f"""
308
  <div class="progress-container">
309
  <div class="progress-bar {color_class}" style="width: {score}%;">
 
312
  </div>
313
  """
314
 
315
+ return (score, checklist_summary, missing_summary, status, progress_bar, escalated, logs, qa_report, punch_list_text, open_punch_items, checklist_status)
 
316
  except Exception as e:
317
  logging.error(f"Error in evaluate_readiness: {str(e)}")
318
  raise
319
 
320
  # Generate PDF report with signature slots
321
+ def generate_pdf(score, checklist_summary, missing_summary, checklist_status, logs, qa_report, punch_list_text):
322
  try:
323
+ # Sanitize inputs
324
+ logging.info("Sanitizing inputs for PDF generation")
325
  score = str(float(score)) if score is not None else "0"
326
+ checklist_summary = str(checklist_summary) if checklist_summary is not None else ""
327
+ checklist_status = str(checklist_status) if checklist_status is not None else ""
328
 
329
+ # Remove non-ASCII characters
330
  checklist_summary = checklist_summary.encode('ascii', 'ignore').decode('ascii')
331
  checklist_status = checklist_status.encode('ascii', 'ignore').decode('ascii')
332
 
333
+ # Define the temporary file path
334
+ pdf_path = "readiness_report.pdf"
335
+ logging.info(f"Creating PDF at {pdf_path}")
336
 
337
+ # Create the PDF
338
  c = canvas.Canvas(pdf_path, pagesize=letter)
339
  c.setFont("Times-Bold", 16)
340
  c.drawString(50, 750, "Project Closure Readiness Report")
341
  c.setFont("Times-Roman", 12)
342
+ c.drawString(50, 720, f"Readiness Score: {score}%")
343
+ c.drawString(50, 700, f"Status: {checklist_status}")
344
+ c.drawString(50, 680, "Checklist Summary:")
345
+ y = 660
 
346
  BOTTOM_MARGIN = 50
347
  for line in checklist_summary.split("\n"):
348
  if y < BOTTOM_MARGIN:
349
  c.showPage()
350
  c.setFont("Times-Roman", 12)
351
  y = 750
352
+ c.drawString(50, y, line)
353
  y -= 20
354
  if y - 40 < BOTTOM_MARGIN:
355
  c.showPage()
 
363
  c.drawString(50, y - 60, "Date: ____________________")
364
  c.save()
365
 
366
+ # Confirm the file exists
367
  if not os.path.exists(pdf_path):
368
  logging.error(f"PDF file {pdf_path} was not created")
369
  raise FileNotFoundError(f"PDF file {pdf_path} was not created")
 
374
  logging.error(f"Error in generate_pdf: {str(e)}")
375
  raise
376
 
377
+ # Gradio interface with updated UI
378
  with gr.Blocks(css="""
379
  .progress-container { background-color: #f0f0f0; width: 100%; height: 20px; border-radius: 5px; overflow: hidden; position: relative; }
380
  .progress-bar { height: 100%; text-align: center; line-height: 20px; color: #000; font-size: 12px; }
 
390
  )
391
  with gr.Row():
392
  with gr.Column(scale=2):
 
393
  logs_input = gr.Textbox(label="Project Logs", lines=5, placeholder="Enter project logs (e.g., 'Project complete, handover done')")
394
  qa_input = gr.Dropdown(
395
  label="QA Report",
 
405
  )
406
  submit_btn = gr.Button("Evaluate and Generate PDF")
407
  with gr.Column(scale=3):
 
408
  score_output = gr.Number(label="Readiness Score (%)")
409
  progress_output = gr.HTML(label="Alert Indicator: Progress")
410
  gr.Markdown("Color-coded readiness: Red (<70%), Yellow (70-90%), Green (>90%)")
 
416
  open_punch_items_output = gr.Number(label="Open Punch Items (Debug)")
417
  pdf_output = gr.File(label="Download PDF Report", type="filepath", interactive=False)
418
  pdf_debug = gr.Textbox(label="PDF Debug Output")
 
419
 
420
+ # Chain the evaluation, PDF generation, and Salesforce record creation
421
  submit_btn.click(
422
  fn=evaluate_readiness,
423
+ inputs=[logs_input, qa_input, punch_input],
424
  outputs=[
425
+ score_output, checklist_output, missing_output, status_output, progress_output,
426
  gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, status_output
427
  ]
428
  ).then(
429
  fn=generate_pdf,
430
+ inputs=[score_output, checklist_output, missing_output, status_output, gr.State(), gr.State(), gr.State()],
431
  outputs=[pdf_output, pdf_debug]
432
  ).then(
433
  fn=create_salesforce_record,
434
  inputs=[
435
+ score_output, checklist_output, missing_output, status_output,
436
  gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, pdf_output
437
  ],
438
+ outputs=None
439
  )
440
+ demo.launch()