| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- """Build paired local report-quality revision. No deployment or host installation."""
- import csv
- import hashlib
- import html
- import importlib.util
- import json
- import re
- import shutil
- import zipfile
- from pathlib import Path
- from workshop_quality_r13_content import VERSION, REFERENCES, STAGES, RETRIEVAL_RULES
- from workshop_quality_r13_examples import EXAMPLES
- from workshop_quality_r13_model import build_model
- from update_workshop_manual_install_r13 import configure_website_entry, configure_evidence_feedback
- ROOT = Path(__file__).resolve().parents[1]
- OUT = ROOT/'docs/workshop-quality-r13-20261110'
- PRIOR = ROOT/'docs/workshop-validated-final-20261110'
- WORK = ROOT/'work/workshop-report-quality-20261110'
- SKILL = ROOT/'skills/ksf-workshop-runtime'
- 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='utf8')
- spec=importlib.util.spec_from_file_location('old_renderer',ROOT/'scripts/build-workshop-self-run.py')
- renderer=importlib.util.module_from_spec(spec);spec.loader.exec_module(renderer)
- md=renderer.markdown
- def cited_markdown(text, base=''):
- """Make evidence identifiers usable without rewriting original quotations."""
- rendered=md(text)
- def link(match):
- code=match.group(0)
- new=code.startswith('R') or (code.startswith('P') and int(code[1:])>=8)
- target=base+'证据底表.html#'+code if new else base+'../workshop-validated-final-20261110/rerun/证据索引.html#'+code
- return '<a href="'+target+'">'+code+'</a>'
- # Do not process HTML attributes, existing links, source quotations or code.
- parts=re.split(r'(<(?:a|pre|blockquote)\b[^>]*>.*?</(?:a|pre|blockquote)>|<[^>]+>)',rendered,flags=re.S)
- return ''.join(part if part.startswith('<') else re.sub(r'(?<![A-Za-z0-9])(?:E\d{2}|P\d{2}|R\d{2}|S\d{2})(?![A-Za-z0-9])',link,part) for part in parts)
- CSS='''*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;color:#26334b;background:#f4f6fa;font:15px/1.85 "Segoe UI","Microsoft YaHei",sans-serif}a{color:#285bb1;text-underline-offset:4px}a:hover{color:#994c25}header{padding:22px max(24px,calc((100vw - 1300px)/2));background:#172d51;color:#fff}header a{color:#d5e5ff;margin-right:20px}header small{letter-spacing:.12em;color:#abc6ed}h1{font-size:29px;line-height:1.5;margin:7px 0}h2{font-size:23px;line-height:1.5;margin:12px 0}h3{font-size:18px;margin:28px 0 8px}h4{font-size:16px}p{margin:10px 0}main{max-width:1320px;margin:24px auto;padding:0 24px;display:grid;grid-template-columns:222px minmax(0,1fr);gap:22px}nav{position:sticky;top:16px;align-self:start;max-height:94vh;overflow:auto}nav a{display:block;padding:7px 10px;font-size:13px;text-decoration:none;border-left:2px solid transparent}nav a:hover{background:#e5ecf6;border-color:#376bbd}nav b{display:block;padding:6px 10px;font-size:12px;color:#6a7990}article,section.panel{min-width:0;background:#fff;padding:26px 30px;border:1px solid #dfe5ef;border-radius:9px;margin:0 0 20px;scroll-margin-top:16px}.reading{min-width:0}.kicker{color:#7c572d;font-size:12px;letter-spacing:.08em}.lead{padding:14px 18px;background:#edf3fc;border-left:3px solid #3769aa;font-size:17px}.muted{font-size:13px;color:#637188}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.card{display:block;padding:16px;border:1px solid #dfe5ef;border-radius:7px;background:#fff;text-decoration:none;color:#26334b}.card:hover{background:#f1f6ff}.card small{display:block;color:#6c7d94}.card strong{display:block;font-size:17px;line-height:1.6}.table-wrap{overflow-x:auto;max-width:100%;margin:15px 0;border:1px solid #dfe5ef;border-radius:6px}table{width:100%;border-collapse:collapse;min-width:650px;font-size:13px;line-height:1.8}th,td{padding:10px 12px;text-align:left;vertical-align:top;border-bottom:1px solid #e6ebf3;overflow-wrap:anywhere}th{background:#eef3fa;color:#234777}tr:last-child td{border-bottom:0}pre{white-space:pre-wrap;overflow-wrap:anywhere;padding:17px;background:#f2f5fa;border-radius:6px;font:13px/1.8 Consolas,"Microsoft YaHei",monospace}blockquote{margin:14px 0;border-left:3px solid #4b75af;padding:12px 18px;background:#f4f7fc;white-space:pre-line}code{overflow-wrap:anywhere;font-size:13px}ul,ol{padding-left:24px}li{margin:5px 0}footer{padding:20px;text-align:center;color:#718098;font-size:12px}button{font:inherit;cursor:pointer}h2,h3{overflow-wrap:anywhere}.bars{display:grid;gap:14px}.barrow{display:grid;grid-template-columns:160px minmax(0,1fr) 85px;align-items:center;gap:10px;font-size:13px}.track{height:16px;background:#e9eef7;border-radius:3px;overflow:hidden}.fill{height:100%;background:#3d6eb7}.barrow:nth-child(2) .fill{background:#b97a45}.barrow:nth-child(4) .fill{background:#a55252}.switch{padding:7px 10px;background:#fff;border:1px solid #ccd7e7;border-radius:4px;color:#254e8b}label{font-size:13px}input[type=number]{width:100%;padding:9px;border:1px solid #c7d2e1;border-radius:4px}.calcgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:12px}output{display:block;padding:14px;background:#edf3fc;margin-top:14px}figure{margin:20px 0}figcaption{font-size:12px;color:#65758c;margin-top:8px}@media(max-width:760px){main{display:block;margin:12px auto;padding:0 12px}nav{position:static;max-height:none;display:flex;overflow:auto;gap:6px;margin-bottom:12px}nav b{display:none}nav a{flex:0 0 auto;font-size:12px;padding:7px}header{padding:16px}h1{font-size:24px}article,section.panel{padding:18px 16px}.grid{grid-template-columns:1fr}.barrow{grid-template-columns:115px minmax(0,1fr) 65px;font-size:12px}.calcgrid{grid-template-columns:1fr}table{min-width:650px}}'''
- def shell(title,body,nav='',base=''):
- return f'''<!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>康师傅 · 产品创新工作坊 / r13 本地修订</small><h1>{html.escape(title)}</h1><a href="{base}index.html">交付首页</a><a href="{base}康师傅工作坊操作手册-r13本地修订.html">操作手册</a><a href="{base}案例报告合集.html">报告案例</a></header><main><nav>{nav}</nav><div class="reading">{body}</div></main><footer>本地内容修订与研究样板 · 不是真实员工对话或完整课程实测 · 未发布线上、未写生产业务</footer></html>'''
- def research_ledger():
- old=json.loads((ROOT/'docs/workshop-self-run-20261110/证据与编码.json').read_bytes())
- added=[]
- pending={6:'汤头措辞与拌面用途歧义',8:'汤底措辞与拌面用途歧义',10:'3种不同12味表述不清'}
- themes={'novelty':['R01','R04','R07','R12','R13','R15','R16'],
- 'preference_difference':['R03','R05'], 'preparation_positive':['R04','R12','R13'],
- 'soup_misidentification':['R11']}
- for i in range(1,19):
- p=WORK/'data'/f'new-review-{i:02}.json';r=json.loads(p.read_bytes())['record'];key=f'R{i:02}'
- added.append(dict(key=key,recordId=r['id'],productId=r['product_id'],quote=r['content'],
- purchasedSku=r['purchased_sku_text'],dateRaw=r['review_date_raw'],
- status='pending' if i in pending else 'excluded' if i==17 else 'usable-with-scope',
- note=pending.get(i,'语义紊乱' if i==17 else {7:'原文末尾截断;仅用可读部分',9:'泛评,不支持产品机制',12:'推广风格;不当正式产品规格',16:'未食期待,不当食后满意'}.get(i,'限定用于原句表达,未验证真人消费')),
- source=r['source'],tags=[t for t,ks in themes.items() if key in ks],
- responseFile='data/'+p.name))
- usable=[r for r in added if r['status']=='usable-with-scope'];den=[r['key'] for r in usable]
- assert len(den)==14
- rows=[]
- labels=['尝新/轮换动机或期待','明确拒绝某味/偏回经典','准备便利正向','明确误当汤面自述']
- for (t,ks),label in zip(themes.items(),labels):
- assert set(ks)<=set(den)
- sens=[k for k in den if k not in ['R07','R12']]
- rows.append(dict(theme=t,label=label,n=len(ks),N=len(den),percent=round(len(ks)/len(den)*100,1),
- members=ks,denominatorMembers=den,unit='评价记录;非独立消费者/需求发生率',
- sensitivityExcluded=['R07','R12'],sensitivityN=len(sens),sensitivityn=len(set(ks)&set(sens))))
- products=[]
- for key,n in [('P08',2),('P09',1),('P10',4),('P11',3)]:
- p=WORK/'data'/f'new-product-{n}.json';r=json.loads(p.read_bytes())['record']
- pricefile=WORK/'data'/f'new-{n}-prices.json'
- prices=json.loads(pricefile.read_bytes()) if pricefile.exists() else None
- products.append(dict(key=key,recordId=r['id'],title=r['title'],url=r['url'],specs=r.get('specs',[]),
- source=r.get('source'),sourceRecordId=r.get('source_record_id'),sourceTime=r.get('source_time_raw'),
- responseFile='data/'+p.name,priceResponseFile='data/'+pricefile.name if prices else None,
- priceRows=prices.get('records',[]) if prices else [],qualification='规格为标题候选;非实物标签核验,价格非成交/企业收入'))
- d=dict(schema='ksf.workshop-quality-evidence.v1',generation=json.loads((WORK/'data/version.json').read_bytes())['generation'],
- oldReviews=old['reviews'],addedReviews=added,oldProducts=old['products'],social=old['social'],statistics=rows,
- addedProducts=products,
- scope=dict(totalRawReviews=67,oldRaw=49,newRaw=18,newUsable=14,newPending=3,newExcluded=1,
- allUsefulWithDifferentScopes=59,independentCodingVerified=False,sourceArchivesRehashed=False),
- warning='真实库记录/原文回读;不是新采集消费事件,真实性/独立消费者未验证;作者概念不作事实')
- dump(OUT/'证据与编码-r13.json',d)
- for filename,rs in [('新增评价逐条编码.csv',added),('提及率复算.csv',rows)]:
- keys=['key','recordId','productId','quote','purchasedSku','dateRaw','status','note','tags'] if rs==added else list(rows[0])
- with (OUT/filename).open('w',encoding='utf-8-sig',newline='') as f:
- w=csv.DictWriter(f,fieldnames=keys,extrasaction='ignore');w.writeheader()
- for r in rs:w.writerow({k:json.dumps(r[k],ensure_ascii=False) if isinstance(r[k],list) else r[k] for k in keys})
- return d
- def charts(stats):
- # Widths are exact fractions of N, not animated or invented KPIs.
- rows=''.join(f'<div class="barrow" data-theme="{r["theme"]}" data-n="{r["n"]}" data-N="{r["N"]}"><span>{r["label"]}</span><div class="track"><div class="fill" style="width:{r["n"]/r["N"]*100:.4f}%"></div></div><b>{r["n"]}/{r["N"]} · {r["percent"]}%</b></div>' for r in stats)
- return '<figure><h3>P08的主题表达:以14条限定记录为分母</h3><div class="bars">'+rows+'</div><figcaption>主题重叠,不求和;这是本批记录提及,非需求发生率/购买转化。R16是未食期待;完整成员与敏感性见下表和编码CSV。</figcaption></figure>'
- def skill_and_manual():
- entry=(SKILL/'SKILL.md').read_text('utf8')
- entry=entry.replace('version: r12-local-final','version: '+VERSION)
- entry=entry.replace('提示词资产由教学材料承载,不放入Skill。','提示词资产是教学成果,Agent应协助形成可复制的本组任务文本;课题原文随成果保存,不固化成本Skill的案例答案。')
- anchor='## 按课表组织数据分析与报告'
- quality='''按当前环节先读 [选材与分类型产出标准](references/workshop-deliverable-contracts.md)。先将业务问题落实为主张、分析单位、所需字段、支持与反证,选择适合的数据,再执行对应报告类型。关键词命中只做发现,比例须另建合格分母;按商品ID回读并复核零命中。技术检查通过、资料诚实披露和产出内容合格分别判断,不用同一段缺口说明代替各节实质分析。\n\n'''
- if quality not in entry:entry=entry.replace(anchor,quality+anchor)
- (SKILL/'SKILL.md').write_text(entry,encoding='utf8')
- ref='# 选材与分类型产出标准\n\n用于课表13类产出。手册负责发起当前任务,本Skill负责选材、分析、反证和成报。不存在固定案例答案,也不要求学员反复填研究表。\n'+RETRIEVAL_RULES
- for s in STAGES:
- ref+=f'\n## {s["code"]} {s["title"]}\n\n要回答:{s["question"]}\n\n选材:{s["retrieval"]}\n\n分析:{s["task"]}\n\n应交付:'+ ';'.join(s['deliverables'])+'。\n\n验收:'+';'.join(s['checks'])+f'\n\n不合格示例:{s["bad"]}\n'
- ref+='\n## 参考学习的采用范围\n\n'
- for r in REFERENCES:
- ref+=f'- [{r["title"]}]({r["url"]})({r["kind"]}):{r["learn"]} 采用:{r["apply"]} 不移植:{r["reject"]}\n'
- (SKILL/'references/workshop-deliverable-contracts.md').write_text(ref,encoding='utf8')
- # Establish the new rule in the existing routes, without changing runtime interfaces.
- for name,paragraph in {
- 'workshop-course-flow.md':'每节开工前按[选材与分类型产出标准](workshop-deliverable-contracts.md)确定分析单位和材料用途,按本节产出类型分析。验收实际判断、比较和可复用结果,不以章节齐全、链接/哈希通过或诚实列缺口代替内容合格。提示词资产包属于D1-02教学成果,Agent须协助形成可复制任务资产;Skill保留方法与验收标准,不固化本题答案。',
- 'workshop-feedback.md':'修改报告时先检查选材是否支持当前判断,再检查分析是否回答本节问题。按[分类型产出标准](workshop-deliverable-contracts.md)分别交图谱、研究报告、聚类/痛点库、机会卡、优先级、回推与提案;同一观察任务换标题不算多个机会。不要求所有探索候选等到真实成本齐全才初排,也不强行给S级。',
- }.items():
- p=SKILL/'references'/name;t=p.read_text('utf8')
- if paragraph not in t:t+='\n## r13选材与内容验收\n\n'+paragraph+'\n'
- p.write_text(t,encoding='utf8')
- p=SKILL/'references/workshop-report-design.md';t=p.read_text('utf8')
- cuts=[t.index(marker) for marker in ['## 设计参考的使用范围','## 分类型学习与内容验收'] if marker in t]
- if cuts:t=t[:min(cuts)]
- t+='''## 分类型学习与内容验收
- 按[选材与分类型产出标准](workshop-deliverable-contracts.md)选用实际已读的行业研究、消费者调查、场景报告、竞品感官研究公开说明、技术经济分析与价值设计方法。报告案例、方法指南和未读完整数据的公开摘要分别标明,不统称已验证优质报告。每类借鉴一个有用途的做法,并说明本题如何实现。
- 图表先有适合本主张的数据和明确单位。图谱应显示层级/关系与对象,矩阵应解释供需错位,机会卡须有具体产品机制和代价,商业回推须拆实际变化,提案须有可评审产品设定。通用框架、过程聊天、引用列表或美观页面不能替代这些内容。
- 旧VOC参考中,茶决决可借鉴满意/未满足与多路径机会,但含SAMPLE和综合画像;香薰部分评分/增长无可回查底表,不采用其数值与雷达;COMPEX报告有分析对象品牌错配,不能作为品牌结论范本。引用报告自身也要核资格,不能因为样式漂亮就整体照搬。
- '''
- p.write_text(t,encoding='utf8')
- data=json.loads((PRIOR/'manual-source.json').read_bytes())
- data['release'].update(version=VERSION,contentVersion=VERSION,installVersion=VERSION,
- guide=(OUT/'INSTALL-本地修订.md').as_uri(),deploymentStatus='local-only')
- data['common']='''使用康师傅产品创新工作坊Skill,作为食品品类研究负责人沿本组同一课题推进当前环节。先恢复已知目标、真实约束、前序成果和选择;只问会改变路线且必须由业务方回答的1—3项,不重复问已有信息。
- 先将本节问题落实为主张、分析单位与所需数据。按对应细则查我方、竞品、用户及反证,核商品/规格/时间/来源;能获取的事实、关联、补查和计算由你完成。关键词命中只用于发现,原文按对象回读,零命中先复核;比例从声明的合格集合计算。
- 交付本节实际报告正文,先给发现及产品含义,再用图谱、比较或原话解释依据、反例与取舍。区别已核事实、研究推断、产品原型设定和真实验证;不得用缺口清单、过程聊天或“继续观察”替代成品。缺实测不编结果,但仍要提出具体可比较方案。末尾核产出标准,更新简短接续摘要与原件/成果位置;沿真实能力保存回读,未接通则本地交付,不编编号。本轮停在当前环节。'''
- by={s['id']:s for s in data['steps']}
- for s in STAGES:
- target=by[s['id']];target.update(question=s['question'],task=s['task'],retrieval=s['retrieval'],deliverables=s['deliverables'],checks=s['checks'],courseCode=s['code'],example='reports/'+s['id']+'.html',references=s['refs'])
- target['inputs']=target.get('inputs','本组研究简报及上一节实际成果;先回读原文与版本。')
- target['skillReference']='references/workshop-deliverable-contracts.md + '+target.get('skillReference','references/workshop-course-flow.md')
- target['prompt']=None
- # Attendees use the centrally configured website agent.
- configure_website_entry(data)
- by['topic']['prompt']='''使用康师傅产品创新工作坊Skill,帮助本组形成一个可研究的产品决策问题。先读我已提供的目标、材料和已有选择;若仍不明确,只问最影响路线的1—3项,例如想作哪项产品决定、在什么使用情境、必须保留或避开什么。能查的事实由你查,未知成本与产线不阻断早期研究。
- 形成简短研究简报:决策问题、研究对象/排除、目标任务与现有替代、真实限制及来源、已有证据、关键未知。然后给本组对应的选材路线,说明首节需要什么数据与为什么。不同小组依各自任务和真实选择形成方向,不随机分题,不把示例拌面方案当本组答案,不替我选择。沿本简报接续后续环节,已答内容不再问。'''
- configure_evidence_feedback(data)
- dump(OUT/'manual-source.json',data)
- prompts=[]
- for s in data['steps']:
- if s.get('kind') == 'introduction':
- prompts.append({**s, 'fullPrompt': ''})
- continue
- text=s.get('prompt') or f"{data['common']}\n\n本节问题:{s['question']}\n复用输入:{s['inputs']}\n按需细则:{s['skillReference']}\n\n选材要求:{s['retrieval']}\n\n本节任务:{s['task']}\n\n交付:\n"+'\n'.join('• '+x for x in s['deliverables'])
- prompts.append({**s,'fullPrompt':text})
- dump(OUT/'prompts-and-outputs.json',dict(version=VERSION,steps=prompts))
- manual=OUT/'manual';manual.mkdir(exist_ok=True)
- (manual/'content.js').write_text('window.WORKSHOP_MANUAL_DATA = '+json.dumps(data,ensure_ascii=False,indent=2)+';\n',encoding='utf8')
- for name in ['index.html','manual.css','manual.js']:
- if not (manual/name).exists():
- shutil.copyfile(PRIOR/'manual'/name,manual/name)
- p=manual/'index.html';h=p.read_text('utf8').replace('r12 定稿','r13 修订').replace('r12 本地定稿 · Skill与手册配套 · 企业账套待后续接入','r13 本地修订 · 按报告类型选材与分析 · 企业账套后续接入');p.write_text(h,encoding='utf8')
- p=manual/'manual.js';js=p.read_text('utf8').replace('r12-local-final','r13-local-quality')
- js=js.replace('本节要回答:','本节问题:').replace('先复用:','复用输入:').replace('执行细则:','按需细则:')
- js=js.replace('\\n\\n本节任务:\\n${step.task}','\\n\\n选材要求:${step.retrieval}\\n\\n本节任务:${step.task}').replace('本节应交付:','交付:').replace('\\n\\n交付:\\n','\\n\\n交付:\\n')
- # Make the DOM prompt exactly match the archived text, without maintaining two generators.
- prompt_map={s['id']:s['fullPrompt'] for s in prompts}
- start=js.index(' const promptMap =') if ' const promptMap =' in js else js.index(' const fullPrompt =')
- end=js.index('\n',js.index(' const fullPrompt =',start))
- js=js[:start]+' const promptMap = '+json.dumps(prompt_map,ensure_ascii=False)+';\n const fullPrompt = step => promptMap[step.id];'+js[end:]
- js=js.replace('<p class="action-copy">${escape(step.action)}</p>','')
- js=js.replace('${version}\n <div class="prompt-box">','${version}${step.example ? `<p class="example-link"><a href="../${escape(step.example)}" target="_blank">看本节产出样板 ↗</a> · <a href="../参考学习与类型对应.html" target="_blank">参考报告与采用方法 ↗</a></p>` : ""}\n <div class="prompt-box">')
- p.write_text(js,encoding='utf8')
- # The standalone file lives one directory above manual/.
- standalone_js=js.replace('href="../${escape(step.example)}"','href="${escape(step.example)}"').replace('href="../参考学习与类型对应.html"','href="参考学习与类型对应.html"')
- css=(manual/'manual.css').read_text('utf8')+'\n.example-link{font-size:12px;margin:9px 0 14px}.chapter-body{padding-top:14px}.prompt-text{font-size:14px;line-height:1.8}.outputs-box{margin-top:0}\n'
- (manual/'manual.css').write_text(css,encoding='utf8')
- safe=lambda s:s.replace('</script','<\\/script')
- standalone=h.replace('<link rel="stylesheet" href="manual.css">','<style>'+css+'</style>').replace('<script src="content.js" defer></script>','').replace('<script src="manual.js" defer></script>','')
- standalone=standalone.replace('</body>','<script>'+safe((manual/'content.js').read_text('utf8'))+'</script><script>'+safe(standalone_js)+'</script></body>')
- (OUT/'康师傅工作坊操作手册-r13本地修订.html').write_text(standalone,encoding='utf8')
- def package():
- baseline=PRIOR/'installation/skills/ksf-workshop-runtime'
- protected=['runtime-lock.json']+['scripts/'+s for s in ['ksf.mjs','ksf-secure.mjs','ksf-workshop.mjs','ksf-identity.mjs','ksf-identity-bridge.mjs','describe-business-schema.mjs']]
- for n in protected:assert sha(SKILL/n)==sha(baseline/n),n
- oldnames=sorted(json.loads((PRIOR/'installation/release.json').read_bytes())['workshop']['files'])
- names=sorted(set(oldnames+['references/workshop-deliverable-contracts.md']))
- (SKILL/'FILES.sha256').write_text(''.join(sha(SKILL/n)+' '+n+'\n' for n in names if n!='FILES.sha256'),encoding='utf8')
- pkg=OUT/'installation';pkg.mkdir(exist_ok=True)
- for n in names:
- target=pkg/'skills/ksf-workshop-runtime'/n;target.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(SKILL/n,target)
- shutil.copyfile(ROOT/'scripts/workshop-quality-install.py',pkg/'install.py')
- shutil.copyfile(PRIOR/'installation/check-voc.cjs',pkg/'check-voc.cjs')
- meta=json.loads((PRIOR/'installation/release.json').read_bytes())
- meta.update(releaseId='ksf-workshop-report-quality-20261110-r13',localOnly=True)
- meta['workshop'].update(version=VERSION,files={n:sha(SKILL/n) for n in names})
- meta['upgradeFrom']={'r12-local-exact':{n:sha(baseline/n) for n in oldnames}}
- dump(pkg/'release.json',meta)
- (pkg/'FILES.sha256').write_text(''.join(sha(p)+' '+p.relative_to(pkg).as_posix()+'\n' for p in sorted(pkg.rglob('*')) if p.is_file() and p!=pkg/'FILES.sha256'),encoding='utf8')
- with zipfile.ZipFile(OUT/'康师傅工作坊Skill-r13本地修订.zip','w',zipfile.ZIP_DEFLATED) as z:
- for p in sorted(pkg.rglob('*')):
- if p.is_file():z.write(p,p.relative_to(pkg).as_posix())
- (OUT/'INSTALL-本地修订.md').write_text(f'''# {VERSION} 本地资源说明
- 固定本地资源:{pkg.as_posix()}。仅本地修订,未发布线上;不能把旧线上r10下载地址称r13。
- 先核FILES.sha256与release.json,再执行包内install.py只读plan:
- ```text
- python -B install.py --skills-dir <宿主实际支持的用户技能根> --out <本地回执目录>
- ```
- 仅新目录create/已有同版verify-only/完整匹配release.json的r12-local-exact可正常安装或备份升级;r7–r11、未知改动或缺文件停止,不强制覆盖。保留本机credential-binding.json等身份、授权与连接和其他技能。VOC仍沿官方0.3.42固定清单,未在本次修改或重新安装;原研究目录仅为运行兼容路径,不恢复其SKILL.md。
- 本次只改研究选材、分析与产出规则:公开文件{len(names)};六个业务脚本与runtime-lock.json对r12逐字节不变。宿主实际加载、真实工具/资料/保存能力须在目标环境分别核验,不能以文件可读替代。企业账套以后接入,本稿不创建或假定已有账套。
- ''',encoding='utf8')
- return dict(version=VERSION,publicFiles=len(names),protected=[dict(file=n,sha256=sha(SKILL/n),unchanged=True) for n in protected],
- sourceSkill=(SKILL.as_posix()),zipSha256=sha(OUT/'康师傅工作坊Skill-r13本地修订.zip'),upgradeFrom='exact r12-local baseline only',productionWrites=0)
- def documents(ledger):
- reportdir=OUT/'reports';reportdir.mkdir(exist_ok=True)
- nav=''.join(f'<a href="#{s["id"]}">{s["code"]} {s["output"]}</a>' for s in STAGES)
- blocks=[]
- for s in STAGES:
- text=EXAMPLES[s['id']]
- (reportdir/(s['id']+'.md')).write_text(text,encoding='utf8')
- figure=charts(ledger['statistics']) if s['id']=='matrix' else ''
- model='<p><a href="../商业回推演算器.html">打开可重算模型与敏感性演算 ↗</a></p>' if s['id']=='feasibility' else ''
- body=f'<article><div class="kicker">{s["code"]} · {s["output"]}</div>'+cited_markdown(text,'../')+figure+model+'<p class="muted"><a href="../证据底表.html">原句、统计与实际请求</a> · <a href="../参考学习与类型对应.html">采用的报告方法</a> · <a href="../康师傅工作坊操作手册-r13本地修订.html#'+s['id']+'">对应任务提示词</a></p></article>'
- side=''.join(f'<a href="{x["id"]}.html">{x["code"]} {x["title"]}</a>' for x in STAGES)
- (reportdir/(s['id']+'.html')).write_text(shell(s['output']+'|本地报告样板',body,side,'../'),encoding='utf8')
- blocks.append(f'<article id="{s["id"]}"><div class="kicker">{s["code"]} · {s["output"]}</div>'+cited_markdown(text)+figure+model.replace('../商业','商业')+'</article>')
- (OUT/'案例报告合集.html').write_text(shell('宿舍常备拌面|13类产出报告样板','<section class="panel"><p class="lead">真实资料支撑的探索性研究;新增18条定向原文回读,补齐风味选择分析,明确既有供给与候选差异。</p><p>产品规格/组合为研究者原型设定;无双模型、KANO、真实试吃、工厂成本或小组批准。各篇保留未完成部分,不标课程全链验收通过。</p><a href="证据底表.html">回查依据与复算</a></section>'+''.join(blocks),nav),encoding='utf8')
- # Honest, substantive audit of all 13 screenshot deliverables.
- oldfiles=['02-品类全景.md','03-验证规则.md','04-竞品对照.md','05-研究任务与市场报告.md','06-调研与预聚类.md','07-调研验收.md','08-需求与证言池.md','09-供需矩阵与线索.md','10-三源研究卡与V1.md','11-证据与优先级.md','12-商业生产与V2.md','13-价值主张与买点.md','14-概念提案与V3.md']
- audit='''# 对照课表截图的旧稿复核
- 总体:上一版不宜作为合格课程案例。安装、文件哈希、链接、复制和移动排版检查只证明技术行为,未证明研究深度或业务内容。部分原句/分母核查可以保留,但不足以让后续概念自动成立。此次按13类核心产出重新验收,不靠七类导航或15份文件数给合格。
- | 环节/截图要求 | 旧稿状态 | 实际不足 | 本次改法 |
- |---|---|---|---|
- '''
- for s,n in zip(STAGES,oldfiles):audit+=f'| [{s["code"]} {s["output"]}](../workshop-validated-final-20261110/rerun/{n}) | {s["old"]} | {s["audit"]} | 先用适合的数据:{s["retrieval"]} [新样板](reports/{s["id"]}.html) |\n'
- audit+='''
- ## 内容验收与研究验证的分别结论
- r13已重写选材规则、提示词及13类报告正文,并实际补查我方系列、组合与P08原文。报告深度和产品具体程度有可见改动,但不因本轮修订就称全课程达标:行业测量/趋势、首提/考虑/转化、真实双模型、KANO、目标用户观察/感官、成本/工艺和真实评审仍未完成。技术检查另见核验回执。
- ## 不能再用的通过口径
- - 有图谱标题不等于分类和关系成立;有3张卡不等于3种产品价值机制。
- - 写明“待核”不等于已经完成应做的研究;能取得的关键原文仍须实际补查。
- - 所有商业输入未知不妨碍合理探索初排,但不能将探索初排报成商业批准。
- - 同一助手自演多角色、自己看自己的结论不构成独立双模型或真实员工测试。
- '''
- (OUT/'旧稿逐项复核.md').write_text(audit,encoding='utf8')
- (OUT/'旧稿逐项复核.html').write_text(shell('原案例为何不合格|逐项复核','<article>'+md(audit)+'</article>','<a href="index.html">返回首页</a>'),encoding='utf8')
- learning='''# 按产出类型学习报告,而非只借一套页面样式
- 报告成品与方法指南分开。本次已访问并实际读取下列注明的部分;没有把网络报告本身一概认定为完全合格,也未把海外行业数字放进康师傅结论。全文/局部阅读、公开摘要与无法取得全文分别注明。各类型至少有对应参考方法和本地重做的报告样板,不将一个综合VOC报告套满全程。
- | 课程产出类型 | 参考 | 要学的方法 | 本地落实 |
- |---|---|---|---|
- '''
- for s in STAGES:learning+=f'| {s["code"]} {s["output"]} | '+ '、'.join(s['refs'])+' | '+s['question']+f' 用匹配分析单位的数据回答,按{ s["output"] }组织成品。 | [对应样板](reports/{s["id"]}.html);手册和Skill的本节选材/任务/验收已重写 |\n'
- for r in REFERENCES:
- learning+=f'\n## {r["id"]} {r["title"]}\n\n类型:{r["kind"]}\n\n[原始公开来源]({r["url"]})\n\n阅读范围:{r["read"]}\n\n采用:{r["learn"]}\n\n用于:{r["apply"]}\n\n不照搬:{r["reject"]}\n'
- learning+='''
- ## 筛除与未成功获取
- 香薰竞品网页的市场增长/评分缺回查底表,不采用;COMPEX报告发现品牌/ASIN错配,不作为品牌结论范本。网页上的营销承诺、动画KPI、综合画像和点赞数不拿来填消费者证据。GOV.UK/ProductTalk请求超时、部分猜测地址404、NECTAR一页PDF连接失败,均未列作已读全文。康师傅IR验证页与WINA维护页不作为行业数据。完整请求回执在本地work目录,公开来源可从本页链接复核。
- '''
- (OUT/'参考学习与类型对应.md').write_text(learning,encoding='utf8')
- (OUT/'参考学习与类型对应.html').write_text(shell('参考学习|13类产出如何分别改进','<article>'+md(learning)+'</article>',''.join(f'<a href="#r{n}">R{n}</a>' for n in [])),encoding='utf8')
- (OUT/'选材与分析规则.md').write_text('# 如何更精准找到适合分析的数据\n'+RETRIEVAL_RULES,encoding='utf8')
- (OUT/'选材与分析规则.html').write_text(shell('从业务问题到适用证据','<article>'+md('# 如何更精准找到适合分析的数据\n'+RETRIEVAL_RULES)+'</article>'),encoding='utf8')
- evidence='# 原句、底表与实际执行\n\n原E/P/S引用沿旧稿原件索引;R01–R18为本轮按P08商品ID新增回读的真实记录。这里没有真实员工对话、独立第二模型输出或工厂报价。\n\n[旧E/P/S完整原话与来源](../workshop-validated-final-20261110/rerun/证据索引.html) · [新旧编码JSON](证据与编码-r13.json) · [新增18条CSV](新增评价逐条编码.csv) · [提及率复算](提及率复算.csv)\n'
- for r in ledger['addedProducts']:
- evidence+=f'\n## {r["key"]}|新增商品定位\n\n{r["title"]}\n\n完整商品键:`{r["recordId"]}`;源记录:`{r["sourceRecordId"]}`;源时间:{r["sourceTime"]}。{r["qualification"]}。[本次商品原文]({r["responseFile"]}) · [商品来源]({r["url"]})\n'
- if r['priceResponseFile']:
- evidence+=f'\n[该商品的价格原始记录]({r["priceResponseFile"]});逐日期列示,金额原单位为分:\n\n```json\n'+json.dumps(r['priceRows'],ensure_ascii=False,indent=2)+'\n```\n'
- for r in ledger['addedReviews']:
- evidence+=f'\n## {r["key"]}|{r["status"]}\n\n> {r["quote"]}\n\n{r["note"]}\n\n商品:{r["productId"]};购买规格:{r["purchasedSku"]};原日期:{r["dateRaw"]}。完整记录:`{r["recordId"]}`。源记录:`{r["source"]["id"]}`;{r["source"]["asset_path"]}第{r["source"]["line_number"]}行。[本次原文响应]({r["responseFile"]})\n'
- evidence+='\n## 查询回执\n\n'
- for p in sorted((OUT/'data').glob('*.request.json')):
- d=json.loads(p.read_bytes());evidence+=f'- [{p.stem}](data/{p.name}):ok={d["ok"]},readOnly={d["readOnly"]},databaseWrites={d["databaseWrites"]}。\n'
- (OUT/'证据底表.md').write_text(evidence,encoding='utf8')
- rendered=md(evidence)
- rendered=re.sub(r'<(h[1-6])>(R\d{2})',r'<\1 id="\2">\2',rendered)
- (OUT/'证据底表.html').write_text(shell('证据底表|原句、成员与实际查询','<article>'+rendered+charts(ledger['statistics'])+'</article>','<a href="案例报告合集.html">返回报告</a>'),encoding='utf8')
- # Comparison reports focus on actual changes rather than word count or file counts.
- change='''# 本次修订改变了什么
- | 旧版 | 新版 | 实际依据 |
- |---|---|---|
- | 各节多次复述有限材料和下一步观察 | 每节确定主张/单位/来源,再分别作图谱、研究报告、聚类、卡片、回推与提案 | 13份重写正文与配套提示词 |
- | 围绕便利、投料、结构三条近似任务 | 风味分层、首次识别、组合/补货三种价值机制 | 实际补查我方系列,P08新增18条原文回读 |
- | 我方只比较一个混合箱 | 纳入经典单味、探索三味与小组合等反证 | 品牌×品类139命中读首50,关键SKU精核 |
- | 关键词查不到便容易停在缺口 | 查同义与按商品ID完整回读 | “吃腻”零命中但R13原文存在,已记录漏项 |
- | 三项商业等级全部待评 | A/A/B探索初排,无S,理由与升降级可讨论 | 原有Skill定性规则+新增支持/反证,不编成功率 |
- | 财务主要是通用毛利公式 | 针对配比、印刷、履约拆成本及新增/替代贡献临界 | 模型可复算;真实成本仍缺,不报盈利 |
- | 最终只建议观察现品 | 有具体风味、配比、基准、使用/购买路径与反证的概念提案 | 原型设定明确,不冒充已量产或已测试 |
- 保留:原始49条评价、旧稿对话与历史文件不覆盖,业务脚本/身份/连接保持。新增内容只在本地;不称真实小组或豆包端到端测试已通过。
- '''
- (OUT/'修订对照.md').write_text(change,encoding='utf8')
- cards=''.join(f'<a class="card" href="reports/{s["id"]}.html"><small>{s["code"]} · {s["title"]}</small><strong>{s["output"]}</strong><span>{html.escape(s["question"])}</span></a>' for s in STAGES)
- homepage='''<section class="panel"><div class="kicker">先看产出内容</div><h2>按13类成果重做选材、分析与报告</h2><p class="lead">原案例不合格:重要类型缺少实质内容,候选过窄,提案退成了观察计划。本版补查供给与原文,用不同报告类型回答不同问题。</p><p>本地探索案例:宿舍常备拌面的风味选择与准备体验。原49条评价+新增18条定向回读,含相反偏好和误认自述;新概念是研究者设定。企业账套、真实员工测试和线上发布未实施。</p><div class="grid"><a class="card" href="康师傅工作坊操作手册-r13本地修订.html"><small>15节 · 提示词默认展开</small><strong>新版操作手册 ↗</strong>每节有选材要求、产出标准与样板</a><a class="card" href="reports/proposal.html"><small>先看最终报告是否有用</small><strong>产品概念提案 ↗</strong>具体设定、对照、代价和判别</a><a class="card" href="旧稿逐项复核.html"><strong>旧稿逐项复核 ↗</strong>对照截图要求,说明不合格处</a><a class="card" href="参考学习与类型对应.html"><strong>优质做法与采用范围 ↗</strong>行业/消费者/感官/技术经济报告分别学习</a></div></section><section class="panel"><h2>13类产出样板</h2><p class="muted">各篇是独立报告。市场测量、趋势、双模型、KANO、真实实验与成本缺项在对应章节单列,未标全课程已验收。</p><div class="grid">'''+cards+'''</div></section><section class="panel"><h2>规则、底表与Skill</h2><div class="grid"><a class="card" href="选材与分析规则.html"><strong>选材与分析规则</strong>主张→单位→来源→支持/反证</a><a class="card" href="证据底表.html"><strong>原句、统计与执行依据</strong>成员、分母、资格与真实请求</a><a class="card" href="康师傅工作坊Skill-r13本地修订.zip"><strong>r13本地Skill包</strong>仅完整r12基线支持升级;不自动安装</a><a class="card" href="installation/skills/ksf-workshop-runtime/SKILL.md"><strong>查看Skill入口</strong>配套新选材与分类型合同</a></div></section>'''
- (OUT/'index.html').write_text(shell('工作坊产出质量修订|r13本地版',homepage,'<a href="旧稿逐项复核.html">旧稿复核</a><a href="参考学习与类型对应.html">参考学习</a><a href="案例报告合集.html">案例合集</a><a href="选材与分析规则.html">选材规则</a><a href="证据底表.html">依据与复算</a>'),encoding='utf8')
- (OUT/'README.md').write_text('# r13本地内容修订\n\n从index.html查看。旧r12文件和对话保留,未覆盖。主交付是配套Skill/提示词/手册、旧稿逐项复核、分类参考学习、13类实质报告样板与证据编码。技术验收和研究结论边界分开;本次不标全课表实测通过。\n',encoding='utf8')
- def main():
- OUT.mkdir(exist_ok=True)
- data=OUT/'data';data.mkdir(exist_ok=True)
- for p in sorted((WORK/'data').glob('*.json')):shutil.copyfile(p,data/p.name)
- ledger=research_ledger()
- skill_and_manual()
- meta=package()
- documents(ledger)
- build_model(OUT,shell)
- dump(OUT/'本地发行清单.json',{**meta,'manualSha256':sha(OUT/'康师傅工作坊操作手册-r13本地修订.html'),
- 'referenceSources':REFERENCES,'courseReportTypes':len(STAGES),'steps':15,'contentStatus':'revised exploratory reports; full empirical course acceptance not claimed',
- 'oldArtifactPreserved':True})
- print(json.dumps(dict(ok=True,version=VERSION,root=str(OUT),publicFiles=meta['publicFiles']),ensure_ascii=False))
- if __name__=='__main__':main()
|