"""Render the actual local rehearsal archive; no generated text is a real employee chat.""" import csv import hashlib import html import json import re import shutil from decimal import Decimal from pathlib import Path ROOT = Path(__file__).resolve().parents[1] BASE = ROOT/'docs/workshop-self-run-20261110' FINAL = ROOT/'docs/workshop-validated-final-20261110' sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest() dump = lambda p,x:p.write_text(json.dumps(x,ensure_ascii=False,indent=2)+'\n',encoding='utf-8') def inline(s): s = html.escape(s) s = re.sub(r'`([^`]+)`',r'\1',s) s = re.sub(r'\[([^\]]+)\]\(([^)]+)\)',r'\1',s) return re.sub(r'\*\*([^*]+)\*\*',r'\1',s) def markdown(text): """The limited Markdown used by these controlled reports: headings, tables, lists, quotes, fences.""" lines=text.splitlines(); result=[];i=0 while i'+html.escape('\n'.join(buf))+'');i+=1;continue m=re.match(r'^(#{1,6}) (.+)',line) if m: n=min(len(m[1])+1,6) key=re.match(r'([EPS]\d{2})[| |]',m[2]) attr=' id="'+key[1]+'"' if key else '' result.append(f''+inline(m[2])+f'');i+=1;continue if line.startswith('|') and i+1'+''.join('' for c in cells(line))+'');i+=2 while i'+''.join('' for c in cells(lines[i]))+'');i+=1 result.append('
'+c+'
'+c+'
');continue if re.match(r'^[-*] |^\d+\. ',line): ordered=bool(re.match(r'^\d+\. ',line)); tag='ol' if ordered else 'ul';result.append('<'+tag+'>') pattern=r'^\d+\. ' if ordered else r'^[-*] ' while i'+inline(re.sub(pattern,'',lines[i]))+'');i+=1 result.append('');continue if line.startswith('> '): buf=[] while i '):buf.append(lines[i][2:]);i+=1 result.append('
'+inline('\n'.join(buf)).replace('\n','
')+'
');continue result.append('

'+inline(line)+'

');i+=1 return '\n'.join(result) CSS='''*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;background:#f5f4f1;color:#263530;font:16px/1.85 "Segoe UI","Microsoft YaHei",sans-serif}a{color:#934534;text-underline-offset:3px}a:hover{color:#b52632}button{font:inherit;cursor:pointer}header{border-bottom:1px solid #e0e0d8;padding:26px 34px;background:#fff}header small{letter-spacing:.1em;color:#797e75}h1{font-size:30px;line-height:1.4;margin:8px 0 12px}h2{font-size:24px;line-height:1.5}h3{font-size:21px;margin:26px 0 12px}h4{font-size:18px;margin:23px 0 9px}h5{font-size:16px}p{margin:9px 0}main{max-width:1480px;margin:auto;padding:24px 30px;display:grid;grid-template-columns:240px minmax(0,1fr);gap:28px}nav{position:sticky;top:16px;align-self:start;max-height:95vh;overflow:auto}nav a{display:block;padding:7px 10px;text-decoration:none;font-size:14px;border-radius:5px}nav a:hover{background:#e9e5de}nav b{display:block;color:#7b8179;font-size:12px;padding:17px 10px 5px}.reading{min-width:0}article,.summary,.resources{background:#fff;border:1px solid #e1e0d9;border-radius:12px;padding:26px 30px;margin:0 0 24px;min-width:0;scroll-margin-top:18px}.label{font-size:12px;letter-spacing:.07em;color:#7f867c}.decision{font-size:20px;border-left:4px solid #8a3a2e;padding:10px 18px;background:#faf4ef;margin:16px 0}.notice{font-size:14px;color:#5f6c63}.paths,.cards{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.cards a{padding:14px;border:1px solid #dce1d7;border-radius:8px;text-decoration:none;background:#f8faf6;font-size:14px}.paths div{padding:12px;border-top:2px solid #b5bdae;background:#f4f6f2;font-size:14px}.table-wrap{max-width:100%;overflow-x:auto;margin:14px 0}table{border-collapse:collapse;width:100%;font-size:14px;line-height:1.8;min-width:560px}td,th{text-align:left;vertical-align:top;padding:11px 13px;border-bottom:1px solid #e2e4dd}th{background:#f2f4ee;font-weight:600}td{min-width:85px}td:first-child{font-weight:500}blockquote{margin:16px 0;padding:10px 18px;background:#f4f7f1;border-left:3px solid #829774;color:#3c513d}code{font:13px/1.8 Consolas,"Microsoft YaHei",monospace;background:#f2f3ef;padding:2px 5px;overflow-wrap:anywhere}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#f4f5f0;padding:16px;border:1px solid #e1e4da;border-radius:7px;font:14px/1.85 Consolas,"Microsoft YaHei",monospace}details{border-top:1px solid #e2e4dc;padding-top:15px;margin-top:24px}summary{cursor:pointer;color:#805144;font-weight:600}.dialogue{background:#fafaf7;border-radius:8px;padding:15px 18px;margin-top:15px}.tools{display:flex;gap:10px;flex-wrap:wrap;margin:16px 0}.tools a,.tools button,.copy{padding:7px 12px;border:1px solid #d6dbcf;border-radius:6px;background:#fff;text-decoration:none;font-size:13px;color:#535f50}.copy{float:right}ul,ol{padding-left:24px}li{margin:7px 0}.muted{color:#7d847b;font-size:13px}.stage-line{display:flex;gap:8px;flex-wrap:wrap;margin:15px 0}.stage-line span{padding:7px 12px;background:#edf2e8;border-radius:5px;font-size:13px}.source-ref{font-size:.85em;color:#7f4938}footer{font-size:13px;color:#737d70;padding:20px 34px}section,article{overflow-wrap:anywhere}a:focus-visible,button:focus-visible,summary:focus-visible{outline:3px solid #a65f3e;outline-offset:3px}@media(max-width:850px){main{grid-template-columns:1fr;padding:16px;gap:12px}nav{position:static;max-height:230px;border:1px solid #dfe2d7;border-radius:8px;padding:8px;column-count:2;background:#f9faf6}nav b{break-after:avoid}.paths,.cards{grid-template-columns:1fr 1fr}header{padding:22px 20px}article,.summary,.resources{padding:22px 20px}h1{font-size:25px}}@media(max-width:480px){main{padding:10px}header{padding:19px 17px}article,.summary,.resources{padding:19px 16px}.paths{grid-template-columns:1fr}.cards{gap:8px}.cards a{padding:10px;font-size:13px}h2{font-size:22px}h3{font-size:19px}.decision{font-size:18px;padding:8px 12px}table{font-size:14px}pre{font-size:14px;padding:12px}nav a{font-size:12px;padding:5px}body{line-height:1.85}}@media print{nav,.tools,.copy{display:none}main{display:block;padding:0}article,.summary{border:0;break-inside:auto}details{display:block}details>*{display:block}table{min-width:0}body{background:white}header{padding:12px}}''' def full_prompt(data, s): return s.get('prompt') or f"{data['common']}\n\n本节要回答:{s['question']}\n\n先复用:{s['inputs']}\n执行细则:{s['skillReference']}\n\n本节任务:\n{s['task']}\n\n本节应交付:\n"+'\n'.join('• '+v for v in s['deliverables']) def render_run(folder, title, manual, extra=''): data=json.loads(manual.read_bytes()); chapters=sorted(folder.glob('[0-1][0-9]-*.md')) assert len(chapters)==15 dialogue=['# '+title+'|完整提示词与自演对话','','记录性质:同一助手分饰研究助手和模拟学员,依据真实只读资料整理的自演记录;不是实际员工聊天、独立模型调用轨迹或真实讲师评审。原始工具请求/时间/响应另见执行证据。','','## 使用版本','',data['release']['contentVersion'],''] turns=[];nav=[];articles=[];coverage=[] for s,chapter in zip(data['steps'],chapters): text=chapter.read_text('utf-8');prompt=full_prompt(data,s) talk=text.split('## 自演对话\n',1)[1].split('## 实质产出\n',1)[0].strip() output=text.split('## 实质产出\n',1)[1] key='s-'+s['id'];nav.append(''+html.escape(s['number']+' '+s['title'])+'') dialogue += ['## '+s['number']+'|'+s['title'],'','### 发起提示词(手册原文)','','```text',prompt,'```','','### 自演对话','',talk,'','### 产出正文','',f'见 [{chapter.name}]({chapter.name})。','',output,''] turns.append({'stepId':s['id'],'prompt':prompt,'promptSha256':hashlib.sha256(prompt.encode()).hexdigest(),'simulatedDialogue':talk,'outputFile':chapter.name,'outputSha256':sha(chapter),'simulation':True}) refs=sorted(set(re.findall(r'\b[EPS]\d{2}\b',output))) source_links='

证据快查:'+ ' · '.join(''+v+'' for v in refs)+'

' if refs else '' articles.append(f'
{html.escape(s.get("module","开始之前"))}

{html.escape(s["number"]+" · "+s["title"])}

'+markdown(output)+source_links+f'
完整提示词与自演对话
'+html.escape(prompt)+'
'+markdown(talk)+'
') coverage.append({'stepId':s['id'],'title':s['title'],'file':chapter.name,'contentPresent':bool(output.strip()),'externalAcceptance':'not-claimed'}) (folder/'完整对话记录.md').write_text('\n'.join(dialogue),encoding='utf-8') dump(folder/'对话与产出.json',{'notice':'one-assistant self-roleplay; not actual employee dialogue','steps':turns}) dump(folder/'环节覆盖.json',{'contentSteps':coverage,'businessSaved':False,'hostLoadingVerified':False}) seven=[('市场报告','s-tasks'),('社媒场景报告','s-voc'),('用户/竞品证言池','s-voc'),('三源研究卡','s-opportunities'),('论证清单V1–V3','s-feasibility'),('场景买点方案','s-value'),('产品概念提案','s-proposal')] summary='''
本次研究得到什么

现品已有人认可便利,先证明值得改的一步

建议先观察现售碗装在目标场景中的实际使用;只有发现可复现的信息理解问题,再比较信息原型。当前依据不支持直接选择新沥水结构。

课题:宿舍夜宵拌面的体验改良。研究设定为有开水、不用锅,保留酱香与面条口感。真实资料包括7款商品、49条拌面评价和10条定向社媒;业务条件与学员回应为模拟。

供需与竞品证言与反例候选全池条件取舍概念与停止条件
真实执行
隔离安装、只读资料回读、逐条编码、计算与页面检查。
本地自演
同一助手扮演学员与研究助手;完整提示词和15节产出可查。
本轮未验证
真实人员任务/感官测试、宿主自动加载、生产保存及小组归属。

七类成果直达

'''+''.join(''+name+' ↗' for name,key in seven)+'
' resources='

完整记录与底表

E/P/S与O1–O3均为本地引用键,不是生产业务编号。资料来自固定正式代际;本轮未重哈希服务器原始ZIP,也未观看视频。

' script='''document.querySelectorAll('[data-copy]').forEach(b=>b.onclick=async()=>{const t=document.getElementById(b.dataset.copy).textContent;try{await navigator.clipboard.writeText(t);b.textContent='已复制'}catch{const a=document.createElement('textarea');a.value=t;document.body.append(a);a.select();document.execCommand('copy');a.remove();b.textContent='已复制'}});document.getElementById('collapse').onclick=()=>document.querySelectorAll('article details').forEach(d=>d.open=false);document.getElementById('expand').onclick=()=>document.querySelectorAll('article details').forEach(d=>d.open=true);''' page=''+html.escape(title)+'
康师傅 · 产品创新工作坊 / 本地演练

'+html.escape(title)+'

同一助手自演,资料真实,学员回应和业务条件模拟;不是豆包或真实员工验收。

'+extra+'
'+summary+''.join(articles)+resources+'
15节内容推演完成与业务端到端验收分别记录;全部文件仅保留本地。
' (folder/'index.html').write_text(page,encoding='utf-8') if (folder/'证据索引.md').exists(): evidence_html=markdown((folder/'证据索引.md').read_text('utf-8')) evidence_html=re.sub(r'(P\d{2})',r'\1',evidence_html) (folder/'证据索引.html').write_text('真实原话与来源
返回演练'+evidence_html+'
',encoding='utf-8') def shared_tables(): pool={} stages={ 'V1': [('O1','研究线索/待证信息设想','先观察有无问题,有问题才比较引导'),('O2','待证投料设想','缺少真实调整困扰'),('O3','待证结构设想','没有沥水困扰直接依据')], 'V2': [('O1','保留并收窄','先观察再决定信息改动'),('O2','修改/部分待评','顺序/料量研究保留,分包另核'),('O3','本轮暂缓新开模','模拟约束不新开模,非技术否决')], 'V3': [('O1','条件式概念讨论','模拟组选择讨论,未获企业批准'),('O2','保留研究线','有相关真实事件才独立验证'),('O3','继续暂缓','条件改变且有问题依据再回评')]} for version,rs in stages.items(): pool[version]=[{'localKey':k,'status':s,'reason':why,'artifactId':None,'opportunityId':None,'businessSaved':False} for k,s,why in rs] dump(BASE/'候选机会全池.json',{'topicKey':'LOCAL-REHEARSAL-DORM-20261110','keysAreLocalOnly':True,'snapshots':pool}) scenarios=[] for key,desc,r,c in [('F0','模拟基准','3.20','2.15'),('F1','信息增量算例','3.20','2.18'),('F2','分包增量算例','3.20','2.25'),('F3','净收入敏感性算例','3.00','2.18'),('F4','净收入敏感性算例','3.40','2.18')]: R=Decimal(r);C=Decimal(c) scenarios.append({'key':key,'description':desc,'R':str(R),'C':str(C),'difference':str(R-C),'marginPercent':str((R-C)/R*100),'unit':'人民币元/份,同一假定不含税口径','formula':'difference=R-C; marginPercent=(R-C)/R*100','nature':'纯演算;模拟基准/任意敏感性;无公司报价/预测依据','usedForBusinessRanking':False}) with (BASE/'商业情景复算.csv').open('w',encoding='utf-8-sig',newline='') as f: w=csv.DictWriter(f,fieldnames=list(scenarios[0]));w.writeheader();w.writerows(scenarios) dump(BASE/'商业情景复算.json',{'actualCompanyInputs':None,'scenarios':scenarios,'actualTargetMargin':None,'actualThreshold':'Cnew <= Rnew*(1-m), only when actual target m is provided','excludedUnknown':'物流、销售、固定改版、规模、真实出成率','note':'不属于经过财务确认的完整企业利润'}) def archive_evidence(): target=BASE/'evidence';target.mkdir(exist_ok=True) source=ROOT/'work/workshop-self-run-20261110/evidence' for p in source.glob('*.json'):shutil.copyfile(p,target/p.name) # Keep only observed receipts and public responses, not dependencies, credentials or host state. checks=BASE/'checks';checks.mkdir(exist_ok=True) for p in (ROOT/'work/workshop-self-run-20261110/checks').glob('*'): if p.is_file():shutil.copyfile(p,checks/p.name) shutil.copyfile(ROOT/'work/workshop-self-run-20261110/install-apply/install-receipt.json',checks/'install-receipt.json') evidence=[] for p in sorted(target.glob('*.request.json')): d=json.loads(p.read_bytes()); response=target/(d['name']+'.json') assert sha(response)==d['responseSha256'] assert d['readOnly'] is True and d['databaseWrites']==0 evidence.append({'receipt':p.relative_to(BASE).as_posix(), 'response':response.relative_to(BASE).as_posix(), 'httpStatus':d['httpStatus'], 'requestedAt':d['requestedAt'], 'responseSha256':d['responseSha256']}) dump(BASE/'执行与核验.json',{'nature':'actual tool receipts, separate from authored self-roleplay','readOnlyApiCalls':len(evidence),'calls':evidence,'businessBridge':'checks/business-bridge.json','installation':'checks/install-receipt.json','directProductPages':'checks/product-page-attempts.json','productionWrites':0,'sourceArchivesRehashed':False}) ledger=json.loads((BASE/'证据与编码.json').read_bytes()) for r in ledger['reviews']:r['responseFile']='evidence/'+Path(r['responseFile']).name dump(BASE/'证据与编码.json',ledger) if __name__=='__main__': shared_tables();archive_evidence() render_run(BASE,'第一轮|按原手册完整自演',BASE/'manual-source.json','查看优化后定稿与第二轮 ↗') print(json.dumps({'ok':True,'chapters':15,'index':str(BASE/'index.html')},ensure_ascii=False))