feat: add Filament admin panel with migrations and design spec
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

- Add AdminPanelProvider mounting panel at `/admin` with `is_admin` auth guard
- Add `is_admin` boolean column to users table
- Add brent_prices and price_predictions tables with appropriate indexes
- Add comprehensive admin design spec covering resources, dashboard, navigation, and build order
- Configure default panel with amber primary color and standard middleware stack
- Add compiled Filament assets (actions.js, app.css)
This commit is contained in:
Ovidiu U
2026-04-04 13:40:56 +01:00
parent e532cc1208
commit d5fb7f85bd
59 changed files with 3422 additions and 28 deletions

View File

@@ -91,6 +91,31 @@ INDEX (user_id, expires_at)
```
OTP codes expire after 10 minutes. Mark `used_at` on success — never delete rows.
### brent_prices (daily Brent crude from FRED)
```
date DATE PRIMARY KEY — trading day (no weekends/holidays)
price_usd DECIMAL(8,2) — spot price USD per barrel
```
Populated daily by `OilPriceService::fetchBrentPrices()` via FRED API.
FRED returns `"."` for non-trading days — these are filtered out before insert.
Upserted on refetch so duplicate dates never occur.
### price_predictions (oil price direction forecast)
```
id BIGINT UNSIGNED AUTO_INCREMENT
predicted_for DATE — the date this prediction covers
source ENUM('llm','ewma') — which method generated it
direction ENUM('rising','falling','flat')
confidence TINYINT — 0100 (LLM max 85, EWMA max 65)
reasoning TEXT NULLABLE — plain-English explanation
generated_at DATETIME
INDEX (predicted_for, source)
```
Generated daily by `OilPriceService::generatePrediction()`.
LLM (Anthropic) is tried first; EWMA is used as fallback if LLM fails or key not set.
Signal 4 in AlertScoringService reads from this table — never from brent_prices directly.
## Supermarket brands (StationTaggingService)
Match station `name` (case-insensitive) against: