@operato/twin-kernel 0.7.43 → 0.7.44
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/contract.d.ts +13 -0
- package/dist/domain-catalog.d.ts +11 -0
- package/dist/domain-catalog.js +11 -1
- package/dist/epcis.d.ts +14 -0
- package/dist/epcis.js +33 -8
- package/dist/flow-engine.d.ts +21 -1
- package/dist/flow-engine.js +37 -1
- package/dist/kernel.js +12 -5
- package/dist/mes-kernel.d.ts +7 -0
- package/dist/mes-kernel.js +9 -0
- package/dist/yms-kernel.js +9 -3
- package/dist-cjs/index.cjs +84 -12
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -2015,6 +2015,19 @@ export declare function readBoardEquipment(def: TwinModelDef | Record<string, un
|
|
|
2015
2015
|
/** 저장된 보드의 반복사용 자산 — 설비와 같은 정규화를 거친다. */
|
|
2016
2016
|
export declare function readBoardAssets(def: TwinModelDef | Record<string, unknown>): NonNullable<TwinModelDef['assets']>;
|
|
2017
2017
|
export interface TwinModelDef {
|
|
2018
|
+
/**
|
|
2019
|
+
* **이 트윈의 정체성 선언** — 팔레트·트레일러·문서의 식별자가 어디서 오나.
|
|
2020
|
+
*
|
|
2021
|
+
* ── 왜 모델에 있나 (2026-08-21) ──────────────────────────────────────────
|
|
2022
|
+
* 처음에는 `ProductionSpec.identity` 하나였다. 그때 선언 모드가 있는 커널이 MES 뿐이었기 때문이다.
|
|
2023
|
+
* 그런데 **정체성은 생산과 무관하다**: 창고는 팔레트(SSCC)를, 야드는 트레일러(GRAI)를 식별하는데
|
|
2024
|
+
* 그 둘은 아무것도 생산하지 않는다. 생산 선언에 두면 생산하지 않는 트윈이 정체성을 선언할 자리가 없고,
|
|
2025
|
+
* 실제로 그래서 두 커널이 프리픽스를 코드에 두고 있었다.
|
|
2026
|
+
*
|
|
2027
|
+
* 읽는 곳은 하나다(§`FlowEngine.identityDeclaration`). `ProductionSpec.identity` 는 **먼저 생긴
|
|
2028
|
+
* 자리**이고, 픽스처·호스트가 이 자리로 옮기면 사라진다.
|
|
2029
|
+
*/
|
|
2030
|
+
identity?: IdentityDeclaration;
|
|
2018
2031
|
/** parallelism = 동시 처리 수(LocationState.parallelism 참조). capacity 는 저장 용량. */
|
|
2019
2032
|
locations: {
|
|
2020
2033
|
id: string;
|
package/dist/domain-catalog.d.ts
CHANGED
|
@@ -61,6 +61,17 @@ export interface TwinAxisInfo {
|
|
|
61
61
|
* (저장·계약의 이름과 어긋나면 그 순간 방언이 생긴다).
|
|
62
62
|
*/
|
|
63
63
|
axis: string;
|
|
64
|
+
/**
|
|
65
|
+
* **무엇이 이 축의 항목을 가리키나** — 없으면 소비처가 `id` 를 쓴다.
|
|
66
|
+
*
|
|
67
|
+
* 대부분의 축은 항목마다 `id` 가 있다. 그렇지 않은 축이 있고, 그것이 결함은 아니다 — 수요 구간의
|
|
68
|
+
* 정체성은 **구간이 언제 시작했나**다(요금의 알갱이가 그 시각으로 정해진다).
|
|
69
|
+
*
|
|
70
|
+
* 이 칸이 없던 동안 소비처는 `id`·`key`·`gtin` 을 **짐작**했고, 그래서 화면이 실재하는 항목을
|
|
71
|
+
* 「식별자 없음 — 참조할 수 없는 항목」으로 보였다. 값은 있는데 가리킬 수 없다고 말한 것이다.
|
|
72
|
+
* 짐작을 없애고 선언이 답한다 — 축이 사는 자리(`path`)를 선언하는 것과 같은 이유다.
|
|
73
|
+
*/
|
|
74
|
+
idField?: string;
|
|
64
75
|
/**
|
|
65
76
|
* 이 축을 **어디서 읽나.**
|
|
66
77
|
*
|
package/dist/domain-catalog.js
CHANGED
|
@@ -115,7 +115,17 @@ export const TWIN_AXES = [
|
|
|
115
115
|
* 아직 세우지 않은 것: **요금 구간**(TariffPeriod)과 **원단위**(EnPI). 둘 다 아직 아무도 만들지
|
|
116
116
|
* 않는다 — 선언만 하면 개념 지도가 언제나 0 을 보여 주고, 그것은 결손처럼 읽힌다(§10 7단계의 일).
|
|
117
117
|
*/
|
|
118
|
-
|
|
118
|
+
/*
|
|
119
|
+
* **무엇이 이 항목을 가리키나** — `idField` 가 말한다.
|
|
120
|
+
*
|
|
121
|
+
* 수요 구간에는 `id` 가 없다. 그것이 결함은 아니다: 구간의 정체성은 **그 구간이 언제 시작했나**이고
|
|
122
|
+
* (요금의 알갱이가 그 시각으로 정해진다) 커널도 그것으로 키를 만든다(`contract-projected-over:<startMs>`).
|
|
123
|
+
*
|
|
124
|
+
* 그런데 소비처가 `id`·`key`·`gtin` 을 **짐작**하고 있어서, 화면이 여섯 구간 모두를 「식별자 없음 —
|
|
125
|
+
* 참조할 수 없는 항목」으로 보였다. 값은 실재하는데 가리킬 수 없다고 말한 것이다. 짐작을 없애고
|
|
126
|
+
* 선언이 답하게 한다 — 축이 사는 자리(`path`)를 선언하는 것과 같은 이유다.
|
|
127
|
+
*/
|
|
128
|
+
{ axis: 'demandWindows', path: 'energy.closed', idField: 'startMs', label: 'twin.axis.demandWindows', kind: 'instance',
|
|
119
129
|
source: 'state', historical: true, standardClass: {}, systems: ['ems'] }
|
|
120
130
|
];
|
|
121
131
|
/** 관계 전체 — 지도의 선과 항목의 이웃이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
package/dist/epcis.d.ts
CHANGED
|
@@ -271,6 +271,20 @@ export declare function gdtiUri(companyPrefix: string, docType: string, serial:
|
|
|
271
271
|
* 이름공간의 모양으로 URL 형태와 URN 형태를 가른다. 판정할 수 없는 모양이면 **답하지 않는다**(지어내지
|
|
272
272
|
* 않는다) — 호출부가 다른 길을 고르게 한다.
|
|
273
273
|
*/
|
|
274
|
+
/**
|
|
275
|
+
* **선언된 이름공간 아래의 개체 식별자** — SSCC·GRAI 를 쓰지 않는 길.
|
|
276
|
+
*
|
|
277
|
+
* 팔레트(SSCC)·트레일러(GRAI)는 개체 식별자이고, 그 조립에는 GS1 회사 프리픽스가 필요하다. 프리픽스가
|
|
278
|
+
* 없는 현장은 그 길로 갈 수 없다 — 그런데 커널이 프리픽스를 지어내면 저널에 남의 번호가 영구히 남는다.
|
|
279
|
+
*
|
|
280
|
+
* 표준이 다른 길을 정해 두었다.
|
|
281
|
+
* · CBV 2.0 §8.2.4 `http(s)://[Subdomain.]Domain/⁎⁎/obj/Objid` — 그 도메인 소유자가 배정
|
|
282
|
+
* · CBV 2.0 §8.2.3 `urn:URNNamespace:⁎⁎:obj:Objid` — URN 이름공간 소유자가 배정
|
|
283
|
+
*
|
|
284
|
+
* `obj` 표지가 필수다(클래스의 `class`·거래문서의 `bt` 와 같은 구조다). 다만 표준은 EPC URI 나 Digital
|
|
285
|
+
* Link 를 **권한다**(SHOULD) — 이 길은 프리픽스가 없을 때의 정합 경로다.
|
|
286
|
+
*/
|
|
287
|
+
export declare function objectUri(namespace: string, objId: string | number): string | undefined;
|
|
274
288
|
export declare function bizTransactionUri(namespace: string, transId: string | number): string | undefined;
|
|
275
289
|
/** 모든 빌더가 공통으로 받는 표준 헤더 옵션(선택) — 방출부가 필요할 때 채운다. */
|
|
276
290
|
export interface EpcisHeaderOptions {
|
package/dist/epcis.js
CHANGED
|
@@ -132,25 +132,50 @@ export function gdtiUri(companyPrefix, docType, serial) {
|
|
|
132
132
|
* 이름공간의 모양으로 URL 형태와 URN 형태를 가른다. 판정할 수 없는 모양이면 **답하지 않는다**(지어내지
|
|
133
133
|
* 않는다) — 호출부가 다른 길을 고르게 한다.
|
|
134
134
|
*/
|
|
135
|
+
/**
|
|
136
|
+
* **선언된 이름공간 아래의 개체 식별자** — SSCC·GRAI 를 쓰지 않는 길.
|
|
137
|
+
*
|
|
138
|
+
* 팔레트(SSCC)·트레일러(GRAI)는 개체 식별자이고, 그 조립에는 GS1 회사 프리픽스가 필요하다. 프리픽스가
|
|
139
|
+
* 없는 현장은 그 길로 갈 수 없다 — 그런데 커널이 프리픽스를 지어내면 저널에 남의 번호가 영구히 남는다.
|
|
140
|
+
*
|
|
141
|
+
* 표준이 다른 길을 정해 두었다.
|
|
142
|
+
* · CBV 2.0 §8.2.4 `http(s)://[Subdomain.]Domain/⁎⁎/obj/Objid` — 그 도메인 소유자가 배정
|
|
143
|
+
* · CBV 2.0 §8.2.3 `urn:URNNamespace:⁎⁎:obj:Objid` — URN 이름공간 소유자가 배정
|
|
144
|
+
*
|
|
145
|
+
* `obj` 표지가 필수다(클래스의 `class`·거래문서의 `bt` 와 같은 구조다). 다만 표준은 EPC URI 나 Digital
|
|
146
|
+
* Link 를 **권한다**(SHOULD) — 이 길은 프리픽스가 없을 때의 정합 경로다.
|
|
147
|
+
*/
|
|
148
|
+
export function objectUri(namespace, objId) {
|
|
149
|
+
return underNamespace(namespace, 'obj', objId);
|
|
150
|
+
}
|
|
135
151
|
export function bizTransactionUri(namespace, transId) {
|
|
152
|
+
return underNamespace(namespace, 'bt', transId);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* 선언된 이름공간 아래에 표지를 붙여 식별자를 만든다 — 표지만 다르고 규칙은 같다.
|
|
156
|
+
*
|
|
157
|
+
* `obj`(개체 §8.2.3·§8.2.4) · `class`(클래스 §8.3.3·§8.3.4) · `bt`(거래문서 §8.5.4·§8.5.5) 가 같은
|
|
158
|
+
* 모양이다. 규칙을 세 곳에 적으면 한 곳만 고쳐지는 날이 온다.
|
|
159
|
+
*/
|
|
160
|
+
function underNamespace(namespace, marker, id) {
|
|
136
161
|
const ns = namespace?.trim();
|
|
137
162
|
if (!ns)
|
|
138
163
|
return undefined;
|
|
139
|
-
const
|
|
140
|
-
/*
|
|
141
|
-
if (!
|
|
164
|
+
const v = String(id);
|
|
165
|
+
/* 표준이 요구하는 「성분 하나」가 깨진다 — 구분자가 든 값은 만들지 않는다. */
|
|
166
|
+
if (!v || v.includes('/') || v.includes(':'))
|
|
142
167
|
return undefined;
|
|
143
168
|
if (/^https?:\/\/[^/\s]+/.test(ns))
|
|
144
|
-
return `${ns.replace(/\/+$/, '')}
|
|
169
|
+
return `${ns.replace(/\/+$/, '')}/${marker}/${v}`;
|
|
145
170
|
/*
|
|
146
|
-
* **GS1 이 소유한 URN 공간에는 우리가
|
|
147
|
-
*
|
|
148
|
-
*
|
|
171
|
+
* **GS1 이 소유한 URN 공간에는 우리가 표지를 만들 수 없다.** `urn:epc:`·`urn:epcglobal:` 의 소유
|
|
172
|
+
* 권한자는 GS1 이고(EPCIS §6.4), 그 안의 형태는 표준이 정해 둔 것만 유효하다. 그 공간을 이름공간으로
|
|
173
|
+
* 선언한 현장은 GS1 키(SSCC·GRAI·GDTI)를 쓰는 쪽으로 가야 한다.
|
|
149
174
|
*/
|
|
150
175
|
if (/^urn:epc(global)?:/.test(ns))
|
|
151
176
|
return undefined;
|
|
152
177
|
if (/^urn:[^:\s]+/.test(ns))
|
|
153
|
-
return `${ns.replace(/:+$/, '')}
|
|
178
|
+
return `${ns.replace(/:+$/, '')}:${marker}:${v}`;
|
|
154
179
|
return undefined;
|
|
155
180
|
}
|
|
156
181
|
function header(type, eventTime, bizStep, opts) {
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, OeeMetrics, AssetState, GeneratorSpec, InterventionOutcome, OrderState, PersonState, ScenarioControl, ScenarioOverride, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState, StructureShift, IdentityGroundingView } from './contract.ts';
|
|
1
|
+
import type { TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, OeeMetrics, AssetState, GeneratorSpec, InterventionOutcome, OrderState, PersonState, ScenarioControl, ScenarioOverride, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState, StructureShift, IdentityGroundingView, IdentityDeclaration } from './contract.ts';
|
|
2
2
|
import type { EpcisEvent, BizTransactionElement } from './epcis.ts';
|
|
3
3
|
import type { AllocationPolicy, SlotView } from './allocation-policy.ts';
|
|
4
4
|
import type { DurationEstimator, DurationContext } from './duration-estimator.ts';
|
|
@@ -1253,6 +1253,26 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
1253
1253
|
* 코어는 여기서 **정체성의 값을 정하지 않는다**: 「어디서 왔나」만 묻는다.
|
|
1254
1254
|
*/
|
|
1255
1255
|
protected identityGroundingView(): IdentityGroundingView;
|
|
1256
|
+
/**
|
|
1257
|
+
* **이 트윈의 정체성 선언을 읽는 한 곳.**
|
|
1258
|
+
*
|
|
1259
|
+
* 모델이 갖는다(§`TwinModelDef.identity`) — 정체성은 생산과 무관하고, 생산하지 않는 트윈도 팔레트·
|
|
1260
|
+
* 트레일러를 식별한다. 선언을 든 커널이 자기 것을 먼저 쓰도록 override 할 수 있다(MES 가 그렇게 한다:
|
|
1261
|
+
* `ProductionSpec.identity` 가 먼저 생긴 자리다).
|
|
1262
|
+
*
|
|
1263
|
+
* 규칙을 두 곳에 적지 않기 위해 조립하는 쪽은 전부 이 함수를 지난다.
|
|
1264
|
+
*/
|
|
1265
|
+
protected identityDeclaration(): IdentityDeclaration | undefined;
|
|
1266
|
+
/**
|
|
1267
|
+
* 선언된 이름공간 아래의 **개체 식별자** — 없으면 답하지 않는다.
|
|
1268
|
+
*
|
|
1269
|
+
* 팔레트·트레일러는 개체 식별자이고 GS1 키(SSCC·GRAI)에는 회사 프리픽스가 필요하다. 프리픽스가 없는
|
|
1270
|
+
* 현장은 CBV §8.2.3·§8.2.4 의 길로 간다. 선언이 없으면 **지어내지 않고 `undefined` 를 답한다** —
|
|
1271
|
+
* 호출부가 무엇을 할지 정한다(레거시 경로는 아직 상수를 쓴다).
|
|
1272
|
+
*/
|
|
1273
|
+
protected declaredObjectId(id: string | number): string | undefined;
|
|
1274
|
+
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
1275
|
+
protected declaredBizTransactionId(id: string | number): string | undefined;
|
|
1256
1276
|
private produceMaterials;
|
|
1257
1277
|
/**
|
|
1258
1278
|
* 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
|
package/dist/flow-engine.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf } from "./contract.js";
|
|
13
13
|
import { ObservedReducer } from "./observed-reducer.js";
|
|
14
|
-
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP } from "./epcis.js";
|
|
14
|
+
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP, objectUri, bizTransactionUri } from "./epcis.js";
|
|
15
15
|
import { OP_PARAM } from "./domain-definition.js";
|
|
16
16
|
import { parseIsoDuration } from "./iso-duration.js";
|
|
17
17
|
import { analyzeCapacity } from "./capacity.js";
|
|
@@ -2578,6 +2578,42 @@ export class FlowEngine {
|
|
|
2578
2578
|
identityGroundingView() {
|
|
2579
2579
|
return identityGroundingOf(undefined);
|
|
2580
2580
|
}
|
|
2581
|
+
/**
|
|
2582
|
+
* **이 트윈의 정체성 선언을 읽는 한 곳.**
|
|
2583
|
+
*
|
|
2584
|
+
* 모델이 갖는다(§`TwinModelDef.identity`) — 정체성은 생산과 무관하고, 생산하지 않는 트윈도 팔레트·
|
|
2585
|
+
* 트레일러를 식별한다. 선언을 든 커널이 자기 것을 먼저 쓰도록 override 할 수 있다(MES 가 그렇게 한다:
|
|
2586
|
+
* `ProductionSpec.identity` 가 먼저 생긴 자리다).
|
|
2587
|
+
*
|
|
2588
|
+
* 규칙을 두 곳에 적지 않기 위해 조립하는 쪽은 전부 이 함수를 지난다.
|
|
2589
|
+
*/
|
|
2590
|
+
identityDeclaration() {
|
|
2591
|
+
return this.boardDef?.identity;
|
|
2592
|
+
}
|
|
2593
|
+
/**
|
|
2594
|
+
* 선언된 이름공간 아래의 **개체 식별자** — 없으면 답하지 않는다.
|
|
2595
|
+
*
|
|
2596
|
+
* 팔레트·트레일러는 개체 식별자이고 GS1 키(SSCC·GRAI)에는 회사 프리픽스가 필요하다. 프리픽스가 없는
|
|
2597
|
+
* 현장은 CBV §8.2.3·§8.2.4 의 길로 간다. 선언이 없으면 **지어내지 않고 `undefined` 를 답한다** —
|
|
2598
|
+
* 호출부가 무엇을 할지 정한다(레거시 경로는 아직 상수를 쓴다).
|
|
2599
|
+
*/
|
|
2600
|
+
declaredObjectId(id) {
|
|
2601
|
+
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
|
2602
|
+
const uri = objectUri(ns, id);
|
|
2603
|
+
if (uri)
|
|
2604
|
+
return uri;
|
|
2605
|
+
}
|
|
2606
|
+
return undefined;
|
|
2607
|
+
}
|
|
2608
|
+
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
2609
|
+
declaredBizTransactionId(id) {
|
|
2610
|
+
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
|
2611
|
+
const uri = bizTransactionUri(ns, id);
|
|
2612
|
+
if (uri)
|
|
2613
|
+
return uri;
|
|
2614
|
+
}
|
|
2615
|
+
return undefined;
|
|
2616
|
+
}
|
|
2581
2617
|
produceMaterials(t) {
|
|
2582
2618
|
/* 도메인이 소유를 주장하면 코어는 비켜선다 — 둘이 만들면 재고가 두 배가 된다. */
|
|
2583
2619
|
if (this.producesOwnOutputs(t.kind))
|
package/dist/kernel.js
CHANGED
|
@@ -57,13 +57,17 @@ export class WmsKernel extends FlowEngine {
|
|
|
57
57
|
/** 입고 도착 — §4 라이프사이클: ASN(PO) → 팔레트 조립 → 수령 → putaway task. */
|
|
58
58
|
onArrival(spec) {
|
|
59
59
|
const dock = this.builtInLocation('dock', 'inbound pallets land here');
|
|
60
|
-
|
|
60
|
+
/* 선언이 있으면 선언에서(CBV §8.2.4) — 없으면 레거시 상수. 가드가 그 상수를 계속 센다. */
|
|
61
|
+
const seq = ++this.epcSeq;
|
|
62
|
+
const epc = this.declaredObjectId(`PAL-${seq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, seq); // 팔레트
|
|
61
63
|
const gtin = this.pickGtin(spec.content.skuMix); // SGTIN idpat = epcClass
|
|
62
64
|
/* 품목 구성이 비어 있으면 **도착을 만들지 않는다** — 무엇이 왔는지 말할 수 없는 입고는 사실이 아니다. */
|
|
63
65
|
if (!gtin)
|
|
64
66
|
return;
|
|
65
67
|
const qty = this.randInt(spec.content.qtyPerLine.min, spec.content.qtyPerLine.max); // 케이스 수(비직렬)
|
|
66
|
-
const
|
|
68
|
+
const poSeq = ++this.poSeq;
|
|
69
|
+
/* 발주 문서 식별자 — 선언된 이름공간 아래(CBV §8.5.5) 또는 선언된 GDTI 문서 타입. */
|
|
70
|
+
const po = this.declaredBizTransactionId(`PO-${poSeq}`) ?? gdtiUri(LEGACY_COMPANY_PREFIX, this.identityDeclaration()?.documentTypes?.purchaseorder ?? '401', poSeq);
|
|
67
71
|
const eventTime = this.now();
|
|
68
72
|
const qtyList = [{ epcClass: gtin, quantity: qty }];
|
|
69
73
|
const poTxn = [{ type: BTT.po, bizTransaction: po }];
|
|
@@ -137,7 +141,8 @@ export class WmsKernel extends FlowEngine {
|
|
|
137
141
|
/** 오더 생성 — 약속(납기·우선순위)은 base `promiseOf` 가 계산한다(도메인마다 다르게 재지 않는다). */
|
|
138
142
|
createSalesOrder(lines, spec) {
|
|
139
143
|
const id = `order-${++this.orderSeq}`;
|
|
140
|
-
const
|
|
144
|
+
const soSeq = ++this.soSeq;
|
|
145
|
+
const so = this.declaredBizTransactionId(`SO-${soSeq}`) ?? gdtiUri(LEGACY_COMPANY_PREFIX, this.identityDeclaration()?.documentTypes?.salesorder ?? '402', soSeq);
|
|
141
146
|
const requested = lines.reduce((s, l) => s + l.qty, 0);
|
|
142
147
|
const order = {
|
|
143
148
|
id, kind: 'outbound', status: 'created', requested, fulfilled: 0, bizTransaction: so,
|
|
@@ -349,7 +354,8 @@ export class WmsKernel extends FlowEngine {
|
|
|
349
354
|
continue;
|
|
350
355
|
/* 클래스 줄을 팔레트로 바꾼다 — 자리 점유는 줄 하나에서 줄 하나로(늘지 않는다). */
|
|
351
356
|
this.items.delete(key);
|
|
352
|
-
const
|
|
357
|
+
const palSeq = ++this.epcSeq;
|
|
358
|
+
const pallet = this.declaredObjectId(`PAL-${palSeq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, palSeq);
|
|
353
359
|
const qtyList = [{ epcClass: m.definitionId, quantity: qty }];
|
|
354
360
|
this.items.set(pallet, { epc: pallet, gtin: m.definitionId, qty, location: at, disposition: DISP.in_progress });
|
|
355
361
|
const eventTime = this.now();
|
|
@@ -366,7 +372,8 @@ export class WmsKernel extends FlowEngine {
|
|
|
366
372
|
창고의 모양이다(도크 단계가 없는 현장이 있다). 그 사실은 화물의 출발 자리로 저널에 남는다. */
|
|
367
373
|
const shipDock = this.locationByType('dock-ship') ?? staging;
|
|
368
374
|
const eventTime = this.now();
|
|
369
|
-
const
|
|
375
|
+
const shpSeq = ++this.epcSeq;
|
|
376
|
+
const shipment = this.declaredObjectId(`SHP-${shpSeq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, shpSeq);
|
|
370
377
|
order.shipmentEpc = shipment;
|
|
371
378
|
const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
|
|
372
379
|
// 패킹: 화물(shipment) ← 팔레트 조립(merge). 팔레트는 출하 DELETE 까지 독립 유지 → consume 없음.
|
package/dist/mes-kernel.d.ts
CHANGED
|
@@ -37,6 +37,13 @@ export declare class MesKernel extends FlowEngine {
|
|
|
37
37
|
* 아니다). 선언이 없는 MES 트윈은 이제 만들 수 없으므로(`loadTwinModel` 이 거절한다) `fabricated` 로
|
|
38
38
|
* 판정될 일이 없다.
|
|
39
39
|
*/
|
|
40
|
+
/**
|
|
41
|
+
* MES 는 **생산 선언의 정체성을 먼저** 쓴다 — `ProductionSpec.identity` 가 먼저 생긴 자리다.
|
|
42
|
+
*
|
|
43
|
+
* 모델 자리(§`TwinModelDef.identity`)로 옮기면 이 override 는 사라진다. 두 자리를 동시에 읽는 것이
|
|
44
|
+
* 아니라 **하나를 고르는** 것이므로 규칙이 갈리지 않는다.
|
|
45
|
+
*/
|
|
46
|
+
protected identityDeclaration(): import("./contract.ts").IdentityDeclaration | undefined;
|
|
40
47
|
protected identityGroundingView(): IdentityGroundingView;
|
|
41
48
|
/**
|
|
42
49
|
* 선언이 말하는 자리 타입이 **이 트윈에 있나** — 로드 시점에 한 번, 모아서 말한다 (2026-08-20).
|
package/dist/mes-kernel.js
CHANGED
|
@@ -99,6 +99,15 @@ export class MesKernel extends FlowEngine {
|
|
|
99
99
|
* 아니다). 선언이 없는 MES 트윈은 이제 만들 수 없으므로(`loadTwinModel` 이 거절한다) `fabricated` 로
|
|
100
100
|
* 판정될 일이 없다.
|
|
101
101
|
*/
|
|
102
|
+
/**
|
|
103
|
+
* MES 는 **생산 선언의 정체성을 먼저** 쓴다 — `ProductionSpec.identity` 가 먼저 생긴 자리다.
|
|
104
|
+
*
|
|
105
|
+
* 모델 자리(§`TwinModelDef.identity`)로 옮기면 이 override 는 사라진다. 두 자리를 동시에 읽는 것이
|
|
106
|
+
* 아니라 **하나를 고르는** 것이므로 규칙이 갈리지 않는다.
|
|
107
|
+
*/
|
|
108
|
+
identityDeclaration() {
|
|
109
|
+
return this.productionSpec?.identity ?? super.identityDeclaration();
|
|
110
|
+
}
|
|
102
111
|
identityGroundingView() {
|
|
103
112
|
return identityGroundingOf(this.productionSpec);
|
|
104
113
|
}
|
package/dist/yms-kernel.js
CHANGED
|
@@ -64,19 +64,25 @@ export class YmsKernel extends FlowEngine {
|
|
|
64
64
|
if (!gate || doors.length === 0)
|
|
65
65
|
return;
|
|
66
66
|
const inbound = kind === 'appointment';
|
|
67
|
-
const
|
|
67
|
+
const trSeq = ++this.epcSeq;
|
|
68
|
+
/* 선언이 있으면 선언에서(CBV §8.2.4) — 없으면 레거시 상수. */
|
|
69
|
+
const epc = this.declaredObjectId(`TRL-${trSeq}`) ?? graiUri(LEGACY_CP, this.identityDeclaration()?.documentTypes?.trailer ?? '10', trSeq);
|
|
68
70
|
this.items.set(epc, { epc, location: gate.id, disposition: DISP.in_progress });
|
|
69
71
|
gate.occupancy++;
|
|
70
72
|
this.emit(objectEvent({ eventTime: this.now(), action: 'ADD', bizStep: YARD_BIZSTEP.arriving, disposition: DISP.in_progress, epcList: [epc], readPoint: gate.id, bizLocation: gate.id }));
|
|
71
73
|
// 인바운드: 화물 적재된 채 도착 → 조립(트레일러←화물) 기록. 아웃바운드: 빈 트레일러(화물은 도크에서 적재).
|
|
72
74
|
if (inbound) {
|
|
73
|
-
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () =>
|
|
75
|
+
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () => {
|
|
76
|
+
const c = ++this.cargoSeq;
|
|
77
|
+
return this.declaredObjectId(`CGO-${c}`) ?? ssccUri(LEGACY_CP, c);
|
|
78
|
+
});
|
|
74
79
|
this.trailerCargo.set(epc, cargo);
|
|
75
80
|
this.aggregate(epc, cargo, { bizStep: YARD_BIZSTEP.arriving, readPoint: gate.id }); // 적재된 채 도착(자식 opaque, 미materialize)
|
|
76
81
|
}
|
|
77
82
|
const door = doors[this.doorRR++ % doors.length];
|
|
78
83
|
const id = `order-${++this.orderSeq}`;
|
|
79
|
-
const
|
|
84
|
+
const apSeq = ++this.soSeq;
|
|
85
|
+
const appt = this.declaredBizTransactionId(`APPT-${apSeq}`) ?? gdtiUri(LEGACY_CP, this.identityDeclaration()?.documentTypes?.appointment ?? '404', apSeq);
|
|
80
86
|
const order = { id, kind, status: 'created', requested: 1, fulfilled: 0, bizTransaction: appt, allocated: [epc], picked: [], dockDoor: door.id,
|
|
81
87
|
/* 내부 스케줄 게이트(시뮬 ms) — 표준 `EarliestStartTime` 과 같은 개념의 우리 단위. */
|
|
82
88
|
windowStartMs: this.clockMs + WINDOW_DELAY_MS,
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -138,6 +138,7 @@ __export(index_exports, {
|
|
|
138
138
|
monteCarloForecast: () => monteCarloForecast,
|
|
139
139
|
monteCarloForecastAsync: () => monteCarloForecastAsync,
|
|
140
140
|
objectEvent: () => objectEvent,
|
|
141
|
+
objectUri: () => objectUri,
|
|
141
142
|
offCalendarAt: () => offCalendarAt,
|
|
142
143
|
offCalendarReasonAt: () => offCalendarReasonAt,
|
|
143
144
|
operationalKindOf: () => operationalKindOf,
|
|
@@ -947,14 +948,20 @@ function parseEpc(uri) {
|
|
|
947
948
|
function gdtiUri(companyPrefix, docType, serial) {
|
|
948
949
|
return `urn:epc:id:gdti:${companyPrefix}.${docType}.${serial}`;
|
|
949
950
|
}
|
|
951
|
+
function objectUri(namespace, objId) {
|
|
952
|
+
return underNamespace(namespace, "obj", objId);
|
|
953
|
+
}
|
|
950
954
|
function bizTransactionUri(namespace, transId) {
|
|
955
|
+
return underNamespace(namespace, "bt", transId);
|
|
956
|
+
}
|
|
957
|
+
function underNamespace(namespace, marker, id) {
|
|
951
958
|
const ns = namespace?.trim();
|
|
952
959
|
if (!ns) return void 0;
|
|
953
|
-
const
|
|
954
|
-
if (!
|
|
955
|
-
if (/^https?:\/\/[^/\s]+/.test(ns)) return `${ns.replace(/\/+$/, "")}
|
|
960
|
+
const v = String(id);
|
|
961
|
+
if (!v || v.includes("/") || v.includes(":")) return void 0;
|
|
962
|
+
if (/^https?:\/\/[^/\s]+/.test(ns)) return `${ns.replace(/\/+$/, "")}/${marker}/${v}`;
|
|
956
963
|
if (/^urn:epc(global)?:/.test(ns)) return void 0;
|
|
957
|
-
if (/^urn:[^:\s]+/.test(ns)) return `${ns.replace(/:+$/, "")}
|
|
964
|
+
if (/^urn:[^:\s]+/.test(ns)) return `${ns.replace(/:+$/, "")}:${marker}:${v}`;
|
|
958
965
|
return void 0;
|
|
959
966
|
}
|
|
960
967
|
function header(type, eventTime, bizStep, opts) {
|
|
@@ -2526,9 +2533,20 @@ var TWIN_AXES = [
|
|
|
2526
2533
|
* 아직 세우지 않은 것: **요금 구간**(TariffPeriod)과 **원단위**(EnPI). 둘 다 아직 아무도 만들지
|
|
2527
2534
|
* 않는다 — 선언만 하면 개념 지도가 언제나 0 을 보여 주고, 그것은 결손처럼 읽힌다(§10 7단계의 일).
|
|
2528
2535
|
*/
|
|
2536
|
+
/*
|
|
2537
|
+
* **무엇이 이 항목을 가리키나** — `idField` 가 말한다.
|
|
2538
|
+
*
|
|
2539
|
+
* 수요 구간에는 `id` 가 없다. 그것이 결함은 아니다: 구간의 정체성은 **그 구간이 언제 시작했나**이고
|
|
2540
|
+
* (요금의 알갱이가 그 시각으로 정해진다) 커널도 그것으로 키를 만든다(`contract-projected-over:<startMs>`).
|
|
2541
|
+
*
|
|
2542
|
+
* 그런데 소비처가 `id`·`key`·`gtin` 을 **짐작**하고 있어서, 화면이 여섯 구간 모두를 「식별자 없음 —
|
|
2543
|
+
* 참조할 수 없는 항목」으로 보였다. 값은 실재하는데 가리킬 수 없다고 말한 것이다. 짐작을 없애고
|
|
2544
|
+
* 선언이 답하게 한다 — 축이 사는 자리(`path`)를 선언하는 것과 같은 이유다.
|
|
2545
|
+
*/
|
|
2529
2546
|
{
|
|
2530
2547
|
axis: "demandWindows",
|
|
2531
2548
|
path: "energy.closed",
|
|
2549
|
+
idField: "startMs",
|
|
2532
2550
|
label: "twin.axis.demandWindows",
|
|
2533
2551
|
kind: "instance",
|
|
2534
2552
|
source: "state",
|
|
@@ -5430,6 +5448,40 @@ var FlowEngine = class {
|
|
|
5430
5448
|
identityGroundingView() {
|
|
5431
5449
|
return identityGroundingOf(void 0);
|
|
5432
5450
|
}
|
|
5451
|
+
/**
|
|
5452
|
+
* **이 트윈의 정체성 선언을 읽는 한 곳.**
|
|
5453
|
+
*
|
|
5454
|
+
* 모델이 갖는다(§`TwinModelDef.identity`) — 정체성은 생산과 무관하고, 생산하지 않는 트윈도 팔레트·
|
|
5455
|
+
* 트레일러를 식별한다. 선언을 든 커널이 자기 것을 먼저 쓰도록 override 할 수 있다(MES 가 그렇게 한다:
|
|
5456
|
+
* `ProductionSpec.identity` 가 먼저 생긴 자리다).
|
|
5457
|
+
*
|
|
5458
|
+
* 규칙을 두 곳에 적지 않기 위해 조립하는 쪽은 전부 이 함수를 지난다.
|
|
5459
|
+
*/
|
|
5460
|
+
identityDeclaration() {
|
|
5461
|
+
return this.boardDef?.identity;
|
|
5462
|
+
}
|
|
5463
|
+
/**
|
|
5464
|
+
* 선언된 이름공간 아래의 **개체 식별자** — 없으면 답하지 않는다.
|
|
5465
|
+
*
|
|
5466
|
+
* 팔레트·트레일러는 개체 식별자이고 GS1 키(SSCC·GRAI)에는 회사 프리픽스가 필요하다. 프리픽스가 없는
|
|
5467
|
+
* 현장은 CBV §8.2.3·§8.2.4 의 길로 간다. 선언이 없으면 **지어내지 않고 `undefined` 를 답한다** —
|
|
5468
|
+
* 호출부가 무엇을 할지 정한다(레거시 경로는 아직 상수를 쓴다).
|
|
5469
|
+
*/
|
|
5470
|
+
declaredObjectId(id) {
|
|
5471
|
+
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
|
5472
|
+
const uri = objectUri(ns, id);
|
|
5473
|
+
if (uri) return uri;
|
|
5474
|
+
}
|
|
5475
|
+
return void 0;
|
|
5476
|
+
}
|
|
5477
|
+
/** 선언된 이름공간 아래의 **거래 문서 식별자**(발주·주문·어포인트먼트) — 없으면 답하지 않는다. */
|
|
5478
|
+
declaredBizTransactionId(id) {
|
|
5479
|
+
for (const ns of this.identityDeclaration()?.namespaces ?? []) {
|
|
5480
|
+
const uri = bizTransactionUri(ns, id);
|
|
5481
|
+
if (uri) return uri;
|
|
5482
|
+
}
|
|
5483
|
+
return void 0;
|
|
5484
|
+
}
|
|
5433
5485
|
produceMaterials(t) {
|
|
5434
5486
|
if (this.producesOwnOutputs(t.kind)) return;
|
|
5435
5487
|
const made = (this.operationSpecs.get(t.kind)?.materialSpecification ?? []).filter((m) => m.use === "produced");
|
|
@@ -6073,11 +6125,13 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6073
6125
|
/** 입고 도착 — §4 라이프사이클: ASN(PO) → 팔레트 조립 → 수령 → putaway task. */
|
|
6074
6126
|
onArrival(spec) {
|
|
6075
6127
|
const dock = this.builtInLocation("dock", "inbound pallets land here");
|
|
6076
|
-
const
|
|
6128
|
+
const seq = ++this.epcSeq;
|
|
6129
|
+
const epc = this.declaredObjectId(`PAL-${seq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, seq);
|
|
6077
6130
|
const gtin = this.pickGtin(spec.content.skuMix);
|
|
6078
6131
|
if (!gtin) return;
|
|
6079
6132
|
const qty = this.randInt(spec.content.qtyPerLine.min, spec.content.qtyPerLine.max);
|
|
6080
|
-
const
|
|
6133
|
+
const poSeq = ++this.poSeq;
|
|
6134
|
+
const po = this.declaredBizTransactionId(`PO-${poSeq}`) ?? gdtiUri(LEGACY_COMPANY_PREFIX, this.identityDeclaration()?.documentTypes?.purchaseorder ?? "401", poSeq);
|
|
6081
6135
|
const eventTime = this.now();
|
|
6082
6136
|
const qtyList = [{ epcClass: gtin, quantity: qty }];
|
|
6083
6137
|
const poTxn = [{ type: BTT.po, bizTransaction: po }];
|
|
@@ -6148,7 +6202,8 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6148
6202
|
/** 오더 생성 — 약속(납기·우선순위)은 base `promiseOf` 가 계산한다(도메인마다 다르게 재지 않는다). */
|
|
6149
6203
|
createSalesOrder(lines, spec) {
|
|
6150
6204
|
const id = `order-${++this.orderSeq}`;
|
|
6151
|
-
const
|
|
6205
|
+
const soSeq = ++this.soSeq;
|
|
6206
|
+
const so = this.declaredBizTransactionId(`SO-${soSeq}`) ?? gdtiUri(LEGACY_COMPANY_PREFIX, this.identityDeclaration()?.documentTypes?.salesorder ?? "402", soSeq);
|
|
6152
6207
|
const requested = lines.reduce((s, l) => s + l.qty, 0);
|
|
6153
6208
|
const order = {
|
|
6154
6209
|
id,
|
|
@@ -6321,7 +6376,8 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6321
6376
|
const qty = row.qty ?? 0;
|
|
6322
6377
|
if (qty <= 0) continue;
|
|
6323
6378
|
this.items.delete(key);
|
|
6324
|
-
const
|
|
6379
|
+
const palSeq = ++this.epcSeq;
|
|
6380
|
+
const pallet = this.declaredObjectId(`PAL-${palSeq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, palSeq);
|
|
6325
6381
|
const qtyList = [{ epcClass: m.definitionId, quantity: qty }];
|
|
6326
6382
|
this.items.set(pallet, { epc: pallet, gtin: m.definitionId, qty, location: at, disposition: DISP.in_progress });
|
|
6327
6383
|
const eventTime = this.now();
|
|
@@ -6335,7 +6391,8 @@ var WmsKernel = class extends FlowEngine {
|
|
|
6335
6391
|
finalizeOrder(order, staging) {
|
|
6336
6392
|
const shipDock = this.locationByType("dock-ship") ?? staging;
|
|
6337
6393
|
const eventTime = this.now();
|
|
6338
|
-
const
|
|
6394
|
+
const shpSeq = ++this.epcSeq;
|
|
6395
|
+
const shipment = this.declaredObjectId(`SHP-${shpSeq}`) ?? ssccUri(LEGACY_COMPANY_PREFIX, shpSeq);
|
|
6339
6396
|
order.shipmentEpc = shipment;
|
|
6340
6397
|
const soTxn = [{ type: BTT.so, bizTransaction: order.bizTransaction }];
|
|
6341
6398
|
this.aggregate(shipment, order.picked.slice(), { bizStep: BIZSTEP.packing, readPoint: staging.id, bizLocation: staging.id });
|
|
@@ -6398,18 +6455,23 @@ var YmsKernel = class extends FlowEngine {
|
|
|
6398
6455
|
const doors = [...this.locations.values()].filter((n) => n.type === "dock-door").sort((a, b) => a.id.localeCompare(b.id));
|
|
6399
6456
|
if (!gate || doors.length === 0) return;
|
|
6400
6457
|
const inbound = kind === "appointment";
|
|
6401
|
-
const
|
|
6458
|
+
const trSeq = ++this.epcSeq;
|
|
6459
|
+
const epc = this.declaredObjectId(`TRL-${trSeq}`) ?? graiUri(LEGACY_CP, this.identityDeclaration()?.documentTypes?.trailer ?? "10", trSeq);
|
|
6402
6460
|
this.items.set(epc, { epc, location: gate.id, disposition: DISP.in_progress });
|
|
6403
6461
|
gate.occupancy++;
|
|
6404
6462
|
this.emit(objectEvent({ eventTime: this.now(), action: "ADD", bizStep: YARD_BIZSTEP.arriving, disposition: DISP.in_progress, epcList: [epc], readPoint: gate.id, bizLocation: gate.id }));
|
|
6405
6463
|
if (inbound) {
|
|
6406
|
-
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () =>
|
|
6464
|
+
const cargo = Array.from({ length: CARGO_PER_TRAILER }, () => {
|
|
6465
|
+
const c = ++this.cargoSeq;
|
|
6466
|
+
return this.declaredObjectId(`CGO-${c}`) ?? ssccUri(LEGACY_CP, c);
|
|
6467
|
+
});
|
|
6407
6468
|
this.trailerCargo.set(epc, cargo);
|
|
6408
6469
|
this.aggregate(epc, cargo, { bizStep: YARD_BIZSTEP.arriving, readPoint: gate.id });
|
|
6409
6470
|
}
|
|
6410
6471
|
const door = doors[this.doorRR++ % doors.length];
|
|
6411
6472
|
const id = `order-${++this.orderSeq}`;
|
|
6412
|
-
const
|
|
6473
|
+
const apSeq = ++this.soSeq;
|
|
6474
|
+
const appt = this.declaredBizTransactionId(`APPT-${apSeq}`) ?? gdtiUri(LEGACY_CP, this.identityDeclaration()?.documentTypes?.appointment ?? "404", apSeq);
|
|
6413
6475
|
const order = {
|
|
6414
6476
|
id,
|
|
6415
6477
|
kind,
|
|
@@ -6602,6 +6664,15 @@ var MesKernel = class extends FlowEngine {
|
|
|
6602
6664
|
* 아니다). 선언이 없는 MES 트윈은 이제 만들 수 없으므로(`loadTwinModel` 이 거절한다) `fabricated` 로
|
|
6603
6665
|
* 판정될 일이 없다.
|
|
6604
6666
|
*/
|
|
6667
|
+
/**
|
|
6668
|
+
* MES 는 **생산 선언의 정체성을 먼저** 쓴다 — `ProductionSpec.identity` 가 먼저 생긴 자리다.
|
|
6669
|
+
*
|
|
6670
|
+
* 모델 자리(§`TwinModelDef.identity`)로 옮기면 이 override 는 사라진다. 두 자리를 동시에 읽는 것이
|
|
6671
|
+
* 아니라 **하나를 고르는** 것이므로 규칙이 갈리지 않는다.
|
|
6672
|
+
*/
|
|
6673
|
+
identityDeclaration() {
|
|
6674
|
+
return this.productionSpec?.identity ?? super.identityDeclaration();
|
|
6675
|
+
}
|
|
6605
6676
|
identityGroundingView() {
|
|
6606
6677
|
return identityGroundingOf(this.productionSpec);
|
|
6607
6678
|
}
|
|
@@ -8489,6 +8560,7 @@ function retiredVocabularyIn(line) {
|
|
|
8489
8560
|
monteCarloForecast,
|
|
8490
8561
|
monteCarloForecastAsync,
|
|
8491
8562
|
objectEvent,
|
|
8563
|
+
objectUri,
|
|
8492
8564
|
offCalendarAt,
|
|
8493
8565
|
offCalendarReasonAt,
|
|
8494
8566
|
operationalKindOf,
|
package/package.json
CHANGED