@things-factory/headless-twin 10.0.17 → 10.0.18
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-server/engine/canonical-ingest.d.ts +12 -85
- package/dist-server/engine/canonical-ingest.js +92 -26
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/energy-topology.js +3 -3
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +91 -3
- package/dist-server/engine/kpi-fold.js +149 -25
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.js +99 -11
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/local-declarations.js +6 -5
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +2 -2
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +2 -1
- package/dist-server/engine/oee-accumulator.js +3 -2
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +72 -2
- package/dist-server/engine/twin-engine.js +150 -4
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/routes.js +1 -1
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/service/index.js +3 -0
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/reference-live.js +1 -1
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +11 -2
- package/dist-server/service/reference/reference-master.js +1 -1
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.js +2 -2
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
- package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
- package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
- package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
- package/dist-server/service/twin-backfill/index.d.ts +2 -0
- package/dist-server/service/twin-backfill/index.js +6 -0
- package/dist-server/service/twin-backfill/index.js.map +1 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +2 -2
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-model/name-index.js +1 -1
- package/dist-server/service/twin-model/name-index.js.map +1 -1
- package/dist-server/service/twin-model/project-structure.js +4 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +1 -1
- package/dist-server/service/twin-model/twin-equipment.js +2 -2
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +4 -4
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.js +1 -1
- package/dist-shared/entity-delta.js.map +1 -1
- package/package.json +7 -6
- package/server/engine/canonical-ingest.ts +72 -84
- package/server/engine/energy-topology.ts +1 -1
- package/server/engine/kpi-fold.ts +219 -36
- package/server/engine/kpi-query.ts +97 -4
- package/server/engine/local-declarations.ts +2 -1
- package/server/engine/model-vocabulary.ts +1 -1
- package/server/engine/oee-accumulator.ts +4 -2
- package/server/engine/twin-engine.ts +163 -5
- package/server/routes.ts +6 -1
- package/server/service/index.ts +3 -0
- package/server/service/reference/reference-live.ts +8 -2
- package/server/service/reference/reference-master.ts +12 -3
- package/server/service/reference/reference-resolver.ts +1 -1
- package/server/service/reference/template-registry.ts +1 -1
- package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
- package/server/service/twin-backfill/backfill-shape.ts +86 -0
- package/server/service/twin-backfill/index.ts +3 -0
- package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
- package/server/service/twin-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +1 -1
- package/server/service/twin-model/name-index.ts +1 -1
- package/server/service/twin-model/project-structure.ts +3 -2
- package/server/service/twin-model/twin-equipment.ts +14 -2
- package/server/service/twin-model/twin-model-item-query.ts +1 -1
- package/server/service/twin-model/twin-model-query.ts +2 -2
- package/server/service/twin-model/twin-model-tree-query.ts +1 -1
- package/server/service/twin-space/twin-space-resolver.ts +1 -1
- package/shared/entity-delta.ts +1 -1
- package/test/axis-read.test.ts +4 -3
- package/test/backfill-shape.test.ts +89 -0
- package/test/broadcast-cost-baseline.test.ts +2 -1
- package/test/canonical-ingest-vocabularies.test.ts +82 -3
- package/test/capability-mapping.test.ts +1 -1
- package/test/contract-layer-guard.test.ts +48 -0
- package/test/declaration-reaches-model.test.ts +2 -0
- package/test/equipment-identity-reaches.test.ts +57 -0
- package/test/fact-scope-wiring.test.ts +96 -0
- package/test/generated-not-counted.test.ts +99 -0
- package/test/ingest-bench.test.ts +2 -1
- package/test/ingest-wiring-guard.test.ts +197 -0
- package/test/kpi-fold.test.ts +81 -0
- package/test/live-mirror-parity.test.ts +6 -3
- package/test/local-declarations.test.ts +3 -2
- package/test/master-to-twin.test.ts +2 -2
- package/test/oee-accumulator.test.ts +18 -3
- package/test/projection-reaches-screen.test.ts +5 -0
- package/test/projection-reads-declared.test.ts +109 -0
- package/test/property-effects.test.ts +7 -3
- package/test/scale-twin-bench.test.ts +2 -1
- package/test/vocabulary-guard.test.ts +1 -1
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-fold.js","sourceRoot":"","sources":["../../server/engine/kpi-fold.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 업무 KPI 폴드 — **이벤트 저널을 계산해** 시간창 지표를 만든다(순수).\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 지금 트윈이 답할 수 있는 것은 \"이 순간의 값\"(점유 92%)과 \"인프라 부하\"(초당 이벤트 수)뿐이다.\n * 운영이 묻는 것은 다르다 — **지난 한 시간에 몇 건 처리했나, 한 건에 얼마나 걸리나, 자원은 얼마나\n * 일했나.** 그 값이 없으면 AI 는 \"점유 92%\" 는 말해도 \"목표보다 나쁜가\" 는 답할 수 없다.\n *\n * ── 원료는 이미 있다 ────────────────────────────────────────────────────────\n * 저널(`TwinEvent`)에 `task.status`·`order.status`·`equipment.status` 전이가 `eventTime`(ISO)과 함께\n * append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 — **계산만 하면 된다.**\n * 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를 계산해 OEE 카운터 생산).\n *\n * ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────\n * · 목표와 비교하지 않는다. \"95% 목표 대비\" 는 목표를 저장하는 별개 층(경영 KPI)의 몫이고,\n * 이 층은 **사실만** 생산한다. 두 층을 섞으면 목표를 정의하지 않은 현장은 사실조차 못 본다.\n * · 체류시간(dwell)은 아직 내지 않는다. 물건이 노드에 머문 시간은 item 단위 이벤트(epcis.*)를\n * 짝지어야 하고, 그 짝맞춤 규칙이 도메인마다 다르다. **없는 것을 0 으로 꾸미지 않는다.**\n * · DB·커널을 모른다. 입력은 평범한 배열이고 출력은 평범한 객체다(테스트가 값싸다).\n */\n\n/*\n * 짝맞춤은 **커널이 소유한다**(`foldTaskRecords`). 완료는 작업당 하나·나중 것이 사실, 착수는 처음 것,\n * 종류·오더는 어느 전이에서든 — 그 규칙은 커널 상태 기계에서 나오는 지식이다. 여기서 다시 적었더니\n * 공정 타임라인과 **다른 답**을 냈다(재전송된 완료를 두 건으로 세어 처리량·점유가 부풀려졌다).\n * 이 파일은 이제 \"계산한 기록 → 창 지표\" 만 한다.\n */\nimport {\n ENERGY_EVENT,\n attributeEnergy,\n electricityCost,\n energyIntensity,\n energyOfWindows,\n foldTaskRecords,\n isOrderTerminal,\n activeShiftAt,\n type ElectricityCost,\n type TariffDeclaration,\n type TaskFacets,\n type WorkCalendarEntry\n} from '@operato/twin-kernel'\n\n/** 저널 한 줄 — 필요한 것만(엔티티·typeorm 비의존). */\nexport interface KpiEvent {\n eventType?: string\n /** ISO 시각. 파싱 불가하면 그 줄은 버린다(알리지 않고 0 으로 세지 않는다). */\n eventTime?: string\n payload?: any\n}\n\nexport interface KpiWindow {\n /** 창의 시작·끝(epoch ms). 완료 시각이 이 안에 든 것만 성과로 센다. */\n fromMs: number\n toMs: number\n}\n\nexport interface DurationStats {\n /** 짝이 맞아 계산된 건수. */\n count: number\n /**\n * 하위 꼬리 — **분포를 양쪽에서 계산하지하기 위해** 낸다.\n * p50·p90 만 있으면 아래쪽 퍼짐을 위쪽에서 거울처럼 베껴 쓸 수밖에 없다(발명). 관측이 말하게 한다.\n */\n p10Ms: number\n p50Ms: number\n p90Ms: number\n avgMs: number\n}\n\n/**\n * 관점 축 — **같은 사실을 다른 각으로** 본다.\n *\n * 고정된 한 렌즈(자원별)만 낼 수 있으면 사용자가 \"구역별로는?\" 이라고 물어도 답이 없다. 사실 계산이\n * 축을 못 내면 AI 도 못 낸다 — 그래서 축은 계산 층의 능력이어야 한다.\n *\n * resource 자원별(누가 했나) taskKind 작업 종류별(무엇을 했나)\n * location 도착 지점별(어디서) order 오더별(무엇을 위해)\n * area 구역별 — 노드→구역 지도가 필요하다(이벤트에 없다. 호출부가 준다)\n * shift 교대별(언제) — 완료 시각 + 현장의 시각 기준에서 **계산한다**(이벤트에 없다)\n */\nexport type KpiGroupBy = 'resource' | 'taskKind' | 'location' | 'order' | 'area' | 'shift'\n\nexport interface KpiGroup {\n /** 축의 값(자원 id·작업 종류·노드 id·오더 id·구역 id). 값이 없던 기록은 'unknown'. */\n key: string\n /** 이 축에서 완료된 건수. */\n tasks: number\n leadTime: DurationStats\n workTime: DurationStats\n waitTime: DurationStats\n /** 이 축에 붙어 있던 시간의 합(자원 축에서 특히 의미 있다). */\n busyMs: number\n /**\n * **품질 판정이 있었던 건수만** 센다 (2026-08-19).\n *\n * 판정이 없는 작업(이동·체류·중간 스테이션)은 어느 쪽에도 세지 않는다 — 모름을 양품으로 세면 양품률이\n * 알리지 않고 100% 로 올라간다. 그래서 `good + scrap` 은 `tasks` 보다 작을 수 있고, 그것이 정상이다.\n *\n * 판정이 하나도 없으면 **필드를 만들지 않는다**(0/0 을 「양품률 0%」로 읽히게 두지 않는다).\n */\n quality?: { good: number; scrap: number }\n}\n\nexport interface KpiResult {\n window: { fromMs: number; toMs: number }\n /** 창 안에 **완료된** 건수 — 성과는 완료 시점으로 센다(시작 시점으로 세면 진행 중인 것이 섞인다). */\n throughput: { tasks: number; orders: number }\n /**\n * 에너지 — **이 창의 전력량과 원단위.** 에너지 트윈이 없으면 `undefined`(0 이 아니다).\n *\n * ── 왜 KPI 폴드 안에 있나 (2026-08-14, §10 7단계) ─────────────────────────\n * 원단위는 분자(전력량)와 분모(산출)를 **같은 창**에서 나눈 값이다. 질의를 따로 두면 두 창을 맞추는\n * 일이 소비처의 몫이 되고, 어긋난 두 사실을 나눈 숫자는 아무것도 뜻하지 않는다(커널의 규칙이 그것을\n * 거절한다). 같은 폴드 안에 두면 창이 하나뿐이라 어긋날 수 없다.\n *\n * 분자는 **저널의 구간 마감 사건**에서 온다(`energy.demand.window`) — 커널 상태가 아니라 저널이므로\n * 멈춘 트윈·과거 구간에도 답할 수 있다.\n */\n energy?: {\n /** 이 창의 전력량. 셀 수 있는 구간이 없으면 `undefined`. */\n kWh?: number\n /**\n * 이 창의 **최대수요**(kW)와 그것이 난 구간의 시작.\n *\n * 전력량과 다른 질문이다: 전력량은 요금의 사용량 부분이고, 최대수요는 **기본요금**과 계약 위반을\n * 정한다. 평균으로 갈음할 수 없어서 커널이 구간의 최대들 중 가장 큰 값을 따로 낸다.\n */\n peakKW?: number\n peakStartMs?: number\n /**\n * 그 피크 구간에서 **설비가 끌어당긴** 최대(발전·방전 차감 전)와 그 차이(깎인 양).\n *\n * 요금은 계통 수요(`peakKW`)로 매겨지지만, 「무엇이 그 수요를 만들었나」와 「무엇이 덜어냈나」는\n * 이 두 수의 간격이 답한다. 발전·축전을 선언하지 않은 현장에서는 없다(차감할 것이 없다).\n */\n peakGrossKW?: number\n shavedKW?: number\n /**\n * 이 창의 **발전량** — 커널이 마감한 기간 사실(`energy.generation.period`)에서 온다.\n *\n * 부하(`kWh`)와 더하지 않는다: 발전은 설비가 만든 양이고 부하는 계량 지점을 지난 양이다. 두 수를\n * 더하면 어느 쪽도 아닌 숫자가 된다.\n *\n * 성과지표(ISO 50001 EnPI)와 태양광 성능비가 이 값을 분자로 쓴다. 발전 사실이 없으면 `undefined`\n * 다(0 이 아니다 — 「발전이 없다」와 「발전 설비가 없다」는 다르다).\n */\n generated?: {\n /** 창에 온전히 든 기간들의 합(kWh). */\n kWh: number\n /** 센 기간 수. */\n counted: number\n /**\n * 그중 **관측이 기간을 다 덮지 못한** 기간 수.\n *\n * 커널이 기간마다 관측 구간을 함께 낸다. 마지막 표본이 기간 끝보다 이르면 그만큼 재지 않은\n * 것이고, 그 수를 감추면 화면이 덜 잰 합을 온전한 합으로 말한다.\n */\n partial: number\n /** 설비별 몫 — 이것은 **측정**이다(배분이 아니다). 각 설비의 적산에서 왔다. */\n byEquipment: { id: string; kWh: number }[]\n }\n /**\n * `kWh` 가 어느 회계에서 왔나.\n *\n * 'demand-window' 우리가 표본에서 마감한 구간 (계량이 오는 현장)\n * 'usage-period' 밖에서 마감되어 온 구간 (계량 표본이 없는 현장)\n *\n * 둘을 더하지 않는다 — 같은 전기를 두 번 센다. 둘 다 있으면 계량 쪽을 쓰고 그 사실을 낸다.\n */\n kWhBasis?: 'demand-window' | 'usage-period'\n /**\n * 기본요금을 매긴 **요금적용전력과 그 근거**.\n *\n * 이 값은 매달 다시 정해진다. 선언에 박아 두면 사람이 갱신해야 하고, 안 하면 낡은 값으로\n * 기본요금이 조용히 틀린다. 그래서 **청구서가 먼저다** — 원본이 매일 실어 보낸다.\n *\n * 'bill' 이 창을 덮는 청구서가 말한 값\n * 'carried-forward' 가장 최근 청구서의 값을 이어 쓴다(요금 제도가 실제로 그렇게 군다)\n * 'declaration' 청구가 오지 않는 현장 — 모델의 선언\n *\n * `declaredKW` 와 다르면 **선언이 낡았다**(`stale`). 그 사실을 감추면 아무도 다시 읽지 않는다.\n */\n billingDemand?: {\n kW: number\n from: 'bill' | 'carried-forward' | 'declaration'\n declaredKW?: number\n stale?: boolean\n }\n /**\n * 밖에서 마감되어 온 사용 구간 — 계량 표본이 없는 현장의 유일한 근거다.\n *\n * `basis` 별 건수를 함께 낸다: 같은 kWh 라도 계량기가 잰 것과 공급자 화면에서 긁어온 것은 무게가\n * 다르고, 실제로 긁어온 값이 망가져 있는 것을 본 적이 있다.\n */\n usage?: {\n kWh: number\n counted: number\n /** 구간마다 제 단가로 매긴 사용량 요금 — 모든 구간이 단가를 실었을 때만. */\n charge?: number\n currency?: string\n /** 단가가 없어 요금을 매기지 못한 구간 수 — 조용히 빼지 않는다. */\n unpriced: number\n byBasis: { basis: string; counted: number }[]\n }\n /**\n * 이 창에 온전히 든 **청구서** — 공급자가 확정한 금액.\n *\n * 우리 계산을 덮지 않는다. 창이 청구 주기와 정확히 같을 때만 견줄 수 있으므로 그 사실을\n * `matchesWindow` 로 함께 낸다 — 다른 창의 두 수를 빼면 그 차이는 아무것도 뜻하지 않는다.\n */\n bill?: {\n from: string\n to: string\n total?: number\n energyCharge?: number\n demandCharge?: number\n currency?: string\n billingDemandKW?: number\n matchesWindow: boolean\n /**\n * 창이 같을 때, **우리 계산에서 청구액을 뺀 값** — 0 이 아니면 우리 계산이 틀렸다는 뜻이다.\n *\n * 항목별로도 낸다(2026-08-29). 총액만 견주면 원본이 총액을 주지 않는 청구서에서는 비교가\n * 영영 나오지 않는다 — 실제로 첫 청구서에 기본요금만 있었다. 양쪽에 있는 항목만 견준다.\n */\n diff?: { total?: number; demandCharge?: number; energyCharge?: number }\n }\n /** 파생의 근거 — 구간 평균부하 × 길이(`mean-kw`). */\n basis: string\n /** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) — 알리지 않고 자르지 않는다. */\n counted: number\n skipped: number\n /** 완료 오더당 전력량 — 답할 수 없으면 `null` 과 이유. */\n intensity: { value: number | null; unit?: string; reason?: string }\n /**\n * 배전 계통의 **자리별 몫** — 「이 분기가 얼마 썼나」.\n *\n * ── 이것은 측정이 아니라 배분이다 ──────────────────────────────────────\n * 저널에 있는 에너지 사실은 **현장 총량 한 줄**이다(뿌리 계량기 합). 계량기별 구간 사실이 없으므로\n * 자리별 몫은 **나눈 것**이고, 무엇으로 나눴는지(`weightKind`)를 값과 함께 낸다. 근거를 감추면\n * 나눈 숫자가 잰 숫자와 같은 무게로 읽힌다 — CBAM·ESG 보고에서 첫 질문에 깨지는 지점이다.\n *\n * 가중치는 **작업 가동시간**이다. 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합으로\n * 파생되므로, 그 총량을 다시 정격으로 나누면 넣은 값이 그대로 나오는 동어반복이 된다.\n *\n * 그래서 이 값에는 **가정이 하나 실려 있다**: 전기가 가동시간에 비례한다. 공조·조명처럼 공정에\n * 붙지 않는 부하도 그 비율에 섞인다 — 화면이 그 문장을 함께 말해야 한다.\n */\n byFeeder?: {\n /** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(알리지 않고 버리지 않는다). */\n key: string\n kWh: number\n /**\n * 이 몫의 근거 — **커널이 정한다.**\n *\n * 선언된 부하가 여럿이면 배분(`apportioned`)이다. 선언된 부하가 **하나뿐인** 현장에서는 커널이\n * 「그 계량기는 그것만 잰다」고 읽어 `measured` 를 낸다 — 모델이 그렇게 말했으므로 그것도 사실이다.\n * 우리가 어느 쪽으로도 낙인찍지 않는다.\n */\n basis: 'apportioned' | 'measured'\n /**\n * 무엇으로 나눴나 — **배분일 때만** 있다.\n *\n * 분기마다 계량기가 있는 현장에서는 나누지 않는다(그 값이 곧 계량이다). 그때 이 칸을\n * `runtimeMs` 로 채우면 「가동시간으로 나눴다」는 없는 사실을 말하게 된다.\n */\n weightKind?: 'runtimeMs'\n /** 이 자리의 몫이 전체의 얼마였나 — 배분일 때만. */\n weightShare?: number\n /**\n * **피크가 찍힌 순간** 이 분기가 얼마였나(kW) — 요금이 걸린 수다.\n *\n * 창의 평균이나 합이 아니다: 최대수요를 낸 구간의 그 순간 값이다(다른 순간을 섞으면 「어느 분기를\n * 깎아야 피크가 내려가나」의 답이 흐려진다). 그 구간에 이 분기의 표본이 없었으면 **없다**.\n */\n peakKW?: number\n }[]\n /**\n * 나누지 못한 양과 그 이유 — **알리지 않고 0 으로 만들지 않는다.**\n * `no-weights` = 그 창에 돌아간 자원이 없다(가동시간이 전부 0). 그러면 총량은 그대로 남는다.\n */\n unattributedKWh?: number\n /**\n * 왜 나누지 못했나(커널 코드) — `no-weights`(가동시간이 전부 0) · `no-consumers`(덮는 대상 미선언) ·\n * `not-submetered`(뿌리에서 재었으나 하위 계량이 덮지 않는 부분 — 결손이 아니라 계량 범위의 사실).\n */\n unattributedReason?: string\n /**\n * 분기 계량의 합이 뿌리 계량보다 **큰** 양 — 두 계량기의 회계가 다르다는 사실이다.\n *\n * 값을 깎아 맞추지 않는다: 그것은 계량을 우리가 고쳐 쓰는 일이고, 어느 쪽이 맞는지는 현장이 안다.\n */\n submeterExcessKWh?: number\n /**\n * 배분에서 **빠진 자원 수** — 그 창에 가동 기록이 없는 설비다. 0kWh 를 주지 않는 대신 몇 대가\n * 빠졌는지 센다(대기전력이 있는 설비에서 0 은 곧 거짓이다).\n */\n excludedResources?: number\n /**\n * 이 창의 전기요금 — **청구서가 아니다.** 계산과 그 한계(TOU·래칫·최소청구액 미모델)는 커널\n * (`electricityCost`)이 갖고 여기서는 나른다. 단가가 없으면 `reason: 'no-tariff'` 로 온다.\n */\n cost?: ElectricityCost\n }\n /** 리드타임 — 생성(created)에서 완료까지. 사용자가 체감하는 소요 시간. */\n leadTime: DurationStats\n /** 작업시간 — 착수(in-progress)에서 완료까지. 대기를 뺀 실작업. */\n workTime: DurationStats\n /** 대기시간 — 생성에서 착수까지. 자원이 부족하면 여기가 늘어난다. */\n waitTime: DurationStats\n /** 자원별 점유 — 작업에 붙어 있던 시간의 합과 창 대비 비율(작업으로 설명되는 부분만). */\n utilization: { resourceId: string; busyMs: number; ratio: number }[]\n /**\n * **셈에서 빠진 것** — 완료는 창 안이지만 시작 이벤트를 못 찾은 건수.\n * 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이 알리지 않고 거짓이 된다.\n */\n unpaired: { leadTime: number; workTime: number }\n /**\n * **완료로 세지 않은 오더 상태 낱말의 인구조사** — 낱말 → 창 안에서 그 낱말로 본 건수.\n *\n * 「못 읽었다」가 **아니다.** `picking` 처럼 완료가 아닌 낱말이 여기 오는 것은 정상이다. 이 값은\n * 판정이 아니라 **재료**다: 완료가 0 인데 여기 `{ finished: 2780 }` 이 있으면 원인이 곧 보인다.\n *\n * ── 왜 필요한가, 그리고 무엇의 대체가 아닌가 ────────────────────────────────\n * 이 폴드는 완료를 **동의어 목록**으로 판정한다(`completed`·`fulfilled`·`done`·`finished`). 그 방식은\n * 원본의 낱말에서 뜻을 **추론**하는 것이고, 이 저장소가 다른 자리에서 거절하는 그것이다\n * (「선언이 있으면 변환이지만 없으면 발명이다」).\n *\n * 실제로 그 대가를 치렀다: `finished` 가 목록에 없어 첫 실 시스템의 완료 오더가 전부 0 이었다.\n * 그런데 **빈 자리는 이 목록이 아니라 커넥터였다** — 그 커넥터에 상태 표(`WORK_ORDER_STATE`)가\n * 정의돼 있으면서 오더 경로에서 쓰이지 않았고(로트 경로는 썼다), 원본 낱말이 그대로 흘렀다.\n * 목록에 `finished` 를 더한 것은 그 사실을 가린 땜빵이었다.\n *\n * **정석은 커넥터가 자기 표로 옮기고, 옮길 수 없는 낱말은 세고 넣지 않는 것이다**(재고 처분이 이미\n * 그 규율로 선다: 「표현할 길이 없는 것은 넣지 않는다」). 그러면 이 폴드는 추론하지 않아도 된다.\n *\n * 그러므로 이 값은 **동의어 목록을 늘리는 근거가 아니다.** 목록은 커넥터들이 옮기기 시작하면\n * 닫힌 어휘로 좁혀야 하고, 그때까지 이 값이 어긋남을 말해 준다. 순서가 중요하다 — 목록을 먼저\n * 좁히면 아직 옮기지 않는 원본의 완료가 그 순간 0 이 된다.\n *\n * 상태를 말하지 않은 사건은 여기 오지 않는다. 그것은 「모르는 낱말」이 아니라 다른 결함이고\n * (유입 문이 상태를 필수로 받는다) 그 진단은 그 문의 몫이다.\n */\n orderStatusNotCounted: Record<string, number>\n /**\n * 소비한 이벤트 수 — **넘겨받은 배열 전체**(창 앞 lookback 포함). 진단용이다.\n *\n * \"측정된 기록이 있는가\" 의 판단에는 쓰지 말 것 — 호출부는 창보다 앞까지 읽어 넘기므로(완료된 작업의\n * 시작이 창 밖일 수 있다) 창 안이 비어 있어도 이 값은 0 이 아니다. 그 판단은 `eventsInWindow` 로 한다.\n */\n eventsSeen: number\n /**\n * **창 안에** 들어온 이벤트 수 — \"이 구간에 측정할 기록이 있었는가\" 의 정답.\n *\n * 이것이 0 이면 \"처리량 0\" 이 아니라 **\"기록이 없다\"** 다. 두 상황을 섞으면 사용자가 잘못된 결론을\n * 낸다(앞 구간의 기록만 읽어 놓고 \"0건 처리\" 라고 말하는 일이 실제로 가능했다).\n */\n eventsInWindow: number\n /** 요청한 관점으로 쪼갠 값(요청했을 때만). */\n groups?: { by: KpiGroupBy; items: KpiGroup[]; truncated?: number; truncatedTasks?: number }\n}\n\n/**\n * 창의 전력량을 **배전 계통의 자리별로 나눈다** — 근거를 함께.\n *\n * ── 왜 커널 함수를 쓰나 ─────────────────────────────────────────────────────\n * 「나눌 근거가 없으면 나누지 않는다」·「합은 보존된다」·「몫이 0 이면 0kWh 를 주지 않는다」는 규칙이\n * 커널의 `attributeEnergy` 에 이미 있다. 여기서 다시 쓰면 두 벌이 되고, 그때부터 두 화면이 다른 합을\n * 말한다. 이 함수가 하는 일은 **재료를 모아 넘기고 답을 자리별로 묶는 것**뿐이다.\n *\n * ── 가중치는 가동시간이다 ───────────────────────────────────────────────────\n * 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합에서 파생되므로 그 총량을 다시 정격으로\n * 나누면 넣은 값이 그대로 나온다(새 정보 0의 동어반복). 가동시간은 **저널이 관측한 사실**이다.\n *\n * 자리를 모르는 자원은 `unknown` 으로 모인다 — 빼면 합이 줄고, 아무 자리에 넣으면 거짓이 된다.\n */\nfunction feederShares(\n kWh: number | undefined,\n utilization: { resourceId: string; busyMs: number }[],\n resourceFeeder?: Record<string, string>\n): Partial<NonNullable<KpiResult['energy']>> {\n /* 선언이 없으면 분해하지 않는다 — 자리를 모르는 채 나눈 숫자는 아무 것의 몫도 아니다. */\n if (!resourceFeeder || !Object.keys(resourceFeeder).length) return {}\n if (kWh === undefined || !(kWh > 0)) return {}\n\n /*\n * ── 풀이 **무엇을 덮는지 사실대로** 말한다 (2026-08-17) ────────────────────\n *\n * 처음에는 「그 창에 돌아간 자원」만 덮는다고 넘겼다. 그러자 돌아간 자원이 하나인 창에서 커널이\n * 그것을 **`measured`(전용 계량)** 로 판정했다 — 커널의 규칙은 옳다(「덮는 소비처가 하나면 그\n * 계량기는 그것만 잰다는 선언이다」). 틀린 것은 내 선언이었다: 현장 계량기는 돌아간 것만 재지 않는다.\n *\n * 그래서 **선언된 부하 전부**를 덮는다고 말한다(`resourceFeeder` 의 키가 곧 그 선언이다).\n * 그러면 그 창에 돌지 않은 설비는 몫 0 으로 **배분에서 빠지고 그 수가 보고된다** — 0kWh 를 주는\n * 대신 「이 설비는 이 창에 가동 기록이 없다」를 말하는 쪽이다.\n */\n /*\n * 덮는 대상은 **선언된 부하 ∪ 그 창에 돌아간 자원**이다.\n *\n * 선언만 쓰면, 돌았는데 전기 계통에 선언되지 않은 설비의 몫이 나머지 설비에 얹힌다 — 그 설비가\n * 안 쓴 전기를 쓴 것으로 만든다. 돌아간 것만 쓰면, 선언됐지만 쉰 설비가 사라져 「이 창에 안 돌았다」를\n * 말할 수 없다. 둘을 합해야 두 사실이 다 남는다(자리를 모르는 몫은 `unknown` 으로 모인다).\n */\n const busyOf = new Map(utilization.map(u => [u.resourceId, u.busyMs]))\n const ids = new Set<string>([...Object.keys(resourceFeeder), ...busyOf.keys()])\n const consumers = [...ids].map(id => ({ id, weight: busyOf.get(id) ?? 0 }))\n if (!consumers.length) return {}\n\n const r = attributeEnergy({\n pools: [{ meterId: 'site', kWh, consumerIds: consumers.map(c => c.id) }],\n consumers,\n weightKind: 'runtimeMs'\n })\n\n const byKey = new Map<string, { kWh: number; weightShare: number; apportioned: boolean }>()\n for (const sh of r.shares) {\n /* 자리를 모르면 `unknown` — 그 몫이 있다는 사실은 남기고, 어디의 것인지는 지어내지 않는다. */\n const key = resourceFeeder[sh.consumerId] ?? 'unknown'\n const cur = byKey.get(key) ?? { kWh: 0, weightShare: 0, apportioned: false }\n cur.kWh += sh.kWh\n cur.weightShare += sh.weightShare ?? 0\n /* **근거는 커널이 정한다.** 여기서 낙인찍으면 커널이 「쟀다」고 한 것을 「나눴다」고 바꿔 말하거나\n 그 반대가 된다. 섞이면 약한 쪽(배분)으로 적는다 — 강한 주장을 올려 잡지 않는다. */\n if (sh.basis === 'apportioned') cur.apportioned = true\n byKey.set(key, cur)\n }\n\n const byFeeder = [...byKey.entries()]\n .map(([key, v]) => ({\n key,\n kWh: v.kWh,\n basis: (v.apportioned ? 'apportioned' : 'measured') as 'apportioned' | 'measured',\n weightKind: 'runtimeMs' as const,\n weightShare: v.weightShare\n }))\n .sort((a, b) => b.kWh - a.kWh)\n\n const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)\n return {\n ...(byFeeder.length ? { byFeeder } : {}),\n ...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),\n ...(r.excluded.length ? { excludedResources: r.excluded.length } : {})\n }\n}\n\n/**\n * 분기별 전력량을 **계량으로** 읽는다 — 계량이 있으면 배분하지 않는다.\n *\n * ── 왜 필요했나 (2026-08-18) ────────────────────────────────────────────────\n * 실 게이트웨이는 분기마다 계량기를 보낸다. 그런데 화면은 「나누지 못함」으로 답하고 있었다 — 여기에\n * 가동시간 배분 경로만 있었기 때문이다. 계량이 있는데 배분으로 말하면 **근거를 실제보다 약하게** 보고하는\n * 것이고, 그 위에 세운 SEU 판단이 무거운 값을 놓친다. 미러 트윈에는 설비 가동 기록조차 없어서\n * (원천이 계량만 준다) 그 경로로는 분기별 값이 영원히 나오지 않았다.\n *\n * 값의 출처는 커널이 구간 마감 사실에 실어 주는 **지점별 몫**이다(적산의 뺄셈). 표본을 직접 읽지 않는\n * 이유는 조회 상한이다 — 계측은 초당 여러 건이라 표본으로는 창을 채우기 전에 상한에 걸린다.\n *\n * 나머지(뿌리 − 분기 합)는 **버리지도 얹지도 않는다**: 수전 직결 부하·손실·계량되지 않은 회로가 그\n * 차이이므로, 어느 분기에 얹으면 그 분기가 쓰지 않은 전기를 쓴 것으로 만든다. 커널의 `remainder` 풀로\n * 넘겨 「하위 계량이 덮지 않는다」로 남긴다.\n *\n * 분기 계량 합이 뿌리보다 크면(회계가 다른 두 계량기) **그 사실을 낸다** — 값을 깎아 맞추면 계량을\n * 우리가 고쳐 쓰는 것이다.\n */\n/**\n * 창의 **발전량** — 커널이 마감한 기간 사실을 합한다.\n *\n * ── 왜 합이 전부인가 ───────────────────────────────────────────────────────\n * 기간 발전량은 커널이 이미 확정한 사실이다(적산의 종류마다 다른 규칙으로 얻는다). 여기서 다시\n * 계산하면 규칙이 두 벌이 되고, 그때부터 두 화면이 다른 발전량을 말한다. 이 함수가 하는 일은\n * **창에 든 것을 고르고, 설비별로 묶고, 근거가 얼마나 덮였는지 세는 것**이다.\n *\n * ── 걸친 기간은 세지 않는다 ─────────────────────────────────────────────────\n * 구간 마감과 같은 규칙이다. 하루의 절반이 창에 들었다고 절반을 세면 그것은 관측이 아니라 우리가 나눈\n * 값이고, 합이 창을 넘는다.\n *\n * ── 덜 덮인 기간을 센다 ─────────────────────────────────────────────────────\n * 기간의 끝이 자정인데 마지막 표본이 저녁이었으면 그 사이는 재지 않은 것이다. 그 수를 함께 내야 화면이\n * 「이 합은 이만큼 덜 잰 것」이라고 말할 수 있다. 얼마나 어긋나면 문제인지는 현장이 정한다.\n */\nfunction generatedOfPeriods(\n periods: readonly {\n equipmentId: string\n kWh: number\n startMs: number\n endMs: number\n observedFromMs: number\n observedToMs: number\n }[],\n range: { startMs: number; endMs: number }\n): Partial<NonNullable<KpiResult['energy']>> {\n const inside = periods.filter(g => g.startMs >= range.startMs && g.endMs <= range.endMs)\n if (!inside.length) return {}\n\n const byEquipment = new Map<string, number>()\n let partial = 0\n for (const g of inside) {\n byEquipment.set(g.equipmentId, (byEquipment.get(g.equipmentId) ?? 0) + g.kWh)\n const coveredFrom = Number.isFinite(g.observedFromMs) ? g.observedFromMs <= g.startMs : false\n const coveredTo = Number.isFinite(g.observedToMs) ? g.observedToMs >= g.endMs : false\n if (!coveredFrom || !coveredTo) partial++\n }\n\n return {\n generated: {\n kWh: inside.reduce((a, g) => a + g.kWh, 0),\n counted: inside.length,\n partial,\n byEquipment: [...byEquipment.entries()]\n .map(([id, kWh]) => ({ id, kWh }))\n .sort((a, b) => b.kWh - a.kWh)\n }\n }\n}\n\n/**\n * 밖에서 마감되어 온 사용 구간을 합한다 — **구간 마감과 더하지 않는다.**\n *\n * 두 회계가 같은 전기를 말한다. 더하면 두 번 세고, 둘 다 있는 현장에서는 계량 쪽이 강한 근거이므로\n * 그쪽을 쓴다. 여기서는 이쪽 몫만 계산하고, 무엇을 쓸지는 부르는 쪽이 정한다.\n *\n * 단가는 **모든 구간이 실었을 때만** 요금을 만든다. 일부만 실었는데 있는 것만 더하면 그 합이 창의\n * 요금인 척한다 — 못 매긴 구간 수를 함께 낸다.\n */\nfunction usageOfPeriods(\n periods: readonly { meterId: string; kWh: number; startMs: number; endMs: number; unitPrice: number; currency?: string; basis: string }[],\n range: { startMs: number; endMs: number }\n): NonNullable<KpiResult['energy']>['usage'] | undefined {\n const inside = periods.filter(u => u.startMs >= range.startMs && u.endMs <= range.endMs)\n if (!inside.length) return undefined\n\n const byBasis = new Map<string, number>()\n let kWh = 0\n let charge = 0\n let unpriced = 0\n let currency: string | undefined\n for (const u of inside) {\n kWh += u.kWh\n byBasis.set(u.basis, (byBasis.get(u.basis) ?? 0) + 1)\n if (Number.isFinite(u.unitPrice)) {\n charge += u.kWh * u.unitPrice\n if (!currency) currency = u.currency\n } else unpriced++\n }\n\n return {\n kWh,\n counted: inside.length,\n ...(unpriced === 0 && currency ? { charge, currency } : {}),\n unpriced,\n byBasis: [...byBasis.entries()].map(([basis, counted]) => ({ basis, counted })).sort((a, b) => b.counted - a.counted)\n }\n}\n\n/**\n * **요금적용전력을 고른다 — 청구서가 먼저다.**\n *\n * 순서에 뜻이 있다. 청구서는 그 기간에 실제로 매겨진 값이고, 선언은 사람이 옮겨 적은 값이다.\n * 그 값이 매달 바뀌므로 옮겨 적은 것은 곧 낡는다.\n */\nfunction billingDemandOf(\n billsInWindow: readonly { endMs: number; billingDemandKW: number }[],\n lastBill: { billingDemandKW?: number } | undefined,\n declaredKW: number | undefined\n): NonNullable<KpiResult['energy']>['billingDemand'] | undefined {\n const declared = Number.isFinite(Number(declaredKW)) ? Number(declaredKW) : undefined\n const withValue = billsInWindow.filter(b => Number.isFinite(b.billingDemandKW))\n const fromBill = withValue.length ? withValue.reduce((a, x) => (x.endMs > a.endMs ? x : a)).billingDemandKW : undefined\n const carried = Number.isFinite(Number(lastBill?.billingDemandKW)) ? Number(lastBill?.billingDemandKW) : undefined\n\n const picked =\n fromBill !== undefined\n ? { kW: fromBill, from: 'bill' as const }\n : carried !== undefined\n ? { kW: carried, from: 'carried-forward' as const }\n : declared !== undefined\n ? { kW: declared, from: 'declaration' as const }\n : undefined\n if (!picked) return undefined\n\n /* 선언이 있는데 청구서와 다르면 그 사실을 낸다 — 낡은 선언은 스스로 말해야 한다. */\n const stale = picked.from !== 'declaration' && declared !== undefined && declared !== picked.kW\n return {\n ...picked,\n ...(declared !== undefined ? { declaredKW: declared } : {}),\n ...(stale ? { stale: true } : {})\n }\n}\n\n/**\n * 창에 온전히 든 청구서 하나 — 가장 늦게 끝난 것.\n *\n * **창이 청구 주기와 같을 때만 견준다.** 한 달 청구서를 하루 창의 값과 빼면 그 차이는 아무것도 뜻하지\n * 않는다. 그래서 같은지를 값과 함께 낸다.\n */\nfunction billOfWindow(\n bills: readonly {\n from: string\n to: string\n startMs: number\n endMs: number\n total: number\n energyCharge: number\n demandCharge: number\n currency?: string\n billingDemandKW: number\n }[],\n range: { startMs: number; endMs: number },\n ours?: { total?: number; demandCharge?: number; energyCharge?: number }\n): NonNullable<KpiResult['energy']>['bill'] | undefined {\n const inside = bills.filter(b => b.startMs >= range.startMs && b.endMs <= range.endMs)\n if (!inside.length) return undefined\n const b = inside.reduce((a, x) => (x.endMs > a.endMs ? x : a))\n const matchesWindow = b.startMs === range.startMs && b.endMs === range.endMs\n /* 양쪽에 있는 항목만 견준다 — 한쪽이 없는 것을 0 으로 두면 그 차이가 통째로 어긋남처럼 보인다. */\n const gap = (mine: unknown, billed: number): number | undefined =>\n Number.isFinite(Number(mine)) && Number.isFinite(billed) ? Number(mine) - billed : undefined\n const diff = matchesWindow\n ? {\n ...(gap(ours?.total, b.total) !== undefined ? { total: gap(ours?.total, b.total) as number } : {}),\n ...(gap(ours?.demandCharge, b.demandCharge) !== undefined\n ? { demandCharge: gap(ours?.demandCharge, b.demandCharge) as number }\n : {}),\n ...(gap(ours?.energyCharge, b.energyCharge) !== undefined\n ? { energyCharge: gap(ours?.energyCharge, b.energyCharge) as number }\n : {})\n }\n : {}\n return {\n from: b.from,\n to: b.to,\n ...(Number.isFinite(b.total) ? { total: b.total } : {}),\n ...(Number.isFinite(b.energyCharge) ? { energyCharge: b.energyCharge } : {}),\n ...(Number.isFinite(b.demandCharge) ? { demandCharge: b.demandCharge } : {}),\n ...(b.currency ? { currency: b.currency } : {}),\n ...(Number.isFinite(b.billingDemandKW) ? { billingDemandKW: b.billingDemandKW } : {}),\n matchesWindow,\n ...(Object.keys(diff).length ? { diff } : {})\n }\n}\n\nfunction measuredFeeders(\n windows: readonly {\n startMs: number\n endMs: number\n maxKW?: number\n points: { id: string; kWh: number; root: boolean }[]\n pointsAtPeak?: { id: string; kW: number }[]\n }[],\n range: { startMs: number; endMs: number },\n totalKWh: number | undefined,\n meterFeeder?: Record<string, string>\n): Partial<NonNullable<KpiResult['energy']>> | undefined {\n if (!meterFeeder || !Object.keys(meterFeeder).length) return undefined\n if (totalKWh === undefined || !(totalKWh > 0)) return undefined\n\n /* 창에 온전히 든 구간만 — 전체 전력량과 같은 규칙이어야 몫과 합이 같은 창을 말한다. */\n const byFeederKWh = new Map<string, number>()\n let sawPoints = false\n for (const w of windows) {\n if (!(w.startMs >= range.startMs && w.endMs <= range.endMs)) continue\n for (const p of w.points ?? []) {\n const feeder = meterFeeder[p.id]\n if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다\n sawPoints = true\n byFeederKWh.set(feeder, (byFeederKWh.get(feeder) ?? 0) + p.kWh)\n }\n }\n if (!sawPoints) return undefined // 계량이 없으면 이 경로를 쓰지 않는다(배분으로 돌아간다)\n\n const feeders = [...byFeederKWh.entries()].filter(([, kWh]) => kWh > 0)\n if (!feeders.length) return undefined\n const submetered = feeders.reduce((sum, [, kWh]) => sum + kWh, 0)\n const remainder = totalKWh - submetered\n\n const r = attributeEnergy({\n pools: [\n ...feeders.map(([feeder, kWh]) => ({ meterId: `${feeder}:meter`, kWh, consumerIds: [feeder] })),\n ...(remainder > 0 ? [{ meterId: 'root:remainder', kWh: remainder, consumerIds: [], remainder: true }] : [])\n ],\n consumers: feeders.map(([feeder]) => ({ id: feeder }))\n })\n\n /*\n * ── 피크를 만든 분기 (2026-08-18) ─────────────────────────────────────────\n *\n * 요금은 kW 로 매겨지므로 「어느 분기를 깎아야 피크가 내려가나」가 실무의 질문이다. 그 답은 **피크가\n * 찍힌 구간의 그 순간** 값이고, 창 안의 여러 구간을 더하거나 평균하면 뜻이 사라진다(다른 순간의 값을\n * 섞는 것이다). 그래서 **최대수요를 낸 구간 하나**만 골라 그 순간의 지점 값을 쓴다.\n */\n const peakWindow = windows\n .filter(w => w.startMs >= range.startMs && w.endMs <= range.endMs && Number.isFinite(Number(w.maxKW)))\n .reduce<(typeof windows)[number] | undefined>((best, w) => (!best || Number(w.maxKW) > Number(best.maxKW) ? w : best), undefined)\n const peakOf = new Map<string, number>()\n for (const p of peakWindow?.pointsAtPeak ?? []) {\n const feeder = meterFeeder[p.id]\n if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다\n peakOf.set(feeder, (peakOf.get(feeder) ?? 0) + p.kW)\n }\n\n const byFeeder = r.shares\n .map(sh => ({\n key: sh.consumerId,\n kWh: sh.kWh,\n basis: sh.basis,\n weightKind: undefined,\n weightShare: undefined,\n /* 피크 순간의 값이 없으면 만들지 않는다 — 그 구간에 이 분기의 표본이 없었다는 뜻이다. */\n ...(peakOf.has(sh.consumerId) ? { peakKW: peakOf.get(sh.consumerId) } : {})\n }))\n .sort((a, b) => b.kWh - a.kWh) as NonNullable<KpiResult['energy']>['byFeeder']\n\n const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)\n return {\n ...(byFeeder?.length ? { byFeeder } : {}),\n ...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),\n /* 분기 합이 뿌리를 넘었다 — 두 계량기의 회계가 다르다는 사실이다(깎아 맞추지 않는다). */\n ...(remainder < 0 ? { submeterExcessKWh: -remainder } : {})\n }\n}\n\n/** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */\nexport interface KpiFoldOptions {\n groupBy?: KpiGroupBy\n /**\n * 이 트윈이 이어받고 있는 **마지막 청구서** — 요금적용전력의 우선 근거(§`billingDemandOf`).\n *\n * 창 밖의 청구서는 저널 조회에 걸리지 않는다. 그런데 그 값은 이 창에도 적용되므로 호출부가\n * 커널 상태에서 읽어 넘긴다.\n */\n lastBill?: { to: string; billingDemandKW?: number }\n /**\n * 노드 → 구역 지도. **이벤트에는 구역이 없다**(노드까지만 있다) — 스냅샷·마스터가 아는 값이라\n * 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(알리지 않고 다른 축으로 바꾸지 않는다).\n */\n locationArea?: Record<string, string>\n /**\n * 자원 → **배전 계통의 자리**(분기·수전) 지도.\n *\n * ── 왜 밖에서 오나 ───────────────────────────────────────────────────────\n * 이벤트에는 전기 계통이 없다. 「이 설비가 어느 분기에서 먹는가」는 에너지 트윈의 모델이 아는\n * 선언이고(설비 하나에 사실 둘 — 공장에서는 자원, 전기에서는 부하), 그 선언을 읽는 것은 호출부의 일이다.\n *\n * 없으면 분기 분해를 **하지 않는다** — 자리를 모르는 채 나누면 그 숫자는 아무 것의 몫도 아니다.\n */\n resourceFeeder?: Record<string, string>\n /**\n * **계량 지점 → 그것이 재는 분기** 지도.\n *\n * 있으면 분기별 전력량을 계량으로 읽는다(배분보다 강한 근거). 이 지도는 모델의 선언에서 오고\n * (계량기의 자리), 그것을 읽는 것은 호출부의 일이다 — 폴드는 저널만 본다.\n */\n meterFeeder?: Record<string, string>\n /**\n * 선언된 **전기요금 단가** — 수를 금액으로 바꾸는 유일한 입력.\n *\n * 이벤트에는 요금이 없다. 단가는 현장의 계약이고(에너지 트윈 모델의 수전 자리에 선언한다), 그것을\n * 읽는 것은 호출부의 일이다. 없으면 금액을 만들지 않는다 — 짐작한 단가로 낸 금액은 숫자가 있다는\n * 것만으로 사람을 결정으로 밀어붙인다.\n */\n tariff?: TariffDeclaration\n /**\n * 교대 선언 — **이벤트에는 교대가 없다**(시각만 있다). 현장 마스터가 아는 값이라 호출부가 넘긴다.\n * 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로 알리지 않고 바꾸지 않는다).\n */\n shifts?: WorkCalendarEntry[]\n /**\n * 현장의 시각 기준(분) — 교대 축이 `HH:MM` 을 어느 기준으로 읽을지. **공간이 갖는 값**이다\n * (테넌트가 아니다: 한 테넌트가 다른 시간대의 공장을 함께 가질 수 있다). 없으면 UTC.\n */\n utcOffsetMinutes?: number\n /**\n * 축의 상한(기본 20). 넘치면 잘라내고 **잘린 축의 수와 그 안의 건수를 함께 알린다**.\n *\n * 축의 수만 알리면 부족하다 — \"그 외 89개\" 로는 그것이 3건인지 300건인지 모른다. 보여준 축이 전부인\n * 것처럼 읽히면 사용자가 꼬리를 놓친다.\n */\n groupLimit?: number\n}\n\n/* ── 관점 축의 재료 ─────────────────────────────────────────────────────────\n * 축의 값은 이벤트 payload 에 이미 들어 있다(`TaskStatusDelta`: kind·orderId·fromNode·toNode·\n * resourceRef). 새 계측을 심을 필요가 없다 — **이미 쌓인 저널을 다르게 묶기만** 한다.\n */\n/** 창 안에 완료된 한 건의 기록 — 통계의 원료이자 관점 축의 원료. */\ninterface CompletionRecord {\n taskId: string\n at: number\n facets: TaskFacets\n leadMs?: number\n workMs?: number\n waitMs?: number\n}\n\n/** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(알리지 않고 버리지 않는다). */\nfunction facetKey(rec: CompletionRecord, options: KpiFoldOptions): string {\n const f = rec.facets\n switch (options.groupBy) {\n case 'resource':\n return f.resource ?? 'unknown'\n case 'taskKind':\n return f.kind ?? 'unknown'\n case 'location':\n return f.location ?? 'unknown'\n case 'order':\n return f.order ?? 'unknown'\n case 'area':\n /* 구역은 이벤트 밖의 지식이다 — 지도가 없으면 없다고 말한다(노드 id 로 대체하면 사용자가\n * 그것을 구역으로 오해한다). */\n return (f.location && options.locationArea?.[f.location]) ?? 'unknown'\n case 'shift':\n /*\n * 교대는 **저널에 찍혀 있지 않다 — 계산한다.**\n *\n * 이벤트에 교대 이름을 스탬프하면 두 가지가 무너진다: ① 같은 사실이 두 곳에 생기고,\n * ② 나중에 교대 시간을 고치면 **과거가 옛 구분으로 굳어** 다시 셀 수 없다. 교대는\n * `완료 시각 + 현장의 시각 기준 + 교대 선언`에서 나오는 순수 파생이므로 여기서 낸다 —\n * 라이브·히스토리·예측이 **커널과 같은 함수**(`activeShiftOf`)를 쓴다.\n *\n * 선언이 없으면 'unknown' 이다. 교대를 나눠 놓지 않은 현장에 이름을 지어내지 않는다.\n */\n if (!options.shifts?.length) return 'unknown'\n /* 시각까지 넘긴다 — **휴일에 일어난 일은 어느 교대에도 속하지 않는다.** 되풀이만 보면 그날 서지도\n 않은 교대에 집계돼, 교대별 성과가 알리지 않고 거짓이 된다. */\n return activeShiftAt(options.shifts, rec.at, options.utcOffsetMinutes) ?? 'unknown'\n default:\n return 'unknown'\n }\n}\n\n/**\n * 이 축의 품질 — **판정이 있었던 것만** 센다(없으면 필드를 만들지 않는다).\n *\n * 양품률의 이력이 여기서 나온다: 커널이 작업 완료에 결과를 적고(`TaskStatusDelta.outcome`), 폴드가 축으로\n * 들고(`TaskFacets.outcome`), 여기서 종류별로 센다. 그 사슬이 없으면 「이 공정의 양품률」은 저널에\n * 남지 않는다(설비 누적 카운터는 종류를 모른다).\n */\nfunction qualityOf(rows: CompletionRecord[]): { quality?: { good: number; scrap: number } } {\n let good = 0\n let scrap = 0\n for (const r of rows) {\n const o = r.facets.outcome\n if (o === 'good') good++\n else if (o === 'scrap') scrap++\n }\n return good + scrap > 0 ? { quality: { good, scrap } } : {}\n}\n\n/**\n * 기록을 축으로 묶어 축별 통계를 낸다 — **전체와 같은 규칙**으로(같은 stats 함수, 같은 창).\n *\n * 축의 값이 아주 많을 수 있다(오더 축은 수천 개). 그래서 건수 큰 것부터 상한까지만 돌려주고\n * **잘라낸 수를 함께 알린다** — 들어온 것이 없는 절단은 \"이게 전부\" 라는 거짓을 만든다.\n */\nfunction groupRecords(\n records: CompletionRecord[],\n options: KpiFoldOptions\n): { by: KpiGroupBy; items: KpiGroup[]; truncated?: number; truncatedTasks?: number } {\n const by = options.groupBy as KpiGroupBy\n const bins = new Map<string, CompletionRecord[]>()\n for (const rec of records) {\n const key = facetKey(rec, options)\n const bin = bins.get(key)\n if (bin) bin.push(rec)\n else bins.set(key, [rec])\n }\n\n const all = [...bins.entries()]\n .map(([key, rows]) => ({\n key,\n tasks: rows.length,\n leadTime: stats(rows.map(r => r.leadMs).filter((v): v is number => v !== undefined)),\n workTime: stats(rows.map(r => r.workMs).filter((v): v is number => v !== undefined)),\n waitTime: stats(rows.map(r => r.waitMs).filter((v): v is number => v !== undefined)),\n busyMs: rows.reduce((a, r) => a + (r.workMs ?? 0), 0),\n ...qualityOf(rows)\n }))\n .sort((a, b) => b.tasks - a.tasks || (a.key < b.key ? -1 : 1))\n\n const limit = Math.max(1, Math.min(100, options.groupLimit ?? 20))\n const items = all.slice(0, limit)\n const dropped = all.slice(limit)\n /* 축별로 \"창 대비 비율\" 은 내지 않는다 — 자원 아닌 축(오더·작업종류)에서 그 비율은 의미가 없고,\n * 의미 없는 숫자를 내면 사용자가 그것으로 판단한다. 자원 점유는 `utilization` 이 이미 낸다. */\n return {\n by,\n items,\n ...(dropped.length\n ? { truncated: dropped.length, truncatedTasks: dropped.reduce((a, g) => a + g.tasks, 0) }\n : {})\n }\n}\n\nfunction ms(value: string | undefined): number | null {\n if (!value) return null\n const t = Date.parse(value)\n return Number.isFinite(t) ? t : null\n}\n\n/** 분위수 — 정렬된 값에서 선형 보간 없이 가장 가까운 아래 값(작은 표본에서 과장하지 않는다). */\nfunction quantile(sorted: number[], q: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(q * sorted.length)))\n return sorted[idx]\n}\n\nfunction stats(values: number[]): DurationStats {\n if (values.length === 0) return { count: 0, p10Ms: 0, p50Ms: 0, p90Ms: 0, avgMs: 0 }\n const sorted = [...values].sort((a, b) => a - b)\n const sum = sorted.reduce((a, b) => a + b, 0)\n return {\n count: sorted.length,\n p10Ms: quantile(sorted, 0.1),\n p50Ms: quantile(sorted, 0.5),\n p90Ms: quantile(sorted, 0.9),\n avgMs: Math.round(sum / sorted.length)\n }\n}\n\n/**\n * 이벤트를 계산해 시간창 KPI 를 만든다.\n *\n * **호출부 규약**: `events` 는 창보다 **넉넉히 앞까지** 담아 넘긴다(예: 창 시작 − 리드타임 상한).\n * 완료된 작업의 시작 이벤트가 창 밖에 있을 수 있기 때문이다. 못 찾은 것은 `unpaired` 로 알린다 —\n * 평균에서 오류 없이 빠지면 지표가 거짓이 된다.\n *\n * 시간축은 `eventTime`(ISO) 차분이다. sim 클록이 실제 시각과 오프셋이 있어도 **차분은 정확**하다\n * (고정 오프셋은 상쇄된다). 파싱 불가한 줄은 버린다.\n */\nexport function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldOptions = {}): KpiResult {\n /* 작업 짝맞춤은 **커널 규칙**으로(중복 구현 금지 — 두 곳에 적으면 같은 저널로 다른 답이 나온다). */\n const { records: tasks } = foldTaskRecords(events.filter(e => e.eventType === 'task.status'))\n let completedOrders = 0\n /* 완료로 읽지 못한 오더 상태 낱말 → 건수(§`orderStatusNotCounted`). */\n const orderStatusNotCounted = new Map<string, number>()\n /* 이미 완료로 센 오더 — 폴링 원본이 같은 종결을 다시 보내도 한 번만 센다. */\n const completedOrderIds = new Set<string>()\n let seen = 0\n let inWindow = 0\n\n for (const e of events) {\n const at = ms(e.eventTime)\n if (at === null) continue\n seen++\n if (at >= window.fromMs && at <= window.toMs) inWindow++\n const d = e.payload?.data ?? e.payload ?? {}\n\n if (e.eventType === 'order.status') {\n /*\n * **끝났나는 커널이 답한다** (2026-08-23) — `isOrderTerminal`.\n *\n * 예전에는 여기서 **동의어 목록**으로 추론했다(`completed`·`fulfilled`·`done`·`finished`).\n * 그 방식이 낸 대가가 둘이었고 둘 다 들어온 것이 없었다.\n *\n * ① 원본의 낱말이 목록에 없으면 완료가 0 이 된다. 실제로 그랬다 — 첫 실 시스템의 `FINISHED`\n * 가 빠져 있었고, 그때 목록에 낱말을 더한 것은 진짜 빈 자리(그 커넥터가 자기 상태 표를\n * 안 쓰고 있었다)를 가린 땜빵이었다.\n * ② **시뮬 트윈에서는 언제나 0 이었다.** 시뮬은 오더를 이행하며 `fulfilled` 만 올리고\n * `status` 는 바꾸지 않는다 — 낱말로 묻는 판정은 시뮬의 완료를 한 건도 세지 못했다.\n *\n * 표준이 그 축을 갈라 두었다: `orders.status` ↔ `RequestState`(표준도 열거하지 않는다)이고,\n * 「됐나」는 **실적**에서 읽는다(`JobResponse`, 요구는 `SegmentRequirement.Quantity`). 그래서\n * 판정의 1차 근거는 **양**이고 낱말은 양으로 말할 수 없는 종결(취소·중도 종료)에만 쓴다.\n */\n const status = String(d.status ?? '').toLowerCase()\n const done = isOrderTerminal({ status: d.status, requested: d.requested, fulfilled: d.fulfilled })\n if (at >= window.fromMs && at <= window.toMs) {\n /*\n * **오더당 한 번만 센다.** 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수\n * 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이 알리지 않고 부풀려진다.\n * 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있다(「완료는 작업당 하나」) — 오더만 없었다.\n */\n if (done) {\n const id = String(d.orderId ?? '')\n if (id && !completedOrderIds.has(id)) {\n completedOrderIds.add(id)\n completedOrders++\n } else if (!id) completedOrders++ // 정체를 모르면 셈에서 빼지 않는다(알리지 않고 줄이지 않는다)\n }\n /*\n * **완료로 세지 않은 낱말의 인구조사.** 「못 읽었다」가 아니다 — `picking` 처럼 완료가 아닌\n * 낱말이 여기 오는 것은 정상이다. 완료가 0 인데 여기 낱말이 쌓여 있으면 원인이 곧 보인다.\n *\n * 빈 낱말은 세지 않는다: 상태를 말하지 않은 사건은 다른 결함이고(유입 문이 상태를 필수로\n * 받는다) 여기서 섞으면 이 값의 뜻이 흐려진다.\n */\n else if (status) orderStatusNotCounted.set(status, (orderStatusNotCounted.get(status) ?? 0) + 1)\n }\n }\n }\n\n /*\n * 에너지 — 창에 **온전히 든** 구간 마감 사건만 센다(걸친 구간은 커널 규칙이 뺀다).\n * 사건이 하나도 없으면 이 트윈에는 에너지가 없는 것이므로 블록 자체를 내지 않는다.\n */\n const demandWindows = events\n .filter(e => e.eventType === ENERGY_EVENT.demandWindow)\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n /* 최대(`maxKW`)도 함께 넘긴다 — 요금이 걸리는 수는 평균이 아니라 최대다.\n 총부하(`grossMaxKW`)는 「무엇이 깎였나」를 되짚는 축이다(발전·방전 차감 전). */\n return {\n startMs: Number(d.startMs),\n endMs: Number(d.endMs),\n meanKW: Number(d.meanKW),\n maxKW: Number(d.maxKW),\n grossMaxKW: Number(d.grossMaxKW),\n /* 지점별 몫 — 계량이 분기에 있으면 그 값은 **측정**이다(배분이 아니다). 커널이 계층 판정\n (`root`)까지 실어 주므로 여기서 같은 규칙을 다시 쓰지 않는다. */\n points: (Array.isArray(d.points) ? d.points : [])\n .map((p: any) => ({ id: String(p?.id ?? ''), kWh: Number(p?.kWh), root: p?.root === true }))\n .filter((p: any) => p.id && Number.isFinite(p.kWh)),\n /* 피크가 찍힌 순간의 지점별 kW — 요금이 걸린 수는 kW 다(전력량이 큰 분기와 피크를 만든 분기는 다르다). */\n pointsAtPeak: (Array.isArray(d.pointsAtPeak) ? d.pointsAtPeak : [])\n .map((p: any) => ({ id: String(p?.id ?? ''), kW: Number(p?.kW) }))\n .filter((p: any) => p.id && Number.isFinite(p.kW))\n }\n })\n .filter(w => Number.isFinite(w.startMs) && Number.isFinite(w.endMs))\n\n /*\n * 발전 기간 사실 — 커널이 기간을 마감할 때 낸 것이다. 구간 마감과 같은 규칙으로 **창에 온전히 든\n * 것만** 센다(걸친 기간을 반만 세면 합이 창을 넘는다).\n *\n * 관측 구간(`observedFrom`·`observedTo`)을 함께 읽는다 — 값의 근거가 얼마나 덮였는지가 그 둘에 있다.\n */\n const generationPeriods = events\n .filter(e => e.eventType === ENERGY_EVENT.generationPeriod)\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n equipmentId: String(d.equipmentId ?? ''),\n kWh: Number(d.kWh),\n startMs: Date.parse(String(d.periodStart ?? '')),\n endMs: Date.parse(String(d.periodEnd ?? '')),\n observedFromMs: Date.parse(String(d.observedFrom ?? '')),\n observedToMs: Date.parse(String(d.observedTo ?? ''))\n }\n })\n .filter(g => g.equipmentId && Number.isFinite(g.kWh) && Number.isFinite(g.startMs) && Number.isFinite(g.endMs))\n\n /*\n * 밖에서 마감되어 온 사용 구간 — 구간 마감과 같은 규칙으로 **창에 온전히 든 것만** 센다.\n */\n const usagePeriods = events\n .filter(e => e.eventType === ENERGY_EVENT.usagePeriod)\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n meterId: String(d.meterId ?? ''),\n kWh: Number(d.kWh),\n startMs: Date.parse(String(d.from ?? '')),\n endMs: Date.parse(String(d.to ?? '')),\n unitPrice: Number(d.unitPrice),\n currency: d.currency ? String(d.currency) : undefined,\n basis: d.basis ? String(d.basis) : 'unstated'\n }\n })\n .filter(u => u.meterId && Number.isFinite(u.kWh) && Number.isFinite(u.startMs) && Number.isFinite(u.endMs))\n\n /* 청구서 — 창에 온전히 든 것 중 가장 늦게 끝난 것 하나. */\n const bills = events\n .filter(e => e.eventType === ENERGY_EVENT.bill)\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n from: String(d.from ?? ''),\n to: String(d.to ?? ''),\n startMs: Date.parse(String(d.from ?? '')),\n endMs: Date.parse(String(d.to ?? '')),\n total: Number(d.total),\n energyCharge: Number(d.energyCharge),\n demandCharge: Number(d.demandCharge),\n currency: d.currency ? String(d.currency) : undefined,\n billingDemandKW: Number(d.billingDemandKW)\n }\n })\n .filter(b => Number.isFinite(b.startMs) && Number.isFinite(b.endMs))\n\n const leads: number[] = []\n const works: number[] = []\n const waits: number[] = []\n const busyByResource = new Map<string, number>()\n /* 창 안에 완료된 건들의 **기록** — 통계를 낸 뒤에도 버리지 않는다. 관점 축은 같은 기록을 다시\n * 묶는 것일 뿐이므로, 기록을 남기면 축을 하나 더 붙이는 값이 거의 0 이 된다. */\n const records: CompletionRecord[] = []\n let tasksInWindow = 0\n let unpairedLead = 0\n let unpairedWork = 0\n\n for (const t of tasks) {\n const completed = t.completedMs\n if (completed === undefined) continue // 아직 완료되지 않은 작업 — 성과로 세지 않는다\n if (completed < window.fromMs || completed > window.toMs) continue // 성과는 완료 시점으로 센다\n tasksInWindow++\n const c = t.createdMs\n const s = t.startedMs\n const rec: CompletionRecord = { taskId: t.taskId, at: completed, facets: t.facets }\n const resource = rec.facets.resource\n if (c !== undefined && completed >= c) leads.push((rec.leadMs = completed - c))\n else unpairedLead++\n if (s !== undefined && completed >= s) {\n const busy = completed - s\n works.push((rec.workMs = busy))\n if (resource) busyByResource.set(resource, (busyByResource.get(resource) ?? 0) + busy)\n } else {\n unpairedWork++\n }\n if (c !== undefined && s !== undefined && s >= c) waits.push((rec.waitMs = s - c))\n records.push(rec)\n }\n\n const span = Math.max(1, window.toMs - window.fromMs)\n const utilization = [...busyByResource.entries()]\n .map(([resourceId, busyMs]) => ({ resourceId, busyMs, ratio: Math.min(1, busyMs / span) }))\n .sort((a, b) => b.busyMs - a.busyMs)\n\n return {\n window: { fromMs: window.fromMs, toMs: window.toMs },\n throughput: { tasks: tasksInWindow, orders: completedOrders },\n /* 발전만 있는 현장(계량 없는 발전소)도 답이 나와야 한다 — 둘 중 하나만 있어도 블록을 낸다. */\n ...(demandWindows.length || generationPeriods.length || usagePeriods.length || bills.length\n ? (() => {\n const e = energyOfWindows(demandWindows, { startMs: window.fromMs, endMs: window.toMs })\n const usage = usageOfPeriods(usagePeriods, { startMs: window.fromMs, endMs: window.toMs })\n /*\n * 어느 회계를 쓸지 정한다 — **더하지 않는다.** 계량 표본에서 우리가 마감한 값이 있으면 그것을\n * 쓰고(강한 근거), 없으면 밖에서 마감되어 온 값을 쓴다. 무엇을 썼는지 함께 낸다.\n */\n const usedKWh = e.kWh !== undefined ? e.kWh : usage?.kWh\n const kWhBasis = e.kWh !== undefined ? 'demand-window' : usage ? 'usage-period' : undefined\n /* 분모는 **완료 오더**다(대당 전력). 창에 완료가 없으면 커널 규칙이 「정의되지 않음」으로 답한다. */\n const i = energyIntensity({\n kWh: usedKWh,\n energyWindow: e.window,\n denominator: { kind: 'output', value: completedOrders, window: e.window }\n })\n /*\n * 요금은 **구간마다 제 단가로** 매긴 값이 있으면 그것을 쓴다. 단일 단가로 접으면 시간대마다\n * 다른 단가가 사라지고, 「피크를 깎아 얼마를 아끼나」가 그 차이 위에 선다.\n */\n /* 요금적용전력은 청구서가 먼저다 — 선언은 청구가 오지 않는 현장의 몫이다. */\n const billingDemand = billingDemandOf(\n bills.filter(b => Number.isFinite(b.billingDemandKW)).map(b => ({ endMs: b.endMs, billingDemandKW: b.billingDemandKW })),\n options.lastBill,\n options.tariff?.billingDemandKW\n )\n const cost = options.tariff\n ? electricityCost({\n kWh: usedKWh,\n peakKW: e.peakKW,\n tariff: { ...options.tariff, ...(billingDemand ? { billingDemandKW: billingDemand.kW } : {}) },\n ...(usage?.charge !== undefined ? { energyChargeFromPeriods: usage.charge } : {})\n })\n : undefined\n return {\n energy: {\n ...(usedKWh !== undefined ? { kWh: usedKWh } : {}),\n ...(kWhBasis ? { kWhBasis } : {}),\n ...(billingDemand ? { billingDemand } : {}),\n ...(usage ? { usage } : {}),\n /* 이 창의 최대수요 — 계약 대비 판정이 보는 수다(전력량과 같은 구간 집합에서 왔다). */\n ...(e.peakKW !== undefined ? { peakKW: e.peakKW, peakStartMs: e.peakStartMs } : {}),\n /* 깎인 양은 **같은 구간의 두 수**의 차이다 — 여기서 한 번만 계산해 두 곳이 갈라지지 않게. */\n ...(e.peakGrossKW !== undefined && e.peakKW !== undefined\n ? { peakGrossKW: e.peakGrossKW, ...(e.peakGrossKW > e.peakKW ? { shavedKW: e.peakGrossKW - e.peakKW } : {}) }\n : {}),\n basis: e.basis,\n counted: e.counted,\n skipped: e.skipped,\n intensity:\n i.value === null\n ? { value: null, reason: (i as any).reason }\n : { value: i.value, unit: (i as any).unit },\n ...(measuredFeeders(demandWindows, { startMs: window.fromMs, endMs: window.toMs }, e.kWh, options.meterFeeder) ??\n feederShares(e.kWh, utilization, options.resourceFeeder)),\n /* 금액은 같은 창의 전력량·최대수요에서 나온다 — 창이 하나뿐이라 어긋날 수 없다. */\n ...(cost ? { cost } : {}),\n ...generatedOfPeriods(generationPeriods, { startMs: window.fromMs, endMs: window.toMs }),\n /* 청구서는 사실이다 — 우리 계산을 덮지 않고 나란히 둔다(§`billOfWindow`). */\n ...(() => {\n const b = billOfWindow(bills, { startMs: window.fromMs, endMs: window.toMs }, cost as any)\n return b ? { bill: b } : {}\n })()\n }\n }\n })()\n : {}),\n leadTime: stats(leads),\n workTime: stats(works),\n waitTime: stats(waits),\n utilization,\n unpaired: { leadTime: unpairedLead, workTime: unpairedWork },\n /* 못 읽은 오더 상태 낱말 — 비면 빈 객체다(§`orderStatusNotCounted`). */\n orderStatusNotCounted: Object.fromEntries(orderStatusNotCounted),\n eventsSeen: seen,\n eventsInWindow: inWindow,\n ...(options.groupBy ? { groups: groupRecords(records, options) } : {})\n }\n}\n\n/**\n * 관점 × 비교 교차 — **\"구역별로 어제 대비 어떤가\"**.\n *\n * 축(무엇을)과 비교(언제 대비)를 따로 보면 사용자가 머릿속에서 두 표를 대조해야 한다. 운영에서 가장\n * 값 있는 질문은 그 교차다: 전체는 비슷한데 **한 구역만 나빠진** 경우를 전체 숫자로는 절대 못 본다.\n *\n * 정직성 규율:\n * · 두 구간에 **모두 있는** 축만 delta 를 낸다.\n * · 지금만 있는 축은 `isNew`(이전에 없던 일) — delta 를 건수와 같게 적으면 \"늘었다\" 로 오독된다.\n * · **사라진 축**(전에 있었고 지금 없는 것)은 따로 돌려준다. 표에서 그냥 빠지면 **멈춘 구역이\n * 눈에 보이지 않는다** — 운영에서는 이것이 가장 중요한 신호일 수 있다.\n */\nexport interface CrossedGroup extends KpiGroup {\n /** 비교 구간의 건수(두 구간에 모두 있을 때만). */\n prevTasks?: number\n /** 현재 − 비교구간 건수(두 구간에 모두 있을 때만). */\n deltaTasks?: number\n /** 비교 구간 대비 작업시간 중앙값 차이(둘 다 측정된 때만). */\n deltaWorkP50Ms?: number\n /** 비교 구간에는 없던 축 — delta 대신 이 표식을 준다. */\n isNew?: boolean\n}\n\nexport function crossGroups(\n now: KpiGroup[],\n prev: KpiGroup[]\n): { items: CrossedGroup[]; disappeared: { key: string; tasks: number }[] } {\n const before = new Map(prev.map(g => [g.key, g]))\n const items: CrossedGroup[] = now.map(g => {\n const p = before.get(g.key)\n if (!p) return { ...g, isNew: true }\n return {\n ...g,\n prevTasks: p.tasks,\n deltaTasks: g.tasks - p.tasks,\n ...(deltaP50(g.workTime, p.workTime) !== undefined ? { deltaWorkP50Ms: deltaP50(g.workTime, p.workTime) } : {})\n }\n })\n const present = new Set(now.map(g => g.key))\n const disappeared = prev.filter(g => !present.has(g.key)).map(g => ({ key: g.key, tasks: g.tasks }))\n return { items, disappeared }\n}\n\n/* ── 시간 비교 ──────────────────────────────────────────────────────────────\n * 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다(\"29초\" 가 개선인지 악화인지). 같은 길이의 창을 옮겨\n * 한 번 더 계산하면 되므로(폴드는 순수) 값싸다. 창 옮기기와 차이 계산은 계산 규칙이라 여기 있다 —\n * 조회 층에 두면 테스트가 DB 를 끌고 와야 한다.\n */\n\n/**\n * 비교 구간의 창 — 같은 **길이**로 옮긴다.\n *\n * previous: 바로 앞 구간(경계가 맞물리게 — 겹치거나 벌어지면 구간의 합이 어긋난다).\n * yesterday: 정확히 24시간 전 같은 자리. 시뮬 트윈의 클록이 실제 시각과 달라도 **차분은 정확**하다.\n */\nexport function shiftWindow(fromMs: number, toMs: number, basis: 'previous' | 'yesterday'): KpiWindow {\n const span = toMs - fromMs\n const back = basis === 'yesterday' ? 24 * 60 * 60_000 : span\n return { fromMs: fromMs - back, toMs: toMs - back }\n}\n\n/**\n * 중앙값 차이(현재 − 비교구간) — **둘 다 측정된 것만** 낸다.\n *\n * 한쪽이 결측이면 차이는 **없는 것**이다(0 이 아니다). 0 으로 내면 \"변화 없음\" 이라는 거짓이 된다.\n * 좋고 나쁨은 판단하지 않는다 — 처리량은 클수록, 소요는 작을수록 좋다는 해석은 소비처의 몫이다.\n */\nexport function deltaP50(now: DurationStats, prev: DurationStats): number | undefined {\n return now.count > 0 && prev.count > 0 ? now.p50Ms - prev.p50Ms : undefined\n}\n\n/** 구간 하나의 최소 요약 — 추세를 그릴 정보만(전체 KPI 를 구간마다 실어 보내지 않는다). */\nexport interface KpiBucket {\n fromMs: number\n toMs: number\n tasks: number\n orders: number\n /** 그 구간의 작업시간 중앙값 — 추세에서 \"느려지고 있는가\" 를 보는 값. */\n workP50Ms: number\n}\n\n/**\n * 창을 N 등분해 구간별로 처리한다 — **같은 이벤트를 창만 바꿔** 다시 계산한다(조회 추가 없음).\n *\n * 마지막 구간은 나머지를 흡수한다(부동소수 나눗셈으로 끝이 밀리면 마지막 완료가 빠진다).\n * `count <= 1` 이면 쪼갤 것이 없으므로 빈 배열 — 호출부가 \"추세 없음\" 으로 다룬다.\n */\nexport function foldKpiBuckets(events: KpiEvent[], window: KpiWindow, count: number): KpiBucket[] {\n const n = Math.max(0, Math.min(48, Math.floor(count)))\n if (n <= 1) return []\n const step = (window.toMs - window.fromMs) / n\n const out: KpiBucket[] = []\n for (let i = 0; i < n; i++) {\n const fromMs = window.fromMs + step * i\n const toMs = i === n - 1 ? window.toMs : window.fromMs + step * (i + 1)\n const b = foldKpi(events, { fromMs, toMs })\n out.push({ fromMs, toMs, tasks: b.throughput.tasks, orders: b.throughput.orders, workP50Ms: b.workTime.p50Ms })\n }\n return out\n}\n"]}
|
|
1
|
+
{"version":3,"file":"kpi-fold.js","sourceRoot":"","sources":["../../server/engine/kpi-fold.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 업무 KPI 폴드 — **이벤트 저널을 계산해** 시간창 지표를 만든다(순수).\n *\n * ── 왜 필요한가 ─────────────────────────────────────────────────────────────\n * 지금 트윈이 답할 수 있는 것은 \"이 순간의 값\"(점유 92%)과 \"인프라 부하\"(초당 이벤트 수)뿐이다.\n * 운영이 묻는 것은 다르다 — **지난 한 시간에 몇 건 처리했나, 한 건에 얼마나 걸리나, 자원은 얼마나\n * 일했나.** 그 값이 없으면 AI 는 \"점유 92%\" 는 말해도 \"목표보다 나쁜가\" 는 답할 수 없다.\n *\n * ── 원료는 이미 있다 ────────────────────────────────────────────────────────\n * 저널(`TwinEvent`)에 `task.status`·`order.status`·`equipment.status` 전이가 `eventTime`(ISO)과 함께\n * append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 — **계산만 하면 된다.**\n * 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를 계산해 OEE 카운터 생산).\n *\n * ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────\n * · 목표와 비교하지 않는다. \"95% 목표 대비\" 는 목표를 저장하는 별개 층(경영 KPI)의 몫이고,\n * 이 층은 **사실만** 생산한다. 두 층을 섞으면 목표를 정의하지 않은 현장은 사실조차 못 본다.\n * · 체류시간(dwell)은 아직 내지 않는다. 물건이 노드에 머문 시간은 item 단위 이벤트(epcis.*)를\n * 짝지어야 하고, 그 짝맞춤 규칙이 도메인마다 다르다. **없는 것을 0 으로 꾸미지 않는다.**\n * · DB·커널을 모른다. 입력은 평범한 배열이고 출력은 평범한 객체다(테스트가 값싸다).\n */\n\n/*\n * 짝맞춤은 **커널이 소유한다**(`foldTaskRecords`). 완료는 작업당 하나·나중 것이 사실, 착수는 처음 것,\n * 종류·오더는 어느 전이에서든 — 그 규칙은 커널 상태 기계에서 나오는 지식이다. 여기서 다시 적었더니\n * 공정 타임라인과 **다른 답**을 냈다(재전송된 완료를 두 건으로 세어 처리량·점유가 부풀려졌다).\n * 이 파일은 이제 \"계산한 기록 → 창 지표\" 만 한다.\n */\nimport { attributeEnergy, electricityCost, energyIntensity, energyOfWindows, foldTaskRecords, type ElectricityCost, type TariffDeclaration, type TaskFacets } from '@operato/twin-kernel'\nimport { type WorkCalendarEntry } from '@operato/ops-contract'\nimport { ENERGY_EVENT, isOrderTerminal, activeShiftAt } from '@operato/ops-contract'\n\n/** 저널 한 줄 — 필요한 것만(엔티티·typeorm 비의존). */\nexport interface KpiEvent {\n eventType?: string\n /** ISO 시각. 파싱 불가하면 그 줄은 버린다(알리지 않고 0 으로 세지 않는다). */\n eventTime?: string\n payload?: any\n}\n\nexport interface KpiWindow {\n /** 창의 시작·끝(epoch ms). 완료 시각이 이 안에 든 것만 성과로 센다. */\n fromMs: number\n toMs: number\n}\n\nexport interface DurationStats {\n /** 짝이 맞아 계산된 건수. */\n count: number\n /**\n * 하위 꼬리 — **분포를 양쪽에서 계산하지하기 위해** 낸다.\n * p50·p90 만 있으면 아래쪽 퍼짐을 위쪽에서 거울처럼 베껴 쓸 수밖에 없다(발명). 관측이 말하게 한다.\n */\n p10Ms: number\n p50Ms: number\n p90Ms: number\n avgMs: number\n}\n\n/**\n * 관점 축 — **같은 사실을 다른 각으로** 본다.\n *\n * 고정된 한 렌즈(자원별)만 낼 수 있으면 사용자가 \"구역별로는?\" 이라고 물어도 답이 없다. 사실 계산이\n * 축을 못 내면 AI 도 못 낸다 — 그래서 축은 계산 층의 능력이어야 한다.\n *\n * resource 자원별(누가 했나) taskKind 작업 종류별(무엇을 했나)\n * location 도착 지점별(어디서) order 오더별(무엇을 위해)\n * area 구역별 — 노드→구역 지도가 필요하다(이벤트에 없다. 호출부가 준다)\n * shift 교대별(언제) — 완료 시각 + 현장의 시각 기준에서 **계산한다**(이벤트에 없다)\n */\nexport type KpiGroupBy = 'resource' | 'taskKind' | 'location' | 'order' | 'area' | 'shift'\n\nexport interface KpiGroup {\n /** 축의 값(자원 id·작업 종류·노드 id·오더 id·구역 id). 값이 없던 기록은 'unknown'. */\n key: string\n /** 이 축에서 완료된 건수. */\n tasks: number\n leadTime: DurationStats\n workTime: DurationStats\n waitTime: DurationStats\n /** 이 축에 붙어 있던 시간의 합(자원 축에서 특히 의미 있다). */\n busyMs: number\n /**\n * **품질 판정이 있었던 건수만** 센다 (2026-08-19).\n *\n * 판정이 없는 작업(이동·체류·중간 스테이션)은 어느 쪽에도 세지 않는다 — 모름을 양품으로 세면 양품률이\n * 알리지 않고 100% 로 올라간다. 그래서 `good + scrap` 은 `tasks` 보다 작을 수 있고, 그것이 정상이다.\n *\n * 판정이 하나도 없으면 **필드를 만들지 않는다**(0/0 을 「양품률 0%」로 읽히게 두지 않는다).\n */\n quality?: { good: number; scrap: number }\n}\n\nexport interface KpiResult {\n window: { fromMs: number; toMs: number }\n /** 창 안에 **완료된** 건수 — 성과는 완료 시점으로 센다(시작 시점으로 세면 진행 중인 것이 섞인다). */\n throughput: { tasks: number; orders: number }\n /**\n * 에너지 — **이 창의 전력량과 원단위.** 에너지 트윈이 없으면 `undefined`(0 이 아니다).\n *\n * ── 왜 KPI 폴드 안에 있나 (2026-08-14, §10 7단계) ─────────────────────────\n * 원단위는 분자(전력량)와 분모(산출)를 **같은 창**에서 나눈 값이다. 질의를 따로 두면 두 창을 맞추는\n * 일이 소비처의 몫이 되고, 어긋난 두 사실을 나눈 숫자는 아무것도 뜻하지 않는다(커널의 규칙이 그것을\n * 거절한다). 같은 폴드 안에 두면 창이 하나뿐이라 어긋날 수 없다.\n *\n * 분자는 **저널의 구간 마감 사건**에서 온다(`energy.demand.window`) — 커널 상태가 아니라 저널이므로\n * 멈춘 트윈·과거 구간에도 답할 수 있다.\n */\n energy?: {\n /** 이 창의 전력량. 셀 수 있는 구간이 없으면 `undefined`. */\n kWh?: number\n /**\n * 이 창의 **최대수요**(kW)와 그것이 난 구간의 시작.\n *\n * 전력량과 다른 질문이다: 전력량은 요금의 사용량 부분이고, 최대수요는 **기본요금**과 계약 위반을\n * 정한다. 평균으로 갈음할 수 없어서 커널이 구간의 최대들 중 가장 큰 값을 따로 낸다.\n */\n peakKW?: number\n peakStartMs?: number\n /**\n * 그 피크 구간에서 **설비가 끌어당긴** 최대(발전·방전 차감 전)와 그 차이(깎인 양).\n *\n * 요금은 계통 수요(`peakKW`)로 매겨지지만, 「무엇이 그 수요를 만들었나」와 「무엇이 덜어냈나」는\n * 이 두 수의 간격이 답한다. 발전·축전을 선언하지 않은 현장에서는 없다(차감할 것이 없다).\n */\n peakGrossKW?: number\n shavedKW?: number\n /**\n * 이 창의 **발전량** — 커널이 마감한 기간 사실(`energy.generation.period`)에서 온다.\n *\n * 부하(`kWh`)와 더하지 않는다: 발전은 설비가 만든 양이고 부하는 계량 지점을 지난 양이다. 두 수를\n * 더하면 어느 쪽도 아닌 숫자가 된다.\n *\n * 성과지표(ISO 50001 EnPI)와 태양광 성능비가 이 값을 분자로 쓴다. 발전 사실이 없으면 `undefined`\n * 다(0 이 아니다 — 「발전이 없다」와 「발전 설비가 없다」는 다르다).\n */\n generated?: {\n /** 창에 온전히 든 기간들의 합(kWh). */\n kWh: number\n /** 센 기간 수. */\n counted: number\n /**\n * 그중 **관측이 기간을 다 덮지 못한** 기간 수.\n *\n * 커널이 기간마다 관측 구간을 함께 낸다. 마지막 표본이 기간 끝보다 이르면 그만큼 재지 않은\n * 것이고, 그 수를 감추면 화면이 덜 잰 합을 온전한 합으로 말한다.\n */\n partial: number\n /** 설비별 몫 — 이것은 **측정**이다(배분이 아니다). 각 설비의 적산에서 왔다. */\n byEquipment: { id: string; kWh: number }[]\n /**\n * **이 창에 다 들어오지 않아 더하지 못한 기간** — 조용히 버리지 않는다.\n *\n * 더하는 것은 창에 온전히 든 기간뿐이다(걸친 기간을 더하면 다른 날 값이 이 창의 값인 척한다).\n * 그런데 하루 단위 기간은 「지금부터 24시간 전」 창에 온전히 드는 일이 거의 없다 — 창이 한낮에서\n * 시작하기 때문이다. 그래서 어제 발전량이 저널에 멀쩡히 있는데 화면이 비었고, 사용자는 그것을\n * 「발전량이 없다」로 읽었다(2026-08-30 실측).\n *\n * 이 값이 있으면 화면은 **「이 구간에 다 들어오지 않은 기간이 있다」**를 말해야 한다.\n */\n notCounted?: { periods: number; kWh: number; earliestStart: string; latestEnd: string }\n /**\n * 이용률 — 만든 양 ÷ (정격 × 그 기간의 시간).\n *\n * **어느 정격으로 나눴는지를 함께 낸다.** 변환기를 낀 발전은 직류 정격이 교류보다 크고,\n * 직류로 나눠 보여 주는 시스템이 실제로 있다. 밝히지 않으면 두 화면의 수가 두 배 달라도\n * 왜 다른지 말할 수 없다.\n *\n * 창 전체가 아니라 **센 기간들의 길이**로 나눈다 — 하루치 하나만 센 24시간 창에서 창 길이로\n * 나누면 그 하루가 창 전체에 걸쳐 있었던 것처럼 작아진다.\n */\n capacityFactor?: { value: number; ratedKW: number; basis: 'ac' | 'dc'; from: 'equipment' | 'location'; hours: number }\n /**\n * 낸 것의 값 — 기간마다 그때의 단가로 매긴다.\n *\n * **모든 기간에 단가가 있을 때만** 금액을 만든다. 일부만 매기고 더하면 그 합이 창 전체의 값인\n * 척한다 — 못 매긴 기간 수를 함께 낸다.\n */\n revenue?: { amount: number; currency: string; priced: number; unpriced: number }\n }\n /**\n * `kWh` 가 어느 회계에서 왔나.\n *\n * 'demand-window' 우리가 표본에서 마감한 구간 (계량이 오는 현장)\n * 'usage-period' 밖에서 마감되어 온 구간 (계량 표본이 없는 현장)\n *\n * 둘을 더하지 않는다 — 같은 전기를 두 번 센다. 둘 다 있으면 계량 쪽을 쓰고 그 사실을 낸다.\n */\n kWhBasis?: 'demand-window' | 'usage-period'\n /**\n * 기본요금을 매긴 **요금적용전력과 그 근거**.\n *\n * 이 값은 매달 다시 정해진다. 선언에 박아 두면 사람이 갱신해야 하고, 안 하면 낡은 값으로\n * 기본요금이 조용히 틀린다. 그래서 **청구서가 먼저다** — 원본이 매일 실어 보낸다.\n *\n * 'bill' 이 창을 덮는 청구서가 말한 값\n * 'carried-forward' 가장 최근 청구서의 값을 이어 쓴다(요금 제도가 실제로 그렇게 군다)\n * 'declaration' 청구가 오지 않는 현장 — 모델의 선언\n *\n * `declaredKW` 와 다르면 **선언이 낡았다**(`stale`). 그 사실을 감추면 아무도 다시 읽지 않는다.\n */\n billingDemand?: {\n kW: number\n from: 'tariff-basis' | 'bill' | 'carried-forward' | 'declaration'\n declaredKW?: number\n stale?: boolean\n /**\n * 이 창에서 관측한 최고 수요가 그 기준을 넘었다 — **다음 주기의 기본요금이 오른다.**\n *\n * 계약 초과와 다른 값이다. 계약 초과는 지금 줄이면 되고, 이것은 이미 정해진 결과다.\n *\n * `observedTo` 는 이 최고값이 언제까지의 것인지다. 최고 수요는 구간이 마감되어야 오므로\n * 넘은 것을 뒤늦게 안다 — 그 한계를 화면이 말할 수 있어야 한다.\n */\n exceeded?: { peakKW: number; overKW: number; observedTo: string }\n }\n /**\n * 밖에서 마감되어 온 사용 구간 — 계량 표본이 없는 현장의 유일한 근거다.\n *\n * `basis` 별 건수를 함께 낸다: 같은 kWh 라도 계량기가 잰 것과 공급자 화면에서 긁어온 것은 무게가\n * 다르고, 실제로 긁어온 값이 망가져 있는 것을 본 적이 있다.\n */\n usage?: {\n kWh: number\n counted: number\n /** 구간마다 제 단가로 매긴 사용량 요금 — 모든 구간이 단가를 실었을 때만. */\n charge?: number\n currency?: string\n /** 단가가 없어 요금을 매기지 못한 구간 수 — 조용히 빼지 않는다. */\n unpriced: number\n byBasis: { basis: string; counted: number }[]\n }\n /**\n * 이 창에 온전히 든 **청구서** — 공급자가 확정한 금액.\n *\n * 우리 계산을 덮지 않는다. 창이 청구 주기와 정확히 같을 때만 견줄 수 있으므로 그 사실을\n * `matchesWindow` 로 함께 낸다 — 다른 창의 두 수를 빼면 그 차이는 아무것도 뜻하지 않는다.\n */\n bill?: {\n from: string\n to: string\n total?: number\n energyCharge?: number\n demandCharge?: number\n currency?: string\n billingDemandKW?: number\n matchesWindow: boolean\n /**\n * 창이 같을 때, **우리 계산에서 청구액을 뺀 값** — 0 이 아니면 우리 계산이 틀렸다는 뜻이다.\n *\n * 항목별로도 낸다(2026-08-29). 총액만 견주면 원본이 총액을 주지 않는 청구서에서는 비교가\n * 영영 나오지 않는다 — 실제로 첫 청구서에 기본요금만 있었다. 양쪽에 있는 항목만 견준다.\n */\n diff?: { total?: number; demandCharge?: number; energyCharge?: number }\n }\n /** 파생의 근거 — 구간 평균부하 × 길이(`mean-kw`). */\n basis: string\n /** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) — 알리지 않고 자르지 않는다. */\n counted: number\n skipped: number\n /** 완료 오더당 전력량 — 답할 수 없으면 `null` 과 이유. */\n intensity: { value: number | null; unit?: string; reason?: string }\n /**\n * 배전 계통의 **자리별 몫** — 「이 분기가 얼마 썼나」.\n *\n * ── 이것은 측정이 아니라 배분이다 ──────────────────────────────────────\n * 저널에 있는 에너지 사실은 **현장 총량 한 줄**이다(뿌리 계량기 합). 계량기별 구간 사실이 없으므로\n * 자리별 몫은 **나눈 것**이고, 무엇으로 나눴는지(`weightKind`)를 값과 함께 낸다. 근거를 감추면\n * 나눈 숫자가 잰 숫자와 같은 무게로 읽힌다 — CBAM·ESG 보고에서 첫 질문에 깨지는 지점이다.\n *\n * 가중치는 **작업 가동시간**이다. 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합으로\n * 파생되므로, 그 총량을 다시 정격으로 나누면 넣은 값이 그대로 나오는 동어반복이 된다.\n *\n * 그래서 이 값에는 **가정이 하나 실려 있다**: 전기가 가동시간에 비례한다. 공조·조명처럼 공정에\n * 붙지 않는 부하도 그 비율에 섞인다 — 화면이 그 문장을 함께 말해야 한다.\n */\n byFeeder?: {\n /** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(알리지 않고 버리지 않는다). */\n key: string\n kWh: number\n /**\n * 이 몫의 근거 — **커널이 정한다.**\n *\n * 선언된 부하가 여럿이면 배분(`apportioned`)이다. 선언된 부하가 **하나뿐인** 현장에서는 커널이\n * 「그 계량기는 그것만 잰다」고 읽어 `measured` 를 낸다 — 모델이 그렇게 말했으므로 그것도 사실이다.\n * 우리가 어느 쪽으로도 낙인찍지 않는다.\n */\n basis: 'apportioned' | 'measured'\n /**\n * 무엇으로 나눴나 — **배분일 때만** 있다.\n *\n * 분기마다 계량기가 있는 현장에서는 나누지 않는다(그 값이 곧 계량이다). 그때 이 칸을\n * `runtimeMs` 로 채우면 「가동시간으로 나눴다」는 없는 사실을 말하게 된다.\n */\n weightKind?: 'runtimeMs'\n /** 이 자리의 몫이 전체의 얼마였나 — 배분일 때만. */\n weightShare?: number\n /**\n * **피크가 찍힌 순간** 이 분기가 얼마였나(kW) — 요금이 걸린 수다.\n *\n * 창의 평균이나 합이 아니다: 최대수요를 낸 구간의 그 순간 값이다(다른 순간을 섞으면 「어느 분기를\n * 깎아야 피크가 내려가나」의 답이 흐려진다). 그 구간에 이 분기의 표본이 없었으면 **없다**.\n */\n peakKW?: number\n }[]\n /**\n * 나누지 못한 양과 그 이유 — **알리지 않고 0 으로 만들지 않는다.**\n * `no-weights` = 그 창에 돌아간 자원이 없다(가동시간이 전부 0). 그러면 총량은 그대로 남는다.\n */\n unattributedKWh?: number\n /**\n * 왜 나누지 못했나(커널 코드) — `no-weights`(가동시간이 전부 0) · `no-consumers`(덮는 대상 미선언) ·\n * `not-submetered`(뿌리에서 재었으나 하위 계량이 덮지 않는 부분 — 결손이 아니라 계량 범위의 사실).\n */\n unattributedReason?: string\n /**\n * 분기 계량의 합이 뿌리 계량보다 **큰** 양 — 두 계량기의 회계가 다르다는 사실이다.\n *\n * 값을 깎아 맞추지 않는다: 그것은 계량을 우리가 고쳐 쓰는 일이고, 어느 쪽이 맞는지는 현장이 안다.\n */\n submeterExcessKWh?: number\n /**\n * 배분에서 **빠진 자원 수** — 그 창에 가동 기록이 없는 설비다. 0kWh 를 주지 않는 대신 몇 대가\n * 빠졌는지 센다(대기전력이 있는 설비에서 0 은 곧 거짓이다).\n */\n excludedResources?: number\n /**\n * 이 창의 전기요금 — **청구서가 아니다.** 계산과 그 한계(TOU·래칫·최소청구액 미모델)는 커널\n * (`electricityCost`)이 갖고 여기서는 나른다. 단가가 없으면 `reason: 'no-tariff'` 로 온다.\n */\n cost?: ElectricityCost\n }\n /** 리드타임 — 생성(created)에서 완료까지. 사용자가 체감하는 소요 시간. */\n leadTime: DurationStats\n /** 작업시간 — 착수(in-progress)에서 완료까지. 대기를 뺀 실작업. */\n workTime: DurationStats\n /** 대기시간 — 생성에서 착수까지. 자원이 부족하면 여기가 늘어난다. */\n waitTime: DurationStats\n /** 자원별 점유 — 작업에 붙어 있던 시간의 합과 창 대비 비율(작업으로 설명되는 부분만). */\n utilization: { resourceId: string; busyMs: number; ratio: number }[]\n /**\n * **셈에서 빠진 것** — 완료는 창 안이지만 시작 이벤트를 못 찾은 건수.\n * 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이 알리지 않고 거짓이 된다.\n */\n unpaired: { leadTime: number; workTime: number }\n /**\n * **완료로 세지 않은 오더 상태 낱말의 인구조사** — 낱말 → 창 안에서 그 낱말로 본 건수.\n *\n * 「못 읽었다」가 **아니다.** `picking` 처럼 완료가 아닌 낱말이 여기 오는 것은 정상이다. 이 값은\n * 판정이 아니라 **재료**다: 완료가 0 인데 여기 `{ finished: 2780 }` 이 있으면 원인이 곧 보인다.\n *\n * ── 왜 필요한가, 그리고 무엇의 대체가 아닌가 ────────────────────────────────\n * 이 폴드는 완료를 **동의어 목록**으로 판정한다(`completed`·`fulfilled`·`done`·`finished`). 그 방식은\n * 원본의 낱말에서 뜻을 **추론**하는 것이고, 이 저장소가 다른 자리에서 거절하는 그것이다\n * (「선언이 있으면 변환이지만 없으면 발명이다」).\n *\n * 실제로 그 대가를 치렀다: `finished` 가 목록에 없어 첫 실 시스템의 완료 오더가 전부 0 이었다.\n * 그런데 **빈 자리는 이 목록이 아니라 커넥터였다** — 그 커넥터에 상태 표(`WORK_ORDER_STATE`)가\n * 정의돼 있으면서 오더 경로에서 쓰이지 않았고(로트 경로는 썼다), 원본 낱말이 그대로 흘렀다.\n * 목록에 `finished` 를 더한 것은 그 사실을 가린 땜빵이었다.\n *\n * **정석은 커넥터가 자기 표로 옮기고, 옮길 수 없는 낱말은 세고 넣지 않는 것이다**(재고 처분이 이미\n * 그 규율로 선다: 「표현할 길이 없는 것은 넣지 않는다」). 그러면 이 폴드는 추론하지 않아도 된다.\n *\n * 그러므로 이 값은 **동의어 목록을 늘리는 근거가 아니다.** 목록은 커넥터들이 옮기기 시작하면\n * 닫힌 어휘로 좁혀야 하고, 그때까지 이 값이 어긋남을 말해 준다. 순서가 중요하다 — 목록을 먼저\n * 좁히면 아직 옮기지 않는 원본의 완료가 그 순간 0 이 된다.\n *\n * 상태를 말하지 않은 사건은 여기 오지 않는다. 그것은 「모르는 낱말」이 아니라 다른 결함이고\n * (유입 문이 상태를 필수로 받는다) 그 진단은 그 문의 몫이다.\n */\n orderStatusNotCounted: Record<string, number>\n /**\n * 소비한 이벤트 수 — **넘겨받은 배열 전체**(창 앞 lookback 포함). 진단용이다.\n *\n * \"측정된 기록이 있는가\" 의 판단에는 쓰지 말 것 — 호출부는 창보다 앞까지 읽어 넘기므로(완료된 작업의\n * 시작이 창 밖일 수 있다) 창 안이 비어 있어도 이 값은 0 이 아니다. 그 판단은 `eventsInWindow` 로 한다.\n */\n eventsSeen: number\n /**\n * **창 안에** 들어온 이벤트 수 — \"이 구간에 측정할 기록이 있었는가\" 의 정답.\n *\n * 이것이 0 이면 \"처리량 0\" 이 아니라 **\"기록이 없다\"** 다. 두 상황을 섞으면 사용자가 잘못된 결론을\n * 낸다(앞 구간의 기록만 읽어 놓고 \"0건 처리\" 라고 말하는 일이 실제로 가능했다).\n */\n eventsInWindow: number\n /** 요청한 관점으로 쪼갠 값(요청했을 때만). */\n groups?: { by: KpiGroupBy; items: KpiGroup[]; truncated?: number; truncatedTasks?: number }\n}\n\n/**\n * 창의 전력량을 **배전 계통의 자리별로 나눈다** — 근거를 함께.\n *\n * ── 왜 커널 함수를 쓰나 ─────────────────────────────────────────────────────\n * 「나눌 근거가 없으면 나누지 않는다」·「합은 보존된다」·「몫이 0 이면 0kWh 를 주지 않는다」는 규칙이\n * 커널의 `attributeEnergy` 에 이미 있다. 여기서 다시 쓰면 두 벌이 되고, 그때부터 두 화면이 다른 합을\n * 말한다. 이 함수가 하는 일은 **재료를 모아 넘기고 답을 자리별로 묶는 것**뿐이다.\n *\n * ── 가중치는 가동시간이다 ───────────────────────────────────────────────────\n * 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합에서 파생되므로 그 총량을 다시 정격으로\n * 나누면 넣은 값이 그대로 나온다(새 정보 0의 동어반복). 가동시간은 **저널이 관측한 사실**이다.\n *\n * 자리를 모르는 자원은 `unknown` 으로 모인다 — 빼면 합이 줄고, 아무 자리에 넣으면 거짓이 된다.\n */\nfunction feederShares(\n kWh: number | undefined,\n utilization: { resourceId: string; busyMs: number }[],\n resourceFeeder?: Record<string, string>\n): Partial<NonNullable<KpiResult['energy']>> {\n /* 선언이 없으면 분해하지 않는다 — 자리를 모르는 채 나눈 숫자는 아무 것의 몫도 아니다. */\n if (!resourceFeeder || !Object.keys(resourceFeeder).length) return {}\n if (kWh === undefined || !(kWh > 0)) return {}\n\n /*\n * ── 풀이 **무엇을 덮는지 사실대로** 말한다 (2026-08-17) ────────────────────\n *\n * 처음에는 「그 창에 돌아간 자원」만 덮는다고 넘겼다. 그러자 돌아간 자원이 하나인 창에서 커널이\n * 그것을 **`measured`(전용 계량)** 로 판정했다 — 커널의 규칙은 옳다(「덮는 소비처가 하나면 그\n * 계량기는 그것만 잰다는 선언이다」). 틀린 것은 내 선언이었다: 현장 계량기는 돌아간 것만 재지 않는다.\n *\n * 그래서 **선언된 부하 전부**를 덮는다고 말한다(`resourceFeeder` 의 키가 곧 그 선언이다).\n * 그러면 그 창에 돌지 않은 설비는 몫 0 으로 **배분에서 빠지고 그 수가 보고된다** — 0kWh 를 주는\n * 대신 「이 설비는 이 창에 가동 기록이 없다」를 말하는 쪽이다.\n */\n /*\n * 덮는 대상은 **선언된 부하 ∪ 그 창에 돌아간 자원**이다.\n *\n * 선언만 쓰면, 돌았는데 전기 계통에 선언되지 않은 설비의 몫이 나머지 설비에 얹힌다 — 그 설비가\n * 안 쓴 전기를 쓴 것으로 만든다. 돌아간 것만 쓰면, 선언됐지만 쉰 설비가 사라져 「이 창에 안 돌았다」를\n * 말할 수 없다. 둘을 합해야 두 사실이 다 남는다(자리를 모르는 몫은 `unknown` 으로 모인다).\n */\n const busyOf = new Map(utilization.map(u => [u.resourceId, u.busyMs]))\n const ids = new Set<string>([...Object.keys(resourceFeeder), ...busyOf.keys()])\n const consumers = [...ids].map(id => ({ id, weight: busyOf.get(id) ?? 0 }))\n if (!consumers.length) return {}\n\n const r = attributeEnergy({\n pools: [{ meterId: 'site', kWh, consumerIds: consumers.map(c => c.id) }],\n consumers,\n weightKind: 'runtimeMs'\n })\n\n const byKey = new Map<string, { kWh: number; weightShare: number; apportioned: boolean }>()\n for (const sh of r.shares) {\n /* 자리를 모르면 `unknown` — 그 몫이 있다는 사실은 남기고, 어디의 것인지는 지어내지 않는다. */\n const key = resourceFeeder[sh.consumerId] ?? 'unknown'\n const cur = byKey.get(key) ?? { kWh: 0, weightShare: 0, apportioned: false }\n cur.kWh += sh.kWh\n cur.weightShare += sh.weightShare ?? 0\n /* **근거는 커널이 정한다.** 여기서 낙인찍으면 커널이 「쟀다」고 한 것을 「나눴다」고 바꿔 말하거나\n 그 반대가 된다. 섞이면 약한 쪽(배분)으로 적는다 — 강한 주장을 올려 잡지 않는다. */\n if (sh.basis === 'apportioned') cur.apportioned = true\n byKey.set(key, cur)\n }\n\n const byFeeder = [...byKey.entries()]\n .map(([key, v]) => ({\n key,\n kWh: v.kWh,\n basis: (v.apportioned ? 'apportioned' : 'measured') as 'apportioned' | 'measured',\n weightKind: 'runtimeMs' as const,\n weightShare: v.weightShare\n }))\n .sort((a, b) => b.kWh - a.kWh)\n\n const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)\n return {\n ...(byFeeder.length ? { byFeeder } : {}),\n ...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),\n ...(r.excluded.length ? { excludedResources: r.excluded.length } : {})\n }\n}\n\n/**\n * 분기별 전력량을 **계량으로** 읽는다 — 계량이 있으면 배분하지 않는다.\n *\n * ── 왜 필요했나 (2026-08-18) ────────────────────────────────────────────────\n * 실 게이트웨이는 분기마다 계량기를 보낸다. 그런데 화면은 「나누지 못함」으로 답하고 있었다 — 여기에\n * 가동시간 배분 경로만 있었기 때문이다. 계량이 있는데 배분으로 말하면 **근거를 실제보다 약하게** 보고하는\n * 것이고, 그 위에 세운 SEU 판단이 무거운 값을 놓친다. 미러 트윈에는 설비 가동 기록조차 없어서\n * (원천이 계량만 준다) 그 경로로는 분기별 값이 영원히 나오지 않았다.\n *\n * 값의 출처는 커널이 구간 마감 사실에 실어 주는 **지점별 몫**이다(적산의 뺄셈). 표본을 직접 읽지 않는\n * 이유는 조회 상한이다 — 계측은 초당 여러 건이라 표본으로는 창을 채우기 전에 상한에 걸린다.\n *\n * 나머지(뿌리 − 분기 합)는 **버리지도 얹지도 않는다**: 수전 직결 부하·손실·계량되지 않은 회로가 그\n * 차이이므로, 어느 분기에 얹으면 그 분기가 쓰지 않은 전기를 쓴 것으로 만든다. 커널의 `remainder` 풀로\n * 넘겨 「하위 계량이 덮지 않는다」로 남긴다.\n *\n * 분기 계량 합이 뿌리보다 크면(회계가 다른 두 계량기) **그 사실을 낸다** — 값을 깎아 맞추면 계량을\n * 우리가 고쳐 쓰는 것이다.\n */\n/**\n * 창의 **발전량** — 커널이 마감한 기간 사실을 합한다.\n *\n * ── 왜 합이 전부인가 ───────────────────────────────────────────────────────\n * 기간 발전량은 커널이 이미 확정한 사실이다(적산의 종류마다 다른 규칙으로 얻는다). 여기서 다시\n * 계산하면 규칙이 두 벌이 되고, 그때부터 두 화면이 다른 발전량을 말한다. 이 함수가 하는 일은\n * **창에 든 것을 고르고, 설비별로 묶고, 근거가 얼마나 덮였는지 세는 것**이다.\n *\n * ── 걸친 기간은 세지 않는다 ─────────────────────────────────────────────────\n * 구간 마감과 같은 규칙이다. 하루의 절반이 창에 들었다고 절반을 세면 그것은 관측이 아니라 우리가 나눈\n * 값이고, 합이 창을 넘는다.\n *\n * ── 덜 덮인 기간을 센다 ─────────────────────────────────────────────────────\n * 기간의 끝이 자정인데 마지막 표본이 저녁이었으면 그 사이는 재지 않은 것이다. 그 수를 함께 내야 화면이\n * 「이 합은 이만큼 덜 잰 것」이라고 말할 수 있다. 얼마나 어긋나면 문제인지는 현장이 정한다.\n */\nfunction generatedOfPeriods(\n periods: readonly {\n equipmentId: string\n kWh: number\n startMs: number\n endMs: number\n observedFromMs: number\n observedToMs: number\n }[],\n range: { startMs: number; endMs: number },\n /** 이용률의 분모 — 없으면 이용률을 내지 않는다(0 이나 짐작을 만들지 않는다). */\n rated?: { kW: number; basis: 'ac' | 'dc'; from: 'equipment' | 'location' },\n /** 기간마다 적용할 단가 — 기간을 덮는 것을 쓴다. 없으면 금액을 만들지 않는다. */\n prices?: readonly { startMs: number; endMs: number; unitPrice: number; currency: string }[]\n): Partial<NonNullable<KpiResult['energy']>> {\n const inside = periods.filter(g => g.startMs >= range.startMs && g.endMs <= range.endMs)\n\n /*\n * ── **걸친 기간을 조용히 버리지 않는다** (2026-08-30) ──────────────────────\n *\n * 더하는 것은 창에 **온전히 들어온 기간**뿐이다. 그것이 맞는 셈이다 — 걸친 기간을 더하면 다른 날의\n * 값이 이 창의 값인 척한다.\n *\n * 그런데 버린 것을 말하지 않았다. 하루 단위 기간은 「지금부터 24시간 전」 창에 온전히 들어오는 일이\n * 거의 없다(창이 한낮에서 시작하므로). 그래서 어제 발전량이 저널에 멀쩡히 있는데 화면이 비었고,\n * 사용자는 그것을 **「발전량이 없다」**로 읽었다. 실측으로 그 일이 났다.\n *\n * 이제 걸친 것을 세어 함께 낸다. 화면이 「이 창에 다 들어오지 않은 기간이 있다」를 말할 수 있다.\n */\n const straddling = periods.filter(\n g => !(g.startMs >= range.startMs && g.endMs <= range.endMs) && g.endMs > range.startMs && g.startMs < range.endMs\n )\n const notCounted = straddling.length\n ? {\n periods: straddling.length,\n kWh: straddling.reduce((a, g) => a + g.kWh, 0),\n earliestStart: new Date(Math.min(...straddling.map(g => g.startMs))).toISOString(),\n latestEnd: new Date(Math.max(...straddling.map(g => g.endMs))).toISOString()\n }\n : undefined\n\n if (!inside.length) {\n /* 온전히 들어온 것이 없다 — 그래도 걸친 것이 있으면 그 사실을 낸다(빈 값과 「없음」은 다르다). */\n return notCounted ? { generated: { kWh: 0, counted: 0, partial: 0, byEquipment: [], notCounted } } : {}\n }\n\n const byEquipment = new Map<string, number>()\n let partial = 0\n for (const g of inside) {\n byEquipment.set(g.equipmentId, (byEquipment.get(g.equipmentId) ?? 0) + g.kWh)\n const coveredFrom = Number.isFinite(g.observedFromMs) ? g.observedFromMs <= g.startMs : false\n const coveredTo = Number.isFinite(g.observedToMs) ? g.observedToMs >= g.endMs : false\n if (!coveredFrom || !coveredTo) partial++\n }\n\n const kWh = inside.reduce((a, g) => a + g.kWh, 0)\n /*\n * 이용률의 분모는 **센 기간들의 길이**다. 창 길이로 나누면, 하루치 하나만 센 24시간 창에서 그 하루가\n * 창 전체에 걸쳐 있었던 것처럼 작아진다.\n */\n const hours = inside.reduce((a, g) => a + (g.endMs - g.startMs) / 3_600_000, 0)\n const capacityFactor =\n rated && rated.kW > 0 && hours > 0\n ? { value: kWh / (rated.kW * hours), ratedKW: rated.kW, basis: rated.basis, from: rated.from, hours }\n : undefined\n\n /*\n * 낸 것의 값 — 기간마다 **그 기간을 덮는 단가**로 매긴다. 걸친 단가는 쓰지 않는다: 하루 단가로\n * 이틀치를 매기면 그 절반은 다른 날의 값이 된다.\n *\n * 모든 기간에 단가가 있을 때만 금액을 만든다. 일부만 매기고 더하면 그 합이 창 전체의 값인 척한다.\n */\n let amount = 0\n let priced = 0\n let currency: string | undefined\n for (const g of inside) {\n const p = (prices ?? []).find(x => x.startMs <= g.startMs && x.endMs >= g.endMs)\n if (!p) continue\n amount += g.kWh * p.unitPrice\n priced++\n currency = currency ?? p.currency\n }\n const unpriced = inside.length - priced\n const revenue = priced > 0 && unpriced === 0 && currency ? { amount, currency, priced, unpriced } : undefined\n\n return {\n generated: {\n kWh,\n counted: inside.length,\n partial,\n byEquipment: [...byEquipment.entries()]\n .map(([id, kWh]) => ({ id, kWh }))\n .sort((a, b) => b.kWh - a.kWh),\n ...(capacityFactor ? { capacityFactor } : {}),\n ...(revenue ? { revenue } : {}),\n /* 이 창에 다 들어오지 않아 더하지 못한 기간 — 있으면 화면이 그것을 말한다. */\n ...(notCounted ? { notCounted } : {})\n }\n }\n}\n\n/**\n * 밖에서 마감되어 온 사용 구간을 합한다 — **구간 마감과 더하지 않는다.**\n *\n * 두 회계가 같은 전기를 말한다. 더하면 두 번 세고, 둘 다 있는 현장에서는 계량 쪽이 강한 근거이므로\n * 그쪽을 쓴다. 여기서는 이쪽 몫만 계산하고, 무엇을 쓸지는 부르는 쪽이 정한다.\n *\n * 단가는 **모든 구간이 실었을 때만** 요금을 만든다. 일부만 실었는데 있는 것만 더하면 그 합이 창의\n * 요금인 척한다 — 못 매긴 구간 수를 함께 낸다.\n */\nfunction usageOfPeriods(\n periods: readonly { meterId: string; kWh: number; startMs: number; endMs: number; unitPrice: number; currency?: string; basis: string }[],\n range: { startMs: number; endMs: number }\n): NonNullable<KpiResult['energy']>['usage'] | undefined {\n const inside = periods.filter(u => u.startMs >= range.startMs && u.endMs <= range.endMs)\n if (!inside.length) return undefined\n\n const byBasis = new Map<string, number>()\n let kWh = 0\n let charge = 0\n let unpriced = 0\n let currency: string | undefined\n for (const u of inside) {\n kWh += u.kWh\n byBasis.set(u.basis, (byBasis.get(u.basis) ?? 0) + 1)\n if (Number.isFinite(u.unitPrice)) {\n charge += u.kWh * u.unitPrice\n if (!currency) currency = u.currency\n } else unpriced++\n }\n\n return {\n kWh,\n counted: inside.length,\n ...(unpriced === 0 && currency ? { charge, currency } : {}),\n unpriced,\n byBasis: [...byBasis.entries()].map(([basis, counted]) => ({ basis, counted })).sort((a, b) => b.counted - a.counted)\n }\n}\n\n/**\n * **요금적용전력을 고른다 — 청구서가 먼저다.**\n *\n * 순서에 뜻이 있다. 청구서는 그 기간에 실제로 매겨진 값이고, 선언은 사람이 옮겨 적은 값이다.\n * 그 값이 매달 바뀌므로 옮겨 적은 것은 곧 낡는다.\n */\nfunction billingDemandOf(\n billsInWindow: readonly { endMs: number; billingDemandKW: number }[],\n tariffBasis: { billingDemandKW?: number } | undefined,\n lastBill: { billingDemandKW?: number } | undefined,\n declaredKW: number | undefined,\n /** 이 창에서 관측한 최고 수요와 그 값이 언제까지의 것인지 — 사용 구간의 `maxKW` 에서 온다. */\n observedPeak?: { kW: number; toMs: number }\n): NonNullable<KpiResult['energy']>['billingDemand'] | undefined {\n const declared = Number.isFinite(Number(declaredKW)) ? Number(declaredKW) : undefined\n const withValue = billsInWindow.filter(b => Number.isFinite(b.billingDemandKW))\n const fromBill = withValue.length ? withValue.reduce((a, x) => (x.endMs > a.endMs ? x : a)).billingDemandKW : undefined\n const carried = Number.isFinite(Number(lastBill?.billingDemandKW)) ? Number(lastBill?.billingDemandKW) : undefined\n /* 이 주기에 적용되는 기준이 가장 먼저다 — 청구서는 끝난 기간의 것이다. */\n const fromBasis = Number.isFinite(Number(tariffBasis?.billingDemandKW)) ? Number(tariffBasis?.billingDemandKW) : undefined\n\n const picked =\n fromBasis !== undefined\n ? { kW: fromBasis, from: 'tariff-basis' as const }\n : fromBill !== undefined\n ? { kW: fromBill, from: 'bill' as const }\n : carried !== undefined\n ? { kW: carried, from: 'carried-forward' as const }\n : declared !== undefined\n ? { kW: declared, from: 'declaration' as const }\n : undefined\n if (!picked) return undefined\n\n /* 선언이 있는데 청구서와 다르면 그 사실을 낸다 — 낡은 선언은 스스로 말해야 한다. */\n const stale = picked.from !== 'declaration' && declared !== undefined && declared !== picked.kW\n const exceeded =\n observedPeak && observedPeak.kW > picked.kW\n ? {\n peakKW: observedPeak.kW,\n overKW: observedPeak.kW - picked.kW,\n observedTo: new Date(observedPeak.toMs).toISOString()\n }\n : undefined\n\n return {\n ...picked,\n ...(declared !== undefined ? { declaredKW: declared } : {}),\n ...(stale ? { stale: true } : {}),\n ...(exceeded ? { exceeded } : {})\n }\n}\n\n/**\n * 창에 온전히 든 청구서 하나 — 가장 늦게 끝난 것.\n *\n * **창이 청구 주기와 같을 때만 견준다.** 한 달 청구서를 하루 창의 값과 빼면 그 차이는 아무것도 뜻하지\n * 않는다. 그래서 같은지를 값과 함께 낸다.\n */\nfunction billOfWindow(\n bills: readonly {\n from: string\n to: string\n startMs: number\n endMs: number\n total: number\n energyCharge: number\n demandCharge: number\n currency?: string\n billingDemandKW: number\n }[],\n range: { startMs: number; endMs: number },\n ours?: { total?: number; demandCharge?: number; energyCharge?: number }\n): NonNullable<KpiResult['energy']>['bill'] | undefined {\n const inside = bills.filter(b => b.startMs >= range.startMs && b.endMs <= range.endMs)\n if (!inside.length) return undefined\n const b = inside.reduce((a, x) => (x.endMs > a.endMs ? x : a))\n const matchesWindow = b.startMs === range.startMs && b.endMs === range.endMs\n /* 양쪽에 있는 항목만 견준다 — 한쪽이 없는 것을 0 으로 두면 그 차이가 통째로 어긋남처럼 보인다. */\n const gap = (mine: unknown, billed: number): number | undefined =>\n Number.isFinite(Number(mine)) && Number.isFinite(billed) ? Number(mine) - billed : undefined\n const diff = matchesWindow\n ? {\n ...(gap(ours?.total, b.total) !== undefined ? { total: gap(ours?.total, b.total) as number } : {}),\n ...(gap(ours?.demandCharge, b.demandCharge) !== undefined\n ? { demandCharge: gap(ours?.demandCharge, b.demandCharge) as number }\n : {}),\n ...(gap(ours?.energyCharge, b.energyCharge) !== undefined\n ? { energyCharge: gap(ours?.energyCharge, b.energyCharge) as number }\n : {})\n }\n : {}\n return {\n from: b.from,\n to: b.to,\n ...(Number.isFinite(b.total) ? { total: b.total } : {}),\n ...(Number.isFinite(b.energyCharge) ? { energyCharge: b.energyCharge } : {}),\n ...(Number.isFinite(b.demandCharge) ? { demandCharge: b.demandCharge } : {}),\n ...(b.currency ? { currency: b.currency } : {}),\n ...(Number.isFinite(b.billingDemandKW) ? { billingDemandKW: b.billingDemandKW } : {}),\n matchesWindow,\n ...(Object.keys(diff).length ? { diff } : {})\n }\n}\n\nfunction measuredFeeders(\n windows: readonly {\n startMs: number\n endMs: number\n maxKW?: number\n points: { id: string; kWh: number; root: boolean }[]\n pointsAtPeak?: { id: string; kW: number }[]\n }[],\n range: { startMs: number; endMs: number },\n totalKWh: number | undefined,\n meterFeeder?: Record<string, string>\n): Partial<NonNullable<KpiResult['energy']>> | undefined {\n if (!meterFeeder || !Object.keys(meterFeeder).length) return undefined\n if (totalKWh === undefined || !(totalKWh > 0)) return undefined\n\n /* 창에 온전히 든 구간만 — 전체 전력량과 같은 규칙이어야 몫과 합이 같은 창을 말한다. */\n const byFeederKWh = new Map<string, number>()\n let sawPoints = false\n for (const w of windows) {\n if (!(w.startMs >= range.startMs && w.endMs <= range.endMs)) continue\n for (const p of w.points ?? []) {\n const feeder = meterFeeder[p.id]\n if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다\n sawPoints = true\n byFeederKWh.set(feeder, (byFeederKWh.get(feeder) ?? 0) + p.kWh)\n }\n }\n if (!sawPoints) return undefined // 계량이 없으면 이 경로를 쓰지 않는다(배분으로 돌아간다)\n\n const feeders = [...byFeederKWh.entries()].filter(([, kWh]) => kWh > 0)\n if (!feeders.length) return undefined\n const submetered = feeders.reduce((sum, [, kWh]) => sum + kWh, 0)\n const remainder = totalKWh - submetered\n\n const r = attributeEnergy({\n pools: [\n ...feeders.map(([feeder, kWh]) => ({ meterId: `${feeder}:meter`, kWh, consumerIds: [feeder] })),\n ...(remainder > 0 ? [{ meterId: 'root:remainder', kWh: remainder, consumerIds: [], remainder: true }] : [])\n ],\n consumers: feeders.map(([feeder]) => ({ id: feeder }))\n })\n\n /*\n * ── 피크를 만든 분기 (2026-08-18) ─────────────────────────────────────────\n *\n * 요금은 kW 로 매겨지므로 「어느 분기를 깎아야 피크가 내려가나」가 실무의 질문이다. 그 답은 **피크가\n * 찍힌 구간의 그 순간** 값이고, 창 안의 여러 구간을 더하거나 평균하면 뜻이 사라진다(다른 순간의 값을\n * 섞는 것이다). 그래서 **최대수요를 낸 구간 하나**만 골라 그 순간의 지점 값을 쓴다.\n */\n const peakWindow = windows\n .filter(w => w.startMs >= range.startMs && w.endMs <= range.endMs && Number.isFinite(Number(w.maxKW)))\n .reduce<(typeof windows)[number] | undefined>((best, w) => (!best || Number(w.maxKW) > Number(best.maxKW) ? w : best), undefined)\n const peakOf = new Map<string, number>()\n for (const p of peakWindow?.pointsAtPeak ?? []) {\n const feeder = meterFeeder[p.id]\n if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다\n peakOf.set(feeder, (peakOf.get(feeder) ?? 0) + p.kW)\n }\n\n const byFeeder = r.shares\n .map(sh => ({\n key: sh.consumerId,\n kWh: sh.kWh,\n basis: sh.basis,\n weightKind: undefined,\n weightShare: undefined,\n /* 피크 순간의 값이 없으면 만들지 않는다 — 그 구간에 이 분기의 표본이 없었다는 뜻이다. */\n ...(peakOf.has(sh.consumerId) ? { peakKW: peakOf.get(sh.consumerId) } : {})\n }))\n .sort((a, b) => b.kWh - a.kWh) as NonNullable<KpiResult['energy']>['byFeeder']\n\n const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)\n return {\n ...(byFeeder?.length ? { byFeeder } : {}),\n ...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),\n /* 분기 합이 뿌리를 넘었다 — 두 계량기의 회계가 다르다는 사실이다(깎아 맞추지 않는다). */\n ...(remainder < 0 ? { submeterExcessKWh: -remainder } : {})\n }\n}\n\n/** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */\n/*\n * ── 같은 사실이 두 벌 있어도 한 번만 센다 (2026-08-30) ──────────────────────\n *\n * 마감된 구간 사실은 정체성이 내용에 있다(커널 0.7.81). 그런데 저널에 이미 여러 벌 적힌 것이 있다 —\n * 쓰는 자리에서 막기 전에 들어간 것들이다(실측: 어느 날의 발전 단가가 세 벌).\n *\n * 지난 기록을 지우지 않는다 — 저널은 일어난 일의 기록이다. 대신 **읽을 때 한 번만 센다.**\n * 그러지 않으면 그 구간의 수익·사용량이 그 횟수만큼 세어진다.\n */\nfunction onceById<T extends { payload?: any }>(rows: readonly T[]): T[] {\n const seen = new Set<string>()\n return rows.filter(r => {\n const id = (r as any)?.payload?.eventId\n if (typeof id !== 'string' || !id) return true\n if (seen.has(id)) return false\n seen.add(id)\n return true\n })\n}\n\nexport interface KpiFoldOptions {\n groupBy?: KpiGroupBy\n /**\n * 이 트윈이 이어받고 있는 **마지막 청구서** — 요금적용전력의 우선 근거(§`billingDemandOf`).\n *\n * 창 밖의 청구서는 저널 조회에 걸리지 않는다. 그런데 그 값은 이 창에도 적용되므로 호출부가\n * 커널 상태에서 읽어 넘긴다.\n */\n lastBill?: { to: string; billingDemandKW?: number }\n /**\n * 이 트윈이 이어받고 있는 **요금 기준** — 이 주기에 적용되는 값(§`ENERGY_EVENT.tariffBasis`).\n *\n * 청구서보다 먼저다. 청구서는 끝난 기간의 정산이라 이번 주기의 기준을 말하지 않는다.\n */\n tariffBasis?: { from: string; billingDemandKW?: number }\n /**\n * 이 트윈의 **발전 정격**과 그 근거 — 이용률의 분모(§ 커널 `generationRated`).\n *\n * 모델의 선언에서 오므로 호출부가 읽어 넘긴다. 폴드는 저널만 본다.\n */\n generationRated?: { kW: number; basis: 'ac' | 'dc'; from: 'equipment' | 'location' }\n /**\n * 이 트윈이 이어받고 있는 **발전 단가** — 창 밖의 단가도 이 창에 적용될 수 있다.\n *\n * 창 안의 단가 사실이 먼저다. 이것은 그 창에 단가가 없을 때 쓴다.\n */\n generationPrice?: { from: string; to: string; unitPrice: number; currency: string }\n /**\n * 노드 → 구역 지도. **이벤트에는 구역이 없다**(노드까지만 있다) — 스냅샷·마스터가 아는 값이라\n * 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(알리지 않고 다른 축으로 바꾸지 않는다).\n */\n locationArea?: Record<string, string>\n /**\n * 자원 → **배전 계통의 자리**(분기·수전) 지도.\n *\n * ── 왜 밖에서 오나 ───────────────────────────────────────────────────────\n * 이벤트에는 전기 계통이 없다. 「이 설비가 어느 분기에서 먹는가」는 에너지 트윈의 모델이 아는\n * 선언이고(설비 하나에 사실 둘 — 공장에서는 자원, 전기에서는 부하), 그 선언을 읽는 것은 호출부의 일이다.\n *\n * 없으면 분기 분해를 **하지 않는다** — 자리를 모르는 채 나누면 그 숫자는 아무 것의 몫도 아니다.\n */\n resourceFeeder?: Record<string, string>\n /**\n * **계량 지점 → 그것이 재는 분기** 지도.\n *\n * 있으면 분기별 전력량을 계량으로 읽는다(배분보다 강한 근거). 이 지도는 모델의 선언에서 오고\n * (계량기의 자리), 그것을 읽는 것은 호출부의 일이다 — 폴드는 저널만 본다.\n */\n meterFeeder?: Record<string, string>\n /**\n * 선언된 **전기요금 단가** — 수를 금액으로 바꾸는 유일한 입력.\n *\n * 이벤트에는 요금이 없다. 단가는 현장의 계약이고(에너지 트윈 모델의 수전 자리에 선언한다), 그것을\n * 읽는 것은 호출부의 일이다. 없으면 금액을 만들지 않는다 — 짐작한 단가로 낸 금액은 숫자가 있다는\n * 것만으로 사람을 결정으로 밀어붙인다.\n */\n tariff?: TariffDeclaration\n /**\n * 교대 선언 — **이벤트에는 교대가 없다**(시각만 있다). 현장 마스터가 아는 값이라 호출부가 넘긴다.\n * 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로 알리지 않고 바꾸지 않는다).\n */\n shifts?: WorkCalendarEntry[]\n /**\n * 현장의 시각 기준(분) — 교대 축이 `HH:MM` 을 어느 기준으로 읽을지. **공간이 갖는 값**이다\n * (테넌트가 아니다: 한 테넌트가 다른 시간대의 공장을 함께 가질 수 있다). 없으면 UTC.\n */\n utcOffsetMinutes?: number\n /**\n * 축의 상한(기본 20). 넘치면 잘라내고 **잘린 축의 수와 그 안의 건수를 함께 알린다**.\n *\n * 축의 수만 알리면 부족하다 — \"그 외 89개\" 로는 그것이 3건인지 300건인지 모른다. 보여준 축이 전부인\n * 것처럼 읽히면 사용자가 꼬리를 놓친다.\n */\n groupLimit?: number\n}\n\n/* ── 관점 축의 재료 ─────────────────────────────────────────────────────────\n * 축의 값은 이벤트 payload 에 이미 들어 있다(`TaskStatusDelta`: kind·orderId·fromNode·toNode·\n * resourceRef). 새 계측을 심을 필요가 없다 — **이미 쌓인 저널을 다르게 묶기만** 한다.\n */\n/** 창 안에 완료된 한 건의 기록 — 통계의 원료이자 관점 축의 원료. */\ninterface CompletionRecord {\n taskId: string\n at: number\n facets: TaskFacets\n leadMs?: number\n workMs?: number\n waitMs?: number\n}\n\n/** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(알리지 않고 버리지 않는다). */\nfunction facetKey(rec: CompletionRecord, options: KpiFoldOptions): string {\n const f = rec.facets\n switch (options.groupBy) {\n case 'resource':\n return f.resource ?? 'unknown'\n case 'taskKind':\n return f.kind ?? 'unknown'\n case 'location':\n return f.location ?? 'unknown'\n case 'order':\n return f.order ?? 'unknown'\n case 'area':\n /* 구역은 이벤트 밖의 지식이다 — 지도가 없으면 없다고 말한다(노드 id 로 대체하면 사용자가\n * 그것을 구역으로 오해한다). */\n return (f.location && options.locationArea?.[f.location]) ?? 'unknown'\n case 'shift':\n /*\n * 교대는 **저널에 찍혀 있지 않다 — 계산한다.**\n *\n * 이벤트에 교대 이름을 스탬프하면 두 가지가 무너진다: ① 같은 사실이 두 곳에 생기고,\n * ② 나중에 교대 시간을 고치면 **과거가 옛 구분으로 굳어** 다시 셀 수 없다. 교대는\n * `완료 시각 + 현장의 시각 기준 + 교대 선언`에서 나오는 순수 파생이므로 여기서 낸다 —\n * 라이브·히스토리·예측이 **커널과 같은 함수**(`activeShiftOf`)를 쓴다.\n *\n * 선언이 없으면 'unknown' 이다. 교대를 나눠 놓지 않은 현장에 이름을 지어내지 않는다.\n */\n if (!options.shifts?.length) return 'unknown'\n /* 시각까지 넘긴다 — **휴일에 일어난 일은 어느 교대에도 속하지 않는다.** 되풀이만 보면 그날 서지도\n 않은 교대에 집계돼, 교대별 성과가 알리지 않고 거짓이 된다. */\n return activeShiftAt(options.shifts, rec.at, options.utcOffsetMinutes) ?? 'unknown'\n default:\n return 'unknown'\n }\n}\n\n/**\n * 이 축의 품질 — **판정이 있었던 것만** 센다(없으면 필드를 만들지 않는다).\n *\n * 양품률의 이력이 여기서 나온다: 커널이 작업 완료에 결과를 적고(`TaskStatusDelta.outcome`), 폴드가 축으로\n * 들고(`TaskFacets.outcome`), 여기서 종류별로 센다. 그 사슬이 없으면 「이 공정의 양품률」은 저널에\n * 남지 않는다(설비 누적 카운터는 종류를 모른다).\n */\nfunction qualityOf(rows: CompletionRecord[]): { quality?: { good: number; scrap: number } } {\n let good = 0\n let scrap = 0\n for (const r of rows) {\n const o = r.facets.outcome\n if (o === 'good') good++\n else if (o === 'scrap') scrap++\n }\n return good + scrap > 0 ? { quality: { good, scrap } } : {}\n}\n\n/**\n * 기록을 축으로 묶어 축별 통계를 낸다 — **전체와 같은 규칙**으로(같은 stats 함수, 같은 창).\n *\n * 축의 값이 아주 많을 수 있다(오더 축은 수천 개). 그래서 건수 큰 것부터 상한까지만 돌려주고\n * **잘라낸 수를 함께 알린다** — 들어온 것이 없는 절단은 \"이게 전부\" 라는 거짓을 만든다.\n */\nfunction groupRecords(\n records: CompletionRecord[],\n options: KpiFoldOptions\n): { by: KpiGroupBy; items: KpiGroup[]; truncated?: number; truncatedTasks?: number } {\n const by = options.groupBy as KpiGroupBy\n const bins = new Map<string, CompletionRecord[]>()\n for (const rec of records) {\n const key = facetKey(rec, options)\n const bin = bins.get(key)\n if (bin) bin.push(rec)\n else bins.set(key, [rec])\n }\n\n const all = [...bins.entries()]\n .map(([key, rows]) => ({\n key,\n tasks: rows.length,\n leadTime: stats(rows.map(r => r.leadMs).filter((v): v is number => v !== undefined)),\n workTime: stats(rows.map(r => r.workMs).filter((v): v is number => v !== undefined)),\n waitTime: stats(rows.map(r => r.waitMs).filter((v): v is number => v !== undefined)),\n busyMs: rows.reduce((a, r) => a + (r.workMs ?? 0), 0),\n ...qualityOf(rows)\n }))\n .sort((a, b) => b.tasks - a.tasks || (a.key < b.key ? -1 : 1))\n\n const limit = Math.max(1, Math.min(100, options.groupLimit ?? 20))\n const items = all.slice(0, limit)\n const dropped = all.slice(limit)\n /* 축별로 \"창 대비 비율\" 은 내지 않는다 — 자원 아닌 축(오더·작업종류)에서 그 비율은 의미가 없고,\n * 의미 없는 숫자를 내면 사용자가 그것으로 판단한다. 자원 점유는 `utilization` 이 이미 낸다. */\n return {\n by,\n items,\n ...(dropped.length\n ? { truncated: dropped.length, truncatedTasks: dropped.reduce((a, g) => a + g.tasks, 0) }\n : {})\n }\n}\n\nfunction ms(value: string | undefined): number | null {\n if (!value) return null\n const t = Date.parse(value)\n return Number.isFinite(t) ? t : null\n}\n\n/** 분위수 — 정렬된 값에서 선형 보간 없이 가장 가까운 아래 값(작은 표본에서 과장하지 않는다). */\nfunction quantile(sorted: number[], q: number): number {\n if (sorted.length === 0) return 0\n const idx = Math.min(sorted.length - 1, Math.max(0, Math.floor(q * sorted.length)))\n return sorted[idx]\n}\n\nfunction stats(values: number[]): DurationStats {\n if (values.length === 0) return { count: 0, p10Ms: 0, p50Ms: 0, p90Ms: 0, avgMs: 0 }\n const sorted = [...values].sort((a, b) => a - b)\n const sum = sorted.reduce((a, b) => a + b, 0)\n return {\n count: sorted.length,\n p10Ms: quantile(sorted, 0.1),\n p50Ms: quantile(sorted, 0.5),\n p90Ms: quantile(sorted, 0.9),\n avgMs: Math.round(sum / sorted.length)\n }\n}\n\n/**\n * 이벤트를 계산해 시간창 KPI 를 만든다.\n *\n * **호출부 규약**: `events` 는 창보다 **넉넉히 앞까지** 담아 넘긴다(예: 창 시작 − 리드타임 상한).\n * 완료된 작업의 시작 이벤트가 창 밖에 있을 수 있기 때문이다. 못 찾은 것은 `unpaired` 로 알린다 —\n * 평균에서 오류 없이 빠지면 지표가 거짓이 된다.\n *\n * 시간축은 `eventTime`(ISO) 차분이다. sim 클록이 실제 시각과 오프셋이 있어도 **차분은 정확**하다\n * (고정 오프셋은 상쇄된다). 파싱 불가한 줄은 버린다.\n */\nexport function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldOptions = {}): KpiResult {\n /* 작업 짝맞춤은 **커널 규칙**으로(중복 구현 금지 — 두 곳에 적으면 같은 저널로 다른 답이 나온다). */\n const { records: tasks } = foldTaskRecords(events.filter(e => e.eventType === 'task.status'))\n let completedOrders = 0\n /* 완료로 읽지 못한 오더 상태 낱말 → 건수(§`orderStatusNotCounted`). */\n const orderStatusNotCounted = new Map<string, number>()\n /* 이미 완료로 센 오더 — 폴링 원본이 같은 종결을 다시 보내도 한 번만 센다. */\n const completedOrderIds = new Set<string>()\n let seen = 0\n let inWindow = 0\n\n for (const e of events) {\n const at = ms(e.eventTime)\n if (at === null) continue\n seen++\n if (at >= window.fromMs && at <= window.toMs) inWindow++\n const d = e.payload?.data ?? e.payload ?? {}\n\n if (e.eventType === 'order.status') {\n /*\n * **끝났나는 커널이 답한다** (2026-08-23) — `isOrderTerminal`.\n *\n * 예전에는 여기서 **동의어 목록**으로 추론했다(`completed`·`fulfilled`·`done`·`finished`).\n * 그 방식이 낸 대가가 둘이었고 둘 다 들어온 것이 없었다.\n *\n * ① 원본의 낱말이 목록에 없으면 완료가 0 이 된다. 실제로 그랬다 — 첫 실 시스템의 `FINISHED`\n * 가 빠져 있었고, 그때 목록에 낱말을 더한 것은 진짜 빈 자리(그 커넥터가 자기 상태 표를\n * 안 쓰고 있었다)를 가린 땜빵이었다.\n * ② **시뮬 트윈에서는 언제나 0 이었다.** 시뮬은 오더를 이행하며 `fulfilled` 만 올리고\n * `status` 는 바꾸지 않는다 — 낱말로 묻는 판정은 시뮬의 완료를 한 건도 세지 못했다.\n *\n * 표준이 그 축을 갈라 두었다: `orders.status` ↔ `RequestState`(표준도 열거하지 않는다)이고,\n * 「됐나」는 **실적**에서 읽는다(`JobResponse`, 요구는 `SegmentRequirement.Quantity`). 그래서\n * 판정의 1차 근거는 **양**이고 낱말은 양으로 말할 수 없는 종결(취소·중도 종료)에만 쓴다.\n */\n const status = String(d.status ?? '').toLowerCase()\n const done = isOrderTerminal({ status: d.status, requested: d.requested, fulfilled: d.fulfilled })\n if (at >= window.fromMs && at <= window.toMs) {\n /*\n * **오더당 한 번만 센다.** 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수\n * 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이 알리지 않고 부풀려진다.\n * 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있다(「완료는 작업당 하나」) — 오더만 없었다.\n */\n if (done) {\n const id = String(d.orderId ?? '')\n if (id && !completedOrderIds.has(id)) {\n completedOrderIds.add(id)\n completedOrders++\n } else if (!id) completedOrders++ // 정체를 모르면 셈에서 빼지 않는다(알리지 않고 줄이지 않는다)\n }\n /*\n * **완료로 세지 않은 낱말의 인구조사.** 「못 읽었다」가 아니다 — `picking` 처럼 완료가 아닌\n * 낱말이 여기 오는 것은 정상이다. 완료가 0 인데 여기 낱말이 쌓여 있으면 원인이 곧 보인다.\n *\n * 빈 낱말은 세지 않는다: 상태를 말하지 않은 사건은 다른 결함이고(유입 문이 상태를 필수로\n * 받는다) 여기서 섞으면 이 값의 뜻이 흐려진다.\n */\n else if (status) orderStatusNotCounted.set(status, (orderStatusNotCounted.get(status) ?? 0) + 1)\n }\n }\n }\n\n /*\n * 에너지 — 창에 **온전히 든** 구간 마감 사건만 센다(걸친 구간은 커널 규칙이 뺀다).\n * 사건이 하나도 없으면 이 트윈에는 에너지가 없는 것이므로 블록 자체를 내지 않는다.\n */\n const demandWindows = events\n .filter(e => e.eventType === ENERGY_EVENT.demandWindow)\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n /* 최대(`maxKW`)도 함께 넘긴다 — 요금이 걸리는 수는 평균이 아니라 최대다.\n 총부하(`grossMaxKW`)는 「무엇이 깎였나」를 되짚는 축이다(발전·방전 차감 전). */\n return {\n startMs: Number(d.startMs),\n endMs: Number(d.endMs),\n meanKW: Number(d.meanKW),\n maxKW: Number(d.maxKW),\n grossMaxKW: Number(d.grossMaxKW),\n /* 지점별 몫 — 계량이 분기에 있으면 그 값은 **측정**이다(배분이 아니다). 커널이 계층 판정\n (`root`)까지 실어 주므로 여기서 같은 규칙을 다시 쓰지 않는다. */\n points: (Array.isArray(d.points) ? d.points : [])\n .map((p: any) => ({ id: String(p?.id ?? ''), kWh: Number(p?.kWh), root: p?.root === true }))\n .filter((p: any) => p.id && Number.isFinite(p.kWh)),\n /* 피크가 찍힌 순간의 지점별 kW — 요금이 걸린 수는 kW 다(전력량이 큰 분기와 피크를 만든 분기는 다르다). */\n pointsAtPeak: (Array.isArray(d.pointsAtPeak) ? d.pointsAtPeak : [])\n .map((p: any) => ({ id: String(p?.id ?? ''), kW: Number(p?.kW) }))\n .filter((p: any) => p.id && Number.isFinite(p.kW))\n }\n })\n .filter(w => Number.isFinite(w.startMs) && Number.isFinite(w.endMs))\n\n /*\n * 발전 기간 사실 — 커널이 기간을 마감할 때 낸 것이다. 구간 마감과 같은 규칙으로 **창에 온전히 든\n * 것만** 센다(걸친 기간을 반만 세면 합이 창을 넘는다).\n *\n * 관측 구간(`observedFrom`·`observedTo`)을 함께 읽는다 — 값의 근거가 얼마나 덮였는지가 그 둘에 있다.\n */\n const generationPeriods = onceById(events.filter(e => e.eventType === ENERGY_EVENT.generationPeriod))\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n equipmentId: String(d.equipmentId ?? ''),\n kWh: Number(d.kWh),\n startMs: Date.parse(String(d.periodStart ?? '')),\n endMs: Date.parse(String(d.periodEnd ?? '')),\n observedFromMs: Date.parse(String(d.observedFrom ?? '')),\n observedToMs: Date.parse(String(d.observedTo ?? ''))\n }\n })\n .filter(g => g.equipmentId && Number.isFinite(g.kWh) && Number.isFinite(g.startMs) && Number.isFinite(g.endMs))\n\n /*\n * 밖에서 마감되어 온 사용 구간 — 구간 마감과 같은 규칙으로 **창에 온전히 든 것만** 센다.\n */\n const usagePeriods = onceById(events.filter(e => e.eventType === ENERGY_EVENT.usagePeriod))\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n meterId: String(d.meterId ?? ''),\n kWh: Number(d.kWh),\n startMs: Date.parse(String(d.from ?? '')),\n endMs: Date.parse(String(d.to ?? '')),\n unitPrice: Number(d.unitPrice),\n maxKW: Number(d.maxKW),\n currency: d.currency ? String(d.currency) : undefined,\n basis: d.basis ? String(d.basis) : 'unstated'\n }\n })\n .filter(u => u.meterId && Number.isFinite(u.kWh) && Number.isFinite(u.startMs) && Number.isFinite(u.endMs))\n\n /* 청구서 — 창에 온전히 든 것 중 가장 늦게 끝난 것 하나. */\n const bills = onceById(events.filter(e => e.eventType === ENERGY_EVENT.bill))\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n from: String(d.from ?? ''),\n to: String(d.to ?? ''),\n startMs: Date.parse(String(d.from ?? '')),\n endMs: Date.parse(String(d.to ?? '')),\n total: Number(d.total),\n energyCharge: Number(d.energyCharge),\n demandCharge: Number(d.demandCharge),\n currency: d.currency ? String(d.currency) : undefined,\n billingDemandKW: Number(d.billingDemandKW)\n }\n })\n .filter(b => Number.isFinite(b.startMs) && Number.isFinite(b.endMs))\n\n /* 발전 단가 — 기간마다 그때의 값이다(날마다 바뀐다). */\n const generationPrices = onceById(events.filter(e => e.eventType === ENERGY_EVENT.generationPrice))\n .map(e => {\n const d = e.payload?.data ?? e.payload ?? {}\n return {\n startMs: Date.parse(String(d.from ?? '')),\n endMs: Date.parse(String(d.to ?? '')),\n unitPrice: Number(d.unitPrice),\n currency: String(d.currency ?? '')\n }\n })\n .filter(p => Number.isFinite(p.startMs) && Number.isFinite(p.endMs) && Number.isFinite(p.unitPrice) && p.currency)\n\n const leads: number[] = []\n const works: number[] = []\n const waits: number[] = []\n const busyByResource = new Map<string, number>()\n /* 창 안에 완료된 건들의 **기록** — 통계를 낸 뒤에도 버리지 않는다. 관점 축은 같은 기록을 다시\n * 묶는 것일 뿐이므로, 기록을 남기면 축을 하나 더 붙이는 값이 거의 0 이 된다. */\n const records: CompletionRecord[] = []\n let tasksInWindow = 0\n let unpairedLead = 0\n let unpairedWork = 0\n\n for (const t of tasks) {\n const completed = t.completedMs\n if (completed === undefined) continue // 아직 완료되지 않은 작업 — 성과로 세지 않는다\n if (completed < window.fromMs || completed > window.toMs) continue // 성과는 완료 시점으로 센다\n tasksInWindow++\n const c = t.createdMs\n const s = t.startedMs\n const rec: CompletionRecord = { taskId: t.taskId, at: completed, facets: t.facets }\n const resource = rec.facets.resource\n if (c !== undefined && completed >= c) leads.push((rec.leadMs = completed - c))\n else unpairedLead++\n if (s !== undefined && completed >= s) {\n const busy = completed - s\n works.push((rec.workMs = busy))\n if (resource) busyByResource.set(resource, (busyByResource.get(resource) ?? 0) + busy)\n } else {\n unpairedWork++\n }\n if (c !== undefined && s !== undefined && s >= c) waits.push((rec.waitMs = s - c))\n records.push(rec)\n }\n\n const span = Math.max(1, window.toMs - window.fromMs)\n const utilization = [...busyByResource.entries()]\n .map(([resourceId, busyMs]) => ({ resourceId, busyMs, ratio: Math.min(1, busyMs / span) }))\n .sort((a, b) => b.busyMs - a.busyMs)\n\n return {\n window: { fromMs: window.fromMs, toMs: window.toMs },\n throughput: { tasks: tasksInWindow, orders: completedOrders },\n /* 발전만 있는 현장(계량 없는 발전소)도 답이 나와야 한다 — 둘 중 하나만 있어도 블록을 낸다. */\n ...(demandWindows.length || generationPeriods.length || usagePeriods.length || bills.length\n ? (() => {\n const e = energyOfWindows(demandWindows, { startMs: window.fromMs, endMs: window.toMs })\n const usage = usageOfPeriods(usagePeriods, { startMs: window.fromMs, endMs: window.toMs })\n /*\n * 어느 회계를 쓸지 정한다 — **더하지 않는다.** 계량 표본에서 우리가 마감한 값이 있으면 그것을\n * 쓰고(강한 근거), 없으면 밖에서 마감되어 온 값을 쓴다. 무엇을 썼는지 함께 낸다.\n */\n const usedKWh = e.kWh !== undefined ? e.kWh : usage?.kWh\n const kWhBasis = e.kWh !== undefined ? 'demand-window' : usage ? 'usage-period' : undefined\n /* 분모는 **완료 오더**다(대당 전력). 창에 완료가 없으면 커널 규칙이 「정의되지 않음」으로 답한다. */\n const i = energyIntensity({\n kWh: usedKWh,\n energyWindow: e.window,\n denominator: { kind: 'output', value: completedOrders, window: e.window }\n })\n /*\n * 요금은 **구간마다 제 단가로** 매긴 값이 있으면 그것을 쓴다. 단일 단가로 접으면 시간대마다\n * 다른 단가가 사라지고, 「피크를 깎아 얼마를 아끼나」가 그 차이 위에 선다.\n */\n /* 요금적용전력은 청구서가 먼저다 — 선언은 청구가 오지 않는 현장의 몫이다. */\n /* 이 창의 최고 수요 — 사용 구간이 실어 온 값 중 가장 큰 것(원본 계량기가 잰 값이다). */\n const peaks = usagePeriods\n .filter(u => u.startMs >= window.fromMs && u.endMs <= window.toMs && Number.isFinite(u.maxKW))\n .map(u => ({ kW: u.maxKW as number, toMs: u.endMs }))\n const observedPeak = peaks.length ? peaks.reduce((a, x) => (x.kW > a.kW ? x : a)) : undefined\n const billingDemand = billingDemandOf(\n bills.filter(b => Number.isFinite(b.billingDemandKW)).map(b => ({ endMs: b.endMs, billingDemandKW: b.billingDemandKW })),\n options.tariffBasis,\n options.lastBill,\n options.tariff?.billingDemandKW,\n observedPeak\n )\n const cost = options.tariff\n ? electricityCost({\n kWh: usedKWh,\n peakKW: e.peakKW,\n tariff: { ...options.tariff, ...(billingDemand ? { billingDemandKW: billingDemand.kW } : {}) },\n ...(usage?.charge !== undefined ? { energyChargeFromPeriods: usage.charge } : {})\n })\n : undefined\n return {\n energy: {\n ...(usedKWh !== undefined ? { kWh: usedKWh } : {}),\n ...(kWhBasis ? { kWhBasis } : {}),\n ...(billingDemand ? { billingDemand } : {}),\n ...(usage ? { usage } : {}),\n /* 이 창의 최대수요 — 계약 대비 판정이 보는 수다(전력량과 같은 구간 집합에서 왔다). */\n ...(e.peakKW !== undefined ? { peakKW: e.peakKW, peakStartMs: e.peakStartMs } : {}),\n /* 깎인 양은 **같은 구간의 두 수**의 차이다 — 여기서 한 번만 계산해 두 곳이 갈라지지 않게. */\n ...(e.peakGrossKW !== undefined && e.peakKW !== undefined\n ? { peakGrossKW: e.peakGrossKW, ...(e.peakGrossKW > e.peakKW ? { shavedKW: e.peakGrossKW - e.peakKW } : {}) }\n : {}),\n basis: e.basis,\n counted: e.counted,\n skipped: e.skipped,\n intensity:\n i.value === null\n ? { value: null, reason: (i as any).reason }\n : { value: i.value, unit: (i as any).unit },\n ...(measuredFeeders(demandWindows, { startMs: window.fromMs, endMs: window.toMs }, e.kWh, options.meterFeeder) ??\n feederShares(e.kWh, utilization, options.resourceFeeder)),\n /* 금액은 같은 창의 전력량·최대수요에서 나온다 — 창이 하나뿐이라 어긋날 수 없다. */\n ...(cost ? { cost } : {}),\n ...generatedOfPeriods(\n generationPeriods,\n { startMs: window.fromMs, endMs: window.toMs },\n options.generationRated,\n /* 창 안의 단가 사실이 먼저이고, 없으면 이어받은 마지막 단가를 쓴다. */\n generationPrices.length\n ? generationPrices\n : options.generationPrice\n ? [\n {\n startMs: Date.parse(options.generationPrice.from),\n endMs: Date.parse(options.generationPrice.to),\n unitPrice: options.generationPrice.unitPrice,\n currency: options.generationPrice.currency\n }\n ]\n : []\n ),\n /* 청구서는 사실이다 — 우리 계산을 덮지 않고 나란히 둔다(§`billOfWindow`). */\n ...(() => {\n const b = billOfWindow(bills, { startMs: window.fromMs, endMs: window.toMs }, cost as any)\n return b ? { bill: b } : {}\n })()\n }\n }\n })()\n : {}),\n leadTime: stats(leads),\n workTime: stats(works),\n waitTime: stats(waits),\n utilization,\n unpaired: { leadTime: unpairedLead, workTime: unpairedWork },\n /* 못 읽은 오더 상태 낱말 — 비면 빈 객체다(§`orderStatusNotCounted`). */\n orderStatusNotCounted: Object.fromEntries(orderStatusNotCounted),\n eventsSeen: seen,\n eventsInWindow: inWindow,\n ...(options.groupBy ? { groups: groupRecords(records, options) } : {})\n }\n}\n\n/**\n * 관점 × 비교 교차 — **\"구역별로 어제 대비 어떤가\"**.\n *\n * 축(무엇을)과 비교(언제 대비)를 따로 보면 사용자가 머릿속에서 두 표를 대조해야 한다. 운영에서 가장\n * 값 있는 질문은 그 교차다: 전체는 비슷한데 **한 구역만 나빠진** 경우를 전체 숫자로는 절대 못 본다.\n *\n * 정직성 규율:\n * · 두 구간에 **모두 있는** 축만 delta 를 낸다.\n * · 지금만 있는 축은 `isNew`(이전에 없던 일) — delta 를 건수와 같게 적으면 \"늘었다\" 로 오독된다.\n * · **사라진 축**(전에 있었고 지금 없는 것)은 따로 돌려준다. 표에서 그냥 빠지면 **멈춘 구역이\n * 눈에 보이지 않는다** — 운영에서는 이것이 가장 중요한 신호일 수 있다.\n */\nexport interface CrossedGroup extends KpiGroup {\n /** 비교 구간의 건수(두 구간에 모두 있을 때만). */\n prevTasks?: number\n /** 현재 − 비교구간 건수(두 구간에 모두 있을 때만). */\n deltaTasks?: number\n /** 비교 구간 대비 작업시간 중앙값 차이(둘 다 측정된 때만). */\n deltaWorkP50Ms?: number\n /** 비교 구간에는 없던 축 — delta 대신 이 표식을 준다. */\n isNew?: boolean\n}\n\nexport function crossGroups(\n now: KpiGroup[],\n prev: KpiGroup[]\n): { items: CrossedGroup[]; disappeared: { key: string; tasks: number }[] } {\n const before = new Map(prev.map(g => [g.key, g]))\n const items: CrossedGroup[] = now.map(g => {\n const p = before.get(g.key)\n if (!p) return { ...g, isNew: true }\n return {\n ...g,\n prevTasks: p.tasks,\n deltaTasks: g.tasks - p.tasks,\n ...(deltaP50(g.workTime, p.workTime) !== undefined ? { deltaWorkP50Ms: deltaP50(g.workTime, p.workTime) } : {})\n }\n })\n const present = new Set(now.map(g => g.key))\n const disappeared = prev.filter(g => !present.has(g.key)).map(g => ({ key: g.key, tasks: g.tasks }))\n return { items, disappeared }\n}\n\n/* ── 시간 비교 ──────────────────────────────────────────────────────────────\n * 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다(\"29초\" 가 개선인지 악화인지). 같은 길이의 창을 옮겨\n * 한 번 더 계산하면 되므로(폴드는 순수) 값싸다. 창 옮기기와 차이 계산은 계산 규칙이라 여기 있다 —\n * 조회 층에 두면 테스트가 DB 를 끌고 와야 한다.\n */\n\n/**\n * 비교 구간의 창 — 같은 **길이**로 옮긴다.\n *\n * previous: 바로 앞 구간(경계가 맞물리게 — 겹치거나 벌어지면 구간의 합이 어긋난다).\n * yesterday: 정확히 24시간 전 같은 자리. 시뮬 트윈의 클록이 실제 시각과 달라도 **차분은 정확**하다.\n */\nexport function shiftWindow(fromMs: number, toMs: number, basis: 'previous' | 'yesterday'): KpiWindow {\n const span = toMs - fromMs\n const back = basis === 'yesterday' ? 24 * 60 * 60_000 : span\n return { fromMs: fromMs - back, toMs: toMs - back }\n}\n\n/**\n * 중앙값 차이(현재 − 비교구간) — **둘 다 측정된 것만** 낸다.\n *\n * 한쪽이 결측이면 차이는 **없는 것**이다(0 이 아니다). 0 으로 내면 \"변화 없음\" 이라는 거짓이 된다.\n * 좋고 나쁨은 판단하지 않는다 — 처리량은 클수록, 소요는 작을수록 좋다는 해석은 소비처의 몫이다.\n */\nexport function deltaP50(now: DurationStats, prev: DurationStats): number | undefined {\n return now.count > 0 && prev.count > 0 ? now.p50Ms - prev.p50Ms : undefined\n}\n\n/** 구간 하나의 최소 요약 — 추세를 그릴 정보만(전체 KPI 를 구간마다 실어 보내지 않는다). */\nexport interface KpiBucket {\n fromMs: number\n toMs: number\n tasks: number\n orders: number\n /** 그 구간의 작업시간 중앙값 — 추세에서 \"느려지고 있는가\" 를 보는 값. */\n workP50Ms: number\n}\n\n/**\n * 창을 N 등분해 구간별로 처리한다 — **같은 이벤트를 창만 바꿔** 다시 계산한다(조회 추가 없음).\n *\n * 마지막 구간은 나머지를 흡수한다(부동소수 나눗셈으로 끝이 밀리면 마지막 완료가 빠진다).\n * `count <= 1` 이면 쪼갤 것이 없으므로 빈 배열 — 호출부가 \"추세 없음\" 으로 다룬다.\n */\nexport function foldKpiBuckets(events: KpiEvent[], window: KpiWindow, count: number): KpiBucket[] {\n const n = Math.max(0, Math.min(48, Math.floor(count)))\n if (n <= 1) return []\n const step = (window.toMs - window.fromMs) / n\n const out: KpiBucket[] = []\n for (let i = 0; i < n; i++) {\n const fromMs = window.fromMs + step * i\n const toMs = i === n - 1 ? window.toMs : window.fromMs + step * (i + 1)\n const b = foldKpi(events, { fromMs, toMs })\n out.push({ fromMs, toMs, tasks: b.throughput.tasks, orders: b.throughput.orders, workP50Ms: b.workTime.p50Ms })\n }\n return out\n}\n"]}
|