# Semantic · 旧的数据层（原数据 SDK）

> 1.20.0 起数据 SDK 并进 [Semantic](semantic.md)：读对象用 `semantic.ontology().objects(…)`（对象集、聚合、链接），实时用 `objectSet.subscribe(…)`，改数据只走 Action；数据从数据源进来（Object Type 定义里的 `datasources`，见[数据流与数据源](connect.md)）。本页的 `data.*` 与 `aidc data` 照样能用；直接写对象（create / update / delete）只给开发者，新代码请用 Action。


公司的**数据层**：语义层对象存在 AIDC 中枢（Supabase Postgres，按公司隔离），这里是它的增删改查、批量导入、聚合、实时订阅，以及把 ERP / MES 的数据同步进来（sync）。另一半是数据分析的常用小工具（解析、画像、聚合、呈现）。

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

## 数据层

应用和智能体**不直连 ERP**：发布端只读地把数据源的变化推上来，按 Object Type 定义里的数据源（`datasources`）写进对象的「数据源层」；人和智能体的修改写进「语义层改动」；查询读两者合并后的当前值。数据源永远只读、永远不会被改。

```
ERP / MES ──发布端（只读）──▶ 数据流 ──同步绑定──▶ 对象.source ─┐
                                                               ├─▶ 对象.props（当前值）──▶ 应用 · 智能体
                  Action（成员）/ 数据 SDK（开发者）──▶ 对象.edits ─┘        改动永远不回写 ERP
```

### 查询与读取

```js
const lines = data.table("production.order_line");

const page = await lines.list({ where: { line_code: "L01-05" }, sort: ["-ok"], limit: 50 });
// page.objects[i].props：当前值；page.total；page.seq（从它订阅，中间的变化不会丢）

const all = await lines.all({ where: { day: "2026-09-26" } });          // 翻页取全（每页 500）
const { object, linked, history } = await lines.get("100000012345|L01-05", { link: "production.order_line.line_code", history: true });
const byLine = await lines.aggregate({ groupBy: ["line_code"], metrics: { ok: ["ok", "sum"], n: "count" } });
```

条件同语义 SDK：`{ 属性: 值 }` 相等，`{ gt, gte, lt, lte, ne, in, nin, contains, startsWith, null }`，`$or: […]`。`includeGone: true` 连数据源里已经没有的对象也要。

### 实时：`live()` 与 `watch()`

```js
const view = data.table("production.order_line").live({
  sort: "-ok",
  onUpdate(objects, change) { render(objects); },          // 先全量，之后每次变化回调一次最新全集
  onStatus({ connection }) { badge(connection); },         // connecting / live / reconnecting / paused / closed
});
view.close();

// 更底层：只要变化
// 页面隐藏超过 1 分钟，连接自动断开（paused），切回来带着序号续上、不丢不重——挂着的连接按时长计入应用的计算分钟

const sub = data.watch({ types: ["production.order_line"], after: page.seq }, {
  onChange(c) { /* { seq, type, pk, op: "upsert"|"delete", props, origin: "sync"|"action"|"edit"|"import", at } */ },
  onReset(seq) { /* 落后太多：重新查询 */ },
});
```

一条订阅服务一个页面：服务端从变化账里按序号推送（Redis 只传唤醒），断线带序号续传；应用只收到它看得见、且订阅了的类型。

### 写（开发者）

成员改数据请用 **Action**（校验、权限、留痕）。数据 SDK 的直接写只给开发者 Key 或应用里的 developer 访客（清单 `semantic.write` 登记了该类型）：

```js
await lines.create({ order_no: "X1", line_code: "L01-05", note: "临时插单" });
await lines.update("X1|L01-05", { issue: "缺料" }, { ifRev: 3 });      // 写进语义层改动
await lines.update("X1|L01-05", {}, { revert: ["ok"] });               // 撤销改动，回到数据源的值
await lines.remove("X1|L01-05");                                        // 语义层墓碑，数据源不受影响
await lines.import(rowsFromExcel, { layer: "source", mode: "mirror" }); // 批量导入：当作数据源数据；mirror = 这批就是全部
```

每一次写都进操作记录（谁、何时、改了什么、改前改后），并唤醒订阅端。

### 从数据流同步（ERP → 数据层）

```js
await data.sync.bind({
  stream: "production-progress",                  // 数据流（发布端装在客户箱上，只读 SAP）
  type: "production.order_line",
  properties: { order_no: "order", line_code: "line", plan: "plan", ok: "ok", day: "day" },  // 属性 ← 流字段
  mode: "mirror",                             // 流的全量就是全部对象；多行汇成一个对象用 "upsert"
});
await data.sync.list();                        // 每条绑定：状态、已同步到的流序号、落后几批
```

- 绑定建好立即全量同步一次，之后流每落账一批就在同一次发布里同步（净变化：新增 / 变化的行写 source，删掉的行标记源头已消失）。
- 类型的主键属性必须都映射上；只在语义层维护的属性（`writeback`）不能从数据源同步。
- 同步失败只记在绑定上（`status: error` + 原因），不影响数据流本身；下一批到来时发现序号接不上会自动全量重同步，也可以手动 `data.sync.resync(id)`。

```bash
aidc data bind production-progress production.order_line --map order_no=order,line_code=line,plan=plan,ok=ok,day=day
aidc data bindings
aidc data query production.order_line --where '{"status":"异常"}' --sort -gap
aidc data watch --types production.order_line
aidc data import production.order_line 导出.csv --layer source --mode mirror
```

## 分析小工具

纯函数、零依赖，浏览器与 Node 通用——解析、画像、分组聚合、透视、时间序列、异常、格式化、Markdown 表格与轻量图表，减少每个应用各写一遍的样板代码。

## 解析与导出

```js
const rows = data.parseCsv(text);           // RFC 4180：引号、转义、字段内换行；数字列自动转 number
const csv = data.toCsv(rows, ["月份", "营收"]);
ui.download("营收.csv", csv);               // 存成文件（界面 SDK）：自动加 UTF-8 BOM，Excel 直接打开中文不乱码
```

给 Excel 用的导出就是 CSV + `ui.download`，不用另找 xlsx 库。读 xlsx 请先另存为 CSV（或在服务端处理）——浏览器 SDK 不打包 xlsx 解析器。

## 画像

```js
data.profile(rows);
// → [{ name, type: "number"|"date"|"string"|"boolean"|"empty", count, missing, distinct, min, max, mean, sum, top }]
```

## 聚合

```js
data.groupBy(rows, "region", { revenue: ["amount", "sum"], orders: "count", avg: ["amount", "avg"] });
data.pivot(rows, "region", "month", "amount", "sum");          // → { columns, rows }
data.timeSeries(rows, "order_date", "month", { revenue: ["amount", "sum"] });
data.topN(rows, "revenue", 10);
data.timeBucket("2026-09-25", "week");                          // → "2026-09-22"（周一）
```

聚合函数：`sum`、`avg`、`count`、`min`、`max`、`distinct`。

## 变化与异常

```js
data.pctChange(1200, 1000);            // 0.2
data.share(300, 1200);                 // 0.25
data.zScores([10, 12, 30]);
data.anomalies(monthly, "revenue", 2); // |z| ≥ 2 的月份
```

## 呈现

```js
data.formatNumber(129363024);          // "1.3 亿"
data.formatNumber(52300);              // "5.2 万"
data.formatPercent(0.2559);            // "25.6%"
data.markdownTable(rows, [["month", "月份"], ["revenue", "营收"]]);
el.innerHTML = data.barChartSvg(points, { color: "#ff4d00", format: data.formatNumber });
el.innerHTML = data.lineChartSvg(points);            // 缺省按数据上下界缩放；zero: true 从 0 起
```

复杂图表请用 Chart.js（从 `cdn.jsdelivr.net` 加载，并在清单的 `cdn` 里声明）。

## 命令行

```bash
aidc data profile orders.csv
aidc data group orders.csv --by region --sum amount --count
```

## 和数据集报告一起用

企业数据集的固定报告由[数据集](connect.md)取回（只含聚合），再用数据 SDK 做二次加工与呈现——样板「ERP 数据自动分析」就是这样做的。
