File size: 21,023 Bytes
8295d42
12810cb
 
 
 
cd77c9b
240ee7a
b6cb32f
4d3e78d
 
3ce8a35
84c6769
cd77c9b
4b251bd
 
 
 
4d3e78d
4b251bd
 
 
4d3e78d
4b251bd
 
 
 
d64dce4
4b251bd
d64dce4
4b251bd
 
 
40ed91d
4b251bd
9172d5c
29109f8
9172d5c
 
 
 
 
4d3e78d
 
 
40ed91d
45e88c4
 
40ed91d
 
4d3e78d
 
b6cb32f
 
 
 
 
 
 
 
d64dce4
b6cb32f
4d3e78d
 
9172d5c
4b251bd
 
d988691
4b251bd
9172d5c
d988691
 
 
d64dce4
9172d5c
 
 
 
 
4d3e78d
0d855c4
40ed91d
d988691
d64dce4
0d855c4
d988691
 
d64dce4
4d3e78d
40ed91d
4d3e78d
cd77c9b
 
40ed91d
4d3e78d
9172d5c
b6cb32f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c61971d
9f6a147
4b251bd
 
d64dce4
4b251bd
cd77c9b
d988691
 
cd77c9b
4d3e78d
9172d5c
d988691
b6cb32f
 
1fd7646
3ce8a35
4d3e78d
d988691
 
 
 
 
 
 
 
eb02c5d
ea0944f
d988691
 
 
 
cdf17ec
31e7955
9172d5c
c61971d
 
3ce8a35
f1c3af4
b6cb32f
8dd5c64
d988691
 
8dd5c64
3ce8a35
 
 
 
7d14612
9172d5c
3ce8a35
d988691
4d3e78d
d988691
84c6769
d988691
cdf17ec
d988691
3473329
84c6769
d988691
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84c6769
d988691
84c6769
d64dce4
 
d988691
0d855c4
d988691
 
 
d64dce4
d988691
4d3e78d
cd77c9b
d988691
4d3e78d
223543d
a93deaa
12810cb
 
 
a93deaa
12810cb
b1396ef
d988691
cd77c9b
d988691
 
 
 
240ee7a
cd77c9b
 
 
d988691
 
 
 
240ee7a
d988691
cd77c9b
 
 
 
d988691
678abc9
62f03de
 
 
 
 
240ee7a
cd77c9b
d988691
62f03de
d988691
62f03de
 
d988691
cd77c9b
109f65d
cd77c9b
d988691
cd77c9b
d988691
cd77c9b
80608fa
 
240ee7a
cd77c9b
 
 
 
d988691
cd77c9b
d988691
cd77c9b
80608fa
 
240ee7a
cd77c9b
 
 
 
d988691
 
 
 
3ce8a35
d988691
62f03de
d988691
 
 
62f03de
d988691
cd77c9b
d988691
cd77c9b
 
 
d988691
80608fa
d988691
80608fa
 
 
 
 
 
 
cd77c9b
d988691
cd77c9b
240ee7a
cd77c9b
12810cb
62f03de
d988691
cd77c9b
d988691
 
37b77cd
d988691
 
a93deaa
d988691
a93deaa
 
 
d988691
 
 
9582e93
d988691
9582e93
a93deaa
cd77c9b
a93deaa
d988691
 
 
 
ea0944f
a93deaa
37b77cd
ea0944f
 
 
d988691
a93deaa
37b77cd
 
a93deaa
37b77cd
a93deaa
3ce8a35
 
6949038
3ce8a35
a93deaa
cd77c9b
9582e93
d988691
9582e93
 
 
 
 
a93deaa
cd77c9b
 
d64dce4
12810cb
d988691
80608fa
b6cb32f
80608fa
 
 
 
 
12810cb
 
 
d64dce4
12810cb
 
 
 
62f03de
ea0944f
 
193525a
ea0944f
 
 
 
 
 
 
 
 
d64dce4
12810cb
 
37b77cd
df24de1
12810cb
31c5a4e
 
 
 
29d2a6c
ea0944f
 
12810cb
d988691
12810cb
d64dce4
d988691
4dd8634
d988691
d64dce4
4dd8634
d64dce4
 
 
 
 
 
 
 
 
 
 
12810cb
d64dce4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
import gradio as gr
import re
from reportlab.lib.pagesizes import letter
from reportlab.pdfgen import canvas
import bleach
import logging
import os
from huggingface_hub import InferenceClient
from retry import retry
import time
from datetime import datetime
import base64

# Print statement to confirm script initialization
print("Starting Project Closure Readiness Evaluator app...")

# Set up console logging for debugging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()
logger.handlers = []  # Clear existing handlers
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

# Attempt to import Salesforce library with fallback
try:
    from simple_salesforce import Salesforce, SalesforceError
    SALESFORCE_AVAILABLE = True
except ImportError as e:
    logging.error(f"Failed to import simple-salesforce: {str(e)}. Salesforce functionality will be disabled.")
    logging.error("Ensure 'simple-salesforce' is included in requirements.txt and installed in your Hugging Face Space.")
    SALESFORCE_AVAILABLE = False

# Salesforce configuration (loaded from environment variables for security)
SF_USERNAME = os.getenv("SF_USERNAME")
SF_PASSWORD = os.getenv("SF_PASSWORD")
SF_SECURITY_TOKEN = os.getenv("SF_SECURITY_TOKEN")
SF_INSTANCE_URL = os.getenv("SF_INSTANCE_URL")

# Validate Salesforce environment variables
if not all([SF_USERNAME, SF_PASSWORD, SF_SECURITY_TOKEN, SF_INSTANCE_URL]):
    logging.error("One or more Salesforce environment variables are missing. Salesforce functionality will be disabled.")
    logging.error(f"SF_USERNAME: {SF_USERNAME if SF_USERNAME else 'Not Set'}")
    logging.error(f"SF_PASSWORD: {'Set' if SF_PASSWORD else 'Not Set'}")
    logging.error(f"SF_SECURITY_TOKEN: {'Set' if SF_SECURITY_TOKEN else 'Not Set'}")
    logging.error(f"SF_INSTANCE_URL: {SF_INSTANCE_URL if SF_INSTANCE_URL else 'Not Set'}")
    logging.error("Please set these variables in Hugging Face Space Settings > Secrets.")
    SALESFORCE_AVAILABLE = False

# Hugging Face configuration
HF_API_TOKEN = os.getenv("HF_API_TOKEN")
if not HF_API_TOKEN:
    logging.error("Hugging Face API token (HF_API_TOKEN) not found. Hugging Face functionality will be disabled.")
    logging.error("Please set HF_API_TOKEN in Hugging Face Space Settings > Secrets.")
    HF_AVAILABLE = False
else:
    HF_AVAILABLE = True
    hf_client = InferenceClient(token=HF_API_TOKEN)

# Initialize Salesforce connection with retry mechanism
@retry(tries=3, delay=2, backoff=2, logger=logger)
def init_salesforce():
    if not SALESFORCE_AVAILABLE:
        logging.error("Salesforce library not available. Skipping connection.")
        return None, "Salesforce library not available"

    try:
        logging.info("Attempting to connect to Salesforce with the following credentials:")
        logging.info(f"Username: {SF_USERNAME}")
        logging.info(f"Instance URL: {SF_INSTANCE_URL}")
        sf = Salesforce(
            username=SF_USERNAME,
            password=SF_PASSWORD,
            security_token=SF_SECURITY_TOKEN,
            instance_url=SF_INSTANCE_URL
        )
        logging.info("Salesforce connected successfully")
        # Test read access on Project_Closure_Handover__c
        test_query = sf.query("SELECT Id FROM Project_Closure_Handover__c LIMIT 1")
        logging.info(f"Test query result (read access): {test_query}")
        # Test create access by attempting to describe the object and check permissions
        object_description = sf.Project_Closure_Handover__c.describe()
        logging.info(f"Object description: {object_description}")
        return sf, "Salesforce connected successfully"
    except SalesforceError as e:
        logging.error(f"Salesforce authentication failed: {str(e)}")
        logging.error("Possible issues: Incorrect credentials, IP restrictions, or insufficient permissions.")
        raise
    except Exception as e:
        logging.error(f"Failed to initialize Salesforce connection: {str(e)}")
        logging.error("Check your Salesforce org settings, network restrictions, or API access.")
        raise

# Summarize text using Hugging Face Inference API
def summarize_text(text, max_length=100, min_length=30):
    if not HF_AVAILABLE:
        logging.error("Hugging Face API not available. Returning original text.")
        return text

    if not text or text == "None":
        return "No summary available"

    try:
        summary = hf_client.summarization(
            text,
            model="facebook/bart-large-cnn",
            parameters={"max_length": max_length, "min_length": min_length}
        )
        return summary
    except Exception as e:
        logging.error(f"Failed to summarize text with Hugging Face: {str(e)}")
        return text  # Fallback to original text

# Create Salesforce record in custom object Project_Closure_Handover__c
def create_salesforce_record(score, checklist_summary, missing_summary, status, escalated, logs, qa_report, punch_list_text, open_punch_items, pdf_path=None):
    if not SALESFORCE_AVAILABLE:
        logging.error("Salesforce library not available. Skipping record creation.")
        return "Salesforce library not available"

    try:
        sf, connection_message = init_salesforce()
        if not sf:
            logging.error(f"Skipping Salesforce record creation due to connection failure: {connection_message}")
            return connection_message

        # Summarize checklist_summary and missing_summary using Hugging Face
        summarized_checklist = summarize_text(checklist_summary)
        summarized_missing = summarize_text(missing_summary)

        # Ensure inputs are properly formatted
        score = float(score) if score is not None else 0.0
        checklist_summary = str(checklist_summary) if checklist_summary else ""
        summarized_checklist = str(summarized_checklist) if summarized_checklist else ""
        missing_summary = str(missing_summary) if missing_summary else ""
        summarized_missing = str(summarized_missing) if summarized_missing else ""
        status = str(status) if status else ""
        logs = str(logs) if logs else ""
        qa_report = str(qa_report) if qa_report else ""
        punch_list_text = str(punch_list_text) if punch_list_text else ""
        missing_documents = len(missing_summary.split(", ")) if missing_summary and missing_summary != "None" else 0
        open_punch_items = int(open_punch_items) if open_punch_items is not None else 0
        evaluated_at = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ")  # Salesforce Date/Time format
        alert_sent = str(bool(escalated)).lower()  # Converts True/False to "true"/"false"
        logging.info(f"Setting Alert_Sent__c to: {alert_sent}")
        escalation_flag = str(bool(escalated)).lower()  # Ensure this is also a proper boolean string

        # Create the record in Project_Closure_Handover__c without the PDF URL for now
        record = {
            "Readiness_Score__c": score,
            "Checklist_Summary__c": checklist_summary,
            "Missing_Documents__c": missing_documents,
            "Status__c": status,
            "Summarized_Missing_Items__c": summarized_missing,
            "Alert_Sent__c": alert_sent,
            "Client_PDF_Pack_URL__c": "",  # Will be updated after attachment is created
            "Closure_Pack_URL__c": "",  # Placeholder; update if you have a closure pack URL
            "Escalation_Flag__c": escalation_flag,
            "Evaluated_At__c": evaluated_at,
            "Logs__c": logs,
            "Open_Punch_Items__c": open_punch_items,
            "Punch_List__c": punch_list_text,
            "QA_Report__c": qa_report
        }

        logging.debug(f"Attempting to create Salesforce record in Project_Closure_Handover__c with data: {record}")
        result = sf.Project_Closure_Handover__c.create(record)
        logging.info(f"Successfully created Salesforce record: {result}")
        record_id = result.get('id')
        logging.info(f"Record ID: {record_id}")

        # Attach the PDF to the record if pdf_path exists and update the URL
        pdf_download_url = ""
        if pdf_path and os.path.exists(pdf_path):
            logging.info(f"Attaching PDF to Salesforce record ID: {record_id}")
            with open(pdf_path, "rb") as pdf_file:
                pdf_content = pdf_file.read()
                pdf_base64 = base64.b64encode(pdf_content).decode('utf-8')
            
            attachment = {
                "ParentId": record_id,
                "Name": "Readiness_Report.pdf",
                "Body": pdf_base64,
                "ContentType": "application/pdf"
            }
            
            attachment_result = sf.Attachment.create(attachment)
            logging.info(f"Successfully attached PDF to record: {attachment_result}")
            attachment_id = attachment_result.get('id')
            logging.info(f"Attachment ID: {attachment_id}")

            # Construct the direct download URL for the attachment
            pdf_download_url = f"{SF_INSTANCE_URL}/servlet/servlet.FileDownload?file={attachment_id}"
            logging.info(f"Generated PDF download URL: {pdf_download_url}")

            # Update the Project_Closure_Handover__c record with the PDF download URL
            update_data = {
                "Client_PDF_Pack_URL__c": pdf_download_url
            }
            sf.Project_Closure_Handover__c.update(record_id, update_data)
            logging.info(f"Updated record {record_id} with Client_PDF_Pack_URL__c: {pdf_download_url}")
        else:
            logging.warning(f"No PDF file found at {pdf_path}. Skipping attachment and URL update.")

        return f"Record created successfully. Record ID: {record_id}. PDF attached and URL set to: {pdf_download_url}"
    except SalesforceError as e:
        logging.error(f"Salesforce error while creating Project_Closure_Handover__c record: {str(e)}")
        logging.error("Possible issues: Object permissions, field-level security, validation rules, or required fields.")
        logging.error("Check the following in your Salesforce org:")
        logging.error("- Ensure the user has Create and Edit permission on Project_Closure_Handover__c.")
        logging.error("- Ensure the user has permission to create and read Attachments.")
        logging.error("- Verify field-level security for all fields in the record.")
        logging.error("- Check for validation rules or required fields that might be failing.")
        return f"Salesforce error: {str(e)}"
    except Exception as e:
        logging.error(f"Failed to create Salesforce Project_Closure_Handover__c record: {str(e)}")
        return f"Error creating record: {str(e)}"

# Clean input to prevent injection attacks
def sanitize_input(text):
    if not text or not isinstance(text, str):
        return ""
    return bleach.clean(text.strip())

# Rule-based completeness engine with weighted scoring
def evaluate_readiness(logs, qa_report, punch_list_text):
    try:
        # Log inputs for debugging
        logging.info(f"Inputs - Logs: {logs}, QA Report: {qa_report}, Punch List: {punch_list_text}")

        # Initialize score and lists for tracking
        score = 0
        missing_items = []
        checklist_details = []

        # Define weights for scoring
        LOGS_WEIGHT = 30  # 30% weight for logs
        QA_WEIGHT = 40    # 40% weight for QA report
        PUNCH_WEIGHT = 30 # 30% weight for punch list

        # Sanitize inputs
        logs = sanitize_input(logs)
        qa_report = sanitize_input(qa_report)
        punch_list_text = sanitize_input(punch_list_text)

        # Process Project Logs (30% weight)
        log_keywords = r"complete|handover done|done|finished|closed|successful"
        negative_keywords = r"issue|pending|incomplete|problem|delay"
        logs_pass = logs and re.search(log_keywords, logs.lower())
        logs_negative = logs and re.search(negative_keywords, logs.lower())
        
        if logs_pass and not logs_negative:
            score += LOGS_WEIGHT
            checklist_details.append("Logs: Completed")
            logging.info(f"Logs Check: Pass (positive keywords found, no negative keywords), Score: {score}%")
        elif logs_pass and logs_negative:
            score += LOGS_WEIGHT // 2  # Partial score for mixed signals
            missing_items.append("Issues detected in logs")
            checklist_details.append("Logs: Partially Completed (issues detected)")
            logging.info(f"Logs Check: Partial Pass (positive and negative keywords found), Score: {score}%")
        else:
            missing_items.append("Project Logs Incomplete")
            checklist_details.append("Logs: Pending")
            logging.info(f"Logs Check: Fail (no positive keywords or only negative keywords), Score: {score}%")

        # Process QA Report (40% weight)
        qa_keywords = r"approved|passed|cleared"
        qa_pass = qa_report and re.search(qa_keywords, qa_report.lower())
        if qa_pass:
            score += QA_WEIGHT
            checklist_details.append("QA Report: Approved")
        else:
            missing_items.append("QA Approval Missing")
            checklist_details.append("QA Report: Pending")
        logging.info(f"QA Check: {'Pass' if qa_pass else 'Fail'}, Score so far: {score}%")

        # Process Punch List (30% weight)
        punch_keywords = r"none|resolved|closed|no issues"
        punch_pass = punch_list_text and re.search(punch_keywords, punch_list_text.lower())
        if punch_pass:
            score += PUNCH_WEIGHT
            checklist_details.append("Punch List: Resolved")
        else:
            missing_items.append("Open Punch Points Detected")
            checklist_details.append("Punch List: Pending")
        logging.info(f"Punch List Check: {'Pass' if punch_pass else 'Fail'}, Final Score: {score}%")

        # Calculate open punch items
        open_punch_items = 0 if punch_pass else 1  # Simplified for dropdown input

        # Enhanced escalation logic
        escalated = score < 70 or open_punch_items > 2
        logging.info(f"Escalation Check: Score < 70 ({score < 70}), Open Punch Items > 2 ({open_punch_items > 2}), Escalated: {escalated}")

        # Map "Pending" to "In Progress" for Salesforce Status__c picklist
        status = "Escalated" if escalated else ("Completed" if not missing_items else "In Progress")
        checklist_status = "Escalated" if escalated else ("Completed" if not missing_items else "Pending")  # For UI display

        # Build summaries
        checklist_summary = "\n".join(checklist_details)
        missing_summary = "None" if not missing_items else ", ".join(missing_items)

        # Generate progress bar HTML
        color_class = "red" if score < 70 else "yellow" if score <= 90 else "green"
        logging.info(f"Readiness Score: {score}%, Color Class: {color_class}")
        progress_bar = f"""
        <div class="progress-container">
            <div class="progress-bar {color_class}" style="width: {score}%;">
                {score}%
            </div>
        </div>
        """

        return (score, checklist_summary, missing_summary, status, progress_bar, escalated, logs, qa_report, punch_list_text, open_punch_items, checklist_status)
    except Exception as e:
        logging.error(f"Error in evaluate_readiness: {str(e)}")
        raise

# Generate PDF report with signature slots
def generate_pdf(score, checklist_summary, missing_summary, checklist_status, logs, qa_report, punch_list_text):
    try:
        # Sanitize inputs
        logging.info("Sanitizing inputs for PDF generation")
        score = str(float(score)) if score is not None else "0"
        checklist_summary = str(checklist_summary) if checklist_summary is not None else ""
        checklist_status = str(checklist_status) if checklist_status is not None else ""

        # Remove non-ASCII characters
        checklist_summary = checklist_summary.encode('ascii', 'ignore').decode('ascii')
        checklist_status = checklist_status.encode('ascii', 'ignore').decode('ascii')

        # Define the temporary file path
        pdf_path = "readiness_report.pdf"
        logging.info(f"Creating PDF at {pdf_path}")

        # Create the PDF
        c = canvas.Canvas(pdf_path, pagesize=letter)
        c.setFont("Times-Bold", 16)
        c.drawString(50, 750, "Project Closure Readiness Report")
        c.setFont("Times-Roman", 12)
        c.drawString(50, 720, f"Readiness Score: {score}%")
        c.drawString(50, 700, f"Status: {checklist_status}")
        c.drawString(50, 680, "Checklist Summary:")
        y = 660
        BOTTOM_MARGIN = 50
        for line in checklist_summary.split("\n"):
            if y < BOTTOM_MARGIN:
                c.showPage()
                c.setFont("Times-Roman", 12)
                y = 750
            c.drawString(50, y, line)
            y -= 20
        if y - 40 < BOTTOM_MARGIN:
            c.showPage()
            c.setFont("Times-Roman", 12)
            y = 750
        c.drawString(50, y - 40, "Stakeholder Signature: ____________________")
        if y - 60 < BOTTOM_MARGIN:
            c.showPage()
            c.setFont("Times-Roman", 12)
            y = 750
        c.drawString(50, y - 60, "Date: ____________________")
        c.save()

        # Confirm the file exists
        if not os.path.exists(pdf_path):
            logging.error(f"PDF file {pdf_path} was not created")
            raise FileNotFoundError(f"PDF file {pdf_path} was not created")

        logging.info(f"PDF generated successfully at {pdf_path}")
        return pdf_path, "PDF generation completed. Click the link to download."
    except Exception as e:
        logging.error(f"Error in generate_pdf: {str(e)}")
        raise

# Gradio interface with updated UI
with gr.Blocks(css="""
    .progress-container { background-color: #f0f0f0; width: 100%; height: 20px; border-radius: 5px; overflow: hidden; position: relative; }
    .progress-bar { height: 100%; text-align: center; line-height: 20px; color: #000; font-size: 12px; }
    .progress-bar.red { background-color: #FF0000; }
    .progress-bar.yellow { background-color: #FFFF00; }
    .progress-bar.green { background-color: #00FF00; }
""") as demo:
    gr.Markdown(
        """
        # Project Closure Readiness Evaluator
        Evaluate project readiness, generate a PDF report with signature slots.
        """
    )
    with gr.Row():
        with gr.Column(scale=2):
            logs_input = gr.Textbox(label="Project Logs", lines=5, placeholder="Enter project logs (e.g., 'Project complete, handover done')")
            qa_input = gr.Dropdown(
                label="QA Report",
                choices=["Approved", "Cleared", "Pending", "Not Started", "Rejected"],
                value="Pending",
                allow_custom_value=False
            )
            punch_input = gr.Dropdown(
                label="Punch List",
                choices=["None", "Resolved", "Closed", "No Issues", "Open Items"],
                value="Open Items",
                allow_custom_value=False
            )
            submit_btn = gr.Button("Evaluate and Generate PDF")
        with gr.Column(scale=3):
            score_output = gr.Number(label="Readiness Score (%)")
            progress_output = gr.HTML(label="Alert Indicator: Progress")
            gr.Markdown("Color-coded readiness: Red (<70%), Yellow (70-90%), Green (>90%)")
            status_output = gr.Textbox(label="Overall Status")
            with gr.Group():
                gr.Markdown("### Handover Summary")
                checklist_output = gr.Textbox(label="Checklist Summary")
                missing_output = gr.Textbox(label="Missing Items")
            open_punch_items_output = gr.Number(label="Open Punch Items (Debug)")
            pdf_output = gr.File(label="Download PDF Report", type="filepath", interactive=False)
            pdf_debug = gr.Textbox(label="PDF Debug Output")

    # Chain the evaluation, PDF generation, and Salesforce record creation
    submit_btn.click(
        fn=evaluate_readiness,
        inputs=[logs_input, qa_input, punch_input],
        outputs=[
            score_output, checklist_output, missing_output, status_output, progress_output,
            gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, status_output
        ]
    ).then(
        fn=generate_pdf,
        inputs=[score_output, checklist_output, missing_output, status_output, gr.State(), gr.State(), gr.State()],
        outputs=[pdf_output, pdf_debug]
    ).then(
        fn=create_salesforce_record,
        inputs=[
            score_output, checklist_output, missing_output, status_output,
            gr.State(), gr.State(), gr.State(), gr.State(), open_punch_items_output, pdf_output
        ],
        outputs=None
    )
    demo.launch()