package-S4-public-research.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. """Build a reviewable public-source packet. No business API calls or writes."""
  2. from datetime import datetime, timezone
  3. import hashlib
  4. import json
  5. from pathlib import Path
  6. import shutil
  7. import zipfile
  8. ROOT = Path(__file__).resolve().parents[1]
  9. CAPTURE = ROOT / 'work/S4-web-research-20261103'
  10. DOCS = Path('F:/文档/工作坊当前资料')
  11. STAGE = ROOT / 'work/S4-public-materials-20261103'
  12. ZIP = DOCS / 'ksf-S4-public-materials-20261103.zip'
  13. assert not STAGE.exists() and not ZIP.exists()
  14. STAGE.mkdir()
  15. def sha(data):
  16. return hashlib.sha256(data).hexdigest()
  17. def put(name, data):
  18. target = STAGE / name
  19. target.parent.mkdir(parents=True, exist_ok=True)
  20. with target.open('xb') as stream:
  21. stream.write(data.encode('utf-8') if isinstance(data, str) else data)
  22. result = '''# 网上测试资料核查与推进说明
  23. 核查日期:2026年11月3日,Codex实际访问公开网页、匿名浏览器查看及原图目视核对。当前测试不用先由公司提供照片。40号状态C仍保留:它只说明此前数字人机器的检查范围没有实物原件;本轮新取得网上资料不否定旧回报。41号外部补料改为可选,实物研究需要时再用。
  24. 已经取得两组直接资料,不使用搜索摘要作证据:
  25. | 材料 | 实际查看到的内容 | 定性与限制 |
  26. |---|---|---|
  27. | 苏宁公开高清图片页,商品编号12447641634 | 标题“康师傅方便面 燃魂拌面 浓浓炸酱面117g*5袋 非油炸 煮面夜宵速食五连包”;参数净含量585克、袋装;5张关联商品图 | 本次可访问的国内平台页面展示;不代表已下单、库存、实际发货规格或当前包装版本已核 |
  28. | 同页第5张单袋正面图 | “浓浓炸酱面”“煮面6-7分钟”“净含量:面饼+配料117克 面饼:90克” | 原图可读的包装标示;117克是含配料净含量,90克是图示面饼重量,均不是本轮称量值 |
  29. | 什么值得买帖子aeqxm74k,显示2023-12-18 21:20:15,作者“没事儿聊聊呗” | 开袋内容、煮制过程、包装背面照片;正文说“包装上写的煮面6-7分钟,实测写少了,煮了10分钟面才软。” | 历史作者自述和其展示的包装照片;不把作者的10分钟当成我们的试验结果或现行统一说明 |
  30. | 该帖第10张图(本包编号post-10) | 背面名“浓浓炸酱面”;制备段可读“先准备1L以上水煮沸,加入面条”“2分钟后挑散面条,4分钟放入菜包,煮面6-7分钟捞出”;有配料和营养表 | 是网上历史包装图,拍摄时间、批次及完整条码未核;不是公司持有实物。后面三张比较图属于其他商品,本交付包不纳入 |
  31. 苏宁标题117g×5与参数585克算术一致;这不证明页面图与当前仓内实物相同。帖中背面与苏宁正面都显示浓浓炸酱面及6—7分钟,但没有将两来源合成同一SKU或同批次。旧cq-01至04与本次商品的精确SKU匹配继续未知。
  32. ## 可直接使用的测试对象
  33. P1:苏宁12447641634页面所示的“燃魂拌面·浓浓炸酱面·袋装117g×5”;面饼90克与煮6—7分钟均明确写为商品图标示。P1是公开资料的对象标记,没有创建KfProduct或其他产品实体。
  34. P2:2023年帖子展示的浓浓炸酱面包装及作者的制备叙述。单独保存为历史旁证,不能与P1拼成一份完整当前SKU档案。帖中配料评价“干净”“健康”不采信为营养或安全结论;作者展示的订单截图也没有被当作已验证购买。
  35. ## 可以推进什么
  36. 当前材料足以继续公开资料支持的流程测试。已整理43号《S4公开资料桌面评估草稿》,将事实、假设、证据缺口与下一步检验分开,不必再让王刚001重新写同一份准备稿。
  37. 1. 规格信息核对:在一个真实页面内分别提取含料净含量、面饼重量与组合件数,检查标题/参数/图片是否吻合。
  38. 2. 煮制告知桌面核对:确认页面标题含“煮面”,单袋正面含时间,历史背面展示步骤;不能据此声称消费者已看懂、页面已有效提升认知。
  39. 3. 份量假设的证据与可行性评估:以明确的网页对象记录已有标示和缺口;是否吃饱、自然搭配、支付意愿等仍未测试。
  40. 实际试吃、称量、招募、页面认知测试和量产评估需要对应实物、人员或企业资料,届时分别补充;它们不是当前全部软件流程测试统一必备的前置。
  41. ## 来源与归档
  42. - 苏宁原页:https://m.suning.com/bigimages/12447641634.html
  43. - 单袋正面原图:https://imgservice.suning.cn/uimg1/b2c/image/4_-1P8wJu87-d-WFGC3YAg.jpg
  44. - 历史帖子:https://post.smzdm.com/p/aeqxm74k/
  45. - 历史背面原图:https://am.zdmimg.com/202312/18/65804034eddf17405.jpg_e1080.jpg
  46. 原始HTTP响应、浏览器DOM与可见正文、匿名截图、选定7张图片原字节、来源索引、字段表及校验清单见同目录ksf-S4-public-materials-20261103.zip。HTTP正文的编码派生件不作唯一依据;报告引用采用浏览器可见正文,原始字节保留供复核。包装标示由原图目视核,不声称OCR自动逐字认证。
  47. 实际采集时间保存在SOURCE-INDEX.json,UTC与北京时间可区分;2023年发表时间保持页面原串,不推断时区。浏览器临时档案不入包。原图未修改,联系表是另存的阅读辅助件。
  48. ## 当前系统状态
  49. 系统仍为S3,会话btrFqkz0yw。S3第2版ZW1FB5zeHr和S1/S2不动。没有调用任何workshop接口,没有入库、创建产品、独立机会卡/项目或保存S4。43号是可审阅正文,不是可执行保存JSON。
  50. 若下一步要测试系统内S4,应先明确选择一个待验证方向,并按现有合同关联独立机会卡及课堂项目,再保存项目下的评估;课堂项目不等于企业审批。当前“份量与用餐目的适配”可作为沿用S3的候选方向,不能擅自称员工已经选定。无需为了进入S4虚构试吃、评分、成本或市场结论。
  51. '''
  52. assessment = '''# S4公开资料桌面评估草稿
  53. 性质:依据真实公开资料形成的证据与验证可行性评估,尚未保存系统S4。不是试吃结果、市场机会确认或量产结论。参考既有S3第2版ZW1FB5zeHr;四篇旧引文不修改、不重导。
  54. ## 评估对象与问题
  55. 沿用S3“燃魂份量与用餐目的适配”作为候选问题:对明确的一份,在实际用餐目的下是否需要额外补主食?本次P1仅锁定苏宁12447641634页面所示浓浓炸酱面117g×5;当前实物、版本及与2023年原话对应的精确SKU未知。
  56. ## 已做的核对及结果
  57. | 评估项 | 直接依据 | 本次结果 | 仍不能判定 |
  58. |---|---|---|---|
  59. | 页面内规格一致性 | 苏宁标题117g×5,参数585克,单袋图面饼+配料117克、面饼90克 | 标题总重与参数总重算术一致,图示单份含料净含量吻合;两种重量分开记录 | 现货、实发版本、实物称量、配料包实测重量 |
  60. | 制备说明是否存在 | 苏宁单袋正面煮6—7分钟;2023年帖子背面准备1L以上水、分时挑散/放菜包/捞出 | 这两个来源中能找到煮制信息,来源与时点分开保留 | 页面首屏是否被用户注意、是否看懂;不证明绝不能泡;不代表全系列当前说明 |
  61. | 份量假设的支持程度 | 旧cq-03/_9mUml0wmh、cq-04/2bFGP7axMo两篇历史自述;本次网页图示重量 | 有历史问题线索,也有可供桌面分析的具体网页对象 | 旧帖是否同SKU、当前用户是否吃不饱、需求强度、普遍程度及支付意愿 |
  62. | 烹饪表现 | 新帖作者称自己煮10分钟才软;背面图示6—7分钟 | 只保留为历史个体叙述,不用10分钟替代包装说明 | 水量、火力、软硬偏好等未控制;不能据此判包装错误或实际工艺表现 |
  63. | 量产、成本与合规 | 未取得公司配方、产线/原料依据、报价或相应检验资料 | 无法评估,保留未知 | 不填写可量产、成本几乎为零或合规已通过 |
  64. ## 三个假设如何处理
  65. 份量方向:保留为待验证问题,本次没有证明加量必要。加菜与补主食的动机分开记录,提前补食和吃后补食都可能相关,习惯与补偿可并存。不以重量本身换算能否吃饱。
  66. 煮制告知方向:本页标题已有“煮面”,单袋图可见6—7分钟,所以下一步问题收窄为真实购买路径上的可见性与理解。包装有字不等于消费者已理解;本次没有受试者或A/B数据,也没有证明现页误导。
  67. 香辣留水方向:本次没有取得该方向具体商品的新原件,保留原假设。不得套用燃魂浓浓炸酱面的重量、时间或说明,不泛称所有香辣款均可泡。
  68. ## 判断与下一步
  69. 本次完成的是公开资料核对,可继续进行软件中的来源关联、证据审阅和阶段评估测试;实际研究结论尚不成立。无需反复寻找本机不存在的公司样品,亦无需等待用户先拍照。
  70. 若继续系统S4测试,先按既有合同将员工选定的候选方向关联课堂机会卡/项目,再关联新来源与评估报告;不能把S3的artifactType=opportunity_card当成已存在独立机会卡。不在无项目时冒报项目阶段已推进。
  71. 若以后实施份量研究,再补实际样品、获取记录和适合目标的观察方案;若先研究购买告知,补真实购买路径与观察对象。样本数、判断门槛、成本、负责人、排期未定不虚填。取得相反证据或动机混杂时调整问题;证据不足不等于已经证明无效。
  72. 本草稿不生成数值评分、KANO分类、配方、采购或招募;没有业务写入。来源详情及原件包见42号。
  73. '''
  74. put('42-网上测试资料核查与推进说明.md', result)
  75. put('43-S4公开资料桌面评估草稿.md', assessment)
  76. checks = []
  77. for name in ('suning-photo-117', 'post-label-review'):
  78. http = json.loads((CAPTURE / (name + '.capture.json')).read_text(encoding='utf-8'))
  79. assert http['status'] == 200
  80. assert sha((CAPTURE / (name + '.response.bin')).read_bytes()) == http['responseSha256']
  81. for ext in ('.capture.json', '.response.bin', '.text.txt'):
  82. put('http/' + name + ext, (CAPTURE / (name + ext)).read_bytes())
  83. for ext in ('.dom.html', '.text.txt', '.png', '.detail-0.png'):
  84. put('browser/' + name + ext, (CAPTURE / 'browser' / (name + ext)).read_bytes())
  85. checks.append({'check': name + ': HTTP response bytes match captured SHA256', 'passed': True})
  86. put('browser/observations.json', (CAPTURE / 'browser/observations.json').read_bytes())
  87. for name in ('post-method-context', 'suning-label-image'):
  88. put('browser/' + name + '.png', (CAPTURE / 'browser-details' / (name + '.png')).read_bytes())
  89. all_images = json.loads((CAPTURE / 'images/index.json').read_text(encoding='utf-8'))
  90. selected = []
  91. for item in all_images:
  92. if not (item['id'].startswith('suning-') or item['id'] in ('post-04', 'post-10')):
  93. continue
  94. data = (CAPTURE / item['file']).read_bytes()
  95. assert sha(data) == item['sha256'] and item['status'] == 200
  96. put(item['file'], data)
  97. selected.append(item)
  98. assert len(selected) == 7
  99. put('images/index.json', json.dumps(selected, ensure_ascii=False, indent=2) + '\n')
  100. put('images/suning-contact-sheet.png', (CAPTURE / 'images/suning-contact-sheet.png').read_bytes())
  101. observations = json.loads((CAPTURE / 'browser/observations.json').read_text(encoding='utf-8'))
  102. retail = next(x for x in observations if x['id'] == 'suning-photo-117')
  103. post = next(x for x in observations if x['id'] == 'post-label-review')
  104. for phrase in ('浓浓炸酱面117g*5袋', '585克', '袋装'):
  105. assert phrase in retail['text']
  106. checks.append({'check': 'Browser retail body contains: ' + phrase, 'passed': True})
  107. for phrase in ('2023-12-18 21:20:15', '没事儿聊聊呗', '包装上写的煮面6-7分钟,实测写少了,煮了10分钟面才软。'):
  108. assert phrase in post['text']
  109. checks.append({'check': 'Browser post body contains: ' + phrase, 'passed': True})
  110. checks.append({'check': 'Seven selected original image files match HTTP capture hashes', 'passed': True})
  111. index = {
  112. 'createdAtUtc': datetime.now(timezone.utc).isoformat(), 'businessApiCalls': 0,
  113. 'businessWrites': 0, 'authenticated': False, 'physicalSampleVerified': False,
  114. 'purpose': 'Public-source workflow testing and desk assessment; not physical validation',
  115. 'sources': [{k: obs[k] for k in ('id', 'requestedUrl', 'url', 'title', 'startedAt', 'observedAt', 'documents')}
  116. for obs in observations],
  117. 'images': selected,
  118. 'manualImageReadings': [
  119. {'file': 'images/suning-05.jpg', 'region': 'lower-left label',
  120. 'visibleText': '净含量:面饼+配料117克 面饼:90克',
  121. 'scope': 'Visible product image label, not measured values'},
  122. {'file': 'images/suning-05.jpg', 'region': 'middle front', 'visibleText': '煮面6-7分钟'},
  123. {'file': 'images/post-10.jpg', 'region': 'upper back preparation strip',
  124. 'visibleText': ['先准备1L以上水煮沸,加入面条', '2分钟后挑散面条,4分钟放入菜包,煮面6-7分钟捞出'],
  125. 'scope': 'Historical image; complete barcode, batch and photograph date unknown'}
  126. ],
  127. 'checks': checks,
  128. 'excluded': ['Search snippets are discovery only', 'Unrelated comparison products in post photos 11-13',
  129. 'Unverified shopping, identity and health claims', 'Browser profiles and credentials'],
  130. }
  131. put('SOURCE-INDEX.json', json.dumps(index, ensure_ascii=False, indent=2) + '\n')
  132. put('README.txt', '真实公开资料测试包,2026-11-03\n先读42号结果与43号桌面评估。未包含执行命令或保存输入,不直接import/save。\n七张选定图片保留下载原字节,索引含URL/时间/SHA256;网页和历史包装不合并SKU。\nFILES.sha256为除自身外所有文件的校验清单;没有浏览器档案、凭据或无关商品照片。\n实际系统仍S3,未调用业务接口。\n')
  133. manifest = []
  134. for file in sorted(STAGE.rglob('*')):
  135. if file.is_file():
  136. manifest.append(f'{sha(file.read_bytes())} {file.relative_to(STAGE).as_posix()}')
  137. put('FILES.sha256', '\n'.join(manifest) + '\n')
  138. with zipfile.ZipFile(ZIP, 'x', compression=zipfile.ZIP_DEFLATED, compresslevel=7) as archive:
  139. for file in sorted(STAGE.rglob('*')):
  140. if file.is_file():
  141. archive.write(file, file.relative_to(STAGE).as_posix())
  142. with zipfile.ZipFile(ZIP) as archive:
  143. assert archive.testzip() is None
  144. for row in manifest:
  145. digest, name = row.split(' ', 1)
  146. assert sha(archive.read(name)) == digest
  147. receipt = {'zip': str(ZIP), 'bytes': ZIP.stat().st_size, 'sha256': sha(ZIP.read_bytes()),
  148. 'fileCount': len(manifest) + 1, 'manifestEntriesVerified': len(manifest),
  149. 'checksPassed': len(checks), 'businessApiCalls': 0, 'businessWrites': 0}
  150. with (ROOT / 'work/S4-public-materials-20261103-receipt.json').open('x', encoding='utf-8') as stream:
  151. json.dump(receipt, stream, ensure_ascii=False, indent=2)
  152. for name in ('42-网上测试资料核查与推进说明.md', '43-S4公开资料桌面评估草稿.md'):
  153. with (DOCS / name).open('xb') as stream:
  154. stream.write((STAGE / name).read_bytes())
  155. print(json.dumps(receipt, ensure_ascii=True, indent=2))