| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129 |
- /**
- * 验证「惠企政策」判定与置顶逻辑。
- *
- * 验证什么:
- * 1. 惠企政策判定——优先用接口字段 is_policy_library,缺失时回退到标题匹配,
- * 标题匹配要能容忍《》全半角等差异
- * 2. 置顶——本轮匹配到的惠企政策排在最前,其余保持库内原顺序
- *
- * 怎么跑(在项目根目录):
- * npm run build:tools # 或手工:npx esbuild <入口> --bundle ...
- * node harness/tools/verify-policy-library.mjs
- *
- * 说明:本脚本直接引用 src 里的同一份实现(policy-library-utils.ts),
- * 不是复制一份逻辑——复制会漂移,测的就不是真代码了。
- */
- import { createRequire } from 'node:module';
- import {
- normPolicyName,
- buildLibraryNameSet,
- isPolicyLibraryItem,
- collectMatchedLibraryNames,
- pinMatchedFirst,
- buildPolicyDetailUrl,
- POLICY_DETAIL_URL_BASE,
- } from './_policy-library-utils.mjs';
- const require = createRequire(import.meta.url);
- const library = require('../../public/merchant-agent/policies_public.v1.json');
- 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);
- }
- };
- console.log('惠企政策库:' + library.length + ' 条');
- const nameSet = buildLibraryNameSet(library);
- console.log('去重后政策名:' + nameSet.size + ' 个\n');
- console.log('=== 1. 名称归一化 ===');
- check(
- '《》差异可忽略',
- normPolicyName('《上海市青浦区加强知识产权保护、促进质量提升和标准体系建设的政策措施》') ===
- normPolicyName('上海市青浦区加强知识产权保护、促进质量提升和标准体系建设的政策措施')
- );
- check('括号差异可忽略', normPolicyName('政策(试行)') === normPolicyName('政策(试行)'));
- check('空白差异可忽略', normPolicyName(' 政策 A ') === normPolicyName('政策A'));
- console.log('\n=== 2. 惠企政策判定 ===');
- const sampleName = String(library[0].name).replace(/[《》]/g, '');
- check(
- '标题能在库里找到 → 是惠企政策',
- isPolicyLibraryItem({ title: sampleName }, nameSet)
- );
- check(
- '标题带《》也能找到 → 是惠企政策',
- isPolicyLibraryItem({ title: '《' + sampleName + '》' }, nameSet)
- );
- check(
- '接口字段 is_policy_library=true → 是惠企政策',
- isPolicyLibraryItem({ title: '库里没有的名字', originalData: { is_policy_library: true } }, nameSet)
- );
- check(
- '库中没有且无字段 → 不是惠企政策',
- !isPolicyLibraryItem({ title: '这个名字肯定不在惠企政策库里' }, nameSet)
- );
- check('空标题 → 不是惠企政策', !isPolicyLibraryItem({ title: '' }, nameSet));
- console.log('\n=== 3. 收集本轮匹配到的惠企政策 ===');
- const cards = [
- { title: sampleName }, // 命中
- { title: '不在库里的政策' },
- { title: '库里没有但后端标记了', originalData: { is_policy_library: true } },
- ];
- const matched = collectMatchedLibraryNames(cards, nameSet);
- check('收集到 2 条命中', matched.size === 2, '实际 ' + matched.size);
- console.log('\n=== 4. 置顶排序 ===');
- const items = [
- { title: 'A-未命中' },
- { title: sampleName }, // 命中,应置顶
- { title: 'B-未命中' },
- { title: '库里没有但后端标记了', originalData: { is_policy_library: true } }, // 命中,应置顶
- { title: 'C-未命中' },
- ];
- const ordered = pinMatchedFirst(items, matched, (i) => i.title);
- check('命中项排在最前', ordered.slice(0, 2).every((i) => matched.has(normPolicyName(i.title))), JSON.stringify(ordered.map((i) => i.title)));
- check('未命中项保持原顺序', JSON.stringify(ordered.slice(2).map((i) => i.title)) === JSON.stringify(['A-未命中', 'B-未命中', 'C-未命中']), JSON.stringify(ordered.slice(2).map((i) => i.title)));
- check('总数不变', ordered.length === items.length);
- check('无命中时顺序不变', JSON.stringify(pinMatchedFirst(items, new Set(), (i) => i.title).map((i) => i.title)) === JSON.stringify(items.map((i) => i.title)));
- console.log('\n=== 5. 真实场景抽样(库里前 3 条政策名)===');
- library.slice(0, 3).forEach((p) => {
- const t = String(p.name).replace(/[《》]/g, '');
- console.log(' ' + (isPolicyLibraryItem({ title: t }, nameSet) ? '✓' : '✗') + ' ' + t.slice(0, 40));
- });
- console.log('\n=== 6. 政策详情页地址(由 市级政策id 拼)===');
- {
- const withId = { data: { '市级政策id': '676eb30861aee302a8607104' } };
- check(
- '有 id → 拼出正确地址',
- buildPolicyDetailUrl(withId) === POLICY_DETAIL_URL_BASE + '?id=676eb30861aee302a8607104',
- buildPolicyDetailUrl(withId)
- );
- check('无 id → 空串(不猜)', buildPolicyDetailUrl({ data: {} }) === '');
- check('data 缺失 → 空串', buildPolicyDetailUrl({}) === '');
- check('null → 空串', buildPolicyDetailUrl(null) === '');
- check('id 为空白 → 空串', buildPolicyDetailUrl({ data: { '市级政策id': ' ' } }) === '');
- check('id 前后空格 → 去掉', buildPolicyDetailUrl({ data: { '市级政策id': ' abc ' } }) === POLICY_DETAIL_URL_BASE + '?id=abc');
- check('特殊字符 → 编码', buildPolicyDetailUrl({ data: { '市级政策id': 'a&b=c' } }) === POLICY_DETAIL_URL_BASE + '?id=a%26b%3Dc');
- // 真实库覆盖率
- const covered = library.filter((p) => buildPolicyDetailUrl(p)).length;
- console.log(' 真实库覆盖率: ' + covered + ' / ' + library.length);
- check('覆盖 261/262', covered === 261, '实际 ' + covered);
- const sample = library.find((p) => buildPolicyDetailUrl(p));
- console.log(' 示例: ' + buildPolicyDetailUrl(sample));
- }
- console.log('\n通过 ' + pass + ' 项,失败 ' + fail + ' 项');
- process.exit(fail ? 1 : 0);
|