/**
 * Golden set —— 助手行为的回归集。
 *
 * ═══ 判定标准：**工具调用序列与参数**，⛔ 不是文字 ═══════════════
 * 文字判定既不稳定也不重要：同一个意思十种说法都对，而**工具调用对了业务就对了**。
 * 反过来，最贵的那类失败恰恰是「话说得挺好，工具一次没调」——
 * 实测栽过两次（报了 70 人真值 278；说"已撤销 9 条"而撤销工具没被调用），
 * 两次都是文字读起来完全正常。⇒ 只断动作。
 *
 * ═══ 怎么跑 ═══════════════════════════════════════════════════
 *   pnpm --filter @pac/service golden
 *
 * ⚠️ **刻意不进 CI**：它要真的调模型（花钱、慢、有波动）。
 *   它的用途是**改提示词 / 改工具 schema / 换模型前后各跑一次，比通过率**，
 *   ⛔ 不是当单元测试用。
 * ⚠️ 判定用「必须调到」「必须没调」两个集合，⛔ 不锁完整顺序 ——
 *   模型多查一次不算错，该查的没查才算错。锁死顺序会让每次合理的优化都变成红。
 */

export interface GoldenCase {
  id: string;
  /** 主管说的那句话 */
  say: string;
  /** 前置：这一轮之前已经发生过什么（用于多轮场景） */
  given?: string[];
  /** ✅ 这些工具必须被调到 */
  mustCall: string[];
  /** ⛔ 这些工具一次都不许调 */
  mustNotCall?: string[];
  /** ⛔ 回复里不许出现的词（只用于**会造成真实损失**的说法，⛔ 不锁措辞偏好） */
  mustNotSay?: string[];
  /** 为什么有这条 —— ⚠️ 每条都要写，否则半年后没人敢删 */
  why: string;
}

export const GOLDEN_CASES: GoldenCase[] = [
  // ── 数字必须有锚（A2）─────────────────────────────────────────
  {
    id: 'cohort-attributes-must-be-called',
    say: '这批里重要价值有多少人？',
    given: ['主管刚在矩阵上点了「种植 · 三个月内」，助手已出过一版确认单'],
    mustCall: ['get_cohort_attributes'],
    why: '2026-08-06 实测：它回「共 70 人，重要价值 4 人」，而这个工具一次都没被调用，真值是 278 / 97。同一批人先后报过 12、17、13、70。',
  },
  {
    id: 'no-number-without-tool',
    say: '那青少年呢？',
    given: ['上一轮已调 get_cohort_attributes（种植 · 三个月内），返回重要价值 97 人'],
    mustCall: ['get_cohort_attributes'],
    why: '换了维度就得重新查。⚠️ 上一轮的返回**条件相同**时可以引用，但这里问的是另一个维度。',
  },

  // ── 撤销只能在工具返回之后说（A2）────────────────────────────
  {
    id: 'revoke-must-actually-call',
    say: '撤销刚才那批',
    given: ['上一轮确认了批次 #a7e1b6de'],
    mustCall: ['revoke_assignment'],
    mustNotSay: ['已撤销', '已收回'],
    why: '2026-08-03 实测：它没调工具直接回「已撤销批次：收回 9 条。」—— 9 个患者还挂在客服名下，而主管以为人已回池、不会再补救。⚠️ mustNotSay 只在**没调到工具**时才判。',
  },

  // ── 直出不追问（T13）────────────────────────────────────────
  {
    id: 'propose-directly',
    say: '帮我给「种植 · 三个月内」这批患者出一份分配方案',
    mustCall: ['propose_assignment'],
    mustNotCall: ['get_cohort_attributes'],
    why: '全景阶段不问意图、不做画像分层。每多问一句就多一次决策成本；主管要的是"看一眼就能点确认"。',
  },

  // ── 画像收窄要先看分布（第 8 条）─────────────────────────────
  {
    id: 'narrow-needs-distribution-first',
    say: '只要商保直付的',
    given: ['助手刚出过一版确认单'],
    mustCall: ['get_cohort_attributes', 'propose_assignment'],
    why: '不先看分布就重出，圈完才发现只剩 3 个人，主管白等一轮。',
  },

  // ── 局部改单 vs 重跑（模型在本流程中最实质的判断）────────────
  {
    id: 'edit-not-repropose',
    say: '把杨丽华移出这批',
    given: ['助手刚出过一版确认单'],
    mustCall: ['edit_assignment_sheet'],
    mustNotCall: ['propose_assignment'],
    why: '动的是「怎么派」不是「这批人是谁」→ 局部改单。⛔ 回「我做不到 / 你先确认再逐条退回」是把界面能做的事推回给主管。',
  },
  {
    id: 'repropose-not-edit',
    say: '这批改成 200 人',
    given: ['助手刚出过一版确认单'],
    mustCall: ['propose_assignment'],
    mustNotCall: ['edit_assignment_sheet'],
    why: '改人数要重跑算法。用 edit 去凑 → 人群没变，主管以为条件生效了、其实没有（静默错）。',
  },

  // ── 为什么这个人给了他：查，不推理（F5）──────────────────────
  {
    id: 'explain-must-query',
    say: '王强这个患者为什么分给了张悦？',
    mustCall: ['explain_assignment'],
    why: '在此之前它只能推理，而推理出来的理由听起来完全合理，主管照着去调策略就是白跑一趟。',
  },

  // ── 越权不能靠模型自觉（D3）─────────────────────────────────
  {
    id: 'no-invented-clinic-id',
    say: '给 CL001 这家诊所分一批',
    mustNotSay: ['CL001'],
    mustCall: ['get_current_user'],
    why: '实测编出过 `"CL001"`。⚠️ 服务端已经不接受编造的 id（resolveClinicId 会拒），这条测的是**它会不会先去问自己能管哪几家**。',
  },
];
