| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899 |
- /**
- * 验证适配层产出的政策卡片字段:**同政策的多个申报事项必须能区分开**。
- *
- * 背景(2026-09-17 用户报的 bug):
- * 适配层原先把卡片的 `title` 与 `declaration_item` 都取自 `card.name.text`(只有政策名),
- * 而后端不下发 `id`/`policy_id`(实测恒为 undefined)——同政策的多个申报事项
- * 在字段上完全无法区分:详情面板标题一样、「查看详情」还会跳到同一条。
- *
- * 验证的不变量:
- * ① `declaration_item` 取后端每条唯一的 `data.title`(含申报事项后缀),同政策两条必须不同
- * ② 卡片标题 `title` 仍是政策名(`card.name.text`)—— 卡片区的观感不变
- * ③ 后端没给后缀时(单事项政策),`declaration_item` 退化为政策名,不出现空值
- *
- * 数据来源:`probe-policy-cards.mjs` 抓到的真实载荷形状(这里用合成数据,保证可重复;
- * 真实载荷每次问法不同、LLM 输出会变,不适合做断言基准)。
- *
- * 怎么跑(在项目根目录):
- * npx esbuild harness/tools/_entry-coordinator.ts --bundle --format=esm \
- * --outfile=harness/tools/_coordinator.mjs --alias:@=./src --define:import.meta.env='{}'
- * node harness/tools/verify-policy-card-fields.mjs
- */
- import { buildPolicyTableContent } from './_coordinator.mjs';
- let pass = 0;
- let fail = 0;
- const check = (name, cond, extra = '') => {
- if (cond) {
- pass++;
- console.log(' ok ' + name);
- } else {
- fail++;
- console.log(' FAIL ' + name + ' ' + extra);
- }
- };
- const POLICY_NAME = '关于进一步完善本区创业扶持政策有关事项的通知';
- /**
- * 按真实 `item` 事件的形状造数据(字段名与 probe 抓到的载荷一致)。
- * `cardName` 与 `title` 分开传:有申报事项后缀时两者不同,单事项时两者相同。
- */
- const makeItem = (title, sourceId, supportText, cardName = POLICY_NAME) => ({
- source_id: sourceId,
- title,
- knowledge_type: 'policy',
- is_policy_library: true,
- card: {
- kind: 'policy',
- name: { text: cardName, citations: [] },
- department: { text: '区人社局' },
- support_objects: { text: '对象' },
- support_methods_standards: { text: supportText },
- application_conditions: { text: '条件' },
- match_reason: { text: '原因' },
- },
- priority: { rank: 1 },
- });
- const items = [
- makeItem(`${POLICY_NAME} - 创业开办费补贴`, 'k_ff8fe476c2708c93b0d7803e', '一次性创业开办费补贴2万元,最高2万元补贴。'),
- makeItem(`${POLICY_NAME} - 青浦区级创业孵化基地的补贴和奖励`, 'k_6dc752bd7d30b42d430ca7fc', '分别给予40万/30万/20万运作经费补贴,最高40万元补贴'),
- ];
- const content = buildPolicyTableContent(items, []);
- const table = JSON.parse(content.replace(/^<!-- POLICY_TABLE /, '').replace(/ POLICY_TABLE -->$/, ''));
- const [a, b] = table.data;
- console.log('场景:同一政策下的两个申报事项\n');
- console.log('【1】详情面板的申报事项必须能区分');
- check('两条的 declaration_item 不同', a.declaration_item !== b.declaration_item, `${a.declaration_item} / ${b.declaration_item}`);
- check('第 1 条含「创业开办费补贴」', a.declaration_item === `${POLICY_NAME} - 创业开办费补贴`, a.declaration_item);
- check('第 2 条含「创业孵化基地」', b.declaration_item === `${POLICY_NAME} - 青浦区级创业孵化基地的补贴和奖励`, b.declaration_item);
- console.log('\n【2】卡片标题保持政策名不变(卡片区观感不动)');
- check('两条的 title 都是政策名', a.title === POLICY_NAME && b.title === POLICY_NAME, `${a.title}`);
- check('title 与 declaration_item 不再相同', a.title !== a.declaration_item);
- console.log('\n【3】没有后缀时退化为政策名,不出现空值');
- // 真实形态:单事项政策的 title 与 card.name.text 相同(见 probe 抓到的 item[1])
- const SOLO_NAME = '青浦区关于促进技能人才发展的实施办法';
- const single = buildPolicyTableContent([makeItem(SOLO_NAME, 'k_x', '补贴', SOLO_NAME)], []);
- const singleRow = JSON.parse(single.replace(/^<!-- POLICY_TABLE /, '').replace(/ POLICY_TABLE -->$/, '')).data[0];
- check('declaration_item 非空', !!singleRow.declaration_item, JSON.stringify(singleRow.declaration_item));
- check('无后缀时等于政策名(与 title 一致)', singleRow.declaration_item === SOLO_NAME && singleRow.title === SOLO_NAME, `${singleRow.declaration_item} / ${singleRow.title}`);
- console.log('\n【4】详情下标反查:字段可区分后能直接命中(与 resolvePolicyDetailIndex 配合)');
- // 模拟 policies 数组(normalizePolicy 之后),declaration_item 不同 → 唯一命中
- const policies = table.data.map((d) => ({ declaration_item: d.declaration_item, originalData: d }));
- const { resolvePolicyDetailIndex } = await import('./_policy-match.mjs');
- check(
- '用「同政策第 2 条」的字段反查 → 命中下标 1',
- resolvePolicyDetailIndex(policies, { originalData: { declaration_item: b.declaration_item } }) === 1,
- String(resolvePolicyDetailIndex(policies, { originalData: { declaration_item: b.declaration_item } }))
- );
- console.log(`\n===== 通过 ${pass} 项,失败 ${fail} 项 =====`);
- process.exit(fail ? 1 : 0);
|