<cfcomponent displayname="HHA Exchange EVV Functions" hint="HHA Exchange EVV integration for agency 1195" output="false">

    <cfset variables.ALLOWED_AGENCY_ID = 1195>
    <cfset variables.SENDTO_EMAIL = "jasmin.s@myhomecarebiz.com,velmurugan@myhomecarebiz.com,vicky.d@myhomecarebiz.com">
    <cfset variables.SENDTO_EMAIL = "velmurugan@myhomecarebiz.com">
    <cfset variables.SENDTO_EMAIL = "jasmin.s@myhomecarebiz.com,velmurugan@myhomecarebiz.com,vicky.d@myhomecarebiz.com">


    <cffunction name="init" access="public" returntype="hhaexchange" hint="Initialize component">
        <cfreturn this>
    </cffunction>

    <cffunction name="validateAgency" access="private" returntype="string" hint="Returns empty string when allowed">
        <cfargument name="Agency_ID" type="any" required="true">
        <cfif Val(arguments.Agency_ID) NEQ variables.ALLOWED_AGENCY_ID>
            <cfreturn "HHA Exchange EVV is only enabled for agency #variables.ALLOWED_AGENCY_ID#.">
        </cfif>
        <cfreturn "">
    </cffunction>

    <!--- True when schedule payer has EVV required by Payer = Yes; used to skip API + email when EVV is off --->
    <cffunction name="schedulePayerRequiresEVV" access="private" returntype="boolean">
        <cfargument name="scheduleID" type="numeric" required="true">
        <cfargument name="agencyID" type="numeric" required="false" default="#variables.ALLOWED_AGENCY_ID#">
        <cftry>
            <cfreturn createObject("component", "components.evv.EVVVisitAutomation").init().scheduleRequiresEVVAutomation(
                scheduleID = arguments.scheduleID,
                agencyID = arguments.agencyID
            )>
            <cfcatch type="any">
                <cflog file="HHAExchange_EVV" text="schedulePayerRequiresEVV check failed schedule #arguments.scheduleID#: #cfcatch.message#">
                <cfreturn false>
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="getConfig" access="private" returntype="struct">
        <cfset var cfg = {}>
        <cfif application.DEV EQ false>
            <!--- Production: Evergreen HC Group Inc DBA Evergreen Home Health (agency 1195) --->
            <cfset cfg.tokenUrl = "https://cloud.hhaexchange.com/identity/connect/token">
            <cfset cfg.caregiverUrl = "https://cloud.hhaexchange.com/api/v2/caregivers">
            <cfset cfg.visitUrl = "https://cloud.hhaexchange.com/api/v2/visits">
            <cfset cfg.transactionUrl = "https://cloud.hhaexchange.com/api/v2/visits/transactions/">
            <cfset cfg.clientId = "794ffc7a-c24f-418b-b0e1-59aecd87d43c">
            <cfset cfg.clientSecret = "1c66f3d9-1ff8-48d6-98a8-fbbcdc8c31ff">
        <cfelse>
            <!--- QA: Evergreen HC Group Inc DBA Evergreen Home Health (agency 1195) --->
            <cfset cfg.tokenUrl = "https://implementation.hhaexchange.com/identity/connect/token">
            <cfset cfg.caregiverUrl = "https://implementation.hhaexchange.com/api/v2/caregivers">
            <cfset cfg.visitUrl = "https://implementation.hhaexchange.com/api/v2/visits">
            <cfset cfg.transactionUrl = "https://implementation.hhaexchange.com/api/v2/visits/transactions/">
            <cfset cfg.clientId = "a6639ad4-f7fa-4287-8d55-96f7e6ea86de">
            <cfset cfg.clientSecret = "eeff9a57-ad27-46af-9f6a-676a15b7eb52">
        </cfif>
        <cfset cfg.scope = "write:aggregator">
        <cfreturn cfg>
    </cffunction>

    <cffunction name="getAgencySettings" access="private" returntype="query">
        <cfquery name="local.qAgency" datasource="#Application.DataSrc#">
            SELECT Agency_ID, Agency_Name, Agent_FEIN, Natl_Provider_ID, Agency_Medicaid_ID, timezone
            FROM #Request.prefix_db_lookup#.Agency
            WHERE Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
            AND status = 'active'
            LIMIT 1
        </cfquery>
        <cfreturn local.qAgency>
    </cffunction>

    <cffunction name="queryRowToStruct" access="private" returntype="struct">
        <cfargument name="q" type="query" required="true">
        <cfargument name="row" type="numeric" required="false" default="1">
        <cfset var result = {}>
        <cfif arguments.q.recordCount LT arguments.row>
            <cfreturn result>
        </cfif>
        <cfloop list="#arguments.q.columnList#" index="local.col">
            <cfset result[local.col] = arguments.q[local.col][arguments.row]>
        </cfloop>
        <cfreturn result>
    </cffunction>

    <cffunction name="digitsOnly" access="private" returntype="string">
        <cfargument name="value" type="string" required="true">
        <cfreturn REReplace(Trim(arguments.value), "[^0-9]", "", "all")>
    </cffunction>

    <cffunction name="formatGender" access="private" returntype="string">
        <cfargument name="value" type="string" required="true">
        <cfset var raw = UCase(Trim(arguments.value))>
        <cfif raw EQ "F" OR FindNoCase("female", raw) GT 0>
            <cfreturn "Female">
        </cfif>
        <cfif raw EQ "M" OR FindNoCase("male", raw) GT 0>
            <cfreturn "Male">
        </cfif>
        <cfreturn "Other">
    </cffunction>

    <cffunction name="formatCaregiverType" access="private" returntype="string">
        <cfargument name="skill1" type="string" required="true">
        <cfargument name="skill2" type="string" required="false" default="">
        <cfargument name="skill3" type="string" required="false" default="">
        <cfset var skills = UCase(Trim(arguments.skill1) & " " & Trim(arguments.skill2) & " " & Trim(arguments.skill3))>
        <cfset var skilled = "RN,PT,OT,ST,SN,PTA,OTA,MSW,SLP">
        <cfset var nonSkilled = "HHA,CNA">
        <cfset var hasSkilled = false>
        <cfset var hasNonSkilled = false>
        <cfreturn "Both">
        <cfloop list="#skilled#" index="local.skill">
            <cfif FindNoCase(local.skill, skills) GT 0>
                <cfset hasSkilled = true>
            </cfif>
        </cfloop>
        <cfloop list="#nonSkilled#" index="local.skill">
            <cfif FindNoCase(local.skill, skills) GT 0>
                <cfset hasNonSkilled = true>
            </cfif>
        </cfloop>
        <cfif hasSkilled AND hasNonSkilled>
            <cfreturn "Both">
        </cfif>
        <cfif hasSkilled>
            <cfreturn "Skilled">
        </cfif>
        <cfif hasNonSkilled>
            <cfreturn "Non-Skilled">
        </cfif>
        <cfreturn "Both">
    </cffunction>

    <cffunction name="getVisitTimezone" access="private" returntype="string">
        <cfset var tz = "US/Eastern">
        <cfif StructKeyExists(application, "TimeZone")>
            <cfswitch expression="#application.TimeZone#">
                <cfcase value="CST"><cfset tz = "US/Central"></cfcase>
                <cfcase value="MST"><cfset tz = "US/Mountain"></cfcase>
                <cfcase value="PST"><cfset tz = "US/Pacific"></cfcase>
                <cfcase value="AKST"><cfset tz = "US/Alaska"></cfcase>
                <cfcase value="HST"><cfset tz = "US/Hawaii"></cfcase>
            </cfswitch>
        </cfif>
        <cfreturn tz>
    </cffunction>

    <cffunction name="splitAddress" access="private" returntype="struct">
        <cfargument name="street" type="string" required="true">
        <cfset var result = { line1 = "", line2 = "" }>
        <cfset var trimmed = Trim(arguments.street)>
        <cfif Len(trimmed) GT 30>
            <cfset result.line1 = Left(trimmed, 30)>
            <cfset result.line2 = Mid(trimmed, 31, Len(trimmed) - 30)>
        <cfelse>
            <cfset result.line1 = trimmed>
        </cfif>
        <cfreturn result>
    </cffunction>

    <cffunction name="formatZipcode" access="private" returntype="string">
        <cfargument name="zip" type="string" required="true">
        <cfset var digits = Left(digitsOnly(arguments.zip), 9)>
        <cfif Len(digits) EQ 0>
            <cfreturn "00000">
        </cfif>
        <cfreturn digits>
    </cffunction>

    <cffunction name="formatDateField" access="private" returntype="string">
        <cfargument name="value" type="any" required="true">
        <cfif IsDate(arguments.value)>
            <cfreturn DateFormat(arguments.value, "yyyy-mm-dd")>
        </cfif>
        <cfreturn DateFormat(Now(), "yyyy-mm-dd")>
    </cffunction>

    <cffunction name="formatHireDateField" access="private" returntype="string">
        <cfargument name="value" type="any" required="true">
        <cfif IsDate(arguments.value)>
            <cfreturn DateFormat(arguments.value, "dd-mm-yyyy")>
        </cfif>
        <cfreturn DateFormat(Now(), "dd-mm-yyyy")>
    </cffunction>

    <cffunction name="formatISODateTime" access="private" returntype="string">
        <cfargument name="value" type="any" required="true">
        <cfif IsDate(arguments.value)>
            <cfreturn DateFormat(arguments.value, "yyyy-mm-dd") & "T" & TimeFormat(arguments.value, "HH:mm:ss") & ".000Z">
        </cfif>
        <cfreturn DateFormat(Now(), "yyyy-mm-dd") & "T" & TimeFormat(Now(), "HH:mm:ss") & ".000Z">
    </cffunction>

    <cffunction name="formatDateTimeField" access="private" returntype="string">
        <cfargument name="dateValue" type="any" required="true">
        <cfargument name="timeValue" type="any" required="true">
        <cfif IsDate(arguments.dateValue) AND IsDate(arguments.timeValue)>
            <cfreturn DateFormat(arguments.dateValue, "yyyy-mm-dd") & "T" & TimeFormat(arguments.timeValue, "HH:mm")>
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="formatCallDateTime" access="private" returntype="string">
        <cfargument name="dateValue" type="any" required="true">
        <cfargument name="timeValue" type="any" required="true">
        <cfif IsDate(arguments.dateValue) AND IsDate(arguments.timeValue)>
            <cfreturn DateFormat(arguments.dateValue, "yyyy-mm-dd") & "T" & TimeFormat(arguments.timeValue, "HH:mm:ss")>
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="toJsonInteger" access="private" returntype="any">
        <cfargument name="value" type="numeric" required="true">
        <cfreturn javaCast("int", Int(arguments.value))>
    </cffunction>

    <cffunction name="toJsonDouble" access="private" returntype="any">
        <cfargument name="value" type="numeric" required="true">
        <cfreturn javaCast("double", Val(arguments.value))>
    </cffunction>

    <cffunction name="formatProcedureCode" access="private" returntype="string">
        <cfargument name="hcpcs" type="string" required="true">
        <cfargument name="modifier" type="string" required="false" default="">
        <cfset var code = Trim(arguments.hcpcs)>
        <cfset var procModifier = Trim(arguments.modifier)>
        <cfif Len(code) EQ 0>
            <cfreturn "">
        </cfif>
        <cfif Len(procModifier) GT 0>
            <cfreturn code & ":" & procModifier>
        </cfif>
        <cfreturn code>
    </cffunction>

    <cffunction name="normalizeEvvmsid" access="private" returntype="string">
        <cfargument name="value" type="string" required="false" default="">
        <cfset var raw = UCase(Trim(arguments.value))>
        <cfif Len(raw) GTE 36>
            <cfreturn raw>
        </cfif>
        <cfreturn UCase(CreateUUID())>
    </cffunction>

    <cffunction name="formatEmployeeSSN" access="private" returntype="string">
        <cfargument name="ssnValue" type="string" required="true">
        <cfset var ssn = digitsOnly(arguments.ssnValue)>
        <cfif Len(ssn) LT 9>
            <cfreturn "">
        </cfif>
        <cfreturn Right(ssn, 9)>
    </cffunction>

    <cffunction name="formatMedicaidID" access="private" returntype="string">
        <cfargument name="rawMedicaidId" type="string" required="true">
        <cfset var raw = Trim(arguments.rawMedicaidId)>
        <cfif Len(raw) EQ 0>
            <cfreturn "">
        </cfif>
        <cfif ReFind("^[0-9]{9}$", raw) GT 0>
            <cfreturn raw>
        </cfif>
        <cfset var digits = digitsOnly(raw)>
        <cfif Len(digits) EQ 0>
            <cfreturn "">
        </cfif>
        <cfreturn Right("000000000" & digits, 9)>
    </cffunction>

    <cffunction name="getCaregiverIdentifier" access="private" returntype="string">
        <cfargument name="empOrSched" type="struct" required="true">
        <cfif StructKeyExists(arguments.empOrSched, "Emp_SSN")>
            <cfreturn formatEmployeeSSN(arguments.empOrSched.Emp_SSN)>
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="getEmployeeInternalNumber" access="private" returntype="string">
        <cfargument name="sched" type="struct" required="true">
        <cfif StructKeyExists(arguments.sched, "Emp_Custom_1") AND Len(Trim(arguments.sched.Emp_Custom_1)) GT 0>
            <cfreturn Trim(arguments.sched.Emp_Custom_1)>
        </cfif>
        <cfreturn ToString(arguments.sched.Emp_ID)>
    </cffunction>

    <cffunction name="calculateVisitHours" access="private" returntype="numeric">
        <cfargument name="sched" type="struct" required="true">
        <cfif StructKeyExists(arguments.sched, "totalhours") AND IsNumeric(arguments.sched.totalhours) AND Val(arguments.sched.totalhours) GT 0>
            <cfreturn Val(arguments.sched.totalhours)>
        </cfif>
        <cfif IsDate(arguments.sched.Visit_Date) AND IsDate(arguments.sched.StartTime) AND IsDate(arguments.sched.EndTime)>
            <cfset var startDateTime = CreateDateTime(Year(arguments.sched.Visit_Date), Month(arguments.sched.Visit_Date), Day(arguments.sched.Visit_Date),
                ListGetAt(arguments.sched.StartTime, 1, ":"), ListGetAt(arguments.sched.StartTime, 2, ":"), 0)>
            <cfset var endDateTime = CreateDateTime(Year(arguments.sched.Visit_Date), Month(arguments.sched.Visit_Date), Day(arguments.sched.Visit_Date),
                ListGetAt(arguments.sched.EndTime, 1, ":"), ListGetAt(arguments.sched.EndTime, 2, ":"), 0)>
            <cfif endDateTime LTE startDateTime>
                <cfset endDateTime = DateAdd("d", 1, endDateTime)>
            </cfif>
            <cfreturn DateDiff("s", startDateTime, endDateTime) / 3600>
        </cfif>
        <cfreturn 0>
    </cffunction>

    <cffunction name="getVisitDiagnosisCodes" access="private" returntype="array">
        <cfargument name="Assmt_ID" type="numeric" required="true">
        <cfset var codes = []>
        <cfif Val(arguments.Assmt_ID) LTE 0>
            <cfreturn codes>
        </cfif>
        <cfquery name="local.qDx" datasource="#Application.DataSrc#">
            SELECT Description
            FROM #Request.prefix_db_agency#.pProblem
            WHERE Assmt_ID = <cfqueryparam value="#arguments.Assmt_ID#" cfsqltype="cf_sql_integer">
            AND Status = 0
            AND (M0_No LIKE 'M1021%' OR M0_No LIKE 'M1023%')
            ORDER BY dx_order, RIGHT(M0_No, 1) ASC
            LIMIT 5
        </cfquery>
        <cfloop query="local.qDx">
            <cfif Len(Trim(local.qDx.Description)) GT 0>
                <cfset arrayAppend(codes, Trim(local.qDx.Description))>
            </cfif>
        </cfloop>
        <cfreturn codes>
    </cffunction>

    <cffunction name="getPerformedTasks" access="private" returntype="array">
        <cfargument name="sched" type="struct" required="true">
        <cfset var tasks = []>
        <cfif Val(arguments.sched.Schedule_ID) GT 0>
            <cfquery name="local.qTasks" datasource="#Application.DataSrc#">
                SELECT DISTINCT LEFT(pProgress.Description, 3) AS taskCode
                FROM #Request.prefix_db_agency#.pProgress
                WHERE Schedule_ID = <cfqueryparam value="#arguments.sched.Schedule_ID#" cfsqltype="cf_sql_integer">
                AND Status = 0
                AND Description IS NOT NULL
                AND TRIM(Description) != ''
                AND LEFT(Description, 3) REGEXP '^[0-9]{3}$'
                LIMIT 10
            </cfquery>
            <cfloop query="local.qTasks">
                <cfset arrayAppend(tasks, { "code": Trim(local.qTasks.taskCode) })>
            </cfloop>
        </cfif>
        <cfif ArrayLen(tasks) EQ 0>
            <cfset tasks = [ { "code": "101" } ]>
        </cfif>
        <cfreturn tasks>
    </cffunction>

    <cffunction name="getAccessToken" access="private" returntype="struct">
        <cfset var cfg = getConfig()>
        <cfset var result = { success = false, token = "", error = "" }>
        <cfset var body = "grant_type=client_credentials&client_id=" & URLEncodedFormat(cfg.clientId) & "&client_secret=" & URLEncodedFormat(cfg.clientSecret) & "&scope=" & URLEncodedFormat(cfg.scope)>
        <cfhttp url="#cfg.tokenUrl#" method="post" timeout="60" result="local.httpResult">
            <cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
            <cfhttpparam type="body" value="#body#">
        </cfhttp>
        <cfif Left(local.httpResult.StatusCode, 3) EQ "200" AND Len(Trim(local.httpResult.FileContent)) GT 0>
            <cfset var tokenData = DeserializeJSON(local.httpResult.FileContent)>
            <cfif StructKeyExists(tokenData, "access_token") AND Len(Trim(tokenData.access_token)) GT 0>
                <cfset result.success = true>
                <cfset result.token = tokenData.access_token>
                <cfreturn result>
            </cfif>
        </cfif>
        <cfset result.error = "Unable to obtain HHA Exchange access token. HTTP #local.httpResult.StatusCode# - #local.httpResult.FileContent#">
        <cfreturn result>
    </cffunction>

    <cffunction name="apiRequest" access="private" returntype="struct">
        <cfargument name="method" type="string" required="true">
        <cfargument name="url" type="string" required="true">
        <cfargument name="payload" type="any" required="false" default="">
        <cfset var auth = getAccessToken()>
        <cfset var response = { success = false, statusCode = "", body = "", error = "" }>
        <cfif NOT auth.success>
            <cfset response.error = auth.error>
            <cfreturn response>
        </cfif>
        <cfhttp url="#arguments.url#" method="#UCase(arguments.method)#" timeout="120" result="local.httpResult">
            <cfhttpparam type="header" name="Authorization" value="Bearer #auth.token#">
            <cfhttpparam type="header" name="Content-Type" value="application/json">
            <cfhttpparam type="header" name="Accept" value="application/json">
            <cfif Len(Trim(arguments.payload)) GT 0>
                <cfhttpparam type="body" value="#arguments.payload#">
            </cfif>
        </cfhttp>
        <cfset response.statusCode = local.httpResult.StatusCode>
        <cfset response.body = local.httpResult.FileContent>
        <cfif Left(local.httpResult.StatusCode, 3) EQ "200" OR Left(local.httpResult.StatusCode, 3) EQ "201" OR Left(local.httpResult.StatusCode, 3) EQ "202">
            <cfset response.success = true>
        <cfelse>
            <cfset response.error = "HTTP #local.httpResult.StatusCode# - #local.httpResult.FileContent#">
        </cfif>
        <cfreturn response>
    </cffunction>

    <cffunction name="extractTransactionId" access="private" returntype="string">
        <cfargument name="responseBody" type="string" required="true">
        <cfif Len(Trim(arguments.responseBody)) GT 0 AND IsJSON(arguments.responseBody)>
            <cfset var responseData = DeserializeJSON(arguments.responseBody)>
            <cfif StructKeyExists(responseData, "transactionId") AND Len(Trim(responseData.transactionId)) GT 0>
                <cfreturn Trim(responseData.transactionId)>
            </cfif>
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="extractTransactionStatusUrl" access="private" returntype="string">
        <cfargument name="responseBody" type="string" required="true">
        <cfif NOT Len(Trim(arguments.responseBody)) OR NOT IsJSON(arguments.responseBody)>
            <cfreturn "">
        </cfif>
        <cfset var responseData = DeserializeJSON(arguments.responseBody)>
        <cfif NOT StructKeyExists(responseData, "links")>
            <cfreturn "">
        </cfif>
        <cfset var links = responseData.links>
        <cfif IsStruct(links) AND StructKeyExists(links, "href") AND Len(Trim(links.href)) GT 0>
            <cfif NOT StructKeyExists(links, "rel") OR LCase(Trim(links.rel)) EQ "status">
                <cfreturn Trim(links.href)>
            </cfif>
        </cfif>
        <cfif IsArray(links)>
            <cfloop array="#links#" index="local.linkItem">
                <cfif IsStruct(local.linkItem) AND StructKeyExists(local.linkItem, "href") AND Len(Trim(local.linkItem.href)) GT 0>
                    <cfif NOT StructKeyExists(local.linkItem, "rel") OR LCase(Trim(local.linkItem.rel)) EQ "status">
                        <cfreturn Trim(local.linkItem.href)>
                    </cfif>
                </cfif>
            </cfloop>
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="buildTransactionStatusUrls" access="private" returntype="array">
        <cfargument name="responseBody" type="string" required="false" default="">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfset var urls = []>
        <cfset var statusUrl = extractTransactionStatusUrl(arguments.responseBody)>
        <cfset var cfg = getConfig()>
        <cfset var txnId = Trim(arguments.transactionId)>

        <cfif Len(statusUrl) GT 0>
            <cfset arrayAppend(urls, statusUrl)>
        </cfif>
        <cfif Len(txnId) GT 0>
            <cfset arrayAppend(urls, cfg.transactionUrl & txnId)>
            <cfset arrayAppend(urls, Replace(cfg.transactionUrl, "/api/v2/", "/api/v1/", "all") & txnId)>
        </cfif>

        <cfreturn urls>
    </cffunction>

    <cffunction name="fetchTransactionStatusResponse" access="private" returntype="struct">
        <cfargument name="responseBody" type="string" required="true">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfset var result = { success = false, url = "", body = "", error = "" }>
        <cfset var statusUrls = buildTransactionStatusUrls(arguments.responseBody, arguments.transactionId)>
        <cfset var statusUrl = "">
        <cfset var apiResponse = "">

        <cfif ArrayLen(statusUrls) EQ 0>
            <cfreturn result>
        </cfif>

        <cfloop array="#statusUrls#" index="statusUrl">
            <cfset apiResponse = apiRequest("GET", statusUrl, "")>
            <cfif apiResponse.success AND Len(Trim(apiResponse.body)) GT 0>
                <cfset result.success = true>
                <cfset result.url = statusUrl>
                <cfset result.body = apiResponse.body>
                <cfreturn result>
            </cfif>
            <cfif NOT Len(result.error)>
                <cfset result.error = apiResponse.error>
            </cfif>
        </cfloop>

        <cfset result.url = statusUrls[1]>
        <cfreturn result>
    </cffunction>

    <cffunction name="buildCaregiverPayload" access="private" returntype="struct">
        <cfargument name="emp" type="struct" required="true">
        <cfargument name="agency" type="struct" required="true">
        <cfset var providerTaxId = digitsOnly(arguments.agency.Agent_FEIN)>
        <cfif Len(providerTaxId) EQ 0>
            <cfset providerTaxId = "824026167">
        </cfif>
         <cfset var externalId = formatEmployeeSSN(arguments.emp.Emp_SSN)>
        <cfif Len(externalId) EQ 0>
            <cfset externalId = ToString(arguments.emp.Emp_ID)>
        </cfif>
        <cfset var employeeId = Len(Trim(arguments.emp.Emp_Custom_1)) GT 0 ? Trim(arguments.emp.Emp_Custom_1) : externalId>
        <cfset var licenseNumber = Len(Trim(arguments.emp.Emp_Custom_1)) GT 0 ? Trim(arguments.emp.Emp_Custom_1) : externalId>
        <cfset var npiValue = Len(Trim(arguments.emp.Phys_NPI)) GT 0 ? Trim(arguments.emp.Phys_NPI) : externalId>
        <cfset var ssn = formatEmployeeSSN(arguments.emp.Emp_SSN)>
        <cfif Len(ssn) EQ 0>
            <cfset ssn = Right("000000000" & digitsOnly(arguments.emp.Emp_SSN), 9)>
        </cfif>
        <cfset var phone = Right(digitsOnly(arguments.emp.Emp_Telephone), 10)>
        <cfif Len(phone) LT 10>
            <cfset phone = "1111123457">
        </cfif>
        <cfset var email = Trim(arguments.emp.Emp_Email)>
        <cfif Len(email) EQ 0 OR NOT IsValid("email", email)>
            <cfset email = "noemail@myhomecarebiz.com">
        </cfif>
        <cfset var addr = splitAddress(arguments.emp.Emp_Street1)>
        <cfset var hireDateValue = formatDateField(arguments.emp.Date_Hire)>
        <cfreturn {
            "providerTaxId": providerTaxId,
            "qualifier": "ExternalID",
            "externalID": externalId,
            "npi": npiValue,
            "ssn": ssn,
            "dateOfBirth": formatDateField(arguments.emp.Emp_DOB),
            "lastName": Trim(arguments.emp.Emp_Last),
            "firstName": Trim(arguments.emp.Emp_First),
            "gender": formatGender(arguments.emp.Emp_Gender),
            "email": email,
            "phoneNumber": phone,
            "type": formatCaregiverType(arguments.emp.Skill_1, arguments.emp.Skill_2, arguments.emp.Skill_3),
            "stateRegistrationID": employeeId,
            "professionalLicenseNumber": licenseNumber,
            "hireDate": hireDateValue,
            "address": {
                "addressLine1": addr.line1,
                "addressLine2": Len(Trim(arguments.emp.Emp_Street2)) GT 0 ? Trim(arguments.emp.Emp_Street2) : addr.line2,
                "city": Trim(arguments.emp.Emp_City),
                "state": UCase(Left(Trim(arguments.emp.Emp_State), 2)),
                "zipcode": formatZipcode(arguments.emp.Emp_Zip)
            },
            "employeeId": employeeId,
            "stateRegistry": {
                "number": licenseNumber,
                "date": formatISODateTime(arguments.emp.Date_Hire)
            }
        }>
    </cffunction>

    <cffunction name="validateCaregiverData" access="private" returntype="string">
        <cfargument name="emp" type="struct" required="true">
        <cfif StructIsEmpty(arguments.emp)>
            <cfreturn "Employee not found.">
        </cfif>
        <cfif Len(Trim(arguments.emp.Emp_Last)) EQ 0 OR Len(Trim(arguments.emp.Emp_First)) EQ 0>
            <cfreturn "Employee first name and last name are required.">
        </cfif>
        <cfif Len(digitsOnly(arguments.emp.Emp_SSN)) LT 9>
            <cfreturn "Employee SSN is required and must be 9 digits.">
        </cfif>
        <cfif NOT IsDate(arguments.emp.Emp_DOB)>
            <cfreturn "Employee date of birth is required.">
        </cfif>
        <cfif NOT IsDate(arguments.emp.Date_Hire)>
            <cfreturn "Employee hire date is required.">
        </cfif>
        <cfif Len(Trim(arguments.emp.Emp_Street1)) EQ 0
            OR Len(Trim(arguments.emp.Emp_City)) EQ 0
            OR Len(Trim(arguments.emp.Emp_State)) EQ 0
            OR Len(Trim(arguments.emp.Emp_Zip)) EQ 0>
            <cfreturn "Employee address is incomplete. Street, city, state, and zip are required.">
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="getEmployeeData" access="private" returntype="struct">
        <cfargument name="Emp_ID" type="numeric" required="true">
        <cfquery name="local.qEmp" datasource="#Application.DataSrc#">
            SELECT e.*, p.Phys_NPI
            FROM #Request.prefix_db_lookup#.pEmployee e
            LEFT JOIN #Request.prefix_db_lookup#.pPhysicians p ON p.Emp_ID = e.Emp_ID AND p.Status = 0
            WHERE e.Status = 0
            AND e.Emp_ID = <cfqueryparam value="#arguments.Emp_ID#" cfsqltype="cf_sql_integer">
            LIMIT 1
        </cfquery>
        <cfif local.qEmp.recordcount EQ 0>
            <cfreturn {}>
        </cfif>
        <cfreturn queryRowToStruct(local.qEmp)>
    </cffunction>

    <cffunction name="getPatientMemberData" access="private" returntype="struct">
        <cfargument name="Patient_ID" type="numeric" required="true">
        <cfargument name="Pay_ID" type="numeric" required="false" default="0">
        <cfif Val(arguments.Pay_ID) GT 0>
            <cfquery name="local.qPatient" datasource="#Application.DataSrc#">
                SELECT p.Patient_ID, p.Pt_First, p.Pt_Middle, p.Pt_Last, p.Pt_Agy_ID, p.Pt_Street, p.Pt_City, p.Pt_State, p.Pt_Zip,
                       p.Pt_Phone, p.Pt_DOB, p.Pt_Gender, p.Pt_SSN, p.Pt_Latitude, p.Pt_Longitude, p.Email,
                       pp.Sub_HIC, pp.PtPayer_ID, pp.Pt_Relationship, py.EVV_Payer_ID, py.Pay_Name, py.Pay_ID, py.PayerSource AS Payer_Source
                FROM #Request.prefix_db_agency#.pPatients p
                JOIN #Request.prefix_db_agency#.pPayer py ON py.Pay_ID = <cfqueryparam value="#arguments.Pay_ID#" cfsqltype="cf_sql_integer"> AND py.Status = 0
                LEFT JOIN #Request.prefix_db_agency#.pPtPayer pp ON pp.Patient_ID = p.Patient_ID
                    AND pp.Payer_ID = py.Pay_ID AND pp.Status = 0
                WHERE p.Patient_ID = <cfqueryparam value="#arguments.Patient_ID#" cfsqltype="cf_sql_integer">
                AND p.Status IN (0,1,4)
                ORDER BY pp.PtPayer_ID DESC
                LIMIT 1
            </cfquery>
        <cfelse>
            <cfquery name="local.qPatient" datasource="#Application.DataSrc#">
                SELECT p.Patient_ID, p.Pt_First, p.Pt_Middle, p.Pt_Last, p.Pt_Agy_ID, p.Pt_Street, p.Pt_City, p.Pt_State, p.Pt_Zip,
                       p.Pt_Phone, p.Pt_DOB, p.Pt_Gender, p.Pt_SSN, p.Pt_Latitude, p.Pt_Longitude, p.Email,
                       pp.Sub_HIC, pp.PtPayer_ID, pp.Pt_Relationship, py.EVV_Payer_ID, py.Pay_Name, py.Pay_ID, py.PayerSource AS Payer_Source
                FROM #Request.prefix_db_agency#.pPatients p
                LEFT JOIN #Request.prefix_db_agency#.pPtPayer pp ON pp.Patient_ID = p.Patient_ID AND pp.Status = 0
                LEFT JOIN #Request.prefix_db_agency#.pPayer py ON py.Pay_ID = pp.Payer_ID AND py.Status = 0
                WHERE p.Patient_ID = <cfqueryparam value="#arguments.Patient_ID#" cfsqltype="cf_sql_integer">
                AND p.Status IN (0,1,4)
                ORDER BY pp.PtPayer_ID DESC
                LIMIT 1
            </cfquery>
        </cfif>
        <cfif local.qPatient.recordcount EQ 0>
            <cfreturn {}>
        </cfif>
        <cfset var medicaidId = formatMedicaidID(local.qPatient.Sub_HIC)>
        <cfset var admissionId = Trim(local.qPatient.Pt_Agy_ID)>
        <cfif Len(admissionId) EQ 0>
            <cfset admissionId = ToString(local.qPatient.Patient_ID)>
        </cfif>
        <cfset var payerId = Trim(local.qPatient.EVV_Payer_ID)>
        <cfif Len(payerId) EQ 0>
            <cfset payerId = "MI73">
        </cfif>
        <cfset var eligibilityStart = "">
        <cfset var eligibilityEnd = "">
        <cfif Val(local.qPatient.PtPayer_ID) GT 0>
            <cfquery name="local.qElig" datasource="#Application.DataSrc#">
                SELECT Start_Date, Expiration_Date
                FROM #Request.prefix_db_agency#.Eligibility
                WHERE Patient_ID = <cfqueryparam value="#arguments.Patient_ID#" cfsqltype="cf_sql_integer">
                AND PtPayer_ID = <cfqueryparam value="#local.qPatient.PtPayer_ID#" cfsqltype="cf_sql_integer">
                AND Status = 0
                ORDER BY Elig_ID DESC
                LIMIT 1
            </cfquery>
            <cfif local.qElig.recordcount GT 0>
                <cfif IsDate(local.qElig.Start_Date)>
                    <cfset eligibilityStart = local.qElig.Start_Date>
                </cfif>
                <cfif IsDate(local.qElig.Expiration_Date)>
                    <cfset eligibilityEnd = local.qElig.Expiration_Date>
                </cfif>
            </cfif>
        </cfif>
        <!--- Pt_Relationship is stored as "code label" (e.g. "18 Self"); HHAX expects the code only --->
        <cfset var relationshipToInsured = Trim(local.qPatient.Pt_Relationship)>
        <cfif ListLen(relationshipToInsured, " ") GT 1>
            <cfset relationshipToInsured = ListFirst(relationshipToInsured, " ")>
        </cfif>
        <cfif Len(relationshipToInsured) EQ 0 OR relationshipToInsured EQ "x">
            <cfset relationshipToInsured = "18">
        </cfif>
        <cfset var claimFilingCode = Trim(local.qPatient.Payer_Source)>
        <cfif Len(claimFilingCode) EQ 0>
            <cfset claimFilingCode = "MC">
        </cfif>
        <cfreturn {
            qualifier = "MedicaidID",
            identifier = medicaidId,
            admissionID = admissionId,
            payerId = payerId,
            payerName = Trim(local.qPatient.Pay_Name),
            relationshipToInsured = relationshipToInsured,
            claimFilingCode = claimFilingCode,
            eligibilityStart = eligibilityStart,
            eligibilityEnd = eligibilityEnd,
            patient = queryRowToStruct(local.qPatient)
        }>
    </cffunction>

    <cffunction name="buildPatientPayload" access="private" returntype="struct">
        <cfargument name="memberData" type="struct" required="true">
        <cfargument name="agency" type="struct" required="true">
        <cfset var pat = arguments.memberData.patient>
        <cfset var providerTaxId = digitsOnly(arguments.agency.Agent_FEIN)>
        <cfif Len(providerTaxId) EQ 0>
            <cfset providerTaxId = "824026167">
        </cfif>
         <cfset var patientAddr = splitAddress(pat.Pt_Street)>
        <cfset var phone = Right(digitsOnly(pat.Pt_Phone), 10)>
        <cfset var email = Trim(pat.Email)>
        <cfif Len(email) EQ 0 OR NOT IsValid("email", email)>
            <cfset email = "noemail@myhomecarebiz.com">
        </cfif>
        <cfset var ssn = digitsOnly(pat.Pt_SSN)>
        <cfreturn {
            "providerTaxId": providerTaxId,
            "payerId": arguments.memberData.payerId,
            "member": {
                "qualifier": arguments.memberData.qualifier,
                "identifier": arguments.memberData.identifier 
            },
            "patient": {
                "patientId": pat.Patient_ID,
                "firstName": Trim(pat.Pt_First),
                "middleName": Trim(pat.Pt_Middle),
                "lastName": Trim(pat.Pt_Last),
                "dateOfBirth": formatDateField(pat.Pt_DOB),
                "gender": formatGender(pat.Pt_Gender),
                "ssn": Len(ssn) GT 0 ? ssn : "999999999",
                "phoneNumber": Len(phone) GTE 10 ? phone : "1111123457",
                "email": email,
                "address": {
                    "addressLine1": patientAddr.line1,
                    "addressLine2": patientAddr.line2,
                    "city": Trim(pat.Pt_City),
                    "state": UCase(Left(Trim(pat.Pt_State), 2)),
                    "zipcode": formatZipcode(pat.Pt_Zip)
                },
                "latitude": Val(pat.Pt_Latitude),
                "longitude": Val(pat.Pt_Longitude)
            }
        }>
    </cffunction>

    <cffunction name="buildVisitPayload" access="private" returntype="struct">
        <cfargument name="sched" type="struct" required="true">
        <cfargument name="agency" type="struct" required="true">
        <cfargument name="memberData" type="struct" required="true">
        <cfargument name="evvmsid" type="string" required="false" default="">
        <cfargument name="isEditVisit" type="boolean" required="false" default="true">
        <cfset var providerTaxId = digitsOnly(arguments.agency.Agent_FEIN)>
        <cfif Len(providerTaxId) EQ 0>
            <cfset providerTaxId = "824026167">
        </cfif>

        <cfset var officeNpi = Trim(arguments.agency.Natl_Provider_ID)>
        <cfif Len(officeNpi) EQ 0>
            <cfset officeNpi = "1184225021">
        </cfif>
        <cfset var memberIdentifier = Trim(arguments.memberData.identifier)>
        <cfset var caregiverId = getCaregiverIdentifier(arguments.sched)>
        <cfset var employeeInternalNumber = getEmployeeInternalNumber(arguments.sched)>
        <cfset var patientAddr = splitAddress(arguments.sched.Pt_Street)>
        <cfset var addressLine2 = patientAddr.line2>
        <cfif StructKeyExists(arguments.sched, "Pt_Street2") AND Len(Trim(arguments.sched.Pt_Street2)) GT 0>
            <cfset addressLine2 = Trim(arguments.sched.Pt_Street2)>
        </cfif>
        <cfset var serviceAddress = {
            "addressLine1": patientAddr.line1,
            "addressLine2": addressLine2,
            "city": Trim(arguments.sched.Pt_City),
            "state": UCase(Left(Trim(arguments.sched.Pt_State), 2)),
            "zipcode": formatZipcode(arguments.sched.Pt_Zip)
        }>
        <cfset var scheduleStart = formatDateTimeField(arguments.sched.Visit_Date, arguments.sched.StartTime)>
        <cfset var scheduleEnd = formatDateTimeField(arguments.sched.Visit_Date, arguments.sched.EndTime)>
        <cfset var visitStart = scheduleStart>
        <cfset var visitEnd = scheduleEnd>
        <cfset var clockInDate = arguments.sched.Visit_Date>
        <cfset var clockInTime = arguments.sched.StartTime>
        <cfset var clockOutDate = arguments.sched.Visit_Date>
        <cfset var clockOutTime = arguments.sched.EndTime>
        <cfif IsDate(arguments.sched.GPS_start_time)>
            <cfset clockInTime = arguments.sched.GPS_start_time>
            <cfset visitStart = formatDateTimeField(arguments.sched.Visit_Date, clockInTime)>
        </cfif>
        <cfif IsDate(arguments.sched.GPS_end_time)>
            <cfset clockOutTime = arguments.sched.GPS_end_time>
            <cfset visitEnd = formatDateTimeField(arguments.sched.Visit_Date, clockOutTime)>
        </cfif>
        <cfset var clockInLat = Val(arguments.sched.GPS_start_latitude)>
        <cfset var clockInLon = Val(arguments.sched.GPS_start_longitude)>
        <cfset var clockOutLat = Val(arguments.sched.GPS_end_latitude)>
        <cfset var clockOutLon = Val(arguments.sched.GPS_end_longitude)>
        <cfif clockInLat EQ 0>
            <cfset clockInLat = Val(arguments.sched.Sch_Latitude)>
        </cfif>
        <cfif clockInLon EQ 0>
            <cfset clockInLon = Val(arguments.sched.Sch_Longitude)>
        </cfif>
        <cfif clockOutLat EQ 0>
            <cfset clockOutLat = clockInLat>
        </cfif>
        <cfif clockOutLon EQ 0>
            <cfset clockOutLon = clockInLon>
        </cfif>
        <!--- Phone comes from pPatients.Pt_Phone (via member data), falling back to schedule join --->
        <cfset var rawPhone = "">
        <cfif StructKeyExists(arguments.memberData, "patient") AND StructKeyExists(arguments.memberData.patient, "Pt_Phone")>
            <cfset rawPhone = arguments.memberData.patient.Pt_Phone>
        </cfif>
        <cfif Len(digitsOnly(rawPhone)) EQ 0 AND StructKeyExists(arguments.sched, "Pt_Phone")>
            <cfset rawPhone = arguments.sched.Pt_Phone>
        </cfif>
        <cfset var phone = Right(digitsOnly(rawPhone), 10)>
        <cfif Len(phone) LT 10>
            <cfset phone = "2281111111">
        </cfif>
        <cfset var hcpcsCode = Trim(arguments.sched.HCPCS)>
        <cfset var modifierValue = StructKeyExists(arguments.sched, "Rev_Code") ? Trim(arguments.sched.Rev_Code) : "">
        <cfset var procedureCode = formatProcedureCode(hcpcsCode, modifierValue)>
        <cfset var payerId = Trim(arguments.memberData.payerId)>
        <cfset var payerName = Len(Trim(arguments.memberData.payerName)) GT 0 ? Trim(arguments.memberData.payerName) : "Medicaid">
        <!--- relationshipToInsured from pPtPayer.Pt_Relationship, claimFilingCode from pPayer.Payer_Source --->
        <cfset var relationshipToInsured = "18">
        <cfif StructKeyExists(arguments.memberData, "relationshipToInsured") AND Len(Trim(arguments.memberData.relationshipToInsured)) GT 0>
            <cfset relationshipToInsured = Trim(arguments.memberData.relationshipToInsured)>
        </cfif>
        <cfset var claimFilingCode = "MC">
        <cfif StructKeyExists(arguments.memberData, "claimFilingCode") AND Len(Trim(arguments.memberData.claimFilingCode)) GT 0>
            <cfset claimFilingCode = Trim(arguments.memberData.claimFilingCode)>
        </cfif>
        <cfset var visitHours = calculateVisitHours(arguments.sched)>
        <cfset var totalUnits = Int(Val(arguments.sched.Total_Units))>
        <cfif totalUnits LTE 0 AND visitHours GT 0>
            <cfset totalUnits = Int(Round(visitHours * 4))>
        </cfif>
        <cfif totalUnits LTE 0>
            <cfset totalUnits = 1>
        </cfif>
        <cfset var contractRate = Val(arguments.sched.Contract_Rate)>
        <cfif contractRate LTE 0>
            <cfset contractRate = Val(arguments.sched.EClaim_Price)>
        </cfif>
        <cfif contractRate LTE 0 AND Val(arguments.sched.Mi_Pay) GT 0 AND totalUnits GT 0>
            <cfset contractRate = Val(arguments.sched.Mi_Pay) / totalUnits>
        </cfif>
        <cfif contractRate LTE 0>
            <cfset contractRate = 10.2>
        </cfif>
        <cfset var totalBilledAmount = Val(arguments.sched.Mi_Pay)>
        <cfif totalBilledAmount LTE 0>
            <cfset totalBilledAmount = Round(totalUnits * contractRate * 100) / 100>
        </cfif>
        <cfset var invoiceNumber = "">
        <cfif StructKeyExists(arguments.sched, "Internal_control_number") AND Len(Trim(arguments.sched.Internal_control_number)) GT 0>
            <cfset invoiceNumber = Trim(arguments.sched.Internal_control_number)>
        <cfelseif Val(arguments.sched.Invoice_ID) GT 0>
            <cfset invoiceNumber = ToString(arguments.sched.Invoice_ID)>
        <cfelse>
            <cfset invoiceNumber = ToString(arguments.sched.Schedule_ID)>
        </cfif>
        <cfset var diagnosisCodes = getVisitDiagnosisCodes(Val(arguments.sched.Assmt_ID))>
        <cfif ArrayLen(diagnosisCodes) EQ 0>
            <cfset diagnosisCodes = [ "Z00.00" ]>
        </cfif>
        <cfset var performedTasks = getPerformedTasks(arguments.sched)>
        <cfset var employeeName = Trim(arguments.sched.Emp_First) & " " & Trim(arguments.sched.Emp_Last)>
        <cfset var isMissedVisit = StructKeyExists(arguments.sched, "Missed") AND Val(arguments.sched.Missed) GT 0>
        <cfset var missedReasonCode = "">
        <cfset var missedActionCode = "">
        <cfset var missedNotes = "">
        <cfif isMissedVisit>
            <cfset missedReasonCode = "603">
            <cfset missedActionCode = "501">
        </cfif>
        <cfset var visitEvvmsid = normalizeEvvmsid(arguments.evvmsid)>
        <cfset var visitEvvmsid ="~C31CFDCD-EF97-6CB0-A061FAB1FA457BE3">
        <cfset var visitEvvmsid = "~" & 'agency_' & #session.agencyId# & '_' & arguments.sched.Schedule_ID>
        <cfset var visitPayload = {
            "providerTaxId": providerTaxId,
            "office": {
                "qualifier": "NPI",
                "identifier": officeNpi
            },
            "member": {
                "qualifier": arguments.memberData.qualifier,
                "identifier": memberIdentifier 
            },
            "caregiver": {
                "qualifier": "ExternalID",
                "identifier": caregiverId
            },
            "residingCaregiver": "No",
            "payerId": payerId,
            "externalVisitId": memberIdentifier,
            "evvmsid": visitEvvmsid,
            "procedureCode": procedureCode,
            "procedureModifierCode": [],
            "timezone": getVisitTimezone(),
            "scheduleStartTime": scheduleStart,
            "scheduleEndTime": scheduleEnd,
            "visitStartDateTime": visitStart,
            "visitEndDateTime": visitEnd,
            "timesheetRequired": true,
            "timesheetApproved": Len(Trim(arguments.sched.patient_sign)) GT 0,
            "missedVisit": {
                "missed": isMissedVisit,
                "reasonCode": missedReasonCode,
                "actionCode": missedActionCode,
                "notes": missedNotes
            },
            "editVisit": {
                "edited": arguments.isEditVisit,
                "reasonCode": arguments.isEditVisit ? "200" : "",
                "actionCode": arguments.isEditVisit ? "101" : "",
                "notes": ""
            },
            "billing": {
                "externalInvoiceNumber": invoiceNumber,
                "totalBilledAmount": toJsonDouble(totalBilledAmount),
                "totalUnitsBilled": toJsonInteger(totalUnits),
                "contractRate": toJsonDouble(contractRate),
                "diagnosisCodes": diagnosisCodes
            },
            "billSecondaryPayer": [
                {
                    "enableSecondaryBilling": true,
                    "otherSubscriberId": memberIdentifier,
                    "primaryPayerId": payerId,
                    "primaryPayerName": payerName,
                    "relationshipToInsured": relationshipToInsured,
                    "primaryPayerPolicyOrGroupNumber": memberIdentifier,
                    "primaryPayerProgramName": payerName,
                    "planType": "MC",
                    "totalPaidAmount": toJsonDouble(0),
                    "paidDate": "2024-05-01T13:16",
                    "deductible": toJsonDouble(0),
                    "coinsurance": toJsonDouble(0),
                    "copay": toJsonDouble(0),
                    "contractedAdjustments": toJsonDouble(0),
                    "notMedicallyNecessary": toJsonDouble(0),
                    "nonCoveredCharges": toJsonDouble(0),
                    "maxBenefitExhausted": toJsonDouble(0),
                    "payerResponsibilitySequence": toJsonInteger(1),
                    "claimFilingCode": claimFilingCode,
                    "otherPayerPaidAmount": toJsonDouble(0)
                }
            ],
            "shiftSignOff": {
                "employerInternalNumber": employeeInternalNumber,
                "employerName": employeeName
            }
        }>
        <!--- Missed visits must not include EVV clock in / clock out --->
        <cfif NOT isMissedVisit>
            <cfset visitPayload.evv = {
                "clockIn": {
                    "callDateTime": formatCallDateTime(clockInDate, clockInTime),
                    "callType": "Mobile",
                    "callLatitude": clockInLat,
                    "callLongitude": clockInLon,
                    "originatingPhoneNumber": phone,
                    "locationType": "Home",
                    "serviceAddress": serviceAddress
                },
                "clockOut": {
                    "callDateTime": formatCallDateTime(clockOutDate, clockOutTime),
                    "callType": "Mobile",
                    "callLatitude": clockOutLat,
                    "callLongitude": clockOutLon,
                    "originatingPhoneNumber": phone,
                    "locationType": "Home",
                    "serviceAddress": serviceAddress,
                    "performedTasks": performedTasks,
                    "refusedTasks": []
                }
            }>
        </cfif>
        <cfreturn visitPayload>
    </cffunction>

    <cffunction name="get_member_from_patient" access="remote" returnFormat="json">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Patient_ID" required="yes" default="0">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn { success = false, error = agencyError }>
        </cfif>
        <cfset var patientId = decrypt(arguments.Patient_ID, application.enckey, "AES", "Hex")>
        <cfset var memberData = getPatientMemberData(patientId)>
        <cfif StructIsEmpty(memberData)>
            <cfreturn { success = false, error = "Patient not found." }>
        </cfif>
        <cfreturn {
            success = true,
            member = {
                qualifier = memberData.qualifier,
                identifier = memberData.identifier 
            },
            payerId = memberData.payerId,
            patient = {
                patientId = memberData.patient.Patient_ID,
                firstName = memberData.patient.Pt_First,
                lastName = memberData.patient.Pt_Last,
                address = memberData.patient.Pt_Street,
                city = memberData.patient.Pt_City,
                state = memberData.patient.Pt_State,
                zip = memberData.patient.Pt_Zip
            }
        }>
    </cffunction>

    <cffunction name="formatPayloadForEmail" access="private" returntype="string">
        <cfargument name="payload" type="any" required="true">
        <cfif IsStruct(arguments.payload) OR IsArray(arguments.payload)>
            <cfreturn SerializeJSON(arguments.payload)>
        </cfif>
        <cfreturn Trim(ToString(arguments.payload))>
    </cffunction>

    <cffunction name="sendMethodCallEmail" access="private" returntype="void">
        <cfargument name="methodName" type="string" required="true">
        <cfargument name="isSuccess" type="boolean" required="true">
        <cfargument name="recordLabel" type="string" required="true">
        <cfargument name="requestPayload" type="any" required="true">
        <cfargument name="responseBody" type="string" required="false" default="">
        <cfargument name="apiUrl" type="string" required="false" default="">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfargument name="errorMessage" type="string" required="false" default="">
        <cfargument name="details" type="struct" required="false" default="#{}#">
        <cfargument name="currentStatus" type="string" required="false" default="">
        <cfargument name="includeTransactionStatus" type="boolean" required="false" default="false">
        <cfargument name="transactionStatusBody" type="string" required="false" default="">
        <cfargument name="transactionStatusUrl" type="string" required="false" default="">
        <cfargument name="transactionStatusError" type="string" required="false" default="">
        <cfset var envLabel = Application.ServerName>
        <cfset var statusWord = arguments.isSuccess ? "Success" : "Failed">
        <cfset var fromEmail = arguments.isSuccess ? "info@myhomecarebiz.com" : "error@myhomecarebiz.com">
        <cfset var subject = envLabel & " HHA Exchange " & arguments.methodName & " " & statusWord & " - " & arguments.recordLabel>
        <cfset var requestText = formatPayloadForEmail(arguments.requestPayload)>
        <cfset var responseText = Len(Trim(arguments.responseBody)) GT 0 ? Trim(arguments.responseBody) : "No response body">
        <cfset var transactionStatus = { success = false, url = "", body = "", error = "" }>
        <cfset var transactionStatusText = "">
        <cfif Len(Trim(arguments.transactionStatusBody)) GT 0 OR Len(Trim(arguments.transactionStatusUrl)) GT 0 OR Len(Trim(arguments.transactionStatusError)) GT 0>
            <cfset transactionStatus.success = Len(Trim(arguments.transactionStatusBody)) GT 0>
            <cfset transactionStatus.url = Trim(arguments.transactionStatusUrl)>
            <cfset transactionStatus.body = Trim(arguments.transactionStatusBody)>
            <cfset transactionStatus.error = Trim(arguments.transactionStatusError)>
            <cfset transactionStatusText = transactionStatus.body>
            <cfset arguments.includeTransactionStatus = true>
        <cfelseif arguments.includeTransactionStatus>
            <cftry>
                <cfset transactionStatus = fetchTransactionStatusResponse(arguments.responseBody, arguments.transactionId)>
                <cfif transactionStatus.success AND Len(Trim(transactionStatus.body)) GT 0>
                    <cfset transactionStatusText = Trim(transactionStatus.body)>
                </cfif>
                <cfcatch type="any">
                    <cfset transactionStatus.error = cfcatch.message>
                </cfcatch>
            </cftry>
        </cfif>
        <cfsavecontent variable="local.emailBody">
            <cfoutput>
            <h3>HHA Exchange #HTMLEditFormat(arguments.methodName)# #statusWord#</h3>
            <p><strong>Method:</strong> #HTMLEditFormat(arguments.methodName)#</p>
            <p><strong>Agency ID:</strong> #variables.ALLOWED_AGENCY_ID#</p>
            <p><strong>Record:</strong> #HTMLEditFormat(arguments.recordLabel)#</p>
            <p><strong>Call Date:</strong> #DateFormat(Now(), "yyyy-mm-dd")# #TimeFormat(Now(), "HH:mm:ss")#</p>
            <cfif Len(Trim(arguments.apiUrl)) GT 0>
                <p><strong>API URL:</strong> #HTMLEditFormat(arguments.apiUrl)#</p>
            </cfif>
            <cfif Len(Trim(arguments.transactionId)) GT 0>
                <p><strong>Transaction ID:</strong> #HTMLEditFormat(arguments.transactionId)#</p>
            </cfif>
            <cfif Len(Trim(arguments.currentStatus)) GT 0>
                <p><strong>EVV Status:</strong> <span style="color: ##28a745; font-weight: bold;">#HTMLEditFormat(arguments.currentStatus)#</span></p>
            </cfif>
            <cfif NOT StructIsEmpty(arguments.details)>
                <p><strong>Details:</strong></p>
                <ul>
                    <cfloop collection="#arguments.details#" item="local.detailKey">
                        <li><strong>#HTMLEditFormat(local.detailKey)#:</strong> #HTMLEditFormat(ToString(arguments.details[local.detailKey]))#</li>
                    </cfloop>
                </ul>
            </cfif>
            <cfif Len(Trim(arguments.errorMessage)) GT 0>
                <p><strong>Error Details:</strong></p>
                <div style="background-color: ##ffe6e6; padding: 10px; border-left: 4px solid ##ff0000; margin: 10px 0;">
                    #HTMLEditFormat(arguments.errorMessage)#
                </div>
            </cfif>
            <p><strong>API Request:</strong></p>
            <pre style="background-color: ##f4f4f4; padding: 10px; border: 1px solid ##ddd; overflow-x: auto;">#HTMLEditFormat(requestText)#</pre>
            <p><strong>API Response:</strong></p>
            <pre style="background-color: ##f4f4f4; padding: 10px; border: 1px solid ##ddd; overflow-x: auto;">#HTMLEditFormat(responseText)#</pre>
            <cfif arguments.includeTransactionStatus AND Len(Trim(transactionStatus.url)) GT 0>
                <p><strong>Transaction Status URL:</strong> #HTMLEditFormat(transactionStatus.url)#</p>
            </cfif>
            <cfif arguments.includeTransactionStatus AND Len(transactionStatusText) GT 0>
                <p><strong>Transaction Status Response:</strong></p>
                <pre style="background-color: ##f4f4f4; padding: 10px; border: 1px solid ##ddd; overflow-x: auto;">#HTMLEditFormat(transactionStatusText)#</pre>
            <cfelseif arguments.includeTransactionStatus AND Len(Trim(transactionStatus.error)) GT 0>
                <p><strong>Transaction Status Error:</strong></p>
                <div style="background-color: ##fff8e6; padding: 10px; border-left: 4px solid ##f0ad4e; margin: 10px 0;">
                    #HTMLEditFormat(transactionStatus.error)#
                </div>
            </cfif>
            </cfoutput>
        </cfsavecontent>
        <cftry>
            <cfmail to="#variables.SENDTO_EMAIL#" from="#fromEmail#" subject="#subject#" type="html">
                #local.emailBody#
            </cfmail>
            <cfcatch>
                <cflog file="HHAExchange_EVV" text="Failed to send #arguments.methodName# email (#arguments.recordLabel#): #cfcatch.message#">
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="sendSubmissionEmail" access="private" returntype="void">
        <cfargument name="submissionType" type="string" required="true">
        <cfargument name="isSuccess" type="boolean" required="true">
        <cfargument name="recordLabel" type="string" required="true">
        <cfargument name="requestPayload" type="any" required="true">
        <cfargument name="responseBody" type="string" required="false" default="">
        <cfargument name="apiUrl" type="string" required="false" default="">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfargument name="errorMessage" type="string" required="false" default="">
        <cfargument name="details" type="struct" required="false" default="#{}#">
        <cfargument name="methodNameOverride" type="string" required="false" default="">
        <cfset var methodName = "visit_creation">
        <cfif Len(Trim(arguments.methodNameOverride)) GT 0>
            <cfset methodName = Trim(arguments.methodNameOverride)>
        <cfelseif arguments.submissionType EQ "Caregiver">
            <cfset methodName = "create_caregiver">
        </cfif>
        <cfset sendMethodCallEmail(
            methodName = methodName,
            isSuccess = arguments.isSuccess,
            recordLabel = arguments.recordLabel,
            requestPayload = arguments.requestPayload,
            responseBody = arguments.responseBody,
            apiUrl = arguments.apiUrl,
            transactionId = arguments.transactionId,
            errorMessage = arguments.errorMessage,
            details = arguments.details,
            includeTransactionStatus = true
        )>
    </cffunction>

    <cffunction name="logEVVSubmission" access="private" returntype="void">
        <cfargument name="API_type" type="string" required="true">
        <cfargument name="UUID" type="string" required="true">
        <cfargument name="status" type="string" required="true">
        <cfargument name="Patient_ID" type="numeric" required="false" default="0">
        <cfargument name="Emp_ID" type="numeric" required="false" default="0">
        <cfargument name="Schedule_ID" type="numeric" required="false" default="0">
        <cfargument name="evvmsid" type="string" required="false" default="">
        <cfset var evvCreatedBy = 0 />
        <cfif structKeyExists(session, "EmployeeId") AND val(session.EmployeeId) GT 0>
            <cfset evvCreatedBy = val(session.EmployeeId) />
        <cfelseif structKeyExists(session, "EmployeeID") AND val(session.EmployeeID) GT 0>
            <cfset evvCreatedBy = val(session.EmployeeID) />
        <cfelseif structKeyExists(session, "employeeid") AND val(session.employeeid) GT 0>
            <cfset evvCreatedBy = val(session.employeeid) />
        </cfif>
        <cfset var seqId = Len(Trim(arguments.evvmsid)) GT 0 ? Trim(arguments.evvmsid) : DateFormat(Now(), "yyyymmdd") & TimeFormat(Now(), "HHmmss") & Right(GetTickCount(), 2)>
        <cfquery datasource="#Application.DataSrc#">
            INSERT INTO #Request.prefix_db_lookup#.EVV
            (UUID, Seq_ID, San_status, API_type, Agency_ID, Patient_ID, Emp_ID, Schedule_ID, Date_Create, Created_by)
            VALUES
            (
                <cfqueryparam value="#arguments.UUID#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#seqId#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#arguments.status#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#arguments.API_type#" cfsqltype="cf_sql_varchar">,
                <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">,
                <cfqueryparam value="#arguments.Patient_ID#" cfsqltype="cf_sql_integer">,
                <cfqueryparam value="#arguments.Emp_ID#" cfsqltype="cf_sql_integer">,
                <cfqueryparam value="#arguments.Schedule_ID#" cfsqltype="cf_sql_integer">,
                <cfqueryparam value="#Now()#" cfsqltype="cf_sql_timestamp">,
                <cfqueryparam value="#evvCreatedBy#" cfsqltype="cf_sql_integer">
            )
        </cfquery>
    </cffunction>

    <cffunction name="validateVisitEligibility" access="private" returntype="string">
        <cfargument name="visitDate" type="any" required="true">
        <cfargument name="memberData" type="struct" required="true">
        <cfargument name="authStart" type="any" required="false" default="">
        <cfargument name="authEnd" type="any" required="false" default="">
        <cfif NOT IsDate(arguments.visitDate)>
            <cfreturn "Visit date is invalid.">
        </cfif>
        <cfset var visitDay = DateFormat(arguments.visitDate, "yyyy-mm-dd")>
        <cfset var eligStart = "">
        <cfset var eligEnd = "">
        <cfif StructKeyExists(arguments.memberData, "eligibilityStart") AND IsDate(arguments.memberData.eligibilityStart)>
            <cfset eligStart = arguments.memberData.eligibilityStart>
        </cfif>
        <cfif StructKeyExists(arguments.memberData, "eligibilityEnd") AND IsDate(arguments.memberData.eligibilityEnd)>
            <cfset eligEnd = arguments.memberData.eligibilityEnd>
        </cfif>
        <cfif NOT IsDate(eligStart) AND IsDate(arguments.authStart)>
            <cfset eligStart = arguments.authStart>
        </cfif>
        <cfif NOT IsDate(eligEnd) AND IsDate(arguments.authEnd)>
            <cfset eligEnd = arguments.authEnd>
        </cfif>
        <cfif IsDate(eligStart) AND arguments.visitDate LT eligStart>
            <cfreturn "Visit date #visitDay# is before eligibility start date #DateFormat(eligStart, 'yyyy-mm-dd')#. HHA Exchange error 101088: Visit date is not in range of Eligibility Start and End date.">
        </cfif>
        <cfif IsDate(eligEnd) AND arguments.visitDate GT eligEnd>
            <cfreturn "Visit date #visitDay# is after eligibility end date #DateFormat(eligEnd, 'yyyy-mm-dd')#. HHA Exchange error 101088: Visit date is not in range of Eligibility Start and End date.">
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="validateMemberData" access="private" returntype="string">
        <cfargument name="memberData" type="struct" required="true">
        <cfif StructIsEmpty(arguments.memberData)>
            <cfreturn "Patient not found.">
        </cfif>
        <cfif Len(Trim(arguments.memberData.identifier)) EQ 0>
            <cfreturn "Member Medicaid ID is missing. Add Sub_HIC on the patient payer before sending EVV.">
        </cfif>
        <cfif Len(Trim(arguments.memberData.admissionID)) EQ 0>
            <cfreturn "Member admission ID is missing. Set Pt_Agy_ID on the patient record.">
        </cfif>
        <cfif Len(Trim(arguments.memberData.patient.Pt_Street)) EQ 0
            OR Len(Trim(arguments.memberData.patient.Pt_City)) EQ 0
            OR Len(Trim(arguments.memberData.patient.Pt_State)) EQ 0
            OR Len(Trim(arguments.memberData.patient.Pt_Zip)) EQ 0>
            <cfreturn "Patient address is incomplete. Street, city, state, and zip are required for HHA Exchange visits.">
        </cfif>
        <cfreturn "">
    </cffunction>

    <cffunction name="create_caregiver" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Emp_ID" required="yes" default="0">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn agencyError>
        </cfif>
        <cfset var employeeId = decrypt(arguments.Emp_ID, application.enckey, "AES", "Hex")>
        <cfset var qAgency = getAgencySettings()>
        <cfif qAgency.recordcount EQ 0>
            <cfreturn "Agency settings not found for agency #variables.ALLOWED_AGENCY_ID#.">
        </cfif>
        <cfset var cfg = getConfig()>
        <cfset var empData = getEmployeeData(employeeId)>
        <cfset var recordLabel = "Employee ID " & employeeId>
        <cfif NOT StructIsEmpty(empData)>
            <cfset recordLabel = Trim(empData.Emp_First) & " " & Trim(empData.Emp_Last) & " (" & employeeId & ")">
        </cfif>
        <cfif StructIsEmpty(empData)>
            <cfset sendSubmissionEmail("Caregiver", false, recordLabel, {}, "", cfg.caregiverUrl, "", "Employee not found.", { "Employee ID" = employeeId })>
            <cfreturn "Employee not found.">
        </cfif>
        <cfset var agencyStruct = queryRowToStruct(qAgency)>
        <cfset var caregiverPayload = buildCaregiverPayload(empData, agencyStruct)>
        <cfset var emailDetails = {
            "Employee ID" = employeeId,
            "External ID" = caregiverPayload.externalID,
            "Employee ID (API)" = caregiverPayload.employeeId,
            "SSN" = caregiverPayload.ssn,
            "NPI" = caregiverPayload.npi
        }>
        <cfset var validationError = validateCaregiverData(empData)>
        <cfif Len(validationError) GT 0>
            <cfset sendSubmissionEmail("Caregiver", false, recordLabel, caregiverPayload, "", cfg.caregiverUrl, "", validationError, emailDetails)>
            <cfreturn validationError>
        </cfif>
        <cfset var apiResponse = apiRequest("POST", cfg.caregiverUrl, SerializeJSON(caregiverPayload))>
        <cfif NOT apiResponse.success>
            <cflog file="HHAExchange_EVV" text="Caregiver upload failed for Emp_ID #employeeId#. Request: #SerializeJSON(caregiverPayload)# Response: #apiResponse.error#">
            <cfset sendSubmissionEmail("Caregiver", false, recordLabel, caregiverPayload, apiResponse.body, cfg.caregiverUrl, "", apiResponse.error, emailDetails)>
            <cfreturn apiResponse.error>
        </cfif>
        <cfset var transactionId = extractTransactionId(apiResponse.body)>
        <cfif Len(transactionId) EQ 0>
            <cfset var missingTxnError = "HHA Exchange response missing transactionId. #apiResponse.body#">
            <cflog file="HHAExchange_EVV" text="Caregiver upload missing transactionId for Emp_ID #employeeId#. Response: #apiResponse.body#">
            <cfset sendSubmissionEmail("Caregiver", false, recordLabel, caregiverPayload, apiResponse.body, cfg.caregiverUrl, "", missingTxnError, emailDetails)>
            <cfreturn missingTxnError>
        </cfif>
        <cflog file="HHAExchange_EVV" text="Caregiver upload for Emp_ID #employeeId#. transactionId: #transactionId#. Response: #apiResponse.body#">
        <cfset logEVVSubmission("employee", transactionId, "Transmitted - Initial submission", 0, employeeId, 0)>
        <cfset sendSubmissionEmail("Caregiver", true, recordLabel, caregiverPayload, apiResponse.body, cfg.caregiverUrl, transactionId, "", emailDetails)>
        <cfreturn "SUCCESS">
    </cffunction>

    <cffunction name="create_client" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Patient_ID" required="yes" default="0">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn agencyError>
        </cfif>
        <cfset var patientId = decrypt(arguments.Patient_ID, application.enckey, "AES", "Hex")>
        <cfset var memberData = getPatientMemberData(patientId)>
        <cfset var validationError = validateMemberData(memberData)>
        <cfif Len(validationError) GT 0>
            <cfreturn validationError>
        </cfif>
        <cfset logEVVSubmission("clients", CreateUUID(), "Transmitted - Initial submission", patientId, 0, 0)>
        <cfreturn "SUCCESS">
    </cffunction>

    <cffunction name="check_client_status" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Patient_ID" required="yes" default="0">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn agencyError>
        </cfif>
        <cfset var patientId = decrypt(arguments.Patient_ID, application.enckey, "AES", "Hex")>
        <cfquery name="local.qStatus" datasource="#Application.DataSrc#">
            SELECT UUID, San_status, Date_Create
            FROM #Request.prefix_db_lookup#.EVV
            WHERE API_Type = 'clients'
            AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
            AND Patient_ID = <cfqueryparam value="#patientId#" cfsqltype="cf_sql_integer">
            AND Status = 0
            ORDER BY EVV_ID DESC
            LIMIT 1
        </cfquery>
        <cfif local.qStatus.recordcount EQ 0>
            <cfreturn "No HHA Exchange member record found for this patient. Use Send EVV to validate member data first.">
        </cfif>
        <cfreturn "SUCCESS">
    </cffunction>

    <cffunction name="create_employee" access="remote" returnFormat="plain">
        <cfreturn create_caregiver(argumentCollection=arguments)>
    </cffunction>

    <cffunction name="check_employee_status" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Emp_ID" required="yes" default="0">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn agencyError>
        </cfif>
        <cfset var employeeId = decrypt(arguments.Emp_ID, application.enckey, "AES", "Hex")>
        <cfquery name="local.qStatus" datasource="#Application.DataSrc#">
            SELECT UUID, San_status, Date_Create
            FROM #Request.prefix_db_lookup#.EVV
            WHERE API_Type = 'employee'
            AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
            AND Emp_ID = <cfqueryparam value="#employeeId#" cfsqltype="cf_sql_integer">
            AND Status = 0
            ORDER BY EVV_ID DESC
            LIMIT 1
        </cfquery>
        <cfif local.qStatus.recordcount EQ 0>
            <cfreturn "No HHA Exchange caregiver record found for this employee. Send EVV first.">
        </cfif>
        <cfif Len(Trim(local.qStatus.UUID)) GT 0>
            <cfreturn get_visit_transaction(arguments.Agency_ID, local.qStatus.UUID)>
        </cfif>
        <cfreturn "SUCCESS">
    </cffunction>

    <cffunction name="sendVisitErrorEmail" access="private" returntype="void">
        <cfargument name="errorMessage" type="string" required="true">
        <cfargument name="recordLabel" type="string" required="true">
        <cfargument name="emailDetails" type="struct" required="false" default="#{}#">
        <cfargument name="requestPayload" type="any" required="false" default="#{}#">
        <cfargument name="responseBody" type="string" required="false" default="">
        <cfargument name="methodNameOverride" type="string" required="false" default="">
        <cfset var cfg = getConfig()>
        <cfset var requestData = arguments.requestPayload>
        <cfset var emailMethod = Len(Trim(arguments.methodNameOverride)) GT 0 ? Trim(arguments.methodNameOverride) : "visit_creation">
        <cfif IsStruct(requestData) AND StructIsEmpty(requestData)>
            <cfset requestData = {
                "method" = emailMethod,
                "note" = "No API request sent - validation failed before visit upload"
            }>
        </cfif>
        <cfset sendSubmissionEmail("Visit", false, arguments.recordLabel, requestData, arguments.responseBody, cfg.visitUrl, "", arguments.errorMessage, arguments.emailDetails, emailMethod)>
    </cffunction>

    <cffunction name="sendVisitStatusCheckEmail" access="private" returntype="void">
        <cfargument name="isSuccess" type="boolean" required="true">
        <cfargument name="recordLabel" type="string" required="true">
        <cfargument name="requestPayload" type="any" required="true">
        <cfargument name="responseBody" type="string" required="false" default="">
        <cfargument name="apiUrl" type="string" required="false" default="">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfargument name="errorMessage" type="string" required="false" default="">
        <cfargument name="details" type="struct" required="false" default="#{}#">
        <cfargument name="currentStatus" type="string" required="false" default="">
        <cfset sendMethodCallEmail(
            methodName = "check_visit_status",
            isSuccess = arguments.isSuccess,
            recordLabel = arguments.recordLabel,
            requestPayload = arguments.requestPayload,
            responseBody = arguments.responseBody,
            apiUrl = arguments.apiUrl,
            transactionId = arguments.transactionId,
            errorMessage = arguments.errorMessage,
            details = arguments.details,
            currentStatus = arguments.currentStatus,
            includeTransactionStatus = false
        )>
    </cffunction>

    <cffunction name="buildVisitStatusCheckRequest" access="private" returntype="struct">
        <cfargument name="scheduleId" type="numeric" required="true">
        <cfargument name="encryptedScheduleId" type="string" required="true">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfset var statusUrls = buildTransactionStatusUrls("", arguments.transactionId)>
        <cfreturn {
            "method" = "check_visit_status",
            "httpMethod" = "GET",
            "Schedule_ID" = arguments.scheduleId,
            "Encrypted_Schedule_ID" = arguments.encryptedScheduleId,
            "transactionId" = arguments.transactionId,
            "statusCheckUrls" = statusUrls
        }>
    </cffunction>

    <cffunction name="resolveScheduleId" access="private" returntype="numeric">
        <cfargument name="Schedule_ID" type="any" required="true">
        <cfset var raw = Trim(ToString(arguments.Schedule_ID))>
        <cfif Len(raw) EQ 0>
            <cfreturn 0>
        </cfif>
        <cfif IsNumeric(raw) AND Val(raw) GT 0 AND REFind("^[0-9]+$", raw) EQ 1>
            <cfreturn Int(Val(raw))>
        </cfif>
        <cftry>
            <cfset var decrypted = decrypt(raw, application.enckey, "AES", "Hex")>
            <cfif IsNumeric(decrypted)>
                <cfreturn Int(Val(decrypted))>
            </cfif>
            <cfcatch type="any">
                <cfreturn 0>
            </cfcatch>
        </cftry>
        <cfreturn 0>
    </cffunction>

    <cffunction name="getActiveVisitEvvRecord" access="private" returntype="query">
        <cfargument name="scheduleId" type="numeric" required="true">
        <cfquery name="local.qEvv" datasource="#Application.DataSrc#">
            SELECT EVV_ID, UUID, Seq_ID, San_status, Patient_ID, Emp_ID, Schedule_ID
            FROM #Request.prefix_db_lookup#.EVV
            WHERE API_Type = 'visits'
            AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
            AND Schedule_ID = <cfqueryparam value="#arguments.scheduleId#" cfsqltype="cf_sql_integer">
            AND Status = 0
            ORDER BY EVV_ID DESC
            LIMIT 1
        </cfquery>
        <cfreturn local.qEvv>
    </cffunction>

    <cffunction name="markLocalVisitEvvDeleted" access="private" returntype="void">
        <cfargument name="scheduleId" type="numeric" required="true">
        <cfargument name="statusText" type="string" required="false" default="Deleted">
        <cfargument name="transactionId" type="string" required="false" default="">
        <cfquery datasource="#Application.DataSrc#">
            UPDATE #Request.prefix_db_lookup#.EVV
            SET San_status = <cfqueryparam value="#Left(arguments.statusText, 255)#" cfsqltype="cf_sql_varchar">,
                <cfif Len(Trim(arguments.transactionId)) GT 0>
                    UUID = <cfqueryparam value="#Trim(arguments.transactionId)#" cfsqltype="cf_sql_varchar">,
                </cfif>
                Date_Update = <cfqueryparam value="#Now()#" cfsqltype="cf_sql_timestamp">,
                Status = 1
            WHERE API_Type = 'visits'
            AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
            AND Schedule_ID = <cfqueryparam value="#arguments.scheduleId#" cfsqltype="cf_sql_integer">
            AND Status = 0
        </cfquery>
    </cffunction>

    <!--- HHA Exchange DELETE /v2/visits/{evvmsid} - used when visit is deleted or Missed > 0 --->
    <cffunction name="delete_visit" access="remote" returnFormat="json">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Schedule_ID" required="yes" default="0">
        <cfargument name="reason" required="no" default="visit_deleted">
        <cfargument name="sendNotification" required="no" default="true" type="boolean">

        <cfset ensureRemoteContext()>
        <cfset var result = {
            "success" = false,
            "message" = "",
            "status" = "",
            "transactionId" = "",
            "evvmsid" = "",
            "attempted" = false
        }>
        <cfset var cfg = getConfig()>
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfset var scheduleId = 0>
        <cfset var recordLabel = "Visit Delete">
        <cfset var emailDetails = {
            "Encrypted/Raw Schedule ID" = arguments.Schedule_ID,
            "Delete Reason" = arguments.reason
        }>
        <cfset var qEvv = "">
        <cfset var evvmsid = "">
        <cfset var deleteUrl = "">
        <cfset var apiResponse = {}>
        <cfset var transactionId = "">
        <cfset var currentStatus = "">
        <cfset var txnStatus = { success = false, url = "", body = "", error = "" }>
        <cfset var requestPayload = {}>
        <cfset var shouldEmail = arguments.sendNotification>

        <cfif Len(agencyError) GT 0>
            <cfset result.message = agencyError>
            <cfif shouldEmail>
                <cfset sendMethodCallEmail(
                    methodName = "delete_visit",
                    isSuccess = false,
                    recordLabel = recordLabel,
                    requestPayload = { "method" = "delete_visit", "Schedule_ID" = arguments.Schedule_ID, "Agency_ID" = arguments.Agency_ID, "reason" = arguments.reason },
                    responseBody = "",
                    errorMessage = agencyError,
                    details = emailDetails
                )>
            </cfif>
            <cfreturn result>
        </cfif>

        <cftry>
            <cfset scheduleId = resolveScheduleId(arguments.Schedule_ID)>
            <cfif scheduleId LTE 0>
                <cfset result.message = "Invalid Schedule ID for HHA Exchange visit delete.">
                <cfif shouldEmail>
                    <cfset sendMethodCallEmail(
                        methodName = "delete_visit",
                        isSuccess = false,
                        recordLabel = recordLabel,
                        requestPayload = { "method" = "delete_visit", "Schedule_ID" = arguments.Schedule_ID, "reason" = arguments.reason },
                        responseBody = "",
                        errorMessage = result.message,
                        details = emailDetails
                    )>
                </cfif>
                <cfreturn result>
            </cfif>

            <cfset recordLabel = "Schedule " & scheduleId>
            <cfset emailDetails["Schedule ID"] = scheduleId>

            <!--- No EVV when payer EVV required by Payer = No: skip API and do not email --->
            <cfif NOT schedulePayerRequiresEVV(scheduleId, Val(arguments.Agency_ID))>
                <cfset result.success = true>
                <cfset result.message = "Skipped HHA Exchange delete: payer does not require EVV for schedule #scheduleId#.">
                <cflog file="HHAExchange_EVV" text="delete_visit skipped schedule #scheduleId#: evv_payer_required not set">
                <cfreturn result>
            </cfif>

            <cfset qEvv = getActiveVisitEvvRecord(scheduleId)>

            <cfif qEvv.recordcount EQ 0 OR Len(Trim(qEvv.Seq_ID)) EQ 0>
                <cfset result.success = true>
                <cfset result.message = "No active HHA Exchange visit (evvmsid) found to delete for schedule #scheduleId#.">
                <cflog file="HHAExchange_EVV" text="delete_visit skipped schedule #scheduleId#: no active Seq_ID/evvmsid">
                <!--- Notify only when EVV is required and delete was requested but nothing to delete --->
                <cfif shouldEmail>
                    <cfset sendMethodCallEmail(
                        methodName = "delete_visit",
                        isSuccess = true,
                        recordLabel = recordLabel,
                        requestPayload = { "method" = "delete_visit", "Schedule_ID" = scheduleId, "reason" = arguments.reason, "note" = "No DELETE API call - no active evvmsid" },
                        responseBody = result.message,
                        errorMessage = "",
                        details = emailDetails,
                        currentStatus = "Skipped - no active evvmsid"
                    )>
                </cfif>
                <cfreturn result>
            </cfif>

            <cfset evvmsid = Trim(qEvv.Seq_ID)>
            <cfset result.evvmsid = evvmsid>
            <cfset result.attempted = true>
            <cfset emailDetails["EVV ID"] = qEvv.EVV_ID>
            <cfset emailDetails["Previous EVV Status"] = qEvv.San_status>
            <cfset emailDetails["Patient ID"] = qEvv.Patient_ID>
            <cfset emailDetails["evvmsid"] = evvmsid>
            <!--- Path param: keep tilde/UUID as-is (URLEncodedFormat would break ~evvmsid formats) --->
            <cfset deleteUrl = cfg.visitUrl & "/" & Trim(evvmsid)>
            <cfset requestPayload = {
                "method" = "delete_visit",
                "httpMethod" = "DELETE",
                "apiUrl" = deleteUrl,
                "Schedule_ID" = scheduleId,
                "evvmsid" = evvmsid,
                "reason" = arguments.reason,
                "previousTransactionId" = Trim(qEvv.UUID)
            }>

            <cfset apiResponse = apiRequest("DELETE", deleteUrl, "")>
            <!--- Always email on DELETE API failure --->
            <cfif NOT apiResponse.success>
                <cfset result.message = Len(Trim(apiResponse.error)) GT 0 ? apiResponse.error : "HHA Exchange delete_visit API failed">
                <cflog file="HHAExchange_EVV" text="delete_visit failed schedule #scheduleId# evvmsid #evvmsid#: #result.message#">
                <cfset sendMethodCallEmail(
                    methodName = "delete_visit",
                    isSuccess = false,
                    recordLabel = recordLabel,
                    requestPayload = requestPayload,
                    responseBody = apiResponse.body,
                    apiUrl = deleteUrl,
                    errorMessage = result.message,
                    details = emailDetails,
                    currentStatus = "Delete Failed"
                )>
                <cfreturn result>
            </cfif>

            <cfset transactionId = extractTransactionId(apiResponse.body)>
            <cfset result.transactionId = transactionId>
            <cfset emailDetails["Transaction ID"] = transactionId>
            <cfset currentStatus = "Deleted - awaiting status check">

            <cfif Len(transactionId) GT 0>
                <cfset txnStatus = fetchTransactionStatusResponse(apiResponse.body, transactionId)>
                <cfif txnStatus.success AND Len(Trim(txnStatus.body)) GT 0>
                    <cfset currentStatus = extractTransactionStatusText(txnStatus.body)>
                    <cfif Len(currentStatus) EQ 0>
                        <cfset currentStatus = "Deleted">
                    </cfif>
                </cfif>
            <cfelse>
                <cfset currentStatus = "Deleted">
            </cfif>

            <cfset markLocalVisitEvvDeleted(scheduleId, currentStatus, transactionId)>
            <cfset result.success = true>
            <cfset result.status = currentStatus>
            <cfset result.message = "HHA Exchange visit deleted successfully.">

            <cftry>
                <cfset createObject("component", "components.evv.EVVVisitAutomation").init().updateAutomationJobFromStatusCheck(
                    scheduleID = scheduleId,
                    agencyID = variables.ALLOWED_AGENCY_ID,
                    evvStatus = currentStatus
                )>
                <cfcatch type="any">
                    <cflog file="EVVVisitAutomation" text="delete_visit automation sync failed schedule #scheduleId#: #cfcatch.message#">
                </cfcatch>
            </cftry>

            <cflog file="HHAExchange_EVV" text="delete_visit success schedule #scheduleId# evvmsid #evvmsid# txn #transactionId# status #currentStatus#">
            <!--- Always email on DELETE API success (request + response + status) --->
            <cfset sendMethodCallEmail(
                methodName = "delete_visit",
                isSuccess = true,
                recordLabel = recordLabel,
                requestPayload = requestPayload,
                responseBody = apiResponse.body,
                apiUrl = deleteUrl,
                transactionId = transactionId,
                details = emailDetails,
                currentStatus = currentStatus,
                transactionStatusBody = txnStatus.body,
                transactionStatusUrl = txnStatus.url,
                transactionStatusError = txnStatus.error
            )>

            <cfcatch type="any">
                <cfset result.message = cfcatch.message>
                <cflog file="HHAExchange_EVV" text="delete_visit exception schedule #scheduleId#: #cfcatch.message# #cfcatch.detail#">
                <cfset sendMethodCallEmail(
                    methodName = "delete_visit",
                    isSuccess = false,
                    recordLabel = recordLabel,
                    requestPayload = NOT structIsEmpty(requestPayload) ? requestPayload : { "method" = "delete_visit", "Schedule_ID" = arguments.Schedule_ID },
                    responseBody = StructKeyExists(apiResponse, "body") ? ToString(apiResponse.body) : "",
                    apiUrl = deleteUrl,
                    transactionId = transactionId,
                    errorMessage = cfcatch.message,
                    details = emailDetails,
                    currentStatus = "Delete Exception"
                )>
            </cfcatch>
        </cftry>

        <cfreturn result>
    </cffunction>

    <cffunction name="visit_creation" access="remote" returnFormat="plain">
        <cfreturn create_visit(argumentCollection=arguments)>
    </cffunction>

    <cffunction name="create_visit" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Schedule_ID" required="yes" default="0">
        <cfargument name="transmissionReason" required="no" default="">
        <cfset ensureRemoteContext()>
        <cfset var cfg = getConfig()>
        <cfset var scheduleId = 0>
        <cfset var recordLabel = "Visit">
        <cfset var emailDetails = { "Encrypted Schedule ID" = arguments.Schedule_ID }>
        <cfset var agencyError = "">
        <cfset var qAgency = "">
        <cfset var memberData = {}>
        <cfset var existingEvvmsid = "">
        <cfset var isEditVisit = false>
        <cfset var visitPayload = {}>
        <cfset var requestBody = {}>
        <cfset var apiResponse = {}>
        <cfset var transactionId = "">
        <cfset var validationError = "">
        <cfset var catchError = "">
        <cfset var emailMethodName = LCase(Trim(arguments.transmissionReason)) EQ "missed_visit" ? "missed_visit_update" : "visit_creation">

        <cftry>
            <cfset agencyError = validateAgency(arguments.Agency_ID)>
            <cfif Len(agencyError) GT 0>
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>

            <cfset scheduleId = resolveScheduleId(arguments.Schedule_ID)>
            <cfif scheduleId LTE 0>
                <cfset agencyError = "Invalid Schedule ID for HHA Exchange visit transmit.">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>
            <cfset recordLabel = "Schedule " & scheduleId>
            <cfset emailDetails["Schedule ID"] = scheduleId>
            <cfif emailMethodName EQ "missed_visit_update">
                <cfset emailDetails["Transmission Type"] = "Missed Visit Update">
            </cfif>

            <!--- No EVV when payer EVV required by Payer = No: skip transmit and do not email --->
            <cfif NOT schedulePayerRequiresEVV(scheduleId, Val(arguments.Agency_ID))>
                <cfset agencyError = "Skipped HHA Exchange visit transmit: payer does not require EVV for schedule #scheduleId#.">
                <cflog file="HHAExchange_EVV" text="create_visit skipped schedule #scheduleId#: evv_payer_required not set">
                <cfreturn agencyError>
            </cfif>

            <cfset qAgency = getAgencySettings()>
            <cfif qAgency.recordcount EQ 0>
                <cfset agencyError = "Agency settings not found for agency #variables.ALLOWED_AGENCY_ID#.">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>

            <cfquery name="local.GtSched" datasource="#Application.DataSrc#">
                SELECT pPatients.Patient_ID, pPatients.Pt_Last, pPatients.Pt_First, pPatients.Pt_Agy_ID,
                       pPatients.Pt_Street, pPatients.Pt_City, pPatients.Pt_State, pPatients.Pt_Zip, pPatients.Pt_Phone,
                       pSchedules.Schedule_ID, pSchedules.Visit_Date, pSchedules.StartTime, pSchedules.EndTime,
                       pSchedules.GPS_start_time, pSchedules.GPS_end_time, pSchedules.GPS_start_date, pSchedules.GPS_end_date,
                       pSchedules.GPS_start_latitude, pSchedules.GPS_start_longitude, pSchedules.GPS_end_latitude, pSchedules.GPS_end_longitude,
                       pSchedules.patient_sign, pSchedules.Emp_ID, pSchedules.Total_Units, pSchedules.Mi_Pay,
                       pSchedules.Missed, pSchedules.Invoice_ID, pSchedules.Assmt_ID, pSchedules.Pay_ID, pSchedules.Auth_ID,
                       pAuths.Date_Start AS Auth_Date_Start, pAuths.Date_End AS Auth_Date_End,
                       IFNULL(pSchedules.Sch_Latitude, 0) AS Sch_Latitude,
                       IFNULL(pSchedules.Sch_Longitude, 0) AS Sch_Longitude,
                       (TIME_TO_SEC(TIMEDIFF(pSchedules.EndTime, pSchedules.StartTime)) / 3600) AS totalhours,
                       pEmployee.Emp_First, pEmployee.Emp_Last, pEmployee.Emp_Custom_1, pEmployee.Emp_SSN,
                       pRevenue_Code.HCPCS, pRevenue_Code.Rev_Code, pRevenue_Code.EClaim_Price, pRevenue_Code.Contract_Rate,
                       Invoices.Internal_control_number
                FROM #Request.prefix_db_agency#.pSchedules
                JOIN #Request.prefix_db_agency#.pPatients ON pPatients.Patient_ID = pSchedules.Patient_ID AND pPatients.Status IN (0,1,4)
                JOIN #Request.prefix_db_lookup#.pEmployee ON pSchedules.Emp_ID = pEmployee.Emp_ID AND pEmployee.Status < 2
                LEFT JOIN #Request.prefix_db_agency#.pRevenue_Code ON pRevenue_Code.Rev_CodeID = pSchedules.HCPCS_ID AND pRevenue_Code.Status = 0
                LEFT JOIN #Request.prefix_db_agency#.pAuths ON pAuths.Auth_ID = pSchedules.Auth_ID AND pAuths.Status = 0
                LEFT JOIN #Request.prefix_db_agency#.Invoices ON Invoices.Invoice_ID = pSchedules.Invoice_ID AND Invoices.Deleted = 0
                WHERE pSchedules.Status = 0
                AND pSchedules.Emp_ID != 0
                AND pSchedules.Schedule_ID = <cfqueryparam value="#scheduleId#" cfsqltype="cf_sql_integer">
                LIMIT 1
            </cfquery>
            <cfif local.GtSched.recordcount EQ 0>
                <cfset agencyError = "Visit not found or does not meet EVV export criteria.">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>

            <cfif Val(local.GtSched.Missed) GT 0>
                <cfset emailMethodName = "missed_visit_update">
                <cfset emailDetails["Transmission Type"] = "Missed Visit Update">
                <cfset emailDetails["Missed Value"] = local.GtSched.Missed>
            </cfif>

            <cfset recordLabel = Trim(local.GtSched.Pt_First) & " " & Trim(local.GtSched.Pt_Last) & " - Schedule " & scheduleId>
            <cfset emailDetails["Patient ID"] = local.GtSched.Patient_ID>
            <cfset emailDetails["Employee ID"] = local.GtSched.Emp_ID>
            <cfset emailDetails["Visit Date"] = IsDate(local.GtSched.Visit_Date) ? DateFormat(local.GtSched.Visit_Date, "yyyy-mm-dd") : "">

            <cfif Len(Trim(local.GtSched.HCPCS)) EQ 0>
                <cfset agencyError = "Cannot transmit visit to HHA Exchange: HCPCS code is missing.">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>
            <cfset emailDetails["Procedure Code"] = local.GtSched.HCPCS>
            <cfif Len(getCaregiverIdentifier(queryRowToStruct(local.GtSched))) EQ 0>
                <cfset agencyError = "Employee SSN is required for HHA Exchange caregiver identifier.">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn agencyError>
            </cfif>

            <cfset memberData = getPatientMemberData(local.GtSched.Patient_ID, Val(local.GtSched.Pay_ID))>
            <cfset validationError = validateMemberData(memberData)>
            <cfif Len(validationError) GT 0>
                <cfset sendVisitErrorEmail(validationError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn validationError>
            </cfif>
            <cfset validationError = validateVisitEligibility(local.GtSched.Visit_Date, memberData, local.GtSched.Auth_Date_Start, local.GtSched.Auth_Date_End)>
            <cfif Len(validationError) GT 0>
                <cfif IsDate(memberData.eligibilityStart) OR IsDate(memberData.eligibilityEnd)>
                    <cfset emailDetails["Eligibility Start"] = IsDate(memberData.eligibilityStart) ? DateFormat(memberData.eligibilityStart, "yyyy-mm-dd") : "">
                    <cfset emailDetails["Eligibility End"] = IsDate(memberData.eligibilityEnd) ? DateFormat(memberData.eligibilityEnd, "yyyy-mm-dd") : "">
                </cfif>
                <cfset sendVisitErrorEmail(validationError, recordLabel, emailDetails, {}, "", emailMethodName)>
                <cfreturn validationError>
            </cfif>

            <cfquery name="local.CheckExistingSubmission" datasource="#Application.DataSrc#">
                SELECT UUID, Seq_ID
                FROM #Request.prefix_db_lookup#.EVV
                WHERE API_Type = 'visits'
                AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
                AND Schedule_ID = <cfqueryparam value="#scheduleId#" cfsqltype="cf_sql_integer">
                AND Status = 0
                ORDER BY EVV_ID ASC
                LIMIT 1
            </cfquery>
            <cfset isEditVisit = false>
            <cfif local.CheckExistingSubmission.recordcount GT 0>
                <cfset isEditVisit = true>
                <cfif Len(Trim(local.CheckExistingSubmission.Seq_ID)) GTE 36>
                    <cfset existingEvvmsid = Trim(local.CheckExistingSubmission.Seq_ID)>
                </cfif>
            </cfif>
            <cfif Len(existingEvvmsid) EQ 0>
                <cfset existingEvvmsid = UCase(CreateUUID())>
            </cfif>

            <cfset visitPayload = buildVisitPayload(queryRowToStruct(local.GtSched), queryRowToStruct(qAgency), memberData, existingEvvmsid, isEditVisit)>
            <cfset requestBody = { "visits": [ visitPayload ] }>

            <cfset apiResponse = apiRequest("POST", cfg.visitUrl, SerializeJSON(requestBody))>
            <cfif NOT apiResponse.success>
                <cflog file="HHAExchange_EVV" text="Visit upload failed for Schedule_ID #scheduleId#. Request: #SerializeJSON(requestBody)# Response: #apiResponse.error#">
                <cfset sendVisitErrorEmail(apiResponse.error, recordLabel, emailDetails, requestBody, apiResponse.body, emailMethodName)>
                <cfreturn apiResponse.error>
            </cfif>

            <cfset transactionId = extractTransactionId(apiResponse.body)>
            <cfif Len(transactionId) EQ 0>
                <cfset agencyError = "HHA Exchange response missing transactionId. #apiResponse.body#">
                <cflog file="HHAExchange_EVV" text="Visit upload missing transactionId for Schedule_ID #scheduleId#. Response: #apiResponse.body#">
                <cfset sendVisitErrorEmail(agencyError, recordLabel, emailDetails, requestBody, apiResponse.body, emailMethodName)>
                <cfreturn agencyError>
            </cfif>

            <cfset logEVVSubmission("visits", transactionId, Val(local.GtSched.Missed) GT 0 ? "Transmitted - Missed visit update" : "Transmitted - Initial submission", local.GtSched.Patient_ID, local.GtSched.Emp_ID, scheduleId, visitPayload.evvmsid)>
            <cftry>
                <cfset createObject("component", "components.evv.EVVVisitAutomation").init().queueStatusPollingOnly(
                    scheduleID = scheduleId,
                    agencyID = variables.ALLOWED_AGENCY_ID,
                    transactionId = transactionId,
                    triggerSource = emailMethodName EQ "missed_visit_update" ? "missed_visit_update" : "manual_send_evv",
                    patientID = local.GtSched.Patient_ID,
                    createdBy = structKeyExists(session, "employeeid") ? val(session.employeeid) : 0
                )>
                <cfcatch type="any">
                    <cflog file="EVVVisitAutomation" text="queueStatusPollingOnly after visit upload schedule #scheduleId#: #cfcatch.message#">
                </cfcatch>
            </cftry>
            <cfset sendSubmissionEmail("Visit", true, recordLabel, requestBody, apiResponse.body, cfg.visitUrl, transactionId, "", emailDetails, emailMethodName)>
            <cfreturn Val(local.GtSched.Missed) GT 0 ? "Missed visit updated successfully." : "Visit uploaded successfully.">

            <cfcatch>
                <cfset catchError = "Unexpected error during HHA Exchange visit upload: #cfcatch.message#">
                <cfif Len(Trim(cfcatch.detail)) GT 0>
                    <cfset catchError &= " #cfcatch.detail#">
                </cfif>
                <cflog file="HHAExchange_EVV" text="Visit creation exception for Schedule_ID #scheduleId#: #cfcatch.message# #cfcatch.detail#">
                <cfset sendVisitErrorEmail(catchError, recordLabel, emailDetails, requestBody, "", emailMethodName)>
                <cfreturn catchError>
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="ensureRemoteContext" access="private" returntype="void" output="false">
        <cfif NOT structKeyExists(application, "enckey") OR NOT len(trim(application.enckey))>
            <cfset application.enckey = "54kczhzgHTj4a8n+SRZPyg==">
        </cfif>
        <cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
            <cfset Request.prefix_db_lookup = "hhapowerpath">
        </cfif>
        <cfif NOT isDefined("Request.prefix_db_agency") OR NOT len(trim(Request.prefix_db_agency)) OR Request.prefix_db_agency EQ "prefix_db_agency_invalid">
            <cfif structKeyExists(session, "agencyid") AND val(session.agencyid) GT 0>
                <cfset Request.prefix_db_agency = "agency_" & val(session.agencyid)>
            <cfelseif structKeyExists(session, "AgencyId") AND val(session.AgencyId) GT 0>
                <cfset Request.prefix_db_agency = "agency_" & val(session.AgencyId)>
            </cfif>
        </cfif>
    </cffunction>

    <cffunction name="pickBestStatusText" access="private" returntype="string">
        <cfargument name="statusList" type="array" required="true">
        <cfset var rawStatus = "">
        <cfset var normalizedStatus = "">
        <cfset var fallbackStatus = "">
        <cfloop array="#arguments.statusList#" index="rawStatus">
            <cfset rawStatus = trim(rawStatus)>
            <cfif NOT len(rawStatus)>
                <cfcontinue>
            </cfif>
            <cfset normalizedStatus = normalizeHHAVisitStatusText(rawStatus)>
            <cfif normalizedStatus EQ "Transaction Received">
                <cfreturn normalizedStatus>
            </cfif>
            <cfif NOT len(fallbackStatus)>
                <cfset fallbackStatus = normalizedStatus>
            </cfif>
        </cfloop>
        <cfreturn fallbackStatus>
    </cffunction>

    <cffunction name="collectStatusesFromArrayItems" access="private" returntype="array">
        <cfargument name="items" type="array" required="true">
        <cfset var statuses = []>
        <cfset var item = "">
        <cfloop array="#arguments.items#" index="item">
            <cfif NOT isStruct(item)>
                <cfcontinue>
            </cfif>
            <cfif structKeyExists(item, "status") AND len(trim(item.status))>
                <cfset arrayAppend(statuses, trim(item.status))>
            </cfif>
            <cfif structKeyExists(item, "transactionStatus") AND len(trim(item.transactionStatus))>
                <cfset arrayAppend(statuses, trim(item.transactionStatus))>
            </cfif>
            <cfif structKeyExists(item, "state") AND len(trim(item.state))>
                <cfset arrayAppend(statuses, trim(item.state))>
            </cfif>
            <cfif structKeyExists(item, "reason") AND len(trim(item.reason))>
                <cfset arrayAppend(statuses, trim(item.reason))>
            </cfif>
        </cfloop>
        <cfreturn statuses>
    </cffunction>

    <cffunction name="mergeStatusArrays" access="private" returntype="array">
        <cfargument name="target" type="array" required="true">
        <cfargument name="source" type="array" required="true">
        <cfset var item = "">
        <cfloop array="#arguments.source#" index="item">
            <cfset arrayAppend(arguments.target, item)>
        </cfloop>
        <cfreturn arguments.target>
    </cffunction>

    <cffunction name="extractStatusesFromStruct" access="private" returntype="array">
        <cfargument name="data" type="struct" required="true">
        <cfset var statuses = []>
        <cfset var arrayKey = "">
        <cfset var arrayKeys = "evvDetails,visits,visitStatuses,results,records,transactions">
        <cfset var item = "">
        <cfloop list="#arrayKeys#" index="arrayKey">
            <cfif structKeyExists(arguments.data, arrayKey) AND isArray(arguments.data[arrayKey]) AND arrayLen(arguments.data[arrayKey]) GT 0>
                <cfset statuses = mergeStatusArrays(statuses, collectStatusesFromArrayItems(arguments.data[arrayKey]))>
            </cfif>
        </cfloop>
        <cfif structKeyExists(arguments.data, "data") AND isStruct(arguments.data.data)>
            <cfset statuses = mergeStatusArrays(statuses, extractStatusesFromStruct(arguments.data.data))>
        </cfif>
        <cfif structKeyExists(arguments.data, "reason") AND len(trim(arguments.data.reason))>
            <cfset arrayAppend(statuses, trim(arguments.data.reason))>
        </cfif>
        <cfif structKeyExists(arguments.data, "transactionStatus") AND len(trim(arguments.data.transactionStatus))>
            <cfset arrayAppend(statuses, trim(arguments.data.transactionStatus))>
        </cfif>
        <cfif structKeyExists(arguments.data, "state") AND len(trim(arguments.data.state))>
            <cfset arrayAppend(statuses, trim(arguments.data.state))>
        </cfif>
        <cfif structKeyExists(arguments.data, "status") AND len(trim(arguments.data.status))>
            <cfset arrayAppend(statuses, trim(arguments.data.status))>
        </cfif>
        <cfif structKeyExists(arguments.data, "message") AND len(trim(arguments.data.message))>
            <cfset arrayAppend(statuses, trim(arguments.data.message))>
        </cfif>
        <cfif structKeyExists(arguments.data, "errors") AND isArray(arguments.data.errors) AND arrayLen(arguments.data.errors) GT 0>
            <cfloop array="#arguments.data.errors#" index="item">
                <cfif isStruct(item) AND structKeyExists(item, "message") AND len(trim(item.message))>
                    <cfset arrayAppend(statuses, trim(item.message))>
                <cfelseif isSimpleValue(item) AND len(trim(item))>
                    <cfset arrayAppend(statuses, trim(item))>
                </cfif>
            </cfloop>
        </cfif>
        <cfreturn statuses>
    </cffunction>

    <cffunction name="extractStatusFromStruct" access="private" returntype="string">
        <cfargument name="data" type="struct" required="true">
        <cfset var statuses = extractStatusesFromStruct(arguments.data)>
        <cfreturn pickBestStatusText(statuses)>
    </cffunction>

    <cffunction name="normalizeHHAVisitStatusText" access="private" returntype="string">
        <cfargument name="statusText" type="string" required="true">
        <cfset var s = Trim(arguments.statusText)>
        <cfset var upperStatus = UCase(s)>

        <cfif NOT Len(s)>
            <cfreturn s>
        </cfif>
        <cfif Right(s, 1) EQ ".">
            <cfset s = Trim(Left(s, Len(s) - 1))>
            <cfset upperStatus = UCase(s)>
        </cfif>
        <cfif FindNoCase("Transaction Received", s) GT 0>
            <cfreturn "Transaction Received">
        </cfif>
        <cfif ListFindNoCase("COMPLETE,COMPLETED,SUCCESS,SUCCESSFUL,ACCEPTED,PROCESSED,APPROVED", upperStatus) GT 0
            OR FindNoCase("transaction received", s) GT 0>
            <cfreturn "Transaction Received">
        </cfif>
        <cfif ListFindNoCase("PENDING,PROCESSING,IN PROGRESS,INPROGRESS,SUBMITTED,QUEUED", upperStatus) GT 0>
            <cfreturn "Transmitted - awaiting status check">
        </cfif>
        <cfif FindNoCase("failed", s) GT 0 OR FindNoCase("rejected", s) GT 0 OR FindNoCase("error", s) GT 0>
            <cfreturn s>
        </cfif>
        <cfreturn s>
    </cffunction>

    <cffunction name="extractTransactionStatusText" access="private" returntype="string">
        <cfargument name="responseBody" type="string" required="true">
        <cfset var rawStatus = "">
        <cfif NOT Len(Trim(arguments.responseBody)) OR NOT IsJSON(arguments.responseBody)>
            <cfreturn "">
        </cfif>
        <cfset rawStatus = extractStatusFromStruct(DeserializeJSON(arguments.responseBody))>
        <cfreturn normalizeHHAVisitStatusText(rawStatus)>
    </cffunction>

    <cffunction name="get_visit_transaction" access="remote" returnFormat="plain">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="transactionId" required="yes">
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfif Len(agencyError) GT 0>
            <cfreturn agencyError>
        </cfif>
        <cfset var txnStatus = fetchTransactionStatusResponse("", arguments.transactionId)>
        <cfif NOT txnStatus.success>
            <cfreturn txnStatus.error>
        </cfif>
        <cfreturn txnStatus.body>
    </cffunction>

    <cffunction name="check_visit_status" access="remote" returnFormat="json">
        <cfargument name="Agency_ID" required="no" default="#session.agencyId#">
        <cfargument name="Schedule_ID" required="yes" default="0">
        <cfargument name="sendNotification" required="no" default="true" type="boolean">

        <cfset ensureRemoteContext()>
        <cfset var result = { "success" = false, "status" = "", "message" = "" }>
        <cfset var agencyError = validateAgency(arguments.Agency_ID)>
        <cfset var scheduleId = 0>
        <cfset var recordLabel = "Visit Status Check">
        <cfset var emailDetails = { "Encrypted Schedule ID" = arguments.Schedule_ID }>
        <cfset var statusRequest = {}>
        <cfset var qEvv = "">
        <cfset var transactionId = "">
        <cfset var txnStatus = "">
        <cfset var currentStatus = "">
        <cfset var syncResult = "">
        <cfset var shouldEmail = arguments.sendNotification>

        <cfif Len(agencyError) GT 0>
            <cfset result.message = agencyError>
            <cfif shouldEmail>
                <cfset sendVisitStatusCheckEmail(
                    isSuccess = false,
                    recordLabel = recordLabel,
                    requestPayload = { "method" = "check_visit_status", "Encrypted_Schedule_ID" = arguments.Schedule_ID, "Agency_ID" = arguments.Agency_ID },
                    responseBody = "",
                    errorMessage = agencyError,
                    details = emailDetails
                )>
            </cfif>
            <cfreturn result>
        </cfif>

        <cftry>
            <cfset scheduleId = decrypt(arguments.Schedule_ID, application.enckey, "AES", "Hex")>
            <cfset recordLabel = "Schedule " & scheduleId>
            <cfset emailDetails["Schedule ID"] = scheduleId>
            <cfset statusRequest = buildVisitStatusCheckRequest(scheduleId, arguments.Schedule_ID)>

            <cfquery name="qEvv" datasource="#Application.DataSrc#">
                SELECT UUID, San_status
                FROM #Request.prefix_db_lookup#.EVV
                WHERE API_Type = 'visits'
                AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
                AND Schedule_ID = <cfqueryparam value="#scheduleId#" cfsqltype="cf_sql_integer">
                AND Status = 0
                ORDER BY EVV_ID DESC
                LIMIT 1
            </cfquery>

            <cfif qEvv.recordcount EQ 0>
                <cfset result.message = "No HHA Exchange visit record found for this schedule. Submit the visit first.">
                <cfif shouldEmail>
                    <cfset sendVisitStatusCheckEmail(
                        isSuccess = false,
                        recordLabel = recordLabel,
                        requestPayload = statusRequest,
                        responseBody = "",
                        errorMessage = result.message,
                        details = emailDetails
                    )>
                </cfif>
                <cfreturn result>
            </cfif>

            <cfset transactionId = Trim(qEvv.UUID)>
            <cfset emailDetails["Previous EVV Status"] = qEvv.San_status>
            <cfset statusRequest = buildVisitStatusCheckRequest(scheduleId, arguments.Schedule_ID, transactionId)>
            <cfif Len(transactionId) EQ 0>
                <cfset result.message = "No transaction ID found for schedule #scheduleId#.">
                <cfif shouldEmail>
                    <cfset sendVisitStatusCheckEmail(
                        isSuccess = false,
                        recordLabel = recordLabel,
                        requestPayload = statusRequest,
                        responseBody = "",
                        errorMessage = result.message,
                        details = emailDetails
                    )>
                </cfif>
                <cfreturn result>
            </cfif>

            <cfset txnStatus = fetchTransactionStatusResponse("", transactionId)>
            <cfif NOT txnStatus.success>
                <cfset result.message = Len(txnStatus.error) ? txnStatus.error : "Unable to fetch HHA Exchange transaction status">
                <cfif shouldEmail>
                    <cfset sendVisitStatusCheckEmail(
                        isSuccess = false,
                        recordLabel = recordLabel,
                        requestPayload = statusRequest,
                        responseBody = txnStatus.error,
                        apiUrl = txnStatus.url,
                        transactionId = transactionId,
                        errorMessage = result.message,
                        details = emailDetails
                    )>
                </cfif>
                <cfreturn result>
            </cfif>

            <cfset currentStatus = extractTransactionStatusText(txnStatus.body)>
            <cfif Len(currentStatus) EQ 0>
                <cfset currentStatus = "Transmitted - awaiting status check">
            </cfif>

            <cfquery datasource="#Application.DataSrc#">
                UPDATE #Request.prefix_db_lookup#.EVV
                SET San_status = <cfqueryparam value="#Left(currentStatus, 255)#" cfsqltype="cf_sql_varchar">,
                    Date_Update = <cfqueryparam value="#Now()#" cfsqltype="cf_sql_timestamp">
                WHERE API_Type = 'visits'
                AND Agency_ID = <cfqueryparam value="#variables.ALLOWED_AGENCY_ID#" cfsqltype="cf_sql_integer">
                AND Schedule_ID = <cfqueryparam value="#scheduleId#" cfsqltype="cf_sql_integer">
                AND Status = 0
            </cfquery>

            <cftry>
                <cfset syncResult = createObject("component", "components.evv.EVVVisitAutomation").init().updateAutomationJobFromStatusCheck(
                    scheduleID = scheduleId,
                    agencyID = variables.ALLOWED_AGENCY_ID,
                    evvStatus = currentStatus
                )>
                <cfcatch type="any">
                    <cflog file="EVVVisitAutomation" text="check_visit_status automation sync failed schedule #scheduleId#: #cfcatch.message#">
                </cfcatch>
            </cftry>

            <cfset result.success = true>
            <cfset result.status = currentStatus>
            <cfset result.message = "Status updated">

            <cfif shouldEmail>
                <cfset sendVisitStatusCheckEmail(
                    isSuccess = true,
                    recordLabel = recordLabel,
                    requestPayload = statusRequest,
                    responseBody = txnStatus.body,
                    apiUrl = txnStatus.url,
                    transactionId = transactionId,
                    details = emailDetails,
                    currentStatus = currentStatus
                )>
            </cfif>

            <cfcatch type="any">
                <cfset result.message = cfcatch.message>
                <cflog file="HHAExchange_EVV" text="check_visit_status exception schedule #scheduleId#: #cfcatch.message# #cfcatch.detail#">
                <cfif shouldEmail>
                    <cfset sendVisitStatusCheckEmail(
                        isSuccess = false,
                        recordLabel = recordLabel,
                        requestPayload = NOT structIsEmpty(statusRequest) ? statusRequest : { "method" = "check_visit_status", "Encrypted_Schedule_ID" = arguments.Schedule_ID },
                        responseBody = "",
                        transactionId = transactionId,
                        errorMessage = cfcatch.message,
                        details = emailDetails
                    )>
                </cfif>
            </cfcatch>
        </cftry>

        <cfreturn result>
    </cffunction>

</cfcomponent>
