review-five-topic-bulk-20261111.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """Explicit article choices and verbatim evidence; discovery counts are never acceptance counts."""
  2. from copy import deepcopy
  3. from datetime import datetime, timezone
  4. import hashlib
  5. import json
  6. from pathlib import Path
  7. ROOT = Path(__file__).resolve().parents[1]
  8. WORK = ROOT / 'work/five-topic-bulk-20261111'
  9. def save(name, value):
  10. (WORK / name).write_text(json.dumps(value, ensure_ascii=False, indent=2), encoding='utf8')
  11. def main():
  12. sources = json.loads((WORK / 'collection-state.json').read_bytes())['sources']
  13. by_id = {r['id']: r for r in sources}
  14. selected = []
  15. def choose(identifier, kind, note, indexes=None, start=None, stop=None):
  16. row = deepcopy(by_id[identifier])
  17. row.update(entity='social', sourceType=kind, reviewNote=note, topics=[],
  18. contentScope='packaged_noodles', limits=[
  19. '作者或媒体的陈述,不是独立实验、产品认证或经核验的购买评价。',
  20. '保留原文日期;历史价格、销量和配方不能直接视为当前事实。'])
  21. lines = row['body'].splitlines()
  22. if start is not None:
  23. first = next(i for i, line in enumerate(lines) if start in line)
  24. last = next((i for i in range(first + 1, len(lines)) if stop and stop in lines[i]), len(lines))
  25. indexes = list(range(first, last))
  26. if indexes is not None:
  27. row['paragraphIndexes'] = indexes
  28. row['body'] = '\n'.join(lines[i] for i in indexes)
  29. row['title'] += '(相关正文摘录)'
  30. row['contentScope'] = 'packaged_noodles_excerpt'
  31. row['bodySha256'] = hashlib.sha256(row['body'].encode()).hexdigest()
  32. selected.append(row)
  33. return row
  34. def relate(row, topic, phrase, purpose):
  35. paragraph = next((p for p in row['body'].splitlines() if phrase in p), None)
  36. assert paragraph is not None, (row['id'], phrase)
  37. row['topics'].append({'topic': topic, 'relation': purpose, 'passages': [paragraph]})
  38. r = choose('smzdm:a4xnepl8', 'community_experience', '真实多口味购买、煮制耗时与口味取舍;历史到手价仅作作者自述。')
  39. relate(r, 'premium', '半筋半肉', '肉料与口感升级体验')
  40. relate(r, 'stockpile', '一次性买了多款', '多口味家庭备餐采购')
  41. relate(r, 'novelty', '冬阴功', '地域风味尝试与价格取舍')
  42. r = choose('smzdm:az3gnm0r', 'community_experience', '保留面条粘连反例、包装指引与家庭烹饪步骤;不验证宣传中的配方。')
  43. relate(r, 'premium', '头部切割的部分总是粘连', '升级速食面的面体失败体验')
  44. relate(r, 'stockpile', '家里有个北方胃', '家庭便捷备餐与组合装购买')
  45. r = choose('smzdm:aqrqkn07', 'community_experience', '保留芝士鬼椒风味和份量不足自述;健康禁忌不作为医疗建议。')
  46. relate(r, 'novelty', '双料包设计', '奶香辣味组合及自主调味')
  47. relate(r, 'student', '杯装免煮', '夜宵免煮与份量取舍;不认定作者为学生')
  48. r = choose('smzdm:az3gn7k0', 'community_experience', '藤椒口味、配色与夜班食用体验;不把包装名义重量作为商品核验规格。')
  49. relate(r, 'novelty', 'Tiffany蓝', '藤椒风味与包装辨识度')
  50. relate(r, 'student', '冬天上夜班', '夜间即食的相邻场景;不认定学生身份')
  51. r = choose('smzdm:a85g4p47', 'community_experience', '保留肉料份量不足、性价比和不再购买的负面取舍。')
  52. relate(r, 'premium', '肥牛是碎碎的几小片', '肉料兑现度和价格取舍')
  53. relate(r, 'stockpile', '不会回购', '备货复购决策反例')
  54. def industry(identifier, note, **kwargs):
  55. return choose('foodaily:' + identifier, 'industry_reporting_or_opinion', note, **kwargs)
  56. r = industry('41238', '2026年火鸡面竞争及风味案例;原文21.8/5的折算与所称4.6不一致,不能直接采用其单价。')
  57. r['limits'].append('原文报价折算存在算术不一致,保留原文,不据此生成价格观察。')
  58. relate(r, 'novelty', '麻酱火鸡面', '火鸡面细分风味与竞品')
  59. relate(r, 'student', '夜宵等消费场景', '夜宵场景产品案例')
  60. r = industry('40137', '品牌报道中的面饼克重、透明包装和饱腹诉求;营销传播数字未经独立验证。', indexes=list(range(0, 51)) + list(range(56, 61)))
  61. relate(r, 'student', '一袋根本吃不饱', '夜宵份量与饱腹研究')
  62. relate(r, 'stockpile', '家庭聚餐', '家庭大分量与采购信息透明度')
  63. r = industry('35325', '冷冻方便面的加热步骤、冰浆冷却和口感还原案例;工艺和营养效果为受访方陈述。')
  64. relate(r, 'premium', '急冻熟面技术', '冷冻面口感与工艺升级')
  65. relate(r, 'stockpile', '冷冻方便面', '冷冻备餐的储存和操作边界')
  66. relate(r, 'wellness', '猪骨的高汤', '中式骨汤及相邻健康化产品研究,非功效证明')
  67. source = by_id['foodaily:26016']
  68. indexes = [i for i, p in enumerate(source['body'].splitlines()) if p.startswith('Foodaily观察发现,鲨鱼菲特')]
  69. r = industry('26016', '仅收录荞麦方便面新品段,排除酸奶、饮料等其他产品。营养及低GI表述待独立验证。', indexes=indexes)
  70. relate(r, 'wellness', '荞麦方便面', '杂粮面体和工艺声明')
  71. relate(r, 'student', '4分钟', '冲泡耗时案例')
  72. relate(r, 'stockpile', '10包', '多包装备货案例,非当前报价')
  73. r = industry('25320', '2021年速食便利性与高端化取舍报道;不是当前行业增速或实时价格。')
  74. relate(r, 'student', '学生时代', '学习阶段与加班速食便利性')
  75. relate(r, 'premium', '高端速食面的便利性正在大打折扣', '品质、价格和煮制门槛取舍')
  76. r = industry('25165', '杯面容器兼作炊具和餐具的包装设计史;不把案例视为统一强制标准。')
  77. relate(r, 'student', '三种功能集于一身', '宿舍即食的容器与清洗负担研究')
  78. relate(r, 'novelty', '猫耳朵', '包装互动与视觉创新')
  79. r = industry('24935', '植物蛋白面体及亚洲汤底案例,作为相邻健康化研究;不是中医养生或营养声称合规证明。')
  80. relate(r, 'wellness', '南瓜籽蛋白', '相邻健康化原料与面体风味取舍')
  81. relate(r, 'premium', '植物蛋白导致面条口感的粗糙', '面体升级与汤底补偿')
  82. r = industry('23315', '2020年包装速食面品牌、研发与渠道研究;保留历史观察口径。')
  83. relate(r, 'novelty', '胡椒猪肚鸡', '地方菜式向包装面风味转化')
  84. relate(r, 'premium', '所见即所得', '高价速食的用料承诺和研发流程')
  85. relate(r, 'stockpile', '基本都是家庭消费', '家庭备餐与便利店即时食用的差别')
  86. r = industry('20968', '劲面堂面、汤、料与供应链案例;0添加及成本倍数为企业说法,不是核实的供应商报价。')
  87. relate(r, 'premium', '原汤汤底的配方调试', '汤底研发和肉料升级')
  88. relate(r, 'wellness', '天然辛香料', '中式辛香料与原汤研究,非养生功效')
  89. r = industry('20612', '仅收包装骨汤面的相关正文,排除鲜面冷藏和无关豆腐面等内容;原文健康说法未独立核实。', indexes=list(range(20, 29)))
  90. relate(r, 'wellness', '传统的养生美食', '中式汤底的海外包装化案例')
  91. r = industry('20247', '仅收冻干原理、面用配料和供应链相关段;保质期及成本倍数为历史媒体陈述。', indexes=list(range(2, 20)) + [23, 28, 29, 36])
  92. relate(r, 'premium', '一次干燥、二次干燥', '冻干配料的工艺研究线索')
  93. relate(r, 'wellness', '蔬菜牛肉', '面用蔬菜和肉类配料,非营养功效证明')
  94. relate(r, 'stockpile', '适当的贮藏至关重要', '干燥配料的贮藏控制')
  95. r = industry('19561', '番茄汤包、蛋花及面饼的历史产品案例;不当作在售规格核验。')
  96. relate(r, 'wellness', '番茄鸡蛋面', '中式熟悉食材组合')
  97. relate(r, 'novelty', '单独汤包', '单一风味强化与视觉包装')
  98. r = industry('17040', '2018年骨汤煮面、酱料拧盖、家庭装与渠道案例;不是当前销量或价格。')
  99. relate(r, 'premium', '四层复合软膜', '汤料包装和面体口感工艺')
  100. relate(r, 'wellness', '营养棒骨汤', '中式骨汤产品声明,非功效证明')
  101. relate(r, 'stockpile', '锁定家庭消费市场', '家庭整箱采购与包装单位')
  102. r = industry('15569', '2016—2017线上消费报告的历史摘要;同文157%与两倍有余存在口径歧义,不转为当前市场统计。')
  103. relate(r, 'stockpile', '5人份', '多包装备货与独立口味的历史线索')
  104. relate(r, 'novelty', '火鸡面', '地域风味的历史消费研究')
  105. r = industry('14418', '藤椒与黑白胡椒的风味及包装案例;保留2017年报道日期。')
  106. relate(r, 'novelty', '统一藤椒牛肉味面', '椒类风味竞争')
  107. r = industry('13190', '仅收AR互动与泡面计时相关段,排除财务和建厂背景;历史方案不代表功能当前可用。', indexes=list(range(0, 9)))
  108. relate(r, 'novelty', '泡面设计了拟人化形象', '风味拟人化和包装互动')
  109. r = industry('13106', '2016年美国杯面减钠和天然调味调整案例;减钠不等于低钠,不代表中国现行配方。')
  110. relate(r, 'wellness', '钠含量将会减少', '相邻健康化减钠与风味取舍')
  111. relate(r, 'student', '大学生', '历史目标人群研究')
  112. r = industry('39746', '白象辣条面、老母鸡汤及用户共创案例;区分品牌宣传和独立用户证言。')
  113. relate(r, 'novelty', '辣条、辣条浓酱', '面饼、酱包和加料的多层风味')
  114. relate(r, 'student', '年轻人深夜', '夜间速食及学生相邻人群场景')
  115. relate(r, 'wellness', '老母鸡汤', '中式慢熬汤底的产品声明,非功效证明')
  116. r = industry('15729', '2017年日本即食面产品集;仅作面体、汤底及联名风味历史案例。')
  117. relate(r, 'novelty', '柚子盐拉面', '清爽风味与店铺联名')
  118. relate(r, 'premium', '浓厚海鲜猪骨拉面', '汤底与粗面体升级')
  119. r = industry('15556', '2017年杯面与袋面风味组合案例;不验证芝麻素等营养宣传。')
  120. relate(r, 'novelty', '芥末蛋黄酱', '强风味与分量升级案例')
  121. relate(r, 'premium', '米其林一星', '名店监制的杯面案例')
  122. assert len(selected) == 25 and all(r['topics'] and len(r['body']) > 100 for r in selected)
  123. save('reviewed-selection.json', {
  124. 'schema': 'ksf.five-topic.reviewed-selection.v2', 'checkedAt': datetime.now(timezone.utc).isoformat(),
  125. 'selected': selected,
  126. 'countsByTopic': {t: sum(any(x['topic'] == t for x in r['topics']) for r in selected)
  127. for t in ['wellness', 'student', 'premium', 'stockpile', 'novelty']},
  128. 'limits': ['同一资料跨课题可关联,跨资料类型不重复计数。',
  129. '社区体验归社媒,媒体行业报道归其他研究资料;存储实体不等于资料体裁。',
  130. '相邻健康化与中式汤底均不证明中医功效,高端产品也不证明购买者为中产。',
  131. '原文只作证据来源,宣传数字和历史信息需要另行核验才能成为业务结论。']})
  132. selected_ids = {r['id'] for r in selected}
  133. save('semantic-review-rejections.json', [
  134. {'id': r['id'], 'url': r['url'], 'reason':
  135. '同一热点的多份改写不叠加采样' if r['id'].startswith('smzdm:') and '20元' in r['title'] else
  136. '未选入本轮:无关品类、重复线索、模板推广、缺乏独立依据的科普,或研究相关性不足;不计有效新增'}
  137. for r in sources if r['id'] not in selected_ids])
  138. print(json.dumps({'selected': len(selected), 'community': 5, 'industry': 20}, ensure_ascii=False))
  139. if __name__ == '__main__':
  140. main()