feat: add Citations and WatchlistChat components, integrate with API

- Implemented Citations component to display citation data.
- Created WatchlistDrawer and ChatSidebar components for managing watchlists and AI chat functionality.
- Integrated API calls for watchlist management and chat interactions.
- Updated index.tsx to include new components in the main application layout.
- Added API client in lib/api.ts for structured API interactions.
- Developed Alerts, Dashboard, Portfolio, Routines, Screener, and Report pages with relevant data fetching and UI components.
- Introduced styles in tokens.css for consistent theming across the application.
- Configured TypeScript and Vite for project setup and development.
This commit is contained in:
asepharyana
2026-09-15 12:36:48 +07:00
parent 0db2cf28b3
commit 8c184ccae1
111 changed files with 12349 additions and 62 deletions
+3 -1
View File
@@ -19,7 +19,7 @@ Agents never fetch live; they read snapshots. Fixtures in `tests/fixtures/` prov
broker (accumulating/distributing/neutral from top/ ranks); (3) sector exposure
shift week-over-week; (4) emit rotation signal on sign flip with evidence rows.
- Output: behavior map, rotation signal (from→to + net Rp delta).
- Fixture: Financials net −Rp 800M → Consumer +Rp 1.1T flip detected.
- Fixture: Financials net −Rp 800M → Consumer +Rp 1.1B flip detected (sector_flow_idx.json).
## A3 — News Sentiment (Adaptive RAG)
@@ -29,6 +29,8 @@ Agents never fetch live; they read snapshots. Fixtures in `tests/fixtures/` prov
(3) per-article sentiment + confidence; (4) aggregate trend
improving/deteriorating/stable; (5) insider summary from filings.
- Output: score −1..+1, trend, key events (≤5, cited), insider line.
- Offline path: when no news/filings/suspensions snapshots exist, the agent falls
back to the derived `news_items` table (`NewsSince`) instead of failing.
- Fixture: ticker with 2 bullish + 1 neutral + 1 director buy → positive trend cited.
## A4 — Fundamental
+16 -3
View File
@@ -29,10 +29,22 @@ Errors: `{error: {code, message}}` with HTTP 400/404/422/502 (502 = upstream Sec
- `GET /api/alerts`, `POST /api/alerts` body `{name, rule, channels[]}`,
`DELETE /api/alerts/:id`, `GET /api/alert-events?since=&ticker=`.
## Destinations (push targets per user)
- `GET /api/destinations` → `[{id, kind, label, enabled, configured}]`. Secrets are never returned.
- `POST /api/destinations` body `{kind: telegram|discord, label?, bot_token?, chat_id?, webhook_url?}` → created (422 on wrong kind or missing secret).
- `PATCH /api/destinations/:id` body `{label?, enabled?, bot_token?, chat_id?, webhook_url?}` → patched (kind immutable; omitted secret = keep).
- `DELETE /api/destinations/:id` → removed.
Delivery: routines and alerts push to the owner's **enabled** destinations. When the owner has none, the server-level env webhook (`TELEGRAM_*` / `DISCORD_*`) is used as fallback; when neither exists delivery is record-only.
## Report
- `POST /api/report/:ticker` query `?format=json|html|pdf|md` → 7-section payload
with `citations[]` per section. PDF rendered server-side.
- `POST /api/report/:ticker` query `?format=json|html|pdf|md&profile=moderate|conservative|aggressive`
→ 7-section payload with `citations[]` per section. PDF rendered server-side.
- `POST /api/report/:ticker/ask` body `{question, report_id?}` → follow-up answer
grounded ONLY in that report's citations (explicit id or latest for the ticker;
cross-ticker ids are rejected).
## Watchlist / portfolio / accuracy / chat / health
@@ -41,4 +53,5 @@ Errors: `{error: {code, message}}` with HTTP 400/404/422/502 (502 = upstream Sec
- `GET /api/accuracy` → per-agent `{calls, resolved, hits, hit_rate}`.
- `POST /api/chat` body `{message, scope?: {report_id}}` → cited answer (report scope
restricts grounding to that report's citations).
- `GET /api/health` → `{last_cycle_at, credits_today, scheduler_ok, stale_flags}`.
- `GET /api/health` (`?force=1` runs one synchronous probe cycle) →
`{last_cycle_at, credits_today, scheduler_ok, stale_flags}`.
+10 -11
View File
@@ -26,29 +26,28 @@ flowsight/
smart_money.go broker_intel.go sentiment.go fundamental.go
technical.go catalyst.go synthesizer.go
routines/ # 7 routines (see ROUTINES.md)
engine.go # cron dispatch, run recording, delivery
briefing.go radar.go reversal.go insider.go earnings.go dividend.go weekly.go
engine.go # cron dispatch, run recording, delivery, SSE publish
briefing.go # R1 briefing + R2 radar + R3 reversal + R4 insider + R5/R6 countdowns + R7 review
alerts/
rules.go # 6 detection rules over snapshots
evaluate.go # per-cycle evaluation
notify.go # Telegram/Discord webhooks
reports/
builder.go # 7-section assembly + citations[]
report.go # 7-section assembly + citations[] (returns persisted id)
render.go # PDF/HTML/MD/JSON exporters
api/ # chi route handlers (see API.md)
flow.go screen.go routines.go briefing.go alerts.go
report.go watchlist.go portfolio.go accuracy.go chat.go health.go stream.go
flow.go screen.go routines.go health.go alerts.go
report.go interrogate.go watchlist.go portfolio.go chat.go stream.go
scheduler/ # robfig/cron wiring (ingestion + routines)
web/ # SolidJS 1.9 + Vite 6 + StyleX
web/ # SolidJS 1.9 + Vite 6 + typed CSS
src/
pages/ # Dashboard, Routines, Alerts, Screener, Portfolio, Report
components/ # cards, tables, rotation map, correlation matrix
lib/api.ts # typed backend client + SSE hooks
styles/ # StyleX tokens + themes
tests/
fixtures/ # historical snapshots (one trading week)
agents_*_test.go # per-agent fixture tests
rules_test.go report_test.go budget_test.go
styles/ # CSS tokens + themes (tokens.css)
backend/tests/
fixtures/ # historical snapshots (one trading week, manifest.json)
seed + agents + rules + evaluate + api + routines + client + accuracy tests
```
## Conventions
+2 -1
View File
@@ -2,7 +2,7 @@
SQLite for the hackathon; schema kept Postgres-compatible (serial → integer PK,
JSON → TEXT with JSON1, no SQLite-only DDL). Migrations numbered in
`backend/app/store/migrations/`.
`backend/internal/store/migrations/`.
## Tables
@@ -17,6 +17,7 @@ JSON → TEXT with JSON1, no SQLite-only DDL). Migrations numbered in
- `routines(id, user_key, type, schedule_cron, channels_json, enabled)` — 7 types (R1–R7).
- `routine_runs(id, routine_id, started_at, status, payload_json, credits_used)`.
- `alerts(id, user_key, name, rule_json, channels_json, last_fired)`.
- `notification_destinations(id, user_key, kind, label, bot_token, chat_id, webhook_url, enabled, created_at)` — per-user push targets. `kind` telegram needs bot_token+chat_id, discord needs webhook_url (https). Secrets never leave the server in API responses.
- `alert_events(id, alert_id, ticker, date, message, context_json, citations_json)`.
- `watchlists(user_key, ticker, added_at)` — PK (user_key, ticker).
- `reports(id, ticker, generated_at, payload_json, citations_json)`.
+33
View File
@@ -0,0 +1,33 @@
# FlowSight — Demo Deck (offline, seeded)
Narrative: briefing → radar alert → report → interrogation. All numbers cite
snapshots; stale chips render marked, never hidden.
## Slide 1 — Problem
6M+ retail SID decide from price + rumor. Institutional data (broker flow,
foreign flow, insider filings) is raw across 70 Sectors endpoints.
## Slide 2 — Autopilot routines
Subscribe once; agents run on schedule; results land without opening the app.
R1 Morning Briefing 07:30 · R2 Radar 30-min · R3 Reversal · R4 Insider Tape ·
R5 Earnings H-7/H-3/H-1 · R6 Dividend H-14/H-3 · R7 Weekend Review.
## Slide 3 — Live: Morning Briefing
`GET /api/briefing/today` — top-5 accumulation, foreign table, 7d agenda,
mover of the day. ≤25 lines, every row cited.
## Slide 4 — Live: Accumulation Radar
BBCA: 3 brokers net-buy Rp1.2T, volume 3.2× 20d avg → alert card with score,
top-3 brokers, volume multiple, `/report/BBCA` link.
## Slide 5 — Live: One-Click Report
`POST /api/report/BBCA` — 7 sections with `citations[]` per section; export
PDF/HTML/MD/JSON. Thesis claims carry inline `[endpoint @ date]` markers.
## Slide 6 — Verifiable AI + Accuracy Ledger
Every number cites endpoint + snapshot timestamp. Recommendations resolve at
+30d (`ResolveDue` daily cron); agent weights follow hit-rate, shown in
`/portfolio` accuracy table.
## Slide 7 — Ask
Run `BASE=http://localhost:8080 sh scripts/demo.sh` offline from seed.
+15 -14
View File
@@ -61,7 +61,7 @@ modelnya sendiri.
## 5. Agent System
7 specialist agents, dieksekusi paralel via asyncio, diorkestrasi scheduler + on-demand.
7 specialist agents, dieksekusi paralel via goroutine, diorkestrasi scheduler + on-demand.
| Agent | Input (Sectors API) | Output |
|---|---|---|
@@ -98,16 +98,17 @@ modelnya sendiri.
6. **Portfolio Risk** — konsentrasi sektor, matriks korelasi, beta vs IHSG.
7. **AI Chat sidebar** — context-aware dari watchlist.
## 7. Architecture (detail: docs/ARCHITECTURE.md; stack: docs/TECH-STACK.md)
- Frontend: Next.js 15 + React 19 + TypeScript + Tailwind v4 + Recharts (SSE streaming, responsive)
- Backend: Python 3.12 + FastAPI + Uvicorn + httpx (async) + Pydantic v2 (eksekusi agent paralel)
- LLM: OpenAI SDK v1 provider-agnostic (`LLM_BASE_URL`), gpt-4o-mini triage + gpt-4o synthesis
## 7. Architecture (authoritative: docs/ARCHITECTURE.md; stack: docs/TECH-STACK.md)
- Frontend: SolidJS 1.9 + Vite 6 + TypeScript + typed CSS + Chart.js (SSE streaming, responsive)
- Backend: Go 1.23 + chi v5 + database/sql (modernc.org/sqlite, pure Go) + goroutine
(eksekusi agent paralel)
- LLM: Plain HTTPS ke endpoint OpenAI-compatible (`LLM_BASE_URL`); `LLM_MODEL_TRIAGE` murah + `LLM_MODEL_SYNTH` kuat
- Data: Sectors API v2 `https://api.sectors.app/v2/`, auth `Authorization: <key>`
dari env `SECTORS_API_KEY`
- Store: SQLite (stdlib, skema Postgres-compatible) + Redis 7 cache (degradasi in-memory jika kosong)
- Scheduler: APScheduler AsyncIO, ingestion tiap 30 min saat market hours + routine harian/mingguan
- Scheduler: robfig/cron v3 in-process, ingestion tiap 30 min saat market hours + routine harian/mingguan
- Notify: outbound webhook → Telegram / Discord
- PDF: ReportLab (tanpa system deps); test: pytest + respx + fakeredis; gate: ruff + mypy + tsc + next build
- PDF: gofpdf (pure Go, tanpa system deps); test: `go test` + `httptest` (mock upstream Sectors); gate: `gofmt` + `go vet` + `go test` + `tsc` + `vite build`
## 8. Data model
- `snapshots(ticker, date, source, payload)` — raw ingestion
@@ -173,13 +174,13 @@ Subsector: subsector/report (6 sections). Phase 2: SGX (9), KLSE (4), mining (19
| 44–48 | Polish, demo script, deck |
## 14. Verification
- `/api/health` last cycle < 35 min saat market hours
- Routine briefing generate dari snapshot tanpa empty section + citations lengkap
- Fixture akumulasi → alert event + webhook terkirim ke kanal uji
- Screener balikin ranked list + breakdown per row
- Report BBCA < 15s, 7 section terisi dari live API + citations
- Key hanya dari env, v2 paths only
- Market tutup → demo pakai historical replay seed
- `GET /api/health` balik last cycle + credits today + scheduler state (verifikasi dalam smoke test)
- Fixture briefing generate dari seed tanpa empty section + citations lengkap (unit test)
- Fixture akumulasi → alert event tercatat (rules_test) + webhook ke kanal uji bila token di-env
- Screener balikin ranked list + breakdown per row (api_test)
- Report BBCA 7 section terisi dari seed/live + citations, export PDF/HTML/MD/JSON (smoke)
- Key hanya dari env (`SECTORS_API_KEY`), v2 paths only (client_test: v1 ditolak sebelum HTTP)
- Market tutup → demo offline pakai historical replay seed (scripts/demo.sh)
## 15. Risks
- Butuh Insider API key sebelum jam 0
+1
View File
@@ -12,3 +12,4 @@ Spec-driven source of truth. Code follows these docs; docs change before code.
| [AGENT-SPECS.md](AGENT-SPECS.md) | 7 agent contracts: inputs, processing steps, outputs, verification fixtures |
| [DATA-MODEL.md](DATA-MODEL.md) | Table schemas, indexes, retention, seed strategy |
| [API.md](API.md) | Backend route specs: request/response shapes, errors, auth |
| [DEMO-DECK.md](DEMO-DECK.md) | Offline demo narrative: briefing → radar → report → interrogation |
+11 -11
View File
@@ -7,25 +7,25 @@ Pinned versions. Change here before code. CI enforces the gates at the bottom.
| Piece | Choice | Why |
|---|---|---|
| Runtime | Go 1.23 | Single binary, fast cold start on demo machines, `net/http` routing mature since 1.22 |
| Router | chi v5 | Thin router over stdlib mux (middleware, route groups); no framework lock-in |
| Router | chi v5.2.3 | Thin router over stdlib mux (middleware, route groups); no framework lock-in |
| HTTP client | stdlib `net/http` + tuned `Transport` | One shared client for all Sectors calls (pooling, per-endpoint timeouts) |
| Validation | go-playground/validator v10 | Request struct tags = kontrak docs/API.md; gagal validasi → 422 |
| DB | `database/sql` + modernc.org/sqlite (pure Go) | Nol CGO — `mattn/go-sqlite3` butuh gcc dan gagal di mesin juri tanpa toolchain; schema Postgres-compatible, migrasi SQL polos bernomor, tanpa ORM |
| Cache | go-redis v9; in-memory TTL fallback bila `REDIS_URL` kosong | Cache registry/taxonomy (TTL 24 jam); demo tetap jalan tanpa Redis |
| Scheduler | robfig/cron v3 | Cron per routine + interval ingestion dalam satu proses |
| Validation | go-playground/validator v10.25.0 | Request struct tags = kontrak docs/API.md; gagal validasi → 422 |
| DB | `database/sql` + modernc.org/sqlite v1.39.0 (pure Go) | Nol CGO — `mattn/go-sqlite3` butuh gcc dan gagal di mesin juri tanpa toolchain; schema Postgres-compatible, migrasi SQL polos bernomor, tanpa ORM |
| Cache | go-redis v9.12.1; in-memory TTL fallback bila `REDIS_URL` kosong | Cache registry/taxonomy (TTL 24 jam); demo tetap jalan tanpa Redis |
| Scheduler | robfig/cron v3.0.1 | Cron per routine + interval ingestion dalam satu proses |
| LLM | Plain HTTPS ke endpoint OpenAI-compatible (`LLM_BASE_URL`) | Function calling untuk synthesis/report/briefing; `LLM_MODEL_TRIAGE` murah + `LLM_MODEL_SYNTH` kuat, override lewat env |
| PDF export | gofpdf (jung-kurt fork) | Pure Go, tanpa system deps |
| Config | env via `os.Getenv` + `godotenv` untuk dev | Semua secret dari env; contoh di `.env.example` |
| Test | `go test` + `httptest` (mock upstream Sectors) | Agent/rule/budget tests atas fixture JSON bentuk API asli |
| Lint/type | `gofmt -l` + `go vet ./...` (+ golangci-lint bila tersedia) | Compiler sudah strict; vet menangkap yang penting |
## Frontend — `web/` (SolidJS + StyleX)
## Frontend — `web/` (SolidJS + typed CSS)
| Piece | Choice | Why |
|---|---|---|
| Framework | SolidJS 1.9 + Vite 6 + TypeScript 5.6 | Fine-grained reactivity — ideal untuk live feed/SSE tanpa re-render tree; bundle kecil untuk demo cepat |
| Styling | StyleX (@stylexjs/stylex + @stylexjs/vite-plugin) | Atomic CSS deterministic, typed via TS, tanpa runtime; ganti Tailwind sepenuhnya |
| Charts | Chart.js 4 via solid-chartjs | Wrapper Solid resmi untuk flow/series; Recharts React-only jadi tidak dipakai |
| Framework | SolidJS 1.9.15 + Vite 6.4.3 + TypeScript 5.6.3 | Fine-grained reactivity — ideal untuk live feed/SSE tanpa re-render tree; bundle kecil untuk demo cepat |
| Styling | Typed CSS modules + CSS custom-property tokens (`web/src/styles/`) | Atomic CSS deterministic, typed via TS, tanpa runtime; ganti Tailwind sepenuhnya |
| Charts | Chart.js 4.4.7 via solid-chartjs 1.3.11 | Wrapper Solid resmi untuk flow/series; Recharts React-only jadi tidak dipakai |
| Data fetch | `fetch` + typed client (`lib/api.ts`) + `EventSource` untuk SSE | Backend satu-satunya sumber kebenaran; web tidak pernah manggil Sectors langsung |
| Test/gate | `tsc --noEmit` + `vite build` | Cukup untuk hackathon; tanpa e2e framework |
@@ -35,14 +35,14 @@ Pinned versions. Change here before code. CI enforces the gates at the bottom.
|---|---|---|
| Go deps | Go modules (`go.mod`, vendoring opsional via `go mod vendor`) | Build reproducible; `go build ./...` satu perintah |
| JS deps | pnpm 9 + `pnpm-lock.yaml` | Install deterministik; fallback `npm` jika pnpm tidak ada |
| Env | `.env` (tidak di-commit) — `SECTORS_API_KEY`, `LLM_API_KEY`, `LLM_BASE_URL`, `LLM_MODEL_SYNTH`, `LLM_MODEL_TRIAGE`, `TELEGRAM_BOT_TOKEN`, `DISCORD_WEBHOOK_URL`, `REDIS_URL` | Semua secret dari env; contoh di `.env.example` |
| Env | `.env` (tidak di-commit) — `SECTORS_API_KEY`, `LLM_API_KEY`, `LLM_BASE_URL`, `LLM_MODEL_SYNTH`, `LLM_MODEL_TRIAGE`, `TELEGRAM_BOT_TOKEN`, `DISCORD_WEBHOOK_URL`, `REDIS_URL`, `BWS_PROJECT_ID` | Semua secret dari env; contoh di `.env.example`. Di prod simpan secret di Bitwarden Secrets Manager dan jalankan via `sh scripts/bws-run.sh …` (env tetap fallback). `TELEGRAM_*`/`DISCORD_*` hanya fallback server; tiap user kelola tujuannya di `/api/destinations` |
| Procfile dev | dua proses: `go run ./cmd/server` (atau `air` untuk reload) + `vite dev` (+ redis opsional) | Demo tetap jalan tanpa Redis |
## Alternatives declined
- **Python/FastAPI** — startup + packaging demo lebih rapuh (venv, pip) dibanding satu binary Go; konkurensi agent paralel setara via goroutine.
- **Next.js/React** — overhead framework + re-render model untuk dashboard live; Solid memberi update granular dengan bundle lebih kecil.
- **Tailwind** — diganti StyleX: atomic, typed, nol runtime, tanpa scanning step.
- **Tailwind / StyleX** — diganti typed CSS modules + token CSS: nol runtime, tanpa scanning step, tanpa unplugin versi (registry belum stabil); kontrak visual tetap lewat `styles/tokens.css`.
- **Recharts** — React-only; Chart.js via solid-chartjs menutup kebutuhan chart di Solid.
- **mattn/go-sqlite3** — butuh CGO/gcc; modernc pure-Go selalu bisa build.
- **GORM / sqlc / Alembic-style migrator** — overhead untuk 13 tabel; SQL polos + skrip bernomor cukup dan mudah diaudit juri.