package-first-live-sources.py 7.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. from pathlib import Path
  2. import hashlib
  3. import json
  4. import zipfile
  5. ROOT=Path(__file__).resolve().parents[1]
  6. SRC=ROOT/'work/first-live-sources-20261102'
  7. DEST=ROOT/'work/delivery-by-recipient-20261102'
  8. KEY='KSF-真实资料-20261102-01'
  9. OPS='''运维001:开始第一批真实官网资料入库准备。不是重跑K轮/查重样例,不改旧目录,不升级Skill/Function,不重启。
  10. 1. 核验ZIP与FILES.sha256全部项。包内采集者为Codex:两页统一官网公开页面,取得时间、响应原始字节、解码HTML、全文和指纹均已留存。不是王刚001亲自抓取,也不是消费者调查。
  11. 2. 建 /opt/data/ksf-executor-20261102/first-live-01,放入包内 scripts/workshop-first-live-import.mjs 和同目录 scripts/first-live-batch.json,两者root:root 0644、hermes可读。保持同目录,执行器内置批次JSON的SHA-256;不修改JSON、脚本或清单。node --check脚本。
  12. 3. archives/与采集汇总.md归档到受控交付目录,保留原件与清单。包内不存在密钥或私有身份值。
  13. 4. 用context-first-live.example.json生成 /opt/data/ksf-workshop-private/context-first-live-01.json;模板agentProfileKey=null不可原样安装,仅填现有已核实Agent档案键并与registration.json核对。hermes(10000)/HOME=/opt/data写,0600;不复制员工账号/会话键,不新登记身份。
  14. 5. 以hermes建 /opt/data/ksf-workshop-private/first-live 父目录0700。检查目标 /opt/data/ksf-workshop-private/first-live/KSF-live-20261102-01 不存在,并查本任务键历史/在途;有痕迹则停下回读,不删除、换键或换目录。
  15. 6. 回报脚本指纹/语法、批次JSON指纹、输入权限和无历史后,交王刚001在新真实消息任务中执行,不由运维冒跑。
  16. 会写两份live来源,最多9条产品口味、9条证据和1个研究工作分类;以实际created/unchanged回执为准。不是9个SKU,也不保证在售。分类是明确标注的研究工作分类。9段引文来自两份官网口味/包装列表,不是9个独立来源或消费者评价。
  17. 执行只做入库与回读,不创建会话/项目/报告、不触碰sample K记录。第一批成功后才基于真实返回sourceId做S1课堂小结。
  18. '''
  19. WANG='''王刚001:待运维落地完成后,在新真实消息授权任务中,以hermes(10000)、HOME=/opt/data执行一次:
  20. node /opt/data/ksf-executor-20261102/first-live-01/workshop-first-live-import.mjs --input /opt/data/ksf-workshop-private/context-first-live-01.json --state-dir /opt/data/ksf-workshop-private/first-live/KSF-live-20261102-01 --execute
  21. 固定任务键KSF-真实资料-20261102-01。导入的是Codex已实际采集并归档的两份官网资料,dataMode=live;你执行归档资料导入,不声称自己刚在平台抓取。9个口味条目,分别是统一酱拌面4种和巧面馆拌面5种;附官网原文证据。
  22. 预期operation=first-live-official-sources-imported,2来源/9产品口味/9证据/1研究工作分类并完成逐条回读。已有相同资料可unchanged,按实际回执报计数,不为凑新增换键。不是SKU粒度,不填未知净含量/价格/消费者评价。
  23. 成功回报ok/operation/taskKey、两批sourceId、categoryId、各itemKey/className/objectId/operation及created/unchanged;保存完整两层调用回执与日志,不贴完整私有上下文。没有会话或报告是预期行为。
  24. 失败立即停:报错误码、最后一步、已返回编号,保留目录和回执;不重试、不删记录、不换键、不重跑K轮,不手工造身份。入库完成后把回执交Codex核对,再组织S1真实资料小结和待补问题。
  25. '''
  26. def main():
  27. bundle=json.loads((SRC/'batch.json').read_text(encoding='utf-8'))
  28. lines=['# 第一批真实官网资料采集汇总','', '任务键:'+KEY, '', '本批由Codex通过HTTPS读取并归档,尚未写入目标业务库。只包含公开官网产品自述,不代表消费者研究、当前在售或独立验证。', '', '## 已取得资料', '', '| 产品口味条目 | 官网包装说明(系列级) | 来源 |', '|---|---|---|']
  29. for f in bundle['facts']:
  30. lines.append('|'+f['name']+'|'+f['packagingStatement']+'|'+f['sourceUrl']+'|')
  31. lines+=['', '本批9个口味条目来自统一同一厂商的2个系列页,不是9个独立来源,也不是完整竞品样本。每个口味关联一段口味/包装列表引文;包装为系列级,不假设每种口味均有所有包装。未填写价格、净含量、配方比例、销量或消费痛点。', '', '## 来源归档']
  32. for b in bundle['batches']:
  33. lines+=['', '- '+b['capture']['url'], '- 取得时间:'+b['capture']['collectedAt'], '- 字符集:gb18030,严格解码;未知发布日期', '- 原始响应SHA-256:'+b['capture']['responseSha256'], '- 提取全文SHA-256:'+b['capture']['rawTextSha256']]
  34. lines+=['', '## 下一步需要补充的细节', '']
  35. lines += [str(i)+'. '+q for i,q in enumerate(bundle['pendingQuestions'],1)]
  36. lines+=['', '建议先完成此批入库及独立回读,再补康师傅同口径产品、包装规格/渠道价格和逐字消费者评价,保存真实S1课堂小结。不要把sample K会话改成live,不根据官网营销句生成需求已验证结论。', '', '本地工作坊测试77/77通过;目标实例导入与看板呈现仍待实际执行、回读。']
  37. report='\n'.join(lines)+'\n'
  38. files={'scripts/workshop-first-live-import.mjs':(ROOT/'scripts/workshop-first-live-import.mjs').read_bytes(), 'scripts/first-live-batch.json':(SRC/'batch.json').read_bytes(), 'context-first-live.example.json':(json.dumps({'version':'ksf.first-live-import.v1','taskKey':KEY,'agentProfileKey':None},ensure_ascii=False,indent=2)+'\n').encode(), '给运维001-真实资料落地.txt':OPS.encode(), '给王刚001-真实资料入库.txt':WANG.encode(), '采集汇总.md':report.encode()}
  39. for b in bundle['batches']:
  40. for suffix in ['response.bin','html','txt','capture.json']:
  41. p=SRC/(b['key']+'.'+suffix)
  42. files['archives/'+p.name]=p.read_bytes()
  43. sha=lambda b:hashlib.sha256(b).hexdigest()
  44. files['FILES.sha256']=''.join(sha(b)+' '+n+'\n' for n,b in files.items()).encode()
  45. DEST.mkdir(parents=True,exist_ok=True)
  46. target=DEST/'ksf-first-live-sources-20261102.zip'
  47. with zipfile.ZipFile(target,'w',compression=zipfile.ZIP_DEFLATED) as z:
  48. for n,b in files.items():z.writestr(n,b)
  49. with zipfile.ZipFile(target) as z:
  50. assert z.testzip() is None
  51. for line in z.read('FILES.sha256').decode().splitlines():
  52. expected,n=line.split(' ',1)
  53. assert sha(z.read(n))==expected
  54. assert sha(z.read('scripts/first-live-batch.json'))=='0118c861ae9a7da5e4d361497050200a2e4e595c7a2d76c733a22970cd179326'
  55. for n in ['给运维001-真实资料落地.txt','给王刚001-真实资料入库.txt','采集汇总.md']:(DEST/n).write_bytes(files[n])
  56. (ROOT/'docs/first-live-sources-20261102.md').write_text(report,encoding='utf-8')
  57. receipt={'file':target.name,'bytes':target.stat().st_size,'sha256':sha(target.read_bytes()),'manifestChecked':str(len(files)-1)+'/'+str(len(files)-1),'executorSha256':sha(files['scripts/workshop-first-live-import.mjs']),'batchSha256':sha(files['scripts/first-live-batch.json']),'sourcePages':2,'productFlavorEntries':9,'databaseWrites':0,'targetExecuted':False}
  58. (DEST/'ksf-first-live-sources-20261102.receipt.json').write_text(json.dumps(receipt,ensure_ascii=False,indent=2),encoding='utf-8')
  59. print(json.dumps(receipt,ensure_ascii=False,indent=2))
  60. if __name__=='__main__':main()