diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 708f3ce..0d50d5b 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -2,62 +2,104 @@ > Another AI should be able to resume without chat history. -## Branch / repo -- Repo: `~/Gitea/Sales Trainer/` (local git initialized; **no remote yet**). -- Branch: `main` (default). +## Branch / repo / deploy +- Repo: `~/Gitea/Sales Trainer/` — **git repo, remote = Gitea**. +- Remote: `https://git.moreminimore.com/kunthawat/sales-trainer.git` (GITEA_TOKEN via credential + helper; never committed). +- **Live deploy:** `https://moreminimoreapps-saletrainer.ahkhwd.easypanel.host` — EasyPanel, + auto-redeploys from Gitea on push to `main` via webhook (≈3 min). Dockerfile ships prebuilt + `frontend/dist/` (no npm in image). LLM vars set in EasyPanel env. ## What this is -Corporate multi-user sales-training simulator. Vue SPA + Flask API + filesystem JSON storage. -Admins build persona groups from a product (form + upload); trainees chat one-shot against -generated customer personas to practice closing; judge-LLM scores + coaches. +Corporate multi-user **sales-training simulator**: admins create persona groups from a +product/service/idea (โฟกัส "สินค้า/บริการ/ไอเดีย"), app auto-generates **15 customer personas** +(5/tier A/B/C) via LLM; trainees pick a **scenario (social / พบหน้า)**, chat 1:1 one-shot to close a +sale; a **per-turn + final judge LLM** evaluates feelings and scores/coaches. Vue SPA + Flask API + +filesystem JSON storage (no SQL). i18n TH/EN. No self-registration (admin provisions). -## Current state — COMPLETE (M0–M7), prototype verified with mock LLM -All backend + frontend built. All 4 backend test suites pass. Frontend builds. Live HTTP smoke -test passes (SPA served, login, group create, register->404). +## Roles +- **super_admin** (bootstrap `admin`) — full recipe (secret persona fields) + tenant admin. +- **admin** — manages groups/users, sees personas with **secret fields stripped** (IP protection). +- **user** (trainee) — trains against personas, own board. + +## Current state — COMPLETE core + hardened +All backend + frontend built. **11 test suites green** (mock LLM): -## Verified commands ```bash -# Backend tests (mock LLM, no key needed) cd backend -uv run python scripts/test_m0.py # auth/roles/no-self-reg -uv run python scripts/test_m1.py # group create + role visibility -uv run python scripts/test_routes.py # 21 routes registered -uv run python scripts/test_e2e.py # full flow (analyze->personas->chat->debrief->one-shot->board->analytics) +uv run python scripts/test_m0.py # auth/roles/no-self-reg +uv run python scripts/test_m1.py # group create + role visibility +uv run python scripts/test_setup.py # first-time admin setup (email+password+ToS) +uv run python scripts/test_security.py # path traversal / IDOR / XSS +uv run python scripts/test_scenario.py # 2 scenarios (social/f2f_call), recontact default +uv run python scripts/test_e2e.py # full flow -> won via judge +uv run python scripts/test_ip_protection.py # secret fields hidden from admin +uv run python scripts/test_saas_tenant.py # tenant isolation + rate-limit + audit +uv run python scripts/test_user_journey.py # idea-flow end-to-end +uv run python scripts/test_variant.py # clone-persona-from-persona +uv run python scripts/test_resume_decision.py # resume + per-turn LLM decision -# Run backend -cd backend && uv run python run.py # Flask :5001 (serves built frontend from frontend/dist) - -# Frontend dev -cd frontend && npm install && npm run dev # Vite :3000 proxying /api -> :5001 -# Frontend build -cd frontend && npm run build # outputs frontend/dist +# run backend (serves SPA from frontend/dist) +cd backend && uv run python run.py # Flask :5001 ``` -## Default account -- super_admin: `admin` / `1234` (bootstrap). First login FORCES setting email + changing the - password (must_setup flow) before use. +## Key behaviors (implemented) +- **One-shot:** 1 persona = 1 chat per user; result final (won/lost). `SessionStore` enforces. +- **Resume:** unfinished session resumes on re-entry — **no** scenario re-pick (same session+scenario). +- **Win/loss = per-turn LLM judge** (`Simulator.evaluate_turn`): every customer reply is evaluated → + `{mood, decision(buy|walk|pending), score_delta, reason}`; session ends when decision = buy/walk. + **Not** fixed keywords. +- **2 scenarios only:** `social`, `f2f_call`. Unknown → social. +- **Recontact = persona trait** (not a scenario): chats normally, then at turn ≥ 2 a time-lapse + system note ("⏳ ผ่านไป 2-3 สัปดาห์…"), then re-engages warmer. +- **Persona variant:** `POST /api/groups//personas//variant` — new persona (new id) that + **locks** pains/objections/levers/tolerance/special/recontact/goal/budget/difficulty/tier/product + **but varies** identity (name/profession/age/location/background/personality/opener). Lets a + trainee re-practice the same challenge (one-shot is per-persona). UI button on finished personas. +- **Auto 15 personas** on create; no "เพิ่มเติม" button (TARGET=15, retry up to 3× + accept ≥ 8 so + real LLM under-count doesn't 500). +- **IP protection:** `SECRET_PERSONA_FIELDS` (pains, objections, negotiation_levers, opener, + tolerance, rootCause, resolutionConditions) stripped for `admin`; full only for `super_admin`. +- **SaaS Phase 1–3 done:** tenant isolation (`g.org_id` + `assert_tenant`), login/chat rate-limit, + audit log (`data/audit/audit.jsonl`), org plan/seats/active model + `PATCH /api/admin/orgs/`, + ToS consent on setup, org-scoped signed expiring CSV export (5-min HMAC). +- **Reduce raw JSON in UI:** `list_groups` returns lightweight summary (persona_count, no full array); + persona detail rendered as readable form/cards (pain = line-by-line, not `[object Object]`). -## Key gotchas -1. **Do NOT invoke `.venv/bin/python