live-provider-normalization-smoke.js 4.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. #!/usr/bin/env node
  2. const { normalizeProviderCreators } = require('../mcp/src/features/tihao-sourcing/live-provider');
  3. const payload = {
  4. data: {
  5. data: {
  6. model: {
  7. itemList: [
  8. {
  9. id: 'xhs-real-001',
  10. nickname: '真实探店号',
  11. xiaohongshuUrl: 'https://www.xiaohongshu.com/user/profile/xhs-real-001',
  12. fans_count: '63,666',
  13. picture_price: '¥2,300',
  14. video_price: '2500元',
  15. city: '北京',
  16. attribute_datas: {
  17. image_cpm: 496.01,
  18. video_cpm: 551.09,
  19. image_cpe: 5.82,
  20. video_cpe: 6.09,
  21. commercial_order_count: 41,
  22. business_read_median: 1283,
  23. business_interaction_median: 158,
  24. business_exposure_median_30d: 11381,
  25. fan_growth_rate_30d: '50%',
  26. invite_reply_rate_48h: '98.8%',
  27. is_low_activity: false,
  28. content_theme_labels_180d: ['美食', '出行旅游'],
  29. author_thin_mid_word_association_index: ['探店', '氛围感']
  30. }
  31. },
  32. {
  33. user_id: 'xhs-real-002',
  34. name: '只给嵌套报价号',
  35. fansNum: 2988,
  36. attributeDatas: {
  37. quotation: 1800,
  38. note_cpm: '223.13',
  39. note_cpe: '13.85',
  40. city: '北京'
  41. }
  42. }
  43. ]
  44. }
  45. }
  46. }
  47. };
  48. const creators = normalizeProviderCreators(payload, 'xiaohongshu');
  49. assert(creators.length === 2, 'should normalize provider creator list');
  50. assert(creators[0].displayName === '真实探店号', 'should map display name');
  51. assert(creators[0].fansCount === 63666, 'should parse comma fan count');
  52. assert(creators[0].imagePrice === 2300, 'should parse snake_case picture price');
  53. assert(creators[0].videoPrice === 2500, 'should parse snake_case video price');
  54. assert(creators[0].minPrice === 2300, 'should derive min price from quotes');
  55. assert(creators[0].quoteStatus === '已获取报价', 'should mark quote as fetched');
  56. assert(creators[0].imageCpm === 496.01, 'should parse image CPM');
  57. assert(creators[0].videoCpm === 551.09, 'should parse video CPM');
  58. assert(creators[0].imageCpe === 5.82, 'should parse image CPE');
  59. assert(creators[0].videoCpe === 6.09, 'should parse video CPE');
  60. assert(creators[0].commercialOrderCount === 41, 'should parse commercial order count');
  61. assert(creators[0].commercialReadMedian === 1283, 'should parse business read median');
  62. assert(creators[0].commercialInteractionMedian === 158, 'should parse business interaction median');
  63. assert(creators[0].commercialExposureMedian30d === 11381, 'should parse 30d exposure median');
  64. assert(creators[0].fanGrowthRate30d === '50%', 'should keep fan growth rate');
  65. assert(creators[0].inviteReplyRate48h === '98.8%', 'should keep 48h reply rate');
  66. assert(creators[0].lowActivity === false, 'should parse low activity boolean');
  67. assert(creators[1].minPrice === 1800, 'should parse nested generic quotation as min price');
  68. assert(creators[1].imageCpm === 223.13, 'should map note CPM to image CPM');
  69. assert(creators[1].imageCpe === 13.85, 'should map note CPE to image CPE');
  70. const douyinCreators = normalizeProviderCreators({
  71. data: {
  72. model: {
  73. authors: [
  74. {
  75. sec_uid: 'MS4wLjABAAAA-real-sec-uid',
  76. user_id: '123456789',
  77. nickName: '抖音护肤号',
  78. followerCount: 51071,
  79. task_infos: [{ quote: 1500 }]
  80. }
  81. ]
  82. }
  83. }
  84. }, 'douyin');
  85. assert(douyinCreators.length === 1, 'should normalize douyin authors');
  86. assert(douyinCreators[0].platformUserId === 'MS4wLjABAAAA-real-sec-uid', 'should prefer douyin sec_uid as profile id');
  87. assert(douyinCreators[0].profileUrl === 'https://www.douyin.com/user/MS4wLjABAAAA-real-sec-uid', 'should build douyin profile url');
  88. assert(douyinCreators[0].videoPrice === 1500, 'should parse douyin task quote');
  89. console.log(JSON.stringify({ ok: true, creators: creators.length, douyinCreators: douyinCreators.length }, null, 2));
  90. function assert(condition, message) {
  91. if (!condition) throw new Error(message);
  92. }