129 lines
7.5 KiB
JavaScript
129 lines
7.5 KiB
JavaScript
// @vitest-environment jsdom
|
||
// tests/frontend/unit/bioflow/intent.test.js —— 生信意图分流(L3)
|
||
// 与 docs/team/cases_bioflow_intent.md 冻结词典 40 用例表同表双跑
|
||
// (TC-BIO-INTENT-001~047;后端 intent.py 同词典同规则,L2 侧由 T 执行同表校验)。
|
||
import { describe, expect, it } from 'vitest';
|
||
import { route, extractReuseName, hasAnyWord } from '../../../../ui/src/utils/intent.js';
|
||
|
||
/** 词典用例表(cases_bioflow_intent.md §4):[text, attachments, activeArtifact, bioflowContext, expected] */
|
||
const DICT_CASES = [
|
||
// # text 附件 活动文档 生信上下文 期望
|
||
['帮我跑水稻 SPL 家族鉴定', false, false, false, 'bioflow'],
|
||
['跑一下拟南芥 MADS-box 家族分析', false, false, false, 'bioflow'],
|
||
['请帮我鉴定水稻 SPL 家族', false, false, false, 'bioflow'],
|
||
['识别玉米 bZIP 家族成员', false, false, false, 'bioflow'],
|
||
['帮我研究一下水稻 SPL 基因家族', false, false, false, 'bioflow'],
|
||
['分析小麦 WRKY 转录因子家族', false, false, false, 'bioflow'],
|
||
['做个大豆 NAC 家族分析', false, false, false, 'bioflow'],
|
||
['跑 SPL 家族', false, false, false, 'bioflow'],
|
||
['水稻 SPL 家族,帮我跑', false, false, false, 'bioflow'],
|
||
['基因家族鉴定:水稻 SPL', false, false, false, 'bioflow'],
|
||
['帮我跑水稻叶片 RNA-seq 差异表达分析', false, false, false, 'bioflow'],
|
||
['帮我做水稻全基因组变异检测', false, false, false, 'bioflow'],
|
||
['帮我跑水稻产量性状 GWAS 关联分析', false, false, false, 'bioflow'],
|
||
['什么是基因家族?', false, false, false, 'qa'],
|
||
['帮我跑一下', false, false, false, 'qa'],
|
||
['SPL 家族', false, false, false, 'qa'],
|
||
['今天天气不错', false, false, false, 'qa'],
|
||
['跑 SPL 家族鉴定需要准备什么输入?', false, false, false, 'qa'], // 无生信上下文漏判安全
|
||
['帮我跑一个数据分析', false, false, false, 'qa'],
|
||
['你好呀', false, false, false, 'qa'],
|
||
['', false, false, false, 'qa'], // 空字符串
|
||
['帮我跑香蕉 XYK 家族鉴定', false, false, false, 'qa'], // XYK 不在家族词典
|
||
['跑水稻 spl 家族鉴定', false, false, false, 'bioflow'], // 家族名大小写不敏感
|
||
['Please run rice SPL family identification', false, false, false, 'bioflow'],
|
||
['帮我跑(水稻 SPL 家族)鉴定!', false, false, false, 'bioflow'],
|
||
['用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定', false, false, false, 'bioflow'], // 复用意图
|
||
['根据 XXX 存档任务的方法帮我跑一下玉米 bZIP', false, false, false, 'bioflow'],
|
||
['参考 XXX 存档的方法再跑一个水稻 SPL', false, false, false, 'bioflow'],
|
||
['用《XXX》存档任务的方法帮我跑小麦 WRKY 家族', false, false, false, 'bioflow'],
|
||
['用 XXX 存档任务的方法', false, false, false, 'qa'], // 无目标任务词
|
||
['用 XXX 存档任务的方法帮我跑 yyy', false, false, false, 'qa'], // yyy 无家族/对象匹配
|
||
['帮我生成一份水稻 SPL 家族分析报告', false, false, false, 'docgen'], // docgen 优先于 bioflow
|
||
['帮我写一篇水稻基因家族研究论文', false, false, false, 'docgen'],
|
||
['导出成 PDF', false, true, false, 'export'],
|
||
['把这句话改成红色', false, true, false, 'edit'], // 无图表词
|
||
['xxx 图表颜色换成红色', false, true, false, 'bioflow'], // 图表词走 FR-BIO-019
|
||
['identify 需要什么输入', false, false, true, 'bioflow_qa'], // 不新建任务
|
||
['帮我总结一下这个问题的要点', false, false, false, 'qa'],
|
||
['写一段水稻基因的介绍', false, false, false, 'qa'], // 介绍在 docgen 排除表
|
||
['跑水稻 SPL 家族鉴定', false, true, false, 'bioflow'], // 活动文档不改判定
|
||
];
|
||
|
||
describe('intent route —— 词典 40 用例同表双跑', () => {
|
||
DICT_CASES.forEach(([text, att, artifact, ctx, expected], i) => {
|
||
const label = text ? text.slice(0, 24) : '(空字符串)';
|
||
it(`#${i + 1} 「${label}」 → ${expected}`, () => {
|
||
const { intent } = route(text, att, artifact, ctx);
|
||
expect(intent).toBe(expected);
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('intent route —— 边界与实现注意(词典 §5)', () => {
|
||
it('文本长度 ≤1 或纯空白 → qa', () => {
|
||
expect(route('a').intent).toBe('qa');
|
||
expect(route(' ').intent).toBe('qa');
|
||
expect(route(null).intent).toBe('qa');
|
||
expect(route(undefined).intent).toBe('qa');
|
||
});
|
||
it('>2000 字符按截断前 2000 判定(长前缀不淹没意图)', () => {
|
||
const long = '啊'.repeat(3000) + ' 帮我跑水稻 SPL 家族鉴定';
|
||
expect(route(long).intent).toBe('qa'); // 意图词在 2000 之后被截掉
|
||
const short = '帮我跑水稻 SPL 家族鉴定' + '啊'.repeat(3000);
|
||
expect(route(short).intent).toBe('bioflow');
|
||
});
|
||
it('bioflow_qa 仅在 has_bioflow_context=true 生效', () => {
|
||
expect(route('identify 需要什么输入', false, false, true).intent).toBe('bioflow_qa');
|
||
expect(route('identify 需要什么输入', false, false, false).intent).toBe('qa');
|
||
});
|
||
it('家族名后须跟语境词或位于句尾(SPL 会议不命中)', () => {
|
||
expect(route('跑 SPL 会议').intent).toBe('qa');
|
||
expect(route('帮我跑玉米 bZIP').intent).toBe('bioflow'); // 句尾命中
|
||
});
|
||
it('「做」动词需对象组合(分析对象词命中)', () => {
|
||
expect(route('帮我做水稻全基因组变异检测').intent).toBe('bioflow');
|
||
expect(route('帮我做个分析').intent).toBe('qa'); // 无对象词
|
||
});
|
||
it('附件 + 写类动词 → docgen(兜底信号,词典规则 4)', () => {
|
||
expect(route('帮我整理一下', true, false, false).intent).toBe('docgen');
|
||
});
|
||
it('导出需 has_active_artifact 才生效', () => {
|
||
expect(route('导出成 PDF', false, false, false).intent).toBe('qa');
|
||
});
|
||
it('命中句不打乱后续 QA 判定(无状态残留)', () => {
|
||
expect(route('什么是 SPL 家族').intent).toBe('qa');
|
||
expect(route('帮我跑水稻 SPL 家族鉴定').intent).toBe('bioflow');
|
||
expect(route('什么是 SPL 家族').intent).toBe('qa');
|
||
});
|
||
});
|
||
|
||
describe('extractReuseName —— 复用方法名提取(词典实现注意 2)', () => {
|
||
it('标准句式:方法名 = REUSE_PHRASE 之前最近的 3-20 字符片段', () => {
|
||
expect(extractReuseName('用 XXX 存档任务的方法帮我跑水稻 SPL 家族鉴定')).toBe('XXX');
|
||
expect(extractReuseName('根据 XXX 存档任务的方法帮我跑一下玉米 bZIP')).toBe('XXX');
|
||
expect(extractReuseName('参考 XXX 存档的方法再跑一个水稻 SPL')).toBe('XXX');
|
||
expect(extractReuseName('用《XXX》存档任务的方法帮我跑小麦 WRKY 家族')).toBe('XXX');
|
||
});
|
||
it('非复用句式返回 null', () => {
|
||
expect(extractReuseName('帮我跑水稻 SPL 家族鉴定')).toBeNull();
|
||
expect(extractReuseName('')).toBeNull();
|
||
});
|
||
it('片段长度不足 3 或超 20 返回 null', () => {
|
||
expect(extractReuseName('用 AB 存档任务的方法帮我跑水稻 SPL 家族鉴定')).toBeNull();
|
||
// 21 字符片段超上限
|
||
expect(extractReuseName('用 一二三四五六七八九十一二三四五六七八九十一 存档任务的方法帮我跑水稻 SPL')).toBeNull();
|
||
});
|
||
});
|
||
|
||
describe('hasAnyWord —— 子串匹配工具', () => {
|
||
it('大小写不敏感(英文词典词)', () => {
|
||
expect(hasAnyWord('Run SPL', ['run'])).toBe(true);
|
||
expect(hasAnyWord('RUN', ['run'])).toBe(true);
|
||
});
|
||
it('null/undefined 容错', () => {
|
||
expect(hasAnyWord(null, ['x'])).toBe(false);
|
||
expect(hasAnyWord(undefined, ['x'])).toBe(false);
|
||
});
|
||
});
|