Loading...
Loading...
CASE STUDY
Full-Stack Web App · NSW Government API Integration · Australian Expansion In Progress

BlazrAI
Find fuel cheaper,
fill up smarter.

A mobile-first progressive web app with an AI decision layer that turns fuel anxiety into clear next steps — detect location, compare trusted nearby options instantly, and get nudged the moment prices fall below your target.

NSW FuelCheck API Cloudflare Workers OpenStreetMap / Leaflet PHP 8.3 + MySQL Nominatim Geocoding Privacy-first alerts (Email only) Gemini Intent Parser Shared hosting + edge proxy Sunset theme sync (Shortbread / OMT) Occlusion-safe route map fit
2,500+
NSW service stations tracked live
$0/mo
Maps & geocoding cost — free stack
7+
States & territories — expansion in progress
4layers
API integrations wired end-to-end
Ko Punithavelu · 2025
Solo build — every layer
Design
Architecture
Engineering
QA
BlazrAI quick action orbit menu view
BlazrAI E10 map view with live pricing markers
BlazrAI voice search interaction view
01 · Problem

The bowser shouldn’t feel like a blindfolded purchase.

For many NSW households, fuel is one of the most volatile weekly costs. Two stations a few minutes apart can differ by 20¢+ per litre, and drivers usually discover that gap only after they pull in.

BlazrAI storyboard: driver sees oil price news, gets shocked at the bowser, a friend recommends the app, finds cheapest nearby station, saves $6, receives a price drop alert

Storyboard illustrating the end-to-end user journey — from price shock at the bowser to confident, informed fill-up decisions via BlazrAI.

20¢+
per-litre variation between stations in the same Sydney suburb
4.2%
annual living cost rise for age pensioners, Dec 2025 quarter
2,500
NSW service stations — impossible to compare manually
$200+
potential annual saving for a typical NSW driver
ACCC: Industry must explain “widely varying” prices

The Australian Competition and Consumer Commission called on fuel retailers to account for significant and unexplained price differentials between stations — noting that consumers lack the tools to identify and respond to these variations in real time.

ACCC Media Release — Widely Varying Fuel Prices →

ABS: Living costs rising across every household type

The ABS reported living costs rose for all household types in the twelve months to December 2025, with annual rises ranging from 2.3% to 4.2%. Transport costs — including fuel — remained a top contributor alongside housing and food.

ABS — Living Costs Increase Across All Household Types, Feb 2026 →

“Every week I fill up without knowing if I’m paying $10 more than I should. There’s no easy way to compare — you just drive past and hope.”

— Composite from user research · Western Sydney commuter

01.5 · Competitor Analysis (Australia Context)

FuelCheck NSW vs Google Maps (AU) vs BlazrAI

This comparison is scoped to real Australian usage patterns. FuelCheck NSW and Google Maps each solve part of the journey in AU, while BlazrAI combines official live pricing, supply context, and predictive guidance into a one-stop fuel decision flow for NSW drivers.

Decision-first UX for drivers

BlazrAI is designed for real driving moments, not app browsing.

Zero-tap context on first open: BlazrAI can voice out the best nearby price immediately, so drivers get the core business value before interacting with the interface.

Voice price on launch Zero-tap decision context Driving-safe signal hierarchy Action in under 3 seconds
Decision Lens
BlazrAI logo BlazrAI
NSW FuelCheck logo FuelCheck NSW
Google Maps logo Google Maps
Primary Job
Fuel decision assistant: find, compare, decide, and act in one flow.
Official NSW fuel price lookup and nearby station checks (state government product).
General navigation and place discovery in Australia, with fuel pricing shown where available in local coverage.
Data Trust Model
Multi-source official model
Uses Data.NSW/FuelCheck API for live station prices and DCCEEW fuel supply datasets for macro context, then adds decision framing on top.
Official NSW source
Government-backed FuelCheck coverage for NSW stations (Data.NSW / FuelCheck API baseline).
Varies by AU location
Fuel price visibility can vary by Australian suburb/region and listing freshness.
Stock Availability Signal
Crowd confidence layer
Community stock check adds real-time station stock confidence where price-only views are incomplete.
No built-in crowd-sourced stock confidence signal in core flow.
No dedicated fuel-stock confidence layer for station-level decisioning.
Decision Speed
Single “best option now” card prioritizes immediate action with minimal scanning.
Good station browsing, but users still compare multiple candidates manually.
Fast for driving/navigation tasks, but fuel choice competes with broader map intent in the same interface.
Value Framing
Frames value as savings per tank / yearly effect to reduce mental math.
Price-led comparison (useful, but often abstract for non-technical users).
Price point visible, not always framed as practical savings impact.
Behavioral UX
Built for fuel behavior: near-me nudges, confidence cues, and action-first recommendations.
Functional and reliable, limited behavioral guidance patterns.
Navigation-first experience; fuel intent is one of many intents.
Personalization
Fuel-type context, watchlist, tank-size logic, and decision engine states tuned per user context.
Useful filters and station exploration.
Strong location context and route convenience.
Alerts & Retention
Price alerts plus smart threshold tuning informed by local trend + monthly supply context.
Can support monitoring behavior, but not deeply behaviorally tuned.
General map notifications in AU use cases; fuel is not the primary retention loop.
Market Context
Adds DCCEEW supply pulse, NSW context, and news-informed signals to estimate likely price pressure (guidance, not certainty).
Station-level view focused on current prices.
Primarily real-time place/map context.
AI Layer
Gemini-powered intent parsing, voice actions, plus forward-looking guidance using DCCEEW supply + NSW station data + relevant fuel news context.
No dedicated natural-language decision assistant layer.
Search and recommendations are broad in AU journeys, not fuel-specific intent coaching.
Where It Wins
Best for “what should I do right now?” fuel decisions with clear savings guidance, while also retaining official NSW data legitimacy.
Best for official NSW-only price checks with strong data legitimacy.
Best for multi-purpose map navigation and route planning convenience.

Positioning takeaway (AU): BlazrAI is a one-stop fuel decision layer combining live Data.NSW/FuelCheck prices, DCCEEW supply context, and news-informed prediction guidance, while Google Maps remains the broad navigation utility baseline.

02 · Research & Framing

What drivers need in the moment, not in theory

Research surfaced three non-negotiables: real-time nearby truth, near-zero friction (no install, no account wall), and proactive nudges that arrive before the expensive mistake.

HMW 01
How might we help drivers find the cheapest nearby fuel before they leave home — not after they’re already at the bowser?
HMW 02
How might we eliminate the need to manually check prices across multiple apps or websites every time someone needs to fill up?
HMW 03
How might we deliver price alerts that feel timely and useful — not spammy — so drivers act on them rather than dismiss them?
Sarah, 34
Western Sydney · Daily commuter · 2 kids

“I fill up twice a week. Even saving 10¢ a litre adds up to over $150 a year. I just don’t have time to compare.”

Pain Points
  • Checks 2–3 apps before deciding — takes too long
  • Has been caught by sudden price spikes mid-week
  • Wants price drop alerts for her specific fuel type (E10)
Marcus, 58
Blue Mountains · Retiree · Large tank diesel

“Diesel prices swing wildly. I’ve driven an extra 10km to save money, only to find it had gone up. I need something smarter.”

Pain Points
  • Diesel prices harder to track than petrol
  • Drives long distances — price per fill is significant
  • Wants low-friction alerts without sharing extra personal data
03 · Solution

BlazrAI — real-time fuel decisions with AI assist

A progressive web app that behaves like a native product without the app-store tax. It opens straight into a live map, anchors to your location, and uses AI-assisted ranking and intent parsing to surface the best value stations before decision fatigue kicks in.

Map-First Interface
The map opens first, not hidden behind a list. Green markers surface best-value options, amber shows alternatives, and one tap opens details plus directions.
Auto Location
On load, GPS resolves to suburb and postcode so users instantly know they are comparing the right area, with zero Google billing dependency.
AI Decision Layer
Switch fuel type in one tap while BlazrAI re-ranks stations with context-aware cues, intent parsing support, and fast relevance scoring for NSW usage patterns.
Price Alerts
Set a target price once. BlazrAI monitors hourly and sends privacy-first email alerts when your suburb and fuel type drop below threshold, with AI-ready rules for smarter alert timing.
Design Decision

Why a bottom sheet instead of a sidebar?

On mobile, sidebars steal space and break flow. A bottom sheet keeps the map visible, respects thumb reach, and matches familiar iOS/Android behavior so people compare and act without context switching.

04 · Live App

Open the real thing — no prototype theater

Fully deployed at kopunithavelu.com.au/fuelcheck. Grant location access and BlazrAI immediately centers on your area, loads live nearby stations, and ranks options with AI-assisted, decision-ready context.

 Allow location for the full experience. If permission is blocked, use the location pill to search your suburb manually.

05 · Design Decisions

Decision UX, documented like a product team

Each pattern here links intent to execution: the behavioral cue, the empathy behind the wording, and the implementation detail that keeps it trustworthy at runtime. Hover the dots to inspect the trade-offs behind each decision.

Latest Product Decisions

The latest build adds system-level refinements in live code: sunset-aware map style sync, verified NSW source disclosure, balanced FAB geometry, improved map/list ergonomics, and route fitting that respects bottom nav + floating controls.

Fuel Lens
Fuel-type focused map state (E10 context)
The active fuel lens keeps users anchored to the right decision context, reducing scan effort and mismatch between map prices and filter intent.
Blazr E10 map view showing fuel-type focused decision context
Route Visibility
Map/List toggle above the main FAB + safe-area fitBounds
Route and nearby map transitions now compute asymmetric viewport padding so markers and price chips stay visible above the footer and floating controls.
Blazr Near Me search surface with manual suburb and postcode fallback
Action Geometry
Semicircle orbit layout with equal angular spacing
Quick-action buttons are positioned along a mathematically even arc around the anchor FAB, keeping spacing, labels, and reachability consistent across viewport sizes.
Blazr floating orbit menu with quick actions
Trust UX
Verified NSW badge with source popover and direct dataset link
A persistent trust control now explains data provenance inline and links users directly to the NSW FuelCheck dataset, improving confidence without interrupting map flow.
Blazr voice input and listening overlay for suburb search
Theme System
Sunset-driven map style switching with smooth cross-fade
The app now auto-switches between MapTiler Shortbread (dark) and MapTiler OMT (light) by local sunrise/sunset, while preserving overlay hierarchy and readability in both modes.
Blazr market trend surface for fuel price movement awareness
Decision Design Playbook

BlazrAI is designed for a fast money decision under real-world pressure. It combines ethical behavioral cues, transparent trust signals, and AI-assisted decision support so users move quickly without feeling manipulated.

Empathy Design
From “price table” to “what should I do now?”
The interface centers the real job-to-be-done: pick confidently in seconds. The lead card turns comparison overload into a clear, contextual recommendation.
Behavioral Psychology
Savings framing, detour-worthiness, and loss aversion
Copy frames effort versus reward (“Drive X km → Save $Y”) with worth-it cues and light loss framing to reduce hesitation without fake urgency.
Trust Architecture
Government provenance + freshness + confidence
Trust appears exactly where decisions happen: verified NSW provenance, freshness timestamps, and community stock confidence.
Personalization
Tank-size economics that make savings tangible
Tank presets and custom sizes convert litre pricing into personal dollar impact, turning abstract data into everyday value.
Accessibility + Inclusion
Voice summaries, large touch targets, map-first clarity
First-load voice briefing and replay support fast auditory confirmation, while high-contrast markers and large tap targets keep interaction legible on the move.
Engagement + Product Growth
Watchlists, alerts, feedback loops, and share hooks
Beyond one-off lookup, alerts, favorites, feedback, and sharing create a durable product loop without disrupting core utility.
UX Pattern
Map & Data
Technical
Alerts
BlazrAI E10 map view with live markers and verified data banner
Map & Data
Verified data is visible before any interaction
The trust ribbon sits above the map so users know prices are sourced from NSW government data before making a stop decision.
Trust cue first · Data provenance shown in-context
UX Pattern
Price stays on-map for instant scanability
Markers expose pricing directly so users can compare without opening cards first, reducing interaction cost under mobile time pressure.
Map-first comparison · Faster decision loop
UX Pattern
Fuel chips keep context anchored to E10 by default
Fuel switching is always one tap away, while E10 default reflects common NSW demand and avoids an empty first impression.
Default state tuned for common use · Instant re-query
Technical
Suburb context is surfaced in compact form
The active suburb/postcode state remains visible without covering marker clusters, preserving confidence in what area is being compared.
Low-clutter context + location fallback
E10 Map View
Fuel-context-first map with trust and marker visibility
Map & Data
Trust cues are embedded where choice happens
Verified source and sync status are placed directly above the map markers so users can act without second-guessing price credibility.
UX Pattern
Map-first remains the fastest path to value
For fuel decisions, location is the first filter. Users can see closest value clusters first, then drill into detail only when needed.
Technical
Single data model powers map + list states
One normalized payload drives markers, chips, and sheet summaries, keeping map context consistent across nearby and manual search flows.
BlazrAI Near Me and Search sheet with location and manual suburb inputs
UX Pattern
Search starts from one simple input pattern
Suburb, postcode, and intent-style queries all start in the same field, reducing friction and avoiding multi-step forms on mobile.
One-field intent entry · Lower input friction
Map & Data
Quick suburb chips keep fallback fast
When live location is unavailable, users can instantly pivot to known suburbs/postcodes instead of hitting a dead end.
Default NSW chips + recent searches
Technical
Voice and location controls stay in the same sheet
Mic and location actions are embedded in the search workflow, so users can retry input modes without leaving context.
Multimodal input in one interaction surface
Near Me + Search Sheet
Location-aware search with manual and voice fallback
UX Pattern
The input model is fast, not form-heavy
Users can type suburb/postcode, use quick chips, or trigger voice in the same place, minimizing context switching and hesitation.
Map & Data
Fallback defaults prevent empty-state confusion
If location is denied or outside NSW, the sheet still offers immediate, local defaults (like NSW 2768) so discovery continues without dead ends.
Technical
State handling keeps repeated opens consistent
The same sheet state machine handles open, close, retry, and re-search flows so users can return to search repeatedly without losing control context.
06 · Technical Architecture

Lean infrastructure, heavyweight product behavior

BlazrAI stays intentionally light on infrastructure while remaining serious about reliability. The browser handles interaction, PHP protects credentials and shapes data, Cloudflare solves network constraints, and MySQL gives the product memory. Gemini is integrated as a focused intent parser for natural-language search, with deterministic local fallback when AI is unavailable. This keeps AI as a value multiplier in the decision flow, not a brittle dependency. This is the real runtime architecture, not a concept diagram.

Client surface
BlazrAI PWA in the browser
The browser owns map interaction, bottom-sheet navigation, watchlist state, manual postcode search, and analytics events. It never touches NSW API credentials.
Leaflet map Bottom sheets GA4 events
Location intelligence
OpenStreetMap + Nominatim
GPS coordinates resolve to suburb/postcode labels that prefill search, alerts, and map context without Google billing overhead.
Reverse geocode No API key
Natural-language layer
Gemini intent parser + local fallback
Free-text requests like "cheapest U91 near me" are mapped into structured intent via /api/parse-intent.php. If Gemini is unavailable, the endpoint and client both fall back to local heuristic parsing so search never blocks.
Gemini API Fail-soft fallback
Outbound actions
Email notifications (privacy-first)
User research showed phone-based alerts felt too personal. SMS/Twilio was removed, and threshold alerts now ship through email-only delivery with the same cooldown safeguards.
PHP mail() User-feedback aligned
Runtime spine
PHP + MySQL core
Shared hosting executes the product logic. PHP receives requests, validates fuel type mappings, checks token cache, formats NSW headers, enriches stations with distance and trend metadata, and returns decision-ready JSON.
Browser requests nearby, search, alerts
NLP intent parse (Gemini + heuristic fallback)
Token cache checked before auth
Results normalized for UI use
History and alerts persisted
Edge bridge
Cloudflare Worker proxy
Cloudflare sits between the PHP backend and NSW API because some shared-hosting IP ranges were blocked upstream. The Worker restores reliability without increasing operating cost.
100K/day free IP abstraction
Government data
NSW FuelCheck API
Live pricing comes from the official NSW API. PHP handles OAuth flow, transaction IDs, timestamps, and endpoint shaping before each upstream call.
OAuth 2.0 2,500 calls/mo
Scheduled memory
Cron snapshots + alert checks
Two cron jobs add product memory: hourly alert evaluation and periodic station snapshots for trend and short-horizon forecasting.
Alert cron History cron
Why this architecture matters: cost is engineered out early, not patched later. Maps, geocoding, hosting, token caching, history capture, and analytics all run within free tiers or already-owned infrastructure while preserving product-grade reliability. AI is layered as an assistive parser, not a hard runtime dependency.
Stored product state

What gives BlazrAI memory

fuel_token prevents wasted request budget on repeat auth.

price_alerts stores user thresholds, areas, and cooldown windows.

price_history stores periodic station snapshots so trends and 48-hour outlooks are based on real observations.

Browser state stores watchlists, recents, and theme without requiring sign-up.
Observability

How the live product is measured

GA4 tracks the actual journey: fuel-type selection, nearby/search result loads, station navigation taps, watchlist opens, manual postcode fallback, and ad interactions. That means the app can be evaluated as a product funnel rather than a static demo.
07 · Engineering Challenges

Where reality fought back, and what fixed it

Every serious build hits constraints that no tutorial prepares you for. These three roadblocks forced architecture-level decisions, not cosmetic fixes.

Challenge 1 · Shared-hosting IP blocked by NSW API gateway
Calls from the shared-hosting environment to api.nsw.gov.au returned empty-body 404s despite valid DNS and connectivity. Root cause: NSW gateway silently blocked shared-hosting IP ranges.

Solution: Inserted a Cloudflare Worker proxy. PHP calls Worker, Worker calls NSW API, and reliability returns with no added run cost.
Challenge 2 · Google Maps requires billing activation
Google Maps and Geocoding both required billing activation, even at free-tier usage, which conflicted with long-term zero-cost operation.

Solution: Swapped to Leaflet + MapTiler (Shortbread dark / OMT light) with CARTO fallback and Nominatim geocoding. Result: no hard billing gate, resilient map delivery, and stronger day/night readability.
Challenge 3 · Outdated OAuth token URL in all documentation
Published docs and guides referenced api.nsw.gov.au/Token, which consistently returned 404. Diagnostic tests across seven URL variants confirmed a domain migration.

Solution: Updated the Worker to api.onegov.nsw.gov.au based on archived TfNSW forum evidence; token flow and live pricing recovered immediately.
Debugging Approach

Each incident was isolated with a dedicated debug.php endpoint that tested one layer at a time: network, DNS, token URL variants, and database access, then returned structured JSON.

That approach replaced guesswork with proof. Once stable, the debug endpoint was removed from production.

08 · Outcomes & Reflection

What ships today, and where it’s heading

BlazrAI is already live as a production-ready web product solving a real weekly cost problem for NSW drivers, with AI-assisted decision support, completed EV charging coverage, and an operating model now focused on WA, VIC, and QLD rollout.

Live & Publicly Deployed
Available now at kopunithavelu.com.au/fuelcheck. NSW drivers can open, allow location, and immediately act on nearby value without install or sign-up friction.
AI + Data Stack Integrated End-to-End
NSW FuelCheck, Cloudflare Workers, OpenStreetMap (maps + geocoding), and Gemini intent parsing are integrated into one cohesive flow despite different auth and rate-limit constraints.
Learning From User Feedback
Research made one priority clear: users wanted pricing help without sharing phone numbers. We removed Twilio SMS and shifted to privacy-first alerts, while retaining reliability through cooldown and clear consent patterns.
What’s Next

Next horizon for BlazrAI

Price trend charts
Expose 7-day station history so users spot weekly cycles and time fill-ups near local lows.
Price prediction
Blend cycle and index signals to forecast likely 48-hour direction: wait or fill now.
Route optimisation
Recommend the best-value station along regular commute paths, not just the closest pin.
Native app rollout (funded by donations)
When funding permits, package iOS/Android apps for store distribution while retaining the web core as the platform baseline.
PWA install + push notifications
Add installability and web push so alerts reach users instantly without SMS/email dependency.
National Expansion

Next expansion: WA, VIC, and QLD

BlazrAI is currently live across NSW using the official NSW FuelCheck API. The next rollout plan is focused on WA, VIC, and QLD, each with its own reporting scheme, regional pricing behavior, and fuel-type mix.

NSW — Live now
NSW FuelCheck API powering 2,500+ stations with real-time prices, alerts, and trend history.
VIC — Planned next
Victorian government fuel data integration is scoped as a priority in the next rollout wave.
WA & QLD — Planned next
WA and QLD are part of the same near-term expansion focus. BlazrAI's modular backend supports state adapters behind a unified API layer, so onboarding each state is a configuration extension, not a rebuild.
Multi-state architecture pattern
State detection via geolocation or manual selection routes each request to the correct regional API adapter — same UX, locally-accurate data.
Why this matters: Fuel price variation is a national problem, not a state-specific one. The ACCC regularly reports on unexplained price differentials across Australian cities. A driver moving from Sydney to Melbourne, Perth, or Brisbane should have the same level of decision support — not a blank map.
Future Fuel Layer · Live

EV charging stations — now sourced from official NSW government data

The EV charging layer is complete and live, powered by the Transport for NSW EV Charging Locations dataset published on Data.NSW. This keeps the same government-sourced, open-data approach as the fuel pricing layer — no third-party APIs, no billing gates, no vendor lock-in.

Data Source
Transport for NSW — EV Charging Locations
Published via Data.NSW open data portal
Licence: Creative Commons Attribution (CC-BY)
Updated: Quarterly · Last updated Sep 2025
Resource ID: 2c53cfbd-d9e6-4688-8f24-bb989e85980b
Access
CSV direct download CKAN DataStore API No API key required
Dataset fields — from the official data dictionary
Station_name
Charger location name
Station_address
Full street address
Opening_hours
Operating hours
Operator
Network operator (NRMA, Tesla, etc.)
Number_of_stations
Physical bays at the site
Number_of_plugs
Total plug count
Charger_type
AC destination or DC fast charge
Charger_rating
Power output in kW
CHAdeMO
CHAdeMO plug availability
CCS_SAE
CCS2 / SAE plug availability
Tesla_Fast
Tesla Supercharger plug
Latitude / Longitude
GPS coordinates for map plotting
LGANAME
Local Government Area name
PCODE
Postcode for location grouping
AC & DC chargers on the same map
Destination (slow AC) and fast (DC) chargers plotted as distinct marker types alongside petrol stations — mixed-fleet households see a complete picture in one view.
Connector filter: CHAdeMO, CCS2, Tesla
The dataset exposes CHAdeMO, CCS_SAE, and Tesla_Fast as separate boolean-style fields — mapping directly to the connector type filter chips in the BlazrAI UI.
Power rating for smart sorting
Charger_rating (kW output) enables the same value-framing logic as petrol: rank by speed-to-range rather than raw proximity.
Operator context & opening hours
Operator and Opening_hours surface network brand and access times — so drivers know whether a Tesla-only site or a 24h DC fast charger is within range before leaving home.
Why this source, not third-party EV APIs: The TfNSW dataset is government-published, CC-BY licensed, no-key, and updated quarterly by Transport for NSW. It covers both operational and planned future sites — giving BlazrAI the same zero-cost, government-verified provenance for EV charging that NSW FuelCheck provides for petrol pricing. The CKAN DataStore API at data.nsw.gov.au supports filtered queries by postcode, LGA, and charger type without authentication, consistent with BlazrAI’s infrastructure philosophy.
09 · Developer Guide & Technical Reference

Source code, architecture & data flows

This section documents the actual deployed source code — every file, endpoint, database table, configuration key, and JS module. It is generated directly from code review, not from memory. Use it as a reference for onboarding, auditing, or extending the codebase.

Stack Summary
What the codebase is made of

BlazrAI is a zero-framework progressive web app. There is no React, no Vue, no build step. The frontend is vanilla HTML + modular JS + CSS. The backend is PHP 8 on shared hosting. Data lives in MySQL. Background tasks run on server cron.

PHP 8.3 MySQL / PDO Vanilla JS Leaflet.js MapTiler styles CARTO fallback Nominatim Cloudflare Workers Email alerts Google Analytics 4 AdSense PayPal SDK
Deployment Topology
How it runs in production

All PHP files deploy to shared hosting. The Cloudflare Worker is a separate lightweight edge script that proxies NSW API requests, solving the IP-block constraint. There is no Docker, no container registry, and no CI/CD pipeline — deployment is manual file publish.

Browser
PHP/MySQL
Shared hosting
CF Worker
Edge proxy
NSW API
onegov.nsw.gov.au
Key Design Principles (from code)
Fail closed, not open
History enrichment, trend generation, and snapshot persistence are all wrapped in try/catch with silent failure. Live price fetches never break because a background feature failed.
Token caching as rate protection
The fuel_token table caches OAuth tokens for up to 12 hours. The token layer retries multiple URL/method variants before throwing, and auto-refreshes on a 401 mid-request.
Proxy as reliability, not complexity
The Cloudflare Worker is a thin HTTP passthrough, not a logic layer. All business logic stays in PHP. The worker simply relays requests from an unblocked IP range.
No frontend framework
The entire frontend is vanilla JS with no build step. Modules are separate files (app-core.js, app-utils.js, etc.) loaded via <script> tags in sequence.
localStorage as user state
Favorites, watchlists, recent searches, theme mode, stock reports, and alert email are all persisted in localStorage, requiring no account system.
History built, not fetched
The NSW API exposes no historical price series. Trend data is built locally via cron snapshots into price_history. The 48h prediction is a local heuristic over this data.

The diagrams below show the runtime architecture and data enrichment pipeline extracted directly from the source code. Both reflect actual deployed behaviour.

CLIENT PHP CORE · GODADDY CPANEL EDGE / EXTERNAL SCHEDULED OUTBOUND BlazrAI PWA Leaflet map Bottom sheets Vanilla JS modules localStorage Favorites · Watchlists Recent searches Theme · Alert email Nominatim Reverse geocoding Suburb + postcode Google Analytics 4 gtag() event tracking Funnel analytics Monetisation AdSense slots PayPal SDK donations config.php config.local.php overrides ENV var fallbacks define() all constants db.php Singleton PDO utf8mb4 · ERRMODE_EXCEPTION token.php getToken() w/ DB cache Multi-URL OAuth retry Auto-refresh on 401 fuelGet() / fuelPost() client.php stationPriceResults() haversineKm() distance persistPriceHistory() buildPrediction48h() fuelType normalisation Cloudflare fallback nearby.php POST lat/lng radius search prices.php POST suburb or postcode alerts.php GET/POST DELETE feedback.php POST form · multi transport email usage.php GET/POST visit counter + baseline MySQL fuel_token price_alerts price_history app_usage_counter Cloudflare Worker IP abstraction layer Thin HTTP passthrough 100K req/day free tier NSW FuelCheck API api.onegov.nsw.gov.au OAuth 2.0 · 12h tokens /FuelPriceCheck/v2/ 2,500 calls/month free snapshot-history.php Every 6 hours (cron) Writes to price_history Builds trend series check-alerts.php Hourly (cron) Groups by location 6h cooldown per alert Email (PHP mail) Alert notifications Feedback submissions Unsubscribe link SMS removed User privacy feedback Email-only alerts now PHP internal NSW API call Cron job Async / notification Optional proxy path
Station data enrichment pipeline
NSW API Raw stations[] + prices[] arrays join Normalise Merge by code Fuel type map P95→U95 etc. enrich Distance haversineKm() from user lat/lng null if no GPS persist price_history INSERT IGNORE 7-day window SELECT back predict 48h Outlook delta + drift rise/fall/steady low/med/high conf. sort JSON response Sorted by price then distance history_7d + pred ① RAW INPUT ② NORMALISE ③ ENRICH ④ PERSIST + LOAD ⑤ PREDICT ⑥ DELIVER

Complete file tree from the deployed source ZIP. Colour coding: yellow = core config, green = API endpoints, blue = cron, orange = JS modules, purple = CSS.

fuelcheck/
├── config.php // master config — loads defaults + config.local.php overrides
├── config.local.php // gitignored — add your API keys and DB credentials here
├── config.example.php // copy to config.local.php to start
├── index.html // single-page shell — all markup, sheet structure, nav, map container
├── database.sql // 4 CREATE TABLE statements — import once into MySQL
├── app-core.js // map init, geolocation, state machine, sheet navigation, theme
├── app-utils.js // decision math, card markup, savings calc, sparklines, stock
├── app-render.js // alerts UI, filters, summary card, brand filters
├── app-actions.js // GA4, AdSense, PayPal SDK, promo banner
├── app-ui.js // toast, loading screen, voice, context menu guard
├── app-icons.js // SVG icon registry, hydrateIcons()
├── app.js // entry point — DOMContentLoaded → initMap()
├── app-base.css // CSS variables, reset, typography, colour tokens
├── app-components.css // map, sheets, station cards, fuel strip
├── app-components-v2.css // decision engine cards, summary grid, sparklines
├── app.css // top-level includes, loading screen, utility overrides
├── api/
├── db.php // singleton PDO connection — shared by all endpoints
├── token.php // OAuth lifecycle, getToken(), fuelGet(), fuelPost()
├── client.php // stationPriceResults(), haversine, history, prediction
├── nearby.php // POST lat/lng/radius → enriched nearby stations
├── prices.php // POST suburb/postcode → enriched search results
├── parse-intent.php // POST free-text intent → structured search/nearby intent (Gemini + fallback)
├── alerts.php // GET/POST/DELETE alert rules, unsubscribe link
├── feedback.php // POST feedback form — multi-transport email
├── usage.php // GET/POST visit counter with configurable baseline
└── debug.php // diagnostic endpoint — REMOVE before production
└── cron/
├── snapshot-history.php // every 6h — fetches + persists station snapshots for trend data
└── check-alerts.php // every 1h — evaluates active alerts, sends email, 6h cooldown

All API endpoints are PHP files. CORS headers (Access-Control-Allow-Origin: *) are applied by jsonResponse() in client.php. All requests and responses are JSON except the unsubscribe GET which returns HTML.

POST /api/nearby.php Geolocation-based fuel search
latfloat — user latitude (required) lngfloat — user longitude (required) fueltypestring — one of E10, U91, U95, U98, DL, PDL, LPG. Default: E10 radiusint — search radius in km. Default: 5. Max returned: 20 results
// Response shape
{ "success": true, "count": 12, "results": [ { "code": "61234", "name": "...", "price": 189.9, "distance_km": 1.2, "history_7d": [...], "prediction_48h": { "direction": "fall", "label": "Likely to soften", "confidence": "medium" } } ] }
POST /api/prices.php Suburb / postcode search
suburbstring — suburb name (use this OR postcode, not both required) postcodestring — 4-digit Australian postcode fueltypestring — same values as nearby.php. Default: E10

Returns same enriched result shape as nearby.php except distance_km is null (no GPS origin).

POST /api/parse-intent.php Natural language intent parsing for search
querystring — free-text request (e.g., "cheapest diesel near me") selected_fuelstring — current fuel tab to bias inference has_locationbool — whether GPS is currently available location_labelstring — optional current suburb/postcode context

Returns intent object with intent, fuel_type, location_mode, suburb/postcode, confidence, and source metadata. Uses Gemini first, then local heuristic fallback to avoid runtime hard-fail.

GET / POST / DELETE /api/alerts.php Price alert management
GET ?email=Returns all alerts for this email. Add &unsubscribe=1 to deactivate all and return HTML confirmation page. POST bodyemail, fueltype, threshold (50–400), suburb or postcode, radius (2|5|10). Creates alert, deduplicates exact matches. DELETE bodyid (int), email (string). Soft-deletes by setting active=0.
POST /api/feedback.php In-app feedback form
namestring — sender display name emailstring — validated sender email (required) messagestring — 8–3000 chars, stripped of HTML contextobject — optional { fueltype, view, area, page } for diagnostics

Uses multi-transport delivery: tries mail() with sendmail params, falls back to mail() without params, then /usr/sbin/sendmail directly. Logs to feedback_submissions.log regardless.

GET / POST /api/usage.php Visit counter

Increments app_usage_counter on each request (unless ?increment=0). Returns { count, label, baseline, live_count }. Total = VISIT_COUNT_BASELINE + DB live count.

Import database.sql once to create all four tables. Tables are InnoDB, utf8mb4. All PHP files also call ensurePriceHistoryTable() and ensureUsageCounterTable() inline as a safety net.

fuel_token
ColumnTypeNotes
idINT UNSIGNED AUTO_INCREMENTPrimary key
access_tokenTEXTRaw Bearer token from NSW OAuth
expires_atDATETIMESet to now() + expires_in - 120s buffer. Indexed.
created_atTIMESTAMP DEFAULT CURRENT_TIMESTAMPAudit
price_alerts
ColumnTypeNotes
idINT UNSIGNEDPrimary key
emailVARCHAR(190)Alert owner. Indexed with active.
phoneVARCHAR(32)Legacy field (SMS removed after privacy feedback)
fuel_typeVARCHAR(10)E10 | U91 | U95 | U98 | DL | PDL | LPG
price_thresholdDECIMAL(6,1)Alert fires when cheapest ≤ this. Range: 50–400.
suburb / postcodeVARCHARLocation for alert evaluation. At least one required.
radius_kmINTAllowed values: 2, 5, 10
activeTINYINT(1)Soft delete flag. 1 = active.
last_notifiedDATETIME NULLUsed to enforce 6-hour cooldown
price_history
ColumnTypeNotes
idBIGINT UNSIGNEDPrimary key
station_codeVARCHAR(32)NSW station code
fuel_typeVARCHAR(10)Normalised fuel type
station_name, brand, suburb, postcodeVARCHARDenormalised for query performance
latitude / longitudeDECIMAL(10,7)Nullable
priceDECIMAL(6,1)Cents per litre
reported_atDATETIME NULLNSW API's lastupdated field, parsed via multiple date formats
captured_atTIMESTAMP DEFAULT CURRENT_TIMESTAMPWhen we stored this snapshot. Key index for 7-day query.
UNIQUE KEYuq_station_snapshot(station_code, fuel_type, price, reported_at) — prevents duplicate snapshots
app_usage_counter
ColumnTypeNotes
idTINYINT UNSIGNED PRIMARY KEYAlways 1 — single-row table
visit_countBIGINT UNSIGNED DEFAULT 0Incremented via INSERT ... ON DUPLICATE KEY UPDATE
updated_atTIMESTAMP ON UPDATE CURRENT_TIMESTAMPAuto-updated on each visit

All constants are set via config.php. Override any key in config.local.php (gitignored). Environment variables take highest priority — useful for CI/hosting env vars. The resolution order is: ENV var > config.local.php > default in config.php.

KeyDefaultDescription
FUEL_API_KEY''NSW FuelCheck API client key (required for direct mode)
FUEL_API_SECRET''NSW FuelCheck API client secret (required for direct mode)
FUEL_API_BASEapi.onegov.nsw.gov.au/FuelPriceCheck/v2Base URL for all fuel data endpoints
FUEL_TOKEN_URLapi.onegov.nsw.gov.au/oauth/…OAuth token endpoint. Confirmed working URL (not the one in old docs)
CLOUDFLARE_WORKER''If set, all NSW API calls route through this Worker URL. Leave empty for direct calls.
GEMINI_API_KEY''Gemini API key for NLP intent parser (/api/parse-intent.php). Optional at runtime because fallback parser exists.
GEMINI_MODELgemini-2.0-flashPreferred Gemini model for intent parsing. Endpoint can fallback to alternate models.
DB_HOST / DB_NAME / DB_USER / DB_PASSlocalhost / fuelcheck / fueluser / ''MySQL credentials for PDO connection
TWILIO_SID / TWILIO_TOKEN / TWILIO_FROM''Legacy only. SMS alert flow was removed after user privacy feedback.
ALERT_FROM_EMAIL / ALERT_FROM_NAMEalerts@example.comFrom address used in alert emails
APP_URLhttp://localhost/fuelcheckUsed to build unsubscribe links in alert emails
SNAPSHOT_ENABLEDtrueEnable/disable the history snapshot cron job
SNAPSHOT_LOCATIONS['Glenwood 2768', 'Parramatta 2150', 'Sydney 2000']Suburb + postcode pairs to snapshot on each cron run
SNAPSHOT_FUEL_TYPES['E10','U91','U95','U98','DL','PDL','LPG']Fuel types to snapshot per location
VISIT_COUNT_BASELINE856Added to live DB count for displayed total. Set to account for pre-DB visits.
// config.local.php — minimal production example
<?php return [
  'FUEL_API_KEY' => 'your-key-here',
  'FUEL_API_SECRET' => 'your-secret-here',
  'CLOUDFLARE_WORKER' => 'https://your-worker.workers.dev', // if host IP is blocked
  'GEMINI_API_KEY' => 'your-gemini-key', // optional NLP layer
  'GEMINI_MODEL' => 'gemini-2.0-flash',
  'DB_HOST' => 'localhost',
  'DB_NAME' => 'fuelcheck',
  'DB_USER' => 'fueluser',
  'DB_PASS' => 'your-db-password',
  'APP_URL' => 'https://yourdomain.com/fuelcheck',
  'TWILIO_SID' => '', // optional
];

No framework, no bundler. Each JS file is a flat module of global functions, loaded in order by index.html. State is global variables + localStorage.

app-core.js
Orchestration — map, navigation, geolocation, state machine
Initialises the Leaflet map with theme-aware tile sources (MapTiler Shortbread/OMT, CARTO fallback). Manages geolocation with GPS fallback. Owns the bottom-sheet navigation state machine (activeSheet, currentView). Handles NLP search flow via /api/parse-intent.php and fail-soft local parsing when AI intent is unavailable. Controls sunrise/sunset theme sync, map/list view switches, verified-source popover, fuel strip, promo banner, and visit counter. Entry point is initMap() called from app.js on DOMContentLoaded.
initMap() initAutoLocation() syncMapThemeLayer() syncUserLocationMarker() updateFuelTrendForCurrentFuel() renderFuelTrendChips() initPromoBanner()
app-utils.js
Decision math, card markup, savings, sparklines, stock
Contains all decision-layer logic: savings calculation, detour worthiness, annual savings estimate, 48h outlook label, sparkline SVG generation, stock status handling, confidence metadata, and the full stationCardMarkup() and renderStationList() renderers. All monetary calculations happen here.
stationCardMarkup() renderStationList() estimatedSavings() detourWorthiness() annualSavingsEstimate() sparklineSvg() confidenceMeta() stockStatusMeta()
app-render.js
Alerts UI, summary card, brand/watch filters, ad rail
Handles async fetch flows for alerts (load, delete, create). Renders the decision-engine summary card (renderSummary()) and full filter pipeline (applyFilters()). Also owns brand filter chip rendering and watchlist filter toggle.
loadAlerts() deleteAlert() applyFilters() renderSummary() renderBrandFilters() renderWatchFilters() renderAdRail()
app-actions.js
Analytics, AdSense, PayPal, promo
GA4 event tracking via gtag(). AdSense slot rendering — lazy-loads the adsbygoogle script only if a valid ca-pub-* client ID is configured via window.FUELCHECK_ADSENSE. PayPal HostedButtons SDK loader with promise deduplication. Manages donation panel rendering.
trackEvent() renderAdSenseInto() ensurePayPalLoaded() renderPayPalDonate()
app-ui.js
Toast, loading screen, voice readout, UX helpers
Toast notifications with auto-dismiss. Loading screen with animated state transitions. Web Speech API voice summary — auto-announces best nearby result on first load if browser supports it, with manual replay FAB. Context menu guard with proprietary source notice.
toast() showLoadingScreen() voiceSummary()
app-icons.js
SVG icon registry
Single source of truth for all SVG icons used across the app. iconSvg(name, size) returns inline SVG markup. hydrateIcons() replaces data-icon placeholder elements post-render, enabling icon use inside dynamically injected HTML.
iconSvg() hydrateIcons()
Client-side state model
mapInstance selectedFuel rawResults / filteredResults currentView (nearby|search) activeSheet userLoc { lat, lng, suburb, postcode } selectedBrand favoritesOnly quickMode

in-memory globals (reset on reload)

fuelcheck-favorites fuelcheck-stock-reports fuelcheck-stock-points fuelcheck-recent-searches fuelcheck-alert-email fuelcheck-theme-mode fuelcheck-fuel-trends fuelcheck-quick-mode

localStorage keys (persist across sessions)

Flow 1 — Nearby search (geolocation)
GPS fires
navigator.geolocation
Nominatim
lat/lng → suburb
POST /api/nearby.php
lat, lng, fueltype, radius
token.php
cache or OAuth
NSW API
/fuel/prices/nearby
client.php
enrich + sort
JSON → UI
map + sheet
Flow 2 — Alert creation and delivery
User sets alert
email, fuel, threshold
POST /api/alerts.php
validates + dedupes
price_alerts table
active = 1
check-alerts.php
runs hourly
NSW API
grouped by location
Email alerts
6h cooldown
Flow 3 — History snapshot (cron)
cron: every 6h
snapshot-history.php
Each location
× each fuel type
NSW API
/fuel/prices/location
stationPriceResults()
normalise + enrich
INSERT IGNORE
price_history
7-day trend
matures over time
Flow 4 — 48h prediction algorithm

The buildPrediction48h() function in client.php runs over the 7-day history_7d array for each station. It does not call any external AI service. Gemini is used only for user-query intent parsing, not price forecasting.

// Simplified algorithm
$delta = $last - $first; // full-period movement
$drift = $last - $recentAvg; // recent 3-point drift

if ($delta >= 2.5 || $drift >= 1.5) direction = 'rise';
if ($delta <= -2.5 || $drift <= -1.5) direction = 'fall';

// Confidence: low (<3 pts) | medium (3–4 pts) | high (5+ pts)

Both cron jobs are configured via the hosting scheduler. They run PHP scripts directly and log output to files for debugging.

CRON cron/snapshot-history.php Every 6 hours · Builds trend history
# Server cron command
0 0,6,12,18 * * * /usr/local/bin/php /home/USERNAME/public_html/fuelcheck/cron/snapshot-history.php >> /home/USERNAME/logs/fuelcheck-history.log 2>&1
ReadsSNAPSHOT_LOCATIONS and SNAPSHOT_FUEL_TYPES from config. Iterates each location × fuel type combination. WritesCalls stationPriceResults() which calls persistPriceHistory() via INSERT IGNORE into price_history. API cost3 locations × 7 fuel types × 4 runs/day = 84 NSW API calls/day. Well within 2,500/month free tier. Trend maturityHigh-confidence predictions (5+ data points) available after ~30 hours. Medium after ~18 hours.
CRON cron/check-alerts.php Every 1 hour · Evaluates and sends alerts
# Server cron command
0 * * * * /usr/local/bin/php /home/USERNAME/public_html/fuelcheck/cron/check-alerts.php >> /home/USERNAME/logs/fuelcheck-alerts.log 2>&1
Step 1Load all active=1 alert rows from price_alerts. Step 2Group alerts by fuel_type|location key to minimise NSW API calls. Step 3For each group, fetch live prices via /fuel/prices/location. Find cheapest price. Step 4If cheapest ≤ threshold AND last_notified was >6h ago → send email alert → update last_notified. Cooldown6-hour per-alert cooldown prevents notification spam even when prices stay low.

These gaps were identified during source code review. They are documented here to support informed decisions about production hardening, not as blockers to the current production deployment.

Alert radius mismatch — UI vs. backend validation
The frontend allows selecting a 15 km alert radius, but alerts.php validates that radius must be one of [2, 5, 10]. Any 15 km submission will be rejected with a 400 error. Fix: remove 15 km from the UI chip options or add it to the backend allowlist.
Research-driven alert privacy change
User interviews showed SMS alerts required sharing phone numbers that many users considered too personal for this use case. BlazrAI now uses email-only alerts. Remaining phone/Twilio references are legacy cleanup items that can be removed in a later schema tidy-up.
No API rate limiting or abuse protection
The PHP endpoints have no request-rate limiting, IP throttling, or token-based access control. A malicious caller could trigger NSW API calls in bulk via /api/nearby.php until the 2,500/month quota is exhausted. Mitigation: add Cloudflare rate limiting rules at the Worker level, or add a simple per-IP counter in PHP before NSW API calls.
debug.php should be removed from production
The source includes api/debug.php, which was used during development to test individual layers (DNS, token, DB). This file exposes diagnostic information and should be deleted or gated behind authentication before any public deployment.
Notification delivery reliability depends on host configuration
feedback.php and check-alerts.php use PHP's mail() function with sendmail fallback. On shared hosting, mail deliverability varies significantly. SPF/DKIM records are required for reliable inbox delivery. Consider switching to a transactional email provider (Postmark, Mailgun, SendGrid) for production-grade alert reliability.
Trend history only covers configured snapshot locations
Only stations in SNAPSHOT_LOCATIONS suburbs accumulate multi-day trend history. Stations in other areas will show single-point history until a user in that area triggers a live request (which also writes to price_history). This means 48h predictions in unconfigured suburbs start at "low confidence" until organic usage generates data.
Official Government Dataset
Transport for NSW — EV Charging Locations
Published on Data.NSW by Transport for NSW. Open data, CC-BY licence. Covers destination (AC) and fast (DC) EV chargers currently operational in NSW plus those in future development pipeline. Updated quarterly.
Key Identifiers
// Dataset page
dataset: 2-ev-charging-locations

// Active CSV resource
resource_id: 2c53cfbd-d9e6-4688-8f24-bb989e85980b

// Direct CSV download
file: ev_chargers_consolidated_sep25.csv
Two access methods
GET CSV direct download Quarterly static file

Download the full dataset as a flat CSV file. No authentication required. Filename versioned by date (ev_chargers_consolidated_sep25.csv). Best for batch import into the BlazrAI database on a scheduled cron.

# Full dataset download
GET opendata.transport.nsw.gov.au/data/dataset/
    be1c4de4-.../resource/7bbb6461-.../download/
    ev_chargers_consolidated_sep25.csv
GET / POST CKAN DataStore API Queryable, filterable

Query the dataset in real time via the CKAN DataStore API. Supports filters, q (full-text), limit, and offset. No API key required. Useful for proximity lookups by postcode or operator.

// Filter by postcode, no auth needed
GET data.nsw.gov.au/data/api/action/
  datastore_search
  ?resource_id=2c53cfbd-...
  &filters={"PCODE":"2000"}
  &limit=50
Data dictionary — all 16 fields
Field nameTypeBlazrAI usage
OBJECTIDtextUnique row identifier — use as stable station key
Station_nametextDisplay name on map marker and charger card
Station_addresstextStreet address for directions CTA
Opening_hourstextShown on charger detail card — helps users avoid closed sites
OperatortextNetwork brand badge (NRMA, Tesla, Chargefox, etc.) — enables operator filter chip
Number_of_stationstextPhysical bay count — displayed as "X bays" on card
Number_of_plugstextTotal plug count — shown alongside bays for capacity context
Charger_typetextAC (destination) or DC (fast) — drives the charger-type filter and marker style on map
Charger_ratingtextkW output — enables sorting by speed and "minutes to 80%" calculation
CHAdeMOtextBoolean-style field — maps to CHAdeMO connector filter chip
CCS_SAEtextBoolean-style field — maps to CCS2 connector filter chip (most common in new vehicles)
Tesla_FasttextBoolean-style field — maps to Tesla Supercharger filter chip
LatitudetextGPS latitude — direct input to Leaflet marker and haversineKm() distance calc
LongitudetextGPS longitude — same as above
LGANAMEtextLocal Government Area — used for regional grouping and alert scoping
PCODEtextPostcode — used as CKAN filter key for proximity queries
Planned integration architecture
TfNSW Dataset data.nsw.gov.au CC-BY licence CSV + CKAN API No auth required cron / batch ev_chargers MySQL table Indexed by PCODE + lat/lng + charger_type query api/ev-nearby.php POST lat/lng/type haversineKm() radius connector filter returns enriched JSON render BlazrAI map layer EV marker type AC=blue / DC=teal Connector chips kW + bay count User decision Which charger? Fast enough? Open now? Navigate → ① SOURCE ② STORE ③ SERVE ④ RENDER ⑤ ACT
Planned database table — ev_chargers
ColumnTypeSource fieldNotes
idINT UNSIGNED PKOBJECTIDStable unique identifier from TfNSW dataset
station_nameVARCHAR(255)Station_nameDisplay label for map marker and card
addressVARCHAR(255)Station_addressStreet address for directions CTA
operatorVARCHAR(120)OperatorIndexed — supports operator filter queries
opening_hoursVARCHAR(120)Opening_hoursFreetext — shown on card
charger_typeENUM('AC','DC')Charger_typeDrives map marker colour and type filter
charger_rating_kwDECIMAL(6,1)Charger_ratingParsed from text; enables speed sorting
num_stationsTINYINTNumber_of_stationsPhysical bay count
num_plugsTINYINTNumber_of_plugsTotal plug capacity
has_chademoTINYINT(1)CHAdeMOBoolean connector flag
has_ccsTINYINT(1)CCS_SAEBoolean connector flag — most common in new EVs
has_teslaTINYINT(1)Tesla_FastBoolean connector flag
latitudeDECIMAL(10,7)LatitudeIndexed alongside longitude for haversine queries
longitudeDECIMAL(10,7)LongitudeSame
lga_nameVARCHAR(120)LGANAMERegional grouping for alert scoping
postcodeVARCHAR(10)PCODEIndexed — matches CKAN filter key for live queries
imported_atTIMESTAMP DEFAULT CURRENT_TIMESTAMPLast batch import timestamp for freshness display
Integration design notes
Same zero-cost philosophy
TfNSW data is CC-BY, no API key, no billing. This mirrors how BlazrAI uses Nominatim for geocoding plus MapTiler/CARTO tile delivery with graceful fallback, keeping map costs and outage risk under control.
Quarterly import, not real-time
TfNSW updates the dataset quarterly. The integration model is a cron-based batch import (similar to snapshot-history.php) that refreshes the local ev_chargers table on a schedule aligned to the dataset update cadence.
Charger_rating needs parsing
The Charger_rating field is a text column. The import script will need to extract the numeric kW value (e.g. "50kW" → 50.0) before storing in charger_rating_kw DECIMAL for sorting and speed calculations.