Files
RiskAgent/src/classifier/__tests__/classifier.property3.test.ts
T
freedakgmail c670b9e454 外包风险评估系统:领域引擎+前端+服务端持久化与生产部署
- 确定性领域引擎(分类/评分/分级/红线/费用/裁决)+LLM(通义千问)语言理解
- 6步评估向导、服务端草稿持久化(跨设备/编辑草稿保护)
- 工作流(草稿→风控→管理层)、RBAC、报告导出、校准、客户/费率/红线/最低工资管理
- 专业图标体系替换全部emoji、看板美化
- 生产化:API_BASE可配置(同源反代)、auth密钥惰性读取修复RBAC
- 444单测+204前端测试+51 e2e
2026-06-13 01:06:39 +08:00

127 lines
5.5 KiB
TypeScript
Raw 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.
/**
* Property 3: 低置信触发候选确认 的属性化测试(ClassifierReq 1.4, 1.5)。
*
* 属性陈述:对任意分类结果,当某判定的 Confidence 低于 0.6(行业判定附加条件:行业标记
* 不为"未识别")时,System 必返回按 Confidence 降序排列、数量至多 3 项的候选列表并置
* 确认标志为真;否则不触发确认。
*
* 本测试以智能生成器构造任意有效项目描述(有效字符数 ≥ 10 且非纯空白,满足 Req 1.6
* 前置以避免 InsufficientInputError),其中混入业务类型与行业关键词片段,使识别打分
* 真实跨越"无命中 / 部分命中 / 多类命中"的输入空间,从而覆盖高/低置信两类分支。
*
* 对每个分类结果断言:
* 业务类型(Req 1.4):
* - 置信度 < 0.6 → needsBusinessTypeConfirm 为真,候选非空、≤3 项且按置信度降序;
* - 否则 needsBusinessTypeConfirm 为假,候选列表为空。
* 行业(Req 1.5):
* - 置信度 < 0.6 且行业 ≠ "未识别" → needsIndustryConfirm 为真,候选非空、≤3 项且降序;
* - 否则 needsIndustryConfirm 为假,候选列表为空。
*
* Feature: outsourcing-risk-assessment, Property 3: 低置信触发候选确认
* Validates: Requirements 1.4, 1.5
*/
import { describe, expect, it } from 'vitest';
import fc from 'fast-check';
import {
classify,
countValidChars,
CONFIRMATION_CONFIDENCE_THRESHOLD,
MAX_CANDIDATES,
type ScoredCandidate,
} from '../classifier.js';
import { BUSINESS_TYPE_KEYWORDS, INDUSTRY_KEYWORDS } from '../keywords.js';
import { INDUSTRY_UNRECOGNIZED } from '../../domain/common.js';
// ----------------------------------------------------------------------------
// 生成器:构造任意有效项目描述。
//
// 输入空间设计:
// - 关键词片段池汇集全部业务类型与行业关键词,使描述高概率命中识别词典,
// 覆盖单类命中、多类命中、跨业务/行业混合命中等分支(驱动高/低置信两类结果)。
// - 随机自由文本片段引入噪声与无命中情形(行业可能落到"未识别",业务类型可能低置信)。
// - 末尾补足填充字符,保证有效字符数 ≥ 10(满足 Req 1.6 前置,避免抛错)。
// ----------------------------------------------------------------------------
/** 全部识别关键词词条,作为可拼接的描述片段池。 */
const KEYWORD_TERMS: string[] = [
...Object.values(BUSINESS_TYPE_KEYWORDS).flatMap((kws) => kws.map((k) => k.term)),
...Object.values(INDUSTRY_KEYWORDS).flatMap((kws) => kws.map((k) => k.term)),
];
/** 片段:关键词词条或任意自由文本(含中英文与空白)。 */
const fragmentArb: fc.Arbitrary<string> = fc.oneof(
fc.constantFrom(...KEYWORD_TERMS),
fc.string({ minLength: 0, maxLength: 12 }),
fc.constantFrom('外包', '用工', '服务', '项目', '客户', '的', '', '。', ' '),
);
/**
* 有效项目描述:拼接若干片段后补足填充字符使有效字符数 ≥ 10。
* 保证生成的描述恒为合法输入(不触发 InsufficientInputError)。
*/
const validDescriptionArb: fc.Arbitrary<string> = fc
.array(fragmentArb, { minLength: 1, maxLength: 8 })
.map((fragments) => {
let desc = fragments.join('');
while (countValidChars(desc) < 10) {
desc += '项';
}
return desc;
});
/** 断言候选列表按置信度由高到低排序(允许相等)。 */
function expectSortedDesc<T extends string>(
candidates: ScoredCandidate<T>[],
): void {
for (let i = 1; i < candidates.length; i++) {
expect(candidates[i - 1]!.confidence).toBeGreaterThanOrEqual(
candidates[i]!.confidence,
);
}
}
describe('Property 3: 低置信触发候选确认 (Req 1.4, 1.5)', () => {
it('低置信判定返回降序、≤3 项候选并置确认标志;否则不触发确认', () => {
fc.assert(
fc.property(validDescriptionArb, (description) => {
const result = classify(description);
// --- 业务类型(Req 1.4---
const businessLow =
result.businessTypeConfidence < CONFIRMATION_CONFIDENCE_THRESHOLD;
expect(result.needsBusinessTypeConfirm).toBe(businessLow);
if (businessLow) {
// 触发确认:候选非空、至多 3 项、按置信度降序。
expect(result.businessTypeCandidates.length).toBeGreaterThan(0);
expect(result.businessTypeCandidates.length).toBeLessThanOrEqual(
MAX_CANDIDATES,
);
expectSortedDesc(result.businessTypeCandidates);
} else {
// 不触发确认:候选列表为空。
expect(result.businessTypeCandidates).toHaveLength(0);
}
// --- 行业(Req 1.5---
const industryLow =
result.industry !== INDUSTRY_UNRECOGNIZED &&
result.industryConfidence < CONFIRMATION_CONFIDENCE_THRESHOLD;
expect(result.needsIndustryConfirm).toBe(industryLow);
if (industryLow) {
// 触发确认:候选非空、至多 3 项、按置信度降序。
expect(result.industryCandidates.length).toBeGreaterThan(0);
expect(result.industryCandidates.length).toBeLessThanOrEqual(
MAX_CANDIDATES,
);
expectSortedDesc(result.industryCandidates);
} else {
// 不触发确认(含行业为"未识别"或高置信):候选列表为空。
expect(result.industryCandidates).toHaveLength(0);
}
}),
{ numRuns: 100 },
);
});
});