1const SYSTEMS_ENDPOINT="https://waterapi.ewg.org/zip_systems.php",SYSTEM_INFO_ENDPOINT="https://waterapi.ewg.org/systeminformation.php",SETTINGS_ID="water-report-config",CONFIG_ID="water-report-contaminants";let _settings=null,_removalIndex=null;export class WaterReportError extends Error{constructor(message,code){super(message),this.name="WaterReportError",this.code=code??"UNKNOWN"}}function settings(){if(_settings)return _settings;const el=document.getElementById(SETTINGS_ID);if(!el?.textContent?.trim())return{};try{_settings=JSON.parse(el.textContent)}catch{console.warn("[water-report] #water-report-config is not valid JSON."),_settings={}}return _settings??{}}export function isConfigured(){return!!settings().apiKey}export function clearCache(zip){if(zip){try{localStorage.removeItem(cacheKey(zip))}catch{}return}_settings=null,_removalIndex=null}function handleize(name){return String(name).toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}const STOREFRONT_API_VERSION="2026-07";export async function primeRemovalIndex(names){const{storefrontToken}=settings(),index=getRemovalIndex();if(!storefrontToken)return index;const wanted=new Map;for(const raw of names??[]){const name=String(raw??"").trim();!name||index.has(name.toLowerCase())||wanted.set(handleize(name),name)}if(!wanted.size)return index;const entries=[...wanted.entries()],CHUNK=50;for(let i=0;i<entries.length;i+=CHUNK){const chunk=entries.slice(i,i+CHUNK),query=`{${chunk.map(([handle],n)=>`c${n}: metaobject(handle:{type:"contaminants", handle:${JSON.stringify(handle)}}) { nm: field(key:"name"){value} pct: field(key:"percent_removed"){value} hc: field(key:"health_concern"){value} top: field(key:"is_top_contaminant"){value} rate: field(key:"top_ranking_rate"){value} }`).join(" ")}}`;try{const res=await fetch(`/api/${STOREFRONT_API_VERSION}/graphql.json`,{method:"POST",headers:{"Content-Type":"application/json","X-Shopify-Storefront-Access-Token":storefrontToken},body:JSON.stringify({query})});if(!res.ok)throw new Error(`HTTP ${res.status}`);const json=await res.json();if(json.errors?.length)throw new Error(json.errors[0]?.message??"GraphQL error");chunk.forEach(([,requestedName],n)=>{const node=json.data?.[`c${n}`];if(!node)return;const value=f=>node[f]?.value??null,record={name:value("nm")||requestedName,category:null,healthConcern:value("hc")||"",percentRemoved:value("pct")||"",topRankingRate:Number(value("rate"))||50,isTop:value("top")==="true",aliases:[]};index.set(requestedName.toLowerCase(),record)})}catch(err){console.warn("[water-report] Storefront removal lookup failed:",err);break}}return index}export async function primeFromPayload(payload){const names=(payload?.ContaminantList??[]).map(raw=>raw?.ContaminantName).filter(Boolean);return primeRemovalIndex(names)}export function getRemovalIndex(){if(_removalIndex!==null)return _removalIndex;const el=document.getElementById(CONFIG_ID);if(!el)return console.warn("[water-report] #water-report-contaminants element not found. Ensure the water-report-config snippet is rendered on this page."),_removalIndex=new Map,_removalIndex;let items;try{items=JSON.parse(el.textContent)}catch(err){return console.error("[water-report] Failed to parse contaminant config JSON:",err),_removalIndex=new Map,_removalIndex}if(!Array.isArray(items))return console.error("[water-report] Contaminant config JSON must be an array."),_removalIndex=new Map,_removalIndex;_removalIndex=new Map;for(const item of items)if(!(!item||!item.name)&&(_removalIndex.set(item.name.toLowerCase(),item),Array.isArray(item.aliases)))for(const alias of item.aliases){if(!alias)continue;const aliasKey=alias.toLowerCase();_removalIndex.has(aliasKey)||_removalIndex.set(aliasKey,item)}return _removalIndex}const cacheKey=zip=>`EWG-${zip}`;function readCache(zip){try{const raw=localStorage.getItem(cacheKey(zip));return raw?JSON.parse(raw):null}catch{return null}}function writeCache(zip,patch){try{const current=readCache(zip)??{zipCode:zip},next={...current,...patch,zipCode:zip,savedAt:Date.now()};patch.EWGResults&&(next.EWGResults={...current.EWGResults,...patch.EWGResults}),localStorage.setItem(cacheKey(zip),JSON.stringify(next))}catch{}}function isFresh(entry){const hours=Number(settings().cacheHours??24);return!entry||!hours||!entry.savedAt?!1:Date.now()-entry.savedAt<hours*60*60*1e3}async function _ewgFetch(url,signal){let res;try{res=await fetch(url,{signal})}catch(err){throw err?.name==="AbortError"?err:new WaterReportError("Unable to reach the water quality database. Check your connection and try again.","NETWORK_E
1RROR")}const body=await res.text();try{return JSON.parse(body)}catch{throw new WaterReportError("Received an unreadable response from the water quality database.","PARSE_ERROR")}}function _url(endpoint,params){const search=new URLSearchParams({...params,key:settings().apiKey??""});return`${endpoint}?${search}`}export async function fetchSystems(zip,options={}){if(!isConfigured())throw new WaterReportError("The water report is temporarily unavailable. Please try again later.","UNCONFIGURED");const cached=readCache(zip);if(!options.force&&isFresh(cached)&&cached?.EWGSystems?.length)return cached.EWGSystems;const data=await _ewgFetch(_url(SYSTEMS_ENDPOINT,{zip}),options.signal);if(data?.ZipCode==null)throw console.error("[water-report] EWG returned an empty response \u2014 the API key was most likely rejected or has expired. Check Theme settings \u2192 CF: Third Party \u2014 EWG (Water Report)."),new WaterReportError("The water report is temporarily unavailable. Please try again later.","KEY_REJECTED");const systems=data.SystemList;if(!Array.isArray(systems)||systems.length===0)throw new WaterReportError("No water system found for that ZIP code. Please verify and try again.","NO_COVERAGE");return writeCache(zip,{EWGSystems:systems}),systems}export async function fetchSystemReport(zip,pws,options={}){if(!isConfigured())throw new WaterReportError("The water report is temporarily unavailable. Please try again later.","UNCONFIGURED");const cached=readCache(zip);if(!options.force&&isFresh(cached)&&cached?.EWGResults?.[pws])return cached.EWGResults[pws];const data=await _ewgFetch(_url(SYSTEM_INFO_ENDPOINT,{pws}),options.signal);if(data?.PWS==null)throw console.error("[water-report] EWG returned an empty response \u2014 the API key was most likely rejected or has expired. Check Theme settings \u2192 CF: Third Party \u2014 EWG (Water Report)."),new WaterReportError("The water report is temporarily unavailable. Please try again later.","KEY_REJECTED");if(!Array.isArray(data.ContaminantList)||data.ContaminantList.length===0)throw new WaterReportError("No test results are published for that water system yet.","NO_RESULTS");return writeCache(zip,{EWGResults:{[pws]:data},activeEWGSystem:{PWS:pws}}),data}function exceedsGuideline(contaminant){return Number(contaminant?.SystemAverage)>Number(contaminant?.ContaminantHGValue)&&Number(contaminant?.ContaminantHGValue)>0}function pick(keys,...sources){for(const key of keys)for(const source of sources){if(!source||typeof source!="object")continue;const value=source[key];if(!(value==null||typeof value=="object")&&String(value).trim()!=="")return String(value).trim()}return""}function formatPopulation(value){const digits=value.replace(/[^\d]/g,"");return!digits||digits.length!==value.trim().length?value:Number(digits).toLocaleString("en-US")}function formatLocation(value){const firstCity=String(value??"").trim().split(",")[0].trim();return!firstCity||!(firstCity===firstCity.toUpperCase()&&/[A-Z]/.test(firstCity))?firstCity:firstCity.replace(/\w\S*/g,word=>word.charAt(0).toUpperCase()+word.slice(1).toLowerCase())}function topRanked(contaminants){const rank=c=>Number(c?.topRankingRate)||Number.MAX_SAFE_INTEGER;return[...contaminants].sort((a,b)=>rank(a)-rank(b)).slice(0,3)}export function buildReport(payload,system,allSystems=[]){const index=getRemovalIndex(),providerName=pick(["SystemName","Systemname"],system,payload),citiesServed=pick(["CityServed","CitiesServed"],system,payload),population=pick(["Population"],system,payload),waterSource=pick(["PrimSource_fulltext","PrimarySourceName","PrimSource_abbr"],system,payload),contaminants=(Array.isArray(payload?.ContaminantList)?payload.ContaminantList:[]).map(raw=>{const name=raw?.ContaminantName??"",lookup=name?index.get(String(name).trim().toLowerCase())??null:null,normalised={...raw,name:String(name).trim(),category:lookup?.category??null,maxValue:raw?.SystemAverage??null,maxValueUnit:raw?.ContaminantDisplayUnits??null,valueUnit:raw?.ContaminantUnits??null,healthGuideline:raw?.ContaminantHGValue??null,legalLimit:raw?.ContaminantMCLValue??null,timeFrame:raw?.ContaminantTimeFrame??null,exceededEwgGuideline:exceedsGuideline(raw),exceededLegal
1Limit:Number(raw?.SystemAverage)>Number(raw?.ContaminantMCLValue)&&Number(raw?.ContaminantMCLValue)>0,percentRemoved:lookup?.percentRemoved??null,healthConcern:lookup?.healthConcern||raw?.ContaminantHealthRisks||null,topRankingRate:lookup?.topRankingRate??null,isTop:lookup?.isTop??!1};return lookup?{...normalised,ClearlyFilteredData:lookup}:normalised}),exceeding=contaminants.filter(c=>c.exceededEwgGuideline),within=contaminants.filter(c=>!c.exceededEwgGuideline),removed=contaminants.filter(c=>c.percentRemoved);return{system:{pwsid:String(system?.PWS??""),name:providerName,citiesServed,population,source:waterSource},totalCount:contaminants.length,exceedingCount:exceeding.length,removedCount:removed.length,providerName,populationServed:formatPopulation(population),yearsAvailable:pick(["ContaminantTimeFrame"],contaminants[0]),waterSource,location:formatLocation(citiesServed),providers:(allSystems.length?allSystems:[system]).filter(Boolean).map(entry=>({pwsid:String(entry.PWS??""),id:String(entry.PWS??""),name:pick(["SystemName","Systemname"],entry)||String(entry.PWS??"")})),selectedProviderId:String(system?.PWS??""),contaminants,exceeding,within,removed,top3:topRanked(contaminants),raw:payload}} 2//# sourceMappingURL=/cdn/shop/t/253/assets/water-report.js.map?v=113679074425486518061790681891
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.