industry-workshop.test.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import test from 'node:test';
  2. import assert from 'node:assert/strict';
  3. import { workshopQuery, workshopPatch, workshopBrief, workshopResultView, WORKSHOP_RESULT_TABS } from '../src/core/industry-workshop.js';
  4. test('结果分页默认商品,保留旧版原评与市场链接的实际目的地', () => {
  5. assert.equal(workshopResultView({}), 'products');
  6. assert.equal(workshopResultView({w_view:'summary'}), 'products');
  7. assert.equal(workshopResultView({w_view:'missing'}), 'products');
  8. assert.equal(workshopResultView({w_view:'evidence'}), 'reviews');
  9. assert.equal(workshopResultView({w_view:'evidence',w_data:'products'}), 'products');
  10. assert.equal(workshopResultView({w_view:'market',w_market:'people'}), 'pains');
  11. assert.equal(workshopResultView({w_view:'market',w_market:'trend'}), 'trend');
  12. for (const [id] of WORKSHOP_RESULT_TABS) assert.equal(workshopResultView({w_view:id}), id);
  13. assert.equal(workshopQuery({w_view:'pains',w_brands:'2'}).theme,'');
  14. });
  15. test('研究多选在组内取或、清除时移除限定,并重置商品与原评页码', () => {
  16. const q = { w_format: 'bag', w_flavor: 'spicy,sour', w_products: '3' };
  17. assert.deepEqual(workshopQuery(q).format, ['bag']);
  18. assert.deepEqual(workshopQuery(q).flavor, ['spicy','sour']);
  19. assert.equal(workshopPatch(q,'format','bowl').w_format,'bag,bowl');
  20. assert.equal(workshopPatch(q,'format','bag').w_format,'');
  21. assert.equal(workshopPatch(q,'flavor','').w_flavor,'');
  22. assert.equal(workshopPatch(q,'flavor','local').w_products,'');
  23. assert.equal(workshopQuery({w_reviews:'2',w_product:'jd:1',w_theme:'seasoning',w_kind:'problem'}).reviewPage,2);
  24. assert.deepEqual(workshopQuery({w_nutrition:'nonfried,protein',w_portion:'small',w_need:'value'}).nutrition,['nonfried','protein']);
  25. assert.deepEqual(workshopQuery({w_nutrition:'nonfried',w_portion:'small',w_need:'value'}).need,['value']);
  26. assert.equal(workshopPatch({w_nutrition:'nonfried'},'nutrition','protein').w_nutrition,'nonfried,protein');
  27. });
  28. test('分析复制保留版本、选项、完整引用,重复证据不反复添加', () => {
  29. const review={reviewId:'jd:1:r1',productId:'jd:1',sourceRecordId:'source-1'};
  30. const d={generation:'g1',rulesVersion:'v1',choices:{format:['bag']},scope:{products:1,reviewRows:2,evidenceUnits:1,eligibleRows:0},products:{rows:[{productId:'jd:1',title:'拌面'}]},reviewEvidence:{rows:[review]},themes:[{name:'口味',types:{problem:1,benefit:0,wish:0},qualifiedUnits:0}],opportunities:[{title:'调味控制',supportUnits:1,qualifiedUnits:0,action:'核原件',support:[review]}],motives:[],audiences:[],scenarios:[],trend:{note:'暂无趋势',noteTimeline:'不是销量'}};
  31. const text=workshopBrief(d,{category:'banmian'});
  32. const refs=text.split('\n').filter(s=>s.startsWith('{')).map(s=>JSON.parse(s));
  33. assert.equal(refs.length,2);
  34. assert.equal(refs.find(r=>r.entity==='reviews').id,'jd:1:r1');
  35. assert.equal(refs.find(r=>r.entity==='products').title,'拌面');
  36. assert.match(text,/generation:g1/);assert.match(text,/"format":\["bag"\]/);
  37. assert.match(text,/非全量导出/);assert.doesNotMatch(text,/jd:jd:/);
  38. assert.throws(()=>workshopBrief({}),/UNAVAILABLE/);
  39. });
  40. test('复制保留关键词、原文筛选和重复关联编号,不把其他关联的来源冒充主来源', () => {
  41. const review={reviewId:'jd:1:a',productId:'jd:1',sourceRecordId:'source-a',associationIds:['jd:1:a','jd:2:a'],content:'太辣,希望分次投料',appendContent:'少放一些合适',analysisEligible:false};
  42. const d={generation:'g1',rulesVersion:'v1',choices:{},filterPolicy:{keyword:'小龙虾',reviewSelection:{theme:'seasoning',kind:'problem'}},scope:{products:2,reviewRows:2,evidenceUnits:1,eligibleRows:0},products:{rows:[]},reviewEvidence:{rows:[review]},themes:[],opportunities:[],motives:[],audiences:[],scenarios:[],trend:{note:'不能判断市场增长',noteTimeline:'不是销量'},prices:{groups:[{date:'2026-11-01',currency:'CNY',basis:'listed',minMinor:1000,maxMinor:1000,products:1,conflicts:0,examples:[{recordId:'price-1',productId:'jd:1',title:'拌面'}]}]}};
  43. d.claims=[{label:'香辣',products:1,examples:['jd:30']}];
  44. const text=workshopBrief(d);
  45. const refs=text.split('\n').filter(s=>s.startsWith('{')&&s.includes('"database"')).map(s=>JSON.parse(s));
  46. assert.match(text,/商品关键词:小龙虾/);assert.match(text,/"theme":"seasoning"/);
  47. assert.match(text,/太辣,希望分次投料/);assert.match(text,/少放一些合适/);
  48. assert.equal(refs.find(r=>r.id==='jd:1:a').source_record_id,'source-a');
  49. assert.equal(refs.find(r=>r.id==='jd:2:a').source_record_id,undefined);
  50. assert.equal(refs.find(r=>r.id==='price-1').productId,'jd:1');
  51. assert.ok(refs.some(r=>r.entity==='products'&&r.id==='jd:30'));
  52. });
  53. test('复制决策包含业务假设、验证条件、优势与不在当前页的依据和竞品编号', () => {
  54. const r={reviewId:'jd:9:r',productId:'jd:9',sourceRecordId:'src-r',associationIds:['jd:9:r','jd:10:r'],content:'太辣',analysisEligible:false};
  55. const strength={reviewId:'jd:8:b',productId:'jd:8',sourceRecordId:'src-b',content:'面条筋道',analysisEligible:true};
  56. const o={name:'调味更可控',status:'先核关键线索',supportUnits:1,products:2,qualifiedUnits:0,pendingUnits:1,adjustmentUnits:0,concept:'比较分次投料',businessValue:'降低尝试门槛',test:{method:'核说明后对照',measure:'辣咸接受度',decisionRule:'先改指引再研究配方'},preserve:'保留特色风味',unknown:'实际投料量未知',representatives:[{productId:'jd:9',title:'拌面9'}],supply:[{label:'香辣',products:1,denominator:20,examples:[{productId:'jd:20',title:'香辣面'}]}],evidenceIds:['jd:9:r'],evidence:[r],benefits:[],adjustments:[],segments:[]};
  57. const d={generation:'g2',rulesVersion:'v2',choices:{},scope:{products:20,reviewRows:30,evidenceUnits:18,eligibleRows:4},products:{rows:[]},reviewEvidence:{rows:[]},themes:[],opportunities:[],motives:[],audiences:[],scenarios:[],trend:{note:'不能估市场增长',noteTimeline:'非销量'},decisions:{headline:'优先研究:调味更可控',reason:'先核依据',ordering:'仅为研究顺序',directions:[o],strengths:[{name:'口感',units:10,qualifiedUnits:1,preserve:'保留筋道',examples:[strength]}],brandThemes:[{name:'调味',brands:[{brand:'ksf',supportUnits:1,qualifiedSupportUnits:0,benefitUnits:2}]}],limits:['业务作用待验证']}};
  58. d.claims=[{label:'香辣',products:1,examples:['jd:30']}];
  59. const text=workshopBrief(d);
  60. d.decisions.eligibleUnits=3;d.scope.reviewedProducts=5;
  61. assert.match(workshopBrief(d),/评价覆盖:5\/20件/);
  62. assert.match(workshopBrief(d),/去重可读依据:3组/);
  63. assert.match(text,/本轮产品判断\n优先研究/);
  64. assert.match(text,/预期业务作用(待验证):降低尝试门槛/);
  65. assert.match(text,/决策条件:先改指引再研究配方/);
  66. assert.match(text,/合格0组、待确认1组/);
  67. assert.match(text,/保留筋道/);assert.match(text,/太辣/);
  68. assert.match(text,/不能证明已解决问题或市场空白/);
  69. const refs=text.split('\n').filter(s=>s.startsWith('{')&&s.includes('"database"')).map(s=>JSON.parse(s));
  70. for(const id of ['jd:9','jd:20','jd:9:r','jd:10:r','jd:8','jd:8:b','jd:30'])assert.ok(refs.some(r=>r.id===id),id);
  71. assert.equal(refs.find(r=>r.id==='jd:9:r').source_record_id,'src-r');
  72. assert.equal(refs.find(r=>r.id==='jd:10:r').source_record_id,undefined);
  73. });