Files
fuelboard-relay-api/README.md
T
FuelBoard Contributor b315f4dc47 FuelBoard Relay API: official Fuel Finder API adapter (sibling of CSV relay)
- Talks to the OFFICIAL GOV.UK Fuel Finder API instead of the public CSV
  mirror: OAuth token (/api/v1/oauth/generate_access_token), batched
  paging (500/page via batch-number) over /api/v1/pfs and
  /api/v1/pfs/fuel-prices, joined on node_id.
- Price strings parsed to pence with pounds->pence correction (< 2.0);
  50-500p sane band keeps junk out of the cheapest reference.
- Fuel mapping: E5/E10 as-is; B7/B7_STANDARD/B7S/B7P/B10 -> DIESEL;
  HVO/SDV dropped (app tracks E5/E10/DIESEL only).
- lat/lng parsed from nested location strings.
- Output contract IDENTICAL to fuelboard-relay (/health + /api/v1/stations),
  so the app/widget/alerts work unchanged; switch = point app at :8789.
- CSV mirror kept as automatic fallback (FUEL_SOURCE=auto) so the full
  path is testable before GOV.UK One Login creds exist.
- 12 unit tests against documented API shapes (no network).
2026-08-12 15:34:57 +01:00

98 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# FuelBoard Relay API
Keyless proxy for the **official UK Fuel Finder API** (the OAuth-protected
GOV.UK service). Sibling of `fuelboard-relay` (which reads the public CSV
mirror): this one talks to the real API, joins the station-info and fuel-price
endpoints on `node_id`, and normalises to the **same output shape** so the
FuelBoard iOS app and widget work against either relay unchanged.
Holds the GOV.UK OAuth 2.0 client credentials server-side so the app never
embeds a secret (secrets inside an IPA are extractable by anyone).
## Architecture
```
FuelBoard app / widget
│ GET http://192.168.1.131:8789/api/v1/stations?fuel=e10&lat=..&lng=..
FuelBoard Relay API (this repo, runs on the Mac Mini)
│ POST /api/v1/oauth/generate_access_token (token cached ~1h)
│ GET /api/v1/pfs?batch-number=N (station info, 500/page)
│ GET /api/v1/pfs/fuel-prices?batch-number=N (prices, 500/page)
UK Fuel Finder API (official gov.uk open data)
```
- Token refreshed automatically before expiry (60s buffer); reused across the
two batched loops (docs: reuse tokens until near expiry).
- Both endpoints paginated 500 records/page via `batch-number` until a short
page is returned.
- Prices arrive as **decimal strings in pence** (`"0120.0000"` = 120.0p); some
stations report pounds (values < 2.0 are multiplied by 100). Out-of-band
values are dropped (50500p sane band) so a pounds-denominated column can't
poison the nationwide cheapest reference.
- Fuel types mapped: `E5`/`E10` → same, `B7`/`B7_STANDARD`/`B7S`/`B7P`/`B10`
`DIESEL`. Others (`HVO`, `SDV`, …) are ignored — the app tracks exactly
E5/E10/DIESEL.
- Station snapshot cached `FUEL_CACHE_TTL` (default 300s); stale cache is
served on upstream failure so the app never sees a hard outage.
- No credentials are exposed to clients — the app calls the relay keyless.
## Setup
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
pip install pytest # only needed for the test suite
cp .env.example .env # then fill in FUEL_API_CLIENT_ID / FUEL_API_CLIENT_SECRET
./scripts/run.sh # serves on FUEL_RELAY_PORT (default 8789)
```
Without credentials, `FUEL_SOURCE=auto` falls back to the public hourly CSV
mirror (same parser as `fuelboard-relay`), so the full relay→app path is
testable end-to-end before GOV.UK One Login creds exist. `/health` reports the
active `source` (`csv` vs `api`).
## Endpoints
| Endpoint | Description |
|---|---|
| `GET /health` | Cache status, active source, last sync, failure counters |
| `GET /api/v1/stations` | All stations with prices (keyless) |
| `GET /api/v1/stations?fuel=e10&lat=53.72&lng=-1.85&radius=10&limit=10` | Filter by fuel, radius around a point, sorted nearest-first |
| `GET /api/v1/stations?fuel=diesel` (no lat/lng) | Sorted cheapest-first |
Fuel grades: `e10`, `e5`, `diesel`. Output contract identical to
`fuelboard-relay` — the app can switch relays by changing the base URL only.
## Getting credentials
1. Sign in at <https://developer.fuel-finder.service.gov.uk/get-started-ifr/onelogin> with GOV.UK One Login.
2. Register an application → get `client_id` + `client_secret`.
3. Put them in `.env` (never in git, never in the app).
## Fair use notes (from the Fuel Finder developer guideline)
- Refresh at least every 5 minutes — we do (cron-style refresh on request + TTL cache).
- Serve data unmodified — this relay only filters/sorts, never alters prices.
- Keep timestamps — `price_updated` is passed through untouched.
- Rate limits: ~100 rpm documented — batched loops issue ~2 requests per page
(info + prices), comfortably inside the limit on a 5-minute cadence.
## Tests
```bash
python -m pytest tests/ -q
```
Covers price-string parsing (pence, pounds, out-of-band), the node_id join,
fuel-type mapping, and CSV-fallback parsing — against the documented API
shapes, no network required.
## Switching the app
Point `Shared/FuelPriceProvider.swift`'s `RelayFuelProvider.baseURL` at
`http://192.168.1.131:8789` (API relay) instead of `:8788` (CSV relay). No
other app change needed — the station JSON is identical in shape.