<cfcomponent displayname="Mobile Progress Notes API"
    hint="JSON API for progress notes visit form (securityKey + Schedule_ID). Mirrors progress_notesnew.cfm section visibility and field types."
    output="false">

    <cfheader name="Access-Control-Allow-Origin" value="*">
    <cfheader name="Access-Control-Allow-Methods" value="GET, POST, PUT, DELETE, OPTIONS">
    <cfheader name="Access-Control-Allow-Headers" value="Content-Type, Authorization, Accept, X-Requested-With">
    <cfheader name="Access-Control-Max-Age" value="86400">
    <cfif structKeyExists(cgi, "REQUEST_METHOD") AND compareNoCase(cgi.REQUEST_METHOD, "OPTIONS") EQ 0>
        <cfheader statuscode="204" statustext="No Content">
        <cfabort>
    </cfif>

    <cfset this.securityKeyEncryptKey = "5A2zPUZJ90AbHOje9DpM+g==" />

    <cfif NOT isDefined("Application.DataSrc")>
        <cfset Application.DataSrc = "hhapowerpath" />
    </cfif>

    <!--- Cast numeric IDs so serializeJSON does not emit 123.0 --->
    <cffunction name="toJsonInt" access="private" returntype="any" output="false">
        <cfargument name="value" required="true" />
        <cftry>
            <cfreturn javacast("int", int(val(arguments.value))) />
            <cfcatch type="any">
                <cfreturn int(val(arguments.value)) />
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="safeTrim" access="private" returntype="string" output="false">
        <cfargument name="value" required="false" default="" />
        <cftry>
            <cfif isNull(arguments.value)>
                <cfreturn "" />
            </cfif>
            <cfreturn trim("" & arguments.value) />
            <cfcatch type="any">
                <cfreturn "" />
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="queryCol" access="private" returntype="any" output="false">
        <cfargument name="q" type="query" required="true" />
        <cfargument name="colName" type="string" required="true" />
        <cfargument name="rowIndex" type="numeric" required="true" />
        <cfargument name="defaultValue" required="false" default="" />
        <cfif listFindNoCase(arguments.q.columnList, arguments.colName)>
            <cfreturn arguments.q[arguments.colName][arguments.rowIndex] />
        </cfif>
        <cfreturn arguments.defaultValue />
    </cffunction>

    <cffunction name="parseSecurityKeyEmpId" access="private" returntype="numeric" output="false">
        <cfargument name="securityKey" type="string" required="true" />
        <cfset var plain = "" />
        <cfset var empPart = "" />
        <cftry>
            <cfif NOT len(trim(arguments.securityKey))>
                <cfreturn 0 />
            </cfif>
            <cfset plain = decrypt(trim(arguments.securityKey), this.securityKeyEncryptKey, "AES", "Base64") />
            <cfset empPart = listFirst(plain, "|") />
            <cfif isNumeric(empPart) AND val(empPart) GT 0>
                <cfreturn val(empPart) />
            </cfif>
            <cfreturn 0 />
            <cfcatch type="any">
                <cfreturn 0 />
            </cfcatch>
        </cftry>
    </cffunction>

    <cffunction name="authorizeSecurityKeyForAgency" access="private" returntype="struct" output="false">
        <cfargument name="securityKey" type="string" required="true" />
        <cfset var result = { ok = false, statusCode = 401, message = "Unauthorized", agencySchema = "", locId = 0 } />
        <cfset var empId = parseSecurityKeyEmpId(arguments.securityKey) />
        <cfset var employeeQuery = "" />
        <cfset var schemaCheckQuery = "" />

        <cfif empId EQ 0>
            <cfset result.message = "Invalid or missing security key" />
            <cfreturn result />
        </cfif>

        <cfquery name="employeeQuery" datasource="#Application.DataSrc#">
            SELECT Emp_ID, Loc_ID
            FROM hhapowerpath.pEmployee
            WHERE Emp_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#empId#">
              AND Emp_Type = 'Employee'
              AND Status = 0
            LIMIT 1
        </cfquery>
        <cfif employeeQuery.recordcount EQ 0>
            <cfset result.message = "Employee not found or inactive" />
            <cfreturn result />
        </cfif>
        <cfif NOT isNumeric(employeeQuery.Loc_ID)>
            <cfset result.message = "Invalid agency mapping for user" />
            <cfset result.statusCode = 400 />
            <cfreturn result />
        </cfif>

        <cfset result.agencySchema = "agency_" & val(employeeQuery.Loc_ID) />
        <cfset result.locId = val(employeeQuery.Loc_ID) />

        <cfquery name="schemaCheckQuery" datasource="#Application.DataSrc#">
            SELECT SCHEMA_NAME
            FROM information_schema.schemata
            WHERE SCHEMA_NAME = <cfqueryparam cfsqltype="cf_sql_varchar" value="#result.agencySchema#">
        </cfquery>
        <cfif schemaCheckQuery.recordcount EQ 0>
            <cfset result.message = "Agency database not found" />
            <cfset result.statusCode = 400 />
            <cfreturn result />
        </cfif>

        <cfset result.ok = true />
        <cfset result.message = "" />
        <cfreturn result />
    </cffunction>

    <cffunction name="getScheduleForEmployee" access="private" returntype="struct" output="false">
        <cfargument name="agencySchema" type="string" required="true" />
        <cfargument name="scheduleId" type="numeric" required="true" />
        <cfargument name="empId" type="numeric" required="true" />
        <cfset var result = { ok = false, message = "", schedule = {} } />
        <cfset var scheduleQuery = "" />

        <cfif arguments.scheduleId LTE 0>
            <cfset result.message = "Schedule_ID is required" />
            <cfreturn result />
        </cfif>

        <cfquery name="scheduleQuery" datasource="#Application.DataSrc#">
            SELECT Schedule_ID, Patient_ID, Assmt_ID, Emp_ID, Visit_Type, Visit_Date, Skill, Status
            FROM #arguments.agencySchema#.pSchedules
            WHERE Schedule_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.scheduleId#">
              AND Status = 0
            LIMIT 1
        </cfquery>
        <cfif scheduleQuery.recordcount EQ 0>
            <cfset result.message = "Schedule not found or inactive" />
            <cfreturn result />
        </cfif>
        <cfif val(scheduleQuery.Emp_ID) NEQ val(arguments.empId)>
            <cfset result.message = "Schedule is not assigned to this employee" />
            <cfreturn result />
        </cfif>

        <cfset result.ok = true />
        <cfset result.schedule = {
            Schedule_ID = toJsonInt(scheduleQuery.Schedule_ID),
            Patient_ID = toJsonInt(scheduleQuery.Patient_ID),
            Assmt_ID = toJsonInt(scheduleQuery.Assmt_ID),
            Emp_ID = toJsonInt(scheduleQuery.Emp_ID),
            Visit_Type = safeTrim(scheduleQuery.Visit_Type),
            Visit_Date = isDate(scheduleQuery.Visit_Date) ? dateFormat(scheduleQuery.Visit_Date, "yyyy-mm-dd") : safeTrim(scheduleQuery.Visit_Date),
            Skill = safeTrim(scheduleQuery.Skill)
        } />
        <cfreturn result />
    </cffunction>

    <cffunction name="parseProgressNotesRequest" access="private" returntype="struct" output="false">
        <cfargument name="securityKeyArg" type="string" required="false" default="" />
        <cfargument name="scheduleIdArg" type="string" required="false" default="" />
        <cfset var out = { securityKey = trim(arguments.securityKeyArg), scheduleId = val(trim(arguments.scheduleIdArg)) } />
        <cfset var httpData = {} />
        <cfset var bodyText = "" />
        <cfset var bodyJson = {} />

        <cfif NOT len(out.securityKey) AND structKeyExists(form, "securityKey")>
            <cfset out.securityKey = trim(form.securityKey) />
        </cfif>
        <cfif out.scheduleId LTE 0 AND structKeyExists(form, "Schedule_ID")>
            <cfset out.scheduleId = val(trim(form.Schedule_ID)) />
        </cfif>
        <cfif out.scheduleId LTE 0 AND structKeyExists(form, "schedule_id")>
            <cfset out.scheduleId = val(trim(form.schedule_id)) />
        </cfif>

        <cfif NOT len(out.securityKey) OR out.scheduleId LTE 0>
            <cftry>
                <cfset httpData = getHttpRequestData() />
                <cfif structKeyExists(httpData, "content") AND isBinary(httpData.content)>
                    <cfset bodyText = toString(httpData.content) />
                <cfelseif structKeyExists(httpData, "content")>
                    <cfset bodyText = "" & httpData.content />
                </cfif>
                <cfif len(trim(bodyText)) AND left(trim(bodyText), 1) EQ "{">
                    <cfset bodyJson = deserializeJSON(bodyText) />
                    <cfif NOT len(out.securityKey) AND structKeyExists(bodyJson, "securityKey")>
                        <cfset out.securityKey = trim("" & bodyJson.securityKey) />
                    </cfif>
                    <cfif out.scheduleId LTE 0 AND structKeyExists(bodyJson, "Schedule_ID")>
                        <cfset out.scheduleId = val(trim("" & bodyJson.Schedule_ID)) />
                    </cfif>
                    <cfif out.scheduleId LTE 0 AND structKeyExists(bodyJson, "schedule_id")>
                        <cfset out.scheduleId = val(trim("" & bodyJson.schedule_id)) />
                    </cfif>
                </cfif>
                <cfcatch type="any"></cfcatch>
            </cftry>
        </cfif>
        <cfreturn out />
    </cffunction>

    <cffunction name="scaleOptions0to4" access="private" returntype="array" output="false">
        <cfreturn [
            { value = "0", label = "0" },
            { value = "1", label = "1" },
            { value = "2", label = "2" },
            { value = "3", label = "3" },
            { value = "4", label = "4" }
        ] />
    </cffunction>

    <cffunction name="physicianAlertField" access="private" returntype="struct" output="false">
        <cfargument name="value" required="false" default="" />
        <cfset var alertVal = val(arguments.value) />
        <cfreturn {
            description = "Physician Alerted?",
            description_type = "radio",
            field = "Physician_alert",
            value = toJsonInt(alertVal),
            options = [
                { value = toJsonInt(1), label = "Yes" },
                { value = toJsonInt(0), label = "No" }
            ]
        } />
    </cffunction>

    <cffunction name="notesField" access="private" returntype="struct" output="false">
        <cfargument name="value" required="false" default="" />
        <cfreturn {
            description = "Notes",
            description_type = "textbox",
            field = "Notes",
            value = safeTrim(arguments.value)
        } />
    </cffunction>

    <cffunction name="groupItemsByHeading" access="private" returntype="array" output="false">
        <cfargument name="items" type="array" required="true" />
        <cfargument name="headingKey" type="string" required="false" default="heading" />
        <cfset var groups = [] />
        <cfset var indexMap = {} />
        <cfset var i = 0 />
        <cfset var item = {} />
        <cfset var heading = "" />
        <cfset var gIdx = 0 />

        <cfloop from="1" to="#arrayLen(arguments.items)#" index="i">
            <cfset item = arguments.items[i] />
            <cfset heading = structKeyExists(item, arguments.headingKey) ? safeTrim(item[arguments.headingKey]) : "" />
            <cfif NOT len(heading)>
                <cfset heading = "" />
            </cfif>
            <cfif NOT structKeyExists(indexMap, heading)>
                <cfset arrayAppend(groups, { heading = heading, items = [] }) />
                <cfset indexMap[heading] = arrayLen(groups) />
            </cfif>
            <cfset gIdx = indexMap[heading] />
            <cfset arrayAppend(groups[gIdx].items, item) />
        </cfloop>
        <cfreturn groups />
    </cffunction>

    <!--- Drop top-level items when groups are present (avoids duplicate rows in JSON) --->
    <cffunction name="finalizeGroupedSection" access="private" returntype="struct" output="false">
        <cfargument name="section" type="struct" required="true" />
        <cfif structKeyExists(arguments.section, "groups") AND isArray(arguments.section.groups) AND arrayLen(arguments.section.groups) GT 0>
            <cfset structDelete(arguments.section, "items") />
        </cfif>
        <cfreturn arguments.section />
    </cffunction>

    <!--- Skip duplicate Progress_ID rows from join-heavy queries --->
    <cffunction name="markProgressIdSeen" access="private" returntype="boolean" output="false">
        <cfargument name="seenIds" type="struct" required="true" />
        <cfargument name="progressId" type="numeric" required="true" />
        <cfif arguments.progressId LTE 0>
            <cfreturn true />
        </cfif>
        <cfif structKeyExists(arguments.seenIds, arguments.progressId)>
            <cfreturn false />
        </cfif>
        <cfset arguments.seenIds[arguments.progressId] = true />
        <cfreturn true />
    </cffunction>

    <cffunction name="formatPatientComplaint" access="private" returntype="struct" output="false">
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />
        <cfset var section = {
            key = "patient_complaint",
            heading = "Patient Complaints",
            visible = arguments.visible,
            items = []
        } />
        <cfset var item = {} />
        <cfif arguments.q.recordcount GT 0>
            <cfset item = {
                progress_id = toJsonInt(queryCol(arguments.q, "Progress_ID", 1, 0)),
                description = "Patient Complaints",
                description_type = "textbox",
                field = "Description",
                value = safeTrim(queryCol(arguments.q, "Description", 1, "")),
                type = "patient_complaint"
            } />
            <cfset arrayAppend(section.items, item) />
        </cfif>
        <cfreturn section />
    </cffunction>

    <cffunction name="formatScaleSection" access="private" returntype="struct" output="false"
        hint="Patient goals / goals / discharge: description label + notes textbox + YesOrNo radio 0-4.">
        <cfargument name="key" type="string" required="true" />
        <cfargument name="heading" type="string" required="true" />
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />
        <cfargument name="scaleLegend" type="string" required="false" default="0-0% 1-25% 2-50% 3-75% 4-100% met" />
        <cfargument name="includePhysicianAlert" type="boolean" required="false" default="false" />
        <cfargument name="includeTargetDate" type="boolean" required="false" default="false" />

        <cfset var section = {
            key = arguments.key,
            heading = arguments.heading,
            visible = arguments.visible,
            scale_legend = arguments.scaleLegend,
            groups = [],
            items = []
        } />
        <cfset var row = 0 />
        <cfset var item = {} />
        <cfset var headerVal = "" />
        <cfset var targetDate = "" />
        <cfset var seenIds = {} />
        <cfset var progressId = 0 />

        <cfloop from="1" to="#arguments.q.recordcount#" index="row">
            <cfset progressId = val(queryCol(arguments.q, "Progress_ID", row, 0)) />
            <cfif NOT markProgressIdSeen(seenIds, progressId)>
                <cfcontinue />
            </cfif>
            <cfset headerVal = safeTrim(queryCol(arguments.q, "path_header", row, "")) />
            <cfif NOT len(headerVal)>
                <cfset headerVal = safeTrim(queryCol(arguments.q, "Header", row, "")) />
            </cfif>
            <cfset targetDate = "" />
            <cfif arguments.includeTargetDate>
                <cfset targetDate = queryCol(arguments.q, "cp_date_target", row, "") />
                <cfif NOT isDate(targetDate)>
                    <cfset targetDate = queryCol(arguments.q, "Date_Target", row, "") />
                </cfif>
                <cfif isDate(targetDate)>
                    <cfset targetDate = dateFormat(targetDate, "yyyy-mm-dd") />
                <cfelse>
                    <cfset targetDate = safeTrim(targetDate) />
                </cfif>
            </cfif>

            <cfset item = {
                progress_id = toJsonInt(queryCol(arguments.q, "Progress_ID", row, 0)),
                item_id = toJsonInt(queryCol(arguments.q, "Item_ID", row, 0)),
                careplan_id = toJsonInt(queryCol(arguments.q, "Careplan_ID", row, 0)),
                type = safeTrim(queryCol(arguments.q, "Type", row, "")),
                heading = headerVal,
                description = safeTrim(queryCol(arguments.q, "Description", row, "")),
                description_type = "radio",
                scale = {
                    description = "Progress / met",
                    description_type = "radio",
                    field = "YesOrNo",
                    value = safeTrim(queryCol(arguments.q, "YesOrNo", row, "")),
                    options = scaleOptions0to4(),
                    legend = arguments.scaleLegend
                },
                notes = notesField(queryCol(arguments.q, "Notes", row, "")),
                date_target = targetDate
            } />
            <cfif arguments.includePhysicianAlert>
                <cfset item.physician_alert = physicianAlertField(queryCol(arguments.q, "Physician_alert", row, 0)) />
            </cfif>
            <cfif listFindNoCase(arguments.q.columnList, "activate")>
                <cfset item.activate = toJsonInt(queryCol(arguments.q, "activate", row, 0)) />
            </cfif>
            <cfset arrayAppend(section.items, item) />
        </cfloop>
        <cfset section.groups = groupItemsByHeading(section.items, "heading") />
        <cfreturn finalizeGroupedSection(section) />
    </cffunction>

    <cffunction name="formatYesNoSection" access="private" returntype="struct" output="false"
        hint="Procedures / standing orders: Yes/No radio + notes.">
        <cfargument name="key" type="string" required="true" />
        <cfargument name="heading" type="string" required="true" />
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />

        <cfset var section = {
            key = arguments.key,
            heading = arguments.heading,
            visible = arguments.visible,
            groups = [],
            items = []
        } />
        <cfset var row = 0 />
        <cfset var item = {} />
        <cfset var headerVal = "" />
        <cfset var seenIds = {} />
        <cfset var progressId = 0 />

        <cfloop from="1" to="#arguments.q.recordcount#" index="row">
            <cfset progressId = val(queryCol(arguments.q, "Progress_ID", row, 0)) />
            <cfif NOT markProgressIdSeen(seenIds, progressId)>
                <cfcontinue />
            </cfif>
            <cfset headerVal = safeTrim(queryCol(arguments.q, "path_header", row, "")) />
            <cfif NOT len(headerVal)>
                <cfset headerVal = safeTrim(queryCol(arguments.q, "Header", row, "")) />
            </cfif>
            <cfset item = {
                progress_id = toJsonInt(queryCol(arguments.q, "Progress_ID", row, 0)),
                item_id = toJsonInt(queryCol(arguments.q, "Item_ID", row, 0)),
                careplan_id = toJsonInt(queryCol(arguments.q, "Careplan_ID", row, 0)),
                type = safeTrim(queryCol(arguments.q, "Type", row, "")),
                heading = headerVal,
                description = safeTrim(queryCol(arguments.q, "Description", row, "")),
                description_type = "radio",
                yes_or_no = {
                    description = "Performed",
                    description_type = "radio",
                    field = "YesOrNo",
                    value = safeTrim(queryCol(arguments.q, "YesOrNo", row, "")),
                    options = [
                        { value = "Yes", label = "Yes" },
                        { value = "No", label = "No" }
                    ]
                },
                notes = notesField(queryCol(arguments.q, "Notes", row, ""))
            } />
            <cfset arrayAppend(section.items, item) />
        </cfloop>
        <cfset section.groups = groupItemsByHeading(section.items, "heading") />
        <cfreturn finalizeGroupedSection(section) />
    </cffunction>

    <cffunction name="formatVitalsSection" access="private" returntype="struct" output="false">
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />
        <cfset var section = {
            key = "vitals",
            heading = "Vitals",
            visible = arguments.visible,
            items = []
        } />
        <cfset var row = 0 />
        <cfset var item = {} />
        <cfset var paramLo = "" />
        <cfset var paramHi = "" />
        <cfset var seenIds = {} />
        <cfset var progressId = 0 />

        <cfloop from="1" to="#arguments.q.recordcount#" index="row">
            <cfset progressId = val(queryCol(arguments.q, "Progress_ID", row, 0)) />
            <cfif NOT markProgressIdSeen(seenIds, progressId)>
                <cfcontinue />
            </cfif>
            <cfset paramLo = safeTrim(queryCol(arguments.q, "wound_param_lo", row, "")) />
            <cfif NOT len(paramLo)>
                <cfset paramLo = safeTrim(queryCol(arguments.q, "Param_Lo", row, "")) />
            </cfif>
            <cfset paramHi = safeTrim(queryCol(arguments.q, "wound_param_hi", row, "")) />
            <cfif NOT len(paramHi)>
                <cfset paramHi = safeTrim(queryCol(arguments.q, "Param_Hi", row, "")) />
            </cfif>

            <cfset item = {
                progress_id = toJsonInt(queryCol(arguments.q, "Progress_ID", row, 0)),
                item_id = toJsonInt(queryCol(arguments.q, "Item_ID", row, 0)),
                careplan_id = toJsonInt(queryCol(arguments.q, "Careplan_ID", row, 0)),
                wounds_vital_id = toJsonInt(queryCol(arguments.q, "woundsid", row, 0)),
                type = "vital",
                description = safeTrim(queryCol(arguments.q, "Description", row, "")),
                description_type = "select",
                value_1 = safeTrim(queryCol(arguments.q, "Value_1", row, "")),
                value_2 = safeTrim(queryCol(arguments.q, "Value_2", row, "")),
                value_sum = safeTrim(queryCol(arguments.q, "Value_Sum", row, "")),
                param_lo = paramLo,
                param_hi = paramHi,
                severity = safeTrim(queryCol(arguments.q, "Severity", row, "")),
                notes = notesField(queryCol(arguments.q, "Notes", row, "")),
                physician_alert = physicianAlertField(queryCol(arguments.q, "Physician_alert", row, 0)),
                pain = {
                    location = safeTrim(queryCol(arguments.q, "pain_location", row, "")),
                    duration = safeTrim(queryCol(arguments.q, "pain_duration", row, "")),
                    relief = safeTrim(queryCol(arguments.q, "pain_relief", row, "")),
                    exacerbate = safeTrim(queryCol(arguments.q, "pain_exacerbate", row, "")),
                    med_effectiveness = safeTrim(queryCol(arguments.q, "pain_med_effectiveness", row, ""))
                }
            } />
            <cfset arrayAppend(section.items, item) />
        </cfloop>
        <cfreturn section />
    </cffunction>

    <cffunction name="woundCheckboxGroup" access="private" returntype="struct" output="false">
        <cfargument name="description" type="string" required="true" />
        <cfargument name="field" type="string" required="true" />
        <cfargument name="options" type="array" required="true" />
        <cfargument name="selectedCsv" type="string" required="false" default="" />
        <cfset var selected = [] />
        <cfset var opt = "" />
        <cfset var i = 0 />
        <cfset var csv = lCase(arguments.selectedCsv) />

        <cfloop from="1" to="#arrayLen(arguments.options)#" index="i">
            <cfset opt = arguments.options[i] />
            <cfif len(csv) AND findNoCase(opt, csv)>
                <cfset arrayAppend(selected, opt) />
            </cfif>
        </cfloop>
        <cfreturn {
            description = arguments.description,
            description_type = "checkbox",
            field = arguments.field,
            options = arguments.options,
            value = selected,
            raw = safeTrim(arguments.selectedCsv)
        } />
    </cffunction>

    <cffunction name="formatSymptomsFromQuery" access="private" returntype="struct" output="false">
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />
        <cfargument name="skill" type="string" required="true" />

        <cfset var section = {
            key = "symptoms",
            heading = "SYMPTOMS",
            visible = arguments.visible,
            severity_legend = "0 - asymptomatic 1 - well-controlled 2 - controlled with difficulty 3 - poorly controlled 4 - hx hospitalizations",
            groups = [],
            items = []
        } />
        <cfset var row = 0 />
        <cfset var item = {} />
        <cfset var headerVal = "" />
        <cfset var woundTitle = "" />
        <cfset var desc = "" />
        <cfset var isWound = false />
        <cfset var isGG = false />
        <cfset var scaleOpts = [] />
        <cfset var scaleLegend = "" />
        <cfset var seenIds = {} />
        <cfset var progressId = 0 />

        <cfloop from="1" to="#arguments.q.recordcount#" index="row">
            <cfset progressId = val(queryCol(arguments.q, "Progress_ID", row, 0)) />
            <cfif NOT markProgressIdSeen(seenIds, progressId)>
                <cfcontinue />
            </cfif>
            <cfset desc = safeTrim(queryCol(arguments.q, "Description", row, "")) />
            <cfset woundTitle = safeTrim(queryCol(arguments.q, "woundstitle", row, "")) />
            <cfset headerVal = safeTrim(queryCol(arguments.q, "Header", row, "")) />
            <cfif len(woundTitle)>
                <cfset headerVal = woundTitle />
            <cfelseif NOT len(headerVal)>
                <cfset headerVal = safeTrim(queryCol(arguments.q, "path_header", row, "")) />
            </cfif>
            <cfif NOT len(headerVal)>
                <cfset headerVal = "General" />
            </cfif>

            <cfset isWound = (len(woundTitle) GT 0) OR (left(desc, 1) EQ "##") />
            <cfset isGG = (left(desc, 2) EQ "GG") OR (findNoCase("GG", desc) EQ 1) />

            <cfif isGG AND arguments.skill NEQ "SN" AND arguments.skill NEQ "HHA">
                <cfset scaleOpts = [
                    { value = "6", label = "6" },
                    { value = "5", label = "5" },
                    { value = "4", label = "4" },
                    { value = "3", label = "3" },
                    { value = "2", label = "2" },
                    { value = "1", label = "1" },
                    { value = "10", label = "not assessed" }
                ] />
                <cfset scaleLegend = "06 Independent / 05 Setup / 04 Supervision / 03 Partial / 02 Substantial / 01 Dependent" />
            <cfelse>
                <cfset scaleOpts = scaleOptions0to4() />
                <cfset scaleLegend = section.severity_legend />
            </cfif>

            <cfset item = {
                progress_id = toJsonInt(queryCol(arguments.q, "Progress_ID", row, 0)),
                item_id = toJsonInt(queryCol(arguments.q, "Item_ID", row, 0)),
                careplan_id = toJsonInt(queryCol(arguments.q, "Careplan_ID", row, 0)),
                type = safeTrim(queryCol(arguments.q, "Type", row, "")),
                heading = headerVal,
                woundstitle = woundTitle,
                description = desc,
                description_type = isWound ? "wound_assessment" : "radio",
                severity = {
                    description = "Severity",
                    description_type = "radio",
                    field = "Severity",
                    value = safeTrim(queryCol(arguments.q, "Severity", row, "")),
                    options = scaleOpts,
                    legend = scaleLegend
                },
                notes = notesField(queryCol(arguments.q, "Notes", row, "")),
                physician_alert = physicianAlertField(queryCol(arguments.q, "Physician_alert", row, 0)),
                value_1 = safeTrim(queryCol(arguments.q, "Value_1", row, "")),
                value_2 = safeTrim(queryCol(arguments.q, "Value_2", row, "")),
                value_3 = safeTrim(queryCol(arguments.q, "Value_3", row, "")),
                value_sum = safeTrim(queryCol(arguments.q, "Value_Sum", row, "")),
                is_wound = isWound,
                is_gg = isGG
            } />

            <cfif isWound>
                <cfset item.wound = {
                    healing_stage = {
                        description = "Healing Stage",
                        description_type = "select",
                        field = "Value_Sum",
                        value = safeTrim(queryCol(arguments.q, "Value_Sum", row, ""))
                    },
                    dimensions = {
                        description = "Dimensions (L / W / D)",
                        description_type = "select",
                        length = safeTrim(queryCol(arguments.q, "Value_1", row, "")),
                        width = safeTrim(queryCol(arguments.q, "Value_2", row, "")),
                        depth = safeTrim(queryCol(arguments.q, "Value_3", row, ""))
                    },
                    tunneling = {
                        description = "tunneling dep/drctn",
                        description_type = "textbox",
                        field = "wnd_tunneling",
                        value = safeTrim(queryCol(arguments.q, "wnd_tunneling", row, ""))
                    },
                    undermining = {
                        description = "undermn dep/drctn",
                        description_type = "textbox",
                        field = "wnd_underm",
                        value = safeTrim(queryCol(arguments.q, "wnd_underm", row, ""))
                    },
                    pct_granulation = {
                        description = "% granulation",
                        description_type = "textbox",
                        field = "wnd_gran",
                        value = safeTrim(queryCol(arguments.q, "wnd_gran", row, ""))
                    },
                    pct_epithelial = {
                        description = "% epithelial",
                        description_type = "textbox",
                        field = "wnd_epith",
                        value = safeTrim(queryCol(arguments.q, "wnd_epith", row, ""))
                    },
                    pct_eschar = {
                        description = "% eschar",
                        description_type = "textbox",
                        field = "wnd_eschar",
                        value = safeTrim(queryCol(arguments.q, "wnd_eschar", row, ""))
                    },
                    pct_slough = {
                        description = "% slough",
                        description_type = "textbox",
                        field = "wnd_slough",
                        value = safeTrim(queryCol(arguments.q, "wnd_slough", row, ""))
                    },
                    pct_fibrinous = {
                        description = "% fibrinous",
                        description_type = "textbox",
                        field = "wnd_fib",
                        value = safeTrim(queryCol(arguments.q, "wnd_fib", row, ""))
                    },
                    margin = woundCheckboxGroup(
                        "Wound Margin",
                        "wnd_margin",
                        ["regular", "irregular", "rolled", "epithelialized", "macerated", "other"],
                        queryCol(arguments.q, "wnd_margin", row, "")
                    ),
                    exudate = woundCheckboxGroup(
                        "Wound Exudate",
                        "wnd_exudate",
                        ["none", "scant", "moderate", "large", "clear", "sero-sanguinous", "sanguinous", "purulent", "other"],
                        queryCol(arguments.q, "wnd_exudate", row, "")
                    ),
                    odor = woundCheckboxGroup(
                        "Wound Odor",
                        "wnd_odor",
                        ["none", "mild", "foul", "other"],
                        queryCol(arguments.q, "wnd_odor", row, "")
                    ),
                    pain = woundCheckboxGroup(
                        "Wound Pain",
                        "wnd_pain",
                        ["none", "mild", "moderate", "severe", "other"],
                        queryCol(arguments.q, "wnd_pain", row, "")
                    )
                } />
            </cfif>

            <cfset arrayAppend(section.items, item) />
        </cfloop>

        <cfset section.groups = groupItemsByHeading(section.items, "heading") />
        <cfreturn finalizeGroupedSection(section) />
    </cffunction>

    <cffunction name="ggAdlSeverityScale" access="private" returntype="struct" output="false"
        hint="PT/OT use 01-06 ADL scale; SN/HHA use 0-4 (matches progress_notesnew.cfm GG section).">
        <cfargument name="skill" type="string" required="true" />
        <cfargument name="description" type="string" required="true" />
        <cfset var out = { options = [], legend = "" } />
        <cfset var desc = safeTrim(arguments.description) />
        <cfset var useAdlScale = (
            compareNoCase(arguments.skill, "SN") NEQ 0
            AND compareNoCase(arguments.skill, "HHA") NEQ 0
            AND findNoCase("GG", desc) GT 0
        ) />

        <cfif useAdlScale>
            <cfset out.options = [
                { value = "6", label = "6" },
                { value = "5", label = "5" },
                { value = "4", label = "4" },
                { value = "3", label = "3" },
                { value = "2", label = "2" },
                { value = "1", label = "1" }
            ] />
            <cfset out.legend = "06 Independent / 05 Setup or cleanup assistance / 04 Supervision or touching assistance / 03 Partial/moderate assistance / 02 Substantial/maximal assistance / 01 Dependent" />
        <cfelse>
            <cfset out.options = scaleOptions0to4() />
            <cfset out.legend = "0 - asymptomatic 1 - well-controlled 2 - controlled with difficulty 3 - poorly controlled 4 - hx hospitalizations" />
        </cfif>
        <cfreturn out />
    </cffunction>

    <!--- Mirrors progress_notesnew.cfm show_GG logic for PT/OT/SN --->
    <cffunction name="shouldShowGgSymptomRow" access="private" returntype="boolean" output="false">
        <cfargument name="skill" type="string" required="true" />
        <cfargument name="description" type="string" required="true" />
        <cfargument name="pathId" type="string" required="false" default="" />
        <cfargument name="careplanGoals" type="query" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="datasource" type="string" required="true" />

        <cfset var qGGpathway = "" />
        <cfset var qGGgoals = "" />
        <cfset var goalsdescription = [] />
        <cfset var desc = safeTrim(arguments.description) />

        <cfif NOT len(trim(arguments.pathId))>
            <cfreturn false />
        </cfif>

        <cfquery name="qGGpathway" datasource="#arguments.datasource#">
            SELECT Item_ID, Description
            FROM #arguments.lookupSchema#.pPathway
            WHERE Pathway_ID = <cfqueryparam value="#arguments.pathId#" cfsqltype="cf_sql_varchar">
              AND type = <cfqueryparam value="goal" cfsqltype="cf_sql_varchar">
              AND Header IS NOT NULL
              AND Description LIKE <cfqueryparam value="% - 0%" cfsqltype="cf_sql_varchar">
        </cfquery>
        <cfif qGGpathway.recordcount EQ 0>
            <cfreturn false />
        </cfif>

        <cfquery name="qGGgoals" dbtype="query">
            SELECT *
            FROM careplanGoals
            WHERE Item_ID IN (<cfqueryparam value="#valueList(qGGpathway.Item_ID)#" cfsqltype="cf_sql_integer" list="true">)
        </cfquery>

        <cfif compareNoCase(arguments.skill, "SN") EQ 0>
            <cfreturn true />
        </cfif>
        <cfif left(desc, 2) NEQ "GG">
            <cfreturn true />
        </cfif>
        <cfif qGGgoals.recordcount GT 0>
            <cfset goalsdescription = listToArray(qGGgoals.Description, "-") />
            <cfif arrayLen(goalsdescription) GTE 2>
                <cfreturn true />
            </cfif>
        </cfif>
        <cfreturn false />
    </cffunction>

    <!--- Goal met + resolved → disable severity radios (block_radiobutton on web) --->
    <cffunction name="evaluateGgGoalMet" access="private" returntype="struct" output="false">
        <cfargument name="severity" type="string" required="true" />
        <cfargument name="pathId" type="string" required="false" default="" />
        <cfargument name="careplanGoals" type="query" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="datasource" type="string" required="true" />

        <cfset var out = { goal_met = false, disabled = false, linked_goals = [] } />
        <cfset var qGGpathway = "" />
        <cfset var qGGgoals = "" />
        <cfset var goalsdescription = [] />
        <cfset var ggGoalSeverity = 0 />
        <cfset var goalDateResolve = "" />
        <cfset var secondPart = "" />
        <cfset var thirdPart = "" />

        <cfif NOT len(trim(arguments.pathId))>
            <cfreturn out />
        </cfif>

        <cfquery name="qGGpathway" datasource="#arguments.datasource#">
            SELECT Item_ID, Description
            FROM #arguments.lookupSchema#.pPathway
            WHERE Pathway_ID = <cfqueryparam value="#arguments.pathId#" cfsqltype="cf_sql_varchar">
              AND type = <cfqueryparam value="goal" cfsqltype="cf_sql_varchar">
              AND Header IS NOT NULL
              AND Description LIKE <cfqueryparam value="% - 0%" cfsqltype="cf_sql_varchar">
        </cfquery>
        <cfloop query="qGGpathway">
            <cfset arrayAppend(out.linked_goals, safeTrim(qGGpathway.Description)) />
        </cfloop>
        <cfif qGGpathway.recordcount EQ 0>
            <cfreturn out />
        </cfif>

        <cfquery name="qGGgoals" dbtype="query">
            SELECT *
            FROM careplanGoals
            WHERE Item_ID IN (<cfqueryparam value="#valueList(qGGpathway.Item_ID)#" cfsqltype="cf_sql_integer" list="true">)
        </cfquery>
        <cfif qGGgoals.recordcount EQ 0>
            <cfreturn out />
        </cfif>

        <cfset goalDateResolve = safeTrim(qGGgoals.Date_Resolve) />
        <cfset goalsdescription = listToArray(qGGgoals.Description, "-") />
        <cfif arrayLen(goalsdescription) GTE 2>
            <cfset secondPart = left(trim(goalsdescription[2]), 2) />
            <cfif isNumeric(secondPart)>
                <cfset ggGoalSeverity = val(secondPart) />
            <cfelseif arrayLen(goalsdescription) GTE 3>
                <cfset thirdPart = left(trim(goalsdescription[3]), 2) />
                <cfif isNumeric(thirdPart)>
                    <cfset ggGoalSeverity = val(thirdPart) />
                </cfif>
            </cfif>
        </cfif>
        <cfif val(arguments.severity) GTE ggGoalSeverity AND len(goalDateResolve) GT 0>
            <cfset out.goal_met = true />
            <cfset out.disabled = true />
        </cfif>
        <cfreturn out />
    </cffunction>

    <cffunction name="formatGgSymptomsSection" access="private" returntype="struct" output="false"
        hint="GG Symptoms (ADLs) — shown under GOALS on web; skill-filtered for PT/OT/SN.">
        <cfargument name="q" type="query" required="true" />
        <cfargument name="visible" type="boolean" required="true" />
        <cfargument name="skill" type="string" required="true" />
        <cfargument name="careplanGoals" type="query" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="datasource" type="string" required="true" />

        <cfset var section = {
            key = "gg_symptoms",
            heading = "GG Symptoms (ADLs)",
            visible = arguments.visible,
            severity_legend = "06 Independent / 05 Setup or cleanup assistance / 04 Supervision or touching assistance / 03 Partial/moderate assistance / 02 Substantial/maximal assistance / 01 Dependent",
            groups = [],
            items = []
        } />
        <cfset var row = 0 />
        <cfset var item = {} />
        <cfset var headerVal = "" />
        <cfset var desc = "" />
        <cfset var scale = {} />
        <cfset var goalState = {} />
        <cfset var seenIds = {} />
        <cfset var progressId = 0 />
        <cfset var pathId = "" />
        <cfset var severityVal = "" />

        <cfloop from="1" to="#arguments.q.recordcount#" index="row">
            <cfset progressId = val(queryCol(arguments.q, "Progress_ID", row, 0)) />
            <cfif NOT markProgressIdSeen(seenIds, progressId)>
                <cfcontinue />
            </cfif>
            <cfset desc = safeTrim(queryCol(arguments.q, "Description", row, "")) />
            <cfset pathId = safeTrim(queryCol(arguments.q, "Path_ID", row, "")) />
            <cfif NOT shouldShowGgSymptomRow(
                skill = arguments.skill,
                description = desc,
                pathId = pathId,
                careplanGoals = arguments.careplanGoals,
                lookupSchema = arguments.lookupSchema,
                datasource = arguments.datasource
            )>
                <cfcontinue />
            </cfif>

            <cfset headerVal = safeTrim(queryCol(arguments.q, "Header", row, "")) />
            <cfif NOT len(headerVal)>
                <cfset headerVal = "ADLs" />
            </cfif>
            <cfset severityVal = safeTrim(queryCol(arguments.q, "Severity", row, "")) />
            <cfset scale = ggAdlSeverityScale(skill = arguments.skill, description = desc) />
            <cfset goalState = evaluateGgGoalMet(
                severity = severityVal,
                pathId = pathId,
                careplanGoals = arguments.careplanGoals,
                lookupSchema = arguments.lookupSchema,
                datasource = arguments.datasource
            ) />

            <cfset item = {
                progress_id = toJsonInt(progressId),
                item_id = toJsonInt(queryCol(arguments.q, "Item_ID", row, 0)),
                careplan_id = toJsonInt(queryCol(arguments.q, "Careplan_ID", row, 0)),
                type = safeTrim(queryCol(arguments.q, "Type", row, "")),
                heading = headerVal,
                description = desc,
                description_type = "radio",
                is_gg = true,
                goal_met = goalState.goal_met,
                disabled = goalState.disabled,
                linked_goals = goalState.linked_goals,
                answer = safeTrim(queryCol(arguments.q, "Answer", row, "")),
                severity = {
                    description = "Severity",
                    description_type = "radio",
                    field = "Severity",
                    value = severityVal,
                    options = scale.options,
                    legend = scale.legend,
                    disabled = goalState.disabled
                },
                notes = notesField(queryCol(arguments.q, "Notes", row, "")),
                physician_alert = physicianAlertField(queryCol(arguments.q, "Physician_alert", row, 0))
            } />
            <cfset arrayAppend(section.items, item) />
        </cfloop>

        <cfset section.groups = groupItemsByHeading(section.items, "heading") />
        <cfif arrayLen(section.items) EQ 0>
            <cfset section.visible = false />
        </cfif>
        <cfreturn finalizeGroupedSection(section) />
    </cffunction>

    <cffunction name="loadPgProblemsAllQuery" access="private" returntype="query" output="false"
        hint="Base pgproblemsall query shared by regular symptoms and GG symptoms.">
        <cfargument name="agencySchema" type="string" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="assmtId" type="numeric" required="true" />
        <cfargument name="scheduleId" type="numeric" required="true" />
        <cfargument name="skill" type="string" required="true" />

        <cfset var admitId = 0 />
        <cfset var qAdmit = "" />
        <cfset var pgproblemsall = "" />

        <cfquery name="qAdmit" datasource="#Application.DataSrc#">
            SELECT Admit_ID
            FROM #arguments.agencySchema#.pAssessments
            WHERE Assmt_ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.assmtId#">
              AND Status = 0
            LIMIT 1
        </cfquery>
        <cfif qAdmit.recordcount GT 0>
            <cfset admitId = val(qAdmit.Admit_ID) />
        </cfif>

        <cfquery name="pgproblemsall" datasource="#Application.DataSrc#">
            SELECT SUBSTRING_INDEX(pProgress.Description, '(', 1) AS Description1,
                   pProgress.*,
                   pProblem.Oasis_Deficits,
                   pProblem.Answer,
                   pProblem.ques_id,
                   pProblem.M0_No,
                   pPathway.Value_Prog,
                   pPathway.Header AS old_header,
                   pPathway.Header AS path_header,
                   pProblem.Path_ID,
                   CASE
                       WHEN pProgress_Unstable.system_description != '' THEN pProgress_Unstable.system_description
                       WHEN Careplan_ID > 0 THEN (
                           SELECT prob2.Description
                           FROM #arguments.agencySchema#.pProblem AS prob2
                           WHERE prob2.Path_ID = pProblem.Path_ID
                             AND prob2.M0_No LIKE 'M102%'
                             AND pProgress.Assmt_ID = prob2.Assmt_ID
                             AND prob2.status = 0
                           LIMIT 0, 1
                       )
                       ELSE NULL
                   END AS Header,
                   CASE
                       WHEN (pProblem.M0_No LIKE 'SK%' OR pProblem.M0_No = 'wound')
                            AND LEFT(pProgress.Description, 1) = '##'
                       THEN 'Wounds'
                       ELSE ''
                   END AS woundstitle,
                   pProblem.Description AS problemdescription,
                   pProblem.Problem_ID AS prob_id,
                   CASE
                       WHEN LEFT(pProgress.Description, 3) = 'GG0' THEN 20
                       WHEN pProgress_Unstable.system_description != '' THEN pProgress_Unstable.ID
                       WHEN Careplan_ID > 0 THEN 1000
                       ELSE pProgress_Unstable.ID
                   END AS header_title,
                   pPathway.Header_No
            FROM #arguments.agencySchema#.pProgress
            LEFT OUTER JOIN #arguments.agencySchema#.pProblem
                ON pProblem.Problem_ID = pProgress.Careplan_ID
               AND pProblem.status = 0
            INNER JOIN #arguments.agencySchema#.pAssessments
                ON pAssessments.Assmt_ID = pProgress.Assmt_ID
               AND pAssessments.status = 0
            INNER JOIN #arguments.agencySchema#.pAdmit
                ON pAdmit.Admit_ID = pAssessments.Admit_ID
               AND pAdmit.status = 0
            <cfif admitId GT 0>
               AND pAssessments.Admit_ID = <cfqueryparam value="#admitId#" cfsqltype="CF_SQL_INTEGER">
            </cfif>
            LEFT OUTER JOIN #arguments.lookupSchema#.pPathway
                ON pPathway.Item_ID = pProgress.Item_ID
               AND pPathway.Type = pProgress.Type
               AND pPathway.Header_No IS NOT NULL
            LEFT OUTER JOIN #arguments.lookupSchema#.pProgress_Unstable
                ON pProgress_Unstable.System = pProblem.M0_No
            WHERE pProgress.Schedule_ID = <cfqueryparam value="#arguments.scheduleId#" cfsqltype="CF_SQL_INTEGER">
              AND pProgress.Status = <cfqueryparam value="0" cfsqltype="CF_SQL_INTEGER">
              AND pProgress.type IN (<cfqueryparam value="problem,symptom" cfsqltype="cf_sql_varchar" list="yes">)
              AND pProgress.Item_ID NOT IN (
                  SELECT Item_ID
                  FROM #arguments.lookupSchema#.pPathway
                  WHERE pPathway.Header = 'Vital Signs'
              )
            GROUP BY pProgress.Progress_ID
            ORDER BY woundstitle ASC, header_title ASC, pProgress.Item_ID ASC
        </cfquery>

        <cfreturn pgproblemsall />
    </cffunction>

    <cffunction name="loadSymptomsQuery" access="private" returntype="query" output="false"
        hint="Non-GG symptoms (pgproblems) — excludes GG% rows.">
        <cfargument name="agencySchema" type="string" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="assmtId" type="numeric" required="true" />
        <cfargument name="scheduleId" type="numeric" required="true" />
        <cfargument name="skill" type="string" required="true" />
        <cfset var pgproblemsall = loadPgProblemsAllQuery(
            agencySchema = arguments.agencySchema,
            lookupSchema = arguments.lookupSchema,
            assmtId = arguments.assmtId,
            scheduleId = arguments.scheduleId,
            skill = arguments.skill
        ) />
        <cfset var pgproblems = "" />

        <cfquery name="pgproblems" dbtype="query">
            SELECT *
            FROM pgproblemsall
            WHERE Description NOT LIKE 'GG%'
              AND (prob_id > 0 OR woundstitle != '' OR Careplan_ID = 0)
            ORDER BY woundstitle ASC, header_title ASC, Item_ID ASC
        </cfquery>
        <cfreturn pgproblems />
    </cffunction>

    <cffunction name="loadGgSymptomsQuery" access="private" returntype="query" output="false"
        hint="GG symptoms (ADLs) — Description LIKE GG%.">
        <cfargument name="agencySchema" type="string" required="true" />
        <cfargument name="lookupSchema" type="string" required="true" />
        <cfargument name="assmtId" type="numeric" required="true" />
        <cfargument name="scheduleId" type="numeric" required="true" />
        <cfargument name="skill" type="string" required="true" />
        <cfset var pgproblemsall = loadPgProblemsAllQuery(
            agencySchema = arguments.agencySchema,
            lookupSchema = arguments.lookupSchema,
            assmtId = arguments.assmtId,
            scheduleId = arguments.scheduleId,
            skill = arguments.skill
        ) />
        <cfset var ggproblems = "" />

        <cfquery name="ggproblems" dbtype="query">
            SELECT *
            FROM pgproblemsall
            WHERE Description LIKE 'GG%'
            ORDER BY woundstitle ASC, header_title ASC, Item_ID ASC
        </cfquery>
        <cfreturn ggproblems />
    </cffunction>

    <!--- Main remote endpoint --->
    <cffunction name="getProgressNotes" access="remote" returntype="struct" returnformat="json" output="false"
        hint="Requires securityKey and Schedule_ID. Returns progress note sections with headings, descriptions, description_type, and physician alert fields (skill-filtered like progress_notesnew.cfm).">
        <cfargument name="securityKey" type="string" required="false" default="" />
        <cfargument name="Schedule_ID" type="string" required="false" default="" />

        <cfset var response = { success = false, message = "", data = {} } />
        <cfset var payload = {} />
        <cfset var auth = {} />
        <cfset var empId = 0 />
        <cfset var scheduleCtx = {} />
        <cfset var skill = "" />
        <cfset var sections = [] />
        <cfset var symptomsQ = "" />
        <cfset var ggSymptomsQ = "" />
        <cfset var careplanGoals = "" />
        <cfset var visible = {} />

        <cfset payload = parseProgressNotesRequest(
            securityKeyArg = arguments.securityKey,
            scheduleIdArg = arguments.Schedule_ID
        ) />

        <cfif NOT len(payload.securityKey)>
            <cfset response.message = "securityKey is required" />
            <cfheader statuscode="401" statustext="Unauthorized" />
            <cfreturn response />
        </cfif>
        <cfif payload.scheduleId LTE 0>
            <cfset response.message = "Schedule_ID is required" />
            <cfheader statuscode="400" statustext="Bad Request" />
            <cfreturn response />
        </cfif>

        <cfset auth = authorizeSecurityKeyForAgency(securityKey = payload.securityKey) />
        <cfif NOT auth.ok>
            <cfset response.message = auth.message />
            <cfheader statuscode="#auth.statusCode#" statustext="Error" />
            <cfreturn response />
        </cfif>

        <cfset empId = parseSecurityKeyEmpId(payload.securityKey) />
        <cfset scheduleCtx = getScheduleForEmployee(
            agencySchema = auth.agencySchema,
            scheduleId = payload.scheduleId,
            empId = empId
        ) />
        <cfif NOT scheduleCtx.ok>
            <cfset response.message = scheduleCtx.message />
            <cfheader statuscode="403" statustext="Forbidden" />
            <cfreturn response />
        </cfif>

        <cfif NOT isDefined("Request.prefix_db_lookup") OR NOT len(trim(Request.prefix_db_lookup))>
            <cfset Request.prefix_db_lookup = "hhapowerpath" />
        </cfif>
        <cfset Request.prefix_db_agency = auth.agencySchema />
        <cfset Request.Agency_ID = auth.locId />

        <cftry>
            <cfset url.api_pgnotes_only = "1" />
            <cfset url.SID = payload.scheduleId />
            <cfset url.AID = val(scheduleCtx.schedule.Assmt_ID) />
            <cfset url.ID = val(scheduleCtx.schedule.Patient_ID) />
            <cfinclude template="../../patientadmin_new/patient/progress_notes_download_data.cfm" />

            <cfset skill = safeTrim(skilltype) />
            <cfset visible = {
                patient_complaint = (ptcomplaint_class NEQ "hide"),
                patient_goal = (ptgoal_class NEQ "hide"),
                vitals = (vital_class NEQ "hide"),
                symptoms = (symp_class NEQ "hide"),
                procedures = (procedure_class NEQ "hide"),
                standing_orders = (standing_class NEQ "hide"),
                goals = (goals_class NEQ "hide"),
                discharge_plan = (discharge_class NEQ "hide"),
                teaching = (teaching_class NEQ "hide"),
                medications = (meds_class NEQ "hide"),
                gg_symptoms = (goals_class NEQ "hide")
            } />

            <cfset careplanGoals = createObject("component", "pathway.careplan").getscareplan(
                url.AID,
                "goal",
                safeTrim(careplan_skill),
                0
            ) />

            <cfset arrayAppend(sections, formatPatientComplaint(getptcomplaint, visible.patient_complaint)) />
            <cfset arrayAppend(sections, formatScaleSection(
                key = "patient_goal",
                heading = "Patient Goal on Admission",
                q = getptgoals,
                visible = visible.patient_goal,
                scaleLegend = "0-0% 1-25% 2-50% 3-75% 4-100% met"
            )) />
            <cfset arrayAppend(sections, formatVitalsSection(getvitals, visible.vitals)) />

            <cfset symptomsQ = loadSymptomsQuery(
                agencySchema = auth.agencySchema,
                lookupSchema = Request.prefix_db_lookup,
                assmtId = url.AID,
                scheduleId = payload.scheduleId,
                skill = skill
            ) />
            <cfset arrayAppend(sections, formatSymptomsFromQuery(symptomsQ, visible.symptoms, skill)) />

            <cfset arrayAppend(sections, formatYesNoSection(
                key = "procedures",
                heading = "PROCEDURES",
                q = getprocedure,
                visible = visible.procedures
            )) />
            <cfset arrayAppend(sections, formatYesNoSection(
                key = "standing_orders",
                heading = "Standing Orders",
                q = getstandingorder,
                visible = visible.standing_orders
            )) />
            <cfset arrayAppend(sections, formatScaleSection(
                key = "goals",
                heading = "GOALS",
                q = getgoals,
                visible = visible.goals,
                scaleLegend = "0-0% 1-25% 2-50% 3-75% 4-100% met",
                includeTargetDate = true
            )) />

            <cfset ggSymptomsQ = loadGgSymptomsQuery(
                agencySchema = auth.agencySchema,
                lookupSchema = Request.prefix_db_lookup,
                assmtId = url.AID,
                scheduleId = payload.scheduleId,
                skill = skill
            ) />
            <cfset arrayAppend(sections, formatGgSymptomsSection(
                q = ggSymptomsQ,
                visible = visible.gg_symptoms,
                skill = skill,
                careplanGoals = careplanGoals,
                lookupSchema = Request.prefix_db_lookup,
                datasource = Application.DataSrc
            )) />

            <cfset arrayAppend(sections, formatScaleSection(
                key = "discharge_plan",
                heading = "Discharge Plan",
                q = getdischargeplan,
                visible = visible.discharge_plan,
                scaleLegend = "0-0% 1-25% 2-50% 3-75% 4-100% met",
                includeTargetDate = true
            )) />
            <cfset arrayAppend(sections, formatScaleSection(
                key = "teaching",
                heading = "Teaching",
                q = getallteaching,
                visible = visible.teaching,
                scaleLegend = "0-0% 1-25% 2-50% 3-75% 4-100% met"
            )) />

            <cfset response.success = true />
            <cfset response.message = "Progress notes loaded" />
            <cfset response.data = {
                schedule = scheduleCtx.schedule,
                skill = skill,
                careplan_skill = safeTrim(careplan_skill),
                section_visibility = visible,
                sections = sections
            } />
            <cfheader statuscode="200" statustext="OK" />
            <cfreturn response />

            <cfcatch type="any">
                <cfset response.message = "Failed to load progress notes: " & cfcatch.message />
                <cfset response.data = {
                    error_detail = safeTrim(cfcatch.detail),
                    error_type = safeTrim(cfcatch.type)
                } />
                <cfheader statuscode="500" statustext="Internal Server Error" />
                <cfreturn response />
            </cfcatch>
        </cftry>
    </cffunction>

</cfcomponent>
