"""Build four self-contained public demo pages from an explicit safe allowlist."""
from pathlib import Path
import re,json,html,hashlib
ROOT=Path(__file__).resolve().parents[1];OUT=ROOT/'public-prototype';OUT.mkdir(exist_ok=True)

def script(s):return s.replace('</script','<\\/script')
def routes(s):
 for old in ['../prototype/food-movement-calendar.html','../prototype/pc.html']:s=s.replace(old,old.split('/')[-1])
 for old in ['../food-movement-calendar-prd.html','../oura-service-blueprint.html','../health-data-integration.html']:s=s.replace(old,'requirements.html')
 return s

def inline(source):
 s=source.read_text()
 def css(m):
  p=source.parent/m[1];assert p.is_file();return '<style>'+p.read_text()+'</style>'
 s=re.sub(r'<link rel="stylesheet" href="([^"]+)">',css,s)
 def js(m):
  p=source.parent/m[1];assert p.is_file();return '<script>'+script(routes(p.read_text()))+'</script>'
 s=re.sub(r'<script src="([^"]+)"></script>',js,s)
 return routes(s)

notice='<div style="padding:7px 14px;background:#fff4e6;color:#714819;text-align:center;font:12px/1.5 sans-serif">公开交互演示 · 仅用合成数据，请勿填写真实健康信息或上传私人照片 · <a href="requirements-prototype-review.html">评审入口</a></div>'
for name in ['food-movement-calendar.html','pc.html']:
 s=inline(ROOT/'prototype'/name);s=re.sub(r'(<body[^>]*>)',lambda m:m[1]+notice,s,count=1);(OUT/name).write_text(s)

source=json.loads((ROOT/'review/review-data.json').read_text());source['version']='2026-09-14-public-r1'
for i in source['items']:i['source']='requirements.html#'+i['id'].lower();i['owner']='对应产品/技术/专业负责人，待评审确定'
screens=json.loads((ROOT/'review/screens.json').read_text())
for x in screens:x['entry']=x['entry'].split('/')[-1]
p=ROOT/'review/requirements-prototype-review.html';s=inline(p)
# The public mirror only exposes reviewed requirements and synthetic demos, not internal research/evidence files.
a=s.index('<section id="documents"');b=s.index('</section>',a)+len('</section>')
s=s[:a]+'''<section id="documents" class="panel" hidden><h2>公开评审资料</h2><div class="two"><article class="card"><h3>需求与原型</h3><ul><li><a href="requirements.html">45项需求与当前覆盖情况</a></li><li><a href="food-movement-calendar.html" target="_blank" rel="noopener">手机小程序交互原型</a></li><li><a href="pc.html" target="_blank" rel="noopener">营养师/教练PC交互原型</a></li></ul></article><article class="card"><h3>公开版本说明</h3><p>可操作原型采用合成数据；真实设备、模型、医疗判断和提醒发送尚未接通。现有45项评审基线包含后续待做需求；附带饮食记录交互仍为演示。</p><p>个人健康资料、内部研究证据和设备协议原件不在公开包内。评审意见仅保存在你的浏览器，可导出交回，未连接团队服务端。</p></article></div></section>'''+s[b:]
s=s.replace('href="scope-and-gaps.html"','href="requirements.html#scope"')
s=re.sub(r'<p><a href="screens.csv".*?</p>','<p>页面状态以本页清单为准；完整规则见<a href="requirements.html">公开需求</a>。</p>',s,flags=re.S)
s=s.replace('<a href="requirements.csv" download>全量需求 CSV</a>','<a href="requirements.html">全量需求阅读版</a>')
s=s.replace('fetch(\'review-data.json\')',"Promise.resolve(new Response(document.getElementById('public-data').textContent))")
s=s.replace("fetch('screens.json')","Promise.resolve(new Response(document.getElementById('public-screens').textContent))")
s=s.replace("cpt-requirements-review-20260911-r1","cpt-public-review-20260914-r1")
s=s.replace('review-data.json','public-data-inlined')
def json_tag(id,obj):return '<script type="application/json" id="'+id+'">'+json.dumps(obj,ensure_ascii=False).replace('<','\\u003c')+'</script>'
s=s.replace('<script>\'use strict\';',json_tag('public-data',source)+json_tag('public-screens',screens)+"<script>'use strict';",1)
assert 'id="public-data"' in s
s=s.replace('2026.09.11 · Review 1','2026.09.14 · 公开评审版')
(OUT/'requirements-prototype-review.html').write_text(s)
css=(ROOT/'review/review.css').read_text();parts=[]
for i in source['items']:
 parts.append(f'<section id="{html.escape(i["id"].lower())}" class="card" style="margin:16px 0"><span class="eyebrow">{html.escape(i["id"])} · {html.escape(i["confirmation"])} · {html.escape(i["priority"])}</span><h2 style="font-size:22px">{html.escape(i["title"])}</h2><p>{html.escape(i["rule"])}</p><p><b>验收：</b>{html.escape(i["acceptance"])}</p><p><b>当前差距：</b>{html.escape(i["gap"])}</p><p class="muted">页面：{html.escape(i["screens"])}；{html.escape(i["uc"])}</p></section>')
(OUT/'requirements.html').write_text('<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>AI运动营养师 · 公开需求清单</title><style>'+css+'</style></head><body class="webpage"><header><div><h1>AI运动营养师 · 公开需求清单</h1><a href="requirements-prototype-review.html">返回可交互评审</a></div></header><main style="max-width:1100px"><section id="scope" class="card"><h2>范围与状态</h2><p>45项评审基线：16项具备合成演示、16项待补完整原型、5项接入/规则待定、5项非界面约束、3项后续建议。排序和试点方案等待人工review，不能作为生产Ready或健康效果证据。</p><p>当前公开副本保留现有手机与专业PC交互。不会接入真实健康账户、下单或发送消息。内部证据和私人资料未发布；请仅使用合成样例。</p></section>'+''.join(parts)+'</main></body></html>')
manifest=[]
for p in sorted(OUT.glob('*.html')):
 t=p.read_text()
 assert not re.search(r'/Users/|127\.0\.0\.1|姜阳|(?<!\d)(4\.91|81\.8|2\.77|0\.08)(?!\d)',t),p.name
 assert not re.search(r'(?:src|href)="\.\./',t),p.name
 manifest.append({'file':str(p.relative_to(ROOT)),'bytes':p.stat().st_size,'sha256':hashlib.sha256(p.read_bytes()).hexdigest()})
(ROOT/'publication/public-manifest.json').write_text(json.dumps({'date':'2026-09-14','files':manifest,'scope':'four self-contained HTML pages; synthetic UI and sanitized requirements only','excluded':'personal PDF/plan, raw device protocols, internal evidence, credentials, source archives'},ensure_ascii=False,indent=2)+'\n')
print('Built',len(manifest),'public pages, bytes',sum(x['bytes'] for x in manifest))
