@operato/twin-kernel 0.8.4 → 0.9.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/flow-engine.d.ts +1 -17
- package/dist/flow-engine.js +3 -29
- package/dist-cjs/index.cjs +104 -39
- package/package.json +2 -2
package/dist/flow-engine.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion,
|
|
1
|
+
import type { TestResult, ISOTime, MaterialQuantity, WorkCalendarEntry, EffectivePeriod, Effectivity, OffCalendarReason, ResourceProperty, ResourceClassDef, MaterialDefinition, Attention, TwinModelDef, CanonicalEnvelope, Command, CommandAck, EventHandler, EquipmentMotion, AssetState, GeneratorSpec, InterventionOutcome, OrderState, PersonState, ScenarioControl, ScenarioOverride, StateSnapshot, TwinKernel, Unsubscribe, LocationState, ItemState, EquipmentState, OrderStatusDelta, TaskState, TaskStatus, StructureShift, IdentityGroundingView, IdentityDeclaration, TestSpecificationCriterion, LocationObservation } from '@operato/ops-contract';
|
|
2
2
|
import type { VocabularyElement } from '@operato/ops-contract';
|
|
3
3
|
import type { ReducerCheckpoint } from './observed-reducer.ts';
|
|
4
4
|
import type { EpcisEvent, BizTransactionElement } from '@operato/ops-contract';
|
|
@@ -411,22 +411,6 @@ nowIso?: ISOTime,
|
|
|
411
411
|
* 부르는 쪽이 모델에서 뽑아 넘긴다(이 함수는 순수하게 남는다 — 모델을 읽는 규칙이 두 곳이 되지 않게).
|
|
412
412
|
*/
|
|
413
413
|
thresholds?: AttentionThresholds): Attention[];
|
|
414
|
-
/** OEE 계측 카운터 — sim 은 tick 으로 누적, live 는 실 텔레메트리 또는 이벤트 누적기가 채운다(face2-inbound-live §1.1). */
|
|
415
|
-
export interface OeeCounters {
|
|
416
|
-
runMs: number;
|
|
417
|
-
setupMs: number;
|
|
418
|
-
downMs: number;
|
|
419
|
-
goodCount: number;
|
|
420
|
-
scrapCount: number;
|
|
421
|
-
holdMs?: number;
|
|
422
|
-
metricsSinceMs?: number;
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* OEE 공식(순수) — 누적 카운터 + 현재 시각 → OEE. sim(oeeOf)과 live 가 **공유하는 계산 층**(face2-inbound-live §1.1).
|
|
426
|
-
* ⚠ attentions 와 달리: 입력(카운터)이 관측 State·이벤트에 없다 → live 는 카운터를 텔레메트리(정확) 또는
|
|
427
|
-
* 이벤트 누적기(근사)로 별도 공급해야 한다. 공식만 공유되고 입력원은 live 데이터-생산 결정.
|
|
428
|
-
*/
|
|
429
|
-
export declare function computeOee(c: OeeCounters, nowMs: number): OeeMetrics;
|
|
430
414
|
/**
|
|
431
415
|
* 물품 저장소 — **맵과 「자리별 색인」을 함께 든다.**
|
|
432
416
|
*
|
package/dist/flow-engine.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* 통합 타입은 도메인 필드를 옵셔널로 넓혀(FlowItem.gtin?, FlowOrder.shipmentEpc? 등) 두 도메인을 담는다.
|
|
10
10
|
* (roadmap Phase5 발견 → 추출. [[project_flow_single_base_vision]] FlowLocation 단일 base 방향과 정합.)
|
|
11
11
|
*/
|
|
12
|
-
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, isOrderTerminal, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf, outsideLimit } from '@operato/ops-contract';
|
|
12
|
+
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, isOrderTerminal, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf, identityGroundingOf, outsideLimit, computeOee } from '@operato/ops-contract';
|
|
13
13
|
import { ObservedReducer } from "./observed-reducer.js";
|
|
14
14
|
/* 주체를 정하는 규칙은 유입 문과 한 벌이다 — 두 곳에 적으면 한쪽만 고쳐진다. */
|
|
15
15
|
import { resolveSubject } from '@operato/ops-contract';
|
|
@@ -332,32 +332,6 @@ thresholds) {
|
|
|
332
332
|
a.state = 'acknowledged';
|
|
333
333
|
return out;
|
|
334
334
|
}
|
|
335
|
-
/**
|
|
336
|
-
* OEE 공식(순수) — 누적 카운터 + 현재 시각 → OEE. sim(oeeOf)과 live 가 **공유하는 계산 층**(face2-inbound-live §1.1).
|
|
337
|
-
* ⚠ attentions 와 달리: 입력(카운터)이 관측 State·이벤트에 없다 → live 는 카운터를 텔레메트리(정확) 또는
|
|
338
|
-
* 이벤트 누적기(근사)로 별도 공급해야 한다. 공식만 공유되고 입력원은 live 데이터-생산 결정.
|
|
339
|
-
*/
|
|
340
|
-
export function computeOee(c, nowMs) {
|
|
341
|
-
/*
|
|
342
|
-
* **계측 창을 모르면 잰 시간이 없다** — `?? 0` 으로 메우면 "1970년부터 재고 있었다" 가 된다.
|
|
343
|
-
*
|
|
344
|
-
* 그 결과가 화면까지 갔다: 갓 뜬 트윈의 절단기가 `idleMs 1767226603000`(약 56,000년) 유휴로 섰다.
|
|
345
|
-
* 시각을 소요시간 자리에 앉힌 것이고, 같은 값이 availability·overall 의 분모라 **성과 숫자 전체가
|
|
346
|
-
* 조용히 거짓**이 된다(가동률 100%, 성능 0%).
|
|
347
|
-
*
|
|
348
|
-
* 모르면 0 이 아니라 **없음**이다: 잰 구간이 없으면 유휴도 없다(아직 아무것도 재지 않았다).
|
|
349
|
-
*/
|
|
350
|
-
const planned = c.metricsSinceMs == null ? 0 : Math.max(0, nowMs - c.metricsSinceMs - (c.holdMs ?? 0));
|
|
351
|
-
const uptime = Math.max(0, planned - c.setupMs - c.downMs); // 가용시간(셋업·고장 제외)
|
|
352
|
-
const availability = planned > 0 ? uptime / planned : 1;
|
|
353
|
-
const performance = uptime > 0 ? Math.min(1, c.runMs / uptime) : (c.runMs > 0 ? 1 : 0);
|
|
354
|
-
const totalQ = c.goodCount + c.scrapCount;
|
|
355
|
-
const quality = totalQ > 0 ? c.goodCount / totalQ : 1;
|
|
356
|
-
return {
|
|
357
|
-
availability, performance, quality, overall: availability * performance * quality,
|
|
358
|
-
runMs: c.runMs, setupMs: c.setupMs, downMs: c.downMs, idleMs: Math.max(0, uptime - c.runMs), goodCount: c.goodCount, scrapCount: c.scrapCount
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
335
|
/**
|
|
362
336
|
* 에너지 능력이 선언한 상태 필드를 스냅샷에 싣는다 — **온 것만.**
|
|
363
337
|
*
|
|
@@ -1075,7 +1049,7 @@ export class FlowEngine {
|
|
|
1075
1049
|
* 한계가 있다 — busy 를 가동으로 근사하고 **셋업을 분리하지 못해 0** 으로 둔다. 여기서는 받은
|
|
1076
1050
|
* 값을 그대로 이어받고, 없으면 0 에서 시작한다(꾸미지 않는다).
|
|
1077
1051
|
*/
|
|
1078
|
-
/* `
|
|
1052
|
+
/* `delayMs` 까지 읽는다 — 그것이 있어야 **잰 구간**을 되세울 수 있다(계획 조업 = 생산+준비+고장+대기). */
|
|
1079
1053
|
const oee = m.oee;
|
|
1080
1054
|
/* **마스터가 말한 것을 관측이 지우지 않는다.** 예전에는 이 자리에서 통째로 새 객체를 만들어
|
|
1081
1055
|
* 교대 캘린더·속성·유효 기간·계획정지를 **전부 버렸다.** 그 결과 라이브(관측) 커널은
|
|
@@ -1105,7 +1079,7 @@ export class FlowEngine {
|
|
|
1105
1079
|
* 뒤로 물려 창을 다시 세운다. 지표가 없으면 창도 없다(그때는 지금부터 새로 잰다).
|
|
1106
1080
|
*/
|
|
1107
1081
|
...(oee
|
|
1108
|
-
? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.
|
|
1082
|
+
? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.delayMs ?? 0)) }
|
|
1109
1083
|
: {}),
|
|
1110
1084
|
/* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를 시뮬레이션한다
|
|
1111
1085
|
(예측이 낙관 쪽으로 치우친다). 씨앗 왕복 대조가 이것을 잡았다. */
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -38,7 +38,6 @@ __export(index_exports, {
|
|
|
38
38
|
YmsKernel: () => YmsKernel,
|
|
39
39
|
attributeEnergy: () => attributeEnergy,
|
|
40
40
|
compareStates: () => compareStates,
|
|
41
|
-
computeOee: () => computeOee,
|
|
42
41
|
constantDuration: () => constantDuration,
|
|
43
42
|
counterfactualAt: () => counterfactualAt,
|
|
44
43
|
demandWindowStart: () => demandWindowStart,
|
|
@@ -63,7 +62,7 @@ __export(index_exports, {
|
|
|
63
62
|
});
|
|
64
63
|
module.exports = __toCommonJS(index_exports);
|
|
65
64
|
|
|
66
|
-
//
|
|
65
|
+
// ../ops-contract/dist/capability.js
|
|
67
66
|
var CAPABILITIES = {
|
|
68
67
|
operable: {
|
|
69
68
|
key: "operable",
|
|
@@ -198,7 +197,7 @@ function stateFieldsOf(caps) {
|
|
|
198
197
|
return [...out];
|
|
199
198
|
}
|
|
200
199
|
|
|
201
|
-
//
|
|
200
|
+
// ../ops-contract/dist/contract.js
|
|
202
201
|
var ORDER_TERMINAL_STATUS = ["completed", "cancelled"];
|
|
203
202
|
function isOrderTerminal(o) {
|
|
204
203
|
const status = String(o?.status ?? "").trim().toLowerCase();
|
|
@@ -582,6 +581,15 @@ var OP_EVENT = {
|
|
|
582
581
|
* 이 채널이 없으면 시험 결과는 **상태에만 있는 축**이 된다 — 재기동에서 사라지고, 폴드가 되살릴 수
|
|
583
582
|
* 없고, 미러가 이어받지 못한다(§상태 ⊆ 이벤트).
|
|
584
583
|
*/
|
|
584
|
+
/**
|
|
585
|
+
* **마감된 설비 상태 구간** — 이 설비가 이 구간 동안 이 상태였다.
|
|
586
|
+
*
|
|
587
|
+
* 시점의 전이(`equipment.status`)와 다른 사실이다. 전이는 설비가 내고, 이것은 **사람이 나중에
|
|
588
|
+
* 적는다.** 자재 대기처럼 신호를 내지 않는 정지는 이 길로만 들어온다.
|
|
589
|
+
*
|
|
590
|
+
* ISO 22400-2 의 가동률 계산이 이 구간들을 읽는다.
|
|
591
|
+
*/
|
|
592
|
+
equipmentPeriod: "equipment.state.period",
|
|
585
593
|
test: "test.result",
|
|
586
594
|
/**
|
|
587
595
|
* **부적합 처분** — 재고 판정을 받은 것을 어떻게 하기로 정했나(재작업 · 특채 · 폐기 · 반품).
|
|
@@ -805,7 +813,7 @@ function gs1Namespaces(prefix) {
|
|
|
805
813
|
return [`urn:epc:idpat:sgtin:${prefix}.`, `urn:epc:class:lgtin:${prefix}.`, `urn:epc:id:sgtin:${prefix}.`];
|
|
806
814
|
}
|
|
807
815
|
|
|
808
|
-
//
|
|
816
|
+
// ../ops-contract/dist/capacity.js
|
|
809
817
|
function isoDurationHours(iso2) {
|
|
810
818
|
if (!iso2)
|
|
811
819
|
return 0;
|
|
@@ -913,7 +921,7 @@ function analyzeCapacity(input) {
|
|
|
913
921
|
};
|
|
914
922
|
}
|
|
915
923
|
|
|
916
|
-
//
|
|
924
|
+
// ../ops-contract/dist/wms-profile.js
|
|
917
925
|
var BIZSTEP = {
|
|
918
926
|
receiving: "urn:epcglobal:cbv:bizstep:receiving",
|
|
919
927
|
storing: "urn:epcglobal:cbv:bizstep:storing",
|
|
@@ -943,7 +951,7 @@ var WMS_TYPES = [
|
|
|
943
951
|
{ key: "packer", role: "equipment", label: "twin.type.packer", standardClass: { epcis: "object", isa95: "Equipment" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] }
|
|
944
952
|
];
|
|
945
953
|
|
|
946
|
-
//
|
|
954
|
+
// ../ops-contract/dist/yms-profile.js
|
|
947
955
|
var YARD_BIZSTEP = {
|
|
948
956
|
arriving: "urn:epcglobal:cbv:bizstep:arriving",
|
|
949
957
|
// 게이트-인
|
|
@@ -969,7 +977,7 @@ var YMS_TYPES = [
|
|
|
969
977
|
{ key: "hostler", role: "equipment", label: "twin.type.hostler", standardClass: { epcis: "object", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["mobile", "operable"] }
|
|
970
978
|
];
|
|
971
979
|
|
|
972
|
-
//
|
|
980
|
+
// ../ops-contract/dist/mes-profile.js
|
|
973
981
|
var MES_BIZSTEP = {
|
|
974
982
|
receiving: "urn:epcglobal:cbv:bizstep:receiving",
|
|
975
983
|
// 원자재 수령
|
|
@@ -1007,7 +1015,7 @@ var MES_TYPES = [
|
|
|
1007
1015
|
{ key: "assembler", role: "equipment", label: "twin.type.assembler", standardClass: { isa95: "Equipment", iso55000: "Asset" }, identity: { scheme: "gs1:GIAI" }, capabilities: ["processable", "operable"] }
|
|
1008
1016
|
];
|
|
1009
1017
|
|
|
1010
|
-
//
|
|
1018
|
+
// ../ops-contract/dist/ems-profile.js
|
|
1011
1019
|
var EMS_LOCATION_TYPES = ["incoming", "feeder", "submeter-zone"];
|
|
1012
1020
|
var METER_DIRECTION = ["import", "export", "bidirectional"];
|
|
1013
1021
|
var EMS_PROPERTY = {
|
|
@@ -1328,7 +1336,7 @@ function isElectricalLocationType(type) {
|
|
|
1328
1336
|
return EMS_LOCATION_TYPES.includes(type);
|
|
1329
1337
|
}
|
|
1330
1338
|
|
|
1331
|
-
//
|
|
1339
|
+
// ../ops-contract/dist/domain-catalog.js
|
|
1332
1340
|
var locationKeys = (types) => types.filter((t) => t.role === "location").map((t) => t.key);
|
|
1333
1341
|
var DOMAIN_CATALOG = {
|
|
1334
1342
|
// label 은 언어 중립 i18n 키(twin.system.<code>) — 사람 언어는 표현계층이 렌더(L2).
|
|
@@ -1338,7 +1346,7 @@ var DOMAIN_CATALOG = {
|
|
|
1338
1346
|
ems: { system: "ems", label: "twin.system.ems", types: EMS_TYPES, locationTypes: locationKeys(EMS_TYPES) }
|
|
1339
1347
|
};
|
|
1340
1348
|
|
|
1341
|
-
//
|
|
1349
|
+
// ../ops-contract/dist/domain-definition.js
|
|
1342
1350
|
var OP_PARAM = {
|
|
1343
1351
|
/** 양품률(0..1, 무차원). 없으면 커널 기본값 — 기본값을 쓴 사실은 `specCoverage()` 가 밝힌다. */
|
|
1344
1352
|
yield: "yield",
|
|
@@ -1347,7 +1355,7 @@ var OP_PARAM = {
|
|
|
1347
1355
|
setupDuration: "setupDuration"
|
|
1348
1356
|
};
|
|
1349
1357
|
|
|
1350
|
-
//
|
|
1358
|
+
// ../ops-contract/dist/energy-ingest.js
|
|
1351
1359
|
function resolveSubject(kind, localId, opts) {
|
|
1352
1360
|
const declared = opts.identityOf?.(kind, localId);
|
|
1353
1361
|
if (declared)
|
|
@@ -1358,7 +1366,7 @@ function periodFactId(tenantId, kind, subject, from, to) {
|
|
|
1358
1366
|
return [tenantId, kind, subject, from, to].join("|");
|
|
1359
1367
|
}
|
|
1360
1368
|
|
|
1361
|
-
//
|
|
1369
|
+
// ../ops-contract/dist/epcis.js
|
|
1362
1370
|
var EPCIS_CONTEXT = "https://ref.gs1.org/standards/epcis/2.0.0/epcis-context.jsonld";
|
|
1363
1371
|
var UTC_OFFSET = "+00:00";
|
|
1364
1372
|
var DISP = {
|
|
@@ -1617,7 +1625,7 @@ function transformationEvent(p) {
|
|
|
1617
1625
|
return e;
|
|
1618
1626
|
}
|
|
1619
1627
|
|
|
1620
|
-
//
|
|
1628
|
+
// ../ops-contract/dist/iso-duration.js
|
|
1621
1629
|
var RE = /^(-)?P(?:(\d+(?:\.\d+)?)W)?(?:(\d+(?:\.\d+)?)D)?(?:T(?:(\d+(?:\.\d+)?)H)?(?:(\d+(?:\.\d+)?)M)?(?:(\d+(?:\.\d+)?)S)?)?$/;
|
|
1622
1630
|
function parseIsoDuration(text) {
|
|
1623
1631
|
if (typeof text !== "string")
|
|
@@ -1643,7 +1651,7 @@ function parseIsoDuration(text) {
|
|
|
1643
1651
|
return sign ? -ms2 : ms2;
|
|
1644
1652
|
}
|
|
1645
1653
|
|
|
1646
|
-
//
|
|
1654
|
+
// ../ops-contract/dist/master-data.js
|
|
1647
1655
|
function expiryFromAttributes(attrs) {
|
|
1648
1656
|
return readEpochMs(attrs?.[ILMD_ATTR.expiry]);
|
|
1649
1657
|
}
|
|
@@ -1660,9 +1668,9 @@ function readEpochMs(raw) {
|
|
|
1660
1668
|
return Number.isNaN(at) ? void 0 : at;
|
|
1661
1669
|
}
|
|
1662
1670
|
|
|
1663
|
-
//
|
|
1671
|
+
// ../ops-contract/dist/operational-ingest.js
|
|
1664
1672
|
var TASK_STATUS = ["created", "assigned", "in-progress", "completed"];
|
|
1665
|
-
var EQUIPMENT_STATUS = ["idle", "busy", "down"];
|
|
1673
|
+
var EQUIPMENT_STATUS = ["idle", "busy", "down", "setup", "planned-stop"];
|
|
1666
1674
|
var PERSON_STATUS = ["idle", "busy"];
|
|
1667
1675
|
var ASSET_STATUS = ["idle", "in-use"];
|
|
1668
1676
|
var SPECS = {
|
|
@@ -1720,6 +1728,8 @@ var SPECS = {
|
|
|
1720
1728
|
effectiveStart: "string",
|
|
1721
1729
|
effectiveEnd: "string",
|
|
1722
1730
|
recordTime: "string",
|
|
1731
|
+
/* 왜 이 상태가 됐나 — 설비가 알려 주면 싣는다. 사람이 나중에 정하는 사유는 구간 사실에 적는다. */
|
|
1732
|
+
reasonCode: "string",
|
|
1723
1733
|
/* 이동 구간 — 실 시스템도 줄 수 있는 사실이다(AGV·RTLS 가 출발·도착·소요를 낸다). 안쪽 필드까지
|
|
1724
1734
|
재검사하지는 않는다: 그 모양은 `EquipmentMotion` 계약이고, 여기서 두 번 지키면 두 벌이 된다. */
|
|
1725
1735
|
motion: "object"
|
|
@@ -1809,6 +1819,49 @@ var SPECS = {
|
|
|
1809
1819
|
* `propertyMeasurements` 안쪽은 재검사하지 않는다 — 그 모양은 `PropertyMeasurement` 계약이고,
|
|
1810
1820
|
* 여기서 두 번 지키면 두 벌이 된다(설비 `motion` 과 같은 규율).
|
|
1811
1821
|
*/
|
|
1822
|
+
/*
|
|
1823
|
+
* **마감된 설비 상태 구간** — 이 설비가 이 구간 동안 이 상태였다.
|
|
1824
|
+
*
|
|
1825
|
+
* ── 전이만으로는 안 되는 이유 (2026-08-30) ───────────────────────────────
|
|
1826
|
+
* 세 가지다. 셋째가 결정적이다.
|
|
1827
|
+
*
|
|
1828
|
+
* 사유가 붙을 자리가 없다 기계가 서는 순간에는 왜 섰는지 아무도 모른다. 작업자가 라인이 다시
|
|
1829
|
+
* 돈 뒤에 적는다. 그 사이에 전이가 더 있으면 어느 전이에 붙일지 정할 수 없다
|
|
1830
|
+
* 계획·비계획을 나중에 정한다 ISO 22400 은 계획정지를 계획 조업 시간에서 빼고 고장은 빼지 않는다.
|
|
1831
|
+
* 그 분류는 전이가 일어난 순간에 모른다
|
|
1832
|
+
* 전이를 내지 않는 정지가 많다 자재 대기 · 앞 공정 대기 · 작업자 부재. 기계는 전원이 켜진 채 `idle`
|
|
1833
|
+
* 이고 신호가 하나도 안 나온다. OEE 에서 가장 크게 깎이는 것이 보통 이 시간이다
|
|
1834
|
+
*
|
|
1835
|
+
* 마지막이 감시 시스템과 MES 가 갈리는 자리이기도 하다. 설비가 말하는 것만 모으면 감시이고,
|
|
1836
|
+
* **설비가 말하지 못하는 것을 사람이 적어 넣는 자리**가 MES 다.
|
|
1837
|
+
*
|
|
1838
|
+
* ── 이름이 `downtime` 이 아닌 이유 ────────────────────────────────────────
|
|
1839
|
+
* 이 구간은 `setup` 과 `planned-stop` 도 나른다. 그 둘은 정지가 아니다. 담는 것은 **상태 구간**이고,
|
|
1840
|
+
* 어느 상태인지는 `status` 가 말한다.
|
|
1841
|
+
*
|
|
1842
|
+
* ── 되돌아가 적는 사실이다 ───────────────────────────────────────────────
|
|
1843
|
+
* 봉투의 사건 시각은 **구간의 끝**(그때 성립한다)이고, 적은 시각은 `recordTime` 이다. 둘이 갈려 있어야
|
|
1844
|
+
* 「언제 일어났나」와 「언제 알았나」를 구별할 수 있다.
|
|
1845
|
+
*/
|
|
1846
|
+
"equipment-period": {
|
|
1847
|
+
eventType: OP_EVENT.equipmentPeriod,
|
|
1848
|
+
identity: "moverId",
|
|
1849
|
+
// vocabulary-guard: allow 저널 와이어 필드 — 전이와 같은 이름을 쓴다
|
|
1850
|
+
required: ["moverId", "status", "from", "to"],
|
|
1851
|
+
// vocabulary-guard: allow 위와 같은 이유
|
|
1852
|
+
fields: {
|
|
1853
|
+
moverId: "string",
|
|
1854
|
+
status: "string",
|
|
1855
|
+
from: "string",
|
|
1856
|
+
to: "string",
|
|
1857
|
+
// vocabulary-guard: allow
|
|
1858
|
+
/* 사람이 정한 사유 — 없을 수 있다(적지 않은 것과 사유가 없는 것은 다르므로 지어내지 않는다). */
|
|
1859
|
+
reasonCode: "string",
|
|
1860
|
+
decidedBy: "string",
|
|
1861
|
+
recordTime: "string"
|
|
1862
|
+
},
|
|
1863
|
+
enums: { status: EQUIPMENT_STATUS }
|
|
1864
|
+
},
|
|
1812
1865
|
/*
|
|
1813
1866
|
* **부적합 처분** — 재고 판정을 받은 것을 어떻게 하기로 정했나.
|
|
1814
1867
|
*
|
|
@@ -1901,7 +1954,7 @@ var SPECS = {
|
|
|
1901
1954
|
}
|
|
1902
1955
|
};
|
|
1903
1956
|
|
|
1904
|
-
//
|
|
1957
|
+
// ../ops-contract/dist/operations-capability.js
|
|
1905
1958
|
function workingHoursBetween(calendar, fromMs, toMs, utcOffsetMinutes) {
|
|
1906
1959
|
if (!(toMs > fromMs))
|
|
1907
1960
|
return 0;
|
|
@@ -1971,6 +2024,39 @@ function operationsCapabilityOf(input) {
|
|
|
1971
2024
|
};
|
|
1972
2025
|
}
|
|
1973
2026
|
|
|
2027
|
+
// ../ops-contract/dist/oee.js
|
|
2028
|
+
function computeOee(c, nowMs) {
|
|
2029
|
+
const missing = [];
|
|
2030
|
+
const planned = c.metricsSinceMs == null ? 0 : Math.max(0, nowMs - c.metricsSinceMs - (c.holdMs ?? 0));
|
|
2031
|
+
if (planned <= 0)
|
|
2032
|
+
missing.push("planned-busy-time");
|
|
2033
|
+
const delayMs = Math.max(0, planned - c.runMs - c.setupMs - c.downMs);
|
|
2034
|
+
const availability = planned > 0 ? Math.min(1, c.runMs / planned) : void 0;
|
|
2035
|
+
const produced = c.goodCount + c.scrapCount;
|
|
2036
|
+
if (produced <= 0)
|
|
2037
|
+
missing.push("produced-quantity");
|
|
2038
|
+
const quality = produced > 0 ? c.goodCount / produced : void 0;
|
|
2039
|
+
if (c.plannedRunTimePerItemMs == null)
|
|
2040
|
+
missing.push("planned-run-time-per-item");
|
|
2041
|
+
else if (c.runMs <= 0)
|
|
2042
|
+
missing.push("actual-production-time");
|
|
2043
|
+
const performance = c.plannedRunTimePerItemMs != null && c.runMs > 0 ? Math.min(1, c.plannedRunTimePerItemMs * produced / c.runMs) : void 0;
|
|
2044
|
+
const overall = availability != null && performance != null && quality != null ? availability * performance * quality : void 0;
|
|
2045
|
+
return {
|
|
2046
|
+
...availability != null ? { availability } : {},
|
|
2047
|
+
...performance != null ? { performance } : {},
|
|
2048
|
+
...quality != null ? { quality } : {},
|
|
2049
|
+
...overall != null ? { overall } : {},
|
|
2050
|
+
...missing.length ? { missing } : {},
|
|
2051
|
+
runMs: c.runMs,
|
|
2052
|
+
setupMs: c.setupMs,
|
|
2053
|
+
downMs: c.downMs,
|
|
2054
|
+
delayMs,
|
|
2055
|
+
goodCount: c.goodCount,
|
|
2056
|
+
scrapCount: c.scrapCount
|
|
2057
|
+
};
|
|
2058
|
+
}
|
|
2059
|
+
|
|
1974
2060
|
// src/divergence.ts
|
|
1975
2061
|
function diffBy(predicted, actual, idOf, valOf) {
|
|
1976
2062
|
const p = new Map(predicted.map((e) => [idOf(e), valOf(e)]));
|
|
@@ -3351,26 +3437,6 @@ function deriveAttentions(view, acked, nowIso, thresholds) {
|
|
|
3351
3437
|
}
|
|
3352
3438
|
return out;
|
|
3353
3439
|
}
|
|
3354
|
-
function computeOee(c, nowMs) {
|
|
3355
|
-
const planned = c.metricsSinceMs == null ? 0 : Math.max(0, nowMs - c.metricsSinceMs - (c.holdMs ?? 0));
|
|
3356
|
-
const uptime = Math.max(0, planned - c.setupMs - c.downMs);
|
|
3357
|
-
const availability = planned > 0 ? uptime / planned : 1;
|
|
3358
|
-
const performance = uptime > 0 ? Math.min(1, c.runMs / uptime) : c.runMs > 0 ? 1 : 0;
|
|
3359
|
-
const totalQ = c.goodCount + c.scrapCount;
|
|
3360
|
-
const quality = totalQ > 0 ? c.goodCount / totalQ : 1;
|
|
3361
|
-
return {
|
|
3362
|
-
availability,
|
|
3363
|
-
performance,
|
|
3364
|
-
quality,
|
|
3365
|
-
overall: availability * performance * quality,
|
|
3366
|
-
runMs: c.runMs,
|
|
3367
|
-
setupMs: c.setupMs,
|
|
3368
|
-
downMs: c.downMs,
|
|
3369
|
-
idleMs: Math.max(0, uptime - c.runMs),
|
|
3370
|
-
goodCount: c.goodCount,
|
|
3371
|
-
scrapCount: c.scrapCount
|
|
3372
|
-
};
|
|
3373
|
-
}
|
|
3374
3440
|
var ENERGY_STATE_FIELDS = [
|
|
3375
3441
|
...stateFieldsOf(["metered", "curtailable", "energyGenerating", "energyStoring", "switching"]),
|
|
3376
3442
|
"measuredAt"
|
|
@@ -3995,7 +4061,7 @@ var FlowEngine = class {
|
|
|
3995
4061
|
* 창은 지표 자신이 말해 준다 — 계획 시간은 가동·준비·고장·유휴의 합이다. 그래서 그 합만큼
|
|
3996
4062
|
* 뒤로 물려 창을 다시 세운다. 지표가 없으면 창도 없다(그때는 지금부터 새로 잰다).
|
|
3997
4063
|
*/
|
|
3998
|
-
...oee ? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.
|
|
4064
|
+
...oee ? { metricsSinceMs: this.clockMs - ((oee.runMs ?? 0) + (oee.setupMs ?? 0) + (oee.downMs ?? 0) + (oee.delayMs ?? 0)) } : {},
|
|
3999
4065
|
/* 계획 정지를 이어받는다 — 잃으면 씨앗이 **정비 중인 설비를 가용으로 놓고** 미래를 시뮬레이션한다
|
|
4000
4066
|
(예측이 낙관 쪽으로 치우친다). 씨앗 왕복 대조가 이것을 잡았다. */
|
|
4001
4067
|
...m.held ? { held: true } : {},
|
|
@@ -9420,7 +9486,6 @@ function electricityCost(input) {
|
|
|
9420
9486
|
YmsKernel,
|
|
9421
9487
|
attributeEnergy,
|
|
9422
9488
|
compareStates,
|
|
9423
|
-
computeOee,
|
|
9424
9489
|
constantDuration,
|
|
9425
9490
|
counterfactualAt,
|
|
9426
9491
|
demandWindowStart,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/twin-kernel",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Twin Domain Kernel — framework-agnostic, zero-dep (domain + sim + 3-channel contract). WMS/YMS/MES, EPCIS 2.0 · ISA-95.",
|
|
6
6
|
"publishConfig": {
|
|
@@ -28,6 +28,6 @@
|
|
|
28
28
|
"test": "node --test test/*.test.ts"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@operato/ops-contract": "^0.
|
|
31
|
+
"@operato/ops-contract": "^0.5.0"
|
|
32
32
|
}
|
|
33
33
|
}
|