# 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:
```php
'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 succeeds** — `Http::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 data** — `response.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
