Files
fuel-price/docs/superpowers/specs/2026-04-14-eia-brent-price-fallback-design.md
Ovidiu U aec547cd86
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
refactor: restructure Stripe pricing config to support monthly and annual tiers
- Nest price IDs under `monthly` and `annual` keys for each tier (basic, plus, pro)
2026-04-14 19:26:01 +01:00

3.2 KiB

EIA Brent Price Source — Primary with FRED Fallback

Date: 2026-04-14
Status: Approved

Problem

FRED (DCOILBRENTEU) publishes Brent crude prices with a lag on top of the EIA's own publication delay. As of 2026-04-14, FRED's most recent data is 2026-04-02 — 12 days stale. EIA is the original data source; FRED mirrors it. Using EIA directly removes the mirroring lag.

Goal

Replace FRED as the primary source for fetchBrentPrices() with the EIA Open Data API, keeping FRED as a fallback so the system degrades gracefully if EIA is unavailable.


Architecture

All changes are confined to app/Services/OilPriceService.php and supporting config/env files.

fetchBrentPrices() — updated flow

1. $rows = fetchFromEia()
2. if ($rows === null):
       Log::warning('OilPriceService: EIA fetch failed, falling back to FRED')
       $rows = fetchFromFred()
3. if ($rows === null):
       Log::error('OilPriceService: both EIA and FRED fetch failed')
       return
4. BrentPrice::upsert($rows, ['date'], ['price_usd'])

fetchFromEia(): ?array (new private method)

  • Endpoint: GET https://api.eia.gov/v2/petroleum/pri/spt/data/
  • Params: api_key, frequency=daily, data[0]=value, facets[series][]=RBRTE, sort[0][column]=period, sort[0][direction]=desc, length=30
  • Returns null on: HTTP error, exception, empty/missing response.data array
  • Maps each row: ['date' => $row['period'], 'price_usd' => (float) $row['value']]
  • Filters rows where value is '.' (EIA uses same placeholder as FRED)

fetchFromFred(): ?array (extracted private method)

  • Existing FRED logic moved verbatim from the current fetchBrentPrices() body
  • Returns null on: HTTP error, exception, empty observations
  • Maps each row: ['date' => $obs['date'], 'price_usd' => (float) $obs['value']]

Configuration

config/services.php

Add under existing entries:

'eia' => [
    'api_key' => env('EIA_API_KEY'),
],

.env.example

Add:

EIA_API_KEY=           # US EIA Open Data API key — register free at eia.gov/opendata

FRED key stays in .env.example (still used as fallback).


Logging

Event Level Message
EIA fetch succeeded (no log — normal path)
EIA fetch failed, FRED used warning OilPriceService: EIA fetch failed, falling back to FRED
Both failed error OilPriceService: both EIA and FRED fetch failed
No rows after filter warning OilPriceService: no valid EIA observations returned (existing pattern)

Tests

Existing OilPriceService fetch tests are updated (not replaced):

  1. EIA succeedsHttp::fake() returns valid EIA response; assert upsert called with correct rows; assert FRED endpoint never called.
  2. EIA fails, FRED succeeds — EIA returns 500; assert FRED endpoint called; assert upsert called with FRED rows; assert warning logged.
  3. Both fail — both return 500; assert upsert never called; assert error logged.
  4. EIA returns empty dataresponse.data is []; assert falls back to FRED.

Out of Scope

  • No changes to generatePrediction(), PredictOilPrices command, or scheduler
  • No new service classes or interfaces
  • No changes to brent_prices schema