@operato/twin-kernel 0.6.12 → 0.6.14
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/event-journal.d.ts +13 -4
- package/dist/event-journal.js +5 -5
- package/dist/observed-reducer.d.ts +2 -2
- package/dist/observed-reducer.js +15 -15
- package/dist/operations-capability.d.ts +12 -1
- package/dist/operations-capability.js +8 -2
- package/dist-cjs/index.cjs +25 -21
- package/package.json +1 -1
package/dist/event-journal.d.ts
CHANGED
|
@@ -13,11 +13,20 @@ export declare class EventJournal {
|
|
|
13
13
|
/** sim 시각(ISO eventTime) 이하 이벤트. */
|
|
14
14
|
untilSimTime(iso: string): CanonicalEnvelope[];
|
|
15
15
|
}
|
|
16
|
-
/** 이벤트열 → 상태 재구성(시간여행).
|
|
17
|
-
export declare function replay(
|
|
18
|
-
/**
|
|
16
|
+
/** 이벤트열 → 상태 재구성(시간여행). `model` = 그 시점의 트윈 모델(토폴로지·자원). */
|
|
17
|
+
export declare function replay(model: TwinModelDef, events: readonly CanonicalEnvelope[]): ProjectedState;
|
|
18
|
+
/**
|
|
19
|
+
* 한 구조 아래에서 일어난 이벤트들 — 재생의 한 마디.
|
|
20
|
+
*
|
|
21
|
+
* 필드 이름이 `model` 인 이유: 이 값의 정체는 **트윈 모델**이다(`TwinModelDef`). 보드는 그것을 그리는
|
|
22
|
+
* 표현 중 하나일 뿐이고, 소비처는 이미 전부 `model` 로 옮겼다. 그동안 호스트가 넘길 때마다 `model` 을
|
|
23
|
+
* `board` 키로 되돌려 담는 **어휘 번역기**가 경계에 끼어 있었다 — 조용히 `model` 로 넘기면 커널이
|
|
24
|
+
* `undefined` 를 읽고 재생이 통째로 죽던 자리다. 이름을 맞추면 그 브릿지가 사라진다.
|
|
25
|
+
*
|
|
26
|
+
* 옛 키를 함께 받지 않는다: 둘 다 받으면 "어느 쪽이 정본인가" 가 남고, 그 애매함이 브릿지의 본질이다.
|
|
27
|
+
*/
|
|
19
28
|
export interface StructureSegment {
|
|
20
|
-
|
|
29
|
+
model: TwinModelDef;
|
|
21
30
|
events: readonly CanonicalEnvelope[];
|
|
22
31
|
}
|
|
23
32
|
/**
|
package/dist/event-journal.js
CHANGED
|
@@ -32,9 +32,9 @@ export class EventJournal {
|
|
|
32
32
|
return this.events.filter(e => e.eventTime <= iso);
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
-
/** 이벤트열 → 상태 재구성(시간여행).
|
|
36
|
-
export function replay(
|
|
37
|
-
const proj = new StateProjector(
|
|
35
|
+
/** 이벤트열 → 상태 재구성(시간여행). `model` = 그 시점의 트윈 모델(토폴로지·자원). */
|
|
36
|
+
export function replay(model, events) {
|
|
37
|
+
const proj = new StateProjector(model);
|
|
38
38
|
for (const e of events)
|
|
39
39
|
proj.apply(e);
|
|
40
40
|
return proj.snapshot();
|
|
@@ -54,12 +54,12 @@ export function replay(board, events) {
|
|
|
54
54
|
export function replaySegments(segments) {
|
|
55
55
|
if (!segments.length)
|
|
56
56
|
throw new Error('재생할 마디가 없다 — 구조를 하나도 주지 않았다');
|
|
57
|
-
const proj = new StateProjector(segments[0].
|
|
57
|
+
const proj = new StateProjector(segments[0].model);
|
|
58
58
|
const shifts = [];
|
|
59
59
|
for (let i = 0; i < segments.length; i++) {
|
|
60
60
|
/* 첫 마디는 생성자가 이미 그 구조로 섰다 — 두 번 세우면 관측 전 상태를 다시 덮는다. */
|
|
61
61
|
if (i > 0)
|
|
62
|
-
shifts.push({ index: i, ...proj.adoptStructure(segments[i].
|
|
62
|
+
shifts.push({ index: i, ...proj.adoptStructure(segments[i].model) });
|
|
63
63
|
for (const e of segments[i].events)
|
|
64
64
|
proj.apply(e);
|
|
65
65
|
}
|
|
@@ -79,7 +79,7 @@ export declare class ObservedReducer {
|
|
|
79
79
|
private classDefs;
|
|
80
80
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
81
81
|
private utcOffsetMinutes?;
|
|
82
|
-
constructor(
|
|
82
|
+
constructor(model: TwinModelDef);
|
|
83
83
|
/**
|
|
84
84
|
* **구조를 갈아탄다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
85
85
|
*
|
|
@@ -98,7 +98,7 @@ export declare class ObservedReducer {
|
|
|
98
98
|
* 관측으로 알게 된 자리(`origin: 'observed'`)는 **새 마스터에 없어도 남긴다** — 마스터가 모르는
|
|
99
99
|
* 자리에서 물건이 실제로 보였다는 사실은 구조를 바꾼다고 사라지지 않는다.
|
|
100
100
|
*/
|
|
101
|
-
adoptStructure(
|
|
101
|
+
adoptStructure(model: TwinModelDef): StructureShift;
|
|
102
102
|
/** 마스터 동기 — 로케이션 추가/변경/제거. */
|
|
103
103
|
applyMaster(u: MasterUpdate): void;
|
|
104
104
|
/**
|
package/dist/observed-reducer.js
CHANGED
|
@@ -71,24 +71,24 @@ export class ObservedReducer {
|
|
|
71
71
|
classDefs = {};
|
|
72
72
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
73
73
|
utcOffsetMinutes;
|
|
74
|
-
constructor(
|
|
75
|
-
this.utcOffsetMinutes =
|
|
76
|
-
this.classDefs = { personnel:
|
|
77
|
-
for (const n of readBoardLocations(
|
|
74
|
+
constructor(model) {
|
|
75
|
+
this.utcOffsetMinutes = model.utcOffsetMinutes;
|
|
76
|
+
this.classDefs = { personnel: model.personnelClasses, equipment: model.equipmentClasses, asset: model.assetClasses };
|
|
77
|
+
for (const n of readBoardLocations(model))
|
|
78
78
|
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: 'master' });
|
|
79
79
|
// 설비 기준선(마스터) — equipment.status 델타로 갱신됨.
|
|
80
|
-
for (const m of readBoardEquipment(
|
|
80
|
+
for (const m of readBoardEquipment(model))
|
|
81
81
|
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), ...effectiveOf(m), origin: 'master' });
|
|
82
82
|
// 사람 기준선(마스터) — person.status 델타로 갱신됨.
|
|
83
|
-
for (const p of
|
|
83
|
+
for (const p of model.persons ?? [])
|
|
84
84
|
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}), ...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.testResults ? { testResults: p.testResults } : {}), ...effectiveOf(p) });
|
|
85
85
|
/* 교대 선언은 **미러도 들고 있어야 한다** — 설비 델타에는 `offShift` 자리조차 없어서, 그것 없이는
|
|
86
86
|
미러의 설비가 점심 휴게 중에도 "대기" 로 보였다. */
|
|
87
|
-
for (const m of readBoardEquipment(
|
|
87
|
+
for (const m of readBoardEquipment(model))
|
|
88
88
|
this.rememberShift(`eq:${m.id}`, m);
|
|
89
|
-
for (const p of
|
|
89
|
+
for (const p of model.persons ?? [])
|
|
90
90
|
this.rememberShift(`person:${p.id}`, p);
|
|
91
|
-
for (const a of readBoardAssets(
|
|
91
|
+
for (const a of readBoardAssets(model))
|
|
92
92
|
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', ...(a.properties ? { properties: a.properties } : {}), ...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.testResults ? { testResults: a.testResults } : {}), ...effectiveOf(a) });
|
|
93
93
|
}
|
|
94
94
|
/**
|
|
@@ -109,9 +109,9 @@ export class ObservedReducer {
|
|
|
109
109
|
* 관측으로 알게 된 자리(`origin: 'observed'`)는 **새 마스터에 없어도 남긴다** — 마스터가 모르는
|
|
110
110
|
* 자리에서 물건이 실제로 보였다는 사실은 구조를 바꾼다고 사라지지 않는다.
|
|
111
111
|
*/
|
|
112
|
-
adoptStructure(
|
|
113
|
-
this.utcOffsetMinutes =
|
|
114
|
-
const locations = readBoardLocations(
|
|
112
|
+
adoptStructure(model) {
|
|
113
|
+
this.utcOffsetMinutes = model.utcOffsetMinutes;
|
|
114
|
+
const locations = readBoardLocations(model);
|
|
115
115
|
const declaredLocationIds = new Set(locations.map(n => n.id));
|
|
116
116
|
let locationsAdded = 0;
|
|
117
117
|
let locationsDropped = 0;
|
|
@@ -128,9 +128,9 @@ export class ObservedReducer {
|
|
|
128
128
|
}
|
|
129
129
|
/* 자원은 **선언이 전부다** — 관측으로 생기지 않는다(설비는 이벤트가 만들어 내지 않는다).
|
|
130
130
|
그래서 새 선언에 없으면 없는 것이고, 남아 있는 것은 지금까지의 상태를 그대로 이어 간다. */
|
|
131
|
-
const equipment = readBoardEquipment(
|
|
132
|
-
const assets = readBoardAssets(
|
|
133
|
-
const persons =
|
|
131
|
+
const equipment = readBoardEquipment(model);
|
|
132
|
+
const assets = readBoardAssets(model);
|
|
133
|
+
const persons = model.persons ?? [];
|
|
134
134
|
const dropMissing = (map, declared) => {
|
|
135
135
|
const ids = new Set(declared.map(d => d.id));
|
|
136
136
|
let dropped = 0;
|
|
@@ -37,8 +37,19 @@ export interface OperationCapability {
|
|
|
37
37
|
* `Math.max(0, …)` 하나로 덮는 일이 실제로 이 트윈에서 여러 번 있었다.
|
|
38
38
|
*/
|
|
39
39
|
available: number | null;
|
|
40
|
-
/** 무엇이 묶고 있나 — 축과 등급(용량 분석이 이미 낸다). */
|
|
40
|
+
/** 무엇이 **천장**을 묶고 있나 — 축과 등급(용량 분석이 이미 낸다). 선언 기준이다. */
|
|
41
41
|
constraint?: CapacityRequirement;
|
|
42
|
+
/**
|
|
43
|
+
* **남은 능력을 묶는 축** — 달성불가를 되찾으려면 여기부터다. `unattainable > 0` 일 때만 있다.
|
|
44
|
+
*
|
|
45
|
+
* 천장을 묶는 축과 **다를 수 있고**, 그 차이가 곧 사람이 헛걸음하는 자리다: 용접기 2대와 용접
|
|
46
|
+
* 자격자 2명이 같은 천장을 낼 때 천장은 어느 쪽으로도 묶이지만, 자격 하나가 만료돼 잃은 몫의
|
|
47
|
+
* 원인은 인원이다. 화면이 천장 쪽만 보이면 사람이 **설비를 보러 간다**(실제로 그랬다).
|
|
48
|
+
*
|
|
49
|
+
* 값은 이미 손에 있다 — 달성불가를 내려고 가용 자원 기준으로 같은 계산을 한 번 더 돌리기 때문이다.
|
|
50
|
+
* 그 계산이 묶는 축이 곧 이 답이고, 따로 판정을 만들지 않는다.
|
|
51
|
+
*/
|
|
52
|
+
unattainableBy?: CapacityRequirement;
|
|
42
53
|
}
|
|
43
54
|
export interface OperationsCapabilityReport {
|
|
44
55
|
/** 표준 `OperationsType` — 지금은 생산뿐이다(정비·품질 능력은 별도 보고서가 될 자리). */
|
|
@@ -90,9 +90,14 @@ export function operationsCapabilityOf(input) {
|
|
|
90
90
|
* 아닌 입력을 받은 것이므로 값을 지어내는 대신 아래 `available` 이 어긋남을 드러낸다.
|
|
91
91
|
*/
|
|
92
92
|
let unattainable = null;
|
|
93
|
+
let unattainableBy;
|
|
93
94
|
if (usable) {
|
|
94
|
-
const
|
|
95
|
+
const left = usable.operations.find(o => o.operation === op.operation);
|
|
96
|
+
const usableAmount = amountOf(left?.perHour ?? NaN);
|
|
95
97
|
unattainable = total != null && usableAmount != null ? Math.max(0, total - usableAmount) : null;
|
|
98
|
+
/* 잃은 몫이 있을 때만 원인을 말한다 — 잃은 것이 없는데 "여기부터" 라고 하면 없는 일을 만든다. */
|
|
99
|
+
if (unattainable != null && unattainable > 0 && left?.constraint)
|
|
100
|
+
unattainableBy = left.constraint;
|
|
96
101
|
}
|
|
97
102
|
const available = total == null || unattainable == null ? null : total - committed - unattainable;
|
|
98
103
|
return {
|
|
@@ -101,7 +106,8 @@ export function operationsCapabilityOf(input) {
|
|
|
101
106
|
committed,
|
|
102
107
|
unattainable,
|
|
103
108
|
available,
|
|
104
|
-
...(op.constraint ? { constraint: op.constraint } : {})
|
|
109
|
+
...(op.constraint ? { constraint: op.constraint } : {}),
|
|
110
|
+
...(unattainableBy ? { unattainableBy } : {})
|
|
105
111
|
};
|
|
106
112
|
});
|
|
107
113
|
return {
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -994,15 +994,15 @@ var ObservedReducer = class {
|
|
|
994
994
|
classDefs = {};
|
|
995
995
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
996
996
|
utcOffsetMinutes;
|
|
997
|
-
constructor(
|
|
998
|
-
this.utcOffsetMinutes =
|
|
999
|
-
this.classDefs = { personnel:
|
|
1000
|
-
for (const n of readBoardLocations(
|
|
1001
|
-
for (const m of readBoardEquipment(
|
|
1002
|
-
for (const p of
|
|
1003
|
-
for (const m of readBoardEquipment(
|
|
1004
|
-
for (const p of
|
|
1005
|
-
for (const a of readBoardAssets(
|
|
997
|
+
constructor(model) {
|
|
998
|
+
this.utcOffsetMinutes = model.utcOffsetMinutes;
|
|
999
|
+
this.classDefs = { personnel: model.personnelClasses, equipment: model.equipmentClasses, asset: model.assetClasses };
|
|
1000
|
+
for (const n of readBoardLocations(model)) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: "master" });
|
|
1001
|
+
for (const m of readBoardEquipment(model)) this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, ...effectiveOf(m), origin: "master" });
|
|
1002
|
+
for (const p of model.persons ?? []) this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...p.testResults ? { testResults: p.testResults } : {}, ...effectiveOf(p) });
|
|
1003
|
+
for (const m of readBoardEquipment(model)) this.rememberShift(`eq:${m.id}`, m);
|
|
1004
|
+
for (const p of model.persons ?? []) this.rememberShift(`person:${p.id}`, p);
|
|
1005
|
+
for (const a of readBoardAssets(model)) this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: "idle", ...a.properties ? { properties: a.properties } : {}, ...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}, ...a.testResults ? { testResults: a.testResults } : {}, ...effectiveOf(a) });
|
|
1006
1006
|
}
|
|
1007
1007
|
/**
|
|
1008
1008
|
* **구조를 갈아탄다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
@@ -1022,9 +1022,9 @@ var ObservedReducer = class {
|
|
|
1022
1022
|
* 관측으로 알게 된 자리(`origin: 'observed'`)는 **새 마스터에 없어도 남긴다** — 마스터가 모르는
|
|
1023
1023
|
* 자리에서 물건이 실제로 보였다는 사실은 구조를 바꾼다고 사라지지 않는다.
|
|
1024
1024
|
*/
|
|
1025
|
-
adoptStructure(
|
|
1026
|
-
this.utcOffsetMinutes =
|
|
1027
|
-
const locations = readBoardLocations(
|
|
1025
|
+
adoptStructure(model) {
|
|
1026
|
+
this.utcOffsetMinutes = model.utcOffsetMinutes;
|
|
1027
|
+
const locations = readBoardLocations(model);
|
|
1028
1028
|
const declaredLocationIds = new Set(locations.map((n) => n.id));
|
|
1029
1029
|
let locationsAdded = 0;
|
|
1030
1030
|
let locationsDropped = 0;
|
|
@@ -1037,9 +1037,9 @@ var ObservedReducer = class {
|
|
|
1037
1037
|
if (!this.master.has(n.id)) locationsAdded++;
|
|
1038
1038
|
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: "master" });
|
|
1039
1039
|
}
|
|
1040
|
-
const equipment = readBoardEquipment(
|
|
1041
|
-
const assets = readBoardAssets(
|
|
1042
|
-
const persons =
|
|
1040
|
+
const equipment = readBoardEquipment(model);
|
|
1041
|
+
const assets = readBoardAssets(model);
|
|
1042
|
+
const persons = model.persons ?? [];
|
|
1043
1043
|
const dropMissing = (map, declared) => {
|
|
1044
1044
|
const ids = new Set(declared.map((d) => d.id));
|
|
1045
1045
|
let dropped = 0;
|
|
@@ -1494,17 +1494,17 @@ var EventJournal = class {
|
|
|
1494
1494
|
return this.events.filter((e) => e.eventTime <= iso2);
|
|
1495
1495
|
}
|
|
1496
1496
|
};
|
|
1497
|
-
function replay(
|
|
1498
|
-
const proj = new ObservedReducer(
|
|
1497
|
+
function replay(model, events) {
|
|
1498
|
+
const proj = new ObservedReducer(model);
|
|
1499
1499
|
for (const e of events) proj.apply(e);
|
|
1500
1500
|
return proj.snapshot();
|
|
1501
1501
|
}
|
|
1502
1502
|
function replaySegments(segments) {
|
|
1503
1503
|
if (!segments.length) throw new Error("\uC7AC\uC0DD\uD560 \uB9C8\uB514\uAC00 \uC5C6\uB2E4 \u2014 \uAD6C\uC870\uB97C \uD558\uB098\uB3C4 \uC8FC\uC9C0 \uC54A\uC558\uB2E4");
|
|
1504
|
-
const proj = new ObservedReducer(segments[0].
|
|
1504
|
+
const proj = new ObservedReducer(segments[0].model);
|
|
1505
1505
|
const shifts = [];
|
|
1506
1506
|
for (let i = 0; i < segments.length; i++) {
|
|
1507
|
-
if (i > 0) shifts.push({ index: i, ...proj.adoptStructure(segments[i].
|
|
1507
|
+
if (i > 0) shifts.push({ index: i, ...proj.adoptStructure(segments[i].model) });
|
|
1508
1508
|
for (const e of segments[i].events) proj.apply(e);
|
|
1509
1509
|
}
|
|
1510
1510
|
return { state: proj.snapshot(), shifts };
|
|
@@ -2276,9 +2276,12 @@ function operationsCapabilityOf(input) {
|
|
|
2276
2276
|
const total = amountOf(op.perHour);
|
|
2277
2277
|
const committed = committedOf(op.operation);
|
|
2278
2278
|
let unattainable = null;
|
|
2279
|
+
let unattainableBy;
|
|
2279
2280
|
if (usable) {
|
|
2280
|
-
const
|
|
2281
|
+
const left = usable.operations.find((o) => o.operation === op.operation);
|
|
2282
|
+
const usableAmount = amountOf(left?.perHour ?? NaN);
|
|
2281
2283
|
unattainable = total != null && usableAmount != null ? Math.max(0, total - usableAmount) : null;
|
|
2284
|
+
if (unattainable != null && unattainable > 0 && left?.constraint) unattainableBy = left.constraint;
|
|
2282
2285
|
}
|
|
2283
2286
|
const available = total == null || unattainable == null ? null : total - committed - unattainable;
|
|
2284
2287
|
return {
|
|
@@ -2287,7 +2290,8 @@ function operationsCapabilityOf(input) {
|
|
|
2287
2290
|
committed,
|
|
2288
2291
|
unattainable,
|
|
2289
2292
|
available,
|
|
2290
|
-
...op.constraint ? { constraint: op.constraint } : {}
|
|
2293
|
+
...op.constraint ? { constraint: op.constraint } : {},
|
|
2294
|
+
...unattainableBy ? { unattainableBy } : {}
|
|
2291
2295
|
};
|
|
2292
2296
|
});
|
|
2293
2297
|
return {
|
package/package.json
CHANGED