@things-factory/headless-twin 10.0.8 → 10.0.10
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/attention-digest.d.ts +52 -0
- package/dist-server/engine/attention-digest.js +76 -0
- package/dist-server/engine/attention-digest.js.map +1 -0
- package/dist-server/engine/canonical-ingest.d.ts +2 -2
- package/dist-server/engine/canonical-ingest.js +24 -2
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.d.ts +33 -0
- package/dist-server/engine/command-routing.js +53 -0
- package/dist-server/engine/command-routing.js.map +1 -0
- package/dist-server/engine/energy-topology.d.ts +86 -0
- package/dist-server/engine/energy-topology.js +144 -0
- package/dist-server/engine/energy-topology.js.map +1 -0
- package/dist-server/engine/index.d.ts +11 -0
- package/dist-server/engine/index.js +17 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-baseline.d.ts +78 -0
- package/dist-server/engine/kpi-baseline.js +123 -0
- package/dist-server/engine/kpi-baseline.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +137 -1
- package/dist-server/engine/kpi-fold.js +225 -0
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +32 -1
- package/dist-server/engine/kpi-query.js +262 -20
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/kpi-target.d.ts +17 -0
- package/dist-server/engine/kpi-target.js +24 -2
- package/dist-server/engine/kpi-target.js.map +1 -1
- package/dist-server/engine/live-attentions.d.ts +1 -0
- package/dist-server/engine/live-attentions.js +7 -1
- package/dist-server/engine/live-attentions.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +25 -0
- package/dist-server/engine/live-feed-registry.js +51 -0
- package/dist-server/engine/live-feed-registry.js.map +1 -0
- package/dist-server/engine/load-meter.d.ts +181 -0
- package/dist-server/engine/load-meter.js +267 -0
- package/dist-server/engine/load-meter.js.map +1 -0
- package/dist-server/engine/local-declarations.d.ts +262 -0
- package/dist-server/engine/local-declarations.js +528 -0
- package/dist-server/engine/local-declarations.js.map +1 -0
- package/dist-server/engine/model-basis.d.ts +39 -6
- package/dist-server/engine/model-basis.js +69 -9
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-vocabulary.d.ts +17 -0
- package/dist-server/engine/model-vocabulary.js +81 -0
- package/dist-server/engine/model-vocabulary.js.map +1 -0
- package/dist-server/engine/oee-accumulator.d.ts +28 -0
- package/dist-server/engine/oee-accumulator.js +26 -1
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/operation-basis.d.ts +32 -0
- package/dist-server/engine/operation-basis.js +87 -0
- package/dist-server/engine/operation-basis.js.map +1 -0
- package/dist-server/engine/property-effects.d.ts +30 -0
- package/dist-server/engine/property-effects.js +192 -0
- package/dist-server/engine/property-effects.js.map +1 -0
- package/dist-server/engine/runtime-key.d.ts +15 -0
- package/dist-server/engine/runtime-key.js +64 -0
- package/dist-server/engine/runtime-key.js.map +1 -0
- package/dist-server/engine/spec-coverage.js +3 -3
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +21 -0
- package/dist-server/engine/state-axes.js +59 -0
- package/dist-server/engine/state-axes.js.map +1 -0
- package/dist-server/engine/structure-diff.js +1 -1
- package/dist-server/engine/structure-diff.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +9 -2
- package/dist-server/engine/travel-estimator.js +11 -6
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +418 -57
- package/dist-server/engine/twin-engine.js +1599 -306
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +93 -13
- package/dist-server/engine/warm-start.js +114 -14
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +17 -11
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
- package/dist-server/migrations/index.d.ts +2 -0
- package/dist-server/migrations/index.js +10 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/service/index.d.ts +3 -2
- package/dist-server/service/index.js +25 -17
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +34 -0
- package/dist-server/service/reference/discovery-result.js +84 -0
- package/dist-server/service/reference/discovery-result.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +53 -0
- package/dist-server/service/reference/ingest-space.js +79 -0
- package/dist-server/service/reference/ingest-space.js.map +1 -0
- package/dist-server/service/reference/knob-defaults.d.ts +20 -0
- package/dist-server/service/reference/knob-defaults.js +59 -0
- package/dist-server/service/reference/knob-defaults.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +23 -0
- package/dist-server/service/reference/reference-live.js +123 -6
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +227 -11
- package/dist-server/service/reference/reference-master.js +206 -25
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +33 -3
- package/dist-server/service/reference/reference-resolver.js +290 -23
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +19 -3
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
- package/dist-server/service/twin-audit/command-audit.js +53 -0
- package/dist-server/service/twin-audit/command-audit.js.map +1 -0
- package/dist-server/service/twin-audit/index.d.ts +4 -0
- package/dist-server/service/twin-audit/index.js +8 -0
- package/dist-server/service/twin-audit/index.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
- package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +14 -5
- package/dist-server/service/twin-event/twin-event.js +46 -14
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
- package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
- package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
- package/dist-server/service/twin-instance/twin-instance.js +66 -18
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
- package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +63 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/epcis-coverage.js +174 -0
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
- package/dist-server/service/twin-model/index.d.ts +13 -0
- package/dist-server/service/twin-model/index.js +29 -0
- package/dist-server/service/twin-model/index.js.map +1 -0
- package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
- package/dist-server/service/twin-model/isa95-coverage.js +155 -0
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.d.ts +16 -0
- package/dist-server/service/twin-model/project-structure.js +173 -0
- package/dist-server/service/twin-model/project-structure.js.map +1 -0
- package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
- package/dist-server/service/twin-model/standard-coverage.js +37 -0
- package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
- package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
- package/dist-server/service/twin-model/twin-equipment.js +134 -0
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-location.d.ts +58 -0
- package/dist-server/service/twin-model/twin-location.js +130 -0
- package/dist-server/service/twin-model/twin-location.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
- package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
- package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
- package/dist-server/service/twin-model/twin-model-query.js +551 -0
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
- package/dist-server/service/twin-model/twin-operation.js +137 -0
- package/dist-server/service/twin-model/twin-operation.js.map +1 -0
- package/dist-server/service/twin-space/move-space.d.ts +65 -0
- package/dist-server/service/twin-space/move-space.js +166 -0
- package/dist-server/service/twin-space/move-space.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
- package/dist-server/service/twin-space/space-integrity.js +182 -0
- package/dist-server/service/twin-space/space-integrity.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-area.js +4 -4
- package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.js +6 -6
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +17 -1
- package/dist-server/service/twin-space/twin-space.js +15 -5
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +8 -7
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/axis-read.d.ts +12 -0
- package/dist-shared/axis-read.js +32 -0
- package/dist-shared/axis-read.js.map +1 -0
- package/dist-shared/entity-delta.d.ts +29 -0
- package/dist-shared/entity-delta.js +287 -0
- package/dist-shared/entity-delta.js.map +1 -0
- package/dist-shared/kpi-broadcast.d.ts +4 -0
- package/dist-shared/kpi-broadcast.js +16 -0
- package/dist-shared/kpi-broadcast.js.map +1 -0
- package/dist-shared/twin-level.d.ts +23 -0
- package/dist-shared/twin-level.js +52 -0
- package/dist-shared/twin-level.js.map +1 -0
- package/package.json +14 -12
- package/server/engine/attention-digest.ts +102 -0
- package/server/engine/canonical-ingest.ts +36 -4
- package/server/engine/command-routing.ts +67 -0
- package/server/engine/energy-topology.ts +189 -0
- package/server/engine/index.ts +17 -0
- package/server/engine/kpi-baseline.ts +202 -0
- package/server/engine/kpi-fold.ts +389 -1
- package/server/engine/kpi-query.ts +295 -22
- package/server/engine/kpi-target.ts +24 -2
- package/server/engine/live-attentions.ts +7 -2
- package/server/engine/live-feed-registry.ts +58 -0
- package/server/engine/load-meter.ts +384 -0
- package/server/engine/local-declarations.ts +700 -0
- package/server/engine/model-basis.ts +78 -10
- package/server/engine/model-vocabulary.ts +82 -0
- package/server/engine/oee-accumulator.ts +34 -1
- package/server/engine/operation-basis.ts +100 -0
- package/server/engine/property-effects.ts +199 -0
- package/server/engine/runtime-key.ts +58 -0
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/state-axes.ts +60 -0
- package/server/engine/structure-diff.ts +1 -1
- package/server/engine/travel-estimator.ts +11 -6
- package/server/engine/twin-engine.ts +1710 -297
- package/server/engine/warm-start.ts +207 -22
- package/server/index.ts +13 -10
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
- package/server/migrations/index.ts +7 -0
- package/server/service/index.ts +9 -1
- package/server/service/reference/discovery-result.ts +95 -0
- package/server/service/reference/ingest-space.ts +104 -0
- package/server/service/reference/knob-defaults.ts +59 -0
- package/server/service/reference/reference-live.ts +121 -6
- package/server/service/reference/reference-master.ts +362 -35
- package/server/service/reference/reference-resolver.ts +307 -23
- package/server/service/reference/template-registry.ts +20 -5
- package/server/service/twin-attention/twin-attention-query.ts +43 -6
- package/server/service/twin-audit/command-audit.ts +81 -0
- package/server/service/twin-audit/index.ts +5 -0
- package/server/service/twin-audit/twin-audit-event.ts +112 -0
- package/server/service/twin-audit/twin-audit-query.ts +72 -0
- package/server/service/twin-control/twin-control-mutation.ts +53 -13
- package/server/service/twin-event/twin-event-keys.ts +1 -1
- package/server/service/twin-event/twin-event.ts +52 -15
- package/server/service/twin-forecast/forecast-metrics.ts +60 -0
- package/server/service/twin-forecast/forecast-tuning.ts +40 -0
- package/server/service/twin-forecast/gap-analytics.ts +48 -9
- package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
- package/server/service/twin-instance/twin-instance.ts +127 -22
- package/server/service/twin-journal/twin-journal-query.ts +108 -14
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
- package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
- package/server/service/twin-model/epcis-coverage.ts +195 -0
- package/server/service/twin-model/iec61850-coverage.ts +117 -0
- package/server/service/twin-model/index.ts +25 -0
- package/server/service/twin-model/isa95-coverage.ts +190 -0
- package/server/service/twin-model/project-structure.ts +199 -0
- package/server/service/twin-model/standard-coverage.ts +35 -0
- package/server/service/twin-model/twin-equipment.ts +150 -0
- package/server/service/twin-model/twin-lineage-query.ts +193 -0
- package/server/service/twin-model/twin-location.ts +140 -0
- package/server/service/twin-model/twin-model-item-query.ts +848 -0
- package/server/service/twin-model/twin-model-mutation.ts +503 -0
- package/server/service/twin-model/twin-model-query.ts +537 -0
- package/server/service/twin-model/twin-model-tree-query.ts +180 -0
- package/server/service/twin-model/twin-operation.ts +150 -0
- package/server/service/twin-space/move-space.ts +262 -0
- package/server/service/twin-space/space-integrity.ts +293 -0
- package/server/service/twin-space/twin-space-area.ts +4 -4
- package/server/service/twin-space/twin-space-representation.ts +6 -6
- package/server/service/twin-space/twin-space-resolver.ts +365 -22
- package/server/service/twin-space/twin-space.ts +40 -6
- package/server/service/twin-state/twin-state-subscription.ts +1 -1
- package/server/service/twin-structure/twin-structure.ts +10 -7
- package/server/service/twin-target/twin-target-resolver.ts +22 -4
- package/shared/axis-read.ts +28 -0
- package/shared/entity-delta.ts +286 -0
- package/shared/kpi-broadcast.ts +13 -0
- package/shared/twin-level.ts +48 -0
- package/test/adopt-structure-live.test.ts +133 -0
- package/test/attention-digest.test.ts +135 -0
- package/test/axis-read.test.ts +87 -0
- package/test/boot-resume.test.ts +205 -0
- package/test/canonical-ingest-vocabularies.test.ts +118 -0
- package/test/capability-mapping.test.ts +5 -5
- package/test/command-routing.test.ts +61 -0
- package/test/declaration-reaches-model.test.ts +178 -0
- package/test/discovery-result.test.ts +75 -0
- package/test/energy-topology.test.ts +113 -0
- package/test/entity-delta.test.ts +195 -25
- package/test/event-time-column.test.ts +67 -0
- package/test/forecast-metrics.test.ts +71 -0
- package/test/forecast-tuning.test.ts +42 -0
- package/test/gap-analytics.test.ts +34 -7
- package/test/ingest-bench.test.ts +9 -9
- package/test/ingest-running-guard.test.ts +136 -0
- package/test/ingest-space.test.ts +78 -0
- package/test/instance-cache-lifecycle.test.ts +110 -0
- package/test/kernel-kind-guard.test.ts +96 -0
- package/test/knob-defaults.test.ts +72 -0
- package/test/kpi-baseline-db.test.ts +216 -0
- package/test/kpi-baseline.test.ts +196 -0
- package/test/kpi-fold.test.ts +393 -2
- package/test/kpi-query-bench.test.ts +130 -0
- package/test/lineage-survives-restart.test.ts +203 -0
- package/test/live-feed-registry.test.ts +67 -0
- package/test/live-kernel-facts.test.ts +161 -0
- package/test/live-mirror-parity.test.ts +54 -14
- package/test/load-meter.test.ts +314 -0
- package/test/local-declarations.test.ts +554 -0
- package/test/master-to-twin.test.ts +128 -36
- package/test/model-basis.test.ts +56 -1
- package/test/model-vocabulary.test.ts +114 -0
- package/test/move-space.test.ts +149 -0
- package/test/mutation-gate.test.ts +122 -0
- package/test/oee-accumulator.test.ts +90 -7
- package/test/operation-basis.test.ts +100 -0
- package/test/operations-capability-db.test.ts +165 -0
- package/test/project-structure-db.test.ts +226 -0
- package/test/projection-reaches-screen.test.ts +272 -0
- package/test/property-effects.test.ts +96 -0
- package/test/registry-key-guard.test.ts +80 -0
- package/test/resync-origin-site.test.ts +77 -0
- package/test/runtime-key.test.ts +66 -0
- package/test/scale-twin-bench.test.ts +7 -7
- package/test/snapshot-freshness.test.ts +60 -0
- package/test/space-integrity.test.ts +223 -0
- package/test/standard-coverage.test.ts +94 -0
- package/test/state-axes.test.ts +74 -0
- package/test/streamline-e2e.test.ts +16 -16
- package/test/structure-revision-db.test.ts +16 -15
- package/test/tenant-registry-db.test.ts +149 -0
- package/test/twin-audit.test.ts +83 -0
- package/test/twin-model-item-db.test.ts +275 -0
- package/test/twin-model-tree-db.test.ts +176 -0
- package/test/twin-origin-resync.test.ts +114 -0
- package/test/warm-start-seam.test.ts +140 -0
- package/test/warm-start.test.ts +223 -4
- package/tsconfig.json +6 -1
- package/tsconfig.shared.json +23 -0
- package/tsconfig.shared.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist-server/engine/entity-delta.d.ts +0 -19
- package/dist-server/engine/entity-delta.js +0 -161
- package/dist-server/engine/entity-delta.js.map +0 -1
- package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
- package/dist-server/service/twin-event/backfill-keys.js +0 -63
- package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/server/engine/entity-delta.ts +0 -169
- package/server/service/twin-event/backfill-keys.ts +0 -72
|
@@ -25,7 +25,19 @@
|
|
|
25
25
|
* 공정 타임라인과 **다른 답**을 냈다(재전송된 완료를 두 건으로 세어 처리량·점유가 부풀려졌다).
|
|
26
26
|
* 이 파일은 이제 "접힌 기록 → 창 지표" 만 한다.
|
|
27
27
|
*/
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
ENERGY_EVENT,
|
|
30
|
+
attributeEnergy,
|
|
31
|
+
electricityCost,
|
|
32
|
+
energyIntensity,
|
|
33
|
+
energyOfWindows,
|
|
34
|
+
foldTaskRecords,
|
|
35
|
+
activeShiftAt,
|
|
36
|
+
type ElectricityCost,
|
|
37
|
+
type TariffDeclaration,
|
|
38
|
+
type TaskFacets,
|
|
39
|
+
type WorkCalendarEntry
|
|
40
|
+
} from '@operato/twin-kernel'
|
|
29
41
|
|
|
30
42
|
/** 저널 한 줄 — 필요한 것만(엔티티·typeorm 비의존). */
|
|
31
43
|
export interface KpiEvent {
|
|
@@ -83,6 +95,113 @@ export interface KpiResult {
|
|
|
83
95
|
window: { fromMs: number; toMs: number }
|
|
84
96
|
/** 창 안에 **완료된** 건수 — 성과는 완료 시점으로 센다(시작 시점으로 세면 진행 중인 것이 섞인다). */
|
|
85
97
|
throughput: { tasks: number; orders: number }
|
|
98
|
+
/**
|
|
99
|
+
* 에너지 — **이 창의 전력량과 원단위.** 에너지 트윈이 없으면 `undefined`(0 이 아니다).
|
|
100
|
+
*
|
|
101
|
+
* ── 왜 KPI 폴드 안에 있나 (2026-08-14, §10 7단계) ─────────────────────────
|
|
102
|
+
* 원단위는 분자(전력량)와 분모(산출)를 **같은 창**에서 나눈 값이다. 질의를 따로 두면 두 창을 맞추는
|
|
103
|
+
* 일이 소비처의 몫이 되고, 어긋난 두 사실을 나눈 숫자는 아무것도 뜻하지 않는다(커널의 규칙이 그것을
|
|
104
|
+
* 거절한다). 같은 폴드 안에 두면 창이 하나뿐이라 어긋날 수 없다.
|
|
105
|
+
*
|
|
106
|
+
* 분자는 **저널의 구간 마감 사건**에서 온다(`energy.demand.window`) — 커널 상태가 아니라 저널이므로
|
|
107
|
+
* 멈춘 트윈·과거 구간에도 답할 수 있다.
|
|
108
|
+
*/
|
|
109
|
+
energy?: {
|
|
110
|
+
/** 이 창의 전력량. 셀 수 있는 구간이 없으면 `undefined`. */
|
|
111
|
+
kWh?: number
|
|
112
|
+
/**
|
|
113
|
+
* 이 창의 **최대수요**(kW)와 그것이 난 구간의 시작.
|
|
114
|
+
*
|
|
115
|
+
* 전력량과 다른 질문이다: 전력량은 요금의 사용량 부분이고, 최대수요는 **기본요금**과 계약 위반을
|
|
116
|
+
* 정한다. 평균으로 갈음할 수 없어서 커널이 구간의 최대들 중 가장 큰 값을 따로 낸다.
|
|
117
|
+
*/
|
|
118
|
+
peakKW?: number
|
|
119
|
+
peakStartMs?: number
|
|
120
|
+
/**
|
|
121
|
+
* 그 피크 구간에서 **설비가 끌어당긴** 최대(발전·방전 차감 전)와 그 차이(깎인 양).
|
|
122
|
+
*
|
|
123
|
+
* 요금은 계통 수요(`peakKW`)로 매겨지지만, 「무엇이 그 수요를 만들었나」와 「무엇이 덜어냈나」는
|
|
124
|
+
* 이 두 수의 간격이 답한다. 발전·축전을 선언하지 않은 현장에서는 없다(차감할 것이 없다).
|
|
125
|
+
*/
|
|
126
|
+
peakGrossKW?: number
|
|
127
|
+
shavedKW?: number
|
|
128
|
+
/** 파생의 근거 — 구간 평균부하 × 길이(`mean-kw`). */
|
|
129
|
+
basis: string
|
|
130
|
+
/** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) — 조용히 자르지 않는다. */
|
|
131
|
+
counted: number
|
|
132
|
+
skipped: number
|
|
133
|
+
/** 완료 오더당 전력량 — 답할 수 없으면 `null` 과 이유. */
|
|
134
|
+
intensity: { value: number | null; unit?: string; reason?: string }
|
|
135
|
+
/**
|
|
136
|
+
* 배전 계통의 **자리별 몫** — 「이 분기가 얼마 썼나」.
|
|
137
|
+
*
|
|
138
|
+
* ── 이것은 측정이 아니라 배분이다 ──────────────────────────────────────
|
|
139
|
+
* 저널에 있는 에너지 사실은 **현장 총량 한 줄**이다(뿌리 계량기 합). 계량기별 구간 사실이 없으므로
|
|
140
|
+
* 자리별 몫은 **나눈 것**이고, 무엇으로 나눴는지(`weightKind`)를 값과 함께 낸다. 근거를 감추면
|
|
141
|
+
* 나눈 숫자가 잰 숫자와 같은 무게로 읽힌다 — CBAM·ESG 보고에서 첫 질문에 깨지는 지점이다.
|
|
142
|
+
*
|
|
143
|
+
* 가중치는 **작업 가동시간**이다. 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합으로
|
|
144
|
+
* 파생되므로, 그 총량을 다시 정격으로 나누면 넣은 값이 그대로 나오는 동어반복이 된다.
|
|
145
|
+
*
|
|
146
|
+
* 그래서 이 값에는 **가정이 하나 실려 있다**: 전기가 가동시간에 비례한다. 공조·조명처럼 공정에
|
|
147
|
+
* 붙지 않는 부하도 그 비율에 섞인다 — 화면이 그 문장을 함께 말해야 한다.
|
|
148
|
+
*/
|
|
149
|
+
byFeeder?: {
|
|
150
|
+
/** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(조용히 버리지 않는다). */
|
|
151
|
+
key: string
|
|
152
|
+
kWh: number
|
|
153
|
+
/**
|
|
154
|
+
* 이 몫의 근거 — **커널이 정한다.**
|
|
155
|
+
*
|
|
156
|
+
* 선언된 부하가 여럿이면 배분(`apportioned`)이다. 선언된 부하가 **하나뿐인** 현장에서는 커널이
|
|
157
|
+
* 「그 계량기는 그것만 잰다」고 읽어 `measured` 를 낸다 — 모델이 그렇게 말했으므로 그것도 사실이다.
|
|
158
|
+
* 우리가 어느 쪽으로도 낙인찍지 않는다.
|
|
159
|
+
*/
|
|
160
|
+
basis: 'apportioned' | 'measured'
|
|
161
|
+
/**
|
|
162
|
+
* 무엇으로 나눴나 — **배분일 때만** 있다.
|
|
163
|
+
*
|
|
164
|
+
* 분기마다 계량기가 있는 현장에서는 나누지 않는다(그 값이 곧 계량이다). 그때 이 칸을
|
|
165
|
+
* `runtimeMs` 로 채우면 「가동시간으로 나눴다」는 없는 사실을 말하게 된다.
|
|
166
|
+
*/
|
|
167
|
+
weightKind?: 'runtimeMs'
|
|
168
|
+
/** 이 자리의 몫이 전체의 얼마였나 — 배분일 때만. */
|
|
169
|
+
weightShare?: number
|
|
170
|
+
/**
|
|
171
|
+
* **피크가 찍힌 순간** 이 분기가 얼마였나(kW) — 요금이 걸린 수다.
|
|
172
|
+
*
|
|
173
|
+
* 창의 평균이나 합이 아니다: 최대수요를 낸 구간의 그 순간 값이다(다른 순간을 섞으면 「어느 분기를
|
|
174
|
+
* 깎아야 피크가 내려가나」의 답이 흐려진다). 그 구간에 이 분기의 표본이 없었으면 **없다**.
|
|
175
|
+
*/
|
|
176
|
+
peakKW?: number
|
|
177
|
+
}[]
|
|
178
|
+
/**
|
|
179
|
+
* 나누지 못한 양과 그 이유 — **조용히 0 으로 만들지 않는다.**
|
|
180
|
+
* `no-weights` = 그 창에 돌아간 자원이 없다(가동시간이 전부 0). 그러면 총량은 그대로 남는다.
|
|
181
|
+
*/
|
|
182
|
+
unattributedKWh?: number
|
|
183
|
+
/**
|
|
184
|
+
* 왜 나누지 못했나(커널 코드) — `no-weights`(가동시간이 전부 0) · `no-consumers`(덮는 대상 미선언) ·
|
|
185
|
+
* `not-submetered`(뿌리에서 재었으나 하위 계량이 덮지 않는 부분 — 결손이 아니라 계량 범위의 사실).
|
|
186
|
+
*/
|
|
187
|
+
unattributedReason?: string
|
|
188
|
+
/**
|
|
189
|
+
* 분기 계량의 합이 뿌리 계량보다 **큰** 양 — 두 계량기의 회계가 다르다는 사실이다.
|
|
190
|
+
*
|
|
191
|
+
* 값을 깎아 맞추지 않는다: 그것은 계량을 우리가 고쳐 쓰는 일이고, 어느 쪽이 맞는지는 현장이 안다.
|
|
192
|
+
*/
|
|
193
|
+
submeterExcessKWh?: number
|
|
194
|
+
/**
|
|
195
|
+
* 배분에서 **빠진 자원 수** — 그 창에 가동 기록이 없는 설비다. 0kWh 를 주지 않는 대신 몇 대가
|
|
196
|
+
* 빠졌는지 센다(대기전력이 있는 설비에서 0 은 곧 거짓이다).
|
|
197
|
+
*/
|
|
198
|
+
excludedResources?: number
|
|
199
|
+
/**
|
|
200
|
+
* 이 창의 전기요금 — **청구서가 아니다.** 계산과 그 한계(TOU·래칫·최소청구액 미모델)는 커널
|
|
201
|
+
* (`electricityCost`)이 갖고 여기서는 나른다. 단가가 없으면 `reason: 'no-tariff'` 로 온다.
|
|
202
|
+
*/
|
|
203
|
+
cost?: ElectricityCost
|
|
204
|
+
}
|
|
86
205
|
/** 리드타임 — 생성(created)에서 완료까지. 사용자가 체감하는 소요 시간. */
|
|
87
206
|
leadTime: DurationStats
|
|
88
207
|
/** 작업시간 — 착수(in-progress)에서 완료까지. 대기를 뺀 실작업. */
|
|
@@ -114,6 +233,188 @@ export interface KpiResult {
|
|
|
114
233
|
groups?: { by: KpiGroupBy; items: KpiGroup[]; truncated?: number; truncatedTasks?: number }
|
|
115
234
|
}
|
|
116
235
|
|
|
236
|
+
/**
|
|
237
|
+
* 창의 전력량을 **배전 계통의 자리별로 나눈다** — 근거를 함께.
|
|
238
|
+
*
|
|
239
|
+
* ── 왜 커널 함수를 쓰나 ─────────────────────────────────────────────────────
|
|
240
|
+
* 「나눌 근거가 없으면 나누지 않는다」·「합은 보존된다」·「몫이 0 이면 0kWh 를 주지 않는다」는 규칙이
|
|
241
|
+
* 커널의 `attributeEnergy` 에 이미 있다. 여기서 다시 쓰면 두 벌이 되고, 그때부터 두 화면이 다른 합을
|
|
242
|
+
* 말한다. 이 함수가 하는 일은 **재료를 모아 넘기고 답을 자리별로 묶는 것**뿐이다.
|
|
243
|
+
*
|
|
244
|
+
* ── 가중치는 가동시간이다 ───────────────────────────────────────────────────
|
|
245
|
+
* 정격(kW)으로 나누지 않는다: 시뮬 트윈의 총부하가 정격의 합에서 파생되므로 그 총량을 다시 정격으로
|
|
246
|
+
* 나누면 넣은 값이 그대로 나온다(새 정보 0의 동어반복). 가동시간은 **저널이 관측한 사실**이다.
|
|
247
|
+
*
|
|
248
|
+
* 자리를 모르는 자원은 `unknown` 으로 모인다 — 빼면 합이 줄고, 아무 자리에 넣으면 거짓이 된다.
|
|
249
|
+
*/
|
|
250
|
+
function feederShares(
|
|
251
|
+
kWh: number | undefined,
|
|
252
|
+
utilization: { resourceId: string; busyMs: number }[],
|
|
253
|
+
resourceFeeder?: Record<string, string>
|
|
254
|
+
): Partial<NonNullable<KpiResult['energy']>> {
|
|
255
|
+
/* 선언이 없으면 분해하지 않는다 — 자리를 모르는 채 나눈 숫자는 아무 것의 몫도 아니다. */
|
|
256
|
+
if (!resourceFeeder || !Object.keys(resourceFeeder).length) return {}
|
|
257
|
+
if (kWh === undefined || !(kWh > 0)) return {}
|
|
258
|
+
|
|
259
|
+
/*
|
|
260
|
+
* ── 풀이 **무엇을 덮는지 사실대로** 말한다 (2026-08-17) ────────────────────
|
|
261
|
+
*
|
|
262
|
+
* 처음에는 「그 창에 돌아간 자원」만 덮는다고 넘겼다. 그러자 돌아간 자원이 하나인 창에서 커널이
|
|
263
|
+
* 그것을 **`measured`(전용 계량)** 로 판정했다 — 커널의 규칙은 옳다(「덮는 소비처가 하나면 그
|
|
264
|
+
* 계량기는 그것만 잰다는 선언이다」). 틀린 것은 내 선언이었다: 현장 계량기는 돌아간 것만 재지 않는다.
|
|
265
|
+
*
|
|
266
|
+
* 그래서 **선언된 부하 전부**를 덮는다고 말한다(`resourceFeeder` 의 키가 곧 그 선언이다).
|
|
267
|
+
* 그러면 그 창에 돌지 않은 설비는 몫 0 으로 **배분에서 빠지고 그 수가 보고된다** — 0kWh 를 주는
|
|
268
|
+
* 대신 「이 설비는 이 창에 가동 기록이 없다」를 말하는 쪽이다.
|
|
269
|
+
*/
|
|
270
|
+
/*
|
|
271
|
+
* 덮는 대상은 **선언된 부하 ∪ 그 창에 돌아간 자원**이다.
|
|
272
|
+
*
|
|
273
|
+
* 선언만 쓰면, 돌았는데 전기 계통에 선언되지 않은 설비의 몫이 나머지 설비에 얹힌다 — 그 설비가
|
|
274
|
+
* 안 쓴 전기를 쓴 것으로 만든다. 돌아간 것만 쓰면, 선언됐지만 쉰 설비가 사라져 「이 창에 안 돌았다」를
|
|
275
|
+
* 말할 수 없다. 둘을 합해야 두 사실이 다 남는다(자리를 모르는 몫은 `unknown` 으로 모인다).
|
|
276
|
+
*/
|
|
277
|
+
const busyOf = new Map(utilization.map(u => [u.resourceId, u.busyMs]))
|
|
278
|
+
const ids = new Set<string>([...Object.keys(resourceFeeder), ...busyOf.keys()])
|
|
279
|
+
const consumers = [...ids].map(id => ({ id, weight: busyOf.get(id) ?? 0 }))
|
|
280
|
+
if (!consumers.length) return {}
|
|
281
|
+
|
|
282
|
+
const r = attributeEnergy({
|
|
283
|
+
pools: [{ meterId: 'site', kWh, consumerIds: consumers.map(c => c.id) }],
|
|
284
|
+
consumers,
|
|
285
|
+
weightKind: 'runtimeMs'
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
const byKey = new Map<string, { kWh: number; weightShare: number; apportioned: boolean }>()
|
|
289
|
+
for (const sh of r.shares) {
|
|
290
|
+
/* 자리를 모르면 `unknown` — 그 몫이 있다는 사실은 남기고, 어디의 것인지는 지어내지 않는다. */
|
|
291
|
+
const key = resourceFeeder[sh.consumerId] ?? 'unknown'
|
|
292
|
+
const cur = byKey.get(key) ?? { kWh: 0, weightShare: 0, apportioned: false }
|
|
293
|
+
cur.kWh += sh.kWh
|
|
294
|
+
cur.weightShare += sh.weightShare ?? 0
|
|
295
|
+
/* **근거는 커널이 정한다.** 여기서 낙인찍으면 커널이 「쟀다」고 한 것을 「나눴다」고 바꿔 말하거나
|
|
296
|
+
그 반대가 된다. 섞이면 약한 쪽(배분)으로 적는다 — 강한 주장을 올려 잡지 않는다. */
|
|
297
|
+
if (sh.basis === 'apportioned') cur.apportioned = true
|
|
298
|
+
byKey.set(key, cur)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const byFeeder = [...byKey.entries()]
|
|
302
|
+
.map(([key, v]) => ({
|
|
303
|
+
key,
|
|
304
|
+
kWh: v.kWh,
|
|
305
|
+
basis: (v.apportioned ? 'apportioned' : 'measured') as 'apportioned' | 'measured',
|
|
306
|
+
weightKind: 'runtimeMs' as const,
|
|
307
|
+
weightShare: v.weightShare
|
|
308
|
+
}))
|
|
309
|
+
.sort((a, b) => b.kWh - a.kWh)
|
|
310
|
+
|
|
311
|
+
const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)
|
|
312
|
+
return {
|
|
313
|
+
...(byFeeder.length ? { byFeeder } : {}),
|
|
314
|
+
...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),
|
|
315
|
+
...(r.excluded.length ? { excludedResources: r.excluded.length } : {})
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* 분기별 전력량을 **계량으로** 읽는다 — 계량이 있으면 배분하지 않는다.
|
|
321
|
+
*
|
|
322
|
+
* ── 왜 필요했나 (2026-08-18) ────────────────────────────────────────────────
|
|
323
|
+
* 실 게이트웨이는 분기마다 계량기를 보낸다. 그런데 화면은 「나누지 못함」으로 답하고 있었다 — 여기에
|
|
324
|
+
* 가동시간 배분 경로만 있었기 때문이다. 계량이 있는데 배분으로 말하면 **근거를 실제보다 약하게** 보고하는
|
|
325
|
+
* 것이고, 그 위에 세운 SEU 판단이 무거운 값을 놓친다. 미러 트윈에는 설비 가동 기록조차 없어서
|
|
326
|
+
* (원천이 계량만 준다) 그 경로로는 분기별 값이 영원히 나오지 않았다.
|
|
327
|
+
*
|
|
328
|
+
* 값의 출처는 커널이 구간 마감 사실에 실어 주는 **지점별 몫**이다(적산의 뺄셈). 표본을 직접 읽지 않는
|
|
329
|
+
* 이유는 조회 상한이다 — 계측은 초당 여러 건이라 표본으로는 창을 채우기 전에 상한에 걸린다.
|
|
330
|
+
*
|
|
331
|
+
* 나머지(뿌리 − 분기 합)는 **버리지도 얹지도 않는다**: 수전 직결 부하·손실·계량되지 않은 회로가 그
|
|
332
|
+
* 차이이므로, 어느 분기에 얹으면 그 분기가 쓰지 않은 전기를 쓴 것으로 만든다. 커널의 `remainder` 풀로
|
|
333
|
+
* 넘겨 「하위 계량이 덮지 않는다」로 남긴다.
|
|
334
|
+
*
|
|
335
|
+
* 분기 계량 합이 뿌리보다 크면(회계가 다른 두 계량기) **그 사실을 낸다** — 값을 깎아 맞추면 계량을
|
|
336
|
+
* 우리가 고쳐 쓰는 것이다.
|
|
337
|
+
*/
|
|
338
|
+
function measuredFeeders(
|
|
339
|
+
windows: readonly {
|
|
340
|
+
startMs: number
|
|
341
|
+
endMs: number
|
|
342
|
+
maxKW?: number
|
|
343
|
+
points: { id: string; kWh: number; root: boolean }[]
|
|
344
|
+
pointsAtPeak?: { id: string; kW: number }[]
|
|
345
|
+
}[],
|
|
346
|
+
range: { startMs: number; endMs: number },
|
|
347
|
+
totalKWh: number | undefined,
|
|
348
|
+
meterFeeder?: Record<string, string>
|
|
349
|
+
): Partial<NonNullable<KpiResult['energy']>> | undefined {
|
|
350
|
+
if (!meterFeeder || !Object.keys(meterFeeder).length) return undefined
|
|
351
|
+
if (totalKWh === undefined || !(totalKWh > 0)) return undefined
|
|
352
|
+
|
|
353
|
+
/* 창에 온전히 든 구간만 — 전체 전력량과 같은 규칙이어야 몫과 합이 같은 창을 말한다. */
|
|
354
|
+
const byFeederKWh = new Map<string, number>()
|
|
355
|
+
let sawPoints = false
|
|
356
|
+
for (const w of windows) {
|
|
357
|
+
if (!(w.startMs >= range.startMs && w.endMs <= range.endMs)) continue
|
|
358
|
+
for (const p of w.points ?? []) {
|
|
359
|
+
const feeder = meterFeeder[p.id]
|
|
360
|
+
if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다
|
|
361
|
+
sawPoints = true
|
|
362
|
+
byFeederKWh.set(feeder, (byFeederKWh.get(feeder) ?? 0) + p.kWh)
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!sawPoints) return undefined // 계량이 없으면 이 경로를 쓰지 않는다(배분으로 돌아간다)
|
|
366
|
+
|
|
367
|
+
const feeders = [...byFeederKWh.entries()].filter(([, kWh]) => kWh > 0)
|
|
368
|
+
if (!feeders.length) return undefined
|
|
369
|
+
const submetered = feeders.reduce((sum, [, kWh]) => sum + kWh, 0)
|
|
370
|
+
const remainder = totalKWh - submetered
|
|
371
|
+
|
|
372
|
+
const r = attributeEnergy({
|
|
373
|
+
pools: [
|
|
374
|
+
...feeders.map(([feeder, kWh]) => ({ meterId: `${feeder}:meter`, kWh, consumerIds: [feeder] })),
|
|
375
|
+
...(remainder > 0 ? [{ meterId: 'root:remainder', kWh: remainder, consumerIds: [], remainder: true }] : [])
|
|
376
|
+
],
|
|
377
|
+
consumers: feeders.map(([feeder]) => ({ id: feeder }))
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
/*
|
|
381
|
+
* ── 피크를 만든 분기 (2026-08-18) ─────────────────────────────────────────
|
|
382
|
+
*
|
|
383
|
+
* 요금은 kW 로 매겨지므로 「어느 분기를 깎아야 피크가 내려가나」가 실무의 질문이다. 그 답은 **피크가
|
|
384
|
+
* 찍힌 구간의 그 순간** 값이고, 창 안의 여러 구간을 더하거나 평균하면 뜻이 사라진다(다른 순간의 값을
|
|
385
|
+
* 섞는 것이다). 그래서 **최대수요를 낸 구간 하나**만 골라 그 순간의 지점 값을 쓴다.
|
|
386
|
+
*/
|
|
387
|
+
const peakWindow = windows
|
|
388
|
+
.filter(w => w.startMs >= range.startMs && w.endMs <= range.endMs && Number.isFinite(Number(w.maxKW)))
|
|
389
|
+
.reduce<(typeof windows)[number] | undefined>((best, w) => (!best || Number(w.maxKW) > Number(best.maxKW) ? w : best), undefined)
|
|
390
|
+
const peakOf = new Map<string, number>()
|
|
391
|
+
for (const p of peakWindow?.pointsAtPeak ?? []) {
|
|
392
|
+
const feeder = meterFeeder[p.id]
|
|
393
|
+
if (!feeder) continue // 뿌리 계량기·모르는 지점 — 분기의 몫이 아니다
|
|
394
|
+
peakOf.set(feeder, (peakOf.get(feeder) ?? 0) + p.kW)
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const byFeeder = r.shares
|
|
398
|
+
.map(sh => ({
|
|
399
|
+
key: sh.consumerId,
|
|
400
|
+
kWh: sh.kWh,
|
|
401
|
+
basis: sh.basis,
|
|
402
|
+
weightKind: undefined,
|
|
403
|
+
weightShare: undefined,
|
|
404
|
+
/* 피크 순간의 값이 없으면 만들지 않는다 — 그 구간에 이 분기의 표본이 없었다는 뜻이다. */
|
|
405
|
+
...(peakOf.has(sh.consumerId) ? { peakKW: peakOf.get(sh.consumerId) } : {})
|
|
406
|
+
}))
|
|
407
|
+
.sort((a, b) => b.kWh - a.kWh) as NonNullable<KpiResult['energy']>['byFeeder']
|
|
408
|
+
|
|
409
|
+
const un = r.unattributed.reduce((sum, u) => sum + u.kWh, 0)
|
|
410
|
+
return {
|
|
411
|
+
...(byFeeder?.length ? { byFeeder } : {}),
|
|
412
|
+
...(un > 0 ? { unattributedKWh: un, unattributedReason: r.unattributed[0]?.reason } : {}),
|
|
413
|
+
/* 분기 합이 뿌리를 넘었다 — 두 계량기의 회계가 다르다는 사실이다(깎아 맞추지 않는다). */
|
|
414
|
+
...(remainder < 0 ? { submeterExcessKWh: -remainder } : {})
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
117
418
|
/** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */
|
|
118
419
|
export interface KpiFoldOptions {
|
|
119
420
|
groupBy?: KpiGroupBy
|
|
@@ -122,6 +423,31 @@ export interface KpiFoldOptions {
|
|
|
122
423
|
* 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(조용히 다른 축으로 바꾸지 않는다).
|
|
123
424
|
*/
|
|
124
425
|
locationArea?: Record<string, string>
|
|
426
|
+
/**
|
|
427
|
+
* 자원 → **배전 계통의 자리**(분기·수전) 지도.
|
|
428
|
+
*
|
|
429
|
+
* ── 왜 밖에서 오나 ───────────────────────────────────────────────────────
|
|
430
|
+
* 이벤트에는 전기 계통이 없다. 「이 설비가 어느 분기에서 먹는가」는 에너지 트윈의 모델이 아는
|
|
431
|
+
* 선언이고(설비 하나에 사실 둘 — 공장에서는 자원, 전기에서는 부하), 그 선언을 읽는 것은 호출부의 일이다.
|
|
432
|
+
*
|
|
433
|
+
* 없으면 분기 분해를 **하지 않는다** — 자리를 모르는 채 나누면 그 숫자는 아무 것의 몫도 아니다.
|
|
434
|
+
*/
|
|
435
|
+
resourceFeeder?: Record<string, string>
|
|
436
|
+
/**
|
|
437
|
+
* **계량 지점 → 그것이 재는 분기** 지도.
|
|
438
|
+
*
|
|
439
|
+
* 있으면 분기별 전력량을 계량으로 읽는다(배분보다 강한 근거). 이 지도는 모델의 선언에서 오고
|
|
440
|
+
* (계량기의 자리), 그것을 읽는 것은 호출부의 일이다 — 폴드는 저널만 본다.
|
|
441
|
+
*/
|
|
442
|
+
meterFeeder?: Record<string, string>
|
|
443
|
+
/**
|
|
444
|
+
* 선언된 **전기요금 단가** — 수를 금액으로 바꾸는 유일한 입력.
|
|
445
|
+
*
|
|
446
|
+
* 이벤트에는 요금이 없다. 단가는 현장의 계약이고(에너지 트윈 모델의 수전 자리에 선언한다), 그것을
|
|
447
|
+
* 읽는 것은 호출부의 일이다. 없으면 금액을 만들지 않는다 — 짐작한 단가로 낸 금액은 숫자가 있다는
|
|
448
|
+
* 것만으로 사람을 결정으로 밀어붙인다.
|
|
449
|
+
*/
|
|
450
|
+
tariff?: TariffDeclaration
|
|
125
451
|
/**
|
|
126
452
|
* 교대 선언 — **이벤트에는 교대가 없다**(시각만 있다). 현장 마스터가 아는 값이라 호출부가 넘긴다.
|
|
127
453
|
* 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로 조용히 바꾸지 않는다).
|
|
@@ -295,6 +621,35 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
295
621
|
}
|
|
296
622
|
}
|
|
297
623
|
|
|
624
|
+
/*
|
|
625
|
+
* 에너지 — 창에 **온전히 든** 구간 마감 사건만 센다(걸친 구간은 커널 규칙이 뺀다).
|
|
626
|
+
* 사건이 하나도 없으면 이 트윈에는 에너지가 없는 것이므로 블록 자체를 내지 않는다.
|
|
627
|
+
*/
|
|
628
|
+
const demandWindows = events
|
|
629
|
+
.filter(e => e.eventType === ENERGY_EVENT.demandWindow)
|
|
630
|
+
.map(e => {
|
|
631
|
+
const d = e.payload?.data ?? e.payload ?? {}
|
|
632
|
+
/* 최대(`maxKW`)도 함께 넘긴다 — 요금이 걸리는 수는 평균이 아니라 최대다.
|
|
633
|
+
총부하(`grossMaxKW`)는 「무엇이 깎였나」를 되짚는 축이다(발전·방전 차감 전). */
|
|
634
|
+
return {
|
|
635
|
+
startMs: Number(d.startMs),
|
|
636
|
+
endMs: Number(d.endMs),
|
|
637
|
+
meanKW: Number(d.meanKW),
|
|
638
|
+
maxKW: Number(d.maxKW),
|
|
639
|
+
grossMaxKW: Number(d.grossMaxKW),
|
|
640
|
+
/* 지점별 몫 — 계량이 분기에 있으면 그 값은 **측정**이다(배분이 아니다). 커널이 계층 판정
|
|
641
|
+
(`root`)까지 실어 주므로 여기서 같은 규칙을 다시 쓰지 않는다. */
|
|
642
|
+
points: (Array.isArray(d.points) ? d.points : [])
|
|
643
|
+
.map((p: any) => ({ id: String(p?.id ?? ''), kWh: Number(p?.kWh), root: p?.root === true }))
|
|
644
|
+
.filter((p: any) => p.id && Number.isFinite(p.kWh)),
|
|
645
|
+
/* 피크가 찍힌 순간의 지점별 kW — 요금이 걸린 수는 kW 다(전력량이 큰 분기와 피크를 만든 분기는 다르다). */
|
|
646
|
+
pointsAtPeak: (Array.isArray(d.pointsAtPeak) ? d.pointsAtPeak : [])
|
|
647
|
+
.map((p: any) => ({ id: String(p?.id ?? ''), kW: Number(p?.kW) }))
|
|
648
|
+
.filter((p: any) => p.id && Number.isFinite(p.kW))
|
|
649
|
+
}
|
|
650
|
+
})
|
|
651
|
+
.filter(w => Number.isFinite(w.startMs) && Number.isFinite(w.endMs))
|
|
652
|
+
|
|
298
653
|
const leads: number[] = []
|
|
299
654
|
const works: number[] = []
|
|
300
655
|
const waits: number[] = []
|
|
@@ -336,6 +691,39 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
336
691
|
return {
|
|
337
692
|
window: { fromMs: window.fromMs, toMs: window.toMs },
|
|
338
693
|
throughput: { tasks: tasksInWindow, orders: completedOrders },
|
|
694
|
+
...(demandWindows.length
|
|
695
|
+
? (() => {
|
|
696
|
+
const e = energyOfWindows(demandWindows, { startMs: window.fromMs, endMs: window.toMs })
|
|
697
|
+
/* 분모는 **완료 오더**다(대당 전력). 창에 완료가 없으면 커널 규칙이 「정의되지 않음」으로 답한다. */
|
|
698
|
+
const i = energyIntensity({
|
|
699
|
+
kWh: e.kWh,
|
|
700
|
+
energyWindow: e.window,
|
|
701
|
+
denominator: { kind: 'output', value: completedOrders, window: e.window }
|
|
702
|
+
})
|
|
703
|
+
return {
|
|
704
|
+
energy: {
|
|
705
|
+
...(e.kWh !== undefined ? { kWh: e.kWh } : {}),
|
|
706
|
+
/* 이 창의 최대수요 — 계약 대비 판정이 보는 수다(전력량과 같은 구간 집합에서 왔다). */
|
|
707
|
+
...(e.peakKW !== undefined ? { peakKW: e.peakKW, peakStartMs: e.peakStartMs } : {}),
|
|
708
|
+
/* 깎인 양은 **같은 구간의 두 수**의 차이다 — 여기서 한 번만 계산해 두 곳이 갈라지지 않게. */
|
|
709
|
+
...(e.peakGrossKW !== undefined && e.peakKW !== undefined
|
|
710
|
+
? { peakGrossKW: e.peakGrossKW, ...(e.peakGrossKW > e.peakKW ? { shavedKW: e.peakGrossKW - e.peakKW } : {}) }
|
|
711
|
+
: {}),
|
|
712
|
+
basis: e.basis,
|
|
713
|
+
counted: e.counted,
|
|
714
|
+
skipped: e.skipped,
|
|
715
|
+
intensity:
|
|
716
|
+
i.value === null
|
|
717
|
+
? { value: null, reason: (i as any).reason }
|
|
718
|
+
: { value: i.value, unit: (i as any).unit },
|
|
719
|
+
...(measuredFeeders(demandWindows, { startMs: window.fromMs, endMs: window.toMs }, e.kWh, options.meterFeeder) ??
|
|
720
|
+
feederShares(e.kWh, utilization, options.resourceFeeder)),
|
|
721
|
+
/* 금액은 같은 창의 전력량·최대수요에서 나온다 — 창이 하나뿐이라 어긋날 수 없다. */
|
|
722
|
+
...(options.tariff ? { cost: electricityCost({ kWh: e.kWh, peakKW: e.peakKW, tariff: options.tariff }) } : {})
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
})()
|
|
726
|
+
: {}),
|
|
339
727
|
leadTime: stats(leads),
|
|
340
728
|
workTime: stats(works),
|
|
341
729
|
waitTime: stats(waits),
|