- start_session now RESUMES an existing ACTIVE session for the persona instead of creating
a new one / forcing re-pick of the scenario (keep original scenario+messages).
- send_message falls back to a text-based decision detector (_detect_customer_decision)
because real LLMs rarely emit structured meta.decision — so a customer who says
'ซื้อไม่ไหว'/'no thanks' now actually ENDS the chat as lost (was stuck active forever).
Fragments handled in TH + EN; buy + walk.
All 11 backend suites pass. Rebuilt dist.
- llm.complete_conversation now maps internal roles (customer->assistant, seller->user,
system->system) before the API call — fixes 'Unknown role: customer' (501).
- Re-contact personality: the customer now chats normally, at turn 2 goes quiet and a
system time-lapse note is shown ('⏳ ผ่านไป 2-3 สัปดาห์...'), then re-engages warmer —
instead of 'pretending you asked before' at start. Driven by persona.recontact trait.
All 9 backend suites pass. Rebuilt dist.
- Scenario picker now has only social + f2f_call (removed recontact) in backend
_scenarios, start_session validation, and Chat.vue picker.
- 'ลูกค้ากลับมาติดต่อ' is no longer a scenario: it's now a PERSONA trait. Persona prompt
generates ~1-in-4 personas with recontact=true (asked before, now returns warmer/ready);
store shape gets recontact field; simulator injects a re-contact note into the persona's
system prompt so it plays as a returning customer naturally.
- test_scenario updated: unknown scenario defaults to social; recontact shown as a
generated persona trait.
All 9 backend suites pass. Rebuilt dist.
Phase 1 (tenant isolation):
- g.org_id set on require_auth; assert_tenant()/current_org_id() choke-point helpers.
- Multi-org provisioning: POST /api/admin/users {new_org:true} (super_admin) creates a
new org + its first admin; GET /api/admin/orgs (super_admin sees all, admin own).
- Fixed latent create_org double-id bug (dict id != store key).
- test_saas_tenant.py: org2 admin blocked from org1 group (403), can't list org1
groups/users, sees only own org; super_admin sees all.
Phase 2 (hardening):
- Rate limit login (per-IP + per-username) + chat send (per-user) to protect LLM cost
and slow brute force; services/rate_limit.py (in-memory + disk, no external deps).
- Audit log data/audit/audit.jsonl on org.create, user.promote_super_admin, analytics.export.
- CSV export now org-scoped (admin exports only own org).
All 8 backend suites pass.
Backend:
- SCENARIOS localized (th/en): scenario preamble + persona tone adaptation follow the
trainee's locale; start accepts {locale} and stores it on the session; send/auto-finish
use it so debrief text (why/coaching/turning points) is in the active language.
- /api/analytics/export returns per-trainee finished-session CSV (admin).
Frontend:
- Scenario picker labels localize by i18n.locale.
- New /guide page (non-IT how-to), linked from Training.
- Analytics: date-filter bar uses line icon + 'Download CSV' button (fetch w/ auth).
Rebuilt dist.
The conversation now ENDS when the persona makes a decision (option C), not when the
trainee clicks a button:
- persona_id replies carry {reply, decision(none/buy/walk), mood}; when decision is
buy/walk the session auto-finishes (won/lost) with a debrief that reveals latent
details + per-turn 'turning points'.
- personas have a tolerance (1-5, 'temper'): impatient personas walk away fast after
poor answers (fed via internal.misses on mood<=-1); tough 'wrong text' cases can
still be won by a strong, gentle response (judge realism).
- trainee 'Finish' button removed; if they leave mid-chat an active session is resumed
via /chat/resume (continue, not restart). One-shot lock still enforced once decided.
- mock/tests updated: persona deciding buy -> send auto-finishes won.
Rebuilt dist.
Backend:
- Channel/initiation now driven by a SCENARIO chosen at chat start, not baked into the
persona: social (customer opens), f2f_call (seller must open, proactive), recontact
(customer re-contacts after 1-3 months).
- /chat/start accepts {scenario}; session stores scenario + internal{turns,score}.
- persona_reply takes scenario + adapts tone; system-role transcript entries are fed to
the persona as hidden scene notes.
- JUDGE updated for realism: good response can WIN even in hard/tough-text scenarios;
long/no-close chats (turns >~12) lose; pushy/ignoring-need loses. Efficiency rewarded.
Frontend:
- Scenario picker before chat (choose Social / Face-to-face-call / Re-contact).
- Chat thread renders role=system as a centered time-lapse/scene note.
- Choose-scenario i18n (EN+TH).
Rebuilt dist.