Commit 9de1af06 by luoqi

merge: main → test(话术确定性 spec 重写 + recall/canonical 测试修复)

冲突解法:
- script-facts.spec.ts:取 main 新写 spec(test 侧 05eafa61 删的孤儿已由重写替代)
- canonical-fact-layer.spec.ts:取 test 侧(1d8a663b 与 main 修复等价)
- recall-suppression.spec.ts:test 演进版为基底 + main 的 priority-scorer v3 因子测试块
  (替代 test 侧"GAP2 已删"墓碑注释)

合并后 pac-service 14 suites / 262 tests 全绿。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
parents 384cb90a b0879852
Pipeline #3405 failed in 0 seconds
......@@ -6,6 +6,12 @@ import {
CLOSE_REASON_DEFAULT_SUPPRESS_DAYS,
} from '@pac/types';
import { resolveSnoozedUntil } from '../src/modules/plan/recall-suppression';
import {
computeConfidenceFactor,
computeFreshness,
calcPriority,
type PriorityInput,
} from '../src/modules/plan/engine/priority-scorer';
/**
* 召回闭环:熔断 / 拒绝 / 回访冷静期(snoozedUntil)。
......@@ -83,8 +89,100 @@ describe('召回抑制窗 snoozedUntil 计算', () => {
});
});
// 「优先级:considering 不再加权(GAP2)」测试块已删 —— computeLikelihoodBonus 在
// 7eb8dbe(三源信号统一标定)重构中移除,likelihood 并入 calcPriority,该行为由引擎侧覆盖。
/**
* 优先级 v3.0 三维模型(7eb8dbe 重构后)。
* 原 GAP2"considering 不加权":v3 scorer 不再消费 execution outcome(likelihood bonus 已删),
* 由设计保证 —— 这里改锁 v3 的确定性因子(置信度 / 新鲜度 / 三维加权)。
*/
describe('priority-scorer v3 | 确定性因子', () => {
test('置信度因子:直用来源置信度,夹在 [0.5, 1](防过罚)', () => {
expect(computeConfidenceFactor(1)).toBe(1); // 医生诊断
expect(computeConfidenceFactor(0.8)).toBe(0.8); // 建议
expect(computeConfidenceFactor(0.2)).toBe(0.5); // 下限地板
expect(computeConfidenceFactor(1.5)).toBe(1); // 上限
});
test('新鲜度:黄金窗内满权重,过窗线性衰减,2×窗见地板 0.4', () => {
expect(computeFreshness(0, 180)).toBe(1.0);
expect(computeFreshness(180, 180)).toBe(1.0); // 窗上界仍满
expect(computeFreshness(270, 180)).toBeCloseTo(0.7); // 过窗一半
expect(computeFreshness(360, 180)).toBeCloseTo(0.4); // 2×窗
expect(computeFreshness(3600, 180)).toBeCloseTo(0.4); // 地板不再降
});
const baseInput: PriorityInput = {
urgencyLevel: 'high',
daysSince: 0,
windowDays: 180,
primaryCode: 'K02',
toothCount: 1,
rfmSegment: null,
consultIntentMatch: false,
lifecycleStage: null,
hasTreatmentHistory: false,
isReferralChampion: false,
recentVisitGood: false,
confidence: 1,
};
test('三维加权:急迫×0.4 + 价值×0.3 + 意愿×0.3(缺省 rfm=5/意向=2/信任=4)', () => {
const r = calcPriority(baseInput);
// urgency high=7 → 2.8;K02 价值 2 → 0.6;意愿 5*0.375+2*0.375+4*0.25=3.625 → 1.0875
expect(r.breakdown.urgency).toBe(7);
expect(r.breakdown.value).toBe(2);
expect(r.breakdown.willingness).toBe(3.63);
expect(r.breakdown.base).toBe(4.49);
expect(r.score).toBe(45); // round(4.4875 × 10)
});
test('种植(K08)价值按牙数分档:单颗8 / 多颗9 / 半口+10', () => {
expect(calcPriority({ ...baseInput, primaryCode: 'K08', toothCount: 1 }).breakdown.value).toBe(8);
expect(calcPriority({ ...baseInput, primaryCode: 'K08', toothCount: 3 }).breakdown.value).toBe(9);
expect(calcPriority({ ...baseInput, primaryCode: 'K08', toothCount: 6 }).breakdown.value).toBe(10);
});
test('修复(K03)≥3颗提档:5 → 6;未映射病种价值兜底 3', () => {
expect(calcPriority({ ...baseInput, primaryCode: 'K03', toothCount: 2 }).breakdown.value).toBe(5);
expect(calcPriority({ ...baseInput, primaryCode: 'K03', toothCount: 3 }).breakdown.value).toBe(6);
expect(calcPriority({ ...baseInput, primaryCode: 'K99', toothCount: 1 }).breakdown.value).toBe(3);
});
test('信任基础:lifecycle 底分 + 治疗史/转介/近1年履约各+1,封顶 10', () => {
const r = calcPriority({
...baseInput,
lifecycleStage: 'mature', // 9
hasTreatmentHistory: true,
isReferralChampion: true,
recentVisitGood: true,
});
expect(r.breakdown.trustBase).toBe(10); // 9+3 → 封顶
});
test('老诊断 + 低置信来源:新鲜度与置信度因子相乘压低总分', () => {
const r = calcPriority({ ...baseInput, daysSince: 360, confidence: 0.3 });
expect(r.breakdown.freshness).toBe(0.4);
expect(r.breakdown.confidenceFactor).toBe(0.5);
expect(r.score).toBe(9); // round(4.4875 × 0.4 × 0.5 × 10)
});
test('urgency 缺失记 0;满配输入分数不破 100', () => {
expect(calcPriority({ ...baseInput, urgencyLevel: null }).breakdown.urgency).toBe(0);
const max = calcPriority({
...baseInput,
urgencyLevel: 'urgent',
primaryCode: 'K08',
toothCount: 8,
rfmSegment: 'important_value',
consultIntentMatch: true,
lifecycleStage: 'mature',
hasTreatmentHistory: true,
isReferralChampion: true,
recentVisitGood: true,
});
expect(max.score).toBeLessThanOrEqual(100);
expect(max.score).toBeGreaterThan(90);
});
});
describe('抑制策略单一真理源 EXECUTION_OUTCOME_META.suppressDays', () => {
test('每个 outcome 都声明了 suppressDays(number 或 null),无遗漏', () => {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment