#!/usr/bin/env python3
from __future__ import annotations

import csv
import json
from datetime import datetime
from html import escape
from pathlib import Path


ROOT = Path(__file__).resolve().parent
REPO = ROOT.parents[1]
GENERATED_AT = "2026-05-17 15:30 +0800"


def write_csv(path: Path, rows: list[dict[str, object]]) -> None:
    if not rows:
        raise ValueError(f"no rows for {path}")
    with path.open("w", encoding="utf-8", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(rows[0].keys()))
        writer.writeheader()
        writer.writerows(rows)


def read_weekly_food_safety_row() -> dict[str, str]:
    path = REPO / "work/weekly-reports/outputs/2026-W20/smart-canteen-project-xlsx-refresh/product-rd-summary.csv"
    with path.open(encoding="utf-8-sig", newline="") as f:
        for row in csv.DictReader(f):
            if row.get("category") == "食安":
                return row
    return {}


SOURCE_INDEX = [
    {
        "source_id": "SRC-W20-RD-FOOD-SAFETY",
        "evidence_level": "B",
        "source_type": "weekly_report_structured_csv",
        "title": "2026-W20 产品研发周报：食安产品进展",
        "path": "work/weekly-reports/outputs/2026-W20/smart-canteen-project-xlsx-refresh/product-rd-summary.csv",
        "used_for": "确认校园食品安全监管大屏、膳食经费与采购库存监管大屏、食安指数算法、数据创建 agent 等已进入产品推进范围。",
    },
    {
        "source_id": "SRC-FOOD-INTRO",
        "evidence_level": "B",
        "source_type": "product_intro_markdown",
        "title": "康比特食品安全监督管理系统产品简介",
        "path": "modules/product/products/food-safety/product-overview/2026-05-14-food-safety-product-introduction/康比特食品安全监督管理系统产品简介.md",
        "used_for": "复用一个平台 + N 类智能设备、监管角色、PC/移动/大屏入口和食安业务中心口径。",
    },
    {
        "source_id": "SRC-FOOD-SOURCE-EVIDENCE",
        "evidence_level": "B",
        "source_type": "source_evidence",
        "title": "食安产品简介来源证据",
        "path": "modules/product/products/food-safety/product-overview/2026-05-14-food-safety-product-introduction/source-evidence.md",
        "used_for": "确认教委大屏、教委端演示思路、一线访谈提纲和政策支撑材料的微盘来源。",
    },
    {
        "source_id": "SRC-EDU-DASHBOARD-DESIGN",
        "evidence_level": "B",
        "source_type": "converted_docx_markdown",
        "title": "校园食堂数智监管指挥中心_教委大屏端产品概要设计",
        "path": "tmp/product-intro-sources/edu-command-center-design.md",
        "used_for": "作为 4 个大屏页面、模块、指标口径、刷新规则、数据来源、接口和验收口径的主输入。",
    },
    {
        "source_id": "SRC-EDU-DEMO-SCRIPT",
        "evidence_level": "B",
        "source_type": "converted_docx_markdown",
        "title": "食安进销存产品-教委端演示思路",
        "path": "tmp/product-intro-sources/edu-demo-script.md",
        "used_for": "复用“校园餐安全与膳食经费数智监管平台”和 1 个监管底座 + 6 个业务中心 + N 个智能终端叙事。",
    },
    {
        "source_id": "SRC-EDU-INTERVIEW",
        "evidence_level": "B",
        "source_type": "converted_docx_markdown",
        "title": "校园食品安全监管数字化产品一线访谈提纲",
        "path": "tmp/product-intro-sources/edu-interview-outline.md",
        "used_for": "补充责任落实、日周月、六大关键环节、风险分级、整改闭环和数据来源验证口径。",
    },
    {
        "source_id": "SRC-WEDRIVE-INDEX",
        "evidence_level": "B",
        "source_type": "company_wedrive_index",
        "title": "work_company_knowledge 文档索引中学校食安、教委、政策、竞品和设备资料",
        "path": "work_company_knowledge/indexes/document-index.tsv",
        "used_for": "定位校园食品安全、膳食经费、明厨亮灶、食安标准规范和竞品招标资料。",
    },
    {
        "source_id": "SRC-DEMO-SYNTHETIC",
        "evidence_level": "D",
        "source_type": "synthetic_demo_data",
        "title": "昌平区校园食品安全监管大屏演示数据",
        "path": "work/2026-05-17-changping-campus-food-safety-dashboard-demo/demo-data-bundle.json",
        "used_for": "为原型和前端演示构造非零样例数据；不代表昌平区真实统计值。",
    },
]


INDICATORS = [
    ["IND-001", "综合监管", "监管学校数", "纳入监管范围的学校数量。", "count(distinct school_id)", "所", "综合监管驾驶舱", "组织学校主数据", "日级", "B+D", 42, "低于接入清单则提示数据缺失"],
    ["IND-002", "综合监管", "食堂总数", "纳入监管范围的学校食堂数量。", "count(distinct canteen_id)", "个", "综合监管驾驶舱", "组织学校主数据", "日级", "B+D", 58, "低于学校备案食堂数则提示缺口"],
    ["IND-003", "综合监管", "今日就餐人次", "当日学校食堂实际就餐汇总。", "sum(today_meal_count)", "人次", "综合监管驾驶舱", "膳食经费管理系统/结算系统", "准实时/日级", "B+D", 38642, "低于近7日均值30%触发关注"],
    ["IND-004", "综合监管", "今日预警总数", "当日新增和未关闭预警数量。", "count(warning where date=today and status!=closed)", "条", "全部页面", "统一预警中心", "实时/准实时", "B+D", 37, "高风险预警大于5条触发红色态"],
    ["IND-005", "综合监管", "食安综合指数", "按 AI 违规、台账、晨检、留样、农残、消毒、证照、整改等权重计算。", "weighted_score(food_safety_dimensions)", "分", "综合监管驾驶舱/食品安全监管大屏", "食品安全智慧管理云平台", "日级", "B+D", 91.8, "低于80进入待关注学校"],
    ["IND-006", "综合监管", "营养综合评分", "按食谱营养均衡度、达标率、食材多样性、执行和家长评价综合计算。", "weighted_score(nutrition_dimensions)", "分", "综合监管驾驶舱/营养食谱监管大屏", "营养数字化管理平台", "周级/日级", "B+D", 88.6, "低于80或连续下降触发关注"],
    ["IND-007", "综合监管", "设备在线率", "在线设备数占接入设备总数比例。", "online_device_count / device_count * 100", "%", "综合监管驾驶舱/食品安全监管大屏", "设备物联网平台", "1-5分钟", "B+D", 96.4, "低于95%黄色，低于90%红色"],
    ["IND-008", "综合监管", "预警处置完成率", "已关闭预警占应处置预警比例。", "closed_warning_count / actionable_warning_count * 100", "%", "综合监管驾驶舱", "统一预警中心/整改后台", "准实时", "B+D", 87.9, "低于85%提示督办"],
    ["IND-009", "食安监管", "今日 AI 预警", "当日 AI 分析设备识别的后厨违规数量。", "count(ai_warning where date=today)", "条", "食品安全监管大屏", "AI 分析盒/视频平台", "实时", "B+D", 16, "高风险违规直接入督办"],
    ["IND-010", "食安监管", "台账上报率", "已上报食安台账数占应上报台账数比例。", "submitted_ledger_count / required_ledger_count * 100", "%", "食品安全监管大屏", "学校端台账", "按提交刷新", "B+D", 94.7, "低于90%预警"],
    ["IND-011", "食安监管", "晨检完成率", "已完成晨检人数占应晨检从业人员比例。", "checked_staff_count / required_staff_count * 100", "%", "食品安全监管大屏", "晨检仪/学校端晨检台账", "按提交刷新", "B+D", 97.2, "低于95%提示补检"],
    ["IND-012", "食安监管", "晨检异常人数", "晨检中体温、健康状态或手部卫生异常人员数量。", "count(morning_check where result=abnormal)", "人", "食品安全监管大屏", "晨检仪/学校端晨检台账", "实时/日级", "B+D", 3, "任一异常需停岗或复核"],
    ["IND-013", "食安监管", "留样完成率", "已完成留样记录占应留样餐次比例。", "completed_sample_count / required_sample_count * 100", "%", "食品安全监管大屏", "留样柜/留样台账", "按提交刷新", "B+D", 96.8, "低于95%预警"],
    ["IND-014", "食安监管", "农残异常数", "农残检测不合格或异常未闭环数量。", "count(pesticide_test where result=abnormal and status!=closed)", "条", "食品安全监管大屏", "农残检测仪/学校台账", "按检测刷新", "B+D", 2, "异常必须进入整改闭环"],
    ["IND-015", "食安监管", "证照临期数", "健康证、许可证、供应商资质等临近到期数量。", "count(certificate where expire_days<=30)", "项", "食品安全监管大屏", "资质证照管理", "日级", "B+D", 11, "临期30天黄色，到期红色"],
    ["IND-016", "食安监管", "整改超期数", "超过处置期限仍未关闭的风险事件。", "count(warning where due_at<now and status!=closed)", "条", "全部页面", "整改督办后台", "准实时", "B+D", 5, "超期进入重点督办"],
    ["IND-017", "食安监管", "明厨亮灶在线率", "在线视频点位占重点视频点位比例。", "online_camera_count / camera_count * 100", "%", "食品安全监管大屏", "摄像头/NVR/视频网关", "1-5分钟", "B+D", 97.1, "离线超过阈值生成设备预警"],
    ["IND-018", "食安监管", "监督检查完成率", "已完成检查任务占应完成任务比例。", "completed_inspection_count / planned_inspection_count * 100", "%", "食品安全监管大屏", "教育局端监督检查", "日级/周级", "B+D", 92.3, "低于90%提示任务逾期"],
    ["IND-019", "经费采购", "今日采购金额", "当日采购订单或采购验收归集金额。", "sum(purchase_amount where date=today)", "元", "膳食经费与采购库存监管大屏", "校园食材集采平台", "按单据刷新", "B+D", 428600, "较7日均值偏离30%提示关注"],
    ["IND-020", "经费采购", "验收金额", "当日完成配送验收的金额。", "sum(accepted_amount where date=today)", "元", "膳食经费与采购库存监管大屏", "集采平台/进销存系统", "按单据刷新", "B+D", 397800, "验收低于采购计划80%提示未完成"],
    ["IND-021", "经费采购", "库存金额", "当前库存按最新入库价折算金额。", "sum(stock_qty * latest_price)", "元", "膳食经费与采购库存监管大屏", "进销存系统", "日级/按单据刷新", "B+D", 1198600, "异常升高或过低提示盘点"],
    ["IND-022", "经费采购", "人均餐费", "膳食支出金额除以实际就餐人次。", "meal_expense / meal_count", "元/人", "膳食经费与采购库存监管大屏", "膳食经费管理系统/结算系统", "日级", "B+D", 12.8, "超出餐标区间提示关注"],
    ["IND-023", "经费采购", "采购执行率", "已验收金额或数量占计划金额或数量比例。", "accepted_amount / planned_amount * 100", "%", "膳食经费与采购库存监管大屏", "采购计划/订单/验收", "按单据刷新", "B+D", 93.4, "低于90%提示执行偏差"],
    ["IND-024", "经费采购", "库存预警数", "库存不足、临期、过期、盘点差异等未关闭预警数量。", "count(stock_warning where status!=closed)", "条", "膳食经费与采购库存监管大屏", "进销存系统", "准实时/日级", "B+D", 14, "过期库存高风险"],
    ["IND-025", "经费采购", "供应商准时配送率", "准时配送订单占应配送订单比例。", "on_time_delivery_count / delivery_order_count * 100", "%", "膳食经费与采购库存监管大屏", "集采平台/供应商端", "日级", "B+D", 95.6, "低于90%提示履约异常"],
    ["IND-026", "经费采购", "采购价格异常数", "当前价格明显偏离历史均价、协议价或同类学校价格的商品数。", "count(price_exception where status!=closed)", "条", "膳食经费与采购库存监管大屏", "集采平台/价格监管", "按单据刷新", "B+D", 6, "偏离超过20%提示人工核查"],
    ["IND-027", "经费采购", "营养关键食材保障率", "奶类、蛋类、肉类、蔬果、豆制品、水产等关键食材采购满足率。", "matched_key_ingredient_qty / planned_key_ingredient_qty * 100", "%", "膳食经费与采购库存监管大屏/营养食谱监管大屏", "营养平台/采购库存系统", "日级/周级", "B+D", 91.2, "低于90%提示采购与食谱不匹配"],
    ["IND-028", "营养食谱", "带量食谱公示率", "已公示学校数占应公示学校数比例。", "published_recipe_school_count / required_recipe_school_count * 100", "%", "营养食谱监管大屏", "营养数字化管理平台/家长端", "按周/按公示刷新", "B+D", 95.2, "低于95%提示未公示"],
    ["IND-029", "营养食谱", "食谱完整率", "填写餐次、学段、人数、菜品、食材、用量、营养标签、过敏原提示的食谱占比。", "complete_recipe_count / recipe_count * 100", "%", "营养食谱监管大屏", "营养数字化管理平台", "周级", "B+D", 92.9, "低于90%提示补全"],
    ["IND-030", "营养食谱", "营养达标率", "营养素与推荐值比对后达标项目比例。", "qualified_nutrition_item_count / nutrition_item_count * 100", "%", "营养食谱监管大屏", "营养分析/营养素库", "周级/日级", "B+D", 89.7, "低于85%预警"],
    ["IND-031", "营养食谱", "食谱执行率", "按计划执行餐次占应执行餐次比例，结合采购入库、出库消耗或执行照片校验。", "executed_meal_count / planned_meal_count * 100", "%", "营养食谱监管大屏", "营养平台/采购库存/学校端", "日级", "B+D", 91.5, "低于90%提示执行偏差"],
    ["IND-032", "营养食谱", "食谱执行偏差数", "计划菜品与实际菜品、采购或出库不一致的偏差数量。", "count(recipe_execution_diff where status!=closed)", "条", "营养食谱监管大屏", "营养平台/采购库存/学校端", "日级", "B+D", 8, "连续偏差进入风险预警"],
    ["IND-033", "营养食谱", "家长评价均分", "家长对菜品、食谱、营养搭配和食堂服务评价均值。", "avg(parent_rating_score)", "分", "综合监管驾驶舱/营养食谱监管大屏", "家长端/公众端", "日级/周级", "B+D", 4.6, "低于4.0提示舆情关注"],
    ["IND-034", "营养食谱", "连续低评分学校数", "连续多个周期营养评分低于阈值的学校数量。", "count(school where nutrition_score<80 for consecutive_periods)", "所", "综合监管驾驶舱/营养食谱监管大屏", "营养数字化管理平台", "周级", "B+D", 3, "连续2周低于80进入重点关注"],
    ["IND-035", "设备物联", "设备告警数", "摄像头、分析盒、晨检仪、留样柜、农残设备、公示盒等设备告警数量。", "count(device_alarm where status!=closed)", "条", "综合监管驾驶舱/食品安全监管大屏", "设备物联网平台", "1-5分钟", "B+D", 12, "核心设备离线高风险"],
    ["IND-036", "社会共治", "投诉反馈处理率", "已回复或关闭投诉反馈占应处理反馈比例。", "handled_feedback_count / actionable_feedback_count * 100", "%", "综合监管驾驶舱/营养食谱监管大屏", "家长端/公示反馈后台", "日级", "B+D", 88.4, "低于85%提示处理滞后"],
]


def indicator_rows() -> list[dict[str, object]]:
    fields = [
        "indicator_id",
        "domain",
        "display_name",
        "definition",
        "formula",
        "unit",
        "page_modules",
        "primary_data_source",
        "refresh_frequency",
        "evidence_level",
        "demo_value",
        "alert_rule",
    ]
    return [dict(zip(fields, row)) for row in INDICATORS]


SCHOOLS = [
    ["CP-DEMO-001", "昌平演示学校A", "小学", "城北片区", 1, 1260, 1760, 92.6, 89.4, "中风险", 96.8, 4.7],
    ["CP-DEMO-002", "昌平演示学校B", "九年一贯制", "回龙观片区", 2, 2380, 3340, 88.9, 86.1, "中风险", 94.2, 4.5],
    ["CP-DEMO-003", "昌平演示学校C", "初中", "沙河片区", 1, 1680, 2210, 95.4, 91.3, "低风险", 98.1, 4.8],
    ["CP-DEMO-004", "昌平演示学校D", "高中", "南口片区", 1, 1520, 2035, 83.7, 82.6, "高风险", 91.5, 4.1],
    ["CP-DEMO-005", "昌平演示学校E", "幼儿园", "天通苑片区", 1, 820, 1096, 94.1, 90.5, "低风险", 97.4, 4.9],
    ["CP-DEMO-006", "昌平演示学校F", "小学", "昌平城区", 1, 1340, 1880, 90.8, 87.9, "中风险", 95.9, 4.6],
    ["CP-DEMO-007", "昌平演示学校G", "中职", "东小口片区", 1, 1180, 1542, 86.5, 84.8, "中风险", 92.8, 4.3],
    ["CP-DEMO-008", "昌平演示学校H", "小学", "北七家片区", 1, 960, 1315, 96.2, 92.1, "低风险", 98.5, 4.8],
]


def school_rows() -> list[dict[str, object]]:
    fields = [
        "school_id",
        "school_name",
        "school_type",
        "area",
        "canteen_count",
        "student_count",
        "today_meal_count",
        "food_safety_index",
        "nutrition_score",
        "risk_level",
        "device_online_rate",
        "parent_rating",
    ]
    return [dict(zip(fields, row)) for row in SCHOOLS]


KPI_ROWS = [
    {"kpi_id": "KPI-001", "kpi_name": "监管学校数", "value": 42, "unit": "所", "trend": "+3", "data_source": "组织学校主数据", "evidence_level": "D"},
    {"kpi_id": "KPI-002", "kpi_name": "食堂总数", "value": 58, "unit": "个", "trend": "+4", "data_source": "组织学校主数据", "evidence_level": "D"},
    {"kpi_id": "KPI-003", "kpi_name": "今日就餐人次", "value": 38642, "unit": "人次", "trend": "+6.2%", "data_source": "结算/膳食经费", "evidence_level": "D"},
    {"kpi_id": "KPI-004", "kpi_name": "今日预警总数", "value": 37, "unit": "条", "trend": "-8.5%", "data_source": "统一预警中心", "evidence_level": "D"},
    {"kpi_id": "KPI-005", "kpi_name": "食安综合指数", "value": 91.8, "unit": "分", "trend": "+1.4", "data_source": "食品安全智慧管理云平台", "evidence_level": "D"},
    {"kpi_id": "KPI-006", "kpi_name": "营养综合评分", "value": 88.6, "unit": "分", "trend": "+2.1", "data_source": "营养数字化管理平台", "evidence_level": "D"},
    {"kpi_id": "KPI-007", "kpi_name": "设备在线率", "value": 96.4, "unit": "%", "trend": "+0.8%", "data_source": "设备物联网平台", "evidence_level": "D"},
    {"kpi_id": "KPI-008", "kpi_name": "预警处置完成率", "value": 87.9, "unit": "%", "trend": "+4.6%", "data_source": "整改督办后台", "evidence_level": "D"},
]


WARNING_EVENTS = [
    ["W-20260517-001", "CP-DEMO-004", "昌平演示学校D", "食品安全", "AI违规-未戴口罩", "高", "待处理", "后厨加工区", "2026-05-17 08:42", "食品安全监管大屏"],
    ["W-20260517-002", "CP-DEMO-002", "昌平演示学校B", "设备物联", "摄像头离线", "中", "处理中", "洗消间", "2026-05-17 09:16", "食品安全监管大屏"],
    ["W-20260517-003", "CP-DEMO-007", "昌平演示学校G", "采购库存", "库存临期", "中", "待复核", "冷藏库", "2026-05-17 09:28", "膳食经费与采购库存监管大屏"],
    ["W-20260517-004", "CP-DEMO-006", "昌平演示学校F", "营养食谱", "食谱执行偏差", "中", "处理中", "午餐", "2026-05-17 10:05", "营养食谱监管大屏"],
    ["W-20260517-005", "CP-DEMO-001", "昌平演示学校A", "食品安全", "台账未上报", "低", "待处理", "留样记录", "2026-05-17 10:21", "食品安全监管大屏"],
    ["W-20260517-006", "CP-DEMO-004", "昌平演示学校D", "食品安全", "整改超期", "高", "已超期", "监督检查", "2026-05-17 10:40", "综合监管驾驶舱"],
    ["W-20260517-007", "CP-DEMO-005", "昌平演示学校E", "家长反馈", "集中低分评价", "低", "处理中", "家长端", "2026-05-17 11:02", "营养食谱监管大屏"],
    ["W-20260517-008", "CP-DEMO-003", "昌平演示学校C", "膳食经费", "人均餐费异常", "中", "待处理", "财务台账", "2026-05-17 11:20", "膳食经费与采购库存监管大屏"],
    ["W-20260517-009", "CP-DEMO-008", "昌平演示学校H", "食品安全", "晨检异常", "中", "已关闭", "晨检仪", "2026-05-17 07:55", "食品安全监管大屏"],
    ["W-20260517-010", "CP-DEMO-002", "昌平演示学校B", "采购库存", "价格异常", "中", "处理中", "肉禽蛋奶", "2026-05-17 12:14", "膳食经费与采购库存监管大屏"],
]


def warning_rows() -> list[dict[str, object]]:
    fields = ["warning_id", "school_id", "school_name", "domain", "warning_type", "risk_level", "status", "location_or_subject", "occurred_at", "primary_page"]
    return [dict(zip(fields, row)) for row in WARNING_EVENTS]


LEDGER_ROWS = [
    ["CP-DEMO-001", "晨检", 32, 31, 96.9, 1, "待补录"],
    ["CP-DEMO-001", "留样", 18, 17, 94.4, 1, "待处理"],
    ["CP-DEMO-002", "消毒", 24, 22, 91.7, 2, "处理中"],
    ["CP-DEMO-003", "陪餐", 10, 10, 100.0, 1, "已完成"],
    ["CP-DEMO-004", "农残快检", 12, 10, 83.3, 2, "重点督办"],
    ["CP-DEMO-005", "废弃物", 8, 8, 100.0, 1, "已完成"],
    ["CP-DEMO-006", "卫生检查", 14, 13, 92.9, 1, "待复核"],
    ["CP-DEMO-007", "日管控", 7, 6, 85.7, 1, "处理中"],
]


def ledger_rows() -> list[dict[str, object]]:
    fields = ["school_id", "ledger_type", "required_count", "submitted_count", "submit_rate", "abnormal_count", "status"]
    return [dict(zip(fields, row)) for row in LEDGER_ROWS]


PURCHASE_ROWS = [
    ["P-001", "CP-DEMO-001", "蔬菜类", "绿叶菜", "供应商A", 38500, 37400, 96.1, 2, 1],
    ["P-002", "CP-DEMO-002", "肉禽蛋奶", "鸡蛋", "供应商B", 61800, 59600, 94.8, 1, 2],
    ["P-003", "CP-DEMO-003", "米面粮油", "大米", "供应商C", 52800, 52800, 98.4, 1, 1],
    ["P-004", "CP-DEMO-004", "水产豆制品", "豆腐", "供应商D", 28600, 24100, 82.5, 3, 1],
    ["P-005", "CP-DEMO-005", "蔬果", "苹果", "供应商E", 22200, 21800, 97.6, 1, 1],
    ["P-006", "CP-DEMO-006", "肉禽蛋奶", "牛奶", "供应商F", 43200, 41900, 95.9, 2, 1],
    ["P-007", "CP-DEMO-007", "调味品", "食用盐", "供应商G", 9600, 9400, 93.1, 1, 1],
    ["P-008", "CP-DEMO-008", "蔬菜类", "西红柿", "供应商H", 19600, 19000, 96.8, 1, 1],
]


def purchase_rows() -> list[dict[str, object]]:
    fields = ["purchase_id", "school_id", "ingredient_category", "goods_name", "supplier_name", "planned_amount", "accepted_amount", "acceptance_rate", "stock_warning_count", "price_exception_count"]
    return [dict(zip(fields, row)) for row in PURCHASE_ROWS]


NUTRITION_ROWS = [
    ["R-001", "CP-DEMO-001", "2026-W20", "小学", "午餐", 91.2, 95.0, 92.0, 2, 4.7],
    ["R-002", "CP-DEMO-002", "2026-W20", "九年一贯制", "午餐", 86.4, 91.0, 88.0, 3, 4.5],
    ["R-003", "CP-DEMO-003", "2026-W20", "初中", "午餐", 92.6, 97.0, 94.0, 1, 4.8],
    ["R-004", "CP-DEMO-004", "2026-W20", "高中", "午餐", 81.7, 88.0, 84.0, 4, 4.1],
    ["R-005", "CP-DEMO-005", "2026-W20", "幼儿园", "午餐", 90.5, 96.0, 93.0, 1, 4.9],
    ["R-006", "CP-DEMO-006", "2026-W20", "小学", "午餐", 87.9, 92.0, 89.0, 2, 4.6],
    ["R-007", "CP-DEMO-007", "2026-W20", "中职", "午餐", 84.8, 90.0, 86.0, 3, 4.3],
    ["R-008", "CP-DEMO-008", "2026-W20", "小学", "午餐", 92.1, 98.0, 95.0, 1, 4.8],
]


def nutrition_rows() -> list[dict[str, object]]:
    fields = ["recipe_id", "school_id", "week_no", "grade_stage", "meal_type", "nutrition_score", "recipe_publish_rate", "execution_rate", "execution_diff_count", "parent_rating"]
    return [dict(zip(fields, row)) for row in NUTRITION_ROWS]


DEVICE_ROWS = [
    ["D-001", "CP-DEMO-001", "摄像头", "后厨加工区", 18, 17, 94.4, 1],
    ["D-002", "CP-DEMO-002", "AI分析盒", "视频机房", 2, 2, 100.0, 1],
    ["D-003", "CP-DEMO-003", "晨检仪", "员工入口", 1, 1, 100.0, 1],
    ["D-004", "CP-DEMO-004", "智能留样柜", "留样区", 2, 1, 50.0, 2],
    ["D-005", "CP-DEMO-005", "农残检测仪", "验收区", 1, 1, 100.0, 1],
    ["D-006", "CP-DEMO-006", "温湿度传感器", "仓库", 8, 8, 100.0, 1],
    ["D-007", "CP-DEMO-007", "燃气/烟雾传感器", "操作间", 6, 5, 83.3, 2],
    ["D-008", "CP-DEMO-008", "公示屏", "前厅", 1, 1, 100.0, 1],
]


def device_rows() -> list[dict[str, object]]:
    fields = ["device_group_id", "school_id", "device_type", "point_area", "device_count", "online_count", "online_rate", "alarm_count"]
    return [dict(zip(fields, row)) for row in DEVICE_ROWS]


PAGE_MODULES = [
    ["综合监管驾驶舱", "核心指标卡", "学校总数、食堂总数、今日就餐、今日预警、食安指数、营养评分", "组织学校主数据; 结算/经费; 食安平台; 营养平台; 预警中心", "schoolSummary, mealStats, warningSummary, scoreSummary", "school_id,date,org_code", "日级/准实时", "demo-dashboard-kpis.csv"],
    ["综合监管驾驶舱", "辖区地图", "学校点位、风险等级、学校悬浮卡片", "组织学校主数据; 统一预警中心; 食安/营养评分", "schoolMapPoints", "school_id,area,risk_level,lat,lng", "日级", "demo-school-snapshot.csv"],
    ["综合监管驾驶舱", "今日就餐态势", "应就餐、实际就餐、就餐率、学校排行", "结算系统; 膳食经费管理系统", "mealStatsBySchool", "school_id,date,meal_type", "准实时/日级", "demo-school-snapshot.csv"],
    ["综合监管驾驶舱", "食安风险概览", "AI违规、台账未上报、农残异常、留样异常、证照临期、设备离线", "食安平台; 设备物联网; 预警中心", "riskCategorySummary", "risk_type,date,status", "准实时", "demo-warning-events.csv"],
    ["综合监管驾驶舱", "近7日风险趋势", "食安、采购库存、营养食谱、设备异常趋势", "统一预警中心", "warningTrend", "domain,date", "日级", "demo-data-bundle.json"],
    ["综合监管驾驶舱", "学校综合排名", "学校名称、食安指数、营养评分、处置完成率", "食安平台; 营养平台; 预警中心", "schoolRanking", "school_id,date", "日级", "demo-school-snapshot.csv"],
    ["综合监管驾驶舱", "营养监管摘要", "食谱公示率、营养评分、达标率、低评分学校、家长评分", "营养数字化管理平台; 家长端", "nutritionSummary", "school_id,week_no", "周级/日级", "demo-nutrition-recipes.csv"],
    ["综合监管驾驶舱", "预警处置状态", "待处理、处理中、待复核、已关闭、已超期", "统一预警中心; 整改督办后台", "warningStatusSummary", "status,risk_level,date", "准实时", "demo-warning-events.csv"],
    ["综合监管驾驶舱", "趋势与督办", "采购经费趋势、设备在线状态、重点督办事项", "经费系统; 设备物联网; 督办后台", "supervisionTicker", "date,school_id,domain", "准实时/日级", "demo-data-bundle.json"],
    ["食品安全监管大屏", "食安核心指标", "监管学校数、食堂数、今日AI预警、台账上报率、食安指数、待整改风险", "食安平台; 预警中心", "foodSafetyKpis", "school_id,date", "准实时", "demo-dashboard-kpis.csv"],
    ["食品安全监管大屏", "明厨亮灶视频墙", "后厨、洗消、备餐、仓储、留样、配餐视频", "摄像头; NVR; 视频网关", "videoWall", "device_id,school_id,point_area", "实时", "demo-device-status.csv"],
    ["食品安全监管大屏", "AI违规抓拍列表", "学校、点位、违规类型、抓拍时间、处理状态", "AI分析盒; 视频平台; 预警中心", "aiCaptureWarnings", "warning_id,device_id,risk_type", "实时", "demo-warning-events.csv"],
    ["食品安全监管大屏", "食安指数总览", "辖区食安指数、高中低风险学校数量", "食安指数算法; 食安台账; 预警中心", "foodSafetyIndexSummary", "school_id,date", "日级", "demo-school-snapshot.csv"],
    ["食品安全监管大屏", "今日台账上报情况", "晨检、留样、消毒、陪餐、废弃物、卫生检查、农残快检", "学校端台账; 教育局端台账监管", "ledgerSubmitSummary", "school_id,ledger_type,date", "按提交刷新", "demo-ledger-status.csv"],
    ["食品安全监管大屏", "学校食安排名", "学校、食安指数、AI预警数、台账上报率", "食安平台; 预警中心; 台账系统", "foodSafetySchoolRanking", "school_id,date", "日级", "demo-school-snapshot.csv"],
    ["食品安全监管大屏", "风险预警分类", "证照临期、晨检异常、留样异常、农残异常、台账未上报、AI违规、设备离线、投诉反馈", "统一预警中心", "warningCategorySummary", "risk_type,date,status", "准实时", "demo-warning-events.csv"],
    ["食品安全监管大屏", "重点风险学校", "学校、风险等级、主要问题、待处理数量、更新时间", "预警中心; 督办后台", "highRiskSchools", "school_id,risk_level,status", "准实时", "demo-warning-events.csv"],
    ["食品安全监管大屏", "整改闭环状态", "待处理、处理中、待复核、已完成、已超期", "整改督办后台", "rectificationStatus", "warning_id,status,due_at", "准实时", "demo-warning-events.csv"],
    ["膳食经费与采购库存监管大屏", "经费采购指标", "今日采购金额、验收金额、出库金额、库存金额、今日就餐、人均餐费", "膳食经费系统; 进销存系统; 集采平台", "fundPurchaseKpis", "school_id,date,goods_code", "按单据刷新", "demo-purchase-inventory.csv"],
    ["膳食经费与采购库存监管大屏", "采购执行闭环流程", "带量食谱、采购计划、订单、配送验收、入库、出库、经费归集", "营养平台; 集采平台; 进销存; 经费系统", "purchaseProcessFlow", "recipe_id,order_no,stock_bill_no,fund_bill_no", "按单据刷新", "demo-purchase-inventory.csv"],
    ["膳食经费与采购库存监管大屏", "学校采购金额排名", "学校、采购金额、环比、验收金额、执行率", "集采平台; 验收系统", "purchaseAmountRanking", "school_id,date", "日级", "demo-purchase-inventory.csv"],
    ["膳食经费与采购库存监管大屏", "膳食经费收支总览", "收入、支出、结余、营养餐收入、营养餐支出", "膳食经费管理系统", "fundBalanceSummary", "school_id,period,fund_type", "日级/月级", "demo-data-bundle.json"],
    ["膳食经费与采购库存监管大屏", "人均餐费监管", "学校、就餐人次、人均餐费、餐标、异常状态", "膳食经费系统; 结算系统", "mealCostPerCapita", "school_id,date,meal_type", "日级", "demo-dashboard-kpis.csv"],
    ["膳食经费与采购库存监管大屏", "库存预警", "库存不足、临期、过期、异常损耗、盘点差异", "进销存系统", "stockWarnings", "goods_code,school_id,status", "准实时/日级", "demo-purchase-inventory.csv"],
    ["膳食经费与采购库存监管大屏", "供应商履约排行", "配送准时率、验收合格率、退货次数、资质状态", "集采平台; 供应商端; 资质系统", "supplierPerformanceRanking", "supplier_code,period", "日级/周级", "demo-purchase-inventory.csv"],
    ["膳食经费与采购库存监管大屏", "采购价格异常", "商品、学校、供应商、当前价、历史均价、协议价、异常幅度", "集采平台; 价格监管", "priceExceptions", "goods_code,supplier_code,school_id,date", "按单据刷新", "demo-purchase-inventory.csv"],
    ["营养食谱监管大屏", "营养核心指标", "带量食谱公示率、完整率、营养评分、达标率、执行率、家长评价", "营养平台; 家长端; 采购库存", "nutritionKpis", "school_id,week_no,meal_type", "周级/日级", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "营养管理闭环图", "菜品库、食材库、营养素库、带量食谱、分析评分、公示、采购计划、执行、评价、预警", "营养平台; 三库; 采购库存; 家长端; 预警中心", "nutritionProcessFlow", "recipe_id,goods_code,warning_id", "周级/按节点刷新", "demo-data-bundle.json"],
    ["营养食谱监管大屏", "营养综合评分", "分值、等级、较上周变化、待关注学校数量", "营养数字化管理平台", "nutritionScoreSummary", "school_id,week_no", "周级", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "学校营养评分排名", "学校、营养评分、达标率、公示状态、执行偏差、家长评分", "营养平台; 家长端", "nutritionSchoolRanking", "school_id,week_no", "周级", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "带量食谱公示监管", "应公示、已公示、未公示、延迟公示", "营养平台; 家长端", "recipePublishSummary", "school_id,week_no", "按公示刷新", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "本周食谱完整性", "餐次、学段、人数、菜品、食材、用量、营养标签、过敏原提示完成率", "营养平台; 菜品库; 食材库", "recipeCompleteness", "recipe_id,week_no", "周级", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "食谱执行偏差", "学校、餐次、计划菜品、实际菜品、偏差原因、处理状态", "营养平台; 学校端; 采购库存", "recipeExecutionDiffs", "recipe_id,school_id,meal_type", "日级", "demo-nutrition-recipes.csv"],
    ["营养食谱监管大屏", "今日营养达标分析", "能量、蛋白质、脂肪、碳水、膳食纤维、钙、铁、锌、维生素", "营养分析服务; 营养素库", "nutritionQualifiedItems", "recipe_id,nutrient_code", "日级/周级", "demo-data-bundle.json"],
    ["营养食谱监管大屏", "营养风险预警", "低评分、脂肪偏高、蔬果不足、奶类不足、过敏原缺失、采购未匹配", "营养平台; 采购库存; 预警中心", "nutritionWarnings", "school_id,week_no,risk_type", "日级/周级", "demo-warning-events.csv"],
]


def module_rows() -> list[dict[str, object]]:
    fields = ["page", "module", "display_content", "source_system", "api_or_dataset", "key_fields", "refresh_frequency", "demo_data_file"]
    return [dict(zip(fields, row)) for row in PAGE_MODULES]


BRIEFING_ROWS = [
    ["00:00-00:35", "开场定位", "把标题从“食安大屏”升级为“北京市昌平区校园餐安全与膳食经费数智监管平台”。", "今天不是看单点台账，而是看区教委如何统一看风险、管过程、查资金、说责任。", "综合监管驾驶舱"],
    ["00:35-01:25", "全区态势", "先看学校数、食堂数、今日就餐、预警总数、食安指数、营养评分、设备在线率。", "这些指标来自食安、经费、集采、营养和设备平台汇总，演示数据均为非零合成样例。", "核心指标卡 + 地图"],
    ["01:25-02:10", "食安风险", "切到食品安全监管大屏，讲明厨亮灶视频墙、AI抓拍、台账上报、学校排名和整改闭环。", "大屏只发现问题和下钻，真正整改、复核、导出仍回到业务后台留痕。", "食品安全监管大屏"],
    ["02:10-03:05", "经费采购", "讲带量食谱生成采购计划，再贯通订单、验收、入库、出库、经费归集。", "监管重点从“有没有采购”升级到“钱、货、票、营养计划是否匹配”。", "膳食经费与采购库存监管大屏"],
    ["03:05-04:05", "营养差异化", "切营养食谱监管大屏，突出带量食谱、营养评分、公示、执行偏差、家长评价和采购保障率。", "这是康比特相对普通明厨亮灶或食安台账产品的差异化：把营养健康和监管闭环放在一起。", "营养食谱监管大屏"],
    ["04:05-04:40", "指挥闭环", "回到预警列表，选一个高风险学校，说明风险分级、督办、整改、复核、关闭和指标回流。", "领导看到风险后能知道谁负责、多久处理、是否超期、处理后是否回流指数。", "预警处置状态 + 重点督办"],
    ["04:40-05:00", "收束与下一步", "说明当前包可直接喂给原型/前端，真实项目上线前要补接口、权限、设备、客户真实数据和验收截图。", "演示包不代表昌平真实数据；它用于让页面、指标和讲解先跑通。", "来源与缺口"],
]


def briefing_rows() -> list[dict[str, object]]:
    fields = ["timebox", "section", "screen_action", "speaker_note", "primary_screen"]
    return [dict(zip(fields, row)) for row in BRIEFING_ROWS]


def trend_rows() -> list[dict[str, object]]:
    return [
        {"date": "2026-05-11", "food_safety": 42, "purchase_inventory": 18, "nutrition_recipe": 12, "device_iot": 16},
        {"date": "2026-05-12", "food_safety": 39, "purchase_inventory": 16, "nutrition_recipe": 11, "device_iot": 15},
        {"date": "2026-05-13", "food_safety": 35, "purchase_inventory": 15, "nutrition_recipe": 10, "device_iot": 13},
        {"date": "2026-05-14", "food_safety": 33, "purchase_inventory": 14, "nutrition_recipe": 9, "device_iot": 12},
        {"date": "2026-05-15", "food_safety": 31, "purchase_inventory": 13, "nutrition_recipe": 9, "device_iot": 11},
        {"date": "2026-05-16", "food_safety": 29, "purchase_inventory": 12, "nutrition_recipe": 8, "device_iot": 10},
        {"date": "2026-05-17", "food_safety": 37, "purchase_inventory": 14, "nutrition_recipe": 8, "device_iot": 12},
    ]


def md_table(rows: list[dict[str, object]], columns: list[str], max_rows: int | None = None) -> str:
    shown = rows[:max_rows] if max_rows else rows
    out = ["| " + " | ".join(columns) + " |", "| " + " | ".join(["---"] * len(columns)) + " |"]
    for row in shown:
        out.append("| " + " | ".join(str(row.get(col, "")).replace("\n", "<br>") for col in columns) + " |")
    return "\n".join(out)


def html_table(rows: list[dict[str, object]], columns: list[str], max_rows: int | None = None) -> str:
    shown = rows[:max_rows] if max_rows else rows
    head = "".join(f"<th>{escape(col)}</th>" for col in columns)
    body = []
    for row in shown:
        body.append("<tr>" + "".join(f"<td>{escape(str(row.get(col, '')))}</td>" for col in columns) + "</tr>")
    return f"<table><thead><tr>{head}</tr></thead><tbody>{''.join(body)}</tbody></table>"


def build_markdown(weekly_row: dict[str, str], indicators: list[dict[str, object]], modules: list[dict[str, object]]) -> str:
    source_cols = ["source_id", "evidence_level", "title", "path", "used_for"]
    indicator_cols = ["indicator_id", "domain", "display_name", "definition", "formula", "unit", "primary_data_source", "refresh_frequency", "demo_value"]
    module_cols = ["page", "module", "display_content", "source_system", "api_or_dataset", "refresh_frequency", "demo_data_file"]
    school_cols = ["school_id", "school_name", "school_type", "area", "canteen_count", "student_count", "today_meal_count", "food_safety_index", "nutrition_score", "risk_level"]
    warning_cols = ["warning_id", "school_name", "domain", "warning_type", "risk_level", "status", "occurred_at", "primary_page"]
    briefing_cols = ["timebox", "section", "screen_action", "speaker_note", "primary_screen"]

    weekly_excerpt = (weekly_row.get("description") or "").replace("\n", " ")
    if len(weekly_excerpt) > 420:
        weekly_excerpt = weekly_excerpt[:420] + "..."

    return f"""# 北京市昌平区校园食品安全监管大屏演示包

生成时间：{GENERATED_AT}

用途：给“北京市昌平区校园食品安全监管大屏”原型、前端和售前讲解使用。

## 0. 证据边界

- **B 级证据**：来自项目内周报 CSV、食安产品简介、教委大屏产品概要设计、教委端演示思路、一线访谈提纲、企业微信微盘派生索引。
- **D 级演示数据**：本包内所有昌平区数值、学校、预警、采购、营养、设备明细均为合成演示数据，只用于保证原型和讲解“不为 0、能跑通、能下钻”，不代表昌平区真实统计。
- **产品边界**：大屏是展示、研判、指挥和下钻层，不替代学校端/教委端后台的新增、审核、整改、导出和归档。

W20 产品研发摘要中与本任务直接相关的片段：

> {weekly_excerpt}

## 1. 大屏产品结构

本演示包采用来源设计稿中的“1 个综合驾驶舱 + 3 个专题监管大屏”：

1. 综合监管驾驶舱：全区态势、地图、排名、趋势、营养摘要、设备在线、督办事项。
2. 食品安全监管大屏：明厨亮灶、AI 预警、食安台账、晨检、留样、农残、整改闭环。
3. 膳食经费与采购库存监管大屏：经费收支、采购计划、配送验收、入库出库、库存预警、供应商履约、价格异常。
4. 营养食谱监管大屏：带量食谱、营养分析、食谱评分、公示、执行偏差、采购保障、家长评价。

## 2. 关键指标字典

完整 CSV：`dashboard-indicator-dictionary.csv`

{md_table(indicators, indicator_cols)}

## 3. 非零演示数据样例

完整 JSON：`demo-data-bundle.json`

CSV 分表：

- `demo-dashboard-kpis.csv`
- `demo-school-snapshot.csv`
- `demo-warning-events.csv`
- `demo-ledger-status.csv`
- `demo-purchase-inventory.csv`
- `demo-nutrition-recipes.csv`
- `demo-device-status.csv`

### 3.1 学校快照样例

{md_table(school_rows(), school_cols)}

### 3.2 预警事件样例

{md_table(warning_rows(), warning_cols)}

## 4. 页面模块到数据来源映射

完整 CSV：`page-module-data-source-map.csv`

{md_table(modules, module_cols)}

## 5. 5 分钟讲解顺序

完整 CSV：`five-minute-briefing-sequence.csv`

{md_table(briefing_rows(), briefing_cols)}

## 6. 原型/前端接入建议

- 首页先接 `demo-data-bundle.json`，按 `kpis`、`schools`、`warnings`、`trends`、`devices`、`nutrition` 分域渲染。
- 大屏组件的数据字段优先参考 `page-module-data-source-map.csv` 的 `api_or_dataset` 和 `key_fields`。
- 指标卡、排行榜、预警列表和趋势图统一从 `dashboard-indicator-dictionary.csv` 取口径、单位、刷新频率和告警规则。
- 前端演示中必须露出“演示数据”标识，避免被误读成真实昌平区运行数据。

## 7. 来源索引

完整 CSV：`source-index.csv`

{md_table(SOURCE_INDEX, source_cols)}

## 8. Review 检查清单

- 指标是否覆盖食安、经费采购、营养食谱、设备物联、风险预警、家长反馈六类监管对象。
- 每个页面模块是否有数据来源、关键字段、刷新频率和演示数据文件。
- 演示数据是否全部非零、能支持地图、指标卡、排行、预警、台账、采购、营养和设备模块。
- 5 分钟讲解是否先总览、再食安、再经费采购、再营养差异化、最后收束到整改闭环。
- 正式项目接入前是否补齐真实接口、权限、设备台账、客户授权和验收证据。
"""


def build_routing_note() -> str:
    return f"""# ROUTING_NOTE：昌平区校园食品安全监管大屏演示包

生成时间：{GENERATED_AT}

## Input

- 用户目标：围绕“北京市昌平区校园食品安全监管大屏”，在 `zhctprompt` 中做可演示的数据包和讲解包。
- 优先来源：
  - `work/weekly-reports/outputs/2026-W20/smart-canteen-project-xlsx-refresh/product-rd-summary.csv`
  - `modules/product/products/food-safety/product-overview/2026-05-14-food-safety-product-introduction/`
  - `work_company_knowledge` 中教委、学校、校园食安、标准规范、竞品和设备资料索引。

## Process

- 使用 `product-design-solution-workspace`：按产品/方案/大屏原型资料组织输出。
- 使用 `company-wedrive-knowledge-base`：只使用微盘派生索引和已转换的少量产品定义文档，不批量复制原始二进制。
- 使用 `output-format-preferences`：Markdown 作为 AI 可读源，CSV/JSON 作为结构化原型数据，HTML 作为人工 review 面。
- 使用 `team-document-review-learning-loop`：生成 Markdown + HTML review，并保留来源、证据等级和待确认缺口。

## Output

- `changping-campus-food-safety-dashboard-demo.md`
- `review.html`
- `source-index.csv`
- `dashboard-indicator-dictionary.csv`
- `page-module-data-source-map.csv`
- `demo-data-bundle.json`
- `demo-*.csv`
- `five-minute-briefing-sequence.csv`

## Non-goals

- 不声明昌平区真实学校数量、食堂数量、就餐人数、预警数量或监管成效。
- 不复制企业微信微盘原始二进制资料。
- 不修改业务代码，不创建真实接口，不接入生产数据库或设备平台。

## Evidence Boundary

- 产品结构与指标口径：B 级。
- 演示数据数值：D 级合成样例。
- 对外发布前需补真实客户授权、接口回读、设备台账、权限策略、验收截图和市场/法务口径。
"""


def build_html(indicators: list[dict[str, object]], modules: list[dict[str, object]]) -> str:
    kpi_cards = "".join(
        f"""<div class="card"><div class="eyebrow">{escape(row['unit'])}</div><div class="value">{escape(str(row['value']))}</div><div class="label">{escape(row['kpi_name'])}</div><div class="trend">{escape(row['trend'])}</div></div>"""
        for row in KPI_ROWS
    )
    status_counts = {"待处理": 0, "处理中": 0, "待复核": 0, "已关闭": 0, "已超期": 0}
    for row in warning_rows():
        status_counts[row["status"]] = status_counts.get(row["status"], 0) + 1
    status_html = "".join(f"<span><b>{escape(k)}</b>{v}</span>" for k, v in status_counts.items())

    return f"""<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>昌平区校园食品安全监管大屏演示包 Review</title>
  <style>
    :root {{
      --bg: #f5f7fb;
      --ink: #152033;
      --muted: #64748b;
      --line: #d8e0ea;
      --panel: #ffffff;
      --blue: #1f6feb;
      --green: #0f9f6e;
      --amber: #c77c02;
      --red: #d64545;
    }}
    * {{ box-sizing: border-box; }}
    body {{ margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: var(--ink); background: var(--bg); }}
    header {{ background: #10233f; color: white; padding: 34px 42px 30px; }}
    header h1 {{ margin: 0; font-size: 30px; font-weight: 760; letter-spacing: 0; }}
    header p {{ margin: 12px 0 0; max-width: 960px; color: #dbe8ff; line-height: 1.7; }}
    main {{ max-width: 1240px; margin: 0 auto; padding: 28px 24px 56px; }}
    section {{ margin: 22px 0; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 24px; }}
    h2 {{ margin: 0 0 16px; font-size: 22px; }}
    h3 {{ margin: 20px 0 10px; font-size: 16px; }}
    .note {{ border-left: 4px solid var(--amber); background: #fff8e8; padding: 12px 14px; color: #4f3b13; line-height: 1.65; }}
    .grid {{ display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }}
    .card {{ border: 1px solid var(--line); border-radius: 8px; padding: 14px; background: #fbfdff; min-height: 112px; }}
    .eyebrow {{ color: var(--muted); font-size: 12px; }}
    .value {{ font-size: 26px; font-weight: 780; margin-top: 8px; }}
    .label {{ font-size: 13px; margin-top: 6px; }}
    .trend {{ color: var(--green); font-size: 12px; margin-top: 8px; }}
    .pillrow {{ display: flex; flex-wrap: wrap; gap: 10px; margin-top: 10px; }}
    .pillrow span {{ display: inline-flex; gap: 8px; align-items: center; border: 1px solid var(--line); border-radius: 999px; padding: 8px 11px; background: #fbfdff; }}
    table {{ width: 100%; border-collapse: collapse; margin-top: 12px; font-size: 13px; }}
    th, td {{ border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; padding: 9px 8px; line-height: 1.5; }}
    th {{ color: #334155; background: #eef3f8; font-weight: 700; }}
    .cols {{ display: grid; grid-template-columns: 1fr 1fr; gap: 16px; }}
    .checklist li {{ margin: 8px 0; line-height: 1.65; }}
    code {{ background: #eef3f8; padding: 2px 5px; border-radius: 4px; }}
    @media (max-width: 900px) {{
      header {{ padding: 24px; }}
      main {{ padding: 18px 14px 36px; }}
      .grid, .cols {{ grid-template-columns: 1fr; }}
      table {{ display: block; overflow-x: auto; white-space: nowrap; }}
    }}
  </style>
</head>
<body>
  <header>
    <h1>北京市昌平区校园食品安全监管大屏演示包 Review</h1>
    <p>面向原型和前端的可演示数据包：关键指标字典、非零样例数据、页面模块到数据来源映射、5 分钟讲解顺序。所有昌平区数值均为 D 级合成演示数据，不代表真实统计。</p>
  </header>
  <main>
    <section>
      <h2>Review 结论</h2>
      <div class="note">本包可用于大屏原型联调和售前演示脚本初稿。正式用于客户现场前，需要替换为真实学校主数据、接口回读、设备在线数据、权限策略、验收截图和客户授权材料。</div>
      <div class="grid">{kpi_cards}</div>
      <h3>预警处置样例分布</h3>
      <div class="pillrow">{status_html}</div>
    </section>

    <section>
      <h2>交付文件</h2>
      <table>
        <thead><tr><th>文件</th><th>用途</th></tr></thead>
        <tbody>
          <tr><td><code>changping-campus-food-safety-dashboard-demo.md</code></td><td>AI 可读源文件，包含完整指标、样例、映射和讲解顺序。</td></tr>
          <tr><td><code>dashboard-indicator-dictionary.csv</code></td><td>指标口径、公式、单位、来源、刷新频率、演示值和告警规则。</td></tr>
          <tr><td><code>demo-data-bundle.json</code></td><td>前端可直接消费的合成数据包。</td></tr>
          <tr><td><code>page-module-data-source-map.csv</code></td><td>4 个页面各模块到系统、接口/数据集、关键字段和演示文件的映射。</td></tr>
          <tr><td><code>five-minute-briefing-sequence.csv</code></td><td>5 分钟讲解节奏、屏幕动作和讲解要点。</td></tr>
          <tr><td><code>source-index.csv</code></td><td>来源、证据等级和使用方式。</td></tr>
        </tbody>
      </table>
    </section>

    <section>
      <h2>关键指标字典预览</h2>
      {html_table(indicators, ["indicator_id", "domain", "display_name", "definition", "unit", "primary_data_source", "demo_value"], 18)}
    </section>

    <section>
      <h2>页面模块到数据来源预览</h2>
      {html_table(modules, ["page", "module", "source_system", "api_or_dataset", "refresh_frequency", "demo_data_file"], 22)}
    </section>

    <section>
      <h2>演示数据预览</h2>
      <div class="cols">
        <div>
          <h3>学校快照</h3>
          {html_table(school_rows(), ["school_id", "school_name", "today_meal_count", "food_safety_index", "nutrition_score", "risk_level"], 8)}
        </div>
        <div>
          <h3>预警事件</h3>
          {html_table(warning_rows(), ["warning_id", "school_name", "domain", "warning_type", "risk_level", "status"], 10)}
        </div>
      </div>
    </section>

    <section>
      <h2>5 分钟讲解顺序</h2>
      {html_table(briefing_rows(), ["timebox", "section", "screen_action", "speaker_note", "primary_screen"])}
    </section>

    <section>
      <h2>人工 Review 清单</h2>
      <ul class="checklist">
        <li>页面叙事是否符合区教委/监管部门领导视角。</li>
        <li>指标是否覆盖食安、经费采购、营养、设备、预警和社会共治。</li>
        <li>每个模块是否能追溯到系统或数据集，是否能落到接口字段。</li>
        <li>演示数据是否明确标注为合成样例，避免被误读为真实昌平数据。</li>
        <li>前端原型是否能直接读取 JSON/CSV 并展示非零状态、排行、趋势和预警闭环。</li>
      </ul>
    </section>
  </main>
</body>
</html>
"""


def main() -> None:
    weekly_row = read_weekly_food_safety_row()
    indicators = indicator_rows()
    modules = module_rows()

    write_csv(ROOT / "source-index.csv", SOURCE_INDEX)
    write_csv(ROOT / "dashboard-indicator-dictionary.csv", indicators)
    write_csv(ROOT / "page-module-data-source-map.csv", modules)
    write_csv(ROOT / "demo-dashboard-kpis.csv", KPI_ROWS)
    write_csv(ROOT / "demo-school-snapshot.csv", school_rows())
    write_csv(ROOT / "demo-warning-events.csv", warning_rows())
    write_csv(ROOT / "demo-ledger-status.csv", ledger_rows())
    write_csv(ROOT / "demo-purchase-inventory.csv", purchase_rows())
    write_csv(ROOT / "demo-nutrition-recipes.csv", nutrition_rows())
    write_csv(ROOT / "demo-device-status.csv", device_rows())
    write_csv(ROOT / "demo-risk-trends.csv", trend_rows())
    write_csv(ROOT / "five-minute-briefing-sequence.csv", briefing_rows())

    bundle = {
        "meta": {
            "title": "北京市昌平区校园食品安全监管大屏演示数据包",
            "generated_at": GENERATED_AT,
            "data_boundary": "synthetic_demo_data_not_real_changping_statistics",
            "evidence_level": "D for values, B for structure and indicator definitions",
        },
        "sources": SOURCE_INDEX,
        "kpis": KPI_ROWS,
        "schools": school_rows(),
        "warnings": warning_rows(),
        "ledgers": ledger_rows(),
        "purchases": purchase_rows(),
        "nutrition": nutrition_rows(),
        "devices": device_rows(),
        "trends": trend_rows(),
        "briefing": briefing_rows(),
    }
    (ROOT / "demo-data-bundle.json").write_text(json.dumps(bundle, ensure_ascii=False, indent=2), encoding="utf-8")
    (ROOT / "changping-campus-food-safety-dashboard-demo.md").write_text(build_markdown(weekly_row, indicators, modules), encoding="utf-8")
    (ROOT / "ROUTING_NOTE.md").write_text(build_routing_note(), encoding="utf-8")
    (ROOT / "review.html").write_text(build_html(indicators, modules), encoding="utf-8")
    (ROOT / "summary.md").write_text(
        f"""# 昌平区校园食品安全监管大屏演示包生成总结

- 时间：{GENERATED_AT}
- 输出目录：`work/2026-05-17-changping-campus-food-safety-dashboard-demo/`
- 结果：已生成指标字典、非零合成演示数据、页面模块数据来源映射、5 分钟讲解顺序、Markdown 源稿和 HTML review。
- 边界：演示数据均为 D 级合成样例，不代表昌平区真实统计；正式项目需替换为真实接口、权限、设备和验收证据。
""",
        encoding="utf-8",
    )


if __name__ == "__main__":
    main()
