@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/contract.d.ts
CHANGED
|
@@ -149,6 +149,41 @@ export interface ResourceProperty {
|
|
|
149
149
|
* 자리가 없으면 사실이 들어오지 못한다.
|
|
150
150
|
*/
|
|
151
151
|
export type TestSpecificationRefs = string[];
|
|
152
|
+
/**
|
|
153
|
+
* 유효 기간 — **ISA-95 `EffectiveStartDate` / `EffectiveEndDate`.**
|
|
154
|
+
*
|
|
155
|
+
* 1차 출처(B2MML v0701): `EquipmentType` · `PersonType` · `PhysicalAssetType` **세 개체 타입 모두**에
|
|
156
|
+
* 있고, 등급 타입 세 개에도 있다. 즉 표준은 이것을 **개체와 등급 양쪽의 공통 축**으로 두었다.
|
|
157
|
+
*
|
|
158
|
+
* **없을 때 무엇이 틀렸나**: 등급에만 있어서(§ResourceClassDef) **폐기한 설비가 영구히 살아 있었다.**
|
|
159
|
+
* 3월에 폐차한 지게차가 7월에도 배정 대상이고, 가용 대수 분모에 들어가 가동률을 낮추고, 화면에 계속
|
|
160
|
+
* 떴다. 도입 예정 설비도 마찬가지로 오늘부터 있는 것처럼 보였다.
|
|
161
|
+
*/
|
|
162
|
+
export interface EffectivePeriod {
|
|
163
|
+
/** 이 시각부터 유효 — 표준 `EffectiveStartDate`. 없으면 "언제부터인지 따지지 않는다". */
|
|
164
|
+
effectiveStart?: ISOTime;
|
|
165
|
+
/** 이 시각까지 유효 — 표준 `EffectiveEndDate`. 없으면 "끝이 정해지지 않았다". */
|
|
166
|
+
effectiveEnd?: ISOTime;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* 유효 기간 밖인 **이유** — 없으면(`undefined`) 유효하다.
|
|
170
|
+
*
|
|
171
|
+
* **왜 참/거짓이 아닌가**: "아직 없다" 와 "이제 없다" 는 사용자가 알고 싶은 **다른 사실**이다. 도입
|
|
172
|
+
* 예정 설비와 폐기한 설비가 화면에서 같은 회색으로 보이면 "왜 안 움직이나" 에 답할 수 없다 —
|
|
173
|
+
* 고장(`down`)·계획정지(`held`)·교대 밖(`offShift`)을 굳이 나눠 둔 것과 같은 이유다.
|
|
174
|
+
*/
|
|
175
|
+
export type Effectivity = 'not-yet' | 'expired';
|
|
176
|
+
/**
|
|
177
|
+
* 이 시각에 유효 기간 밖인가 — **한 규칙**으로 개체·등급·설비↔자산 매핑을 모두 판정한다.
|
|
178
|
+
*
|
|
179
|
+
* **표준은 날짜만 정하고 "밖이면 어떻게 되는가" 는 정하지 않는다.** 그 판단은 우리 것이므로 여기 밝힌다:
|
|
180
|
+
* 기간 밖이면 **그 자원은 그 시각의 모델에 참여하지 않는다**(배정되지 않고, 가용 분모에 들지 않는다).
|
|
181
|
+
* 지우지는 않는다 — 이유를 달아 남긴다. 조용히 사라지면 "왜 없어졌나" 를 아무도 답할 수 없다.
|
|
182
|
+
*
|
|
183
|
+
* `at` 를 주지 않으면 **판단하지 않는다**(`undefined`). 모르는 시각으로 폐기를 단정하면, 시각을 안 넘긴
|
|
184
|
+
* 소비처 전부가 자원을 잃는다. 파싱 불가한 시각도 같다 — 짐작해 고치지 않는다.
|
|
185
|
+
*/
|
|
186
|
+
export declare function effectivityAt(p: EffectivePeriod | undefined, at?: ISOTime): Effectivity | undefined;
|
|
152
187
|
/**
|
|
153
188
|
* 자원 **등급 정의** — ISA-95 가 세 자원에 똑같이 정의한 한 모양.
|
|
154
189
|
*
|
|
@@ -165,7 +200,7 @@ export type TestSpecificationRefs = string[];
|
|
|
165
200
|
* 그 사람은 요구를 만족하지 못한다. 표준이 `ClassBaseID` 를 복수로 둔 이유가 이것이고(다중 상속),
|
|
166
201
|
* 우리는 소속을 **상속을 타고 닫아** 판정한다(`classClosure`).
|
|
167
202
|
*/
|
|
168
|
-
export interface ResourceClassDef {
|
|
203
|
+
export interface ResourceClassDef extends EffectivePeriod {
|
|
169
204
|
id: string;
|
|
170
205
|
description?: string;
|
|
171
206
|
/** 상위 등급들 — 표준 `<X>ClassBaseID`(복수). 순환은 `classClosure` 가 끊는다. */
|
|
@@ -174,15 +209,6 @@ export interface ResourceClassDef {
|
|
|
174
209
|
properties?: ResourceProperty[];
|
|
175
210
|
/** 이 등급의 적격을 정하는 시험 명세들 — 표준 `TestSpecificationID`. */
|
|
176
211
|
testSpecificationIds?: TestSpecificationRefs;
|
|
177
|
-
/**
|
|
178
|
-
* 유효 기간 — 표준 `EffectiveStartDate` / `EffectiveEndDate`(ISO 시각).
|
|
179
|
-
*
|
|
180
|
-
* **표준은 날짜만 정하고 "밖이면 어떻게 되는가" 는 정하지 않는다.** 그 판단은 소비처 몫이므로
|
|
181
|
-
* 우리가 정한다: **기간 밖이면 그 등급으로 자격이 성립하지 않는다**(만료된 자격으로 배정되지 않는다).
|
|
182
|
-
* 우리가 정한 규칙이라는 사실을 여기 밝힌다.
|
|
183
|
-
*/
|
|
184
|
-
effectiveStart?: ISOTime;
|
|
185
|
-
effectiveEnd?: ISOTime;
|
|
186
212
|
}
|
|
187
213
|
/**
|
|
188
214
|
* 등급 소속을 **상속을 타고 닫는다** — "이 개체가 이 등급으로 통하는가".
|
|
@@ -193,6 +219,283 @@ export interface ResourceClassDef {
|
|
|
193
219
|
* `at` 를 주면 **유효 기간 밖의 등급은 제외**한다. 안 주면 기간을 보지 않는다(모르면 판단하지 않는다).
|
|
194
220
|
*/
|
|
195
221
|
export declare function classClosure(directIds: readonly string[] | undefined, defs: readonly ResourceClassDef[] | undefined, at?: ISOTime): Set<string>;
|
|
222
|
+
/**
|
|
223
|
+
* 우선순위 — **ISA-95 `Priority`**(`JobOrderType`·`OperationsRequestType`, 타입은 `PriorityType` =
|
|
224
|
+
* `NumericType` 제한). 즉 표준은 **숫자라는 것만 정하고 방향은 정하지 않는다.**
|
|
225
|
+
*
|
|
226
|
+
* **그래서 방향은 우리가 정한다: 작은 값이 급하다(1 = 가장 급함).** 흔한 관행이고, 무엇보다
|
|
227
|
+
* 한쪽으로 못 박아 두지 않으면 소비처마다 반대로 읽는다. 우리가 정한 규약이라는 사실을 여기 밝힌다.
|
|
228
|
+
*
|
|
229
|
+
* 미지정은 **0 이 아니라 "우선순위 없음"** 이다 — 선언한 것들 뒤에 선다(0 으로 채우면 미지정이
|
|
230
|
+
* 가장 급한 것이 된다).
|
|
231
|
+
*/
|
|
232
|
+
export declare const PRIORITY_UNSET: number;
|
|
233
|
+
/** 정렬 키 — 미지정을 맨 뒤로 보낸다. 같은 우선순위는 **입력 순서**를 지킨다(결정성). */
|
|
234
|
+
export declare function priorityRank(p?: number): number;
|
|
235
|
+
/**
|
|
236
|
+
* 납기 대비 상태 — **파생**이다(저장하지 않는다). `locationStatusOf` 와 같은 규율.
|
|
237
|
+
*
|
|
238
|
+
* 예정 창(`endTime`)이 없으면 `undefined` — **"늦지 않았다" 가 아니라 "판단할 수 없다"** 다.
|
|
239
|
+
* 납기가 없는데 정시라고 말하면 그건 없는 사실을 만드는 것이다.
|
|
240
|
+
*/
|
|
241
|
+
export declare function dueStatusOf(x: {
|
|
242
|
+
endTime?: ISOTime;
|
|
243
|
+
}, nowIso?: ISOTime): 'on-time' | 'late' | undefined;
|
|
244
|
+
/**
|
|
245
|
+
* 자재 수량 하나 — **ISA-95 `MaterialLot.Quantity`(`maxOccurs="unbounded"`)** 의 한 항목.
|
|
246
|
+
* 타입은 `QuantityValueType` = `QuantityString` + `DataType?` + `UnitOfMeasure?` + `Key?`.
|
|
247
|
+
*
|
|
248
|
+
* **왜 복수인가**: 같은 로트를 여러 단위로 함께 쓴다 — *100 EA · 250 KG · 5 CS*. 창고는 개수로 세고,
|
|
249
|
+
* 운송은 무게로 싣고, 주문은 케이스로 온다. 하나만 담을 수 있으면 나머지는 **버려진다.**
|
|
250
|
+
*
|
|
251
|
+
* **단위를 환산하지 않는다.** 100 EA 가 250 KG 인지는 품목마다 다른 계수이고 우리에게 없다.
|
|
252
|
+
* `quantityIn` 은 **선언된 단위만** 답하고, 없으면 `undefined` 다(계산해서 만들지 않는다).
|
|
253
|
+
*
|
|
254
|
+
* 값은 **숫자**로 든다 — 우리 인제스트 정본이 EPCIS 이고 `QuantityElement.quantity` 가 숫자다.
|
|
255
|
+
* ISA-95 는 문자열(`QuantityString`)로 두지만, 두 표준 사이에서는 EPCIS 쪽을 따른다(§정본).
|
|
256
|
+
*/
|
|
257
|
+
export interface MaterialQuantity {
|
|
258
|
+
value: number;
|
|
259
|
+
/** UN/CEFACT 권고 20 코드(`EA`·`KGM`·`CS`…). 없으면 개수로 읽는다(EPCIS 규약과 같다). */
|
|
260
|
+
uom?: string;
|
|
261
|
+
/** 표준 `DataType` — 미지정이면 형을 짐작하지 않는다. */
|
|
262
|
+
dataType?: string;
|
|
263
|
+
/** 표준 `Key` — 같은 단위가 여러 번 올 때 구별하는 이름(예: 정미/총). */
|
|
264
|
+
key?: string;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* 선언된 단위로 수량을 읽는다 — **환산하지 않는다.**
|
|
268
|
+
*
|
|
269
|
+
* 없으면 `undefined`: "0" 이 아니고 "계산한 값" 도 아니다. 환산 계수를 모르는데 값을 만들면
|
|
270
|
+
* 그 뒤 모든 계산이 거짓 위에 선다.
|
|
271
|
+
*/
|
|
272
|
+
/**
|
|
273
|
+
* 로트의 부분 식별자를 **한 규칙으로** 만든다 — 표준 `MaterialSubLot.ID`.
|
|
274
|
+
*
|
|
275
|
+
* 시뮬(생산)과 미러(관측)가 각자 만들면 같은 부분이 다른 이름을 갖고, 두 구동이 갈라진다
|
|
276
|
+
* (적합성 하네스가 실제로 잡았다). 부분을 가르는 것은 **자리**다.
|
|
277
|
+
*/
|
|
278
|
+
/**
|
|
279
|
+
* 물품을 구별하는 키 — 직렬 물품은 `epc`, 로트의 부분은 `subLotId`(표준 `MaterialSubLot.ID`).
|
|
280
|
+
*
|
|
281
|
+
* **한 곳에서 정한다.** 소비처마다 `epc` 로 키를 잡으면 같은 로트의 두 부분이 하나로 접히고,
|
|
282
|
+
* 그 순간 재고가 조용히 줄어든다(실제로 그랬다 — §ItemState.subLotId).
|
|
283
|
+
*/
|
|
284
|
+
export declare function subLotIdOf(classUri: string, location: string): string;
|
|
285
|
+
export declare function itemKeyOf(item: {
|
|
286
|
+
epc: string;
|
|
287
|
+
subLotId?: string;
|
|
288
|
+
}): string;
|
|
289
|
+
export declare function quantityIn(item: {
|
|
290
|
+
qty?: number;
|
|
291
|
+
uom?: string;
|
|
292
|
+
quantities?: MaterialQuantity[];
|
|
293
|
+
gtin?: string;
|
|
294
|
+
definitionId?: string;
|
|
295
|
+
}, uom?: string, definitions?: MaterialDefinitionIndex): number | undefined;
|
|
296
|
+
/**
|
|
297
|
+
* 품목 정의 — **ISA-95 `MaterialDefinition`.**
|
|
298
|
+
*
|
|
299
|
+
* 1차 출처(B2MML v0701) `MaterialDefinitionType`: `ID` · `Version` · `Description*` · `PublishedDate` ·
|
|
300
|
+
* `EffectiveStartDate`/`EffectiveEndDate` · `HierarchyScope` · `SpatialDefinition` ·
|
|
301
|
+
* **`MaterialDefinitionProperty*`** · **`MaterialClassID*`(복수)** · `MaterialLotSourceID*` ·
|
|
302
|
+
* `TestSpecificationID*` · `AssemblyDefinition*`.
|
|
303
|
+
*
|
|
304
|
+
* ── 없을 때 무엇이 안 됐나 ────────────────────────────────────────────────
|
|
305
|
+
* 로트만 관측으로 들어오고 **품목이 무엇인지는 아무 데도 없었다.** 그래서 같은 로트를 100 EA 로
|
|
306
|
+
* 받아도 "몇 kg 인가" 에 답할 수 없었고(§quantityIn 이 환산을 거부하는 근거), 화면은 GTIN 원문을
|
|
307
|
+
* 그대로 보여 줄 수밖에 없었다.
|
|
308
|
+
*
|
|
309
|
+
* ── 표준에는 환산 요소가 없다 ─────────────────────────────────────────────
|
|
310
|
+
* 전수 대조 결과 `MaterialDefinitionType` 에 단위 환산을 담을 **전용 요소가 없다.** 표준이 주는 것은
|
|
311
|
+
* **속성 주머니**(`MaterialDefinitionProperty`)뿐이다 — 설비 속도(`EQUIPMENT_PROPERTY.speed`)와 같은
|
|
312
|
+
* 상황이다. 그래서 자리는 표준 것을 쓰고 **이름은 우리가 정하고 밝힌다**(§MATERIAL_PROPERTY).
|
|
313
|
+
*/
|
|
314
|
+
export interface MaterialDefinition extends EffectivePeriod {
|
|
315
|
+
/** 표준 `ID` — 우리는 **GTIN**(`urn:epc:idpat:sgtin:…`)을 쓴다. 정체성의 정본이 EPCIS 이기 때문이다. */
|
|
316
|
+
id: string;
|
|
317
|
+
description?: string;
|
|
318
|
+
/** 속한 등급들 — 표준 `MaterialClassID`(**복수**). 인원·자산과 같은 이유로 복수다. */
|
|
319
|
+
materialClassIds?: string[];
|
|
320
|
+
/** 품목 속성 — 표준 `MaterialDefinitionProperty`. 환산 계수가 여기 들어간다(§MATERIAL_PROPERTY). */
|
|
321
|
+
properties?: ResourceProperty[];
|
|
322
|
+
/** 적격을 검증한 시험 명세들 — 표준 `MaterialDefinition.TestSpecificationID`. */
|
|
323
|
+
testSpecificationIds?: TestSpecificationRefs;
|
|
324
|
+
}
|
|
325
|
+
/** 품목 정의 색인 — id(GTIN) → 정의. 소비처가 매번 배열을 훑지 않게. */
|
|
326
|
+
export type MaterialDefinitionIndex = ReadonlyMap<string, MaterialDefinition>;
|
|
327
|
+
/**
|
|
328
|
+
* **우리가 정한 품목 속성 이름** — 표준은 자리만 정하고 이름을 정하지 않는다.
|
|
329
|
+
*
|
|
330
|
+
* `perBaseUnit`: 값은 **기준 단위 하나당 그 단위의 양**이고, 단위는 속성의 `uom` 이 말한다.
|
|
331
|
+
* 예) 한 개(EA)가 2.5 kg 이면 `{ id: 'perBaseUnit', value: 2.5, uom: 'KGM' }`.
|
|
332
|
+
*
|
|
333
|
+
* **왜 이 모양인가**: 임의의 단위쌍 환산표(CS↔KGM↔EA…)는 품목마다 다르고 연쇄가 필요해 금세
|
|
334
|
+
* 커진다. 현장에서 실제로 필요한 것은 **"세는 단위에서 다른 단위로"** 이므로, 기준 단위를 축으로
|
|
335
|
+
* 두면 선언 한 줄로 끝난다. 기준 단위가 아닌 수량에서 출발하는 환산은 **하지 않는다**(§convertQuantity).
|
|
336
|
+
*/
|
|
337
|
+
export declare const MATERIAL_PROPERTY: {
|
|
338
|
+
/** 기준 단위 하나당 이 단위의 양. `uom` 이 대상 단위. */
|
|
339
|
+
readonly perBaseUnit: "perBaseUnit";
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* 이 품목에서 `uom` 으로 가는 계수 — **선언된 것만.** 없으면 `undefined`(추정하지 않는다).
|
|
343
|
+
*/
|
|
344
|
+
export declare function conversionFactorOf(def: MaterialDefinition | undefined, uom?: string): number | undefined;
|
|
345
|
+
/**
|
|
346
|
+
* 근무·비근무 구간 하나 — **ISA-95 `WorkCalendarEntry`.**
|
|
347
|
+
*
|
|
348
|
+
* 1차 출처(B2MML v0701) `WorkCalendarEntryType`: `ID` · `Description*` · `StartDateTime` ·
|
|
349
|
+
* `FinishDateTime` · `EntryType` · `WorkCalendarEntryChild*` · `WorkCalendarEntryProperty*`.
|
|
350
|
+
* 반복 규칙은 `WorkCalendarDefinitionEntryType.RecurrenceTime`/`DurationRule` 에 있다.
|
|
351
|
+
*
|
|
352
|
+
* **프레임워크와 같은 모양으로 맞췄다** — `@things-factory/work-shift` 의 `WorkShift` 는
|
|
353
|
+
* `fromDate`(전일 −1 · 당일 0 · 익일 +1) · `fromTime` · `toDate` · `toTime` 로 교대를 든다.
|
|
354
|
+
* 그것이 자정을 넘는 교대를 정확히 표현하는 모양이고, 표준의 Start/Finish 와도 맞는다. 그래서 우리도
|
|
355
|
+
* 같은 축을 쓴다 — 나중에 그 엔티티를 정본으로 결선할 때 **직선 매핑**이 되게(재발명 금지).
|
|
356
|
+
*
|
|
357
|
+
* 여기 담는 것은 **하루 안의 되풀이 구간**이다(달력 날짜가 아니다). 특정 날짜의 휴일·정비는 표준
|
|
358
|
+
* `WorkCalendarEntry` 의 `StartDateTime`/`FinishDateTime` 이 담는 영역이고 아직 없다(⬜).
|
|
359
|
+
*
|
|
360
|
+
* ── 시각의 기준을 반드시 밝혀야 한다 ──────────────────────────────────────
|
|
361
|
+
* 표준은 `StartDateTime`/`FinishDateTime`(**절대 시각**)을 쓴다. 우리가 시각대(`HH:MM`)로 두는 것은
|
|
362
|
+
* 되풀이를 간단히 적기 위한 선택이고, 그러면 **어느 기준의 06:00 인가**가 반드시 필요하다 —
|
|
363
|
+
* Rosarito(UTC−7)의 06:00 을 UTC 로 읽으면 7시간이 틀린다.
|
|
364
|
+
*
|
|
365
|
+
* 그래서 `BoardDef.utcOffsetMinutes` 가 그 기준이다. **선언하지 않으면 UTC 로 읽고, 그 사실을 여기
|
|
366
|
+
* 밝힌다**(조용히 가정하지 않기 위해). 프레임워크에는 이미 테넌트 시간대(`Domain.timezone`)와
|
|
367
|
+
* 교대→절대구간 변환(`@things-factory/work-shift` `work-shift-range`, moment-timezone)이 있으므로,
|
|
368
|
+
* **기준을 푸는 일은 호스트의 몫**이다(커널은 zero-dep — 시간대 데이터를 들 수 없다).
|
|
369
|
+
*
|
|
370
|
+
* 한계도 적는다: 고정 오프셋은 **일광절약시간을 따라가지 못한다.** 긴 지평선에서 정확히 하려면
|
|
371
|
+
* 호스트가 표준대로 **절대 구간**을 계산해 넣어야 한다(⬜ — 그때 이 필드는 필요 없어진다).
|
|
372
|
+
*/
|
|
373
|
+
export interface WorkCalendarEntry {
|
|
374
|
+
/**
|
|
375
|
+
* 표준 `WorkCalendarEntryType.ID` — **교대의 이름**(`A`·`B`·`C`, `night`…).
|
|
376
|
+
*
|
|
377
|
+
* 3교대처럼 **하루를 끊김 없이 덮는** 현장에서는 이 이름이 이 선언의 거의 전부다: 설비 가용은
|
|
378
|
+
* 24시간 그대로이므로 "쉬는 시간" 은 생기지 않고, 대신 **"이 일이 어느 교대에 일어났나"** 를
|
|
379
|
+
* 말할 수 있게 된다. 그 축이 없으면 교대별 성과를 물을 수 없다(`activeShiftOf`).
|
|
380
|
+
*/
|
|
381
|
+
id?: string;
|
|
382
|
+
/**
|
|
383
|
+
* **한 번뿐인 구간** — 표준 `WorkCalendarEntry.StartDateTime` / `FinishDateTime`(절대 시각).
|
|
384
|
+
*
|
|
385
|
+
* ── 표준은 둘로 나눈다 ───────────────────────────────────────────────────
|
|
386
|
+
* `WorkCalendarEntryType` 은 **절대 구간**이고, 되풀이 규칙은 `WorkCalendarDefinitionEntryType`
|
|
387
|
+
* (`RecurrenceTime`/`DurationRule`)에 있으며 항목이 `WorkCalendarDefintionEntryID` 로 그것을 가리킨다.
|
|
388
|
+
* 우리 `fromTime`/`toTime` 은 **되풀이 쪽**에 해당하는 우리식 단순형이고, **절대 구간 자체가 없었다.**
|
|
389
|
+
*
|
|
390
|
+
* 그래서 **휴일·연휴·특정일 정비창을 표현할 수 없었다** — 하루 안에서 반복되는 것만 말할 수 있었다.
|
|
391
|
+
* 8월 15일 하루를 쉰다는 사실을 `HH:MM` 으로는 적을 방법이 없다.
|
|
392
|
+
*
|
|
393
|
+
* 절대 구간은 **그 자체로 완결**이라 시각 기준(`utcOffsetMinutes`)이 필요 없다 — ISO 시각에 이미
|
|
394
|
+
* 들어 있다. 일광절약시간 문제도 여기서는 생기지 않는다(§4-7 이 남긴 한계가 이 항목에는 없다).
|
|
395
|
+
*/
|
|
396
|
+
startDateTime?: ISOTime;
|
|
397
|
+
finishDateTime?: ISOTime;
|
|
398
|
+
/**
|
|
399
|
+
* **어느 요일에만** — 0 일요일 … 6 토요일. 없으면 매일이다(기존 거동).
|
|
400
|
+
*
|
|
401
|
+
* ── 표준에 되풀이 표기가 없다 (1차 출처 확인) ────────────────────────────
|
|
402
|
+
* `WorkCalendarDefinitionEntryType.RecurrenceTime`·`DurationRule` 은 **`CodeType`**, 즉 **불투명한
|
|
403
|
+
* 코드**다 — 표준은 자리만 두고 문법을 정하지 않는다. 설비 속도(`EQUIPMENT_PROPERTY.speed`)·
|
|
404
|
+
* 단위 환산(`MATERIAL_PROPERTY.perBaseUnit`)과 같은 상황이라, **우리가 정하고 밝힌다.**
|
|
405
|
+
*
|
|
406
|
+
* ── 없을 때 무엇을 못 했나 ───────────────────────────────────────────────
|
|
407
|
+
* `HH:MM` 되풀이는 **하루 안**만 말한다. 그래서 **주말에 안 도는 공장을 표현할 수 없었다** —
|
|
408
|
+
* 토·일을 쉬는 현장이 24시간 x 7일 도는 것으로 예측됐고, 그만큼 처리량이 부풀었다.
|
|
409
|
+
*
|
|
410
|
+
* 요일은 **선언된 시각 기준**으로 읽는다(Rosarito 의 일요일과 UTC 의 일요일은 다르다).
|
|
411
|
+
*
|
|
412
|
+
* ── 자정을 넘는 교대는 **시작한 날**로 센다 ─────────────────────────────
|
|
413
|
+
* 야간 교대(22:00→06:00)에 `[1..5]`(월~금)를 주면 뜻은 **"월~금에 시작한다"** 이다. 그래야
|
|
414
|
+
* **금요일 밤 교대가 토요일 새벽까지** 이어지고, **일요일 밤에 없는 교대가 월요일 새벽에 생기지
|
|
415
|
+
* 않는다.** 순간의 요일로 세면 그 둘이 정확히 반대로 틀린다.
|
|
416
|
+
*/
|
|
417
|
+
daysOfWeek?: number[];
|
|
418
|
+
/** 시작 날짜 오프셋 — `-1` 전일 · `0` 당일 · `+1` 익일(프레임워크 `WorkShiftDateType` 와 같은 축). */
|
|
419
|
+
fromDayOffset?: -1 | 0 | 1;
|
|
420
|
+
/** 시작 시각 `HH:MM`(24시간). 분 단위까지 — 시(hour)만으로는 07:30 교대를 표현할 수 없다.
|
|
421
|
+
* **절대 구간(`startDateTime`)을 쓰는 항목에는 없다.** */
|
|
422
|
+
fromTime?: string;
|
|
423
|
+
toDayOffset?: -1 | 0 | 1;
|
|
424
|
+
/** 종료 시각 `HH:MM`. 시작보다 이르면 자정을 넘는 것으로 읽는다. */
|
|
425
|
+
toTime?: string;
|
|
426
|
+
/**
|
|
427
|
+
* 표준 `EntryType` — 이 구간이 **근무**인가 **비근무**(휴일·정비·휴게)인가.
|
|
428
|
+
* 미지정은 `working`(선언한 구간은 일하는 시간이라는 흔한 뜻).
|
|
429
|
+
*/
|
|
430
|
+
entryType?: 'working' | 'non-working';
|
|
431
|
+
}
|
|
432
|
+
/** 선언된 기준의 요일(0 일요일 … 6 토요일). */
|
|
433
|
+
export declare function weekdayAt(ms: number, utcOffsetMinutes?: number): number;
|
|
434
|
+
export declare function inWorkCalendar(entries: readonly WorkCalendarEntry[] | undefined, minuteOfDay: number): boolean;
|
|
435
|
+
/**
|
|
436
|
+
* 이 **시각**이 근무 시간인가 — 되풀이(`HH:MM`)와 **한 번뿐인 구간**(휴일·정비창)을 함께 본다.
|
|
437
|
+
*
|
|
438
|
+
* 절대 구간은 시각 기준이 필요 없다(ISO 시각에 이미 들어 있다). 되풀이는 **선언된 기준**으로 읽는다.
|
|
439
|
+
* 겹치면 규칙은 하나다 — **비근무가 근무를 이긴다**(휴일이 교대 위에 얹힌다).
|
|
440
|
+
*/
|
|
441
|
+
export declare function inWorkCalendarAt(entries: readonly WorkCalendarEntry[] | undefined, atMs: number, utcOffsetMinutes?: number): boolean;
|
|
442
|
+
/**
|
|
443
|
+
* 지금 어느 교대인가 — **근무 구간의 이름**(표준 `WorkCalendarEntryType.ID`).
|
|
444
|
+
*
|
|
445
|
+
* 비근무가 이기는 규칙은 여기서도 같다: 휴게·정비 중이면 **어느 교대도 아니다**(`undefined`).
|
|
446
|
+
* 이름 없는 구간은 이름을 지어내지 않는다 — 교대를 나눠 놓지 않은 현장에서 `'1'` 같은 값을
|
|
447
|
+
* 만들어 붙이면, 그 뒤 모든 교대별 집계가 없는 구분 위에 선다.
|
|
448
|
+
*
|
|
449
|
+
* 겹치는 근무 구간이 여럿이면 **먼저 선언된 것**을 답한다(선언 순서가 현장의 우선순위다).
|
|
450
|
+
*/
|
|
451
|
+
export declare function activeShiftOf(entries: readonly WorkCalendarEntry[] | undefined, minuteOfDay: number): string | undefined;
|
|
452
|
+
/**
|
|
453
|
+
* 이 **시각**에 어느 교대인가 — 휴일까지 반영한다.
|
|
454
|
+
*
|
|
455
|
+
* 휴일에 일어난 일은 **어느 교대에도 속하지 않는다**(그날 교대는 서지 않았다). 되풀이만 보는
|
|
456
|
+
* `activeShiftOf` 로는 그것을 알 수 없어, 휴일에 찍힌 기록이 평소 교대로 집계됐다.
|
|
457
|
+
*/
|
|
458
|
+
export declare function activeShiftAt(entries: readonly WorkCalendarEntry[] | undefined, atMs: number, utcOffsetMinutes?: number): string | undefined;
|
|
459
|
+
/**
|
|
460
|
+
* 절대 시각(ms) → **선언된 기준의** 하루 중 분(0..1439).
|
|
461
|
+
*
|
|
462
|
+
* `HH:MM` 만으로는 "어느 기준의 06시" 인지 알 수 없다. 예전에 이것을 UTC 로 읽어 Rosarito(UTC−7)의
|
|
463
|
+
* 06시 교대가 **7시간 틀렸다.** 선언이 없으면 UTC 이고, 그 기본값을 숨기지 않고 밝힌다.
|
|
464
|
+
*/
|
|
465
|
+
export declare function minuteOfDayAt(ms: number, utcOffsetMinutes?: number): number;
|
|
466
|
+
/**
|
|
467
|
+
* 이 시각에 **근무 시간 밖인가** — 캘린더가 있으면 그것으로, 없으면 옛 `window` 로 판정한다.
|
|
468
|
+
*
|
|
469
|
+
* **두 구동이 이 함수를 함께 쓴다.** 예전에는 이 판정이 시뮬 안에만 있고 미러에는 **아예 없었다** —
|
|
470
|
+
* 설비 델타에는 `offShift` 자리조차 없어서, 미러의 설비는 점심 휴게 중에도 "대기" 로 보였다.
|
|
471
|
+
* 사람 델타에는 자리가 있었지만 그것은 **전이 순간의 값**이라, 유휴로 교대 경계를 넘긴 사람은
|
|
472
|
+
* 옛 판정에 머물렀다. 시각으로만 바뀌는 사실은 실어 보낼 수 없고 **각자 계산해야** 한다.
|
|
473
|
+
*/
|
|
474
|
+
/**
|
|
475
|
+
* **왜 안 하고 있나** — 쉬는 이유를 가른다.
|
|
476
|
+
*
|
|
477
|
+
* `offShift` 만으로는 화면이 "교대 밖" 이라고밖에 못 말한다. 그런데 사용자가 알고 싶은 것은
|
|
478
|
+
* **휴일이라 오늘 통째로 서는 것인지**, 잠깐 휴게인지, 그냥 교대 시간이 아닌지다 — 셋은 기다릴
|
|
479
|
+
* 시간도 할 일도 다르다. 신정에 24시간 멈춘 트윈이 그냥 빈 화면으로 보이면 고장으로 읽힌다.
|
|
480
|
+
*
|
|
481
|
+
* `'non-working'` 선언된 비근무 구간(휴일·휴게·정비창)이 덮었다.
|
|
482
|
+
* `'off-hours'` 근무 구간이 하나도 안 덮었다(주말·교대 사이).
|
|
483
|
+
*/
|
|
484
|
+
export type OffCalendarReason = 'non-working' | 'off-hours';
|
|
485
|
+
export declare function offCalendarReasonAt(r: {
|
|
486
|
+
window?: {
|
|
487
|
+
startHour: number;
|
|
488
|
+
endHour: number;
|
|
489
|
+
};
|
|
490
|
+
workCalendar?: WorkCalendarEntry[];
|
|
491
|
+
}, ms: number, utcOffsetMinutes?: number): OffCalendarReason | undefined;
|
|
492
|
+
export declare function offCalendarAt(r: {
|
|
493
|
+
window?: {
|
|
494
|
+
startHour: number;
|
|
495
|
+
endHour: number;
|
|
496
|
+
};
|
|
497
|
+
workCalendar?: WorkCalendarEntry[];
|
|
498
|
+
}, ms: number, utcOffsetMinutes?: number): boolean;
|
|
196
499
|
export interface LocationState {
|
|
197
500
|
id: string;
|
|
198
501
|
type: string;
|
|
@@ -258,6 +561,29 @@ export interface LocationState {
|
|
|
258
561
|
export interface ItemState {
|
|
259
562
|
/** 식별자 — 개체(urn:epc:id:…) 또는 클래스(urn:epc:class:lgtin:… / urn:epc:idpat:…). */
|
|
260
563
|
epc: string;
|
|
564
|
+
/**
|
|
565
|
+
* **로트의 한 부분** — 표준 `MaterialSubLot.ID`.
|
|
566
|
+
*
|
|
567
|
+
* ── 없을 때 무엇이 사라졌나 ──────────────────────────────────────────────
|
|
568
|
+
* 비직렬 로트(LGTIN)의 키가 **로트 식별자 하나**였다. 그래서 같은 로트를 rack-1 에 100개,
|
|
569
|
+
* rack-2 에 60개 관측하면 **뒤에 온 관측이 앞을 덮어 100개가 조용히 사라졌다**(합계 160 → 60).
|
|
570
|
+
* 로트가 여러 자리에 나뉘어 놓이는 것은 창고에서 일상이다.
|
|
571
|
+
*
|
|
572
|
+
* 표준은 이 자리를 `MaterialSubLot` 으로 둔다 — 각 부분이 **자기 `ID`·`StorageLocation`·`Quantity`**
|
|
573
|
+
* 를 갖는다. 그래서 우리도 부분마다 한 줄로 든다: `epc` 는 여전히 **로트의 식별자**(같은 로트의 두
|
|
574
|
+
* 부분은 같은 `epc` 를 갖는다)이고, **개체를 구별하는 키는 이것**이다.
|
|
575
|
+
*
|
|
576
|
+
* 직렬 물품(SGTIN)은 그 자체가 유일하므로 비어 있다 — 소비처는 `subLotId ?? epc` 로 키를 잡는다.
|
|
577
|
+
* **EPC 처럼 생긴 식별자를 지어내지 않는다**(그러면 파서가 깨지고 상류의 것과 구별할 수 없다).
|
|
578
|
+
*/
|
|
579
|
+
subLotId?: string;
|
|
580
|
+
/**
|
|
581
|
+
* 이 로트가 **무슨 품목인가** — 표준 `MaterialLot.MaterialDefinitionID`.
|
|
582
|
+
*
|
|
583
|
+
* 보통은 `gtin` 이 곧 정의 id 라 비어 있다(정체성의 정본이 EPCIS 다). 상류가 GTIN 과 다른 품목 코드를
|
|
584
|
+
* 쓸 때 그 사실이 **들어올 자리**가 이것이다 — 자리가 없으면 사실이 들어오지 못한다.
|
|
585
|
+
*/
|
|
586
|
+
definitionId?: string;
|
|
261
587
|
/**
|
|
262
588
|
* 품목 클래스 식별자 — **URI 원문 그대로**(`urn:epc:idpat:sgtin:…` 또는 `urn:epc:class:lgtin:…`).
|
|
263
589
|
* 오더의 skuMix·할당이 이 값으로 매칭하므로 뜻을 바꾸지 않는다.
|
|
@@ -280,6 +606,13 @@ export interface ItemState {
|
|
|
280
606
|
qty?: number;
|
|
281
607
|
/** 수량 단위(UN/ECE Rec 20 코드: EA·KGM 등). 없으면 개수로 읽는다. */
|
|
282
608
|
uom?: string;
|
|
609
|
+
/**
|
|
610
|
+
* **선언된 모든 수량** — 표준 `MaterialLot.Quantity`(복수). 같은 로트가 100 EA 이면서 250 KG 다.
|
|
611
|
+
*
|
|
612
|
+
* `qty`/`uom` 은 그중 **계산에 쓰는 주 수량**이다(배정·이행 산식이 이 값으로 굳어 있다).
|
|
613
|
+
* `quantities` 는 **받은 것 전부**이고 주 수량도 그 안에 든다 — 읽을 때는 `quantityIn` 을 쓴다.
|
|
614
|
+
*/
|
|
615
|
+
quantities?: MaterialQuantity[];
|
|
283
616
|
expiry?: number;
|
|
284
617
|
/**
|
|
285
618
|
* 개체·로트 마스터데이터 원문 — 표준이 속성 이름을 정의하지 않으므로(상위 문서 소관) **받은 것을
|
|
@@ -319,7 +652,7 @@ export interface OeeMetrics {
|
|
|
319
652
|
goodCount: number;
|
|
320
653
|
scrapCount: number;
|
|
321
654
|
}
|
|
322
|
-
export interface EquipmentState {
|
|
655
|
+
export interface EquipmentState extends EffectivePeriod {
|
|
323
656
|
id: string;
|
|
324
657
|
kind: string;
|
|
325
658
|
/** **지금 어디에 있나.** 운반 작업이 끝나면 도착 자리로 옮겨진다(제자리 작업은 안 움직인다). */
|
|
@@ -346,6 +679,23 @@ export interface EquipmentState {
|
|
|
346
679
|
* 셋을 뭉개면 "왜 안 움직이나" 에 답할 수 없다(고쳐야 하나·풀어야 하나·기다려야 하나).
|
|
347
680
|
*/
|
|
348
681
|
offShift?: boolean;
|
|
682
|
+
/**
|
|
683
|
+
* 유효 기간 밖이라 이 시각의 모델에 없다 — **네 번째 이유**(§Effectivity).
|
|
684
|
+
* 도입 예정(`not-yet`)과 폐기(`expired`)를 구별한다. 유효하면 값이 없다.
|
|
685
|
+
*/
|
|
686
|
+
effectivity?: Effectivity;
|
|
687
|
+
/**
|
|
688
|
+
* **왜 쉬나** — `offShift` 가 참일 때만 있다(§OffCalendarReason).
|
|
689
|
+
* `non-working` 휴일·휴게·정비창 · `off-hours` 주말·교대 사이. 셋은 기다릴 시간도 할 일도 다르다.
|
|
690
|
+
*/
|
|
691
|
+
offShiftReason?: OffCalendarReason;
|
|
692
|
+
/**
|
|
693
|
+
* 지금 어느 교대인가 — 표준 `WorkCalendarEntry.ID`(§activeShiftOf).
|
|
694
|
+
*
|
|
695
|
+
* **파생값이다** — 델타로 실어 보내지 않는다(교대 경계는 이벤트 없이 시각만으로 넘어간다).
|
|
696
|
+
* 3교대처럼 하루를 끊김 없이 덮는 현장에서 이 값이 **교대별 성과를 물을 수 있는 유일한 축**이다.
|
|
697
|
+
*/
|
|
698
|
+
shift?: string;
|
|
349
699
|
/** 이 자원을 어떻게 알게 됐는가 — LocationState.origin 과 같은 뜻(성장 정책을 한 규칙으로 선언). */
|
|
350
700
|
origin?: 'master' | 'observed';
|
|
351
701
|
/**
|
|
@@ -353,6 +703,8 @@ export interface EquipmentState {
|
|
|
353
703
|
* 여기 실린다. 지금까지 계약 밖으로 흘러 호스트까지 `any` 로 전달됐다(§ResourceProperty).
|
|
354
704
|
*/
|
|
355
705
|
properties?: ResourceProperty[];
|
|
706
|
+
/** 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 가동시간·정비창을 함께 표현한다. */
|
|
707
|
+
workCalendar?: WorkCalendarEntry[];
|
|
356
708
|
/** 적격을 검증한 시험 명세들 — 표준 `Equipment.TestSpecificationID`(§TestSpecificationRefs). */
|
|
357
709
|
testSpecificationIds?: TestSpecificationRefs;
|
|
358
710
|
}
|
|
@@ -366,7 +718,7 @@ export interface EquipmentState {
|
|
|
366
718
|
* 그리고 **인원은 현장에서 가장 자주 부족한 자원**이다. 모델에 없으면 "사람을 두 명 더 넣으면
|
|
367
719
|
* 어떻게 되나" 를 물을 수 없고, 사람이 만들어 내는 줄이 예측에서 통째로 사라진다.
|
|
368
720
|
*/
|
|
369
|
-
export interface PersonState {
|
|
721
|
+
export interface PersonState extends EffectivePeriod {
|
|
370
722
|
id: string;
|
|
371
723
|
/**
|
|
372
724
|
* 소속 등급들 — **ISA-95 `Person.PersonnelClassID`, `maxOccurs="unbounded"`**(B2MML-Personnel.xsd).
|
|
@@ -384,6 +736,18 @@ export interface PersonState {
|
|
|
384
736
|
taskId?: string;
|
|
385
737
|
/** 교대 밖 — 자원(EquipmentState.offShift)과 같은 뜻. */
|
|
386
738
|
offShift?: boolean;
|
|
739
|
+
/**
|
|
740
|
+
* 유효 기간 밖 — 설비와 같은 뜻(§Effectivity). 사람에게는 **입사 전·퇴사 후**가 이것이다
|
|
741
|
+
* (자격 만료는 등급 쪽 유효 기간이다 — 사람은 남고 자격만 끊긴다).
|
|
742
|
+
*/
|
|
743
|
+
effectivity?: Effectivity;
|
|
744
|
+
/**
|
|
745
|
+
* 지금 어느 교대인가 — 표준 `WorkCalendarEntry.ID`(§activeShiftOf).
|
|
746
|
+
*
|
|
747
|
+
* **파생값이다** — 델타로 실어 보내지 않는다(교대 경계는 이벤트 없이 시각만으로 넘어간다).
|
|
748
|
+
* 3교대처럼 하루를 끊김 없이 덮는 현장에서 이 값이 **교대별 성과를 물을 수 있는 유일한 축**이다.
|
|
749
|
+
*/
|
|
750
|
+
shift?: string;
|
|
387
751
|
/**
|
|
388
752
|
* 지금 어디에 있나 — **표준 `Person.OperationalLocation`**(B2MML-Personnel.xsd, `ResourceLocationType`).
|
|
389
753
|
*
|
|
@@ -394,6 +758,11 @@ export interface PersonState {
|
|
|
394
758
|
location?: string;
|
|
395
759
|
/** 자원 속성 — 표준 `PersonProperty`. 자격증·숙련 등급 같은 사실이 여기 들어간다(§ResourceProperty). */
|
|
396
760
|
properties?: ResourceProperty[];
|
|
761
|
+
/**
|
|
762
|
+
* 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 교대 여러 개와 휴게·휴일을 함께 표현한다.
|
|
763
|
+
* 예전에는 하루에 창 하나뿐이어서 2교대·야간+주간 조합을 표현할 수 없었다.
|
|
764
|
+
*/
|
|
765
|
+
workCalendar?: WorkCalendarEntry[];
|
|
397
766
|
/** 자격을 검증한 시험 명세들 — 표준 `Person.TestSpecificationID`(§TestSpecificationRefs). */
|
|
398
767
|
testSpecificationIds?: TestSpecificationRefs;
|
|
399
768
|
}
|
|
@@ -407,7 +776,7 @@ export interface PersonState {
|
|
|
407
776
|
*
|
|
408
777
|
* 그리고 빈 팔레트 부족은 현장의 실제 제약이다 — 자산이 없어 작업이 못 나가는 일이 사람 부족만큼 잦다.
|
|
409
778
|
*/
|
|
410
|
-
export interface AssetState {
|
|
779
|
+
export interface AssetState extends EffectivePeriod {
|
|
411
780
|
id: string;
|
|
412
781
|
/** 자산 등급 — pallet · rack · bin · trailer 등. 도메인 소유(코어는 강제하지 않는다). */
|
|
413
782
|
/**
|
|
@@ -431,6 +800,11 @@ export interface AssetState {
|
|
|
431
800
|
properties?: ResourceProperty[];
|
|
432
801
|
/** 적격을 검증한 시험 명세들 — 표준 `PhysicalAsset.TestSpecificationID`(§TestSpecificationRefs). */
|
|
433
802
|
testSpecificationIds?: TestSpecificationRefs;
|
|
803
|
+
/**
|
|
804
|
+
* 유효 기간 밖 — 설비와 같은 뜻(§Effectivity). 자산에서는 **폐기한 팔레트**가 풀에서 빠지는 것이다.
|
|
805
|
+
* 빠뜨리면 회수 대상 수가 실제보다 많게 잡히고, 빈 팔레트 부족이 보이지 않는다.
|
|
806
|
+
*/
|
|
807
|
+
effectivity?: Effectivity;
|
|
434
808
|
}
|
|
435
809
|
export interface TaskState {
|
|
436
810
|
id: string;
|
|
@@ -461,6 +835,26 @@ export interface TaskState {
|
|
|
461
835
|
* 한 작업이 설비 하나와 사람 여럿을 동시에 잡을 수 있다(용접 로봇 1대 + 작업자 2명).
|
|
462
836
|
*/
|
|
463
837
|
personnel?: string[];
|
|
838
|
+
/**
|
|
839
|
+
* 우선순위 — 표준 `JobOrder.Priority`. **작은 값이 급하다**(§PRIORITY_UNSET). 없으면 우선순위 없음.
|
|
840
|
+
*
|
|
841
|
+
* 없을 때는 선착순만 가능했다 — 급한 일을 앞세우는 것이 운영의 기본인데 모델에 자리가 없었다.
|
|
842
|
+
*/
|
|
843
|
+
priority?: number;
|
|
844
|
+
/** 예정 착수 — 표준 `JobOrder.StartTime`. 실제 착수(`startedAtSimMs`)와 다른 축이다. */
|
|
845
|
+
startTime?: ISOTime;
|
|
846
|
+
/** 예정 완료(납기) — 표준 `JobOrder.EndTime`. 없으면 **지연을 정의할 수 없다**(§dueStatusOf). */
|
|
847
|
+
endTime?: ISOTime;
|
|
848
|
+
/**
|
|
849
|
+
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`(`OpMaterialActualType`).
|
|
850
|
+
* 명세(계획)가 아니라 일어난 일이다. 투입 인원·설비와 같은 채널에 실어 실적을 한 곳에서 읽는다.
|
|
851
|
+
*/
|
|
852
|
+
materialActual?: {
|
|
853
|
+
definitionId: string;
|
|
854
|
+
use: 'consumed' | 'produced';
|
|
855
|
+
quantity: number;
|
|
856
|
+
uom?: string;
|
|
857
|
+
}[];
|
|
464
858
|
}
|
|
465
859
|
export interface OrderState {
|
|
466
860
|
id: string;
|
|
@@ -480,6 +874,12 @@ export interface OrderState {
|
|
|
480
874
|
requested?: number;
|
|
481
875
|
fulfilled?: number;
|
|
482
876
|
lines?: ObservedOrderLine[];
|
|
877
|
+
/** 우선순위 — 표준 `OperationsRequest.Priority`. 작은 값이 급하다. 오더 할당 순서를 정한다. */
|
|
878
|
+
priority?: number;
|
|
879
|
+
/** 예정 착수 — 표준 `OperationsRequest.StartTime`. */
|
|
880
|
+
startTime?: ISOTime;
|
|
881
|
+
/** 납기 — 표준 `OperationsRequest.EndTime`. 이것이 있어야 "늦었나" 를 물을 수 있다. */
|
|
882
|
+
endTime?: ISOTime;
|
|
483
883
|
}
|
|
484
884
|
export type AttentionSeverity = 'low' | 'medium' | 'high' | 'critical';
|
|
485
885
|
export type AttentionState = 'active' | 'acknowledged' | 'cleared';
|
|
@@ -488,12 +888,35 @@ export interface Attention {
|
|
|
488
888
|
kind: string;
|
|
489
889
|
severity: AttentionSeverity;
|
|
490
890
|
state?: AttentionState;
|
|
891
|
+
/**
|
|
892
|
+
* 이 신호가 가리키는 대상 — 공간 위 위치로 해석한다.
|
|
893
|
+
*
|
|
894
|
+
* `operation` 은 개체가 아니라 **공정(단계)** 이다. 개체 셋(자리·설비·오더)만으로는 "도장이라는
|
|
895
|
+
* 단계가 라인의 제약이다" 를 가리킬 수 없다 — 도장 부스는 여럿이고 그중 하나가 문제인 게 아니라
|
|
896
|
+
* **그 단계 전체**가 모자란 것이다. 표준에도 자리가 있다(ISA-95 `OperationsSegment`).
|
|
897
|
+
*/
|
|
491
898
|
anchor: {
|
|
492
899
|
locationId?: string;
|
|
493
900
|
moverId?: string;
|
|
494
901
|
orderId?: string;
|
|
902
|
+
operation?: string;
|
|
495
903
|
};
|
|
496
|
-
|
|
904
|
+
/**
|
|
905
|
+
* **이 조건이 언제부터인가** — 표준 `WorkAlertType.TimeStamp`(ISO 절대 시각).
|
|
906
|
+
*
|
|
907
|
+
* ── 죽은 필드였다 ────────────────────────────────────────────────────────
|
|
908
|
+
* 예전 이름은 `since`(simClockMs)였는데 **한 번도 채워지지 않았고 아무도 읽지 않았다.** 그래서
|
|
909
|
+
* 사용자는 병목이 **10초째인지 3시간째인지** 구별할 수 없었다 — 방금 찬 자리와 세 시간째 막힌 자리는
|
|
910
|
+
* 할 일이 완전히 다르다. 표준이 알림에 시각을 두는 이유가 그것이다.
|
|
911
|
+
*
|
|
912
|
+
* **재평가 시각이 아니라 조건이 처음 성립한 시각**이다. 주목 신호는 매 스냅샷 다시 계산되므로,
|
|
913
|
+
* "지금" 을 찍으면 언제나 방금 생긴 것처럼 보인다(그 값은 스냅샷 시각과 같아 아무 정보가 없다).
|
|
914
|
+
* 그래서 커널이 **처음 본 시각을 기억**한다(조건이 사라지면 함께 지운다 — 확인(ack)과 같은 수명).
|
|
915
|
+
*
|
|
916
|
+
* 시뮬 클록(ms)이 아니라 **절대 시각**이다: 소비처가 화면에 "3시간째" 를 쓰려면 기준이 필요하고,
|
|
917
|
+
* 라이브에서는 시뮬 클록이 아예 뜻이 없다.
|
|
918
|
+
*/
|
|
919
|
+
timeStamp?: ISOTime;
|
|
497
920
|
/**
|
|
498
921
|
* 표현용 원시 파라미터(언어 중립). 커널은 사람이 읽는 문장을 만들지 않는다 — kind + params 만 방출하고
|
|
499
922
|
* title/detail/rationale 는 표현계층(클라 i18next 템플릿)이 kind 로 키를 골라 params 를 보간해 렌더.
|
|
@@ -520,6 +943,16 @@ export interface RecommendedAction {
|
|
|
520
943
|
export interface StateSnapshot {
|
|
521
944
|
revision: number;
|
|
522
945
|
simClockMs: number;
|
|
946
|
+
/**
|
|
947
|
+
* **이 트윈의 "지금"**(ISO 절대 시각) — 시각으로 재는 모든 판단의 기준.
|
|
948
|
+
*
|
|
949
|
+
* `simClockMs` 로는 부족하다: 소비처가 절대 시각을 얻으려면 **기준 epoch 를 복제**해야 하고,
|
|
950
|
+
* 관측(라이브) 모드에서는 그 계산이 아예 틀린다(그때의 "지금" 은 마지막으로 들은 발생 시각이다).
|
|
951
|
+
* 트윈은 자기 시계로 사니, **그 시계를 트윈이 직접 말한다**.
|
|
952
|
+
*
|
|
953
|
+
* 없으면 소비처는 **재지 않는다** — 벽시계로 대신 재면 시뮬 트윈에서 엉뚱한 값이 나온다.
|
|
954
|
+
*/
|
|
955
|
+
nowTime?: ISOTime;
|
|
523
956
|
locations: LocationState[];
|
|
524
957
|
items: ItemState[];
|
|
525
958
|
/**
|
|
@@ -610,6 +1043,15 @@ export interface GeneratorSpec {
|
|
|
610
1043
|
stimulus?: 'arrival' | 'order';
|
|
611
1044
|
rate: RateSpec;
|
|
612
1045
|
content: ContentSpec;
|
|
1046
|
+
/**
|
|
1047
|
+
* 이 자극이 만드는 오더의 **약속 리드타임(분)** — 생성 시각 + 이 값이 납기(`endTime`)가 된다.
|
|
1048
|
+
*
|
|
1049
|
+
* 표준 `OperationsRequest.EndTime` 을 시나리오가 채우는 경로다. **선언하지 않으면 납기가 없다** —
|
|
1050
|
+
* 그러면 `dueStatusOf` 가 판단하지 않고 지연 신호도 뜨지 않는다(없는 약속을 만들지 않는다).
|
|
1051
|
+
*/
|
|
1052
|
+
promisedLeadMinutes?: number;
|
|
1053
|
+
/** 이 자극이 만드는 오더의 우선순위 — 표준 `OperationsRequest.Priority`(작은 값이 급하다). */
|
|
1054
|
+
priority?: number;
|
|
613
1055
|
/**
|
|
614
1056
|
* 운영시간 — 이 구간 밖에서는 자극이 발생하지 않는다(문 닫은 시간에 트럭이 오지 않는다).
|
|
615
1057
|
* `startHour <= endHour` 면 같은 날 구간, 넘어가면 자정을 가로지르는 야간 구간(22→6).
|
|
@@ -644,7 +1086,7 @@ export declare const OP_EVENT: {
|
|
|
644
1086
|
readonly quality: "quality.output";
|
|
645
1087
|
};
|
|
646
1088
|
/** 사람 상태 델타 — 배정·해제·교대 전이 시 방출. 미러가 인원 가용을 비추는 근거. */
|
|
647
|
-
export interface PersonStatusDelta {
|
|
1089
|
+
export interface PersonStatusDelta extends EffectivePeriod {
|
|
648
1090
|
personId: string;
|
|
649
1091
|
personnelClassIds?: string[];
|
|
650
1092
|
status: string;
|
|
@@ -654,7 +1096,7 @@ export interface PersonStatusDelta {
|
|
|
654
1096
|
location?: string;
|
|
655
1097
|
}
|
|
656
1098
|
/** 물리 자산 상태 델타 — 이동·투입·적재/하역 시 방출. 미러가 자산 가용을 비추는 근거. */
|
|
657
|
-
export interface AssetStatusDelta {
|
|
1099
|
+
export interface AssetStatusDelta extends EffectivePeriod {
|
|
658
1100
|
assetId: string;
|
|
659
1101
|
assetClassIds?: string[];
|
|
660
1102
|
status: string;
|
|
@@ -706,8 +1148,22 @@ export interface TaskStatusDelta {
|
|
|
706
1148
|
remainingMs?: number;
|
|
707
1149
|
/** 이 작업의 총 소요 예상(ms) — 진척의 분모. */
|
|
708
1150
|
durationMs?: number;
|
|
1151
|
+
/** 우선순위 — 나가지 않으면 미러가 배정 순서의 근거를 모른다(상태⊆이벤트). */
|
|
1152
|
+
priority?: number;
|
|
1153
|
+
startTime?: ISOTime;
|
|
1154
|
+
endTime?: ISOTime;
|
|
1155
|
+
/**
|
|
1156
|
+
* **실제로 들어가고 나온 자재** — ISA-95 `JobResponse.MaterialActual`(`OpMaterialActualType`).
|
|
1157
|
+
* 명세(계획)가 아니라 일어난 일이다. 투입 인원·설비와 같은 채널에 실어 실적을 한 곳에서 읽는다.
|
|
1158
|
+
*/
|
|
1159
|
+
materialActual?: {
|
|
1160
|
+
definitionId: string;
|
|
1161
|
+
use: 'consumed' | 'produced';
|
|
1162
|
+
quantity: number;
|
|
1163
|
+
uom?: string;
|
|
1164
|
+
}[];
|
|
709
1165
|
}
|
|
710
|
-
export interface EquipmentStatusDelta {
|
|
1166
|
+
export interface EquipmentStatusDelta extends EffectivePeriod {
|
|
711
1167
|
moverId: string;
|
|
712
1168
|
kind: string;
|
|
713
1169
|
status: string;
|
|
@@ -716,6 +1172,15 @@ export interface EquipmentStatusDelta {
|
|
|
716
1172
|
homeLocation?: string;
|
|
717
1173
|
/** 지금 붙어 있는 작업 — 사람·자산 델타와 같은 자리. 없으면 미러가 작업↔자원 연결을 모른다. */
|
|
718
1174
|
taskId?: string;
|
|
1175
|
+
/**
|
|
1176
|
+
* 계획 정지(정비·오프라인) — **커맨드로만 바뀌는 사실**이므로 델타로 실어 보내는 것이 맞다
|
|
1177
|
+
* (시각으로 바뀌는 판정과 다르다: 여기엔 항상 이벤트가 있다).
|
|
1178
|
+
*
|
|
1179
|
+
* 없어서 미러는 계획 정지를 **영영 몰랐다.** 그런데 적합성 하네스는 조용했다 — 대조가 값 있는 필드만
|
|
1180
|
+
* 세는데 아무도 정지 상태가 아닌 실행에서는 양쪽 다 비어 있었기 때문이다. 그래서 하네스가 실행 중간에
|
|
1181
|
+
* **조건을 일부러 일으키게** 고치고(`provoke`), 그 눈으로 이 구멍을 찾았다.
|
|
1182
|
+
*/
|
|
1183
|
+
held?: boolean;
|
|
719
1184
|
motion?: EquipmentMotion;
|
|
720
1185
|
}
|
|
721
1186
|
/** 관측된 오더 라인(SKU 데맨드) — 실 시스템 오더는 품목 라인을 가짐. 이행 예측(남은 데맨드 재계획)에 필요. */
|
|
@@ -736,6 +1201,10 @@ export interface OrderStatusDelta {
|
|
|
736
1201
|
* 현재 재고로 어떻게 채우나"를 재계획할 수 있다. 없으면 top-level 카운트만(이행 예측 불가, 재고 예측만).
|
|
737
1202
|
*/
|
|
738
1203
|
lines?: ObservedOrderLine[];
|
|
1204
|
+
/** 우선순위·예정 창 — 미러가 "늦었나" 를 판단할 재료. */
|
|
1205
|
+
priority?: number;
|
|
1206
|
+
startTime?: ISOTime;
|
|
1207
|
+
endTime?: ISOTime;
|
|
739
1208
|
}
|
|
740
1209
|
export declare const CMD: {
|
|
741
1210
|
readonly orderHold: "order.hold";
|
|
@@ -793,7 +1262,7 @@ export interface BoardDef {
|
|
|
793
1262
|
* 설비(설비). mtbfMs/mttrMs 지정 시 확률적 고장 모델 참여(OEE Availability 손실). 미지정=고장 없음.
|
|
794
1263
|
* `window` 지정 시 그 시간대에만 일한다(교대·가동시간) — 미지정이면 24시간 가용(기존 거동).
|
|
795
1264
|
*/
|
|
796
|
-
equipment: {
|
|
1265
|
+
equipment: (EffectivePeriod & {
|
|
797
1266
|
id: string;
|
|
798
1267
|
kind: string;
|
|
799
1268
|
homeLocation: string;
|
|
@@ -803,39 +1272,59 @@ export interface BoardDef {
|
|
|
803
1272
|
startHour: number;
|
|
804
1273
|
endHour: number;
|
|
805
1274
|
};
|
|
1275
|
+
workCalendar?: WorkCalendarEntry[];
|
|
806
1276
|
properties?: ResourceProperty[];
|
|
807
1277
|
testSpecificationIds?: TestSpecificationRefs;
|
|
808
|
-
}[];
|
|
1278
|
+
})[];
|
|
809
1279
|
/**
|
|
810
1280
|
* 사람 — ISA-95 `Person`. `personnelClasses` 로 **속한 등급들**을 밝히고(복수가 표준), `window` 로
|
|
811
1281
|
* 교대를 선언한다.
|
|
812
1282
|
* 선언하지 않으면 인원 제약이 없는 트윈이다(기존 거동).
|
|
813
1283
|
*/
|
|
814
|
-
persons?: {
|
|
1284
|
+
persons?: (EffectivePeriod & {
|
|
815
1285
|
id: string;
|
|
816
1286
|
personnelClassIds?: string[];
|
|
817
1287
|
window?: {
|
|
818
1288
|
startHour: number;
|
|
819
1289
|
endHour: number;
|
|
820
1290
|
};
|
|
1291
|
+
workCalendar?: WorkCalendarEntry[];
|
|
821
1292
|
homeLocation?: string;
|
|
822
1293
|
properties?: ResourceProperty[];
|
|
823
1294
|
testSpecificationIds?: TestSpecificationRefs;
|
|
824
|
-
}[];
|
|
1295
|
+
})[];
|
|
825
1296
|
/** 물리 자산(반복사용) — ISA-95 `PhysicalAsset` / GS1 `GRAI`. 선언하지 않으면 자산 제약이 없다. */
|
|
826
|
-
assets?: {
|
|
1297
|
+
assets?: (EffectivePeriod & {
|
|
827
1298
|
id: string;
|
|
828
1299
|
assetClassIds?: string[];
|
|
829
1300
|
homeLocation?: string;
|
|
830
1301
|
properties?: ResourceProperty[];
|
|
831
1302
|
testSpecificationIds?: TestSpecificationRefs;
|
|
832
|
-
}[];
|
|
1303
|
+
})[];
|
|
1304
|
+
/**
|
|
1305
|
+
* 이 트윈의 **시각 해석 기준**(UTC 로부터의 분). 근무 캘린더의 `HH:MM` 이 어느 기준인지 정한다.
|
|
1306
|
+
*
|
|
1307
|
+
* 예: Rosarito(UTC−7) = `-420`. **선언하지 않으면 UTC**(0)로 읽는다 — 조용히 현지 시각으로
|
|
1308
|
+
* 가정하지 않는다. 테넌트 시간대는 호스트가 안다(`Domain.timezone`) — 그것을 오프셋으로 풀어 준다.
|
|
1309
|
+
*
|
|
1310
|
+
* 일광절약시간은 고정 오프셋으로 따라갈 수 없다. 긴 지평선의 정확한 답은 호스트가 표준대로
|
|
1311
|
+
* **절대 구간**(`StartDateTime`/`FinishDateTime`)을 계산해 넣는 것이다.
|
|
1312
|
+
*/
|
|
1313
|
+
utcOffsetMinutes?: number;
|
|
833
1314
|
/**
|
|
834
1315
|
* **등급 정의** — 표준 `PersonnelClass` · `EquipmentClass` · `PhysicalAssetClass`.
|
|
835
1316
|
*
|
|
836
1317
|
* 선언하지 않아도 트윈은 돈다(소속 문자열 그대로 판정). 선언하면 **상속과 유효기간**이 살아난다 —
|
|
837
1318
|
* "생산직 2명" 요구를 "용접 자격자" 가 만족하고, 만료된 자격은 배정되지 않는다.
|
|
838
1319
|
*/
|
|
1320
|
+
/**
|
|
1321
|
+
* **품목 정의**(표준 `MaterialDefinition`)와 **품목 등급**(`MaterialClass`).
|
|
1322
|
+
*
|
|
1323
|
+
* 선언하지 않아도 트윈은 돈다 — 그때는 단위 환산을 할 수 없고, `quantityIn` 이 **없다고 답한다**
|
|
1324
|
+
* (계수를 모르는데 값을 만들면 그 뒤 모든 계산이 거짓 위에 선다).
|
|
1325
|
+
*/
|
|
1326
|
+
materialDefinitions?: MaterialDefinition[];
|
|
1327
|
+
materialClasses?: ResourceClassDef[];
|
|
839
1328
|
personnelClasses?: ResourceClassDef[];
|
|
840
1329
|
equipmentClasses?: ResourceClassDef[];
|
|
841
1330
|
assetClasses?: ResourceClassDef[];
|