| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106 |
- /**
- * 探测 /api/chat 下发的政策卡片(`item` 事件)原始载荷。
- *
- * 为什么需要:用户报「两张卡片政策名相同、申报事项不同,但点『查看详情』内容一样」。
- * 前端 openDetailByItem 是按 `originalData.id / policy_id / declaration_item` 反查卡片索引的,
- * 若后端给同一政策的多个申报事项下发了**相同的 id**,findIndex 就会永远命中第一张。
- *
- * 排查协议问题必须看**原始载荷**,不能只看封装后的中间事件(踩过)。
- *
- * 用法:
- * node harness/tools/probe-policy-cards.mjs "我想在青浦开一家奶茶店,有什么相关扶持政策吗" [chatApiBase]
- * 默认 chatApiBase = http://192.168.2.23:8000(可用 dev 代理 https://localhost:8083/chat-api)
- */
- const question = process.argv[2] || '我想在青浦开一家奶茶店,有什么相关扶持政策吗';
- const base = (process.argv[3] || 'http://192.168.2.23:8000').replace(/\/+$/, '');
- const url = base.endsWith('/api/chat') ? base : `${base}/api/chat`;
- console.log(`POST ${url}\nquestion: ${question}\n`);
- const response = await fetch(url, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
- body: JSON.stringify({ thread_id: `probe_${Date.now()}`, question }),
- });
- if (!response.ok) {
- console.error('HTTP', response.status, await response.text());
- process.exit(1);
- }
- const reader = response.body.getReader();
- const decoder = new TextDecoder();
- let buffer = '';
- const items = [];
- let currentEvent = '';
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- buffer += decoder.decode(value, { stream: true });
- const lines = buffer.split('\n');
- buffer = lines.pop() || '';
- for (const raw of lines) {
- const line = raw.replace(/\r$/, '');
- if (line.startsWith('event:')) {
- currentEvent = line.slice(6).trim();
- } else if (line.startsWith('data:')) {
- const payload = line.slice(5).trim();
- if (currentEvent === 'item') {
- try {
- items.push(JSON.parse(payload));
- } catch {
- /* 分片 JSON,忽略,靠完整帧 */
- }
- }
- if (currentEvent === 'done' || currentEvent === 'error') {
- currentEvent = '';
- }
- }
- }
- }
- console.log(`收到 ${items.length} 个 item 事件\n`);
- console.log('═'.repeat(70));
- items.forEach((evt, i) => {
- const d = evt?.data ?? evt;
- const list = Array.isArray(d) ? d : d?.data || [];
- const arr = Array.isArray(list) ? list : [list];
- arr.forEach((it, k) => {
- console.log(`\n【item ${i}.${k}】`);
- console.log(' id :', JSON.stringify(it?.id));
- console.log(' policy_id :', JSON.stringify(it?.policy_id));
- console.log(' name/title :', JSON.stringify(it?.name ?? it?.title ?? it?.['政策名称']));
- console.log(' declaration :', JSON.stringify(it?.declaration_item ?? it?.['申报事项']));
- console.log(' 企业收益 :', JSON.stringify(it?.data?.['企业收益'] ?? it?.['企业收益']));
- console.log(' 顶层字段 :', Object.keys(it || {}).join(','));
- });
- });
- // ── 关键判据:有没有两条 item 共用同一个 id/policy_id ──────────────────
- const ids = [];
- for (const evt of items) {
- const d = evt?.data ?? evt;
- const list = Array.isArray(d) ? d : d?.data || [];
- for (const it of Array.isArray(list) ? list : [list]) {
- ids.push({ id: it?.id, policy_id: it?.policy_id, decl: it?.declaration_item ?? it?.['申报事项'] });
- }
- }
- const dupId = new Map();
- for (const x of ids) {
- const key = x.id ?? x.policy_id;
- if (key === undefined || key === null) continue;
- if (!dupId.has(key)) dupId.set(key, []);
- dupId.get(key).push(x.decl);
- }
- const clashes = [...dupId.entries()].filter(([, v]) => v.length > 1);
- console.log('\n' + '═'.repeat(70));
- console.log('关键判据 —— 是否有不同申报事项共用同一个 id:');
- if (!clashes.length) {
- console.log(' ✅ 没有:每条 item 的 id 都是独立的(问题可能出在别处)');
- } else {
- console.log(` ❌ 有 ${clashes.length} 组共用 id —— 这就是「点进去内容一样」的原因:`);
- clashes.forEach(([id, decls]) => console.log(` id=${id} 对应 ${decls.length} 个申报事项:${decls.join(' / ')}`));
- }
|