| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150 |
- """Build a local business-output preview and verify evidence/arithmetic, not insight quality."""
- from pathlib import Path
- from decimal import Decimal
- from collections import Counter
- from datetime import datetime
- import hashlib
- import html
- import json
- import re
- import unicodedata
- WORK = Path(__file__).resolve().parent
- ROOT = WORK.parents[1]
- SKILL = ROOT / 'skills/ksf-workshop-runtime'
- load = lambda p: json.loads(p.read_text(encoding='utf-8'))
- sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
- inputs = load(WORK / 'probe-inputs.json')
- outputs = load(WORK / 'probe-outputs.json')
- raw = load((WORK / inputs['rawConsumerAndCostFixture']).resolve())
- samples = {s['id']: s for s in outputs['samples']}
- checks = []
- def check(name, passed, actual=None):
- checks.append({'name': name, 'passed': bool(passed), 'actual': actual})
- assert passed, (name, actual)
- market = inputs['marketFixture']
- prior, current = market['periods']
- check('同一固定面板', set(prior['panelIds']) == set(current['panelIds']))
- facts = samples['trend']['facts']
- prior_n, current_n, denominator = len(prior['adjustableClaimIds']), len(current['adjustableClaimIds']), len(current['panelIds'])
- check('趋势分子分母来自原始列表', (facts['priorClaimCount'], facts['currentClaimCount'], facts['panelSize']) == (prior_n, current_n, denominator))
- check('占比与百分点分别计算', facts['priorShare'] == prior_n / denominator and facts['currentShare'] == current_n / denominator and Decimal(str(facts['changePercentagePoints'])) == (Decimal(current_n) - Decimal(prior_n)) / Decimal(denominator) * 100)
- check('同源转载未计作独立资料', facts['independentNewsOrigins'] == len({n['original'] for n in market['news']}))
- check('目录范围不同且没有需求增长数', market['broadSearch'][0]['catalogueScope'] != market['broadSearch'][1]['catalogueScope'] and market['sales'] is None and not facts['demandGrowthKnown'])
- reviews = {r['id']: r for r in raw['reviews']}
- seen = set()
- duplicates = []
- for r in raw['reviews']:
- if r['upstreamId'] in seen:
- duplicates.append(r['id'])
- seen.add(r['upstreamId'])
- cf = samples['consumer']['facts']
- check('原始记录规模与重复取得', cf['rawReviewCount'] == len(reviews) and cf['repeatedAcquisitionIds'] == duplicates)
- check('占位记录依据原文', [reviews[x]['text'] for x in cf['placeholderIds']] == ['此用户没有填写评价'])
- check('物流用途排除保留原件', cf['logisticsOnlyIds'] == ['E10'] and reviews['E10']['text'] == '发来外箱压了,面没破,味道还没吃。')
- excluded = set(duplicates + cf['placeholderIds'] + cf['logisticsOnlyIds'])
- check('口味/使用集合没有混入重复占位物流', set(cf['tasteUsageIds']) == set(reviews) - excluded and len(cf['tasteUsageIds']) == len(set(cf['tasteUsageIds'])), len(cf['tasteUsageIds']))
- check('关键原句逐字对应', samples['consumer']['rows'][0][0] == 'E02:' + reviews['E02']['text'] and samples['consumer']['rows'][1][0] == 'E03:' + reviews['E03']['text'])
- check('相反取舍索引回查', cf['counterexampleIds'] == ['E05', 'E06'] and all(x in cf['tasteUsageIds'] for x in cf['counterexampleIds']))
- conditions = raw['scenarioConditions']
- dec = lambda n: Decimal(str(n))
- revenue, base, margin = map(dec, (conditions['netRevenuePerServing'], conditions['baseUnitCost'], conditions['minimumGrossMargin']))
- ceiling = revenue * (1 - margin)
- rf = samples['rd']['facts']
- check('成本上限与增量空间', dec(rf['costCeiling']) == ceiling and dec(rf['incrementalHeadroom']) == ceiling - base, {'ceiling': str(ceiling), 'headroom': str(ceiling - base)})
- estimates = {v['change']: dec(v['extraCost']) for v in conditions['incrementalCostEstimates']}
- for option in rf['options']:
- cost = base + estimates[option['change']]
- gross = (revenue - cost) / revenue
- check('情景成本与毛利:' + option['change'], dec(option['cost']) == cost and abs(dec(option['grossMargin']) - gross) < Decimal('0.000000000001'))
- rev = samples['revision']['facts']
- check('修订保留经营与设备条件', dec(rev['netRevenue']) == revenue and dec(rev['minimumGrossMargin']) == margin and dec(rev['costCeiling']) == ceiling and rev['noodleEquipmentChangeAllowed'] == conditions['noodleEquipmentChangeAllowed'])
- check('修订没有假称新路线已核价或实测', rev['noNewSachet'] and not rev['newRecipeCostVerified'] and not rev['physicalTestPerformed'])
- before = load(WORK / 'before-fingerprints.json')
- runtime = {n: h for n, h in before.items() if n.startswith('scripts/') or n == 'runtime-lock.json'}
- check('七个运行/锁定文件未变', len(runtime) == 7 and all(sha(SKILL / n) == h for n, h in runtime.items()))
- old_course = WORK / 'before/references/workshop-course-flow.md'
- new_course = SKILL / 'references/workshop-course-flow.md'
- check('课表映射文件原字节未变', sha(old_course) == sha(new_course))
- check('13个研究环节顺序保留', re.findall(r'^\| (D[12]-(?:\d\d|N)) \| \d\d \|', new_course.read_text(encoding='utf-8'), re.M) == ['D1-01','D1-02','D1-03','D1-04','D1-N','D2-01','D2-02','D2-03','D2-04','D2-05','D2-06','D2-07','D2-08'])
- # Check real relative resources. Fragment anchors are checked for the newly introduced reference.
- links = []
- for p in SKILL.rglob('*.md'):
- for target in re.findall(r'(?<!!)\[[^\]]*\]\(([^)]+)\)', p.read_text(encoding='utf-8')):
- if re.match(r'^[a-zA-Z][a-zA-Z0-9+.-]*:', target):
- continue
- path, _, fragment = target.partition('#')
- resolved = (p.parent / path).resolve() if path else p
- check('资源可达:' + p.relative_to(SKILL).as_posix() + ' → ' + target, resolved.is_file())
- if resolved.name == 'workshop-innovation-reasoning.md' and fragment:
- def anchor(s):
- return ''.join(c for c in s.strip().lower().replace(' ', '-') if c in '-_' or not unicodedata.category(c).startswith(('P', 'S')))
- headings = re.findall(r'^#+ (.+)$', resolved.read_text(encoding='utf-8'), re.M)
- check('新分析细则锚点:' + fragment, fragment in {anchor(x) for x in headings})
- links.append(target)
- tracked = sorted(p.relative_to(SKILL).as_posix() for p in SKILL.rglob('*') if p.is_file() and p.name != 'FILES.sha256')
- (SKILL / 'FILES.sha256').write_text(''.join(sha(SKILL / n) + ' ' + n + '\n' for n in tracked), encoding='utf-8')
- current_hashes = {p.relative_to(SKILL).as_posix(): sha(p) for p in SKILL.rglob('*') if p.is_file()}
- changed = [n for n in current_hashes if before.get(n) != current_hashes[n]]
- receipt = {
- 'schema': 'ksf.decision-quality-evidence-checks.v1',
- 'checkedAt': datetime.now().astimezone().isoformat(),
- 'scope': 'Deterministic source/arithmetic/link/integrity checks for five manually authored local synthetic probes; not a semantic quality score or independent Agent evaluation',
- 'allChecksPassed': all(c['passed'] for c in checks),
- 'checks': checks,
- 'checksCount': len(checks),
- 'relativeLinks': len(links),
- 'manifestEntries': len(tracked),
- 'skillSha256': current_hashes['SKILL.md'],
- 'manifestSha256': current_hashes['FILES.sha256'],
- 'changedFiles': sorted(changed),
- 'newFiles': sorted(set(current_hashes) - set(before)),
- 'runtimeFilesUnchanged': 7,
- 'researchModulesPreserved': 13,
- 'manualProbes': len(outputs['samples']),
- 'actualDifferentModelReview': False,
- 'independentAgentExecution': False,
- 'realVOCInvocation': False,
- 'productionDatabaseWrites': 0,
- 'paidCalls': 0,
- 'remoteInstalled': False,
- 'forwardRequired': False
- }
- (WORK / 'evidence-checks.json').write_text(json.dumps(receipt, ensure_ascii=False, indent=2) + '\n', encoding='utf-8')
- data = json.dumps(outputs, ensure_ascii=False).replace('<', '\\u003c')
- template = '''<!doctype html>
- <html lang="zh-CN"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
- <meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:">
- <title>工作坊 · 分析与成果样张</title>
- <style>
- :root{color-scheme:light;--ink:#16302c;--muted:#546965;--line:#d8e4dd;--accent:#176b55;--wash:#f2f6f2}
- *{box-sizing:border-box}body{margin:0;background:var(--wash);color:var(--ink);font:16px/1.85 'Microsoft YaHei','PingFang SC',system-ui,sans-serif}button{font:inherit;cursor:pointer}button:focus-visible{outline:3px solid #f2b44c;outline-offset:3px}
- .wrap{max-width:1200px;margin:auto;padding:38px 30px 64px}.eyebrow{font-size:14px;letter-spacing:.08em;color:var(--accent);font-weight:700}h1{font-size:34px;line-height:1.4;margin:10px 0 15px}h2{font-size:27px;line-height:1.55;margin:0 0 18px}h3{font-size:18px;margin:0 0 10px}.intro{max-width:800px;margin:0;color:var(--muted)}.notice{padding:12px 18px;background:#fff5dd;border:1px solid #e8d9aa;border-radius:10px;margin:22px 0;font-size:15px;color:#695323}
- nav{display:flex;gap:9px;flex-wrap:wrap;margin:22px 0}nav button,.mode button{border:1px solid var(--line);border-radius:8px;background:white;padding:9px 16px;color:var(--ink)}nav button[aria-current=true],.mode button[aria-pressed=true]{background:var(--accent);border-color:var(--accent);color:white}.layout{display:grid;grid-template-columns:minmax(0,1fr) 260px;gap:24px;align-items:start}article{min-width:0;background:white;border:1px solid var(--line);border-radius:16px;padding:30px}.kicker{font-size:15px;color:var(--accent);margin-bottom:10px;font-weight:700}.mode{display:flex;gap:8px;margin:0 0 22px}.mode button{padding:7px 13px;font-size:15px}.answer{font-size:19px;line-height:1.9;font-weight:600;margin:0 0 18px}.meaning{margin:0 0 22px}.next{border-left:4px solid var(--accent);background:#f0f7f2;padding:15px 20px;margin-top:24px}.next p{margin:0}.aside-card{border:1px solid var(--line);border-radius:12px;padding:20px;background:#e8efe8}.aside-card p{margin:0 0 16px}.aside-card p:last-child{margin-bottom:0}.aside-card strong{display:block}.evidence{border-top:1px solid var(--line);padding-top:18px;margin-top:25px;color:var(--muted);font-size:15px}.table-wrap{overflow-x:auto;border:1px solid var(--line);border-radius:9px}table{border-collapse:collapse;width:100%;min-width:660px;table-layout:fixed}th,td{padding:14px 16px;border-bottom:1px solid var(--line);vertical-align:top;text-align:left;font-size:15px;overflow-wrap:anywhere}th{background:#ecf3ed;color:#235442;font-weight:700}tr:last-child td{border:0}td:first-child{font-weight:600}.view-note{font-size:15px;color:var(--muted);margin-bottom:14px}footer{margin-top:25px;font-size:15px;color:var(--muted)}[hidden]{display:none!important}
- @media(max-width:800px){.layout{grid-template-columns:minmax(0,1fr)}.wrap{padding:25px 20px 45px}.aside-card{display:none}h1{font-size:28px}article{padding:24px}}
- @media(max-width:480px){.wrap{padding:20px 14px 40px}h1{font-size:25px}h2{font-size:23px}.answer{font-size:17px}.notice{padding:10px 13px}nav{gap:7px}nav button{padding:7px 11px;font-size:15px}article{padding:20px 17px;border-radius:11px}.next{padding:13px 15px}.mode{gap:7px}th,td{padding:12px}}
- </style>
- <div class="wrap"><div class="eyebrow">产品创新工作坊 · 本地质量走查</div><h1>让分析进入产品讨论</h1><p class="intro">查看同一组构造素材怎样形成趋势判断、消费者洞察、研发比较,以及用户提出修改后怎样接续。业务回复便于快速讨论,评审底表保留具体对照。</p>
- <div class="notice">构造素材示例 · 全部产品、原话与经营数字均为本地测试数据。未进行真实消费者研究、试制、业务保存或发布。</div>
- <nav aria-label="样张类型" id="tabs"></nav><div class="layout"><article id="content"></article><aside class="aside-card"><h3>怎样判断它有用</h3><p><strong>先看认识</strong>有没有回答当前问题,说明它为什么影响产品选择。</p><p><strong>再看取舍</strong>能否比较现有办法与改动,保留成本、操作与体验代价。</p><p><strong>最后看判别</strong>下一项证据会让我们继续、修改还是停止。</p><p>样张由主助手人工走查,不代表独立模型或真实业务验收。</p></aside></div><footer>本页只展示业务内容。完整素材、算术检查和本地规则修改记录保留在本地档案;未生成新的部署包。</footer></div>
- <script>
- const DATA=__DATA__;let selected=0;let mode='reply';const names=['趋势扫描','消费者洞察','研发对照','修改反馈','简短回答'];
- const esc=s=>String(s).replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
- function render(){const s=DATA.samples[selected];document.querySelector('#tabs').innerHTML=DATA.samples.map((v,i)=>`<button data-select="${i}" aria-current="${i===selected}">${esc(names[i])}</button>`).join('');
- let table=s.columns.length?`<div class="view-note">横向比较具体选项;窄屏可在表格内滑动,字号保持可读。</div><div class="table-wrap"><table><thead><tr>${s.columns.map(c=>`<th>${esc(c)}</th>`).join('')}</tr></thead><tbody>${s.rows.map(r=>`<tr>${r.map(c=>`<td>${esc(c)}</td>`).join('')}</tr>`).join('')}</tbody></table></div>`:'<p>这个问题只需简短判断,不额外套用报告或图表。</p>';
- document.querySelector('#content').innerHTML=`<div class="kicker">${esc(s.kicker)} · 构造样张</div><h2>${esc(s.title)}</h2><div class="mode"><button data-mode="reply" aria-pressed="${mode==='reply'}">业务回复</button><button data-mode="report" aria-pressed="${mode==='report'}">评审底表</button></div><p class="answer">${esc(s.answer)}</p><p class="meaning">${esc(s.meaning)}</p><section id="report" ${mode==='report'?'':'hidden'}>${table}</section>${s.next?`<div class="next"><h3>建议接续</h3><p>${esc(s.next)}</p></div>`:''}<div class="evidence"><strong>依据与适用范围</strong><br>${esc(s.evidence)}</div>`;
- document.querySelectorAll('[data-select]').forEach(b=>b.onclick=()=>{selected=Number(b.dataset.select);render()});document.querySelectorAll('[data-mode]').forEach(b=>b.onclick=()=>{mode=b.dataset.mode;render()});}
- render();
- </script></html>'''
- (WORK / '分析与成果样张.html').write_text(template.replace('__DATA__', data), encoding='utf-8')
- print(json.dumps({k: receipt[k] for k in ['allChecksPassed','checksCount','relativeLinks','manifestEntries','changedFiles','newFiles','skillSha256','runtimeFilesUnchanged','researchModulesPreserved']}, ensure_ascii=False, indent=2))
|