register_round07.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. """Archive the seventh received round, review scope and local instruction changes."""
  2. from pathlib import Path
  3. import difflib
  4. import hashlib
  5. import json
  6. import re
  7. WORK = Path(__file__).resolve().parent
  8. ROOT = WORK.parents[1]
  9. def sha(path):
  10. return hashlib.sha256(path.read_bytes()).hexdigest()
  11. def main():
  12. case = json.loads((WORK / 'case.json').read_bytes())
  13. edits = json.loads((WORK / 'presentation-edits.json').read_bytes())
  14. for name, expected in edits['originalFileSha256'].items():
  15. assert sha(WORK / name) == expected, name
  16. source = 'rounds/R07/reply-verbatim.md'
  17. display = 'rounds/R07/reply-display.md'
  18. transformations = [
  19. (source, display, '线上回读校验已通过:HTTP 200、34,834 B,本地与线上 sha256 一致,关键内容全部命中。\n\n', '', 'omit operational preface'),
  20. (source, display, '清单:https://s3.fmode.cn/user/DpPsnlBm0K/report/ksf-banmian-checklist-r1-20261109.html', '清单见“研究成果”中的任务与取舍清单。', 'point to the embedded received report'),
  21. (source, display, '本轮未新增检索扩搜、未购样、未付费测试、未开展访谈、未编写回答、未改 Skill、未改研究库。\n', '', 'omit operational tail; retain explicit unexecuted interview status in business content'),
  22. ]
  23. css = '''<style id="presentation-layout">
  24. body{font-size:16px}.sub,table.g,table.g th,table.g td,.costs,.costs .c b,.lv,.ss,.qn,.ty,.flag,.qt,.qc,.qr,.qc b,.qr b,.qs,.meta,.vl,.note,.key,ol.ol li,.qs-item,.qs-item .why{font-size:15px!important}
  25. h2{font-size:21px}h3{font-size:18px}.wrap,.q,.qs-item,.note,.key{min-width:0;overflow-wrap:anywhere}.sc{max-width:100%;overflow-x:auto}table.g{min-width:1260px}table.g th,table.g td{min-width:155px;overflow-wrap:anywhere}.task{min-width:155px}.meta{color:#52635b}.qs a{overflow-wrap:anywhere}
  26. @media(max-width:640px){.wrap{padding:22px 14px 40px}.q,.qs-item{padding:16px}.key,.note{padding:16px}table.g th,table.g td{padding:12px 10px}}
  27. </style></head>'''
  28. transformations.append(('rounds/R07/artifacts/checklist-received.html', 'rounds/R07/artifacts/checklist-display.html', '</head>', css, 'presentation-only readable typography and table scrolling; claims unchanged'))
  29. received_html = (WORK / 'rounds/R07/artifacts/checklist-received.html').read_text(encoding='utf-8')
  30. for tag in dict.fromkeys(re.findall(r'<a\b[^>]*>', received_html)):
  31. if 'target=' not in tag:
  32. transformations.append(('rounds/R07/artifacts/checklist-received.html', 'rounds/R07/artifacts/checklist-display.html',
  33. tag, tag[:-1] + ' target="_blank" rel="noopener noreferrer">',
  34. 'open explicit source chips in a separate tab without replacing the embedded report'))
  35. displays = {}
  36. for source, display, before, after, reason in transformations:
  37. content = displays.get(display, (WORK / source).read_text(encoding='utf-8'))
  38. assert content.count(before) >= 1, source
  39. displays[display] = content.replace(before, after)
  40. record = {'source': source, 'display': display, 'before': before, 'after': after, 'reason': reason}
  41. if record not in edits['edits']:
  42. edits['edits'].append(record)
  43. edits['originalFileSha256'][source] = sha(WORK / source)
  44. for path, text in displays.items():
  45. (WORK / path).write_text(text, encoding='utf-8', newline='\n')
  46. edits['originalFileSha256']['rounds/R07/prompt-verbatim.txt'] = sha(WORK / 'rounds/R07/prompt-verbatim.txt')
  47. (WORK / 'presentation-edits.json').write_text(json.dumps(edits, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  48. received = json.loads((WORK / 'rounds/R07/artifacts/download-receipt.json').read_bytes())
  49. seventh = next(r for r in case['rounds'] if r['roundId'] == 'R07')
  50. seventh.update({
  51. 'status': 'received-checklist-and-interview-draft-reviewed-revision-needed',
  52. 'forwardedConfirmed': None, 'executionConfirmed': True, 'replyReceived': True,
  53. 'replyPath': 'rounds/R07/reply-verbatim.md', 'replySha256': sha(WORK / 'rounds/R07/reply-verbatim.md'),
  54. 'replyComplete': True, 'reviewPath': 'rounds/R07/review.md',
  55. 'promptExecutionMatch': 'inferred-from-conversation-sequence-not-tool-log',
  56. 'interviewsConducted': False, 'actualUserDecision': None,
  57. 'claimsIndependentlyVerified': False,
  58. 'sourceCheckScope': 'Douban full thread and Thepaper article read locally; PTT intermediary timed out; not all report sources verified',
  59. 'receivedArtifact': {
  60. 'path': 'rounds/R07/artifacts/checklist-received.html',
  61. 'presentationPath': 'rounds/R07/artifacts/checklist-display.html',
  62. 'url': received['url'], 'httpStatus': received['httpStatus'], 'bytes': received['bytes'], 'sha256': received['sha256'],
  63. 'sourceAnchorLinks': len(received['sourceLinks']), 'uniqueSourceUrls': len({x['href'] for x in received['sourceLinks']}),
  64. 'fileIndependentlyDownloaded': True,
  65. 'scope': 'received report; limited cited-source review, not upstream operations or all research claims',
  66. },
  67. })
  68. if not any(r['roundId'] == 'R08' for r in case['rounds']):
  69. case['rounds'].append({
  70. 'roundId': 'R08', 'moduleId': 'D1-04',
  71. 'purpose': '把价值比较、调味调整及制作体验转为可判别研究任务',
  72. 'promptPath': 'rounds/R08/prompt-verbatim.txt', 'promptSha256': sha(WORK / 'rounds/R08/prompt-verbatim.txt'),
  73. 'promptOrigin': 'authored-locally-for-user-forwarding',
  74. 'status': 'prepared-for-user-forwarding', 'forwardedConfirmed': False,
  75. 'executionConfirmed': False, 'replyReceived': False, 'reportIds': [], 'actualUserDecision': None,
  76. })
  77. case['status'] = 'R07-checklist-and-interview-draft-received-R08-prepared'
  78. case['currentModuleId'] = 'D1-04'
  79. case['courseModules'][3]['status'] = 'task-checklist-and-interview-draft-received-neutrality-and-priority-need-revision'
  80. case['demonstration'].update({'actualReplies': 7, 'status': 'seven-replies-six-artifacts-layout-pending', 'latestReplyComplete': True})
  81. (WORK / 'case.json').write_text(json.dumps(case, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  82. local = WORK / 'rounds/R07/local-skill-review'
  83. skill = ROOT / 'skills/ksf-workshop-runtime'
  84. baseline = json.loads((local / 'before-fingerprints.json').read_bytes())
  85. changed = [name for name, digest in baseline.items() if sha(skill / name) != digest]
  86. assert set(changed) == {'references/workshop-innovation-reasoning.md', 'references/workshop-research-tasking.md'}, changed
  87. runtime = [name for name in baseline if name.startswith('scripts/') or name == 'runtime-lock.json']
  88. assert len(runtime) == 7 and all(sha(skill / name) == baseline[name] for name in runtime)
  89. (skill / 'FILES.sha256').write_text(''.join(sha(skill / name) + ' ' + name + '\n' for name in baseline), encoding='utf-8', newline='\n')
  90. for name in changed + ['FILES.sha256']:
  91. target = local / 'diff' / (name + '.diff')
  92. target.parent.mkdir(parents=True, exist_ok=True)
  93. target.write_text(''.join(difflib.unified_diff((local / 'before' / name).read_text(encoding='utf-8').splitlines(True),
  94. (skill / name).read_text(encoding='utf-8').splitlines(True), fromfile='before/' + name, tofile='local/' + name)), encoding='utf-8')
  95. assert sha(ROOT / 'docs/workshop-skill-release-20261109-r6/ksf-workshop-skill-update-20261109-r6.zip') == 'c329432b9773cfb0a8e5d4a3e5c162b3778eab28d576b79875da6854f33c4dc5'
  96. receipt = {'ok': True, 'changedInstructions': changed, 'manifestEntries': len(baseline), 'manifestSha256': sha(skill / 'FILES.sha256'),
  97. 'runtimeChanged': False, 'remoteInstalled': False, 'sealedR6Modified': False,
  98. 'basis': 'observed leading interview questions and prioritizing the emptiest evidence row',
  99. 'changes': [{'path': name, 'before': baseline[name], 'after': sha(skill / name)} for name in changed]}
  100. (local / 'receipt.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
  101. print(json.dumps({'actualReplies': 7, 'artifacts': 6, 'nextPromptExecuted': False, 'remoteInstalled': False}, ensure_ascii=False))
  102. if __name__ == '__main__':
  103. main()