probe-policy-cards.mjs 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /**
  2. * 探测 /api/chat 下发的政策卡片(`item` 事件)原始载荷。
  3. *
  4. * 为什么需要:用户报「两张卡片政策名相同、申报事项不同,但点『查看详情』内容一样」。
  5. * 前端 openDetailByItem 是按 `originalData.id / policy_id / declaration_item` 反查卡片索引的,
  6. * 若后端给同一政策的多个申报事项下发了**相同的 id**,findIndex 就会永远命中第一张。
  7. *
  8. * 排查协议问题必须看**原始载荷**,不能只看封装后的中间事件(踩过)。
  9. *
  10. * 用法:
  11. * node harness/tools/probe-policy-cards.mjs "我想在青浦开一家奶茶店,有什么相关扶持政策吗" [chatApiBase]
  12. * 默认 chatApiBase = http://192.168.2.23:8000(可用 dev 代理 https://localhost:8083/chat-api)
  13. */
  14. const question = process.argv[2] || '我想在青浦开一家奶茶店,有什么相关扶持政策吗';
  15. const base = (process.argv[3] || 'http://192.168.2.23:8000').replace(/\/+$/, '');
  16. const url = base.endsWith('/api/chat') ? base : `${base}/api/chat`;
  17. console.log(`POST ${url}\nquestion: ${question}\n`);
  18. const response = await fetch(url, {
  19. method: 'POST',
  20. headers: { 'Content-Type': 'application/json', Accept: 'text/event-stream' },
  21. body: JSON.stringify({ thread_id: `probe_${Date.now()}`, question }),
  22. });
  23. if (!response.ok) {
  24. console.error('HTTP', response.status, await response.text());
  25. process.exit(1);
  26. }
  27. const reader = response.body.getReader();
  28. const decoder = new TextDecoder();
  29. let buffer = '';
  30. const items = [];
  31. let currentEvent = '';
  32. while (true) {
  33. const { done, value } = await reader.read();
  34. if (done) break;
  35. buffer += decoder.decode(value, { stream: true });
  36. const lines = buffer.split('\n');
  37. buffer = lines.pop() || '';
  38. for (const raw of lines) {
  39. const line = raw.replace(/\r$/, '');
  40. if (line.startsWith('event:')) {
  41. currentEvent = line.slice(6).trim();
  42. } else if (line.startsWith('data:')) {
  43. const payload = line.slice(5).trim();
  44. if (currentEvent === 'item') {
  45. try {
  46. items.push(JSON.parse(payload));
  47. } catch {
  48. /* 分片 JSON,忽略,靠完整帧 */
  49. }
  50. }
  51. if (currentEvent === 'done' || currentEvent === 'error') {
  52. currentEvent = '';
  53. }
  54. }
  55. }
  56. }
  57. console.log(`收到 ${items.length} 个 item 事件\n`);
  58. console.log('═'.repeat(70));
  59. items.forEach((evt, i) => {
  60. const d = evt?.data ?? evt;
  61. const list = Array.isArray(d) ? d : d?.data || [];
  62. const arr = Array.isArray(list) ? list : [list];
  63. arr.forEach((it, k) => {
  64. console.log(`\n【item ${i}.${k}】`);
  65. console.log(' id :', JSON.stringify(it?.id));
  66. console.log(' policy_id :', JSON.stringify(it?.policy_id));
  67. console.log(' name/title :', JSON.stringify(it?.name ?? it?.title ?? it?.['政策名称']));
  68. console.log(' declaration :', JSON.stringify(it?.declaration_item ?? it?.['申报事项']));
  69. console.log(' 企业收益 :', JSON.stringify(it?.data?.['企业收益'] ?? it?.['企业收益']));
  70. console.log(' 顶层字段 :', Object.keys(it || {}).join(','));
  71. });
  72. });
  73. // ── 关键判据:有没有两条 item 共用同一个 id/policy_id ──────────────────
  74. const ids = [];
  75. for (const evt of items) {
  76. const d = evt?.data ?? evt;
  77. const list = Array.isArray(d) ? d : d?.data || [];
  78. for (const it of Array.isArray(list) ? list : [list]) {
  79. ids.push({ id: it?.id, policy_id: it?.policy_id, decl: it?.declaration_item ?? it?.['申报事项'] });
  80. }
  81. }
  82. const dupId = new Map();
  83. for (const x of ids) {
  84. const key = x.id ?? x.policy_id;
  85. if (key === undefined || key === null) continue;
  86. if (!dupId.has(key)) dupId.set(key, []);
  87. dupId.get(key).push(x.decl);
  88. }
  89. const clashes = [...dupId.entries()].filter(([, v]) => v.length > 1);
  90. console.log('\n' + '═'.repeat(70));
  91. console.log('关键判据 —— 是否有不同申报事项共用同一个 id:');
  92. if (!clashes.length) {
  93. console.log(' ✅ 没有:每条 item 的 id 都是独立的(问题可能出在别处)');
  94. } else {
  95. console.log(` ❌ 有 ${clashes.length} 组共用 id —— 这就是「点进去内容一样」的原因:`);
  96. clashes.forEach(([id, decls]) => console.log(` id=${id} 对应 ${decls.length} 个申报事项:${decls.join(' / ')}`));
  97. }