feat(P01-03-01,P01-03-02): Pydantic scenario schema + refund YAML

server/scenarios/schema.py defines the typed model: Scenario (id, path,
market, language, title, difficulty, failure_mode, persona, setup,
success_criteria, common_mistakes, branches[], debrief) + Branch (id,
trigger.learner_signals, outcome, failure_mode, debrief_focus) +
ScenarioDebrief (model=deepseek-v4-flash:cloud, mode=no_think, D-020).
failure_mode field present per D-009. server/scenarios/loader.py loads
YAML → Pydantic, validates at load time, raises typed ValidationError on
bad input.

scenarios/customer_service_refund_ca_v01.yaml — the v0.1 Canada Customer
Service scenario (D-010): 'Angry customer requesting refund on a damaged
product', one branch point (accept_resolution vs escalate),
failure_mode=escalates_unresolved, success criteria, common mistakes,
debrief config. Matches the RESEARCH.md example.

5 unit tests pass (valid parse, invalid raises typed error, branch
outcome Literal, branch_by_id, real YAML load). load() returns a valid
Scenario with both branches.

---ci---
phase: 1
milestone: v0.1
plan: 03
task: 03-01,03-02
status: execute
persona: data-engineer
requirements:
  covered: [REQ-SCEN-01, REQ-SCEN-FMT-01]
---/ci---
This commit is contained in:
Praxis CI
2026-08-01 13:10:00 +00:00
parent 7b1b296430
commit be3df525d8
5 changed files with 339 additions and 0 deletions
@@ -0,0 +1,55 @@
# Praxis v0.1 scenario — Customer Service refund role-play (D-010, D-018).
# One branch point: accept_resolution vs escalate (D-010).
# failure_mode present (D-009 — not provoked in v0.1).
# Debrief via deepseek-v4-flash:cloud no_think (D-020).
id: cs_refund_ca_v01
path: customer_service
market: CA
language: en-CA
title: "Angry customer requesting refund on a damaged product"
difficulty: 1
failure_mode: escalates_unresolved # D-009: present, not provoked in v0.1
persona:
voice_id: "cartesia:a3536a36-1d18-4efb-a95a-7c44b7b5e384" # D-006: same voice as mentor
character: "Customer (Jordan)"
setup:
system_prompt: |
You are Jordan, a customer who received a damaged product.
You are frustrated but not abusive. You want a refund.
Stay in character. Do not break role.
Keep responses concise for voice (1-3 sentences).
Do not give legal, financial, or medical advice.
Do not impersonate a real employee of any actual company.
opening_line: "Hi, I received my order yesterday and the item is cracked. I want my money back."
success_criteria:
- "Acknowledged the customer's frustration empathetically"
- "Offered a concrete resolution (refund or replacement)"
- "Confirmed next steps"
common_mistakes:
- "Jumping to policy before acknowledging emotion"
- "Using jargon ('RMA', 'SLA')"
- "Getting defensive about the company"
branches:
- id: accept_resolution
trigger:
learner_signals: ["empathy", "concrete_resolution", "next_steps"]
outcome: success
debrief_focus: "What you did well — you acknowledged the customer's frustration and offered a concrete resolution."
- id: escalate
trigger:
learner_signals: ["defensive", "policy_first", "no_acknowledgement"]
outcome: failure
failure_mode: escalates_unresolved
debrief_focus: "The customer escalated because they felt unheard. You led with policy before acknowledging their frustration."
debrief:
model: deepseek-v4-flash:cloud
mode: no_think # D-020: latency
prompt_template: debrief/default