@things-factory/headless-twin 10.0.15 → 10.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/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 +18 -82
- package/dist-server/engine/canonical-ingest.js +183 -18
- 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 +4 -4
- 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 +90 -10
- package/dist-server/engine/ingest-health.js +125 -16
- 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 +212 -14
- package/dist-server/engine/kpi-fold.js +374 -21
- 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 +281 -36
- 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 +15 -14
- 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 +3 -3
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +6 -5
- package/dist-server/engine/oee-accumulator.js +6 -5
- 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 +307 -54
- package/dist-server/engine/twin-engine.js +822 -196
- 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 +30 -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 +116 -9
- 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 +76 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +25 -16
- package/dist-server/service/reference/reference-master.js +35 -13
- 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 +12 -12
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/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-backfill/backfill-period-facts.d.ts +24 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
- package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
- package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
- package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
- package/dist-server/service/twin-backfill/index.d.ts +2 -0
- package/dist-server/service/twin-backfill/index.js +6 -0
- package/dist-server/service/twin-backfill/index.js.map +1 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
- 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 +2 -2
- 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/domain-catalog.js +2 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.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 +34 -3
- 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 +12 -9
- package/dist-server/service/twin-model/isa95-coverage.js +33 -9
- 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 +2 -2
- 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 +7 -6
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
- package/dist-server/service/twin-model/twin-equipment.js +3 -3
- 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 +15 -15
- 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 +51 -30
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
- 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 +37 -8
- 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 +4 -4
- 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 +7 -6
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +202 -87
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +2 -2
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +171 -22
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +592 -41
- package/server/engine/kpi-query.ts +307 -48
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +15 -14
- 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 +2 -2
- package/server/engine/oee-accumulator.ts +8 -6
- 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 +906 -205
- 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 +73 -5
- package/server/service/index.ts +7 -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 +145 -10
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +87 -15
- package/server/service/reference/reference-master.ts +51 -20
- package/server/service/reference/reference-resolver.ts +11 -11
- package/server/service/reference/template-registry.ts +1 -1
- 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-backfill/backfill-period-facts.ts +168 -0
- package/server/service/twin-backfill/backfill-shape.ts +86 -0
- package/server/service/twin-backfill/index.ts +3 -0
- package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
- package/server/service/twin-control/twin-control-mutation.ts +2 -2
- package/server/service/twin-event/twin-event-keys.ts +2 -2
- 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/domain-catalog.ts +2 -1
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
- 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 +34 -3
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +43 -16
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +2 -2
- package/server/service/twin-model/project-structure.ts +7 -6
- package/server/service/twin-model/twin-equipment.ts +16 -4
- 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 +15 -15
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +49 -28
- package/server/service/twin-model/twin-model-tree-query.ts +26 -2
- 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 +40 -8
- 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 +4 -4
- 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 +6 -5
- package/test/backfill-shape.test.ts +89 -0
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-cost-baseline.test.ts +2 -1
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +259 -8
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/contract-layer-guard.test.ts +48 -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 +7 -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/equipment-identity-reaches.test.ts +57 -0
- 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/fact-scope-wiring.test.ts +96 -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/generated-not-counted.test.ts +99 -0
- package/test/ingest-bench.test.ts +2 -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/ingest-wiring-guard.test.ts +197 -0
- 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 +266 -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 +9 -6
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +13 -12
- package/test/log-stamp.test.ts +1 -1
- package/test/master-to-twin.test.ts +2 -2
- 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 +20 -5
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +7 -2
- package/test/projection-reads-declared.test.ts +109 -0
- package/test/property-effects.test.ts +10 -6
- 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/scale-twin-bench.test.ts +2 -1
- 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 +19 -21
- 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 +1 -1
- 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/vocabulary-guard.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 +2 -2
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -15,9 +15,12 @@
|
|
|
15
15
|
* 어떤 기준을 썼는지는 결과의 `window.basis` 로 밝힌다 — 숨기면 사용자가 숫자를 오해한다.
|
|
16
16
|
*/
|
|
17
17
|
import { twinWarn } from './log.js'
|
|
18
|
+
import { mergeStreams } from '../service/twin-journal/event-type-split-shape.js'
|
|
19
|
+
import { hydratedDate } from './read-time.js'
|
|
18
20
|
import { Between, In } from 'typeorm'
|
|
19
21
|
|
|
20
|
-
import {
|
|
22
|
+
import { type TariffDeclaration } from '@operato/twin-kernel'
|
|
23
|
+
import { hierarchyOf, levelOfLocationType, electricalUpstreamOf, EMS_LOCATION_TYPES, EMS_PROPERTY } from '@operato/ops-contract'
|
|
21
24
|
|
|
22
25
|
import { getReadRepository, getRepository } from '@things-factory/shell'
|
|
23
26
|
|
|
@@ -49,20 +52,49 @@ import { MAX_BASELINE_PERIODS, baselineWindows, summarizeBaseline, type Baseline
|
|
|
49
52
|
* 나눈 값이라, 두 사실이 한 폴드에 들어와야 창이 어긋날 수 없다. 표본(`energy.measured`)은 읽지 않는다 —
|
|
50
53
|
* 저널에 없고(§4.1 인제스트 계약) 여기서 필요한 것은 마감된 구간뿐이다.
|
|
51
54
|
*/
|
|
52
|
-
const BUSINESS_EVENTS = ['task.status', 'order.status', 'energy.demand.window']
|
|
55
|
+
const BUSINESS_EVENTS = ['task.status', 'order.status', 'energy.demand.window', 'energy.generation.period', 'energy.usage.period', 'energy.bill', 'energy.generation.price']
|
|
53
56
|
|
|
54
|
-
/** 한 번에
|
|
57
|
+
/** 한 번에 계산할 이벤트 상한 — 큰 구간이 서버를 붙잡지 않게. */
|
|
55
58
|
const MAX_EVENTS = 20000
|
|
56
59
|
|
|
60
|
+
/**
|
|
61
|
+
* 종류를 나눠 물을 때 만들 갈래 수의 상한 — 대상 × 종류.
|
|
62
|
+
*
|
|
63
|
+
* 갈래마다 상한(`MAX_EVENTS`)까지 받아야 합친 결과가 정확하다(§`mergeStreams`). 그래서 갈래가 늘면
|
|
64
|
+
* 메모리가 그만큼 든다. 셋(종류) × 둘(트윈)까지는 최악에 12만 행이고, 그 이상은 예전 한 질의로 간다 —
|
|
65
|
+
* 느리지만 답은 같다.
|
|
66
|
+
*/
|
|
67
|
+
/*
|
|
68
|
+
* ── 나눠 물을 수 있는 갈래의 최대 (2026-08-30 실측) ──────────────────────────
|
|
69
|
+
*
|
|
70
|
+
* 6 이었다. 그런데 **한 현장에 트윈이 여럿인 것이 이 제품의 정상**이다(창고·공정·야드가 한 현실을
|
|
71
|
+
* 각각 본다). 트윈 3개 × 종류 6가지 = 18 이라 조건에 안 걸렸고, 한 질의로 떨어졌다.
|
|
72
|
+
*
|
|
73
|
+
* 같은 창·같은 행으로 두 모양을 쟀다(hatio-us, order.status 345만 건이 있는 하루):
|
|
74
|
+
*
|
|
75
|
+
* 한 질의(종류 IN + 시각순) ix_twin_event_1 로 그 날 전체를 걷는다 25.987초 · 20,000건(잘림)
|
|
76
|
+
* 갈래 하나(종류 정확일치) ix_twin_event_7 이 순서까지 준다 0.286초
|
|
77
|
+
*
|
|
78
|
+
* 91배다. 그리고 25초를 기다린 끝에 나오는 값이 **잘린 값**이다 — 사용자는 그 사실을 25초 뒤에 본다.
|
|
79
|
+
*
|
|
80
|
+
* ── 늘리는 대가 ────────────────────────────────────────────────────────────
|
|
81
|
+
* 갈래마다 상한까지 받아야 합칠 때 정확하므로(§`mergeStreams`), 갈래가 많으면 그만큼 든다.
|
|
82
|
+
* 최악은 `갈래 수 × MAX_EVENTS` 행이다. 다만 그 최악은 **모든 갈래가 창을 가득 채울 때**이고,
|
|
83
|
+
* 그때는 어차피 결과가 잘린다(그 사실은 `capped` 로 나간다).
|
|
84
|
+
*
|
|
85
|
+
* 32 는 트윈 4개 × 종류 8가지를 덮는다. 어휘가 더 늘면 이 수를 다시 재고 정한다.
|
|
86
|
+
*/
|
|
87
|
+
const KPI_MAX_SPLIT = 32
|
|
88
|
+
|
|
57
89
|
export interface TwinKpiInput {
|
|
58
90
|
domainId: string
|
|
59
91
|
/** 한 트윈. `spaceId` 와 둘 중 하나는 있어야 한다. */
|
|
60
92
|
instanceId?: string
|
|
61
93
|
/**
|
|
62
|
-
* 한 **공간** — 그 공간에서 가동 중인 트윈 전부를 **한 번에
|
|
94
|
+
* 한 **공간** — 그 공간에서 가동 중인 트윈 전부를 **한 번에 계산한다**.
|
|
63
95
|
*
|
|
64
|
-
* 트윈별로
|
|
65
|
-
* 그래서 이벤트를 모아 **한 번**
|
|
96
|
+
* 트윈별로 계산해 평균을 다시 평균하면 분위수가 거짓이 된다(p90 의 평균은 p90 이 아니다).
|
|
97
|
+
* 그래서 이벤트를 모아 **한 번** 계산한다. 사용자는 "부산 창고" 를 보고, 그 안에 트윈이 몇 개든
|
|
66
98
|
* 합쳐서 본다(헤더 트윈 셀렉터를 없앤 결정과 같은 규율).
|
|
67
99
|
*/
|
|
68
100
|
spaceId?: string
|
|
@@ -76,14 +108,14 @@ export interface TwinKpiInput {
|
|
|
76
108
|
lookbackMinutes?: number
|
|
77
109
|
/**
|
|
78
110
|
* 창을 N 등분해 **구간별 값도** 함께 낸다(추세용). 1~48, 기본 0(쪼개지 않음).
|
|
79
|
-
* 조회는 한 번이고
|
|
111
|
+
* 조회는 한 번이고 계산만 N 번 반복한다 — 폴드가 순수하므로 값싸다.
|
|
80
112
|
*/
|
|
81
113
|
buckets?: number
|
|
82
114
|
/**
|
|
83
115
|
* 같은 사실을 **다른 관점으로** 쪼갠다: 자원별·작업종류별·도착지점별·오더별·구역별.
|
|
84
116
|
*
|
|
85
117
|
* 고정된 한 렌즈만 낼 수 있으면 "구역별로는 어떤가" 라는 질문에 화면도 AI 도 답할 수 없다.
|
|
86
|
-
* 축은 조회 인자 하나이고,
|
|
118
|
+
* 축은 조회 인자 하나이고, 계산는 순수 함수라 축을 하나 더 보는 값이 거의 없다.
|
|
87
119
|
*/
|
|
88
120
|
groupBy?: KpiGroupBy
|
|
89
121
|
/** 축의 상한(기본 20, 최대 100). 넘치면 잘라내고 잘린 수를 함께 알린다. */
|
|
@@ -92,12 +124,12 @@ export interface TwinKpiInput {
|
|
|
92
124
|
* **무엇과 비교할 것인가** — 같은 길이의 앞 구간(previous)이나 하루 전 같은 시간(yesterday).
|
|
93
125
|
*
|
|
94
126
|
* 숫자 하나로는 좋아졌는지 나빠졌는지 알 수 없다("29초" 가 개선인지 악화인지). 창만 옮겨 한 번 더
|
|
95
|
-
*
|
|
127
|
+
* 계산하면 되므로(폴드는 순수) 값싸다. 비교 구간에 기록이 없으면 **delta 를 만들지 않는다** —
|
|
96
128
|
* 없는 것을 0% 로 그리면 "변화 없음" 이라는 거짓이 된다.
|
|
97
129
|
*/
|
|
98
130
|
compareTo?: 'previous' | 'yesterday'
|
|
99
131
|
/**
|
|
100
|
-
* **여러 구간 동시 비교** — 최근 N 구간의 같은 시간대를 함께
|
|
132
|
+
* **여러 구간 동시 비교** — 최근 N 구간의 같은 시간대를 함께 계산해 **분포**와 그 안의 자리를 낸다.
|
|
101
133
|
*
|
|
102
134
|
* `compareTo` 는 한 구간만 봐서 판단이 "어제보다 나아졌다" 에서 멈춘다 — 어제도 나빴으면 쓸모가 없다.
|
|
103
135
|
* 최근 며칠의 같은 시간대를 보면 목표 없이도 "지난 7일 중 가장 낮다" 를 말할 수 있다.
|
|
@@ -122,7 +154,7 @@ export interface TwinKpiOutput extends Omit<Partial<KpiResult>, 'window'> {
|
|
|
122
154
|
/**
|
|
123
155
|
* 조회가 **상한에 걸렸는가**(구간이 너무 커서 일부만 읽었을 수 있다).
|
|
124
156
|
*
|
|
125
|
-
* true 면 이 구간의 숫자는 **하한**이다 — 실제로는 더 있었을 수 있다.
|
|
157
|
+
* true 면 이 구간의 숫자는 **하한**이다 — 실제로는 더 있었을 수 있다. 알리지 않고 잘라내면 숫자가 알리지 않고
|
|
126
158
|
* 작아지고 사용자는 그것을 성과 하락으로 읽는다. 구간을 좁혀 다시 물어야 한다.
|
|
127
159
|
*/
|
|
128
160
|
eventsCapped?: boolean
|
|
@@ -132,9 +164,9 @@ export interface TwinKpiOutput extends Omit<Partial<KpiResult>, 'window'> {
|
|
|
132
164
|
*/
|
|
133
165
|
measured: boolean
|
|
134
166
|
note?: string
|
|
135
|
-
/** 무엇을 집계했나 — 공간이면 합쳐
|
|
167
|
+
/** 무엇을 집계했나 — 공간이면 합쳐 계산한 트윈 목록을 밝힌다(숫자의 출처를 숨기지 않는다). */
|
|
136
168
|
/**
|
|
137
|
-
* 무엇을 집계했나 — 공간이면 합쳐
|
|
169
|
+
* 무엇을 집계했나 — 공간이면 합쳐 계산한 트윈 목록.
|
|
138
170
|
*
|
|
139
171
|
* `restartPolicies` 는 **이 숫자가 무엇의 성과인지**를 말한다: mirror(실제 시스템을 반영) ·
|
|
140
172
|
* `resume`(이어지는 가상 세계) · `reset`(재현 실험 사본). 같은 "345건" 이 현실의 실적일 수도, 시뮬레이션의
|
|
@@ -152,7 +184,7 @@ export interface TwinKpiOutput extends Omit<Partial<KpiResult>, 'window'> {
|
|
|
152
184
|
*/
|
|
153
185
|
targets?: TargetVerdict[]
|
|
154
186
|
/**
|
|
155
|
-
* 비교 구간(요청했을 때만) — 같은 규칙·같은 길이로
|
|
187
|
+
* 비교 구간(요청했을 때만) — 같은 규칙·같은 길이로 계산한 앞 구간과의 차이.
|
|
156
188
|
*
|
|
157
189
|
* `measured:false` 면 그 구간에 기록이 없다는 뜻이고 `delta` 는 없다(0 이 아니다).
|
|
158
190
|
* delta 는 **현재 − 비교구간** 이다. 좋고 나쁨은 판단하지 않는다 — 처리량은 클수록 좋고 소요는
|
|
@@ -212,7 +244,7 @@ type KpiGroupOut = NonNullable<KpiResult['groups']>['items'][number] &
|
|
|
212
244
|
*
|
|
213
245
|
* **한 홉만 보면 안 된다.** `parentId` 는 구역일 수도 있고 중간 단(라인·셀)일 수도 있다. 라인이 끼면
|
|
214
246
|
* 한 홉짜리 코드는 "구역이 LINE-1" 이라고 답하고, 그 id 는 구역 목록에 없으므로 그 아래 스테이션이
|
|
215
|
-
* **집계에서
|
|
247
|
+
* **집계에서 오류 없이 빠진다**(에러 없이 틀린 숫자). 그래서 커널 `hierarchyOf` 로 사슬을 끝까지 걷는다 —
|
|
216
248
|
* 걷는 규칙은 커널에 한 벌뿐이고 소비처는 그것만 부른다.
|
|
217
249
|
*/
|
|
218
250
|
/**
|
|
@@ -338,7 +370,7 @@ async function locationAreaMap(
|
|
|
338
370
|
*
|
|
339
371
|
* 여러 에너지 트윈이 한 현장에 있으면 **처음 선언된 것 하나**를 쓴다. 평균을 내거나 합치지 않는다:
|
|
340
372
|
* 요금은 계약이고, 두 계약을 섞은 단가는 어느 계약도 아니다. 서로 다른 단가가 선언돼 있으면 그것을
|
|
341
|
-
* 밝힌다(
|
|
373
|
+
* 밝힌다(알리지 않고 하나를 고르면 왜 그 금액인지 아무도 설명할 수 없다).
|
|
342
374
|
*/
|
|
343
375
|
async function tariffOf(
|
|
344
376
|
domainId: string,
|
|
@@ -357,11 +389,14 @@ async function tariffOf(
|
|
|
357
389
|
}
|
|
358
390
|
const d = num(EMS_PROPERTY.demandChargePerKW)
|
|
359
391
|
const e = num(EMS_PROPERTY.energyChargePerKWh)
|
|
392
|
+
/* 요금적용전력 — 있으면 기본요금이 조건부 파생이 아니라 사실이 된다(§`electricityCost`). */
|
|
393
|
+
const b = num(EMS_PROPERTY.billingDemandKW)
|
|
360
394
|
const cur = props.get(EMS_PROPERTY.currency)
|
|
361
395
|
if (d === undefined && e === undefined) continue
|
|
362
396
|
found.push({
|
|
363
397
|
...(d !== undefined ? { demandChargePerKW: d } : {}),
|
|
364
398
|
...(e !== undefined ? { energyChargePerKWh: e } : {}),
|
|
399
|
+
...(b !== undefined ? { billingDemandKW: b } : {}),
|
|
365
400
|
...(cur ? { currency: String(cur) } : {})
|
|
366
401
|
})
|
|
367
402
|
}
|
|
@@ -369,7 +404,11 @@ async function tariffOf(
|
|
|
369
404
|
if (!found.length) return {}
|
|
370
405
|
const first = found[0]
|
|
371
406
|
const same = found.every(
|
|
372
|
-
t =>
|
|
407
|
+
t =>
|
|
408
|
+
t.demandChargePerKW === first.demandChargePerKW &&
|
|
409
|
+
t.energyChargePerKWh === first.energyChargePerKWh &&
|
|
410
|
+
t.billingDemandKW === first.billingDemandKW &&
|
|
411
|
+
t.currency === first.currency
|
|
373
412
|
)
|
|
374
413
|
return { tariff: first, ...(same ? {} : { conflict: true }) }
|
|
375
414
|
}
|
|
@@ -435,11 +474,86 @@ async function resourceFeederMap(
|
|
|
435
474
|
}
|
|
436
475
|
|
|
437
476
|
/**
|
|
438
|
-
* 이 공간의 에너지 트윈들의 계통도 — 모델을 읽어 트리로
|
|
477
|
+
* 이 공간의 에너지 트윈들의 계통도 — 모델을 읽어 트리로 처리한다.
|
|
439
478
|
*
|
|
440
479
|
* 여러 트윈이 한 공간에 있으면 **각 트윈의 뿌리를 나란히** 둔다(하나로 합치면 없는 결선을 그리게 된다).
|
|
441
480
|
* 에너지 트윈이 없으면 만들지 않는다 — 빈 그림은 고장으로 읽힌다.
|
|
442
481
|
*/
|
|
482
|
+
/**
|
|
483
|
+
* 이 트윈들이 이어받고 있는 **마지막 청구서** — 요금적용전력의 우선 근거.
|
|
484
|
+
*
|
|
485
|
+
* 저널이 아니라 상태에서 읽는 이유: 청구는 한 달에 한 번이라 하루 창의 조회에는 걸리지 않는다.
|
|
486
|
+
* 그런데 그 값은 **그 창에도 적용된다**(요금 제도가 그렇게 군다). 커널이 재기동에서도 이어받으므로
|
|
487
|
+
* 상태가 그 자리다.
|
|
488
|
+
*
|
|
489
|
+
* 트윈이 여럿이면 가장 늦게 끝난 것을 쓴다 — 현장 하나에 청구가 여럿이면 그것은 다른 문제이고,
|
|
490
|
+
* 여기서 골라 감추지 않는다.
|
|
491
|
+
*/
|
|
492
|
+
function latestBillOf(domainId: string, instanceIds: string[]): { to: string; billingDemandKW?: number } | undefined {
|
|
493
|
+
let best: { to: string; billingDemandKW?: number } | undefined
|
|
494
|
+
for (const instanceId of instanceIds) {
|
|
495
|
+
const bill = (TwinEngine.snapshot(domainId, instanceId) as any)?.energy?.lastBill
|
|
496
|
+
if (!bill?.to) continue
|
|
497
|
+
if (!best || Date.parse(bill.to) > Date.parse(best.to)) {
|
|
498
|
+
best = { to: String(bill.to), ...(Number.isFinite(Number(bill.billingDemandKW)) ? { billingDemandKW: Number(bill.billingDemandKW) } : {}) }
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return best
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/** 이 트윈들이 이어받고 있는 **발전 단가** — 창 안에 단가 사실이 없을 때 쓴다. */
|
|
505
|
+
function latestGenerationPriceOf(
|
|
506
|
+
domainId: string,
|
|
507
|
+
instanceIds: string[]
|
|
508
|
+
): { from: string; to: string; unitPrice: number; currency: string } | undefined {
|
|
509
|
+
let best: { from: string; to: string; unitPrice: number; currency: string } | undefined
|
|
510
|
+
for (const instanceId of instanceIds) {
|
|
511
|
+
const p = (TwinEngine.snapshot(domainId, instanceId) as any)?.energy?.generationPrice
|
|
512
|
+
if (!p?.from || !Number.isFinite(Number(p.unitPrice)) || !p.currency) continue
|
|
513
|
+
if (!best || Date.parse(p.from) > Date.parse(best.from)) {
|
|
514
|
+
best = { from: String(p.from), to: String(p.to), unitPrice: Number(p.unitPrice), currency: String(p.currency) }
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
return best
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* 이 트윈들의 **발전 정격** — 이용률의 분모.
|
|
522
|
+
*
|
|
523
|
+
* 커널이 고른다(설비 합 → 자리 합계, 교류 → 직류). 여러 트윈이면 합한다 — 한 현장의 발전은
|
|
524
|
+
* 트윈마다 나뉘어 있어도 같은 현장의 발전이다. 근거가 갈리면 약한 쪽(직류·자리)으로 적는다.
|
|
525
|
+
*/
|
|
526
|
+
function generationRatedOf(
|
|
527
|
+
domainId: string,
|
|
528
|
+
instanceIds: string[]
|
|
529
|
+
): { kW: number; basis: 'ac' | 'dc'; from: 'equipment' | 'location' } | undefined {
|
|
530
|
+
let kW = 0
|
|
531
|
+
let basis: 'ac' | 'dc' = 'ac'
|
|
532
|
+
let from: 'equipment' | 'location' = 'equipment'
|
|
533
|
+
for (const instanceId of instanceIds) {
|
|
534
|
+
/* 스냅샷에서 읽는다 — 커널이 고른 값이다. 여기서 모델을 다시 뒤지면 고르는 규칙이 두 벌이 된다. */
|
|
535
|
+
const r = (TwinEngine.snapshot(domainId, instanceId) as any)?.energy?.generationRated
|
|
536
|
+
if (!r || !(Number(r.kW) > 0)) continue
|
|
537
|
+
kW += Number(r.kW)
|
|
538
|
+
if (r.basis === 'dc') basis = 'dc'
|
|
539
|
+
if (r.from === 'location') from = 'location'
|
|
540
|
+
}
|
|
541
|
+
return kW > 0 ? { kW, basis, from } : undefined
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** 이 트윈들이 이어받고 있는 **요금 기준** — 이 주기에 적용되는 값(청구서보다 먼저다). */
|
|
545
|
+
function latestTariffBasisOf(domainId: string, instanceIds: string[]): { from: string; billingDemandKW?: number } | undefined {
|
|
546
|
+
let best: { from: string; billingDemandKW?: number } | undefined
|
|
547
|
+
for (const instanceId of instanceIds) {
|
|
548
|
+
const b = (TwinEngine.snapshot(domainId, instanceId) as any)?.energy?.tariffBasis
|
|
549
|
+
if (!b?.from) continue
|
|
550
|
+
if (!best || Date.parse(b.from) > Date.parse(best.from)) {
|
|
551
|
+
best = { from: String(b.from), ...(Number.isFinite(Number(b.billingDemandKW)) ? { billingDemandKW: Number(b.billingDemandKW) } : {}) }
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return best
|
|
555
|
+
}
|
|
556
|
+
|
|
443
557
|
async function energyTopologyOf(
|
|
444
558
|
domainId: string,
|
|
445
559
|
instanceIds: string[],
|
|
@@ -465,15 +579,50 @@ async function energyTopologyOf(
|
|
|
465
579
|
return merged.roots.length ? merged : undefined
|
|
466
580
|
}
|
|
467
581
|
|
|
468
|
-
/**
|
|
582
|
+
/**
|
|
583
|
+
* 이 대상들의 **마지막 업무 기록 시각**(ISO). 기록이 없으면 null.
|
|
584
|
+
*
|
|
585
|
+
* ── 두 가지를 함께 고쳤다 (2026-08-28 실측) ─────────────────────────────────
|
|
586
|
+
* 예전 판은 이랬다.
|
|
587
|
+
*
|
|
588
|
+
* findOne({ where: { domain: { id }, instanceId: In(…), eventType: In(셋) }, order: { revision: 'DESC' } })
|
|
589
|
+
*
|
|
590
|
+
* **① 관계로 물어 두 단계 질의가 된다.** 안쪽에 상한이 없어 조건에 맞는 행을 **전부 만든 뒤** 하나를
|
|
591
|
+
* 고른다 — 그 트윈의 업무 사실이 97만 행이고 `payload` 까지 실려 온다. 이 파일의 다른 자리가 같은 것을
|
|
592
|
+
* 실측해 적어 두었다(§`twinEvents`: 0.024초 대 12초). 서버 로그에 「느린 질의」로 찍혀 있던 것이
|
|
593
|
+
* 이것이고, 현장 성과 화면의 7.2초가 여기서 나왔다.
|
|
594
|
+
*
|
|
595
|
+
* **② 「마지막 시각」을 원하면서 `revision` 으로 정렬했다.** 그 둘은 어긋난다 — 연결된 시스템을 되읽는
|
|
596
|
+
* 트윈은 원본의 시각을 싣고 도착 순서로 리비전을 받으므로, 리비전이 가장 큰 행이 4월 사실일 수 있다
|
|
597
|
+
* (같은 어긋남을 오늘 저널 목록에서도 고쳤다). 창의 끝을 그 값으로 잡으면 **창 자체가 틀린다.**
|
|
598
|
+
*
|
|
599
|
+
* 지금은 종류마다 `MAX(eventTime)` 을 묻는다. `(domain, instance, event_type, event_time)` 색인의
|
|
600
|
+
* 끝을 집으므로 종류당 한 번이고, 셋 중 가장 늦은 것이 답이다.
|
|
601
|
+
*/
|
|
469
602
|
async function latestEventTime(domainId: string, instanceIds: string[]): Promise<string | null> {
|
|
470
603
|
if (instanceIds.length === 0) return null
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
604
|
+
const rows = await Promise.all(
|
|
605
|
+
instanceIds.flatMap(instanceId =>
|
|
606
|
+
BUSINESS_EVENTS.map(eventType =>
|
|
607
|
+
getReadRepository(TwinEvent)
|
|
608
|
+
.createQueryBuilder('e')
|
|
609
|
+
.select('MAX(e.eventTime)', 'at')
|
|
610
|
+
.where('e.domain = :domainId', { domainId })
|
|
611
|
+
.andWhere('e.instanceId = :instanceId', { instanceId })
|
|
612
|
+
.andWhere('e.eventType = :eventType', { eventType })
|
|
613
|
+
.getRawOne<{ at: unknown }>()
|
|
614
|
+
.catch(() => null)
|
|
615
|
+
)
|
|
616
|
+
)
|
|
617
|
+
)
|
|
618
|
+
/* 드라이버가 준 값은 시간대가 없을 수 있다 — 컬럼 변환을 지나게 한다(§`hydratedDate`). */
|
|
619
|
+
const repo = getReadRepository(TwinEvent)
|
|
620
|
+
let latest: Date | null = null
|
|
621
|
+
for (const r of rows) {
|
|
622
|
+
const at = hydratedDate(repo, 'eventTime', r?.at)
|
|
623
|
+
if (at && (!latest || at.getTime() > latest.getTime())) latest = at
|
|
624
|
+
}
|
|
625
|
+
return latest ? latest.toISOString() : null
|
|
477
626
|
}
|
|
478
627
|
|
|
479
628
|
/**
|
|
@@ -539,24 +688,82 @@ async function fetchEvents(domainId: string, instanceIds: string[], fromMs: numb
|
|
|
539
688
|
* 남는 비용을 감추지 않는다: `payload` 파싱은 여전히 메인 스레드 일이고 연결을 나눠도 나뉘지 않는다
|
|
540
689
|
* (실측 평균 1.5초). 그것을 더 줄이려면 폴드가 payload 에서 무엇을 쓰는지 좁혀야 한다 — 별 건이다.
|
|
541
690
|
*/
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
691
|
+
const from = new Date(fromMs - lookbackMs)
|
|
692
|
+
const to = new Date(toMs)
|
|
693
|
+
|
|
694
|
+
/**
|
|
695
|
+
* 한 갈래를 묻는다 — **종류를 정확일치로** 준다. 그것이 이 고침의 요점이다.
|
|
696
|
+
*/
|
|
697
|
+
const readOne = (instanceId: string, eventType: string) =>
|
|
698
|
+
getReadRepository(TwinEvent)
|
|
699
|
+
.createQueryBuilder('e')
|
|
700
|
+
.select(['e.eventType AS "eventType"', 'e.eventTime AS "eventTime"', 'e.revision AS "revision"', 'e.payload AS "payload"'])
|
|
701
|
+
.where('e.domain = :domainId', { domainId })
|
|
702
|
+
.andWhere('e.instanceId = :instanceId', { instanceId })
|
|
703
|
+
.andWhere('e.eventType = :eventType', { eventType })
|
|
704
|
+
.andWhere('e.eventTime BETWEEN :from AND :to', { from, to })
|
|
705
|
+
.orderBy('e.eventTime', 'ASC')
|
|
706
|
+
.addOrderBy('e.revision', 'ASC')
|
|
707
|
+
.limit(MAX_EVENTS)
|
|
708
|
+
.getRawMany<{ eventType: string; eventTime: unknown; revision: unknown; payload: unknown }>()
|
|
709
|
+
|
|
710
|
+
/*
|
|
711
|
+
* ── **종류를 나눠 묻는다** (2026-08-28 실측) ────────────────────────────────
|
|
712
|
+
*
|
|
713
|
+
* 예전에는 `eventType IN (셋)` + 시각 범위 + 시각순이었다. 그 모양에서 sqlite 는 종류 색인을 버리고
|
|
714
|
+
* 시각 색인으로 **구간 전체를 걸으며 행마다 종류를 확인한다.** 승화푸드(저널 3,140만행) 실측:
|
|
715
|
+
*
|
|
716
|
+
* 창 1시간 구간 안 0행 · 쓰는 행 0 40밀리초
|
|
717
|
+
* 창 24시간 구간 안 254,185행 · 쓰는 행 0 3,776밀리초 ← 25만 행을 걸어 0건을 쓴다
|
|
718
|
+
* 창 7일 구간 안 2,070,625행 · 34,143 173밀리초 ← 상한에 일찍 닿아 오히려 빠르다
|
|
719
|
+
*
|
|
720
|
+
* 창을 넓히면 빨라지는 것 자체가 신호였다. 종류를 정확일치로 물으면
|
|
721
|
+
* `ix_twin_event_7 (domain, instance, event_type, event_time)` 이 범위까지 색인에서 준다.
|
|
722
|
+
*
|
|
723
|
+
* 나눌 수를 제한한다 — 갈래마다 상한까지 받아야 정확하므로(§`mergeStreams`) 갈래가 많으면 메모리가
|
|
724
|
+
* 그만큼 든다. 넘으면 예전 한 질의로 간다(느리지만 답은 같다).
|
|
725
|
+
*/
|
|
726
|
+
const pairs = instanceIds.length * BUSINESS_EVENTS.length
|
|
727
|
+
let raw: { eventType: string; eventTime: unknown; revision: unknown; payload: unknown }[]
|
|
728
|
+
let mergedCapped = false
|
|
729
|
+
if (pairs && pairs <= KPI_MAX_SPLIT) {
|
|
730
|
+
const streams = await Promise.all(
|
|
731
|
+
instanceIds.flatMap(instanceId => BUSINESS_EVENTS.map(eventType => readOne(instanceId, eventType)))
|
|
732
|
+
)
|
|
733
|
+
const merged = mergeStreams(streams, MAX_EVENTS, (a, b) => {
|
|
734
|
+
const at = String(a.eventTime ?? '').localeCompare(String(b.eventTime ?? ''))
|
|
735
|
+
if (at !== 0) return at
|
|
736
|
+
return Number(a.revision ?? 0) - Number(b.revision ?? 0)
|
|
737
|
+
})
|
|
738
|
+
if (merged.underfilled) {
|
|
739
|
+
/* 있을 수 없는 일이다 — 갈래가 상한보다 많이 받아 왔다. 조용히 쓰지 않고 말한다. */
|
|
740
|
+
twinWarn('[twin-kpi] a per-type stream returned more than the cap — the merge cannot be trusted; falling back')
|
|
741
|
+
raw = []
|
|
742
|
+
} else {
|
|
743
|
+
raw = merged.items
|
|
744
|
+
mergedCapped = merged.capped
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
raw = await getReadRepository(TwinEvent)
|
|
748
|
+
.createQueryBuilder('e')
|
|
749
|
+
.select(['e.eventType AS "eventType"', 'e.eventTime AS "eventTime"', 'e.revision AS "revision"', 'e.payload AS "payload"'])
|
|
750
|
+
.where('e.domain = :domainId', { domainId })
|
|
751
|
+
.andWhere('e.instanceId IN (:...instanceIds)', { instanceIds })
|
|
752
|
+
.andWhere('e.eventType IN (:...types)', { types: BUSINESS_EVENTS })
|
|
753
|
+
.andWhere('e.eventTime BETWEEN :from AND :to', { from, to })
|
|
754
|
+
.orderBy('e.eventTime', 'ASC')
|
|
755
|
+
.addOrderBy('e.revision', 'ASC')
|
|
756
|
+
.limit(MAX_EVENTS)
|
|
757
|
+
.getRawMany<{ eventType: string; eventTime: unknown; revision: unknown; payload: unknown }>()
|
|
758
|
+
mergedCapped = raw.length >= MAX_EVENTS
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
/* 상한에 정확히 닿으면 **더 있었을 수 있다** — 그 사실을 돌려준다. 알리지 않고 잘라내면 숫자가 알리지 않고
|
|
555
762
|
* 작아지고, 사용자는 그것을 성과 하락으로 읽는다(가장 나쁜 종류의 거짓이다). */
|
|
556
763
|
return {
|
|
557
764
|
/* 폴드는 ISO 문자열 축으로 수행된다(kpi-fold) — 경계에서 한 번만 옮긴다. */
|
|
558
765
|
events: raw.map(r => ({ eventType: r.eventType, eventTime: isoOf(r.eventTime), payload: jsonOf(r.payload) })),
|
|
559
|
-
capped:
|
|
766
|
+
capped: mergedCapped
|
|
560
767
|
}
|
|
561
768
|
}
|
|
562
769
|
|
|
@@ -594,15 +801,37 @@ function jsonOf(v: unknown): any {
|
|
|
594
801
|
/**
|
|
595
802
|
* 집계 대상 트윈 목록.
|
|
596
803
|
*
|
|
597
|
-
* 공간이 주어지면
|
|
804
|
+
* 공간이 주어지면 그 공간의 **운영 목적** 트윈을 모은다 — 벤치 사본은 사용자가 보는 현장이 아니다.
|
|
598
805
|
* 트윈이 지정되면 그것 하나. 둘 다 없으면 무엇을 재야 할지 알 수 없으므로 명시 실패한다.
|
|
806
|
+
*
|
|
807
|
+
* ── **「지금 도는가」로 걸러내지 않는다** (2026-08-28 실측) ──────────────────
|
|
808
|
+
* 여기에 `running` 조건이 있었다. 그래서 멈춘 트윈은 현장으로 물었을 때 대상에서 빠졌고, 화면은
|
|
809
|
+
* 「기록이 없습니다」라고 말했다 — **저널에는 있는데.**
|
|
810
|
+
*
|
|
811
|
+
* twinEventList(instanceId: "ems-plant-1") 1,792건
|
|
812
|
+
* twinEventList(spaceId: "ems-plant-1-site") 0건 ← 같은 트윈, 같은 도메인
|
|
813
|
+
*
|
|
814
|
+
* 규모로 보면 이것은 예외가 아니라 기본값이었다: 등록된 트윈 30개 중 **도는 것이 2개, 멈춘 것이 28개**다.
|
|
815
|
+
* 즉 현장으로 묻는 저널 조회가 대부분의 현장에서 거짓 「없다」를 냈다.
|
|
816
|
+
*
|
|
817
|
+
* 지운 이유는 두 가지다.
|
|
818
|
+
*
|
|
819
|
+
* ① **저널은 기록이고, 프로세스가 지금 도는지는 다른 축이다.** 창 안에 있는 사실은 사실이다.
|
|
820
|
+
* 아래 줄이 트윈을 지정한 경우에 대해 이미 그 원칙을 적어 두었다(「정지·비등록이어도 저널은 남아
|
|
821
|
+
* 있을 수 있다」). 같은 원칙이 현장 경로에만 적용되지 않고 있었다.
|
|
822
|
+
* ② 옛 주석이 대는 이유(「벤치 사본은 사용자가 보는 현장이 아니다」)는 **벤치 제외만** 설명한다.
|
|
823
|
+
* `running` 에는 근거가 적혀 있지 않았다.
|
|
824
|
+
*
|
|
825
|
+
* **바뀌는 것**: 현장 KPI 가 이제 멈춘 트윈의 지난 기록도 센다. 창 안의 사실을 세는 것이 그 지표의
|
|
826
|
+
* 정의이므로 그것이 맞다. 「지금 도는 것만」이 필요한 화면이 생기면 그때 **이름 있는 축**으로 받는다 —
|
|
827
|
+
* 여기에 숨겨 두면 두 물음이 한 칸을 쓴다.
|
|
599
828
|
*/
|
|
600
829
|
async function resolveTargets(input: TwinKpiInput): Promise<{ instanceIds: string[]; restartPolicies: string[] }> {
|
|
601
830
|
if (!input.instanceId && !input.spaceId) throw new Error('either instanceId or spaceId is required')
|
|
602
831
|
const list = await TwinEngine.list(input.domainId)
|
|
603
832
|
const rows = input.instanceId
|
|
604
833
|
? list.filter((x: any) => x?.instanceId === input.instanceId)
|
|
605
|
-
: list.filter((x: any) => x?.
|
|
834
|
+
: list.filter((x: any) => x?.spaceId === input.spaceId && x.purpose !== 'bench' && x.instanceId)
|
|
606
835
|
/* 목록에서 못 찾아도(정지·비등록) 지정된 트윈은 그대로 조회한다 — 저널은 남아 있을 수 있다. */
|
|
607
836
|
const instanceIds = rows.length ? rows.map((x: any) => x.instanceId as string) : input.instanceId ? [input.instanceId] : []
|
|
608
837
|
/* 컬럼이 NOT NULL 이라 'unknown' 으로 메울 일이 없다 — 비면 계약이 깨진 것이지 모르는 상태가 아니다. */
|
|
@@ -655,7 +884,19 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
655
884
|
: 'no operational events in the journal yet — there is nothing to measure (this is NOT "zero throughput").'
|
|
656
885
|
}
|
|
657
886
|
}
|
|
658
|
-
|
|
887
|
+
/*
|
|
888
|
+
* ── **미래를 성과 구간으로 삼지 않는다** (2026-08-30) ──────────────────────
|
|
889
|
+
*
|
|
890
|
+
* 마지막 기록이 미래일 수 있다. 한 원본이 **끝나지 않은 이번 달**의 요금 정보를 청구서로 보냈고,
|
|
891
|
+
* 청구서의 사건 시각은 기간의 끝이라 그 사실이 **내일** 일어난 것으로 적혔다. 그러자 구간이
|
|
892
|
+
* `내일 00:00 → 모레 00:00` 이 되어 그 현장의 화면에서 **어제 발전량이 사라졌다.** 오류는 나지
|
|
893
|
+
* 않았고 화면만 비어 있었다.
|
|
894
|
+
*
|
|
895
|
+
* 커널이 이제 그런 사실을 받지 않는다. 그래도 여기서 막는다 — **이미 적힌 것은 사라지지 않고**,
|
|
896
|
+
* 저널은 지우지 않기 때문이다. 그리고 성과는 지나간 일이다. 아직 오지 않은 구간의 성과라는 것은
|
|
897
|
+
* 없다.
|
|
898
|
+
*/
|
|
899
|
+
toMs = Math.min(Date.parse(latest), Date.now())
|
|
659
900
|
basis = 'latest-event'
|
|
660
901
|
}
|
|
661
902
|
|
|
@@ -677,12 +918,30 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
677
918
|
const feederMap = await resourceFeederMap(input.domainId, instanceIds)
|
|
678
919
|
/* 요금 단가도 축과 무관하게 읽는다 — 금액은 같은 창의 전력량·최대수요에서 나오는 사실이다. */
|
|
679
920
|
const tariffFound = await tariffOf(input.domainId, instanceIds)
|
|
921
|
+
/*
|
|
922
|
+
* ── 요금적용전력은 **청구서가 먼저다** (2026-08-29) ─────────────────────────
|
|
923
|
+
*
|
|
924
|
+
* 그 값은 최근 몇 달의 최고 수요로 **매달 다시 정해진다.** 선언에 박아 두면 매달 사람이 갱신해야
|
|
925
|
+
* 하고, 안 하면 낡은 값으로 기본요금이 조용히 틀린다.
|
|
926
|
+
*
|
|
927
|
+
* 원본이 청구서에 그 값을 실어 매일 보낸다. 창 밖의 청구서는 저널 조회에 걸리지 않으므로
|
|
928
|
+
* **커널 상태에 이어져 있는 마지막 청구서**를 함께 넘긴다(§`EnergyState.lastBill`).
|
|
929
|
+
*/
|
|
930
|
+
const lastBill = latestBillOf(input.domainId, instanceIds)
|
|
931
|
+
const tariffBasis = latestTariffBasisOf(input.domainId, instanceIds)
|
|
932
|
+
/* 이용률의 분모 — 커널이 모델의 선언에서 고른다(설비 합 → 자리 합계, 교류 → 직류). */
|
|
933
|
+
const generationRated = generationRatedOf(input.domainId, instanceIds)
|
|
934
|
+
const generationPrice = latestGenerationPriceOf(input.domainId, instanceIds)
|
|
680
935
|
const kpi = foldKpi(events, { fromMs, toMs }, {
|
|
681
936
|
groupBy: input.groupBy,
|
|
682
937
|
locationArea: areaMap.locationArea,
|
|
683
938
|
resourceFeeder: feederMap.resourceFeeder,
|
|
684
939
|
meterFeeder: feederMap.meterFeeder,
|
|
685
940
|
...(tariffFound.tariff ? { tariff: tariffFound.tariff } : {}),
|
|
941
|
+
...(lastBill ? { lastBill } : {}),
|
|
942
|
+
...(tariffBasis ? { tariffBasis } : {}),
|
|
943
|
+
...(generationRated ? { generationRated } : {}),
|
|
944
|
+
...(generationPrice ? { generationPrice } : {}),
|
|
686
945
|
...shiftCtx,
|
|
687
946
|
groupLimit: input.groupLimit
|
|
688
947
|
})
|
|
@@ -691,14 +950,14 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
691
950
|
*
|
|
692
951
|
* 폴드는 순수하다(로그를 모른다) — 그래서 부르는 쪽이 말한다. 왜 말해야 하나: 이 폴드는 오더의
|
|
693
952
|
* 완료를 **동의어 목록**으로 판정하고, 그것은 원본의 낱말에서 뜻을 추론하는 것이다. 커넥터가
|
|
694
|
-
* 자기 표로 옮기지 않으면 그 낱말이 목록에 없어 **완료가
|
|
953
|
+
* 자기 표로 옮기지 않으면 그 낱말이 목록에 없어 **완료가 알리지 않고 0 으로 세어진다.**
|
|
695
954
|
*
|
|
696
955
|
* 실제로 그랬다: 첫 실 원본의 `FINISHED` 가 목록에 없어 완료 오더가 전부 0 이었고, 사람이 화면을
|
|
697
956
|
* 보고 의심해서야 드러났다(그 커넥터에는 상태 표가 정의돼 **있으면서** 오더 경로에서 쓰이지 않았다).
|
|
698
957
|
* 그때 목록에 낱말을 더한 것은 그 사실을 가린 땜빵이었다.
|
|
699
958
|
*
|
|
700
959
|
* 그러니 이 로그는 「목록을 늘려라」가 아니라 **「그 커넥터가 옮기지 않았다」**를 뜻한다. 화면을 보지
|
|
701
|
-
* 않는 사람도 알아야 하므로 로그에 남긴다 —
|
|
960
|
+
* 않는 사람도 알아야 하므로 로그에 남긴다 — 들어온 것이 없는 0 이 이 시스템에서 가장 비싼 침묵이다.
|
|
702
961
|
*
|
|
703
962
|
* 완료가 하나도 없을 때만 말한다: `picking` 처럼 완료가 아닌 낱말이 섞여 있는 것은 정상이고,
|
|
704
963
|
* 그것까지 매번 경고하면 진짜 신호가 묻힌다.
|
|
@@ -741,7 +1000,7 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
741
1000
|
for (const w of windows) {
|
|
742
1001
|
const { events: pastEvents, capped: pastCapped } = await fetchEvents(input.domainId, instanceIds, w.fromMs, w.toMs, lookbackMs)
|
|
743
1002
|
baseCapped = baseCapped || pastCapped
|
|
744
|
-
/* 축 없이, **같은 교대 정의**로
|
|
1003
|
+
/* 축 없이, **같은 교대 정의**로 처리한다(정의가 다르면 같은 시간대가 아니게 된다). */
|
|
745
1004
|
const past = foldKpi(pastEvents, w, { locationArea: areaMap.locationArea, ...shiftCtx })
|
|
746
1005
|
const values: Partial<Record<TwinMetricId, number | undefined>> = {}
|
|
747
1006
|
for (const metric of Object.keys(TWIN_METRIC) as TwinMetricId[]) values[metric] = TWIN_METRIC[metric].of(past)
|
|
@@ -783,7 +1042,7 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
783
1042
|
}))
|
|
784
1043
|
: undefined
|
|
785
1044
|
|
|
786
|
-
/* 비교 구간 — **같은 길이·같은 규칙**으로 한 번 더
|
|
1045
|
+
/* 비교 구간 — **같은 길이·같은 규칙**으로 한 번 더 계산한다. 기록이 없으면 delta 를 만들지 않는다
|
|
787
1046
|
* (없는 것을 0 으로 그리면 "변화 없음" 이라는 거짓이 된다). */
|
|
788
1047
|
let comparison: TwinKpiOutput['comparison']
|
|
789
1048
|
/* 관점 × 비교 교차 — 축별 delta 와 **사라진 축**(전에 있었고 지금 없는 것). */
|
|
@@ -799,13 +1058,13 @@ export async function computeTwinKpi(input: TwinKpiInput): Promise<TwinKpiOutput
|
|
|
799
1058
|
w.toMs,
|
|
800
1059
|
lookbackMs
|
|
801
1060
|
)
|
|
802
|
-
/* 축이 요청됐으면 비교 구간도 **같은 축**으로
|
|
1061
|
+
/* 축이 요청됐으면 비교 구간도 **같은 축**으로 처리한다 — 교차("구역별로 어제 대비")를 내기 위해.
|
|
803
1062
|
* 상한은 축이 서로 어긋나지 않게 넉넉히 둔다(현재 축이 상한 안에 있는데 비교 축에서 밀려나면
|
|
804
1063
|
* 있는데 없다고 말하게 된다). */
|
|
805
1064
|
const prev = foldKpi(prevEvents, w, {
|
|
806
1065
|
groupBy: input.groupBy,
|
|
807
1066
|
locationArea: areaMap.locationArea,
|
|
808
|
-
...shiftCtx, // 비교 구간도 **같은 교대 정의**로
|
|
1067
|
+
...shiftCtx, // 비교 구간도 **같은 교대 정의**로 처리한다(정의가 다르면 축이 어긋난다)
|
|
809
1068
|
groupLimit: input.groupBy ? 100 : undefined
|
|
810
1069
|
})
|
|
811
1070
|
prevCappedFlag = prevCapped
|
|
@@ -29,7 +29,7 @@ export function registerLiveFeedProbe(probe: LiveFeedProbe): void {
|
|
|
29
29
|
* 피드가 붙어 있나 — 어느 장부든 그렇다고 하면 그렇다.
|
|
30
30
|
*
|
|
31
31
|
* 장부가 터지는 것을 이 판정 전체의 실패로 만들지 않는다: 한 계층이 답하지 못해도 다른 계층의 사실은
|
|
32
|
-
* 그대로 참이다. 다만
|
|
32
|
+
* 그대로 참이다. 다만 **알리지 않고 넘기지 않는다** — 답하지 못한 장부는 로그에 남긴다.
|
|
33
33
|
*/
|
|
34
34
|
export function liveFeedAttached(instanceId: string): boolean {
|
|
35
35
|
for (const probe of probes) {
|