record_r02.py 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. """Archive receipt metadata and presentation-only changes for the second reply."""
  2. from pathlib import Path
  3. import hashlib
  4. import json
  5. import re
  6. WORK = Path(__file__).resolve().parent
  7. def digest(path):
  8. return hashlib.sha256((WORK / path).read_bytes()).hexdigest()
  9. def main():
  10. edits = json.loads((WORK / 'presentation-edits.json').read_bytes())
  11. edits['edits'] = [e for e in edits['edits'] if e['source'] not in {
  12. 'rounds/R02/reply-verbatim.md', 'rounds/R02/artifacts/atlas-received.html'}]
  13. def presentation(source, destination, replacements):
  14. value = (WORK / source).read_text(encoding='utf-8')
  15. for before, after, reason in replacements:
  16. assert value.count(before) == 1, (source, before)
  17. value = value.replace(before, after, 1)
  18. edits['edits'].append({'source': source, 'display': destination,
  19. 'before': before, 'after': after, 'reason': reason})
  20. (WORK / destination).write_text(value, encoding='utf-8')
  21. original = (WORK / 'rounds/R02/reply-verbatim.md').read_text(encoding='utf-8')
  22. ops = original[original.index('已同步看板\n'):original.index('这轮我没做到的\n')]
  23. presentation('rounds/R02/reply-verbatim.md', 'rounds/R02/reply-display.md', [
  24. ('https://s3.fmode.cn/user/DpPsnlBm0K/report/ksf-atlas-s1-20261109.html**',
  25. '全景图见“研究成果”。', 'replace operational file URL with the local artifact view; source retained privately'),
  26. (ops, '', 'omit save/readback/HTTP/hash operational receipt; raw reply and claimed artifact retained privately'),
  27. ])
  28. atlas_source = 'rounds/R02/artifacts/atlas-received.html'
  29. atlas_display = 'rounds/R02/artifacts/atlas-display.html'
  30. original_atlas = (WORK / atlas_source).read_text(encoding='utf-8')
  31. assert not re.search(r'<script|<iframe|<link|<img|\bon\w+\s*=|@import|url\(', original_atlas, re.I)
  32. style = '''<style id="presentation-layout">
  33. body{font-size:16px}.sub,.lead,.row,.brands,.tag,.claim,.col h4,.reps h4,.col ul,.reps ul,.evid,table,th,td,.note,.q li,footer{font-size:15px}
  34. .cards{grid-template-columns:repeat(2,minmax(0,1fr))}.card,.col{min-width:0;overflow-wrap:anywhere}.table-scroll{max-width:100%;overflow-x:auto}table{min-width:540px}
  35. @media(max-width:820px){.cards{grid-template-columns:minmax(0,1fr)}.row b{flex-basis:auto}.row{align-items:flex-start}.wrap{padding:24px 16px 44px}}
  36. </style>'''
  37. presentation(atlas_source, atlas_display, [
  38. ('康师傅拌面品类全景 · S1 品类扫描', '康师傅拌面品类全景', 'remove internal stage label from title'),
  39. ('S1 品类扫描 · 开放探索(不锁现品、不假设产线可改造)· 数据代际 82b104fc',
  40. '开放探索(不锁现品、不假设产线可改造)', 'remove stage and internal generation from subtitle'),
  41. ('数据来源:ksf-voc-library 研究库(代际 82b104fc)', '数据来源:现有研究资料',
  42. 'remove runtime identity and internal generation; keep raw provenance privately'),
  43. ('S1 阶段初稿 · 供团队讨论,不构成新品方向结论', '初稿 · 供团队讨论,不构成新品方向结论',
  44. 'remove internal stage label from footer'),
  45. ('</head>', style + '</head>', 'presentation-only font and responsive layout adjustment'),
  46. ])
  47. atlas = (WORK / atlas_display).read_text(encoding='utf-8')
  48. wrapped = re.sub(r'(<table\b.*?</table>)', r'<div class="table-scroll">\1</div>', atlas, flags=re.S)
  49. assert wrapped != atlas
  50. (WORK / atlas_display).write_text(wrapped, encoding='utf-8')
  51. edits['edits'].append({'source': atlas_source, 'display': atlas_display,
  52. 'kind': 'wrap-each-table-in-scroll-container',
  53. 'reason': 'presentation layout only; table cell contents unchanged'})
  54. edits['originalFileSha256'] = {p: digest(p) for p in [
  55. 'rounds/R01/reply-verbatim.md', 'rounds/R02/prompt-verbatim.txt',
  56. 'rounds/R02/reply-verbatim.md', atlas_source]}
  57. (WORK / 'presentation-edits.json').write_text(json.dumps(edits, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  58. case = json.loads((WORK / 'case.json').read_bytes())
  59. second = next(r for r in case['rounds'] if r['roundId'] == 'R02')
  60. second.update({
  61. 'purpose': '开放探索,形成产品方案与任务、收益、代价的初版对照',
  62. 'status': 'reply-received-reviewed', 'executionConfirmed': True, 'replyReceived': True,
  63. 'executionEvidence': 'user-relayed-full-reply; independent query/board responses unavailable',
  64. 'replyPath': 'rounds/R02/reply-verbatim.md', 'replySha256': digest('rounds/R02/reply-verbatim.md'),
  65. 'reviewPath': 'rounds/R02/review.md', 'reportIds': ['7lX5ZGTZaO'],
  66. 'reportIdsEvidence': 'operator-reported; not independently read back here',
  67. 'claimsIndependentlyVerified': False,
  68. 'reportedArtifacts': [{'artifactId': '7lX5ZGTZaO', 'savedAndReadBack': 'reported',
  69. 'publicBoardVisible': 'reported', 'independentlyVerifiedHere': False}],
  70. 'receivedArtifact': {'path': atlas_source, 'presentationPath': atlas_display,
  71. 'url': 'https://s3.fmode.cn/user/DpPsnlBm0K/report/ksf-atlas-s1-20261109.html',
  72. 'httpStatus': 200, 'bytes': (WORK / atlas_source).stat().st_size,
  73. 'sha256': digest(atlas_source), 'fileIndependentlyDownloaded': True,
  74. 'independentVerificationScope': 'file availability, bytes and reported hash; not research claims or board operations'},
  75. })
  76. third = {'roundId': 'R03', 'moduleId': 'D1-02',
  77. 'purpose': '复核影响产品选择的主张,固定可沿用的证据与统计规则',
  78. 'promptPath': 'rounds/R03/prompt-verbatim.txt',
  79. 'promptOrigin': 'authored-locally-for-user-forwarding',
  80. 'status': 'prepared-for-user-forwarding', 'forwardedConfirmed': False,
  81. 'executionConfirmed': False, 'replyReceived': False, 'reportIds': [],
  82. 'actualUserDecision': None}
  83. case['rounds'] = [r for r in case['rounds'] if r['roundId'] != 'R03'] + [third]
  84. case['status'] = 'R02-received-key-claims-under-review-R03-prepared'
  85. case['currentModuleId'] = 'D1-02'
  86. case['moduleComplete'] = False
  87. case['courseModules'][0]['status'] = 'initial-panorama-received-decision-critical-claims-pending'
  88. case['courseModules'][1]['status'] = 'targeted-verification-prepared-not-executed'
  89. case['demonstration'].update({'status': 'two-actual-replies-and-received-panorama',
  90. 'actualReplies': 2, 'completedModules': 0,
  91. 'preserveFullDialogue': False,
  92. 'preserveFullBusinessDialogue': True,
  93. 'preserveFullRawDialoguePrivately': True})
  94. (WORK / 'case.json').write_text(json.dumps(case, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  95. print(json.dumps({'receivedReplies': 2, 'nextPrompt': 'prepared-not-forwarding-confirmed',
  96. 'rawFilesUnchanged': True}, ensure_ascii=False))
  97. if __name__ == '__main__':
  98. main()