Files
fuel-price/docs/api-reference.md
Ovidiu U 6a80c11f38
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
feat: add LLM prediction providers with structured output support
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-07 14:42:44 +01:00

300 lines
8.6 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.

# FuelAlert API Reference
Base URL: `https://fuel-price.test/api`
All endpoints return JSON. All endpoints require an `X-Api-Key` header.
**Authentication:**
```
X-Api-Key: your-secret-key
```
All requests without a valid key return `403 Forbidden`.
---
## Stations
### GET `/api/stations`
Returns nearby petrol stations with live prices for a given fuel type.
**Location — provide one of:**
| Parameter | Type | Description |
|---|---|---|
| `postcode` | string | UK postcode, outcode, or place name (resolved via postcodes.io) |
| `lat` + `lng` | float | Decimal lat/lng (required if no postcode) |
**Optional:**
| Parameter | Type | Default | Notes |
|---|---|---|---|
| `fuel_type` | string | — | **Required.** See fuel type aliases below |
| `radius` | float | `10.0` | Search radius in km (0.150) |
| `sort` | string | `"price"` | `"price"`, `"distance"`, `"updated"`, or `"brand"` |
| `pricing_mode` | string | — | `"pump"` (reserved, no effect yet) |
**Sort values:**
| Value | Sorts by |
|---|---|
| `price` | Price ascending (cheapest first) — **default** |
| `distance` | Distance ascending (closest first) |
| `updated` | Price freshness descending (most recently updated first) |
| `brand` | Brand name AZ |
**Fuel type aliases** (`fuel_type` accepts any of these):
| Alias | Maps to |
|---|---|
| `petrol`, `unleaded`, `e10` | E10 (standard unleaded) |
| `premium_unleaded`, `e5` | E5 (super unleaded) |
| `diesel`, `b7_standard` | B7 Standard (standard diesel) |
| `premium_diesel`, `b7_premium` | B7 Premium (premium diesel) |
| `b10` | B10 (biodiesel blend) |
| `hvo` | HVO (hydrotreated vegetable oil) |
**Example request:**
```
GET /api/stations?postcode=SW1A1AA&fuel_type=petrol&radius=5&sort=price
GET /api/stations?lat=51.5074&lng=-0.1278&fuel_type=diesel&radius=10&sort=distance
GET /api/stations?postcode=M11AE&fuel_type=petrol&sort=updated
GET /api/stations?postcode=M11AE&fuel_type=petrol&sort=brand
```
**Response:**
```json
{
"data": [
{
"station_id": "0028acef5f3afc41...",
"name": "Alex Fuel Station",
"brand": "BP",
"is_supermarket": false,
"address": "123 High Street, London",
"postcode": "SW1A 1AA",
"lat": 51.5074,
"lng": -0.1278,
"distance_km": 1.23,
"fuel_type": "e10",
"price_pence": 14390,
"price": 143.9,
"price_updated_at": "2026-04-05T08:00:00.000Z"
}
],
"meta": {
"count": 12,
"fuel_type": "e10",
"radius_km": 10.0,
"lowest_pence": 14290,
"highest_pence": 14890,
"cheapest_price_pence": 14290,
"avg_pence": 14523.5
}
}
```
**Notes:**
- `price_pence` is the raw integer (e.g. `14390` = 143.90p). `price` is the float in pence (e.g. `143.9`).
- Closed stations (temporary or permanent) are excluded.
- Every call logs a search record used by the stats endpoint.
**Error — postcode not found:**
```json
{ "errors": { "postcode": ["Postcode not found."] } }
```
**Error — missing location:**
```json
{ "errors": { "lat": ["The lat field is required when postcode is not present."] } }
```
---
## Stats
### GET `/api/stats/searches`
Aggregate search statistics. Useful for a "social proof" counter on the frontend.
| Parameter | Type | Default | Notes |
|---|---|---|---|
| `period` | string | `"week"` | `"week"` (7 days) or `"month"` (30 days) |
**Example request:**
```
GET /api/stats/searches
GET /api/stats/searches?period=month
```
**Response:**
```json
{
"total_searches": 3842,
"unique_searchers": 1201,
"avg_results": 14.3,
"avg_lowest_price": 141.2,
"avg_highest_price": 148.7,
"avg_price": 144.9,
"period": "week",
"message": "Helped 1201 drivers find cheaper fuel this week so far!"
}
```
**Notes:**
- `avg_lowest_price`, `avg_highest_price`, `avg_price` are in **pence** as floats (e.g. `141.2` = 141.2p).
- `unique_searchers` is based on hashed IP — approximate unique users.
---
## Prediction
### GET `/api/prediction`
National or regional E10 fuel price direction forecast for the next 7 days, based on live price data signals. Always analyses E10 — the most widely available fuel and the one with the most price history.
| Parameter | Type | Description |
|---|---|---|
| `lat` | float | Optional. Decimal latitude. Enables regional prediction (50km radius). |
| `lng` | float | Optional. Decimal longitude. Required if `lat` is provided. |
**Example request:**
```
GET /api/prediction
GET /api/prediction?lat=51.5074&lng=-0.1278
```
**Response:**
```json
{
"fuel_type": "e10",
"current_avg": 143.9,
"predicted_direction": "down",
"predicted_change_pence": -2.1,
"confidence_score": 74.5,
"confidence_label": "high",
"action": "wait",
"reasoning": "National prices have been falling at 0.3p/day. Supermarkets led last week — independents typically follow within 48h.",
"prediction_horizon_days": 7,
"region_key": "national",
"methodology": "multi_signal_live_fallback",
"signals": {
"trend": {
"score": -0.8,
"confidence": 0.92,
"direction": "down",
"detail": "Slope: -0.30p/day over 5 days (R²=0.92) [Adaptive lookback active]",
"data_points": 5,
"enabled": true,
"slope": -0.3,
"r_squared": 0.92
},
"day_of_week": {
"score": 0.0,
"confidence": 0.0,
"direction": "stable",
"detail": "Insufficient history for day-of-week pattern.",
"data_points": 0,
"enabled": true
},
"brand_behaviour": {
"score": -1.0,
"confidence": 0.6,
"direction": "down",
"detail": "Supermarkets fell 3.5p vs majors 0.5p (divergence: 3.0p). Expect majors to follow.",
"data_points": 2800,
"enabled": true
},
"national_momentum": {
"score": 0.0,
"confidence": 0.0,
"direction": "stable",
"detail": "National momentum disabled for national predictions",
"data_points": 0,
"enabled": false
},
"regional_momentum": {
"score": 0.0,
"confidence": 0.0,
"direction": "stable",
"detail": "No coordinates provided for regional momentum analysis",
"data_points": 0,
"enabled": false
},
"price_stickiness": {
"score": 0.05,
"confidence": 0.8,
"direction": "stable",
"detail": "Sticky prices (avg hold: 5.2 days) — more predictable.",
"data_points": 160,
"enabled": true
}
}
}
```
**`region_key` values:**
| Value | Meaning |
|---|---|
| `"national"` | No coordinates provided. `current_avg` and signals use national data. `regional_momentum` is disabled. |
| `"regional"` | Coordinates provided. `current_avg` uses stations within 50km. `regional_momentum` is the primary signal (50% weight). Falls back to national average if no stations found in radius. |
**Key fields:**
| Field | Values | Meaning |
|---|---|---|
| `predicted_direction` | `"up"`, `"down"`, `"stable"` | Price trend direction |
| `action` | `"fill_now"`, `"wait"`, `"no_signal"` | Consumer-facing recommendation |
| `confidence_label` | `"high"` (≥70), `"medium"` (≥40), `"low"` (<40) | Signal strength |
| `predicted_change_pence` | float | Expected p/litre change over 7 days |
| `current_avg` | float | Average price in pence (e.g. `143.9` = 143.9p). Regional if lat/lng given, else national. |
**Signal weights:**
| Scope | Signal | Weight |
|---|---|---|
| National | trend | 45% |
| National | brand_behaviour | 25% |
| National | day_of_week | 20% |
| National | price_stickiness | 10% |
| Regional | regional_momentum | 50% |
| Regional | trend | 20% |
| Regional | day_of_week | 15% |
| Regional | brand_behaviour | 10% |
| Regional | price_stickiness | 5% |
**Signal structure** (each signal in `signals`):
| Field | Type | Notes |
|---|---|---|
| `score` | float (-1.0 to 1.0) | Negative = falling pressure, positive = rising |
| `confidence` | float (0.01.0) | How reliable this signal is |
| `direction` | `"up"` / `"down"` / `"stable"` | Signal direction |
| `detail` | string | Human-readable explanation |
| `data_points` | int | Number of price records used |
| `enabled` | bool | False if signal was skipped (missing data or coordinates) |
**LLM-backed prediction** separately, the nightly `oil:predict` command generates an oil price direction from Brent crude data and stores it in `price_predictions`. This feeds into `AlertScoringService` (Signal 4) but is not exposed directly through this endpoint. See [LLM Prediction Providers](llm-prediction-providers.md).
---
## Error Shapes
**Validation error (422):**
```json
{
"message": "The fuel type field is required.",
"errors": {
"fuel_type": ["The fuel type field is required."]
}
}
```
**Forbidden (403)** missing or invalid `X-Api-Key`:
```json
{ "message": "Forbidden." }
```