| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253 |
- """Render the actual independent local case outputs; all data remain offline."""
- from pathlib import Path
- import json
- ROOT = Path(__file__).resolve().parents[1]
- WORK = ROOT / 'docs/workshop-skill-retest-20261109-r2'
- def render():
- samples = json.loads((WORK / 'sample-outputs.json').read_text(encoding='utf-8'))
- checks = json.loads((WORK / 'interface-acceptance.json').read_text(encoding='utf-8'))
- brief = (WORK / 'inputs/business-brief.md').read_text(encoding='utf-8')
- payload = json.dumps({'samples': samples, 'checks': checks, 'brief': brief}, ensure_ascii=False).replace('<', '\\u003c')
- page = r'''<!doctype html>
- <html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>工作日午餐拌面 · 独立案例复测</title>
- <style>
- :root{color-scheme:light;--ink:#1c2d43;--sub:#52647c;--line:#dce4ee;--blue:#2447a7;--green:#08715f;--bg:#f3f6fa;--pale:#edf2fb}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:16px/1.75 "Microsoft YaHei",system-ui,sans-serif}header{background:#172b60;color:white;padding:34px max(20px,calc((100vw - 1220px)/2)) 28px}h1{font-size:clamp(27px,3.2vw,38px);line-height:1.35;margin:8px 0 14px}header p{color:#e2e8f8;max-width:900px;margin:0}button,select{font:inherit;touch-action:manipulation}button{cursor:pointer}button:focus-visible,select:focus-visible,summary:focus-visible,a:focus-visible{outline:3px solid #e7a438;outline-offset:3px}button:disabled{opacity:.4;cursor:default}.eyebrow{font-size:14px;color:#ccd8f6}.tabs{display:flex;gap:9px;flex-wrap:wrap;margin-top:22px}.tabs button{border:1px solid #7d94bd;border-radius:9px;background:transparent;color:white;padding:9px 15px}.tabs button[aria-selected=true]{background:white;color:var(--blue);border-color:white}.shell{max-width:1268px;margin:auto;padding:24px}.layout{display:grid;grid-template-columns:246px minmax(0,1fr);gap:22px}.side,.paper{background:white;border:1px solid var(--line);border-radius:14px;min-width:0}.side{padding:16px;position:sticky;top:16px;align-self:start}.side h2{font-size:14px;margin:0 0 12px;color:var(--sub)}#steps{display:flex;flex-direction:column;gap:4px}#steps button{font-size:14px;text-align:left;border:0;background:transparent;border-radius:8px;padding:8px 10px;color:var(--ink)}#steps button[aria-current=step]{background:var(--pale);color:var(--blue);font-weight:700}.code{display:block;font-size:12px;color:var(--sub)}.paper{padding:26px 28px}.meta{display:flex;flex-wrap:wrap;gap:7px;color:var(--sub);font-size:13px}.tag{padding:3px 8px;border-radius:6px;background:var(--pale);color:var(--blue)}.warn{background:#fff1db;color:#874906}h2{font-size:25px;line-height:1.45;margin:15px 0}h3{font-size:18px;line-height:1.5;margin:24px 0 10px}h4{font-size:16px;margin:16px 0 8px}p{margin:10px 0 14px}ul,ol{padding-left:24px}li{margin:5px 0}.lead{background:#edf6f2;border-left:4px solid var(--green);border-radius:0 10px 10px 0;padding:17px 20px;margin:20px 0}.lead p{font-size:19px;font-weight:600;line-height:1.65;margin:0}.lead small{display:block;font-size:14px;color:var(--green);margin-bottom:7px}.modebar{display:flex;gap:8px;flex-wrap:wrap;border-top:1px solid var(--line);padding-top:20px;margin:22px 0}.modebar button,.endnav button{background:white;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:9px 13px}.modebar button[aria-pressed=true]{background:var(--pale);border-color:#96aadd;color:var(--blue);font-weight:700}.tablebox{max-width:100%;overflow-x:auto;border:1px solid var(--line);border-radius:10px;margin:9px 0 18px;overscroll-behavior-x:contain}table{width:100%;border-collapse:collapse;font-size:15px;line-height:1.65}th,td{vertical-align:top;text-align:left;padding:12px 14px;min-width:118px;overflow-wrap:anywhere}th{background:#edf1f8;font-weight:600}td{border-top:1px solid var(--line)}tbody tr:nth-child(even){background:#fbfcfe}.scrollhint{font-size:13px;color:var(--sub);margin:3px 0 7px}.reply-body{overflow-wrap:anywhere}.reply-body h3:first-child{margin-top:0}.next{background:var(--pale);padding:15px 19px;border-radius:10px;margin-top:24px}.next strong{color:var(--blue)}.next p{margin-bottom:0}.note{font-size:13px;color:var(--sub);margin-top:20px}.evidence{border-top:1px solid var(--line);padding-top:16px;margin-top:22px}summary{cursor:pointer;font-weight:600}.quote{border-left:3px solid #aabddc;padding:10px 14px;background:#f7f9fc;margin:12px 0}.quote small{display:block;color:var(--sub);font-size:13px}.endnav{display:flex;justify-content:space-between;gap:12px;margin-top:24px}.mobile-select{display:none}.panel[hidden]{display:none!important}.wide{max-width:1120px;margin:auto}.card-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:18px}.candidate{border:1px solid var(--line);border-radius:10px;padding:18px;min-width:0}.candidate h3{margin-top:0}.candidate dt{font-size:13px;font-weight:700;color:var(--sub);margin-top:12px}.candidate dd{margin:4px 0 10px}.costchart{border:1px solid var(--line);border-radius:10px;padding:12px;overflow:auto}.costchart svg{display:block;width:100%;min-width:570px}.sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0,0,0,0)}pre{white-space:pre-wrap;overflow-wrap:anywhere;font:14px/1.7 inherit;background:#f5f7fb;padding:16px;border-radius:9px}a{color:var(--blue)}footer{max-width:1268px;margin:auto;padding:0 24px 26px;font-size:13px;color:var(--sub)}
- @media(max-width:850px){.layout{grid-template-columns:1fr}.side{position:static}#steps{display:grid;grid-template-columns:repeat(3,minmax(0,1fr))}.card-grid{grid-template-columns:1fr}}
- @media(max-width:520px){header{padding:24px 18px}.shell{padding:14px 12px}.paper{padding:19px 16px}.side{padding:12px}.side h2{margin-bottom:7px}.side #steps{display:none}.mobile-select{display:block;width:100%;background:white;color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:10px}.lead{padding:14px}.lead p{font-size:18px}h2{font-size:23px}.tabs button{font-size:14px;padding:8px 11px}td,th{min-width:145px}.meta{font-size:12px}}
- @media print{header{background:white;color:black}.tabs,.side,.modebar,.endnav{display:none}.layout{display:block}.paper{border:0}.tablebox{overflow:visible}td,th{min-width:0}.lead{break-inside:avoid}}
- </style></head><body><header><div class="eyebrow">康师傅产品创新工作坊 · 第二轮本地案例复测</div><h1>工作日午餐拌面,值得改什么?</h1><p>当前Skill独立试跑:查看逐节回复、完整成果与候选取舍。全部为构造测试材料。</p><nav class="tabs" role="tablist" aria-label="案例内容"><button id="tab-samples" role="tab" aria-controls="samples" aria-selected="true">逐节案例</button><button id="tab-candidates" role="tab" aria-controls="candidates" aria-selected="false">候选与成本取舍</button><button id="tab-checks" role="tab" aria-controls="checks" aria-selected="false">复测范围</button></nav></header>
- <main class="shell"><section class="panel layout" id="samples" role="tabpanel" aria-labelledby="tab-samples"><aside class="side"><h2>课表顺序 · 不绑定钟点</h2><label for="mobile-stage" class="sr">选择研究环节</label><select id="mobile-stage" class="mobile-select"></select><nav id="steps" aria-label="研究环节"></nav></aside><article class="paper" id="sample" aria-live="polite"></article></section>
- <section class="panel wide" id="candidates" role="tabpanel" aria-labelledby="tab-candidates" hidden><article class="paper"><div class="meta"><span class="tag">机会候选</span><span class="tag warn">构造题干 · 条件式研究建议</span></div><h2>既看体验收益,也看改动代价</h2><div id="recommendation"></div><div id="candidate-cards" class="card-grid"></div><h3>题干成本情景对照</h3><p>净实现收入4.20元/份、基准完整单位成本2.50元/份,均为测试设定。目标毛利35%对应成本上限2.73元/份,增量空间0.23元。概算达标也不代表消费者效果、工厂兼容或合规已经验证。</p><div class="costchart" id="costchart"></div><h3>仍需验证</h3><div id="unresolved"></div></article></section>
- <section class="panel wide" id="checks" role="tabpanel" aria-labelledby="tab-checks" hidden><article class="paper"><div class="meta"><span class="tag">独立上下文试跑</span><span class="tag warn">非真实市场调研/非线上验收</span></div><h2>这次复测实际做了什么</h2><p>独立执行者只接收题干、原材料和当前Skill,未获得上一轮样张或预设结论。当前主助手另行检查来源、计算、完整性及阅读效果;同一模型体系中的独立上下文不等于双模型。</p><div id="scope"></div><p>报告和机会卡通过当前业务处理代码保存到隔离的内存库,再按实际本地编号回读并核公开投影。页面中的本地编号不能用于线上访问。未继续原案例、未部署、未写生产库、未采集、未付费、未正式发布。</p><details class="evidence"><summary>查看完整测试题干</summary><pre id="brief"></pre></details><details class="evidence"><summary>查看实际执行检查项目</summary><ol id="checklist"></ol></details></article></section></main><footer>可离线打开。所有产品、原话和经营条件均为构造测试材料;不作为康师傅的真实商业结论。</footer>
- <script id="payload" type="application/json">__PAYLOAD__</script><script>
- 'use strict';const DATA=JSON.parse(document.getElementById('payload').textContent),OUT=DATA.samples.outputs;let selected=0,full=false;
- const esc=v=>String(v??'未知').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
- const inline=v=>esc(v).replace(/\*\*([^*]+)\*\*/g,'<strong>$1</strong>').replace(/`([^`]+)`/g,'<code>$1</code>');
- const short=['品类全景扫描','验证纪律前置','竞品格局掌握','假设定稿与任务','AI自驱调研','调研战报验收','VOC聚类与痛点库','风味×供需矩阵','三源交叉与V1','机会验证与优先级','商业供应链与V2','价值主张与卖点','概念提案与V3'];
- function table(cols,rows,title){return '<div class="tablebox" role="region" tabindex="0" aria-label="'+esc(title)+'"><table><thead><tr>'+cols.map(c=>'<th scope="col">'+inline(c)+'</th>').join('')+'</tr></thead><tbody>'+rows.map(r=>'<tr>'+r.map(c=>'<td>'+inline(c)+'</td>').join('')+'</tr>').join('')+'</tbody></table></div>';}
- function markdown(text){const lines=String(text).split(/\r?\n/);let out='',list=false;for(let i=0;i<lines.length;i++){const l=lines[i].trim();if(!l){if(list){out+='</ul>';list=false;}continue;}if(l.startsWith('|')&&i+1<lines.length&&/^\|?\s*:?-+/.test(lines[i+1].trim())){if(list){out+='</ul>';list=false;}const cells=s=>s.trim().replace(/^\||\|$/g,'').split('|').map(x=>x.trim());const cols=cells(l),rows=[];i++;while(i+1<lines.length&&lines[i+1].trim().startsWith('|'))rows.push(cells(lines[++i]));out+=table(cols,rows,'业务回复对照');continue;}if(/^[-*]\s/.test(l)){if(!list){out+='<ul>';list=true;}out+='<li>'+inline(l.replace(/^[-*]\s/,''))+'</li>';continue;}if(list){out+='</ul>';list=false;}const h=l.match(/^(#{1,6})\s+(.+)$/);out+=h?'<h3>'+inline(h[2])+'</h3>':'<p>'+inline(l)+'</p>';}return out+(list?'</ul>':'');}
- function section(s){return '<section class="report-section"><h3>'+esc(s.title)+'</h3>'+(s.kind==='table'?table(s.data.cols,s.data.rows,s.title):'<p style="white-space:pre-line">'+esc(s.text)+'</p>')+'</section>';}
- function draw(){const m=OUT[selected],displayTitle=m.title.replace(/^样例[//]工作日拌面午餐[//]/,'');document.querySelector('#steps').innerHTML=OUT.map((x,i)=>'<button data-index="'+i+'" aria-current="'+(i===selected?'step':'false')+'"><span class="code">'+esc(x.id)+' · '+esc(x.stage)+'</span>'+short[i]+'</button>').join('');document.querySelectorAll('#steps button').forEach(b=>b.onclick=()=>{selected=+b.dataset.index;full=false;draw();});const select=document.querySelector('#mobile-stage');select.innerHTML=OUT.map((x,i)=>'<option value="'+i+'" '+(selected===i?'selected':'')+'>'+esc(x.id)+' · '+short[i]+'</option>').join('');select.onchange=()=>{selected=+select.value;full=false;draw();};
- const supplements=full?(m.supplementalReports||[]).map(s=>'<details class="evidence supplementary" data-supplement="'+esc(s.id)+'"><summary>独立配套成果:'+esc(s.title)+'</summary><p class="note">已在本地保存并全文回读 · '+esc(s.artifactId)+'</p>'+s.sections.map(x=>section(x).replace('class="report-section"','class="supplement-section"')).join('')+'</details>').join(''):'';
- document.querySelector('#sample').innerHTML='<div class="meta"><span class="tag">'+esc(m.id)+' · '+esc(m.stage)+'</span><span class="tag warn">构造测试材料</span></div><h2>'+esc(displayTitle)+'</h2><div class="lead"><small>本轮产品认识</small><p>'+esc(m.finding)+'</p></div><div class="modebar" role="group" aria-label="查看方式"><button id="reply-mode" aria-pressed="'+!full+'">业务回复</button><button id="report-mode" aria-pressed="'+full+'">完整成果 · '+m.sections.length+'部分</button></div>'+(!full?'<p class="note">以下为独立执行者保存前的原始回复;后续本地保存和回读结果单列在下方。</p>':'')+'<div id="report-body" class="'+(!full?'reply-body':'')+'">'+(full?m.sections.map(section).join(''):markdown(m.reply))+'</div>'+supplements+'<div class="next"><strong>下一步接续</strong><p>'+esc(m.next)+'</p></div><p class="note">后续核查:这份报告已在本地内存库保存并全文回读;未上传线上。'+esc(m.artifactId)+'</p><details class="evidence"><summary>查看本节引用原话与语境</summary>'+m.usedRecordIds.map(id=>{const r=[...DATA.samples.fixture.products,...DATA.samples.fixture.reviews,...DATA.samples.fixture.social].find(x=>x.id===id);return '<blockquote class="quote">'+esc(r.text||r.title)+'<small>'+esc(id)+' · '+esc(r.product||(r.title?'商品记录':'未归属商品'))+' · '+esc(r.platform||'商品题干')+' · 构造材料</small></blockquote>';}).join('')+'</details><div class="endnav"><button id="prev" '+(selected===0?'disabled':'')+'>上一环节</button><button id="next" '+(selected===OUT.length-1?'disabled':'')+'>下一环节</button></div>';
- document.querySelector('#reply-mode').onclick=()=>{full=false;draw();};document.querySelector('#report-mode').onclick=()=>{full=true;draw();};document.querySelector('#prev').onclick=()=>{selected--;full=false;draw();};document.querySelector('#next').onclick=()=>{selected++;full=false;draw();};}
- function value(v){if(Array.isArray(v))return '<ul>'+v.map(x=>'<li>'+value(x)+'</li>').join('')+'</ul>';if(v&&typeof v==='object')return '<dl>'+Object.entries(v).map(([k,x])=>'<dt>'+esc(k)+'</dt><dd>'+value(x)+'</dd>').join('')+'</dl>';return inline(v);}
- const labels={task:'用户任务',existingAlternatives:'现有解决办法',change:'拟改要素',benefitHypothesis:'预期收益(待验证)',supportRecordIds:'支持记录',counterRecordIds:'反证与取舍记录',decisiveTest:'决定性验证',versionDisposition:'V1→V2→V3去向'};
- document.querySelector('#candidate-cards').innerHTML=DATA.samples.candidates.map(c=>'<article class="candidate"><h3>'+esc(c.title)+'</h3><dl>'+Object.entries(labels).map(([key,label])=>'<dt>'+label+'</dt><dd>'+value(c[key]??'见独立候选正文')+'</dd>').join('')+'</dl><details class="evidence candidate-report" data-candidate="'+esc(c.key)+'"><summary>查看本候选完整分析</summary>'+c.reportSections.map(s=>section(s).replace('class="report-section"','class="candidate-section"')).join('')+'</details></article>').join('');const rec=DATA.samples.finalRecommendation;document.querySelector('#recommendation').innerHTML='<div class="lead"><small>当前条件式建议</small><p>'+esc(rec.recommendation)+'</p></div><p><strong>比较基准:</strong>'+esc(rec.comparisonBaseline)+'</p><p><strong>改变或停止的条件:</strong>'+esc(rec.stopOrSwitch)+'</p>';document.querySelector('#unresolved').innerHTML=DATA.samples.unresolved.map(u=>typeof u==='string'?'<p>'+esc(u)+'</p>':'<details class="evidence"><summary>'+esc(u.question||u.key)+'</summary><p><strong>影响:</strong>'+esc(u.impact)+'</p><p><strong>需取得:</strong>'+esc(u.nextCondition)+'</p></details>').join('');document.querySelector('#brief').textContent=DATA.brief;
- document.querySelector('#scope').innerHTML=table(['项目','实际范围'],[['内容顺序','13个研究环节;不另设D3'],['本地接口',DATA.checks.checksPassed+'项检查通过;'+DATA.checks.candidates+'张独立候选卡'],['原始材料','15条电商原件与2条未归属社媒;去重、资格、分母分别处理'],['行为验证','独立上下文Agent执行;未记作真实双模型']], '复测范围');document.querySelector('#checklist').innerHTML=DATA.checks.checks.map(x=>'<li>'+esc(x)+'</li>').join('');
- const eco=DATA.samples.fixture.scenarioConditions,limit=eco.netRevenuePerServing*(1-eco.minimumGrossMargin),max=3.5,scale=260/max,x0=225;
- document.querySelector('#costchart').innerHTML='<svg viewBox="0 0 650 300" role="img" aria-labelledby="cost-title cost-desc"><title id="cost-title">测试题干增量方案的单位成本对照</title><desc id="cost-desc">目标成本上限2.73元。新增设备不符合本题限制,即使账面毛利达标也不可按本方案推进。</desc><line x1="'+(x0+limit*scale)+'" x2="'+(x0+limit*scale)+'" y1="35" y2="275" stroke="#b36a18" stroke-dasharray="5 4"/><text x="'+(x0+limit*scale-10)+'" y="20" text-anchor="end" font-size="14" fill="#874906">成本上限2.73</text>'+eco.incrementalCostEstimates.map((r,i)=>{const cost=eco.baseUnitCost+r.extraCost,y=58+i*43,blocked=r.change.includes('设备'),color=blocked?'#8996aa':cost>limit?'#a66226':'#177e68';return '<text x="8" y="'+y+'" font-size="15" fill="#1c2d43">'+esc(r.change)+(blocked?'(不符产线限制)':'')+'</text><rect x="'+x0+'" y="'+(y-17)+'" width="'+(cost*scale)+'" height="23" rx="3" fill="'+color+'"/><text x="'+505+'" y="'+y+'" font-size="15" fill="#1c2d43">'+cost.toFixed(2)+'元 · '+((eco.netRevenuePerServing-cost)/eco.netRevenuePerServing*100).toFixed(2)+'%</text>';}).join('')+'</svg>';
- document.querySelectorAll('.tabs button').forEach(b=>b.onclick=()=>{document.querySelectorAll('.tabs button').forEach(t=>t.setAttribute('aria-selected',t===b));document.querySelectorAll('.panel').forEach(p=>p.hidden=p.id!==b.getAttribute('aria-controls'));});draw();
- </script></body></html>'''
- (WORK / '独立案例复测.html').write_text(page.replace('__PAYLOAD__', payload), encoding='utf-8')
- importmap = (ROOT / 'index.html').read_text(encoding='utf-8').split('<script type="importmap">', 1)[1].split('</script>', 1)[0]
- harness = '''<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>独立案例本地报告组件核验</title><script type="importmap">'''+importmap+'''</script><style>body{margin:0;padding:16px;background:#f4f6fa;font-family:system-ui}#notice{padding:10px;background:#fff1d8;font-size:14px}view-report{display:block}</style><p id="notice">本地独立案例 · 构造题干 · 未上传线上</p><view-report></view-report><script type="module">import {ViewReport} from '/src/pages/view-report.js';import {buildModel} from '/src/core/kfs/build.js';import '/src/components/index.js';const raw=await fetch('/docs/workshop-skill-retest-20261109-r2/local-board.json').then(r=>r.json());window.localDB=buildModel(raw);const v=document.querySelector('view-report');v.db=window.localDB;v.artifactId=raw.artifact[0].objectId;await v.updateComplete;window.localReady=true;window.localSet=async id=>{v.artifactId=id;await v.updateComplete;return true;};</script></html>'''
- (WORK / 'report-render-harness.html').write_text(harness, encoding='utf-8')
- print(json.dumps({'preview': str(WORK / '独立案例复测.html'), 'modules': len(samples['outputs'])}, ensure_ascii=False))
- if __name__ == '__main__':
- render()
|