@operato/twin-kernel 0.7.23 → 0.7.25
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 +41 -0
- package/dist/ems-kernel.d.ts +23 -1
- package/dist/ems-kernel.js +32 -0
- package/dist/flow-engine.d.ts +29 -1
- package/dist/flow-engine.js +32 -2
- package/dist-cjs/index.cjs +48 -2
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -131,6 +131,26 @@ levelOfType?: (type: string) => EquipmentLevel | undefined): Hierarchy;
|
|
|
131
131
|
* 왜 이제 계약에 넣는가: 지금까지 설비 속성(`speed`)이 **계약 밖으로 흘러** 마스터에서 호스트
|
|
132
132
|
* 추정기까지 `any` 로 전달됐다. 자원에 붙는 사실이 계약에 자리가 없으면 소비처마다 다르게 읽는다.
|
|
133
133
|
*/
|
|
134
|
+
/**
|
|
135
|
+
* 이 속성 값을 **누가 말했나** — 근거를 값과 함께 나르는 규율의 속성 판(`basis`·`derived` 와 같은 결).
|
|
136
|
+
*
|
|
137
|
+
* ── 두 낱말이 한 축이다 (2026-08-18 확정) ───────────────────────────────────
|
|
138
|
+
* · `reference` — **외부 원천 시스템**이 준 값이다(SCADA·WMS·MES 의 마스터). 우리가 고르지 않았다.
|
|
139
|
+
* · `local` — **이 트윈 쪽에서 정한 값**이다. 원천이 모르는 수(계약전력·요금 단가·축전지 충전율)를
|
|
140
|
+
* 운영 주체가 넣은 것이고, 사람이 넣었든 대리로 AI 가 채웠든 같다 — 축은 「누가 입력
|
|
141
|
+
* 했나」가 아니라 **「권위가 어디 있나」**다.
|
|
142
|
+
*
|
|
143
|
+
* ── 장소 낱말과 섞지 않는다 ────────────────────────────────────────────────
|
|
144
|
+
* 예전 표식은 `'site'` 였다. 그런데 이 코드에서 `site` 는 **외부 시스템이 부르는 사업장**을 뜻한다
|
|
145
|
+
* (SAP 는 `P3`, Oracle 은 `W01` 로 같은 현장을 부른다). 그래서 「원천이 모르는 값」에 `site` 를 달면
|
|
146
|
+
* 이름이 내용과 정반대가 됐다 — 내가 쓴 편지를 「받은 편지함」에 넣어 둔 셈이다. `space`(트윈이 서는
|
|
147
|
+
* 그릇)도 답이 아니다: 그것은 **어디**에 답하는 낱말이고, 이 축은 **누가 말했나**에 답한다.
|
|
148
|
+
*
|
|
149
|
+
* ── 표식이 없으면 ──────────────────────────────────────────────────────────
|
|
150
|
+
* **모델을 그린 쪽이 말한 값**이다 — 참조에서 그린 모델이면 원천이고, 템플릿으로 만든 트윈이면 그
|
|
151
|
+
* 템플릿이다. 즉 「모른다」가 아니라 「원천 쪽」이다. 표식은 그 기본값과 다를 때 달린다.
|
|
152
|
+
*/
|
|
153
|
+
export type PropertySource = 'reference' | 'local';
|
|
134
154
|
export interface ResourceProperty {
|
|
135
155
|
/** 속성 식별자 — 표준 `ID`. 어휘는 표준이 정하지 않으므로 우리가 한 곳에서 정한다 — 그 자리는 읽는 쪽에 있다
|
|
136
156
|
* (headless-twin `EQUIPMENT_PROPERTY`·`PROPERTY_EFFECTS`). 커널은 속성을 **나르기만** 한다. */
|
|
@@ -146,6 +166,14 @@ export interface ResourceProperty {
|
|
|
146
166
|
children?: ResourceProperty[];
|
|
147
167
|
/** 이 개체 속성이 구체화하는 **등급 속성** — 표준 `<X>ClassPropertyID`. */
|
|
148
168
|
classPropertyId?: string;
|
|
169
|
+
/**
|
|
170
|
+
* 이 값을 누가 말했나(`PropertySource`) — 없으면 모델을 그린 쪽이다(대개 원천).
|
|
171
|
+
*
|
|
172
|
+
* 커널은 이 표식을 **읽지 않는다**(속성의 뜻은 `id`·`dataType`·`uom` 이 정한다). 나르기만 한다 —
|
|
173
|
+
* 계약에 선언해 두는 이유는, 예전에 이 표식이 **계약에 없는 필드로 얹혀 다녔기** 때문이다. 그러면
|
|
174
|
+
* 소비처가 `any` 로 읽고, 상태에 정말 실리는지 아무도 검사할 수 없다.
|
|
175
|
+
*/
|
|
176
|
+
source?: PropertySource;
|
|
149
177
|
}
|
|
150
178
|
/**
|
|
151
179
|
* 자격·적격을 **검증한 시험 명세**들 — 표준 `TestSpecificationID`(`maxOccurs="unbounded"`).
|
|
@@ -1376,6 +1404,19 @@ export interface StateSnapshot {
|
|
|
1376
1404
|
* 왕복시켜야 재기동·재계산에서 확인 상태가 유지된다.
|
|
1377
1405
|
*/
|
|
1378
1406
|
acked?: string[];
|
|
1407
|
+
/**
|
|
1408
|
+
* 주목 신호가 **처음 성립한 시각**(id → ISO) — 확인과 마찬가지로 **계산으로 되살릴 수 없다.**
|
|
1409
|
+
*
|
|
1410
|
+
* 신호는 매 스냅샷 다시 계산되지만 「언제부터인가」는 그 계산 안에 없다. 왕복시키지 않으면 재기동·
|
|
1411
|
+
* 웜스타트 뒤 **세 시간째 지속된 조건이 「0초째」로 되살아난다** — 방금 찬 자리와 세 시간째 막힌
|
|
1412
|
+
* 자리는 할 일이 다르므로, 그 값이 거짓이면 화면은 사람을 잘못된 순서로 움직인다.
|
|
1413
|
+
*
|
|
1414
|
+
* 사라진 조건은 함께 지운다(재발은 새 시작이다) — 그 규율은 `computeAttentions` 가 지킨다.
|
|
1415
|
+
*/
|
|
1416
|
+
attentionSince?: {
|
|
1417
|
+
id: string;
|
|
1418
|
+
since: ISOTime;
|
|
1419
|
+
}[];
|
|
1379
1420
|
/**
|
|
1380
1421
|
* 에너지 — **에너지 트윈만 채운다**(계량 지점·수요 구간·피크). 다른 종류에서는 없다.
|
|
1381
1422
|
*
|
package/dist/ems-kernel.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { FlowEngine } from './flow-engine.ts';
|
|
2
2
|
import { type AllocationPolicy } from './allocation-policy.ts';
|
|
3
|
-
import { type CanonicalEnvelope, type DemandWindowState, type StateSnapshot, type Attention, type StructureShift, type TwinModelDef } from './contract.ts';
|
|
3
|
+
import { type CanonicalEnvelope, type DemandWindowState, type EnergyState, type StateSnapshot, type Attention, type StructureShift, type TwinModelDef } from './contract.ts';
|
|
4
4
|
/** 수요 구간 — 요금의 알갱이다. 15분은 한국·다수 요금제의 최대수요 산정 단위다. */
|
|
5
5
|
export declare const DEMAND_WINDOW_MS: number;
|
|
6
6
|
/** 그 시각이 속한 구간의 시작 — 벽시계 경계(00·15·30·45분)에 맞춘다. */
|
|
@@ -208,5 +208,27 @@ export declare class EmsKernel extends FlowEngine {
|
|
|
208
208
|
/** 글자로 선언된 값(발전 형상·통화 같은 것) — 비어 있으면 없는 것으로 본다. */
|
|
209
209
|
private stringProperty;
|
|
210
210
|
tick(dtMs: number): void;
|
|
211
|
+
/**
|
|
212
|
+
* 에너지의 연속성 — **SCADA 는 「이번 15분에 지금까지 얼마」를 모른다.**
|
|
213
|
+
*
|
|
214
|
+
* 계량기는 순간값과 적산값을 준다. 「이 구간의 최대」·「구간이 열릴 때의 적산」·「관측 이후 최대」는
|
|
215
|
+
* 우리가 접어 만든 값이라, 재기동하면 원천이 되풀어 주지 않는다. 잃으면 오류 없이 값이 작아진다 —
|
|
216
|
+
* 구간 전력량이 기준점을 새로 잡아 짧아지고, 그 구간의 피크가 부팅 이후로만 잡힌다(요금이 걸린 수다).
|
|
217
|
+
*
|
|
218
|
+
* 지난 마감 구간도 이어받는다: 이미 저널에 있는 사실이지만, 화면이 그것을 보려고 매번 저널을 접지
|
|
219
|
+
* 않게 한다(자른 사실은 `closedTotal` 이 그대로 나른다).
|
|
220
|
+
*
|
|
221
|
+
* 지금 열린 구간이 **이미 지나간 것**이어도 그대로 받는다 — 다음 표본이 오면 커널이 그 구간을 정상
|
|
222
|
+
* 경로로 마감한다(그 사실은 자기 시각을 달고 있어 늦게 나가도 참이다). 여기서 버리면 그 15분은
|
|
223
|
+
* 통째로 사라진다.
|
|
224
|
+
*/
|
|
225
|
+
hydrateContinuity(snap: {
|
|
226
|
+
acked?: string[];
|
|
227
|
+
attentionSince?: {
|
|
228
|
+
id: string;
|
|
229
|
+
since: string;
|
|
230
|
+
}[];
|
|
231
|
+
energy?: EnergyState;
|
|
232
|
+
}): void;
|
|
211
233
|
getSnapshot(): StateSnapshot;
|
|
212
234
|
}
|
package/dist/ems-kernel.js
CHANGED
|
@@ -875,6 +875,38 @@ export class EmsKernel extends FlowEngine {
|
|
|
875
875
|
this.deriveLoad(at, dtMs);
|
|
876
876
|
this.closeDue(at);
|
|
877
877
|
}
|
|
878
|
+
/**
|
|
879
|
+
* 에너지의 연속성 — **SCADA 는 「이번 15분에 지금까지 얼마」를 모른다.**
|
|
880
|
+
*
|
|
881
|
+
* 계량기는 순간값과 적산값을 준다. 「이 구간의 최대」·「구간이 열릴 때의 적산」·「관측 이후 최대」는
|
|
882
|
+
* 우리가 접어 만든 값이라, 재기동하면 원천이 되풀어 주지 않는다. 잃으면 오류 없이 값이 작아진다 —
|
|
883
|
+
* 구간 전력량이 기준점을 새로 잡아 짧아지고, 그 구간의 피크가 부팅 이후로만 잡힌다(요금이 걸린 수다).
|
|
884
|
+
*
|
|
885
|
+
* 지난 마감 구간도 이어받는다: 이미 저널에 있는 사실이지만, 화면이 그것을 보려고 매번 저널을 접지
|
|
886
|
+
* 않게 한다(자른 사실은 `closedTotal` 이 그대로 나른다).
|
|
887
|
+
*
|
|
888
|
+
* 지금 열린 구간이 **이미 지나간 것**이어도 그대로 받는다 — 다음 표본이 오면 커널이 그 구간을 정상
|
|
889
|
+
* 경로로 마감한다(그 사실은 자기 시각을 달고 있어 늦게 나가도 참이다). 여기서 버리면 그 15분은
|
|
890
|
+
* 통째로 사라진다.
|
|
891
|
+
*/
|
|
892
|
+
hydrateContinuity(snap) {
|
|
893
|
+
super.hydrateContinuity(snap);
|
|
894
|
+
const e = snap?.energy;
|
|
895
|
+
if (!e)
|
|
896
|
+
return;
|
|
897
|
+
for (const p of e.points ?? [])
|
|
898
|
+
if (p?.id)
|
|
899
|
+
this.points.set(p.id, { ...p });
|
|
900
|
+
if (e.open)
|
|
901
|
+
this.open = { ...e.open };
|
|
902
|
+
if (e.closed?.length)
|
|
903
|
+
this.closed = e.closed.map(w => ({ ...w })).slice(-KEEP_CLOSED);
|
|
904
|
+
/* 자른 수는 이어받은 목록으로 다시 세지 않는다 — 그러면 잘려 나간 과거가 조용히 0 이 된다. */
|
|
905
|
+
if (Number.isFinite(e.closedTotal))
|
|
906
|
+
this.closedTotal = Number(e.closedTotal);
|
|
907
|
+
if (e.peakSince && Number.isFinite(e.peakSince.kW))
|
|
908
|
+
this.peak = { kW: e.peakSince.kW, windowStartMs: e.peakSince.windowStartMs };
|
|
909
|
+
}
|
|
878
910
|
getSnapshot() {
|
|
879
911
|
const snap = super.getSnapshot();
|
|
880
912
|
const contractKW = this.declaredContractKW();
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -407,6 +407,29 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
407
407
|
* 것이고, 그것을 허용하면 이 함수가 막으려던 일이 이 함수를 통해 일어난다.
|
|
408
408
|
*/
|
|
409
409
|
resumeRevision(from: number): void;
|
|
410
|
+
/**
|
|
411
|
+
* **원천이 되풀어 주지 않는 것만 잇는다** — 미러(관측 구동) 재기동의 연속성.
|
|
412
|
+
*
|
|
413
|
+
* ── 왜 따로 두나 (2026-08-18) ──────────────────────────────────────────────
|
|
414
|
+
* 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 스냅샷에서 심지 않는다 — 다음 계측·다음
|
|
415
|
+
* 마스터 재읽기가 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다.
|
|
416
|
+
*
|
|
417
|
+
* 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 접어 만든 것들이다:
|
|
418
|
+
* · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각
|
|
419
|
+
* · 열린 구간의 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다
|
|
420
|
+
*
|
|
421
|
+
* 이것을 잃으면 오류 없이 값이 작아진다(실측: 재기동 뒤 그 구간의 전력량·피크가 작게 나오고, 세
|
|
422
|
+
* 시간째 지속된 경보가 「0초째」가 된다). 그래서 축을 갈라, 되풀어 주지 않는 것만 이어받는다.
|
|
423
|
+
*
|
|
424
|
+
* 도메인 축(에너지 등)은 서브클래스가 override 로 덧붙인다(`super.hydrateContinuity(snap)` 합성).
|
|
425
|
+
*/
|
|
426
|
+
hydrateContinuity(snap: {
|
|
427
|
+
acked?: string[];
|
|
428
|
+
attentionSince?: {
|
|
429
|
+
id: string;
|
|
430
|
+
since: string;
|
|
431
|
+
}[];
|
|
432
|
+
}): void;
|
|
410
433
|
hydrateObserved(snap: {
|
|
411
434
|
locations: LocationState[];
|
|
412
435
|
items: ItemState[];
|
|
@@ -415,8 +438,13 @@ export declare abstract class FlowEngine implements TwinKernel {
|
|
|
415
438
|
assets?: AssetState[];
|
|
416
439
|
tasks?: TaskState[];
|
|
417
440
|
orders?: OrderState[];
|
|
418
|
-
/** 확인해 둔 주목 신호 id — 계산으로 되살릴 수
|
|
441
|
+
/** 확인해 둔 주목 신호 id — 계산으로 되살릴 수 없어 스냅샷에서 이어받는다. */
|
|
419
442
|
acked?: string[];
|
|
443
|
+
/** 주목 신호가 처음 성립한 시각 — 이어받지 않으면 지속된 조건이 「방금」으로 되살아난다. */
|
|
444
|
+
attentionSince?: {
|
|
445
|
+
id: string;
|
|
446
|
+
since: string;
|
|
447
|
+
}[];
|
|
420
448
|
}, orders?: OrderStatusDelta[]): void;
|
|
421
449
|
/**
|
|
422
450
|
* what-if 구성 변주 — **선언을 덮어쓴다**(fork 대상). 바꿨으면 true.
|
package/dist/flow-engine.js
CHANGED
|
@@ -465,11 +465,39 @@ export class FlowEngine {
|
|
|
465
465
|
throw new Error(`리비전을 뒤로 되돌릴 수 없다: ${this.revision} → ${from} (겹치는 번호가 생긴다)`);
|
|
466
466
|
this.revision = from;
|
|
467
467
|
}
|
|
468
|
+
/**
|
|
469
|
+
* **원천이 되풀어 주지 않는 것만 잇는다** — 미러(관측 구동) 재기동의 연속성.
|
|
470
|
+
*
|
|
471
|
+
* ── 왜 따로 두나 (2026-08-18) ──────────────────────────────────────────────
|
|
472
|
+
* 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 스냅샷에서 심지 않는다 — 다음 계측·다음
|
|
473
|
+
* 마스터 재읽기가 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다.
|
|
474
|
+
*
|
|
475
|
+
* 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 접어 만든 것들이다:
|
|
476
|
+
* · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각
|
|
477
|
+
* · 열린 구간의 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다
|
|
478
|
+
*
|
|
479
|
+
* 이것을 잃으면 오류 없이 값이 작아진다(실측: 재기동 뒤 그 구간의 전력량·피크가 작게 나오고, 세
|
|
480
|
+
* 시간째 지속된 경보가 「0초째」가 된다). 그래서 축을 갈라, 되풀어 주지 않는 것만 이어받는다.
|
|
481
|
+
*
|
|
482
|
+
* 도메인 축(에너지 등)은 서브클래스가 override 로 덧붙인다(`super.hydrateContinuity(snap)` 합성).
|
|
483
|
+
*/
|
|
484
|
+
hydrateContinuity(snap) {
|
|
485
|
+
for (const id of snap?.acked ?? [])
|
|
486
|
+
this._acked.add(id);
|
|
487
|
+
for (const e of snap?.attentionSince ?? [])
|
|
488
|
+
if (e?.id && e.since)
|
|
489
|
+
this._attentionSince.set(e.id, e.since);
|
|
490
|
+
}
|
|
468
491
|
hydrateObserved(snap, orders = []) {
|
|
469
492
|
/* 확인 처리를 먼저 이어받는다 — 아래에서 상태를 주입하면 곧바로 주목 신호가 계산되므로, 늦게
|
|
470
493
|
* 이어받으면 그 한 번은 확인 안 된 것으로 계산된다(화면이 잠깐 빨개진다). */
|
|
471
494
|
for (const id of snap.acked ?? [])
|
|
472
495
|
this._acked.add(id);
|
|
496
|
+
/* 「언제부터인가」도 함께 이어받는다 — 상태를 심으면 곧바로 신호가 계산되므로, 늦게 받으면 그 한 번은
|
|
497
|
+
모두 방금 생긴 것으로 찍히고 그 값이 그대로 굳는다(다음 계산은 이미 기억된 시각을 쓴다). */
|
|
498
|
+
for (const e of snap.attentionSince ?? [])
|
|
499
|
+
if (e?.id && e.since)
|
|
500
|
+
this._attentionSince.set(e.id, e.since);
|
|
473
501
|
/* **관측된 것을 버리지 않는다.** 예전에는 자리·설비 상태를 'idle' 로, OEE 누적을 0 으로 덮고
|
|
474
502
|
* 물품의 로트·단위·소속·마스터데이터를 떨어뜨렸다. 씨앗이 잃은 것은 **예측도 모른다** —
|
|
475
503
|
* 고장 난 설비를 정상으로, 진행 중인 일을 없는 것으로 놓고 미래를 실행하면 답이 낙관 쪽으로 치우친다. */
|
|
@@ -748,7 +776,7 @@ export class FlowEngine {
|
|
|
748
776
|
*/
|
|
749
777
|
abandonedTasks = 0;
|
|
750
778
|
/** 주목 신호가 **처음 성립한 시각**(id → ISO). 조건이 사라지면 지운다 — 재발은 새 시작이다. */
|
|
751
|
-
_attentionSince = new Map();
|
|
779
|
+
_attentionSince = new Map();
|
|
752
780
|
dispatch(cmd) {
|
|
753
781
|
/*
|
|
754
782
|
* 이 커맨드가 낳는 이벤트에 상관값을 단다. 명시값이 없으면 **commandId 를 쓴다** — 호출자가
|
|
@@ -1022,7 +1050,9 @@ export class FlowEngine {
|
|
|
1022
1050
|
})),
|
|
1023
1051
|
attentions: this.computeAttentions(),
|
|
1024
1052
|
/* 확인해 둔 신호 — 스냅샷으로 왕복해야 재기동 후에도 확인 상태가 유지된다. */
|
|
1025
|
-
acked: [...this._acked]
|
|
1053
|
+
acked: [...this._acked],
|
|
1054
|
+
/* 언제부터인가 — 이것도 계산으로 되살릴 수 없다. 위 계산이 사라진 조건을 이미 지웠으므로 지금 남은 것이 곧 진실이다. */
|
|
1055
|
+
attentionSince: [...this._attentionSince].map(([id, since]) => ({ id, since }))
|
|
1026
1056
|
};
|
|
1027
1057
|
}
|
|
1028
1058
|
/*
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -3254,8 +3254,29 @@ var FlowEngine = class {
|
|
|
3254
3254
|
if (from < this.revision) throw new Error(`\uB9AC\uBE44\uC804\uC744 \uB4A4\uB85C \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uB2E4: ${this.revision} \u2192 ${from} (\uACB9\uCE58\uB294 \uBC88\uD638\uAC00 \uC0DD\uAE34\uB2E4)`);
|
|
3255
3255
|
this.revision = from;
|
|
3256
3256
|
}
|
|
3257
|
+
/**
|
|
3258
|
+
* **원천이 되풀어 주지 않는 것만 잇는다** — 미러(관측 구동) 재기동의 연속성.
|
|
3259
|
+
*
|
|
3260
|
+
* ── 왜 따로 두나 (2026-08-18) ──────────────────────────────────────────────
|
|
3261
|
+
* 미러의 진실은 원천이다. 그래서 재고·위치·설비 상태는 스냅샷에서 심지 않는다 — 다음 계측·다음
|
|
3262
|
+
* 마스터 재읽기가 곧 정정하므로, 심으면 **떠난 물건이 되살아나는** 위험만 남는다.
|
|
3263
|
+
*
|
|
3264
|
+
* 그런데 원천이 **애초에 다시 말해 주지 않는 축**이 있다. 우리가 접어 만든 것들이다:
|
|
3265
|
+
* · 확인(ack)·조건이 처음 성립한 시각 — 사람이 한 일과 우리가 기억한 시각
|
|
3266
|
+
* · 열린 구간의 누적·적산 기준점·관측 이후 최대 — SCADA 는 「이번 15분에 지금까지 얼마」를 모른다
|
|
3267
|
+
*
|
|
3268
|
+
* 이것을 잃으면 오류 없이 값이 작아진다(실측: 재기동 뒤 그 구간의 전력량·피크가 작게 나오고, 세
|
|
3269
|
+
* 시간째 지속된 경보가 「0초째」가 된다). 그래서 축을 갈라, 되풀어 주지 않는 것만 이어받는다.
|
|
3270
|
+
*
|
|
3271
|
+
* 도메인 축(에너지 등)은 서브클래스가 override 로 덧붙인다(`super.hydrateContinuity(snap)` 합성).
|
|
3272
|
+
*/
|
|
3273
|
+
hydrateContinuity(snap) {
|
|
3274
|
+
for (const id of snap?.acked ?? []) this._acked.add(id);
|
|
3275
|
+
for (const e of snap?.attentionSince ?? []) if (e?.id && e.since) this._attentionSince.set(e.id, e.since);
|
|
3276
|
+
}
|
|
3257
3277
|
hydrateObserved(snap, orders = []) {
|
|
3258
3278
|
for (const id of snap.acked ?? []) this._acked.add(id);
|
|
3279
|
+
for (const e of snap.attentionSince ?? []) if (e?.id && e.since) this._attentionSince.set(e.id, e.since);
|
|
3259
3280
|
for (const n of snap.locations) {
|
|
3260
3281
|
this.locations.set(n.id, { id: n.id, type: n.type, capacity: n.capacity ?? 0, parallelism: n.parallelism, occupancy: n.occupancy ?? 0, status: n.status ?? "idle", parentId: n.parentId });
|
|
3261
3282
|
}
|
|
@@ -3515,7 +3536,6 @@ var FlowEngine = class {
|
|
|
3515
3536
|
abandonedTasks = 0;
|
|
3516
3537
|
/** 주목 신호가 **처음 성립한 시각**(id → ISO). 조건이 사라지면 지운다 — 재발은 새 시작이다. */
|
|
3517
3538
|
_attentionSince = /* @__PURE__ */ new Map();
|
|
3518
|
-
// 확인(ack)된 주목 신호 id — 조건 지속돼도 acknowledged 로 표시(재발 시 재활성)
|
|
3519
3539
|
dispatch(cmd) {
|
|
3520
3540
|
this._correlationId = cmd.correlationId ?? cmd.commandId;
|
|
3521
3541
|
try {
|
|
@@ -3758,7 +3778,9 @@ var FlowEngine = class {
|
|
|
3758
3778
|
})),
|
|
3759
3779
|
attentions: this.computeAttentions(),
|
|
3760
3780
|
/* 확인해 둔 신호 — 스냅샷으로 왕복해야 재기동 후에도 확인 상태가 유지된다. */
|
|
3761
|
-
acked: [...this._acked]
|
|
3781
|
+
acked: [...this._acked],
|
|
3782
|
+
/* 언제부터인가 — 이것도 계산으로 되살릴 수 없다. 위 계산이 사라진 조건을 이미 지웠으므로 지금 남은 것이 곧 진실이다. */
|
|
3783
|
+
attentionSince: [...this._attentionSince].map(([id, since]) => ({ id, since }))
|
|
3762
3784
|
};
|
|
3763
3785
|
}
|
|
3764
3786
|
/*
|
|
@@ -6676,6 +6698,30 @@ var EmsKernel = class extends FlowEngine {
|
|
|
6676
6698
|
this.deriveLoad(at, dtMs);
|
|
6677
6699
|
this.closeDue(at);
|
|
6678
6700
|
}
|
|
6701
|
+
/**
|
|
6702
|
+
* 에너지의 연속성 — **SCADA 는 「이번 15분에 지금까지 얼마」를 모른다.**
|
|
6703
|
+
*
|
|
6704
|
+
* 계량기는 순간값과 적산값을 준다. 「이 구간의 최대」·「구간이 열릴 때의 적산」·「관측 이후 최대」는
|
|
6705
|
+
* 우리가 접어 만든 값이라, 재기동하면 원천이 되풀어 주지 않는다. 잃으면 오류 없이 값이 작아진다 —
|
|
6706
|
+
* 구간 전력량이 기준점을 새로 잡아 짧아지고, 그 구간의 피크가 부팅 이후로만 잡힌다(요금이 걸린 수다).
|
|
6707
|
+
*
|
|
6708
|
+
* 지난 마감 구간도 이어받는다: 이미 저널에 있는 사실이지만, 화면이 그것을 보려고 매번 저널을 접지
|
|
6709
|
+
* 않게 한다(자른 사실은 `closedTotal` 이 그대로 나른다).
|
|
6710
|
+
*
|
|
6711
|
+
* 지금 열린 구간이 **이미 지나간 것**이어도 그대로 받는다 — 다음 표본이 오면 커널이 그 구간을 정상
|
|
6712
|
+
* 경로로 마감한다(그 사실은 자기 시각을 달고 있어 늦게 나가도 참이다). 여기서 버리면 그 15분은
|
|
6713
|
+
* 통째로 사라진다.
|
|
6714
|
+
*/
|
|
6715
|
+
hydrateContinuity(snap) {
|
|
6716
|
+
super.hydrateContinuity(snap);
|
|
6717
|
+
const e = snap?.energy;
|
|
6718
|
+
if (!e) return;
|
|
6719
|
+
for (const p of e.points ?? []) if (p?.id) this.points.set(p.id, { ...p });
|
|
6720
|
+
if (e.open) this.open = { ...e.open };
|
|
6721
|
+
if (e.closed?.length) this.closed = e.closed.map((w) => ({ ...w })).slice(-KEEP_CLOSED);
|
|
6722
|
+
if (Number.isFinite(e.closedTotal)) this.closedTotal = Number(e.closedTotal);
|
|
6723
|
+
if (e.peakSince && Number.isFinite(e.peakSince.kW)) this.peak = { kW: e.peakSince.kW, windowStartMs: e.peakSince.windowStartMs };
|
|
6724
|
+
}
|
|
6679
6725
|
getSnapshot() {
|
|
6680
6726
|
const snap = super.getSnapshot();
|
|
6681
6727
|
const contractKW = this.declaredContractKW();
|
package/package.json
CHANGED