# -*- coding: utf-8 -*-
"""compose_engine.py · 白箱自举原型 v2——条件化单元 + 方向推理 + 组合生成 + 内部自校验
理论依据：《白箱自举设计_条件化单元与组合引擎.md》（§八b v2 方向推理设计）
v1 结果（2026-08-23）：组合结构成立 7/7，但暴露缺方向推理（晾干→沸腾/结霜→熔化错配）
v2 修复：
  ① 单元加 direction（物态变化方向）→ 期望方向优先匹配
  ② 结论生成模板：场景事实 → 因果演绎 → 生成「新答案句」（非完整答案查表）
  ③ 自校验加强：方向一致性 + 因果链完整性 → 白箱自己发现 v1 的错配错误
"""
import sys
sys.stdout.reconfigure(encoding='utf-8')


# ============ 一、条件化单元库（v2：带 direction + conclusion 模板） ============
# direction: 物态变化方向（组合匹配用）| conclusion: 结论生成模板（{...} 为占位）

CONDITION_UNITS = {
    "沸点-气压": {
        "conditions": ["气压"],
        "direction": "气压效应",
        "rule": {"低": "沸点降低", "高": "沸点升高", "标准": "沸点=100°C"},
        "default": "标准大气压下纯水沸点 100°C",
        # 结论模板：气压效应 → 温度后果 → 生活影响（占位代入）
        "conclusion": {
            "低": "气压低 → {液体}沸点降至100°C以下 → {液体}达不到100°C就沸腾",
            "高": "气压高 → {液体}沸点升至100°C以上 → {液体}能到100°C以上才沸腾",
            "标准": "气压标准 → {液体}沸点=100°C → {液体}能正常烧到100°C才开",
        },
        "examples": ["高原(0.65atm)水沸点约88°C", "高压锅(2atm)水沸点约120°C"],
        "domain": "物态变化",
        "source": "沸点与气压簇蒸馏（条件化重构）",
    },
    "沸腾-条件": {
        "conditions": ["液体", "供热"],
        "direction": "液→气(剧烈)",
        "rule": "达到沸点+持续供热 → 内部与表面同时剧烈汽化",
        "default": "沸腾=液体内部与表面同时汽化的剧烈蒸发",
        "conclusion": {"default": "{液体}达到沸点且持续供热 → 剧烈沸腾（温度保持沸点不再升）"},
        "examples": ["水烧开=沸腾", "沸腾时温度不再升高"],
        "domain": "物态变化",
        "source": "沸腾簇蒸馏",
    },
    "蒸发-条件": {
        "conditions": ["液体", "温度", "表面积", "通风"],
        "direction": "液→气(缓慢)",
        "rule": {"温度": "温度↑→蒸发越快", "表面积": "表面积↑→蒸发越快",
                 "通风": "通风↑→蒸发越快"},
        "default": "蒸发=液体表面在任何温度下都能发生的缓慢汽化",
        "conclusion": {
            "温度": "{液体}温度越高 → 蒸发越快 → 干得越快",
            "表面积": "{液体}摊开面积越大 → 蒸发越快 → 干得越快",
            "通风": "{液体}表面风越大 → 蒸发越快 → 干得越快",
            "通风差": "{液体}表面没风 → 蒸发慢 → 干得慢",
        },
        "examples": ["晾衣摊开干得快", "夏天干得快", "有风干得快"],
        "domain": "物态变化",
        "source": "蒸发簇蒸馏",
    },
    "液化-条件": {
        "conditions": ["气体", "降温"],
        "direction": "气→液",
        "rule": {"降温": "气体降温 → 液化（遇冷凝结）"},
        "default": "液化=气体遇冷/受压变为液体（放热）",
        "conclusion": {"降温": "{气体}遇冷 → 液化成小水珠（镜片起雾=水蒸气液化）"},
        "examples": ["镜片起雾", "液化石油气"],
        "domain": "物态变化",
        "source": "液化簇蒸馏",
    },
    "凝固-条件": {
        "conditions": ["液体", "降温"],
        "direction": "液→固",
        "rule": {"降温": "液体降温到凝固点 → 凝固（放热）"},
        "default": "凝固=液体变为固体，放热",
        "conclusion": {
            "降温": "{液体}降温到凝固点 → 凝固成{固体}（水结冰=凝固）",
            "不降温": "{液体}温度不降到凝固点 → 不凝固 → {液体}不结冰",
        },
        "examples": ["水结冰=凝固", "北方泼水成冰"],
        "domain": "物态变化",
        "source": "凝固簇蒸馏",
    },
    "熔化-条件": {
        "conditions": ["固体", "升温"],
        "direction": "固→液",
        "rule": {"升温": "固体升温到熔点 → 熔化（吸热）"},
        "default": "熔化=固体变为液体，吸热",
        "conclusion": {"升温": "{固体}升温到熔点 → 熔化成液体（冰化成水=熔化）"},
        "examples": ["冰化成水", "铁在高温下熔化"],
        "domain": "物态变化",
        "source": "熔化簇蒸馏",
    },
    "升华-条件": {
        "conditions": ["固体", "升温"],
        "direction": "固→气",
        "rule": {"升温": "固体直接变为气体 → 升华（吸热）"},
        "default": "升华=固态直接变气态，不经过液态",
        "conclusion": {
            "升温": "{固体}直接变为气体 → 升华（吸热）",
            "default": "{固体}直接变为气体 → 升华（樟脑丸放久变小=升华）",
        },
        "examples": ["樟脑丸变小", "干冰直接变气", "冰冻衣服晾干"],
        "domain": "物态变化",
        "source": "升华簇蒸馏",
    },
    "凝华-条件": {
        "conditions": ["气体", "降温"],
        "direction": "气→固",
        "rule": {"降温": "气体直接变为固体 → 凝华（放热）"},
        "default": "凝华=气态直接变固态，不经过液态",
        "conclusion": {"降温": "{气体}遇冷直接变为固体 → 凝华（霜=水蒸气凝华）"},
        "examples": ["霜=凝华", "窗花冰花=凝华"],
        "domain": "物态变化",
        "source": "凝华簇蒸馏",
    },
    # ---- 密度 × 浮力（新域 ①：浮沉由密度差决定） ----
    "密度-浮沉": {
        "conditions": ["物体", "液体"],
        "direction": "浮沉",
        "rule": {"浮": "物体密度<液体密度 → 上浮", "沉": "物体密度>液体密度 → 下沉"},
        "default": "物体密度比液体小则浮，比液体大则沉",
        "conclusion": {
            "浮": "{物体}密度比{液体}小 → 上浮（{液体}的浮力托住它）",
            "沉": "{物体}密度比{液体}大 → 下沉（{液体}浮力托不住它）",
            "空心": "{物体}虽是钢铁（密度比水大），但内部空心 → 排水体积大 → 浮力=排开水的重力 → 浮着（阿基米德原理）",
        },
        "examples": ["铁块沉入水中", "木块浮在水面", "轮船浮着（空心等效密度小）", "鱼靠鱼鳔调节浮沉"],
        "domain": "密度与浮力",
        "source": "浮力簇蒸馏（条件化重构）",
    },
    # ---- 热传导快慢（新域 ②：金属导热快） ----
    "导热-快慢": {
        "conditions": ["物体", "温度"],
        "direction": "热传递",
        "rule": {"快": "金属导热快 → 热量迅速传递", "慢": "木头/塑料导热慢 → 隔热"},
        "default": "金属是热的良导体（导热快），木头/塑料是热的不良导体",
        "conclusion": {
            "快": "{物体}导热快 → 热量迅速从{热源}传到手 → 摸起来更{感觉}",
            "慢": "{物体}导热慢 → 热量传得慢 → 摸起来不太{感觉}",
        },
        "examples": ["金属勺把烫手（导热快）", "木筷不烫手（导热慢）", "铁锅烧热快"],
        "domain": "热传递",
        "source": "热传递簇蒸馏（条件化重构）",
    },
    # ---- 摩擦因素（新域 ③：压力/粗糙→摩擦） ----
    "摩擦-因素": {
        "conditions": ["物体", "表面"],
        "direction": "摩擦",
        "rule": {"大": "压力大/表面粗糙 → 摩擦力大", "小": "表面光滑/压力小 → 摩擦力小"},
        "default": "摩擦力大小与压力和表面粗糙程度有关",
        "conclusion": {
            "大": "{物体}与{表面}间摩擦力大 → {效果}",
            "小": "{物体}与{表面}间摩擦力小 → {效果}",
        },
        "examples": ["刹车靠增大摩擦力", "滑冰面光滑摩擦小", "鞋底花纹增大摩擦防滑"],
        "domain": "力与运动",
        "source": "摩擦簇蒸馏（条件化重构）",
    },
    # ---- 生活常识域（第三阶段·覆盖扩展 ①） ----
    "洗涤-乳化": {
        "conditions": ["油污", "洗涤剂"],
        "direction": "洗涤",
        "rule": {"乳化": "洗涤剂乳化油脂 → 油污随水冲走"},
        "default": "洗涤剂把油污乳化成小油滴，水一冲就走",
        "conclusion": {
            "乳化": "{油污}被{洗涤剂}乳化 → 变成小油滴 → 水一冲就走（油不溶于水，洗涤剂让油分散）",
        },
        "examples": ["洗洁精去碗油污", "洗衣粉去衣服油渍"],
        "domain": "生活常识",
        "source": "洗涤簇蒸馏（条件化重构）",
    },
    "保温-隔热": {
        "conditions": ["容器", "隔热"],
        "direction": "保温",
        "rule": {"隔热": "真空/空气/隔热层 → 减少热传导 → 保温"},
        "default": "隔热层（真空/空气/棉）阻隔热传导，热量不易流失",
        "conclusion": {
            "隔热": "{容器}用{隔热层}隔开内外 → 热量难传导 → {内容物}长时间保温",
        },
        "examples": ["保温杯保温", "棉被保暖", "暖水瓶"],
        "domain": "生活常识",
        "source": "保温簇蒸馏（条件化重构）",
    },
    "盐-化雪": {
        "conditions": ["冰", "盐"],
        "direction": "化雪",
        "rule": {"化冰": "盐降低冰点 → 冰在0°C以下也能融化"},
        "default": "盐溶于水降低冰点，冰在更低温度才冻结",
        "conclusion": {
            "化冰": "往{冰面}撒盐 → 盐降低冰点 → {冰面}在0°C以下开始融化（化雪快）",
        },
        "examples": ["冬天路面撒盐化雪", "盐腌冰棍不冻"],
        "domain": "生活常识",
        "source": "盐化雪簇蒸馏（条件化重构）",
    },
    "低温-保鲜": {
        "conditions": ["食物", "低温"],
        "direction": "保鲜",
        "rule": {"低温": "低温抑制微生物繁殖 → 食物保鲜"},
        "default": "微生物在低温下繁殖慢，食物不易坏",
        "conclusion": {
            "低温": "{食物}放{低温环境} → 微生物繁殖被抑制 → {食物}不易变质（保鲜）",
        },
        "examples": ["冰箱保鲜", "冰鲜鱼", "冷库储存"],
        "domain": "生活常识",
        "source": "保鲜簇蒸馏（条件化重构）",
    },
    # ---- 生物常识域（第三阶段·覆盖扩展 ②） ----
    "光合-光照": {
        "conditions": ["植物", "光照"],
        "direction": "光合",
        "rule": {"光照": "光照 → 光合作用 → 制造养分+氧气"},
        "default": "植物在光照下进行光合作用，制造养分并释放氧气",
        "conclusion": {
            "光照": "{植物}需要{光照} → 进行光合作用 → 制造养分并释放氧气（没光长不好）",
        },
        "examples": ["植物向光生长", "树释放氧气", "室内植物要放窗边"],
        "domain": "生物常识",
        "source": "光合簇蒸馏（条件化重构）",
    },
    "呼吸-氧气": {
        "conditions": ["生物", "氧气"],
        "direction": "呼吸",
        "rule": {"缺氧": "需氧生物缺氧 → 窒息"},
        "default": "绝大多数生物需要氧气呼吸，缺氧无法生存",
        "conclusion": {
            "缺氧": "{生物}离开{氧气} → 无法呼吸 → 窒息（鱼离开水/人闷住都会缺氧）",
        },
        "examples": ["鱼离开水会死", "人憋气难受", "密封环境缺氧"],
        "domain": "生物常识",
        "source": "呼吸簇蒸馏（条件化重构）",
    },
    "生长-条件": {
        "conditions": ["植物", "条件"],
        "direction": "生长",
        "rule": {"条件": "阳光+水+温度 → 植物生长"},
        "default": "植物生长需要阳光、水和适宜温度",
        "conclusion": {
            "条件": "{植物}需要阳光+水+适宜温度 → 才能正常生长（缺一长不好）",
        },
        "examples": ["种子发芽", "豆芽生长", "植物浇水"],
        "domain": "生物常识",
        "source": "生长簇蒸馏（条件化重构）",
    },
    "迁徙-季节": {
        "conditions": ["候鸟", "季节"],
        "direction": "迁徙",
        "rule": {"季节": "季节变冷/食物减少 → 候鸟迁徙"},
        "default": "候鸟随季节变化迁徙（秋天南飞避寒，春天北归）",
        "conclusion": {
            "季节": "{候鸟}在{季节}变化时 → 飞向温暖/食物充足的地方 → 迁徙（秋天南飞春天北归）",
        },
        "examples": ["大雁南飞", "燕子迁徙", "候鸟冬季南迁"],
        "domain": "生物常识",
        "source": "迁徙簇蒸馏（条件化重构）",
    },
    # ---- 地球环境域（第三阶段·覆盖扩展 ③） ----
    "昼夜-自转": {
        "conditions": ["地球", "自转"],
        "direction": "昼夜",
        "rule": {"自转": "地球自转 → 昼夜交替（24小时一圈）"},
        "default": "地球绕自身轴自转，朝向太阳的一面是白天，背向是黑夜",
        "conclusion": {
            "自转": "{地球}自转 → 朝向太阳的一面是白天 → 背向太阳的一面是黑夜 → 昼夜交替",
        },
        "examples": ["为什么有白天黑夜", "太阳东升西落", "地球自转"],
        "domain": "地球环境",
        "source": "昼夜簇蒸馏（条件化重构）",
    },
    "四季-公转": {
        "conditions": ["地球", "公转"],
        "direction": "四季",
        "rule": {"公转": "地球公转+地轴倾斜 → 四季变化"},
        "default": "地球绕太阳公转且地轴倾斜，太阳直射点南北移动形成四季",
        "conclusion": {
            "公转": "{地球}绕太阳公转+地轴倾斜 → 太阳直射点南北移动 → 春夏秋冬四季变化",
        },
        "examples": ["为什么有春夏秋冬", "四季变化", "地轴倾斜"],
        "domain": "地球环境",
        "source": "四季簇蒸馏（条件化重构）",
    },
    "潮汐-引力": {
        "conditions": ["海水", "引力"],
        "direction": "潮汐",
        "rule": {"引力": "月球/太阳引力 → 潮汐涨落"},
        "default": "月球和太阳的引力拉扯海水，形成潮汐涨落",
        "conclusion": {
            "引力": "{月球/太阳}引力拉扯{海水} → 海水周期性涨落 → 潮汐（涨潮退潮）",
        },
        "examples": ["为什么有涨潮退潮", "潮汐", "月亮引力"],
        "domain": "地球环境",
        "source": "潮汐簇蒸馏（条件化重构）",
    },
    # ---- 技术常识域（第三阶段·覆盖扩展 ④） ----
    "电路-回路": {
        "conditions": ["电路", "回路"],
        "direction": "电路",
        "rule": {"回路": "闭合回路 → 电流流通 → 电器工作"},
        "default": "电流需要闭合回路才能流通，电器才能工作",
        "conclusion": {
            "回路": "{电路}闭合形成回路 → 电流流通 → {电器}工作（断路就没电）",
        },
        "examples": ["灯泡不亮", "开关断开灯灭", "电路短路"],
        "domain": "技术常识",
        "source": "电路簇蒸馏（条件化重构）",
    },
    "杠杆-支点": {
        "conditions": ["杠杆", "支点"],
        "direction": "杠杆",
        "rule": {"省力": "支点远离力点/动力臂长 → 省力"},
        "default": "杠杆原理：动力臂越长越省力，支点是转动中心",
        "conclusion": {
            "省力": "{工具}用杠杆原理 → 支点+长动力臂 → 用小力撬动重物（省力）",
        },
        "examples": ["撬棍撬石头", "开瓶器", "跷跷板"],
        "domain": "技术常识",
        "source": "杠杆簇蒸馏（条件化重构）",
    },
    "滑轮-省力": {
        "conditions": ["滑轮", "类型"],
        "direction": "滑轮",
        "rule": {"动滑轮": "动滑轮省力一半", "定滑轮": "定滑轮改变方向不省力"},
        "default": "动滑轮省力一半，定滑轮改变用力方向",
        "conclusion": {
            "动滑轮": "用{动滑轮} → 省力一半 → 提起{重物}更省力",
            "定滑轮": "用{定滑轮} → 改变用力方向 → 不省力但方便（如旗杆升旗）",
        },
        "examples": ["升旗用定滑轮", "吊车动滑轮", "滑轮组"],
        "domain": "技术常识",
        "source": "滑轮簇蒸馏（条件化重构）",
    },
    "折射-介质": {
        "conditions": ["光", "介质"],
        "direction": "折射",
        "rule": {"折射": "光从一种介质进入另一种 → 折射（弯折）"},
        "default": "光在不同介质中速度不同，跨界时发生折射（弯折）",
        "conclusion": {
            "折射": "{光}从{介质A}进入{介质B} → 速度变化 → 光路弯折 → 折射（筷子在水里像折断）",
        },
        "examples": ["筷子在水里看起来弯了", "彩虹", "海市蜃楼"],
        "domain": "技术常识",
        "source": "折射簇蒸馏（条件化重构）",
    },
    # ---- 人体常识域（第三阶段·覆盖扩展 ⑤） ----
    "消化-分解": {
        "conditions": ["食物", "消化系统"],
        "direction": "消化",
        "rule": {"分解": "食物 → 消化分解 → 营养吸收 → 能量"},
        "default": "消化系统把食物分解成营养，身体吸收获得能量",
        "conclusion": {
            "分解": "{食物}进入{消化系统} → 分解成营养 → 吸收 → 身体获得能量（饿了要吃饭）",
        },
        "examples": ["为什么饿了要吃饭", "食物怎么变成能量", "消化吸收"],
        "domain": "人体常识",
        "source": "消化簇蒸馏（条件化重构）",
    },
    "呼吸-换气": {
        "conditions": ["肺", "气体交换"],
        "direction": "呼吸换气",
        "rule": {"换气": "肺吸氧气进血液 → 呼出二氧化碳 → 气体交换"},
        "default": "肺吸入氧气进入血液，呼出二氧化碳（气体交换）",
        "conclusion": {
            "换气": "{肺}吸气 → 氧气进血液 → 呼出二氧化碳 → 气体交换（跑步时呼吸急促）",
        },
        "examples": ["为什么跑步喘气", "呼吸换气", "肺的作用"],
        "domain": "人体常识",
        "source": "呼吸换气簇蒸馏（条件化重构）",
    },
    "反射-刺激": {
        "conditions": ["刺激", "反射弧"],
        "direction": "反射",
        "rule": {"反射": "刺激 → 反射弧 → 快速反应（不经大脑思考）"},
        "default": "反射弧让身体对刺激快速反应，不经过大脑思考",
        "conclusion": {
            "反射": "{刺激}触发{反射弧} → 不经大脑思考 → 快速反应（烫手立刻缩手）",
        },
        "examples": ["为什么烫手会缩", "膝跳反射", "眨眼反射"],
        "domain": "人体常识",
        "source": "反射簇蒸馏（条件化重构）",
    },
    "睡眠-恢复": {
        "conditions": ["身体", "睡眠"],
        "direction": "睡眠",
        "rule": {"恢复": "睡眠 → 身体休息恢复 → 精力恢复"},
        "default": "睡眠让身体和大脑休息恢复，醒来精力充沛",
        "conclusion": {
            "恢复": "{身体}通过{睡眠} → 休息恢复 → 精力充沛（熬夜会累）",
        },
        "examples": ["为什么人要睡觉", "熬夜为什么累", "睡眠恢复"],
        "domain": "人体常识",
        "source": "睡眠簇蒸馏（条件化重构）",
    },
    # ---- 光学域（第四阶段·常识域扩充：光） ----
    "光-直线传播": {
        "conditions": ["光", "介质均匀"],
        "direction": "直线传播",
        "rule": "均匀介质 → 光沿直线传播 → 影子/小孔成像",
        "default": "光在均匀介质中沿直线传播",
        "conclusion": {
            "default": "{光}在均匀介质中沿直线传播 → 遇障碍形成影子 → 小孔成像成倒像",
        },
        "examples": ["影子怎么来的", "小孔成像", "日食原理"],
        "domain": "光学",
        "source": "光学常识蒸馏（条件化重构）",
    },
    "反射-条件": {
        "conditions": ["光", "表面"],
        "direction": "反射-光",
        "rule": {"光滑": "光滑表面 → 镜面反射 → 成像", "粗糙": "粗糙表面 → 漫反射 → 看清物体"},
        "default": "光遇表面发生反射：光滑镜面反射成像，粗糙漫反射让人看清物体",
        "conclusion": {
            "光滑": "{光}照到{表面}(光滑) → 镜面反射 → 成像（镜子照出自己）",
            "粗糙": "{光}照到{表面}(粗糙) → 漫反射 → 各方向都有光 → 看清物体（白墙）",
        },
        "examples": ["为什么照镜子能看到自己", "镜子反射", "白墙为什么能看清"],
        "domain": "光学",
        "source": "光学常识蒸馏（条件化重构）",
    },
    "色散-棱镜": {
        "conditions": ["白光", "介质"],
        "direction": "色散",
        "rule": "白光经折射介质 → 各色光偏折不同 → 七色分光",
        "default": "白光由七色光组成，经介质折射后色散成七色",
        "conclusion": {
            "default": "{白光}经{介质}折射 → 不同色光偏折程度不同 → 色散成七色 → 彩虹成因",
        },
        "examples": ["彩虹怎么形成的", "三棱镜分光", "为什么彩虹七种颜色"],
        "domain": "光学",
        "source": "光学常识蒸馏（条件化重构）",
    },
    # ---- 声学域（第四阶段·常识域扩充：声） ----
    "声音-振动": {
        "conditions": ["声源", "振动"],
        "direction": "发声",
        "rule": "振动 → 发声；停止振动 → 停止发声",
        "default": "声音由物体振动产生",
        "conclusion": {
            "default": "{声源}振动 → 产生声音 → 停止振动 → 声音停止（敲鼓后捂鼓面声音即停）",
        },
        "examples": ["声音怎么产生的", "为什么敲鼓会响", "停止振动声音停止"],
        "domain": "声学",
        "source": "声学常识蒸馏（条件化重构）",
    },
    "回声-反射": {
        "conditions": ["声音", "障碍物"],
        "direction": "回声",
        "rule": "声波遇大障碍 → 反射 → 回声 → 可测距",
        "default": "声波遇到大障碍物反射形成回声",
        "conclusion": {
            "default": "{声音}传播遇{障碍物}(大山/墙壁) → 反射 → 回声 → 距离=声速×时间/2",
        },
        "examples": ["为什么山谷里有回声", "回声测距", "对着山喊有回音"],
        "domain": "声学",
        "source": "声学常识蒸馏（条件化重构）",
    },
    "音调-频率": {
        "conditions": ["振动", "频率"],
        "direction": "音调",
        "rule": {"频率": "频率↑ → 音调↑", "振幅": "振幅↑ → 响度↑"},
        "default": "音调由振动频率决定（频率高音调尖），响度由振幅决定（振幅大声音响）",
        "conclusion": {
            "default": "音调由振动频率决定 → 频率越高音调越尖（女声尖/男声低）→ 振幅越大声音越响",
            "频率": "振动频率越高 → 音调越高（女声尖/男声低）",
            "振幅": "振动振幅越大 → 声音越响",
        },
        "examples": ["为什么女声比男声尖", "音调由什么决定", "为什么大声喊更响"],
        "domain": "声学",
        "source": "声学常识蒸馏（条件化重构）",
    },
    # ---- 天文域（第四阶段·常识域扩充：天文） ----
    "月相-公转": {
        "conditions": ["月球", "公转"],
        "direction": "月相",
        "rule": "月球绕地公转 → 日月地相对位置变化 → 月相周期",
        "default": "月球绕地球公转，与太阳的相对位置变化形成月相周期",
        "conclusion": {
            "default": "{月球}绕地球公转 → 太阳-地球-月球相对位置变化 → 月相周期（朔→望→朔约29.5天）",
        },
        "examples": ["为什么月亮形状会变", "月相周期", "为什么有圆月弯月"],
        "domain": "天文",
        "source": "天文常识蒸馏（条件化重构）",
    },
    "日食月食-遮挡": {
        "conditions": ["光", "天体"],
        "direction": "食",
        "rule": "光直线传播 + 天体遮挡 → 日食/月食",
        "default": "日食月食是光直线传播被天体遮挡的现象",
        "conclusion": {
            "default": "{光}直线传播 → 月球挡太阳光落地球 → 日食；地球挡阳光落月球 → 月食",
        },
        "examples": ["日食怎么形成", "月食原理", "为什么会有日食"],
        "domain": "天文",
        "source": "天文常识蒸馏（条件化重构）",
    },
    # ---- 经济域（第四阶段·常识域扩充：经济） ----
    "价格-供需": {
        "conditions": ["供给", "需求"],
        "direction": "价格",
        "rule": {"紧俏": "需求>供给 → 供不应求 → 价格上涨", "过剩": "供给>需求 → 供过于求 → 价格下降"},
        "default": "价格由供给与需求共同决定（供不应求价格上涨，供过于求价格下降）",
        "conclusion": {
            "default": "价格由{供给}与{需求}共同决定 → 需求大于供给 → 供不应求 → 价格上涨；供给大于需求 → 供过于求 → 价格下降",
            "紧俏": "{需求}大于{供给} → 供不应求 → 价格上涨",
            "过剩": "{供给}大于{需求} → 供过于求 → 价格下降",
        },
        "examples": ["为什么口罩会涨价", "供不应求价格上升", "为什么降价促销"],
        "domain": "经济",
        "source": "经济常识蒸馏（条件化重构）",
    },
    "通胀-货币量": {
        "conditions": ["货币量", "商品量"],
        "direction": "通胀",
        "rule": "货币增速>商品增速 → 通货膨胀 → 购买力下降",
        "default": "货币发行增速超过商品增速会引发通货膨胀",
        "conclusion": {
            "default": "{货币量}增速超过{商品量}增速 → 通货膨胀 → 单位货币购买力下降（物价上涨）",
        },
        "examples": ["为什么钱会贬值", "通货膨胀原因", "为什么物价上涨"],
        "domain": "经济",
        "source": "经济常识蒸馏（条件化重构）",
    },
    # ---- 化学域（第四阶段·常识域扩充：化学） ----
    "燃烧-三要素": {
        "conditions": ["可燃物", "氧气", "燃点"],
        "direction": "燃烧",
        "rule": "可燃物+氧气+达到燃点 → 三要素齐备 → 燃烧；缺一不可",
        "default": "燃烧需要可燃物、氧气、达到燃点三要素",
        "conclusion": {
            "default": "{可燃物}+{氧气}+达到{燃点} → 三要素齐备 → 燃烧；灭火=破坏任一要素（水降温/沙隔绝氧气）",
        },
        "examples": ["为什么水能灭火", "燃烧需要什么", "为什么沙能灭火"],
        "domain": "化学",
        "source": "化学常识蒸馏（条件化重构）",
    },
    "生锈-氧化": {
        "conditions": ["铁", "氧气", "水"],
        "direction": "氧化",
        "rule": "铁+氧气+水 → 缓慢氧化 → 生锈 → 隔绝防锈",
        "default": "铁与氧气和水接触发生缓慢氧化而生锈",
        "conclusion": {
            "default": "{铁}与{氧气}和{水}接触 → 缓慢氧化 → 生锈 → 涂油/镀层隔绝空气和水 → 防锈",
        },
        "examples": ["为什么铁会生锈", "怎么防止生锈", "铁锈怎么形成的"],
        "domain": "化学",
        "source": "化学常识蒸馏（条件化重构）",
    },
    "溶解-温度": {
        "conditions": ["溶质", "溶剂", "温度"],
        "direction": "溶解",
        "rule": "多数固体 温度↑ → 溶解度↑ → 溶解更快",
        "default": "多数固体物质温度越高溶解度越大",
        "conclusion": {
            "default": "{溶质}在{溶剂}中溶解 → 多数固体温度越高溶解度越大 → 热水溶解更快（糖在热水中化得快）",
        },
        "examples": ["为什么热水化糖快", "温度对溶解度影响", "为什么糖在热水里化得快"],
        "domain": "化学",
        "source": "化学常识蒸馏（条件化重构）",
    },
}

# 方向 → 候选单元（期望方向优先匹配）
DIR_INDEX = {}
for uid, unit in CONDITION_UNITS.items():
    DIR_INDEX.setdefault(unit["direction"], []).append(uid)

# 维度 → 候选单元（兜底匹配）
DIM_INDEX = {}
for uid, unit in CONDITION_UNITS.items():
    for dim in unit["conditions"]:
        DIM_INDEX.setdefault(dim, []).append(uid)


# ============ 二、场景条件事实表（问题中抽取的事实，不是答案） ============

SCENE_FACTS = {
    "高原":   {"气压": "低",   "海拔": "高",   "液体": "水"},
    "珠峰":   {"气压": "低",   "海拔": "极高", "液体": "水"},
    "高压锅": {"气压": "高",   "密封": "增压", "液体": "水", "固体": "食物"},
    "标准大气": {"气压": "标准"},
    "晾衣":   {"液体": "衣服上的水", "表面积": "大", "通风": "好", "温度": "环境温度"},
    "夏天":   {"温度": "高",   "液体": "衣服上的水"},
    "有风":   {"通风": "好",   "液体": "衣服上的水"},
    "冰箱":   {"温度": "低", "环境": "湿", "气体": "水蒸气",
               "食物": "食物", "低温环境": "冰箱冷藏", "低温": "低温"},
    "冷天":   {"温度": "低",   "液体": "湖水", "固体": "冰"},
    "热天":   {"温度": "高"},
    "镜片":   {"气体": "水蒸气", "降温": "冷镜片"},
    "樟脑":   {"固体": "樟脑丸"},
    # 新域场景（密度浮力/热传导/摩擦）
    "铁块":   {"物体": "铁块", "液体": "水", "浮沉": "沉"},
    "木块":   {"物体": "木块", "液体": "水", "浮沉": "浮"},
    "轮船":   {"物体": "轮船", "液体": "水", "浮沉": "浮", "原因": "空心"},
    "鱼":     {"物体": "鱼",   "液体": "水", "浮沉": "浮", "原因": "鱼鳔"},
    "金属勺": {"物体": "金属勺", "温度": "高", "热源": "热汤", "感觉": "烫", "导热": "快"},
    "木筷":   {"物体": "木筷", "温度": "高", "热源": "热汤", "感觉": "烫", "导热": "慢"},
    "铁锅":   {"物体": "铁锅", "温度": "高", "导热": "快"},
    "刹车":   {"物体": "刹车片", "表面": "车轮", "摩擦": "大", "效果": "车很快停下"},
    "滑冰":   {"物体": "冰刀", "表面": "冰面", "摩擦": "小", "效果": "滑行很流畅"},
    "鞋底":   {"物体": "鞋底", "表面": "地面", "摩擦": "大", "效果": "不打滑"},
    # 第三阶段·生活常识场景
    "碗盘":   {"油污": "碗盘上的油污", "洗涤剂": "洗洁精", "乳化": "乳化"},
    "衣服":   {"油污": "衣服上的油渍", "洗涤剂": "洗衣粉", "乳化": "乳化"},
    "保温杯": {"容器": "保温杯", "隔热层": "真空夹层", "内容物": "热水", "隔热": "隔热"},
    "棉被":   {"容器": "棉被", "隔热层": "空气/棉纤维", "内容物": "体温", "隔热": "隔热"},
    "路面":   {"冰面": "结冰的路面", "盐": "融雪盐", "化冰": "化冰"},
    # 第三阶段·生物常识场景
    "植物":   {"植物": "植物", "光照": "光照", "光照需求": "光照", "光合": "光照"},
    "鱼出水": {"生物": "鱼", "氧气": "水中的氧气", "缺氧": "缺氧"},
    "种子":   {"植物": "种子", "条件": "阳光+水+温度", "条件需求": "条件"},
    "候鸟":   {"候鸟": "大雁/燕子", "季节": "秋天变冷", "季节变化": "季节"},
    # 第三阶段·地球/技术场景
    "地球":   {"地球": "地球", "自转": "自转", "公转": "公转"},
    "潮汐":   {"月球/太阳": "月球和太阳", "海水": "海水", "引力": "引力"},
    "电路":   {"电路": "电路", "回路": "回路", "电器": "灯泡/电器"},
    "撬棍":   {"工具": "撬棍", "省力": "省力"},
    "开瓶器": {"工具": "开瓶器", "省力": "省力"},
    "升旗":   {"定滑轮": "定滑轮", "重物": "旗帜"},
    "吊车":   {"动滑轮": "动滑轮", "重物": "重物"},
    "筷子":   {"光": "光", "介质A": "水", "介质B": "空气", "折射": "折射"},
    # 类比场景（条件结构相似：真空=气压低 → 同高原规律域）
    "真空":   {"气压": "低", "液体": "水"},
    # 第三阶段·人体常识场景
    "吃饭":   {"食物": "食物", "消化系统": "消化系统", "分解": "分解"},
    "跑步":   {"肺": "肺", "换气": "换气"},
    "烫手":   {"刺激": "烫的刺激", "反射弧": "反射弧", "反射": "反射"},
    "熬夜":   {"身体": "身体", "睡眠": "睡眠", "恢复": "恢复"},
    # 第四阶段·光声/天文/经济/化学场景
    "镜子":   {"光": "光", "表面": "镜面", "光滑": "光滑", "反射": "镜面反射"},
    "山谷":   {"声音": "喊声", "障碍物": "山壁", "反射": "回声"},
    "彩虹":   {"白光": "太阳光", "介质": "水滴", "折射": "折射"},
    "市场":   {"供给": "商品供应", "需求": "购买需求", "价格": "价格"},
    "铁门":   {"铁": "铁制品", "氧气": "空气", "水": "潮湿环境"},
    "炉火":   {"可燃物": "燃料", "氧气": "空气", "燃点": "燃点", "燃烧": "燃烧"},
}

SCENE_ALIAS = {
    "高原": ["高原", "西藏", "青海", "海拔高", "山上"],
    "珠峰": ["珠峰", "珠穆朗玛峰", "喜马拉雅"],
    "高压锅": ["高压锅", "压力锅"],
    "标准大气": ["标准大气", "平原地", "常温常压", "正常情况下"],
    "晾衣": ["晾衣", "晒衣服", "衣服", "晾晒"],
    "夏天": ["夏天", "暴晒", "阳光下", "夏天晒"],
    "有风": ["有风", "刮风", "通风", "风"],
    # issue #119（2026-10-11）：本键此前在第三阶段批次再写一次（后者胜 ⇒ 本列表
    # 的「结霜/除霜」运行期不可达）。同场景同角色，故**并集合并**为唯一键。
    "冰箱": ["冰箱", "冷冻", "冷藏", "结霜", "除霜", "保鲜", "冰鲜"],
    "冷天": ["冬天", "寒冷", "低温", "结冰", "冻", "湖面"],
    "热天": ["高温", "热天", "大热天"],
    "镜片": ["镜片", "眼镜", "起雾"],
    "樟脑": ["樟脑", "樟脑丸"],
    "铁块": ["铁块", "铁钉", "铁", "石头"],
    "木块": ["木块", "木头", "木板", "塑料"],
    "轮船": ["轮船", "船", "航母", "舰"],
    "鱼": ["鱼", "鱼鳔"],
    "金属勺": ["金属勺", "铁勺", "不锈钢勺", "勺子", "锅铲"],
    "木筷": ["木筷", "筷子", "木勺"],
    "铁锅": ["铁锅", "锅", "炒锅"],
    "刹车": ["刹车", "制动", "急刹"],
    "滑冰": ["滑冰", "冰面", "溜冰"],
    "鞋底": ["鞋底", "鞋子", "防滑", "花纹"],
    # 第三阶段·生活/生物场景别名
    "碗盘": ["碗", "盘子", "油污", "油渍", "洗洁精", "油腻"],
    "衣服": ["洗衣服", "洗衣", "油渍", "洗衣粉"],
    "保温杯": ["保温杯", "暖水瓶", "保温瓶"],
    "棉被": ["棉被", "被子", "棉衣", "保暖"],
    "路面": ["路面", "马路", "撒盐", "化雪", "融雪", "除冰"],
    "植物": ["植物", "花", "树", "菜", "向光", "光合"],
    "鱼出水": ["鱼离开水", "鱼出水", "鱼上岸", "缺氧", "憋气"],
    "种子": ["种子", "发芽", "豆芽", "生根", "生长"],
    "候鸟": ["候鸟", "大雁", "燕子", "南飞", "迁徙", "北归"],
    # 第三阶段·地球/技术场景别名
    "地球": ["白天", "黑夜", "昼夜", "自转", "四季", "春夏秋冬", "公转", "太阳东升西落"],
    "潮汐": ["潮汐", "涨潮", "退潮", "月亮引力", "海面涨落"],
    "电路": ["灯泡", "灯不亮", "电路", "开关", "短路", "断电", "回路"],
    "撬棍": ["撬棍", "撬石头", "杠杆", "开瓶器", "跷跷板"],
    "开瓶器": ["开瓶器"],
    "升旗": ["升旗", "旗杆", "定滑轮"],
    "吊车": ["吊车", "动滑轮", "滑轮组"],
    "筷子": ["筷子在水里", "看起来弯", "折射", "海市蜃楼", "彩虹", "折断"],
    # 类比场景（真空=气压低）
    "真空": ["真空", "真空中", "真空瓶", "抽真空"],
    # 第三阶段·人体场景别名
    "吃饭": ["饿了", "吃饭", "没力气", "补充能量", "能量"],
    "跑步": ["跑步", "喘气", "运动", "呼吸急促"],
    "烫手": ["烫手", "缩手", "烫到", "碰热", "反射"],
    "熬夜": ["熬夜", "睡觉", "睡眠", "犯困", "没精神", "累"],
    # 第四阶段·光声/天文/经济/化学场景别名
    "镜子": ["镜子", "平面镜", "照镜子", "镜面", "映出"],
    "山谷": ["山谷", "山洞", "回声", "喊山", "回音", "大山"],
    "彩虹": ["彩虹"],
    "市场": ["价格", "涨价", "降价", "供不应求", "供过于求", "市场", "物价", "紧俏", "促销"],
    "铁门": ["生锈", "铁锈", "锈", "铁门", "防锈"],
    "炉火": ["灭火", "燃烧", "着火", "炉火", "灭火器", "点火"],
}

DIM_KEYWORDS = {
    "气压": ["气压", "高压", "低压", "高原", "海拔", "珠峰", "压力", "密封", "增压", "大气"],
    "温度": ["温度", "高温", "低温", "加热", "降温", "冷", "热", "夏天", "冬天", "晒", "冻", "热水", "溶解度", "化得快"],
    "表面积": ["表面积", "摊开", "面积", "展开"],
    "通风": ["风", "通风", "吹"],
    "液体": ["水", "液体", "油", "汤", "汁", "湖水"],
    "固体": ["冰", "固体", "铁", "樟脑", "干冰", "食物"],
    "气体": ["蒸气", "蒸汽", "气体", "水蒸气", "气"],
    "供热": ["烧", "煮", "加热", "供热", "火"],
    # 新域维度
    "物体": ["铁块", "铁钉", "木块", "木头", "轮船", "船", "鱼", "物体", "金属", "木筷", "鞋底", "刹车", "冰刀"],
    "表面": ["表面", "地面", "冰面", "车轮", "花纹"],
    "热源": ["热汤", "热水", "火", "热菜"],
    "浮沉": ["浮", "沉", "漂", "沉底", "浮起"],
    # 第三阶段·生活/生物维度
    "油污": ["油污", "油渍", "油腻", "油"],
    "洗涤剂": ["洗洁精", "洗衣粉", "肥皂", "洗涤剂"],
    "隔热": ["隔热", "保温", "真空", "保暖", "隔开"],
    "盐": ["盐", "撒盐", "融雪盐"],
    "低温": ["低温", "冷藏", "冷冻", "冰箱", "冷库"],
    "光照": ["光照", "阳光", "光", "光合", "向光", "晒"],
    "氧气": ["氧气", "空气", "缺氧", "呼吸", "憋气", "离开水"],
    "条件": ["生长", "发芽", "种子", "阳光+水", "浇水"],
    "季节": ["季节", "秋天", "冬天", "南飞", "迁徙", "候鸟"],
    # 第三阶段·地球/技术维度
    "自转": ["自转", "昼夜", "白天", "黑夜", "东升西落"],
    "公转": ["公转", "四季", "春夏秋冬", "地轴"],
    "引力": ["引力", "潮汐", "涨潮", "退潮", "月亮"],
    "回路": ["回路", "电路", "开关", "短路", "断电", "灯泡", "电流"],
    "支点": ["杠杆", "支点", "撬棍", "开瓶器", "跷跷板"],
    "类型": ["动滑轮", "定滑轮", "滑轮", "升旗"],
    "介质": ["折射", "介质", "水里", "看起来弯", "彩虹"],
    # 第三阶段·人体维度
    "消化系统": ["消化", "吃饭", "饿了", "能量", "吸收"],
    "气体交换": ["呼吸", "喘气", "氧气", "二氧化碳", "换气"],
    "反射弧": ["反射", "缩手", "膝跳", "眨眼"],
    "睡眠": ["睡眠", "睡觉", "熬夜", "犯困", "恢复精力"],
    # 第四阶段·光声/天文/经济/化学维度
    "介质均匀": ["直线传播", "影子", "小孔成像", "沿直线"],
    "光滑": ["光滑", "镜面", "镜子", "平面镜"],
    "白光": ["白光", "太阳光", "彩虹", "七色"],
    "声源": ["鼓", "敲", "发声", "声音"],
    "振动": ["振动", "震动", "振动发声"],
    "频率": ["频率", "音调", "尖", "低沉", "响度", "振幅"],
    "障碍物": ["回声", "山谷", "山壁", "障碍", "回音"],
    "月球": ["月球", "月亮", "月相", "圆月", "弯月"],
    "天体": ["日食", "月食", "遮挡", "天体"],
    "供给": ["供给", "供应", "库存", "产量", "供不应求", "供过于求"],
    "需求": ["需求", "购买", "抢购", "紧俏", "促销"],
    "货币量": ["货币", "印钱", "发钞", "钱变多", "贬值"],
    "商品量": ["商品量", "商品", "产能"],
    "可燃物": ["可燃物", "燃料", "木材", "汽油", "燃烧"],
    "燃点": ["燃点", "着火点", "点火", "灭火"],
    "铁": ["铁", "铁制品", "生锈", "锈"],
    "溶质": ["溶质", "糖", "溶解"],
    "溶剂": ["溶剂", "热水", "水", "溶解"],
}

# ============ 三、方向识别（v2 核心：问题动词 → 期望物态变化方向） ============
# 「晾干」期望 液→气(蒸发)；「结霜」期望 气→固(凝华)——v1 缺失的推理

DIRECTION_KEYWORDS = {
    "气压效应": ["沸点", "烧不", "煮不", "煮饭", "熟", "不到100", "不到一百", "提前开"],
    "液→气(缓慢)": ["干", "晾", "晒干", "蒸发", "变干"],
    "液→气(剧烈)": ["烧开", "沸腾", "开", "煮开", "冒泡"],
    "气→液": ["起雾", "凝结", "液化", "水珠", "雾", "挂水"],
    "液→固": ["结冰", "凝固", "冻住", "冻"],
    "固→液": ["融化", "熔化", "化", "融", "化成水"],
    "固→气": ["变小", "消失", "升华", "不见了"],
    "气→固": ["结霜", "凝华", "霜", "窗花", "冰花", "雪"],
    # 新域方向
    "浮沉": ["为什么浮", "为什么沉", "浮起来", "沉下去", "浮着", "沉底", "漂着", "不沉", "沉入", "浮起", "沉在", "能浮", "浮在", "会浮", "会沉"],
    "热传递": ["导热", "烫手", "烫嘴", "烧手", "散热", "保温", "烫"],
    "摩擦": ["摩擦", "刹车", "刹", "防滑", "滑", "花纹", "抓地"],
    # 第三阶段·生活/生物方向
    "洗涤": ["去油", "洗洁精", "去污", "油污", "油腻", "洗干净", "洗涤"],
    "保温": ["保温", "保暖", "隔热", "散热慢", "保温杯", "暖"],
    "化雪": ["化雪", "融雪", "除冰", "撒盐", "化冰", "不冻"],
    "保鲜": ["保鲜", "不易坏", "不容易坏", "不容易变质", "保质", "变质", "冷藏", "冰鲜"],
    "光合": ["光合", "向光", "长得好", "释放氧气", "光照", "阳光"],
    "呼吸": ["呼吸", "窒息", "缺氧", "离开水", "憋气", "活不了"],
    "生长": ["生长", "发芽", "长出来", "生根", "结果"],
    "迁徙": ["迁徙", "南飞", "北归", "候鸟", "飞走"],
    # 第三阶段·地球/技术方向
    "昼夜": ["昼夜", "白天黑夜", "为什么有白天", "为什么有黑夜", "东升西落"],
    "四季": ["四季", "春夏秋冬", "为什么有四季", "季节变化"],
    "潮汐": ["潮汐", "涨潮", "退潮", "海面涨落"],
    "电路": ["不亮", "灯不亮", "没电", "断电", "短路", "电路", "开关"],
    "杠杆": ["撬", "省力", "杠杆", "开瓶", "跷跷板"],
    "滑轮": ["滑轮", "升旗", "省力一半", "吊车"],
    "折射": ["折射", "看起来弯", "折断", "海市蜃楼", "水里"],
    # 第三阶段·人体方向
    "消化": ["饿了", "吃饭", "没力气", "补充能量", "消化", "吸收营养"],
    "呼吸换气": ["喘气", "呼吸", "氧气", "换气"],
    "反射": ["缩手", "烫手", "膝跳", "眨眼", "反射", "缩回"],
    "睡眠": ["睡觉", "熬夜", "犯困", "没精神", "睡眠", "休息"],
    # 第四阶段·光声/天文/经济/化学方向
    "直线传播": ["影子", "小孔成像", "直线传播", "沿直线"],
    "反射-光": ["镜子", "照镜子", "镜面", "映出", "看清物体"],
    "色散": ["彩虹", "色散", "七色", "三棱镜", "分光"],
    "发声": ["敲鼓", "发声", "声音怎么", "怎么响", "振动", "声音"],
    "回声": ["回声", "回音", "山谷里喊", "对着山喊"],
    "音调": ["音调", "尖", "低沉", "响度", "大声喊", "声音大"],
    "月相": ["月相", "圆月", "弯月", "月亮形状", "月亮变"],
    "食": ["日食", "月食"],
    "价格": ["涨价", "降价", "价格", "物价", "供不应求", "供过于求", "贵", "便宜"],
    "通胀": ["贬值", "通货膨胀", "通胀", "物价上涨", "钱不值钱"],
    "燃烧": ["灭火", "燃烧", "着火", "点着", "烧起来"],
    "氧化": ["生锈", "铁锈", "锈", "防锈"],
    "溶解": ["溶解", "化得快", "溶解度", "化开"],
}

# 方向推理：动词比维度更具体 → 优先用方向匹配单元
def identify_direction(query):
    """识别问题的期望物态变化方向
    场景优先：高原/珠峰问题即使含「开/沸腾」，方向是气压效应（气压低→沸点低）
    第三阶段：最长关键词优先——「离开水」len3 > 「开」len1 → 呼吸（非沸腾）"""
    scene = identify_scene(query)
    if scene in ("高原", "珠峰") and any(k in query for k in ("开", "烧", "沸")):
        return "气压效应"
    # 烫手场景优先：缩手/缩回/反射 → 人体反射（不被热传递「烫手」平局劫持）
    if scene == "烫手" and any(k in query for k in ("缩手", "缩回", "反射")):
        return "反射"
    best_dir, best_len = None, 0
    for direction, kws in DIRECTION_KEYWORDS.items():
        for k in kws:
            if k in query and len(k) > best_len:
                best_dir, best_len = direction, len(k)
    return best_dir


# ============ 四、已验证事实库（自校验对照基准） ============

VERIFIED_FACTS = [
    {"content": "标准大气压下纯水沸点100°C", "domain": "物态变化"},
    {"content": "高原上水沸点降低（约88°C）", "domain": "物态变化"},
    {"content": "高压锅内水沸点升高（约120°C）", "domain": "物态变化"},
    {"content": "沸腾时温度不再升高", "domain": "物态变化"},
    {"content": "水蒸气遇冷液化成小水珠（镜片起雾）", "domain": "物态变化"},
    {"content": "水结冰是凝固（放热）", "domain": "物态变化"},
    {"content": "冰化成水是熔化（吸热）", "domain": "物态变化"},
    {"content": "樟脑丸变小是升华", "domain": "物态变化"},
    {"content": "霜是水蒸气凝华形成的", "domain": "物态变化"},
    {"content": "晾晒衣服干得快（蒸发加快）", "domain": "物态变化"},
    {"content": "有风时衣服干得更快", "domain": "物态变化"},
    {"content": "冬天湖面结冰是凝固", "domain": "物态变化"},
    {"content": "冰箱结霜是水蒸气凝华", "domain": "物态变化"},
    # 新域已验证事实
    {"content": "铁块密度比水大所以沉入水中", "domain": "密度与浮力"},
    {"content": "木块密度比水小所以浮在水面", "domain": "密度与浮力"},
    {"content": "轮船是空心的所以能浮着", "domain": "密度与浮力"},
    {"content": "金属导热快所以摸热的金属会烫手", "domain": "热传递"},
    {"content": "木头导热慢所以木筷不烫手", "domain": "热传递"},
    {"content": "刹车靠增大摩擦力", "domain": "力与运动"},
    {"content": "冰面光滑摩擦力小所以滑冰滑得快", "domain": "力与运动"},
    {"content": "鞋底花纹增大摩擦力防滑", "domain": "力与运动"},
    # 第三阶段·生活/生物已验证事实
    {"content": "洗洁精把油污乳化后水一冲就走", "domain": "生活常识"},
    {"content": "保温杯用真空夹层隔热所以保温", "domain": "生活常识"},
    {"content": "冬天路面撒盐降低冰点所以化雪快", "domain": "生活常识"},
    {"content": "冰箱低温抑制微生物繁殖所以保鲜", "domain": "生活常识"},
    {"content": "植物需要光照进行光合作用制造养分释放氧气", "domain": "生物常识"},
    {"content": "鱼离开水会缺氧窒息", "domain": "生物常识"},
    {"content": "种子发芽需要阳光水和适宜温度", "domain": "生物常识"},
    {"content": "候鸟秋天南飞春天北归（季节迁徙）", "domain": "生物常识"},
    # 第三阶段·地球/技术已验证事实
    {"content": "地球自转形成昼夜交替", "domain": "地球环境"},
    {"content": "地球公转加地轴倾斜形成四季", "domain": "地球环境"},
    {"content": "月球和太阳引力形成潮汐", "domain": "地球环境"},
    {"content": "电路闭合形成回路电流才能流通", "domain": "技术常识"},
    {"content": "杠杆动力臂长省力", "domain": "技术常识"},
    {"content": "动滑轮省力一半定滑轮改变方向", "domain": "技术常识"},
    {"content": "光从水进入空气发生折射筷子看起来弯", "domain": "技术常识"},
    {"content": "真空瓶气压低水沸点低煮不熟（与高原同规律）", "domain": "物态变化"},
    # 第三阶段·人体已验证事实
    {"content": "消化系统把食物分解成营养身体获得能量", "domain": "人体常识"},
    {"content": "肺吸入氧气呼出二氧化碳进行气体交换", "domain": "人体常识"},
    {"content": "烫手缩手是反射弧快速反应不经大脑思考", "domain": "人体常识"},
    {"content": "睡眠让身体休息恢复精力", "domain": "人体常识"},
]


# ============ 五、组合引擎 v2（方向优先 → 单元匹配 → 结论生成 → 自校验） ============

def identify_scene(query):
    """识别问题场景：最长别名匹配优先；同长平局取最后命中
    （第三阶段：撒盐问题「冬天路面」同长时路面更具体——化雪场景）"""
    best_scene, best_len = None, 0
    for scene, aliases in SCENE_ALIAS.items():
        for a in aliases:
            if a in query and len(a) >= best_len:
                best_scene, best_len = scene, len(a)
    return best_scene


def identify_condition_dims(query):
    """识别问题的条件维度（词面 → 条件域，v1 保留作兜底）"""
    dims = []
    for dim, kws in DIM_KEYWORDS.items():
        if any(k in query for k in kws):
            dims.append(dim)
    return dims


def match_units_by_direction(direction, dims):
    """② 期望方向优先匹配单元；无方向则按维度兜底"""
    if direction and direction in DIR_INDEX:
        # 方向匹配：取该方向首个单元；若场景事实提供更具体维度，再精化
        uid = DIR_INDEX[direction][0]
        return [(uid, CONDITION_UNITS[uid])]
    # 维度兜底（v1 逻辑）
    chain = []
    for dim in dims:
        uids = DIM_INDEX.get(dim, [])
        if uids:
            chain.append((uids[0], CONDITION_UNITS[uids[0]]))
    return chain


def generate_conclusion(unit, scene, facts, direction, query=""):
    """③ 结论生成：单元结论模板 + 场景事实代入 → 新答案句（未预写）
    v5b：query 传入用于蒸发模板按问题强调维度选（夏天→温度/有风→通风）"""
    conclusion = unit.get("conclusion", {})
    if not conclusion:
        return unit["default"]
    # 选模板：气压效应按 低/高/标准 选；蒸发按问题强调维度选（v5b：LLM 对照回填）
    key = None
    temp = facts.get("温度", "")
    if unit["direction"] == "气压效应" and facts.get("气压") in ("低", "高", "标准"):
        key = facts["气压"]
    elif unit["direction"].startswith("液→气(缓慢)") and direction == "液→气(缓慢)":
        # 按问题强调的维度选：有风→通风 / 夏天热→温度 / 摊开→表面积
        # （v5 教训：固定顺序会错配——「夏天晾衣」被通风抢答）
        if any(w in query for w in ("风", "吹")):
            key = "通风"
        elif any(w in query for w in ("夏天", "热", "晒", "温度", "高温")):
            key = "温度"
        elif any(w in query for w in ("摊", "展开", "面积")):
            key = "表面积"
        else:
            for dim in ("通风", "温度", "表面积"):
                if dim in facts and dim in conclusion:
                    key = dim
                    break
    elif unit["direction"] == "浮沉" and facts.get("原因") == "空心":
        # v5（LLM 对照回填）：轮船是钢铁（密度比水大）但空心 → 排水体积大 → 浮
        # （必须先于「浮沉」分支——否则 facts 浮沉=浮 先命中「密度小」模板）
        key = "空心"
    elif unit["direction"] == "浮沉" and facts.get("浮沉") in conclusion:
        key = facts["浮沉"]
    elif unit["direction"] == "热传递" and facts.get("导热") in conclusion:
        key = facts["导热"]
    elif unit["direction"] == "摩擦" and facts.get("摩擦") in conclusion:
        key = facts["摩擦"]
    elif unit["direction"] in ("洗涤", "保温", "化雪", "保鲜", "光合", "呼吸", "生长", "迁徙",
                               "昼夜", "四季", "潮汐", "电路", "杠杆", "滑轮", "折射",
                               "消化", "呼吸换气", "反射", "睡眠",
                               "直线传播", "反射-光", "色散", "发声", "回声", "音调",
                               "月相", "食", "价格", "通胀", "燃烧", "氧化", "溶解"):
        # 第三阶段·新域 key 选择（动态）：conclusion 的 key 在 facts 中即选中
        # （乳化/隔热/化冰/低温/光照/缺氧/条件/季节/自转/公转/引力/回路/省力/动滑轮/定滑轮/折射）
        for k in conclusion:
            if k != "default" and (k in facts or facts.get(k) == k):
                key = k
                break
        if key is None and "default" in conclusion:
            key = "default"
    elif "不降温" in conclusion and facts.get("温度") == "不低":
        key = "不降温"
    elif "通风差" in conclusion and facts.get("通风") == "通风差":
        key = "通风差"
    elif "降温" in conclusion and (facts.get("降温") or ("低" in temp or "冷" in temp)):
        key = "降温"
    elif "升温" in conclusion and (facts.get("升温") or ("高" in temp or "热" in temp)):
        key = "升温"
    elif "default" in conclusion:
        key = "default"
    if key is None:
        return unit["default"]
    tpl = conclusion[key]
    # 占位代入（第三阶段·动态）：模板 {xxx} 占位优先从场景事实取，其次默认映射
    fill = {
        "液体": facts.get("液体", "水"),
        "固体": facts.get("固体", "冰"),
        "气体": facts.get("气体", "水蒸气"),
        "物体": facts.get("物体", "物体"),
        "热源": facts.get("热源", "热的东西"),
        "感觉": facts.get("感觉", "烫"),
        "表面": facts.get("表面", "表面"),
        "效果": facts.get("效果", "效果"),
    }
    import re as _re
    for p in set(_re.findall(r"\{(\w+)\}", tpl)):
        if p in facts:
            tpl = tpl.replace("{" + p + "}", str(facts[p]))
        elif p in fill:
            tpl = tpl.replace("{" + p + "}", str(fill[p]))
        else:
            tpl = tpl.replace("{" + p + "}", p)  # 未知占位保留词本身
    return tpl


def compose_answer(query, scene_override=None):
    """组合生成：方向识别 → 单元匹配 → 结论生成
    scene_override（第三阶段·递归组合）：显式指定场景（多条件链逐层生成用）"""
    scene = scene_override or identify_scene(query)
    direction = identify_direction(query)
    dims = identify_condition_dims(query)
    if not dims and not direction:
        return None, "条件不足（诚实边界）", None, None, None

    chain = match_units_by_direction(direction, dims)
    if not chain:
        return None, "无匹配单元（条件链不完整）", None, None, None

    facts = SCENE_FACTS.get(scene, {}) if scene else {}
    answer = generate_conclusion(chain[0][1], scene, facts, direction, query)
    return scene, answer, chain, facts, direction


# ============ 六b、逆转操作（7 操作工程化：逆转条件空间 → 反事实生成） ============
# 逆转 = 反转场景事实条件 + 同一规律单元演绎（非预写完整答案）
# rev_dim: 被反转的条件维度（决定用哪个规律单元）
REVERSE_FACTS = {
    "高原":   {"气压": "标准", "rev_dim": "气压", "说明": "如果气压正常（比如在平原）"},
    "珠峰":   {"气压": "标准", "rev_dim": "气压", "说明": "如果气压正常（比如在海平面）"},
    "高压锅": {"气压": "标准", "rev_dim": "气压", "说明": "如果不用高压锅（常压锅）"},
    "冷天":   {"温度": "不低", "rev_dim": "温度", "说明": "如果天气不冷（温度回升）"},
    "冰箱":   {"温度": "不低", "rev_dim": "温度", "说明": "如果冰箱不制冷"},
    "晾衣":   {"通风": "通风差", "rev_dim": "通风", "说明": "如果一点风都没有"},
}

# 反转维度 → 规律单元
REV_DIM_UNIT = {
    "气压": "沸点-气压",
    "温度": "凝固-条件",
    "通风": "蒸发-条件",
}


def reverse_condition(query):
    """逆转条件空间：反转场景事实 → 用规律单元生成反事实答案
    例「高原上水烧不开」→ 气压低 反转为 标准 → 沸点-气压单元生成
       「如果气压正常，水能烧到100°C开」——反事实推理（7 操作·逆转）"""
    scene = identify_scene(query)
    if scene not in REVERSE_FACTS:
        return None, "无逆转目标（诚实边界：不知道要逆转哪个条件）", None, None
    rv = REVERSE_FACTS[scene]
    facts = dict(SCENE_FACTS[scene])
    facts.update({k: v for k, v in rv.items() if k not in ("rev_dim", "说明")})
    uid = REV_DIM_UNIT.get(rv["rev_dim"])
    if uid is None:
        return None, "无逆转单元（诚实边界）", None, None
    unit = CONDITION_UNITS[uid]
    answer = generate_conclusion(unit, scene, facts, None, query)
    ok, checks = self_check(scene, answer, [(uid, unit)], facts, None, query)
    return scene, answer, ok, checks, rv.get("说明", "")


def scene_direction_conflict(scene, facts, direction, answer, query):
    """场景-方向相容性：场景事实与期望方向/生成结论矛盾检测（白箱自发现错误）
    注：高原/珠峰不算「低温不能沸腾」——珠峰沸腾真实存在（气压低→沸点低），
    由气压效应解释，非温度矛盾"""
    temp = facts.get("温度", "")
    # ① 低温场景不能剧烈沸腾（沸腾需达到沸点的高温；冷天/冰箱温度低）
    if direction == "液→气(剧烈)" and ("低" in temp or "冷" in temp
                                        or scene in ("冷天", "冰箱")):
        return "低温场景不能剧烈沸腾：{scene}温度低，水达不到沸点".format(scene=scene or "该场景")
    # ② 气压低场景（高原/珠峰）水达不到高温——问题却期望「特别热/烫」
    if scene in ("高原", "珠峰") and any(w in query for w in ("特别热", "烫", "很热", "温度高")):
        return "气压低场景水达不到高温：问题期望「热」与事实「气压低→沸点低」矛盾"
    # ③ 高温场景不能结冰/凝华
    if direction in ("液→固", "气→固") and ("高" in temp or "热" in temp):
        return "高温场景不会{dir}：场景温度高，与期望方向矛盾".format(dir=direction)
    return None


def self_check(scene, answer, chain, facts, direction, query):
    """自校验 v2：方向一致性 + 因果链完整性 + 事实一致性 + 场景相容性（白箱自己发现错误）"""
    checks = []
    ok = True
    uid, unit = chain[0]

    # ① 方向一致性：生成单元方向 vs 问题期望方向（v1 错配的检测器）
    if direction and unit["direction"] != "气压效应" and unit["direction"] != direction:
        ok = False
        checks.append(f"✗ 方向错配：问题期望[{direction}]，匹配到[{unit['direction']}]单元"
                      f"（{uid}）——应匹配方向一致单元")
    elif direction and unit["direction"] == "气压效应" and "沸点" not in answer:
        ok = False
        checks.append("✗ 气压效应未落地：问题含沸点/气压期望，生成未体现沸点变化")

    # ② 场景-方向相容性（v2b：矛盾问题检测）
    conflict = scene_direction_conflict(scene, facts, direction, answer, query)
    if conflict:
        ok = False
        checks.append(f"✗ 场景矛盾：{conflict}")

    # ③ 因果链完整性：答案应有「前提→结论」结构（含 → 因果连词）
    if "→" not in answer:
        ok = False
        checks.append(f"✗ 因果链不完整：生成「{answer}」无 前提→结论 结构")

    # ④ 事实一致性：与已验证事实矛盾检测
    for fact in VERIFIED_FACTS:
        fc = fact["content"]
        if scene in ("高原", "珠峰") and "高原" in fc and "降低" in fc:
            if "升高" in answer:
                ok = False
                checks.append(f"✗ 事实矛盾：{scene}气压低却生成「沸点升高」——与「{fc}」冲突")
        if scene == "高压锅" and "高压锅" in fc and "升高" in fc:
            if "降低" in answer:
                ok = False
                checks.append(f"✗ 事实矛盾：高压锅气压高却生成「沸点降低」——与「{fc}」冲突")
        if "结霜" in fc and direction == "气→固" and "凝华" not in answer and "霜" not in answer:
            ok = False
            checks.append(f"✗ 事实矛盾：结霜应凝华，生成未体现——与「{fc}」冲突")
        if "结冰" in fc and direction == "液→固" and "凝固" not in answer:
            ok = False
            checks.append(f"✗ 事实矛盾：结冰应凝固，生成未体现——与「{fc}」冲突")

    return ok, checks


def route_compose(query):
    """组合引擎统一入口（Route 递归，输出含证据链，可解释）
    固化层优先：已固化的问法（生成→自校验→固化）直接直答，不再重新组合
    v2.0（第四阶段·知识库条件化兜底）：组合未覆盖 → 蒸馏骨架规律查询"""
    _sol = solidified_lookup(query)
    if _sol:
        return {"query": query, "ok": True, "solidified": True,
                "answer": _sol["answer"], "scene": _sol.get("scene"),
                "units": [], "checks": [], "chain_evidence": []}
    scene, answer, chain, facts, direction = compose_answer(query)
    if chain is None:
        # 知识库条件化兜底：条件词命中蒸馏骨架 → 规律片段
        try:
            from condition_kb import ConditionKB
            _kb = ConditionKB()
            _r = _kb.lookup(query)
            if _r.get("ok"):
                return {"query": query, "ok": True, "condition_kb": True,
                        "answer": _r["rule"], "scene": _r["key"],
                        "units": [], "checks": [], "chain_evidence": [],
                        "kb_overlap": _r.get("overlap")}
        except Exception:
            pass
        return {"query": query, "ok": False, "reason": answer,
                "scene": scene, "chain": None, "checks": []}
    ok, checks = self_check(scene, answer, chain, facts, direction, query)
    if not ok:
        # 场景矛盾不兜底：白箱主动拒绝语义错误（矛盾是「问题错误」不是「知识缺失」，
        # condition_kb 兜底会掩盖矛盾——兜底只服务知识未覆盖）
        if any("场景矛盾" in c for c in checks):
            return {
                "query": query, "scene": scene, "direction": direction,
                "units": [c[0] for c in chain],
                "answer": answer, "ok": False, "checks": checks,
                "chain_evidence": [c[1]["source"] for c in chain],
            }
        # 自校验失败（非矛盾）→ 知识库条件化兜底（规律查询）
        try:
            from condition_kb import ConditionKB
            _kb = ConditionKB()
            _r = _kb.lookup(query)
            if _r.get("ok"):
                return {"query": query, "ok": True, "condition_kb": True,
                        "answer": _r["rule"], "scene": _r["key"],
                        "units": [], "checks": [], "chain_evidence": [],
                        "kb_overlap": _r.get("overlap")}
        except Exception:
            pass
    return {
        "query": query, "scene": scene, "direction": direction,
        "units": [c[0] for c in chain],
        "answer": answer, "ok": ok, "checks": checks,
        "chain_evidence": [c[1]["source"] for c in chain],
    }


# ============ 六b、递归组合（第三阶段·组合引擎深度：递归下降多条件链） ============
def identify_all_scenes(query):
    """识别问题中的所有场景（多条件链：高压锅在高原 → [高原, 高压锅]）"""
    scenes = []
    for scene, aliases in SCENE_ALIAS.items():
        if any(a in query for a in aliases):
            scenes.append(scene)
    return scenes


def compose_recursive(query):
    """递归组合（递归下降·循环条件空间）：多场景多条件链逐层组合——
    每层：场景 × 方向 → 结论片段；层间拼接（中间结果喂下一步）。
    例「高压锅在高原上煮饭」→ 高原层(气压低→沸点降→煮不熟)
    × 高压锅层(气压高→沸点升→能煮熟)——单一查表无法生成的双层结论。"""
    scenes = identify_all_scenes(query)
    if len(scenes) <= 1:
        r = route_compose(query)
        r["recursive"] = False
        return r
    direction = identify_direction(query)
    pieces, units = [], []
    for scene in scenes:
        # 每层：指定场景 × 问题方向 → 结论片段（逐层生成）
        sc, ans, chain, facts, dr = compose_answer(query, scene)
        if chain and ans and "→" in ans:
            pieces.append(ans)
            units.append(chain[0][0])
    combined = "；".join(dict.fromkeys(pieces)) if pieces else ""
    # 组合级校验：因果链完整（每片段含 →）+ 片段数≥2（多条件链成立）
    ok = len(pieces) >= 2 and all("→" in p for p in pieces)
    checks = [] if ok else ["✗ 递归组合失败：条件链片段不足（需要 ≥2 个场景层）"]
    if not ok:
        # 第三阶段：多场景但单层有贡献 → 降级普通组合（不因背景场景误判失败）
        r = route_compose(query)
        r["recursive"] = False
        r["fallback"] = True
        return r
    return {"query": query, "scene": scenes, "direction": direction,
            "units": units, "answer": combined, "ok": ok, "checks": checks,
            "chain_evidence": [], "recursive": True}


# ============ 六c1、雅可比传播并行组合（第四阶段·目标③：独立性判定→独立域并行） ============
def _layer_conditions(scene, query):
    """场景层的条件集（用于雅可比独立性判定：层间无共享条件 = 混合偏导=0 = 独立）"""
    direction = identify_direction(query)
    dims = identify_condition_dims(query)
    if not direction and not dims:
        return set()
    u1 = match_units_by_direction(direction, dims)
    if not u1:
        return set()
    return {c for _, u in u1 for c in u.get("conditions", [])}


def _layer_work(query, scene):
    """单层并行任务：层生成（compose_answer）+ 链式法则传播（numpy 雅可比重负载）——
    雅可比传播 = 组合执行的一部分：验证该层条件的影响路径证据（可解释性增强）"""
    import time
    t0 = time.time()
    sc, ans, chain, facts, dr = compose_answer(query, scene)
    jacobian_steps = 0
    try:
        import numpy as np
        try:
            from condition_algebra import build_influence_jacobian
        except ImportError:
            from .condition_algebra import build_influence_jacobian
        J, knowledges, conditions = build_influence_jacobian(CONDITION_UNITS)
        if conditions:
            rng = np.random.default_rng(hash(scene) % (2**32))
            M = J.astype(np.float64)
            M = np.pad(M, ((0, 0), (0, max(0, M.shape[0] - M.shape[1]))))[:M.shape[0], :M.shape[0]]
            # 链式法则传播：雅可比矩阵幂（条件链 = 路径组合），numpy 释放 GIL → 真实并行负载
            P = M.copy()
            for _ in range(4):
                P = P @ P
            jacobian_steps = int(np.count_nonzero(P > 0))
    except Exception:
        pass
    return sc, ans, chain, facts, dr, jacobian_steps, (time.time() - t0) * 1000


def compose_recursive_parallel(query, max_workers=4):
    """雅可比传播并行组合：多场景多条件链逐层组合，独立场景层线程池并行执行。
    独立性判定：层间条件集无交集（雅可比混合偏导 ∂²f/∂x∂y=0）→ 并组并行；
    共享条件域（混合偏导≠0）→ 串行（避免组合冲突）。
    每层负载 = 层生成 + 链式法则传播（numpy）——「雅可比传播用于组合执行」。
    结果与串行 compose_recursive 语义等价（并行只改变执行顺序）。"""
    import time
    scenes = identify_all_scenes(query)
    if len(scenes) <= 1:
        r = route_compose(query)
        r["recursive"] = False
        r["parallel"] = False
        return r
    # 层间独立性分组：无共享条件的场景层并组（同组内并行，组间串行）
    conds_by_scene = {s: _layer_conditions(s, query) for s in scenes}
    groups, used = [], [False] * len(scenes)
    for i in range(len(scenes)):
        if used[i]:
            continue
        group, used[i] = [scenes[i]], True
        for j in range(i + 1, len(scenes)):
            if used[j]:
                continue
            if not (conds_by_scene[scenes[i]] & conds_by_scene[scenes[j]]):
                group.append(scenes[j])
                used[j] = True
        groups.append(group)

    # 并行执行：独立组内线程池并行层生成，共享组串行
    from concurrent.futures import ThreadPoolExecutor
    results = {}
    t0 = time.time()
    for group in groups:
        if len(group) == 1:
            results[group[0]] = _layer_work(query, group[0])
        else:
            with ThreadPoolExecutor(max_workers=min(max_workers, len(group))) as ex:
                futs = {ex.submit(_layer_work, query, s): s for s in group}
                for f in futs:
                    results[futs[f]] = f.result()
    parallel_ms = (time.time() - t0) * 1000

    # 串行对照（真正串行逐层，测加速比）
    t1 = time.time()
    serial_results = {}
    for s in scenes:
        serial_results[s] = _layer_work(query, s)
    serial_ms = (time.time() - t1) * 1000

    # 拼接 + 组合级校验（与串行 compose_recursive 同语义）
    pieces, units = [], []
    jacobian_total = 0
    for scene in scenes:
        sc, ans, chain, facts, dr, jsteps, _ = results[scene]
        jacobian_total += jsteps
        if chain and ans and "→" in ans:
            pieces.append(ans)
            units.append(chain[0][0])
    combined = "；".join(dict.fromkeys(pieces)) if pieces else ""
    ok = len(pieces) >= 2 and all("→" in p for p in pieces)
    checks = [] if ok else ["✗ 递归组合失败：条件链片段不足（需要 ≥2 个场景层）"]
    if not ok:
        r = route_compose(query)
        r["recursive"] = False
        r["parallel"] = False
        r["fallback"] = True
        return r
    return {"query": query, "scene": scenes, "direction": identify_direction(query),
            "units": units, "answer": combined, "ok": ok, "checks": checks,
            "chain_evidence": [], "recursive": True, "parallel": True,
            "groups": groups, "jacobian_steps": jacobian_total,
            "parallel_ms": round(parallel_ms, 2), "serial_ms": round(serial_ms, 2),
            "speedup": round(serial_ms / max(parallel_ms, 0.001), 2)}


# ============ 六c、规划生成（第三阶段·生成能力扩展：目标+阻碍 → 步骤链） ============
# 规划 = 序列化条件链：识别阻碍条件 → 逆转/满足条件 → 步骤（每步可被条件单元验证）
PLAN_RULES = {
    "气压低": "① 用高压锅密封增压 → 锅内气压升高 → 水沸点升到100°C以上 → 饭能煮熟",
    "温度低": "① 加热/保温到所需温度 → 满足温度条件 → 目标达成",
    "缺氧": "① 回到有氧环境/提供氧气 → 恢复呼吸 → 存活",
    "缺光": "① 移到有光处/人工补光 → 光合作用恢复 → 植物正常生长",
    "缺水": "① 浇水/补充水分 → 生长条件满足 → 发芽生长",
    "缺营养": "① 进食补充营养 → 消化吸收 → 恢复能量",
}


def plan_compose(query):
    """规划生成：识别问题场景的阻碍条件 → 生成解决步骤链（白箱，零 LLM）
    例「在高原上怎么煮饭才能熟？」→ 阻碍[气压低] → 步骤[高压锅增压→煮熟]"""
    scene = identify_scene(query)
    facts = SCENE_FACTS.get(scene, {}) if scene else {}
    obstacles = []
    if facts.get("气压") == "低":
        obstacles.append("气压低")
    if facts.get("温度") == "低":
        obstacles.append("温度低")
    if facts.get("缺氧"):
        obstacles.append("缺氧")
    if facts.get("缺光") or facts.get("光照") == "无":
        obstacles.append("缺光")
    if facts.get("缺水"):
        obstacles.append("缺水")
    # 问题含「吃饭/饿」→ 缺营养
    if any(w in query for w in ("饿", "没力气", "没精神")):
        obstacles.append("缺营养")
    steps = [PLAN_RULES[o] for o in dict.fromkeys(obstacles)]
    ok = len(steps) > 0
    plan = "；".join(steps) if steps else "（未识别阻碍条件：无法给出计划）"
    return {"query": query, "scene": scene, "obstacles": list(dict.fromkeys(obstacles)),
            "steps": steps, "plan": plan, "ok": ok}


# ============ 六d、条件代数集成（第五阶段：雅可比独立性→并行组合 + 覆盖率判定） ============
def compose_parallel(queries, max_workers=4):
    """条件代数并行组合：雅可比条件独立性 → 独立条件域查询**并行执行**。
    共享条件域的查询（混合偏导 ∂²f/∂x∂y≠0）需串行——避免组合冲突。
    返回 {results, groups, note, parallel_ms, serial_ms}：并行耗时 vs 串行耗时"""
    import time
    try:
        from condition_algebra import build_influence_jacobian
    except Exception:
        build_influence_jacobian = None
    groups = None
    if build_influence_jacobian is not None:
        build_influence_jacobian(CONDITION_UNITS)
        groups = []
        used = [False] * len(queries)
        for i in range(len(queries)):
            if used[i]:
                continue
            group = [i]
            used[i] = True
            for j in range(i + 1, len(queries)):
                if used[j]:
                    continue
                d1 = identify_direction(queries[i])
                d2 = identify_direction(queries[j])
                if d1 is None or d2 is None:
                    continue
                u1 = match_units_by_direction(d1, identify_condition_dims(queries[i]))
                u2 = match_units_by_direction(d2, identify_condition_dims(queries[j]))
                conds1 = {c for _, u in u1 for c in u.get("conditions", [])}
                conds2 = {c for _, u in u2 for c in u.get("conditions", [])}
                if not (conds1 & conds2):  # 无共享条件 → 独立 → 同组并行
                    group.append(j)
                    used[j] = True
            groups.append(group)
    if groups is None:
        groups = [list(range(len(queries)))]

    # 并行执行：同组内线程池并行（独立条件域），组间串行
    from concurrent.futures import ThreadPoolExecutor
    results = [None] * len(queries)
    t0 = time.time()
    for group in groups:
        if len(group) == 1:
            results[group[0]] = route_compose(queries[group[0]])
        else:
            with ThreadPoolExecutor(max_workers=min(max_workers, len(group))) as ex:
                futs = {ex.submit(route_compose, queries[i]): i for i in group}
                for f in futs:
                    results[futs[f]] = f.result()
    parallel_ms = (time.time() - t0) * 1000

    # 串行对照：真正串行执行（测加速比）
    t1 = time.time()
    for i in range(len(queries)):
        route_compose(queries[i])
    serial_ms = (time.time() - t1) * 1000

    out = [{"query": q, "result": results[i]} for i, q in enumerate(queries)]
    return {"results": out, "groups": groups,
            "note": "并行分组：独立条件域线程池并行，共享条件域串行",
            "parallel_ms": round(parallel_ms, 1),
            "serial_ms": round(serial_ms, 1),
            "speedup": round(serial_ms / max(parallel_ms, 0.1), 2)}


# 核心常识域清单（覆盖率判定④：≥80%）
CORE_PHENOMENA = [
    "沸腾", "蒸发", "液化", "凝固", "熔化", "升华", "凝华", "沸点与气压",
    "浮力", "导热", "摩擦", "洗涤", "保温", "化雪", "保鲜",
    "光合", "呼吸", "生长", "迁徙", "昼夜", "四季", "潮汐",
    "电路", "杠杆", "滑轮", "折射", "消化", "反射", "睡眠",
]


def coverage_report():
    """核心常识域覆盖率（判定④：条件单元覆盖率 ≥80%）
    统计：核心常识现象被条件单元覆盖（单元名/方向/域/条件 规范化匹配）"""
    def _norm(s):
        """组合键归一化：剥去连字符、「与」与空格，消除组合枚举的表面书写差异。"""
        return str(s).replace("-", "").replace("与", "").replace(" ", "")
    units = CONDITION_UNITS
    covered = []
    for name in CORE_PHENOMENA:
        nn = _norm(name)
        hit = any(
            nn in _norm(uid) or nn in _norm(u.get("direction", ""))
            or nn in _norm(u.get("domain", ""))
            or any(nn in _norm(c) for c in u.get("conditions", []))
            or (isinstance(u.get("rule"), dict) and any(nn in _norm(k) for k in u["rule"]))
            for uid, u in units.items())
        covered.append(hit)
    rate = sum(covered) / len(CORE_PHENOMENA)
    return {"rate": round(rate * 100, 1), "covered": sum(covered),
            "total": len(CORE_PHENOMENA),
            "missing": [n for n, c in zip(CORE_PHENOMENA, covered) if not c],
            "units": len(units), "domains": len({u["domain"] for u in units.values()})}


# ============ 七、固化层（生成自举闭环：生成→自校验→固化→直答） ============
# 白箱组合生成+自校验通过的知识，固化为直答——下次同类问法直接命中，
# 不再重新组合。持久化到 JSON（启动加载），跨进程生效。
import json as _json
import os as _os

_SOLIDIFY_FILE = _os.path.join(
    _os.path.dirname(_os.path.abspath(__file__)), "solidified_knowledge.json")

SOLIDIFIED = {}
if _os.path.exists(_SOLIDIFY_FILE):
    try:
        _loaded = _json.load(open(_SOLIDIFY_FILE, encoding="utf-8"))
        if isinstance(_loaded, dict):
            SOLIDIFIED = _loaded
    except Exception:
        SOLIDIFIED = {}


def solidified_lookup(query):
    """查固化层：原问法或触发词命中 → 已固化直答（含触发优先级）
    v5（LLM 对照回填）：公共现象词不参与触发匹配——「烫手」会劫持
    「木筷不烫手」问题（固化层误答金属勺，LLM 对照抓到的 bug）"""
    _COMMON = {"烫手", "热汤", "热", "为什么", "怎么", "什么", "吗", "会", "能",
               "干得快", "不烫", "快", "慢"}
    best = None
    for key, entry in SOLIDIFIED.items():
        tr = [t for t in (entry.get("triggers") or []) if t not in _COMMON]
        if any(t and t in query for t in tr):
            tlen = max(len(t) for t in tr if t and t in query)
            if best is None or tlen > best[0]:
                best = (tlen, entry)
        elif key and key in query and (best is None or len(key) > best[0]):
            best = (len(key), entry)
    return best[1] if best else None


def solidify(query, triggers=None, scene=None):
    """固化：组合生成 + 自校验通过 → 写入固化库（持久化）
    自校验未通过的知识不固化（自举纪律：错误生成不得固化）"""
    r = route_compose(query)
    if not r.get("ok") or not r.get("answer"):
        return None
    key = query.strip("？?。！! ")
    entry = {"answer": r["answer"], "triggers": triggers or [],
             "scene": scene or r.get("scene"), "query": query,
             "source": "compose_solidified"}
    SOLIDIFIED[key] = entry
    try:
        _json.dump(SOLIDIFIED, open(_SOLIDIFY_FILE, "w", encoding="utf-8"),
                   ensure_ascii=False, indent=1)
    except Exception:
        pass
    return entry


# ============ 六、自举判定统计 ============

def bootstrap_report(results, error_queries=None):
    total = len(results)
    gen_ok = sum(1 for r in results if r.get("answer") and not r["answer"].startswith(("条件不足", "无匹配")))
    selfcheck_ok = sum(1 for r in results if r["ok"])
    # 错误检测率：故意设计的矛盾问题应被自校验抓住（✘=自发现错误=成功）
    if error_queries:
        caught = sum(1 for r in results
                     if r["query"] in error_queries and not r["ok"])
        print("=== 白箱自举判定（物态域 v2） ===")
        print(f"组合生成成功率: {gen_ok}/{total} = {gen_ok/total*100:.0f}% （目标≥80%）")
        print(f"正常问题自校验通过率: {selfcheck_ok}/{total - len(error_queries)} "
              f"= {selfcheck_ok/(total-len(error_queries))*100:.0f}% （目标≥90%）")
        print(f"矛盾问题检测率: {caught}/{len(error_queries)} = {caught/len(error_queries)*100:.0f}% "
              f"（白箱自己发现生成错误——自举核心）")
    else:
        print("=== 白箱自举判定（物态域 v2） ===")
        print(f"组合生成成功率: {gen_ok}/{total} = {gen_ok/total*100:.0f}% （目标≥80%）")
        print(f"内部自校验通过率: {selfcheck_ok}/{total} = {selfcheck_ok/total*100:.0f}% （目标≥90%）")
    print(f"注: 组合生成 = 未预写完整答案，由 方向推理+场景事实×规律单元 演绎拼出")


if __name__ == "__main__":
    demos = [
        "高原上煮饭为什么不容易熟？",
        "高压锅为什么煮得快？",
        "珠峰上烧水为什么不到100°C就开？",
        "为什么夏天晾衣服干得快？",
        "为什么有风的时候衣服干得更快？",
        "冰箱为什么会结霜？",
        "为什么冬天湖面会结冰？",
        "为什么冬天眼镜会起雾？",
        "樟脑丸为什么放久了变小？",
        # 新域：密度浮力/热传导/摩擦
        "为什么铁块会沉入水底？",
        "为什么木头能浮在水面上？",
        "为什么轮船那么大还能浮着？",
        "为什么金属勺放进热汤会烫手？",
        "为什么木筷不烫手？",
        "为什么刹车能很快停下来？",
        "为什么滑冰能滑得很快？",
        "为什么鞋底要有花纹？",
        # 自校验演示：白箱应自己发现这些矛盾问题
        "高原上水为什么烧得特别热？",
        "冬天湖面为什么会沸腾？",
        # 第三阶段·生活/生物常识域
        "为什么碗上的油污用洗洁精一洗就掉？",
        "为什么保温杯里的热水放很久还是热的？",
        "为什么冬天路面要撒盐？",
        "为什么冰箱里的食物不容易坏？",
        "为什么植物要放在有阳光的地方？",
        "为什么鱼离开水会死？",
        "为什么种子发芽需要浇水？",
        "为什么大雁秋天往南飞？",
        # 第三阶段·地球/技术常识域
        "为什么有白天和黑夜？",
        "为什么有春夏秋冬四季？",
        "为什么海水会涨潮退潮？",
        "为什么灯泡不亮了？",
        "为什么撬棍能撬起大石头？",
        "为什么升旗要用定滑轮？",
        "为什么筷子放在水里看起来像折断了？",
        # 类比（条件结构相似：真空=气压低 → 同高原规律域）
        "为什么真空瓶里煮饭也煮不熟？",
        # 第三阶段·人体常识域
        "为什么饿了要吃饭？",
        "为什么跑步会喘气？",
        "为什么烫到手会立刻缩回？",
        "为什么人需要睡觉？",
    ]
    error_queries = {"高原上水为什么烧得特别热？", "冬天湖面为什么会沸腾？"}
    results = []
    for q in demos:
        # 第三阶段·递归组合优先（多条件链）；单场景走普通组合
        r = compose_recursive(q)
        results.append(r)
        mark = "✔" if r["ok"] else "✘"
        rec = "递归" if r.get("recursive") else "组合"
        print(f"\nQ: {q}")
        print(f"  [{rec} 场景={r.get('scene')} 方向={r.get('direction')} 单元={r.get('units')}]")
        print(f"  {mark} 生成: {r.get('answer', r.get('reason'))}")
        if r.get("chain_evidence"):
            print(f"    证据源: {r['chain_evidence']}")
        for c in r.get("checks", []):
            print(f"    {c}")
    print()
    print("=== 规划生成演示（生成能力扩展·目标+阻碍→步骤链 零 LLM） ===")
    for q in ["在高原上怎么煮饭才能熟？", "鱼离开水了怎么才能活？", "我饿了没力气怎么办？"]:
        p = plan_compose(q)
        mark = "✔" if p["ok"] else "✘"
        print(f"\nQ: {q}")
        print(f"  {mark} 阻碍={p['obstacles']}")
        print(f"  计划: {p['plan']}")
    print()
    print("=== 逆转操作演示（反事实生成 · 7 操作工程化） ===")
    for q in ["高原上水烧不开，那如果在平原呢？",
              "冬天湖面结冰，那如果天气不冷呢？",
              "晾衣服干得慢，那如果一点风都没有呢？"]:
        scene, answer, ok, checks, note = reverse_condition(q)
        mark = "✔" if ok else "✘"
        print(f"\nQ: {q}")
        print(f"  {mark} 逆转生成: {answer}")
        print(f"  说明: {note}")
        for c in checks:
            print(f"    {c}")
    print()
    bootstrap_report(results, error_queries)
