初始提交:智能水务泵站知识图谱系统 v2.1 把此前游离在版本控制之外的工程代码全部纳入 Git。 后端(app/) FastAPI,图谱四层本体(资产/诊断/处置/经验),InMemoryGraph 与 Neo4jGraph 双后端自动降级 黑板模型驱动的 6 个专职 Agent 协作 诊断推理:证据匹配评分 -> 故障级排序,评分口径集中于 app/scoring.py, 可用 scripts/calibrate.py 重新标定 处置闭环写端点:接单 / 派单 / 工序销项 / 复测 / 闭环归档 / 案例沉淀, 含”无复测数据不许闭环””仍越限则挂起””强制闭环需管理员”等闸门 RBAC 三角色白名单 + 接口级能力校验 SQLite(WAL) 持久化,业务与 HTTP 双层操作审计 外部数据三源接入:落盘缓存 + stale/available 降级纪律,取不到不伪造 前端(frontend/) 单页控制台,经 /api 访问后端;需先启动服务,双击 HTML 无效 评测与自检(scripts/、tests/) selftest.py 36 项、selftest_ops.py 29 项、selftest_external.py 37 项 tests/run_all.py 零依赖单测跑法(用例为 pytest 风格) check_static.py 静态检查、test_frontend.mjs 前端冒烟 17 项 calibrate.py 评分权重标定:留一验证 + 网格搜索 文档(docs/) version-compare.md 版本对比 review-2026-09-23.md 评审报告与整改回填 scoring-calibration.md 标定结果与效力边界 neo4j-setup.md 图库部署说明(含只读账号配置) 工程约定 .gitignore 排除 storage/ 运行期数据、.env 与日志 .gitattributes 统一 LF;本机 core.autocrlf=true,不固定会引入假差异
初始提交:智能水务泵站知识图谱系统 v2.1
把此前游离在版本控制之外的工程代码全部纳入 Git。
后端(app/)
前端(frontend/)
评测与自检(scripts/、tests/)
文档(docs/)
工程约定
基于 Neo4j 知识图谱 的多智能体水务泵站运维决策系统。图谱负责”知识与推理路径”, 大模型负责”语义研判与表达”,多智能体负责”分工与协作”。
一句话概括设计立场:图谱里存的不是数据表,是因果关系。 诊断不是查规则表,而是沿 测点 → 征兆 → 故障 → 成因 做反向图检索; 大模型不吃原始数据,吃的是图谱给出的证据链,只在证据之上做鉴别。
测点 → 征兆 → 故障 → 成因
# 1. 安装依赖 pip install -r requirements.txt # 2. 环境自检(缺什么、端口占没占,一次说清) python scripts/check_env.py # 3. 启动 python scripts/run.py
打开 http://127.0.0.1:8788/ 即是控制台。
两件事不用做:不用装 Neo4j(默认内存图后端,开箱即用), 不用配大模型 Key(未配置时以规则推理模式运行,功能完整)。
评审报告见 docs/review-2026-09-23.md, 与上一版的对比见 docs/version-compare.md。 下面只写改了什么、怎么证明改对了,以及哪些还没做。
docs/review-2026-09-23.md
docs/version-compare.md
scripts/selftest_ops.py
app/security/rbac.py
Depends(need(...))
app/external/
stale
available
scripts/selftest_external.py
faultCode
extra="forbid"
app/scoring.py
tests/test_diagnosis.py::test_scoring_params_have_single_source
CREDIT_PARTIAL=0.5
test_partial_credit_halves
test_veto_factor_steps_and_floor
scripts/calibrate.py
docs/scoring-calibration.md
selftest_ops.py
finish_reason
app/llm/client.py
asyncio.Event
app/agents/base.py
app/kg/repository.py
这一轮的价值不只在上表 —— scripts/calibrate.py 的告警级校验把 5 处误判顶到了台面上, 逐条修完后 8/8 条演示告警的 top-1 故障落在期望集合内(修前 3/8):
is_stopped()
另外补了一个原本定义了却从未生效的口径:故障自带的 triggers 从未参与排序。 水锤的成因证据全是时序曲线与操作记录,在线测点根本测不到,证据分天生只有 0.07, 而它最能区分的那句「瞬时压力峰值超过工作压力 1.5 倍」只出现在页面上。 现在触发条件成立会抬起置信度下限(不是加成),全不成立则轻微降权。
triggers
跑真实联网刷新、真重启进程才暴露出来的(假 fetcher 与不重启的测试都测不到), 三条都已修 + 补回归断言:
{"data":{"now":{"temperature":25.6,...}}}
data.temp
data.now.temp
data.now.temperature
code
status
available=true, stale=false
lastSuccessAt
available=false
stale=false
ageSeconds=8
ok
responded
in_graph=0
in_graph
/api/ops/cases
in_graph=true
/api/system/graph-stats
/api/graph/overview
frontend/app.js
tests/test_diagnosis.py
python tests/run_all.py
pytest -q
这是本工程最重要的架构决策。演示环境千差万别,不能把”跑不起来”归咎于环境。
/api/health
neo4jFallback
关键点:降级是显式声明的,不是静默的。 每个智能体在降级时都会写一条带 degraded 标记的消息进时间线,汇总答复里也会写”说明:……已切换为规则推理”。 宁可让运维人员看见系统退到了哪一步,也不假装一切正常。
degraded
资产层 Plant → Station → Zone / Pump / Valve → Sensor → Metric 回答:现场有什么、谁测什么、装在哪 诊断层 Metric → Standard / Alarm → Fault → Cause → Symptom 回答:为什么会这样、还可能是别的什么 处置层 Cause → Action → Spare / Crew / PRECEDES 回答:该怎么修、需要谁、要备什么、先做什么 经验层 Case(横跨三层) 回答:这台泵上次是怎么坏的、怎么修好的
规模:612 节点 / 1510 关系 / 16 类节点 / 25 类关系。
OBSERVED_BY
整套图谱推理的支点,只有一条关系:
(:Symptom) -[:OBSERVED_BY]-> (:Metric)
有了它,”某个测点超限了”才能反查到”这说明什么”:
// 振动烈度超限 → 承载它的征兆 → 指向哪些故障 MATCH (f:Fault)-[:HAS_SYMPTOM]->(s:Symptom)-[:OBSERVED_BY]->(m:Metric {code:'M-VIB'}) RETURN DISTINCT f.name // → 水泵汽蚀、联轴器对中不良、轴承磨损或失效、叶轮堵塞或缠绕、 // 泵体气缚、叶轮水力不平衡、管道水锤
没有硬编码的故障映射表。 新增一个故障,只要在知识库里声明它的征兆, 诊断链路自动生效。
┌──────────────────┐ 运维人员提问 ──▶ │ 总控调度智能体 │ 意图识别 → 动态路由 └────────┬─────────┘ │ 黑板(共享工作区) ┌───────────┬───────┼───────┬───────────┐ ▼ ▼ ▼ ▼ ▼ 故障诊断 水质合规 处置工单 巡检计划 图谱问答 └───────────┴───────┼───────┴───────────┘ ▼ 汇总答复(面向运维人员)
为什么不用链式管道(A→B→C):泵站经常出现”水质与设备同时异常”, 两个方向互相独立,串行跑纯属浪费。黑板模型让互不依赖的智能体并行执行, 每一步中间结论都留在板上,可回溯、可解释、可审计。
6 个智能体:
路由里的一条硬规则:诊断与工单必须有目标设备才调度。 没有目标就调度它们,只会在时间线上留下两条 no_input / need_diagnosis 的空跑记录,反而让运维人员看不清重点。 系统会自动从提问文本里识别设备编号、泵站编号或站名来补目标; 识别不到就不调度,并说明原因。
no_input
need_diagnosis
纯原生 ES Module,零外部依赖(无 CDN、无构建步骤、离线可用)。
力导向图是自己用 Canvas 2D 写的(斥力 + 弹簧 + 阻尼),约 120 行, 不引第三方图库 —— 免得为了一个可视化把整个前端拖进构建工具链。
water-station-kg/ ├── app/ │ ├── config.py Pydantic Settings,含 llm_enabled / use_neo4j 开关 │ ├── main.py FastAPI 入口,启动时自动建图 + 校验引用一致性 │ ├── data/ 领域数据(本体知识的唯一来源) │ │ ├── standards.py GB 5749-2022 限值、设备判据、57 项指标 │ │ ├── knowledge_base.py 44 征兆 / 18 故障 / 105 处置 / 20 备件 / 5 班组 │ │ ├── assets.py 2 水厂 / 6 泵站 / 18 机组 / 48 阀门 / 160 传感器 │ │ └── runtime.py 8 告警 + 11 历史案例(遥测按故障证据反向构造) │ ├── kg/ │ │ ├── schema.py 本体定义、约束/索引 Cypher、配色 │ │ ├── builder.py 领域数据 → 图谱(612 节点 / 1510 关系) │ │ └── repository.py GraphRepository 协议 + InMemoryGraph + Neo4jGraph │ ├── llm/ │ │ ├── client.py DeepSeek(OpenAI 兼容)异步客户端,JSON 容错 │ │ └── prompts.py 各智能体系统提示词(含置信度口径硬规则) │ ├── agents/ │ │ ├── base.py 黑板 + 消息总线 + 智能体基类 + 编排器 │ │ ├── kg_tools.py 图谱工具集(全部推理原语都在这) │ │ ├── orchestrator_agent.py 总控:路由 + 目标识别 + 汇总 │ │ └── diagnostic.py / water_quality.py / dispatch.py / inspection.py / knowledge.py │ └── api/ graph_routes / domain_routes / agent_routes / system_routes ├── frontend/ index.html + styles.css + app.js(零依赖) ├── scripts/ │ ├── run.py 一键启动(含前置自检) │ ├── check_env.py 环境自检 │ ├── selftest.py 后端端到端自检,8 阶段 36 项 │ ├── test_frontend.mjs 前端逻辑冒烟测试,17 项(零依赖) │ └── check_refs.py 知识库引用缺口检查 ├── docs/neo4j-setup.md Neo4j 5.x Windows 安装与切换指南 ├── requirements.txt └── .env.example
本工程对”数字从哪来”这件事有明确交代,因为含糊的百分比在答辩时最容易被追问。
national
empirical
vendor
界面上用不同颜色标签区分,「知识库 → 标准限值」页逐条可见。
所有出现在界面上的置信度,口径统一为:
置信度 = 0.35 × 前验经验权重 + 0.45 × 证据命中率 + 0.20 × 证据覆盖度 多测点佐证加成:每多一个异常测点 +12%
并在结论里写明这句话:
置信度为经验置信度,非统计概率,用于同设备内不同成因的相对排序。
巡检风险分同理,界面直接展示权重公式:
风险分 = 100 × (0.40×健康度缺口 + 0.30×告警强度 + 0.20×故障频次 + 0.10×运行时长归一化)
本系统没有任何”统计概率”意义上的准确率数字。 数据是按 GB 5749-2022 限值 构造的模拟数据,没有统计样本支撑,就不会声称统计显著性。
知识库有两类问题都不会让程序报错,却会让结论悄悄失真,因此各设一道检查:
① 跨实体引用一致性(1051 项) 处置引用备件、故障引用征兆、传感器引用指标、案例引用处置……逐项解析。 引用了不存在的备件编码、征兆编码写错一个字母,诊断时某条证据就会匹配不上、 工单里某个备件就查不到库存。
② 主键唯一性(542 项) 同一编码被声明两遍时,建图会按主键 MERGE,重复项被静默去重 —— 图谱节点数比数据源少一个,没有任何报错。
MERGE
第二道闸门是补上的,因为它真实发生过:M-ODOR(嗅味等级)在 standards.py 里被完整复制了两遍,58 条指标落成 57 个图谱节点。 发现方式是手工比对「本体接口报的指标数」与「图谱里的 Metric 节点数」, 两者差 1。现在这项检查会在启动日志和系统状态页同时暴露。
M-ODOR
standards.py
两处校验入口:
app/main.py
GET /api/system/integrity
python scripts/selftest.py # 全部检查 python scripts/selftest.py --llm # 额外实际调用大模型验证 python scripts/selftest.py --out report.txt
覆盖:图谱构建 → 引用一致性 → 检索原语 → 多智能体协作推理 → 水质合规 → 巡检编排 → 图谱问答 → 大模型降级路径。
通过 36/36 全部通过 ✓
python scripts/check_static.py
字节码编译 + pyflakes + 实际导入 app.main 三层。 之所以固化成一个脚本:本项目在编辑过程中多次出现”改了一半”的情况 (比如函数体没落盘、import 漏掉),这种问题只会在运行时才炸,且只在特定分支触发。 现在每次改完先跑它,未定义名 / 重复定义一律视为致命。
app.main
import
python tests/run_all.py # 零依赖跑法(本机装不上 pytest) pytest -q # 装了 pytest 时等价
锁定的是几处曾经改错的地方:判据优先级、停机状态、否定词与趋势动词的冲突、 证据自带阈值与派生量的区分、触发条件的地板/降权、评分参数只有一个来源、 外部数据的 stale / available 纪律、外部嵌套字段解析与”有响应≠有数据”口径、 内存图后端的案例重放必须全量。另有一份防回归断言: test_scoring_params_have_single_source 保证 kg_tools.py 里不再出现硬编码权重。
test_scoring_params_have_single_source
kg_tools.py
python scripts/run.py # 先启动服务 python scripts/selftest_ops.py --base http://127.0.0.1:8788
刻意去撞四道闸门:复测不给数据想闭环必须被拒、复测仍越限必须挂起、 只读角色闭环必须 403、运维角色强制闭环必须 403。
这个脚本可以连着跑很多遍(连跑两遍都是 29/29,第二遍会自动换一个告警、 换一套诊断结论)。要做到这点有两个坑,都已在脚本里处理:
python scripts/selftest_external.py # 纯离线 32 项 python scripts/selftest_external.py --base http://127.0.0.1:8788 # 追加接口层 5 项
用假 fetcher 替换真实网络调用,断网可跑。要证的不是”能取到数据”, 而是”取不到的时候系统怎么说话”:从未成功过 → 如实标不可用; 失败但有过成功 → 保留旧值并标 stale 且给出过期秒数;坏缓存 → 降级为空缓存不抛异常; 有响应但字段名变了没解析出值 → 走失败账,不许自称”刚成功过”。 其中 [4b] 一节专为”嵌套 JSON 路径漏字段”这条实测 bug 留了回归断言。
[4b]
python scripts/calibrate.py # 全网格 4620 组,并重写标定文档 python scripts/calibrate.py --quick # 粗网格,用于快速回归
产出 docs/scoring-calibration.md: 当前取值在候选里排第几、前五位参数、逐案例明细、告警级 top-1 校验。 文档里专设”效力边界”一节,写清样本只有 7 条可用、结论只能用于排除明显更差的取值。
python scripts/run.py # 先启动服务(另开一个窗口) node scripts/test_frontend.mjs
把真实的前端 app.js 跑起来打真实接口,逐视图验证渲染、弹窗与流式协作:
app.js
通过 视图 overview 1170 系统规模 图谱与资产的实时统计 图谱节点 614 ... 通过 流式协作 30 时间线 30 条;结论 4143 字 检查项: 17 |通过: 17 问题数: 0
(图谱规模会随闭环产生的运行期案例增长:种子 612 节点,跑过两轮闭环后是 614。)
没有引 Playwright:前端是零依赖的原生 ES Module, 用 Node 自带 fetch + 一个轻量 DOM 桩就能把 「模块加载 → 接口调用 → 模板渲染 → 事件绑定」整条链路真实跑一遍, 不必为冒烟测试拖进浏览器驱动和 300MB 的 Chromium。 (代价是验不到 CSS 表现与 Canvas 实际绘制,这两项需人工过目。)
?probeLlm=true
/api/graph/subgraph
/api/graph/node/{label}/{id}
/api/graph/cypher
/api/stations
/api/pumps
/api/alarms
/api/cases
/api/alarms/{id}/context
/api/quality/{station_id}
/api/knowledge/faults/{code}
/api/agent/chat
/api/agent/stream
/api/agent/{name}/invoke
/api/system/integrity
/api/ops/overview
/roles
/me
/api/ops/alarms
/alarms/{id}/ack
/alarms/{id}/close
/api/ops/orders
/orders/batch
/orders/{id}
/api/ops/orders/{id}/steps/{seq}
/verify
/close
/audit
scope=business|http|all
/api/ops/autopilot
/api/external
/api/external/refresh
realtime:refresh
v2.1 新增的业务写端点全部要求对应能力,能力名与接口一一对应; 越权统一返回 403,并在审计里留下失败记录。
完整文档:启动后访问 http://127.0.0.1:8788/docs
大模型走 DeepSeek(OpenAI 兼容协议,国内直连,无需代理)。
copy .env.example .env
编辑 .env:
.env
LLM_API_KEY=sk-你的key LLM_BASE_URL=https://api.deepseek.com/v1 LLM_MODEL=deepseek-chat
重启服务即生效。顶栏徽标会从「规则推理模式」变成「大模型 deepseek-chat」。
接入后有什么不同:智能体在规则层给出的候选与证据之上做语义研判 —— 诊断智能体会给出鉴别诊断的排除理由,汇总智能体会把结论组织成人话, 知识问答会走 Text2Cypher。规则层依旧负责”证据在哪、数值多少”, 模型不凭空生成结论。
见 **docs/neo4j-setup.md**。
要点:Neo4j 5.x 需要 JDK 17(ZIP 包不自带 Java),本机现有 Java 8 不够用。 装好后把 .env 的 GRAPH_BACKEND 改成 neo4j 重启即可 —— 约束与索引由系统自动创建,不需要手工执行任何 Cypher。
GRAPH_BACKEND
neo4j
本系统使用模拟数据。 泵站、机组、传感器、告警、案例均为按真实工程规范 构造的示例数据,用于验证系统架构与推理链路,不代表任何真实站点的运行状态。
标准限值部分(GB 5749-2022、GB/T 6075.3-2011 等)引用真实国家标准条文; 设备与工艺参数按行业常见配置设定。所有模拟数据在界面与文档中均不声称 来自真实运行统计。
智慧水务泵站运维平台:知识图谱 + 多智能体,支持水泵异常诊断、水质告警分析与处置闭环。Python + FastAPI,DeepSeek 对话交互,诊断可解释。
版权所有:中国计算机学会技术支持:开源发展技术委员会 京ICP备13000930号-9 京公网安备 11010802047560号
智能水务泵站 · 知识图谱多智能体系统
基于 Neo4j 知识图谱 的多智能体水务泵站运维决策系统。图谱负责”知识与推理路径”, 大模型负责”语义研判与表达”,多智能体负责”分工与协作”。
快速开始
打开 http://127.0.0.1:8788/ 即是控制台。
两件事不用做:不用装 Neo4j(默认内存图后端,开箱即用), 不用配大模型 Key(未配置时以规则推理模式运行,功能完整)。
本次优化(v2.1):对照评审报告逐条修
评审报告见
docs/review-2026-09-23.md, 与上一版的对比见docs/version-compare.md。 下面只写改了什么、怎么证明改对了,以及哪些还没做。已补回的退步项
scripts/selftest_ops.py(29 项,含”复测不通过必须拒绝闭环”)app/security/rbac.py,三角色白名单 + 接口级Depends(need(...))+ 中文名百分号编码app/external/:气象/径流/国控水质三源 + 落盘缓存 +stale/available降级纪律scripts/selftest_external.py(37 项,含接口层;纯离线跑 32 项,假 fetcher,断网可跑)已修正的不完美项
faultCode被静默丢弃extra="forbid",传错字段名直接 422app/scoring.py,并把每个数字的性质与局限写进注释tests/test_diagnosis.py::test_scoring_params_have_single_sourceCREDIT_PARTIAL=0.5)test_partial_credit_halvestest_veto_factor_steps_and_floorscripts/calibrate.py用 11 条历史案例做留一验证 + 4620 组网格搜索,结果记录在docs/scoring-calibration.mdselftest_ops.py含”新案例能被相似案例检索命中”;重启实测 612→614 节点、Case 11→13finish_reasonapp/llm/client.pyasyncio.Event事件驱动 + 客户端断连取消任务app/agents/base.pyapp/kg/repository.py标定过程中额外发现并修掉的诊断错误
这一轮的价值不只在上表 ——
scripts/calibrate.py的告警级校验把 5 处误判顶到了台面上, 逐条修完后 8/8 条演示告警的 top-1 故障落在期望集合内(修前 3/8):is_stopped()识别停机/停运并跳过该测点的异常判定。另外补了一个原本定义了却从未生效的口径:故障自带的
triggers从未参与排序。 水锤的成因证据全是时序曲线与操作记录,在线测点根本测不到,证据分天生只有 0.07, 而它最能区分的那句「瞬时压力峰值超过工作压力 1.5 倍」只出现在页面上。 现在触发条件成立会抬起置信度下限(不是加成),全不成立则轻微降权。收尾时又抓到三个真 bug
跑真实联网刷新、真重启进程才暴露出来的(假 fetcher 与不重启的测试都测不到), 三条都已修 + 补回归断言:
{"data":{"now":{"temperature":25.6,...}}},而解析器只试过data.temp/data.now.temp,没试data.now.temperature**,于是明明拿到了 25.6 ℃ 却报”未识别出数值”。 现在每个源显式声明取值路径 + 候选键名递归兜底 + 文本页正则, 且只认明确列出的键名(不会把code/status当测量值抓出来)。 修后实测:武汉 25.6 ℃ 判为available=true, stale=false。lastSuccessAt, 于是出现available=false(没值)却stale=false、ageSeconds=8(自称 8 秒前刚成功)、 失败计数还是 0 的怪状态,刷新文案也写成”成功 2 个”却随即弹出”均不可用”。 现在ok只表示解析出了可用数值,另有responded表示收到了响应; 有响应但解析不出→走失败账(不推进成功时刻、累计失败次数、保留旧值)。 刷新说明改为”取到可用数值 X 个,有响应但未解析出数值 Y 个,请求失败 Z 个”。in_graph=0的案例 —— 而in_graph是写在 SQLite 里的标志,重启后仍是 1。 结果图谱退回种子规模(612 节点 / Case 11),/api/ops/cases却显示in_graph=true。 现按后端是否易失决定:内存图全量重放(upsert 幂等),Neo4j 只补未入图的。 实测重启后 614 节点 / 1542 关系 / Case 13,且/api/system/graph-stats的 boot 段 与/api/graph/overview数字一致(原先两处各显示一个数)。本次仍未做(不含糊)
frontend/app.js仍是单文件约 1900 行tests/test_diagnosis.py,并提供零依赖跑法python tests/run_all.py(当前 37/37 通过);装了 pytest 用pytest -q即可降级设计:为什么没装 Neo4j、没配 Key 也能跑
这是本工程最重要的架构决策。演示环境千差万别,不能把”跑不起来”归咎于环境。
/api/health的neo4jFallback字段说明原因关键点:降级是显式声明的,不是静默的。 每个智能体在降级时都会写一条带
degraded标记的消息进时间线,汇总答复里也会写”说明:……已切换为规则推理”。 宁可让运维人员看见系统退到了哪一步,也不假装一切正常。系统架构
四层本体
规模:612 节点 / 1510 关系 / 16 类节点 / 25 类关系。
推理桥梁:
OBSERVED_BY整套图谱推理的支点,只有一条关系:
有了它,”某个测点超限了”才能反查到”这说明什么”:
没有硬编码的故障映射表。 新增一个故障,只要在知识库里声明它的征兆, 诊断链路自动生效。
多智能体协作:黑板模型
为什么不用链式管道(A→B→C):泵站经常出现”水质与设备同时异常”, 两个方向互相独立,串行跑纯属浪费。黑板模型让互不依赖的智能体并行执行, 每一步中间结论都留在板上,可回溯、可解释、可审计。
6 个智能体:
前端控制台
纯原生 ES Module,零外部依赖(无 CDN、无构建步骤、离线可用)。
力导向图是自己用 Canvas 2D 写的(斥力 + 弹簧 + 阻尼),约 120 行, 不引第三方图库 —— 免得为了一个可视化把整个前端拖进构建工具链。
目录结构
数据可信性
本工程对”数字从哪来”这件事有明确交代,因为含糊的百分比在答辩时最容易被追问。
1. 标准限值逐条标注来源性质
nationalempiricalvendor界面上用不同颜色标签区分,「知识库 → 标准限值」页逐条可见。
2. 置信度是经验权重,不是统计概率
所有出现在界面上的置信度,口径统一为:
并在结论里写明这句话:
巡检风险分同理,界面直接展示权重公式:
本系统没有任何”统计概率”意义上的准确率数字。 数据是按 GB 5749-2022 限值 构造的模拟数据,没有统计样本支撑,就不会声称统计显著性。
3. 知识库校验双闸门
知识库有两类问题都不会让程序报错,却会让结论悄悄失真,因此各设一道检查:
① 跨实体引用一致性(1051 项) 处置引用备件、故障引用征兆、传感器引用指标、案例引用处置……逐项解析。 引用了不存在的备件编码、征兆编码写错一个字母,诊断时某条证据就会匹配不上、 工单里某个备件就查不到库存。
② 主键唯一性(542 项) 同一编码被声明两遍时,建图会按主键
MERGE,重复项被静默去重 —— 图谱节点数比数据源少一个,没有任何报错。两处校验入口:
app/main.py的 lifespan 自动校验,结果写进日志GET /api/system/integrity,前端「系统状态」页可见端到端自检
后端:8 阶段 36 项
覆盖:图谱构建 → 引用一致性 → 检索原语 → 多智能体协作推理 → 水质合规 → 巡检编排 → 图谱问答 → 大模型降级路径。
静态检查:未定义名 / 重复定义
字节码编译 + pyflakes + 实际导入
app.main三层。 之所以固化成一个脚本:本项目在编辑过程中多次出现”改了一半”的情况 (比如函数体没落盘、import漏掉),这种问题只会在运行时才炸,且只在特定分支触发。 现在每次改完先跑它,未定义名 / 重复定义一律视为致命。单元测试:37 项
锁定的是几处曾经改错的地方:判据优先级、停机状态、否定词与趋势动词的冲突、 证据自带阈值与派生量的区分、触发条件的地板/降权、评分参数只有一个来源、 外部数据的
stale/available纪律、外部嵌套字段解析与”有响应≠有数据”口径、 内存图后端的案例重放必须全量。另有一份防回归断言:test_scoring_params_have_single_source保证kg_tools.py里不再出现硬编码权重。处置闭环自检:29 项
刻意去撞四道闸门:复测不给数据想闭环必须被拒、复测仍越限必须挂起、 只读角色闭环必须 403、运维角色强制闭环必须 403。
这个脚本可以连着跑很多遍(连跑两遍都是 29/29,第二遍会自动换一个告警、 换一套诊断结论)。要做到这点有两个坑,都已在脚本里处理:
外部数据降级自检:37 项(纯离线 32 项)
用假 fetcher 替换真实网络调用,断网可跑。要证的不是”能取到数据”, 而是”取不到的时候系统怎么说话”:从未成功过 → 如实标不可用; 失败但有过成功 → 保留旧值并标
stale且给出过期秒数;坏缓存 → 降级为空缓存不抛异常; 有响应但字段名变了没解析出值 → 走失败账,不许自称”刚成功过”。 其中[4b]一节专为”嵌套 JSON 路径漏字段”这条实测 bug 留了回归断言。评分权重标定
产出
docs/scoring-calibration.md: 当前取值在候选里排第几、前五位参数、逐案例明细、告警级 top-1 校验。 文档里专设”效力边界”一节,写清样本只有 7 条可用、结论只能用于排除明显更差的取值。前端:17 项
把真实的前端
app.js跑起来打真实接口,逐视图验证渲染、弹窗与流式协作:(图谱规模会随闭环产生的运行期案例增长:种子 612 节点,跑过两轮闭环后是 614。)
没有引 Playwright:前端是零依赖的原生 ES Module, 用 Node 自带 fetch + 一个轻量 DOM 桩就能把 「模块加载 → 接口调用 → 模板渲染 → 事件绑定」整条链路真实跑一遍, 不必为冒烟测试拖进浏览器驱动和 300MB 的 Chromium。 (代价是验不到 CSS 表现与 Canvas 实际绘制,这两项需人工过目。)
API 速览
/api/health?probeLlm=true实际探测模型端点)/api/graph/overview/api/graph/subgraph/api/graph/node/{label}/{id}/api/graph/cypher/api/stations/api/pumps/api/alarms/api/cases/api/alarms/{id}/context/api/quality/{station_id}/api/knowledge/faults/{code}/api/agent/chat/api/agent/stream/api/agent/{name}/invoke/api/system/integrity/api/ops/overview/roles/me/api/ops/alarms/alarms/{id}/ack/alarms/{id}/close/api/ops/orders/orders/batch/orders/{id}/api/ops/orders/{id}/steps/{seq}/verify/close/api/ops/cases/auditscope=business|http|all)/api/ops/autopilot/api/external/api/external/refreshrealtime:refresh)完整文档:启动后访问 http://127.0.0.1:8788/docs
接入大模型(可选)
大模型走 DeepSeek(OpenAI 兼容协议,国内直连,无需代理)。
编辑
.env:重启服务即生效。顶栏徽标会从「规则推理模式」变成「大模型 deepseek-chat」。
接入后有什么不同:智能体在规则层给出的候选与证据之上做语义研判 —— 诊断智能体会给出鉴别诊断的排除理由,汇总智能体会把结论组织成人话, 知识问答会走 Text2Cypher。规则层依旧负责”证据在哪、数值多少”, 模型不凭空生成结论。
持久化到 Neo4j(可选)
见 **docs/neo4j-setup.md**。
要点:Neo4j 5.x 需要 JDK 17(ZIP 包不自带 Java),本机现有 Java 8 不够用。 装好后把
.env的GRAPH_BACKEND改成neo4j重启即可 —— 约束与索引由系统自动创建,不需要手工执行任何 Cypher。数据说明
本系统使用模拟数据。 泵站、机组、传感器、告警、案例均为按真实工程规范 构造的示例数据,用于验证系统架构与推理链路,不代表任何真实站点的运行状态。
标准限值部分(GB 5749-2022、GB/T 6075.3-2011 等)引用真实国家标准条文; 设备与工艺参数按行业常见配置设定。所有模拟数据在界面与文档中均不声称 来自真实运行统计。