@operato/twin-kernel 0.3.0 → 0.4.1
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/capacity.d.ts +99 -0
- package/dist/capacity.js +172 -0
- package/dist/contract.d.ts +512 -23
- package/dist/contract.js +337 -13
- package/dist/divergence.js +5 -2
- package/dist/domain-definition.d.ts +45 -0
- package/dist/epcis.d.ts +12 -0
- package/dist/epcis.js +12 -0
- package/dist/event-journal.d.ts +30 -0
- package/dist/event-journal.js +26 -0
- package/dist/flow-engine.d.ts +270 -7
- package/dist/flow-engine.js +727 -52
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/job-response.d.ts +59 -0
- package/dist/job-response.js +56 -0
- package/dist/kernel.d.ts +5 -0
- package/dist/kernel.js +25 -5
- package/dist/mes-kernel.d.ts +28 -0
- package/dist/mes-kernel.js +58 -4
- package/dist/observed-reducer.d.ts +61 -0
- package/dist/observed-reducer.js +214 -17
- package/dist/vocabulary.js +6 -0
- package/dist/yms-kernel.js +26 -5
- package/dist-cjs/index.cjs +1240 -85
- package/package.json +1 -1
package/dist/flow-engine.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type { ResourceProperty, ResourceClassDef, Attention, BoardDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, OeeMetrics, AssetState, GeneratorSpec, OrderState, PersonState, ScenarioControl, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState } from './contract.ts';
|
|
1
|
+
import type { ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, BoardDef, 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';
|
|
5
5
|
import type { OperationDef } from './domain-definition.ts';
|
|
6
|
+
import { type CapacityAnalysis } from './capacity.ts';
|
|
6
7
|
export interface FlowLocation {
|
|
7
8
|
id: string;
|
|
8
9
|
type: string;
|
|
@@ -24,6 +25,10 @@ export interface FlowItem {
|
|
|
24
25
|
epc: string;
|
|
25
26
|
location: string;
|
|
26
27
|
disposition: string;
|
|
28
|
+
/** 무슨 품목인가 — 표준 `MaterialLot.MaterialDefinitionID`. 보통은 `gtin` 이 곧 정의 id 라 비어 있다. */
|
|
29
|
+
definitionId?: string;
|
|
30
|
+
/** 로트의 부분(표준 `MaterialSubLot.ID`) — 비직렬 로트가 자리마다 갈릴 때. */
|
|
31
|
+
subLotId?: string;
|
|
27
32
|
gtin?: string;
|
|
28
33
|
qty?: number;
|
|
29
34
|
/** 수량 단위(UN/CEFACT). 개수면 없다 — 없는 것을 'EA' 로 꾸미지 않는다. */
|
|
@@ -35,9 +40,11 @@ export interface FlowItem {
|
|
|
35
40
|
expiry?: number;
|
|
36
41
|
/** 개체·로트 마스터데이터 원문 — 생겨날 때 정해지고 뒤 이벤트가 지우지 않는다. */
|
|
37
42
|
ilmd?: Record<string, unknown>;
|
|
43
|
+
/** 선언된 모든 수량 — 표준 `MaterialLot.Quantity`(복수). 관측에서 온 물품이 여러 단위를 들 수 있다. */
|
|
44
|
+
quantities?: MaterialQuantity[];
|
|
38
45
|
}
|
|
39
46
|
/** 물리 자산 — 반복사용(팔레트·랙·용기). 설비도 물품도 아니다(GRAI vs SSCC 구분은 계약 주석 참조). */
|
|
40
|
-
export interface FlowAsset {
|
|
47
|
+
export interface FlowAsset extends EffectivePeriod {
|
|
41
48
|
id: string;
|
|
42
49
|
/** 속한 등급들 — 표준 `PhysicalAssetClassID`(복수). */
|
|
43
50
|
assetClassIds?: string[];
|
|
@@ -51,7 +58,7 @@ export interface FlowAsset {
|
|
|
51
58
|
testSpecificationIds?: string[];
|
|
52
59
|
}
|
|
53
60
|
/** 사람 — 설비와 별개 자원(고장·OEE 가 아니라 등급·교대로 산다). */
|
|
54
|
-
export interface FlowPerson {
|
|
61
|
+
export interface FlowPerson extends EffectivePeriod {
|
|
55
62
|
id: string;
|
|
56
63
|
/** 속한 등급들 — 표준 `PersonnelClassID`(복수). 자격이 여럿인 사람을 표현한다. */
|
|
57
64
|
personnelClassIds?: string[];
|
|
@@ -61,6 +68,8 @@ export interface FlowPerson {
|
|
|
61
68
|
startHour: number;
|
|
62
69
|
endHour: number;
|
|
63
70
|
};
|
|
71
|
+
/** 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 있으면 `window` 보다 이것을 본다. */
|
|
72
|
+
workCalendar?: WorkCalendarEntry[];
|
|
64
73
|
/** 지금 어디에 있나 — 표준 `Person.OperationalLocation`. 커널은 사람을 움직이지 않는다(마스터·관측이 말한다). */
|
|
65
74
|
location?: string;
|
|
66
75
|
/** 자원 속성 — 표준 `PersonProperty`. */
|
|
@@ -68,7 +77,7 @@ export interface FlowPerson {
|
|
|
68
77
|
/** 자격을 검증한 시험 명세들 — 표준 `TestSpecificationID`. */
|
|
69
78
|
testSpecificationIds?: string[];
|
|
70
79
|
}
|
|
71
|
-
export interface FlowEquipment {
|
|
80
|
+
export interface FlowEquipment extends EffectivePeriod {
|
|
72
81
|
id: string;
|
|
73
82
|
kind: string;
|
|
74
83
|
location: string;
|
|
@@ -85,6 +94,8 @@ export interface FlowEquipment {
|
|
|
85
94
|
startHour: number;
|
|
86
95
|
endHour: number;
|
|
87
96
|
};
|
|
97
|
+
/** 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 있으면 `window` 보다 이것을 본다. */
|
|
98
|
+
workCalendar?: WorkCalendarEntry[];
|
|
88
99
|
runMs: number;
|
|
89
100
|
setupMs: number;
|
|
90
101
|
downMs: number;
|
|
@@ -103,12 +114,29 @@ export interface FlowTask {
|
|
|
103
114
|
id: string;
|
|
104
115
|
kind: string;
|
|
105
116
|
status: 'created' | 'in-progress' | 'completed';
|
|
117
|
+
/**
|
|
118
|
+
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`.
|
|
119
|
+
*
|
|
120
|
+
* 명세(계획)가 아니라 **일어난 일**이다. 투입 인원·설비가 이미 작업 델타에 타고 있으니 자재도 같은
|
|
121
|
+
* 채널에 실어야 실적을 **한 곳에서** 읽는다(EPCIS 이벤트에서 되짚으려면 작업과 잇는 끈이 없다).
|
|
122
|
+
*/
|
|
123
|
+
materialActual?: {
|
|
124
|
+
definitionId: string;
|
|
125
|
+
use: 'consumed' | 'produced';
|
|
126
|
+
quantity: number;
|
|
127
|
+
uom?: string;
|
|
128
|
+
}[];
|
|
106
129
|
itemEpc: string;
|
|
107
130
|
fromNode: string;
|
|
108
131
|
toNode: string;
|
|
109
132
|
resource: string | null;
|
|
110
133
|
/** 착수 시각(절대 sim-clock) — 보간 앵커(모션과 같은 규율). */
|
|
111
134
|
startedAtSimMs?: number;
|
|
135
|
+
/** 우선순위 — 표준 `JobOrder.Priority`(작은 값이 급하다). 배정 순서를 정한다. */
|
|
136
|
+
priority?: number;
|
|
137
|
+
/** 예정 창 — 표준 `JobOrder.StartTime`/`EndTime`. 실제 착수와 다른 축. */
|
|
138
|
+
startTime?: string;
|
|
139
|
+
endTime?: string;
|
|
112
140
|
/** 이 작업에 투입된 사람들 — 설비와 별개 축(설비 1대 + 작업자 2명이 동시에 잡힌다). */
|
|
113
141
|
personnel?: string[];
|
|
114
142
|
/** 이 작업에 투입된 물리 자산(팔레트 등). */
|
|
@@ -150,6 +178,11 @@ export interface FlowOrder {
|
|
|
150
178
|
held?: boolean;
|
|
151
179
|
dockDoor?: string;
|
|
152
180
|
windowStartMs?: number;
|
|
181
|
+
/** 우선순위 — 표준 `OperationsRequest.Priority`(작은 값이 급하다). 할당 순서를 정한다. */
|
|
182
|
+
priority?: number;
|
|
183
|
+
/** 예정 창 — 표준 `StartTime`/`EndTime`. 납기가 있어야 "늦었나" 를 물을 수 있다. */
|
|
184
|
+
startTime?: string;
|
|
185
|
+
endTime?: string;
|
|
153
186
|
}
|
|
154
187
|
interface Rng {
|
|
155
188
|
(): number;
|
|
@@ -167,6 +200,7 @@ export declare function deriveAttentions(view: {
|
|
|
167
200
|
location?: string;
|
|
168
201
|
goodCount?: number;
|
|
169
202
|
scrapCount?: number;
|
|
203
|
+
offShift?: boolean;
|
|
170
204
|
}[];
|
|
171
205
|
locations: {
|
|
172
206
|
id: string;
|
|
@@ -176,8 +210,23 @@ export declare function deriveAttentions(view: {
|
|
|
176
210
|
orders: {
|
|
177
211
|
id: string;
|
|
178
212
|
held?: boolean;
|
|
213
|
+
status?: string;
|
|
214
|
+
endTime?: ISOTime;
|
|
215
|
+
priority?: number;
|
|
216
|
+
}[];
|
|
217
|
+
/**
|
|
218
|
+
* 작업 — 대기 행렬을 재는 데 쓴다. **주지 않으면 재지 않는다**(모르면 판단하지 않는다).
|
|
219
|
+
* 두 구동(시뮬·미러)이 **똑같이 넣어야** 한다 — 한쪽만 넣으면 같은 공장이 한쪽에서만 막힌
|
|
220
|
+
* 것처럼 보인다.
|
|
221
|
+
*/
|
|
222
|
+
tasks?: {
|
|
223
|
+
id: string;
|
|
224
|
+
kind: string;
|
|
225
|
+
status: string;
|
|
179
226
|
}[];
|
|
180
|
-
}, acked?: ReadonlySet<string
|
|
227
|
+
}, acked?: ReadonlySet<string>,
|
|
228
|
+
/** 지금(ISO) — 납기 판정에 필요하다. **주지 않으면 지연을 판정하지 않는다**(모르면 판단하지 않는다). */
|
|
229
|
+
nowIso?: ISOTime): Attention[];
|
|
181
230
|
/** OEE 계측 카운터 — sim 은 tick 으로 누적, live 는 실 텔레메트리 또는 이벤트 누적기가 채운다(face2-inbound-live §1.1). */
|
|
182
231
|
export interface OeeCounters {
|
|
183
232
|
runMs: number;
|
|
@@ -204,7 +253,13 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
204
253
|
personnel?: ResourceClassDef[];
|
|
205
254
|
equipment?: ResourceClassDef[];
|
|
206
255
|
asset?: ResourceClassDef[];
|
|
256
|
+
material?: ResourceClassDef[];
|
|
207
257
|
};
|
|
258
|
+
/**
|
|
259
|
+
* 품목 정의 색인(표준 `MaterialDefinition`) — 단위 환산의 **유일한 근거**.
|
|
260
|
+
* 비어 있으면 환산하지 않는다(계수를 모르는데 값을 만들지 않는다).
|
|
261
|
+
*/
|
|
262
|
+
protected materialDefs: Map<string, MaterialDefinition>;
|
|
208
263
|
/** 사람 — 선언하지 않으면 빈 맵(인원 제약 없는 트윈, 기존 거동). */
|
|
209
264
|
persons: Map<string, FlowPerson>;
|
|
210
265
|
/** 물리 자산 — 선언하지 않으면 빈 맵(자산 제약 없는 트윈, 기존 거동). */
|
|
@@ -274,6 +329,17 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
274
329
|
* 오더는 남은 데맨드(라인별 requested-fulfilled)를 'created' 로 복원(현재 재고에서 재계획).
|
|
275
330
|
* 진행 중 개별 task 의 내부 상태는 관측만으론 복원 불가 → 재계획에 맡김(정직한 한계).
|
|
276
331
|
*/
|
|
332
|
+
/**
|
|
333
|
+
* **이어서 센다** — 재기동 뒤 저널을 이어 쓸 때.
|
|
334
|
+
*
|
|
335
|
+
* 커널은 리비전을 0부터 센다. 새로 시작하는 우주라면 맞지만, **이어지는 현실**(sim-world)에서는
|
|
336
|
+
* 그 번호가 이미 저널에 있는 행과 겹친다 — 같은 트윈에 revision 7이 두 개 생기고, 재생 순서가
|
|
337
|
+
* 뒤섞이며, 시간여행이 엉뚱한 시점을 답한다. 조용히 깨지는 종류다(오류가 안 난다).
|
|
338
|
+
*
|
|
339
|
+
* 그래서 이어 쓰기 전에 마지막 번호를 알려 준다. **뒤로는 못 간다** — 뒤로 가는 것은 곧 겹치는
|
|
340
|
+
* 것이고, 그것을 허용하면 이 함수가 막으려던 일이 이 함수를 통해 일어난다.
|
|
341
|
+
*/
|
|
342
|
+
resumeRevision(from: number): void;
|
|
277
343
|
hydrateObserved(snap: {
|
|
278
344
|
locations: LocationState[];
|
|
279
345
|
items: ItemState[];
|
|
@@ -298,6 +364,15 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
298
364
|
* State 델타를 유발한다(command → 행위 → 관측 폐루프).
|
|
299
365
|
*/
|
|
300
366
|
private _acked;
|
|
367
|
+
/**
|
|
368
|
+
* 완료 시점에 **주체가 사라져 접은 작업 수** — 조용한 누락이 되지 않게 센다.
|
|
369
|
+
*
|
|
370
|
+
* 물품이 포장·출하·소비로 없어지는 것은 정상이지만, 그 물품을 향한 작업이 남아 있었다는 것은
|
|
371
|
+
* 상류에 어긋남이 있다는 신호다. 0 이 아니면 그 사실을 소비처가 볼 수 있어야 한다.
|
|
372
|
+
*/
|
|
373
|
+
protected abandonedTasks: number;
|
|
374
|
+
/** 주목 신호가 **처음 성립한 시각**(id → ISO). 조건이 사라지면 지운다 — 재발은 새 시작이다. */
|
|
375
|
+
private _attentionSince;
|
|
301
376
|
dispatch(cmd: Command): CommandAck;
|
|
302
377
|
/** 도메인 커맨드 처리(order.release 등). 기본은 거절 — 도메인이 override. */
|
|
303
378
|
protected handleCommand(cmd: Command): CommandAck;
|
|
@@ -312,7 +387,39 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
312
387
|
* rng 는 fork 의 시나리오 load 시 재시드(드레인 예측은 생성 없어 rng 무관·결정적).
|
|
313
388
|
*/
|
|
314
389
|
fork(tenantId?: string): this;
|
|
390
|
+
/**
|
|
391
|
+
* 이 커널의 **지금**(ms) — 시각으로 바뀌는 모든 판정의 단일 기준.
|
|
392
|
+
*
|
|
393
|
+
* ── 관측 모드에서 시계가 멈춰 있었다 ────────────────────────────────────
|
|
394
|
+
* `apply()` 는 `clockMs` 를 밀지 않는다(관측은 시간을 굴리지 않는다). 그래서 라이브 트윈의 "지금" 이
|
|
395
|
+
* **BASE_EPOCH(2026-01-01)에 얼어 있었다.** 실 이벤트는 실제 시각을 달고 오므로 결과가 이렇게 된다:
|
|
396
|
+
* · **납기 초과를 영원히 보고하지 않는다** — 실 납기가 항상 미래로 보인다
|
|
397
|
+
* · 폐기·도입예정 판정이 틀린다(§EffectivePeriod)
|
|
398
|
+
* · 교대 판정이 틀린다(§offCalendarAt)
|
|
399
|
+
* 셋 다 "시각으로만 바뀌는 사실" 이라 같은 결함이었다. 그래서 기준을 **한 곳**으로 모은다.
|
|
400
|
+
*
|
|
401
|
+
* 관측 중이면 **마지막으로 들은 발생 시각**을 쓴다(미러의 `lastObservedMs` 와 같은 기준 — 규칙 한 벌).
|
|
402
|
+
* 아직 아무것도 못 들었으면 시뮬 기준으로 떨어진다(그때는 판정할 사실도 없다).
|
|
403
|
+
*/
|
|
404
|
+
protected nowMs(): number;
|
|
315
405
|
protected now(): string;
|
|
406
|
+
/**
|
|
407
|
+
* 자극이 선언한 **약속**을 오더 필드로 — 표준 `OperationsRequest.Priority`·`StartTime`·`EndTime`.
|
|
408
|
+
*
|
|
409
|
+
* **base 에 한 곳만 둔다.** 오더를 만드는 곳이 도메인마다 있어(WMS 판매오더·MES 작업지시·YMS
|
|
410
|
+
* 어포인트먼트) 각자 계산하면 시간축이나 규약이 갈린다 — 실제로 한 번 갈렸다(납기를 `clockMs` 로
|
|
411
|
+
* 재고 `now()` 와 비교해 갓 만든 오더가 56년 늦은 것으로 판정됐다).
|
|
412
|
+
*
|
|
413
|
+
* 선언이 없으면 **빈 객체**를 준다 — 없는 약속을 만들지 않는다.
|
|
414
|
+
*/
|
|
415
|
+
protected promiseOf(spec: {
|
|
416
|
+
promisedLeadMinutes?: number;
|
|
417
|
+
priority?: number;
|
|
418
|
+
}): {
|
|
419
|
+
priority?: number;
|
|
420
|
+
startTime?: string;
|
|
421
|
+
endTime?: string;
|
|
422
|
+
};
|
|
316
423
|
protected randInt(min: number, max: number): number;
|
|
317
424
|
/**
|
|
318
425
|
* 관측 구동(P0 스파이크) — **이벤트로 커널을 굴린다.**
|
|
@@ -342,6 +449,28 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
342
449
|
* 같은 key 를 다시 실으면 덮어쓴다(정의가 권위).
|
|
343
450
|
*/
|
|
344
451
|
loadOperations(ops?: OperationDef[]): void;
|
|
452
|
+
/**
|
|
453
|
+
* 라우트(공정 순서) — 수율을 거슬러 올릴 때 필요하다. 기본은 모른다(선언 순서를 쓴다).
|
|
454
|
+
* 생산 정의를 가진 커널이 override 해서 자기 라우트를 답한다.
|
|
455
|
+
*/
|
|
456
|
+
protected routeKeys(): string[] | undefined;
|
|
457
|
+
/**
|
|
458
|
+
* **이 공장이 하루 몇 대를 낼 수 있는가** — 굴려 보지 않고 답한다.
|
|
459
|
+
*
|
|
460
|
+
* 커널이 직접 답하는 이유: 필요한 사실이 전부 여기 있다(공정 명세·설비와 신뢰도·인원·물리자산·
|
|
461
|
+
* 자리·근무 달력·시각 기준). 밖에서 모으면 그 값을 옮겨 적게 되고, 한쪽만 바뀌는 순간 "충분하다"
|
|
462
|
+
* 가 조용히 거짓이 된다. 계산 자체는 순수 함수(`analyzeCapacity`)에 맡긴다.
|
|
463
|
+
*
|
|
464
|
+
* 달력은 **자원이 선언한 것**을 쓴다. 자원마다 다른 달력을 쓰는 현장이면 대표를 고를 수 없으므로
|
|
465
|
+
* 그 사실을 결과에 실어 보낸다(`mixedCalendars`) — 조용히 하나를 골라 계산하면 천장이 틀린 채로
|
|
466
|
+
* 그럴듯해 보인다.
|
|
467
|
+
*/
|
|
468
|
+
capacity(opts: {
|
|
469
|
+
unitsPerDay: number;
|
|
470
|
+
sampleWeekStartMs: number;
|
|
471
|
+
}): CapacityAnalysis & {
|
|
472
|
+
mixedCalendars: boolean;
|
|
473
|
+
};
|
|
345
474
|
/**
|
|
346
475
|
* task 소요 산출 — 우선순위: **① 추정기(이력 보정) → ② 명세(ISA-95 Duration + 변동) → ③ 도메인 상수.**
|
|
347
476
|
*
|
|
@@ -385,11 +514,20 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
385
514
|
declaredDurations: number;
|
|
386
515
|
defaultDurations: number;
|
|
387
516
|
};
|
|
388
|
-
/**
|
|
517
|
+
/**
|
|
518
|
+
* `skuMix` 에서 weight 로 gtin 선택(rng) — 도착·오더 자극의 품목 결정.
|
|
519
|
+
*
|
|
520
|
+
* **빈 목록이면 고르지 않는다**(`undefined`). 예전에는 `mix[mix.length - 1].gtin` 으로 떨어져
|
|
521
|
+
* `mix[-1]` 이 undefined 가 되고 거기서 던졌다 — 그 예외가 서버까지 올라가 **정확도 추세 전체를
|
|
522
|
+
* 죽였다**(품목 구성을 선언하지 않은 자극 하나가 예측 전체를 껐다).
|
|
523
|
+
*
|
|
524
|
+
* 없는 품목을 지어내지 않는다: 무엇을 만들지 모르면 **만들지 않는 것**이 맞고, 부르는 쪽이
|
|
525
|
+
* 그 사실을 알고 건너뛴다.
|
|
526
|
+
*/
|
|
389
527
|
protected pickGtin(mix: {
|
|
390
528
|
gtin: string;
|
|
391
529
|
weight: number;
|
|
392
|
-
}[]): string;
|
|
530
|
+
}[]): string | undefined;
|
|
393
531
|
protected locationByType(type: string): FlowLocation | undefined;
|
|
394
532
|
/**
|
|
395
533
|
* process 변화 대수(EPCIS TransformationEvent · ISA-95 Material Consumed/Produced · 씬 Processable.transform).
|
|
@@ -534,6 +672,76 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
534
672
|
* 여기서는 고르기만 하고 잡지 않는다: 설비까지 확보된 뒤 `assignCrew` 가 확정한다
|
|
535
673
|
* (반쯤 잡고 실패하면 사람이 아무 일도 못 하면서 묶인다).
|
|
536
674
|
*/
|
|
675
|
+
/**
|
|
676
|
+
* 필요 자재를 확보한다 — **ISA-95 `OperationsSegment.MaterialSpecification`**(`use: 'consumed'`).
|
|
677
|
+
*
|
|
678
|
+
* ── 4대 자원 중 자재만 게이트가 없었다 ────────────────────────────────────
|
|
679
|
+
* 인원·자산·설비는 모자라면 기다리는데 **자재는 없어도 작업이 시작됐다.** 그래서 부품이 떨어져
|
|
680
|
+
* 라인이 서는 상황이 예측에 아예 나타나지 않았다 — 트레일러 조립 공장에서 그것은 가장 흔한 정지다.
|
|
681
|
+
*
|
|
682
|
+
* 인원·자산과 **같은 규칙**이다: 등급(또는 품목)으로 요구하고, **부분 확보 없이 전량 아니면 대기.**
|
|
683
|
+
* 자재는 **작업이 일어나는 자리에 있어야** 한다(다른 창고의 부품은 지금 쓸 수 없다).
|
|
684
|
+
*
|
|
685
|
+
* 소비 시점은 **작업 시작**이다 — 자재는 들어가는 순간 없어진다(완료 시점에 빼면 그 사이 다른
|
|
686
|
+
* 작업이 같은 부품을 또 잡는다). 산출(`produced`)은 여기서 다루지 않는다(완료 시점의 일이다).
|
|
687
|
+
*/
|
|
688
|
+
private claimMaterials;
|
|
689
|
+
/** 이 물품이 명세를 만족하나 — 품목 지목이 우선, 없으면 등급(상속을 타고 닫는다). */
|
|
690
|
+
private materialMatches;
|
|
691
|
+
/**
|
|
692
|
+
* 산출 자재를 만든다 — ISA-95 `MaterialSpecification`(`use: 'produced'`).
|
|
693
|
+
*
|
|
694
|
+
* **소비의 짝이다**: 자재는 작업이 시작될 때 들어가고 끝날 때 나온다. 산출이 없으면 조립 공정이
|
|
695
|
+
* 부품만 먹고 아무것도 내놓지 않아, 다음 공정이 영원히 재료를 기다린다(라인 전체가 한 칸에서 멈춘다).
|
|
696
|
+
*
|
|
697
|
+
* 산출물의 자리는 **작업이 끝난 자리**(`toNode`)다. 식별자는 품목 식별자에 작업 id 를 붙여 만든다 —
|
|
698
|
+
* **EPC 처럼 보이게 꾸미지 않는다**(상류가 준 직렬번호와 구별돼야 한다). 상류가 진짜 식별자를
|
|
699
|
+
* 말해 주면 그것이 들어올 자리는 관측 경로다.
|
|
700
|
+
*
|
|
701
|
+
* 같은 품목이 그 자리에 이미 있으면 **수량을 더한다** — 새 줄을 만들면 같은 자리의 같은 로트가
|
|
702
|
+
* 둘로 갈려 재고가 부푼다(§MaterialSubLot 에서 겪은 것과 반대 방향의 같은 오류).
|
|
703
|
+
*/
|
|
704
|
+
private produceMaterials;
|
|
705
|
+
/**
|
|
706
|
+
* 이 작업이 **딛고 선 것**이 아직 있나 — 없으면 무엇이 없는지 답한다.
|
|
707
|
+
*
|
|
708
|
+
* 작업은 혼자 서지 못한다: 옮길 **물품**과, (있다면) 그것을 시킨 **오더** 위에 선다. 진행 중에
|
|
709
|
+
* 둘 중 하나가 사라질 수 있다 — 물품은 포장·출하·소비로, 오더는 이미 이행돼 씨앗이 심지 않아서.
|
|
710
|
+
*
|
|
711
|
+
* 그때 도메인 훅은 없는 것을 딛으려다 던진다(`order.gtin` · `item.location`). 그 예외 하나가
|
|
712
|
+
* **예측 전체를 죽였다** — 사용자에게는 기능이 통째로 사라진 것으로 보였다. 그래서 완료 **전에**
|
|
713
|
+
* 여기서 묻고, 없으면 그 작업만 접는다.
|
|
714
|
+
*/
|
|
715
|
+
protected missingContextOf(t: FlowTask): 'item' | 'order' | 'domain' | undefined;
|
|
716
|
+
/**
|
|
717
|
+
* 이 도메인이 이 작업을 **끝맺을 수 있나** — 코어가 모르는 조건은 도메인이 답한다.
|
|
718
|
+
*
|
|
719
|
+
* 코어는 물품과 오더까지만 안다. 그런데 도메인은 더 필요할 수 있다 — MES 는 완료 시점에 **오더와
|
|
720
|
+
* 제품 정의**를 딛고 서고, 그 중 하나만 없어도 던진다. 코어가 그 조건을 추측하면 도메인마다 다른
|
|
721
|
+
* 가정을 코어에 박게 되므로(방언), **묻는다.**
|
|
722
|
+
*
|
|
723
|
+
* 기본은 `true` — 대부분의 작업은 코어가 확인한 것으로 충분하다.
|
|
724
|
+
*/
|
|
725
|
+
protected canComplete(_t: FlowTask): boolean;
|
|
726
|
+
/**
|
|
727
|
+
* 작업이 가리키는 물품을 찾는다 — **참조와 키가 다를 수 있다.**
|
|
728
|
+
*
|
|
729
|
+
* 물품 맵의 키는 `itemKeyOf`(직렬 물품은 `epc`, 로트의 부분은 `subLotId`)인데, 작업은 로트 식별자
|
|
730
|
+
* (`itemEpc`)로 가리킨다. 비직렬 로트에서는 둘이 **다르다** — 그래서 그냥 `get` 하면 못 찾는다.
|
|
731
|
+
* 실제로 그 회귀를 냈다: 예측(씨앗) 경로에서 `item.location = …` 이 `undefined` 위에서 던져
|
|
732
|
+
* **정확도 추세 전체가 서버 오류로 죽었다.**
|
|
733
|
+
*
|
|
734
|
+
* **부분이 여럿이면 풀지 않는다** — 어느 부분을 가리키는지 알 수 없고, 아무거나 고르면 그 뒤
|
|
735
|
+
* 이동·처분이 엉뚱한 자리에 적힌다. 그때는 참조가 부족한 것이고, 부르는 쪽이 그 사실을 말해야 한다.
|
|
736
|
+
*/
|
|
737
|
+
protected itemByRef(ref: string): FlowItem | undefined;
|
|
738
|
+
/**
|
|
739
|
+
* 실제 자재 이동을 작업에 적어 둔다 — ISA-95 `JobResponse.MaterialActual`.
|
|
740
|
+
* 같은 품목·같은 쓰임은 **한 줄로 합친다**(줄을 늘리면 실적을 세는 쪽이 중복을 걷어내야 한다).
|
|
741
|
+
*/
|
|
742
|
+
private recordMaterialActual;
|
|
743
|
+
/** 확보한 자재를 **작업 시작 시점에 소비**한다 — 수량이 0 이 되면 물품 자체가 사라진다. */
|
|
744
|
+
private consumeMaterials;
|
|
537
745
|
private claimPersonnel;
|
|
538
746
|
/**
|
|
539
747
|
* 필요 설비를 고른다 — **인원·자산과 같은 규칙**(등급으로 요구, 부분 확보 없이 전량 아니면 대기).
|
|
@@ -554,6 +762,61 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
554
762
|
private stationFull;
|
|
555
763
|
/** 교대 밖인가 — 자원이 지금 일하지 않는 이유 중 고장·계획정지와 구별되는 세 번째. */
|
|
556
764
|
protected offShift(m: FlowEquipment): boolean;
|
|
765
|
+
/** 왜 쉬는가 — 휴일·휴게(non-working)인지 주말·교대 사이(off-hours)인지(§OffCalendarReason). */
|
|
766
|
+
protected offReason(r: {
|
|
767
|
+
window?: {
|
|
768
|
+
startHour: number;
|
|
769
|
+
endHour: number;
|
|
770
|
+
};
|
|
771
|
+
workCalendar?: WorkCalendarEntry[];
|
|
772
|
+
}): {
|
|
773
|
+
offShiftReason?: OffCalendarReason;
|
|
774
|
+
};
|
|
775
|
+
/**
|
|
776
|
+
* 유효 기간 밖인가 — **네 번째 이유**(§Effectivity). 설비·사람·자산이 같은 규칙을 쓴다.
|
|
777
|
+
*
|
|
778
|
+
* 시뮬 시각을 ISO 로 풀어 계약의 `effectivityAt` 에 넘긴다. **판정은 커널에 두지 않는다** —
|
|
779
|
+
* 호스트(라이브 관측)도 같은 판정을 해야 하고, 규칙이 두 벌이면 갈라진다.
|
|
780
|
+
*/
|
|
781
|
+
protected effectivityOf(r: EffectivePeriod): Effectivity | undefined;
|
|
782
|
+
/**
|
|
783
|
+
* 지금 어느 교대인가 — 계약의 `activeShiftOf` 에 위임한다(미러도 같은 함수를 쓴다).
|
|
784
|
+
* 선언이 없으면 `undefined`: 교대를 나눠 놓지 않은 현장에 이름을 지어내지 않는다.
|
|
785
|
+
*/
|
|
786
|
+
protected shiftOf(r: {
|
|
787
|
+
workCalendar?: WorkCalendarEntry[];
|
|
788
|
+
}): string | undefined;
|
|
789
|
+
/** 유효 기간 밖이라 이 시각의 모델에 참여하지 않는가 — 배정 게이트가 쓰는 형태. */
|
|
790
|
+
protected outOfEffect(r: EffectivePeriod): boolean;
|
|
791
|
+
/**
|
|
792
|
+
* 스냅샷·델타에 실을 조각 — **선언한 기간과 판정을 함께** 낸다.
|
|
793
|
+
*
|
|
794
|
+
* 판정만 내면 화면이 "왜" 를 말할 수 없고(언제 폐기됐나), 기간만 내면 소비처마다 다시 판정해 갈라진다.
|
|
795
|
+
* 선언이 없으면 아무것도 붙이지 않는다(대부분의 자원이 그렇다 — 필드를 늘리지 않는다).
|
|
796
|
+
*/
|
|
797
|
+
protected effectivePart(r: EffectivePeriod): Partial<EffectivePeriod> & {
|
|
798
|
+
effectivity?: Effectivity;
|
|
799
|
+
};
|
|
800
|
+
/**
|
|
801
|
+
* 지금 근무 시간 밖인가 — **캘린더가 있으면 그것으로, 없으면 옛 `window` 로** 판정한다.
|
|
802
|
+
*
|
|
803
|
+
* `window`(시 단위 하나)는 캘린더의 특수한 경우다. 둘을 한 함수로 모아 두면 인원·설비가 같은 규칙을
|
|
804
|
+
* 쓴다(예전에는 같은 식이 두 곳에 복사돼 있었다 — 한쪽만 고치면 조용히 갈라진다).
|
|
805
|
+
*/
|
|
806
|
+
protected offCalendar(r: {
|
|
807
|
+
window?: {
|
|
808
|
+
startHour: number;
|
|
809
|
+
endHour: number;
|
|
810
|
+
};
|
|
811
|
+
workCalendar?: WorkCalendarEntry[];
|
|
812
|
+
}): boolean;
|
|
813
|
+
/**
|
|
814
|
+
* 시뮬 시각의 분(0..1439) — 캘린더 판정의 기준.
|
|
815
|
+
*
|
|
816
|
+
* **보드가 선언한 기준으로 읽는다**(`utcOffsetMinutes`). 예전에는 UTC 로 읽어서 Rosarito(UTC−7)의
|
|
817
|
+
* 06시 교대가 7시간 틀렸다 — 시각대만 적고 기준을 안 적으면 반드시 이렇게 된다.
|
|
818
|
+
*/
|
|
819
|
+
protected minuteOfDay(): number;
|
|
557
820
|
/** 시뮬 시각의 시(0..23) — 운영시간·시간대 배율의 기준. */
|
|
558
821
|
protected hourOfDay(): number;
|
|
559
822
|
/**
|