@things-factory/headless-twin 10.0.14 → 10.0.17
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 +4 -4
- package/dist-server/engine/attention-digest.js +7 -7
- package/dist-server/engine/attention-digest.js.map +1 -1
- package/dist-server/engine/canonical-ingest.d.ts +11 -2
- package/dist-server/engine/canonical-ingest.js +137 -8
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.js +1 -1
- package/dist-server/engine/declared-stimulus.js.map +1 -1
- package/dist-server/engine/energy-topology.js +1 -1
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +38 -0
- package/dist-server/engine/ingest-dedupe.js +145 -0
- package/dist-server/engine/ingest-dedupe.js.map +1 -0
- package/dist-server/engine/ingest-health.d.ts +124 -9
- package/dist-server/engine/ingest-health.js +169 -15
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/integration-probes.d.ts +3 -3
- package/dist-server/engine/integration-probes.js +6 -6
- package/dist-server/engine/integration-probes.js.map +1 -1
- package/dist-server/engine/kpi-baseline.js +4 -4
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +122 -12
- package/dist-server/engine/kpi-fold.js +247 -18
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +11 -11
- package/dist-server/engine/kpi-query.js +185 -28
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +1 -1
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +7 -7
- package/dist-server/engine/local-declarations.js +9 -9
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +1 -1
- package/dist-server/engine/log.js +1 -1
- package/dist-server/engine/log.js.map +1 -1
- package/dist-server/engine/measured-estimator.js +1 -1
- package/dist-server/engine/measured-estimator.js.map +1 -1
- package/dist-server/engine/measured-yield.js +1 -1
- package/dist-server/engine/measured-yield.js.map +1 -1
- package/dist-server/engine/model-basis.d.ts +1 -1
- package/dist-server/engine/model-basis.js +1 -1
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-gap.d.ts +3 -3
- package/dist-server/engine/model-gap.js +1 -1
- package/dist-server/engine/model-gap.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +1 -1
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +4 -4
- package/dist-server/engine/oee-accumulator.js +3 -3
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/property-effects.js +2 -2
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/read-time.d.ts +22 -0
- package/dist-server/engine/read-time.js +81 -0
- package/dist-server/engine/read-time.js.map +1 -0
- package/dist-server/engine/restart-policy.js +1 -1
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +2 -2
- package/dist-server/engine/spec-coverage.js +1 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +4 -4
- package/dist-server/engine/stage-path.js +1 -1
- package/dist-server/engine/stage-path.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +1 -1
- package/dist-server/engine/state-axes.js +2 -2
- package/dist-server/engine/state-axes.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +2 -2
- package/dist-server/engine/travel-estimator.js +1 -1
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +243 -53
- package/dist-server/engine/twin-engine.js +690 -191
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +2 -2
- package/dist-server/engine/warm-start.js +5 -5
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +69 -5
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +27 -24
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +1 -1
- package/dist-server/service/reference/control-routing.js +1 -1
- package/dist-server/service/reference/control-routing.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +2 -2
- package/dist-server/service/reference/discovery-result.js +3 -3
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/hook-contract.d.ts +43 -0
- package/dist-server/service/reference/hook-contract.js +59 -0
- package/dist-server/service/reference/hook-contract.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +1 -1
- package/dist-server/service/reference/ingest-space.js +4 -4
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/knob-defaults.d.ts +1 -1
- package/dist-server/service/reference/knob-defaults.js +4 -4
- package/dist-server/service/reference/knob-defaults.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +140 -8
- package/dist-server/service/reference/reference-adapter.js +27 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-hook.d.ts +20 -0
- package/dist-server/service/reference/reference-hook.js +133 -0
- package/dist-server/service/reference/reference-hook.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +2 -2
- package/dist-server/service/reference/reference-live.js +88 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +14 -14
- package/dist-server/service/reference/reference-master.js +34 -12
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +1 -1
- package/dist-server/service/reference/reference-resolver.js +10 -10
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +3 -3
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +22 -2
- package/dist-server/service/twin-event/twin-event-keys.js +37 -5
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event-type.js +8 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +2 -2
- package/dist-server/service/twin-event/twin-event.js +32 -4
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
- package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
- package/dist-server/service/twin-journal/event-type-split.js +102 -0
- package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
- package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
- package/dist-server/service/twin-journal/journal-order.js +17 -0
- package/dist-server/service/twin-journal/journal-order.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
- package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +36 -1
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +15 -2
- package/dist-server/service/twin-model/isa95-coverage.js +32 -8
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.js +2 -2
- package/dist-server/service/twin-model/item-ref.js.map +1 -1
- package/dist-server/service/twin-model/name-index.d.ts +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.d.ts +1 -1
- package/dist-server/service/twin-model/project-structure.js +3 -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 +1 -1
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-location.d.ts +1 -1
- package/dist-server/service/twin-model/twin-location.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
- package/dist-server/service/twin-model/twin-model-item-query.js +14 -14
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
- package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +117 -28
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +22 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
- package/dist-server/service/twin-model/twin-operation.js +1 -1
- package/dist-server/service/twin-model/twin-operation.js.map +1 -1
- package/dist-server/service/twin-space/move-space.js +2 -2
- package/dist-server/service/twin-space/move-space.js.map +1 -1
- package/dist-server/service/twin-space/representation-role.d.ts +2 -0
- package/dist-server/service/twin-space/representation-role.js +18 -0
- package/dist-server/service/twin-space/representation-role.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
- package/dist-server/service/twin-space/space-integrity.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
- package/dist-server/service/twin-space/twin-space-representation.js +16 -0
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +36 -7
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +3 -3
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
- package/dist-server/service/twin-subject/event-subjects.js +137 -0
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
- package/dist-server/service/twin-subject/index.d.ts +9 -0
- package/dist-server/service/twin-subject/index.js +16 -0
- package/dist-server/service/twin-subject/index.js.map +1 -0
- package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
- package/dist-server/service/twin-subject/item-resolver.js +93 -0
- package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
- package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
- package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
- package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
- package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
- package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
- package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +1 -1
- package/dist-shared/entity-delta.js +3 -3
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/touched-items.js +2 -2
- package/dist-shared/touched-items.js.map +1 -1
- package/dist-shared/twin-level.js +1 -1
- package/dist-shared/twin-level.js.map +1 -1
- package/package.json +3 -3
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +170 -13
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +1 -1
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +234 -20
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +395 -27
- package/server/engine/kpi-query.ts +212 -46
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +13 -13
- package/server/engine/log.ts +1 -1
- package/server/engine/measured-estimator.ts +1 -1
- package/server/engine/measured-yield.ts +1 -1
- package/server/engine/model-basis.ts +2 -2
- package/server/engine/model-gap.ts +3 -3
- package/server/engine/model-vocabulary.ts +1 -1
- package/server/engine/oee-accumulator.ts +4 -4
- package/server/engine/property-effects.ts +2 -2
- package/server/engine/read-time.ts +77 -0
- package/server/engine/restart-policy.ts +1 -1
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/stage-path.ts +5 -5
- package/server/engine/state-axes.ts +2 -2
- package/server/engine/travel-estimator.ts +2 -2
- package/server/engine/twin-engine.ts +764 -201
- package/server/engine/warm-start.ts +6 -6
- package/server/index.ts +1 -1
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
- package/server/migrations/index.ts +7 -1
- package/server/routes.ts +68 -5
- package/server/service/index.ts +4 -1
- package/server/service/reference/control-routing.ts +1 -1
- package/server/service/reference/discovery-result.ts +3 -3
- package/server/service/reference/hook-contract.ts +103 -0
- package/server/service/reference/ingest-space.ts +4 -4
- package/server/service/reference/knob-defaults.ts +4 -4
- package/server/service/reference/reference-adapter.ts +166 -9
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +92 -14
- package/server/service/reference/reference-master.ts +39 -17
- package/server/service/reference/reference-resolver.ts +10 -10
- package/server/service/reference/twin-reference.ts +3 -3
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-audit/command-audit.ts +1 -1
- package/server/service/twin-audit/twin-audit-event.ts +2 -2
- package/server/service/twin-audit/twin-audit-query.ts +1 -1
- package/server/service/twin-control/twin-control-mutation.ts +1 -1
- package/server/service/twin-event/twin-event-keys.ts +37 -5
- package/server/service/twin-event/twin-event-type.ts +18 -1
- package/server/service/twin-event/twin-event.ts +32 -6
- package/server/service/twin-forecast/forecast-metrics.ts +1 -1
- package/server/service/twin-forecast/gap-analytics.ts +1 -1
- package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
- package/server/service/twin-instance/twin-instance.ts +4 -4
- package/server/service/twin-journal/event-type-split-shape.ts +112 -0
- package/server/service/twin-journal/event-type-split.ts +144 -0
- package/server/service/twin-journal/journal-order.ts +20 -0
- package/server/service/twin-journal/twin-journal-query.ts +224 -20
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +57 -3
- package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
- package/server/service/twin-model/axis-journal-evidence.ts +1 -1
- package/server/service/twin-model/epcis-coverage.ts +36 -1
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +45 -8
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +1 -1
- package/server/service/twin-model/project-structure.ts +4 -4
- package/server/service/twin-model/twin-equipment.ts +2 -2
- package/server/service/twin-model/twin-lineage-query.ts +16 -5
- package/server/service/twin-model/twin-location.ts +1 -1
- package/server/service/twin-model/twin-model-item-query.ts +14 -14
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +117 -28
- package/server/service/twin-model/twin-model-tree-query.ts +25 -1
- package/server/service/twin-model/twin-operation.ts +2 -2
- package/server/service/twin-space/move-space.ts +3 -3
- package/server/service/twin-space/representation-role.ts +15 -0
- package/server/service/twin-space/space-integrity.ts +1 -1
- package/server/service/twin-space/twin-space-representation.ts +37 -0
- package/server/service/twin-space/twin-space-resolver.ts +39 -7
- package/server/service/twin-structure/twin-structure.ts +3 -3
- package/server/service/twin-subject/event-subjects.ts +155 -0
- package/server/service/twin-subject/index.ts +10 -0
- package/server/service/twin-subject/item-resolver.ts +92 -0
- package/server/service/twin-subject/subject-query-shape.ts +84 -0
- package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
- package/server/service/twin-subject/twin-subject-event.ts +101 -0
- package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
- package/server/service/twin-subject/twin-subject-query.ts +210 -0
- package/server/service/twin-subject/twin-subject-writer.ts +189 -0
- package/server/service/twin-target/twin-target-resolver.ts +1 -1
- package/shared/entity-delta.ts +3 -3
- package/shared/touched-items.ts +2 -2
- package/shared/twin-level.ts +1 -1
- package/test/adopt-structure-live.test.ts +3 -3
- package/test/aggregate-not-findone.test.ts +112 -0
- package/test/attention-digest.test.ts +6 -6
- package/test/axis-read.test.ts +2 -2
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +179 -7
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/control-capability.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +2 -2
- package/test/declaration-reaches-model.test.ts +5 -5
- package/test/declared-stimulus.test.ts +4 -4
- package/test/discovery-result.test.ts +2 -2
- package/test/entity-delta.test.ts +4 -4
- package/test/event-subjects.test.ts +147 -0
- package/test/event-time-column.test.ts +3 -3
- package/test/event-type-split.test.ts +155 -0
- package/test/fold-gap-speaks.test.ts +53 -0
- package/test/forecast-metrics.test.ts +1 -1
- package/test/forecast-tuning.test.ts +1 -1
- package/test/ingest-health-engine.test.ts +2 -2
- package/test/ingest-health-wiring.test.ts +7 -5
- package/test/ingest-health.test.ts +6 -6
- package/test/ingest-history.test.ts +2 -2
- package/test/ingest-idempotent.test.ts +132 -0
- package/test/ingest-space.test.ts +3 -3
- package/test/integration-probes.test.ts +3 -3
- package/test/integration-runner.test.ts +1 -1
- package/test/item-ref.test.ts +1 -1
- package/test/journal-order-axis.test.ts +80 -0
- package/test/journal-read-discipline.test.ts +16 -6
- package/test/journal-retention.test.ts +171 -29
- package/test/journal-sort-axis.test.ts +1 -1
- package/test/journal-write-door.test.ts +3 -3
- package/test/journal-write-trend.test.ts +1 -1
- package/test/kernel-kind-guard.test.ts +3 -3
- package/test/knob-defaults.test.ts +1 -1
- package/test/kpi-baseline-db.test.ts +2 -2
- package/test/kpi-fold.test.ts +185 -14
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/live-cursor-wiring.test.ts +6 -6
- package/test/live-kernel-facts.test.ts +11 -11
- package/test/live-mirror-parity.test.ts +3 -3
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +10 -10
- package/test/log-stamp.test.ts +1 -1
- package/test/measured-yield.test.ts +2 -2
- package/test/merge-streams.test.ts +70 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
- package/test/model-basis.test.ts +2 -2
- package/test/model-gap.test.ts +4 -4
- package/test/model-vocabulary.test.ts +2 -2
- package/test/move-space.test.ts +1 -1
- package/test/oee-accumulator.test.ts +2 -2
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +2 -2
- package/test/property-effects.test.ts +3 -3
- package/test/read-failure-ledger.test.ts +106 -0
- package/test/read-failure-visible.test.ts +7 -7
- package/test/read-time.test.ts +118 -0
- package/test/reference-grounding.test.ts +1 -1
- package/test/reference-hook.test.ts +86 -0
- package/test/registry-key-guard.test.ts +1 -1
- package/test/rename-twin.test.ts +81 -0
- package/test/restart-policy.test.ts +3 -3
- package/test/resync-origin-site.test.ts +3 -3
- package/test/revision-axis.test.ts +14 -8
- package/test/runtime-key.test.ts +3 -3
- package/test/snapshot-freshness.test.ts +4 -4
- package/test/source-outcome-audit.test.ts +1 -1
- package/test/space-integrity.test.ts +1 -1
- package/test/space-representation.test.ts +66 -0
- package/test/space-scope-includes-stopped.test.ts +55 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/standard-coverage.test.ts +43 -1
- package/test/status-tally.test.ts +1 -1
- package/test/structure-revision-db.test.ts +7 -7
- package/test/subject-fast-path.test.ts +148 -0
- package/test/subject-read-model.test.ts +175 -0
- package/test/touched-items.test.ts +2 -2
- package/test/twin-audit.test.ts +1 -1
- package/test/twin-event-keys.test.ts +49 -3
- package/test/twin-model-item-db.test.ts +5 -5
- package/test/twin-model-tree-db.test.ts +35 -2
- package/test/twin-origin-resync.test.ts +1 -1
- package/test/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +95 -0
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* "어제보다 나아졌다." **어제도 나빴으면 그 말은 쓸모가 없다.** 목표(`kpi-target`)가 그 한계를 한쪽에서
|
|
7
7
|
* 풀지만, 목표를 아직 걸지 않은 현장은 여전히 자기 숫자가 평소와 같은지도 알 수 없다.
|
|
8
8
|
*
|
|
9
|
-
* 최근 며칠의 **같은 시간대**를 함께
|
|
9
|
+
* 최근 며칠의 **같은 시간대**를 함께 계산하면 목표 없이도 말할 수 있다 — "오늘 이 시간대는 지난 7일 중
|
|
10
10
|
* 가장 낮다." 그것이 이 파일이 만드는 것이다: 창 여러 개의 **분포**와 그 안에서 현재의 **자리**.
|
|
11
11
|
*
|
|
12
12
|
* ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
* 퍼센트 임계를 두면 그 숫자가 곧 숨은 목표가 된다.
|
|
19
19
|
*
|
|
20
20
|
* ── 정직성 규율 (이 도메인의 핵심) ──────────────────────────────────────────
|
|
21
|
-
* ① **결측 창을
|
|
21
|
+
* ① **결측 창을 알리지 않고 빼지 않는다.** 요청한 창 전부를 `measured` 와 함께 돌려주고, 분포는 측정된
|
|
22
22
|
* 것만으로 만든다. 빼고 말하면 "지난 7일" 이 실제로는 3일이었다는 것을 아무도 모른다.
|
|
23
23
|
* ② **표본이 둘 미만이면 자리를 말하지 않는다.** 하나를 분포라 부를 수 없다.
|
|
24
24
|
* ③ **현재 값이 없으면 자리를 말하지 않는다.** 결측을 0 으로 읽으면 시간 지표는 "낮을수록 좋다" 라서
|
|
25
25
|
* **일이 없던 구간이 최고 성적**으로 보고된다(목표 판정에서 이미 겪은 결함이다).
|
|
26
|
-
* ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 —
|
|
26
|
+
* ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 — 알리지 않고 빈 값을 주면 화면이 "분포 없음" 을 "평범함" 으로
|
|
27
27
|
* 그린다.
|
|
28
28
|
*/
|
|
29
29
|
|
|
@@ -168,7 +168,7 @@ export function summarizeBaseline<M extends string>(
|
|
|
168
168
|
...(sorted.length ? { min: sorted[0], median: medianOf(sorted), max: sorted[sorted.length - 1] } : {})
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
/* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(
|
|
171
|
+
/* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(들어온 것이 없는 빈 값이 "평범함" 으로 읽힌다). */
|
|
172
172
|
if (!hasCurrent) row.noPosition = 'no-current'
|
|
173
173
|
else if (sorted.length < 2) row.noPosition = 'too-few-samples'
|
|
174
174
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* 업무 KPI 폴드 — **이벤트 저널을
|
|
2
|
+
* 업무 KPI 폴드 — **이벤트 저널을 계산해** 시간창 지표를 만든다(순수).
|
|
3
3
|
*
|
|
4
4
|
* ── 왜 필요한가 ─────────────────────────────────────────────────────────────
|
|
5
5
|
* 지금 트윈이 답할 수 있는 것은 "이 순간의 값"(점유 92%)과 "인프라 부하"(초당 이벤트 수)뿐이다.
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
*
|
|
9
9
|
* ── 원료는 이미 있다 ────────────────────────────────────────────────────────
|
|
10
10
|
* 저널(`TwinEvent`)에 `task.status`·`order.status`·`equipment.status` 전이가 `eventTime`(ISO)과 함께
|
|
11
|
-
* append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 —
|
|
12
|
-
* 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를
|
|
11
|
+
* append-only 로 쌓인다. 그래서 새 계측을 심을 필요가 없다 — **계산만 하면 된다.**
|
|
12
|
+
* 같은 모양의 선례가 이 폴더에 있다(`oee-accumulator.ts` — equipment 전이를 계산해 OEE 카운터 생산).
|
|
13
13
|
*
|
|
14
14
|
* ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────
|
|
15
15
|
* · 목표와 비교하지 않는다. "95% 목표 대비" 는 목표를 저장하는 별개 층(경영 KPI)의 몫이고,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
* 짝맞춤은 **커널이 소유한다**(`foldTaskRecords`). 완료는 작업당 하나·나중 것이 사실, 착수는 처음 것,
|
|
24
24
|
* 종류·오더는 어느 전이에서든 — 그 규칙은 커널 상태 기계에서 나오는 지식이다. 여기서 다시 적었더니
|
|
25
25
|
* 공정 타임라인과 **다른 답**을 냈다(재전송된 완료를 두 건으로 세어 처리량·점유가 부풀려졌다).
|
|
26
|
-
* 이 파일은 이제 "
|
|
26
|
+
* 이 파일은 이제 "계산한 기록 → 창 지표" 만 한다.
|
|
27
27
|
*/
|
|
28
28
|
import {
|
|
29
29
|
ENERGY_EVENT,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
/** 저널 한 줄 — 필요한 것만(엔티티·typeorm 비의존). */
|
|
44
44
|
export interface KpiEvent {
|
|
45
45
|
eventType?: string
|
|
46
|
-
/** ISO 시각. 파싱 불가하면 그 줄은 버린다(
|
|
46
|
+
/** ISO 시각. 파싱 불가하면 그 줄은 버린다(알리지 않고 0 으로 세지 않는다). */
|
|
47
47
|
eventTime?: string
|
|
48
48
|
payload?: any
|
|
49
49
|
}
|
|
@@ -58,7 +58,7 @@ export interface DurationStats {
|
|
|
58
58
|
/** 짝이 맞아 계산된 건수. */
|
|
59
59
|
count: number
|
|
60
60
|
/**
|
|
61
|
-
* 하위 꼬리 — **분포를 양쪽에서
|
|
61
|
+
* 하위 꼬리 — **분포를 양쪽에서 계산하지하기 위해** 낸다.
|
|
62
62
|
* p50·p90 만 있으면 아래쪽 퍼짐을 위쪽에서 거울처럼 베껴 쓸 수밖에 없다(발명). 관측이 말하게 한다.
|
|
63
63
|
*/
|
|
64
64
|
p10Ms: number
|
|
@@ -94,7 +94,7 @@ export interface KpiGroup {
|
|
|
94
94
|
* **품질 판정이 있었던 건수만** 센다 (2026-08-19).
|
|
95
95
|
*
|
|
96
96
|
* 판정이 없는 작업(이동·체류·중간 스테이션)은 어느 쪽에도 세지 않는다 — 모름을 양품으로 세면 양품률이
|
|
97
|
-
*
|
|
97
|
+
* 알리지 않고 100% 로 올라간다. 그래서 `good + scrap` 은 `tasks` 보다 작을 수 있고, 그것이 정상이다.
|
|
98
98
|
*
|
|
99
99
|
* 판정이 하나도 없으면 **필드를 만들지 않는다**(0/0 을 「양품률 0%」로 읽히게 두지 않는다).
|
|
100
100
|
*/
|
|
@@ -135,9 +135,99 @@ export interface KpiResult {
|
|
|
135
135
|
*/
|
|
136
136
|
peakGrossKW?: number
|
|
137
137
|
shavedKW?: number
|
|
138
|
+
/**
|
|
139
|
+
* 이 창의 **발전량** — 커널이 마감한 기간 사실(`energy.generation.period`)에서 온다.
|
|
140
|
+
*
|
|
141
|
+
* 부하(`kWh`)와 더하지 않는다: 발전은 설비가 만든 양이고 부하는 계량 지점을 지난 양이다. 두 수를
|
|
142
|
+
* 더하면 어느 쪽도 아닌 숫자가 된다.
|
|
143
|
+
*
|
|
144
|
+
* 성과지표(ISO 50001 EnPI)와 태양광 성능비가 이 값을 분자로 쓴다. 발전 사실이 없으면 `undefined`
|
|
145
|
+
* 다(0 이 아니다 — 「발전이 없다」와 「발전 설비가 없다」는 다르다).
|
|
146
|
+
*/
|
|
147
|
+
generated?: {
|
|
148
|
+
/** 창에 온전히 든 기간들의 합(kWh). */
|
|
149
|
+
kWh: number
|
|
150
|
+
/** 센 기간 수. */
|
|
151
|
+
counted: number
|
|
152
|
+
/**
|
|
153
|
+
* 그중 **관측이 기간을 다 덮지 못한** 기간 수.
|
|
154
|
+
*
|
|
155
|
+
* 커널이 기간마다 관측 구간을 함께 낸다. 마지막 표본이 기간 끝보다 이르면 그만큼 재지 않은
|
|
156
|
+
* 것이고, 그 수를 감추면 화면이 덜 잰 합을 온전한 합으로 말한다.
|
|
157
|
+
*/
|
|
158
|
+
partial: number
|
|
159
|
+
/** 설비별 몫 — 이것은 **측정**이다(배분이 아니다). 각 설비의 적산에서 왔다. */
|
|
160
|
+
byEquipment: { id: string; kWh: number }[]
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `kWh` 가 어느 회계에서 왔나.
|
|
164
|
+
*
|
|
165
|
+
* 'demand-window' 우리가 표본에서 마감한 구간 (계량이 오는 현장)
|
|
166
|
+
* 'usage-period' 밖에서 마감되어 온 구간 (계량 표본이 없는 현장)
|
|
167
|
+
*
|
|
168
|
+
* 둘을 더하지 않는다 — 같은 전기를 두 번 센다. 둘 다 있으면 계량 쪽을 쓰고 그 사실을 낸다.
|
|
169
|
+
*/
|
|
170
|
+
kWhBasis?: 'demand-window' | 'usage-period'
|
|
171
|
+
/**
|
|
172
|
+
* 기본요금을 매긴 **요금적용전력과 그 근거**.
|
|
173
|
+
*
|
|
174
|
+
* 이 값은 매달 다시 정해진다. 선언에 박아 두면 사람이 갱신해야 하고, 안 하면 낡은 값으로
|
|
175
|
+
* 기본요금이 조용히 틀린다. 그래서 **청구서가 먼저다** — 원본이 매일 실어 보낸다.
|
|
176
|
+
*
|
|
177
|
+
* 'bill' 이 창을 덮는 청구서가 말한 값
|
|
178
|
+
* 'carried-forward' 가장 최근 청구서의 값을 이어 쓴다(요금 제도가 실제로 그렇게 군다)
|
|
179
|
+
* 'declaration' 청구가 오지 않는 현장 — 모델의 선언
|
|
180
|
+
*
|
|
181
|
+
* `declaredKW` 와 다르면 **선언이 낡았다**(`stale`). 그 사실을 감추면 아무도 다시 읽지 않는다.
|
|
182
|
+
*/
|
|
183
|
+
billingDemand?: {
|
|
184
|
+
kW: number
|
|
185
|
+
from: 'bill' | 'carried-forward' | 'declaration'
|
|
186
|
+
declaredKW?: number
|
|
187
|
+
stale?: boolean
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* 밖에서 마감되어 온 사용 구간 — 계량 표본이 없는 현장의 유일한 근거다.
|
|
191
|
+
*
|
|
192
|
+
* `basis` 별 건수를 함께 낸다: 같은 kWh 라도 계량기가 잰 것과 공급자 화면에서 긁어온 것은 무게가
|
|
193
|
+
* 다르고, 실제로 긁어온 값이 망가져 있는 것을 본 적이 있다.
|
|
194
|
+
*/
|
|
195
|
+
usage?: {
|
|
196
|
+
kWh: number
|
|
197
|
+
counted: number
|
|
198
|
+
/** 구간마다 제 단가로 매긴 사용량 요금 — 모든 구간이 단가를 실었을 때만. */
|
|
199
|
+
charge?: number
|
|
200
|
+
currency?: string
|
|
201
|
+
/** 단가가 없어 요금을 매기지 못한 구간 수 — 조용히 빼지 않는다. */
|
|
202
|
+
unpriced: number
|
|
203
|
+
byBasis: { basis: string; counted: number }[]
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* 이 창에 온전히 든 **청구서** — 공급자가 확정한 금액.
|
|
207
|
+
*
|
|
208
|
+
* 우리 계산을 덮지 않는다. 창이 청구 주기와 정확히 같을 때만 견줄 수 있으므로 그 사실을
|
|
209
|
+
* `matchesWindow` 로 함께 낸다 — 다른 창의 두 수를 빼면 그 차이는 아무것도 뜻하지 않는다.
|
|
210
|
+
*/
|
|
211
|
+
bill?: {
|
|
212
|
+
from: string
|
|
213
|
+
to: string
|
|
214
|
+
total?: number
|
|
215
|
+
energyCharge?: number
|
|
216
|
+
demandCharge?: number
|
|
217
|
+
currency?: string
|
|
218
|
+
billingDemandKW?: number
|
|
219
|
+
matchesWindow: boolean
|
|
220
|
+
/**
|
|
221
|
+
* 창이 같을 때, **우리 계산에서 청구액을 뺀 값** — 0 이 아니면 우리 계산이 틀렸다는 뜻이다.
|
|
222
|
+
*
|
|
223
|
+
* 항목별로도 낸다(2026-08-29). 총액만 견주면 원본이 총액을 주지 않는 청구서에서는 비교가
|
|
224
|
+
* 영영 나오지 않는다 — 실제로 첫 청구서에 기본요금만 있었다. 양쪽에 있는 항목만 견준다.
|
|
225
|
+
*/
|
|
226
|
+
diff?: { total?: number; demandCharge?: number; energyCharge?: number }
|
|
227
|
+
}
|
|
138
228
|
/** 파생의 근거 — 구간 평균부하 × 길이(`mean-kw`). */
|
|
139
229
|
basis: string
|
|
140
|
-
/** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) —
|
|
230
|
+
/** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) — 알리지 않고 자르지 않는다. */
|
|
141
231
|
counted: number
|
|
142
232
|
skipped: number
|
|
143
233
|
/** 완료 오더당 전력량 — 답할 수 없으면 `null` 과 이유. */
|
|
@@ -157,7 +247,7 @@ export interface KpiResult {
|
|
|
157
247
|
* 붙지 않는 부하도 그 비율에 섞인다 — 화면이 그 문장을 함께 말해야 한다.
|
|
158
248
|
*/
|
|
159
249
|
byFeeder?: {
|
|
160
|
-
/** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(
|
|
250
|
+
/** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(알리지 않고 버리지 않는다). */
|
|
161
251
|
key: string
|
|
162
252
|
kWh: number
|
|
163
253
|
/**
|
|
@@ -186,7 +276,7 @@ export interface KpiResult {
|
|
|
186
276
|
peakKW?: number
|
|
187
277
|
}[]
|
|
188
278
|
/**
|
|
189
|
-
* 나누지 못한 양과 그 이유 —
|
|
279
|
+
* 나누지 못한 양과 그 이유 — **알리지 않고 0 으로 만들지 않는다.**
|
|
190
280
|
* `no-weights` = 그 창에 돌아간 자원이 없다(가동시간이 전부 0). 그러면 총량은 그대로 남는다.
|
|
191
281
|
*/
|
|
192
282
|
unattributedKWh?: number
|
|
@@ -222,7 +312,7 @@ export interface KpiResult {
|
|
|
222
312
|
utilization: { resourceId: string; busyMs: number; ratio: number }[]
|
|
223
313
|
/**
|
|
224
314
|
* **셈에서 빠진 것** — 완료는 창 안이지만 시작 이벤트를 못 찾은 건수.
|
|
225
|
-
* 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이
|
|
315
|
+
* 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이 알리지 않고 거짓이 된다.
|
|
226
316
|
*/
|
|
227
317
|
unpaired: { leadTime: number; workTime: number }
|
|
228
318
|
/**
|
|
@@ -372,6 +462,183 @@ function feederShares(
|
|
|
372
462
|
* 분기 계량 합이 뿌리보다 크면(회계가 다른 두 계량기) **그 사실을 낸다** — 값을 깎아 맞추면 계량을
|
|
373
463
|
* 우리가 고쳐 쓰는 것이다.
|
|
374
464
|
*/
|
|
465
|
+
/**
|
|
466
|
+
* 창의 **발전량** — 커널이 마감한 기간 사실을 합한다.
|
|
467
|
+
*
|
|
468
|
+
* ── 왜 합이 전부인가 ───────────────────────────────────────────────────────
|
|
469
|
+
* 기간 발전량은 커널이 이미 확정한 사실이다(적산의 종류마다 다른 규칙으로 얻는다). 여기서 다시
|
|
470
|
+
* 계산하면 규칙이 두 벌이 되고, 그때부터 두 화면이 다른 발전량을 말한다. 이 함수가 하는 일은
|
|
471
|
+
* **창에 든 것을 고르고, 설비별로 묶고, 근거가 얼마나 덮였는지 세는 것**이다.
|
|
472
|
+
*
|
|
473
|
+
* ── 걸친 기간은 세지 않는다 ─────────────────────────────────────────────────
|
|
474
|
+
* 구간 마감과 같은 규칙이다. 하루의 절반이 창에 들었다고 절반을 세면 그것은 관측이 아니라 우리가 나눈
|
|
475
|
+
* 값이고, 합이 창을 넘는다.
|
|
476
|
+
*
|
|
477
|
+
* ── 덜 덮인 기간을 센다 ─────────────────────────────────────────────────────
|
|
478
|
+
* 기간의 끝이 자정인데 마지막 표본이 저녁이었으면 그 사이는 재지 않은 것이다. 그 수를 함께 내야 화면이
|
|
479
|
+
* 「이 합은 이만큼 덜 잰 것」이라고 말할 수 있다. 얼마나 어긋나면 문제인지는 현장이 정한다.
|
|
480
|
+
*/
|
|
481
|
+
function generatedOfPeriods(
|
|
482
|
+
periods: readonly {
|
|
483
|
+
equipmentId: string
|
|
484
|
+
kWh: number
|
|
485
|
+
startMs: number
|
|
486
|
+
endMs: number
|
|
487
|
+
observedFromMs: number
|
|
488
|
+
observedToMs: number
|
|
489
|
+
}[],
|
|
490
|
+
range: { startMs: number; endMs: number }
|
|
491
|
+
): Partial<NonNullable<KpiResult['energy']>> {
|
|
492
|
+
const inside = periods.filter(g => g.startMs >= range.startMs && g.endMs <= range.endMs)
|
|
493
|
+
if (!inside.length) return {}
|
|
494
|
+
|
|
495
|
+
const byEquipment = new Map<string, number>()
|
|
496
|
+
let partial = 0
|
|
497
|
+
for (const g of inside) {
|
|
498
|
+
byEquipment.set(g.equipmentId, (byEquipment.get(g.equipmentId) ?? 0) + g.kWh)
|
|
499
|
+
const coveredFrom = Number.isFinite(g.observedFromMs) ? g.observedFromMs <= g.startMs : false
|
|
500
|
+
const coveredTo = Number.isFinite(g.observedToMs) ? g.observedToMs >= g.endMs : false
|
|
501
|
+
if (!coveredFrom || !coveredTo) partial++
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return {
|
|
505
|
+
generated: {
|
|
506
|
+
kWh: inside.reduce((a, g) => a + g.kWh, 0),
|
|
507
|
+
counted: inside.length,
|
|
508
|
+
partial,
|
|
509
|
+
byEquipment: [...byEquipment.entries()]
|
|
510
|
+
.map(([id, kWh]) => ({ id, kWh }))
|
|
511
|
+
.sort((a, b) => b.kWh - a.kWh)
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* 밖에서 마감되어 온 사용 구간을 합한다 — **구간 마감과 더하지 않는다.**
|
|
518
|
+
*
|
|
519
|
+
* 두 회계가 같은 전기를 말한다. 더하면 두 번 세고, 둘 다 있는 현장에서는 계량 쪽이 강한 근거이므로
|
|
520
|
+
* 그쪽을 쓴다. 여기서는 이쪽 몫만 계산하고, 무엇을 쓸지는 부르는 쪽이 정한다.
|
|
521
|
+
*
|
|
522
|
+
* 단가는 **모든 구간이 실었을 때만** 요금을 만든다. 일부만 실었는데 있는 것만 더하면 그 합이 창의
|
|
523
|
+
* 요금인 척한다 — 못 매긴 구간 수를 함께 낸다.
|
|
524
|
+
*/
|
|
525
|
+
function usageOfPeriods(
|
|
526
|
+
periods: readonly { meterId: string; kWh: number; startMs: number; endMs: number; unitPrice: number; currency?: string; basis: string }[],
|
|
527
|
+
range: { startMs: number; endMs: number }
|
|
528
|
+
): NonNullable<KpiResult['energy']>['usage'] | undefined {
|
|
529
|
+
const inside = periods.filter(u => u.startMs >= range.startMs && u.endMs <= range.endMs)
|
|
530
|
+
if (!inside.length) return undefined
|
|
531
|
+
|
|
532
|
+
const byBasis = new Map<string, number>()
|
|
533
|
+
let kWh = 0
|
|
534
|
+
let charge = 0
|
|
535
|
+
let unpriced = 0
|
|
536
|
+
let currency: string | undefined
|
|
537
|
+
for (const u of inside) {
|
|
538
|
+
kWh += u.kWh
|
|
539
|
+
byBasis.set(u.basis, (byBasis.get(u.basis) ?? 0) + 1)
|
|
540
|
+
if (Number.isFinite(u.unitPrice)) {
|
|
541
|
+
charge += u.kWh * u.unitPrice
|
|
542
|
+
if (!currency) currency = u.currency
|
|
543
|
+
} else unpriced++
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
kWh,
|
|
548
|
+
counted: inside.length,
|
|
549
|
+
...(unpriced === 0 && currency ? { charge, currency } : {}),
|
|
550
|
+
unpriced,
|
|
551
|
+
byBasis: [...byBasis.entries()].map(([basis, counted]) => ({ basis, counted })).sort((a, b) => b.counted - a.counted)
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* **요금적용전력을 고른다 — 청구서가 먼저다.**
|
|
557
|
+
*
|
|
558
|
+
* 순서에 뜻이 있다. 청구서는 그 기간에 실제로 매겨진 값이고, 선언은 사람이 옮겨 적은 값이다.
|
|
559
|
+
* 그 값이 매달 바뀌므로 옮겨 적은 것은 곧 낡는다.
|
|
560
|
+
*/
|
|
561
|
+
function billingDemandOf(
|
|
562
|
+
billsInWindow: readonly { endMs: number; billingDemandKW: number }[],
|
|
563
|
+
lastBill: { billingDemandKW?: number } | undefined,
|
|
564
|
+
declaredKW: number | undefined
|
|
565
|
+
): NonNullable<KpiResult['energy']>['billingDemand'] | undefined {
|
|
566
|
+
const declared = Number.isFinite(Number(declaredKW)) ? Number(declaredKW) : undefined
|
|
567
|
+
const withValue = billsInWindow.filter(b => Number.isFinite(b.billingDemandKW))
|
|
568
|
+
const fromBill = withValue.length ? withValue.reduce((a, x) => (x.endMs > a.endMs ? x : a)).billingDemandKW : undefined
|
|
569
|
+
const carried = Number.isFinite(Number(lastBill?.billingDemandKW)) ? Number(lastBill?.billingDemandKW) : undefined
|
|
570
|
+
|
|
571
|
+
const picked =
|
|
572
|
+
fromBill !== undefined
|
|
573
|
+
? { kW: fromBill, from: 'bill' as const }
|
|
574
|
+
: carried !== undefined
|
|
575
|
+
? { kW: carried, from: 'carried-forward' as const }
|
|
576
|
+
: declared !== undefined
|
|
577
|
+
? { kW: declared, from: 'declaration' as const }
|
|
578
|
+
: undefined
|
|
579
|
+
if (!picked) return undefined
|
|
580
|
+
|
|
581
|
+
/* 선언이 있는데 청구서와 다르면 그 사실을 낸다 — 낡은 선언은 스스로 말해야 한다. */
|
|
582
|
+
const stale = picked.from !== 'declaration' && declared !== undefined && declared !== picked.kW
|
|
583
|
+
return {
|
|
584
|
+
...picked,
|
|
585
|
+
...(declared !== undefined ? { declaredKW: declared } : {}),
|
|
586
|
+
...(stale ? { stale: true } : {})
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* 창에 온전히 든 청구서 하나 — 가장 늦게 끝난 것.
|
|
592
|
+
*
|
|
593
|
+
* **창이 청구 주기와 같을 때만 견준다.** 한 달 청구서를 하루 창의 값과 빼면 그 차이는 아무것도 뜻하지
|
|
594
|
+
* 않는다. 그래서 같은지를 값과 함께 낸다.
|
|
595
|
+
*/
|
|
596
|
+
function billOfWindow(
|
|
597
|
+
bills: readonly {
|
|
598
|
+
from: string
|
|
599
|
+
to: string
|
|
600
|
+
startMs: number
|
|
601
|
+
endMs: number
|
|
602
|
+
total: number
|
|
603
|
+
energyCharge: number
|
|
604
|
+
demandCharge: number
|
|
605
|
+
currency?: string
|
|
606
|
+
billingDemandKW: number
|
|
607
|
+
}[],
|
|
608
|
+
range: { startMs: number; endMs: number },
|
|
609
|
+
ours?: { total?: number; demandCharge?: number; energyCharge?: number }
|
|
610
|
+
): NonNullable<KpiResult['energy']>['bill'] | undefined {
|
|
611
|
+
const inside = bills.filter(b => b.startMs >= range.startMs && b.endMs <= range.endMs)
|
|
612
|
+
if (!inside.length) return undefined
|
|
613
|
+
const b = inside.reduce((a, x) => (x.endMs > a.endMs ? x : a))
|
|
614
|
+
const matchesWindow = b.startMs === range.startMs && b.endMs === range.endMs
|
|
615
|
+
/* 양쪽에 있는 항목만 견준다 — 한쪽이 없는 것을 0 으로 두면 그 차이가 통째로 어긋남처럼 보인다. */
|
|
616
|
+
const gap = (mine: unknown, billed: number): number | undefined =>
|
|
617
|
+
Number.isFinite(Number(mine)) && Number.isFinite(billed) ? Number(mine) - billed : undefined
|
|
618
|
+
const diff = matchesWindow
|
|
619
|
+
? {
|
|
620
|
+
...(gap(ours?.total, b.total) !== undefined ? { total: gap(ours?.total, b.total) as number } : {}),
|
|
621
|
+
...(gap(ours?.demandCharge, b.demandCharge) !== undefined
|
|
622
|
+
? { demandCharge: gap(ours?.demandCharge, b.demandCharge) as number }
|
|
623
|
+
: {}),
|
|
624
|
+
...(gap(ours?.energyCharge, b.energyCharge) !== undefined
|
|
625
|
+
? { energyCharge: gap(ours?.energyCharge, b.energyCharge) as number }
|
|
626
|
+
: {})
|
|
627
|
+
}
|
|
628
|
+
: {}
|
|
629
|
+
return {
|
|
630
|
+
from: b.from,
|
|
631
|
+
to: b.to,
|
|
632
|
+
...(Number.isFinite(b.total) ? { total: b.total } : {}),
|
|
633
|
+
...(Number.isFinite(b.energyCharge) ? { energyCharge: b.energyCharge } : {}),
|
|
634
|
+
...(Number.isFinite(b.demandCharge) ? { demandCharge: b.demandCharge } : {}),
|
|
635
|
+
...(b.currency ? { currency: b.currency } : {}),
|
|
636
|
+
...(Number.isFinite(b.billingDemandKW) ? { billingDemandKW: b.billingDemandKW } : {}),
|
|
637
|
+
matchesWindow,
|
|
638
|
+
...(Object.keys(diff).length ? { diff } : {})
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
375
642
|
function measuredFeeders(
|
|
376
643
|
windows: readonly {
|
|
377
644
|
startMs: number
|
|
@@ -455,9 +722,16 @@ function measuredFeeders(
|
|
|
455
722
|
/** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */
|
|
456
723
|
export interface KpiFoldOptions {
|
|
457
724
|
groupBy?: KpiGroupBy
|
|
725
|
+
/**
|
|
726
|
+
* 이 트윈이 이어받고 있는 **마지막 청구서** — 요금적용전력의 우선 근거(§`billingDemandOf`).
|
|
727
|
+
*
|
|
728
|
+
* 창 밖의 청구서는 저널 조회에 걸리지 않는다. 그런데 그 값은 이 창에도 적용되므로 호출부가
|
|
729
|
+
* 커널 상태에서 읽어 넘긴다.
|
|
730
|
+
*/
|
|
731
|
+
lastBill?: { to: string; billingDemandKW?: number }
|
|
458
732
|
/**
|
|
459
733
|
* 노드 → 구역 지도. **이벤트에는 구역이 없다**(노드까지만 있다) — 스냅샷·마스터가 아는 값이라
|
|
460
|
-
* 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(
|
|
734
|
+
* 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(알리지 않고 다른 축으로 바꾸지 않는다).
|
|
461
735
|
*/
|
|
462
736
|
locationArea?: Record<string, string>
|
|
463
737
|
/**
|
|
@@ -487,7 +761,7 @@ export interface KpiFoldOptions {
|
|
|
487
761
|
tariff?: TariffDeclaration
|
|
488
762
|
/**
|
|
489
763
|
* 교대 선언 — **이벤트에는 교대가 없다**(시각만 있다). 현장 마스터가 아는 값이라 호출부가 넘긴다.
|
|
490
|
-
* 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로
|
|
764
|
+
* 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로 알리지 않고 바꾸지 않는다).
|
|
491
765
|
*/
|
|
492
766
|
shifts?: WorkCalendarEntry[]
|
|
493
767
|
/**
|
|
@@ -518,7 +792,7 @@ interface CompletionRecord {
|
|
|
518
792
|
waitMs?: number
|
|
519
793
|
}
|
|
520
794
|
|
|
521
|
-
/** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(
|
|
795
|
+
/** 이 기록이 요청한 축에서 어느 값에 속하는가. 값이 없으면 'unknown'(알리지 않고 버리지 않는다). */
|
|
522
796
|
function facetKey(rec: CompletionRecord, options: KpiFoldOptions): string {
|
|
523
797
|
const f = rec.facets
|
|
524
798
|
switch (options.groupBy) {
|
|
@@ -547,7 +821,7 @@ function facetKey(rec: CompletionRecord, options: KpiFoldOptions): string {
|
|
|
547
821
|
*/
|
|
548
822
|
if (!options.shifts?.length) return 'unknown'
|
|
549
823
|
/* 시각까지 넘긴다 — **휴일에 일어난 일은 어느 교대에도 속하지 않는다.** 되풀이만 보면 그날 서지도
|
|
550
|
-
않은 교대에 집계돼, 교대별 성과가
|
|
824
|
+
않은 교대에 집계돼, 교대별 성과가 알리지 않고 거짓이 된다. */
|
|
551
825
|
return activeShiftAt(options.shifts, rec.at, options.utcOffsetMinutes) ?? 'unknown'
|
|
552
826
|
default:
|
|
553
827
|
return 'unknown'
|
|
@@ -576,7 +850,7 @@ function qualityOf(rows: CompletionRecord[]): { quality?: { good: number; scrap:
|
|
|
576
850
|
* 기록을 축으로 묶어 축별 통계를 낸다 — **전체와 같은 규칙**으로(같은 stats 함수, 같은 창).
|
|
577
851
|
*
|
|
578
852
|
* 축의 값이 아주 많을 수 있다(오더 축은 수천 개). 그래서 건수 큰 것부터 상한까지만 돌려주고
|
|
579
|
-
* **잘라낸 수를 함께 알린다** —
|
|
853
|
+
* **잘라낸 수를 함께 알린다** — 들어온 것이 없는 절단은 "이게 전부" 라는 거짓을 만든다.
|
|
580
854
|
*/
|
|
581
855
|
function groupRecords(
|
|
582
856
|
records: CompletionRecord[],
|
|
@@ -644,11 +918,11 @@ function stats(values: number[]): DurationStats {
|
|
|
644
918
|
}
|
|
645
919
|
|
|
646
920
|
/**
|
|
647
|
-
* 이벤트를
|
|
921
|
+
* 이벤트를 계산해 시간창 KPI 를 만든다.
|
|
648
922
|
*
|
|
649
923
|
* **호출부 규약**: `events` 는 창보다 **넉넉히 앞까지** 담아 넘긴다(예: 창 시작 − 리드타임 상한).
|
|
650
924
|
* 완료된 작업의 시작 이벤트가 창 밖에 있을 수 있기 때문이다. 못 찾은 것은 `unpaired` 로 알린다 —
|
|
651
|
-
* 평균에서
|
|
925
|
+
* 평균에서 오류 없이 빠지면 지표가 거짓이 된다.
|
|
652
926
|
*
|
|
653
927
|
* 시간축은 `eventTime`(ISO) 차분이다. sim 클록이 실제 시각과 오프셋이 있어도 **차분은 정확**하다
|
|
654
928
|
* (고정 오프셋은 상쇄된다). 파싱 불가한 줄은 버린다.
|
|
@@ -676,7 +950,7 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
676
950
|
* **끝났나는 커널이 답한다** (2026-08-23) — `isOrderTerminal`.
|
|
677
951
|
*
|
|
678
952
|
* 예전에는 여기서 **동의어 목록**으로 추론했다(`completed`·`fulfilled`·`done`·`finished`).
|
|
679
|
-
* 그 방식이 낸 대가가 둘이었고 둘 다
|
|
953
|
+
* 그 방식이 낸 대가가 둘이었고 둘 다 들어온 것이 없었다.
|
|
680
954
|
*
|
|
681
955
|
* ① 원본의 낱말이 목록에 없으면 완료가 0 이 된다. 실제로 그랬다 — 첫 실 시스템의 `FINISHED`
|
|
682
956
|
* 가 빠져 있었고, 그때 목록에 낱말을 더한 것은 진짜 빈 자리(그 커넥터가 자기 상태 표를
|
|
@@ -693,7 +967,7 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
693
967
|
if (at >= window.fromMs && at <= window.toMs) {
|
|
694
968
|
/*
|
|
695
969
|
* **오더당 한 번만 센다.** 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수
|
|
696
|
-
* 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이
|
|
970
|
+
* 있고(그것은 결함이 아니라 폴링의 성질이다), 사건마다 세면 처리량이 알리지 않고 부풀려진다.
|
|
697
971
|
* 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있다(「완료는 작업당 하나」) — 오더만 없었다.
|
|
698
972
|
*/
|
|
699
973
|
if (done) {
|
|
@@ -701,7 +975,7 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
701
975
|
if (id && !completedOrderIds.has(id)) {
|
|
702
976
|
completedOrderIds.add(id)
|
|
703
977
|
completedOrders++
|
|
704
|
-
} else if (!id) completedOrders++ // 정체를 모르면 셈에서 빼지 않는다(
|
|
978
|
+
} else if (!id) completedOrders++ // 정체를 모르면 셈에서 빼지 않는다(알리지 않고 줄이지 않는다)
|
|
705
979
|
}
|
|
706
980
|
/*
|
|
707
981
|
* **완료로 세지 않은 낱말의 인구조사.** 「못 읽었다」가 아니다 — `picking` 처럼 완료가 아닌
|
|
@@ -744,6 +1018,65 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
744
1018
|
})
|
|
745
1019
|
.filter(w => Number.isFinite(w.startMs) && Number.isFinite(w.endMs))
|
|
746
1020
|
|
|
1021
|
+
/*
|
|
1022
|
+
* 발전 기간 사실 — 커널이 기간을 마감할 때 낸 것이다. 구간 마감과 같은 규칙으로 **창에 온전히 든
|
|
1023
|
+
* 것만** 센다(걸친 기간을 반만 세면 합이 창을 넘는다).
|
|
1024
|
+
*
|
|
1025
|
+
* 관측 구간(`observedFrom`·`observedTo`)을 함께 읽는다 — 값의 근거가 얼마나 덮였는지가 그 둘에 있다.
|
|
1026
|
+
*/
|
|
1027
|
+
const generationPeriods = events
|
|
1028
|
+
.filter(e => e.eventType === ENERGY_EVENT.generationPeriod)
|
|
1029
|
+
.map(e => {
|
|
1030
|
+
const d = e.payload?.data ?? e.payload ?? {}
|
|
1031
|
+
return {
|
|
1032
|
+
equipmentId: String(d.equipmentId ?? ''),
|
|
1033
|
+
kWh: Number(d.kWh),
|
|
1034
|
+
startMs: Date.parse(String(d.periodStart ?? '')),
|
|
1035
|
+
endMs: Date.parse(String(d.periodEnd ?? '')),
|
|
1036
|
+
observedFromMs: Date.parse(String(d.observedFrom ?? '')),
|
|
1037
|
+
observedToMs: Date.parse(String(d.observedTo ?? ''))
|
|
1038
|
+
}
|
|
1039
|
+
})
|
|
1040
|
+
.filter(g => g.equipmentId && Number.isFinite(g.kWh) && Number.isFinite(g.startMs) && Number.isFinite(g.endMs))
|
|
1041
|
+
|
|
1042
|
+
/*
|
|
1043
|
+
* 밖에서 마감되어 온 사용 구간 — 구간 마감과 같은 규칙으로 **창에 온전히 든 것만** 센다.
|
|
1044
|
+
*/
|
|
1045
|
+
const usagePeriods = events
|
|
1046
|
+
.filter(e => e.eventType === ENERGY_EVENT.usagePeriod)
|
|
1047
|
+
.map(e => {
|
|
1048
|
+
const d = e.payload?.data ?? e.payload ?? {}
|
|
1049
|
+
return {
|
|
1050
|
+
meterId: String(d.meterId ?? ''),
|
|
1051
|
+
kWh: Number(d.kWh),
|
|
1052
|
+
startMs: Date.parse(String(d.from ?? '')),
|
|
1053
|
+
endMs: Date.parse(String(d.to ?? '')),
|
|
1054
|
+
unitPrice: Number(d.unitPrice),
|
|
1055
|
+
currency: d.currency ? String(d.currency) : undefined,
|
|
1056
|
+
basis: d.basis ? String(d.basis) : 'unstated'
|
|
1057
|
+
}
|
|
1058
|
+
})
|
|
1059
|
+
.filter(u => u.meterId && Number.isFinite(u.kWh) && Number.isFinite(u.startMs) && Number.isFinite(u.endMs))
|
|
1060
|
+
|
|
1061
|
+
/* 청구서 — 창에 온전히 든 것 중 가장 늦게 끝난 것 하나. */
|
|
1062
|
+
const bills = events
|
|
1063
|
+
.filter(e => e.eventType === ENERGY_EVENT.bill)
|
|
1064
|
+
.map(e => {
|
|
1065
|
+
const d = e.payload?.data ?? e.payload ?? {}
|
|
1066
|
+
return {
|
|
1067
|
+
from: String(d.from ?? ''),
|
|
1068
|
+
to: String(d.to ?? ''),
|
|
1069
|
+
startMs: Date.parse(String(d.from ?? '')),
|
|
1070
|
+
endMs: Date.parse(String(d.to ?? '')),
|
|
1071
|
+
total: Number(d.total),
|
|
1072
|
+
energyCharge: Number(d.energyCharge),
|
|
1073
|
+
demandCharge: Number(d.demandCharge),
|
|
1074
|
+
currency: d.currency ? String(d.currency) : undefined,
|
|
1075
|
+
billingDemandKW: Number(d.billingDemandKW)
|
|
1076
|
+
}
|
|
1077
|
+
})
|
|
1078
|
+
.filter(b => Number.isFinite(b.startMs) && Number.isFinite(b.endMs))
|
|
1079
|
+
|
|
747
1080
|
const leads: number[] = []
|
|
748
1081
|
const works: number[] = []
|
|
749
1082
|
const waits: number[] = []
|
|
@@ -785,18 +1118,47 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
785
1118
|
return {
|
|
786
1119
|
window: { fromMs: window.fromMs, toMs: window.toMs },
|
|
787
1120
|
throughput: { tasks: tasksInWindow, orders: completedOrders },
|
|
788
|
-
|
|
1121
|
+
/* 발전만 있는 현장(계량 없는 발전소)도 답이 나와야 한다 — 둘 중 하나만 있어도 블록을 낸다. */
|
|
1122
|
+
...(demandWindows.length || generationPeriods.length || usagePeriods.length || bills.length
|
|
789
1123
|
? (() => {
|
|
790
1124
|
const e = energyOfWindows(demandWindows, { startMs: window.fromMs, endMs: window.toMs })
|
|
1125
|
+
const usage = usageOfPeriods(usagePeriods, { startMs: window.fromMs, endMs: window.toMs })
|
|
1126
|
+
/*
|
|
1127
|
+
* 어느 회계를 쓸지 정한다 — **더하지 않는다.** 계량 표본에서 우리가 마감한 값이 있으면 그것을
|
|
1128
|
+
* 쓰고(강한 근거), 없으면 밖에서 마감되어 온 값을 쓴다. 무엇을 썼는지 함께 낸다.
|
|
1129
|
+
*/
|
|
1130
|
+
const usedKWh = e.kWh !== undefined ? e.kWh : usage?.kWh
|
|
1131
|
+
const kWhBasis = e.kWh !== undefined ? 'demand-window' : usage ? 'usage-period' : undefined
|
|
791
1132
|
/* 분모는 **완료 오더**다(대당 전력). 창에 완료가 없으면 커널 규칙이 「정의되지 않음」으로 답한다. */
|
|
792
1133
|
const i = energyIntensity({
|
|
793
|
-
kWh:
|
|
1134
|
+
kWh: usedKWh,
|
|
794
1135
|
energyWindow: e.window,
|
|
795
1136
|
denominator: { kind: 'output', value: completedOrders, window: e.window }
|
|
796
1137
|
})
|
|
1138
|
+
/*
|
|
1139
|
+
* 요금은 **구간마다 제 단가로** 매긴 값이 있으면 그것을 쓴다. 단일 단가로 접으면 시간대마다
|
|
1140
|
+
* 다른 단가가 사라지고, 「피크를 깎아 얼마를 아끼나」가 그 차이 위에 선다.
|
|
1141
|
+
*/
|
|
1142
|
+
/* 요금적용전력은 청구서가 먼저다 — 선언은 청구가 오지 않는 현장의 몫이다. */
|
|
1143
|
+
const billingDemand = billingDemandOf(
|
|
1144
|
+
bills.filter(b => Number.isFinite(b.billingDemandKW)).map(b => ({ endMs: b.endMs, billingDemandKW: b.billingDemandKW })),
|
|
1145
|
+
options.lastBill,
|
|
1146
|
+
options.tariff?.billingDemandKW
|
|
1147
|
+
)
|
|
1148
|
+
const cost = options.tariff
|
|
1149
|
+
? electricityCost({
|
|
1150
|
+
kWh: usedKWh,
|
|
1151
|
+
peakKW: e.peakKW,
|
|
1152
|
+
tariff: { ...options.tariff, ...(billingDemand ? { billingDemandKW: billingDemand.kW } : {}) },
|
|
1153
|
+
...(usage?.charge !== undefined ? { energyChargeFromPeriods: usage.charge } : {})
|
|
1154
|
+
})
|
|
1155
|
+
: undefined
|
|
797
1156
|
return {
|
|
798
1157
|
energy: {
|
|
799
|
-
...(
|
|
1158
|
+
...(usedKWh !== undefined ? { kWh: usedKWh } : {}),
|
|
1159
|
+
...(kWhBasis ? { kWhBasis } : {}),
|
|
1160
|
+
...(billingDemand ? { billingDemand } : {}),
|
|
1161
|
+
...(usage ? { usage } : {}),
|
|
800
1162
|
/* 이 창의 최대수요 — 계약 대비 판정이 보는 수다(전력량과 같은 구간 집합에서 왔다). */
|
|
801
1163
|
...(e.peakKW !== undefined ? { peakKW: e.peakKW, peakStartMs: e.peakStartMs } : {}),
|
|
802
1164
|
/* 깎인 양은 **같은 구간의 두 수**의 차이다 — 여기서 한 번만 계산해 두 곳이 갈라지지 않게. */
|
|
@@ -813,7 +1175,13 @@ export function foldKpi(events: KpiEvent[], window: KpiWindow, options: KpiFoldO
|
|
|
813
1175
|
...(measuredFeeders(demandWindows, { startMs: window.fromMs, endMs: window.toMs }, e.kWh, options.meterFeeder) ??
|
|
814
1176
|
feederShares(e.kWh, utilization, options.resourceFeeder)),
|
|
815
1177
|
/* 금액은 같은 창의 전력량·최대수요에서 나온다 — 창이 하나뿐이라 어긋날 수 없다. */
|
|
816
|
-
...(
|
|
1178
|
+
...(cost ? { cost } : {}),
|
|
1179
|
+
...generatedOfPeriods(generationPeriods, { startMs: window.fromMs, endMs: window.toMs }),
|
|
1180
|
+
/* 청구서는 사실이다 — 우리 계산을 덮지 않고 나란히 둔다(§`billOfWindow`). */
|
|
1181
|
+
...(() => {
|
|
1182
|
+
const b = billOfWindow(bills, { startMs: window.fromMs, endMs: window.toMs }, cost as any)
|
|
1183
|
+
return b ? { bill: b } : {}
|
|
1184
|
+
})()
|
|
817
1185
|
}
|
|
818
1186
|
}
|
|
819
1187
|
})()
|
|
@@ -876,7 +1244,7 @@ export function crossGroups(
|
|
|
876
1244
|
|
|
877
1245
|
/* ── 시간 비교 ──────────────────────────────────────────────────────────────
|
|
878
1246
|
* 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다("29초" 가 개선인지 악화인지). 같은 길이의 창을 옮겨
|
|
879
|
-
* 한 번 더
|
|
1247
|
+
* 한 번 더 계산하면 되므로(폴드는 순수) 값싸다. 창 옮기기와 차이 계산은 계산 규칙이라 여기 있다 —
|
|
880
1248
|
* 조회 층에 두면 테스트가 DB 를 끌고 와야 한다.
|
|
881
1249
|
*/
|
|
882
1250
|
|
|
@@ -913,7 +1281,7 @@ export interface KpiBucket {
|
|
|
913
1281
|
}
|
|
914
1282
|
|
|
915
1283
|
/**
|
|
916
|
-
* 창을 N 등분해 구간별로
|
|
1284
|
+
* 창을 N 등분해 구간별로 처리한다 — **같은 이벤트를 창만 바꿔** 다시 계산한다(조회 추가 없음).
|
|
917
1285
|
*
|
|
918
1286
|
* 마지막 구간은 나머지를 흡수한다(부동소수 나눗셈으로 끝이 밀리면 마지막 완료가 빠진다).
|
|
919
1287
|
* `count <= 1` 이면 쪼갤 것이 없으므로 빈 배열 — 호출부가 "추세 없음" 으로 다룬다.
|