@operato/ops-contract 0.9.4 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/contract.d.ts +32 -0
- package/dist/contract.js +33 -0
- package/dist/domain-catalog.d.ts +39 -0
- package/dist/domain-catalog.js +12 -0
- package/dist/reliability.d.ts +35 -0
- package/dist/reliability.js +64 -15
- package/dist-cjs/index.cjs +51 -14
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -3172,6 +3172,38 @@ export declare const EQUIPMENT_STATUS: readonly ["busy", "setup", "down", "idle"
|
|
|
3172
3172
|
export type EquipmentStatus = (typeof EQUIPMENT_STATUS)[number];
|
|
3173
3173
|
/** 선언된 상태인가. */
|
|
3174
3174
|
export declare function isEquipmentStatus(value: unknown): value is EquipmentStatus;
|
|
3175
|
+
/**
|
|
3176
|
+
* **ISA-95 의 네 자원** — 「무엇이 자원인가」를 한 자리에서 말한다.
|
|
3177
|
+
*
|
|
3178
|
+
* ── 왜 필요한가 (MES 레인이 물음, 2026-09-05) ──────────────────────────────
|
|
3179
|
+
* 계약이 네 자원을 여러 곳에서 다루는데, **넷이라고 말하는 값이 없었다.** 그래서 소비처가 저마다
|
|
3180
|
+
* 손으로 든다 — 자원 하나에 걸린 것을 한자리에 보이는 화면(360)이 종류마다 무엇을 물을지 정하려면
|
|
3181
|
+
* 그 목록이 필요하고, 지금은 화면마다 적게 된다.
|
|
3182
|
+
*
|
|
3183
|
+
* 이미 갈라지기 시작했다: `CapacityAxis`(§`capacity.ts`)가 넷을 손으로 들고 있다.
|
|
3184
|
+
* `EQUIPMENT_STATUS` 가 정확히 같은 자리였다 — 계약에 있는데 소비처가 따로 선언하고 있었다.
|
|
3185
|
+
*
|
|
3186
|
+
* ── 왜 넷만인가 ────────────────────────────────────────────────────────────
|
|
3187
|
+
* 표준이 넷이라고 말한다(ISA-95 Part 1 §5: Personnel · Equipment · Material · Physical Asset).
|
|
3188
|
+
* 자리·공정·로트를 여기 섞고 싶은 힘이 있는데 — 화면이 그것들도 보여 주므로 — **섞으면 이 상수가
|
|
3189
|
+
* ISA-95 를 말하지 않게 된다.** 그러면 다음 사람이 이것을 근거로 쓸 수 없다.
|
|
3190
|
+
*
|
|
3191
|
+
* 자리 자원을 **담는 곳**이다. 자원이 아니다
|
|
3192
|
+
* 로트 자재의 **개체**다. 자재 종류와 다른 층이다
|
|
3193
|
+
* 공정 자원을 **쓰는 일**이다
|
|
3194
|
+
*
|
|
3195
|
+
* 화면이 그 셋도 필요하면 다른 이름으로 든다. 이 목록은 「무엇이 자원인가」에만 답한다.
|
|
3196
|
+
*
|
|
3197
|
+
* ── `CapacityAxis` 와 다르다 ───────────────────────────────────────────────
|
|
3198
|
+
* 그것은 **제약이 걸린 축**이고(무엇을 늘려야 하는가), 자리가 들어간다 — 자리가 모자라도 못 돌기
|
|
3199
|
+
* 때문이다. 자재는 없다(늘린다고 지금 나오는 것이 아니다). 둘은 겹치지만 같지 않다.
|
|
3200
|
+
*
|
|
3201
|
+
* 합치면 한 값이 두 물음에 답하게 되고, 그러면 어느 쪽 뜻으로 읽어야 하는지 알 수 없다.
|
|
3202
|
+
*/
|
|
3203
|
+
export declare const RESOURCE_KIND: readonly ["equipment", "personnel", "material", "asset"];
|
|
3204
|
+
export type ResourceKind = (typeof RESOURCE_KIND)[number];
|
|
3205
|
+
/** 선언된 자원 종류인가. */
|
|
3206
|
+
export declare function isResourceKind(value: unknown): value is ResourceKind;
|
|
3175
3207
|
/**
|
|
3176
3208
|
* 원본의 낱말을 우리 상태로 옮긴다 — **모르면 `undefined` 다.**
|
|
3177
3209
|
*
|
package/dist/contract.js
CHANGED
|
@@ -1276,6 +1276,39 @@ export const EQUIPMENT_STATUS = ['busy', 'setup', 'down', 'idle', 'planned-stop'
|
|
|
1276
1276
|
export function isEquipmentStatus(value) {
|
|
1277
1277
|
return typeof value === 'string' && EQUIPMENT_STATUS.includes(value);
|
|
1278
1278
|
}
|
|
1279
|
+
/**
|
|
1280
|
+
* **ISA-95 의 네 자원** — 「무엇이 자원인가」를 한 자리에서 말한다.
|
|
1281
|
+
*
|
|
1282
|
+
* ── 왜 필요한가 (MES 레인이 물음, 2026-09-05) ──────────────────────────────
|
|
1283
|
+
* 계약이 네 자원을 여러 곳에서 다루는데, **넷이라고 말하는 값이 없었다.** 그래서 소비처가 저마다
|
|
1284
|
+
* 손으로 든다 — 자원 하나에 걸린 것을 한자리에 보이는 화면(360)이 종류마다 무엇을 물을지 정하려면
|
|
1285
|
+
* 그 목록이 필요하고, 지금은 화면마다 적게 된다.
|
|
1286
|
+
*
|
|
1287
|
+
* 이미 갈라지기 시작했다: `CapacityAxis`(§`capacity.ts`)가 넷을 손으로 들고 있다.
|
|
1288
|
+
* `EQUIPMENT_STATUS` 가 정확히 같은 자리였다 — 계약에 있는데 소비처가 따로 선언하고 있었다.
|
|
1289
|
+
*
|
|
1290
|
+
* ── 왜 넷만인가 ────────────────────────────────────────────────────────────
|
|
1291
|
+
* 표준이 넷이라고 말한다(ISA-95 Part 1 §5: Personnel · Equipment · Material · Physical Asset).
|
|
1292
|
+
* 자리·공정·로트를 여기 섞고 싶은 힘이 있는데 — 화면이 그것들도 보여 주므로 — **섞으면 이 상수가
|
|
1293
|
+
* ISA-95 를 말하지 않게 된다.** 그러면 다음 사람이 이것을 근거로 쓸 수 없다.
|
|
1294
|
+
*
|
|
1295
|
+
* 자리 자원을 **담는 곳**이다. 자원이 아니다
|
|
1296
|
+
* 로트 자재의 **개체**다. 자재 종류와 다른 층이다
|
|
1297
|
+
* 공정 자원을 **쓰는 일**이다
|
|
1298
|
+
*
|
|
1299
|
+
* 화면이 그 셋도 필요하면 다른 이름으로 든다. 이 목록은 「무엇이 자원인가」에만 답한다.
|
|
1300
|
+
*
|
|
1301
|
+
* ── `CapacityAxis` 와 다르다 ───────────────────────────────────────────────
|
|
1302
|
+
* 그것은 **제약이 걸린 축**이고(무엇을 늘려야 하는가), 자리가 들어간다 — 자리가 모자라도 못 돌기
|
|
1303
|
+
* 때문이다. 자재는 없다(늘린다고 지금 나오는 것이 아니다). 둘은 겹치지만 같지 않다.
|
|
1304
|
+
*
|
|
1305
|
+
* 합치면 한 값이 두 물음에 답하게 되고, 그러면 어느 쪽 뜻으로 읽어야 하는지 알 수 없다.
|
|
1306
|
+
*/
|
|
1307
|
+
export const RESOURCE_KIND = ['equipment', 'personnel', 'material', 'asset'];
|
|
1308
|
+
/** 선언된 자원 종류인가. */
|
|
1309
|
+
export function isResourceKind(value) {
|
|
1310
|
+
return typeof value === 'string' && RESOURCE_KIND.includes(value);
|
|
1311
|
+
}
|
|
1279
1312
|
/**
|
|
1280
1313
|
* 원본의 낱말을 우리 상태로 옮긴다 — **모르면 `undefined` 다.**
|
|
1281
1314
|
*
|
package/dist/domain-catalog.d.ts
CHANGED
|
@@ -170,6 +170,25 @@ export type TwinRelationTarget = {
|
|
|
170
170
|
kind: 'external';
|
|
171
171
|
entity: string;
|
|
172
172
|
};
|
|
173
|
+
/**
|
|
174
|
+
* **그 관계가 가리키는 것이 언제의 일인가** — 화면의 판단이 여기서 갈린다.
|
|
175
|
+
*
|
|
176
|
+
* ── 왜 필요한가 (MES 레인이 실측으로 올림, 2026-09-05) ─────────────────────
|
|
177
|
+
* 설비 하나에 걸린 것을 한자리에 보이면, 들어오는 관계라도 뜻이 갈린다.
|
|
178
|
+
*
|
|
179
|
+
* past 이미 일어났다 — 세워도 안 바뀐다 실적 · 상태 구간
|
|
180
|
+
* future 아직이다 — 세우면 이것이 걸린다 정비 계획 · 지시
|
|
181
|
+
* standing 시간이 없는 사실 등급 · 붙박인 자리 · 종류
|
|
182
|
+
*
|
|
183
|
+
* 한 묶음에 담으면 **「걸린 것이 열」인데 아홉이 지난 일인 경우와 아홉이 앞의 일인 경우가 같아
|
|
184
|
+
* 보인다.** 「이 설비를 세워도 되나」의 답이 정반대인 두 상황이다.
|
|
185
|
+
*
|
|
186
|
+
* ── 왜 `via` 로 가르면 안 되나 ──────────────────────────────────────────────
|
|
187
|
+
* `via` 는 i18n 키다. **「이것이 지난 일인가」를 말하지 않는다.** 화면이 키 이름을 보고 짐작하게
|
|
188
|
+
* 되고, 축이 하나 늘 때 한 화면만 따라간다. 읽는 곳은 제품마다여도 **무엇을 아는지는 한 벌**이어야
|
|
189
|
+
* 한다.
|
|
190
|
+
*/
|
|
191
|
+
export type RelationTense = 'past' | 'future' | 'standing';
|
|
173
192
|
export interface TwinRelationInfo {
|
|
174
193
|
/** 출발 축. */
|
|
175
194
|
from: string;
|
|
@@ -183,7 +202,27 @@ export interface TwinRelationInfo {
|
|
|
183
202
|
via: string;
|
|
184
203
|
/** 없을 수 있는 참조인가. `false` 인데 비면 **끊어진 참조**로 보고한다. */
|
|
185
204
|
optional?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* 언제의 일인가. **없으면 「선언이 말하지 않았다」** — `standing` 이 아니다.
|
|
207
|
+
*
|
|
208
|
+
* 기본값을 두고 싶은 힘이 있는데 두면 거짓이 된다. 예를 들어 `tasks.resourceRef → equipment` 는
|
|
209
|
+
* **그 작업이 끝났는지에 따라** 과거이기도 미래이기도 하다. 선언이 답할 수 없는 자리이고, 거기
|
|
210
|
+
* `standing` 을 씌우면 「시간이 없는 사실」이라고 잘못 말하게 된다.
|
|
211
|
+
*
|
|
212
|
+
* 그러니 말할 수 있는 관계만 말한다. 안 말한 것을 화면이 시간으로 묶으려 하면 안 된다.
|
|
213
|
+
*/
|
|
214
|
+
tense?: RelationTense;
|
|
186
215
|
}
|
|
216
|
+
/**
|
|
217
|
+
* 그 관계가 언제의 일인가 — **모르면 `undefined`.**
|
|
218
|
+
*
|
|
219
|
+
* 소비처가 저마다 `?? 'standing'` 을 적지 않게 한 자리에 둔다. 그리고 그 기본값을 **주지 않는
|
|
220
|
+
* 것**이 이 함수의 요점이다 — 선언이 말하지 않은 것을 「시간이 없는 사실」로 바꾸면 화면이 모르는
|
|
221
|
+
* 것을 아는 척한다.
|
|
222
|
+
*
|
|
223
|
+
* 시간으로 묶는 화면은 `undefined` 인 것을 **묶지 않고 그대로 둔다.**
|
|
224
|
+
*/
|
|
225
|
+
export declare function tenseOf(r: Pick<TwinRelationInfo, 'tense'> | undefined): RelationTense | undefined;
|
|
187
226
|
/** 관계 전체 — 지도의 선과 항목의 이웃이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
188
227
|
export declare const TWIN_RELATIONS: TwinRelationInfo[];
|
|
189
228
|
export type TwinObservationKind =
|
package/dist/domain-catalog.js
CHANGED
|
@@ -149,6 +149,18 @@ export const TWIN_AXES = [
|
|
|
149
149
|
{ axis: 'demandWindows', path: 'energy.closed', idField: 'startMs', label: 'twin.axis.demandWindows', kind: 'instance',
|
|
150
150
|
source: 'state', historical: true, standardClass: {}, systems: ['ems'] }
|
|
151
151
|
];
|
|
152
|
+
/**
|
|
153
|
+
* 그 관계가 언제의 일인가 — **모르면 `undefined`.**
|
|
154
|
+
*
|
|
155
|
+
* 소비처가 저마다 `?? 'standing'` 을 적지 않게 한 자리에 둔다. 그리고 그 기본값을 **주지 않는
|
|
156
|
+
* 것**이 이 함수의 요점이다 — 선언이 말하지 않은 것을 「시간이 없는 사실」로 바꾸면 화면이 모르는
|
|
157
|
+
* 것을 아는 척한다.
|
|
158
|
+
*
|
|
159
|
+
* 시간으로 묶는 화면은 `undefined` 인 것을 **묶지 않고 그대로 둔다.**
|
|
160
|
+
*/
|
|
161
|
+
export function tenseOf(r) {
|
|
162
|
+
return r?.tense;
|
|
163
|
+
}
|
|
152
164
|
/** 관계 전체 — 지도의 선과 항목의 이웃이 여기서 나온다(화면은 이 목록을 갖지 않는다). */
|
|
153
165
|
export const TWIN_RELATIONS = [
|
|
154
166
|
/* 자리가 속한 구역 — **board 밖**(호스트의 `TwinArea`). 해소는 호스트가 한다. */
|
package/dist/reliability.d.ts
CHANGED
|
@@ -42,3 +42,38 @@ export interface Mtbf {
|
|
|
42
42
|
* 사라지고, 그러면 이 수가 실제보다 좋아진다 — 자료가 나쁠수록 수가 좋아지는 쪽은 두지 않는다.
|
|
43
43
|
*/
|
|
44
44
|
export declare function computeMtbf(input: MtbfInput): Mtbf;
|
|
45
|
+
export interface MttrInput {
|
|
46
|
+
/** 어느 설비의. */
|
|
47
|
+
equipmentId: string;
|
|
48
|
+
/** 그 설비의 상태 구간들. 순서는 상관없다 — 여기서 시각으로 세운다. */
|
|
49
|
+
periods: readonly ReliabilityPeriod[];
|
|
50
|
+
}
|
|
51
|
+
/** 낼 수 없는 이유 — 「0」이나 「무한」으로 답하지 않는다. */
|
|
52
|
+
export type MttrMissing =
|
|
53
|
+
/** 고장이 없다 — 나눌 수가 0이다. 고칠 일이 없었던 것이지 복구가 빠른 것이 아니다. */
|
|
54
|
+
'no-failure'
|
|
55
|
+
/** 모르는 상태 낱말이 있다 — 조용히 빼면 고장이 사라진다. */
|
|
56
|
+
| 'unknown-status'
|
|
57
|
+
/** 길이가 없거나 시각을 읽을 수 없는 구간이 있다. */
|
|
58
|
+
| 'invalid-period';
|
|
59
|
+
export interface Mttr {
|
|
60
|
+
equipmentId: string;
|
|
61
|
+
/** 평균 복구 시간(ms). **낼 수 없으면 없다.** */
|
|
62
|
+
mttrMs?: number;
|
|
63
|
+
/** 분자 — `down` 구간 길이의 합. */
|
|
64
|
+
downMs: number;
|
|
65
|
+
/** 분모 — **고장으로 들어간 횟수**(이어진 `down` 은 한 번). MTBF 와 같은 수다. */
|
|
66
|
+
failures: number;
|
|
67
|
+
/** 받은 `down` 구간의 수 — `failures` 와 다르면 이어진 것이 있었다. */
|
|
68
|
+
downPeriods: number;
|
|
69
|
+
/** 옮길 수 없던 상태 낱말들 — 무엇이 왔는지 말한다. */
|
|
70
|
+
unknownStatuses: string[];
|
|
71
|
+
missing: MttrMissing[];
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 이 설비의 MTTR.
|
|
75
|
+
*
|
|
76
|
+
* **모르는 상태를 만나면 답하지 않는다.** MTBF 와 같은 이유다 — 그 구간이 고장이었다면 빼는 순간
|
|
77
|
+
* 복구 시간이 짧아 보인다. 자료가 나쁠수록 수가 좋아지는 쪽은 두지 않는다.
|
|
78
|
+
*/
|
|
79
|
+
export declare function computeMttr(input: MttrInput): Mttr;
|
package/dist/reliability.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* **MTBF** — ISO 22400-2. 고장 사이에 얼마나
|
|
2
|
+
* **MTBF · MTTR** — ISO 22400-2. 고장 사이에 얼마나 돌았나 · 고치는 데 얼마나 걸렸나.
|
|
3
|
+
*
|
|
4
|
+
* 표준이 둘을 **짝으로** 정의한다. 한쪽만 두면 소비처가 나머지를 자기 식으로 짓고, 그러면 같은
|
|
5
|
+
* 설비가 화면마다 다른 수를 갖는다 — 이 파일이 애초에 존재하는 이유가 그것이다.
|
|
3
6
|
*
|
|
4
7
|
* ── 왜 구간을 받나 (집계 값이 아니라) ────────────────────────────────────────
|
|
5
8
|
* `computeMtbf(가동시간, 고장횟수)` 로 두면 **「고장 횟수」를 부르는 쪽이 센다.** 그러면 세는 방법이
|
|
@@ -42,15 +45,22 @@ const at = (t) => (typeof t === 'string' ? Date.parse(t) : NaN);
|
|
|
42
45
|
* **모르는 상태를 만나면 답하지 않는다.** 빼고 계산하면 그 구간이 고장이었을 때 고장이 조용히
|
|
43
46
|
* 사라지고, 그러면 이 수가 실제보다 좋아진다 — 자료가 나쁠수록 수가 좋아지는 쪽은 두지 않는다.
|
|
44
47
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* 구간을 한 번 훑어 두 산출식이 쓸 것을 다 센다.
|
|
50
|
+
*
|
|
51
|
+
* **두 함수가 따로 걸으면 안 된다** — 「이어진 `down` 은 한 번」 규칙이 두 곳에 살게 되고, 한 곳만
|
|
52
|
+
* 고쳐지는 날 같은 설비의 MTBF 와 MTTR 이 서로 어긋난다. 그 둘은 같은 고장 횟수로 나눈 값이므로
|
|
53
|
+
* 어긋나면 둘 중 하나가 반드시 틀렸는데, 어느 쪽인지 아무도 모른다.
|
|
54
|
+
*/
|
|
55
|
+
function scanPeriods(periods) {
|
|
48
56
|
const unknown = new Set();
|
|
49
57
|
/* 시각으로 세운다 — 받은 순서를 믿지 않는다. 이어짐 판정이 순서에 걸린다. */
|
|
50
|
-
const sorted = [...(
|
|
58
|
+
const sorted = [...(periods ?? [])]
|
|
51
59
|
.map(p => ({ status: normalizeEquipmentStatus(p?.status), said: String(p?.status ?? ''), from: at(p?.from), to: at(p?.to) }))
|
|
52
60
|
.sort((a, b) => a.from - b.from);
|
|
53
61
|
let operatingMs = 0;
|
|
62
|
+
/** 고친 시간의 합 — MTTR 의 분자. */
|
|
63
|
+
let downMs = 0;
|
|
54
64
|
let failures = 0;
|
|
55
65
|
let downPeriods = 0;
|
|
56
66
|
let invalid = false;
|
|
@@ -74,6 +84,7 @@ export function computeMtbf(input) {
|
|
|
74
84
|
if (p.status === 'busy')
|
|
75
85
|
operatingMs += p.to - p.from;
|
|
76
86
|
if (p.status === 'down') {
|
|
87
|
+
downMs += p.to - p.from;
|
|
77
88
|
downPeriods++;
|
|
78
89
|
if (!wasDown)
|
|
79
90
|
failures++;
|
|
@@ -83,23 +94,61 @@ export function computeMtbf(input) {
|
|
|
83
94
|
wasDown = false;
|
|
84
95
|
}
|
|
85
96
|
}
|
|
86
|
-
|
|
97
|
+
return { operatingMs, downMs, failures, downPeriods, unknownStatuses: [...unknown], invalid };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 이 설비의 MTBF.
|
|
101
|
+
*
|
|
102
|
+
* **모르는 상태를 만나면 답하지 않는다.** 빼고 계산하면 그 구간이 고장이었을 때 고장이 조용히
|
|
103
|
+
* 사라지고, 그러면 이 수가 실제보다 좋아진다 — 자료가 나쁠수록 수가 좋아지는 쪽은 두지 않는다.
|
|
104
|
+
*/
|
|
105
|
+
export function computeMtbf(input) {
|
|
106
|
+
const s = scanPeriods(input.periods);
|
|
107
|
+
const missing = [];
|
|
108
|
+
if (s.invalid)
|
|
87
109
|
missing.push('invalid-period');
|
|
88
|
-
if (
|
|
110
|
+
if (s.unknownStatuses.length)
|
|
89
111
|
missing.push('unknown-status');
|
|
90
|
-
if (operatingMs <= 0)
|
|
112
|
+
if (s.operatingMs <= 0)
|
|
91
113
|
missing.push('no-operating-time');
|
|
92
|
-
if (failures === 0)
|
|
114
|
+
if (s.failures === 0)
|
|
115
|
+
missing.push('no-failure');
|
|
116
|
+
const out = {
|
|
117
|
+
equipmentId: input.equipmentId,
|
|
118
|
+
operatingMs: s.operatingMs,
|
|
119
|
+
failures: s.failures,
|
|
120
|
+
downPeriods: s.downPeriods,
|
|
121
|
+
unknownStatuses: s.unknownStatuses,
|
|
122
|
+
missing
|
|
123
|
+
};
|
|
124
|
+
if (missing.length)
|
|
125
|
+
return out;
|
|
126
|
+
return { ...out, mtbfMs: s.operatingMs / s.failures };
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 이 설비의 MTTR.
|
|
130
|
+
*
|
|
131
|
+
* **모르는 상태를 만나면 답하지 않는다.** MTBF 와 같은 이유다 — 그 구간이 고장이었다면 빼는 순간
|
|
132
|
+
* 복구 시간이 짧아 보인다. 자료가 나쁠수록 수가 좋아지는 쪽은 두지 않는다.
|
|
133
|
+
*/
|
|
134
|
+
export function computeMttr(input) {
|
|
135
|
+
const s = scanPeriods(input.periods);
|
|
136
|
+
const missing = [];
|
|
137
|
+
if (s.invalid)
|
|
138
|
+
missing.push('invalid-period');
|
|
139
|
+
if (s.unknownStatuses.length)
|
|
140
|
+
missing.push('unknown-status');
|
|
141
|
+
if (s.failures === 0)
|
|
93
142
|
missing.push('no-failure');
|
|
94
143
|
const out = {
|
|
95
|
-
equipmentId,
|
|
96
|
-
|
|
97
|
-
failures,
|
|
98
|
-
downPeriods,
|
|
99
|
-
unknownStatuses:
|
|
144
|
+
equipmentId: input.equipmentId,
|
|
145
|
+
downMs: s.downMs,
|
|
146
|
+
failures: s.failures,
|
|
147
|
+
downPeriods: s.downPeriods,
|
|
148
|
+
unknownStatuses: s.unknownStatuses,
|
|
100
149
|
missing
|
|
101
150
|
};
|
|
102
151
|
if (missing.length)
|
|
103
152
|
return out;
|
|
104
|
-
return { ...out,
|
|
153
|
+
return { ...out, mttrMs: s.downMs / s.failures };
|
|
105
154
|
}
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -62,6 +62,7 @@ __export(index_exports, {
|
|
|
62
62
|
OP_PARAM: () => OP_PARAM,
|
|
63
63
|
ORDER_TERMINAL_STATUS: () => ORDER_TERMINAL_STATUS,
|
|
64
64
|
PRIORITY_UNSET: () => PRIORITY_UNSET,
|
|
65
|
+
RESOURCE_KIND: () => RESOURCE_KIND,
|
|
65
66
|
RETIRED_VOCABULARY: () => RETIRED_VOCABULARY,
|
|
66
67
|
SCHEDULE_STATUS: () => SCHEDULE_STATUS,
|
|
67
68
|
TWIN_AXES: () => TWIN_AXES,
|
|
@@ -103,6 +104,7 @@ __export(index_exports, {
|
|
|
103
104
|
commandsOf: () => commandsOf,
|
|
104
105
|
computeFirstPassYield: () => computeFirstPassYield,
|
|
105
106
|
computeMtbf: () => computeMtbf,
|
|
107
|
+
computeMttr: () => computeMttr,
|
|
106
108
|
computeOee: () => computeOee,
|
|
107
109
|
conversionFactorOf: () => conversionFactorOf,
|
|
108
110
|
criterionSaysNothing: () => criterionSaysNothing,
|
|
@@ -151,6 +153,7 @@ __export(index_exports, {
|
|
|
151
153
|
isOperationalRecord: () => isOperationalRecord,
|
|
152
154
|
isOrderTerminal: () => isOrderTerminal,
|
|
153
155
|
isPlannedStopStatus: () => isPlannedStopStatus,
|
|
156
|
+
isResourceKind: () => isResourceKind,
|
|
154
157
|
isTransformationRecord: () => isTransformationRecord,
|
|
155
158
|
isoDurationHours: () => isoDurationHours,
|
|
156
159
|
itemKeyOf: () => itemKeyOf,
|
|
@@ -203,6 +206,7 @@ __export(index_exports, {
|
|
|
203
206
|
ssccUri: () => ssccUri,
|
|
204
207
|
stateFieldsOf: () => stateFieldsOf,
|
|
205
208
|
subLotIdOf: () => subLotIdOf,
|
|
209
|
+
tenseOf: () => tenseOf,
|
|
206
210
|
testEvidenceGaps: () => testEvidenceGaps,
|
|
207
211
|
testPassedAt: () => testPassedAt,
|
|
208
212
|
transactionEvent: () => transactionEvent,
|
|
@@ -1024,6 +1028,10 @@ var EQUIPMENT_STATUS = ["busy", "setup", "down", "idle", "planned-stop"];
|
|
|
1024
1028
|
function isEquipmentStatus(value) {
|
|
1025
1029
|
return typeof value === "string" && EQUIPMENT_STATUS.includes(value);
|
|
1026
1030
|
}
|
|
1031
|
+
var RESOURCE_KIND = ["equipment", "personnel", "material", "asset"];
|
|
1032
|
+
function isResourceKind(value) {
|
|
1033
|
+
return typeof value === "string" && RESOURCE_KIND.includes(value);
|
|
1034
|
+
}
|
|
1027
1035
|
function normalizeEquipmentStatus(value) {
|
|
1028
1036
|
const said = typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
1029
1037
|
return isEquipmentStatus(said) ? said : void 0;
|
|
@@ -1860,6 +1868,9 @@ var TWIN_AXES = [
|
|
|
1860
1868
|
systems: ["ems"]
|
|
1861
1869
|
}
|
|
1862
1870
|
];
|
|
1871
|
+
function tenseOf(r) {
|
|
1872
|
+
return r?.tense;
|
|
1873
|
+
}
|
|
1863
1874
|
var TWIN_RELATIONS = [
|
|
1864
1875
|
/* 자리가 속한 구역 — **board 밖**(호스트의 `TwinArea`). 해소는 호스트가 한다. */
|
|
1865
1876
|
{ from: "locations", field: "parentId", target: { kind: "external", entity: "space.area" }, via: "twin.rel.area", optional: true },
|
|
@@ -4070,12 +4081,11 @@ function rolledThroughputYield(steps) {
|
|
|
4070
4081
|
|
|
4071
4082
|
// src/reliability.ts
|
|
4072
4083
|
var at = (t) => typeof t === "string" ? Date.parse(t) : NaN;
|
|
4073
|
-
function
|
|
4074
|
-
const { equipmentId } = input;
|
|
4075
|
-
const missing = [];
|
|
4084
|
+
function scanPeriods(periods) {
|
|
4076
4085
|
const unknown = /* @__PURE__ */ new Set();
|
|
4077
|
-
const sorted = [...
|
|
4086
|
+
const sorted = [...periods ?? []].map((p) => ({ status: normalizeEquipmentStatus(p?.status), said: String(p?.status ?? ""), from: at(p?.from), to: at(p?.to) })).sort((a, b) => a.from - b.from);
|
|
4078
4087
|
let operatingMs = 0;
|
|
4088
|
+
let downMs = 0;
|
|
4079
4089
|
let failures = 0;
|
|
4080
4090
|
let downPeriods = 0;
|
|
4081
4091
|
let invalid = false;
|
|
@@ -4092,6 +4102,7 @@ function computeMtbf(input) {
|
|
|
4092
4102
|
}
|
|
4093
4103
|
if (p.status === "busy") operatingMs += p.to - p.from;
|
|
4094
4104
|
if (p.status === "down") {
|
|
4105
|
+
downMs += p.to - p.from;
|
|
4095
4106
|
downPeriods++;
|
|
4096
4107
|
if (!wasDown) failures++;
|
|
4097
4108
|
wasDown = true;
|
|
@@ -4099,20 +4110,42 @@ function computeMtbf(input) {
|
|
|
4099
4110
|
wasDown = false;
|
|
4100
4111
|
}
|
|
4101
4112
|
}
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4113
|
+
return { operatingMs, downMs, failures, downPeriods, unknownStatuses: [...unknown], invalid };
|
|
4114
|
+
}
|
|
4115
|
+
function computeMtbf(input) {
|
|
4116
|
+
const s = scanPeriods(input.periods);
|
|
4117
|
+
const missing = [];
|
|
4118
|
+
if (s.invalid) missing.push("invalid-period");
|
|
4119
|
+
if (s.unknownStatuses.length) missing.push("unknown-status");
|
|
4120
|
+
if (s.operatingMs <= 0) missing.push("no-operating-time");
|
|
4121
|
+
if (s.failures === 0) missing.push("no-failure");
|
|
4122
|
+
const out = {
|
|
4123
|
+
equipmentId: input.equipmentId,
|
|
4124
|
+
operatingMs: s.operatingMs,
|
|
4125
|
+
failures: s.failures,
|
|
4126
|
+
downPeriods: s.downPeriods,
|
|
4127
|
+
unknownStatuses: s.unknownStatuses,
|
|
4128
|
+
missing
|
|
4129
|
+
};
|
|
4130
|
+
if (missing.length) return out;
|
|
4131
|
+
return { ...out, mtbfMs: s.operatingMs / s.failures };
|
|
4132
|
+
}
|
|
4133
|
+
function computeMttr(input) {
|
|
4134
|
+
const s = scanPeriods(input.periods);
|
|
4135
|
+
const missing = [];
|
|
4136
|
+
if (s.invalid) missing.push("invalid-period");
|
|
4137
|
+
if (s.unknownStatuses.length) missing.push("unknown-status");
|
|
4138
|
+
if (s.failures === 0) missing.push("no-failure");
|
|
4106
4139
|
const out = {
|
|
4107
|
-
equipmentId,
|
|
4108
|
-
|
|
4109
|
-
failures,
|
|
4110
|
-
downPeriods,
|
|
4111
|
-
unknownStatuses:
|
|
4140
|
+
equipmentId: input.equipmentId,
|
|
4141
|
+
downMs: s.downMs,
|
|
4142
|
+
failures: s.failures,
|
|
4143
|
+
downPeriods: s.downPeriods,
|
|
4144
|
+
unknownStatuses: s.unknownStatuses,
|
|
4112
4145
|
missing
|
|
4113
4146
|
};
|
|
4114
4147
|
if (missing.length) return out;
|
|
4115
|
-
return { ...out,
|
|
4148
|
+
return { ...out, mttrMs: s.downMs / s.failures };
|
|
4116
4149
|
}
|
|
4117
4150
|
|
|
4118
4151
|
// src/erp.ts
|
|
@@ -4319,6 +4352,7 @@ function commandSpecGaps(specs, command) {
|
|
|
4319
4352
|
OP_PARAM,
|
|
4320
4353
|
ORDER_TERMINAL_STATUS,
|
|
4321
4354
|
PRIORITY_UNSET,
|
|
4355
|
+
RESOURCE_KIND,
|
|
4322
4356
|
RETIRED_VOCABULARY,
|
|
4323
4357
|
SCHEDULE_STATUS,
|
|
4324
4358
|
TWIN_AXES,
|
|
@@ -4360,6 +4394,7 @@ function commandSpecGaps(specs, command) {
|
|
|
4360
4394
|
commandsOf,
|
|
4361
4395
|
computeFirstPassYield,
|
|
4362
4396
|
computeMtbf,
|
|
4397
|
+
computeMttr,
|
|
4363
4398
|
computeOee,
|
|
4364
4399
|
conversionFactorOf,
|
|
4365
4400
|
criterionSaysNothing,
|
|
@@ -4408,6 +4443,7 @@ function commandSpecGaps(specs, command) {
|
|
|
4408
4443
|
isOperationalRecord,
|
|
4409
4444
|
isOrderTerminal,
|
|
4410
4445
|
isPlannedStopStatus,
|
|
4446
|
+
isResourceKind,
|
|
4411
4447
|
isTransformationRecord,
|
|
4412
4448
|
isoDurationHours,
|
|
4413
4449
|
itemKeyOf,
|
|
@@ -4460,6 +4496,7 @@ function commandSpecGaps(specs, command) {
|
|
|
4460
4496
|
ssccUri,
|
|
4461
4497
|
stateFieldsOf,
|
|
4462
4498
|
subLotIdOf,
|
|
4499
|
+
tenseOf,
|
|
4463
4500
|
testEvidenceGaps,
|
|
4464
4501
|
testPassedAt,
|
|
4465
4502
|
transactionEvent,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@operato/ops-contract",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Operations domain contract — the standard vocabulary that producers and readers agree on (EPCIS 2.0/GS1, ISA-95, IEC 61850/ISO 50001). Types, guards, validation. No state, no engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist-cjs/index.cjs",
|