# 闭环：从 ERP 到持续变好的应用

这一页以一个车间的生产进度看板为例（示例公司 Demo Company），把 Semantic（数据平台）的各部分和自进化串成一条线：**数据流**（ERP 只读推上来）→ **Ontology**（对象、Action）→ **数据源**（流接到哪个类型，写在定义里）→ **应用**（读对象集、实时订阅、用 Action 改）→ **访问**（谁能看、谁能改、分享）→ **留痕**（每次修改、应用日志）→ **自提升**（下一个版本）。车缝产量、库存、订单流程这类看板是同一套做法。

```
SAP / MES（只读）
   │  发布端：aidc semantic streams pipe + 适配器（客户箱上的 systemd 服务，不是 cron）
   ▼
Semantic ── 数据流 production-progress
   │         └─ Object Type 定义里的 datasources：哪一列对哪个属性
   ▼
Ontology ── 工单线体 / 线体、链接、Action「登记异常」「解决异常」「维护线体」
   ▼
Nexus 应用 · 智能体 ── 访问：成员可改、公开链接只读 ── 留痕：每次修改、打开、反馈、版本
   ▲                                                                     │
   └──────────────── 自进化 SDK：证据 → 提案 → 新语义版本 / 新应用版本 ◀──┘
```

## 1. 数据流：数据源只读地推上来

发布端装在客户箱上，读 SAP 的报工确认（AFRU）与生产进度报表，有变化才推一批到数据流 `production-progress`（见[数据流与数据源](connect.md)）。它只有一把只能往这条流发布的 Key，**永远不会写 SAP**。

## 2. Ontology：先定义「这是什么」

```bash
aidc semantic define ontology/          # production.issue_status（枚举）、production.line、production.order_line、三个 Action
aidc semantic publish --notes "生产进度首版"
```

`production.order_line` 的属性分两类：工单、线体、计划、累计合格、实际 JPH……来自 SAP（只读）；异常状态、异常说明、登记人、处理说明……只在语义层维护（`writeback`，Palantir 的 editOnly）。Action 定义「登记异常」要哪些参数、改哪些属性、谁能做（见 [Semantic](semantic.md)）。

## 3. 数据源：流接到哪个类型，写在定义里

数据流的哪一列对哪个属性，是 Object Type 定义的一部分（照 Palantir 的 backing datasource）。`production.order_line` 的定义里写：

```json
"datasources": [
  { "type": "stream", "stream": "production-progress", "mode": "mirror",
    "propertyMapping": { "order_no": "order", "line_code": "line", "material": "material", "product": "product", "shift": "shift", "plan": "plan",
                         "ok": "ok", "gap": "gap", "jph_actual": "jph_actual", "jph_std": "jph_std", "last_op": "last_op", "day": "day" } }
]
```

线体由工单行汇成：`production.line` 接同一条流，只映射 `line_code ← line`，`mode` 用 `upsert`。不改文件也行，命令改的同样是定义：

```bash
aidc semantic datasource set production.line --stream production-progress --map line_code=line --mode upsert
aidc semantic datasource list                                      # 每个类型的数据源与同步进度
aidc semantic objects production.order_line --order-by gap:desc --page-size 5
```

定义落了之后，平台用流的当前全量同步一次；从这一刻起，SAP 每一次变化都进 Semantic：来自源头的属性更新，人改过的值不动。应用和智能体不再碰数据流，更不碰 SAP。智能体要改数据源，和改别的定义一样：在分支上改、开提案，人审核合并。

## 4. 应用：读对象集、实时订阅、用 Action 改

```js
import { semantic } from "/developer/sdk/v1/aidc.js";

const client = semantic.ontology();
const lines = client.objects("production.order_line");
render((await lines.fetchPage({ $orderBy: { gap: "desc" } })).data);
lines.subscribe({ onChange: ({ object, state }) => patch(object, state), onOutOfDate: reload });   // 实时
const me = await semantic.admin.getCurrentUser();                                                   // 我能做什么
await client.action("production.flag_issue").applyAction({ __object: row.__primaryKey, issue: "缺料", severity: "异常" });
semantic.observability.feedback({ topic: "看板", message: "希望按班次筛选" });
```

清单登记用到的 SDK 与语义资源（部署时会核对 import，用了没登记的直接拒收）：

```json
{
  "sdk": ["semantic"],
  "semantic": { "types": ["production.order_line", "production.line"], "actions": ["production.flag_issue", "production.resolve_issue", "production.update_line"] }
}
```

```bash
aidc app deploy app/        # → Developer 预览（test），版本号 = 清单 version
aidc app publish production-live --notes "语义层 + 异常登记"   # 测过的版本 → Nexus
aidc app registry           # 登记表：这个应用用了哪些 SDK、读写哪些类型 / Action
```

## 5. 访问：给谁用

- 本公司成员打开 `/nexus/cell-demo/apps/production-live` 就能看、能执行「登记异常」（Action 的 roles 含 member）。
- 班组长在别的公司账号下？`aidc share production-live --user 他的邮箱 --role editor --days 30`。
- 给客户或领导一个只读大屏链接：`aidc share production-live --public`（真实数据，发之前确认；随时 `aidc share revoke`）。
- 类型、Ontology 本身谁能看：Private / Group / Public / Open to Internet，见[访问与账号](auth.md)。

## 6. 留痕：用得怎么样

```bash
aidc semantic observability summary production-live --days 7
aidc semantic edits-history production.order_line --pk <主键>
```

打开几次、谁在用、登记了多少次异常、哪些反馈还没处理、每个版本号的状态（已上线 / 已被替换…）、语义版本——都在第一条里。第二条看一个对象的每次修改（谁、何时、改前改后）；开了 `actionLog` 的 Action 每次提交还会写一个 Action Log 对象。

## 7. 自进化：下一个版本

```bash
aidc evolve suggest production-live --file 生产日报口径.md --conversation 班组长群.txt
aidc evolve proposals --status open
aidc evolve accept <id> && aidc evolve apply <id>      # 语义类：改说明 / 同义词 / 加属性 → 自动发 v2
```

语义类提案由开发者直接应用；智能体作者要在分支上改、开 Ontology 提案，由人审核合并。应用类提案（「加按班次筛选」）由开发者或智能体改代码，`aidc app deploy` → 测试 → `aidc app publish`，最后 `aidc evolve apply <id> --version 1.2.0`；提案关联的反馈自动标记为已处理，反馈人下次打开就能看到改进。

## 8. 自动化：把各部门的应用串起来

每个部门的应用发布后，就是公司能力目录里的一组能力（清单 `exports` 显式导出的，和从登记的类型 / Action 自动提取的）。跨部门的事——比如报价要用营业的询价、设计的 BOM、采购的行情、制造的工艺、财务的核价口径——写成一个工作流应用：

```bash
aidc semantic automate capabilities                        # 看有哪些能力可以用
aidc app deploy quote-workflow --dry-run                   # 部署前对照能力目录校验
aidc semantic automate run quote-workflow --param rfq_no=RFQ-2609-002 --preview
```

照 Palantir Automate：`trigger.change` 是条件（对象新建 / 修改时，登记询价 → 自动出报价草稿），步骤是效果；画布上看得到每一步用了谁的能力、产出了什么。它写回的结果照样进 Semantic、有留痕、进应用日志——闭环里多了一条「部门之间」的线。见[自动化](workflow.md)。

## 数据放在哪里

- Semantic 的对象、变化记录、日志、分享、提案都在 AIDC 中枢（Supabase Postgres），按公司隔离；只存看板与协作需要的业务字段，身份证、银行卡、密码之类的字段在定义层就被拒收。
- ERP / MES 只被发布端读；发布 Key 只能往一条流发。
- 还是某个类型数据源的流删不掉，先从定义里去掉数据源（同步就停，对象保留）；删掉对象类型的定义会归档它（对象保留，直到开发者清理）。

## 7. 不写 cron：Loop 从 Semantic 读、由 Automate 触发（标准写法）

客户箱上的定时任务（每 5 分钟查一遍 ERP、和快照比、有新行就发钉钉）换成 Semantic 的三件东西：**TableImport**（ERP 只被读一次）、**Object Type**（数据的业务名字）、**Automation**（数据一变 / 到点就做事）。照 Palantir 的 Data Connection 与 Automate，名字不改；设计见 `docs/semantic-data-connection.md`。

```
ERP（只对客户箱开白名单）──▶ agent（装在客户箱上，不常驻：Semantic 要数据时经 SSM 叫醒一次，只回传变了的行）
                              ──▶ Object Type（Semantic）──▶ Automation（Objects added / modified / removed · 到点）──▶ 钉钉 / 企业微信 · Loop 脚本
```

**① 连接与 TableImport（一次，开发者）**：Connection 只写主机、端口、库名和箱上的凭证名；口令留在箱上。查询只能是一条 SELECT，带窗口（例：近 45 天），只选 Loop 用得到的列。

```bash
aidc semantic connectivity connection --ontology cell-demo --file stock-connection.json
aidc semantic connectivity import <connectionRid> --file inbound-lines.json      # SNAPSHOT 窗口；target = Object Type 与列 → 属性
aidc semantic connectivity execute <connectionRid> <tableImportRid> --wait       # 第一次全量，之后只传变化
```

**② 读：自动连接**。读由 TableImport 供数的类型时，比 `freshness.maxAgeSeconds` 旧就按需同步一次（先给当前值；`?fresh=true` / `fresh=True` 等结果）。没人读 = 不同步 = 不花钱。

**③ 触发：Automation**。「这个合同的线类物料一入库就私发质检」：

```json
{
  "apiName": "po-0001-inbound", "displayName": "ACME-0001 线类入库 → 私发质检员", "status": "active",
  "condition": { "type": "objectsAdded", "evaluation": { "everyMinutes": 10, "workHours": "07:00-21:00" },
    "objectSet": { "type": "filter", "objectSet": { "type": "base", "objectType": "stock.inboundLine" },
      "where": { "type": "and", "value": [ { "type": "startsWith", "field": "orderNo", "value": "ACME-0001" }, { "type": "containsAnyTerm", "field": "materialName", "value": "涤纶线 弹力线 高弹" } ] } } },
  "effects": [ { "type": "notification", "channel": "dingtalk", "via": { "agent": "demo-box", "profile": "purchasing" },
    "recipients": [ { "type": "user", "id": "000000001", "name": "质检员" } ], "title": "辅料到货入库", "quietHours": "21:00-07:00",
    "template": "### 📦 到货入库，请做辅料质检\n{{#objects}}- {{materialName}} {{materialRule}} **{{amount}} {{unitName}}**（入库单 {{billId}}）\n{{/objects}}" } ],
  "replaces": [ { "box": "demo", "profile": "purchasing", "jobId": "a1b2c3d4e5f6", "name": "ACME-0001 到货入库", "runsPerDay": 144 } ]
}
```

```bash
aidc semantic automations upsert --ontology cell-demo --file liya.json --dry-run   # 编译对象集、看当前成员
aidc semantic automations upsert --ontology cell-demo --file liya.json            # 从「现在」开始，不回放历史
aidc semantic automations show <automationRid>                                              # 最近 20 次运行、每个收件人的结果
```

**④ 还要写代码的 Loop（日报、渲染看板）**：脚本改成从 Semantic 读，触发写成 Automation 的 `agentScript` 效果（到点、或数据变了之后跑一次）。箱上脚本用 `aidc_semantic`（只用标准库，随 agent 装在 `/opt/aidc-semantic/lib`；运行时自带一次性只读令牌）：

```python
from aidc_semantic import sql, objects
rows = sql('SELECT "orderNo", "materialName", amount FROM "stock.inboundLine" WHERE "inputDate" >= now() - interval \'1 day\'', fresh=True)
```

**迁移一个 cron 的定式**：写 Automation（带 `replaces`）→ `--dry-run` 看对象集 → 建好后与原 cron 并行一轮、对上 → `hermes cron pause <jobId>`（暂停不删，保留两周）。`/semantic/<组织>/connections` 能看到每个 Automation 替掉了哪些 cron、原来每天跑多少次。
