@operato/ops-contract 0.9.7 → 0.9.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/domain-catalog.d.ts +32 -2
- package/dist/domain-catalog.js +21 -10
- package/dist/operational-ingest.d.ts +1 -1
- package/dist/webhook.d.ts +19 -1
- package/dist/webhook.js +1 -1
- package/dist-cjs/index.cjs +15 -11
- package/package.json +1 -1
package/dist/domain-catalog.d.ts
CHANGED
|
@@ -176,19 +176,49 @@ export type TwinRelationTarget = {
|
|
|
176
176
|
* ── 왜 필요한가 (MES 레인이 실측으로 올림, 2026-09-05) ─────────────────────
|
|
177
177
|
* 설비 하나에 걸린 것을 한자리에 보이면, 들어오는 관계라도 뜻이 갈린다.
|
|
178
178
|
*
|
|
179
|
-
* past 이미 일어났다 — 세워도 안 바뀐다 실적 · 상태 구간
|
|
179
|
+
* past 이미 일어났다 — 세워도 안 바뀐다 실적 · 끝난 정비 · 상태 구간
|
|
180
|
+
* current 지금 그렇다 — **끝나면 바뀐다** 정비 중 · 물품의 지금 자리
|
|
180
181
|
* future 아직이다 — 세우면 이것이 걸린다 정비 계획 · 지시
|
|
181
182
|
* standing 시간이 없는 사실 등급 · 붙박인 자리 · 종류
|
|
182
183
|
*
|
|
183
184
|
* 한 묶음에 담으면 **「걸린 것이 열」인데 아홉이 지난 일인 경우와 아홉이 앞의 일인 경우가 같아
|
|
184
185
|
* 보인다.** 「이 설비를 세워도 되나」의 답이 정반대인 두 상황이다.
|
|
185
186
|
*
|
|
187
|
+
* ── `current` 는 나중에 왔다 (2026-09-06) ──────────────────────────────────
|
|
188
|
+
* 처음 셋으로 냈고, 「지금 그렇고 곧 바뀐다」가 그 셋에 안 들어가는 자리를 **두 레인이 각자
|
|
189
|
+
* 만났다.** 미리 넣은 것이 아니라 두 번 걸려서 넣었다.
|
|
190
|
+
*
|
|
191
|
+
* 아키텍트 `items.location` — 이 상자가 지금 A 에 있다. 곧 옮겨진다
|
|
192
|
+
* plant/MES `MaintenanceOrderStatus.IN_PROGRESS` — 지금 이미 서 있다. 끝나면 바뀐다
|
|
193
|
+
*
|
|
194
|
+
* plant 의 상태 넷 중 `IN_PROGRESS` 하나가 셋 어디에도 안 들어갔다: `OPEN` 은 미래,
|
|
195
|
+
* `DONE`·`CANCELLED` 는 과거인데, **정비 중은 「지금 서 있다」이고 「세워도 되나」의 답이 앞의
|
|
196
|
+
* 일과 정반대다**(이미 서 있으니 세울 것이 없다).
|
|
197
|
+
*
|
|
198
|
+
* ── `standing` 과 무엇이 다른가 ────────────────────────────────────────────
|
|
199
|
+
* **다시 물어야 하나**가 갈린다. `standing` 은 등급·종류·붙박인 자리처럼 안 바뀌므로 화면이 담아
|
|
200
|
+
* 두어도 된다. `current` 는 담아 두면 거짓이 된다 — 「이 설비는 정비 중이다」와 「이 상자는 A 에
|
|
201
|
+
* 있다」를 등급처럼 캐시하면 사람이 낡은 사실을 보고 판단한다.
|
|
202
|
+
*
|
|
203
|
+
* 그래서 셋에 억지로 넣지 않고 비워 두었다. 짐작으로 `standing` 을 씌웠으면 그 뒤로 아무도 이
|
|
204
|
+
* 물음을 다시 안 했을 것이다.
|
|
205
|
+
*
|
|
186
206
|
* ── 왜 `via` 로 가르면 안 되나 ──────────────────────────────────────────────
|
|
187
207
|
* `via` 는 i18n 키다. **「이것이 지난 일인가」를 말하지 않는다.** 화면이 키 이름을 보고 짐작하게
|
|
188
208
|
* 되고, 축이 하나 늘 때 한 화면만 따라간다. 읽는 곳은 제품마다여도 **무엇을 아는지는 한 벌**이어야
|
|
189
209
|
* 한다.
|
|
190
210
|
*/
|
|
191
|
-
export type RelationTense = 'past' | 'future' | 'standing';
|
|
211
|
+
export type RelationTense = 'past' | 'current' | 'future' | 'standing';
|
|
212
|
+
/**
|
|
213
|
+
* 화면이 이 관계를 **담아 두어도 되나** — `standing` 만 그렇다.
|
|
214
|
+
*
|
|
215
|
+
* 이 판정을 화면마다 하면 낱말이 하나 늘 때 한 화면만 따라간다. 무엇을 아는지는 한 벌이어야 한다.
|
|
216
|
+
*
|
|
217
|
+
* **미선언은 `false` 다.** 모르면 다시 묻는 쪽이 안전하다 — 낡은 사실을 보이는 것보다 한 번 더
|
|
218
|
+
* 묻는 것이 싸다. `tenseOf` 가 미선언에 `undefined` 를 내는 것과 방향이 다른데, 그것은 「말할 수
|
|
219
|
+
* 없는 것을 단언하지 않는다」이고 이것은 「모를 때 어느 쪽으로 틀리나」다.
|
|
220
|
+
*/
|
|
221
|
+
export declare function isCacheableTense(t: RelationTense | undefined): boolean;
|
|
192
222
|
export interface TwinRelationInfo {
|
|
193
223
|
/** 출발 축. */
|
|
194
224
|
from: string;
|
package/dist/domain-catalog.js
CHANGED
|
@@ -149,6 +149,18 @@ export const TWIN_AXES = [
|
|
|
149
149
|
{ axis: 'demandWindows', path: 'energy.closed', idField: 'startMs', label: 'twin.axis.demandWindows', kind: 'instance',
|
|
150
150
|
source: 'state', historical: true, standardClass: {}, systems: ['ems'] }
|
|
151
151
|
];
|
|
152
|
+
/**
|
|
153
|
+
* 화면이 이 관계를 **담아 두어도 되나** — `standing` 만 그렇다.
|
|
154
|
+
*
|
|
155
|
+
* 이 판정을 화면마다 하면 낱말이 하나 늘 때 한 화면만 따라간다. 무엇을 아는지는 한 벌이어야 한다.
|
|
156
|
+
*
|
|
157
|
+
* **미선언은 `false` 다.** 모르면 다시 묻는 쪽이 안전하다 — 낡은 사실을 보이는 것보다 한 번 더
|
|
158
|
+
* 묻는 것이 싸다. `tenseOf` 가 미선언에 `undefined` 를 내는 것과 방향이 다른데, 그것은 「말할 수
|
|
159
|
+
* 없는 것을 단언하지 않는다」이고 이것은 「모를 때 어느 쪽으로 틀리나」다.
|
|
160
|
+
*/
|
|
161
|
+
export function isCacheableTense(t) {
|
|
162
|
+
return t === 'standing';
|
|
163
|
+
}
|
|
152
164
|
/**
|
|
153
165
|
* 그 관계가 언제의 일인가 — **모르면 `undefined`.**
|
|
154
166
|
*
|
|
@@ -221,18 +233,17 @@ export const TWIN_RELATIONS = [
|
|
|
221
233
|
* 않고 `external` 로 둔다. 축을 가리키게 적으면 없는 필드를 가리키는 선언이 된다.
|
|
222
234
|
*/
|
|
223
235
|
/*
|
|
224
|
-
* ── 물품의 지금 자리는
|
|
225
|
-
* `location`·`parent`·`carriedBy` 는 지난 일도 앞의 일도
|
|
226
|
-
*
|
|
227
|
-
*
|
|
236
|
+
* ── 물품의 지금 자리는 `current` 다 (2026-09-06) ───────────────────────────
|
|
237
|
+
* `location`·`parent`·`carriedBy` 는 지난 일도 앞의 일도 아니고 `standing` 도 아니다 — **지금
|
|
238
|
+
* 그렇고 곧 바뀐다.** 붙박인 자리(`homeLocation`)와 정반대 성질이고, 두 줄이 같은 낱말을 받으면
|
|
239
|
+
* 화면이 「이 상자는 A 에 있다」를 등급처럼 담아 둔다(캐시해도 되는 사실로 읽는다).
|
|
228
240
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
* (짐작으로 채우면 그 뒤로는 아무도 이 물음을 다시 안 한다).
|
|
241
|
+
* 하루 비워 두었다. 낱말이 없어서였고, 소비처가 실제로 그 구별에 걸릴 때 늘리기로 했다 — plant 가
|
|
242
|
+
* `MaintenanceOrderStatus.IN_PROGRESS` 로 같은 자리를 만나 걸렸고, 그때 넣었다.
|
|
232
243
|
*/
|
|
233
|
-
{ from: 'items', field: 'location', target: { kind: 'axis', axis: 'locations' }, via: 'ops.rel.at' },
|
|
234
|
-
{ from: 'items', field: 'parent', target: { kind: 'axis', axis: 'items' }, via: 'ops.rel.parent', optional: true },
|
|
235
|
-
{ from: 'items', field: 'carriedBy', target: { kind: 'axis', axis: 'assets' }, via: 'ops.rel.asset', optional: true },
|
|
244
|
+
{ from: 'items', field: 'location', target: { kind: 'axis', axis: 'locations' }, via: 'ops.rel.at', tense: 'current' },
|
|
245
|
+
{ from: 'items', field: 'parent', target: { kind: 'axis', axis: 'items' }, via: 'ops.rel.parent', optional: true, tense: 'current' },
|
|
246
|
+
{ from: 'items', field: 'carriedBy', target: { kind: 'axis', axis: 'assets' }, via: 'ops.rel.asset', optional: true, tense: 'current' },
|
|
236
247
|
{ from: 'items', field: 'gtin', target: { kind: 'external', entity: 'gs1.itemRef' }, via: 'ops.rel.item', optional: true, tense: 'standing' },
|
|
237
248
|
/*
|
|
238
249
|
* 자격을 검증한 시험 — **여덟 갈래.** 자원(개체)과 등급 양쪽이 가리킨다: 표준이 그 둘 모두에 이
|
|
@@ -34,7 +34,7 @@ export interface OperationalIngestOptions {
|
|
|
34
34
|
* 거절도 경고도 없었다. **같은 계약의 ERP 경로는 이것을 잡는다**(§`erp.ts` 의 자재 줄 검사).
|
|
35
35
|
* 한 계약 안에 검사가 두 벌이고 한쪽만 봤다.
|
|
36
36
|
*
|
|
37
|
-
* 이름을 맞춰 달라고 원본에
|
|
37
|
+
* 이름을 맞춰 달라고 원본에 요청하는 것으로는 다음에 또 오류보고없이 넘어간다. 다음 원본이 `use: 'out'` 같은
|
|
38
38
|
* 것을 보내면 같은 일이 난다(실제로 그런 시드가 있었다: 계약에 `out` 이라는 낱말이 없다).
|
|
39
39
|
*/
|
|
40
40
|
export interface ObjectShape {
|
package/dist/webhook.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export interface WebhookEnvelope {
|
|
|
79
79
|
/**
|
|
80
80
|
* 번호 판정 — **네 가지이고, 합치지 않는다.**
|
|
81
81
|
*
|
|
82
|
-
* first 이 단위의 첫 봉투다. 받는 쪽에 커서가 없다
|
|
82
|
+
* first 이 단위의 첫 봉투다. 받는 쪽에 커서가 없다 — 그 앞을 몇 개 못 봤는지 함께 낸다
|
|
83
83
|
* next 바로 다음 번호다
|
|
84
84
|
* behind 이미 본 번호다. 재전송이다
|
|
85
85
|
* gap 건너뛰었다. `expectedSeq` 부터 다시 받아야 한다
|
|
@@ -94,6 +94,24 @@ export interface WebhookEnvelope {
|
|
|
94
94
|
export type SequenceVerdict = {
|
|
95
95
|
kind: 'first';
|
|
96
96
|
lastSeq: number;
|
|
97
|
+
/**
|
|
98
|
+
* 이 번호 아래로 몇 개가 있나 — **받은 적이 없는 개수**다(`seq - 1`).
|
|
99
|
+
*
|
|
100
|
+
* ── 왜 필요한가 (2026-09-06 실측, 인티그레이션 레인이 찾음) ──────────────
|
|
101
|
+
* plant 아웃박스의 1~20 이 400 으로 거절돼 그 연결에 커서가 없었다. 그래서 다음에 온 21번이
|
|
102
|
+
* **첫 봉투로 앉으며 커서가 21 로 뛰었다.** 커서가 0 이었다면 같은 21이 `gap` 이었다.
|
|
103
|
+
*
|
|
104
|
+
* 스무 건이 없는데 빈 구간으로도 안 세어지고, 경고도 없고, backfill 대상도 아니었다. backfill 은
|
|
105
|
+
* 커서(48)부터 묻기 때문에 영영 메워지지 않는다.
|
|
106
|
+
*
|
|
107
|
+
* ── 「잃었다」가 아니다 ────────────────────────────────────────────────
|
|
108
|
+
* 이 수가 곧 손실은 아니다. 원본이 오래 돌고 있는데 트윈을 나중에 붙이면 seq 4,712 부터 받는
|
|
109
|
+
* 것이 정상이고, 그 아래 4,711 개는 **애초에 안 받기로 한 것**이다.
|
|
110
|
+
*
|
|
111
|
+
* 계약은 둘을 구별할 수 없다. 그래서 「못 봤다」까지만 말하고 손실인지는 판정하지 않는다.
|
|
112
|
+
* 판정에 필요한 것(원본이 언제부터 보냈나)은 원본만 안다.
|
|
113
|
+
*/
|
|
114
|
+
unseenBefore: number;
|
|
97
115
|
} | {
|
|
98
116
|
kind: 'next';
|
|
99
117
|
lastSeq: number;
|
package/dist/webhook.js
CHANGED
|
@@ -90,7 +90,7 @@ export function checkSequence(lastSeq, seq) {
|
|
|
90
90
|
throw new Error(`checkSequence: 번호는 1 이상의 정수여야 한다 — 받은 값 ${JSON.stringify(seq)}`);
|
|
91
91
|
}
|
|
92
92
|
if (lastSeq === undefined || lastSeq === null)
|
|
93
|
-
return { kind: 'first', lastSeq: seq };
|
|
93
|
+
return { kind: 'first', lastSeq: seq, unseenBefore: seq - 1 };
|
|
94
94
|
if (seq <= lastSeq)
|
|
95
95
|
return { kind: 'behind', lastSeq };
|
|
96
96
|
if (seq === lastSeq + 1)
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -136,6 +136,7 @@ __export(index_exports, {
|
|
|
136
136
|
ingestMasterData: () => ingestMasterData,
|
|
137
137
|
ingestOperationalRecords: () => ingestOperationalRecords,
|
|
138
138
|
isAggregationRecord: () => isAggregationRecord,
|
|
139
|
+
isCacheableTense: () => isCacheableTense,
|
|
139
140
|
isCommandTerminal: () => isCommandTerminal,
|
|
140
141
|
isElectricalLocationType: () => isElectricalLocationType,
|
|
141
142
|
isEnergyBillRecord: () => isEnergyBillRecord,
|
|
@@ -1873,6 +1874,9 @@ var TWIN_AXES = [
|
|
|
1873
1874
|
systems: ["ems"]
|
|
1874
1875
|
}
|
|
1875
1876
|
];
|
|
1877
|
+
function isCacheableTense(t) {
|
|
1878
|
+
return t === "standing";
|
|
1879
|
+
}
|
|
1876
1880
|
function tenseOf(r) {
|
|
1877
1881
|
return r?.tense;
|
|
1878
1882
|
}
|
|
@@ -1935,18 +1939,17 @@ var TWIN_RELATIONS = [
|
|
|
1935
1939
|
* 않고 `external` 로 둔다. 축을 가리키게 적으면 없는 필드를 가리키는 선언이 된다.
|
|
1936
1940
|
*/
|
|
1937
1941
|
/*
|
|
1938
|
-
* ── 물품의 지금 자리는
|
|
1939
|
-
* `location`·`parent`·`carriedBy` 는 지난 일도 앞의 일도
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
+
* ── 물품의 지금 자리는 `current` 다 (2026-09-06) ───────────────────────────
|
|
1943
|
+
* `location`·`parent`·`carriedBy` 는 지난 일도 앞의 일도 아니고 `standing` 도 아니다 — **지금
|
|
1944
|
+
* 그렇고 곧 바뀐다.** 붙박인 자리(`homeLocation`)와 정반대 성질이고, 두 줄이 같은 낱말을 받으면
|
|
1945
|
+
* 화면이 「이 상자는 A 에 있다」를 등급처럼 담아 둔다(캐시해도 되는 사실로 읽는다).
|
|
1942
1946
|
*
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
* (짐작으로 채우면 그 뒤로는 아무도 이 물음을 다시 안 한다).
|
|
1947
|
+
* 하루 비워 두었다. 낱말이 없어서였고, 소비처가 실제로 그 구별에 걸릴 때 늘리기로 했다 — plant 가
|
|
1948
|
+
* `MaintenanceOrderStatus.IN_PROGRESS` 로 같은 자리를 만나 걸렸고, 그때 넣었다.
|
|
1946
1949
|
*/
|
|
1947
|
-
{ from: "items", field: "location", target: { kind: "axis", axis: "locations" }, via: "ops.rel.at" },
|
|
1948
|
-
{ from: "items", field: "parent", target: { kind: "axis", axis: "items" }, via: "ops.rel.parent", optional: true },
|
|
1949
|
-
{ from: "items", field: "carriedBy", target: { kind: "axis", axis: "assets" }, via: "ops.rel.asset", optional: true },
|
|
1950
|
+
{ from: "items", field: "location", target: { kind: "axis", axis: "locations" }, via: "ops.rel.at", tense: "current" },
|
|
1951
|
+
{ from: "items", field: "parent", target: { kind: "axis", axis: "items" }, via: "ops.rel.parent", optional: true, tense: "current" },
|
|
1952
|
+
{ from: "items", field: "carriedBy", target: { kind: "axis", axis: "assets" }, via: "ops.rel.asset", optional: true, tense: "current" },
|
|
1950
1953
|
{ from: "items", field: "gtin", target: { kind: "external", entity: "gs1.itemRef" }, via: "ops.rel.item", optional: true, tense: "standing" },
|
|
1951
1954
|
/*
|
|
1952
1955
|
* 자격을 검증한 시험 — **여덟 갈래.** 자원(개체)과 등급 양쪽이 가리킨다: 표준이 그 둘 모두에 이
|
|
@@ -4013,7 +4016,7 @@ function checkSequence(lastSeq, seq) {
|
|
|
4013
4016
|
if (!Number.isInteger(seq) || seq < 1) {
|
|
4014
4017
|
throw new Error(`checkSequence: \uBC88\uD638\uB294 1 \uC774\uC0C1\uC758 \uC815\uC218\uC5EC\uC57C \uD55C\uB2E4 \u2014 \uBC1B\uC740 \uAC12 ${JSON.stringify(seq)}`);
|
|
4015
4018
|
}
|
|
4016
|
-
if (lastSeq === void 0 || lastSeq === null) return { kind: "first", lastSeq: seq };
|
|
4019
|
+
if (lastSeq === void 0 || lastSeq === null) return { kind: "first", lastSeq: seq, unseenBefore: seq - 1 };
|
|
4017
4020
|
if (seq <= lastSeq) return { kind: "behind", lastSeq };
|
|
4018
4021
|
if (seq === lastSeq + 1) return { kind: "next", lastSeq: seq };
|
|
4019
4022
|
return { kind: "gap", lastSeq, expectedSeq: lastSeq + 1 };
|
|
@@ -4451,6 +4454,7 @@ function commandSpecGaps(specs, command) {
|
|
|
4451
4454
|
ingestMasterData,
|
|
4452
4455
|
ingestOperationalRecords,
|
|
4453
4456
|
isAggregationRecord,
|
|
4457
|
+
isCacheableTense,
|
|
4454
4458
|
isCommandTerminal,
|
|
4455
4459
|
isElectricalLocationType,
|
|
4456
4460
|
isEnergyBillRecord,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/ops-contract",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.9",
|
|
4
4
|
"description": "Operations domain contract — the standard vocabulary that producers and readers agree on (EPCIS 2.0/GS1, ISA-95, IEC 61850/ISO 50001). Types, guards, validation. No state, no engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist-cjs/index.cjs",
|