| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- """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'<code>\1</code>',s)
- s = re.sub(r'\[([^\]]+)\]\(([^)]+)\)',r'<a href="\2">\1</a>',s)
- return re.sub(r'\*\*([^*]+)\*\*',r'<strong>\1</strong>',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<len(lines):
- line=lines[i]
- if not line.strip():i+=1;continue
- if line.startswith('```'):
- buf=[];i+=1
- while i<len(lines) and not lines[i].startswith('```'):buf.append(lines[i]);i+=1
- result.append('<pre>'+html.escape('\n'.join(buf))+'</pre>');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'<h{n}{attr}>'+inline(m[2])+f'</h{n}>');i+=1;continue
- if line.startswith('|') and i+1<len(lines) and re.match(r'^\|[\s:|\-]+\|$',lines[i+1]):
- cells=lambda l:[inline(c.strip()) for c in l.strip().strip('|').split('|')]
- result.append('<div class="table-wrap"><table><thead><tr>'+''.join('<th>'+c+'</th>' for c in cells(line))+'</tr></thead><tbody>');i+=2
- while i<len(lines) and lines[i].startswith('|'):
- result.append('<tr>'+''.join('<td>'+c+'</td>' for c in cells(lines[i]))+'</tr>');i+=1
- result.append('</tbody></table></div>');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<len(lines) and re.match(pattern,lines[i]):
- result.append('<li>'+inline(re.sub(pattern,'',lines[i]))+'</li>');i+=1
- result.append('</'+tag+'>');continue
- if line.startswith('> '):
- buf=[]
- while i<len(lines) and lines[i].startswith('> '):buf.append(lines[i][2:]);i+=1
- result.append('<blockquote>'+inline('\n'.join(buf)).replace('\n','<br>')+'</blockquote>');continue
- result.append('<p>'+inline(line)+'</p>');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('<a href="#'+key+'">'+html.escape(s['number']+' '+s['title'])+'</a>')
- 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='<p class="muted">证据快查:'+ ' · '.join('<a href="证据索引.html#'+v+'">'+v+'</a>' for v in refs)+'</p>' if refs else ''
- articles.append(f'<article id="{key}" data-step="{s["id"]}"><div class="label">{html.escape(s.get("module","开始之前"))}</div><h2>{html.escape(s["number"]+" · "+s["title"])}</h2>'+markdown(output)+source_links+f'<details open><summary>完整提示词与自演对话 <button class="copy" data-copy="p-{s["id"]}">复制提示词</button></summary><pre id="p-{s["id"]}">'+html.escape(prompt)+'</pre><div class="dialogue">'+markdown(talk)+'</div></details></article>')
- 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='''<section class="summary" id="overview"><div class="label">本次研究得到什么</div><h2>现品已有人认可便利,先证明值得改的一步</h2><p class="decision">建议先观察现售碗装在目标场景中的实际使用;只有发现可复现的信息理解问题,再比较信息原型。当前依据不支持直接选择新沥水结构。</p><p>课题:宿舍夜宵拌面的体验改良。研究设定为有开水、不用锅,保留酱香与面条口感。真实资料包括7款商品、49条拌面评价和10条定向社媒;业务条件与学员回应为模拟。</p><div class="stage-line"><span>供需与竞品</span><span>证言与反例</span><span>候选全池</span><span>条件取舍</span><span>概念与停止条件</span></div><div class="paths"><div><b>真实执行</b><br>隔离安装、只读资料回读、逐条编码、计算与页面检查。</div><div><b>本地自演</b><br>同一助手扮演学员与研究助手;完整提示词和15节产出可查。</div><div><b>本轮未验证</b><br>真实人员任务/感官测试、宿主自动加载、生产保存及小组归属。</div></div><h3>七类成果直达</h3><div class="cards">'''+''.join('<a data-output="true" href="#'+key+'">'+name+' ↗</a>' for name,key in seven)+'</div></section>'
- resources='<section class="resources" id="resources"><h2>完整记录与底表</h2><div class="tools"><a href="完整对话记录.md">完整对话记录</a><a href="证据索引.html">原话与来源</a><a href="评价逐条编码.csv">49条逐条编码 CSV</a><a href="提及率复算.csv">提及率复算 CSV</a><a href="候选机会全池.json">V1/V2/V3全池</a><a href="商业情景复算.csv">商业情景复算</a><a href="执行与核验.json">执行证据索引</a></div><p class="muted">E/P/S与O1–O3均为本地引用键,不是生产业务编号。资料来自固定正式代际;本轮未重哈希服务器原始ZIP,也未观看视频。</p></section>'
- 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='<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>'+html.escape(title)+'</title><style>'+CSS+'</style><header><small>康师傅 · 产品创新工作坊 / 本地演练</small><h1>'+html.escape(title)+'</h1><p class="notice">同一助手自演,资料真实,学员回应和业务条件模拟;不是豆包或真实员工验收。</p><div class="tools"><button id="collapse">只看产出</button><button id="expand">展开提示词与对话</button>'+extra+'</div></header><main><nav><a href="#overview">当前判断与成果直达</a>'+''.join(nav)+'<a href="#resources">完整记录与底表</a></nav><div class="reading">'+summary+''.join(articles)+resources+'</div></main><footer>15节内容推演完成与业务端到端验收分别记录;全部文件仅保留本地。</footer><script>'+script+'</script></html>'
- (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'<strong>(P\d{2})</strong>',r'<strong id="\1">\1</strong>',evidence_html)
- (folder/'证据索引.html').write_text('<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>真实原话与来源</title><style>'+CSS+'main{display:block;max-width:1000px}</style><main><a href="index.html">返回演练</a>'+evidence_html+'</main></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','<a href="../workshop-validated-final-20261110/index.html">查看优化后定稿与第二轮 ↗</a>')
- print(json.dumps({'ok':True,'chapters':15,'index':str(BASE/'index.html')},ensure_ascii=False))
|