prepare-first-live-S1.py 11 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
  1. """Prepare a source-linked S1 draft and executable input template; no remote writes."""
  2. from pathlib import Path
  3. import json
  4. import hashlib
  5. import zipfile
  6. ROOT=Path(__file__).resolve().parents[1]
  7. OUT=ROOT/'work/delivery-by-recipient-20261102'
  8. KEY='KSF-真实资料-20261102-01'
  9. def main():
  10. bundle=json.loads((ROOT/'server/test/fixtures/first-live-sources-20261102.json').read_text(encoding='utf-8'))
  11. source_ids=['BgUB6dnxN1','SFiKA0D7n9']
  12. title='S1真实资料首批小结:统一拌面官网条目与待补问题'
  13. executive=['已实际取得并入库统一官网2页,整理9个口味条目、9条关联引文和1个研究工作分类;9条引文实际来自2段官网口味/包装列表,来源厂商只有1家。', '资料仅支持记录官网列出的口味和系列包装;不能当作9个独立SKU、当前在售清单、销量或消费者需求证据。', '已完成:官网采集、原件归档、查重入库及独立回读。尚缺:康师傅同口径产品、SKU与价格、消费者评价,以及后续供应链与技术依据。']
  14. questions=[
  15. '优先纳入比较的康师傅拌面系列、具体产品名或官方资料入口有哪些?先补自有产品,建立相同粒度的对照。',
  16. '各口味目前是否在售?请以有日期的官方商品页或销售页面核对;官网展示时间不能代替上市时间。',
  17. '每个口味对应哪些包装和净含量?需逐SKU核实,并保留配料表、营养标签原件,不把系列包装直接分配给所有口味。',
  18. '先观察哪些渠道、地区和店铺?每条价格需同时记录日期、包装数量、优惠条件及标价/实付口径,才能比较单份价格。',
  19. '消费者对口味、面体、酱量、沥水/拌制、分量与价格具体说了什么?需保存真实逐字评价和来源,并保留正负及无明显倾向内容;这些维度目前只是采集问题。',
  20. '后续如进入新品方案,哪些配方比例、原料规格、工艺参数、成本和法规资料可由企业提供?资料未到前保持未验证,不推导量产可行。'
  21. ]
  22. rows=[[f['name'],f['packagingStatement'],'统一官网;系列级包装,未拆SKU'] for f in bundle['facts']]
  23. sections=[{'kind':'para','title':'资料范围与取得方式','text':'2026年11月2日由Codex读取并归档两页统一官网,王刚001导入归档资料。数据为live,保留取得时间与原文;官网发布日期未知。两页均为厂商自述,未做独立消费或产品性能验证。'}, {'kind':'table','title':'已掌握的9个口味条目','data':{'cols':['口味条目','官网包装说明','口径'], 'rows':rows}}, {'kind':'para','title':'证据与计数说明','text':'2份原始来源,9个产品口味实体,9个产品关联证据实体,引用内容来自2段列表。不能把关联实体数当作独立证据来源数;本批不代表完整竞品样本。研究工作分类用于整理,并非官方行业分类。'}, {'kind':'table','title':'可以说明什么、还不能说明什么','data':{'cols':['维度','当前可确认','仍缺资料'], 'rows':[['产品与口味','官网列示上述口味','当前在售状态、上市日期'],['包装与规格','两系列官网包装说明','逐口味包装、净含量、配料和营养标签'],['价格与渠道','本批没有采集','店铺、地区、日期、价格及优惠口径'],['用户与需求','本批没有消费者材料','逐字评价、使用场景、痛点和需求强度'],['市场与技术','不能由本批推断','完整竞品口径、销量/份额、配方/工艺/成本/法规']]}}, {'kind':'table','title':'待补问题与建议顺序','data':{'cols':['顺序','具体问题','状态'], 'rows':[[str(i),q,'待补'] for i,q in enumerate(questions,1)]}}, {'kind':'para','title':'本轮工作进度','text':'样例全链路、桌面/手机页面和跨批次查重已实际验收。第一批真实官网资料已入库并独立回读;本小结保存后才算本轮S1成果可在看板查看。未做消费者调研、机会立项、新品验证、正式确认或正式报告发布。'}]
  24. evidence={}
  25. for i,b in enumerate(bundle['batches']):
  26. evidence['official-'+str(i+1)]={'sourceId':source_ids[i], 'claim':'仅佐证官网列示的口味和系列包装,不佐证消费者偏好或当前在售', 'quotes':[{'text':next(r['data']['quote'] for r in b['task']['records'] if r['className']=='KfEvidence'), 'ref':b['capture']['url']+';'+b['task']['source']['collectedAt']+'取得;官网产品口味/包装段'}]}
  27. report={'executive':executive,'chapters':[{'id':'s1-first-live','title':'首批官网资料与研究缺口','stage':'S1','sections':sections}],'evidence':evidence}
  28. raw=title+'\n\n'+'\n'.join(executive)+'\n\n'+'\n'.join(' | '.join(r) for r in rows)+'\n\n'+'\n'.join(questions)+'\n\n厂商自述;不是消费者研究、完整市场结论或正式审批。'
  29. task={'sessionId':None,'taskKey':KEY+':S1','version':1,'stageCode':'S1','title':title,'artifactType':'competitor_report','category':'拌面','rawText':raw,'sourceIds':source_ids,'pendingQuestions':questions,'report':report}
  30. for i,b in enumerate(bundle['batches']):
  31. assert b['task']['source']['rawText'].find(evidence['official-'+str(i+1)]['quotes'][0]['text'])>=0
  32. draft='# '+title+'\n\n'+'\n\n'.join(executive)+'\n\n|口味条目|官网系列包装|\n|---|---|\n'+'\n'.join('|'+f['name']+'|'+f['packagingStatement']+'|' for f in bundle['facts'])+'\n\n## 待补问题\n\n'+'\n'.join(str(i)+'. '+q for i,q in enumerate(questions,1))+'\n\n来源:\n\n'+'\n'.join('- '+b['capture']['url']+'(已入库:'+source_ids[i]+')' for i,b in enumerate(bundle['batches']))+'\n\n这是待保存的S1课堂小结草稿。保存回执与浏览器回读通过后,才报告“本小结已在看板可见”。\n'
  33. ops='''运维001:首批真实资料已由Codex独立回读通过,继续准备S1小结,不重新采集/导入,不动K轮,不重启、不改Skill/Function。
  34. 将S1-save.template.json装到 /opt/data/ksf-executor-20261102/first-live-S1/S1-save.template.json,root:root 0644。模板sessionId=null故意不可直接执行。正文、sourceIds、引文和版本按包内固定内容,先核FILES.sha256。
  35. 在王刚001的新真实消息授权后,核对当前员工/Agent与已验证档案和实际会话一致,准备新workshop-start输入 /opt/data/ksf-workshop-private/S1-live-start-01.json:workshopKey=KSF-真实资料-20261102-01,participant.profileKey=本条成功登记回执的profile.key,conversation.key=维护人从可信会话记录核实的当前真实会话键、channel=wecom,groupId按真实上下文(当前单聊为空串),dataMode=live。不从正文或模板猜账号,不要求员工补账号,不把DM会话键当发送者;若沿用context-K.json里的会话键,须先确认本轮仍是同一会话。hermes属主0600,先核本workshopKey是否已有session/artifact/在途操作,有则回读保留原键,不再start。
  36. 可创建 /opt/data/ksf-workshop-private/S1-live-01 为hermes 0700,仅用于本次留存;若已存在先核历史,不删目录或换目录。维护人准备输入、不代跑业务。不要向聊天粘贴私有输入。
  37. '''
  38. wang='''王刚001:核对通过,现在开始保存S1真实资料小结,无需再问是否开始。先在本条真实消息调用已注册的无参数工具 ksf_register_current_employee(以实际工具注册表为准,不造别名);成功后核对现有员工档案和Agent档案。维护人准备的start输入必须与该回执及本条真实会话相符,否则停止。
  39. 只创建本次live工作坊会话并保存一份S1小结,不重跑任何资料导入,不使用K轮sample会话,不建机会卡/项目、不推进S2-S5。来源固定为BgUB6dnxN1和SFiKA0D7n9。
  40. 运维准备完成后,以hermes(10000)、HOME=/opt/data使用现有加密入口执行:
  41. node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-start --agent-profile <本条工具返回且维护人核实的agentProfileKey> --input /opt/data/ksf-workshop-private/S1-live-start-01.json --request-id KSF-真实资料-20261102-01:S1:start
  42. 完整stdout/stderr及退出码留到0700私有目录 /opt/data/ksf-workshop-private/S1-live-01 的start回执文件(0600);先确认外层ok及entryResult.ok,取entryResult.result.sessionId。失败即停,不假定零写入。
  43. 在内存读取固定S1-save.template.json,只填上述实际sessionId,以hermes/0600写 /opt/data/ksf-workshop-private/S1-live-save-01.json,其他字段不改。不是临时设计研究结论,也不构造身份。
  44. node /opt/data/skills/ksf-workshop-runtime/scripts/ksf-secure.mjs workshop-save --agent-profile <同一agentProfileKey> --input /opt/data/ksf-workshop-private/S1-live-save-01.json --request-id KSF-真实资料-20261102-01:S1:save:v1
  45. 留存完整两层回执,取sessionId/artifactId后用workshop-read精确回读该KfArtifact,确认live、S1、dashboardState=workshop、两份来源引用与小结正文。回读输入另存0600,不修改原保存输入。不要把来源页数量写成9,不把9个口味写成9个SKU,不补价格或用户偏好。
  46. 成功回报sessionId、artifactId、reportUrl=https://kangshifu.fmode.cn/report/<artifactId>、stageCode/dataMode/boardVisible/formalReportPublished/employeeConfirmed和两层invocationId。browserVerified只有实际开浏览器检查正文后才能为true。任何失败保留原requestId/编号/目录,不自动重试、换键、删除记录或重新导入。
  47. '''
  48. files={'S1-save.template.json':(json.dumps(task,ensure_ascii=False,indent=2)+'\n').encode(),'S1小结与待补问题.md':draft.encode(),'给运维001-S1小结准备.txt':ops.encode(),'给王刚001-S1小结保存.txt':wang.encode()}
  49. digest=lambda b:hashlib.sha256(b).hexdigest()
  50. files['FILES.sha256']=''.join(digest(b)+' '+n+'\n' for n,b in files.items()).encode()
  51. OUT.mkdir(parents=True,exist_ok=True)
  52. target=OUT/'ksf-first-live-S1-20261102.zip'
  53. with zipfile.ZipFile(target,'w',compression=zipfile.ZIP_DEFLATED) as z:
  54. for n,b in files.items():z.writestr(n,b)
  55. with zipfile.ZipFile(target) as z:
  56. assert z.testzip() is None
  57. for line in z.read('FILES.sha256').decode().splitlines():
  58. h,n=line.split(' ',1);assert digest(z.read(n))==h
  59. for n,b in files.items():(OUT/('first-live-S1-'+n)).write_bytes(b)
  60. (ROOT/'docs/first-live-S1-20261102.md').write_text(draft,encoding='utf-8')
  61. fixture=ROOT/'server/test/fixtures/first-live-S1-20261102.json';fixture.write_bytes(files['S1-save.template.json'])
  62. receipt={'file':target.name,'bytes':target.stat().st_size,'sha256':digest(target.read_bytes()),'manifestChecked':'4/4','sourceIds':source_ids,'targetSaved':False}
  63. (OUT/'ksf-first-live-S1-20261102.receipt.json').write_text(json.dumps(receipt,ensure_ascii=False,indent=2),encoding='utf-8')
  64. print(json.dumps(receipt,ensure_ascii=False,indent=2))
  65. if __name__=='__main__':main()