aps-agent/docs/architecture/database.md

5.3 KiB
Raw Permalink Blame History

数据库层(业务数据 + 共享审批存储)

M-A 落地:主数据不再写死在代码里,康尼只是数据库里的一个「项目」。 代码:server/db/(models / database / sync)、server/state/packs.py、server/importers/。

定位

  • SQLite(server/data/master.db,APS_DB_PATH 可覆盖):主数据的持久源——项目、 主数据宽表(资源/物料/BOM/工序/路线/订单/柔性表)、行业工艺路线模板。
  • world.json:排产运行态(版本/工单/冲突/检查点/审计)仍留在 WorldStore; 引擎零改动,读的还是 world dict。
  • knowledge.json + embeddings.json:RAG 知识库(见 rag.md)。
  • 共享审批库(显式启用):APS_APPROVAL_BACKEND=database 时复用 APS_DATABASE_URL 指向的 MySQL;审批请求、一次性凭据和决策事件由数据库事务协调, 不再依赖单机文件锁。SQLite 只允许测试显式开启,不能作为跨主机共享部署。

表结构(server/db/models.py,SQLAlchemy 2.0)

表 作用
projects 项目/租户(demo、kangni…),is_active 标记当前投影项目
master_records 通用宽表:(project_id, table_key, seq) + code/name 索引列 + payload JSON 全量;table_key 覆盖 factories/materials/flexOrders/routingSteps/...(清单见 sync.MASTER_LIST_KEYS)
project_settings dict 型主数据(factoryCalendar/changeoverMatrix/...,见 MASTER_DICT_KEYS)
routing_templates / routing_template_steps 行业工艺路线模板库(M-E,含推荐工时区间/设备类型/知识资产关联)
aps_approval_requests 审批请求当前状态;revision 条件更新保证批准/驳回只有一个终态
aps_approval_grants P3 一次性执行凭据;只持久化 token SHA-256,按状态与 revision 全局一次消费
aps_approval_events append-only 审批事件投影;应用路径只追加、不更新历史事件

宽表设计的取舍:world 的主数据形态仍在快速演进,payload 存整行 JSON、 code/name/seq 提列做索引,避免每加一个字段就迁 schema;引擎兼容性优先。

共享审批存储

  • 默认 APS_APPROVAL_BACKEND=file,继续使用 APS_APPROVAL_PATH 或 APS_HOME/data/approvals.json,适合桌面和单机部署。
  • APS_APPROVAL_BACKEND=database 必须显式设置 MySQL APS_DATABASE_URL;同时设置 APS_APPROVAL_PATH、缺少 URL、使用非 MySQL 方言或连接失败均直接启动失败,不回退文件。
  • APS_APPROVAL_DATABASE_ALLOW_SQLITE=1 仅供测试本地合同和 CAS 竞态;SQLite 不提供本项目 所要求的跨主机共享部署保证。
  • 最终批准以一个事务完成请求 CAS、grant 插入和 APPROVED 事件追加;grant 消费用 ACTIVE + revision + expiresAt 条件更新保证只有一个连接成功。
  • 当前 get_engine() 会执行 Base.metadata.create_all() 供本地兼容。共享环境仍必须先执行 alembic upgrade head(revision 20260731_03)再部署应用;create_all() 不是生产迁移机制, 不记录 Alembic revision,也不能替代受控迁移。

从文件后端切换时必须先停止所有 Gateway 写入,等待或迁移活动审批/执行凭据,再一次性切换 全部节点;禁止 file/database 混跑和双写。本切片未提供自动 JSON 导入器。

投影与同步(server/db/sync.py + state/store.py)

启动: WorldStore.__init__ → _sync_with_db()
  DB 有主数据  → db_to_world() 覆盖 world 主数据键(DB 为准)
  DB 空 world 有 → world_to_db() 一次性迁移(保留现场已导入数据)
写入: store.save() → _sync_master_to_db()(master_fingerprint 脏检查,变了才写)
  • set_active_project(code) 切换活跃项目;db_to_world 只投影该项目数据。
  • APS_DB_DISABLED=1 完全绕过 DB(测试/降级);DB 异常不阻断 world 主流程。

数据包(server/state/packs.py)

  • export_pack(world, name) → server/data/packs/<name>.json(记录 baseDate)。
  • load_pack(name) 重放时把所有日期按 baseDate→今天 平移(演示数据永不过期)。
  • seed_world():APS_SEED_PACK 指定则重放数据包,否则 build_demo_world() 程序化种子。
  • scripts/generate_demo_pack.py 重新生成 demo.json。

导入器配置化(server/importers/)

  • excel_importer.import_site_excel(profile_name, path):按 profile 解析现场 Excel → 入库 → 投影。
  • profiles/kangni.json:工厂名、区域、工时推断规则(部装 45/装配 30…)、换型/接刀默认、 班组、日历——原 kangni_intake.py 的硬编码全部搬进配置;解析函数吃 profile 参数。
  • 新客户 = 新增一个 profile JSON,不改代码。

API

端点 用途
GET /api/projects 项目清单 + 活跃项目(P0)
GET /api/packs 可重放数据包清单(P0)

测试

tests/golden/test_db_projection.py(入库→投影→PoolEngine 可排、多项目隔离、数据包日期平移)、 tests/golden/test_approval_database_store.py(SQLite 本地数据库合同、CAS 竞态、token 摘要和配置失败关闭;不代表 MySQL 实机验收)、 test_excel_importer.py(profile 等价性 + 覆写生效 + stdTimeSource 标记)。