record_r04.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """Archive the fourth actual reply and product comparison without altering claims."""
  2. from pathlib import Path
  3. from html.parser import HTMLParser
  4. import hashlib
  5. import json
  6. import re
  7. import shutil
  8. WORK = Path(__file__).resolve().parent
  9. def sha(path):
  10. return hashlib.sha256((WORK / path).read_bytes()).hexdigest()
  11. class Visible(HTMLParser):
  12. def __init__(self):
  13. super().__init__()
  14. self.hidden = 0
  15. self.parts = []
  16. def handle_starttag(self, tag, attrs):
  17. if tag in ('style', 'script'):
  18. self.hidden += 1
  19. def handle_endtag(self, tag):
  20. if tag in ('style', 'script'):
  21. self.hidden -= 1
  22. def handle_data(self, value):
  23. if not self.hidden:
  24. self.parts.append(value)
  25. def visible(value):
  26. parser = Visible()
  27. parser.feed(value)
  28. return re.sub(r'\s+', ' ', ''.join(parser.parts)).strip()
  29. def main():
  30. snapshot = WORK / 'internal-archive/R03-reviewed'
  31. snapshot.mkdir(parents=True, exist_ok=True)
  32. for path in ['案例样板演示.html', 'demo-build.json', 'browser-checks.json', 'case.json']:
  33. if not (snapshot / path).exists():
  34. shutil.copy2(WORK / path, snapshot / path)
  35. edits = json.loads((WORK / 'presentation-edits.json').read_bytes())
  36. edits['edits'] = [e for e in edits['edits'] if e['source'] not in {
  37. 'rounds/R04/reply-verbatim.md', 'rounds/R04/artifacts/comparison-received.html'}]
  38. def present(src, dst, replacements):
  39. value = (WORK / src).read_text(encoding='utf-8')
  40. for before, after, reason in replacements:
  41. assert value.count(before) == 1, (src, before)
  42. value = value.replace(before, after, 1)
  43. edits['edits'].append({'source': src, 'display': dst, 'before': before,
  44. 'after': after, 'reason': reason})
  45. (WORK / dst).write_text(value, encoding='utf-8')
  46. return value
  47. raw = (WORK / 'rounds/R04/reply-verbatim.md').read_text(encoding='utf-8')
  48. present('rounds/R04/reply-verbatim.md', 'rounds/R04/reply-display.md', [
  49. ('全部核验通过。新件已经进入公开看板,是 52 条中的第 1 条,标记为 S1 / owner 用户8108 / live / v1。\n\n', '', 'omit operational board receipt only'),
  50. ('对照图:https://s3.fmode.cn/user/DpPsnlBm0K/report/ksf-products-compare-r1-20261109.html', '对照图见“研究成果”中的产品对照。', 'replace implementation URL with local embedded received artifact'),
  51. (raw[raw.index('本轮无付费、无实物测试、无新增采集;产线、成'):], '', 'omit incomplete operational tail; never reconstruct missing content'),
  52. ])
  53. src = 'rounds/R04/artifacts/comparison-received.html'
  54. dst = 'rounds/R04/artifacts/comparison-display.html'
  55. atlas = (WORK / src).read_text(encoding='utf-8')
  56. assert not re.search(r'<script|<iframe|<link|<img|\bon\w+\s*=|@import|url\(', atlas, re.I)
  57. style = '''<style id="presentation-layout">
  58. body{font-size:16px}.sub,.meta-line,.glogic,table,th,td,td.pname small,.base-tag,.b,.gc>div,.gc h4,.dim,.note,.alert,.unknown-list li,.q dt,.q dd,.q h4,footer,th span,td span{font-size:15px!important}
  59. .card,.gc>div{min-width:0;overflow-wrap:anywhere}.tscroll{max-width:100%;overflow-x:auto}.dims{min-width:0;flex-wrap:wrap}.dim{white-space:normal}
  60. @media(max-width:640px){.wrap{padding:0 14px}.card{padding:17px 14px}.gc{grid-template-columns:minmax(0,1fr)}table{min-width:880px}}
  61. </style>'''
  62. replacements = [
  63. ('<code>evidence-rules.json v1.0</code>', '本次研究规则', 'remove private implementation filename only'),
  64. ('未改研究库与 Skill', '未改研究资料', 'remove operational tool identity'),
  65. ('</head>', style + '</head>', 'presentation-only readable fonts and mobile scrolling'),
  66. ]
  67. displayed = present(src, dst, replacements)
  68. expected_text = atlas
  69. for before, after, _ in replacements[:-1]:
  70. expected_text = expected_text.replace(before, after)
  71. assert visible(displayed) == visible(expected_text)
  72. edits['originalFileSha256'].update({p: sha(p) for p in [
  73. 'rounds/R04/reply-verbatim.md', 'rounds/R04/prompt-verbatim.txt', src]})
  74. edits['artifactBusinessTextPreserved'] = True
  75. (WORK / 'presentation-edits.json').write_text(json.dumps(edits, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  76. case = json.loads((WORK / 'case.json').read_bytes())
  77. fourth = next(r for r in case['rounds'] if r['roundId'] == 'R04')
  78. fourth.update({'status': 'reply-received-reviewed-incomplete-tail', 'forwardedConfirmed': None,
  79. 'executionConfirmed': True, 'replyReceived': True,
  80. 'replyPath': 'rounds/R04/reply-verbatim.md', 'replySha256': sha('rounds/R04/reply-verbatim.md'),
  81. 'reviewPath': 'rounds/R04/review.md', 'replyComplete': False,
  82. 'completeness': 'received message ends at 产线、成; no missing text reconstructed',
  83. 'executionEvidence': 'user-relayed-reply; prompt association inferred from conversation sequence',
  84. 'claimsIndependentlyVerified': False,
  85. 'reportedArtifacts': [{'artifactId': None, 'publicBoardVisible': 'reported',
  86. 'boardCount': 52, 'independentlyVerifiedHere': False}],
  87. 'receivedArtifact': {'path': src, 'presentationPath': dst,
  88. **json.loads((WORK / 'rounds/R04/artifacts/download-receipt.json').read_bytes())}})
  89. fifth = {'roundId': 'R05', 'moduleId': 'D1-03',
  90. 'purpose': '固定四款产品版本,核准包装与食用说明及评论归属',
  91. 'promptPath': 'rounds/R05/prompt-verbatim.txt', 'promptOrigin': 'authored-locally-for-user-forwarding',
  92. 'status': 'prepared-for-user-forwarding', 'forwardedConfirmed': False,
  93. 'executionConfirmed': False, 'replyReceived': False, 'reportIds': [], 'actualUserDecision': None}
  94. case['rounds'] = [r for r in case['rounds'] if r['roundId'] != 'R05'] + [fifth]
  95. case['status'] = 'R04-product-comparison-received-identity-corrections-needed-R05-prepared'
  96. case['currentModuleId'] = 'D1-03'
  97. case['moduleComplete'] = False
  98. case['courseModules'][2]['status'] = 'comparison-received-product-identity-and-evidence-unresolved'
  99. case['demonstration'].update({'actualReplies': 4, 'latestReplyComplete': False,
  100. 'status': 'four-replies-three-artifacts-awaiting-layout-check'})
  101. (WORK / 'case.json').write_text(json.dumps(case, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  102. print(json.dumps({'actualReplies': 4, 'latestReplyComplete': False, 'nextPromptExecuted': False,
  103. 'artifactBusinessTextPreserved': True}, ensure_ascii=False))
  104. if __name__ == '__main__':
  105. main()