<cfcomponent displayname="Whisper AI API" hint="REST API for Whisper AI patient data imports" output="true">
    
    <!--- Add CORS headers --->
    <cfheader name="Access-Control-Allow-Origin" value="*">
    <cfheader name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE">
    <cfheader name="Access-Control-Allow-Headers" value="Content-Type, Authorization">
    
    <!--- Set password encryption key (same as main application) --->
    <cfset this.passwordEncryptKey = "WTq8zYcZfaWVvMncigHqwQ==" />

    <!--- Dedicated API key for the public getAssessmentQuestionsByReason endpoint --->
    <!--- This key is separate from Application.enckey used by other codebases --->  
    <cfset this.assessmentLookupApiKey = "WhisperQL_9f3Kx2mP8vTd5Zn7Yw4Rb6Qe1Aj" />
    
    <!--- Email notification recipients --->
    <cfset this.notificationEmails = "velmurugan@myhomecarebiz.com" />
    
    <!--- Set datasource name (fallback if Application scope not available) --->
    <cfif not isDefined("Application.DataSrc")>
        <cfset Application.DataSrc = "hhapowerpath" />
    </cfif>
    
    <!--- Validate Basic Authentication --->
    <cffunction name="validateBasicAuth" access="private" returntype="struct" hint="Validate HTTP Basic Authentication">
        <cfargument name="appkey" required="yes" hint="appkey">
        <cfargument name="agency_id" required="no" default="0" hint="Agency ID for logging">
        <cfargument name="emp_id" required="no" default="0" hint="Employee ID for logging">
        <cfargument name="patient_id" required="no" default="0" hint="Patient ID for auth generation">
        
        <cfset var result = {
            "valid": false,
            "message": "Authentication required",
            "agency_db": "",
            "emp_id": 0
        } />
        
        <cftry>
           
            <cfset var agencyKey = trim("" & arguments.agency_id) />
            <cfset var patientKey = trim("" & arguments.patient_id) />
            <cfset var expectedKey = "" />

            <cfif len(agencyKey) AND len(patientKey)>
                <cfset expectedKey = agencyKey & "_" & patientKey />
            </cfif>
            <cfheader statuscode="200" statustext="success">
            <cfset result.valid = true />
            <cfset result.message = "Authentication successful" />
                
            <cfif len(expectedKey) AND appkey EQ expectedKey>
                <!--- Authentication successful --->
                <cfheader statuscode="200" statustext="success">
                <cfset result.valid = true />
                <cfset result.message = "Authentication successful" />
                
                <!--- Send success email notification 
                <cftry>
                    <cfmail to="#this.notificationEmails#" 
                            from="success@myhomecarebiz.com" 
                            subject="Bulk Import AI API - Authentication Success" 
                            type="html">
                        <h3 style="color: green;">Bulk Import AI - Authentication Successful</h3>
                        <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                        <p><strong>Agency ID:</strong> #arguments.agency_id#</p>
                        <p><strong>Employee ID:</strong> #arguments.emp_id#</p>
                        <p><strong>IP Address:</strong> #CGI.REMOTE_ADDR#</p>
                        <p><strong>User Agent:</strong> #CGI.HTTP_USER_AGENT#</p>
                        <p><strong>expectedKey:</strong> #expectedKey#</p>
                        <hr>
                        <p><em>This is an automated notification from the Bulk Import AI API</em></p>
                    </cfmail>
                    <cfcatch type="any">
                     </cfcatch>
                </cftry>--->
            <cfelse>   
                <cfheader statuscode="401" statustext="Unauthorized">
                <cfset result.message = "Invalid username or password" />
                
                <!--- Send authentication failure email notification --->
                <cftry>
                    <cfmail to="#this.notificationEmails#" 
                            from="error@myhomecarebiz.com" 
                            subject="Bulk Import AI API - AUTHENTICATION FAILED" 
                            type="html">
                        <h3 style="color: red;">Bulk Import AI - Authentication Failed!</h3>
                        <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                        <p><strong>Reason:</strong> Invalid API Key</p>
                        <p><strong>Agency ID:</strong> #arguments.agency_id#</p>
                        <p><strong>Employee ID:</strong> #arguments.emp_id#</p>
                        <p><strong>IP Address:</strong> #CGI.REMOTE_ADDR#</p>
                        <p><strong>User Agent:</strong> #CGI.HTTP_USER_AGENT#</p>
                        <p><strong>Provided Key (masked):</strong> #Left(arguments.appkey, 10)#***</p>
                        <hr>
                        <p style="color: red;"><strong>Security Alert:</strong> Unauthorized access attempt detected.</p>
                        <hr>
                        <p><em>This is an automated security notification from the Bulk Import AI API</em></p>
                    </cfmail>
                    <cfcatch type="any">
                        <!--- Silently fail if email notification fails --->
                    </cfcatch>
                </cftry>
                
                <cfreturn result />
            </cfif> 
            
            

            <cfcatch type="any">
                <cfheader statuscode="500" statustext="Internal Server Error">
                <cfset result.valid = false />
                <cfset result.message = "Authentication error: " & cfcatch.message />
                
                <!--- Send authentication error email notification --->
                <cftry>
                    <cfmail to="#this.notificationEmails#" 
                            from="error@myhomecarebiz.com" 
                            subject="Bulk Import AI API - AUTHENTICATION ERROR" 
                            type="html">
                        <h3 style="color: red;">Bulk Import AI - Authentication Error!</h3>
                        <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                        <p><strong>Agency ID:</strong> #arguments.agency_id#</p>
                        <p><strong>Employee ID:</strong> #arguments.emp_id#</p>
                        <p><strong>IP Address:</strong> #CGI.REMOTE_ADDR#</p>
                        <p><strong>User Agent:</strong> #CGI.HTTP_USER_AGENT#</p>
                        <hr>
                        <h4 style="color: red;">Error Details:</h4>
                        <p><strong>Error Type:</strong> #cfcatch.type#</p>
                        <p><strong>Error Message:</strong> #cfcatch.message#</p>
                        <p><strong>Error Detail:</strong> #cfcatch.detail#</p>
                        <cfif structKeyExists(cfcatch, "TagContext") and arrayLen(cfcatch.TagContext) gt 0>
                            <p><strong>File:</strong> #cfcatch.TagContext[1].template#</p>
                            <p><strong>Line:</strong> #cfcatch.TagContext[1].line#</p>
                        </cfif>
                        <hr>
                        <h4>Stack Trace:</h4>
                        <pre>#cfcatch.stacktrace#</pre>
                        <hr>
                        <p><em>This is an automated error notification from the Bulk Import AI API</em></p>
                    </cfmail>
                    <cfcatch>
                        <!--- Silently fail if error email notification fails --->
                    </cfcatch>
                </cftry>
            </cfcatch>
        </cftry>
         <cfreturn result />
    </cffunction>
    
    <!--- Helper function to parse date --->
    <cffunction name="parseDate" access="private" returntype="any" hint="Parse date string to valid date or null">
        <cfargument name="dateString" required="yes" hint="Date string to parse">
        
        <cfif len(trim(arguments.dateString)) eq 0>
            <cfreturn "" />
        </cfif>
        
        <cftry>
            <!--- Try to parse MM/DD/YYYY format --->
            <cfif find("/", arguments.dateString)>
                <cfset var dateParts = listToArray(arguments.dateString, "/") />
                <cfif arrayLen(dateParts) eq 3>
                    <cfset var month = dateParts[1] />
                    <cfset var day = dateParts[2] />
                    <cfset var year = dateParts[3] />
                    <cfreturn createDate(year, month, day) />
                </cfif>
            </cfif>
            
            <!--- Try standard date parsing --->
            <cfif isDate(arguments.dateString)>
                <cfreturn parseDateTime(arguments.dateString) />
            </cfif>
            
            <cfcatch type="any">
                <cfreturn "" />
            </cfcatch>
        </cftry>
        
        <cfreturn "" />
    </cffunction>

    <!--- Helper: Pull latest job payload from transcript jobs API --->
    <cffunction name="fetchTranscriptJobPayload" access="private" returntype="struct" hint="Fetch job payload from transcript jobs API">
        <cfargument name="jobID" type="string" required="yes" hint="Job identifier">
        <cfargument name="jobsApiUrl" type="string" required="no" default="https://transcriptapi.myhomecarebiz.com/jobs/" hint="Base Jobs API URL">

        <cfset var result = {
            "success": false,
            "statusCode": 0,
            "message": "",
            "data": {}
        } />
        <cfset var targetUrl = "" />
        <cfset var httpResponse = "" />
        <cfset var bodyText = "" />
        <cfset var parsedBody = {} />
        <cfset var baseUrl = trim(arguments.jobsApiUrl) />
        <cfset var cleanJobID = trim(arguments.jobID) />

        <cfif NOT len(cleanJobID)>
            <cfset result.message = "Job ID is required" />
            <cfreturn result />
        </cfif>

        <cfif NOT len(baseUrl)>
            <cfset baseUrl = "https://transcriptapi.myhomecarebiz.com/jobs/" />
        </cfif>

        <!--- Normalize URL and fetch by path parameter first --->
        <cfif right(baseUrl, 1) NEQ "/">
            <cfset baseUrl = baseUrl & "/" />
        </cfif>
        <cfset targetUrl = baseUrl & cleanJobID />

        <cftry>
            <cfhttp method="get" url="#targetUrl#" timeout="30" result="httpResponse" throwOnError="false" />

            <cfset result.statusCode = val(httpResponse.statusCode) />
            <cfset bodyText = structKeyExists(httpResponse, "FileContent") ? trim(httpResponse.FileContent) : "" />

            <cfif result.statusCode GTE 200 AND result.statusCode LT 300 AND len(bodyText) AND isJSON(bodyText)>
                <cfset parsedBody = deserializeJSON(bodyText) />
                <cfset result.success = true />
                <cfset result.message = "Jobs API fetch successful" />
                <cfset result.data = parsedBody />
                <cfreturn result />
            </cfif>

            <!--- Fallback: query parameter style if path style is unavailable --->
            <cfset targetUrl = baseUrl & "?job_id=" & urlEncodedFormat(cleanJobID) />
            <cfhttp method="get" url="#targetUrl#" timeout="30" result="httpResponse" throwOnError="false" />

            <cfset result.statusCode = val(httpResponse.statusCode) />
            <cfset bodyText = structKeyExists(httpResponse, "FileContent") ? trim(httpResponse.FileContent) : "" />

            <cfif result.statusCode GTE 200 AND result.statusCode LT 300 AND len(bodyText) AND isJSON(bodyText)>
                <cfset parsedBody = deserializeJSON(bodyText) />
                <cfset result.success = true />
                <cfset result.message = "Jobs API fetch successful" />
                <cfset result.data = parsedBody />
            <cfelse>
                <cfset result.message = "Jobs API fetch failed with HTTP " & result.statusCode />
            </cfif>

            <cfcatch type="any">
                <cfset result.success = false />
                <cfset result.message = "Jobs API error: " & cfcatch.message />
            </cfcatch>
        </cftry>

        <cfreturn result />
    </cffunction>
    
    <!--- Helper to safely attempt decryption using application key --->
    <cffunction name="maybeDecrypt" access="private" returntype="any" hint="Attempt to decrypt hex-encoded AES values; fall back to original on failure">
        <cfargument name="value" required="yes" hint="Value to decrypt">
        <cfargument name="expectNumeric" required="no" default="false" hint="Whether the decrypted result must be numeric">
        
        <cfset var decryptedValue = arguments.value />
        <cfset var trimmedValue = "" />
        
        <cfif NOT len(trim(arguments.value))>
            <cfreturn arguments.value />
        </cfif>
        
        <cfif NOT structKeyExists(Application, "enckey") OR NOT len(Application.enckey)>
            <cfreturn arguments.value />
        </cfif>
        
        <cfset trimmedValue = trim(arguments.value) />
        
        <cftry>
            <cfset decryptedValue = decrypt(trimmedValue, Application.enckey, "AES", "Hex") />
            
            <cfif arguments.expectNumeric AND NOT isNumeric(decryptedValue)>
                <cfreturn arguments.value />
            </cfif>
            
            <cfreturn decryptedValue />
            <cfcatch type="any">
                <cfreturn arguments.value />
            </cfcatch>
        </cftry>
    </cffunction>
    
   
    
    <!--- Helper: Send success email notification --->
    <cffunction name="sendSuccessEmail" access="private" returntype="void" hint="Send success email notification">
        <cfargument name="insertedPatientID" type="numeric" required="yes">
        <cfargument name="patientAction" type="string" required="yes">
        <cfargument name="insertedContactID" type="numeric" required="yes">
        <cfargument name="contactInserted" type="boolean" required="yes">
        <cfargument name="insertedPayerID" type="numeric" required="yes">
        <cfargument name="payerInserted" type="boolean" required="yes">
        <cfargument name="insertedAdmitID" type="numeric" required="yes">
        <cfargument name="admitInserted" type="boolean" required="yes">
        <cfargument name="medicationCount" type="numeric" required="yes">
        <cfargument name="problemCount" type="numeric" required="yes">
        <cfargument name="errors" type="array" required="yes">
        <cfargument name="agencyID" type="numeric" required="yes">
        <cfargument name="empID" type="numeric" required="yes">
        <cfargument name="patientID" type="numeric" required="yes">
        <cfargument name="assessmentID" type="numeric" required="yes">
        <cfargument name="websiteName" type="string" required="yes">
        <cfargument name="extractedJSONString" type="string" required="yes">
        <cfargument name="originalAgencyID" type="string" required="yes">
        <cfargument name="originalEmpID" type="string" required="yes">
        <cfargument name="originalPatientID" type="string" required="yes">
        <cfargument name="originalAssessmentID" type="string" required="yes">
        <cfargument name="originalAppKey" type="string" required="yes">
        <cfargument name="decryptedAgencyID" type="string" required="yes">
        <cfargument name="decryptedEmpID" type="string" required="yes">
        <cfargument name="decryptedPatientID" type="string" required="yes">
        <cfargument name="decryptedAssessmentID" type="string" required="yes">
        <cfargument name="decryptedAppKey" type="string" required="yes">
        <cfargument name="agency_db" type="string" required="yes">
        
        <cftry>
            <cfmail to="#this.notificationEmails#" 
                    from="success@myhomecarebiz.com" 
                    subject="Bulk Import AI API - Success: Patient AI ID #arguments.insertedPatientID#" 
                    type="html">
                <h3 style="color: green;">Bulk Import AI - Patient Data Imported Successfully!</h3>
                <p><strong>Agency ID:</strong> #arguments.agencyID#</p>
                <p><strong>Employee ID:</strong> #arguments.empID#</p>
                <p><strong>Patient ID:</strong> #arguments.patientID#</p>
                <p><strong>Assessment ID:</strong> #arguments.assessmentID#</p>
                <cfif len(trim(arguments.websiteName)) gt 0>
                    <p><strong>Website:</strong> #arguments.websiteName#</p>
                </cfif>
                <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                <hr>
                <h4>Encryption IDs and AppKeys:</h4>
                <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse; width: 100%;">
                    <tr style="background-color: ##f0f0f0;">
                        <th style="text-align: left;">Field</th>
                        <th style="text-align: left;">Original (Encrypted)</th>
                        <th style="text-align: left;">Decrypted Value</th>
                        <th style="text-align: left;">Was Encrypted</th>
                    </tr>
                    <tr>
                        <td><strong>Agency_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAgencyID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAgencyID)#</code></td>
                        <td>#iif(arguments.decryptedAgencyID NEQ arguments.originalAgencyID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Emp_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalEmpID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedEmpID)#</code></td>
                        <td>#iif(arguments.decryptedEmpID NEQ arguments.originalEmpID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Patient_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalPatientID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedPatientID)#</code></td>
                        <td>#iif(arguments.decryptedPatientID NEQ arguments.originalPatientID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Assessment_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAssessmentID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAssessmentID)#</code></td>
                        <td>#iif(arguments.decryptedAssessmentID NEQ arguments.originalAssessmentID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>appkey</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAppKey)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAppKey)#</code></td>
                        <td>#iif(len(arguments.decryptedAppKey) GT 0 AND arguments.decryptedAppKey NEQ arguments.originalAppKey, DE('Yes'), DE('No'))#</td>
                    </tr>
                </table>
                <cfif structKeyExists(Application, "enckey") AND len(Application.enckey)>
                    <p><strong>Encryption Key Available:</strong> Yes (Length: #len(Application.enckey)#)</p>
                <cfelse>
                    <p><strong>Encryption Key Available:</strong> No</p>
                </cfif>
                <hr>
                <h4>Import Results:</h4>
                <ul>
                    <li><strong>Patient AI ID:</strong> #arguments.insertedPatientID# (#arguments.patientAction#)</li>
                    <cfif arguments.insertedContactID gt 0>
                        <li><strong>Emergency Contact:</strong> #iif(arguments.contactInserted, DE('Inserted'), DE('Updated'))# (ID: #arguments.insertedContactID#)</li>
                    </cfif>
                    <cfif arguments.insertedPayerID gt 0>
                        <li><strong>Payer/Subscriber:</strong> #iif(arguments.payerInserted, DE('Inserted'), DE('Updated'))# (ID: #arguments.insertedPayerID#)</li>
                    </cfif>
                    <cfif arguments.insertedAdmitID gt 0>
                        <li><strong>Admission Record:</strong> #iif(arguments.admitInserted, DE('Inserted'), DE('Updated'))# (ID: #arguments.insertedAdmitID#)</li>
                    </cfif>
                    <cfif arguments.medicationCount gt 0>
                        <li><strong>Medications:</strong> #arguments.medicationCount# record(s) inserted</li>
                    </cfif>
                    <cfif arguments.problemCount gt 0>
                        <li><strong>Problems/Diagnoses:</strong> #arguments.problemCount# record(s) inserted/updated</li>
                    </cfif>
                </ul>
                <cfif len(trim(arguments.extractedJSONString)) gt 0>
                    <hr>
                    <h4>Extracted Data (JSON):</h4>
                    <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(arguments.extractedJSONString)#</pre>
                </cfif>
                <cfif arrayLen(arguments.errors) gt 0>
                    <hr>
                    <h4 style="color: orange;">Partial Errors:</h4>
                    <ul>
                        <cfloop array="#arguments.errors#" index="err">
                            <li style="color: orange;">#err#</li>
                        </cfloop>
                    </ul>
                </cfif>
                <hr>
                <h4>Debug Information:</h4>
                <ul>
                    <li><strong>Datasource:</strong> #Application.DataSrc#</li>
                    <li><strong>Agency Database:</strong> #arguments.agency_db#</li>
                    <li><strong>Transaction Status:</strong> Committed Successfully</li>
                </ul>
                <hr>
                <p><em>This is an automated notification from the Bulk Import AI API</em></p>
            </cfmail>
            <cfcatch type="any">
                <!--- Silently fail if email notification fails --->
            </cfcatch>
        </cftry>
    </cffunction>
    
    <!--- Helper: Send error email notification --->
    <cffunction name="sendErrorEmail" access="private" returntype="void" hint="Send error email notification">
        <cfargument name="error" type="any" required="yes">
        <cfargument name="agencyID" type="numeric" required="yes">
        <cfargument name="empID" type="numeric" required="yes">
        <cfargument name="patientID" type="numeric" required="yes">
        <cfargument name="assessmentID" type="numeric" required="yes">
        <cfargument name="websiteName" type="string" required="yes">
        <cfargument name="extractedJSONString" type="string" required="yes">
        <cfargument name="originalAgencyID" type="string" required="yes">
        <cfargument name="originalEmpID" type="string" required="yes">
        <cfargument name="originalPatientID" type="string" required="yes">
        <cfargument name="originalAssessmentID" type="string" required="yes">
        <cfargument name="originalAppKey" type="string" required="yes">
        <cfargument name="decryptedAgencyID" type="string" required="yes">
        <cfargument name="decryptedEmpID" type="string" required="yes">
        <cfargument name="decryptedPatientID" type="string" required="yes">
        <cfargument name="decryptedAssessmentID" type="string" required="yes">
        <cfargument name="decryptedAppKey" type="string" required="yes">
        <cfargument name="insertedPatientID" type="numeric" required="yes">
        <cfargument name="medicationCount" type="numeric" required="yes">
        <cfargument name="problemCount" type="numeric" required="yes">
        <cfargument name="errors" type="array" required="yes">
        <cfargument name="stepErrors" type="array" required="no" default="#ArrayNew(1)#">
        <cfargument name="currentStep" type="string" required="no" default="Unknown">
        
        <cfset var errorType = structKeyExists(arguments.error, "type") ? arguments.error.type : "Unknown" />
        <cfset var errorMessage = structKeyExists(arguments.error, "message") ? arguments.error.message : "Unknown error" />
        <cfset var errorDetail = structKeyExists(arguments.error, "detail") ? arguments.error.detail : "" />
        <cfset var errorStacktrace = structKeyExists(arguments.error, "stacktrace") ? arguments.error.stacktrace : "" />
        <cfset var errorTagContext = structKeyExists(arguments.error, "TagContext") ? arguments.error.TagContext : [] />
        
        <cftry>
            <cfmail to="#this.notificationEmails#" 
                    from="error@myhomecarebiz.com" 
                    subject="Bulk Import AI API - ERROR: Agency #arguments.agencyID#" 
                    type="html">
                <h3 style="color: red;">Bulk Import AI - Patient Data Import Failed!</h3>
                <p><strong>Agency ID:</strong> #arguments.agencyID#</p>
                <p><strong>Employee ID:</strong> #arguments.empID#</p>
                <p><strong>Patient ID:</strong> #arguments.patientID#</p>
                <p><strong>Assessment ID:</strong> #arguments.assessmentID#</p>
                <cfif len(trim(arguments.websiteName)) gt 0>
                    <p><strong>Website:</strong> #arguments.websiteName#</p>
                </cfif>
                <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                <hr>
                <h4>Error Information:</h4>
                <p><strong>Error Type:</strong> #errorType#</p>
                <p><strong>Error Message:</strong> #errorMessage#</p>
                <p><strong>Error Detail:</strong> #errorDetail#</p>
                <p><strong>Failed at Step:</strong> <span style="background-color: ##ffcccc; padding: 5px;">#htmlEditFormat(arguments.currentStep)#</span></p>
                <cfif arrayLen(errorTagContext) gt 0>
                    <p><strong>File:</strong> #errorTagContext[1].template#</p>
                    <p><strong>Line:</strong> #errorTagContext[1].line#</p>
                </cfif>
                <hr>
                <cfif arrayLen(arguments.stepErrors) gt 0>
                    <h4 style="color: ##cc0000;">Step-by-Step Error Details:</h4>
                    <ol>
                        <cfloop array="#arguments.stepErrors#" index="stepErr">
                            <li style="margin-bottom: 10px; padding: 8px; background-color: ##ffe6e6; border-left: 4px solid ##cc0000;">
                                #htmlEditFormat(stepErr)#
                            </li>
                        </cfloop>
                    </ol>
                </cfif>
                <hr>
                <h4>Encryption IDs and AppKeys:</h4>
                <table border="1" cellpadding="5" cellspacing="0" style="border-collapse: collapse; width: 100%;">
                    <tr style="background-color: ##f0f0f0;">
                        <th style="text-align: left;">Field</th>
                        <th style="text-align: left;">Original (Encrypted)</th>
                        <th style="text-align: left;">Decrypted Value</th>
                        <th style="text-align: left;">Was Encrypted</th>
                    </tr>
                    <tr>
                        <td><strong>Agency_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAgencyID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAgencyID)#</code></td>
                        <td>#iif(arguments.decryptedAgencyID NEQ arguments.originalAgencyID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Emp_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalEmpID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedEmpID)#</code></td>
                        <td>#iif(arguments.decryptedEmpID NEQ arguments.originalEmpID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Patient_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalPatientID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedPatientID)#</code></td>
                        <td>#iif(arguments.decryptedPatientID NEQ arguments.originalPatientID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>Assessment_ID</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAssessmentID)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAssessmentID)#</code></td>
                        <td>#iif(arguments.decryptedAssessmentID NEQ arguments.originalAssessmentID, DE('Yes'), DE('No'))#</td>
                    </tr>
                    <tr>
                        <td><strong>appkey</strong></td>
                        <td><code>#htmlEditFormat(arguments.originalAppKey)#</code></td>
                        <td><code>#htmlEditFormat(arguments.decryptedAppKey)#</code></td>
                        <td>#iif(len(arguments.decryptedAppKey) GT 0 AND arguments.decryptedAppKey NEQ arguments.originalAppKey, DE('Yes'), DE('No'))#</td>
                    </tr>
                </table>
                <cfif structKeyExists(Application, "enckey") AND len(Application.enckey)>
                    <p><strong>Encryption Key Available:</strong> Yes (Length: #len(Application.enckey)#)</p>
                <cfelse>
                    <p><strong>Encryption Key Available:</strong> No</p>
                </cfif>
                <hr>
                <h4>Partial Import Results (before error):</h4>
                <ul>
                    <li><strong>Patient AI ID:</strong> #arguments.insertedPatientID#</li>
                    <li><strong>Medications:</strong> #arguments.medicationCount# record(s)</li>
                    <li><strong>Problems/Diagnoses:</strong> #arguments.problemCount# record(s)</li>
                </ul>
                <cfif arrayLen(arguments.errors) gt 0>
                    <hr>
                    <h4>Previous Errors:</h4>
                    <ul>
                        <cfloop array="#arguments.errors#" index="err">
                            <li style="color: orange;">#err#</li>
                        </cfloop>
                    </ul>
                </cfif>
                <cfif len(trim(arguments.extractedJSONString)) gt 0>
                    <hr>
                    <h4>Extracted Data (JSON):</h4>
                    <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(arguments.extractedJSONString)#</pre>
                </cfif>
                <hr>
                <h4>Stack Trace:</h4>
                <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(errorStacktrace)#</pre>
                <hr>
                <p><em>This is an automated error notification from the Bulk Import AI API</em></p>
            </cfmail>
            <cfcatch type="any">
                <!--- Silently fail if email notification fails --->
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="sendAssessmentSuccessEmail" access="private" returntype="void" hint="Send assessment import success email notification">
        <cfargument name="agencyID" type="numeric" required="yes">
        <cfargument name="empID" type="numeric" required="yes">
        <cfargument name="patientID" type="numeric" required="yes">
        <cfargument name="assessmentID" type="numeric" required="yes">
        <cfargument name="websiteName" type="string" required="yes">
        <cfargument name="assessmentReason" type="string" required="yes">
        <cfargument name="mappedAnswerCount" type="numeric" required="yes">
        <cfargument name="skippedAnswers" type="array" required="yes">
        <cfargument name="stepErrors" type="array" required="yes">
        <cfargument name="jobID" type="string" required="no" default="">
        <cfargument name="extractedJSONString" type="string" required="no" default="">
        <cfargument name="agency_db" type="string" required="yes">

        <cftry>
            <cfmail to="#this.notificationEmails#"
                    from="success@myhomecarebiz.com"
                    subject="Whisper Assessment Import Success - Assessment #arguments.assessmentID#"
                    type="html">
                <h3 style="color: green;">Whisper Assessment Import Completed Successfully</h3>
                <p><strong>Agency ID:</strong> #arguments.agencyID#</p>
                <p><strong>Employee ID:</strong> #arguments.empID#</p>
                <p><strong>Patient ID:</strong> #arguments.patientID#</p>
                <p><strong>Assessment ID:</strong> #arguments.assessmentID#</p>
                <cfif len(trim(arguments.websiteName)) gt 0>
                    <p><strong>Website:</strong> #arguments.websiteName#</p>
                </cfif>
                <cfif len(trim(arguments.jobID)) gt 0>
                    <p><strong>Job ID:</strong> #arguments.jobID#</p>
                </cfif>
                <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                <hr>
                <h4>Assessment Update Results</h4>
                <ul>
                    <li><strong>Assessment Reason:</strong> #htmlEditFormat(arguments.assessmentReason)#</li>
                    <li><strong>Mapped F-columns Updated:</strong> #arguments.mappedAnswerCount#</li>
                    <li><strong>Skipped Answers:</strong> #arrayLen(arguments.skippedAnswers)#</li>
                    <li><strong>Transaction Status:</strong> Committed Successfully</li>
                    <li><strong>Datasource:</strong> #Application.DataSrc#</li>
                    <li><strong>Agency Database:</strong> #arguments.agency_db#</li>
                </ul>
                <cfif arrayLen(arguments.skippedAnswers) gt 0>
                    <hr>
                    <h4 style="color: ##cc7a00;">Skipped Answers</h4>
                    <ul>
                        <cfloop array="#arguments.skippedAnswers#" index="skippedAnswer">
                            <li>#htmlEditFormat(skippedAnswer)#</li>
                        </cfloop>
                    </ul>
                </cfif>
                <cfif arrayLen(arguments.stepErrors) gt 0>
                    <hr>
                    <h4 style="color: ##cc7a00;">Warnings Captured During Import</h4>
                    <ul>
                        <cfloop array="#arguments.stepErrors#" index="stepErr">
                            <li>#htmlEditFormat(stepErr)#</li>
                        </cfloop>
                    </ul>
                </cfif>
                <cfif len(trim(arguments.extractedJSONString)) gt 0>
                    <hr>
                    <h4>Extracted Data (JSON)</h4>
                    <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(arguments.extractedJSONString)#</pre>
                </cfif>
                <hr>
                <p><em>This is an automated notification from the Whisper Assessment Import API</em></p>
            </cfmail>
            <cfcatch type="any">
                <!--- Silently fail if success email notification fails --->
            </cfcatch>
        </cftry>
    </cffunction>

    <!--- Format a filled answer value for trace email display --->
    <cffunction name="formatTraceFilledAnswerValue" access="private" returntype="string" output="false">
        <cfargument name="qaItem" type="any" required="true" />
        <cfargument name="questionId" type="string" required="false" default="" />

        <cfset var parts = [] />
        <cfset var rawAnswer = "" />
        <cfset var normalizedTraceAnswer = "" />
        <cfset var isVital = reFindNoCase("^vital_", arguments.questionId) GT 0 />

        <cfif NOT isStruct(arguments.qaItem)>
            <cfreturn htmlEditFormat(trim("" & arguments.qaItem)) />
        </cfif>

        <cfif structKeyExists(arguments.qaItem, "answer") AND NOT isNull(arguments.qaItem.answer)>
            <cfset rawAnswer = arguments.qaItem.answer />
            <cfset normalizedTraceAnswer = normalizeWhisperFilledAnswerValue(rawAnswer) />
            <cfif len(normalizedTraceAnswer)>
                <cfset arrayAppend(parts, normalizedTraceAnswer) />
            </cfif>
        </cfif>

        <cfif isVital>
            <cfif structKeyExists(arguments.qaItem, "reading") AND len(trim("" & arguments.qaItem.reading))>
                <cfset arrayAppend(parts, "Reading: " & trim("" & arguments.qaItem.reading)) />
            </cfif>
            <cfif structKeyExists(arguments.qaItem, "reading_primary") AND len(trim("" & arguments.qaItem.reading_primary))>
                <cfset arrayAppend(parts, "Primary: " & trim("" & arguments.qaItem.reading_primary)) />
            </cfif>
            <cfif structKeyExists(arguments.qaItem, "reading_secondary") AND len(trim("" & arguments.qaItem.reading_secondary))>
                <cfset arrayAppend(parts, "Secondary: " & trim("" & arguments.qaItem.reading_secondary)) />
            </cfif>
            <cfif structKeyExists(arguments.qaItem, "descriptor") AND len(trim("" & arguments.qaItem.descriptor))>
                <cfset arrayAppend(parts, "Descriptor: " & trim("" & arguments.qaItem.descriptor)) />
            </cfif>
            <cfif structKeyExists(arguments.qaItem, "notes") AND len(trim("" & arguments.qaItem.notes))>
                <cfset arrayAppend(parts, "Notes: " & trim("" & arguments.qaItem.notes)) />
            </cfif>
        </cfif>

        <cfif arrayLen(parts) EQ 0>
            <cfreturn "" />
        </cfif>
        <cfreturn htmlEditFormat(arrayToList(parts, " | ")) />
    </cffunction>

    <!--- Build HTML table of questions and filled answers for import trace email --->
    <cffunction name="buildFilledAnswersTraceTableHtml" access="private" returntype="string" output="false">
        <cfargument name="filledAnswers" type="struct" required="true" />
        <cfargument name="assessmentReason" type="string" required="false" default="" />
        <cfargument name="lookup_db" type="string" required="false" default="hhapowerpath" />
        <cfargument name="agency_db" type="string" required="false" default="" />
        <cfargument name="answerLabelMap" type="struct" required="false" default="#structNew()#" />
        <cfargument name="answerUpdates" type="struct" required="false" default="#structNew()#" />
        <cfargument name="skippedAnswers" type="array" required="false" default="#arrayNew(1)#" />

        <cfset var html = "" />
        <cfset var questionMeta = {} />
        <cfset var questionIds = "" />
        <cfset var mapQid = "" />
        <cfset var qaItem = {} />
        <cfset var questionText = "" />
        <cfset var questionDescription = "" />
        <cfset var answerLabel = "" />
        <cfset var filledValue = "" />
        <cfset var importStatus = "" />
        <cfset var skippedReason = "" />
        <cfset var skippedPrefix = "" />
        <cfset var qMeta = {} />
        <cfset var getQuestions = "" />
        <cfset var vitalQuestions = [] />
        <cfset var vitalRow = {} />
        <cfset var woundQuestions = [] />
        <cfset var woundRow = {} />

        <cfif structCount(arguments.filledAnswers) EQ 0>
            <cfreturn "<p><em>No filled answers in payload.</em></p>" />
        </cfif>

        <cfif len(trim(arguments.assessmentReason))>
            <cfquery name="getQuestions" datasource="#Application.DataSrc#">
                SELECT ques_id,
                       answer_label,
                       questions_label,
                       CASE
                           WHEN questions_2 <> '' THEN
                               TRIM(CONCAT_WS(' ', NULLIF(TRIM(ques_identifier_2), ''), NULLIF(TRIM(questions_2), '')))
                           ELSE TRIM(CONCAT_WS(' ', NULLIF(TRIM(ques_identifier), ''), NULLIF(TRIM(questions), '')))
                       END AS question_description
                FROM #arguments.lookup_db#.assessment_questions_lookup
                WHERE assessment_reason = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(arguments.assessmentReason)#">
                  AND Deleted = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
            </cfquery>
            <cfloop query="getQuestions">
                <cfset questionMeta[trim("" & getQuestions.ques_id)] = {
                    questions = trim("" & getQuestions.question_description),
                    questions_label = trim("" & getQuestions.questions_label),
                    answer_label = trim("" & getQuestions.answer_label)
                } />
            </cfloop>
        </cfif>

        <cfif len(trim(arguments.agency_db))>
            <cfset vitalQuestions = buildVitalAssessmentQuestions(agency_db = arguments.agency_db, lookup_db = arguments.lookup_db) />
            <cfloop array="#vitalQuestions#" index="vitalRow">
                <cfset questionMeta[trim("" & vitalRow.ques_id)] = {
                    questions = len(trim("" & vitalRow.description)) ? trim("" & vitalRow.description) : trim("" & vitalRow.questions),
                    questions_label = "",
                    answer_label = trim("" & vitalRow.answer_label)
                } />
            </cfloop>
            <cfset woundQuestions = buildWoundAssessmentQuestions(lookup_db = arguments.lookup_db) />
            <cfif arrayLen(woundQuestions) GT 0 AND structKeyExists(woundQuestions[1], "fields")>
                <cfloop array="#woundQuestions[1].fields#" index="woundRow">
                    <cfset questionMeta[trim("" & woundRow.ques_id)] = {
                        questions = len(trim("" & woundRow.description)) ? trim("" & woundRow.description) : trim("" & woundRow.questions),
                        questions_label = "",
                        answer_label = trim("" & woundRow.answer_label)
                    } />
                </cfloop>
            </cfif>
        </cfif>

        <cfset questionIds = structKeyArray(arguments.filledAnswers) />
        <cftry>
            <cfset questionIds = listSort(arrayToList(questionIds), "text", "asc") />
            <cfcatch type="any">
                <!--- keep original order if sort unavailable --->
            </cfcatch>
        </cftry>

        <cfset html = html & '<h4>Questions and Filled Answers</h4>' />
        <cfif len(trim(arguments.assessmentReason))>
            <cfset html = html & '<p><strong>Assessment Reason:</strong> ' & htmlEditFormat(trim(arguments.assessmentReason)) & '</p>' />
        </cfif>
        <cfset html = html & '<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%;font-family:Arial,sans-serif;font-size:13px;">' />
        <cfset html = html & '<thead style="background-color:##f3f4f6;"><tr>' />
        <cfset html = html & '<th align="left">Question</th><th align="left">Answer</th><th align="left">Import Status</th>' />
        <cfset html = html & '</tr></thead><tbody>' />

        <cfloop list="#questionIds#" index="mapQid">
            <cfset qaItem = arguments.filledAnswers[mapQid] />
            <cfset questionText = "" />
            <cfset questionDescription = "" />
            <cfset answerLabel = "" />
            <cfset importStatus = "" />
            <cfset skippedReason = "" />

            <cfif structKeyExists(questionMeta, mapQid)>
                <cfset qMeta = questionMeta[mapQid] />
                <cfset questionText = qMeta.questions />
                <cfset questionDescription = structKeyExists(qMeta, "questions_label") ? qMeta.questions_label : "" />
                <cfif len(qMeta.answer_label)>
                    <cfset answerLabel = uCase(qMeta.answer_label) />
                </cfif>
            </cfif>

            <cfif isStruct(qaItem)>
                <cfif structKeyExists(qaItem, "question") AND len(trim("" & qaItem.question))>
                    <cfset questionText = trim("" & qaItem.question) />
                <cfelseif structKeyExists(qaItem, "questions") AND len(trim("" & qaItem.questions))>
                    <cfset questionText = trim("" & qaItem.questions) />
                </cfif>
                <cfif structKeyExists(qaItem, "description") AND len(trim("" & qaItem.description))>
                    <cfset questionDescription = trim("" & qaItem.description) />
                <cfelseif structKeyExists(qaItem, "questions_label") AND len(trim("" & qaItem.questions_label))>
                    <cfset questionDescription = trim("" & qaItem.questions_label) />
                </cfif>
            </cfif>

            <cfif isStruct(qaItem) AND structKeyExists(qaItem, "answer_label") AND len(trim("" & qaItem.answer_label))>
                <cfset answerLabel = uCase(trim("" & qaItem.answer_label)) />
            <cfelseif NOT len(answerLabel) AND structKeyExists(arguments.answerLabelMap, mapQid)>
                <cfset answerLabel = arguments.answerLabelMap[mapQid] />
            </cfif>

            <cfif NOT len(questionText) AND len(questionDescription)>
                <cfset questionText = questionDescription />
            </cfif>
            <cfif NOT len(questionText) AND reFindNoCase("^vital_", mapQid)>
                <cfset questionText = "Vital sign (Item " & reReplaceNoCase(mapQid, "^vital_", "", "all") & ")" />
            </cfif>
            <cfif NOT len(questionText) AND reFindNoCase("^wound_", mapQid)>
                <cfset questionText = reReplaceNoCase(mapQid, "^wound_", "", "all") />
            </cfif>
            <cfif NOT len(questionText)>
                <cfset questionText = mapQid />
            </cfif>
            <cfif NOT len(answerLabel) AND reFindNoCase("^vital_", mapQid)>
                <cfset answerLabel = uCase(reReplaceNoCase(mapQid, "^", "", "all")) />
            </cfif>

            <cfset filledValue = formatTraceFilledAnswerValue(qaItem = qaItem, questionId = mapQid) />

            <cfloop array="#arguments.skippedAnswers#" index="skippedPrefix">
                <cfif left(skippedPrefix, len(mapQid) + 1) EQ mapQid & ":">
                    <cfset skippedReason = listRest(skippedPrefix, ":") />
                    <cfbreak />
                </cfif>
            </cfloop>

            <cfif len(skippedReason)>
                <cfset importStatus = "Skipped: " & htmlEditFormat(skippedReason) />
            <cfelseif reFindNoCase("^vital_", mapQid) AND len(filledValue)>
                <cfset importStatus = "Vital → pWoundsVitals" />
            <cfelseif reFindNoCase("^wound_", mapQid) AND len(filledValue)>
                <cfset importStatus = "Wound → pWoundsVitals" />
            <cfelseif isAssessmentFilledAnswerKey(mapQid) AND structKeyExists(arguments.answerUpdates, uCase(trim(mapQid)))>
                <cfset importStatus = "Applied to " & htmlEditFormat(uCase(trim(mapQid))) />
            <cfelseif len(answerLabel) AND reFind("^F[0-9]+$", answerLabel) AND structKeyExists(arguments.answerUpdates, answerLabel)>
                <cfset importStatus = "Applied to " & htmlEditFormat(answerLabel) />
            <cfelseif len(filledValue)>
                <cfset importStatus = "Received (not mapped)" />
            <cfelse>
                <cfset importStatus = "Empty" />
            </cfif>

            <cfset html = html & '<tr>' />
            <cfset html = html & '<td>' & htmlEditFormat(questionText) & '</td>' />
            <cfset html = html & '<td>' & filledValue & '</td>' />
            <cfset html = html & '<td>' & importStatus & '</td>' />
            <cfset html = html & '</tr>' />
        </cfloop>

        <cfset html = html & '</tbody></table>' />
        <cfreturn html />
    </cffunction>

    <!--- Keep trace email bodies within mail server limits --->
    <cffunction name="truncateForTraceEmail" access="private" returntype="string" output="false">
        <cfargument name="text" type="string" required="true" />
        <cfargument name="maxLen" type="numeric" required="false" default="200000" />
        <cfif len(arguments.text) LTE arguments.maxLen>
            <cfreturn arguments.text />
        </cfif>
        <cfreturn left(arguments.text, arguments.maxLen) & chr(10) & chr(10) & "[... truncated for email; total length " & len(arguments.text) & " chars ...]" />
    </cffunction>

    <cffunction name="sendImportAssessmentTraceEmail" access="private" returntype="void" hint="Send importAssessmentData request, response, and SQL trace email">
        <cfargument name="agencyID" type="numeric" required="yes">
        <cfargument name="empID" type="numeric" required="yes">
        <cfargument name="patientID" type="numeric" required="yes">
        <cfargument name="assessmentID" type="numeric" required="yes">
        <cfargument name="websiteName" type="string" required="yes">
        <cfargument name="requestJSONString" type="string" required="yes">
        <cfargument name="responseJSONString" type="string" required="yes">
        <cfargument name="queryTrace" type="array" required="yes">
        <cfargument name="currentStep" type="string" required="yes">
        <cfargument name="stepErrors" type="array" required="no" default="#ArrayNew(1)#">
        <cfargument name="jobID" type="string" required="no" default="">
        <cfargument name="filledAnswers" type="struct" required="no" default="#structNew()#">
        <cfargument name="assessmentReason" type="string" required="no" default="">
        <cfargument name="lookup_db" type="string" required="no" default="hhapowerpath">
        <cfargument name="agency_db" type="string" required="no" default="">
        <cfargument name="answerLabelMap" type="struct" required="no" default="#structNew()#">
        <cfargument name="answerUpdates" type="struct" required="no" default="#structNew()#">
        <cfargument name="skippedAnswers" type="array" required="no" default="#arrayNew(1)#">
        <cfargument name="vitalImportResult" type="struct" required="no" default="#structNew()#">
        <cfargument name="woundImportResult" type="struct" required="no" default="#structNew()#">

        <cfset var traceEntry = "" />
        <cfset var questionsAnswersTableHtml = "" />
        <cfset var vitalsWoundsSummaryHtml = "" />
        <cfset var requestBody = "" />
        <cfset var responseBody = "" />
        <cfset var mailError = "" />
        <cfset var vitalUpdated = 0 />
        <cfset var vitalInserted = 0 />
        <cfset var vitalSkipped = 0 />
        <cfset var woundUpdated = 0 />
        <cfset var woundInserted = 0 />
        <cfset var woundSkipped = 0 />
        <cfset sendto_email = "velmurugan@myhomecarebiz.com,melissa@myhomecarebiz.com,jasmin.s@myhomecarebiz.com" />

        <cfset requestBody = truncateForTraceEmail(arguments.requestJSONString) />
        <cfset responseBody = truncateForTraceEmail(arguments.responseJSONString) />

        <cfif structKeyExists(arguments.vitalImportResult, "updated")>
            <cfset vitalUpdated = val(arguments.vitalImportResult.updated) />
        </cfif>
        <cfif structKeyExists(arguments.vitalImportResult, "inserted")>
            <cfset vitalInserted = val(arguments.vitalImportResult.inserted) />
        </cfif>
        <cfif structKeyExists(arguments.vitalImportResult, "skipped")>
            <cfset vitalSkipped = val(arguments.vitalImportResult.skipped) />
        </cfif>
        <cfif structKeyExists(arguments.woundImportResult, "updated")>
            <cfset woundUpdated = val(arguments.woundImportResult.updated) />
        </cfif>
        <cfif structKeyExists(arguments.woundImportResult, "inserted")>
            <cfset woundInserted = val(arguments.woundImportResult.inserted) />
        </cfif>
        <cfif structKeyExists(arguments.woundImportResult, "skipped")>
            <cfset woundSkipped = val(arguments.woundImportResult.skipped) />
        </cfif>

        <cfset vitalsWoundsSummaryHtml = '<h4>Vitals &amp; Wounds Import Response</h4>' />
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;font-family:Arial,sans-serif;font-size:13px;margin-bottom:12px;">' />
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '<thead style="background-color:##f3f4f6;"><tr><th align="left">Type</th><th align="left">Updated</th><th align="left">Inserted</th><th align="left">Skipped</th><th align="left">Notes</th></tr></thead><tbody>' />
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '<tr><td>Vitals (vital_* → pWoundsVitals)</td><td>' & vitalUpdated & '</td><td>' & vitalInserted & '</td><td>' & vitalSkipped & '</td><td>' />
        <cfif (vitalUpdated + vitalInserted + vitalSkipped) EQ 0>
            <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & 'No vital_* keys in payload (optional)' />
        <cfelse>
            <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & 'Applied' />
        </cfif>
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '</td></tr>' />
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '<tr><td>Wounds (wound_* → pWoundsVitals)</td><td>' & woundUpdated & '</td><td>' & woundInserted & '</td><td>' & woundSkipped & '</td><td>' />
        <cfif (woundUpdated + woundInserted + woundSkipped) EQ 0>
            <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & 'No wound_* keys in payload (optional)' />
        <cfelse>
            <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & 'Applied' />
        </cfif>
        <cfset vitalsWoundsSummaryHtml = vitalsWoundsSummaryHtml & '</td></tr></tbody></table>' />

        <cftry>
            <cfset questionsAnswersTableHtml = buildFilledAnswersTraceTableHtml(
                filledAnswers = arguments.filledAnswers,
                assessmentReason = arguments.assessmentReason,
                lookup_db = arguments.lookup_db,
                agency_db = arguments.agency_db,
                answerLabelMap = arguments.answerLabelMap,
                answerUpdates = arguments.answerUpdates,
                skippedAnswers = arguments.skippedAnswers
            ) />
            <cfcatch type="any">
                <cfset questionsAnswersTableHtml = "<p style=""color:##b45309;""><strong>Questions table could not be built:</strong> " & htmlEditFormat(cfcatch.message) & "</p>" />
            </cfcatch>
        </cftry>

        <cftry>
            <cfmail to="#sendto_email#,bairammarcelo03@gmail.com,ivan_buhain@yahoo.com,shereenalainah@gmail.com,maecalija15@gmail.com,crstldizon@gmail.com,malizane.cupcupin@gmail.com,janicesexon@yahoo.com"
                    from="success@myhomecarebiz.com"
                    subject="Whisper importAssessmentData Trace - Assessment #arguments.assessmentID#"
                    type="html">
                <h3 style="color: ##1d4ed8;">importAssessmentData Request / Response Trace</h3>
                <p><strong>Agency ID:</strong> #arguments.agencyID#</p>
                <p><strong>Employee ID:</strong> #arguments.empID#</p>
                <p><strong>Patient ID:</strong> #arguments.patientID#</p>
                <p><strong>Assessment ID:</strong> #arguments.assessmentID#</p>
                <cfif len(trim(arguments.websiteName)) gt 0>
                    <p><strong>Website:</strong> #htmlEditFormat(arguments.websiteName)#</p>
                </cfif>
                <cfif len(trim(arguments.jobID)) gt 0>
                    <p><strong>Job ID:</strong> #htmlEditFormat(arguments.jobID)#</p>
                </cfif>
                <p><strong>Timestamp:</strong> #DateFormat(Now(), 'mm/dd/yyyy')# #TimeFormat(Now(), 'HH:mm:ss')#</p>
                <p><strong>Last Step:</strong> #htmlEditFormat(arguments.currentStep)#</p>
                <hr>
                #vitalsWoundsSummaryHtml#
                <hr>
                #questionsAnswersTableHtml#
                <hr>
                <h4>Request Payload</h4>
                <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(requestBody)#</pre>
                <h4>Response Payload</h4>
                <pre style="white-space: pre-wrap; font-family: monospace;">#htmlEditFormat(responseBody)#</pre>
                <cfif arrayLen(arguments.stepErrors) gt 0>
                    <h4>Step Errors / Warnings</h4>
                    <ul>
                        <cfloop array="#arguments.stepErrors#" index="traceEntry">
                            <li>#htmlEditFormat(traceEntry)#</li>
                        </cfloop>
                    </ul>
                </cfif>
                <hr>
                <p><em>This trace email is sent only for importAssessmentData.</em></p>
            </cfmail>
            <cfcatch type="any">
                <cfset mailError = cfcatch.message & (len(trim(cfcatch.detail)) ? " | " & cfcatch.detail : "") />
                <cftry>
                    <cflog file="whisper_import_trace" type="error" text="Trace email failed for Assessment #arguments.assessmentID#: #mailError#" />
                    <cfcatch type="any"></cfcatch>
                </cftry>
                <cftry>
                    <cfmail to="#sendto_email#"
                            from="success@myhomecarebiz.com"
                            subject="Whisper importAssessmentData Trace FAILED - Assessment #arguments.assessmentID#"
                            type="html">
                        <p>The full trace email could not be sent.</p>
                        <p><strong>Error:</strong> #htmlEditFormat(mailError)#</p>
                        <p><strong>Assessment ID:</strong> #arguments.assessmentID#</p>
                        <p><strong>Last Step:</strong> #htmlEditFormat(arguments.currentStep)#</p>
                    </cfmail>
                    <cfcatch type="any"></cfcatch>
                </cftry>
            </cfcatch>
        </cftry>
    </cffunction>

    <!--- Helper: Log query for debugging --->
    <cffunction name="logQuery" access="private" returntype="void" hint="Log query for debugging purposes">
        <cfargument name="queryName" type="string" required="yes">
        <cfargument name="querySQL" type="string" required="yes">
        <cfargument name="queryResult" type="any" required="no">
        
        <!--- Store queries in request scope for email dumping --->
        <cfif not structKeyExists(request, "bulk_import_queries")>
            <cfset request.bulk_import_queries = [] />
        </cfif>
        
        <cfset var queryInfo = {
            name: arguments.queryName,
            sql: arguments.querySQL,
            timestamp: now(),
            hasResult: structKeyExists(arguments, "queryResult"),
            recordCount: structKeyExists(arguments, "queryResult") and isQuery(arguments.queryResult) ? arguments.queryResult.recordcount : 0
        } />
        
        <cfset arrayAppend(request.bulk_import_queries, queryInfo) />
    </cffunction>

    <!--- Helper: Validate key for public assessment lookup endpoint --->
    <!--- Uses this.assessmentLookupApiKey — a dedicated key, not Application.enckey --->  
    <cffunction name="isAssessmentLookupKeyValid" access="private" returntype="boolean" hint="Validate API key for assessment lookup endpoint">
        <cfargument name="api_key" type="string" required="yes" hint="API key">

        <!--- Use Application-scope override if configured, otherwise fall back to the dedicated component key --->
        <cfset var expectedKey = structKeyExists(Application, "whisperLookupApiKey") AND len(trim(Application.whisperLookupApiKey))
            ? trim(Application.whisperLookupApiKey)
            : trim(this.assessmentLookupApiKey) />

        <cfreturn len(trim(arguments.api_key)) GT 0 AND trim(arguments.api_key) EQ expectedKey />
    </cffunction>

    <!--- Title-case Vital_Sign from pVital_Param for pWoundsVitals.Description display --->
    <cffunction name="formatVitalSignLabel" access="private" returntype="string" output="false">
        <cfargument name="vital_sign" type="string" required="true" />
        <cfset var parts = listToArray(trim(arguments.vital_sign), " ") />
        <cfset var out = "" />
        <cfset var i = 0 />
        <cfset var part = "" />
        <cfloop from="1" to="#arrayLen(parts)#" index="i">
            <cfset part = trim(parts[i]) />
            <cfif len(part)>
                <!--- Right() requires count GT 0; single-char tokens cannot use right(part, 0) --->
                <cfif len(part) EQ 1>
                    <cfset part = uCase(part) />
                <cfelse>
                    <cfset part = uCase(left(part, 1)) & lCase(right(part, len(part) - 1)) />
                </cfif>
                <cfset out = len(out) ? out & " " & part : part />
            </cfif>
        </cfloop>
        <cfreturn out />
    </cffunction>

    <!--- Build { ANSWER: value } options for dropdown/radio/checkbox questions --->
    <cffunction name="buildAnswerOptionsFromArray" access="private" returntype="array" output="false">
        <cfargument name="values" type="array" required="true" />
        <cfset var answers = [] />
        <cfset var valItem = "" />
        <cfloop array="#arguments.values#" index="valItem">
            <cfset valItem = trim("" & valItem) />
            <cfif len(valItem)>
                <cfset arrayAppend(answers, { "ANSWER" = valItem }) />
            </cfif>
        </cfloop>
        <cfreturn answers />
    </cffunction>

    <cffunction name="buildAnswerOptionsFromList" access="private" returntype="array" output="false">
        <cfargument name="values" type="string" required="true" />
        <cfargument name="delimiter" type="string" required="false" default="," />
        <cfreturn buildAnswerOptionsFromArray(listToArray(arguments.values, arguments.delimiter)) />
    </cffunction>

    <!--- Standard assessment-question row for mobile / Whisper (matches lookup question shape) --->
    <cffunction name="makeFormAssessmentQuestion" access="private" returntype="struct" output="false">
        <cfargument name="quesId" type="string" required="true" />
        <cfargument name="label" type="string" required="true" />
        <cfargument name="answerLabel" type="string" required="true" hint="pWoundsVitals / save column name" />
        <cfargument name="questionsType" type="string" required="true" />
        <cfargument name="answers" type="array" required="false" default="#[]#" />
        <cfargument name="extra" type="struct" required="false" default="#{}#" />

        <cfset var qRow = {
            "ques_id" = arguments.quesId,
            "questions" = arguments.label,
            "description" = arguments.label,
            "answer_label" = arguments.answerLabel,
            "questions_type" = arguments.questionsType,
            "answers" = arguments.answers
        } />
        <cfset structAppend(qRow, arguments.extra, true) />
        <cfreturn qRow />
    </cffunction>

    <!--- Location dropdown values from woundsadd.cfm txtmap --->
    <cffunction name="getWoundLocationOptions" access="private" returntype="array" output="false">
        <cfreturn [
            "Anterior Left Shoulder", "Anterior Right Shoulder", "Anterior Right Upper Chest",
            "Anterior Right Above Knee", "Anterior Right Ankle", "Anterior Left Upper Chest",
            "Anterior Right Face", "Anterior Left Face", "Anterior Right Upper Arm", "Anterior Left Upper Arm",
            "Anterior Right Abdomen", "Anterior Midline", "Anterior Right Knee", "Anterior Left Abdomen",
            "Anterior Right Elbow/Forearm", "Anterior Left Elbow/Forearm", "Anterior Right Wrist/Hand",
            "Anterior Left Wrist/Hand", "Anterior Right Thigh", "Anterior Left Thigh", "Anterior Right Hip",
            "Anterior Left Hip", "Anterior Left Groin", "Anterior Left Above Knee", "Anterior Left Ankle",
            "Anterior Left Knee", "Anterior Right Below Knee", "Anterior Left Below Knee",
            "Anterior Right Foot", "Anterior Left Foot", "Posterior Right Head", "Posterior Left Neck",
            "Posterior Right Neck", "Posterior Midline", "Posterior Left Shoulder/Upper Back",
            "Posterior Right Shoulder/Upper Back", "Posterior Right Middle/Lower Back", "Posterior Sacrum",
            "Posterior Left Middle Back", "Posterior Left Upper Arm", "Posterior Right Shoulder/Upper Arm",
            "Posterior Left Elbow/Forearm", "Posterior Right Elbow/Forearm", "Posterior Right Wrist",
            "Posterior Right Hand", "Posterior Left Hand", "Posterior Left Thigh", "Posterior Right Thigh",
            "Posterior Left Calf", "Posterior Right Buttock", "Posterior Right Calf", "Posterior Left Buttock",
            "Posterior Left Knee", "Posterior Right Knee", "Posterior Left Ankle/Foot", "Posterior Right Ankle/Foot",
            "Posterior Left Heel", "Posterior Right Heel", "Other"
        ] />
    </cffunction>

    <!--- Wound form fields from woundsadd.cfm mapped to pWoundsVitals columns (pwoundvital.cfc addEditWounds) --->
    <cffunction name="buildWoundAssessmentQuestions" access="private" returntype="array" output="false"
        hint="Returns one wound container with fields[] sub-questions; ques_id prefix wound_ on each field">
        <cfargument name="lookup_db" type="string" required="true" />

        <cfset var woundFields = [] />
        <cfset var measureQuery = "" />
        <cfset var measurementAnswers = [] />
        <cfset var locAnswers = [] />
        <cfset var loc = "" />
        <cfset var measureUnit = "cm" />

        <cfquery name="measureQuery" datasource="#Application.DataSrc#">
            SELECT Lookup_1, Lookup_2, Measure
            FROM #arguments.lookup_db#.pValue_Lookup
            WHERE Item_ID = <cfqueryparam value="201" cfsqltype="cf_sql_integer">
            ORDER BY ID
        </cfquery>
        <cfloop query="measureQuery">
            <cfif measureQuery.currentRow EQ 1 AND len(trim(measureQuery.Measure))>
                <cfset measureUnit = trim(measureQuery.Measure) />
            </cfif>
            <cfset arrayAppend(measurementAnswers, {
                "ANSWER" = trim(measureQuery.Lookup_1),
                "ANSWER_2" = trim(measureQuery.Lookup_2)
            }) />
        </cfloop>

        <cfloop array="#getWoundLocationOptions()#" index="loc">
            <cfset arrayAppend(locAnswers, { "ANSWER" = loc }) />
        </cfloop>

        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_woundtype",
            label = "Wound Type",
            answerLabel = "woundtype",
            questionsType = "radio",
            answers = buildAnswerOptionsFromArray([
                "Abrasion", "Bite", "Burn", "Contusion", "Diabetic Ulcer", "Blister", "Laceration", "Nodule",
                "IV Access - Peripheral", "IV Access - Midline", "IV Access - Central", "Tracheostomy", "GI Ostomy",
                "GU Ostomy", "Open Wound", "Pressure Ulcer", "Puncture", "Observable Stasis Ulcer",
                "Non-Observable Stasis Ulcer", "Observable Surgical Wound", "Non-Observable Surgical Wound", "Other"
            ]),
            extra = { "form_field" = "WoundType" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_pressure_stage",
            label = "If pressure ulcer stage",
            answerLabel = "woundtype",
            questionsType = "dropdown",
            answers = buildAnswerOptionsFromArray([
                "1 - Intact skin with non-blanchable redness of a localized area usually over a bony prominence.",
                "2 - Partial thickness loss of dermis presenting as a shallow open ulcer with red pink wound bed.",
                "3 - Full thickness tissue loss. Subcutaneous fat may be visible but bone, tendon, or muscles not exposed.",
                "4 - Full thickness tissue loss with visible bone, tendon, or muscle. Slough or eschar may be present.",
                "5 - Unstageable due to non-removable dressing, slough or eschar.",
                "6 - Unstageable due to coverage of wound bed by slough and/or eschar.",
                "7 - Unstageable due to suspected deep tissue injury in evolution."
            ]),
            extra = { "form_field" = "selectstage", "show_when_woundtype" = "Pressure Ulcer" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_location",
            label = "Location",
            answerLabel = "Location",
            questionsType = "dropdown",
            answers = locAnswers,
            extra = { "form_field" = "txtmap" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_other_locations",
            label = "Other Location",
            answerLabel = "other_locations",
            questionsType = "text",
            extra = { "form_field" = "other_locations", "show_when_location" = "Other" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_healingstage",
            label = "Healing Stage",
            answerLabel = "healingstage",
            questionsType = "dropdown",
            answers = buildAnswerOptionsFromArray([
                "0 - Re-epithelialized or healed",
                "1 - Fully granulating",
                "2 - Early/partial granulation",
                "3 - Not healing",
                "NA - Not applicable or no observable wound/ulcer"
            ]),
            extra = { "form_field" = "healingStage" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_length",
            label = "Length",
            answerLabel = "Length",
            questionsType = "measurement_dropdown",
            answers = measurementAnswers,
            extra = { "form_field_primary" = "lengthmin", "form_field_secondary" = "lengthmin2", "measure" = measureUnit }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_width",
            label = "Width",
            answerLabel = "Width",
            questionsType = "measurement_dropdown",
            answers = measurementAnswers,
            extra = { "form_field_primary" = "widthmin", "form_field_secondary" = "widthmin2", "measure" = measureUnit }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_depth",
            label = "Depth",
            answerLabel = "Depth",
            questionsType = "measurement_dropdown",
            answers = measurementAnswers,
            extra = { "form_field_primary" = "depthmin", "form_field_secondary" = "depthmin2", "measure" = measureUnit }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_tunneling",
            label = "tunneling dep/drctn",
            answerLabel = "wnd_tunneling",
            questionsType = "text",
            extra = { "form_field" = "WoundTunneling" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_underm",
            label = "undermn dep/drctn",
            answerLabel = "wnd_underm",
            questionsType = "text",
            extra = { "form_field" = "WoundUnderm" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_gran",
            label = "% granulation",
            answerLabel = "wnd_gran",
            questionsType = "text",
            extra = { "form_field" = "WoundGran" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_epith",
            label = "% epithelial",
            answerLabel = "wnd_epith",
            questionsType = "text",
            extra = { "form_field" = "WoundEpith" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_eschar",
            label = "% eschar",
            answerLabel = "wnd_eschar",
            questionsType = "text",
            extra = { "form_field" = "WoundEschar" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_slough",
            label = "% slough",
            answerLabel = "wnd_slough",
            questionsType = "text",
            extra = { "form_field" = "WoundSlough" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_fib",
            label = "% fibrinous",
            answerLabel = "wnd_fib",
            questionsType = "text",
            extra = { "form_field" = "WoundFib" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_margin",
            label = "Wound Margin",
            answerLabel = "wnd_margin",
            questionsType = "checkbox",
            answers = buildAnswerOptionsFromList("regular,irregular,rolled,epithelialized,macerated,other"),
            extra = { "form_field" = "WoundMargin", "other_form_field" = "otherWoundMargin_textarea" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_exudate",
            label = "Wound Exudate",
            answerLabel = "wnd_exudate",
            questionsType = "checkbox",
            answers = buildAnswerOptionsFromList("none,scant,moderate,large clear,sanguinous,purulent,other"),
            extra = { "form_field" = "WoundExudate", "other_form_field" = "otherWoundExudate_textarea" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_odor",
            label = "Wound Odor",
            answerLabel = "wnd_odor",
            questionsType = "checkbox",
            answers = buildAnswerOptionsFromList("none,mild,foul,other"),
            extra = { "form_field" = "WoundOdor", "other_form_field" = "otherWoundOdor_textarea" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_wnd_pain",
            label = "Wound Pain",
            answerLabel = "wnd_pain",
            questionsType = "checkbox",
            answers = buildAnswerOptionsFromList("none,mild,strong,severe,other"),
            extra = { "form_field" = "WoundPain", "other_form_field" = "otherWoundPain_textarea" }
        )) />
        <cfset arrayAppend(woundFields, makeFormAssessmentQuestion(
            quesId = "wound_description",
            label = "Notes",
            answerLabel = "Description",
            questionsType = "text",
            extra = { "form_field" = "txtNotes" }
        )) />

        <!--- Single wound entry in questions list; all form fields live under fields[] --->
        <cfreturn [{
            "ques_id" = "wound",
            "questions" = "Wounds",
            "description" = "Wounds",
            "questions_type" = "wound",
            "answer_label" = "",
            "answers" = [],
            "fields" = woundFields,
            "field_count" = arrayLen(woundFields)
        }] />
    </cffunction>

    <!--- Build vital sign questions: Description from lookup, valid range from pVital_Param (no answers list) --->
    <cffunction name="buildVitalAssessmentQuestions" access="private" returntype="array" output="false"
        hint="Returns vital question structs; ques_id vital_{item_id}, answer_label Reading1, range param_lo/param_hi">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="lookup_db" type="string" required="true" />

        <cfset var vitalQuestions = [] />
        <cfset var paramRows = "" />
        <cfset var qRow = {} />
        <cfset var valueLow = "" />
        <cfset var valueHigh = "" />
        <cfset var description = "" />

        <cfif NOT len(trim(arguments.agency_db))>
            <cfreturn vitalQuestions />
        </cfif>

        <cfquery name="paramRows" datasource="#Application.DataSrc#">
            SELECT vp.Item_ID,
                   vp.Value_Low,
                   vp.Value_High,
                   MAX(vl.Description) AS Description,
                   MAX(vl.Measure) AS Measure,
                   MAX(vl.Required) AS Required
            FROM #arguments.agency_db#.pVital_Param vp
            INNER JOIN #arguments.lookup_db#.pValue_Lookup vl
              ON vl.Item_ID = vp.Item_ID
             AND vl.Description <> <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">
            WHERE vp.Status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
              AND vp.Vital_Sign <> <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">
            GROUP BY vp.Item_ID, vp.Value_Low, vp.Value_High
            ORDER BY vp.Item_ID
        </cfquery>

        <cfloop query="paramRows">
            <cfset valueLow = trim(paramRows.Value_Low) />
            <cfset valueHigh = trim(paramRows.Value_High) />
            <cfset description = trim(paramRows.Description) />
            <cfif NOT len(description)>
                <cfcontinue />
            </cfif>
            <cfset qRow = makeFormAssessmentQuestion(
                quesId = "vital_" & val(paramRows.Item_ID),
                label = description,
                answerLabel = "Reading1",
                questionsType = "numeric",
                answers = [],
                extra = {
                    "item_id" = val(paramRows.Item_ID),
                    "param_lo" = valueLow,
                    "param_hi" = valueHigh,
                    "measure" = trim(paramRows.Measure),
                    "required" = (trim(paramRows.Required) EQ "*"),
                    "notes_answer_label" = "Notes"
                }
            ) />
            <cfset arrayAppend(vitalQuestions, qRow) />
        </cfloop>

        <cfreturn vitalQuestions />
    </cffunction>

    <!--- Normalize filled answer payload value to string for DB storage --->
    <cffunction name="normalizeWhisperFilledAnswerValue" access="private" returntype="string" output="false">
        <cfargument name="rawAnswer" required="true" />
        <cfif isNull(arguments.rawAnswer)>
            <cfreturn "" />
        </cfif>
        <cfif isArray(arguments.rawAnswer)>
            <cfif arrayLen(arguments.rawAnswer) EQ 0>
                <cfreturn "" />
            </cfif>
            <cfreturn arrayToList(arguments.rawAnswer, ", ") />
        </cfif>
        <cfif isStruct(arguments.rawAnswer)>
            <cfreturn serializeJSON(arguments.rawAnswer) />
        </cfif>
        <cfreturn trim("" & arguments.rawAnswer) />
    </cffunction>

    <!--- True when filled answer key is an assessment F-column (F30, F242, etc.) --->
    <cffunction name="isAssessmentFilledAnswerKey" access="private" returntype="boolean" output="false">
        <cfargument name="key" type="string" required="true" />
        <cfreturn reFind("^F[0-9]+$", uCase(trim(arguments.key))) GT 0 />
    </cffunction>

    <cffunction name="isVitalFilledAnswerKey" access="private" returntype="boolean" output="false">
        <cfargument name="key" type="string" required="true" />
        <cfreturn reFindNoCase("^vital_", trim(arguments.key)) GT 0 />
    </cffunction>

    <cffunction name="isWoundFilledAnswerKey" access="private" returntype="boolean" output="false">
        <cfargument name="key" type="string" required="true" />
        <cfreturn reFindNoCase("^wound_", trim(arguments.key)) GT 0 />
    </cffunction>

    <!--- Normalize AI vital payload into pWoundsVitals fields --->
    <cffunction name="normalizeVitalFilledAnswer" access="private" returntype="struct" output="false">
        <cfargument name="qaItem" type="struct" required="true" />
        <cfargument name="itemId" type="numeric" required="true" />
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="lookup_db" type="string" required="false" default="hhapowerpath" />

        <cfset var out = {
            ok = false,
            item_id = arguments.itemId,
            description = "",
            reading1 = "",
            notes = "",
            pain_location = "",
            pain_duration = "",
            pain_exacerbate = "",
            pain_relief = "",
            pain_med_effectiveness = ""
        } />
        <cfset var rawAnswer = "" />
        <cfset var readingPrimary = "" />
        <cfset var readingSecondary = "" />
        <cfset var descriptor = "" />
        <cfset var freeNotes = "" />
        <cfset var qVitalParam = "" />

        <cfif structKeyExists(arguments.qaItem, "question") AND len(trim("" & arguments.qaItem.question))>
            <cfset out.description = formatVitalSignLabel(trim("" & arguments.qaItem.question)) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "description") AND len(trim("" & arguments.qaItem.description))>
            <cfset out.description = trim("" & arguments.qaItem.description) />
        </cfif>
        <cfif NOT len(out.description) AND structKeyExists(arguments.qaItem, "vital_sign") AND len(trim("" & arguments.qaItem.vital_sign))>
            <cfset out.description = formatVitalSignLabel(trim("" & arguments.qaItem.vital_sign)) />
        </cfif>
        <cfif NOT len(out.description) AND arguments.itemId GT 0>
            <cfquery name="qVitalDesc" datasource="#Application.DataSrc#">
                SELECT MAX(vl.Description) AS Description
                FROM #arguments.lookup_db#.pValue_Lookup vl
                WHERE vl.Item_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.itemId#">
                  AND vl.Description <> <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">
            </cfquery>
            <cfif qVitalDesc.recordCount GT 0 AND len(trim("" & qVitalDesc.Description))>
                <cfset out.description = trim("" & qVitalDesc.Description) />
            </cfif>
        </cfif>
        <cfif NOT len(out.description) AND arguments.itemId GT 0 AND len(trim(arguments.agency_db))>
            <cfquery name="qVitalParam" datasource="#Application.DataSrc#">
                SELECT Vital_Sign
                FROM #arguments.agency_db#.pVital_Param
                WHERE Item_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.itemId#">
                  AND Status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
                LIMIT 1
            </cfquery>
            <cfif qVitalParam.recordCount GT 0>
                <cfset out.description = formatVitalSignLabel(trim("" & qVitalParam.Vital_Sign)) />
            </cfif>
        </cfif>
        <cfif NOT len(out.description)>
            <cfreturn out />
        </cfif>

        <cfif structKeyExists(arguments.qaItem, "reading") AND len(trim("" & arguments.qaItem.reading))>
            <cfset out.reading1 = trim("" & arguments.qaItem.reading) />
        <cfelseif structKeyExists(arguments.qaItem, "reading_primary") OR structKeyExists(arguments.qaItem, "reading_secondary")>
            <cfset readingPrimary = structKeyExists(arguments.qaItem, "reading_primary") ? trim("" & arguments.qaItem.reading_primary) : "" />
            <cfset readingSecondary = structKeyExists(arguments.qaItem, "reading_secondary") ? trim("" & arguments.qaItem.reading_secondary) : "" />
            <cfif len(readingPrimary) AND len(readingSecondary)>
                <cfset out.reading1 = readingPrimary & "." & readingSecondary />
            <cfelseif len(readingPrimary)>
                <cfset out.reading1 = readingPrimary />
            <cfelseif len(readingSecondary)>
                <cfset out.reading1 = readingSecondary />
            </cfif>
        </cfif>

        <cfif NOT len(out.reading1) AND structKeyExists(arguments.qaItem, "answer")>
            <cfset out.reading1 = normalizeWhisperFilledAnswerValue(arguments.qaItem.answer) />
        </cfif>

        <cfif structKeyExists(arguments.qaItem, "descriptor")>
            <cfset descriptor = trim("" & arguments.qaItem.descriptor) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "notes")>
            <cfset freeNotes = trim("" & arguments.qaItem.notes) />
        </cfif>
        <cfif len(descriptor) AND len(freeNotes)>
            <cfset out.notes = descriptor & chr(10) & freeNotes />
        <cfelseif len(descriptor)>
            <cfset out.notes = descriptor />
        <cfelse>
            <cfset out.notes = freeNotes />
        </cfif>

        <cfif structKeyExists(arguments.qaItem, "pain_location")>
            <cfset out.pain_location = trim("" & arguments.qaItem.pain_location) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "pain_duration")>
            <cfset out.pain_duration = trim("" & arguments.qaItem.pain_duration) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "pain_exacerbate")>
            <cfset out.pain_exacerbate = trim("" & arguments.qaItem.pain_exacerbate) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "pain_relief")>
            <cfset out.pain_relief = trim("" & arguments.qaItem.pain_relief) />
        </cfif>
        <cfif structKeyExists(arguments.qaItem, "pain_med_effectiveness")>
            <cfset out.pain_med_effectiveness = trim("" & arguments.qaItem.pain_med_effectiveness) />
        </cfif>

        <cfif len(out.reading1) OR len(out.notes)>
            <cfset out.ok = true />
        </cfif>

        <cfreturn out />
    </cffunction>

    <!--- Persist Whisper vital answers into pWoundsVitals for Assmt_ID --->
    <cffunction name="applyFilledVitalsToPWoundsVitals" access="private" returntype="struct" output="false">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="assmt_id" type="numeric" required="true" />
        <cfargument name="filledAnswers" type="struct" required="true" />
        <cfargument name="created_by" type="numeric" required="true" />
        <cfargument name="skippedAnswers" type="array" required="true" />
        <cfargument name="queryTrace" type="array" required="true" />
        <cfargument name="lookup_db" type="string" required="false" default="hhapowerpath" />

        <cfset var result = { updated = 0, inserted = 0, skipped = 0 } />
        <cfset var mapQid = "" />
        <cfset var qaItem = {} />
        <cfset var itemId = 0 />
        <cfset var normalized = {} />
        <cfset var existingVital = "" />
        <cfset var paramRow = "" />
        <cfset var paramLo = "" />
        <cfset var paramHi = "" />

        <cfif arguments.assmt_id LTE 0>
            <cfreturn result />
        </cfif>

        <cfloop collection="#arguments.filledAnswers#" item="mapQid">
            <cfif NOT reFindNoCase("^vital_", mapQid)>
                <cfcontinue />
            </cfif>

            <cfset qaItem = arguments.filledAnswers[mapQid] />
            <cfif NOT isStruct(qaItem)>
                <cfset arrayAppend(arguments.skippedAnswers, mapQid & ":invalid_vital_payload") />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>

            <cfset itemId = val(reReplaceNoCase(mapQid, "^vital_", "", "all")) />
            <cfif itemId LTE 0 AND structKeyExists(qaItem, "item_id")>
                <cfset itemId = val(qaItem.item_id) />
            </cfif>
            <cfif itemId LTE 0>
                <cfset arrayAppend(arguments.skippedAnswers, mapQid & ":missing_item_id") />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>

            <cfset normalized = normalizeVitalFilledAnswer(
                qaItem = qaItem,
                itemId = itemId,
                agency_db = arguments.agency_db,
                lookup_db = arguments.lookup_db
            ) />
            <cfif NOT normalized.ok>
                <cfset arrayAppend(arguments.skippedAnswers, mapQid & ":empty_vital_reading_or_notes") />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>

            <cfquery name="paramRow" datasource="#Application.DataSrc#">
                SELECT Value_Low, Value_High
                FROM #arguments.agency_db#.pVital_Param
                WHERE Item_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#itemId#">
                  AND Status = <cfqueryparam value="0" cfsqltype="cf_sql_integer">
                LIMIT 1
            </cfquery>
            <cfset paramLo = paramRow.recordCount GT 0 ? trim(paramRow.Value_Low) : "" />
            <cfset paramHi = paramRow.recordCount GT 0 ? trim(paramRow.Value_High) : "" />

            <cfquery name="existingVital" datasource="#Application.DataSrc#">
                SELECT ID
                FROM #arguments.agency_db#.pWoundsVitals
                WHERE Assmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.assmt_id#">
                  AND Status = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
                  AND VitalOrWound = <cfqueryparam value="vital" cfsqltype="cf_sql_varchar">
                  AND Item_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#itemId#">
                LIMIT 1
            </cfquery>

            <cfif existingVital.recordCount GT 0>
                <cfquery datasource="#Application.DataSrc#">
                    UPDATE #arguments.agency_db#.pWoundsVitals
                    SET Description = <cfqueryparam value="#normalized.description#" cfsqltype="cf_sql_varchar">,
                        Reading1 = <cfqueryparam value="#normalized.reading1#" cfsqltype="cf_sql_varchar">,
                        Notes = <cfqueryparam value="#normalized.notes#" cfsqltype="cf_sql_varchar">,
                        Param_Lo = <cfqueryparam value="#paramLo#" cfsqltype="cf_sql_varchar" null="#NOT len(paramLo)#">,
                        Param_Hi = <cfqueryparam value="#paramHi#" cfsqltype="cf_sql_varchar" null="#NOT len(paramHi)#">,
                        pain_location = <cfqueryparam value="#normalized.pain_location#" cfsqltype="cf_sql_varchar">,
                        pain_duration = <cfqueryparam value="#normalized.pain_duration#" cfsqltype="cf_sql_varchar">,
                        pain_exacerbate = <cfqueryparam value="#normalized.pain_exacerbate#" cfsqltype="cf_sql_varchar">,
                        pain_relief = <cfqueryparam value="#normalized.pain_relief#" cfsqltype="cf_sql_varchar">,
                        pain_med_effectiveness = <cfqueryparam value="#normalized.pain_med_effectiveness#" cfsqltype="cf_sql_varchar">,
                        Date_Change = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                        Change_by = <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">
                    WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#existingVital.ID#">
                </cfquery>
                <cfset arrayAppend(arguments.queryTrace, "update_pWoundsVitals vital_" & itemId & " ID=" & existingVital.ID) />
                <cfset result.updated = result.updated + 1 />
            <cfelse>
                <cfquery datasource="#Application.DataSrc#">
                    INSERT INTO #arguments.agency_db#.pWoundsVitals (
                        Assmt_ID, Status, Pathway_ID, Item_ID, VitalOrWound, Description,
                        Param_Lo, Param_Hi, Notes, Reading1,
                        pain_location, pain_duration, pain_exacerbate, pain_relief, pain_med_effectiveness,
                        Date_Create, Created_by, Date_Change, Change_by
                    ) VALUES (
                        <cfqueryparam value="#arguments.assmt_id#" cfsqltype="cf_sql_integer">,
                        <cfqueryparam value="0" cfsqltype="cf_sql_integer">,
                        <cfqueryparam value="#itemId#" cfsqltype="cf_sql_integer">,
                        <cfqueryparam value="#itemId#" cfsqltype="cf_sql_integer">,
                        <cfqueryparam value="vital" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.description#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#paramLo#" cfsqltype="cf_sql_varchar" null="#NOT len(paramLo)#">,
                        <cfqueryparam value="#paramHi#" cfsqltype="cf_sql_varchar" null="#NOT len(paramHi)#">,
                        <cfqueryparam value="#normalized.notes#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.reading1#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.pain_location#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.pain_duration#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.pain_exacerbate#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.pain_relief#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#normalized.pain_med_effectiveness#" cfsqltype="cf_sql_varchar">,
                        <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                        <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">,
                        <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                        <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">
                    )
                </cfquery>
                <cfset arrayAppend(arguments.queryTrace, "insert_pWoundsVitals vital_" & itemId & " Assmt_ID=" & arguments.assmt_id) />
                <cfset result.inserted = result.inserted + 1 />
            </cfif>
        </cfloop>

        <cfreturn result />
    </cffunction>

    <!--- Map woundtype text to Pathway_ID (matches pwoundvital.cfc addEditWounds) --->
    <cffunction name="resolveWoundPathwayIdFromType" access="private" returntype="numeric" output="false">
        <cfargument name="woundtype" type="string" required="true" />
        <cfset var wt = trim(arguments.woundtype) />
        <cfif NOT len(wt)><cfreturn 32 /></cfif>
        <cfif findNoCase("1 -", wt)><cfreturn 30 /></cfif>
        <cfif findNoCase("2 -", wt)><cfreturn 34 /></cfif>
        <cfif findNoCase("3 -", wt)><cfreturn 171 /></cfif>
        <cfif findNoCase("4 -", wt)><cfreturn 172 /></cfif>
        <cfif findNoCase("Unstageable due to", wt)><cfreturn 173 /></cfif>
        <cfif findNoCase("Abrasion", wt)><cfreturn 174 /></cfif>
        <cfif findNoCase("Bite", wt)><cfreturn 175 /></cfif>
        <cfif findNoCase("Burn", wt)><cfreturn 176 /></cfif>
        <cfif findNoCase("Diabetic Ulcer", wt)><cfreturn 177 /></cfif>
        <cfif findNoCase("Laceration", wt)><cfreturn 178 /></cfif>
        <cfif findNoCase("Puncture", wt)><cfreturn 179 /></cfif>
        <cfif findNoCase("Blister", wt)><cfreturn 180 /></cfif>
        <cfif findNoCase("Open Wound", wt)><cfreturn 181 /></cfif>
        <cfif findNoCase("Surgical", wt)><cfreturn 38 /></cfif>
        <cfif findNoCase("Stasis", wt)><cfreturn 39 /></cfif>
        <cfif findNoCase("Tracheost", wt)><cfreturn 1 /></cfif>
        <cfif findNoCase("GI Ost", wt)><cfreturn 52 /></cfif>
        <cfif findNoCase("GU Ost", wt)><cfreturn 58 /></cfif>
        <cfreturn 32 />
    </cffunction>

    <!--- Normalize pWoundsVitals column name from wound_* filled answer --->
    <cffunction name="resolveWoundColumnFromFilledAnswer" access="private" returntype="string" output="false">
        <cfargument name="mapQid" type="string" required="true" />
        <cfargument name="qaItem" type="struct" required="true" />
        <cfset var col = "" />
        <cfif structKeyExists(arguments.qaItem, "answer_label") AND len(trim("" & arguments.qaItem.answer_label))>
            <cfset col = trim("" & arguments.qaItem.answer_label) />
        </cfif>
        <cfif len(col)><cfreturn col /></cfif>
        <cfreturn reReplaceNoCase(trim(arguments.mapQid), "^wound_", "", "all") />
    </cffunction>

    <!--- Aggregate wound_* filled answers and upsert one pWoundsVitals wound row --->
    <cffunction name="applyFilledWoundsToPWoundsVitals" access="private" returntype="struct" output="false">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="assmt_id" type="numeric" required="true" />
        <cfargument name="filledAnswers" type="struct" required="true" />
        <cfargument name="created_by" type="numeric" required="true" />
        <cfargument name="skippedAnswers" type="array" required="true" />
        <cfargument name="queryTrace" type="array" required="true" />

        <cfset var result = { updated = 0, inserted = 0, skipped = 0 } />
        <cfset var mapQid = "" />
        <cfset var qaItem = {} />
        <cfset var colName = "" />
        <cfset var colValue = "" />
        <cfset var woundCols = {} />
        <cfset var hasData = false />
        <cfset var existingWound = "" />
        <cfset var getpwound = "" />
        <cfset var wounditemid = 201 />
        <cfset var woundpathid = 32 />
        <cfset var lengthVal = 0 />
        <cfset var widthVal = 0 />
        <cfset var depthVal = 0 />
        <cfset var readings = 0 />

        <cfif arguments.assmt_id LTE 0>
            <cfreturn result />
        </cfif>

        <cfloop collection="#arguments.filledAnswers#" item="mapQid">
            <cfif NOT isWoundFilledAnswerKey(mapQid)>
                <cfcontinue />
            </cfif>
            <cfset qaItem = arguments.filledAnswers[mapQid] />
            <cfif NOT isStruct(qaItem)>
                <cfset arrayAppend(arguments.skippedAnswers, mapQid & ":invalid_wound_payload") />
                <cfcontinue />
            </cfif>
            <cfif NOT structKeyExists(qaItem, "answer")>
                <cfcontinue />
            </cfif>
            <cfset colValue = normalizeWhisperFilledAnswerValue(qaItem.answer) />
            <cfif NOT len(colValue)>
                <cfcontinue />
            </cfif>
            <cfset colName = resolveWoundColumnFromFilledAnswer(mapQid = mapQid, qaItem = qaItem) />
            <cfif NOT len(colName)>
                <cfcontinue />
            </cfif>
            <cfset woundCols[colName] = colValue />
            <cfset hasData = true />
        </cfloop>

        <cfif NOT hasData>
            <cfreturn result />
        </cfif>

        <cfset lengthVal = structKeyExists(woundCols, "Length") ? val(woundCols.Length) : 0 />
        <cfset widthVal = structKeyExists(woundCols, "Width") ? val(woundCols.Width) : 0 />
        <cfset depthVal = structKeyExists(woundCols, "Depth") ? val(woundCols.Depth) : 0 />
        <cfset readings = lengthVal + widthVal + depthVal />

        <cfif structKeyExists(woundCols, "woundtype") AND len(trim(woundCols.woundtype))>
            <cfset woundpathid = resolveWoundPathwayIdFromType(woundCols.woundtype) />
        </cfif>

        <cfquery name="existingWound" datasource="#Application.DataSrc#">
            SELECT ID, Item_ID, Pathway_ID
            FROM #arguments.agency_db#.pWoundsVitals
            WHERE Assmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.assmt_id#">
              AND Status = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
              AND VitalOrWound = <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">
            ORDER BY Pathway_ID DESC
            LIMIT 1
        </cfquery>

        <cfif existingWound.recordCount GT 0>
            <cfquery datasource="#Application.DataSrc#">
                UPDATE #arguments.agency_db#.pWoundsVitals
                SET
                    <cfif structKeyExists(woundCols, "Description")>Description = <cfqueryparam value="#woundCols.Description#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "Location")>Location = <cfqueryparam value="#woundCols.Location#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "other_locations")>other_locations = <cfqueryparam value="#woundCols.other_locations#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "Length")>Length = <cfqueryparam value="#woundCols.Length#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "Width")>Width = <cfqueryparam value="#woundCols.Width#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "Depth")>Depth = <cfqueryparam value="#woundCols.Depth#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "healingstage")>healingstage = <cfqueryparam value="#woundCols.healingstage#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "woundtype")>woundtype = <cfqueryparam value="#woundCols.woundtype#" cfsqltype="cf_sql_varchar">, Pathway_ID = <cfqueryparam value="#woundpathid#" cfsqltype="cf_sql_integer">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_tunneling")>wnd_tunneling = <cfqueryparam value="#woundCols.wnd_tunneling#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_underm")>wnd_underm = <cfqueryparam value="#woundCols.wnd_underm#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_gran")>wnd_gran = <cfqueryparam value="#woundCols.wnd_gran#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_epith")>wnd_epith = <cfqueryparam value="#woundCols.wnd_epith#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_eschar")>wnd_eschar = <cfqueryparam value="#woundCols.wnd_eschar#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_slough")>wnd_slough = <cfqueryparam value="#woundCols.wnd_slough#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_fib")>wnd_fib = <cfqueryparam value="#woundCols.wnd_fib#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_margin")>wnd_margin = <cfqueryparam value="#woundCols.wnd_margin#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_exudate")>wnd_exudate = <cfqueryparam value="#woundCols.wnd_exudate#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_odor")>wnd_odor = <cfqueryparam value="#woundCols.wnd_odor#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "wnd_pain")>wnd_pain = <cfqueryparam value="#woundCols.wnd_pain#" cfsqltype="cf_sql_varchar">,</cfif>
                    <cfif structKeyExists(woundCols, "Length") OR structKeyExists(woundCols, "Width") OR structKeyExists(woundCols, "Depth")>
                        Param_Hi = <cfqueryparam value="#readings#" cfsqltype="cf_sql_varchar">,
                        Reading1 = <cfqueryparam value="#readings#" cfsqltype="cf_sql_varchar">,
                    </cfif>
                    Date_Change = <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                    Change_by = <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">
                WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#existingWound.ID#">
            </cfquery>
            <cfset arrayAppend(arguments.queryTrace, "update_pWoundsVitals wound ID=" & existingWound.ID) />
            <cfset result.updated = 1 />
        <cfelse>
            <cfquery name="getpwound" datasource="#Application.DataSrc#">
                SELECT Item_ID
                FROM #arguments.agency_db#.pWoundsVitals
                WHERE VitalOrWound = <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">
                  AND Assmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.assmt_id#">
                  AND Status = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
                ORDER BY Pathway_ID DESC
            </cfquery>
            <cfif getpwound.recordCount GT 0>
                <cfset wounditemid = val(getpwound.Item_ID) + 1 />
            </cfif>

            <cfquery datasource="#Application.DataSrc#">
                INSERT INTO #arguments.agency_db#.pWoundsVitals (
                    Assmt_ID, Status, Pathway_ID, Item_ID, VitalOrWound,
                    Description, Location, other_locations, Length, Width, Depth,
                    Param_Hi, Reading1, Date_Create, Created_by, Date_Change, Change_by,
                    healingstage, frompgnote, woundtype,
                    wnd_margin, wnd_exudate, wnd_odor, wnd_pain,
                    wnd_tunneling, wnd_underm, wnd_gran, wnd_epith, wnd_eschar, wnd_slough, wnd_fib
                ) VALUES (
                    <cfqueryparam value="#arguments.assmt_id#" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="0" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="#woundpathid#" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="#wounditemid#" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="wound" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'Description') ? woundCols.Description : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'Location') ? woundCols.Location : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'other_locations') ? woundCols.other_locations : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'Length') ? woundCols.Length : '0'#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'Width') ? woundCols.Width : '0'#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'Depth') ? woundCols.Depth : '0'#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#readings#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#readings#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                    <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="#now()#" cfsqltype="cf_sql_timestamp">,
                    <cfqueryparam value="#arguments.created_by#" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'healingstage') ? woundCols.healingstage : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="0" cfsqltype="cf_sql_integer">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'woundtype') ? woundCols.woundtype : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_margin') ? woundCols.wnd_margin : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_exudate') ? woundCols.wnd_exudate : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_odor') ? woundCols.wnd_odor : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_pain') ? woundCols.wnd_pain : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_tunneling') ? woundCols.wnd_tunneling : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_underm') ? woundCols.wnd_underm : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_gran') ? woundCols.wnd_gran : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_epith') ? woundCols.wnd_epith : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_eschar') ? woundCols.wnd_eschar : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_slough') ? woundCols.wnd_slough : ''#" cfsqltype="cf_sql_varchar">,
                    <cfqueryparam value="#structKeyExists(woundCols, 'wnd_fib') ? woundCols.wnd_fib : ''#" cfsqltype="cf_sql_varchar">
                )
            </cfquery>
            <cfset arrayAppend(arguments.queryTrace, "insert_pWoundsVitals wound Assmt_ID=" & arguments.assmt_id) />
            <cfset result.inserted = 1 />
        </cfif>

        <cfreturn result />
    </cffunction>
    <cffunction name="getAssessmentQuestionsByReason" access="remote" returnformat="JSON" hint="Return assessment questions and answers filtered by assessment_reason">
        <cfargument name="assessment_reason" type="string" required="yes" hint="Assessment reason filter">
        <cfargument name="api_key" type="string" required="yes" hint="Public endpoint API key">
        <cfargument name="agency_id" type="string" required="no" default="" hint="Agency ID — required for vital questions from pVital_Param">

        <cfset var response = {} />
        <cfset var result = [] />
        <cfset var vitalQuestions = [] />
        <cfset var woundQuestions = [] />
        <cfset var woundFieldCount = 0 />
        <cfset var answersByQid = {} />
        <cfset var qid = "" />
        <cfset var qRow = {} />
        <cfset var aRow = {} />
        <cfset var col = "" />
        <cfset var val = "" />
        <cfset var lookup_db = "hhapowerpath" />
        <cfset var agency_db = "" />

        <cftry>
            <cfif NOT isAssessmentLookupKeyValid(arguments.api_key)>
                <cfheader statuscode="401" statustext="Unauthorized">
                <cfset response = {
                    "success": false,
                    "message": "Invalid API key",
                    "assessment_reason": trim(arguments.assessment_reason),
                    "questions": [],
                    "count": 0,
                    "data": {
                        "assessment_reason": trim(arguments.assessment_reason),
                        "questions": [],
                        "count": 0
                    }
                } />
                <cfreturn serializeJSON(response) />
            </cfif>

            <cfif structKeyExists(Request, "prefix_db_lookup") AND len(trim(Request.prefix_db_lookup))>
                <cfset lookup_db = trim(Request.prefix_db_lookup) />
            </cfif>

            <cfif val(arguments.agency_id) GT 0>
                <cfset agency_db = "agency_" & val(arguments.agency_id) />
            </cfif>

            <cfquery name="get_questions" datasource="#Application.DataSrc#">
                SELECT  ques_id,
                CASE
                    WHEN questions_2 <> '' THEN
                        TRIM(CONCAT_WS(' ', NULLIF(TRIM(ques_identifier_2), ''), NULLIF(TRIM(questions_2), '')))
                    ELSE questions
                END AS questions,
                    ques_identifier_2 AS questions_new, quescolor,title_2 as title, 
                 questions_label, answer_label, questions_type, 
                  new_order_id AS order_id , assessment_reason, new_wizard AS wizard,
                  ques_identifier_2 AS ques_identifier
                FROM #lookup_db#.assessment_questions_lookup
                WHERE assessment_reason = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(arguments.assessment_reason)#">
                  AND Deleted = 0
                ORDER BY order_id, wizard, answer_label
            </cfquery>

            <cfquery name="get_answers" datasource="#Application.DataSrc#">
                                SELECT
                                        val_id AS VAL_ID,
                                        answer_2 AS ANSWER,
                                        text_labels AS TEXT_LABELS,
                                        assessment_reason AS ASSESSMENT_REASON,
                                        ans_id AS ANS_ID,
                                        item_id AS ITEM_ID,
                                        answer_2 AS ANSWER_2,
                                        question_id AS QUESTION_ID,
                                        answer_type AS ANSWER_TYPE
                FROM #lookup_db#.assessment_answers_lookup
                WHERE assessment_reason = <cfqueryparam cfsqltype="cf_sql_varchar" value="#trim(arguments.assessment_reason)#">
                  AND Deleted = 0
                ORDER BY question_id, srt_id, answer_label
            </cfquery>

            <cfloop query="get_answers">
                <cfset qid = get_answers.question_id />
                <cfif NOT structKeyExists(answersByQid, qid)>
                    <cfset answersByQid[qid] = [] />
                </cfif>

                <cfset aRow = {} />
                <cfloop list="#get_answers.columnList#" index="col">
                    <cfset val = get_answers[col][get_answers.currentRow] />
                    <cfif isBinary(val)>
                        <cfcontinue>
                    </cfif>
                    <cfset aRow[col] = val />
                </cfloop>

                <cfset arrayAppend(answersByQid[qid], aRow) />
            </cfloop>

            <cfloop query="get_questions">
                <cfset qRow = {} />
                <cfloop list="#get_questions.columnList#" index="col">
                    <cfset val = get_questions[col][get_questions.currentRow] />
                    <cfif isBinary(val)>
                        <cfcontinue>
                    </cfif>
                    <cfset qRow[col] = val />
                </cfloop>

                <cfset qid = get_questions.ques_id />
                <cfif structKeyExists(answersByQid, qid)>
                    <cfset qRow["answers"] = answersByQid[qid] />
                <cfelse>
                    <cfset qRow["answers"] = [] />
                </cfif>

                <cfset arrayAppend(result, qRow) />
            </cfloop>

            <cfset vitalQuestions = buildVitalAssessmentQuestions(agency_db = agency_db, lookup_db = lookup_db) />
            <cfloop array="#vitalQuestions#" index="qRow">
                <cfset arrayAppend(result, qRow) />
            </cfloop>

            <cfset woundQuestions = buildWoundAssessmentQuestions(lookup_db = lookup_db) />
            <cfloop array="#woundQuestions#" index="qRow">
                <cfset arrayAppend(result, qRow) />
                <cfif structKeyExists(qRow, "field_count")>
                    <cfset woundFieldCount = val(qRow.field_count) />
                </cfif>
            </cfloop>

            <cfset response = {
                "success": true,
                "message": "Questions loaded successfully",
                "assessment_reason": trim(arguments.assessment_reason),
                "questions": result,
                "count": arrayLen(result),
                "vital_question_count": arrayLen(vitalQuestions),
                "wound_question_count": arrayLen(woundQuestions),
                "wound_field_count": woundFieldCount,
                "data": {
                    "assessment_reason": trim(arguments.assessment_reason),
                    "questions": result,
                    "count": arrayLen(result),
                    "vital_question_count": arrayLen(vitalQuestions),
                    "wound_question_count": arrayLen(woundQuestions),
                    "wound_field_count": woundFieldCount
                }
            } />

            <cfcatch type="any">
                <cfheader statuscode="500" statustext="Internal Server Error">
                <cfset response = {
                    "success": false,
                    "message": "Failed to load assessment questions",
                    "error": cfcatch.message,
                    "detail": cfcatch.detail,
                    "assessment_reason": trim(arguments.assessment_reason),
                    "questions": [],
                    "count": 0,
                    "data": {
                        "assessment_reason": trim(arguments.assessment_reason),
                        "questions": [],
                        "count": 0
                    }
                } />
            </cfcatch>
        </cftry>

        <cfreturn serializeJSON(response) />
    </cffunction>

    <!--- Assessment Update Data --->
    <!--- After AI import: ensure Review_Status 1 if upload left it at 0 (legacy rows) --->
    <cffunction name="ensureWhisperFilesReviewColumns" access="private" returntype="void" output="false">
        <cfargument name="agency_db" type="string" required="true" />
        <cfset var existingCols = "" />
        <cfset var existingIndexes = "" />
        <cftry>
            <cfquery name="qRevCols" datasource="#Application.DataSrc#">
                SELECT COLUMN_NAME
                FROM INFORMATION_SCHEMA.COLUMNS
                WHERE TABLE_SCHEMA = <cfqueryparam value="#arguments.agency_db#" cfsqltype="cf_sql_varchar">
                  AND TABLE_NAME = <cfqueryparam value="whisper_files" cfsqltype="cf_sql_varchar">
                  AND COLUMN_NAME IN ('Review_Status', 'Upload_Source')
            </cfquery>
            <cfset existingCols = valueList(qRevCols.COLUMN_NAME) />
            <cfif NOT listFindNoCase(existingCols, 'Review_Status')>
                <cfquery datasource="#Application.DataSrc#">
                    ALTER TABLE #arguments.agency_db#.whisper_files
                    ADD COLUMN Review_Status TINYINT NOT NULL DEFAULT 0
                </cfquery>
            </cfif>
            <cfif NOT listFindNoCase(existingCols, 'Upload_Source')>
                <cfquery datasource="#Application.DataSrc#">
                    ALTER TABLE #arguments.agency_db#.whisper_files
                    ADD COLUMN Upload_Source VARCHAR(20) NOT NULL DEFAULT ''
                </cfquery>
            </cfif>
            <cfquery name="qRevIdx" datasource="#Application.DataSrc#">
                SELECT INDEX_NAME
                FROM INFORMATION_SCHEMA.STATISTICS
                WHERE TABLE_SCHEMA = <cfqueryparam value="#arguments.agency_db#" cfsqltype="cf_sql_varchar">
                  AND TABLE_NAME = <cfqueryparam value="whisper_files" cfsqltype="cf_sql_varchar">
                  AND INDEX_NAME IN ('idx_whisper_review_status', 'idx_whisper_assmt_review')
                GROUP BY INDEX_NAME
            </cfquery>
            <cfset existingIndexes = valueList(qRevIdx.INDEX_NAME) />
            <cfif NOT listFindNoCase(existingIndexes, 'idx_whisper_review_status')>
                <cfquery datasource="#Application.DataSrc#">
                    ALTER TABLE #arguments.agency_db#.whisper_files
                    ADD INDEX idx_whisper_review_status (Review_Status)
                </cfquery>
            </cfif>
            <cfif NOT listFindNoCase(existingIndexes, 'idx_whisper_assmt_review')>
                <cfquery datasource="#Application.DataSrc#">
                    ALTER TABLE #arguments.agency_db#.whisper_files
                    ADD INDEX idx_whisper_assmt_review (Assmt_ID, Review_Status)
                </cfquery>
            </cfif>
            <cfcatch type="any"></cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="markWhisperFileAIReviewRequired" access="private" returntype="void" output="false">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="assessment_id" type="numeric" required="true" />
        <cfargument name="whisper_files_id" type="numeric" required="false" default="0" />
        <cfset ensureWhisperFilesReviewColumns(arguments.agency_db) />
        <cfif val(arguments.whisper_files_id) GT 0>
            <cfquery datasource="#Application.DataSrc#">
                UPDATE #arguments.agency_db#.whisper_files
                SET Review_Status = <cfqueryparam value="1" cfsqltype="cf_sql_tinyint">
                WHERE ID = <cfqueryparam value="#val(arguments.whisper_files_id)#" cfsqltype="cf_sql_bigint">
                  AND Status = <cfqueryparam value="0" cfsqltype="cf_sql_tinyint">
                  AND Review_Status = <cfqueryparam value="0" cfsqltype="cf_sql_tinyint">
            </cfquery>
        <cfelseif val(arguments.assessment_id) GT 0>
            <cfquery name="qLatestMobileWhisper" datasource="#Application.DataSrc#">
                SELECT ID
                FROM #arguments.agency_db#.whisper_files
                WHERE Assmt_ID = <cfqueryparam value="#val(arguments.assessment_id)#" cfsqltype="cf_sql_varchar">
                  AND Status = <cfqueryparam value="0" cfsqltype="cf_sql_tinyint">
                  AND Review_Status = <cfqueryparam value="0" cfsqltype="cf_sql_tinyint">
                ORDER BY ID DESC
                LIMIT 1
            </cfquery>
            <cfif qLatestMobileWhisper.recordcount GT 0>
                <cfquery datasource="#Application.DataSrc#">
                    UPDATE #arguments.agency_db#.whisper_files
                    SET Review_Status = <cfqueryparam value="1" cfsqltype="cf_sql_tinyint">
                    WHERE ID = <cfqueryparam value="#qLatestMobileWhisper.ID#" cfsqltype="cf_sql_bigint">
                </cfquery>
            </cfif>
        </cfif>
    </cffunction>

    <cffunction name="clearWhisperFileAIReviewRequired" access="public" returntype="void" output="false" hint="Call when clinician completes assessment review (Review_Status 1 -> 2)">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="assessment_id" type="numeric" required="true" />
        <cfif val(arguments.assessment_id) LTE 0>
            <cfreturn />
        </cfif>
        <cfset ensureWhisperFilesReviewColumns(arguments.agency_db) />
        <cfquery datasource="#Application.DataSrc#">
            UPDATE #arguments.agency_db#.whisper_files
            SET Review_Status = <cfqueryparam value="2" cfsqltype="cf_sql_tinyint">
            WHERE Assmt_ID = <cfqueryparam value="#val(arguments.assessment_id)#" cfsqltype="cf_sql_varchar">
              AND Status = <cfqueryparam value="0" cfsqltype="cf_sql_tinyint">
              AND Review_Status = <cfqueryparam value="1" cfsqltype="cf_sql_tinyint">
        </cfquery>
    </cffunction>

    <cffunction name="importAssessmentDataFromJobResponse" access="remote" returnformat="JSON" hint="Update assessment data directly from parsed job status response">
        <cfargument name="parsedJobResponse" required="yes" hint="Struct or JSON from jobs API response">
        <cfargument name="jobStatusResponse" required="no" default="" hint="Original cfhttp job status response (optional)">
        <cfargument name="whisper_files_id" required="no" default="0" hint="whisper_files row to flag for AI review after import">

        <cfset var payload = {} />
        <cfset var parsedPayload = {} />
        <cfset var resultNode = {} />
        <cfset var apiResponse = "" />
        <cfset var response = {} />

        <cftry>
            <cfif isStruct(arguments.parsedJobResponse)>
                <cfset parsedPayload = arguments.parsedJobResponse />
            <cfelseif isSimpleValue(arguments.parsedJobResponse) AND len(trim(arguments.parsedJobResponse)) AND isJSON(arguments.parsedJobResponse)>
                <cfset parsedPayload = deserializeJSON(arguments.parsedJobResponse) />
            <cfelse>
                <cfthrow type="AssessmentImport" message="Invalid parsedJobResponse" detail="Expected struct or JSON payload" />
            </cfif>

            <cfif NOT isStruct(parsedPayload)>
                <cfthrow type="AssessmentImport" message="Invalid parsedJobResponse" detail="Payload is not a struct" />
            </cfif>

            <cfif structKeyExists(parsedPayload, "result") AND isStruct(parsedPayload.result)>
                <cfset resultNode = parsedPayload.result />
            <cfelse>
                <cfset resultNode = parsedPayload />
            </cfif>

            <cfset payload.Agency_ID = structKeyExists(resultNode, "AGENCY_ID") ? resultNode.AGENCY_ID : "" />
            <cfset payload.Patient_ID = structKeyExists(resultNode, "PATIENT_ID") ? resultNode.PATIENT_ID : "" />
            <cfset payload.Assessment_ID = structKeyExists(resultNode, "ASSESSMENT_ID") ? resultNode.ASSESSMENT_ID : "" />
            <cfset payload.Emp_ID = structKeyExists(resultNode, "Emp_ID") ? resultNode.Emp_ID : "0" />
            <cfset payload.appkey = structKeyExists(resultNode, "appkey") ? resultNode.appkey : (structKeyExists(resultNode, "appKey") ? resultNode.appKey : "") />
            <cfset payload.Website_name = structKeyExists(resultNode, "Website_name") ? resultNode.Website_name : "" />

            <cfif NOT len(trim(payload.Agency_ID)) OR NOT len(trim(payload.Patient_ID)) OR NOT len(trim(payload.Assessment_ID)) OR NOT len(trim(payload.appkey))>
                <cfthrow type="AssessmentImport" message="Missing required encrypted fields" detail="AGENCY_ID, PATIENT_ID, ASSESSMENT_ID and appKey/appkey are required in parsedJobResponse.result" />
            </cfif>

            <cfset apiResponse = importAssessmentData(
                Agency_ID = payload.Agency_ID,
                appkey = payload.appkey,
                Emp_ID = payload.Emp_ID,
                Patient_ID = payload.Patient_ID,
                Assessment_ID = payload.Assessment_ID,
                Website_name = payload.Website_name,
                filled_answers = serializeJSON(parsedPayload),
                jobStatusResponse = arguments.jobStatusResponse,
                job_id = structKeyExists(parsedPayload, "job_id") ? parsedPayload.job_id : "",
                whisper_files_id = val(arguments.whisper_files_id)
            ) />

            <cfif isSimpleValue(apiResponse) AND len(trim(apiResponse)) AND isJSON(apiResponse)>
                <cfset response = deserializeJSON(apiResponse) />
            <cfelseif isStruct(apiResponse)>
                <cfset response = apiResponse />
            <cfelse>
                <cfset response = {
                    "success": false,
                    "message": "Invalid response from importAssessmentData",
                    "data": {}
                } />
            </cfif>

            <cfreturn serializeJSON(response) />

            <cfcatch type="any">
                <!--- Extract what we can from the payload for error reporting --->
                <cfset var errorAgencyID = structKeyExists(payload, "Agency_ID") ? payload.Agency_ID : 0 />
                <cfset var errorPatientID = structKeyExists(payload, "Patient_ID") ? payload.Patient_ID : 0 />
                <cfset var errorAssessmentID = structKeyExists(payload, "Assessment_ID") ? payload.Assessment_ID : 0 />
                <cfset var errorEmpID = structKeyExists(payload, "Emp_ID") ? payload.Emp_ID : 0 />
                <cfset var errorWebsiteName = structKeyExists(payload, "Website_name") ? payload.Website_name : "" />
                
                <!--- Send error email for wrapper function errors --->
                <cftry>
                    <cfset sendErrorEmail(
                        error = cfcatch,
                        agencyID = val(errorAgencyID),
                        empID = val(errorEmpID),
                        patientID = val(errorPatientID),
                        assessmentID = val(errorAssessmentID),
                        websiteName = errorWebsiteName,
                        extractedJSONString = serializeJSON(parsedPayload),
                        originalAgencyID = structKeyExists(resultNode, "AGENCY_ID") ? resultNode.AGENCY_ID : "",
                        originalEmpID = structKeyExists(resultNode, "Emp_ID") ? resultNode.Emp_ID : "0",
                        originalPatientID = structKeyExists(resultNode, "PATIENT_ID") ? resultNode.PATIENT_ID : "",
                        originalAssessmentID = structKeyExists(resultNode, "ASSESSMENT_ID") ? resultNode.ASSESSMENT_ID : "",
                        originalAppKey = structKeyExists(resultNode, "appkey") ? resultNode.appkey : (structKeyExists(resultNode, "appKey") ? resultNode.appKey : ""),
                        decryptedAgencyID = "",
                        decryptedEmpID = "",
                        decryptedPatientID = "",
                        decryptedAssessmentID = "",
                        decryptedAppKey = "",
                        insertedPatientID = 0,
                        medicationCount = 0,
                        problemCount = 0,
                        errors = [],
                        stepErrors = ["Wrapper Function Error: " & cfcatch.message],
                        currentStep = "Parsing job response in wrapper function"
                    ) />
                    <cfcatch>
                        <!--- Error email failed silently --->
                    </cfcatch>
                </cftry>
                
                <cfset response = {
                    "success": false,
                    "message": "Failed to import assessment data from job response",
                    "error": cfcatch.message,
                    "detail": cfcatch.detail,
                    "data": {}
                } />
                <cfreturn serializeJSON(response) />
            </cfcatch>
        </cftry>
    </cffunction>

    <!--- OASIS date fields F176-F199 (matches assessments.cfc update logic) --->
    <cffunction name="isAssessmentDateField" access="private" returntype="boolean" output="false">
        <cfargument name="fieldName" type="string" required="true" />
        <cfset var fieldNum = val(reReplace(arguments.fieldName, "^F", "", "all")) />
        <cfreturn reFind("^F[0-9]+$", arguments.fieldName) AND len(arguments.fieldName) EQ 4 AND fieldNum GT 175 AND fieldNum LT 200 />
    </cffunction>

  <!--- Prepare one F-column value for pAssessments UPDATE (validate column, type, length) --->
    <cffunction name="prepareAssessmentAnswerUpdate" access="private" returntype="struct" output="false">
        <cfargument name="fieldName" type="string" required="true" />
        <cfargument name="answerValue" type="string" required="true" />
        <cfargument name="columnMeta" type="struct" required="true" />

        <cfset var out = {
            ok = false,
            fieldName = uCase(trim(arguments.fieldName)),
            sqlType = "varchar",
            value = trim(arguments.answerValue),
            useNull = false,
            skipReason = "",
            truncated = false
        } />
        <cfset var maxLen = 0 />
        <cfset var dataType = "" />
        <cfset var parsedDate = "" />

        <cfif NOT structKeyExists(arguments.columnMeta, out.fieldName)>
            <cfset out.skipReason = "column_not_in_pAssessments" />
            <cfreturn out />
        </cfif>

        <cfset dataType = lCase(trim(arguments.columnMeta[out.fieldName].data_type)) />
        <cfset maxLen = val(arguments.columnMeta[out.fieldName].max_length) />

        <cfif isAssessmentDateField(out.fieldName) OR listFindNoCase("date,datetime,timestamp", dataType)>
            <cfif NOT len(out.value)>
                <cfset out.ok = true />
                <cfset out.useNull = true />
                <cfset out.sqlType = "date" />
                <cfreturn out />
            </cfif>
            <cftry>
                <cfset parsedDate = parseDateTime(out.value) />
                <cfset out.ok = true />
                <cfset out.sqlType = "date" />
                <cfset out.value = dateFormat(parsedDate, "yyyy-mm-dd") />
                <cfcatch type="any">
                    <cfset out.skipReason = "invalid_date_value" />
                </cfcatch>
            </cftry>
            <cfreturn out />
        </cfif>

        <cfif maxLen GT 0 AND len(out.value) GT maxLen>
            <cfset out.value = left(out.value, maxLen) />
            <cfset out.truncated = true />
        </cfif>

        <cfset out.ok = true />
        <cfset out.sqlType = "varchar" />
        <cfreturn out />
    </cffunction>

    <!--- Apply mapped F-column answers in validated batches; fall back to per-field updates on batch failure --->
    <cffunction name="applyMappedAnswersToAssessmentRow" access="private" returntype="struct" output="false">
        <cfargument name="agency_db" type="string" required="true" />
        <cfargument name="insertedAutoAssmtID" type="numeric" required="true" />
        <cfargument name="answerUpdates" type="struct" required="true" />
        <cfargument name="validColumnList" type="string" required="true" />
        <cfargument name="created_by" type="numeric" required="true" />
        <cfargument name="skippedAnswers" type="array" required="true" />
        <cfargument name="stepErrors" type="array" required="true" />
        <cfargument name="queryTrace" type="array" required="true" />
        <cfargument name="batchSize" type="numeric" required="false" default="25" />

        <cfset var result = { applied = 0, skipped = 0, batches = 0, prepared = 0 } />
        <cfset var columnMeta = {} />
        <cfset var qColumns = "" />
        <cfset var preparedUpdates = {} />
        <cfset var fieldName = "" />
        <cfset var prep = {} />
        <cfset var preparedFields = [] />
        <cfset var batchFields = [] />
        <cfset var batchStart = 0 />
        <cfset var batchEnd = 0 />
        <cfset var idx = 0 />
        <cfset var i = 0 />
        <cfset var batchNum = 0 />
        <cfset var updateResult = {} />

        <cfif structCount(arguments.answerUpdates) EQ 0 OR arguments.insertedAutoAssmtID LTE 0>
            <cfreturn result />
        </cfif>

        <cfquery name="qColumns" datasource="#Application.DataSrc#">
            SELECT COLUMN_NAME,
                   DATA_TYPE,
                   CHARACTER_MAXIMUM_LENGTH
            FROM INFORMATION_SCHEMA.COLUMNS
            WHERE TABLE_SCHEMA = <cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.agency_db#">
              AND TABLE_NAME = <cfqueryparam cfsqltype="cf_sql_varchar" value="pAssessments">
        </cfquery>

        <cfloop query="qColumns">
            <cfset columnMeta[uCase(trim(qColumns.COLUMN_NAME))] = {
                data_type = trim(qColumns.DATA_TYPE),
                max_length = val(qColumns.CHARACTER_MAXIMUM_LENGTH)
            } />
        </cfloop>

        <cfloop collection="#arguments.answerUpdates#" item="fieldName">
            <cfset fieldName = uCase(trim(fieldName)) />
            <cfif NOT reFind("^F[0-9]+$", fieldName)>
                <cfset arrayAppend(arguments.skippedAnswers, fieldName & ":invalid_field_name") />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>
            <cfif NOT listFindNoCase(arguments.validColumnList, fieldName)>
                <cfset arrayAppend(arguments.skippedAnswers, fieldName & ":column_not_in_pAssessments") />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>

            <cfset prep = prepareAssessmentAnswerUpdate(
                fieldName = fieldName,
                answerValue = "" & arguments.answerUpdates[fieldName],
                columnMeta = columnMeta
            ) />
            <cfif NOT prep.ok>
                <cfset arrayAppend(arguments.skippedAnswers, fieldName & ":" & prep.skipReason) />
                <cfset result.skipped = result.skipped + 1 />
                <cfcontinue />
            </cfif>
            <cfif prep.truncated>
                <cfset arrayAppend(arguments.stepErrors, "Answer truncated for " & fieldName & " to fit column max length") />
            </cfif>
            <cfset preparedUpdates[fieldName] = prep />
            <cfset arrayAppend(preparedFields, fieldName) />
            <cfset result.prepared = result.prepared + 1 />
        </cfloop>

        <cfif arrayLen(preparedFields) EQ 0>
            <cfreturn result />
        </cfif>

        <cftry>
            <cfset preparedFields = listToArray(listSort(arrayToList(preparedFields), "text", "asc")) />
            <cfcatch type="any"></cfcatch>
        </cftry>

        <cfloop from="1" to="#arrayLen(preparedFields)#" index="batchStart" step="#val(arguments.batchSize)#">
            <cfset batchEnd = min(batchStart + val(arguments.batchSize) - 1, arrayLen(preparedFields)) />
            <cfset batchFields = [] />
            <cfloop from="#batchStart#" to="#batchEnd#" index="idx">
                <cfset arrayAppend(batchFields, preparedFields[idx]) />
            </cfloop>
            <cfset batchNum = batchNum + 1 />

            <cftry>
                <cfset arrayAppend(arguments.queryTrace, "update_assessment_answers batch " & batchNum & " fields=" & arrayToList(batchFields, ",")) />
                <cfquery name="update_assessment_answers" datasource="#Application.DataSrc#" result="updateResult">
                    UPDATE #arguments.agency_db#.pAssessments
                    SET
                    <cfloop from="1" to="#arrayLen(batchFields)#" index="i">
                        #batchFields[i]# =
                        <cfif preparedUpdates[batchFields[i]].useNull>
                            NULL
                        <cfelseif preparedUpdates[batchFields[i]].sqlType EQ "date">
                            <cfqueryparam cfsqltype="cf_sql_date" value="#preparedUpdates[batchFields[i]].value#">
                        <cfelse>
                            <cfqueryparam cfsqltype="cf_sql_varchar" value="#preparedUpdates[batchFields[i]].value#">
                        </cfif><cfif i LT arrayLen(batchFields)>,</cfif>
                    </cfloop>,
                        Record_Mod_Date = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#">,
                        Record_Mod_By = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.created_by#">
                    WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.insertedAutoAssmtID#">
                </cfquery>
                <cfset result.applied = result.applied + arrayLen(batchFields) />
                <cfset result.batches = result.batches + 1 />
                <cfcatch type="any">
                    <cfset arrayAppend(arguments.stepErrors, "Batch update failed (batch " & batchNum & "): " & cfcatch.message & (len(trim(cfcatch.detail)) ? " | " & cfcatch.detail : "")) />
                    <cfset arrayAppend(arguments.queryTrace, "update_assessment_answers batch " & batchNum & " error: " & cfcatch.message) />
                    <cfloop from="1" to="#arrayLen(batchFields)#" index="i">
                        <cfset fieldName = batchFields[i] />
                        <cftry>
                            <cfquery datasource="#Application.DataSrc#">
                                UPDATE #arguments.agency_db#.pAssessments
                                SET
                                    #fieldName# =
                                    <cfif preparedUpdates[fieldName].useNull>
                                        NULL
                                    <cfelseif preparedUpdates[fieldName].sqlType EQ "date">
                                        <cfqueryparam cfsqltype="cf_sql_date" value="#preparedUpdates[fieldName].value#">
                                    <cfelse>
                                        <cfqueryparam cfsqltype="cf_sql_varchar" value="#preparedUpdates[fieldName].value#">
                                    </cfif>,
                                    Record_Mod_Date = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#">,
                                    Record_Mod_By = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.created_by#">
                                WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.insertedAutoAssmtID#">
                            </cfquery>
                            <cfset result.applied = result.applied + 1 />
                            <cfcatch type="any">
                                <cfset arrayAppend(arguments.skippedAnswers, fieldName & ":update_failed_" & replace(cfcatch.message, ":", "-", "all")) />
                                <cfset arrayAppend(arguments.stepErrors, "Field update failed for " & fieldName & ": " & cfcatch.message) />
                                <cfset result.skipped = result.skipped + 1 />
                            </cfcatch>
                        </cftry>
                    </cfloop>
                </cfcatch>
            </cftry>
        </cfloop>

        <cfreturn result />
    </cffunction>

    <cffunction name="importAssessmentData" access="remote" returnformat="JSON" hint="Update assessment data only from extracted AI payload">
        <cfargument name="Agency_ID" required="yes" hint="Agency ID">
        <cfargument name="appkey" required="yes" hint="appkey">
        <cfargument name="Emp_ID" required="no" default="0" hint="Emp ID (optional, defaults to 0)">
        <cfargument name="Patient_ID" required="yes" hint="Patient ID">
        <cfargument name="Assessment_ID" required="yes" hint="Assessment ID">
        <cfargument name="Website_name" required="no" default="" hint="Website name">
        <cfargument name="questions_URL" required="no" default="" hint="questions URL">
        <cfargument name="jobStatusResponse" required="no" default="" hint="Jobs API response payload or cfhttp result struct">
        <cfargument name="job_id" required="no" default="" hint="Transcript job ID">
        <cfargument name="jobs_api_url" required="no" default="https://transcriptapi.myhomecarebiz.com/jobs/" hint="Transcript jobs API URL">
        <cfargument name="filled_answers" required="yes" hint="JSON string or struct with filled answers payload">
        <cfargument name="whisper_files_id" required="no" default="0" hint="whisper_files.ID to flag AI review required after successful import">
        
        <cfset var response = {} />
        <cfset var insertedPatientID = 0 />
        <cfset var insertedContactID = 0 />
        <cfset var insertedPayerID = 0 />
        <cfset var insertedAdmitID = 0 />
        <cfset var insertedMedicationIDs = [] />
        <cfset var insertedProblemIDs = [] />
        <cfset var medicationCount = 0 />
        <cfset var problemCount = 0 />
        <cfset var contactInserted = false />
        <cfset var payerInserted = false />
        <cfset var admitInserted = false />
        <cfset var errors = [] />
        <cfset var stepErrors = [] />
        <cfset var currentStep = "" />
        <cfset var decryptedAgencyID = "" />
        <cfset var decryptedEmpID = "" />
        <cfset var decryptedPatientID = "" />
        <cfset var decryptedAssessmentID = "" />
        <cfset var decryptedAppKey = "" />
        <cfset var extractedJSONString = "" />
        <cfset var lookup_db = "hhapowerpath" />
        <cfset var filledAnswers = {} />
        <cfset var answerUpdates = {} />
        <cfset var skippedAnswers = [] />
        <cfset var updateFields = [] />
        <cfset var fieldName = "" />
        <cfset var i = 0 />
        <cfset var qaItem = {} />
        <cfset var rawAnswer = "" />
        <cfset var normalizedAnswer = "" />
        <cfset var mapQid = "" />
        <cfset var updateResult = {} />
        <cfset var currentAssessmentReason = "" />
        <cfset var assessmentReasonForLookup = "" />
        <cfset var answerLabelMap = {} />
        <cfset var data = {} />
        <cfset var jobStatusPayloadRaw = "" />
        <cfset var jobStatusPayloadUsed = false />
        <cfset var effectiveJobID = "" />
        <cfset var sourceAutoAssmtID = 0 />
        <cfset var insertedAutoAssmtID = 0 />
        <cfset var assessmentColumnList = "" />
        <cfset var queryTrace = [] />
        <cfset var requestSnapshot = {} />
        <cfset var requestJSONString = "" />
        <cfset var responseJSONString = "" />
        <cfset var cloneAssessmentSql = "" />
        <cfset var retireSourceSql = "" />
        <cfset var activateInsertedSql = "" />
        <cfset var updateAssessmentSql = "" />
        <cfset var vitalImportResult = { updated = 0, inserted = 0, skipped = 0 } />
        <cfset var woundImportResult = { updated = 0, inserted = 0, skipped = 0 } />
        <cfset var filledVitals = {} />
        <cfset var answerApplyResult = { applied = 0, skipped = 0, batches = 0, prepared = 0 } />
        <!--- Store original encrypted values for email reporting --->
        <cfset var originalAgencyID = arguments.Agency_ID />
        <cfset var originalEmpID = arguments.Emp_ID />
        <cfset var originalPatientID = arguments.Patient_ID />
        <cfset var originalAssessmentID = arguments.Assessment_ID />
        <cfset var originalAppKey = arguments.appkey />
        
        <cftry>
            <!--- Step 1: Attempt to decrypt incoming values (handles encrypted payloads) --->
            <cfset currentStep = "Decrypting authentication parameters" />
            <cftry>
                <cfset decryptedAgencyID = maybeDecrypt(arguments.Agency_ID, true) />
                <cfset decryptedEmpID = maybeDecrypt(arguments.Emp_ID, true) />
                <cfset decryptedPatientID = maybeDecrypt(arguments.Patient_ID, true) />
                <cfset decryptedAssessmentID = maybeDecrypt(arguments.Assessment_ID, true) />
                <cfset decryptedAppKey = maybeDecrypt(arguments.appkey) />
 
                <cfif decryptedAgencyID NEQ arguments.Agency_ID>
                    <cfset arguments.Agency_ID = decryptedAgencyID />
                </cfif>
                <cfif decryptedEmpID NEQ arguments.Emp_ID>
                    <cfset arguments.Emp_ID = decryptedEmpID />
                </cfif>
                <cfif decryptedPatientID NEQ arguments.Patient_ID>
                    <cfset arguments.Patient_ID = decryptedPatientID />
                </cfif>
                <cfif decryptedAssessmentID NEQ arguments.Assessment_ID>
                    <cfset arguments.Assessment_ID = decryptedAssessmentID />
                </cfif>
                <cfif len(decryptedAppKey)>
                    <cfset arguments.appkey = decryptedAppKey />
                </cfif> 

                <!--- Normalize numeric arguments --->
                <cfset arguments.Agency_ID = val(arguments.Agency_ID) />
                <cfset arguments.Emp_ID = val(arguments.Emp_ID) />
                <cfset arguments.Patient_ID = val(arguments.Patient_ID) />
                <cfset arguments.Assessment_ID = val(arguments.Assessment_ID) />
                <cfcatch type="any">
                    <cfset arrayAppend(stepErrors, "Decryption Error: " & cfcatch.message) />
                </cfcatch>
            </cftry>

            <!--- Step 2: Validate Basic Authentication --->
            <cfset currentStep = "Validating authentication" />
            <cftry>
                <cfset var authResult = validateBasicAuth(arguments.appkey, arguments.Agency_ID, arguments.Emp_ID, arguments.Patient_ID) />
                <cfif not authResult.valid>
                    <cfset response = {
                        "success": false,
                        "message": authResult.message,
                        "data": {}
                    } />
                    <cfreturn serializeJSON(response) />
                </cfif>
                <cfcatch type="any">
                    <cfset arrayAppend(stepErrors, "Authentication Validation Error: " & cfcatch.message) />
                </cfcatch>
            </cftry>
            
            <cfset var agency_db = 'agency_' & arguments.Agency_ID   />
            <cfset var created_by = arguments.Emp_ID   />

            <!--- Step 3: Parse job status response payload --->
            <cfset currentStep = "Parsing job status response payload" />
            <cftry>
                <!--- Prefer upstream jobStatusResponse passed from call_whisper_api.cfm --->
                <cfif isStruct(arguments.jobStatusResponse)>
                    <cfif structKeyExists(arguments.jobStatusResponse, "FileContent") AND len(trim(arguments.jobStatusResponse.FileContent)) AND isJSON(arguments.jobStatusResponse.FileContent)>
                        <cfset data = deserializeJSON(arguments.jobStatusResponse.FileContent) />
                        <cfset extractedJSONString = trim(arguments.jobStatusResponse.FileContent) />
                        <cfset jobStatusPayloadUsed = true />
                    <cfelse>
                        <cfset data = arguments.jobStatusResponse />
                        <cftry>
                            <cfset extractedJSONString = serializeJSON(data) />
                            <cfset jobStatusPayloadUsed = true />
                            <cfcatch>
                                <cfset extractedJSONString = "" />
                            </cfcatch>
                        </cftry>
                    </cfif>
                <cfelseif isSimpleValue(arguments.jobStatusResponse) AND len(trim(arguments.jobStatusResponse))>
                    <cfset jobStatusPayloadRaw = trim(arguments.jobStatusResponse) />
                    <cfif isJSON(jobStatusPayloadRaw)>
                        <cfset data = deserializeJSON(jobStatusPayloadRaw) />
                        <cfset extractedJSONString = jobStatusPayloadRaw />
                        <cfset jobStatusPayloadUsed = true />
                    </cfif>
                </cfif>

                <!--- Fallback to filled_answers when no jobStatusResponse payload is provided --->
                <cfif NOT jobStatusPayloadUsed>
                    <cfif isJSON(arguments.filled_answers)>
                        <cfset data = deserializeJSON(arguments.filled_answers) />
                        <cfset extractedJSONString = arguments.filled_answers />
                    <cfelse>
                        <cfset data = arguments.filled_answers />
                        <cftry>
                            <cfset extractedJSONString = serializeJSON(data) />
                            <cfcatch>
                                <cfset extractedJSONString = "" />
                            </cfcatch>
                        </cftry>
                    </cfif>
                </cfif>

                <cfset effectiveJobID = trim(arguments.job_id) />
                <cfif NOT len(effectiveJobID) AND isStruct(data) AND structKeyExists(data, "job_id")>
                    <cfset effectiveJobID = trim("" & data.job_id) />
                <cfelseif NOT len(effectiveJobID) AND isStruct(data) AND structKeyExists(data, "result") AND isStruct(data.result) AND structKeyExists(data.result, "job_id")>
                    <cfset effectiveJobID = trim("" & data.result.job_id) />
                </cfif>
                
                <cfcatch type="any">
                    <cfset arrayAppend(stepErrors, "Payload Parsing Error: " & cfcatch.message & " | Detail: " & cfcatch.detail) />
                </cfcatch>
            </cftry>

            <cfset request.bulk_import_queries = [] />

            <cfif structKeyExists(Request, "prefix_db_lookup") AND len(trim(Request.prefix_db_lookup))>
                <cfset lookup_db = trim(Request.prefix_db_lookup) />
            </cfif>

            <!--- Step 4: Extract filled answers from payload --->
            <cfset currentStep = "Extracting filled answers from payload" />
            <cftry>
                <!--- Support payloads where filled_answers is nested under result --->
                <cfif isStruct(data) AND structKeyExists(data, "result") AND isStruct(data.result) AND structKeyExists(data.result, "filled_answers") AND isStruct(data.result.filled_answers)>
                    <cfset filledAnswers = data.result.filled_answers />
                <cfelseif isStruct(data) AND structKeyExists(data, "filled_answers") AND isStruct(data.filled_answers)>
                    <cfset filledAnswers = data.filled_answers />
                <cfelseif isStruct(data)>
                    <!--- Support payloads where the body itself is the filled_answers object --->
                    <cfset filledAnswers = data />
                </cfif>

                <!--- Optional nested filled_vitals (merge into filledAnswers as vital_{item_id}) --->
                <cfif isStruct(data) AND structKeyExists(data, "result") AND isStruct(data.result) AND structKeyExists(data.result, "filled_vitals")>
                    <cfset filledVitals = data.result.filled_vitals />
                <cfelseif isStruct(data) AND structKeyExists(data, "filled_vitals")>
                    <cfset filledVitals = data.filled_vitals />
                </cfif>
                <cfif isStruct(filledVitals)>
                    <cfloop collection="#filledVitals#" item="mapQid">
                        <cfif NOT structKeyExists(filledAnswers, mapQid)>
                            <cfset filledAnswers[mapQid] = filledVitals[mapQid] />
                        </cfif>
                    </cfloop>
                </cfif>
                <cfcatch type="any">
                    <cfset arrayAppend(stepErrors, "Answer Extraction Error: " & cfcatch.message) />
                </cfcatch>
            </cftry>
             <!--- Start transaction --->
            <cftransaction>
                <!--- Step 5: Read current active assessment to determine the source row for cloning --->
                <cfset currentStep = "Reading current active assessment source row" />
                <cftry>
                    <cfquery name="get_current_assessment" datasource="#Application.DataSrc#">
                        SELECT AutoAssmt_ID, F26
                        FROM #agency_db#.pAssessments
                        WHERE Assmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.Assessment_ID#">
                          AND Patient_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.Patient_ID#">
                          AND Status = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
                        ORDER BY AutoAssmt_ID DESC
                        LIMIT 1
                    </cfquery>

                    <cfif get_current_assessment.recordCount EQ 0>
                        <cfset arrayAppend(stepErrors, "Active Assessment Lookup: No active assessment found with Status=0 for Assmt_ID=#arguments.Assessment_ID#, Patient_ID=#arguments.Patient_ID#") />
                        <cfthrow type="AssessmentImport" message="No active assessment row found" detail="Assmt_ID #arguments.Assessment_ID# with Status=0 was not found for patient #arguments.Patient_ID#" />
                    </cfif>

                    <cfset sourceAutoAssmtID = val(get_current_assessment.AutoAssmt_ID[1]) />
                    <cfset currentAssessmentReason = trim("" & get_current_assessment.F26[1]) />
                    <cfif len(currentAssessmentReason) EQ 0>
                        <cfset arrayAppend(stepErrors, "Assessment Reason (F26) Empty: F26 column is empty for Assmt_ID=#arguments.Assessment_ID#") />
                        <cfthrow type="AssessmentImport" message="Assessment reason (F26) is empty" detail="Cannot map questions without F26 for Assmt_ID #arguments.Assessment_ID#" />
                    </cfif>
                    <cfif sourceAutoAssmtID LTE 0>
                        <cfset arrayAppend(stepErrors, "Active Assessment Lookup: AutoAssmt_ID is missing for Assmt_ID=#arguments.Assessment_ID#") />
                        <cfthrow type="AssessmentImport" message="Active assessment source row is missing AutoAssmt_ID" detail="Cannot create an insert-based assessment version without AutoAssmt_ID for Assmt_ID #arguments.Assessment_ID#" />
                    </cfif>
                    <cfcatch type="any">
                        <cfif arrayLen(stepErrors) EQ 0>
                            <cfset arrayAppend(stepErrors, "Current Assessment Lookup Error: " & cfcatch.message) />
                        </cfif>
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 6: Build the clone column list for insert-based versioning --->
                <cfset currentStep = "Loading assessment column list for insert-based versioning" />
                <cftry>
                    <cfquery name="get_assessment_columns" datasource="#Application.DataSrc#">
                        SELECT COLUMN_NAME
                        FROM INFORMATION_SCHEMA.COLUMNS
                        WHERE TABLE_SCHEMA = <cfqueryparam cfsqltype="cf_sql_varchar" value="#agency_db#">
                          AND TABLE_NAME = <cfqueryparam cfsqltype="cf_sql_varchar" value="pAssessments">
                          AND COLUMN_NAME NOT IN (
                              <cfqueryparam cfsqltype="cf_sql_varchar" value="AutoAssmt_ID">,
                              <cfqueryparam cfsqltype="cf_sql_varchar" value="record_timestamp">
                          )
                        ORDER BY ORDINAL_POSITION
                    </cfquery>
                    <cfset assessmentColumnList = valueList(get_assessment_columns.COLUMN_NAME) />
                    <cfif NOT len(trim(assessmentColumnList))>
                        <cfset arrayAppend(stepErrors, "Assessment Clone Column Lookup Error: No insertable columns found for #agency_db#.pAssessments") />
                        <cfthrow type="AssessmentImport" message="Could not determine assessment clone columns" detail="INFORMATION_SCHEMA returned no insertable columns for #agency_db#.pAssessments" />
                    </cfif>
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Assessment Clone Column Error: " & cfcatch.message & " | Failed to load INFORMATION_SCHEMA columns for pAssessments") />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 7: Clone the active assessment row so this import is insert-based --->
                <cfset currentStep = "Cloning active assessment row into a new inserted version" />
                <cftry>
                    <cfset cloneAssessmentSql = "INSERT INTO #agency_db#.pAssessments (#assessmentColumnList#) SELECT #assessmentColumnList# FROM #agency_db#.pAssessments WHERE AutoAssmt_ID = #sourceAutoAssmtID# LIMIT 1" />
                    <cfset arrayAppend(queryTrace, cloneAssessmentSql) />
                    <cfquery name="clone_assessment_row" datasource="#Application.DataSrc#">
                        INSERT INTO #agency_db#.pAssessments
                        (#preserveSingleQuotes(assessmentColumnList)#)
                        SELECT #preserveSingleQuotes(assessmentColumnList)#
                        FROM #agency_db#.pAssessments
                        WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#sourceAutoAssmtID#">
                        LIMIT 1
                    </cfquery>

                    <cfquery name="get_inserted_assessment_row" datasource="#Application.DataSrc#">
                        SELECT LAST_INSERT_ID() AS LastInsertID
                    </cfquery>

                    <cfset insertedAutoAssmtID = val(get_inserted_assessment_row.LastInsertID) />
                    <cfif insertedAutoAssmtID LTE 0>
                        <cfset arrayAppend(stepErrors, "Assessment Clone Error: INSERT succeeded but LAST_INSERT_ID() returned 0 for Assmt_ID=#arguments.Assessment_ID#") />
                        <cfthrow type="AssessmentImport" message="Failed to determine inserted assessment row" detail="LAST_INSERT_ID() returned 0 after cloning AutoAssmt_ID #sourceAutoAssmtID#" />
                    </cfif>

                    <cfset retireSourceSql = "UPDATE #agency_db#.pAssessments SET Status = 2, Record_Mod_Date = '#DateTimeFormat(Now(), 'yyyy-mm-dd HH:nn:ss')#', Record_Mod_By = #created_by# WHERE AutoAssmt_ID = #sourceAutoAssmtID#" />
                    <cfset arrayAppend(queryTrace, retireSourceSql) />
                    <cfquery name="retire_source_assessment" datasource="#Application.DataSrc#">
                        UPDATE #agency_db#.pAssessments
                        SET Status = <cfqueryparam cfsqltype="cf_sql_integer" value="2">,
                            Record_Mod_Date = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#">,
                            Record_Mod_By = <cfqueryparam cfsqltype="cf_sql_integer" value="#created_by#">
                        WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#sourceAutoAssmtID#">
                    </cfquery>

                    <cfset activateInsertedSql = "UPDATE #agency_db#.pAssessments SET Status = 0, Record_Creation_Date = '#DateTimeFormat(Now(), 'yyyy-mm-dd HH:nn:ss')#', Record_Created_By = #created_by#, Record_Mod_Date = '#DateTimeFormat(Now(), 'yyyy-mm-dd HH:nn:ss')#', Record_Mod_By = #created_by# WHERE AutoAssmt_ID = #insertedAutoAssmtID#" />
                    <cfset arrayAppend(queryTrace, activateInsertedSql) />
                    <cfquery name="activate_inserted_assessment" datasource="#Application.DataSrc#">
                        UPDATE #agency_db#.pAssessments
                        SET Status = <cfqueryparam cfsqltype="cf_sql_integer" value="0">,
                            Record_Creation_Date = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#">,
                            Record_Created_By = <cfqueryparam cfsqltype="cf_sql_integer" value="#created_by#">,
                            Record_Mod_Date = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#Now()#">,
                            Record_Mod_By = <cfqueryparam cfsqltype="cf_sql_integer" value="#created_by#">
                        WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#insertedAutoAssmtID#">
                    </cfquery>
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Assessment Clone Error: " & cfcatch.message & " | Failed to clone active assessment row AutoAssmt_ID=#sourceAutoAssmtID#") />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 8: Verify the inserted assessment row is active and retrieve assessment reason --->
                <cfset currentStep = "Verifying inserted assessment version and retrieving assessment reason" />
                <cftry>
                    <cfquery name="get_active_reason" datasource="#Application.DataSrc#">
                        SELECT F26
                        FROM #agency_db#.pAssessments
                        WHERE AutoAssmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#insertedAutoAssmtID#">
                    </cfquery>

                    <cfif get_active_reason.recordCount EQ 0>
                        <cfset arrayAppend(stepErrors, "Inserted Version Verification Failed: Could not find cloned assessment row AutoAssmt_ID=#insertedAutoAssmtID#") />
                        <cfthrow type="AssessmentImport" message="Unable to verify inserted assessment row" detail="AutoAssmt_ID #insertedAutoAssmtID# was not found after cloning Assmt_ID #arguments.Assessment_ID#" />
                    </cfif>

                    <cfset assessmentReasonForLookup = trim("" & get_active_reason.F26[1]) />
                    <cfcatch type="any">
                        <cfif arrayLen(stepErrors) EQ 0>
                            <cfset arrayAppend(stepErrors, "Active Reason Lookup Error: " & cfcatch.message) />
                        </cfif>
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 9: Build lookup map: question_id -> answer_label(Fxx) using F26 --->
                <cfset currentStep = "Building question ID to F-column answer label mapping (Assessment Reason: #assessmentReasonForLookup#)" />
                <cftry>
                    <cfquery name="get_question_map" datasource="#Application.DataSrc#">
                        SELECT ques_id, answer_label
                        FROM #lookup_db#.assessment_questions_lookup
                        WHERE assessment_reason = <cfqueryparam cfsqltype="cf_sql_varchar" value="#assessmentReasonForLookup#">
                          AND Deleted = <cfqueryparam cfsqltype="cf_sql_integer" value="0">
                          AND answer_label IS NOT NULL
                          AND trim(answer_label) != ''
                    </cfquery>

                    <cfloop query="get_question_map">
                        <cfset mapQid = trim("" & get_question_map.ques_id) />
                        <cfif len(mapQid)>
                            <cfset answerLabelMap[mapQid] = uCase(trim("" & get_question_map.answer_label)) />
                        </cfif>
                    </cfloop>
                    
                    <cfif structCount(answerLabelMap) EQ 0>
                        <cfset arrayAppend(stepErrors, "Question Lookup Warning: No questions found in assessment_questions_lookup for assessment_reason=#assessmentReasonForLookup#") />
                    </cfif>
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Question Mapping Error: " & cfcatch.message & " | Failed to query assessment_questions_lookup") />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 10: Convert incoming answers into F-column updates --->
                <cfset currentStep = "Mapping filled answers to F-columns" />
                <cftry>
                    <cfset var shouldUpdateField = false />
                    <cfloop collection="#filledAnswers#" item="mapQid">
                        <cfset qaItem = filledAnswers[mapQid] />
                        <cfif isVitalFilledAnswerKey(mapQid) OR isWoundFilledAnswerKey(mapQid)>
                            <cfcontinue />
                        </cfif>
                        <cfif NOT isStruct(qaItem) OR NOT structKeyExists(qaItem, "answer")>
                            <cfset arrayAppend(skippedAnswers, mapQid & ":missing_answer_field_in_payload") />
                            <cfcontinue>
                        </cfif>

                        <!--- Resolve F-column: key F242, payload answer_label, or DB lookup map --->
                        <cfset fieldName = "" />
                        <cfif isAssessmentFilledAnswerKey(mapQid)>
                            <cfset fieldName = uCase(trim(mapQid)) />
                        <cfelseif isStruct(qaItem) AND structKeyExists(qaItem, "answer_label") AND len(trim("" & qaItem.answer_label))>
                            <cfset fieldName = uCase(trim("" & qaItem.answer_label)) />
                        <cfelseif structKeyExists(answerLabelMap, mapQid)>
                            <cfset fieldName = answerLabelMap[mapQid] />
                        </cfif>

                        <cfif NOT reFind("^F[0-9]+$", fieldName)>
                            <cfset arrayAppend(skippedAnswers, mapQid & ":invalid_or_missing_answer_label_" & fieldName) />
                            <cfcontinue>
                        </cfif>

                        <cfset rawAnswer = qaItem.answer />
                        <cfset shouldUpdateField = false />

                        <!--- Skip null or empty values - no update intent --->
                        <cfif isNull(rawAnswer)>
                            <cfcontinue>
                        </cfif>

                        <cfif isArray(rawAnswer)>
                            <cfif arrayLen(rawAnswer) EQ 0>
                                <cfcontinue>
                            </cfif>
                            <cfset normalizedAnswer = normalizeWhisperFilledAnswerValue(rawAnswer) />
                            <cfset shouldUpdateField = len(normalizedAnswer) GT 0 />
                        <cfelseif isSimpleValue(rawAnswer)>
                            <cfset normalizedAnswer = trim("" & rawAnswer) />
                            <cfif NOT len(normalizedAnswer)>
                                <cfcontinue>
                            </cfif>
                            <cfset shouldUpdateField = true />
                        <cfelse>
                            <cfset normalizedAnswer = serializeJSON(rawAnswer) />
                            <cfset shouldUpdateField = true />
                        </cfif>

                        <cfif shouldUpdateField>
                            <cfset answerUpdates[fieldName] = normalizedAnswer />
                        </cfif>
                    </cfloop>
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Answer Mapping Error: " & cfcatch.message & " | Error while processing filled_answers iteration") />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 11: Apply mapped answers into the newly inserted assessment row --->
                <cfset currentStep = "Updating inserted assessment record with mapped answers (#structCount(answerUpdates)# F-columns)" />
                <cftry>
                    <cfif structCount(answerUpdates) GT 0>
                        <cfset answerApplyResult = applyMappedAnswersToAssessmentRow(
                            agency_db = agency_db,
                            insertedAutoAssmtID = insertedAutoAssmtID,
                            answerUpdates = answerUpdates,
                            validColumnList = assessmentColumnList,
                            created_by = created_by,
                            skippedAnswers = skippedAnswers,
                            stepErrors = stepErrors,
                            queryTrace = queryTrace
                        ) />
                        <cfset arrayAppend(queryTrace, "update_assessment_answers summary: prepared=" & answerApplyResult.prepared & ", applied=" & answerApplyResult.applied & ", skipped=" & answerApplyResult.skipped & ", batches=" & answerApplyResult.batches) />
                        <cfif answerApplyResult.applied EQ 0 AND answerApplyResult.prepared GT 0>
                            <cfset arrayAppend(stepErrors, "Assessment Update Warning: No F-columns were applied although " & answerApplyResult.prepared & " were prepared") />
                        </cfif>
                    <cfelse>
                        <cfset arrayAppend(queryTrace, "update_assessment_answers skipped: no mapped answers found. filled_answers keys may not map to assessment_questions_lookup.answer_label for assessment_reason='" & assessmentReasonForLookup & "'") />
                    </cfif>
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Database Update Error: " & cfcatch.message & (len(trim(cfcatch.detail)) ? " | Detail: " & cfcatch.detail : "") & " | Failed to update cloned pAssessments row AutoAssmt_ID=#insertedAutoAssmtID#") />
                        <cfset arrayAppend(queryTrace, "update_assessment_answers error: " & cfcatch.message & " | detail: " & cfcatch.detail) />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 12: Apply vital sign answers to pWoundsVitals for this Assmt_ID --->
                <cfset currentStep = "Updating pWoundsVitals from filled vital answers" />
                <cftry>
                    <cfset vitalImportResult = applyFilledVitalsToPWoundsVitals(
                        agency_db = agency_db,
                        assmt_id = arguments.Assessment_ID,
                        filledAnswers = filledAnswers,
                        created_by = created_by,
                        skippedAnswers = skippedAnswers,
                        queryTrace = queryTrace,
                        lookup_db = lookup_db
                    ) />
                    <cfset arrayAppend(queryTrace, "vitals_import summary: updated=" & vitalImportResult.updated & ", inserted=" & vitalImportResult.inserted & ", skipped=" & vitalImportResult.skipped) />
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Vital Import Error: " & cfcatch.message & " | Failed to update pWoundsVitals for Assmt_ID=#arguments.Assessment_ID#") />
                        <cfthrow>
                    </cfcatch>
                </cftry>

                <!--- Step 13: Apply wound answers to pWoundsVitals for this Assmt_ID --->
                <cfset currentStep = "Updating pWoundsVitals from filled wound answers" />
                <cftry>
                    <cfset woundImportResult = applyFilledWoundsToPWoundsVitals(
                        agency_db = agency_db,
                        assmt_id = arguments.Assessment_ID,
                        filledAnswers = filledAnswers,
                        created_by = created_by,
                        skippedAnswers = skippedAnswers,
                        queryTrace = queryTrace
                    ) />
                    <cfset arrayAppend(queryTrace, "wounds_import summary: updated=" & woundImportResult.updated & ", inserted=" & woundImportResult.inserted & ", skipped=" & woundImportResult.skipped) />
                    <cfcatch type="any">
                        <cfset arrayAppend(stepErrors, "Wound Import Error: " & cfcatch.message & " | Failed to update pWoundsVitals wound row for Assmt_ID=#arguments.Assessment_ID#") />
                        <cfthrow>
                    </cfcatch>
                </cftry>
            </cftransaction>
            <!--- Transaction auto-commits here if successful --->

            <cftry>
                <cfset markWhisperFileAIReviewRequired(
                    agency_db = agency_db,
                    assessment_id = arguments.Assessment_ID,
                    whisper_files_id = val(arguments.whisper_files_id)
                ) />
                <cfcatch type="any"></cfcatch>
            </cftry>
            
            <!--- Build success response --->
            <cfset response = {
                "success": true,
                "message": "Assessment data inserted successfully",
                "data": {
                    "assessment_id": arguments.Assessment_ID,
                    "source_auto_assmt_id": sourceAutoAssmtID,
                    "inserted_auto_assmt_id": insertedAutoAssmtID,
                    "errors": errors,
                    "step_errors": stepErrors,
                    "assessment_reason": assessmentReasonForLookup,
                    "mapped_answers": answerApplyResult.applied,
                    "mapped_answers_prepared": answerApplyResult.prepared,
                    "mapped_answers_skipped": answerApplyResult.skipped,
                    "vitals_updated": vitalImportResult.updated,
                    "vitals_inserted": vitalImportResult.inserted,
                    "vitals_skipped": vitalImportResult.skipped,
                    "wounds_updated": woundImportResult.updated,
                    "wounds_inserted": woundImportResult.inserted,
                    "wounds_skipped": woundImportResult.skipped,
                    "skipped_answers": skippedAnswers,
                    "job_id": effectiveJobID,
                    "job_status_response_used": jobStatusPayloadUsed,
                    "debug_info": {
                        "datasource": Application.DataSrc,
                        "agency_db": agency_db,
                        "transaction_completed": true,
                        "last_step": currentStep
                    }
                }
            } />

            <cfset requestSnapshot = {
                "method": "importAssessmentData",
                "request": {
                    "Agency_ID": arguments.Agency_ID,
                    "Emp_ID": arguments.Emp_ID,
                    "Patient_ID": arguments.Patient_ID,
                    "Assessment_ID": arguments.Assessment_ID,
                    "Website_name": arguments.Website_name,
                    "job_id": effectiveJobID,
                    "job_status_response_used": jobStatusPayloadUsed,
                    "filled_answers": filledAnswers,
                    "source_auto_assmt_id": sourceAutoAssmtID,
                    "inserted_auto_assmt_id": insertedAutoAssmtID,
                    "mapped_answer_updates": answerUpdates
                }
            } />
            <cfset requestJSONString = serializeJSON(requestSnapshot) />
            <cfset responseJSONString = serializeJSON(response) />

            <cfset sendImportAssessmentTraceEmail(
                agencyID = arguments.Agency_ID,
                empID = arguments.Emp_ID,
                patientID = arguments.Patient_ID,
                assessmentID = arguments.Assessment_ID,
                websiteName = arguments.Website_name,
                requestJSONString = requestJSONString,
                responseJSONString = responseJSONString,
                queryTrace = queryTrace,
                currentStep = currentStep,
                stepErrors = stepErrors,
                jobID = effectiveJobID,
                filledAnswers = filledAnswers,
                assessmentReason = assessmentReasonForLookup,
                lookup_db = lookup_db,
                agency_db = agency_db,
                answerLabelMap = answerLabelMap,
                answerUpdates = answerUpdates,
                skippedAnswers = skippedAnswers,
                vitalImportResult = vitalImportResult,
                woundImportResult = woundImportResult
            ) />

            <!--- <cfset sendAssessmentSuccessEmail(
                agencyID = arguments.Agency_ID,
                empID = arguments.Emp_ID,
                patientID = arguments.Patient_ID,
                assessmentID = arguments.Assessment_ID,
                websiteName = arguments.Website_name,
                assessmentReason = assessmentReasonForLookup,
                mappedAnswerCount = structCount(answerUpdates),
                skippedAnswers = skippedAnswers,
                stepErrors = stepErrors,
                jobID = effectiveJobID,
                extractedJSONString = extractedJSONString,
                agency_db = agency_db
            ) /> --->
            
            <cfcatch type="any">
                <!--- Send query dump email (even on error) --->
                <cfif structKeyExists(this, "sendQueryDumpEmail")>
                    <cfset sendQueryDumpEmail(
                        agencyID = arguments.Agency_ID,
                        empID = arguments.Emp_ID,
                        patientID = arguments.Patient_ID,
                        agency_db = agency_db
                    ) />
                </cfif>
                
                <!--- Send error email notification with step-by-step details --->
                <cfset sendErrorEmail(
                    error = cfcatch,
                    agencyID = arguments.Agency_ID,
                    empID = arguments.Emp_ID,
                    patientID = arguments.Patient_ID,
                    assessmentID = arguments.Assessment_ID,
                    websiteName = arguments.Website_name,
                    extractedJSONString = extractedJSONString,
                    originalAgencyID = originalAgencyID,
                    originalEmpID = originalEmpID,
                    originalPatientID = originalPatientID,
                    originalAssessmentID = originalAssessmentID,
                    originalAppKey = originalAppKey,
                    decryptedAgencyID = decryptedAgencyID,
                    decryptedEmpID = decryptedEmpID,
                    decryptedPatientID = decryptedPatientID,
                    decryptedAssessmentID = decryptedAssessmentID,
                    decryptedAppKey = decryptedAppKey,
                    insertedPatientID = insertedPatientID,
                    medicationCount = medicationCount,
                    problemCount = problemCount,
                    errors = errors,
                    stepErrors = stepErrors,
                    currentStep = currentStep
                ) />
                
                <cfset response = {
                    "success": false,
                    "message": "Failed to update assessment data",
                    "error": cfcatch.message,
                    "detail": cfcatch.detail,
                    "failed_at_step": currentStep,
                    "step_errors": stepErrors,
                    "data": {
                        "assessment_id": arguments.Assessment_ID,
                        "errors": errors
                    }
                } />

                <cfset requestSnapshot = {
                    "method": "importAssessmentData",
                    "request": {
                        "Agency_ID": arguments.Agency_ID,
                        "Emp_ID": arguments.Emp_ID,
                        "Patient_ID": arguments.Patient_ID,
                        "Assessment_ID": arguments.Assessment_ID,
                        "Website_name": arguments.Website_name,
                        "job_id": effectiveJobID,
                        "job_status_response_used": jobStatusPayloadUsed,
                        "filled_answers": filledAnswers,
                        "source_auto_assmt_id": sourceAutoAssmtID,
                        "inserted_auto_assmt_id": insertedAutoAssmtID,
                        "mapped_answer_updates": answerUpdates
                    }
                } />
                <cfset requestJSONString = serializeJSON(requestSnapshot) />
                <cfset responseJSONString = serializeJSON(response) />

                <cfset sendImportAssessmentTraceEmail(
                    agencyID = arguments.Agency_ID,
                    empID = arguments.Emp_ID,
                    patientID = arguments.Patient_ID,
                    assessmentID = arguments.Assessment_ID,
                    websiteName = arguments.Website_name,
                    requestJSONString = requestJSONString,
                    responseJSONString = responseJSONString,
                    queryTrace = queryTrace,
                    currentStep = currentStep,
                    stepErrors = stepErrors,
                    jobID = effectiveJobID,
                    filledAnswers = filledAnswers,
                    assessmentReason = assessmentReasonForLookup,
                    lookup_db = lookup_db,
                    agency_db = agency_db,
                    answerLabelMap = answerLabelMap,
                    answerUpdates = answerUpdates,
                    skippedAnswers = skippedAnswers,
                    vitalImportResult = vitalImportResult,
                    woundImportResult = woundImportResult
                ) />
            </cfcatch>
        </cftry>
        
        <cfreturn serializeJSON(response) />
    </cffunction>
    
</cfcomponent>

