Remove obsolete Livewire fuel search components and consolidate pricing tiers
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (8.3) (push) Has been cancelled
tests / ci (8.4) (push) Has been cancelled
tests / ci (8.5) (push) Has been cancelled

- Delete unused Livewire Search test and fuel type select Blade component
- Move subscription webhook listener from EventServiceProvider to AppServiceProvider
- Add FUEL_TYPES global config to app layout for client-side use
- Add Billable trait to User model and include email_verified_at in fillable
- Implement monthly/annual cadence toggle with pricing display and smart CTA routing on homepage
- Update VerifyApiKeyMiddlewareTest to use e10 instead of petrol
- Refactor PollFuelPrices to auto-refresh stale stations based on last_seen_at
- Add incremental polling with cached timestamp and effective-start-timestamp param to FuelPriceService
- Normalize amenities/fuel_types from API objects to flat arrays, skip stations missing required fields
- Log response body on API failures in ApiLogger
- Default homepage sort to 'reliable' instead of 'price'
This commit is contained in:
Ovidiu U
2026-04-20 14:12:15 +01:00
parent aec547cd86
commit 5acb99c9e3
33 changed files with 739 additions and 391 deletions

View File

@@ -5,7 +5,7 @@
- Base URL: `https://www.fuel-finder.service.gov.uk/api/v1/`
- Returns: all UK station prices + station metadata (~14,500 stations)
- Update frequency: stations report within 30 minutes of price change
- Our polling interval: every 15 minutes via scheduler (incremental), full refresh once daily
- Our polling interval: every 30 minutes via scheduler (incremental using `effective-start-timestamp`), station metadata auto-refreshed once per day on the first poll after midnight
### Authentication
@@ -35,24 +35,34 @@ Content-Type: application/json
- Include token in every API request: `Authorization: Bearer {token}`
#### Endpoints
- `GET /api/v1/pfs/fuel-prices?batch-number` — all/incremental station prices
- `GET /api/v1/pfs?batch-number`all/incremental station metadata
- `GET /api/v1/pfs/fuel-prices?batch-number={n}` — all station prices (500 stations per batch)
- `GET /api/v1/pfs/fuel-prices?batch-number={n}&effective-start-timestamp=YYYY-MM-DD HH:MM:SS` — incremental, only prices changed since timestamp
- `GET /api/v1/pfs?batch-number={n}` — all station metadata (500 per batch)
- `GET /api/v1/pfs?batch-number={n}&effective-start-timestamp=YYYY-MM-DD HH:MM:SS` — incremental station metadata
**Fuel prices response fields** (array of stations):
- `node_id` — station identifier
- `trading_name` — station name
- `node_id`, `public_phone_number`, `trading_name` — station identifiers
- `fuel_prices[]` — array of `{fuel_type, price, price_last_updated, price_change_effective_timestamp}`
- Fuel types: `E5`, `E10`, `B7_STANDARD`, `B7_PREMIUM`, `B10`, `HVO`
- Fuel types (API casing): `E5`, `E10`, `B7_Standard`, `B7_Premium`, `B10`, `HVO` — lowercased on ingest via `FuelType::fromApiValue()`
- Price is a float (e.g. `159.9` = 159.9p) — multiply × 100 and store as integer pence
**Station metadata response fields** (array of stations):
- `node_id`, `trading_name`, `brand_name`
- `node_id`, `trading_name`, `brand_name`, `is_same_trading_and_brand_name`, `public_phone_number`
- `is_supermarket_service_station`, `is_motorway_service_station`
- `temporary_closure`, `permanent_closure`
- `location``{address_line_1, city, postcode, latitude, longitude}`
- `amenities`string array (e.g. `car_wash`, `adblue_pumps`)
- `fuel_types`string array of available fuel types
- `opening_times`per-day open/close times (not used in scoring)
- `temporary_closure`, `permanent_closure`, `permanent_closure_date`
- `location``{address_line_1, address_line_2, city, county, country, postcode, latitude, longitude}`
- `amenities`**OBJECT** with boolean flags: `{adblue_pumps, adblue_packaged, lpg_pumps, car_wash, air_pump_or_screenwash, water_filling, twenty_four_hour_fuel, customer_toilets}`. Normalised at ingest to a flat array of enabled keys.
- `fuel_types`**OBJECT** with boolean flags: `{E10, E5, B7_Standard, B7_Premium, B10, HVO}`. Normalised at ingest to a flat array of enabled keys.
- `opening_times``usual_days.{monday..sunday}.{open, close, is_24_hours}` + `bank_holidays.type.{open_time, close_time, is_24_hours}`. Stored as raw JSON, not used in scoring.
### Required-field validation
Stations missing any of `node_id`, `trading_name`, `location.postcode`, `location.latitude`, `location.longitude` are dropped at ingest with a warning. Price rows missing any of `fuel_type`, `price`, `price_last_updated`, `price_change_effective_timestamp` are skipped silently.
### Incremental polling (FuelPriceService::pollPrices)
On each successful poll the wall-clock start time is cached under `fuel_finder_last_price_poll_at` (forever). The next poll sends this as `effective-start-timestamp`. Cold start (cache miss) performs a full fetch.
### FK safety
Price batches are filtered against the `stations` table before insert — any station not yet in `stations` is skipped and logged. This guards against new stations appearing in the prices endpoint before the next metadata refresh picks them up.
### FuelPriceService responsibilities
1. Fetch OAuth token (cache it)