@operato/twin-kernel 0.4.3 → 0.6.0
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 +21 -7
- package/dist/domain-catalog.d.ts +124 -0
- package/dist/domain-catalog.js +88 -0
- package/dist/domain-definition.js +1 -1
- package/dist/event-journal.d.ts +3 -3
- package/dist/flow-engine.d.ts +4 -4
- package/dist/flow-engine.js +2 -2
- package/dist/observed-reducer.d.ts +3 -3
- package/dist-cjs/index.cjs +199 -2
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -362,7 +362,7 @@ export declare function conversionFactorOf(def: MaterialDefinition | undefined,
|
|
|
362
362
|
* 되풀이를 간단히 적기 위한 선택이고, 그러면 **어느 기준의 06:00 인가**가 반드시 필요하다 —
|
|
363
363
|
* Rosarito(UTC−7)의 06:00 을 UTC 로 읽으면 7시간이 틀린다.
|
|
364
364
|
*
|
|
365
|
-
* 그래서 `
|
|
365
|
+
* 그래서 `TwinModelDef.utcOffsetMinutes` 가 그 기준이다. **선언하지 않으면 UTC 로 읽고, 그 사실을 여기
|
|
366
366
|
* 밝힌다**(조용히 가정하지 않기 위해). 프레임워크에는 이미 테넌트 시간대(`Domain.timezone`)와
|
|
367
367
|
* 교대→절대구간 변환(`@things-factory/work-shift` `work-shift-range`, moment-timezone)이 있으므로,
|
|
368
368
|
* **기준을 푸는 일은 호스트의 몫**이다(커널은 zero-dep — 시간대 데이터를 들 수 없다).
|
|
@@ -1267,14 +1267,14 @@ export type Unsubscribe = () => void;
|
|
|
1267
1267
|
* 이 함수를 거치지 않고 `def.locations` 를 직접 읽는 코드를 두지 않는다 — 하나라도 남으면 그 경로에서만
|
|
1268
1268
|
* 옛 보드의 자리가 조용히 사라진다(빈 배열 = 자리 없는 트윈 = 아무 일도 일어나지 않는다).
|
|
1269
1269
|
*/
|
|
1270
|
-
export declare function readBoardLocations(def:
|
|
1270
|
+
export declare function readBoardLocations(def: TwinModelDef | (Record<string, unknown> & {
|
|
1271
1271
|
locations?: unknown;
|
|
1272
1272
|
nodes?: unknown;
|
|
1273
|
-
})):
|
|
1274
|
-
export declare function readBoardEquipment(def:
|
|
1273
|
+
})): TwinModelDef['locations'];
|
|
1274
|
+
export declare function readBoardEquipment(def: TwinModelDef | Record<string, unknown>): TwinModelDef['equipment'];
|
|
1275
1275
|
/** 저장된 보드의 반복사용 자산 — 설비와 같은 정규화를 거친다. */
|
|
1276
|
-
export declare function readBoardAssets(def:
|
|
1277
|
-
export interface
|
|
1276
|
+
export declare function readBoardAssets(def: TwinModelDef | Record<string, unknown>): NonNullable<TwinModelDef['assets']>;
|
|
1277
|
+
export interface TwinModelDef {
|
|
1278
1278
|
/** parallelism = 동시 처리 수(LocationState.parallelism 참조). capacity 는 저장 용량. */
|
|
1279
1279
|
locations: {
|
|
1280
1280
|
id: string;
|
|
@@ -1391,6 +1391,20 @@ export interface BoardDef {
|
|
|
1391
1391
|
* 유통가공(VAS)은 비직렬 클래스+수량이 맞는 모양이므로 코어의 일반 경로를 쓴다.
|
|
1392
1392
|
*/
|
|
1393
1393
|
productionSpec?: ProductionSpec;
|
|
1394
|
+
/**
|
|
1395
|
+
* 공정 명세 — **ISA-95 `OperationsSegment`.** 이 현장에서 각 작업이 어디서·무엇으로·얼마나 걸리나.
|
|
1396
|
+
*
|
|
1397
|
+
* ── 왜 이제야 선언하나 (2026-08-08) ──────────────────────────────────────────
|
|
1398
|
+
* 이 자리는 **이미 쓰이고 있었다.** 마스터 인제스트가 `board.operations` 로 통과시키고 호스트가
|
|
1399
|
+
* `applyOperations` 로 커널에 싣는다. 그런데 `TwinModelDef` 는 그것을 말한 적이 없다.
|
|
1400
|
+
*
|
|
1401
|
+
* 바로 위 `productionSpec` 이 겪은 것과 **같은 일**이다 — "계약에 없으니 아무도 이 자리를 발견할
|
|
1402
|
+
* 수 없었다. 타입이 말해 주지 않는 필드는 없는 필드다." 그때 배운 것을 여기 적용한다.
|
|
1403
|
+
*
|
|
1404
|
+
* 소비 경로가 둘인 것은 그대로 둔다(여기와 `productionSpec.definition.operations`) — 후자는
|
|
1405
|
+
* 라우트의 단계를 해소할 때 읽힌다. 집을 하나로 모으는 것은 별건이고, 그 전에 **선언부터** 한다.
|
|
1406
|
+
*/
|
|
1407
|
+
operations?: import('./domain-definition.ts').OperationDef[];
|
|
1394
1408
|
}
|
|
1395
1409
|
/**
|
|
1396
1410
|
* 생산 선언 — 도메인 정의(ISA-95 operations + BOM)와, 그 추상 자재 키를 실제 GS1 식별자로 잇는 바인딩.
|
|
@@ -1408,7 +1422,7 @@ export interface ProductionSpec {
|
|
|
1408
1422
|
recipeKey?: string;
|
|
1409
1423
|
}
|
|
1410
1424
|
export interface TwinKernel {
|
|
1411
|
-
|
|
1425
|
+
loadTwinModel(def: TwinModelDef): void;
|
|
1412
1426
|
getSnapshot(): StateSnapshot;
|
|
1413
1427
|
onEvent(handler: EventHandler): Unsubscribe;
|
|
1414
1428
|
dispatch(cmd: Command): CommandAck;
|
package/dist/domain-catalog.d.ts
CHANGED
|
@@ -28,6 +28,130 @@ export interface DomainProfileInfo {
|
|
|
28
28
|
locationTypes: readonly string[];
|
|
29
29
|
}
|
|
30
30
|
export declare const DOMAIN_CATALOG: Record<DomainSystem, DomainProfileInfo>;
|
|
31
|
+
export interface TwinAxisInfo {
|
|
32
|
+
/**
|
|
33
|
+
* 축의 이름 — 화면·관계 선언이 쓰는 키. 최상위 축은 `TwinModelDef` 의 키 **그대로**다
|
|
34
|
+
* (저장·계약의 이름과 갈라지면 그 순간 방언이 생긴다).
|
|
35
|
+
*/
|
|
36
|
+
axis: string;
|
|
37
|
+
/**
|
|
38
|
+
* board 안에서의 자리. 최상위면 `axis` 와 같고, 중첩이면 경로다
|
|
39
|
+
* (`productionSpec.definition.recipes`).
|
|
40
|
+
*
|
|
41
|
+
* **레시피·라우트는 저장상 `productionSpec` 안에 있지만 개념으로는 1급**이다 — 사람은
|
|
42
|
+
* "레시피" 를 찾지 "생산 정의 안의 레시피" 를 찾지 않는다. 저장 위치가 개념을 가두면
|
|
43
|
+
* 화면이 저장 구조를 흉내 내게 되고, 그건 `ADR-0032`(board 는 캐시) 뒤에 무의미해진다.
|
|
44
|
+
*/
|
|
45
|
+
path: string;
|
|
46
|
+
/** i18n 키 — 사람 언어는 표현 계층이 렌더한다(L2). */
|
|
47
|
+
label: string;
|
|
48
|
+
/** 표준 온톨로지 투영. 표준에 자리가 없으면 **빈 객체**(숨기지 않는다). */
|
|
49
|
+
standardClass: {
|
|
50
|
+
epcis?: string;
|
|
51
|
+
isa95?: string;
|
|
52
|
+
iso55000?: string;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* 표준이 **정의(class)와 개체(instance)를 가른다.** 같은 자재라도 `MaterialDefinition` 과
|
|
56
|
+
* `MaterialLot` 은 다른 것이다. 화면이 둘을 같은 칸에 담지 않도록 선언에 싣는다.
|
|
57
|
+
* `spec` 은 자원이 아니라 **명세**(공정·생산 정의).
|
|
58
|
+
*/
|
|
59
|
+
kind: 'instance' | 'class' | 'spec';
|
|
60
|
+
/** 이 축의 항목이 카탈로그 타입을 갖는가 — 있으면 `types` 와 이어진다(자리·설비만). */
|
|
61
|
+
typeRole?: 'location' | 'equipment';
|
|
62
|
+
}
|
|
63
|
+
/** board 축 전체 — 인스펙션의 **개념 목록**이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
64
|
+
export declare const TWIN_AXES: TwinAxisInfo[];
|
|
65
|
+
export type TwinRelationTarget = {
|
|
66
|
+
kind: 'axis';
|
|
67
|
+
axis: string;
|
|
68
|
+
} | {
|
|
69
|
+
kind: 'type';
|
|
70
|
+
role: 'location' | 'equipment';
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* **커널 밖**을 가리키는 참조. 커널은 대상의 이름만 말하고 해소는 호스트가 한다.
|
|
74
|
+
*
|
|
75
|
+
* 실물: `locations[].parentId` 는 자리가 아니라 **공간의 구역**을 가리킨다. 마스터에서는
|
|
76
|
+
* 구역도 자리와 같은 목록에 있다가(`role: 'area'`) 인제스트가 갈라 `TwinArea` 로 보내기 때문에,
|
|
77
|
+
* board 안에는 **대상이 없는 참조**가 남는다. 이것을 `axis: 'locations'` 로 적으면 선언이
|
|
78
|
+
* 거짓말을 하고 지도에 없는 선이 생긴다 — 실제로 그렇게 적었다가 실 데이터로 걸어 보고 잡았다.
|
|
79
|
+
*
|
|
80
|
+
* 커널이 `TwinArea` 를 알 수는 없다(공간은 호스트 개념). 그래서 **모른다는 사실을 선언한다.**
|
|
81
|
+
*/
|
|
82
|
+
| {
|
|
83
|
+
kind: 'external';
|
|
84
|
+
entity: string;
|
|
85
|
+
};
|
|
86
|
+
export interface TwinRelationInfo {
|
|
87
|
+
/** 출발 축. */
|
|
88
|
+
from: string;
|
|
89
|
+
/**
|
|
90
|
+
* 그 축의 항목에서 대상 키를 꺼내는 경로. `[]` 는 **배열 펼침**이다.
|
|
91
|
+
* 예: `route` · `steps[]` · `inputs[].material`.
|
|
92
|
+
*/
|
|
93
|
+
field: string;
|
|
94
|
+
target: TwinRelationTarget;
|
|
95
|
+
/** 관계의 이름(i18n 키) — 화면의 선과 이웃 묶음에 붙는다. */
|
|
96
|
+
via: string;
|
|
97
|
+
/** 없을 수 있는 참조인가. `false` 인데 비면 **끊어진 참조**로 보고한다. */
|
|
98
|
+
optional?: boolean;
|
|
99
|
+
}
|
|
100
|
+
/** 관계 전체 — 지도의 선과 항목의 이웃이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
101
|
+
export declare const TWIN_RELATIONS: TwinRelationInfo[];
|
|
102
|
+
export type TwinObservationKind =
|
|
103
|
+
/** 여러 번 일어난 일의 **퍼짐**. 중앙값 + p10·p90 + 표본 수. 예: 공정 소요, 체류시간. */
|
|
104
|
+
'distribution'
|
|
105
|
+
/** 어느 시점의 **차 있음**. 현재 · 최대 · 평균. 예: 자리 점유, 재고. */
|
|
106
|
+
| 'level'
|
|
107
|
+
/** 창(window)당 **비(比)**. 값 + 추세. 예: 가동률, 처리량. */
|
|
108
|
+
| 'rate'
|
|
109
|
+
/** 시간축 위의 **연속 계측**. 스파크라인 + 구간 통계. 예: 부하곡선, 온도. */
|
|
110
|
+
| 'series'
|
|
111
|
+
/** 벌어진 **횟수**. 누계 + 기간. 예: 고장 횟수, 완료 건수. */
|
|
112
|
+
| 'count';
|
|
113
|
+
/**
|
|
114
|
+
* 한 축의 항목이 갖는 **볼 수 있는 속성** 하나.
|
|
115
|
+
*
|
|
116
|
+
* 선언값(원본이 말한 것)과 관측값(트윈이 잰 것)은 **둘 다 없을 수 있다.**
|
|
117
|
+
* · 선언만 있음 — 원본이 말했는데 아직 못 쟀다(표본 부족)
|
|
118
|
+
* · 관측만 있음 — 원본이 침묵한 자리를 트윈이 채우고 있다. **트윈을 가진 이유가 여기 있다**
|
|
119
|
+
* · 둘 다 있고 어긋남 — 원본의 표준값이 이 현장과 다르다는 **발견**
|
|
120
|
+
* 그래서 어느 쪽도 없다고 숨기지 않는다. 없으면 `—` 로 낸다.
|
|
121
|
+
*/
|
|
122
|
+
export interface TwinPropertyInfo {
|
|
123
|
+
/** 어느 축의 속성인가. */
|
|
124
|
+
axis: string;
|
|
125
|
+
/** 속성 키 — 화면·질의가 쓰는 이름. */
|
|
126
|
+
key: string;
|
|
127
|
+
label: string;
|
|
128
|
+
kind: TwinObservationKind;
|
|
129
|
+
/**
|
|
130
|
+
* 선언값이 항목의 어느 필드에 있나. **없으면 원본이 말하지 않는 속성**이다
|
|
131
|
+
* (그래도 관측은 있을 수 있다 — 위 두 번째 경우).
|
|
132
|
+
*/
|
|
133
|
+
declaredField?: string;
|
|
134
|
+
/**
|
|
135
|
+
* 이 관측을 **누가 내나.** 커널·호스트의 실제 산출 이름을 적는다. 지어낸 관측을 선언하지
|
|
136
|
+
* 않기 위한 표식이다 — 없으면 아직 아무도 안 낸다는 뜻이고, 화면은 `—` 를 낸다.
|
|
137
|
+
*/
|
|
138
|
+
observedBy?: string;
|
|
139
|
+
/** 단위(UN/CEFACT 공통코드 또는 `ms`·`ratio`). **단위 없는 물리량은 쓰지 않는다**(계약의 규율). */
|
|
140
|
+
uom?: string;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 오늘 **실제로 짝이 서는 것**만 선언한다. 그럴듯한 속성을 미리 적어 두면 화면이 빈칸을 줄줄이
|
|
144
|
+
* 내고, 그건 "아직 안 쟀다" 가 아니라 "이 트윈은 부실하다" 로 읽힌다.
|
|
145
|
+
* `series` 는 아직 **아무도 내지 않는다** — EMS 가 붙을 때 생산자와 함께 선언한다.
|
|
146
|
+
*/
|
|
147
|
+
export declare const TWIN_PROPERTIES: TwinPropertyInfo[];
|
|
148
|
+
/** 이 축이 보여 줄 속성들 — 없으면 빈 배열(속성 없는 축이 정상이다). */
|
|
149
|
+
export declare const propertiesOf: (axis: string) => TwinPropertyInfo[];
|
|
150
|
+
/** 이 축에서 나가는 관계 · 이 축으로 들어오는 관계 — 항목 패널의 "이어진 것" 양방향. */
|
|
151
|
+
export declare const relationsFrom: (axis: string) => TwinRelationInfo[];
|
|
152
|
+
export declare const relationsTo: (axis: string) => TwinRelationInfo[];
|
|
153
|
+
/** 축 이름 → 서술. 모르는 축이면 `undefined` — 화면은 그것을 "미선언" 으로 낸다(숨기지 않는다). */
|
|
154
|
+
export declare function axisInfo(axis: string): TwinAxisInfo | undefined;
|
|
31
155
|
export declare const DOMAIN_SYSTEMS: DomainSystem[];
|
|
32
156
|
/** 타입 키 → 능력 프로파일(커널 SSOT). 호스트가 라이브 페이로드에 투영, 컴포넌트가 능력을 렌더. */
|
|
33
157
|
export declare function capabilitiesForType(system: DomainSystem, typeKey: string): CapabilityKey[];
|
package/dist/domain-catalog.js
CHANGED
|
@@ -8,6 +8,94 @@ export const DOMAIN_CATALOG = {
|
|
|
8
8
|
yms: { system: 'yms', label: 'twin.system.yms', types: YMS_TYPES, locationTypes: locationKeys(YMS_TYPES) },
|
|
9
9
|
mes: { system: 'mes', label: 'twin.system.mes', types: MES_TYPES, locationTypes: locationKeys(MES_TYPES) }
|
|
10
10
|
};
|
|
11
|
+
/** board 축 전체 — 인스펙션의 **개념 목록**이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
12
|
+
export const TWIN_AXES = [
|
|
13
|
+
{ axis: 'locations', path: 'locations', label: 'twin.axis.locations', kind: 'instance', typeRole: 'location',
|
|
14
|
+
standardClass: { epcis: 'SGLN' } },
|
|
15
|
+
{ axis: 'equipment', path: 'equipment', label: 'twin.axis.equipment', kind: 'instance', typeRole: 'equipment',
|
|
16
|
+
standardClass: { isa95: 'Equipment', iso55000: 'Asset', epcis: 'GIAI' } },
|
|
17
|
+
{ axis: 'persons', path: 'persons', label: 'twin.axis.persons', kind: 'instance',
|
|
18
|
+
standardClass: { isa95: 'Person' } },
|
|
19
|
+
{ axis: 'assets', path: 'assets', label: 'twin.axis.assets', kind: 'instance',
|
|
20
|
+
standardClass: { isa95: 'PhysicalAsset', iso55000: 'Asset' } },
|
|
21
|
+
{ axis: 'personnelClasses', path: 'personnelClasses', label: 'twin.axis.personnelClasses', kind: 'class',
|
|
22
|
+
standardClass: { isa95: 'PersonnelClass' } },
|
|
23
|
+
{ axis: 'equipmentClasses', path: 'equipmentClasses', label: 'twin.axis.equipmentClasses', kind: 'class',
|
|
24
|
+
standardClass: { isa95: 'EquipmentClass' } },
|
|
25
|
+
{ axis: 'assetClasses', path: 'assetClasses', label: 'twin.axis.assetClasses', kind: 'class',
|
|
26
|
+
standardClass: { isa95: 'PhysicalAssetClass' } },
|
|
27
|
+
{ axis: 'materialDefinitions', path: 'materialDefinitions', label: 'twin.axis.materialDefinitions', kind: 'class',
|
|
28
|
+
standardClass: { isa95: 'MaterialDefinition' } },
|
|
29
|
+
{ axis: 'materialClasses', path: 'materialClasses', label: 'twin.axis.materialClasses', kind: 'class',
|
|
30
|
+
standardClass: { isa95: 'MaterialClass' } },
|
|
31
|
+
{ axis: 'operations', path: 'operations', label: 'twin.axis.operations', kind: 'spec',
|
|
32
|
+
standardClass: { isa95: 'OperationsSegment' } },
|
|
33
|
+
{ axis: 'productionSpec', path: 'productionSpec', label: 'twin.axis.productionSpec', kind: 'spec',
|
|
34
|
+
standardClass: { isa95: 'OperationsSegment' } },
|
|
35
|
+
/*
|
|
36
|
+
* 아래 셋은 저장상 `productionSpec.definition` 안에 있지만 **개념으로는 1급**이다.
|
|
37
|
+
* `materials` 가 최상위 `materialDefinitions` 와 **둘 다** 있는 것은 지금 상태 그대로다 —
|
|
38
|
+
* 전자는 도메인 정의의 가벼운 목록(`{key,label}`), 후자는 ISA-95 풍부형이다. 같은 것의 집이
|
|
39
|
+
* 둘이라는 사실을 **선언이 드러낸다**(감추면 화면이 어느 쪽을 보는지 아무도 모른다).
|
|
40
|
+
*/
|
|
41
|
+
{ axis: 'recipes', path: 'productionSpec.definition.recipes', label: 'twin.axis.recipes', kind: 'spec',
|
|
42
|
+
standardClass: { isa95: 'OperationsSegment' } },
|
|
43
|
+
{ axis: 'routes', path: 'productionSpec.definition.routes', label: 'twin.axis.routes', kind: 'spec',
|
|
44
|
+
standardClass: { isa95: 'OperationsSegment' } },
|
|
45
|
+
{ axis: 'materials', path: 'productionSpec.definition.materials', label: 'twin.axis.materials', kind: 'class',
|
|
46
|
+
standardClass: { isa95: 'MaterialDefinition' } }
|
|
47
|
+
];
|
|
48
|
+
/** 관계 전체 — 지도의 선과 항목의 이웃이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
49
|
+
export const TWIN_RELATIONS = [
|
|
50
|
+
/* 자리가 속한 구역 — **board 밖**(호스트의 `TwinArea`). 해소는 호스트가 한다. */
|
|
51
|
+
{ from: 'locations', field: 'parentId', target: { kind: 'external', entity: 'space.area' }, via: 'twin.rel.area', optional: true },
|
|
52
|
+
/* 자원이 사는 자리 */
|
|
53
|
+
{ from: 'equipment', field: 'homeLocation', target: { kind: 'axis', axis: 'locations' }, via: 'twin.rel.home', optional: true },
|
|
54
|
+
{ from: 'persons', field: 'homeLocation', target: { kind: 'axis', axis: 'locations' }, via: 'twin.rel.home', optional: true },
|
|
55
|
+
{ from: 'assets', field: 'homeLocation', target: { kind: 'axis', axis: 'locations' }, via: 'twin.rel.home', optional: true },
|
|
56
|
+
/* 자원의 등급 — 설비만 `kind`(타입)로 가고 사람·자산은 등급 목록으로 간다. 표준의 비대칭 그대로. */
|
|
57
|
+
{ from: 'persons', field: 'personnelClassIds[]', target: { kind: 'axis', axis: 'personnelClasses' }, via: 'twin.rel.class', optional: true },
|
|
58
|
+
{ from: 'assets', field: 'assetClassIds[]', target: { kind: 'axis', axis: 'assetClasses' }, via: 'twin.rel.class', optional: true },
|
|
59
|
+
{ from: 'equipment', field: 'kind', target: { kind: 'type', role: 'equipment' }, via: 'twin.rel.type' },
|
|
60
|
+
{ from: 'locations', field: 'type', target: { kind: 'type', role: 'location' }, via: 'twin.rel.type' },
|
|
61
|
+
/* 생산 — 레시피에서 시작해 라우트·공정·품목으로 퍼진다 */
|
|
62
|
+
{ from: 'recipes', field: 'route', target: { kind: 'axis', axis: 'routes' }, via: 'twin.rel.route', optional: true },
|
|
63
|
+
{ from: 'recipes', field: 'inputs[].material', target: { kind: 'axis', axis: 'materials' }, via: 'twin.rel.input' },
|
|
64
|
+
{ from: 'recipes', field: 'outputs[].material', target: { kind: 'axis', axis: 'materials' }, via: 'twin.rel.output' },
|
|
65
|
+
{ from: 'routes', field: 'steps[]', target: { kind: 'axis', axis: 'operations' }, via: 'twin.rel.step' },
|
|
66
|
+
/* 공정이 도는 자리·자원 — 항목이 아니라 **타입**을 가리킨다 */
|
|
67
|
+
{ from: 'operations', field: 'locationType', target: { kind: 'type', role: 'location' }, via: 'twin.rel.at', optional: true },
|
|
68
|
+
{ from: 'operations', field: 'resourceType', target: { kind: 'type', role: 'equipment' }, via: 'twin.rel.by', optional: true }
|
|
69
|
+
];
|
|
70
|
+
/**
|
|
71
|
+
* 오늘 **실제로 짝이 서는 것**만 선언한다. 그럴듯한 속성을 미리 적어 두면 화면이 빈칸을 줄줄이
|
|
72
|
+
* 내고, 그건 "아직 안 쟀다" 가 아니라 "이 트윈은 부실하다" 로 읽힌다.
|
|
73
|
+
* `series` 는 아직 **아무도 내지 않는다** — EMS 가 붙을 때 생산자와 함께 선언한다.
|
|
74
|
+
*/
|
|
75
|
+
export const TWIN_PROPERTIES = [
|
|
76
|
+
/* 공정 — 선언(ISO 8601 기간)과 관측(저널에서 접은 분포)이 둘 다 있는 유일한 짝. */
|
|
77
|
+
{ axis: 'operations', key: 'duration', label: 'twin.prop.duration', kind: 'distribution',
|
|
78
|
+
declaredField: 'duration', observedBy: 'kpi-fold.workTime', uom: 'ms' },
|
|
79
|
+
/* 자리 — 용량은 선언, 점유는 관측. 단위가 다른 것이 아니라 **같은 축의 두 값**이다. */
|
|
80
|
+
{ axis: 'locations', key: 'capacity', label: 'twin.prop.capacity', kind: 'level',
|
|
81
|
+
declaredField: 'capacity', observedBy: 'state.location.occupancy' },
|
|
82
|
+
/* 설비 — 고장 모델은 선언, 가동은 관측(OEE). 선언 없이도 관측은 선다. */
|
|
83
|
+
{ axis: 'equipment', key: 'mtbf', label: 'twin.prop.mtbf', kind: 'distribution',
|
|
84
|
+
declaredField: 'mtbfMs', observedBy: 'oee.availability', uom: 'ms' },
|
|
85
|
+
{ axis: 'equipment', key: 'mttr', label: 'twin.prop.mttr', kind: 'distribution',
|
|
86
|
+
declaredField: 'mttrMs', observedBy: 'oee.availability', uom: 'ms' },
|
|
87
|
+
{ axis: 'equipment', key: 'availability', label: 'twin.prop.availability', kind: 'rate',
|
|
88
|
+
observedBy: 'oee.availability', uom: 'ratio' }
|
|
89
|
+
];
|
|
90
|
+
/** 이 축이 보여 줄 속성들 — 없으면 빈 배열(속성 없는 축이 정상이다). */
|
|
91
|
+
export const propertiesOf = (axis) => TWIN_PROPERTIES.filter(p => p.axis === axis);
|
|
92
|
+
/** 이 축에서 나가는 관계 · 이 축으로 들어오는 관계 — 항목 패널의 "이어진 것" 양방향. */
|
|
93
|
+
export const relationsFrom = (axis) => TWIN_RELATIONS.filter(r => r.from === axis);
|
|
94
|
+
export const relationsTo = (axis) => TWIN_RELATIONS.filter(r => r.target.kind === 'axis' && r.target.axis === axis);
|
|
95
|
+
/** 축 이름 → 서술. 모르는 축이면 `undefined` — 화면은 그것을 "미선언" 으로 낸다(숨기지 않는다). */
|
|
96
|
+
export function axisInfo(axis) {
|
|
97
|
+
return TWIN_AXES.find(a => a.axis === axis);
|
|
98
|
+
}
|
|
11
99
|
export const DOMAIN_SYSTEMS = ['wms', 'yms', 'mes'];
|
|
12
100
|
/** 타입 키 → 능력 프로파일(커널 SSOT). 호스트가 라이브 페이로드에 투영, 컴포넌트가 능력을 렌더. */
|
|
13
101
|
export function capabilitiesForType(system, typeKey) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* 도메인 정의(DomainDefinition) — 커널의 선언적 도메인 계약. `
|
|
2
|
+
* 도메인 정의(DomainDefinition) — 커널의 선언적 도메인 계약. `TwinModelDef`(토폴로지 입력)의 확장 격.
|
|
3
3
|
* 커널이 "무엇이 있고 어떻게 흐르나"(타입 + 공정 route/BOM)를 **데이터로** 받는 형식. zero-dep(자기 타입).
|
|
4
4
|
*
|
|
5
5
|
* 특정 공정이 커널 코드에 하드코딩되던 것을 이 데이터 계약으로 대체한다(design/plans/domain-catalog-layering.md).
|
package/dist/event-journal.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TwinModelDef, CanonicalEnvelope } from './contract.ts';
|
|
2
2
|
import { type ProjectedState } from './state-projector.ts';
|
|
3
3
|
export declare class EventJournal {
|
|
4
4
|
private events;
|
|
@@ -14,10 +14,10 @@ export declare class EventJournal {
|
|
|
14
14
|
untilSimTime(iso: string): CanonicalEnvelope[];
|
|
15
15
|
}
|
|
16
16
|
/** 이벤트열 → 상태 재구성(시간여행). board = 마스터(토폴로지·설비). */
|
|
17
|
-
export declare function replay(board:
|
|
17
|
+
export declare function replay(board: TwinModelDef, events: readonly CanonicalEnvelope[]): ProjectedState;
|
|
18
18
|
/** 한 구조 아래에서 일어난 이벤트들 — 재생의 한 마디. */
|
|
19
19
|
export interface StructureSegment {
|
|
20
|
-
board:
|
|
20
|
+
board: TwinModelDef;
|
|
21
21
|
events: readonly CanonicalEnvelope[];
|
|
22
22
|
}
|
|
23
23
|
/** 구조가 바뀐 지점마다 무엇이 사라졌는지. 조용히 넘어가지 않는다. */
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention,
|
|
1
|
+
import type { ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, OeeMetrics, AssetState, GeneratorSpec, OrderState, PersonState, ScenarioControl, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState } 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';
|
|
@@ -283,7 +283,7 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
283
283
|
private observedDirty;
|
|
284
284
|
private observeMode;
|
|
285
285
|
/** 관측 구동이 투영기를 세울 때 필요한 원본 보드(구조는 이벤트가 아니라 마스터에서 온다). */
|
|
286
|
-
protected boardDef?:
|
|
286
|
+
protected boardDef?: TwinModelDef;
|
|
287
287
|
/** 명세 소비 기록 — 무엇을 선언값으로, 무엇을 기본값으로 계산했나(정직한 자기보고). */
|
|
288
288
|
private specUse;
|
|
289
289
|
protected epcSeq: number;
|
|
@@ -310,9 +310,9 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
310
310
|
* 이동(WMS/YMS: from→to 이동) 이든 변환(MES: 소비→생산) 이든 여기서 결정 → base 가 이동-중립.
|
|
311
311
|
*/
|
|
312
312
|
protected abstract onTaskComplete(task: FlowTask): void;
|
|
313
|
-
|
|
313
|
+
loadTwinModel(def: TwinModelDef): void;
|
|
314
314
|
/**
|
|
315
|
-
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가.
|
|
315
|
+
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가. loadTwinModel 설비 삽입과 동일 규약.
|
|
316
316
|
* 기본은 mtbf 미지정(고장 없는 신뢰 자원) → sampleExp(rng) 무소비라 baseline fork 와 깨끗이 비교 가능.
|
|
317
317
|
*/
|
|
318
318
|
addEquipment(m: {
|
package/dist/flow-engine.js
CHANGED
|
@@ -253,7 +253,7 @@ export class FlowEngine {
|
|
|
253
253
|
this.policy = policy;
|
|
254
254
|
}
|
|
255
255
|
// ── TwinKernel (mechanics, 도메인 무관) ───────────────────────────────────
|
|
256
|
-
|
|
256
|
+
loadTwinModel(def) {
|
|
257
257
|
this.boardDef = def;
|
|
258
258
|
/*
|
|
259
259
|
* **생산 선언은 어느 커널이든 싣는다.** 예전에는 MES 커널만 생성자에서 이것을 실었고(그 시절
|
|
@@ -283,7 +283,7 @@ export class FlowEngine {
|
|
|
283
283
|
}
|
|
284
284
|
}
|
|
285
285
|
/**
|
|
286
|
-
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가.
|
|
286
|
+
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가. loadTwinModel 설비 삽입과 동일 규약.
|
|
287
287
|
* 기본은 mtbf 미지정(고장 없는 신뢰 자원) → sampleExp(rng) 무소비라 baseline fork 와 깨끗이 비교 가능.
|
|
288
288
|
*/
|
|
289
289
|
addEquipment(m) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssetState,
|
|
1
|
+
import type { AssetState, TwinModelDef, CanonicalEnvelope, LocationState, ItemState, EquipmentState, PersonState, TaskState, OrderState } from './contract.ts';
|
|
2
2
|
/**
|
|
3
3
|
* 마스터 동기 — 선언적 로케이션 upsert/remove.
|
|
4
4
|
*
|
|
@@ -72,7 +72,7 @@ export declare class ObservedReducer {
|
|
|
72
72
|
private shifts;
|
|
73
73
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
74
74
|
private utcOffsetMinutes?;
|
|
75
|
-
constructor(board:
|
|
75
|
+
constructor(board: TwinModelDef);
|
|
76
76
|
/**
|
|
77
77
|
* **구조를 갈아탄다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
78
78
|
*
|
|
@@ -91,7 +91,7 @@ export declare class ObservedReducer {
|
|
|
91
91
|
* 관측으로 알게 된 자리(`origin: 'observed'`)는 **새 마스터에 없어도 남긴다** — 마스터가 모르는
|
|
92
92
|
* 자리에서 물건이 실제로 보였다는 사실은 구조를 바꾼다고 사라지지 않는다.
|
|
93
93
|
*/
|
|
94
|
-
adoptStructure(board:
|
|
94
|
+
adoptStructure(board: TwinModelDef): {
|
|
95
95
|
locationsAdded: number;
|
|
96
96
|
locationsDropped: number;
|
|
97
97
|
equipmentDropped: number;
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -51,6 +51,9 @@ __export(index_exports, {
|
|
|
51
51
|
PRIORITY_UNSET: () => PRIORITY_UNSET,
|
|
52
52
|
RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
|
|
53
53
|
StateProjector: () => ObservedReducer,
|
|
54
|
+
TWIN_AXES: () => TWIN_AXES,
|
|
55
|
+
TWIN_PROPERTIES: () => TWIN_PROPERTIES,
|
|
56
|
+
TWIN_RELATIONS: () => TWIN_RELATIONS,
|
|
54
57
|
TwinHistory: () => TwinHistory,
|
|
55
58
|
TwinObserver: () => TwinObserver,
|
|
56
59
|
TwinRuntime: () => TwinRuntime,
|
|
@@ -67,6 +70,7 @@ __export(index_exports, {
|
|
|
67
70
|
activeShiftOf: () => activeShiftOf,
|
|
68
71
|
aggregationEvent: () => aggregationEvent,
|
|
69
72
|
analyzeCapacity: () => analyzeCapacity,
|
|
73
|
+
axisInfo: () => axisInfo,
|
|
70
74
|
capabilitiesForType: () => capabilitiesForType,
|
|
71
75
|
classClosure: () => classClosure,
|
|
72
76
|
compareStates: () => compareStates,
|
|
@@ -102,10 +106,13 @@ __export(index_exports, {
|
|
|
102
106
|
parseIsoDuration: () => parseIsoDuration,
|
|
103
107
|
partialFitPolicy: () => partialFitPolicy,
|
|
104
108
|
priorityRank: () => priorityRank,
|
|
109
|
+
propertiesOf: () => propertiesOf,
|
|
105
110
|
quantityIn: () => quantityIn,
|
|
106
111
|
readBoardAssets: () => readBoardAssets,
|
|
107
112
|
readBoardEquipment: () => readBoardEquipment,
|
|
108
113
|
readBoardLocations: () => readBoardLocations,
|
|
114
|
+
relationsFrom: () => relationsFrom,
|
|
115
|
+
relationsTo: () => relationsTo,
|
|
109
116
|
replay: () => replay,
|
|
110
117
|
replaySegments: () => replaySegments,
|
|
111
118
|
retiredVocabularyIn: () => retiredVocabularyIn,
|
|
@@ -1555,6 +1562,189 @@ var DOMAIN_CATALOG = {
|
|
|
1555
1562
|
yms: { system: "yms", label: "twin.system.yms", types: YMS_TYPES, locationTypes: locationKeys(YMS_TYPES) },
|
|
1556
1563
|
mes: { system: "mes", label: "twin.system.mes", types: MES_TYPES, locationTypes: locationKeys(MES_TYPES) }
|
|
1557
1564
|
};
|
|
1565
|
+
var TWIN_AXES = [
|
|
1566
|
+
{
|
|
1567
|
+
axis: "locations",
|
|
1568
|
+
path: "locations",
|
|
1569
|
+
label: "twin.axis.locations",
|
|
1570
|
+
kind: "instance",
|
|
1571
|
+
typeRole: "location",
|
|
1572
|
+
standardClass: { epcis: "SGLN" }
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
axis: "equipment",
|
|
1576
|
+
path: "equipment",
|
|
1577
|
+
label: "twin.axis.equipment",
|
|
1578
|
+
kind: "instance",
|
|
1579
|
+
typeRole: "equipment",
|
|
1580
|
+
standardClass: { isa95: "Equipment", iso55000: "Asset", epcis: "GIAI" }
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
axis: "persons",
|
|
1584
|
+
path: "persons",
|
|
1585
|
+
label: "twin.axis.persons",
|
|
1586
|
+
kind: "instance",
|
|
1587
|
+
standardClass: { isa95: "Person" }
|
|
1588
|
+
},
|
|
1589
|
+
{
|
|
1590
|
+
axis: "assets",
|
|
1591
|
+
path: "assets",
|
|
1592
|
+
label: "twin.axis.assets",
|
|
1593
|
+
kind: "instance",
|
|
1594
|
+
standardClass: { isa95: "PhysicalAsset", iso55000: "Asset" }
|
|
1595
|
+
},
|
|
1596
|
+
{
|
|
1597
|
+
axis: "personnelClasses",
|
|
1598
|
+
path: "personnelClasses",
|
|
1599
|
+
label: "twin.axis.personnelClasses",
|
|
1600
|
+
kind: "class",
|
|
1601
|
+
standardClass: { isa95: "PersonnelClass" }
|
|
1602
|
+
},
|
|
1603
|
+
{
|
|
1604
|
+
axis: "equipmentClasses",
|
|
1605
|
+
path: "equipmentClasses",
|
|
1606
|
+
label: "twin.axis.equipmentClasses",
|
|
1607
|
+
kind: "class",
|
|
1608
|
+
standardClass: { isa95: "EquipmentClass" }
|
|
1609
|
+
},
|
|
1610
|
+
{
|
|
1611
|
+
axis: "assetClasses",
|
|
1612
|
+
path: "assetClasses",
|
|
1613
|
+
label: "twin.axis.assetClasses",
|
|
1614
|
+
kind: "class",
|
|
1615
|
+
standardClass: { isa95: "PhysicalAssetClass" }
|
|
1616
|
+
},
|
|
1617
|
+
{
|
|
1618
|
+
axis: "materialDefinitions",
|
|
1619
|
+
path: "materialDefinitions",
|
|
1620
|
+
label: "twin.axis.materialDefinitions",
|
|
1621
|
+
kind: "class",
|
|
1622
|
+
standardClass: { isa95: "MaterialDefinition" }
|
|
1623
|
+
},
|
|
1624
|
+
{
|
|
1625
|
+
axis: "materialClasses",
|
|
1626
|
+
path: "materialClasses",
|
|
1627
|
+
label: "twin.axis.materialClasses",
|
|
1628
|
+
kind: "class",
|
|
1629
|
+
standardClass: { isa95: "MaterialClass" }
|
|
1630
|
+
},
|
|
1631
|
+
{
|
|
1632
|
+
axis: "operations",
|
|
1633
|
+
path: "operations",
|
|
1634
|
+
label: "twin.axis.operations",
|
|
1635
|
+
kind: "spec",
|
|
1636
|
+
standardClass: { isa95: "OperationsSegment" }
|
|
1637
|
+
},
|
|
1638
|
+
{
|
|
1639
|
+
axis: "productionSpec",
|
|
1640
|
+
path: "productionSpec",
|
|
1641
|
+
label: "twin.axis.productionSpec",
|
|
1642
|
+
kind: "spec",
|
|
1643
|
+
standardClass: { isa95: "OperationsSegment" }
|
|
1644
|
+
},
|
|
1645
|
+
/*
|
|
1646
|
+
* 아래 셋은 저장상 `productionSpec.definition` 안에 있지만 **개념으로는 1급**이다.
|
|
1647
|
+
* `materials` 가 최상위 `materialDefinitions` 와 **둘 다** 있는 것은 지금 상태 그대로다 —
|
|
1648
|
+
* 전자는 도메인 정의의 가벼운 목록(`{key,label}`), 후자는 ISA-95 풍부형이다. 같은 것의 집이
|
|
1649
|
+
* 둘이라는 사실을 **선언이 드러낸다**(감추면 화면이 어느 쪽을 보는지 아무도 모른다).
|
|
1650
|
+
*/
|
|
1651
|
+
{
|
|
1652
|
+
axis: "recipes",
|
|
1653
|
+
path: "productionSpec.definition.recipes",
|
|
1654
|
+
label: "twin.axis.recipes",
|
|
1655
|
+
kind: "spec",
|
|
1656
|
+
standardClass: { isa95: "OperationsSegment" }
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
axis: "routes",
|
|
1660
|
+
path: "productionSpec.definition.routes",
|
|
1661
|
+
label: "twin.axis.routes",
|
|
1662
|
+
kind: "spec",
|
|
1663
|
+
standardClass: { isa95: "OperationsSegment" }
|
|
1664
|
+
},
|
|
1665
|
+
{
|
|
1666
|
+
axis: "materials",
|
|
1667
|
+
path: "productionSpec.definition.materials",
|
|
1668
|
+
label: "twin.axis.materials",
|
|
1669
|
+
kind: "class",
|
|
1670
|
+
standardClass: { isa95: "MaterialDefinition" }
|
|
1671
|
+
}
|
|
1672
|
+
];
|
|
1673
|
+
var TWIN_RELATIONS = [
|
|
1674
|
+
/* 자리가 속한 구역 — **board 밖**(호스트의 `TwinArea`). 해소는 호스트가 한다. */
|
|
1675
|
+
{ from: "locations", field: "parentId", target: { kind: "external", entity: "space.area" }, via: "twin.rel.area", optional: true },
|
|
1676
|
+
/* 자원이 사는 자리 */
|
|
1677
|
+
{ from: "equipment", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
|
|
1678
|
+
{ from: "persons", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
|
|
1679
|
+
{ from: "assets", field: "homeLocation", target: { kind: "axis", axis: "locations" }, via: "twin.rel.home", optional: true },
|
|
1680
|
+
/* 자원의 등급 — 설비만 `kind`(타입)로 가고 사람·자산은 등급 목록으로 간다. 표준의 비대칭 그대로. */
|
|
1681
|
+
{ from: "persons", field: "personnelClassIds[]", target: { kind: "axis", axis: "personnelClasses" }, via: "twin.rel.class", optional: true },
|
|
1682
|
+
{ from: "assets", field: "assetClassIds[]", target: { kind: "axis", axis: "assetClasses" }, via: "twin.rel.class", optional: true },
|
|
1683
|
+
{ from: "equipment", field: "kind", target: { kind: "type", role: "equipment" }, via: "twin.rel.type" },
|
|
1684
|
+
{ from: "locations", field: "type", target: { kind: "type", role: "location" }, via: "twin.rel.type" },
|
|
1685
|
+
/* 생산 — 레시피에서 시작해 라우트·공정·품목으로 퍼진다 */
|
|
1686
|
+
{ from: "recipes", field: "route", target: { kind: "axis", axis: "routes" }, via: "twin.rel.route", optional: true },
|
|
1687
|
+
{ from: "recipes", field: "inputs[].material", target: { kind: "axis", axis: "materials" }, via: "twin.rel.input" },
|
|
1688
|
+
{ from: "recipes", field: "outputs[].material", target: { kind: "axis", axis: "materials" }, via: "twin.rel.output" },
|
|
1689
|
+
{ from: "routes", field: "steps[]", target: { kind: "axis", axis: "operations" }, via: "twin.rel.step" },
|
|
1690
|
+
/* 공정이 도는 자리·자원 — 항목이 아니라 **타입**을 가리킨다 */
|
|
1691
|
+
{ from: "operations", field: "locationType", target: { kind: "type", role: "location" }, via: "twin.rel.at", optional: true },
|
|
1692
|
+
{ from: "operations", field: "resourceType", target: { kind: "type", role: "equipment" }, via: "twin.rel.by", optional: true }
|
|
1693
|
+
];
|
|
1694
|
+
var TWIN_PROPERTIES = [
|
|
1695
|
+
/* 공정 — 선언(ISO 8601 기간)과 관측(저널에서 접은 분포)이 둘 다 있는 유일한 짝. */
|
|
1696
|
+
{
|
|
1697
|
+
axis: "operations",
|
|
1698
|
+
key: "duration",
|
|
1699
|
+
label: "twin.prop.duration",
|
|
1700
|
+
kind: "distribution",
|
|
1701
|
+
declaredField: "duration",
|
|
1702
|
+
observedBy: "kpi-fold.workTime",
|
|
1703
|
+
uom: "ms"
|
|
1704
|
+
},
|
|
1705
|
+
/* 자리 — 용량은 선언, 점유는 관측. 단위가 다른 것이 아니라 **같은 축의 두 값**이다. */
|
|
1706
|
+
{
|
|
1707
|
+
axis: "locations",
|
|
1708
|
+
key: "capacity",
|
|
1709
|
+
label: "twin.prop.capacity",
|
|
1710
|
+
kind: "level",
|
|
1711
|
+
declaredField: "capacity",
|
|
1712
|
+
observedBy: "state.location.occupancy"
|
|
1713
|
+
},
|
|
1714
|
+
/* 설비 — 고장 모델은 선언, 가동은 관측(OEE). 선언 없이도 관측은 선다. */
|
|
1715
|
+
{
|
|
1716
|
+
axis: "equipment",
|
|
1717
|
+
key: "mtbf",
|
|
1718
|
+
label: "twin.prop.mtbf",
|
|
1719
|
+
kind: "distribution",
|
|
1720
|
+
declaredField: "mtbfMs",
|
|
1721
|
+
observedBy: "oee.availability",
|
|
1722
|
+
uom: "ms"
|
|
1723
|
+
},
|
|
1724
|
+
{
|
|
1725
|
+
axis: "equipment",
|
|
1726
|
+
key: "mttr",
|
|
1727
|
+
label: "twin.prop.mttr",
|
|
1728
|
+
kind: "distribution",
|
|
1729
|
+
declaredField: "mttrMs",
|
|
1730
|
+
observedBy: "oee.availability",
|
|
1731
|
+
uom: "ms"
|
|
1732
|
+
},
|
|
1733
|
+
{
|
|
1734
|
+
axis: "equipment",
|
|
1735
|
+
key: "availability",
|
|
1736
|
+
label: "twin.prop.availability",
|
|
1737
|
+
kind: "rate",
|
|
1738
|
+
observedBy: "oee.availability",
|
|
1739
|
+
uom: "ratio"
|
|
1740
|
+
}
|
|
1741
|
+
];
|
|
1742
|
+
var propertiesOf = (axis) => TWIN_PROPERTIES.filter((p) => p.axis === axis);
|
|
1743
|
+
var relationsFrom = (axis) => TWIN_RELATIONS.filter((r) => r.from === axis);
|
|
1744
|
+
var relationsTo = (axis) => TWIN_RELATIONS.filter((r) => r.target.kind === "axis" && r.target.axis === axis);
|
|
1745
|
+
function axisInfo(axis) {
|
|
1746
|
+
return TWIN_AXES.find((a) => a.axis === axis);
|
|
1747
|
+
}
|
|
1558
1748
|
var DOMAIN_SYSTEMS = ["wms", "yms", "mes"];
|
|
1559
1749
|
function capabilitiesForType(system, typeKey) {
|
|
1560
1750
|
return DOMAIN_CATALOG[system]?.types.find((t) => t.key === typeKey)?.capabilities ?? [];
|
|
@@ -2090,7 +2280,7 @@ var FlowEngine = class {
|
|
|
2090
2280
|
this.policy = policy;
|
|
2091
2281
|
}
|
|
2092
2282
|
// ── TwinKernel (mechanics, 도메인 무관) ───────────────────────────────────
|
|
2093
|
-
|
|
2283
|
+
loadTwinModel(def) {
|
|
2094
2284
|
this.boardDef = def;
|
|
2095
2285
|
if (def.productionSpec?.definition?.operations?.length) this.loadOperations(def.productionSpec.definition.operations);
|
|
2096
2286
|
for (const n of readBoardLocations(def)) this.locations.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, occupancy: 0, status: "idle", parentId: n.parentId });
|
|
@@ -2109,7 +2299,7 @@ var FlowEngine = class {
|
|
|
2109
2299
|
}
|
|
2110
2300
|
}
|
|
2111
2301
|
/**
|
|
2112
|
-
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가.
|
|
2302
|
+
* what-if 구성 변주 — fork(또는 실행 중) 엔진에 설비 추가. loadTwinModel 설비 삽입과 동일 규약.
|
|
2113
2303
|
* 기본은 mtbf 미지정(고장 없는 신뢰 자원) → sampleExp(rng) 무소비라 baseline fork 와 깨끗이 비교 가능.
|
|
2114
2304
|
*/
|
|
2115
2305
|
addEquipment(m) {
|
|
@@ -4679,6 +4869,9 @@ function retiredVocabularyIn(line) {
|
|
|
4679
4869
|
PRIORITY_UNSET,
|
|
4680
4870
|
RETIRED_VOCABULARY,
|
|
4681
4871
|
StateProjector,
|
|
4872
|
+
TWIN_AXES,
|
|
4873
|
+
TWIN_PROPERTIES,
|
|
4874
|
+
TWIN_RELATIONS,
|
|
4682
4875
|
TwinHistory,
|
|
4683
4876
|
TwinObserver,
|
|
4684
4877
|
TwinRuntime,
|
|
@@ -4695,6 +4888,7 @@ function retiredVocabularyIn(line) {
|
|
|
4695
4888
|
activeShiftOf,
|
|
4696
4889
|
aggregationEvent,
|
|
4697
4890
|
analyzeCapacity,
|
|
4891
|
+
axisInfo,
|
|
4698
4892
|
capabilitiesForType,
|
|
4699
4893
|
classClosure,
|
|
4700
4894
|
compareStates,
|
|
@@ -4730,10 +4924,13 @@ function retiredVocabularyIn(line) {
|
|
|
4730
4924
|
parseIsoDuration,
|
|
4731
4925
|
partialFitPolicy,
|
|
4732
4926
|
priorityRank,
|
|
4927
|
+
propertiesOf,
|
|
4733
4928
|
quantityIn,
|
|
4734
4929
|
readBoardAssets,
|
|
4735
4930
|
readBoardEquipment,
|
|
4736
4931
|
readBoardLocations,
|
|
4932
|
+
relationsFrom,
|
|
4933
|
+
relationsTo,
|
|
4737
4934
|
replay,
|
|
4738
4935
|
replaySegments,
|
|
4739
4936
|
retiredVocabularyIn,
|
package/package.json
CHANGED