@operato/twin-kernel 0.7.88 → 0.7.89
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 +21 -1
- package/dist/contract.js +2 -12
- package/dist/ems-kernel.js +17 -7
- package/dist-cjs/index.cjs +7 -6
- package/package.json +1 -1
package/dist/contract.d.ts
CHANGED
|
@@ -671,7 +671,27 @@ export type Effectivity = 'not-yet' | 'expired';
|
|
|
671
671
|
* `at` 를 주지 않으면 **판단하지 않는다**(`undefined`). 모르는 시각으로 폐기를 단정하면, 시각을 안 넘긴
|
|
672
672
|
* 소비처 전부가 자원을 잃는다. 파싱 불가한 시각도 같다 — 짐작해 고치지 않는다.
|
|
673
673
|
*/
|
|
674
|
-
|
|
674
|
+
/**
|
|
675
|
+
* 끝나는 시각을 **포함하는가** — 축마다 다르다. 규칙은 한 벌이고 규약만 밝힌다.
|
|
676
|
+
*
|
|
677
|
+
* ── 왜 이 칸이 생겼나 (2026-08-30) ────────────────────────────────────────
|
|
678
|
+
* 처음에는 이 판정이 한 곳뿐이었고 끝을 포함했다. 자원에는 그것이 맞다 — 폐기 일자가 있는 지게차는
|
|
679
|
+
* 띄엄띄엄 있고, 두 자원의 기간이 한 순간을 함께 갖는 일이 문제가 되지 않는다.
|
|
680
|
+
*
|
|
681
|
+
* 그런데 **선언은 빈틈없이 이어진다.** 8월 요금 주기가 끝나는 순간이 9월 주기가 시작하는 순간이다.
|
|
682
|
+
* 끝을 포함하면 그 한 순간에 두 단가가 동시에 유효해지고, 어느 것으로 계산했는지 말할 수 없다.
|
|
683
|
+
*
|
|
684
|
+
* 그때 같은 판정을 하는 함수를 하나 더 만들었다. 규칙이 두 벌이 되면 한쪽만 고쳐지는 날이 온다 —
|
|
685
|
+
* 이 저장소가 오늘 하루 그 부류를 여러 번 고쳤다. 그래서 함수는 하나로 두고 **규약을 인자로** 받는다.
|
|
686
|
+
*/
|
|
687
|
+
export interface EffectivityOptions {
|
|
688
|
+
/**
|
|
689
|
+
* `'inclusive'`(기본) — 끝나는 시각까지 유효하다. 자원의 유효 기간이 이쪽이다.
|
|
690
|
+
* `'exclusive'` — 끝나는 시각은 다음 구간의 것이다. 이어지는 주기(요금·단가)가 이쪽이다.
|
|
691
|
+
*/
|
|
692
|
+
end?: 'inclusive' | 'exclusive';
|
|
693
|
+
}
|
|
694
|
+
export declare function effectivityAt(p: EffectivePeriod | undefined, at?: ISOTime, opts?: EffectivityOptions): Effectivity | undefined;
|
|
675
695
|
/**
|
|
676
696
|
* 자원 **등급 정의** — ISA-95 가 세 자원에 똑같이 정의한 한 모양.
|
|
677
697
|
*
|
package/dist/contract.js
CHANGED
|
@@ -376,17 +376,7 @@ export function judgeAgainstSpec(spec, result) {
|
|
|
376
376
|
}
|
|
377
377
|
return allJudged ? 'pass' : undefined;
|
|
378
378
|
}
|
|
379
|
-
|
|
380
|
-
* 이 시각에 유효 기간 밖인가 — **한 규칙**으로 개체·등급·설비↔자산 매핑을 모두 판정한다.
|
|
381
|
-
*
|
|
382
|
-
* **표준은 날짜만 정하고 "밖이면 어떻게 되는가" 는 정하지 않는다.** 그 판단은 우리 것이므로 여기 밝힌다:
|
|
383
|
-
* 기간 밖이면 **그 자원은 그 시각의 모델에 참여하지 않는다**(배정되지 않고, 가용 분모에 들지 않는다).
|
|
384
|
-
* 지우지는 않는다 — 이유를 달아 남긴다. 조용히 사라지면 "왜 없어졌나" 를 아무도 답할 수 없다.
|
|
385
|
-
*
|
|
386
|
-
* `at` 를 주지 않으면 **판단하지 않는다**(`undefined`). 모르는 시각으로 폐기를 단정하면, 시각을 안 넘긴
|
|
387
|
-
* 소비처 전부가 자원을 잃는다. 파싱 불가한 시각도 같다 — 짐작해 고치지 않는다.
|
|
388
|
-
*/
|
|
389
|
-
export function effectivityAt(p, at) {
|
|
379
|
+
export function effectivityAt(p, at, opts) {
|
|
390
380
|
if (!p || !at)
|
|
391
381
|
return undefined;
|
|
392
382
|
const atMs = parsedMs(at);
|
|
@@ -396,7 +386,7 @@ export function effectivityAt(p, at) {
|
|
|
396
386
|
if (Number.isFinite(from) && atMs < from)
|
|
397
387
|
return 'not-yet';
|
|
398
388
|
const to = p.effectiveEnd ? parsedMs(p.effectiveEnd) : NaN;
|
|
399
|
-
if (Number.isFinite(to) && atMs > to)
|
|
389
|
+
if (Number.isFinite(to) && (opts?.end === 'exclusive' ? atMs >= to : atMs > to))
|
|
400
390
|
return 'expired';
|
|
401
391
|
return undefined;
|
|
402
392
|
}
|
package/dist/ems-kernel.js
CHANGED
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
import { FlowEngine } from "./flow-engine.js";
|
|
29
29
|
/* 기간 사실의 이름은 유입 문과 한 규칙이다(§`periodFactId`). */
|
|
30
30
|
import { periodFactId } from "./energy-ingest.js";
|
|
31
|
+
/* 「이 시각에 유효한가」는 커널에 규칙이 한 벌이다 — 선언 축도 그것을 쓴다. */
|
|
32
|
+
import { effectivityAt } from "./contract.js";
|
|
31
33
|
import { firstFitPolicy } from "./allocation-policy.js";
|
|
32
34
|
import { CMD, ENERGY_EVENT, minuteOfDayAt, localDayIndexAt, localDayStartMs } from "./contract.js";
|
|
33
35
|
import { EMS_PROPERTY, METER_DIRECTION, electricalUpstreamOf, generationFractionAt } from "./ems-profile.js";
|
|
@@ -1749,22 +1751,30 @@ function putDeclaration(list, d) {
|
|
|
1749
1751
|
return next.length > 24 ? next.slice(next.length - 24) : next;
|
|
1750
1752
|
}
|
|
1751
1753
|
/**
|
|
1752
|
-
* **이 시각에 유효한 선언** —
|
|
1754
|
+
* **이 시각에 유효한 선언** — 판정은 커널의 한 규칙이 한다(§`effectivityAt`).
|
|
1755
|
+
*
|
|
1756
|
+
* 여기서 하는 일은 **고르는 것**뿐이다. 「유효한가」를 여기서 다시 판정하면 규칙이 두 벌이 되고,
|
|
1757
|
+
* 한쪽만 고쳐지는 날이 온다. 실제로 이 함수는 처음에 자기 판정을 들고 있었다.
|
|
1758
|
+
*
|
|
1759
|
+
* 선언은 **빈틈없이 이어진다** — 8월 주기가 끝나는 순간이 9월 주기가 시작하는 순간이다. 그래서 끝을
|
|
1760
|
+
* 포함하지 않는다(`end: 'exclusive'`). 포함하면 그 한 순간에 두 단가가 동시에 유효해진다.
|
|
1753
1761
|
*
|
|
1754
1762
|
* 유효한 것이 없으면 답하지 않는다. 가장 가까운 것을 골라 주지 않는다 — 아직 시작하지 않은 단가로
|
|
1755
|
-
* 오늘을
|
|
1763
|
+
* 오늘을 계산하거나 이미 끝난 단가를 지금 것인 양 쓰게 된다. 둘 다 오류 없이 틀린 값을 만든다.
|
|
1756
1764
|
*
|
|
1757
1765
|
* 구간이 겹치면 **나중에 시작한 것**이 이긴다(더 좁게 정한 선언이 앞선다).
|
|
1758
1766
|
*/
|
|
1759
1767
|
function inEffectAt(list, atMs) {
|
|
1768
|
+
if (!list?.length)
|
|
1769
|
+
return undefined;
|
|
1770
|
+
const at = new Date(atMs).toISOString();
|
|
1760
1771
|
let best;
|
|
1761
1772
|
let bestFrom = -Infinity;
|
|
1762
|
-
for (const d of list
|
|
1763
|
-
|
|
1764
|
-
const to = Date.parse(String(d.to));
|
|
1765
|
-
if (!Number.isFinite(from) || !Number.isFinite(to))
|
|
1773
|
+
for (const d of list) {
|
|
1774
|
+
if (effectivityAt({ effectiveStart: d.from, effectiveEnd: d.to }, at, { end: 'exclusive' }) !== undefined)
|
|
1766
1775
|
continue;
|
|
1767
|
-
|
|
1776
|
+
const from = Date.parse(String(d.from));
|
|
1777
|
+
if (!Number.isFinite(from))
|
|
1768
1778
|
continue;
|
|
1769
1779
|
if (from > bestFrom) {
|
|
1770
1780
|
best = d;
|
package/dist-cjs/index.cjs
CHANGED
|
@@ -366,14 +366,14 @@ function judgeAgainstSpec(spec, result) {
|
|
|
366
366
|
}
|
|
367
367
|
return allJudged ? "pass" : void 0;
|
|
368
368
|
}
|
|
369
|
-
function effectivityAt(p, at) {
|
|
369
|
+
function effectivityAt(p, at, opts) {
|
|
370
370
|
if (!p || !at) return void 0;
|
|
371
371
|
const atMs = parsedMs(at);
|
|
372
372
|
if (!Number.isFinite(atMs)) return void 0;
|
|
373
373
|
const from = p.effectiveStart ? parsedMs(p.effectiveStart) : NaN;
|
|
374
374
|
if (Number.isFinite(from) && atMs < from) return "not-yet";
|
|
375
375
|
const to = p.effectiveEnd ? parsedMs(p.effectiveEnd) : NaN;
|
|
376
|
-
if (Number.isFinite(to) && atMs > to) return "expired";
|
|
376
|
+
if (Number.isFinite(to) && (opts?.end === "exclusive" ? atMs >= to : atMs > to)) return "expired";
|
|
377
377
|
return void 0;
|
|
378
378
|
}
|
|
379
379
|
var PARSED_MAX = 64;
|
|
@@ -9650,13 +9650,14 @@ function putDeclaration(list, d) {
|
|
|
9650
9650
|
return next.length > 24 ? next.slice(next.length - 24) : next;
|
|
9651
9651
|
}
|
|
9652
9652
|
function inEffectAt(list, atMs) {
|
|
9653
|
+
if (!list?.length) return void 0;
|
|
9654
|
+
const at = new Date(atMs).toISOString();
|
|
9653
9655
|
let best;
|
|
9654
9656
|
let bestFrom = -Infinity;
|
|
9655
|
-
for (const d of list
|
|
9657
|
+
for (const d of list) {
|
|
9658
|
+
if (effectivityAt({ effectiveStart: d.from, effectiveEnd: d.to }, at, { end: "exclusive" }) !== void 0) continue;
|
|
9656
9659
|
const from = Date.parse(String(d.from));
|
|
9657
|
-
|
|
9658
|
-
if (!Number.isFinite(from) || !Number.isFinite(to)) continue;
|
|
9659
|
-
if (atMs < from || atMs >= to) continue;
|
|
9660
|
+
if (!Number.isFinite(from)) continue;
|
|
9660
9661
|
if (from > bestFrom) {
|
|
9661
9662
|
best = d;
|
|
9662
9663
|
bestFrom = from;
|
package/package.json
CHANGED