Fix tax rate lookup and add price extraction from __OO_STATE__

- Tax rate: Use Zippopotam (free, no key) to get state, then lookup
  from built-in state+local rate tables instead of API Ninjas
- Prices: Extract prices from Toast __OO_STATE__ MenuItem objects
  when visible HTML prices are missing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-13 12:05:24 -08:00
parent 76f089d1b9
commit 3cd7bbb8b7
2 changed files with 66 additions and 31 deletions

View file

@ -285,9 +285,10 @@
<!--- Debug: log all top-level keys in OO_STATE --->
<cfset ooStateKeys = structKeyList(ooState)>
<cfset arrayAppend(response.steps, "OO_STATE keys: " & left(ooStateKeys, 500))>
<!--- Build name -> image URL map and name -> category map from OO_STATE --->
<!--- Build name -> image URL map, name -> category map, and name -> price map from OO_STATE --->
<cfset imageMap = structNew()>
<cfset itemCategoryMap = structNew()>
<cfset itemPriceMap = structNew()>
<cfloop collection="#ooState#" item="key">
<!--- Extract restaurant/business info --->
<cfif left(key, 11) EQ "Restaurant:">
@ -339,6 +340,12 @@
<cfif len(groupName)>
<cfset itemCategoryMap[item.name] = groupName>
</cfif>
<!--- Extract price --->
<cfif structKeyExists(item, "price") AND isNumeric(item.price)>
<cfset itemPriceMap[item.name] = val(item.price)>
<cfelseif structKeyExists(item, "unitPrice") AND isNumeric(item.unitPrice)>
<cfset itemPriceMap[item.name] = val(item.unitPrice)>
</cfif>
<!--- Extract image URLs --->
<cfif structKeyExists(item, "imageUrls")>
<cfset imgUrls = item.imageUrls>
@ -355,9 +362,10 @@
</cfif>
</cfif>
</cfloop>
<!--- Apply images and categories to items --->
<!--- Apply images, categories, and prices to items --->
<cfset imagesMatched = 0>
<cfset categoriesMatched = 0>
<cfset pricesMatched = 0>
<cfloop from="1" to="#arrayLen(toastItems)#" index="i">
<cfif structKeyExists(imageMap, toastItems[i].name)>
<cfset toastItems[i]["imageUrl"] = imageMap[toastItems[i].name]>
@ -370,8 +378,13 @@
<cfset toastItems[i]["category"] = itemCategoryMap[toastItems[i].name]>
<cfset categoriesMatched++>
</cfif>
<!--- Apply price from __OO_STATE__ if not already set or is 0 --->
<cfif structKeyExists(itemPriceMap, toastItems[i].name) AND (NOT structKeyExists(toastItems[i], "price") OR toastItems[i].price EQ 0)>
<cfset toastItems[i]["price"] = itemPriceMap[toastItems[i].name]>
<cfset pricesMatched++>
</cfif>
</cfloop>
<cfset arrayAppend(response.steps, "Matched " & imagesMatched & " images, " & categoriesMatched & " categories from __OO_STATE__")>
<cfset arrayAppend(response.steps, "Matched " & imagesMatched & " images, " & categoriesMatched & " categories, " & pricesMatched & " prices from __OO_STATE__")>
<cfif structCount(toastBusiness) GT 0>
<cfset arrayAppend(response.steps, "Extracted business info: " & structKeyList(toastBusiness))>
</cfif>

View file

@ -3,7 +3,30 @@
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfset response = { "OK": false, "taxRate": 0, "message": "" }>
<cfset response = { "OK": false, "taxRate": 0, "message": "", "state": "" }>
<!--- State base sales tax rates (2024) - local rates may add 1-5% more --->
<cfset stateTaxRates = {
"AL": 4, "AK": 0, "AZ": 5.6, "AR": 6.5, "CA": 7.25,
"CO": 2.9, "CT": 6.35, "DE": 0, "FL": 6, "GA": 4,
"HI": 4, "ID": 6, "IL": 6.25, "IN": 7, "IA": 6,
"KS": 6.5, "KY": 6, "LA": 4.45, "ME": 5.5, "MD": 6,
"MA": 6.25, "MI": 6, "MN": 6.875, "MS": 7, "MO": 4.225,
"MT": 0, "NE": 5.5, "NV": 6.85, "NH": 0, "NJ": 6.625,
"NM": 4.875, "NY": 4, "NC": 4.75, "ND": 5, "OH": 5.75,
"OK": 4.5, "OR": 0, "PA": 6, "RI": 7, "SC": 6,
"SD": 4.2, "TN": 7, "TX": 6.25, "UT": 6.1, "VT": 6,
"VA": 5.3, "WA": 6.5, "WV": 6, "WI": 5, "WY": 4, "DC": 6
}>
<!--- Estimated average local rate addition by state (rough estimates) --->
<cfset stateLocalAdd = {
"AL": 5.2, "AZ": 2.8, "AR": 3, "CA": 1.5, "CO": 4.8,
"GA": 4, "IL": 2.5, "KS": 2.5, "LA": 5.5, "MO": 4,
"NV": 1.4, "NM": 2.7, "NY": 4.5, "NC": 2.3, "OH": 1.5,
"OK": 4.5, "SC": 2, "SD": 2, "TN": 2.5, "TX": 2,
"UT": 1.3, "WA": 3, "WI": 0.6
}>
<cftry>
<!--- Get ZIP code from URL or form --->
@ -24,44 +47,43 @@
<!--- Use just the 5-digit portion --->
<cfset zipCode = left(zipCode, 5)>
<!--- Call API Ninjas Sales Tax API --->
<cfhttp url="https://api.api-ninjas.com/v1/salestax?zip_code=#zipCode#" method="GET" timeout="10" result="httpResult">
<cfhttpparam type="header" name="X-Api-Key" value="free">
<!--- Use Zippopotam.us to get state from ZIP (free, no API key) --->
<cfhttp url="https://api.zippopotam.us/us/#zipCode#" method="GET" timeout="10" result="httpResult">
</cfhttp>
<cfif httpResult.statusCode CONTAINS "200">
<cfset apiData = deserializeJSON(httpResult.fileContent)>
<cfset zipData = deserializeJSON(httpResult.fileContent)>
<!--- API returns an array, get first result --->
<cfif isArray(apiData) AND arrayLen(apiData) GT 0>
<cfset taxData = apiData[1]>
<cfif structKeyExists(zipData, "places") AND isArray(zipData.places) AND arrayLen(zipData.places) GT 0>
<cfset place = zipData.places[1]>
<cfset stateAbbr = uCase(place["state abbreviation"])>
<cfset response.state = stateAbbr>
<!--- overall_rate is decimal (e.g., 0.095 for 9.5%) --->
<cfif structKeyExists(taxData, "overall_rate")>
<cfset overallRate = val(taxData.overall_rate)>
<!--- Convert to percentage --->
<cfset response.taxRate = round(overallRate * 10000) / 100>
<!--- Look up state base rate --->
<cfif structKeyExists(stateTaxRates, stateAbbr)>
<cfset baseRate = stateTaxRates[stateAbbr]>
<!--- Add estimated local rate if available --->
<cfset localAdd = 0>
<cfif structKeyExists(stateLocalAdd, stateAbbr)>
<cfset localAdd = stateLocalAdd[stateAbbr]>
</cfif>
<cfset totalRate = baseRate + localAdd>
<!--- Round to 2 decimal places --->
<cfset response.taxRate = round(totalRate * 100) / 100>
<cfset response.OK = true>
<cfset response.message = "Tax rate found">
<!--- Include breakdown for transparency --->
<cfif structKeyExists(taxData, "state_rate")>
<cfset response.stateRate = round(val(taxData.state_rate) * 10000) / 100>
</cfif>
<cfif structKeyExists(taxData, "county_rate")>
<cfset response.countyRate = round(val(taxData.county_rate) * 10000) / 100>
</cfif>
<cfif structKeyExists(taxData, "city_rate")>
<cfset response.cityRate = round(val(taxData.city_rate) * 10000) / 100>
</cfif>
<cfset response.stateRate = baseRate>
<cfset response.localRate = localAdd>
<cfset response.message = "Estimated rate for #place['place name']#, #stateAbbr#">
<cfelse>
<cfset response.message = "No rate data in response">
<cfset response.message = "Unknown state: #stateAbbr#">
</cfif>
<cfelse>
<cfset response.message = "No results for ZIP code">
<cfset response.message = "No location data for ZIP">
</cfif>
<cfelse>
<cfset response.message = "API returned status: #httpResult.statusCode#">
<cfset response.message = "ZIP lookup failed: #httpResult.statusCode#">
</cfif>
<cfcatch type="any">