dashboard: clickable news — detail dialogs + on-demand AI summary endpoint
- news_engine: RSS parser captures link/description/publisher (three new optional NewsSentiment fields); _clean_html_text/_safe_link helpers - news.json: headlines carry id = sha1(headline) + enrichment fields - static/news_summary.php (NEW): on-demand DeepSeek gold-impact summary, whitelist-gated against news.json ids, cache-forever in ai/, fail-closed file-based daily cap (150/day), key at /etc/gold-dashboard/deepseek.key; installed to the docroot by install_static - dashboard.html: newswire + calendar detail dialogs (score tiles, source description, article link, AI summary with loading/error states); delegated on stable containers, dialog keyed by id across polls - tests: +6 (357 total); docs: README-dashboard §6 one-time setup (key file, apache-owned ai/, SELinux httpd_can_network_connect), CLAUDE.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Aq1jZ6jBsBdEC2xT3u46SA
This commit is contained in:
+66
-10
@@ -2,10 +2,12 @@
|
||||
|
||||
The dashboard is a **static publisher**: `dashboard_publisher.py` runs as its
|
||||
own daemon, scans XAU/USD at candle closes, and atomically writes
|
||||
`dashboard.html` + `snapshot.json` + `news.json` into a folder Apache already
|
||||
serves. Apache does all web serving — no new port, no Python web server, and
|
||||
the telegram bot process is completely unaffected by anything that happens
|
||||
here (see the module docstring for the independence rules).
|
||||
`dashboard.html` + `news_summary.php` + `snapshot.json` + `news.json` into a
|
||||
folder Apache already serves. Apache does all web serving — no new port, no
|
||||
Python web server, and the telegram bot process is completely unaffected by
|
||||
anything that happens here (see the module docstring for the independence
|
||||
rules). The one dynamic piece is `news_summary.php` (§6): an on-demand
|
||||
DeepSeek summary for a clicked headline, whitelist-gated against news.json.
|
||||
|
||||
All steps below run **on the live server** (`/home/Forex/ForexBot2`).
|
||||
|
||||
@@ -22,9 +24,10 @@ No new Python dependencies are required.
|
||||
## 2. Make the web folder writable
|
||||
|
||||
The dashboard publishes straight into the portal docroot `/var/www/trade/`
|
||||
(owner decision 2026-08-03) — it adds exactly three files there:
|
||||
`dashboard.html`, `snapshot.json`, `news.json`. The daemon user must be able
|
||||
to write them:
|
||||
(owner decision 2026-08-03) — it adds exactly four files there:
|
||||
`dashboard.html`, `news_summary.php`, `snapshot.json`, `news.json` (plus the
|
||||
apache-owned `ai/` cache dir from §6). The daemon user must be able to write
|
||||
them:
|
||||
|
||||
```bash
|
||||
# if the portal files can be owned by the daemon user:
|
||||
@@ -72,8 +75,9 @@ every 3 h (240 backups), same scheme as the bot.
|
||||
|
||||
## 5. Optional Apache polish
|
||||
|
||||
The page cache-busts its JSON fetches (`?t=<epoch>`), so no Apache config is
|
||||
strictly needed. To be explicit anyway:
|
||||
The page polls its JSON with conditional GETs (`cache: "no-cache"` → Apache
|
||||
answers unchanged files with 304), so no Apache config is strictly needed. To
|
||||
be explicit anyway:
|
||||
|
||||
```apache
|
||||
<Directory "/var/www/trade">
|
||||
@@ -87,15 +91,67 @@ The page is deliberately public (owner decision 2026-08-03). To restrict it
|
||||
later, add standard Apache basic auth (`htpasswd` + `Require valid-user`) on
|
||||
that directory — no code changes needed.
|
||||
|
||||
## 6. AI summary endpoint (PHP) — one-time setup
|
||||
|
||||
Clicking a newswire headline opens a detail dialog that fetches an on-demand
|
||||
DeepSeek gold-impact summary from `news_summary.php` (installed next to the
|
||||
page by the daemon). Owner decision 2026-08-07: a real on-demand endpoint on
|
||||
the public page, accepted with these guards baked into the PHP — the id must
|
||||
match a headline in the published news.json (arbitrary text can never reach
|
||||
the LLM), each headline's summary is cached forever in `ai/` (≤1 API call per
|
||||
headline, ever), and a file-based daily cap fails **closed** on I/O errors.
|
||||
|
||||
Requirements: `php` + the curl extension (`php -m | grep curl`).
|
||||
|
||||
```bash
|
||||
# 1. SELinux: allow httpd outbound HTTPS — WITHOUT this every summary call
|
||||
# dies as a curl timeout even with perfect perms (RHEL-family default).
|
||||
sudo setsebool -P httpd_can_network_connect 1
|
||||
|
||||
# 2. API key OUTSIDE the docroot, readable by Apache only:
|
||||
sudo install -d -o root -g root -m 755 /etc/gold-dashboard
|
||||
sudo sh -c 'umask 137; echo "sk-..." > /etc/gold-dashboard/deepseek.key'
|
||||
sudo chown root:apache /etc/gold-dashboard/deepseek.key # mode 640
|
||||
|
||||
# 3. apache-owned cache dir (the daemon runs as Forex and can't create it):
|
||||
sudo install -d -o apache -g apache -m 755 /var/www/trade/ai
|
||||
sudo semanage fcontext -a -t httpd_sys_rw_content_t '/var/www/trade/ai(/.*)?'
|
||||
sudo restorecon -Rv /var/www/trade/ai
|
||||
```
|
||||
|
||||
Knobs (Apache `SetEnv` in the vhost, both optional):
|
||||
`AI_SUMMARY_DAILY_CAP` (default 150), `DEEPSEEK_KEY_FILE` (default
|
||||
`/etc/gold-dashboard/deepseek.key`).
|
||||
|
||||
Verify from the shell — expect 400 / 404 / 200 / 200-from-cache:
|
||||
|
||||
```bash
|
||||
curl -s "http://<server>/news_summary.php?id=zzz" # {"error":"bad_id"}
|
||||
curl -s "http://<server>/news_summary.php?id=$(printf 'a%.0s' {1..40})" # unknown_id
|
||||
ID=$(python3 -c "import json;print(json.load(open('/var/www/trade/news.json'))['headlines'][0]['id'])")
|
||||
curl -s "http://<server>/news_summary.php?id=$ID" # real summary (2-12 s)
|
||||
curl -s "http://<server>/news_summary.php?id=$ID" # instant, from ai/<id>.json
|
||||
```
|
||||
|
||||
## Operational notes
|
||||
|
||||
- **Updating**: `git pull` + `systemctl restart gold-dashboard`. The daemon
|
||||
re-copies `static/dashboard.html` into the web folder at startup.
|
||||
re-copies `static/dashboard.html` and `static/news_summary.php` into the
|
||||
web folder at startup.
|
||||
- **If the daemon dies**, systemd restarts it in 30 s; worst case the page
|
||||
shows stale data with its own "updated N min ago" evidence. Apache and the
|
||||
telegram bot are unaffected.
|
||||
- **DeepSeek**: the publisher process has its own daily request cap
|
||||
(`deepseek.daily_request_cap`, default 500) separate from the bot's —
|
||||
expect roughly up to ~430 extra calls/day worst case, V4-Flash pricing.
|
||||
- **Two independent DeepSeek meters** hit the same API key: the publisher's
|
||||
in-process cap above, and the PHP endpoint's file cap (`ai/.ht_cap.json`,
|
||||
default 150/day). They don't communicate — worst-case spend is their sum;
|
||||
DeepSeek-side spend alerting is the true backstop. Kept separate on
|
||||
purpose (independence rule: the PHP never imports/reads bot code or
|
||||
config.json).
|
||||
- **Restart window**: for the first minutes after a daemon restart the new
|
||||
page can meet a pre-upgrade news.json without headline ids — those rows
|
||||
simply render non-clickable until the first news publish.
|
||||
- **Credentials**: uses the same env/.env/config.json resolution as the rest
|
||||
of the platform (Oanda candles feed; news needs no key; DeepSeek optional).
|
||||
|
||||
Reference in New Issue
Block a user