Dineshpopuri commited on
Commit
a93deaa
·
verified ·
1 Parent(s): c4b931f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +78 -106
app.py CHANGED
@@ -5,7 +5,6 @@ from reportlab.pdfgen import canvas
5
  import bleach
6
  import logging
7
  import os
8
- import tempfile
9
  from huggingface_hub import InferenceClient
10
  from retry import retry
11
  import time
@@ -114,7 +113,7 @@ def summarize_text(text, max_length=100, min_length=30):
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_url):
118
  if not SALESFORCE_AVAILABLE:
119
  logging.error("Salesforce library not available. Skipping record creation.")
120
  return "Salesforce library not available"
@@ -143,9 +142,19 @@ def create_salesforce_record(score, checklist_summary, missing_summary, status,
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
  # Explicitly convert escalated to lowercase boolean string for Salesforce
146
- alert_sent = str(bool(escalated)).lower()
147
  logging.info(f"Setting Alert_Sent__c to: {alert_sent}")
148
- escalation_flag = str(bool(escalated)).lower()
 
 
 
 
 
 
 
 
 
 
149
 
150
  # Create a record in the custom object Project_Closure_Handover__c
151
  record = {
@@ -155,8 +164,8 @@ def create_salesforce_record(score, checklist_summary, missing_summary, status,
155
  "Status__c": status,
156
  "Summarized_Missing_Items__c": summarized_missing,
157
  "Alert_Sent__c": alert_sent,
158
- "Client_PDF_Pack_URL__c": pdf_url if pdf_url else "",
159
- "Closure_Pack_URL__c": "",
160
  "Escalation_Flag__c": escalation_flag,
161
  "Evaluated_At__c": evaluated_at,
162
  "Logs__c": logs,
@@ -182,33 +191,34 @@ def create_salesforce_record(score, checklist_summary, missing_summary, status,
182
  logging.error(f"Failed to create Salesforce Project_Closure_Handover__c record: {str(e)}")
183
  return f"Error creating record: {str(e)}"
184
 
185
- # Clean input to prevent injection attacks and ensure PDF compatibility
186
  def sanitize_input(text):
187
  if not text or not isinstance(text, str):
188
  return ""
189
- # Clean HTML and strip whitespace
190
- cleaned = bleach.clean(text.strip(), tags=[], attributes={})
191
- # Replace non-ASCII characters with a space
192
- cleaned = cleaned.encode('ascii', 'replace').decode('ascii').replace('?', ' ')
193
- return cleaned
194
 
195
  # Rule-based completeness engine with weighted scoring
196
  def evaluate_readiness(logs, qa_report, punch_list_text):
197
  try:
 
198
  logging.info(f"Inputs - Logs: {logs}, QA Report: {qa_report}, Punch List: {punch_list_text}")
199
 
 
200
  score = 0
201
  missing_items = []
202
  checklist_details = []
203
 
204
- LOGS_WEIGHT = 30
205
- QA_WEIGHT = 40
206
- PUNCH_WEIGHT = 30
 
207
 
 
208
  logs = sanitize_input(logs)
209
  qa_report = sanitize_input(qa_report)
210
  punch_list_text = sanitize_input(punch_list_text)
211
 
 
212
  log_keywords = r"complete|handover done|finished|closed|successful"
213
  logs_pass = logs and re.search(log_keywords, logs.lower())
214
  if logs_pass:
@@ -219,6 +229,7 @@ def evaluate_readiness(logs, qa_report, punch_list_text):
219
  checklist_details.append("Logs: Pending")
220
  logging.info(f"Logs Check: {'Pass' if logs_pass else 'Fail'}, Score so far: {score}%")
221
 
 
222
  qa_keywords = r"approved|passed|cleared"
223
  qa_pass = qa_report and re.search(qa_keywords, qa_report.lower())
224
  if qa_pass:
@@ -229,6 +240,7 @@ def evaluate_readiness(logs, qa_report, punch_list_text):
229
  checklist_details.append("QA Report: Pending")
230
  logging.info(f"QA Check: {'Pass' if qa_pass else 'Fail'}, Score so far: {score}%")
231
 
 
232
  punch_keywords = r"none|resolved|closed|no issues"
233
  punch_pass = punch_list_text and re.search(punch_keywords, punch_list_text.lower())
234
  if punch_pass:
@@ -239,15 +251,20 @@ def evaluate_readiness(logs, qa_report, punch_list_text):
239
  checklist_details.append("Punch List: Pending")
240
  logging.info(f"Punch List Check: {'Pass' if punch_pass else 'Fail'}, Final Score: {score}%")
241
 
 
242
  open_punch_items = 0 if punch_pass else len(punch_list_text.split(",")) if punch_list_text else 1
243
 
 
244
  escalated = any("incomplete" in item.lower() or "missing" in item.lower() for item in missing_items)
 
245
  status = "Escalated" if escalated else ("Completed" if not missing_items else "In Progress")
246
- checklist_status = "Escalated" if escalated else ("Completed" if not missing_items else "Pending")
247
 
 
248
  checklist_summary = "\n".join(checklist_details)
249
  missing_summary = "None" if not missing_items else ", ".join(missing_items)
250
 
 
251
  color_class = "red" if score < 70 else "yellow" if score <= 90 else "green"
252
  logging.info(f"Readiness Score: {score}%, Color Class: {color_class}")
253
  progress_bar = f"""
@@ -258,116 +275,71 @@ def evaluate_readiness(logs, qa_report, punch_list_text):
258
  </div>
259
  """
260
 
 
261
  return (score, checklist_summary, missing_summary, status, progress_bar, escalated, logs, qa_report, punch_list_text, open_punch_items, checklist_status)
262
  except Exception as e:
263
  logging.error(f"Error in evaluate_readiness: {str(e)}")
264
  raise
265
 
266
- # Generate PDF report with signature slots and return a publicly accessible URL
267
  def generate_pdf(score, checklist_summary, missing_summary, checklist_status, logs, qa_report, punch_list_text):
268
  try:
269
- # Sanitize inputs and handle None values
270
  logging.info("Sanitizing inputs for PDF generation")
271
  score = str(float(score)) if score is not None else "0"
272
- checklist_summary = sanitize_input(checklist_summary) if checklist_summary else "No checklist summary provided"
273
- missing_summary = sanitize_input(missing_summary) if missing_summary else "None"
274
- checklist_status = sanitize_input(checklist_status) if checklist_status else "Unknown"
275
- logs = sanitize_input(logs) if logs else "No logs provided"
276
- qa_report = sanitize_input(qa_report) if qa_report else "No QA report provided"
277
- punch_list_text = sanitize_input(punch_list_text) if punch_list_text else "No punch list provided"
278
-
279
- # Use a temporary directory for PDF generation
280
- with tempfile.NamedTemporaryFile(suffix=".pdf", delete=False, dir="/tmp") as temp_file:
281
- pdf_path = temp_file.name
282
- logging.info(f"Attempting to create PDF at {pdf_path}")
283
 
284
  # Create the PDF using reportlab
285
  c = canvas.Canvas(pdf_path, pagesize=letter)
286
-
287
- # Set initial font and draw title
288
- logging.info("Setting font to Helvetica-Bold for title (fallback)")
289
- try:
290
- c.setFont("Helvetica-Bold", 16)
291
- except Exception as e:
292
- logging.error(f"Font error: {str(e)}. Using default font.")
293
- c.setFont("Helvetica", 16)
294
  c.drawString(50, 750, "Project Closure Readiness Report")
295
-
296
- # Draw basic fields
297
- logging.info("Drawing basic fields with Helvetica")
298
- try:
299
- c.setFont("Helvetica", 12)
300
- except Exception as e:
301
- logging.error(f"Font error: {str(e)}. Continuing with default font.")
302
  c.drawString(50, 720, f"Readiness Score: {score}%")
303
  c.drawString(50, 700, f"Status: {checklist_status}")
 
304
 
305
- # Draw checklist summary
306
  logging.info("Drawing checklist summary")
307
- y = 680
308
- LINE_HEIGHT = 20
309
- BOTTOM_MARGIN = 50
310
- c.drawString(50, y, "Checklist Summary:")
311
- y -= LINE_HEIGHT
312
-
313
- lines = checklist_summary.split("\n")
314
- if not lines or lines == [""]:
315
- lines = ["No checklist summary available"]
316
- for line in lines:
317
- # Truncate long lines to prevent overflow
318
- if len(line) > 80:
319
- line = line[:77] + "..."
320
  if y < BOTTOM_MARGIN:
321
  logging.info("Y position below margin, creating new page")
322
- c.showPage()
323
- try:
324
- c.setFont("Helvetica", 12)
325
- except:
326
- pass
327
- y = 750
328
  c.drawString(50, y, line)
329
- y -= LINE_HEIGHT
330
-
331
- # Draw missing summary
332
- logging.info("Drawing missing summary")
333
- y -= 20
334
- c.drawString(50, y, "Missing Items:")
335
- y -= LINE_HEIGHT
336
- missing_lines = missing_summary.split("\n") if missing_summary else ["None"]
337
- for line in missing_lines:
338
- if len(line) > 80:
339
- line = line[:77] + "..."
340
- if y < BOTTOM_MARGIN:
341
- c.showPage()
342
- try:
343
- c.setFont("Helvetica", 12)
344
- except:
345
- pass
346
- y = 750
347
- c.drawString(50, y, line)
348
- y -= LINE_HEIGHT
349
 
350
  # Draw signature slots
351
- logging.info("Drawing signature slots")
352
- y -= 20
353
  if y - 40 < BOTTOM_MARGIN:
 
354
  c.showPage()
355
- try:
356
- c.setFont("Helvetica", 12)
357
- except:
358
- pass
359
  y = 750
360
- c.drawString(50, y - 20, "Stakeholder Signature: ____________________")
361
 
362
- y -= 20
363
  if y - 60 < BOTTOM_MARGIN:
 
364
  c.showPage()
365
- try:
366
- c.setFont("Helvetica", 12)
367
- except:
368
- pass
369
  y = 750
370
- c.drawString(50, y - 40, "Date: ____________________")
371
 
372
  # Finalize the PDF
373
  logging.info("Finalizing PDF")
@@ -378,12 +350,8 @@ def generate_pdf(score, checklist_summary, missing_summary, checklist_status, lo
378
  logging.error(f"PDF file {pdf_path} was not created")
379
  raise FileNotFoundError(f"PDF file {pdf_path} was not created")
380
 
381
- # Simulate uploading the PDF to a publicly accessible location
382
- simulated_public_url = f"https://example.com/pdf/readiness_report_{int(time.time())}.pdf"
383
- logging.info(f"Simulated public URL for PDF: {simulated_public_url}")
384
-
385
  logging.info(f"PDF generated successfully at {pdf_path}")
386
- return simulated_public_url, f"PDF generation completed. File saved at {pdf_path}. Click the link to download."
387
  except Exception as e:
388
  logging.error(f"Error in generate_pdf: {str(e)}")
389
  raise
@@ -417,9 +385,10 @@ with gr.Blocks(css="""
417
  gr.Markdown("### Handover Summary")
418
  checklist_output = gr.Textbox(label="Checklist Summary")
419
  missing_output = gr.Textbox(label="Missing Items")
420
- pdf_output = gr.File(label="Download PDF Report", type="filepath", interactive=False)
421
- pdf_debug = gr.Textbox(label="PDF Debug Output")
422
 
 
423
  submit_btn.click(
424
  fn=evaluate_readiness,
425
  inputs=[logs_input, qa_input, punch_input],
@@ -432,6 +401,9 @@ with gr.Blocks(css="""
432
  ).then(
433
  fn=create_salesforce_record,
434
  inputs=[score_output, checklist_output, missing_output, status_output, gr.State(), gr.State(), gr.State(), gr.State(), gr.State(), pdf_output],
435
- outputs=None
436
  )
437
- demo.launch()
 
 
 
 
5
  import bleach
6
  import logging
7
  import os
 
8
  from huggingface_hub import InferenceClient
9
  from retry import retry
10
  import time
 
113
  return text # Fallback to original text
114
 
115
  # Create Salesforce record in custom object Project_Closure_Handover__c
116
+ def create_salesforce_record(score, checklist_summary, missing_summary, status, escalated, logs, qa_report, punch_list_text, open_punch_items, pdf_path=None):
117
  if not SALESFORCE_AVAILABLE:
118
  logging.error("Salesforce library not available. Skipping record creation.")
119
  return "Salesforce library not available"
 
142
  open_punch_items = int(open_punch_items) if open_punch_items is not None else 0
143
  evaluated_at = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") # Salesforce Date/Time format
144
  # Explicitly convert escalated to lowercase boolean string for Salesforce
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
+ # Explicitly convert escalated to lowercase boolean string for Escalation_Flag__c
148
+ escalation_flag = str(bool(escalated)).lower() # Ensure this is also a proper boolean string
149
+
150
+ # Check if pdf_path is a valid URL
151
+ pdf_url = ""
152
+ if pdf_path:
153
+ # In a real environment, you'd need to upload the PDF to a publicly accessible location
154
+ # For now, we'll log a warning and set the URL to empty to avoid Salesforce validation errors
155
+ logging.warning(f"PDF path provided: {pdf_path}. However, this is a local file path, not a valid URL.")
156
+ logging.warning("To set Client_PDF_Pack_URL__c, the PDF must be hosted publicly (e.g., via S3 or another service).")
157
+ logging.warning("Setting Client_PDF_Pack_URL__c to empty string to avoid Salesforce validation error.")
158
 
159
  # Create a record in the custom object Project_Closure_Handover__c
160
  record = {
 
164
  "Status__c": status,
165
  "Summarized_Missing_Items__c": summarized_missing,
166
  "Alert_Sent__c": alert_sent,
167
+ "Client_PDF_Pack_URL__c": pdf_url, # Set to empty string to avoid validation error
168
+ "Closure_Pack_URL__c": "", # Placeholder; update if you have a closure pack URL
169
  "Escalation_Flag__c": escalation_flag,
170
  "Evaluated_At__c": evaluated_at,
171
  "Logs__c": logs,
 
191
  logging.error(f"Failed to create Salesforce Project_Closure_Handover__c record: {str(e)}")
192
  return f"Error creating record: {str(e)}"
193
 
194
+ # Clean input to prevent injection attacks
195
  def sanitize_input(text):
196
  if not text or not isinstance(text, str):
197
  return ""
198
+ return bleach.clean(text.strip())
 
 
 
 
199
 
200
  # Rule-based completeness engine with weighted scoring
201
  def evaluate_readiness(logs, qa_report, punch_list_text):
202
  try:
203
+ # Log inputs for debugging
204
  logging.info(f"Inputs - Logs: {logs}, QA Report: {qa_report}, Punch List: {punch_list_text}")
205
 
206
+ # Initialize score and lists for tracking
207
  score = 0
208
  missing_items = []
209
  checklist_details = []
210
 
211
+ # Define weights for scoring
212
+ LOGS_WEIGHT = 30 # 30% weight for logs
213
+ QA_WEIGHT = 40 # 40% weight for QA report
214
+ PUNCH_WEIGHT = 30 # 30% weight for punch list
215
 
216
+ # Sanitize inputs
217
  logs = sanitize_input(logs)
218
  qa_report = sanitize_input(qa_report)
219
  punch_list_text = sanitize_input(punch_list_text)
220
 
221
+ # Process Project Logs (30% weight)
222
  log_keywords = r"complete|handover done|finished|closed|successful"
223
  logs_pass = logs and re.search(log_keywords, logs.lower())
224
  if logs_pass:
 
229
  checklist_details.append("Logs: Pending")
230
  logging.info(f"Logs Check: {'Pass' if logs_pass else 'Fail'}, Score so far: {score}%")
231
 
232
+ # Process QA Report (40% weight)
233
  qa_keywords = r"approved|passed|cleared"
234
  qa_pass = qa_report and re.search(qa_keywords, qa_report.lower())
235
  if qa_pass:
 
240
  checklist_details.append("QA Report: Pending")
241
  logging.info(f"QA Check: {'Pass' if qa_pass else 'Fail'}, Score so far: {score}%")
242
 
243
+ # Process Punch List (30% weight)
244
  punch_keywords = r"none|resolved|closed|no issues"
245
  punch_pass = punch_list_text and re.search(punch_keywords, punch_list_text.lower())
246
  if punch_pass:
 
251
  checklist_details.append("Punch List: Pending")
252
  logging.info(f"Punch List Check: {'Pass' if punch_pass else 'Fail'}, Final Score: {score}%")
253
 
254
+ # Calculate open punch items
255
  open_punch_items = 0 if punch_pass else len(punch_list_text.split(",")) if punch_list_text else 1
256
 
257
+ # Handle critical issues (escalation)
258
  escalated = any("incomplete" in item.lower() or "missing" in item.lower() for item in missing_items)
259
+ # Map "Pending" to "In Progress" for Salesforce Status__c picklist
260
  status = "Escalated" if escalated else ("Completed" if not missing_items else "In Progress")
261
+ checklist_status = "Escalated" if escalated else ("Completed" if not missing_items else "Pending") # For UI display
262
 
263
+ # Build summaries
264
  checklist_summary = "\n".join(checklist_details)
265
  missing_summary = "None" if not missing_items else ", ".join(missing_items)
266
 
267
+ # Generate progress bar HTML using div elements
268
  color_class = "red" if score < 70 else "yellow" if score <= 90 else "green"
269
  logging.info(f"Readiness Score: {score}%, Color Class: {color_class}")
270
  progress_bar = f"""
 
275
  </div>
276
  """
277
 
278
+ # Return necessary values for PDF generation and Salesforce record
279
  return (score, checklist_summary, missing_summary, status, progress_bar, escalated, logs, qa_report, punch_list_text, open_punch_items, checklist_status)
280
  except Exception as e:
281
  logging.error(f"Error in evaluate_readiness: {str(e)}")
282
  raise
283
 
284
+ # Generate PDF report with signature slots and write to a temporary file
285
  def generate_pdf(score, checklist_summary, missing_summary, checklist_status, logs, qa_report, punch_list_text):
286
  try:
287
+ # Sanitize inputs to ensure they are strings and handle None
288
  logging.info("Sanitizing inputs for PDF generation")
289
  score = str(float(score)) if score is not None else "0"
290
+ checklist_summary = str(checklist_summary) if checklist_summary is not None else ""
291
+ checklist_status = str(checklist_status) if checklist_status is not None else ""
292
+
293
+ # Remove non-ASCII characters to prevent rendering issues
294
+ checklist_summary = checklist_summary.encode('ascii', 'ignore').decode('ascii')
295
+ checklist_status = checklist_status.encode('ascii', 'ignore').decode('ascii')
296
+
297
+ # Define the temporary file path
298
+ pdf_path = "readiness_report.pdf"
299
+ logging.info(f"Creating PDF at {pdf_path}")
 
300
 
301
  # Create the PDF using reportlab
302
  c = canvas.Canvas(pdf_path, pagesize=letter)
303
+
304
+ # Set initial font and draw title using Times-Bold
305
+ logging.info("Setting font to Times-Bold")
306
+ c.setFont("Times-Bold", 16)
 
 
 
 
307
  c.drawString(50, 750, "Project Closure Readiness Report")
308
+
309
+ # Draw basic fields using Times-Roman
310
+ logging.info("Drawing basic fields")
311
+ c.setFont("Times-Roman", 12)
 
 
 
312
  c.drawString(50, 720, f"Readiness Score: {score}%")
313
  c.drawString(50, 700, f"Status: {checklist_status}")
314
+ c.drawString(50, 680, "Checklist Summary:")
315
 
316
+ # Draw checklist summary with Y-position overflow handling
317
  logging.info("Drawing checklist summary")
318
+ y = 660
319
+ BOTTOM_MARGIN = 50 # Minimum Y position before creating a new page
320
+ for line in checklist_summary.split("\n"):
 
 
 
 
 
 
 
 
 
 
321
  if y < BOTTOM_MARGIN:
322
  logging.info("Y position below margin, creating new page")
323
+ c.showPage() # Create a new page
324
+ c.setFont("Times-Roman", 12) # Reset font for new page
325
+ y = 750 # Reset Y position
 
 
 
326
  c.drawString(50, y, line)
327
+ y -= 20
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
328
 
329
  # Draw signature slots
 
 
330
  if y - 40 < BOTTOM_MARGIN:
331
+ logging.info("Y position below margin, creating new page")
332
  c.showPage()
333
+ c.setFont("Times-Roman", 12)
 
 
 
334
  y = 750
335
+ c.drawString(50, y - 40, "Stakeholder Signature: ____________________")
336
 
 
337
  if y - 60 < BOTTOM_MARGIN:
338
+ logging.info("Y position below margin, creating new page")
339
  c.showPage()
340
+ c.setFont("Times-Roman", 12)
 
 
 
341
  y = 750
342
+ c.drawString(50, y - 60, "Date: ____________________")
343
 
344
  # Finalize the PDF
345
  logging.info("Finalizing PDF")
 
350
  logging.error(f"PDF file {pdf_path} was not created")
351
  raise FileNotFoundError(f"PDF file {pdf_path} was not created")
352
 
 
 
 
 
353
  logging.info(f"PDF generated successfully at {pdf_path}")
354
+ return pdf_path, "PDF generation completed. Click the link to download."
355
  except Exception as e:
356
  logging.error(f"Error in generate_pdf: {str(e)}")
357
  raise
 
385
  gr.Markdown("### Handover Summary")
386
  checklist_output = gr.Textbox(label="Checklist Summary")
387
  missing_output = gr.Textbox(label="Missing Items")
388
+ pdf_output = gr.File(label="Download PDF Report", type="filepath", interactive=False) # Output only, no auto-download
389
+ pdf_debug = gr.Textbox(label="PDF Debug Output") # Temporary debug output
390
 
391
+ # Chain the evaluation, PDF generation, and Salesforce record creation
392
  submit_btn.click(
393
  fn=evaluate_readiness,
394
  inputs=[logs_input, qa_input, punch_input],
 
401
  ).then(
402
  fn=create_salesforce_record,
403
  inputs=[score_output, checklist_output, missing_output, status_output, gr.State(), gr.State(), gr.State(), gr.State(), gr.State(), pdf_output],
404
+ outputs=None # No UI output for Salesforce status
405
  )
406
+ demo.launch()
407
+
408
+ # Comment out demo.launch() for Hugging Face Spaces deployment
409
+ # demo.launch()