33 Commits

Author SHA1 Message Date
Kunthawat Greethong
2b7a065ae5 fix(ui,health): move source-health log to bottom of page; one row per source (latest)
- 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.
2026-08-31 17:03:07 +07:00
Kunthawat Greethong
053b94b45f fix(ui): eliminate table/grid overflow breaking the frame at every width
Two root causes of tables and panels spilling past the window frame:

1. Global 'table { min-width: 850px }' forced EVERY table (source/score/modal/
   backtest) to 850px regardless of container -> now min-width:0 so tables
   shrink to 100%; only .factor-table (stock board, inside .table-wrap)
   keeps its 850px and scrolls. Small tables get table-layout:fixed +
   overflow-wrap on mobile so long content wraps instead of pushing out.

2. Grid/flex tracks lacked minmax(0,1fr) / min-width:0, so wide grid items
   (KPI numbers, theme cards) pushed the whole row past the viewport at
   768-1040px (doc overflow 208px). Fixed: all grid-template-columns use
   minmax(0,1fr); .content is now flex:1 1 auto + min-width:0; KPI/theme/bt
   cards get min-width:0 + overflow-wrap.

Verified programmatically in-browser: document overflow = 0 at 320/360/500/
600/720/760/768/820/900/1040/1200/1440px; score-table + source-table over=0
at all widths (incl inside the per-source modal). Board scrolls in .table-wrap
as designed. Backend suite 369 green (CSS-only change).
2026-08-30 09:10:21 +07:00
Kunthawat Greethong
3010ab287f fix(ui): show theme weighted-average verification in per-source modal
After reviewer suggestion: per theme, display sum(contribution)/sum(|weight|) =
theme surprise so the owner can confirm the source audit matches the real score.
2026-08-29 12:18:40 +07:00
Kunthawat Greethong
7f175e5a05 feat(scoring): flexible source fallback (no board crash) + per-source calc audit (Q2, Q3)
Q2 flexible scoring: _fetch_with_cache now degrades instead of raising
DashboardError — a source that fails with no cached value returns {} so the
theme scorer drops that source's factors; a previously-good value is kept as
stale by the daily cache. Verified: all-sources-down still builds 13 themes.

Q3 per-source audit: new themes.factor_source_breakdown(fetched, theme) shows
per factor source/raw/normalized/weight/contribution; dashboard exposes
fetch_data + factor_sources; per-symbol modal renders symbolDetail.factor_sources
(e.g. retail: te_thailand ยอดขายปลีก -14.5 -> -1.0 x 0.7 = -0.7).

Suite 369 green; independent review passed: true.
Q1 (HAR for deferred sources) spike recorded: method works, REIC needs deeper
interaction; NBTC 403 likely unbpassable without a session.
2026-08-29 12:15:54 +07:00
Kunthawat Greethong
12b34929d7 feat(factor): add energy_irpc (IRPC net margin) as 2nd Thai refiner signal
- new energy_irpc collector parsing IRPC performance-highlights table
  (net profit/EBITDA/ROE margins, latest period 3M26: +10.27%)
- factor energy_irpc_net_margin (sign +1) wired into refining_energy/
  exploration/utilities, extending the energy theme beyond TOP
- scheduler job + dashboard fetch + sources table row (now 9 sources)
- tests: parse (incl paren-negatives), value-key resolution, direction;
  suite 368 OK. Independent review passed: true
- Phase B feasibility: REIC/EPPO/NBTC/PTTEP are JS-rendered or anti-bot
  (recorded deferred in plan); IRPC was the clean server-rendered win
2026-08-29 11:13:17 +07:00
Kunthawat Greethong
9516a7a8e8 fix(ui): theme-card reads for new sources + padding/undefined-css-var fixes
- define missing design tokens (--card/--foreground/--accent/--font/--text-2)
  that theme/modal components referenced but :root never declared (they rendered
  transparent/wrong color)
- zero section-panel padding so .signal-header is the single top-spacing source
  (was double 22px+22px on theme/lineage/health/sim/backtest panels)
- theme cards now surface the new source reads (banks rate, retail sales YoY,
  nonbank household debt, property prices, telecom business confidence)
2026-08-29 09:19:01 +07:00
Kunthawat Greethong
7a3cfac19a feat(ui): rename Simulation→Suggestion, remove forward-test mode entirely (backend routes/store + frontend panel/option), fix stock-list overflow in allocation display 2026-08-29 01:57:13 +07:00
Kunthawat Greethong
14b2aeff7c feat(scheduler): per-source cadence + source-health log with failure diagnosis + UI copy 2026-08-28 11:41:48 +07:00
Kunthawat Greethong
d73a58b3d1 [verified] Task 7: frontend PIT readiness gate + event-driven backtest report 2026-08-28 10:32:10 +07:00
Kunthawat Greethong
f9b8cd10b2 feat(deploy): single-container Docker packaging (nginx + Flask + prebuilt SPA)
Add self-contained Docker deployment for EasyPanel / docker-compose:

- Dockerfile: python:3.11-slim + nginx. Serves the PREBUILT Vue SPA
  (frontend/dist, committed) via nginx and reverse-proxies /api to the Flask
  backend on 127.0.0.1:5000. No node/npm in the image (avoids Vite/npm
  flakiness in Docker builds). VOLUME /app/backend/data for persistence
  (factor vintages, dividend ledger, forward runs, prices). HEALTHCHECK hits
  /api/v1/dashboard/summary. Exposes :80.
- deploy/nginx.conf: SPA fallback + /api proxy to 127.0.0.1:5000 + gzip.
- deploy/entrypoint.sh: starts Flask (HOST=0.0.0.0:5000) + nginx foreground,
  forwards signals.
- docker-compose.yml: single service 'set50', port 8080:80, mounts
  ./backend/data for persistence, healthcheck.
- .dockerignore excludes .git/.venv/backend/data/node_modules/docs.
- .gitignore now tracks frontend/dist/ (prebuilt bundle required by the
  image); backend/data stays untracked.
- Backend runtime verified: test_client GET /api/v1/dashboard/summary=200
  (the HEALTHCHECK path). entrypoint sh -n, compose YAML parse, and all
  Dockerfile-referenced files exist. NOTE: no local docker here, so the
  image itself was not built — that happens on EasyPanel.
2026-08-27 13:26:58 +07:00
Kunthawat Greethong
f3fad16183 feat(ui): surface dated_ledger vs proxy synthesis in backtest results
Backtest result card and saved-run history now show an explicit dividend
badge per run: green 'ตามวันจริง' for dated_ledger (real ex-date x qty cash
flow), amber 'Proxy (ต่อหุ้น)'/'Proxy' for estimates, plus a footnote that
matches the actual dividend_method. Frontend build passes (bundle
index-DDItjwYy.js); served bundle contains the new strings.
2026-08-27 12:54:44 +07:00
Kunthawat Greethong
80c6d792f4 feat(ui): real forward-test lifecycle panel (not cosmetic)
Forward mode in the simulation tab now calls the durable /api/v1/forward
lifecycle instead of the single-pass /api/v1/simulation (which only differed
by a mode string). runSimulation branches to /api/v1/forward for forward mode,
loads runs on mount, and renders a forward panel listing each paper run with
its status (frozen/executed/marked/matured), non-PIT badge, holdings and a
Mark / Mature action per run. Frontend build passes (bundle index-INuvPOCF.js).
2026-08-27 12:09:01 +07:00
Kunthawat Greethong
5b8b6d12b7 feat(ui): disclose dividend method + honest estimate badge in backtest
Map dividend_method (dated_ledger / dps_annual_proxy / final_holdings_yield
_proxy) to an honest Thai label. Show a Proxy badge whenever the dividend is
an estimate (everything but dated_ledger) on both the result card and the
saved-run history rows, and set the footnote to match the actual method.
Frontend build passes.
2026-08-27 11:47:44 +07:00
Kunthawat Greethong
1b971ac72d [verified] Fix backtest accounting identity + honest UI disclosure
Correct the multi-rebalance backtest accounting so ending wealth is
capital + price_pnl + dividend_income with no double counting:
- price_pnl now measures equity change excluding dividends (was reusing
  ending holdings value as 'price profit')
- dividend proxy is included in final_value and net_return, exposed as
  dividend_method=final_holdings_yield_proxy
- regression tests: flat price => zero price_pnl; flat + dividend =>
  dividend-only return; rising no-dividend => correct bucket P&L;
  multi-rebalance accounting identity
- UI (result card + saved-run history) labels dividends as ประมาณการปันผล
  (Proxy) and shows descriptive non-PIT badge when leakage_guard=false

Backend 239 tests passed; targeted backtest 11 passed; frontend build,
npm audit (0), static scan and diff check passed; fresh independent
review deleg_10918fed passed with empty blocker arrays.
Backtest remains descriptive non-PIT (leakage_guard=false) with the
default current-score scorer.
2026-08-27 09:00:39 +07:00
Kunthawat Greethong
325e164dd3 [verified] Fix P1-P2-P5 audit findings: simulation reuses board, source_summary clarity, dead-code removal + conftest
- P1: /api/v1/simulation now uses the canonical board score (default_scores)
  instead of a divergent 3-theme recompute -> 'จำลอง' can't disagree with board
  (live check: sim top pick PTT == top board combined 1.600). Removes binary
  auto/en signs, restores quality+momentum+dividend screen consistency.
- P2: dashboard emits source_summary{factor_keys, rows}; frontend shows
  'N ปัจจัย · M แหล่ง' so the 7-vs-5 count confusion is impossible.
- P5: removed dead themes.list_themes()/Theme/build_theme_scores/_map_index and
  the tests that locked them; added tests/conftest.py so pytest needs no PYTHONPATH.
- docs: audit-and-plan-2026-08-26.md (full P0-P5 plan) + engineering-log entry.
- 203 backend tests pass; Vite build passes. Independent reviewer: no security or
  logic blockers (minor error-leak suggestion applied: 503 message no longer leaks
  exception detail).
2026-08-27 03:21:22 +07:00
Kunthawat Greethong
fc592d8aa9 [verified] Real backtest engine + backtest UI section (start/end dates, P&L, persisted)
- backtest.py: buy-and-hold backtest over [start,end] — allocates 50/20/30 at first available rebalance date, marks to market to end, accrues dividend, reports {final_value, price_pnl, dividend_income, net_return, trades, holdings}
- Fixed double-spend bug (was allocating full capital every rebalance -> negative cash)
- dashboard.default_scores(): per-symbol combined/dividend/yield baseline for backtest
- POST /api/v1/backtest + GET /api/v1/backtest/runs (results persisted in app state -> survive refresh)
- Frontend: backtest section w/ start/end/capital/freq inputs + P&L KPIs + run history table
- Honest note: uses current combined scores as static baseline (non-PIT); PIT score_fn pluggable
- Verified: 1M -> 1.088M (+8.80%) over 2024-06..2026-06; history persists across refresh
2026-08-26 16:00:17 +07:00
Kunthawat Greethong
2da73b8a8c [verified] Sources table auto-derived from FACTORS registry + next-update column
- _build_sources now iterates FACTORS registry (grouped by fetch module) instead of hardcoded 5-row list -> adding a FACTOR auto-appends its source row (single source of truth, answers 'เพิ่มแหล่งควรอัตโนมัติ')
- Each source row gains ความถี่ + อัปเดตครั้งต่อไป (from frequency cadence)
- Frontend sources table: 6 columns (ข้อมูล/แหล่ง/ช่วงข้อมูล/ความถี่/อัปเดตครั้งต่อไป/อัปเดตล่าสุด), all from /api/v1/dashboard (not mock)
- Verified live: 5 sources auto-derived w/ next-update; browser shows 6 cols
2026-08-26 15:55:30 +07:00
Kunthawat Greethong
d850955c44 [verified] Declarative factor engine + per-symbol stock selection (full-app consistency)
- factors.py: FACTORS registry (10 declarative entries: source/fetch/frequency/sign/weight) + normalize/z-score helpers. Add a source = one dict entry, no scoring-function edit.
- themes.THEMES: 13 themes reference FACTORS with per-theme weights (flexible), replacing hardcoded _theme_surprises/_theme_narrative.
- themes.quality_within_theme(): per-symbol quality vs theme cohort (ROE/EPS) -> real stock picking. dashboard board now surprise×quality (BBL 0.5 vs KTB 1.5 in banks).
- board rows carry per-symbol themes[]; /api/v1/themes delegates to RealDashboard.build() -> 13-theme consistency with /api/v1/dashboard (removed 115 lines dead dup logic).
- frontend: deleted THEME_BY_SYMBOL/themeLabelById hardcode; theme column + modal labels+quality all from API. Modal shows surprise×quality=theme_score.
- Tests: 202 OK (quality selection, breakdown quality, themes/dashboard consistency).
- Verified: BBL modal 1.00σ×0.5=0.50σ; KTB 1.5 vs BBL 0.5, PTT 2 themes; 49/49 rows theme from API.
2026-08-26 15:17:41 +07:00
Kunthawat Greethong
55b3574040 [verified] Cover full SET50 with 13 themes + per-theme score detail in symbol view
- THEME_SYMBOLS expanded: added banks, retail, telecom_it, property, healthcare, petrochem_materials, consumer_staples, utilities, nonbank_finance, exploration -> all 49 SET50 names now in a theme
- THEME_LABELS_TH Thai labels; THEME_FREQUENCY per theme
- symbol_breakdown now lists EVERY theme the symbol belongs to (label_th + surprise, or 'ยังไม่มีข้อมูล'), so theme_score is transparent per source
- frontend: theme column maps all 49 symbols (mirrors backend); modal shows per-theme score detail
- Fixed test for BANPU multi-theme; full suite 199 OK
- Verified: 49/49 rows have theme chip; AOT modal shows ท่องเที่ยว 0.57σ + full calc
2026-08-26 14:05:10 +07:00
Kunthawat Greethong
6c26bf99dc [verified] Fix dashboard: real-data badge + sortable คะแนนรวม + modal calc steps
- badge now driven by /api/v1/dashboard availability (ข้อมูลจริงจากแหล่งไทย), not stale fixture summary (#request)
- factorValue handles 'combined' -> คะแนนรวม column now sorts + toggles asc/desc
- symbol modal shows full calculation: combined_formula, per-step calc (ธีม/พื้นฐาน + weights), z-score derivation with population mean/stdev/universe size
- panel-header layout fixes (full-width subtitle, header breathing room)
- Verified: badge real, sort 0.11->1.43, modal calc steps render
2026-08-26 06:22:52 +07:00
Kunthawat Greethong
225ef2cb9c [verified] Dashboard polish: per-symbol detail modal + theme column + uniform kickers — 7-point review
- F1/F2: table full-width; 'รวม' -> 'คะแนนรวม (60/40)' sortable w/ tooltip
- F3: removed 05/บันทึกการวิเคราะห์ (redundant w/ theme panel)
- F4: uniform section-kickers (stripped 07/ 05/)
- F5: theme cards show long-form Thai narrative (auto: ยอดขายรถ+NPL tศัพท; energy/tourism)
- F6: stock table has ธีม column (chips per symbol's themes)
- F7: click row -> per-symbol modal showing themes + fundamentals + transparent combined derivation (theme_score×0.6 + siamchart×0.4)
- Verified: browser modal for AOT shows 0.57×0.6 + -0.59×0.4 = 0.11
2026-08-25 21:24:28 +07:00
Kunthawat Greethong
4b6ce2c09e [verified] Remove tourism-only sections (01 surprise pulse, 02 provenance, 03 signal table, 04 research run); thesis across 3 themes; sources show fetch date
- Removed 'เชื่อถือข้อมูลได้ไหม' (02), 'ตารางสัญญาณ' dup (03), 'หลักฐานพอจะรองรับ study' (04), tourism surprise pulse — superseded by 3-theme panel + sources table
- 05 บันทึกการวิเคราะห์ now lists thesis per theme (tourism/auto/energy) with surprise
- Sources table shows fetched-at date (ข้อ 2)
- All dashboard analysis is real (no mock); automation note: no cron yet (data fetched on-demand + daily cache)
- Verified: build + browser 8 checks pass
2026-08-25 20:53:24 +07:00
Kunthawat Greethong
0f706e285e [verified] Thai-only cleanup: freq labels (รายเดือน/รายไตรมาส), section titles Thai 2026-08-25 20:41:15 +07:00
Kunthawat Greethong
489920b3c9 [verified] Rebuild dashboard: real multi-theme (3 themes + macro + sources table) — user 10-point review
- Remove obsolete KPIs (Theme surprise card, Paper ledger, คุณภาพข้อมูล, ประตู backtest) -> now 2 cards: สัญญาณ (long/short/neutral) + แหล่งข้อมูล
- Theme panel: 3 themes with uniform surprise (0.57/0.81/1.62σ) + per-theme thesis (#8,#10)
- Add macro backdrop panel (การบริโภค/การลงทุน/เงินเฟ้อ/การว่างงาน/นักท่องเที่ยว) from BOT Thai Economy (#6)
- Add sources lineage table (ที่มาข้อมูล: ข้อมูล/แหล่ง/ช่วง/อัปเดต) (#9)
- Merge signal+fundamental into one table with combined column (#7)
- Live verified via /api/v1/dashboard (real data, 49-symbol board)
2026-08-25 20:39:43 +07:00
Kunthawat Greethong
e24023cf42 [verified] Add Thai Simulation tab (capital allocation 50/20/30); remove legacy paper-ledger section
- Simulation panel (07): capital input (บาท), mode (backtest/forward), calls /api/v1/simulation, renders 3 buckets with orders (symbol/qty/price/notional), invested + unallocated cash, honest non-PIT label
- Removed legacy 'Paper ledger' section (superseded by simulation/forward-test per user)
- Sidebar thumb จำลอง; responsive 1-col mobile
- Verified: npm build + browser (3 bucket cards render, ลงทุนรวม/เงินสดเหลือ show)
2026-08-25 16:28:12 +07:00
Kunthawat Greethong
2caf814e21 [verified] Localize dashboard to Thai (navbar, KPI, section titles, headers); fix fixture badge honesty
- Sidebar nav all Thai (ภาพรวม/ธีม/ตารางหุ้น/สัญญาณ/ปัจจัย/ที่มาข้อมูล/งานวิจัย)
- KPI labels Thai (สัญญาณที่ใช้งาน/คุณภาพข้อมูล/ประตู backtest/สมุดบันทึก paper)
- Header H1 -> SET50 Signal Lab (multi-theme, not tourism-only); Thai subtitle
- Section titles -> Thai (สิ่งที่เปลี่ยนไป/ที่มาของข้อมูล/ผลลัพธ์เชิงกำหนด/Research run/บันทึกการวิเคราะห์)
- FIX: badge now shows ข้อมูลจำลอง (fixture) consistent with data-quality card (was wrongly 'ข้อมูลจริง')
- Stock board header -> ตารางหุ้น (pure Thai); technical terms (theme surprise, paper, backtest gate) kept EN per user
- Verified: npm build + browser full-page; all Thai labels render
2026-08-25 16:21:17 +07:00
Kunthawat Greethong
90e63e3384 [verified] Add Thai multi-theme panel to dashboard (3 themes + combined summary)
- Theme panel (ธีม): shows tourism/auto_credit/refining_energy with Thai labels, frequency chips (monthly/quarterly), and real factor reads (tourism surprise, car sales YoY, TOP net profit) from /api/v1/themes
- Combined count badge; responsive 1-col on mobile
- Loads /api/v1/themes in loadDashboard (already wired backend)
- Verified: npm build passes, browser shows 3 Thai cards with live values
2026-08-25 15:59:02 +07:00
Kunthawat Greethong
8a6991b7dd [verified] Add Siamchart factor view + redesigned SET50 dashboard stock board
Backend:
- siamchart_factors.py: build per-symbol factor view from Siamchart snapshot (PE, EPS latest, EPS growth YoY derived from series, dividend yield, P/BV, ROE, is_dividend). eps_latest now returns the most recent year.
- /api/v1/factors endpoint: merge Siamchart fundamentals with the tourism signal (side/score), signal-led sorting.
- test_siamchart_factors.py: 5 tests incl. regression asserting eps == year5 value.

Frontend:
- App.vue/style.css: new 'Stock board' dashboard table (Signal, Symbol, P/E, EPS, EPS YoY, Yield%, P/BV, ROE) with a Dividend-only filter and click-to-sort columns.

Verified: full backend suite 140 tests OK, frontend build OK, static scan clean, live /api/v1/factors 200 (49 factors/46 dividends), rendered table filter+sort verified in browser. Independent review deleg_969513e5 caught+fixed eps bug; re-review deleg_e6bd80db passed=true.
2026-08-25 09:03:28 +07:00
Kunthawat Greethong
f55ff69c31 [verified] add frozen research runner and durable paper ledger 2026-08-23 14:42:02 +07:00
Kunthawat Greethong
7f7a6145cd [verified] add SET price snapshot adapter 2026-08-23 12:38:17 +07:00
Kunthawat Greethong
0b47a06238 [verified] add event-study readiness gate 2026-08-23 12:02:22 +07:00
Kunthawat Greethong
a113a51589 [verified] add BOT tourism source adapter 2026-08-23 10:19:40 +07:00
Kunthawat Greethong
3e978c5948 [verified] build tourism signal dashboard 2026-08-23 07:41:31 +07:00