"""相容性知识库数据文件加载 / 校验 / 降级测试(compatibility-engine-upgrade 任务 1.4)。 覆盖需求 1.2(外置数据文件、加载失败优雅降级、结构可校验)。 """ from __future__ import annotations import os from skills.compatibility import rules from skills.compatibility.knowledge_base import KnowledgeBase, get_default_kb def test_default_kb_loads_data_files_without_degradation(): """默认数据目录下三份 yaml 应正常加载,辅料 / 反应部分不降级。""" kb = KnowledgeBase.load() # 辅料与反应来自数据文件(非内置降级)。 assert "excipients" not in kb.degraded assert "reactions" not in kb.degraded assert len(kb.excipients()) >= 1 assert len(kb.rules()) >= 1 def test_kb_validate_reports_no_problems(): """打包的数据文件应通过结构校验(必填字段、枚举合法、定量字段带 source)。""" kb = KnowledgeBase.load() problems = kb.validate() assert problems == [], f"知识库校验应无问题,实际:{problems}" def test_missing_data_dir_degrades_to_builtin_without_crash(): """数据目录缺失 → 降级到内置最小集,不抛异常,仍可用。""" kb = KnowledgeBase.load(data_dir=os.path.join(os.sep, "nonexistent_kb_dir_xyz")) assert "excipients" in kb.degraded assert "reactions" in kb.degraded # 降级后仍能解析与评估。 profile = kb.resolve("乳糖") assert profile.known is True assert "reducing_sugar" in profile.tags assert len(kb.rules()) == len(rules.RULES) def test_resolve_equivalent_to_builtin_for_known_excipients(): """数据文件解析结果与内置最小集对已知辅料等价(任务 1.1 只搬运、不改判定)。""" kb = KnowledgeBase.load() for name in ["乳糖", "硬脂酸镁", "微晶纤维素", "聚维酮", "magnesium stearate"]: from_kb = kb.resolve(name) builtin = rules._resolve_builtin(name) assert from_kb.known == builtin.known, name assert set(from_kb.tags) == set(builtin.tags), name def test_unknown_excipient_marked_not_known(): kb = KnowledgeBase.load() profile = kb.resolve("某种不存在的辅料XYZ") assert profile.known is False assert profile.tags == frozenset() def test_wildcard_group_ids_expanded_to_all_groups(): """reactions.yaml 中 group_ids: '*'(吸附规则)应展开为全部已知官能团。""" kb = KnowledgeBase.load() adsorption_rules = [r for r in kb.rules() if r.reaction == "adsorption"] assert adsorption_rules, "应存在吸附规则" assert adsorption_rules[0].group_ids == frozenset(rules.FUNCTIONAL_GROUP_META.keys()) def test_evaluate_uses_knowledge_base_path(): """evaluate 经知识库路径产出与历史一致的经典判定(胺 + 乳糖 → 高风险美拉德)。""" assessment = rules.evaluate([{"id": "primary_amine"}], ["乳糖"]) assert assessment.overall_risk == rules.RISK_HIGH er = assessment.excipient_results[0] assert "maillard" in [r.reaction for r in er.reactions]