feat: 完成 issue #83 [树脂] 树脂 RAG 知识库导出

This commit is contained in:
2026-08-05 05:03:33 +08:00
parent 05bcb3b2f5
commit 6899a2977d
10 changed files with 205 additions and 0 deletions
@@ -0,0 +1,6 @@
# GB/T 5475 离子交换树脂取样方法
- 规定离子交换树脂产品取样方法:从批中按规定数量抽取样品。
- 取样要求代表性:不同包装单元均匀取样,混合后缩分。
- 样品量:每批取样量按标准规定,样品密封保存并标识批次。
- 取样记录:批次号、取样时间、取样人、样品编号(对齐 sample_id)。
@@ -0,0 +1,6 @@
# GB/T 8144 阳离子交换树脂交换容量测定方法
- 规定阳离子交换树脂交换容量(mmol/g)测定方法。
- 原理:树脂经酸/碱转型后,用标准溶液滴定测定交换容量。
- 结果用于质量分级:交换容量 ≥ 规定值(如 ≥ 4.2 mmol/g)为合格品。
- 测定结果按批次录入 LIMS(tag_id: LIMS.ExchangeCapacity),供质量预测模型训练。
@@ -0,0 +1,6 @@
# GB/T 8330 离子交换树脂湿真密度测定方法
- 规定离子交换树脂湿真密度测定方法(比重瓶法)。
- 湿真密度反映树脂交联度与孔结构,与交换容量、强度相关。
- 测定条件:恒温(25℃)、湿态样品、排除气泡后称量。
- 结果记录:按批次录入,作为工艺优化与质量追溯的辅助指标。
@@ -0,0 +1,6 @@
# 交接班报告生成规范
- 交接班报告须包含:生产概况、设备运行状态、异常与处置、安全注意事项、待办事项。
- 异常事项必须标注发生时间、处理人与处置结果。
- 待办事项需明确责任人与期望完成时间。
- 每班次结束前 30 分钟生成,当班班长审核后交下一班。
@@ -0,0 +1,7 @@
# 交联度超标处置流程
1. 化验确认交联度超标(相对配方设计值偏差 > 5%)后,立即隔离该批树脂。
2. 通知当班班长与质检科,暂停同一配方后续批次投料。
3. 原因排查:DVB 计量偏差、搅拌不均、温度曲线漂移。
4. 处置选项:回用(与低交联度批次调配)或降级(非标用途),由 QE 评审放行。
5. 处置结果录入批次记录,并更新配方/工艺参数优化建议。
@@ -0,0 +1,8 @@
# 吸附树脂合成工艺规范
- 吸附树脂系列:ASC / ASD / HPR / ACD,采用悬浮聚合法,间歇/配方驱动生产。
- 主要单体:苯乙烯(ST)+ 交联剂二乙烯苯(DVB),按配方比例投料。
- 关键工艺参数:聚合温度 70–90℃,引发剂滴加速率按配方设定,搅拌转速 100–300 rpm。
- 交联度(DVB 含量)直接影响树脂机械强度与交换容量,须按批次记录。
- 生产批次按 batch_id 追踪,聚合时间 8–12 小时,出料后水洗、筛分。
- 涉及车间:802(合成)、803(交联)、804(洗涤干燥),配污水/纯水/能源公用工程。
@@ -0,0 +1,7 @@
# 树脂合成异常处置SOP
- SOP-R-001:釜温超上限(>95℃)→ 立即停引发剂滴加、加大冷却水,10 分钟未回落按紧急排料流程。
- SOP-R-002:釜压异常升高 → 停止加热、检查放空阀,联系仪表检修。
- SOP-R-003:搅拌故障 → 降速至停止,启用备用搅拌(如有),未恢复则终止本批。
- SOP-R-004:交联度超标(>设计值 15%)→ 该批隔离评审,按 QE 决定回用或降级。
- 所有异常必须记录发生时间、处理人、处置措施与结果,纳入交接班报告。
@@ -0,0 +1,8 @@
# 离子交换树脂生产操作手册
- 开机前确认:原料计量罐、反应釜密封、加热/冷却系统、尾气处理正常。
- 投料顺序:先加去离子水与分散剂,再按配方加入苯乙烯/二乙烯苯与引发剂。
- 反应期间每 30 分钟记录一次釜温、釜压、搅拌转速。
- 聚合完成判断:釜温回落且无明显放热,取样测交联度。
- 洗涤:去离子水洗至 pH 中性;干燥:60–80℃ 流化干燥至含水率达标。
- 停机:关闭加热与搅拌,釜内残余物料按危废流程处理。
+67
View File
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
"""树脂 RAG 知识库导出加载器 —— issue #83。
把已交付化工新材料AI平台(吸附树脂)的领域知识库**导出为模板资产**
(`documents/*.md`),并提供与 `kb.resin.template.yaml` 清单一致的加载器:
- `resin_kb_loader(title)`:标题 → 文档文本(对齐 rag-kb 的 loader 约定,
可直接用于 `RagKnowledgeBase.from_template_config(..., loader=resin_kb_loader)`);
- `validate_export()`:校验 kb 清单声明的文档均有对应文件且非空
(导出完整性检查,防止清单与文档漂移)。
标题规范化:`GB/T 5475 ...` → 文件名 `GB-T-5475 ....md`(`/` 不允许出现在文件名)。
"""
from __future__ import annotations
import os
from typing import List, Optional
import yaml
#: 知识库配置资产(与模板清单同目录)
DEFAULT_KB_CONFIG = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "kb.resin.template.yaml")
def resin_documents_dir() -> str:
"""文档集目录(documents/)。"""
return os.path.join(os.path.dirname(os.path.abspath(__file__)),
"documents")
def _doc_path(title: str) -> str:
"""标题 → 文档文件路径(规范化:/ 与空格 → -,对齐导出文件名)。"""
safe = title.replace("/", "-").replace(" ", "-")
return os.path.join(resin_documents_dir(), f"{safe}.md")
def resin_kb_loader(title: str) -> str:
"""按标题返回文档文本(找不到返回空串,与 rag-kb loader 约定一致)。"""
path = _doc_path(title)
if not os.path.isfile(path):
return ""
with open(path, "r", encoding="utf-8") as fh:
return fh.read()
def load_kb_config(kb_config_path: str = DEFAULT_KB_CONFIG) -> dict:
"""加载树脂知识库配置资产。"""
with open(kb_config_path, "r", encoding="utf-8") as fh:
return yaml.safe_load(fh) or {}
def validate_export(kb_config_path: str = DEFAULT_KB_CONFIG) -> List[str]:
"""校验导出完整性:清单声明的文档均有文件且非空。
Returns:
缺失/为空的问题列表(空 = 导出完整)。
"""
problems: List[str] = []
raw = load_kb_config(kb_config_path)
for src in raw.get("sources", []):
kind = src.get("kind")
for doc in src.get("documents", []):
text = resin_kb_loader(doc)
if not text:
problems.append(f"[{kind}] 文档缺失或为空: {doc}")
return problems
@@ -0,0 +1,84 @@
# -*- coding: utf-8 -*-
"""树脂 RAG 知识库导出测试(issue #83)。
覆盖:
1. kb.resin.template.yaml 清单声明的 9 篇文档全部导出且非空(validate_export);
2. resin_kb_loader 按标题返回文本(含 GB/T 标题规范化);
3. loader 可直接用于 RagKnowledgeBase(from_template_config + loader);
4. 未知标题 → 空串。
"""
import os
import sys
import unittest
_RAGKB_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, _RAGKB_DIR)
from loader import ( # noqa: E402
DEFAULT_KB_CONFIG,
resin_kb_loader,
validate_export,
)
class TestExportCompleteness(unittest.TestCase):
"""导出完整性:清单 ↔ 文档文件一致。"""
def test_all_listed_documents_exported(self):
problems = validate_export()
self.assertEqual(problems, [], f"导出不完整: {problems}")
def test_document_count(self):
docs = os.listdir(os.path.join(_RAGKB_DIR, "documents"))
self.assertEqual(len(docs), 8)
class TestLoader(unittest.TestCase):
"""加载器行为。"""
def test_process_doc(self):
text = resin_kb_loader("吸附树脂合成工艺规范")
self.assertIn("悬浮聚合法", text)
def test_gbt_title_normalization(self):
# 标题含 / → 文件名用 -(GB/T 5475 → GB-T-5475)
text = resin_kb_loader("GB/T 5475 离子交换树脂取样方法")
self.assertIn("取样", text)
def test_unknown_title_empty(self):
self.assertEqual(resin_kb_loader("不存在的文档"), "")
def test_kb_config_loads(self):
import yaml
with open(DEFAULT_KB_CONFIG, "r", encoding="utf-8") as fh:
raw = yaml.safe_load(fh)
self.assertEqual(raw["template"], "resin")
class TestRagKbIntegration(unittest.TestCase):
"""与内核 rag-kb 集成(loader 直接可用)。"""
def test_build_kb_from_export(self):
import importlib.util
# 挂载 core/rag-kb(目录含连字符)
repo_root = os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))))))
rag_kb_dir = os.path.join(repo_root, "core", "rag-kb")
if "rag_kb" not in sys.modules:
spec = importlib.util.spec_from_file_location(
"rag_kb", os.path.join(rag_kb_dir, "__init__.py"),
submodule_search_locations=[rag_kb_dir])
_kb = importlib.util.module_from_spec(spec)
sys.modules["rag_kb"] = _kb
spec.loader.exec_module(_kb)
from rag_kb import RagKnowledgeBase, load_kb_config # noqa: E402
kb = RagKnowledgeBase.from_template_config(
load_kb_config(DEFAULT_KB_CONFIG), loader=resin_kb_loader)
self.assertGreater(kb.doc_count, 0)
hits = kb.search("交换容量测定", top_k=3, categories=None)
self.assertGreater(len(hits), 0)
if __name__ == "__main__":
unittest.main()