@operato/twin-kernel 0.4.3 → 0.5.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 +14 -0
- package/dist/domain-catalog.d.ts +124 -0
- package/dist/domain-catalog.js +88 -0
- package/dist-cjs/index.cjs +197 -0
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -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` 로 커널에 싣는다. 그런데 `BoardDef` 는 그것을 말한 적이 없다.
|
|
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 식별자로 잇는 바인딩.
|
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
|
+
* 축의 이름 — 화면·관계 선언이 쓰는 키. 최상위 축은 `BoardDef` 의 키 **그대로**다
|
|
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) {
|
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 ?? [];
|
|
@@ -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