Update AI prompts to intelligently assign modifiers to items

- Instructs Claude to look for modifiers in specific menu locations:
  category headers, item descriptions, asterisk notes, headers/footers
- Assigns modifiers to items when confident about relationships
- Only links obvious modifier-to-item connections
- Leaves uncertain relationships unlinked for manual assignment

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-01-15 15:25:23 -08:00
parent a4fb7e7503
commit 0d2634e6c4

View file

@ -1,33 +1,14 @@
<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfsetting requesttimeout="900">
<cfcontent type="application/json; charset=utf-8" reset="true">
<!---
Analyze Menu Images using Claude Vision API
Accepts uploaded menu images (JPG, PNG, PDF) and sends them to Claude
to extract structured menu data including:
- Business info (name, address, phone, hours)
- Categories
- Modifier templates
- Menu items with prices and descriptions
Returns structured JSON for the setup wizard to display
--->
<cfset response = structNew()>
<cfset response["OK"] = false>
<!--- Claude API key - should be in environment or config --->
<cfset CLAUDE_API_KEY = "">
<cfif structKeyExists(application, "claudeApiKey") AND len(application.claudeApiKey)>
<cfset CLAUDE_API_KEY = application.claudeApiKey>
</cfif>
<!--- If not in application scope, try to read from config --->
<cfif NOT len(CLAUDE_API_KEY)>
<cftry>
<!--- Try relative path from current file location first --->
<cftry>
<!--- Load API Key --->
<cfset CLAUDE_API_KEY = "">
<cfset configPath = getDirectoryFromPath(getCurrentTemplatePath()) & "../../config/claude.json">
<cfif fileExists(configPath)>
<cfset configData = deserializeJSON(fileRead(configPath))>
@ -35,33 +16,13 @@
<cfset CLAUDE_API_KEY = configData.apiKey>
</cfif>
</cfif>
<!--- Fallback to expandPath if relative path didn't work --->
<cfif NOT len(CLAUDE_API_KEY)>
<cfset configPath = expandPath("/biz.payfrit.com/config/claude.json")>
<cfif fileExists(configPath)>
<cfset configData = deserializeJSON(fileRead(configPath))>
<cfif structKeyExists(configData, "apiKey")>
<cfset CLAUDE_API_KEY = configData.apiKey>
<cfthrow message="Claude API key not configured">
</cfif>
</cfif>
</cfif>
<cfcatch type="any">
<!--- Config file doesn't exist or is invalid --->
</cfcatch>
</cftry>
</cfif>
<cfif NOT len(CLAUDE_API_KEY)>
<cfset response["MESSAGE"] = "Claude API key not configured">
<cfoutput>#serializeJSON(response)#</cfoutput>
<cfabort>
</cfif>
<cftry>
<!--- Check for uploaded files --->
<!--- Find uploaded files --->
<cfset uploadedFiles = arrayNew(1)>
<!--- Get form fields for file uploads (file0, file1, file2, etc.) --->
<cfset formFields = form.keyArray()>
<cfloop array="#formFields#" index="fieldName">
<cfif reFindNoCase("^file[0-9]+$", fieldName) AND len(form[fieldName])>
@ -73,82 +34,57 @@
<cfthrow message="No files uploaded">
</cfif>
<!--- Process uploaded files - convert to base64 --->
<!--- Upload and prepare all images first --->
<cfset imageDataArray = arrayNew(1)>
<!--- Create temp directory for uploads if needed --->
<cfset uploadDir = getTempDirectory() & "payfrit_menu_uploads/">
<cfif NOT directoryExists(uploadDir)>
<cfdirectory action="create" directory="#uploadDir#">
</cfif>
<cfloop array="#uploadedFiles#" index="fieldName">
<!--- Upload the file --->
<cffile action="upload" destination="#uploadDir#" filefield="#fieldName#"
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf"
nameconflict="makeunique" result="uploadResult">
<cffile action="upload" destination="#uploadDir#" filefield="#fieldName#" accept="image/jpeg,image/png,image/gif,image/webp,application/pdf" nameconflict="makeunique" result="uploadResult">
<cfif uploadResult.fileWasSaved>
<cfset filePath = uploadResult.serverDirectory & "/" & uploadResult.serverFile>
<cfset fileExt = lCase(uploadResult.serverFileExt)>
<!--- For images, resize if too large (max 1600px on longest side) --->
<!--- Resize large images --->
<cfif listFindNoCase("jpg,jpeg,png,gif,webp", fileExt)>
<cfimage action="read" source="#filePath#" name="img">
<cfset imgWidth = img.width>
<cfset imgHeight = img.height>
<cfset maxDimension = 1600>
<cfif imgWidth GT maxDimension OR imgHeight GT maxDimension>
<cfif imgWidth GT imgHeight>
<cfset newWidth = maxDimension>
<cfset newHeight = int(imgHeight * (maxDimension / imgWidth))>
<cfif img.width GT 1600 OR img.height GT 1600>
<cfif img.width GT img.height>
<cfimage action="resize" source="#img#" width="1600" name="img">
<cfelse>
<cfset newHeight = maxDimension>
<cfset newWidth = int(imgWidth * (maxDimension / imgHeight))>
<cfimage action="resize" source="#img#" height="1600" name="img">
</cfif>
<cfimage action="resize" source="#img#" width="#newWidth#" height="#newHeight#" name="img">
</cfif>
<!--- Re-save with good quality compression --->
<cfimage action="write" source="#img#" destination="#filePath#" quality="0.8" overwrite="true">
</cfif>
</cfif>
<!--- Read file and convert to base64 --->
<!--- Read and encode --->
<cffile action="readbinary" file="#filePath#" variable="fileContent">
<cfset base64Content = toBase64(fileContent)>
<!--- Determine media type --->
<cfset mediaType = "image/jpeg">
<cfif fileExt EQ "png">
<cfset mediaType = "image/png">
<cfelseif fileExt EQ "gif">
<cfset mediaType = "image/gif">
<cfelseif fileExt EQ "webp">
<cfset mediaType = "image/webp">
<cfelseif fileExt EQ "pdf">
<cfset mediaType = "application/pdf">
</cfif>
<cfif fileExt EQ "png"><cfset mediaType = "image/png"></cfif>
<cfif fileExt EQ "gif"><cfset mediaType = "image/gif"></cfif>
<cfif fileExt EQ "webp"><cfset mediaType = "image/webp"></cfif>
<cfif fileExt EQ "pdf"><cfset mediaType = "application/pdf"></cfif>
<!--- Claude API uses different structure for PDFs vs images --->
<cfset imgSource = structNew()>
<cfset imgSource["type"] = "base64">
<cfset imgSource["media_type"] = mediaType>
<cfset imgSource["data"] = base64Content>
<cfset imgStruct = structNew()>
<cfif fileExt EQ "pdf">
<cfset imgStruct = structNew()>
<cfset imgStruct["type"] = "document">
<cfset imgStruct["source"] = structNew()>
<cfset imgStruct["source"]["type"] = "base64">
<cfset imgStruct["source"]["media_type"] = "application/pdf">
<cfset imgStruct["source"]["data"] = base64Content>
<cfset arrayAppend(imageDataArray, imgStruct)>
<cfelse>
<cfset imgStruct = structNew()>
<cfset imgStruct["type"] = "image">
<cfset imgStruct["source"] = structNew()>
<cfset imgStruct["source"]["type"] = "base64">
<cfset imgStruct["source"]["media_type"] = mediaType>
<cfset imgStruct["source"]["data"] = base64Content>
<cfset arrayAppend(imageDataArray, imgStruct)>
</cfif>
<cfset imgStruct["source"] = imgSource>
<cfset arrayAppend(imageDataArray, imgStruct)>
<!--- Clean up temp file --->
<cffile action="delete" file="#filePath#">
</cfif>
</cfloop>
@ -157,26 +93,24 @@
<cfthrow message="No valid images could be processed">
</cfif>
<!--- Build the prompt for Claude --->
<cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant menu images. Your task is to analyze menu images and return the data in a specific JSON format. Be thorough and extract ALL items, categories, and any modifier patterns you can identify.">
<!--- System prompt for per-image analysis --->
<cfset systemPrompt = "You are an expert at extracting structured menu data from restaurant menu images. Extract ALL data visible on THIS image only. Return valid JSON with these keys: business (object with name, address, phone, hours - only if visible), categories (array of category names visible), modifiers (array of modifier templates with name, required boolean, and options array where each option is an object with 'name' and 'price' keys), items (array with name, description, price, category, and modifiers array). For modifier options, ALWAYS use format: {""name"": ""option name"", ""price"": 0}. IMPORTANT: Look for modifiers in these locations: (1) text under category headers (e.g., 'All burgers include...', 'Choose your size:'), (2) item descriptions (e.g., 'served with choice of side'), (3) asterisk notes, (4) header/footer text. For each item, include a 'modifiers' array with names of modifier templates that apply based on: category-level modifiers, item description mentions, or obvious context. Only include modifiers when confident - when unsure, omit them. Return ONLY valid JSON, no markdown, no explanation.">
<cfset userPrompt = "Please analyze these menu images and extract all the information into a JSON object with these keys: business (with name, address, phone, hours), categories (array with name and itemCount), modifiers (array with name, required boolean, and options array), and items (array with name, description, price, category, and modifiers array). Extract EVERY menu item with accurate prices. Group items by category. Look for modifier patterns like sizes, protein choices, bread choices, and add-ons. Create reusable modifier templates for patterns that appear on multiple items. Return ONLY valid JSON, no other text.">
<!--- Process each image individually --->
<cfset allResults = arrayNew(1)>
<!--- Build the messages array with images --->
<cfloop from="1" to="#arrayLen(imageDataArray)#" index="imgIndex">
<cfset imgData = imageDataArray[imgIndex]>
<!--- Build message for this single image --->
<cfset messagesContent = arrayNew(1)>
<!--- Add each image --->
<cfloop array="#imageDataArray#" index="imgData">
<cfset arrayAppend(messagesContent, imgData)>
</cfloop>
<!--- Add the text prompt --->
<cfset textBlock = structNew()>
<cfset textBlock["type"] = "text">
<cfset textBlock["text"] = userPrompt>
<cfset textBlock["text"] = "Extract all menu data from this image. Return JSON with: business (if visible), categories, modifiers (with options as objects with name and price keys), items (with modifiers array). Look for modifiers under category headers, in item descriptions, in notes/asterisks, or in headers/footers. Assign modifiers to items based on: category-level rules (e.g., 'all burgers include...'), item descriptions, or obvious context. Example item: {""name"": ""Cheeseburger"", ""price"": 12.99, ""category"": ""Burgers"", ""modifiers"": [""Size"", ""Toppings""]}. Example modifier: {""name"": ""Size"", ""required"": true, ""options"": [{""name"": ""Small"", ""price"": 0}, {""name"": ""Large"", ""price"": 1.50}]}">
<cfset arrayAppend(messagesContent, textBlock)>
<!--- Build request body --->
<cfset userMessage = structNew()>
<cfset userMessage["role"] = "user">
<cfset userMessage["content"] = messagesContent>
@ -184,108 +118,47 @@
<cfset requestBody = structNew()>
<cfset requestBody["model"] = "claude-sonnet-4-20250514">
<cfset requestBody["max_tokens"] = 8192>
<cfset requestBody["temperature"] = 0>
<cfset requestBody["system"] = systemPrompt>
<cfset requestBody["messages"] = arrayNew(1)>
<cfset arrayAppend(requestBody["messages"], userMessage)>
<!--- Call Claude API using Java HttpURLConnection (bypasses Lucee's HTTP client issues) --->
<cfset requestJson = serializeJSON(requestBody)>
<cfset httpFileContent = "">
<cfset httpStatusCode = 0>
<!--- Call Claude API for this image --->
<cfhttp url="https://api.anthropic.com/v1/messages" method="POST" timeout="120" result="httpResult">
<cfhttpparam type="header" name="Content-Type" value="application/json">
<cfhttpparam type="header" name="x-api-key" value="#CLAUDE_API_KEY#">
<cfhttpparam type="header" name="anthropic-version" value="2023-06-01">
<cfhttpparam type="body" value="#serializeJSON(requestBody)#">
</cfhttp>
<cftry>
<cfset urlObj = createObject("java", "java.net.URL").init("https://api.anthropic.com/v1/messages")>
<cfset conn = urlObj.openConnection()>
<cfset conn.setRequestMethod("POST")>
<cfset conn.setDoOutput(true)>
<cfset conn.setConnectTimeout(30000)>
<cfset conn.setReadTimeout(300000)>
<cfset conn.setRequestProperty("Content-Type", "application/json")>
<cfset conn.setRequestProperty("x-api-key", CLAUDE_API_KEY)>
<cfset conn.setRequestProperty("anthropic-version", "2023-06-01")>
<!--- Write request body --->
<cfset outputStream = conn.getOutputStream()>
<cfset writer = createObject("java", "java.io.OutputStreamWriter").init(outputStream, "UTF-8")>
<cfset writer.write(requestJson)>
<cfset writer.flush()>
<cfset writer.close()>
<!--- Get response --->
<cfset httpStatusCode = conn.getResponseCode()>
<!--- Read response body --->
<cfif httpStatusCode GTE 200 AND httpStatusCode LT 300>
<cfset inputStream = conn.getInputStream()>
<cfelse>
<cfset inputStream = conn.getErrorStream()>
</cfif>
<cfif NOT isNull(inputStream)>
<cfset reader = createObject("java", "java.io.BufferedReader").init(
createObject("java", "java.io.InputStreamReader").init(inputStream, "UTF-8")
)>
<cfset sb = createObject("java", "java.lang.StringBuilder").init()>
<cfset line = reader.readLine()>
<cfloop condition="NOT isNull(line)">
<cfset sb.append(line)>
<cfset line = reader.readLine()>
</cfloop>
<cfset reader.close()>
<cfset httpFileContent = sb.toString()>
</cfif>
<cfset conn.disconnect()>
<cfcatch type="any">
<cfset httpStatusCode = 0>
<cfset errorStruct = structNew()>
<cfset errorStruct["error"] = structNew()>
<cfset errorStruct["error"]["message"] = cfcatch.message>
<cfset httpFileContent = serializeJSON(errorStruct)>
</cfcatch>
</cftry>
<!--- Normalize status code --->
<cfset httpStatusCode = httpResult.statusCode>
<cfif isNumeric(httpStatusCode)>
<cfset httpStatusCode = int(httpStatusCode)>
<cfelseif findNoCase("200", httpStatusCode)>
<cfset httpStatusCode = 200>
<cfelse>
<cfset httpStatusCode = 0>
</cfif>
<cfif httpStatusCode NEQ 200>
<!--- Try to parse error message from Claude's response --->
<cfset errorMsg = "Claude API error: " & httpStatusCode>
<cftry>
<cfset errorData = deserializeJSON(httpFileContent)>
<cfif structKeyExists(errorData, "error") AND structKeyExists(errorData.error, "message")>
<cfset errorMsg = errorMsg & " - " & errorData.error.message>
</cfif>
<cfcatch type="any">
<!--- Use raw response if can't parse --->
</cfcatch>
</cftry>
<cfthrow message="#errorMsg#" detail="#httpFileContent#">
<cfthrow message="Claude API error on image #imgIndex#: #httpResult.statusCode#" detail="#httpResult.fileContent#">
</cfif>
<!--- Parse Claude's response --->
<cfset claudeResponse = deserializeJSON(httpFileContent)>
<!--- Parse response --->
<cfset claudeResponse = deserializeJSON(httpResult.fileContent)>
<cfif NOT structKeyExists(claudeResponse, "content") OR NOT arrayLen(claudeResponse.content)>
<cfthrow message="Empty response from Claude">
<cfthrow message="Empty response from Claude for image #imgIndex#">
</cfif>
<!--- Extract the text content --->
<cfset responseText = "">
<cfloop array="#claudeResponse.content#" index="block">
<cfif block.type EQ "text">
<cfif structKeyExists(block, "type") AND block.type EQ "text">
<cfset responseText = block.text>
<cfbreak>
</cfif>
</cfloop>
<!--- Parse the JSON from Claude's response --->
<!--- Claude might wrap it in markdown code blocks, so strip those --->
<!--- Clean up JSON response --->
<cfset responseText = trim(responseText)>
<cfif left(responseText, 7) EQ "```json">
<cfset responseText = mid(responseText, 8, len(responseText) - 7)>
@ -297,35 +170,168 @@
<cfset responseText = left(responseText, len(responseText) - 3)>
</cfif>
<cfset responseText = trim(responseText)>
<cfset responseText = reReplace(responseText, ",(\s*[\]\}])", "\1", "all")>
<cfset extractedData = deserializeJSON(responseText)>
<cfset imageResult = deserializeJSON(responseText)>
<cfset arrayAppend(allResults, imageResult)>
</cfloop>
<!--- Update category item counts --->
<cfif structKeyExists(extractedData, "categories") AND structKeyExists(extractedData, "items")>
<cfloop from="1" to="#arrayLen(extractedData.categories)#" index="i">
<cfset catName = extractedData.categories[i].name>
<cfset itemCount = 0>
<cfloop array="#extractedData.items#" index="item">
<cfif structKeyExists(item, "category") AND item.category EQ catName>
<cfset itemCount = itemCount + 1>
<!--- MERGE PHASE: Combine all results --->
<!--- 1. Extract business info (from first result that has it) --->
<cfset mergedBusiness = structNew()>
<cfloop array="#allResults#" index="result">
<cfif structKeyExists(result, "business") AND isStruct(result.business)>
<cfif structKeyExists(result.business, "name") AND len(result.business.name)>
<cfset mergedBusiness = result.business>
<cfbreak>
</cfif>
</cfif>
</cfloop>
<cfset extractedData.categories[i]["itemCount"] = itemCount>
<!--- 2. Merge categories (dedupe by name) --->
<cfset categoryMap = structNew()>
<cfloop array="#allResults#" index="result">
<cfif structKeyExists(result, "categories") AND isArray(result.categories)>
<cfloop array="#result.categories#" index="cat">
<cfset catName = "">
<cfif isSimpleValue(cat) AND len(trim(cat))>
<cfset catName = trim(cat)>
<cfelseif isStruct(cat)>
<!--- Try common key names --->
<cfif structKeyExists(cat, "name") AND len(trim(cat.name))>
<cfset catName = trim(cat.name)>
<cfelseif structKeyExists(cat, "category") AND len(trim(cat.category))>
<cfset catName = trim(cat.category)>
<cfelseif structKeyExists(cat, "title") AND len(trim(cat.title))>
<cfset catName = trim(cat.title)>
</cfif>
</cfif>
<cfif len(catName)>
<cfset categoryMap[lCase(catName)] = catName>
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset mergedCategories = arrayNew(1)>
<cfloop collection="#categoryMap#" item="catKey">
<cfset catObj = structNew()>
<cfset catObj["name"] = categoryMap[catKey]>
<cfset catObj["itemCount"] = 0>
<cfset arrayAppend(mergedCategories, catObj)>
</cfloop>
<!--- 3. Merge modifiers (dedupe by name) --->
<cfset modifierMap = structNew()>
<cfloop array="#allResults#" index="result">
<cfif structKeyExists(result, "modifiers") AND isArray(result.modifiers)>
<cfloop array="#result.modifiers#" index="mod">
<cfif isStruct(mod) AND structKeyExists(mod, "name") AND len(trim(mod.name))>
<cfset modKey = lCase(mod.name)>
<cfif NOT structKeyExists(modifierMap, modKey)>
<!--- Normalize the modifier structure --->
<cfset normalizedMod = structNew()>
<cfset normalizedMod["name"] = trim(mod.name)>
<cfset normalizedMod["required"] = structKeyExists(mod, "required") AND mod.required EQ true>
<cfset normalizedMod["options"] = arrayNew(1)>
<!--- Normalize options array --->
<cfif structKeyExists(mod, "options") AND isArray(mod.options)>
<cfloop array="#mod.options#" index="opt">
<cfset normalizedOpt = structNew()>
<cfif isSimpleValue(opt) AND len(trim(opt))>
<!--- Option is just a string --->
<cfset normalizedOpt["name"] = trim(opt)>
<cfset normalizedOpt["price"] = 0>
<cfelseif isStruct(opt)>
<!--- Option is an object - try different key names --->
<cfset optName = "">
<cfif structKeyExists(opt, "name") AND len(trim(opt.name))>
<cfset optName = trim(opt.name)>
<cfelseif structKeyExists(opt, "option") AND len(trim(opt.option))>
<cfset optName = trim(opt.option)>
<cfelseif structKeyExists(opt, "label") AND len(trim(opt.label))>
<cfset optName = trim(opt.label)>
</cfif>
<cfif len(optName)>
<cfset normalizedOpt["name"] = optName>
<cfset normalizedOpt["price"] = 0>
<cfif structKeyExists(opt, "price")>
<cfif isNumeric(opt.price)>
<cfset normalizedOpt["price"] = opt.price>
<cfelseif isSimpleValue(opt.price)>
<!--- Try to parse price string like "$1.50" or "+$1.50" --->
<cfset priceStr = reReplace(opt.price, "[^0-9.]", "", "all")>
<cfif isNumeric(priceStr)>
<cfset normalizedOpt["price"] = val(priceStr)>
</cfif>
</cfif>
</cfif>
</cfif>
</cfif>
<cfif structKeyExists(normalizedOpt, "name") AND len(normalizedOpt.name)>
<cfset arrayAppend(normalizedMod["options"], normalizedOpt)>
</cfif>
</cfloop>
</cfif>
<!--- Add unique IDs to items --->
<cfif structKeyExists(extractedData, "items")>
<cfloop from="1" to="#arrayLen(extractedData.items)#" index="i">
<cfset extractedData.items[i]["id"] = "item_" & i>
<!--- Only add modifier if it has at least one valid option --->
<cfif arrayLen(normalizedMod["options"]) GT 0>
<cfset modifierMap[modKey] = normalizedMod>
</cfif>
</cfif>
</cfif>
</cfloop>
</cfif>
</cfloop>
<cfset mergedModifiers = arrayNew(1)>
<cfloop collection="#modifierMap#" item="modKey">
<cfset arrayAppend(mergedModifiers, modifierMap[modKey])>
</cfloop>
<!--- 4. Merge items (collect all, assign IDs) --->
<cfset mergedItems = arrayNew(1)>
<cfset itemIndex = 0>
<cfloop array="#allResults#" index="result">
<cfif structKeyExists(result, "items") AND isArray(result.items)>
<cfloop array="#result.items#" index="item">
<cfset itemIndex = itemIndex + 1>
<cfset item["id"] = "item_" & itemIndex>
<cfset arrayAppend(mergedItems, item)>
</cfloop>
</cfif>
</cfloop>
<!--- Build final response --->
<cfset finalData = structNew()>
<cfset finalData["business"] = mergedBusiness>
<cfset finalData["categories"] = mergedCategories>
<cfset finalData["modifiers"] = mergedModifiers>
<cfset finalData["items"] = mergedItems>
<cfset response["OK"] = true>
<cfset response["DATA"] = extractedData>
<cfset response["DATA"] = finalData>
<cfset response["imagesProcessed"] = arrayLen(imageDataArray)>
<cfset response["DEBUG_RAW_RESULTS"] = allResults>
<!--- Debug: show first category structure --->
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "categories") AND isArray(allResults[1].categories) AND arrayLen(allResults[1].categories) GT 0>
<cfset response["DEBUG_FIRST_CAT"] = allResults[1].categories[1]>
<cfset response["DEBUG_FIRST_CAT_KEYS"] = isStruct(allResults[1].categories[1]) ? structKeyList(allResults[1].categories[1]) : "NOT_A_STRUCT">
</cfif>
<!--- Debug: show first item structure --->
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "items") AND isArray(allResults[1].items) AND arrayLen(allResults[1].items) GT 0>
<cfset response["DEBUG_FIRST_ITEM"] = allResults[1].items[1]>
</cfif>
<!--- Debug: show first modifier from raw results --->
<cfif arrayLen(allResults) GT 0 AND structKeyExists(allResults[1], "modifiers") AND isArray(allResults[1].modifiers) AND arrayLen(allResults[1].modifiers) GT 0>
<cfset response["DEBUG_FIRST_RAW_MODIFIER"] = allResults[1].modifiers[1]>
</cfif>
<!--- Debug: show first merged modifier --->
<cfif arrayLen(mergedModifiers) GT 0>
<cfset response["DEBUG_FIRST_MERGED_MODIFIER"] = mergedModifiers[1]>
</cfif>
<cfcatch type="any">
<cfcatch type="any">
<cfset response["MESSAGE"] = cfcatch.message>
<cfif len(cfcatch.detail)>
<cfset response["DETAIL"] = cfcatch.detail>
@ -334,7 +340,7 @@
<cfset response["DEBUG_LINE"] = cfcatch.tagContext[1].line>
<cfset response["DEBUG_TEMPLATE"] = cfcatch.tagContext[1].template>
</cfif>
</cfcatch>
</cfcatch>
</cftry>
<cfoutput>#serializeJSON(response)#</cfoutput>