- Health panel relocated below Backtest (last section) per owner — it's
operational detail, not an investment view.
- scheduler._load_source_health now returns only the LATEST entry per source
key (was returning the full refresh history, so siamchart_vintages and
price_snapshot appeared as 2 rows). Verified: 11 sources / 11 unique rows.
- frontend v-for drops slice(0,20) and keys by source key.
Verified in-browser: #health is the last section; 11 unique source rows.
Automatically keep the real dated dividend ledger fresh inside the app's own
refresh loop (this app runs on its own server, independent of Hermes):
- backend/app/scheduler.py: AppDataScheduler gained a cooldown-gated
_maybe_refresh_dated_dividends() that fetches real dated dividend history
(siamchart /stock-info) into data/dividends/ledger.json at most once per
dividend_cooldown_seconds (default 6h) — dividend history changes only a
few times a year, so we never hammer the source every refresh tick. The
fetch is non-fatal: a network failure leaves the previous ledger intact.
- backend/app/__init__.py: passes DIVIDEND_REFRESH_COOLDOWN_SECONDS to the
scheduler (default 21600s).
- tests: cooldown fires once then skips, and refetches after it elapses (2)
— full backend 294 passed.
A. Cross-theme comparability:
- compute_theme_surprises now weight-normalizes by total |weight| (weighted
average), so every theme surprise on same [-1,1] scale regardless of factor
count/weight (retail 0.189->0.145; auto_credit 1.0->0.64).
B. Historical factor store (enables learning macro/demographic factors):
- New factor_history.py: append-only per-factor JSONL, dedupes unchanged
values, rejects non-finite, records every FACTORS value each scheduler run.
- scheduler.py: jobs carry fetch_module; refresh_all records factor history
(non-fatal); added bank_npl job.
- GET /api/v1/learning/factors?min_points= reports n_points/learnable per
factor so users see when P4 learning unlocks (validated query parsing).
- weight_learning: generic learn_factor_series() aggregator (momentum reuses).
Independent review deleg_5dd358e3 passed=true (empty security/logic arrays);
its two robustness suggestions applied (finite guard in record(), clean 400 on
bad min_points). 234 tests pass; Vite build passes.
- scheduler.py: daemon thread inside Flask refreshes all real Thai collectors on interval (default 60min, REFRESH_INTERVAL_SECONDS) via shared daily cache + writes timestamped marker
- create_app starts scheduler (skipped in TESTING); shared daily_cache now an extension
- GET /api/v1/data/last-refresh: automation status + last refresh (every N hours)
- Live verified: refresh_all pulls 5/5 real sources (tourism/auto/NPL/energy/macro)
- 2 tests; full suite 197 OK