@operato/twin-kernel 0.6.7 → 0.6.9
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 +36 -7
- package/dist/contract.js +17 -0
- package/dist/flow-engine.d.ts +17 -1
- package/dist/flow-engine.js +39 -27
- package/dist/observed-reducer.d.ts +14 -0
- package/dist/observed-reducer.js +31 -10
- package/dist-cjs/index.cjs +78 -33
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -617,6 +617,14 @@ export declare function offCalendarAt(r: {
|
|
|
617
617
|
};
|
|
618
618
|
workCalendar?: WorkCalendarEntry[];
|
|
619
619
|
}, ms: number, utcOffsetMinutes?: number): boolean;
|
|
620
|
+
/**
|
|
621
|
+
* 이 자원에게 **필수인 시험 목록** — 등급 상속을 타고 닫아 모은다.
|
|
622
|
+
*
|
|
623
|
+
* 요구는 등급이 말하고(`ResourceClassDef.testSpecificationIds`) 기록은 개체가 든다(`testResults`).
|
|
624
|
+
* 그 사이를 잇는 이 수집이 계약에 있는 이유: **두 구동이 같은 답을 내야 한다.** 시뮬과 미러가 각자
|
|
625
|
+
* 모으면 "이 사람에게 무엇이 필수인가" 가 갈리고, 같은 자원을 한쪽은 막고 다른 쪽은 통과시킨다.
|
|
626
|
+
*/
|
|
627
|
+
export declare function requiredTestsFor(directIds: readonly string[] | undefined, defs: readonly ResourceClassDef[] | undefined, at?: ISOTime): string[];
|
|
620
628
|
/**
|
|
621
629
|
* 자원의 **가용 능력을 판정한다** — 하나의 규칙, 하나의 자리.
|
|
622
630
|
*
|
|
@@ -846,8 +854,6 @@ export interface EquipmentState extends EffectivePeriod {
|
|
|
846
854
|
* 여기 실린다. 지금까지 계약 밖으로 흘러 호스트까지 `any` 로 전달됐다(§ResourceProperty).
|
|
847
855
|
*/
|
|
848
856
|
properties?: ResourceProperty[];
|
|
849
|
-
/** 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 가동시간·정비창을 함께 표현한다. */
|
|
850
|
-
workCalendar?: WorkCalendarEntry[];
|
|
851
857
|
/** 적격을 검증한 시험 명세들 — 표준 `Equipment.TestSpecificationID`(§TestSpecificationRefs). */
|
|
852
858
|
testSpecificationIds?: TestSpecificationRefs;
|
|
853
859
|
/**
|
|
@@ -857,6 +863,18 @@ export interface EquipmentState extends EffectivePeriod {
|
|
|
857
863
|
* 없으면 판정하지 않는다(없는 것으로 막으면 자격자가 전부 사라진다).
|
|
858
864
|
*/
|
|
859
865
|
testResults?: TestResult[];
|
|
866
|
+
/**
|
|
867
|
+
* **왜 지금 쓰일 수 없나** — 판정과 사유를 계약이 낸다(`capabilityOf` · `CapabilityReason`).
|
|
868
|
+
*
|
|
869
|
+
* 상태에 실어 보내는 이유: 이 판정은 **시각과 등급 정의를 함께 알아야** 나온다(유효기간·교대·자격
|
|
870
|
+
* 만료). 소비처는 그 둘을 갖고 있지 않다 — 화면은 시뮬 시각을 모르고, 호스트는 등급 상속을 타고
|
|
871
|
+
* 필수 시험을 모으는 규칙을 모른다. 그래서 예전에는 소비처가 플래그를 보고 짐작했고, 자격이 만료된
|
|
872
|
+
* 사람이 `대기` 로 보였다. 놀고 있는 것은 맞지만 **쓸 수 있는 것은 아니다** — "대기 7명" 이 조용히
|
|
873
|
+
* 틀린 숫자가 됐다.
|
|
874
|
+
*
|
|
875
|
+
* 배정이 쓰는 판정과 **같은 함수의 결과**다(규칙 한 벌). 사유의 순서도 계약이 정한다.
|
|
876
|
+
*/
|
|
877
|
+
capability?: Capability;
|
|
860
878
|
}
|
|
861
879
|
/**
|
|
862
880
|
* 사람 — **ISA-95 `Person`.** 설비와 다른 자원 종류다.
|
|
@@ -908,11 +926,6 @@ export interface PersonState extends EffectivePeriod {
|
|
|
908
926
|
location?: string;
|
|
909
927
|
/** 자원 속성 — 표준 `PersonProperty`. 자격증·숙련 등급 같은 사실이 여기 들어간다(§ResourceProperty). */
|
|
910
928
|
properties?: ResourceProperty[];
|
|
911
|
-
/**
|
|
912
|
-
* 근무 캘린더 — 표준 `WorkCalendarEntry`(복수). 교대 여러 개와 휴게·휴일을 함께 표현한다.
|
|
913
|
-
* 예전에는 하루에 창 하나뿐이어서 2교대·야간+주간 조합을 표현할 수 없었다.
|
|
914
|
-
*/
|
|
915
|
-
workCalendar?: WorkCalendarEntry[];
|
|
916
929
|
/** 자격을 검증한 시험 명세들 — 표준 `Person.TestSpecificationID`(§TestSpecificationRefs). */
|
|
917
930
|
testSpecificationIds?: TestSpecificationRefs;
|
|
918
931
|
/**
|
|
@@ -922,6 +935,18 @@ export interface PersonState extends EffectivePeriod {
|
|
|
922
935
|
* 없으면 판정하지 않는다(없는 것으로 막으면 자격자가 전부 사라진다).
|
|
923
936
|
*/
|
|
924
937
|
testResults?: TestResult[];
|
|
938
|
+
/**
|
|
939
|
+
* **왜 지금 쓰일 수 없나** — 판정과 사유를 계약이 낸다(`capabilityOf` · `CapabilityReason`).
|
|
940
|
+
*
|
|
941
|
+
* 상태에 실어 보내는 이유: 이 판정은 **시각과 등급 정의를 함께 알아야** 나온다(유효기간·교대·자격
|
|
942
|
+
* 만료). 소비처는 그 둘을 갖고 있지 않다 — 화면은 시뮬 시각을 모르고, 호스트는 등급 상속을 타고
|
|
943
|
+
* 필수 시험을 모으는 규칙을 모른다. 그래서 예전에는 소비처가 플래그를 보고 짐작했고, 자격이 만료된
|
|
944
|
+
* 사람이 `대기` 로 보였다. 놀고 있는 것은 맞지만 **쓸 수 있는 것은 아니다** — "대기 7명" 이 조용히
|
|
945
|
+
* 틀린 숫자가 됐다.
|
|
946
|
+
*
|
|
947
|
+
* 배정이 쓰는 판정과 **같은 함수의 결과**다(규칙 한 벌). 사유의 순서도 계약이 정한다.
|
|
948
|
+
*/
|
|
949
|
+
capability?: Capability;
|
|
925
950
|
}
|
|
926
951
|
/**
|
|
927
952
|
* 물리 자산 — **ISA-95 `PhysicalAsset`, GS1 `GRAI`(반복사용 자산).**
|
|
@@ -962,6 +987,10 @@ export interface AssetState extends EffectivePeriod {
|
|
|
962
987
|
* 빠뜨리면 회수 대상 수가 실제보다 많게 잡히고, 빈 팔레트 부족이 보이지 않는다.
|
|
963
988
|
*/
|
|
964
989
|
effectivity?: Effectivity;
|
|
990
|
+
/** 그 시험들의 결과 — 사람과 같은 뜻(§TestResult). 검사에서 떨어진 팔레트는 배정에서 빠진다. */
|
|
991
|
+
testResults?: TestResult[];
|
|
992
|
+
/** 왜 지금 쓰일 수 없나 — 사람·설비와 **같은 판정**(§PersonState.capability). */
|
|
993
|
+
capability?: Capability;
|
|
965
994
|
}
|
|
966
995
|
/**
|
|
967
996
|
* 작업 하나의 관측 상태 — **ISA-95 `SegmentResponse`**(Part 4, 실적).
|
package/dist/contract.js
CHANGED
|
@@ -517,6 +517,23 @@ export function offCalendarAt(r, ms, utcOffsetMinutes) {
|
|
|
517
517
|
const h = Math.floor(minute / 60);
|
|
518
518
|
return !(w.startHour <= w.endHour ? h >= w.startHour && h < w.endHour : h >= w.startHour || h < w.endHour);
|
|
519
519
|
}
|
|
520
|
+
/**
|
|
521
|
+
* 이 자원에게 **필수인 시험 목록** — 등급 상속을 타고 닫아 모은다.
|
|
522
|
+
*
|
|
523
|
+
* 요구는 등급이 말하고(`ResourceClassDef.testSpecificationIds`) 기록은 개체가 든다(`testResults`).
|
|
524
|
+
* 그 사이를 잇는 이 수집이 계약에 있는 이유: **두 구동이 같은 답을 내야 한다.** 시뮬과 미러가 각자
|
|
525
|
+
* 모으면 "이 사람에게 무엇이 필수인가" 가 갈리고, 같은 자원을 한쪽은 막고 다른 쪽은 통과시킨다.
|
|
526
|
+
*/
|
|
527
|
+
export function requiredTestsFor(directIds, defs, at) {
|
|
528
|
+
if (!defs?.length)
|
|
529
|
+
return [];
|
|
530
|
+
const closure = classClosure(directIds, defs, at);
|
|
531
|
+
const required = [];
|
|
532
|
+
for (const d of defs)
|
|
533
|
+
if (closure.has(d.id))
|
|
534
|
+
required.push(...(d.testSpecificationIds ?? []));
|
|
535
|
+
return required;
|
|
536
|
+
}
|
|
520
537
|
/**
|
|
521
538
|
* 자원의 **가용 능력을 판정한다** — 하나의 규칙, 하나의 자리.
|
|
522
539
|
*
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -56,6 +56,13 @@ export interface FlowAsset extends EffectivePeriod {
|
|
|
56
56
|
properties?: ResourceProperty[];
|
|
57
57
|
/** 적격을 검증한 시험 명세들 — 표준 `TestSpecificationID`. */
|
|
58
58
|
testSpecificationIds?: string[];
|
|
59
|
+
/**
|
|
60
|
+
* 그 시험들의 **결과** — 자격이 성립하는지는 이것이 말한다(§TestResult).
|
|
61
|
+
*
|
|
62
|
+
* 사람에게만 두었다가 **설비·자산의 결과는 커널이 받아서 버렸다** — 선언은 들어오는데 자리가
|
|
63
|
+
* 없으니 배정 판정에서 아예 읽히지 않았다(검사에서 떨어진 도장 부스가 계속 배정됐다).
|
|
64
|
+
*/
|
|
65
|
+
testResults?: TestResult[];
|
|
59
66
|
}
|
|
60
67
|
/** 사람 — 설비와 별개 자원(고장·OEE 가 아니라 등급·교대로 산다). */
|
|
61
68
|
export interface FlowPerson extends EffectivePeriod {
|
|
@@ -96,6 +103,8 @@ export interface FlowEquipment extends EffectivePeriod {
|
|
|
96
103
|
properties?: ResourceProperty[];
|
|
97
104
|
/** 적격을 검증한 시험 명세들 — 표준 `TestSpecificationID`. */
|
|
98
105
|
testSpecificationIds?: string[];
|
|
106
|
+
/** 그 시험들의 **결과** — 사람과 같은 뜻(§FlowAsset.testResults). 없으면 판정하지 않는다. */
|
|
107
|
+
testResults?: TestResult[];
|
|
99
108
|
/** 교대(가동시간) — 지정 시 이 시간대에만 배정된다. 미지정=24시간 가용. */
|
|
100
109
|
window?: {
|
|
101
110
|
startHour: number;
|
|
@@ -814,7 +823,14 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
814
823
|
*
|
|
815
824
|
* 상속을 타고 닫은 등급 전부의 요구를 모은다: 상위 등급이 요구하는 시험도 자격의 조건이다.
|
|
816
825
|
*/
|
|
817
|
-
|
|
826
|
+
/**
|
|
827
|
+
* 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
|
|
828
|
+
*
|
|
829
|
+
* 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
|
|
830
|
+
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(놀고 있는 것은 맞지만 쓸 수 있는 것은 아니다).
|
|
831
|
+
* 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
|
|
832
|
+
*/
|
|
833
|
+
private capabilityOfResource;
|
|
818
834
|
private claimPersonnel;
|
|
819
835
|
/**
|
|
820
836
|
* 필요 설비를 고른다 — **인원·자산과 같은 규칙**(등급으로 요구, 부분 확보 없이 전량 아니면 대기).
|
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, priorityRank, dueStatusOf, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf } from "./contract.js";
|
|
12
|
+
import { OP_EVENT, CMD, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, classClosure, capabilityOf, requiredTestsFor, priorityRank, dueStatusOf, effectivityAt, offCalendarAt, offCalendarReasonAt, minuteOfDayAt, activeShiftAt, subLotIdOf, itemKeyOf } from "./contract.js";
|
|
13
13
|
import { ObservedReducer } from "./observed-reducer.js";
|
|
14
14
|
import { transformationEvent, aggregationEvent, objectEvent, parseEpc, DISP, ILMD_ATTR, CBV_BIZSTEP } from "./epcis.js";
|
|
15
15
|
import { parseIsoDuration } from "./iso-duration.js";
|
|
@@ -285,11 +285,11 @@ export class FlowEngine {
|
|
|
285
285
|
}
|
|
286
286
|
buildPerson(p) {
|
|
287
287
|
return { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', taskId: null, window: p.window, ...(p.workCalendar ? { workCalendar: p.workCalendar } : {}), ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}),
|
|
288
|
-
...(p.
|
|
288
|
+
...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.testResults ? { testResults: p.testResults } : {}), ...effectiveOnly(p) };
|
|
289
289
|
}
|
|
290
290
|
buildAsset(a) {
|
|
291
291
|
return { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', taskId: null, ...(a.properties ? { properties: a.properties } : {}),
|
|
292
|
-
...(a.
|
|
292
|
+
...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.testResults ? { testResults: a.testResults } : {}), ...effectiveOnly(a) };
|
|
293
293
|
}
|
|
294
294
|
/**
|
|
295
295
|
* 이 커널이 **관측으로 구동된다**고 선언한다 — 미러가 첫 이벤트를 받기 전에도 그렇다.
|
|
@@ -369,7 +369,7 @@ export class FlowEngine {
|
|
|
369
369
|
};
|
|
370
370
|
}
|
|
371
371
|
buildEquipment(m) {
|
|
372
|
-
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), status: 'idle', taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0, window: m.window, ...(m.workCalendar ? { workCalendar: m.workCalendar } : {}), ...effectiveOnly(m) };
|
|
372
|
+
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), status: 'idle', taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0, window: m.window, ...(m.workCalendar ? { workCalendar: m.workCalendar } : {}), ...effectiveOnly(m) };
|
|
373
373
|
if (m.mtbfMs !== undefined) {
|
|
374
374
|
eq.mtbfMs = m.mtbfMs;
|
|
375
375
|
eq.mttrMs = m.mttrMs;
|
|
@@ -384,7 +384,7 @@ export class FlowEngine {
|
|
|
384
384
|
addEquipment(m) {
|
|
385
385
|
if (this.equipment.has(m.id))
|
|
386
386
|
return;
|
|
387
|
-
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), status: 'idle', taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
387
|
+
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), status: 'idle', taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
388
388
|
if (m.mtbfMs !== undefined) {
|
|
389
389
|
eq.mtbfMs = m.mtbfMs;
|
|
390
390
|
eq.mttrMs = m.mttrMs;
|
|
@@ -469,6 +469,8 @@ export class FlowEngine {
|
|
|
469
469
|
/* 관측 스냅샷이 들고 있는 것은 관측을 따른다(미러가 보드에서 읽어 실어 온다). */
|
|
470
470
|
...(m.properties ? { properties: m.properties } : {}),
|
|
471
471
|
...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}),
|
|
472
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
473
|
+
...(m.testResults ? { testResults: m.testResults } : {}),
|
|
472
474
|
...effectiveOnly(m)
|
|
473
475
|
});
|
|
474
476
|
}
|
|
@@ -483,6 +485,8 @@ export class FlowEngine {
|
|
|
483
485
|
...(p.location ? { location: p.location } : {}),
|
|
484
486
|
...(p.properties ? { properties: p.properties } : {}),
|
|
485
487
|
...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}),
|
|
488
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
489
|
+
...(p.testResults ? { testResults: p.testResults } : {}),
|
|
486
490
|
...effectiveOnly(p)
|
|
487
491
|
});
|
|
488
492
|
}
|
|
@@ -494,6 +498,8 @@ export class FlowEngine {
|
|
|
494
498
|
id: a.id, assetClassIds: a.assetClassIds ?? prev?.assetClassIds, location: a.location, status: 'idle', taskId: null, carrying: a.carrying,
|
|
495
499
|
...(a.properties ? { properties: a.properties } : {}),
|
|
496
500
|
...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}),
|
|
501
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
502
|
+
...(a.testResults ? { testResults: a.testResults } : {}),
|
|
497
503
|
...effectiveOnly(a)
|
|
498
504
|
});
|
|
499
505
|
}
|
|
@@ -812,7 +818,7 @@ export class FlowEngine {
|
|
|
812
818
|
}),
|
|
813
819
|
items: [...this.items.values()].map(i => this.itemState(i)),
|
|
814
820
|
equipment: [...this.equipment.values()].map(m => {
|
|
815
|
-
const s = { id: m.id, kind: m.kind, location: m.location, ...(m.homeLocation ? { homeLocation: m.homeLocation } : {}), status: m.status, taskId: m.taskId ?? undefined, oee: this.oeeOf(m), held: m.held, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), origin: 'master', ...(this.offShift(m) ? { offShift: true, ...this.offReason(m) } : {}), ...this.effectivePart(m), ...(this.shiftOf(m) ? { shift: this.shiftOf(m) } : {}) };
|
|
821
|
+
const s = { id: m.id, kind: m.kind, location: m.location, ...(m.homeLocation ? { homeLocation: m.homeLocation } : {}), status: m.status, taskId: m.taskId ?? undefined, oee: this.oeeOf(m), held: m.held, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), capability: this.capabilityOfResource(m, m.kind ? [m.kind] : [], this.classDefs.equipment), origin: 'master', ...(this.offShift(m) ? { offShift: true, ...this.offReason(m) } : {}), ...this.effectivePart(m), ...(this.shiftOf(m) ? { shift: this.shiftOf(m) } : {}) };
|
|
816
822
|
const t = m.taskId ? this.tasks.get(m.taskId) : undefined;
|
|
817
823
|
if (t && t.status === 'in-progress' && t.intent !== 'process')
|
|
818
824
|
s.motion = { fromNode: t.fromNode, toNode: t.toNode, startedAtSimMs: this.clockMs - (t.durationMs - t.remainingMs), durationMs: t.durationMs, progress: this.progressOf(t), elapsedMs: t.durationMs - t.remainingMs };
|
|
@@ -826,7 +832,10 @@ export class FlowEngine {
|
|
|
826
832
|
st.properties = a.properties;
|
|
827
833
|
if (a.testSpecificationIds)
|
|
828
834
|
st.testSpecificationIds = a.testSpecificationIds;
|
|
835
|
+
if (a.testResults)
|
|
836
|
+
st.testResults = a.testResults;
|
|
829
837
|
Object.assign(st, this.effectivePart(a));
|
|
838
|
+
st.capability = this.capabilityOfResource(a, a.assetClassIds, this.classDefs.asset);
|
|
830
839
|
return st;
|
|
831
840
|
}),
|
|
832
841
|
persons: [...this.persons.values()].map(p => {
|
|
@@ -837,6 +846,10 @@ export class FlowEngine {
|
|
|
837
846
|
st.properties = p.properties;
|
|
838
847
|
if (p.testSpecificationIds)
|
|
839
848
|
st.testSpecificationIds = p.testSpecificationIds;
|
|
849
|
+
/* 결과도 함께 낸다 — 참조만 보내면 소비처는 "무엇으로 검증하는가" 까지만 알고 "지금 유효한가" 를
|
|
850
|
+
물을 수 없다(사실은 자기 집에서 나온다). 판정은 아래 `capability` 가 이미 답한다. */
|
|
851
|
+
if (p.testResults)
|
|
852
|
+
st.testResults = p.testResults;
|
|
840
853
|
if (this.personOffShift(p)) {
|
|
841
854
|
st.offShift = true;
|
|
842
855
|
Object.assign(st, this.offReason(p));
|
|
@@ -845,6 +858,7 @@ export class FlowEngine {
|
|
|
845
858
|
if (sh)
|
|
846
859
|
st.shift = sh;
|
|
847
860
|
Object.assign(st, this.effectivePart(p));
|
|
861
|
+
st.capability = this.capabilityOfResource(p, p.personnelClassIds, this.classDefs.personnel);
|
|
848
862
|
return st;
|
|
849
863
|
}),
|
|
850
864
|
/* 스냅샷이 **델타보다 가난하면 안 된다** — 예전에는 소요·남은 시간을 빼고 내보내서, 이 스냅샷으로
|
|
@@ -1567,9 +1581,8 @@ export class FlowEngine {
|
|
|
1567
1581
|
continue;
|
|
1568
1582
|
const avail = [...this.assets.values()].filter(
|
|
1569
1583
|
/* 인원과 같은 규칙 — 상속을 타고 닫아 판정하고 유효기간 밖은 제외한다. 요구는 등급 하나+수량. */
|
|
1570
|
-
a => a.
|
|
1584
|
+
a => this.capabilityOfResource(a, a.assetClassIds, this.classDefs.asset).available &&
|
|
1571
1585
|
!picked.includes(a.id) &&
|
|
1572
|
-
!this.outOfEffect(a) && // 폐기한 팔레트는 풀에서 빠진다
|
|
1573
1586
|
(req.assetClass === undefined ||
|
|
1574
1587
|
classClosure(a.assetClassIds, this.classDefs.asset, this.now()).has(req.assetClass)));
|
|
1575
1588
|
if (avail.length < want)
|
|
@@ -1859,16 +1872,19 @@ export class FlowEngine {
|
|
|
1859
1872
|
*
|
|
1860
1873
|
* 상속을 타고 닫은 등급 전부의 요구를 모은다: 상위 등급이 요구하는 시험도 자격의 조건이다.
|
|
1861
1874
|
*/
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1875
|
+
/**
|
|
1876
|
+
* 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
|
|
1877
|
+
*
|
|
1878
|
+
* 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
|
|
1879
|
+
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(놀고 있는 것은 맞지만 쓸 수 있는 것은 아니다).
|
|
1880
|
+
* 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
|
|
1881
|
+
*/
|
|
1882
|
+
capabilityOfResource(r, directClassIds, defs) {
|
|
1883
|
+
return capabilityOf(r, {
|
|
1884
|
+
at: this.now(),
|
|
1885
|
+
utcOffsetMinutes: this.boardDef?.utcOffsetMinutes,
|
|
1886
|
+
requiredTests: requiredTestsFor(directClassIds, defs, this.now())
|
|
1887
|
+
});
|
|
1872
1888
|
}
|
|
1873
1889
|
claimPersonnel(t) {
|
|
1874
1890
|
const need = this.operationSpecs.get(t.kind)?.personnelSpecification;
|
|
@@ -1887,11 +1903,7 @@ export class FlowEngine {
|
|
|
1887
1903
|
*
|
|
1888
1904
|
* 요구된 시험은 등급에서 모아 넘긴다 — 등급 정의를 아는 것은 이쪽이다.
|
|
1889
1905
|
*/
|
|
1890
|
-
|
|
1891
|
-
at: this.now(),
|
|
1892
|
-
utcOffsetMinutes: this.boardDef?.utcOffsetMinutes,
|
|
1893
|
-
requiredTests: this.requiredTestsOf(p)
|
|
1894
|
-
}).available &&
|
|
1906
|
+
this.capabilityOfResource(p, p.personnelClassIds, this.classDefs.personnel).available &&
|
|
1895
1907
|
/* 자격은 **상속을 타고 닫아** 판정한다 — 사람은 여러 등급에 속할 수 있고(표준), 등급은
|
|
1896
1908
|
다른 등급을 상속한다. "생산직 2명" 요구를 "용접 자격자" 가 만족해야 한다. */
|
|
1897
1909
|
(req.personnelClass === undefined ||
|
|
@@ -1909,10 +1921,10 @@ export class FlowEngine {
|
|
|
1909
1921
|
* 여기서는 고르기만 한다 — 확정은 호출부가 다른 자원까지 확보한 뒤에 한다.
|
|
1910
1922
|
*/
|
|
1911
1923
|
claimEquipment(t) {
|
|
1912
|
-
const free = (kind, picked = []) => [...this.equipment.values()].filter(m =>
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1924
|
+
const free = (kind, picked = []) => [...this.equipment.values()].filter(m =>
|
|
1925
|
+
/* 사람과 **같은 판정**(§capabilityOfResource) — 예전에는 이 자리에 조건을 늘어놓아서
|
|
1926
|
+
설비의 검사 결과는 배정에서 아예 읽히지 않았다(사람만 자격을 봤다). */
|
|
1927
|
+
this.capabilityOfResource(m, m.kind ? [m.kind] : [], this.classDefs.equipment).available &&
|
|
1916
1928
|
!picked.includes(m.id) &&
|
|
1917
1929
|
/* 설비의 소속은 아직 단수(`kind`)다 — 저널에 기록이 쌓여 복수화를 별도 작업으로 두었다.
|
|
1918
1930
|
다만 등급 정의가 있으면 **상속은 지금도 탄다**: 'welder-6axis' 가 'welder' 를 상속하면
|
|
@@ -70,6 +70,13 @@ export declare class ObservedReducer {
|
|
|
70
70
|
* 갈라진다(적합성 하네스가 `mirrorOnly` 로 잡는다). 이것은 **판정의 입력**이고, 나가는 것은 판정뿐이다.
|
|
71
71
|
*/
|
|
72
72
|
private shifts;
|
|
73
|
+
/**
|
|
74
|
+
* 자원 **등급 정의** — 교대 선언과 같은 성격의 *판정 입력*이다(상태로 내보내지 않는다).
|
|
75
|
+
*
|
|
76
|
+
* 이것이 없으면 미러는 "이 사람에게 어떤 시험이 필수인가" 를 모른다. 시뮬은 막는데 미러는
|
|
77
|
+
* 통과시키는 어긋남이 생기고, 그 어긋남은 조용하다.
|
|
78
|
+
*/
|
|
79
|
+
private classDefs;
|
|
73
80
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
74
81
|
private utcOffsetMinutes?;
|
|
75
82
|
constructor(board: TwinModelDef);
|
|
@@ -164,5 +171,12 @@ export declare class ObservedReducer {
|
|
|
164
171
|
private offShiftPart;
|
|
165
172
|
/** 유효 기간 밖이면 그 이유를 붙인다 — 미러의 시각 기준은 `observedAtMs`(마지막으로 들은 시점). */
|
|
166
173
|
private effectivityPart;
|
|
174
|
+
/**
|
|
175
|
+
* **왜 지금 쓰일 수 없나** — 시뮬과 **같은 함수**로 낸다(`capabilityOf`).
|
|
176
|
+
*
|
|
177
|
+
* 델타로 받지 않고 여기서 파생하는 이유는 `effectivity`·`shift` 와 같다: 자격 만료와 교대 경계는
|
|
178
|
+
* **이벤트 없이 시각만으로** 넘어간다. 델타로 받아 두면 만료된 자격이 영원히 유효하게 남는다.
|
|
179
|
+
*/
|
|
180
|
+
private capabilityPart;
|
|
167
181
|
snapshot(): ProjectedState;
|
|
168
182
|
}
|
package/dist/observed-reducer.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* - 운영 델타(task/equipment/order.status) → tasks·equipment·orders (EPCIS 로 재구성 불가한 절반)
|
|
20
20
|
* 마스터(로케이션)는 board 초기화 + applyMaster 로 갱신(마스터 동기).
|
|
21
21
|
*/
|
|
22
|
-
import { OP_EVENT, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, effectivityAt, offCalendarAt, offCalendarReasonAt, activeShiftAt } from "./contract.js";
|
|
22
|
+
import { OP_EVENT, capabilityOf, requiredTestsFor, locationStatusOf, readBoardEquipment, readBoardLocations, readBoardAssets, effectivityAt, offCalendarAt, offCalendarReasonAt, activeShiftAt } from "./contract.js";
|
|
23
23
|
import { ILMD_ATTR, parseEpc } from "./epcis.js";
|
|
24
24
|
/**
|
|
25
25
|
* 투영이 들고 있는 물품 — **계약(ItemState)을 축소하지 않는다.**
|
|
@@ -62,18 +62,26 @@ export class ObservedReducer {
|
|
|
62
62
|
* 갈라진다(적합성 하네스가 `mirrorOnly` 로 잡는다). 이것은 **판정의 입력**이고, 나가는 것은 판정뿐이다.
|
|
63
63
|
*/
|
|
64
64
|
shifts = new Map();
|
|
65
|
+
/**
|
|
66
|
+
* 자원 **등급 정의** — 교대 선언과 같은 성격의 *판정 입력*이다(상태로 내보내지 않는다).
|
|
67
|
+
*
|
|
68
|
+
* 이것이 없으면 미러는 "이 사람에게 어떤 시험이 필수인가" 를 모른다. 시뮬은 막는데 미러는
|
|
69
|
+
* 통과시키는 어긋남이 생기고, 그 어긋남은 조용하다.
|
|
70
|
+
*/
|
|
71
|
+
classDefs = {};
|
|
65
72
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
66
73
|
utcOffsetMinutes;
|
|
67
74
|
constructor(board) {
|
|
68
75
|
this.utcOffsetMinutes = board.utcOffsetMinutes;
|
|
76
|
+
this.classDefs = { personnel: board.personnelClasses, equipment: board.equipmentClasses, asset: board.assetClasses };
|
|
69
77
|
for (const n of readBoardLocations(board))
|
|
70
78
|
this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: 'master' });
|
|
71
79
|
// 설비 기준선(마스터) — equipment.status 델타로 갱신됨.
|
|
72
80
|
for (const m of readBoardEquipment(board))
|
|
73
|
-
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...effectiveOf(m), origin: 'master' });
|
|
81
|
+
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), ...effectiveOf(m), origin: 'master' });
|
|
74
82
|
// 사람 기준선(마스터) — person.status 델타로 갱신됨.
|
|
75
83
|
for (const p of board.persons ?? [])
|
|
76
|
-
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}), ...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...effectiveOf(p) });
|
|
84
|
+
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}), ...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.testResults ? { testResults: p.testResults } : {}), ...effectiveOf(p) });
|
|
77
85
|
/* 교대 선언은 **미러도 들고 있어야 한다** — 설비 델타에는 `offShift` 자리조차 없어서, 그것 없이는
|
|
78
86
|
미러의 설비가 점심 휴게 중에도 "대기" 로 보였다. */
|
|
79
87
|
for (const m of readBoardEquipment(board))
|
|
@@ -81,7 +89,7 @@ export class ObservedReducer {
|
|
|
81
89
|
for (const p of board.persons ?? [])
|
|
82
90
|
this.rememberShift(`person:${p.id}`, p);
|
|
83
91
|
for (const a of readBoardAssets(board))
|
|
84
|
-
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', ...(a.properties ? { properties: a.properties } : {}), ...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...effectiveOf(a) });
|
|
92
|
+
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', ...(a.properties ? { properties: a.properties } : {}), ...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.testResults ? { testResults: a.testResults } : {}), ...effectiveOf(a) });
|
|
85
93
|
}
|
|
86
94
|
/**
|
|
87
95
|
* **구조를 갈아탄다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
@@ -138,17 +146,17 @@ export class ObservedReducer {
|
|
|
138
146
|
const assetsDropped = dropMissing(this.assets, assets);
|
|
139
147
|
for (const m of equipment) {
|
|
140
148
|
if (!this.equipment.has(m.id))
|
|
141
|
-
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...effectiveOf(m), origin: 'master' });
|
|
149
|
+
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: 'idle', location: m.homeLocation, homeLocation: m.homeLocation, ...(m.properties ? { properties: m.properties } : {}), ...(m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}), ...(m.testResults ? { testResults: m.testResults } : {}), ...effectiveOf(m), origin: 'master' });
|
|
142
150
|
this.rememberShift(`eq:${m.id}`, m);
|
|
143
151
|
}
|
|
144
152
|
for (const p of persons) {
|
|
145
153
|
if (!this.persons.has(p.id))
|
|
146
|
-
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}), ...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...effectiveOf(p) });
|
|
154
|
+
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: 'idle', ...(p.homeLocation ? { location: p.homeLocation } : {}), ...(p.properties ? { properties: p.properties } : {}), ...(p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.testResults ? { testResults: p.testResults } : {}), ...effectiveOf(p) });
|
|
147
155
|
this.rememberShift(`person:${p.id}`, p);
|
|
148
156
|
}
|
|
149
157
|
for (const a of assets)
|
|
150
158
|
if (!this.assets.has(a.id))
|
|
151
|
-
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', ...(a.properties ? { properties: a.properties } : {}), ...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...effectiveOf(a) });
|
|
159
|
+
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: 'idle', ...(a.properties ? { properties: a.properties } : {}), ...(a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.testResults ? { testResults: a.testResults } : {}), ...effectiveOf(a) });
|
|
152
160
|
return { locationsAdded, locationsDropped, equipmentDropped, personsDropped, assetsDropped };
|
|
153
161
|
}
|
|
154
162
|
/** 마스터 동기 — 로케이션 추가/변경/제거. */
|
|
@@ -571,6 +579,19 @@ export class ObservedReducer {
|
|
|
571
579
|
const e = effectivityAt(r, new Date(this.observedAtMs).toISOString());
|
|
572
580
|
return e ? { effectivity: e } : {};
|
|
573
581
|
}
|
|
582
|
+
/**
|
|
583
|
+
* **왜 지금 쓰일 수 없나** — 시뮬과 **같은 함수**로 낸다(`capabilityOf`).
|
|
584
|
+
*
|
|
585
|
+
* 델타로 받지 않고 여기서 파생하는 이유는 `effectivity`·`shift` 와 같다: 자격 만료와 교대 경계는
|
|
586
|
+
* **이벤트 없이 시각만으로** 넘어간다. 델타로 받아 두면 만료된 자격이 영원히 유효하게 남는다.
|
|
587
|
+
*/
|
|
588
|
+
capabilityPart(r, key, directClassIds, defs) {
|
|
589
|
+
const at = this.observedAtMs === undefined ? undefined : new Date(this.observedAtMs).toISOString();
|
|
590
|
+
const decl = this.shifts.get(key);
|
|
591
|
+
return {
|
|
592
|
+
capability: capabilityOf({ ...r, ...(decl?.window ? { window: decl.window } : {}), ...(decl?.workCalendar ? { workCalendar: decl.workCalendar } : {}) }, { at, utcOffsetMinutes: this.utcOffsetMinutes, requiredTests: requiredTestsFor(directClassIds, defs, at) })
|
|
593
|
+
};
|
|
594
|
+
}
|
|
574
595
|
snapshot() {
|
|
575
596
|
const occ = new Map();
|
|
576
597
|
for (const it of this.items.values())
|
|
@@ -595,10 +616,10 @@ export class ObservedReducer {
|
|
|
595
616
|
items: [...this.items.values()].map(i => ({ ...i })),
|
|
596
617
|
/* 유효 기간 판정은 **저장하지 않고 여기서 낸다** — 시뮬과 **같은 함수**(`effectivityAt`)를 부른다.
|
|
597
618
|
만료는 이벤트 없이 시각만으로 일어나므로, 델타로 받아 두면 유휴 자원이 영원히 유효하게 남는다. */
|
|
598
|
-
persons: [...this.persons.values()].map(p => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`) })),
|
|
599
|
-
assets: [...this.assets.values()].map(a => ({ ...a, ...this.effectivityPart(a) })),
|
|
619
|
+
persons: [...this.persons.values()].map(p => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`), ...this.capabilityPart(p, `person:${p.id}`, p.personnelClassIds, this.classDefs.personnel) })),
|
|
620
|
+
assets: [...this.assets.values()].map(a => ({ ...a, ...this.effectivityPart(a), ...this.capabilityPart(a, `asset:${a.id}`, a.assetClassIds, this.classDefs.asset) })),
|
|
600
621
|
tasks: [...this.tasks.values()].map(t => ({ ...t })),
|
|
601
|
-
equipment: [...this.equipment.values()].map(m => ({ ...m, ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`) })),
|
|
622
|
+
equipment: [...this.equipment.values()].map(m => ({ ...m, ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`), ...this.capabilityPart(m, `eq:${m.id}`, m.kind ? [m.kind] : [], this.classDefs.equipment) })),
|
|
602
623
|
orders: [...this.orders.values()].map(o => ({ ...o })),
|
|
603
624
|
acked: [...this.acked]
|
|
604
625
|
};
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -119,6 +119,7 @@ __export(index_exports, {
|
|
|
119
119
|
relationsTo: () => relationsTo,
|
|
120
120
|
replay: () => replay,
|
|
121
121
|
replaySegments: () => replaySegments,
|
|
122
|
+
requiredTestsFor: () => requiredTestsFor,
|
|
122
123
|
retiredVocabularyIn: () => retiredVocabularyIn,
|
|
123
124
|
sgtinClass: () => sgtinClass,
|
|
124
125
|
sgtinUri: () => sgtinUri,
|
|
@@ -410,6 +411,13 @@ function offCalendarAt(r, ms2, utcOffsetMinutes) {
|
|
|
410
411
|
const h = Math.floor(minute / 60);
|
|
411
412
|
return !(w.startHour <= w.endHour ? h >= w.startHour && h < w.endHour : h >= w.startHour || h < w.endHour);
|
|
412
413
|
}
|
|
414
|
+
function requiredTestsFor(directIds, defs, at) {
|
|
415
|
+
if (!defs?.length) return [];
|
|
416
|
+
const closure = classClosure(directIds, defs, at);
|
|
417
|
+
const required = [];
|
|
418
|
+
for (const d of defs) if (closure.has(d.id)) required.push(...d.testSpecificationIds ?? []);
|
|
419
|
+
return required;
|
|
420
|
+
}
|
|
413
421
|
function capabilityOf(r, ctx) {
|
|
414
422
|
const at = ctx?.at;
|
|
415
423
|
const eff = effectivityAt(r, at);
|
|
@@ -975,16 +983,24 @@ var ObservedReducer = class {
|
|
|
975
983
|
* 갈라진다(적합성 하네스가 `mirrorOnly` 로 잡는다). 이것은 **판정의 입력**이고, 나가는 것은 판정뿐이다.
|
|
976
984
|
*/
|
|
977
985
|
shifts = /* @__PURE__ */ new Map();
|
|
986
|
+
/**
|
|
987
|
+
* 자원 **등급 정의** — 교대 선언과 같은 성격의 *판정 입력*이다(상태로 내보내지 않는다).
|
|
988
|
+
*
|
|
989
|
+
* 이것이 없으면 미러는 "이 사람에게 어떤 시험이 필수인가" 를 모른다. 시뮬은 막는데 미러는
|
|
990
|
+
* 통과시키는 어긋남이 생기고, 그 어긋남은 조용하다.
|
|
991
|
+
*/
|
|
992
|
+
classDefs = {};
|
|
978
993
|
/** 시각 해석 기준(보드 선언) — 없으면 UTC. 캘린더의 `HH:MM` 이 어느 기준인지 정한다. */
|
|
979
994
|
utcOffsetMinutes;
|
|
980
995
|
constructor(board) {
|
|
981
996
|
this.utcOffsetMinutes = board.utcOffsetMinutes;
|
|
997
|
+
this.classDefs = { personnel: board.personnelClasses, equipment: board.equipmentClasses, asset: board.assetClasses };
|
|
982
998
|
for (const n of readBoardLocations(board)) this.master.set(n.id, { id: n.id, type: n.type, capacity: n.capacity, parallelism: n.parallelism, parentId: n.parentId, origin: "master" });
|
|
983
|
-
for (const m of readBoardEquipment(board)) this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...effectiveOf(m), origin: "master" });
|
|
984
|
-
for (const p of board.persons ?? []) this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...effectiveOf(p) });
|
|
999
|
+
for (const m of readBoardEquipment(board)) this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, ...effectiveOf(m), origin: "master" });
|
|
1000
|
+
for (const p of board.persons ?? []) this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...p.testResults ? { testResults: p.testResults } : {}, ...effectiveOf(p) });
|
|
985
1001
|
for (const m of readBoardEquipment(board)) this.rememberShift(`eq:${m.id}`, m);
|
|
986
1002
|
for (const p of board.persons ?? []) this.rememberShift(`person:${p.id}`, p);
|
|
987
|
-
for (const a of readBoardAssets(board)) this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: "idle", ...a.properties ? { properties: a.properties } : {}, ...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}, ...effectiveOf(a) });
|
|
1003
|
+
for (const a of readBoardAssets(board)) this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: "idle", ...a.properties ? { properties: a.properties } : {}, ...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}, ...a.testResults ? { testResults: a.testResults } : {}, ...effectiveOf(a) });
|
|
988
1004
|
}
|
|
989
1005
|
/**
|
|
990
1006
|
* **구조를 갈아탄다** — 관측된 사실은 지키고 토폴로지만 새 선언으로 바꾼다.
|
|
@@ -1036,17 +1052,17 @@ var ObservedReducer = class {
|
|
|
1036
1052
|
const assetsDropped = dropMissing(this.assets, assets);
|
|
1037
1053
|
for (const m of equipment) {
|
|
1038
1054
|
if (!this.equipment.has(m.id))
|
|
1039
|
-
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...effectiveOf(m), origin: "master" });
|
|
1055
|
+
this.equipment.set(m.id, { id: m.id, kind: m.kind, status: "idle", location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, ...effectiveOf(m), origin: "master" });
|
|
1040
1056
|
this.rememberShift(`eq:${m.id}`, m);
|
|
1041
1057
|
}
|
|
1042
1058
|
for (const p of persons) {
|
|
1043
1059
|
if (!this.persons.has(p.id))
|
|
1044
|
-
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...effectiveOf(p) });
|
|
1060
|
+
this.persons.set(p.id, { id: p.id, personnelClassIds: p.personnelClassIds, status: "idle", ...p.homeLocation ? { location: p.homeLocation } : {}, ...p.properties ? { properties: p.properties } : {}, ...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {}, ...p.testResults ? { testResults: p.testResults } : {}, ...effectiveOf(p) });
|
|
1045
1061
|
this.rememberShift(`person:${p.id}`, p);
|
|
1046
1062
|
}
|
|
1047
1063
|
for (const a of assets)
|
|
1048
1064
|
if (!this.assets.has(a.id))
|
|
1049
|
-
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: "idle", ...a.properties ? { properties: a.properties } : {}, ...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}, ...effectiveOf(a) });
|
|
1065
|
+
this.assets.set(a.id, { id: a.id, assetClassIds: a.assetClassIds, location: a.homeLocation, status: "idle", ...a.properties ? { properties: a.properties } : {}, ...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {}, ...a.testResults ? { testResults: a.testResults } : {}, ...effectiveOf(a) });
|
|
1050
1066
|
return { locationsAdded, locationsDropped, equipmentDropped, personsDropped, assetsDropped };
|
|
1051
1067
|
}
|
|
1052
1068
|
/** 마스터 동기 — 로케이션 추가/변경/제거. */
|
|
@@ -1399,6 +1415,22 @@ var ObservedReducer = class {
|
|
|
1399
1415
|
const e = effectivityAt(r, new Date(this.observedAtMs).toISOString());
|
|
1400
1416
|
return e ? { effectivity: e } : {};
|
|
1401
1417
|
}
|
|
1418
|
+
/**
|
|
1419
|
+
* **왜 지금 쓰일 수 없나** — 시뮬과 **같은 함수**로 낸다(`capabilityOf`).
|
|
1420
|
+
*
|
|
1421
|
+
* 델타로 받지 않고 여기서 파생하는 이유는 `effectivity`·`shift` 와 같다: 자격 만료와 교대 경계는
|
|
1422
|
+
* **이벤트 없이 시각만으로** 넘어간다. 델타로 받아 두면 만료된 자격이 영원히 유효하게 남는다.
|
|
1423
|
+
*/
|
|
1424
|
+
capabilityPart(r, key, directClassIds, defs) {
|
|
1425
|
+
const at = this.observedAtMs === void 0 ? void 0 : new Date(this.observedAtMs).toISOString();
|
|
1426
|
+
const decl = this.shifts.get(key);
|
|
1427
|
+
return {
|
|
1428
|
+
capability: capabilityOf(
|
|
1429
|
+
{ ...r, ...decl?.window ? { window: decl.window } : {}, ...decl?.workCalendar ? { workCalendar: decl.workCalendar } : {} },
|
|
1430
|
+
{ at, utcOffsetMinutes: this.utcOffsetMinutes, requiredTests: requiredTestsFor(directClassIds, defs, at) }
|
|
1431
|
+
)
|
|
1432
|
+
};
|
|
1433
|
+
}
|
|
1402
1434
|
snapshot() {
|
|
1403
1435
|
const occ = /* @__PURE__ */ new Map();
|
|
1404
1436
|
for (const it of this.items.values()) occ.set(it.location, (occ.get(it.location) ?? 0) + 1);
|
|
@@ -1424,10 +1456,10 @@ var ObservedReducer = class {
|
|
|
1424
1456
|
items: [...this.items.values()].map((i) => ({ ...i })),
|
|
1425
1457
|
/* 유효 기간 판정은 **저장하지 않고 여기서 낸다** — 시뮬과 **같은 함수**(`effectivityAt`)를 부른다.
|
|
1426
1458
|
만료는 이벤트 없이 시각만으로 일어나므로, 델타로 받아 두면 유휴 자원이 영원히 유효하게 남는다. */
|
|
1427
|
-
persons: [...this.persons.values()].map((p) => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`) })),
|
|
1428
|
-
assets: [...this.assets.values()].map((a) => ({ ...a, ...this.effectivityPart(a) })),
|
|
1459
|
+
persons: [...this.persons.values()].map((p) => ({ ...p, ...this.effectivityPart(p), ...this.offShiftPart(`person:${p.id}`), ...this.capabilityPart(p, `person:${p.id}`, p.personnelClassIds, this.classDefs.personnel) })),
|
|
1460
|
+
assets: [...this.assets.values()].map((a) => ({ ...a, ...this.effectivityPart(a), ...this.capabilityPart(a, `asset:${a.id}`, a.assetClassIds, this.classDefs.asset) })),
|
|
1429
1461
|
tasks: [...this.tasks.values()].map((t) => ({ ...t })),
|
|
1430
|
-
equipment: [...this.equipment.values()].map((m) => ({ ...m, ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`) })),
|
|
1462
|
+
equipment: [...this.equipment.values()].map((m) => ({ ...m, ...this.effectivityPart(m), ...this.offShiftPart(`eq:${m.id}`), ...this.capabilityPart(m, `eq:${m.id}`, m.kind ? [m.kind] : [], this.classDefs.equipment) })),
|
|
1431
1463
|
orders: [...this.orders.values()].map((o) => ({ ...o })),
|
|
1432
1464
|
acked: [...this.acked]
|
|
1433
1465
|
};
|
|
@@ -2441,7 +2473,6 @@ var FlowEngine = class {
|
|
|
2441
2473
|
...p.workCalendar ? { workCalendar: p.workCalendar } : {},
|
|
2442
2474
|
...p.homeLocation ? { location: p.homeLocation } : {},
|
|
2443
2475
|
...p.properties ? { properties: p.properties } : {},
|
|
2444
|
-
...p.testResults ? { testResults: p.testResults } : {},
|
|
2445
2476
|
...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {},
|
|
2446
2477
|
...p.testResults ? { testResults: p.testResults } : {},
|
|
2447
2478
|
...effectiveOnly(p)
|
|
@@ -2455,7 +2486,6 @@ var FlowEngine = class {
|
|
|
2455
2486
|
status: "idle",
|
|
2456
2487
|
taskId: null,
|
|
2457
2488
|
...a.properties ? { properties: a.properties } : {},
|
|
2458
|
-
...a.testResults ? { testResults: a.testResults } : {},
|
|
2459
2489
|
...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {},
|
|
2460
2490
|
...a.testResults ? { testResults: a.testResults } : {},
|
|
2461
2491
|
...effectiveOnly(a)
|
|
@@ -2525,7 +2555,7 @@ var FlowEngine = class {
|
|
|
2525
2555
|
};
|
|
2526
2556
|
}
|
|
2527
2557
|
buildEquipment(m) {
|
|
2528
|
-
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0, window: m.window, ...m.workCalendar ? { workCalendar: m.workCalendar } : {}, ...effectiveOnly(m) };
|
|
2558
|
+
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0, window: m.window, ...m.workCalendar ? { workCalendar: m.workCalendar } : {}, ...effectiveOnly(m) };
|
|
2529
2559
|
if (m.mtbfMs !== void 0) {
|
|
2530
2560
|
eq.mtbfMs = m.mtbfMs;
|
|
2531
2561
|
eq.mttrMs = m.mttrMs;
|
|
@@ -2539,7 +2569,7 @@ var FlowEngine = class {
|
|
|
2539
2569
|
*/
|
|
2540
2570
|
addEquipment(m) {
|
|
2541
2571
|
if (this.equipment.has(m.id)) return;
|
|
2542
|
-
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
2572
|
+
const eq = { id: m.id, kind: m.kind, location: m.homeLocation, homeLocation: m.homeLocation, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, status: "idle", taskId: null, runMs: 0, setupMs: 0, downMs: 0, goodCount: 0, scrapCount: 0 };
|
|
2543
2573
|
if (m.mtbfMs !== void 0) {
|
|
2544
2574
|
eq.mtbfMs = m.mtbfMs;
|
|
2545
2575
|
eq.mttrMs = m.mttrMs;
|
|
@@ -2615,6 +2645,8 @@ var FlowEngine = class {
|
|
|
2615
2645
|
/* 관측 스냅샷이 들고 있는 것은 관측을 따른다(미러가 보드에서 읽어 실어 온다). */
|
|
2616
2646
|
...m.properties ? { properties: m.properties } : {},
|
|
2617
2647
|
...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {},
|
|
2648
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
2649
|
+
...m.testResults ? { testResults: m.testResults } : {},
|
|
2618
2650
|
...effectiveOnly(m)
|
|
2619
2651
|
});
|
|
2620
2652
|
}
|
|
@@ -2629,6 +2661,8 @@ var FlowEngine = class {
|
|
|
2629
2661
|
...p.location ? { location: p.location } : {},
|
|
2630
2662
|
...p.properties ? { properties: p.properties } : {},
|
|
2631
2663
|
...p.testSpecificationIds ? { testSpecificationIds: p.testSpecificationIds } : {},
|
|
2664
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
2665
|
+
...p.testResults ? { testResults: p.testResults } : {},
|
|
2632
2666
|
...effectiveOnly(p)
|
|
2633
2667
|
});
|
|
2634
2668
|
}
|
|
@@ -2644,6 +2678,8 @@ var FlowEngine = class {
|
|
|
2644
2678
|
carrying: a.carrying,
|
|
2645
2679
|
...a.properties ? { properties: a.properties } : {},
|
|
2646
2680
|
...a.testSpecificationIds ? { testSpecificationIds: a.testSpecificationIds } : {},
|
|
2681
|
+
/* 결과도 이어받는다 — 잃으면 예측이 **자격 만료를 모르는 현장**에서 출발한다(낙관 쪽으로 치우친다). */
|
|
2682
|
+
...a.testResults ? { testResults: a.testResults } : {},
|
|
2647
2683
|
...effectiveOnly(a)
|
|
2648
2684
|
});
|
|
2649
2685
|
}
|
|
@@ -2929,7 +2965,7 @@ var FlowEngine = class {
|
|
|
2929
2965
|
}),
|
|
2930
2966
|
items: [...this.items.values()].map((i) => this.itemState(i)),
|
|
2931
2967
|
equipment: [...this.equipment.values()].map((m) => {
|
|
2932
|
-
const s = { id: m.id, kind: m.kind, location: m.location, ...m.homeLocation ? { homeLocation: m.homeLocation } : {}, status: m.status, taskId: m.taskId ?? void 0, oee: this.oeeOf(m), held: m.held, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, origin: "master", ...this.offShift(m) ? { offShift: true, ...this.offReason(m) } : {}, ...this.effectivePart(m), ...this.shiftOf(m) ? { shift: this.shiftOf(m) } : {} };
|
|
2968
|
+
const s = { id: m.id, kind: m.kind, location: m.location, ...m.homeLocation ? { homeLocation: m.homeLocation } : {}, status: m.status, taskId: m.taskId ?? void 0, oee: this.oeeOf(m), held: m.held, ...m.properties ? { properties: m.properties } : {}, ...m.testSpecificationIds ? { testSpecificationIds: m.testSpecificationIds } : {}, ...m.testResults ? { testResults: m.testResults } : {}, capability: this.capabilityOfResource(m, m.kind ? [m.kind] : [], this.classDefs.equipment), origin: "master", ...this.offShift(m) ? { offShift: true, ...this.offReason(m) } : {}, ...this.effectivePart(m), ...this.shiftOf(m) ? { shift: this.shiftOf(m) } : {} };
|
|
2933
2969
|
const t = m.taskId ? this.tasks.get(m.taskId) : void 0;
|
|
2934
2970
|
if (t && t.status === "in-progress" && t.intent !== "process") s.motion = { fromNode: t.fromNode, toNode: t.toNode, startedAtSimMs: this.clockMs - (t.durationMs - t.remainingMs), durationMs: t.durationMs, progress: this.progressOf(t), elapsedMs: t.durationMs - t.remainingMs };
|
|
2935
2971
|
return s;
|
|
@@ -2939,7 +2975,9 @@ var FlowEngine = class {
|
|
|
2939
2975
|
if (a.carrying) st.carrying = a.carrying;
|
|
2940
2976
|
if (a.properties) st.properties = a.properties;
|
|
2941
2977
|
if (a.testSpecificationIds) st.testSpecificationIds = a.testSpecificationIds;
|
|
2978
|
+
if (a.testResults) st.testResults = a.testResults;
|
|
2942
2979
|
Object.assign(st, this.effectivePart(a));
|
|
2980
|
+
st.capability = this.capabilityOfResource(a, a.assetClassIds, this.classDefs.asset);
|
|
2943
2981
|
return st;
|
|
2944
2982
|
}),
|
|
2945
2983
|
persons: [...this.persons.values()].map((p) => {
|
|
@@ -2947,6 +2985,7 @@ var FlowEngine = class {
|
|
|
2947
2985
|
if (p.location) st.location = p.location;
|
|
2948
2986
|
if (p.properties) st.properties = p.properties;
|
|
2949
2987
|
if (p.testSpecificationIds) st.testSpecificationIds = p.testSpecificationIds;
|
|
2988
|
+
if (p.testResults) st.testResults = p.testResults;
|
|
2950
2989
|
if (this.personOffShift(p)) {
|
|
2951
2990
|
st.offShift = true;
|
|
2952
2991
|
Object.assign(st, this.offReason(p));
|
|
@@ -2954,6 +2993,7 @@ var FlowEngine = class {
|
|
|
2954
2993
|
const sh = this.shiftOf(p);
|
|
2955
2994
|
if (sh) st.shift = sh;
|
|
2956
2995
|
Object.assign(st, this.effectivePart(p));
|
|
2996
|
+
st.capability = this.capabilityOfResource(p, p.personnelClassIds, this.classDefs.personnel);
|
|
2957
2997
|
return st;
|
|
2958
2998
|
}),
|
|
2959
2999
|
/* 스냅샷이 **델타보다 가난하면 안 된다** — 예전에는 소요·남은 시간을 빼고 내보내서, 이 스냅샷으로
|
|
@@ -3623,8 +3663,7 @@ var FlowEngine = class {
|
|
|
3623
3663
|
if (!want) continue;
|
|
3624
3664
|
const avail = [...this.assets.values()].filter(
|
|
3625
3665
|
/* 인원과 같은 규칙 — 상속을 타고 닫아 판정하고 유효기간 밖은 제외한다. 요구는 등급 하나+수량. */
|
|
3626
|
-
(a) => a.
|
|
3627
|
-
(req.assetClass === void 0 || classClosure(a.assetClassIds, this.classDefs.asset, this.now()).has(req.assetClass))
|
|
3666
|
+
(a) => this.capabilityOfResource(a, a.assetClassIds, this.classDefs.asset).available && !picked.includes(a.id) && (req.assetClass === void 0 || classClosure(a.assetClassIds, this.classDefs.asset, this.now()).has(req.assetClass))
|
|
3628
3667
|
);
|
|
3629
3668
|
if (avail.length < want) return null;
|
|
3630
3669
|
for (let i = 0; i < want; i++) picked.push(avail[i].id);
|
|
@@ -3875,13 +3914,19 @@ var FlowEngine = class {
|
|
|
3875
3914
|
*
|
|
3876
3915
|
* 상속을 타고 닫은 등급 전부의 요구를 모은다: 상위 등급이 요구하는 시험도 자격의 조건이다.
|
|
3877
3916
|
*/
|
|
3878
|
-
|
|
3879
|
-
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3917
|
+
/**
|
|
3918
|
+
* 자원 하나의 **가용 능력** — 계약의 판정에 이 커널의 시각·시간대·필수 시험을 채워 넘긴다.
|
|
3919
|
+
*
|
|
3920
|
+
* 배정도 스냅샷도 이 자리를 지난다. 예전에는 배정이 조건을 늘어놓고 화면이 플래그를 보고 짐작해서,
|
|
3921
|
+
* **자격이 만료된 사람이 화면에서는 `대기` 로 보였다**(놀고 있는 것은 맞지만 쓸 수 있는 것은 아니다).
|
|
3922
|
+
* 판정에 필요한 둘(시각·등급 상속)을 아는 것은 커널뿐이므로, 답도 커널이 낸다.
|
|
3923
|
+
*/
|
|
3924
|
+
capabilityOfResource(r, directClassIds, defs) {
|
|
3925
|
+
return capabilityOf(r, {
|
|
3926
|
+
at: this.now(),
|
|
3927
|
+
utcOffsetMinutes: this.boardDef?.utcOffsetMinutes,
|
|
3928
|
+
requiredTests: requiredTestsFor(directClassIds, defs, this.now())
|
|
3929
|
+
});
|
|
3885
3930
|
}
|
|
3886
3931
|
claimPersonnel(t) {
|
|
3887
3932
|
const need = this.operationSpecs.get(t.kind)?.personnelSpecification;
|
|
@@ -3898,11 +3943,7 @@ var FlowEngine = class {
|
|
|
3898
3943
|
*
|
|
3899
3944
|
* 요구된 시험은 등급에서 모아 넘긴다 — 등급 정의를 아는 것은 이쪽이다.
|
|
3900
3945
|
*/
|
|
3901
|
-
|
|
3902
|
-
at: this.now(),
|
|
3903
|
-
utcOffsetMinutes: this.boardDef?.utcOffsetMinutes,
|
|
3904
|
-
requiredTests: this.requiredTestsOf(p)
|
|
3905
|
-
}).available && /* 자격은 **상속을 타고 닫아** 판정한다 — 사람은 여러 등급에 속할 수 있고(표준), 등급은
|
|
3946
|
+
this.capabilityOfResource(p, p.personnelClassIds, this.classDefs.personnel).available && /* 자격은 **상속을 타고 닫아** 판정한다 — 사람은 여러 등급에 속할 수 있고(표준), 등급은
|
|
3906
3947
|
다른 등급을 상속한다. "생산직 2명" 요구를 "용접 자격자" 가 만족해야 한다. */
|
|
3907
3948
|
(req.personnelClass === void 0 || classClosure(p.personnelClassIds, this.classDefs.personnel, this.now()).has(req.personnelClass))
|
|
3908
3949
|
);
|
|
@@ -3918,11 +3959,14 @@ var FlowEngine = class {
|
|
|
3918
3959
|
*/
|
|
3919
3960
|
claimEquipment(t) {
|
|
3920
3961
|
const free = (kind, picked2 = []) => [...this.equipment.values()].filter(
|
|
3921
|
-
(m) =>
|
|
3922
|
-
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3962
|
+
(m) => (
|
|
3963
|
+
/* 사람과 **같은 판정**(§capabilityOfResource) — 예전에는 이 자리에 조건을 늘어놓아서
|
|
3964
|
+
설비의 검사 결과는 배정에서 아예 읽히지 않았다(사람만 자격을 봤다). */
|
|
3965
|
+
this.capabilityOfResource(m, m.kind ? [m.kind] : [], this.classDefs.equipment).available && !picked2.includes(m.id) && /* 설비의 소속은 아직 단수(`kind`)다 — 저널에 기록이 쌓여 복수화를 별도 작업으로 두었다.
|
|
3966
|
+
다만 등급 정의가 있으면 **상속은 지금도 탄다**: 'welder-6axis' 가 'welder' 를 상속하면
|
|
3967
|
+
'welder' 요구를 만족한다. 단수/복수와 상속은 다른 축이므로 하나를 기다리지 않는다. */
|
|
3968
|
+
(kind === void 0 || classClosure(m.kind ? [m.kind] : [], this.classDefs.equipment, this.now()).has(kind))
|
|
3969
|
+
)
|
|
3926
3970
|
);
|
|
3927
3971
|
const need = this.operationSpecs.get(t.kind)?.equipmentSpecification;
|
|
3928
3972
|
if (!need?.length) {
|
|
@@ -5232,6 +5276,7 @@ function retiredVocabularyIn(line) {
|
|
|
5232
5276
|
relationsTo,
|
|
5233
5277
|
replay,
|
|
5234
5278
|
replaySegments,
|
|
5279
|
+
requiredTestsFor,
|
|
5235
5280
|
retiredVocabularyIn,
|
|
5236
5281
|
sgtinClass,
|
|
5237
5282
|
sgtinUri,
|
package/package.json
CHANGED