Files
sales-trainer/docs/PLAN.md
Macky c3d31c06e2 Sales Trainer v0.1: corporate sales-training simulator (Flask+Vue, 15 personas, chat simulator, judge, analytics)
- Auth/roles (no self-reg), admin user provision, JWT
- Analyze: sales kit + initial pain-fit from form/upload
- Persona generator: 15 personas (5/tier) w/ pain variety, negotiation, init mode, channel, latent/revealable, wrong_text special
- Chat simulator: per-mode initiation, one-shot, hidden signals, judge-LLM debrief+coaching
- Trainee loop: win/lose board, weak-areas, user-generated personas
- Admin analytics; EN+TH Vue SPA served by Flask
- Deploy: Dockerfile, docker-compose, README, eng-log + HANDOFF
- Tests (mock LLM): m0/m1/routes/e2e all pass
2026-08-07 15:31:06 +07:00

484 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Sales Trainer — Plan & Architecture
App platform for Sales Training. Users develop **customer personas with pain points**
from uploaded files + a natural-language description, then **chat with simulated
customers** to practice closing a sale. Informed by two codebases:
- **MiroFish** (`~/Gitea/MiroFish`) — original full-stack CrowdSight engine (Flask + Vue, OASIS, Zep, persona/report/chat).
- **hermes-brain-and-tools** (`~/Gitea/hermes-brain-and-tools`) — clean-room CrowdSight plugin (seed → ontology → graph → environment; profile generator; durable workflow; report contract; social simulation).
This app is a **standalone web app** (not a Hermes plugin) with its own **login/auth**,
deployable via Dockerfile/EasyPanel (the user's established pattern).
---
## 1. Product goals
1. **Input**: setup form (product / customer segment / description) AND/OR upload files
(`.pdf/.md/.txt`). Product info is used **primarily to extract pains** as the raw material
for persona generation — it is just initial grounding.
2. **Analyze**: extract value propositions, features, pricing anchors, target profile, and an
**initial pain-fit**. A generated persona is **reusable across products in the same/similar
category** — it need not be locked to the exact uploaded product.
3. **Create persona pool**: generate 15 personas (5 × intent tier A/B/C), each marked with a
buying tier, a set of pains (varied, not all product-solvable), negotiation levers, and an
initiation mode (customer-initiated vs seller-initiated). Channel: Facebook / LINE.
4. **Standard report**: human-readable analysis report (per persona: background, pains,
objections, price sensitivity, buying signals, revealable/latent fields) — downloadable.
5. **Sales Simulation (chat)**: trainee chats 1:1 with a persona (one-shot). The persona
reacts realistically: negotiates, stalls, and **refuses to buy** unless the trainee actually
resolves the persona's specific pain(s). On a result, the app reveals the pain and
**why the close succeeded/failed** (short debrief + coaching).
6. **Training loop**: track each user's wins/losses; analyze the personas a user tends to lose
against and **generate new harder/variant personas** (or manual form) to keep training.
---
## 2. Persona design (core requirement)
### 2.1 Intent tiers (23 levels) — REQUIRED
| Tier | Name | Behavior |
|------|------|----------|
| **A** | **Ready-to-buy** (ตั้งใจซื้อ) | Has budget + authority + urgency. Short close window, but still expects the seller to confirm fit & handle 12 objections. Won't buy if the offer clearly misses their need. |
| **B** | **Unsure / educating** (ไม่แน่ใจ) | Researching/considering. Needs discovery, trust-building, proof, comparison, and a clear reason to act now. High chance to stall or go silent. |
| **C** | **Not interested but has pain** (ไม่สนใจแต่มี pain) | Unaware of the category/value, budget-constrained, or skeptical. Strong resistance, but has a real, unresolved pain — the ONLY path to a close is surfacing and resolving that pain. |
### 2.1b Tier counts
- **5 personas per tier** → **15 personas minimum** per project (3 tiers × 5).
- Each persona is a distinct, realistic individual — no two share the same background/income/personality combination.
### 2.2 Persona variety (REQUIRED) — every persona gets:
- **Background** (life story / situation that justifies their behavior)
- **Income & occupation** (varied across personas: salary, e.g. employee / freelancer / SME owner / executive / student / homemaker)
- **Age group & lifestyle** (varied: age ranges, family stage, living situation, habits)
- **Personality & temperament** (e.g. skeptical, analytical, impulsive, cautious, price-haggler, relationship-driven, impatient, distrustful)
- **Communication style** (tone, vocabulary, formality, emoji usage, sentence length)
- **Goal** when entering the chat + **objections** + **budget** + **decision timeline**
- **Pains** (14), each with: description, root need, and **the specific condition(s)** the seller must satisfy to resolve it.
**Consistency rule:** persona demographics (age band, occupation, lifestyle, income) must be
**consistent with the product's target user** (from the sales kit / product data), and the
**additional description / scenario field acts as a framing constraint** for persona creation
(e.g. "product targets SME restaurants in Bangkok" → all 15 personas fit that world, with
sensible income/lifestyle spread).
### 2.3 Realism rules (from user)
- Customers **never buy easily**. Every close requires "earning" it.
- **All tiers can lose — including Ready-to-buy (A).** A customer walks away (no sale) if the
conversation is genuinely bad — e.g. rude/aggressive language, ignoring their needs, pushy
pitching. Even a customer who desperately wants the product will refuse if the seller
violates basic decency or trust. "Wanting it" never overrides "bad experience."
- They **negotiate** (price, timeline, scope, add-ons).
- They **refuse / end the chat** if the seller fails to address their pain.
- Each persona has hidden pains the seller must **discover** (ask questions), not just pitch.
### 2.3a Negotiation — ALL tiers (REQUIRED)
Every persona bargains over **concessions/benefits**, at every tier:
- **Price reduction** (discount ask),
- **Freebies / add-ons / bundling**,
- **Delivery / fulfillment timeline** (especially for made-to-order / production goods),
- Scope adjustments, payment terms, guarantees, etc.
The seller must respond to negotiation realistically — give value in exchange, not just give in.
The specific negotiable leverage each persona will push on is defined in the persona card.
### 2.3b Pain variety (REQUIRED)
- **NOT every pain aligns with the product.** Persona pains may be:
- **Directly solvable** by the product (the clear win),
- **Partially solvable / nearby** (the product helps but doesn't fully close it — seller must
manage expectation / bundle / reframe), or
- **Unrelated/unsolvable** by this product (a genuine red herring — seller must recognize it
and redirect rather than force a fit).
- This mirrors reality: real customers rarely have exactly the pain a product solves. The
seller must **diagnose which pain is which** and only claim what the product truly delivers.
### 2.3c Chat initiation — TWO modes (REQUIRED)
The simulation supports **two initiation modes**, set per persona (a persona is either one or the other):
- **Customer-initiated** (ลูกค้าทักก่อน): for products/brands marketed to drive engagement
(e.g. cars, food, retail). The customer opens the chat; the seller responds.
- **Seller-initiated** (ฝ่ายเราเปิดการขายก่อน): for products/services sold proactively /
outbound (e.g. insurance, B2B services). **The customer does NOT message first — the seller
must open the sale.** The simulator gives the seller an opening task to start the conversation
with a cold/warm lead, and the persona reacts accordingly.
Each persona card declares which mode applies (and, for customer-initiated, includes the
persona's opener; for seller-initiated, the persona's initial mood/receptiveness).
> Note: this supersedes any earlier "customer always messages first" rule. Initiation is
> per-case: some personas are customer-initiated, some are seller-initiated.
### 2.3d Special tier-3 case — "wrong text / lost interest" (REQUIRED, ≥1 in tier C)
- At least **one tier-C persona** is designed as a **false lead**: their opening message makes
them *appear* ready to buy (e.g. "I need this, tell me the price"). But once the seller
responds, the persona immediately reveals lost interest and wants to **end the conversation**
("never mind, forget it") — yet **deep down the pain still exists**.
- To close: the seller must not take the "forget it" at face value; they must gently re-engage,
rebuild a moment of connection, and surface the still-live pain without being pushy. High
difficulty, frequent refusal — it tests resilience + empathy + non-pushy discovery.
### 2.3e Channel context (REQUIRED)
- The simulated chat is presented as a real messaging thread. Primary channels: **Facebook** and
**LINE**. A persona/case declares which channel it happens on (affects the look, and can color
tone — e.g. LINE more casual, FB page vs Messenger).
- Other channels optional later; v1 ships Facebook + LINE.
### 2.4 Persona lifecycle & one-shot rule (REQUIRED)
- **1 persona ↔ many users**: a persona is a shared playable asset; any user who has not yet
chatted it may practice on it.
- **1 user ↔ 1 chat per persona max (one-shot)**: a user can chat a given persona **only once**
the outcome is final and that persona is "used" for that user. It cannot be re-chatted/replayed
by the same user. (Reels/retry against it is not allowed.)
- To practice again on a similar customer, the user must **generate a new persona** (2.5).
### 2.5 Persona sources & generation (REQUIRED)
- **Admin-created baseline pool**: admin/upload-generated personas form the shared pool that
**new users** can pick from and practice on.
- **User-generated personas**: after practicing, a user can generate **their own persona** to
train on, in two ways:
1. **Weak-area generation** (4 in feature list): auto-analyze the personas this user tends to
lose against → generate a new, harder/variant persona targeting that weakness, as a "lock"
to overcome it; **or**
2. **Manual form**: the user describes the persona they want to practice (target profile,
situation, difficulty level) and the system generates it.
- User-generated personas are private to that user (unlike the admin pool).
### 2.6 Data exposure by view (REQUIRED)
- **Approve/edit view (admin)**: full persona data — pains, income, personality, negotiation
levers, hidden details. Admin sees everything to review/edit/approve.
- **Select/chat view (trainee)**: **only** the persona's name + basic info one would plausibly
know up front (profession, age group, channel, initiation mode, product context). **Hidden**:
pain, income, personality, budget, negotiation levers — anything you couldn't know without
talking. Revealed only **after** the conversation ends (win/lose + debrief).
- **Win/lose status**: the user can always see which personas they've **won** vs **lost** vs
**not yet tried**.
---
## 3. App architecture
```
┌────────────────────────────────────────────────────────────┐
│ Vue 3 Frontend (port 3000) │
│ Login · Dashboard · Project setup · Report · Chat view │
└───────────────▲────────────────────────────┬───────────────┘
│ HTTP/JSON (JWT) │
┌───────────────┴────────────────────────────▼───────────────┐
│ Flask Backend (port 5001) │
│ │
│ auth/ → register, login, JWT, per-user isolation, ROLES │
│ api/ → projects, uploads, personas, report, chat, groups │
│ services/ → analyzer · persona generator │
│ → sales_kit (product facts + initial pain-fit) │
│ → report builder │
│ → sales_simulator (chat engine) │
│ storage/ → filesystem JSON per user (no SQL DB) │
│ llm_client/ → OpenAI/DeepSeek/custom OpenAI-compatible calls │
└────────────────────────────────────────────────────────────────────┘
```
### 3.0 Roles & permissions (multi-user corporate)
| Role | Can do |
|------|--------|
| **Super Admin** | Manage all users, assign roles, manage org-level persona groups, view all data + analytics. |
| **Admin** (Manager) | Create/edit/delete **persona groups** (define a product/offer + its personas), **manually edit any persona**, manage users in their scope, view analytics. |
| **User** (trainee) | **Cannot create groups.** Only **selects an existing persona group** and practices (chat) on its personas; sees own results. |
- Org model: `organization``users``persona_groups``projects`/`sessions`.
- **Persona group** = a reusable packaged scenario: product definition (sales kit) + the 15
personas generated for it. Admins build groups **and may edit/re-analyze them later**
(groups are editable; regenerate is allowed; **admin can hand-edit any persona field**).
- A trainee's "project" is a **training session** bound to a group + a chosen persona.
### 3.0b Registration (confirmed)
- **No self-registration.** Admin creates users and sends invites (email/account-creation).
Only Super Admin and Admin can provision accounts. Roles: Super Admin / Admin / User (no Trainer).
- **Repo root**: `~/Gitea/Sales Trainer/`
- **Stack**: Flask 3 + Vue 3 (Vite) + JWT auth + filesystem JSON persistence + OpenAI-compatible LLM.
- **LLM**: reuse the provider-agnostic pattern from MiroFish (`llm_client.py`): configurable
`LLM_PROVIDER` / `LLM_BASE_URL` / `LLM_MODEL_NAME` / `LLM_API_KEY` via `.env`. Supports
**OpenAI, DeepSeek, or any OpenAI-compatible custom model** (base URL + model name overridable).
- **Persistence**: per-user `data/<user_id>/projects/<project_id>/…` JSON (mirrors
MiroFish/Hermes durable file approach). No external DB needed for v1.
### 3.1 Data flow
1. **Setup (admin)** → create a persona group: form (product/segment/description) + upload files
(+ channel & initiation-mode preferences).
2. **Analyze** (background, async) →
- **Sales Kit** extracted from input (what we sell, features, pricing, value prop, target, use cases)
**mainly for pain extraction** (reusable across same/similar category);
- **Personas** generated (15 personas w/ tier, pains, background, negotiation levers, initiation mode, channel, latent/revealable fields).
3. **Approve/Edit (admin)** → review full persona data; edit any field; approve pool.
4. **Report** (background, async) → structured report assembled from personas (downloadable; full data).
5. **Chat** → a trainee picks a persona (one-shot) → real-time conversational close attempt,
per the persona's initiation mode.
- Each message: persona responds; the simulator also returns an internal **state update**
(tier, pain-resolution progress, trust, buying-signal flags) — kept hidden.
- On **close** or **refusal**, the chat terminates with a debrief that reveals latent fields.
6. **Training loop** → record win/lose; weak-area analysis; generate-new-persona (lock or manual).
---
## 4. Services detail
### 4.1 `analyzer` (Sales Kit extraction + initial pain-fit)
Input: **setup form** with:
- **Product** (what it is) — required,
- **Initial customer segment** (optional),
- **Additional description / scenario** (optional framing for persona creation),
- **OR upload files** (`.pdf/.md/.txt`) carrying all of this — user may skip the form entirely.
**Decision logic (clear/simple):** if files are uploaded, parse them for product + target + pains;
if the form is also filled, the **form's explicit fields win** and file text fills the gaps
(and is still analyzed for extra context / pain-fit). If only files → derive everything from files.
If only the form → use the form.
Output (JSON): `productName`, `category`, `valueProps[]`, `features[]`, `pricing`
(budget anchors), `targetAudience` (incl. segment from form), `useCases[]`, `competitors[]`,
`objectionHandlers[]`, and — critically — **`initialPainFit[]`**: the analyzer's first-pass
judgement of **which pains the product can plausibly solve** (with evidence/claims from the
inputs), so persona pains can be built partly against and partly away from this baseline.
`scenario`/description is preserved as a **framing constraint** passed to persona generation.
This grounds all persona + chat generation so the simulation stays on-product.
### 4.2 `persona_generator`
Builds the **15 personas (5 × tier A/B/C)** for a given persona group. Prompt engineered to enforce:
- exactly 5 personas per tier;
- the required variety fields (background, income/occupation, personality,
communication style, goals, budget, timeline, objections);
- **pain variety** (2.3b): not every pain is product-solvable — include directly-solvable,
partially-solvable, and unrelated pains; use `initialPainFit` as the baseline;
- **the tier-C "wrong text / lost interest" special persona** (2.3d) — at least 1;
- **chat opener** for every persona + whether they initiate friendly/blunt/indifferent;
- **initiation mode** (customer-initiated vs seller-initiated) and **channel** (facebook/line);
- **latent vs revealable fields** (2.6) so the UI can hide what a real seller wouldn't know;
- JSON output schema (strict), language follows project `language` (en/th).
### 4.2b `weak_area_analyzer` (win/loss insight → new persona)
- Tracks each user's per-persona outcomes (won/lost) and their scores.
- On request, analyzes the user's **losses**: which tier / initiation mode / channel / pain-type
/ objection-type / negotiation-style they tend to lose against.
- Produces (a) a **summary insight** ("you lose most against seller-initiated, price-hardball
persona; you rarely handle discount + delivery-time pressure together"), and (b) a **spec** to
**generate a new persona** targeting that weakness (as a 'lock' to overcome) — or the user can
use the manual persona form instead.
- A user can also see their **win/lose status board** (which personas won/lost/not-tried).
### 4.3 `sales_simulator` (chat engine) — the heart
State machine per chat session:
```
READY → (customer or seller opens per mode) TALKING ⇄ (negotiating/objecting/thinking) → CLOSED | REFUSED | TIMEOUT
```
- **Initiation depends on the persona's mode (2.3c)**:
- **customer-initiated**: the customer sends the first message; the seller responds.
- **seller-initiated**: the customer does NOT message first — the simulator gives the seller
an **opening task** ("open the sale"), and the seller must start the conversation; the
persona then reacts as a cold/warm lead.
- **Channels**: Facebook or LINE (2.3e) — affects presentation + some tone.
- **One-shot (2.4)**: a user may not start a second session on a persona they've already
finished (won or lost). Backend enforces it.
- **Hidden/latent data (2.6)**: the trainee only ever sees revealable fields during the chat;
pain / income / personality / budget / negotiation levers / opener are latent and hidden until
the end.
- **Hidden pain state**: each persona has hidden unresolved pains. The simulator tracks
per-pain resolution. Pitching without discovery does NOT resolve pain.
- **Pain-fit realism (2.3b)**: since not all pains are product-solvable, the simulator must
let sellers mis-diagnose — claiming to solve an unrelated pain must backfire (trust down)
or lead down a dead end, while the real (product-solvable) pain stays unresolved.
- **Per message**: LLM plays the persona in-character (using persona card + sales kit +
chat history + current internal state). Returns:
- `reply` (the persona's in-character message),
- `internal`: updated `{ trust, painProgress, buyingSignals, tier, mayRefuse }`.
- **Close trigger**: the judge-LM decides if the seller has satisfied the pain-resolution
conditions (visible+hidden) AND the persona verbally accepts the offer/price. Only then `CLOSED`.
- **Refuse trigger**: if trust collapses, the seller pushes a hard pitch without
addressing pain, or after N failed attempts → `REFUSED`.
- **Negotiation**: personas actively counter (price, scope, timeline, freebies, delivery). Seller
must handle these on top of resolving pain.
- **Timeout/abandon**: persona goes silent if seller is repetitive/low-value.
**Internal signals are NEVER shown live.** Trust/pain/buying meters stay hidden during the
conversation (default) — the trainee reads the customer's words only.
**Debrief (on CLOSED or REFUSED) — REQUIRED, shown only at conversation end:** reveal the
latent fields (pain, income, personality, budget, negotiation levers, hidden opener), then:
- Keep the summary **short**, then give **coaching**: for each message that hurt the score,
suggest **how the seller should have responded** so the trainee understands and can improve.
- **CLOSED**: brief note on the persona's `pain` (what it was) + why it closed.
- **REFUSED**: the unaddressed pain(s), where trust was lost, which pain was mis-diagnosed (if
any) — each with **a concrete "better reply" suggestion**.
- A judge-LM (separate from the persona LLM) produces the score + coaching.
- Toggle: admin can choose to show live meters **per persona group** if desired; default hidden.
---
## 5. API surface (v1)
| Method | Path | Purpose |
|--------|------|---------|
| POST | `/api/auth/login` | JWT login |
| GET | `/api/auth/me` | current user + role |
| POST/PUT | `/api/admin/users` | admin: create users + invite, assign roles (super-admin/admin/user) |
| GET | `/api/admin/users` | admin: list users |
| POST | `/api/groups` | admin: create persona group (product files/description) |
| GET | `/api/groups` | list persona groups visible to role (user: selectable only) |
| GET | `/api/groups/<id>` | get group + personas (admin: full; user: practice view) |
| POST | `/api/groups/<id>/analyze` | admin: trigger analyze (sales kit + 15 personas) |
| GET | `/api/groups/<id>/report` | get/download report |
| GET | `/api/groups/<id>/personas` | list personas (grouped by tier; user sees revealable fields only) |
| GET | `/api/groups/<id>/personas/<pid>` | persona card (admin: full; user: revealable only) |
| PUT | `/api/groups/<id>/personas/<pid>` | admin: hand-edit a persona |
| POST | `/api/groups/<id>/reanalyze` | admin: re-run analyze / regenerate personas |
| POST | `/api/groups/<id>/personas/<pid>/chat` | user: start/send message → persona reply + hidden state (one-shot enforced) |
| GET | `/api/groups/<id>/personas/<pid>/session` | user: session state/history |
| GET/POST | `/api/sessions` | user: my training sessions + debriefs (own results) |
| GET | `/api/sessions/<id>/debrief` | user: end-of-chat debrief (pain + reason + coaching + latent reveal) |
| GET | `/api/me/board` | user: win/lose status board per persona (won/lost/not-tried) |
| GET | `/api/me/weak-areas` | user: analyze which personas I tend to lose against (insight) |
| POST | `/api/me/personas/generate` | user: generate own persona — body: {mode: "weak-area" \| "manual", ...spec} |
| GET | `/api/me/personas` | user: list my generated (private) personas + status |
| GET | `/api/analytics` | admin: aggregate trainee analytics (close rate, avg score, hardest personas) |
All `/api/*` except login require `Authorization: Bearer <jwt>`; data scoped by role + org.
Admins call analyze/persona-edit endpoints; trainees read revealable fields + run one-shot sessions +
generate their own personas.
---
## 6. Frontend views (Vue 3 + Vite)
1. **Login** (no self-registration — accounts created by admin)
2. **Dashboard** — (admin) persona groups + user mgmt; (user) selectable groups + my sessions
3. **Admin: Group Builder** — setup form (product/segment/description) + upload files; pick
channel (Facebook/LINE) + initiation-mode mix; trigger analyze → approve/edit the 15 personas
4. **Admin: User Management** — no self-registration; create + invite users, set roles
5. **Personas (approve/edit, admin only)****full data** for all 15 personas; edit any field, approve
6. **Personas (select, user)** — win/lose status board + list of available personas; each card shows
**only revealable info** (name, profession, age group, channel, initiation mode, product context)
7. **Report** — rendered analysis report + download (admin; full persona data)
8. **Simulation (chat)** — Facebook/LINE-style thread. **Internal signals + latent fields hidden**
during chat. Initiation per mode: customer opens OR seller gets an "open the sale" task.
**One-shot enforced.** On end → **debrief overlay**: reveal latent fields + short summary +
coaching (how to improve weak-score replies) + pain + reason + score. Result saved; persona
marked won/lost for this user.
9. **Gen persona (user)** — generate own persona: **weak-area** (from my loss analysis) or
**manual form** (describe target persona). Private to the user.
10. **Weak-areas (user)** — insight: which personas I tend to lose against + generate-a-lock CTA
11. **Admin Analytics** — aggregate trainee results (close rate, avg score, hardest personas).
i18n: en + th (mirrors MiroFish pattern). Role-based navigation (admin vs trainee).
---
## 7. Security, config, deploy
- **Auth**: JWT (HS256) with password hashing (werkzeug `generate_password_hash`).
Secrets in `.env`. No raw tokens/keys in UI. **No self-registration** — only admin-provisioned accounts.
- **LLM credentials**: `.env` only, never shipped/logged.
- **File safety**: upload allowed types + size caps; parse text server-side; strip anything
executable; keep raw uploads out of any served path.
- **LLM route discipline**: the chat + persona gen + judge are the only LLM-touching callers.
- **Deploy**: single `Dockerfile` (python:3.11 + Node 18, build Vue → serve static via
Flask or nginx) + `docker-compose.yml` with `.env`, per the user's EasyPanel pattern.
Local tests via `http.server` / Flask dev (no Docker on local Mac).
---
## 8. Milestones (build order) — ALL COMPLETE ✅
- **M0 — Scaffold & auth**: repo, Flask app factory, JWT auth, roles (super-admin/admin/user),
user + org store, **admin-user creation/invite (no self-registration)**. ✅
- **M1 — Input & analyze**: setup form (product/segment/description) + file upload/parse with
precedence rule, sales-kit extraction + **initial pain-fit**, JSON storage. ✅
- **M2 — Persona groups & persona generation**: group model (editable/re-analyzeable); 15 personas
(5 × tier) with variety, pain variety, negotiation levers, chat openers, **initiation mode
(customer/seller)**, **channel (facebook/line)**, **latent vs revealable fields**, and the
tier-C "wrong text" special case; **admin hand-edit persona endpoints**. ✅
- **M3 — Report**: assemble + render + download report. ✅
- **M4 — Chat simulation**: stateful persona chat (per-mode initiation: customer opens OR seller
"open the sale" task), Facebook/LINE thread, negotiation, all-tiers-can-lose, close/refuse logic,
**hidden signals + latent fields**, **one-shot enforcement**, **separate judge LLM** for scoring,
**short debrief with coaching** (latent reveal + pain + reason + how to improve). ✅
- **M5 — Trainee loop**: win/lose status board, weak-area analysis, **user-generated personas**
(weak-area "lock" + manual form), my-sessions. ✅
- **M6 — Frontend polish**: login, role-based dashboard, group builder (+ persona approve/edit UI),
user mgmt, personas (select view), report, chat UI, debrief overlay, gen-persona, weak-areas,
**admin analytics dashboard**, EN+TH i18n. ✅ (SPA served by Flask; verified live)
- **M7 — Deploy & docs**: Dockerfile, docker-compose, README, engineering-handoff docs,
E2E verification with honest status reporting. ✅ (mock-LLM E2E; real-key + Docker pending)
> Status: prototype complete + verified with mock LLM. **Pending: real-LLM live smoke test
> and remote Docker/EasyPanel validation** (see docs/HANDOFF.md).
---
## 9. Acceptance criteria
1. Multi-user corporate, **no self-registration**: super-admin/admin create + invite users; roles
Super Admin / Admin / User (no Trainer). **User (trainee) cannot create** — selects a group + practices.
2. Setup accepts **form (product / segment / description) AND/OR uploaded files**, with a clear
decision rule (form-wins, file-fills-gaps; files-only → derive all).
3. Analyzer produces sales kit **+ initial pain-fit**; persona demographics (age, occupation,
lifestyle, income) are **varied AND consistent with product + scenario framing**.
4. → 15 personas (5 × tier A/B/C); **admin can edit any persona and re-analyze the group**.
5. Persona group has **≥1 tier-C "wrong text / lost interest" special persona**.
6. Report is human-readable + downloadable.
7. **All tiers can lose** — bad conversation (e.g. rude language) = no sale, even for ready-to-buy.
8. **All tiers negotiate** concessions (price / freebies / delivery timeline / scope / payment).
9. Initiation is **per-persona mode**: customer-initiated (customer opens) OR seller-initiated
(seller gets an "open the sale" task). Channels **Facebook + LINE**. Internal signals + latent
fields are **hidden** during chat; only revealable fields shown.
10. **One-shot rule**: a user can chat a persona only once (won/lost = final); the same persona
stays playable for other users.
11. **Two persona views**: admin sees full data (approve/edit); trainee sees only revealable info
(name, profession, age group, channel, initiation mode) — latent fields revealed only after result.
12. **Win/lose status board**; **weak-area analysis**; **user-generated personas** (weak-area "lock"
OR manual form), private to the user.
13. Debrief is **short + coaches**: suggests how to improve on weak-score messages; reveals pain +
reason. Scored by a **separate judge LLM**; **no speed factor** in scoring.
14. Product data is used primarily for **pain extraction**; personas are reusable across the
same/similar product category.
15. **Admin analytics dashboard** aggregates trainee results (close rate, avg score, hardest personas).
16. Single `docker compose up` runs the whole app (or EasyPanel build); LLM pickable as
OpenAI / DeepSeek / any OpenAI-compatible custom model via `.env`.
17. E2E tests pass; honest report of any blocked stages.
---
## 10. Confirmed decisions (all from user)
- Standalone web app (Flask + Vue + JWT), Docker/EasyPanel deploy.
- Multi-user corporate, **no self-registration** — Super Admin / Admin / User (no Trainer).
Admin creates + invites users.
- **Admin** builds/edits persona groups, **hand-edits any persona**, re-analyzes groups, sees analytics.
- LLM: **OpenAI, DeepSeek, or custom OpenAI-compatible** (configurable via `.env`).
- **5 personas per tier → 15 min per group.**
- Persona variety: **age-group, occupation, lifestyle, income** — consistent with product + scenario framing.
- Internal chat signals **hidden**, revealed at end; **short debrief + coaching** (how to answer better on weak-score messages).
- **All tiers can lose**; **all tiers negotiate** concessions.
- **Separate judge LLM** for close/refuse + scoring; **no speed factor** in scoring.
- **Admin analytics dashboard** (aggregate trainee results).
- UI **EN + TH**.
- Product **user-input via form AND/OR uploaded file** with clear precedence; analyzer computes
initial pain-fit. Product data mainly for **pain extraction** — personas reusable across same/similar category.
- Personas have **pain variety** (not only product-solvable), demographically consistent with product.
- **Initiation per persona**: customer-initiated OR seller-initiated (open-the-sale task).
- **Channels**: Facebook + LINE.
- **Two views**: admin full data; trainee revealable-only (latent hidden until result).
- **One-shot rule**: 1 user = 1 chat per persona; persona shared across users.
- **User-generated personas**: weak-area "lock" OR manual form (private).
- **Win/lose board** + **weak-area analysis**.
- **≥1 tier-C "wrong text / lost interest" persona.**
## 11. Remaining open questions (low-risk; defaults noted)
- **Invite delivery**: email link to set password, or admin pre-sets a temporary password?
(Default: admin sets temporary password on account creation; optional email later.)
- **Analytics granularity**: just group-level aggregates, or drill-down per persona/trainee?
(Default: group + per-persona close rate + avg score; per-trainee detail on request.)
- **Scoring weights** (beyond dropping speed): pain 40 / trust 30 / objection-handling 30 ok?
- **Persona edit UI**: full form for all fields, or JSON editor for power users?
(Default: structured form for common fields + JSON for advanced.)
_Plan is ready for build. Confirm the low-risk defaults above if you disagree, otherwise I begin M0._