@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
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**
|
|
4
4
|
*
|
|
5
|
-
* 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이
|
|
5
|
+
* 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 오류 없이 사라지는 것을 막는다.
|
|
6
6
|
* 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.
|
|
7
7
|
*
|
|
8
|
-
* ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로
|
|
9
|
-
* 없애려고 무엇이든 하게 되고, 모른다로
|
|
8
|
+
* ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 처리하면 사람이 그 빨간 칸을
|
|
9
|
+
* 없애려고 무엇이든 하게 되고, 모른다로 처리하면 **무엇을 아직 모르는지**가 남는다. 판정
|
|
10
10
|
* (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.
|
|
11
11
|
* ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.
|
|
12
12
|
* 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면
|
|
@@ -42,16 +42,16 @@ exports.PROBE_SEQUENCE = [
|
|
|
42
42
|
const by = (rs, id) => rs.find(r => r.id === id);
|
|
43
43
|
/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */
|
|
44
44
|
/**
|
|
45
|
-
* 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가
|
|
45
|
+
* 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」와 구별되지 않는다.
|
|
46
46
|
* 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.
|
|
47
47
|
*/
|
|
48
48
|
const counted = (r) => r?.applicable === false ? null : r?.ran && r.count !== undefined ? r.count : null;
|
|
49
49
|
/** 돌렸고 셌으면 그 수, 아니면 0. **존재 여부**를 묻는 자리에만 쓴다(모름과 0 을 구별할 필요가 없는 축). */
|
|
50
50
|
const countedOrZero = (r) => counted(r) ?? 0;
|
|
51
51
|
/**
|
|
52
|
-
* 프로브 결과를 **증거**로
|
|
52
|
+
* 프로브 결과를 **증거**로 처리한다.
|
|
53
53
|
*
|
|
54
|
-
*
|
|
54
|
+
* 계산 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로
|
|
55
55
|
* 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.
|
|
56
56
|
*/
|
|
57
57
|
function foldProbeResults(results) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"integration-probes.js","sourceRoot":"","sources":["../../server/engine/integration-probes.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;;AAwFH,4CAiBC;AAQD,sCAUC;AAGD,sCAEC;AAnGD;;;;;GAKG;AACU,QAAA,cAAc,GAAyB;IAClD,EAAE,EAAE,EAAE,SAAS,EAAW,KAAK,EAAE,WAAW,EAAI,MAAM,EAAE,eAAe,EAAU,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9G,EAAE,EAAE,EAAE,UAAU,EAAU,KAAK,EAAE,WAAW,EAAI,MAAM,EAAE,gBAAgB,EAAS,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9G,EAAE,EAAE,EAAE,QAAQ,EAAY,KAAK,EAAE,WAAW,EAAI,MAAM,EAAE,cAAc,EAAW,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9G,EAAE,EAAE,EAAE,iBAAiB,EAAG,KAAK,EAAE,WAAW,EAAI,MAAM,EAAE,sBAAsB,EAAG,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/G,EAAE,EAAE,EAAE,OAAO,EAAa,KAAK,EAAE,WAAW,EAAI,MAAM,EAAE,aAAa,EAAY,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/G,EAAE,EAAE,EAAE,aAAa,EAAO,KAAK,EAAE,MAAM,EAAS,MAAM,EAAE,kBAAkB,EAAO,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9G,EAAE,EAAE,EAAE,cAAc,EAAM,KAAK,EAAE,MAAM,EAAS,MAAM,EAAE,mBAAmB,EAAM,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/G,EAAE,EAAE,EAAE,cAAc,EAAM,KAAK,EAAE,UAAU,EAAK,MAAM,EAAE,oBAAoB,EAAK,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/G,EAAE,EAAE,EAAE,UAAU,EAAU,KAAK,EAAE,UAAU,EAAK,MAAM,EAAE,gBAAgB,EAAS,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE;IAC/G,EAAE,EAAE,EAAE,kBAAkB,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,uBAAuB,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE;IAC9G,EAAE,EAAE,EAAE,gBAAgB,EAAI,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,qBAAqB,EAAI,MAAM,EAAE,IAAI,EAAG,MAAM,EAAE,KAAK,EAAE;CAChH,CAAA;AAwBD,MAAM,EAAE,GAAG,CAAC,EAA0B,EAAE,EAAW,EAAE,EAAE,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAA;AACjF,6DAA6D;AAC7D;;;GAGG;AACH,MAAM,OAAO,GAAG,CAAC,CAA0B,EAAiB,EAAE,CAC5D,CAAC,EAAE,UAAU,KAAK,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAA;AACnF,uEAAuE;AACvE,MAAM,aAAa,GAAG,CAAC,CAA0B,EAAU,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;AAE7E;;;;;GAKG;AACH,SAAgB,gBAAgB,CAAC,OAA+B;IAC9D,OAAO;QACL,aAAa,EAAE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QACnD,SAAS,EAAE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QAC9C,cAAc,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;QACvD,iDAAiD;QACjD,wBAAwB,EAAE,EAAE,CAAC,OAAO,EAAE,iBAAiB,CAAC,EAAE,UAAU,KAAK,KAAK;QAC9E,eAAe,EAAE,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;QAC1D,gBAAgB,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;QACtD,WAAW,EAAE,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,GAAG,KAAK,IAAI,IAAI,EAAE,CAAC,OAAO,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,IAAI;QAC9F,YAAY,EAAE,CAAC,GAAG,EAAE;YAClB,MAAM,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,cAAc,CAAC,CAAA;YACrC,OAAO,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAA;QAC7D,CAAC,CAAC,EAAE;QACJ,iBAAiB,EAAE,EAAE,CAAC,OAAO,EAAE,kBAAkB,CAAC,EAAE,MAAM,KAAK,IAAI;QACnE,wBAAwB,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;KACjE,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,SAAgB,aAAa,CAAC,OAA+B,EAAE,OAAkC,EAAE;IACjG,MAAM,GAAG,GAAc,EAAE,CAAA;IACzB,KAAK,MAAM,IAAI,IAAI,sBAAc,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,SAAQ;QAC9C,MAAM,KAAK,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,CAAC,CAAA;QAClC,IAAI,KAAK,EAAE,UAAU,KAAK,KAAK;YAAE,SAAQ,CAAC,sBAAsB;QAChE,IAAI,CAAC,KAAK,EAAE,GAAG;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QAClC,IAAI,IAAI,CAAC,MAAM,IAAI,KAAK,EAAE,GAAG,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK;YAAE,MAAK;IAChE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,yDAAyD;AACzD,SAAgB,aAAa,CAAC,OAA+B;IAC3D,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,KAAK,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AACxH,CAAC","sourcesContent":["/*\n * 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**\n *\n * 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 조용히 사라지는 것을 막는다.\n * 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.\n *\n * ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 접으면 사람이 그 빨간 칸을\n * 없애려고 무엇이든 하게 되고, 모른다로 접으면 **무엇을 아직 모르는지**가 남는다. 판정\n * (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.\n * ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.\n * 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면\n * 폐루프 칸은 「검증되지 않음」으로 남는다(그게 사실이다).\n *\n * 순수 모듈이다(전송·DB 무의존). 실행기는 이 선언을 읽어 프로브를 돌리고 `ProbeResult[]` 를 낸다.\n * 화면은 그 결과와 `assessStagePath` 의 판정을 함께 보인다 — 초록/빨강과 **그 초록이 무엇을 증명하나**.\n */\n\nimport type { StageEvidence, StageId } from './stage-path.ts'\n\nexport type ProbeId =\n | 'connect' // 붙는다\n | 'discover' // 사이트가 열거된다\n | 'master' // 구조가 들어온다\n | 'master-warnings' // 못 옮긴 값을 세었다\n | 'items' // 자리에 물품이 놓인다(비직렬 수량 포함)\n | 'live-ingest' // 이벤트가 들어온다\n | 'live-removal' // **반출이 관측된다** — 진짜 delta 의 증거\n | 'declarations' // 공정 선언이 실려 있다\n | 'forecast' // 예측이 돈다\n | 'actuate-declared' // 액추에이션 능력이 선언∩구현이다\n | 'actuate-effect' // 명령의 효과가 상관되어 돌아온다 (쓰기)\n\nexport interface ProbeSpec {\n id: ProbeId\n /** 이 프로브가 증거를 대는 단계. */\n stage: StageId\n /** **무엇을 증명하나** — 언어중립 키(사람 말은 표현계층이 렌더). 「무엇을 호출하나」가 아니다. */\n proves: string\n /** 대상 시스템을 바꾸는가. 참이면 사람이 켜야 돈다. */\n writes: boolean\n /** 이 프로브가 실패하면 뒤 프로브를 돌릴 의미가 없다. */\n gating: boolean\n}\n\n/**\n * 시퀀스 — 순서가 곧 방법론이다. 앞이 막히면 뒤는 돌리지 않는다(돌려도 답이 무의미하다).\n *\n * `master-warnings` 를 **별개 프로브**로 둔 이유: 구조가 들어오는 것과 못 옮긴 값을 세는 것은 다른\n * 사실이고, 세지 않은 인제스트는 도크를 랙으로 바꿔 놓고도 성공으로 보인다.\n */\nexport const PROBE_SEQUENCE: readonly ProbeSpec[] = [\n { id: 'connect', stage: 'structure', proves: 'probe.connect', writes: false, gating: true },\n { id: 'discover', stage: 'structure', proves: 'probe.discover', writes: false, gating: true },\n { id: 'master', stage: 'structure', proves: 'probe.master', writes: false, gating: true },\n { id: 'master-warnings', stage: 'structure', proves: 'probe.masterWarnings', writes: false, gating: false },\n { id: 'items', stage: 'structure', proves: 'probe.items', writes: false, gating: false },\n { id: 'live-ingest', stage: 'live', proves: 'probe.liveIngest', writes: false, gating: true },\n { id: 'live-removal', stage: 'live', proves: 'probe.liveRemoval', writes: false, gating: false },\n { id: 'declarations', stage: 'forecast', proves: 'probe.declarations', writes: false, gating: false },\n { id: 'forecast', stage: 'forecast', proves: 'probe.forecast', writes: false, gating: false },\n { id: 'actuate-declared', stage: 'closed-loop', proves: 'probe.actuateDeclared', writes: false, gating: true },\n { id: 'actuate-effect', stage: 'closed-loop', proves: 'probe.actuateEffect', writes: true, gating: false }\n]\n\n/** 프로브 한 건의 결과. `ran: false` = 돌리지 않았다(실패가 아니다). */\nexport interface ProbeResult {\n id: ProbeId\n ran: boolean\n /**\n * 이 대상에 **해당하지 않는다**. 통과도 실패도 미측정도 아닌 네 번째 값이다.\n *\n * 시뮬레이션 트윈에 라이브 프로브를 들이대면 빨간 칸이 생기는데 그건 결함이 아니다 — 시뮬은 원천에서\n * 받는 것이 없다. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로 「못 옮긴 값」도 없다.\n * 이걸 통과로 접으면 없는 능력을 주장하고, 미측정으로 접으면 영원히 배선을 기다린다.\n */\n applicable?: boolean\n /** 돌렸을 때의 판정. 돌리지 않았으면 무의미하다. */\n passed?: boolean\n /** 센 값(자리 수·반출 건수·선언 수 등). 세지 않았으면 없다. */\n count?: number\n /** 선언 적재량 프로브만 — `declared`/`total`. */\n coverage?: { declared: number; total: number }\n /** 왜 실패했나·왜 건너뛰었나. 언어중립 코드 또는 원문 오류. */\n detail?: string\n}\n\nconst by = (rs: readonly ProbeResult[], id: ProbeId) => rs.find(r => r.id === id)\n/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */\n/**\n * 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」로 접힌다.\n * 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.\n */\nconst counted = (r: ProbeResult | undefined): number | null =>\n r?.applicable === false ? null : r?.ran && r.count !== undefined ? r.count : null\n/** 돌렸고 셌으면 그 수, 아니면 0. **존재 여부**를 묻는 자리에만 쓴다(모름과 0 을 구별할 필요가 없는 축). */\nconst countedOrZero = (r: ProbeResult | undefined): number => counted(r) ?? 0\n\n/**\n * 프로브 결과를 **증거**로 접는다.\n *\n * 접는 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로\n * 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.\n */\nexport function foldProbeResults(results: readonly ProbeResult[]): StageEvidence {\n return {\n locationCount: countedOrZero(by(results, 'master')),\n itemCount: countedOrZero(by(results, 'items')),\n masterWarnings: counted(by(results, 'master-warnings')),\n /* 해당 없음은 **미측정이 아니다** — 물을 대상이 아닌 축을 판정에서 뺀다. */\n masterWarningsApplicable: by(results, 'master-warnings')?.applicable !== false,\n liveIngestTotal: countedOrZero(by(results, 'live-ingest')),\n removalsObserved: counted(by(results, 'live-removal')),\n forecastRan: by(results, 'forecast')?.ran === true && by(results, 'forecast')?.passed === true,\n specCoverage: (() => {\n const r = by(results, 'declarations')\n return r?.ran && r.passed && r.coverage ? r.coverage : null\n })(),\n actuateCapability: by(results, 'actuate-declared')?.passed === true,\n correlatedEffectObserved: counted(by(results, 'actuate-effect'))\n }\n}\n\n/**\n * 지금 돌릴 프로브 — 게이팅과 쓰기 동의를 지킨다.\n *\n * 앞의 게이팅 프로브가 실패했으면 **거기서 멈춘다.** 뒤를 돌려 빨간 칸을 늘리는 것은 정보를 주지\n * 않는다(원인은 이미 앞에 있다). 쓰기 프로브는 `allowWrites` 없이는 목록에 들어오지 않는다.\n */\nexport function plannedProbes(results: readonly ProbeResult[], opts: { allowWrites?: boolean } = {}): ProbeId[] {\n const out: ProbeId[] = []\n for (const spec of PROBE_SEQUENCE) {\n if (spec.writes && !opts.allowWrites) continue\n const prior = by(results, spec.id)\n if (prior?.applicable === false) continue // 해당 없는 프로브는 계획하지 않는다\n if (!prior?.ran) out.push(spec.id)\n if (spec.gating && prior?.ran && prior.passed === false) break\n }\n return out\n}\n\n/** 해당 없음으로 판정된 프로브 — 화면이 「왜 회색인가」를 말할 수 있게 사유와 함께 낸다. */\nexport function notApplicable(results: readonly ProbeResult[]): { id: ProbeId; detail?: string }[] {\n return results.filter(r => r.applicable === false).map(r => ({ id: r.id, ...(r.detail ? { detail: r.detail } : {}) }))\n}\n"]}
|
|
1
|
+
{"version":3,"file":"integration-probes.js","sourceRoot":"","sources":["../../server/engine/integration-probes.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 연결 적합성 하네스 — **통합을 시험 스위트처럼 돌린다.**\n *\n * 선언 적합성 하네스 세 벌과 같은 규율을 **연결**에 적용한다: 사실이 오류 없이 사라지는 것을 막는다.\n * 다른 점은 대상이 우리 선언이 아니라 **남의 시스템**이라는 것이고, 그래서 두 규칙이 더 붙는다.\n *\n * ① **돌리지 않은 프로브는 「실패」가 아니라 「모른다」다.** 실패로 처리하면 사람이 그 빨간 칸을\n * 없애려고 무엇이든 하게 되고, 모른다로 처리하면 **무엇을 아직 모르는지**가 남는다. 판정\n * (`assessStagePath`)이 「모른다」를 달성으로 바꾸지 않으므로 이 구분이 끝까지 살아 있다.\n * ② **쓰기 프로브는 명시 동의 없이 돌지 않는다.** 이 스위트는 고객의 운영 시스템을 두드린다.\n * 읽기는 기본으로 돌리고, 남의 공장에 명령을 넣는 프로브는 사람이 켜야 돈다 — 켜지 않으면\n * 폐루프 칸은 「검증되지 않음」으로 남는다(그게 사실이다).\n *\n * 순수 모듈이다(전송·DB 무의존). 실행기는 이 선언을 읽어 프로브를 돌리고 `ProbeResult[]` 를 낸다.\n * 화면은 그 결과와 `assessStagePath` 의 판정을 함께 보인다 — 초록/빨강과 **그 초록이 무엇을 증명하나**.\n */\n\nimport type { StageEvidence, StageId } from './stage-path.ts'\n\nexport type ProbeId =\n | 'connect' // 붙는다\n | 'discover' // 사이트가 열거된다\n | 'master' // 구조가 들어온다\n | 'master-warnings' // 못 옮긴 값을 세었다\n | 'items' // 자리에 물품이 놓인다(비직렬 수량 포함)\n | 'live-ingest' // 이벤트가 들어온다\n | 'live-removal' // **반출이 관측된다** — 진짜 delta 의 증거\n | 'declarations' // 공정 선언이 실려 있다\n | 'forecast' // 예측이 돈다\n | 'actuate-declared' // 액추에이션 능력이 선언∩구현이다\n | 'actuate-effect' // 명령의 효과가 상관되어 돌아온다 (쓰기)\n\nexport interface ProbeSpec {\n id: ProbeId\n /** 이 프로브가 증거를 대는 단계. */\n stage: StageId\n /** **무엇을 증명하나** — 언어중립 키(사람 말은 표현계층이 렌더). 「무엇을 호출하나」가 아니다. */\n proves: string\n /** 대상 시스템을 바꾸는가. 참이면 사람이 켜야 돈다. */\n writes: boolean\n /** 이 프로브가 실패하면 뒤 프로브를 돌릴 의미가 없다. */\n gating: boolean\n}\n\n/**\n * 시퀀스 — 순서가 곧 방법론이다. 앞이 막히면 뒤는 돌리지 않는다(돌려도 답이 무의미하다).\n *\n * `master-warnings` 를 **별개 프로브**로 둔 이유: 구조가 들어오는 것과 못 옮긴 값을 세는 것은 다른\n * 사실이고, 세지 않은 인제스트는 도크를 랙으로 바꿔 놓고도 성공으로 보인다.\n */\nexport const PROBE_SEQUENCE: readonly ProbeSpec[] = [\n { id: 'connect', stage: 'structure', proves: 'probe.connect', writes: false, gating: true },\n { id: 'discover', stage: 'structure', proves: 'probe.discover', writes: false, gating: true },\n { id: 'master', stage: 'structure', proves: 'probe.master', writes: false, gating: true },\n { id: 'master-warnings', stage: 'structure', proves: 'probe.masterWarnings', writes: false, gating: false },\n { id: 'items', stage: 'structure', proves: 'probe.items', writes: false, gating: false },\n { id: 'live-ingest', stage: 'live', proves: 'probe.liveIngest', writes: false, gating: true },\n { id: 'live-removal', stage: 'live', proves: 'probe.liveRemoval', writes: false, gating: false },\n { id: 'declarations', stage: 'forecast', proves: 'probe.declarations', writes: false, gating: false },\n { id: 'forecast', stage: 'forecast', proves: 'probe.forecast', writes: false, gating: false },\n { id: 'actuate-declared', stage: 'closed-loop', proves: 'probe.actuateDeclared', writes: false, gating: true },\n { id: 'actuate-effect', stage: 'closed-loop', proves: 'probe.actuateEffect', writes: true, gating: false }\n]\n\n/** 프로브 한 건의 결과. `ran: false` = 돌리지 않았다(실패가 아니다). */\nexport interface ProbeResult {\n id: ProbeId\n ran: boolean\n /**\n * 이 대상에 **해당하지 않는다**. 통과도 실패도 미측정도 아닌 네 번째 값이다.\n *\n * 시뮬레이션 트윈에 라이브 프로브를 들이대면 빨간 칸이 생기는데 그건 결함이 아니다 — 시뮬은 원천에서\n * 받는 것이 없다. 레퍼런스 없이 템플릿으로 세운 트윈에는 옮겨 온 마스터가 없으므로 「못 옮긴 값」도 없다.\n * 이걸 통과로 처리하면 없는 능력을 주장하고, 미측정으로 처리하면 영원히 배선을 기다린다.\n */\n applicable?: boolean\n /** 돌렸을 때의 판정. 돌리지 않았으면 무의미하다. */\n passed?: boolean\n /** 센 값(자리 수·반출 건수·선언 수 등). 세지 않았으면 없다. */\n count?: number\n /** 선언 적재량 프로브만 — `declared`/`total`. */\n coverage?: { declared: number; total: number }\n /** 왜 실패했나·왜 건너뛰었나. 언어중립 코드 또는 원문 오류. */\n detail?: string\n}\n\nconst by = (rs: readonly ProbeResult[], id: ProbeId) => rs.find(r => r.id === id)\n/** 돌렸고 셌으면 그 수, 아니면 `null`(모른다). 실패한 프로브의 수를 0 으로 읽지 않는다. */\n/**\n * 센 값은 **판정과 무관한 사실**이다 — `passed` 를 요구하면 「세어서 0 을 얻었다」가 「모른다」와 구별되지 않는다.\n * 그 구분이 이 하네스의 전부이므로 여기서 잃으면 나머지가 무의미하다. 해당 없음만 `null` 이다.\n */\nconst counted = (r: ProbeResult | undefined): number | null =>\n r?.applicable === false ? null : r?.ran && r.count !== undefined ? r.count : null\n/** 돌렸고 셌으면 그 수, 아니면 0. **존재 여부**를 묻는 자리에만 쓴다(모름과 0 을 구별할 필요가 없는 축). */\nconst countedOrZero = (r: ProbeResult | undefined): number => counted(r) ?? 0\n\n/**\n * 프로브 결과를 **증거**로 처리한다.\n *\n * 계산 규칙의 정수: **돌리지 않은 프로브는 `null`** 로 간다. 그러면 판정이 그 단계를 달성으로\n * 만들지 않고 `unmeasured` 로 말한다 — 스위트를 절반만 돌리고 초록을 주장할 길이 없다.\n */\nexport function foldProbeResults(results: readonly ProbeResult[]): StageEvidence {\n return {\n locationCount: countedOrZero(by(results, 'master')),\n itemCount: countedOrZero(by(results, 'items')),\n masterWarnings: counted(by(results, 'master-warnings')),\n /* 해당 없음은 **미측정이 아니다** — 물을 대상이 아닌 축을 판정에서 뺀다. */\n masterWarningsApplicable: by(results, 'master-warnings')?.applicable !== false,\n liveIngestTotal: countedOrZero(by(results, 'live-ingest')),\n removalsObserved: counted(by(results, 'live-removal')),\n forecastRan: by(results, 'forecast')?.ran === true && by(results, 'forecast')?.passed === true,\n specCoverage: (() => {\n const r = by(results, 'declarations')\n return r?.ran && r.passed && r.coverage ? r.coverage : null\n })(),\n actuateCapability: by(results, 'actuate-declared')?.passed === true,\n correlatedEffectObserved: counted(by(results, 'actuate-effect'))\n }\n}\n\n/**\n * 지금 돌릴 프로브 — 게이팅과 쓰기 동의를 지킨다.\n *\n * 앞의 게이팅 프로브가 실패했으면 **거기서 멈춘다.** 뒤를 돌려 빨간 칸을 늘리는 것은 정보를 주지\n * 않는다(원인은 이미 앞에 있다). 쓰기 프로브는 `allowWrites` 없이는 목록에 들어오지 않는다.\n */\nexport function plannedProbes(results: readonly ProbeResult[], opts: { allowWrites?: boolean } = {}): ProbeId[] {\n const out: ProbeId[] = []\n for (const spec of PROBE_SEQUENCE) {\n if (spec.writes && !opts.allowWrites) continue\n const prior = by(results, spec.id)\n if (prior?.applicable === false) continue // 해당 없는 프로브는 계획하지 않는다\n if (!prior?.ran) out.push(spec.id)\n if (spec.gating && prior?.ran && prior.passed === false) break\n }\n return out\n}\n\n/** 해당 없음으로 판정된 프로브 — 화면이 「왜 회색인가」를 말할 수 있게 사유와 함께 낸다. */\nexport function notApplicable(results: readonly ProbeResult[]): { id: ProbeId; detail?: string }[] {\n return results.filter(r => r.applicable === false).map(r => ({ id: r.id, ...(r.detail ? { detail: r.detail } : {}) }))\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* "어제보다 나아졌다." **어제도 나빴으면 그 말은 쓸모가 없다.** 목표(`kpi-target`)가 그 한계를 한쪽에서
|
|
8
8
|
* 풀지만, 목표를 아직 걸지 않은 현장은 여전히 자기 숫자가 평소와 같은지도 알 수 없다.
|
|
9
9
|
*
|
|
10
|
-
* 최근 며칠의 **같은 시간대**를 함께
|
|
10
|
+
* 최근 며칠의 **같은 시간대**를 함께 계산하면 목표 없이도 말할 수 있다 — "오늘 이 시간대는 지난 7일 중
|
|
11
11
|
* 가장 낮다." 그것이 이 파일이 만드는 것이다: 창 여러 개의 **분포**와 그 안에서 현재의 **자리**.
|
|
12
12
|
*
|
|
13
13
|
* ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────
|
|
@@ -19,12 +19,12 @@
|
|
|
19
19
|
* 퍼센트 임계를 두면 그 숫자가 곧 숨은 목표가 된다.
|
|
20
20
|
*
|
|
21
21
|
* ── 정직성 규율 (이 도메인의 핵심) ──────────────────────────────────────────
|
|
22
|
-
* ① **결측 창을
|
|
22
|
+
* ① **결측 창을 알리지 않고 빼지 않는다.** 요청한 창 전부를 `measured` 와 함께 돌려주고, 분포는 측정된
|
|
23
23
|
* 것만으로 만든다. 빼고 말하면 "지난 7일" 이 실제로는 3일이었다는 것을 아무도 모른다.
|
|
24
24
|
* ② **표본이 둘 미만이면 자리를 말하지 않는다.** 하나를 분포라 부를 수 없다.
|
|
25
25
|
* ③ **현재 값이 없으면 자리를 말하지 않는다.** 결측을 0 으로 읽으면 시간 지표는 "낮을수록 좋다" 라서
|
|
26
26
|
* **일이 없던 구간이 최고 성적**으로 보고된다(목표 판정에서 이미 겪은 결함이다).
|
|
27
|
-
* ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 —
|
|
27
|
+
* ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 — 알리지 않고 빈 값을 주면 화면이 "분포 없음" 을 "평범함" 으로
|
|
28
28
|
* 그린다.
|
|
29
29
|
*/
|
|
30
30
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -90,7 +90,7 @@ function summarizeBaseline(specs, current, inputs) {
|
|
|
90
90
|
measuredCount: values.length,
|
|
91
91
|
...(sorted.length ? { min: sorted[0], median: medianOf(sorted), max: sorted[sorted.length - 1] } : {})
|
|
92
92
|
};
|
|
93
|
-
/* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(
|
|
93
|
+
/* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(들어온 것이 없는 빈 값이 "평범함" 으로 읽힌다). */
|
|
94
94
|
if (!hasCurrent)
|
|
95
95
|
row.noPosition = 'no-current';
|
|
96
96
|
else if (sorted.length < 2)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kpi-baseline.js","sourceRoot":"","sources":["../../server/engine/kpi-baseline.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 여러 구간 동시 비교 — **\"어제보다 나아졌다\" 를 넘어선다.** 순수.\n *\n * ── 왜 필요한가 (성과 맵 ③) ─────────────────────────────────────────────────\n * 지금 비교 축은 **한 구간**만 본다(직전 구간 또는 하루 전). 그래서 판단이 딱 한 문장에서 멈춘다:\n * \"어제보다 나아졌다.\" **어제도 나빴으면 그 말은 쓸모가 없다.** 목표(`kpi-target`)가 그 한계를 한쪽에서\n * 풀지만, 목표를 아직 걸지 않은 현장은 여전히 자기 숫자가 평소와 같은지도 알 수 없다.\n *\n * 최근 며칠의 **같은 시간대**를 함께 접으면 목표 없이도 말할 수 있다 — \"오늘 이 시간대는 지난 7일 중\n * 가장 낮다.\" 그것이 이 파일이 만드는 것이다: 창 여러 개의 **분포**와 그 안에서 현재의 **자리**.\n *\n * ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────\n * · **지표 어휘를 모른다.** 이름·방향·값 꺼내는 법은 `kpi-target.ts` 의 `TWIN_METRIC` 이 소유한다. 여기는\n * **이미 꺼낸 값**만 받는다 — 어휘가 두 곳에 생기면 그 사이에 매핑 표가 자라고 그것이 곧 방언이다.\n * (덤으로: 이 모듈은 런타임 import 가 없어 테스트가 소스를 그대로 든다.)\n * · **DB·조회를 모른다.** 창 목록만 계산하고, 그 창을 읽는 것은 호출부의 일이다.\n * · **좋고 나쁨의 임계를 발명하지 않는다.** \"평소보다 나쁘다\" 는 중앙값과의 비교로만 말한다 — 임의의\n * 퍼센트 임계를 두면 그 숫자가 곧 숨은 목표가 된다.\n *\n * ── 정직성 규율 (이 도메인의 핵심) ──────────────────────────────────────────\n * ① **결측 창을 조용히 빼지 않는다.** 요청한 창 전부를 `measured` 와 함께 돌려주고, 분포는 측정된\n * 것만으로 만든다. 빼고 말하면 \"지난 7일\" 이 실제로는 3일이었다는 것을 아무도 모른다.\n * ② **표본이 둘 미만이면 자리를 말하지 않는다.** 하나를 분포라 부를 수 없다.\n * ③ **현재 값이 없으면 자리를 말하지 않는다.** 결측을 0 으로 읽으면 시간 지표는 \"낮을수록 좋다\" 라서\n * **일이 없던 구간이 최고 성적**으로 보고된다(목표 판정에서 이미 겪은 결함이다).\n * ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 — 조용히 빈 값을 주면 화면이 \"분포 없음\" 을 \"평범함\" 으로\n * 그린다.\n */\n\n/** 창 하나 — 폴드의 창과 같은 모양(ms). */\nexport interface BaselineWindow {\n fromMs: number\n toMs: number\n}\n\n/** 며칠 전 같은 시간대인가, 몇 주 전 같은 요일·시간대인가. */\nexport type BaselineStride = 'day' | 'week'\n\n/** 한 번에 볼 수 있는 과거 구간 수 — 창마다 조회가 한 번 더 붙으므로 상한을 둔다. */\nexport const MAX_BASELINE_PERIODS = 14\n\nconst MS_DAY = 24 * 60 * 60_000\n\n/**\n * 비교할 과거 창들 — 현재 창을 **같은 길이로** 하루(또는 한 주)씩 뒤로 옮긴다.\n *\n * 가까운 것부터 준다(어제 → 그제 → …). 화면이 시간 순서를 다시 정하지 않아도 되게.\n *\n * **한계를 밝힌다**: 옮기는 것은 ms 산술이다. 트윈 공간은 고정 오프셋(`utcOffsetMinutes`)을 선언하므로\n * 현재 모델에서는 \"같은 시간대\" 가 정확하다. 일광절약시간을 쓰는 시간대를 나중에 지원하면 전환이 있는\n * 주의 하루가 한 시간 밀린다 — 그때는 오프셋 기준 캘린더 산술로 바꿔야 한다(지금 지어내지 않는다).\n */\nexport function baselineWindows(\n fromMs: number,\n toMs: number,\n periods: number,\n stride: BaselineStride = 'day'\n): BaselineWindow[] {\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) return []\n const count = Math.max(0, Math.min(MAX_BASELINE_PERIODS, Math.floor(periods)))\n const step = stride === 'week' ? 7 * MS_DAY : MS_DAY\n\n return Array.from({ length: count }, (_, i) => ({\n fromMs: fromMs - step * (i + 1),\n toMs: toMs - step * (i + 1)\n }))\n}\n\n/** 지표의 성질 — 이름과 값 꺼내기는 호출부(`TWIN_METRIC`)가 소유한다. */\nexport interface MetricSpecLike {\n direction: 'higher' | 'lower'\n unit: string\n}\n\n/** 과거 창 하나에서 꺼낸 값들 — 그 창에 기록이 없으면 값이 `undefined` 다. */\nexport interface BaselineSampleInput<M extends string> {\n fromMs: number\n toMs: number\n values: Partial<Record<M, number | undefined>>\n}\n\n/** 과거 창 하나의 결과 — **결측도 남긴다**(빼면 \"지난 7일\" 이 거짓이 된다). */\nexport interface BaselineSample {\n fromMs: number\n toMs: number\n measured: boolean\n value?: number\n}\n\n/** 왜 자리를 말하지 않는지. */\nexport type BaselineNoPosition = 'no-current' | 'too-few-samples'\n\n/**\n * 분포 안에서 현재의 자리 — **방향을 적용한 읽기.**\n *\n * 임의의 임계가 없다: 표본 전부보다 좋으면 `best`, 전부보다 나쁘면 `worst`, 그 사이는 중앙값을 기준으로\n * 구분한다. `typical` 은 중앙값과 **같을 때**만이다.\n */\nexport type BaselineStanding = 'best' | 'better-than-typical' | 'typical' | 'worse-than-typical' | 'worst'\n\nexport interface MetricBaseline<M extends string = string> {\n metric: M\n direction: 'higher' | 'lower'\n unit: string\n /** 지금 창의 값 — 없으면 `undefined`(0 이 아니다). */\n current?: number\n /** 요청한 과거 창 전부(결측 포함, 가까운 것부터). */\n samples: BaselineSample[]\n /** 요청한 창 수 / 그중 실제로 측정된 수 — 둘을 함께 봐야 \"지난 7일\" 을 믿을 수 있다. */\n requested: number\n measuredCount: number\n /** 측정된 표본만의 요약. 표본이 없으면 전부 `undefined`. */\n min?: number\n median?: number\n max?: number\n /** 측정 표본을 오름차순으로 놓았을 때 현재 값이 몇 번째인가(1 = 가장 작음) — **방향 없는 사실**. */\n rank?: number\n /** 그 순위를 셀 때의 표본 수(+현재 1). 순위만 보면 표본이 둘이었는지 열이었는지 알 수 없다. */\n rankOf?: number\n /** 방향을 적용한 읽기. 말할 수 없으면 없다. */\n standing?: BaselineStanding\n /** 자리를 말하지 않은 이유. */\n noPosition?: BaselineNoPosition\n}\n\n/** 중앙값 — 짝수 개면 가운데 둘의 평균(폴드의 분위수 규약과 달리 여기 표본은 아주 작다). */\nfunction medianOf(sorted: readonly number[]): number | undefined {\n if (!sorted.length) return undefined\n const mid = sorted.length >> 1\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2\n}\n\n/**\n * 지표별로 분포와 자리를 만든다.\n *\n * @param specs 지표 성질(방향·단위). 열거된 지표만 낸다 — 모르는 이름을 짐작하지 않는다.\n * @param current 지금 창에서 꺼낸 값들(결측은 `undefined`).\n * @param inputs 과거 창들에서 꺼낸 값들(가까운 것부터).\n */\nexport function summarizeBaseline<M extends string>(\n specs: Readonly<Record<M, MetricSpecLike>>,\n current: Partial<Record<M, number | undefined>>,\n inputs: readonly BaselineSampleInput<M>[]\n): MetricBaseline<M>[] {\n const out: MetricBaseline<M>[] = []\n\n for (const metric of Object.keys(specs) as M[]) {\n const spec = specs[metric]\n const samples: BaselineSample[] = inputs.map(s => {\n const v = s.values[metric]\n const measured = typeof v === 'number' && Number.isFinite(v)\n return measured ? { fromMs: s.fromMs, toMs: s.toMs, measured: true, value: v as number } : { fromMs: s.fromMs, toMs: s.toMs, measured: false }\n })\n\n const values = samples.filter(s => s.measured).map(s => s.value as number)\n const sorted = [...values].sort((a, b) => a - b)\n const cur = current[metric]\n const hasCurrent = typeof cur === 'number' && Number.isFinite(cur)\n\n const row: MetricBaseline<M> = {\n metric,\n direction: spec.direction,\n unit: spec.unit,\n ...(hasCurrent ? { current: cur as number } : {}),\n samples,\n requested: inputs.length,\n measuredCount: values.length,\n ...(sorted.length ? { min: sorted[0], median: medianOf(sorted), max: sorted[sorted.length - 1] } : {})\n }\n\n /* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(조용한 빈 값이 \"평범함\" 으로 읽힌다). */\n if (!hasCurrent) row.noPosition = 'no-current'\n else if (sorted.length < 2) row.noPosition = 'too-few-samples'\n else {\n const value = cur as number\n const median = medianOf(sorted) as number\n /* 오름차순에서 몇 번째인가 — 같은 값은 뒤에 놓는다(동률이면 \"더 큰 쪽\" 으로 세지 않는다). */\n row.rank = sorted.filter(v => v < value).length + 1\n row.rankOf = sorted.length + 1\n\n const higherIsBetter = spec.direction === 'higher'\n const beatsAll = higherIsBetter ? value > sorted[sorted.length - 1] : value < sorted[0]\n const losesToAll = higherIsBetter ? value < sorted[0] : value > sorted[sorted.length - 1]\n /* 중앙값보다 큰 것이 \"좋은\" 쪽인가 — 방향을 여기 한 번만 적용한다. */\n const aboveMedianIsBetter = higherIsBetter === value > median\n\n row.standing = beatsAll\n ? 'best'\n : losesToAll\n ? 'worst'\n : value === median\n ? 'typical'\n : aboveMedianIsBetter\n ? 'better-than-typical'\n : 'worse-than-typical'\n }\n\n out.push(row)\n }\n\n return out\n}\n"]}
|
|
1
|
+
{"version":3,"file":"kpi-baseline.js","sourceRoot":"","sources":["../../server/engine/kpi-baseline.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 여러 구간 동시 비교 — **\"어제보다 나아졌다\" 를 넘어선다.** 순수.\n *\n * ── 왜 필요한가 (성과 맵 ③) ─────────────────────────────────────────────────\n * 지금 비교 축은 **한 구간**만 본다(직전 구간 또는 하루 전). 그래서 판단이 딱 한 문장에서 멈춘다:\n * \"어제보다 나아졌다.\" **어제도 나빴으면 그 말은 쓸모가 없다.** 목표(`kpi-target`)가 그 한계를 한쪽에서\n * 풀지만, 목표를 아직 걸지 않은 현장은 여전히 자기 숫자가 평소와 같은지도 알 수 없다.\n *\n * 최근 며칠의 **같은 시간대**를 함께 계산하면 목표 없이도 말할 수 있다 — \"오늘 이 시간대는 지난 7일 중\n * 가장 낮다.\" 그것이 이 파일이 만드는 것이다: 창 여러 개의 **분포**와 그 안에서 현재의 **자리**.\n *\n * ── 이 파일이 하지 않는 것 ──────────────────────────────────────────────────\n * · **지표 어휘를 모른다.** 이름·방향·값 꺼내는 법은 `kpi-target.ts` 의 `TWIN_METRIC` 이 소유한다. 여기는\n * **이미 꺼낸 값**만 받는다 — 어휘가 두 곳에 생기면 그 사이에 매핑 표가 자라고 그것이 곧 방언이다.\n * (덤으로: 이 모듈은 런타임 import 가 없어 테스트가 소스를 그대로 든다.)\n * · **DB·조회를 모른다.** 창 목록만 계산하고, 그 창을 읽는 것은 호출부의 일이다.\n * · **좋고 나쁨의 임계를 발명하지 않는다.** \"평소보다 나쁘다\" 는 중앙값과의 비교로만 말한다 — 임의의\n * 퍼센트 임계를 두면 그 숫자가 곧 숨은 목표가 된다.\n *\n * ── 정직성 규율 (이 도메인의 핵심) ──────────────────────────────────────────\n * ① **결측 창을 알리지 않고 빼지 않는다.** 요청한 창 전부를 `measured` 와 함께 돌려주고, 분포는 측정된\n * 것만으로 만든다. 빼고 말하면 \"지난 7일\" 이 실제로는 3일이었다는 것을 아무도 모른다.\n * ② **표본이 둘 미만이면 자리를 말하지 않는다.** 하나를 분포라 부를 수 없다.\n * ③ **현재 값이 없으면 자리를 말하지 않는다.** 결측을 0 으로 읽으면 시간 지표는 \"낮을수록 좋다\" 라서\n * **일이 없던 구간이 최고 성적**으로 보고된다(목표 판정에서 이미 겪은 결함이다).\n * ④ 왜 말하지 않는지를 `reason` 으로 밝힌다 — 알리지 않고 빈 값을 주면 화면이 \"분포 없음\" 을 \"평범함\" 으로\n * 그린다.\n */\n\n/** 창 하나 — 폴드의 창과 같은 모양(ms). */\nexport interface BaselineWindow {\n fromMs: number\n toMs: number\n}\n\n/** 며칠 전 같은 시간대인가, 몇 주 전 같은 요일·시간대인가. */\nexport type BaselineStride = 'day' | 'week'\n\n/** 한 번에 볼 수 있는 과거 구간 수 — 창마다 조회가 한 번 더 붙으므로 상한을 둔다. */\nexport const MAX_BASELINE_PERIODS = 14\n\nconst MS_DAY = 24 * 60 * 60_000\n\n/**\n * 비교할 과거 창들 — 현재 창을 **같은 길이로** 하루(또는 한 주)씩 뒤로 옮긴다.\n *\n * 가까운 것부터 준다(어제 → 그제 → …). 화면이 시간 순서를 다시 정하지 않아도 되게.\n *\n * **한계를 밝힌다**: 옮기는 것은 ms 산술이다. 트윈 공간은 고정 오프셋(`utcOffsetMinutes`)을 선언하므로\n * 현재 모델에서는 \"같은 시간대\" 가 정확하다. 일광절약시간을 쓰는 시간대를 나중에 지원하면 전환이 있는\n * 주의 하루가 한 시간 밀린다 — 그때는 오프셋 기준 캘린더 산술로 바꿔야 한다(지금 지어내지 않는다).\n */\nexport function baselineWindows(\n fromMs: number,\n toMs: number,\n periods: number,\n stride: BaselineStride = 'day'\n): BaselineWindow[] {\n if (!Number.isFinite(fromMs) || !Number.isFinite(toMs) || toMs <= fromMs) return []\n const count = Math.max(0, Math.min(MAX_BASELINE_PERIODS, Math.floor(periods)))\n const step = stride === 'week' ? 7 * MS_DAY : MS_DAY\n\n return Array.from({ length: count }, (_, i) => ({\n fromMs: fromMs - step * (i + 1),\n toMs: toMs - step * (i + 1)\n }))\n}\n\n/** 지표의 성질 — 이름과 값 꺼내기는 호출부(`TWIN_METRIC`)가 소유한다. */\nexport interface MetricSpecLike {\n direction: 'higher' | 'lower'\n unit: string\n}\n\n/** 과거 창 하나에서 꺼낸 값들 — 그 창에 기록이 없으면 값이 `undefined` 다. */\nexport interface BaselineSampleInput<M extends string> {\n fromMs: number\n toMs: number\n values: Partial<Record<M, number | undefined>>\n}\n\n/** 과거 창 하나의 결과 — **결측도 남긴다**(빼면 \"지난 7일\" 이 거짓이 된다). */\nexport interface BaselineSample {\n fromMs: number\n toMs: number\n measured: boolean\n value?: number\n}\n\n/** 왜 자리를 말하지 않는지. */\nexport type BaselineNoPosition = 'no-current' | 'too-few-samples'\n\n/**\n * 분포 안에서 현재의 자리 — **방향을 적용한 읽기.**\n *\n * 임의의 임계가 없다: 표본 전부보다 좋으면 `best`, 전부보다 나쁘면 `worst`, 그 사이는 중앙값을 기준으로\n * 구분한다. `typical` 은 중앙값과 **같을 때**만이다.\n */\nexport type BaselineStanding = 'best' | 'better-than-typical' | 'typical' | 'worse-than-typical' | 'worst'\n\nexport interface MetricBaseline<M extends string = string> {\n metric: M\n direction: 'higher' | 'lower'\n unit: string\n /** 지금 창의 값 — 없으면 `undefined`(0 이 아니다). */\n current?: number\n /** 요청한 과거 창 전부(결측 포함, 가까운 것부터). */\n samples: BaselineSample[]\n /** 요청한 창 수 / 그중 실제로 측정된 수 — 둘을 함께 봐야 \"지난 7일\" 을 믿을 수 있다. */\n requested: number\n measuredCount: number\n /** 측정된 표본만의 요약. 표본이 없으면 전부 `undefined`. */\n min?: number\n median?: number\n max?: number\n /** 측정 표본을 오름차순으로 놓았을 때 현재 값이 몇 번째인가(1 = 가장 작음) — **방향 없는 사실**. */\n rank?: number\n /** 그 순위를 셀 때의 표본 수(+현재 1). 순위만 보면 표본이 둘이었는지 열이었는지 알 수 없다. */\n rankOf?: number\n /** 방향을 적용한 읽기. 말할 수 없으면 없다. */\n standing?: BaselineStanding\n /** 자리를 말하지 않은 이유. */\n noPosition?: BaselineNoPosition\n}\n\n/** 중앙값 — 짝수 개면 가운데 둘의 평균(폴드의 분위수 규약과 달리 여기 표본은 아주 작다). */\nfunction medianOf(sorted: readonly number[]): number | undefined {\n if (!sorted.length) return undefined\n const mid = sorted.length >> 1\n return sorted.length % 2 ? sorted[mid] : (sorted[mid - 1] + sorted[mid]) / 2\n}\n\n/**\n * 지표별로 분포와 자리를 만든다.\n *\n * @param specs 지표 성질(방향·단위). 열거된 지표만 낸다 — 모르는 이름을 짐작하지 않는다.\n * @param current 지금 창에서 꺼낸 값들(결측은 `undefined`).\n * @param inputs 과거 창들에서 꺼낸 값들(가까운 것부터).\n */\nexport function summarizeBaseline<M extends string>(\n specs: Readonly<Record<M, MetricSpecLike>>,\n current: Partial<Record<M, number | undefined>>,\n inputs: readonly BaselineSampleInput<M>[]\n): MetricBaseline<M>[] {\n const out: MetricBaseline<M>[] = []\n\n for (const metric of Object.keys(specs) as M[]) {\n const spec = specs[metric]\n const samples: BaselineSample[] = inputs.map(s => {\n const v = s.values[metric]\n const measured = typeof v === 'number' && Number.isFinite(v)\n return measured ? { fromMs: s.fromMs, toMs: s.toMs, measured: true, value: v as number } : { fromMs: s.fromMs, toMs: s.toMs, measured: false }\n })\n\n const values = samples.filter(s => s.measured).map(s => s.value as number)\n const sorted = [...values].sort((a, b) => a - b)\n const cur = current[metric]\n const hasCurrent = typeof cur === 'number' && Number.isFinite(cur)\n\n const row: MetricBaseline<M> = {\n metric,\n direction: spec.direction,\n unit: spec.unit,\n ...(hasCurrent ? { current: cur as number } : {}),\n samples,\n requested: inputs.length,\n measuredCount: values.length,\n ...(sorted.length ? { min: sorted[0], median: medianOf(sorted), max: sorted[sorted.length - 1] } : {})\n }\n\n /* 자리를 말할 수 없는 두 경우 — 이유를 밝힌다(들어온 것이 없는 빈 값이 \"평범함\" 으로 읽힌다). */\n if (!hasCurrent) row.noPosition = 'no-current'\n else if (sorted.length < 2) row.noPosition = 'too-few-samples'\n else {\n const value = cur as number\n const median = medianOf(sorted) as number\n /* 오름차순에서 몇 번째인가 — 같은 값은 뒤에 놓는다(동률이면 \"더 큰 쪽\" 으로 세지 않는다). */\n row.rank = sorted.filter(v => v < value).length + 1\n row.rankOf = sorted.length + 1\n\n const higherIsBetter = spec.direction === 'higher'\n const beatsAll = higherIsBetter ? value > sorted[sorted.length - 1] : value < sorted[0]\n const losesToAll = higherIsBetter ? value < sorted[0] : value > sorted[sorted.length - 1]\n /* 중앙값보다 큰 것이 \"좋은\" 쪽인가 — 방향을 여기 한 번만 적용한다. */\n const aboveMedianIsBetter = higherIsBetter === value > median\n\n row.standing = beatsAll\n ? 'best'\n : losesToAll\n ? 'worst'\n : value === median\n ? 'typical'\n : aboveMedianIsBetter\n ? 'better-than-typical'\n : 'worse-than-typical'\n }\n\n out.push(row)\n }\n\n return out\n}\n"]}
|
|
@@ -2,7 +2,7 @@ import { type ElectricityCost, type TariffDeclaration, type WorkCalendarEntry }
|
|
|
2
2
|
/** 저널 한 줄 — 필요한 것만(엔티티·typeorm 비의존). */
|
|
3
3
|
export interface KpiEvent {
|
|
4
4
|
eventType?: string;
|
|
5
|
-
/** ISO 시각. 파싱 불가하면 그 줄은 버린다(
|
|
5
|
+
/** ISO 시각. 파싱 불가하면 그 줄은 버린다(알리지 않고 0 으로 세지 않는다). */
|
|
6
6
|
eventTime?: string;
|
|
7
7
|
payload?: any;
|
|
8
8
|
}
|
|
@@ -15,7 +15,7 @@ export interface DurationStats {
|
|
|
15
15
|
/** 짝이 맞아 계산된 건수. */
|
|
16
16
|
count: number;
|
|
17
17
|
/**
|
|
18
|
-
* 하위 꼬리 — **분포를 양쪽에서
|
|
18
|
+
* 하위 꼬리 — **분포를 양쪽에서 계산하지하기 위해** 낸다.
|
|
19
19
|
* p50·p90 만 있으면 아래쪽 퍼짐을 위쪽에서 거울처럼 베껴 쓸 수밖에 없다(발명). 관측이 말하게 한다.
|
|
20
20
|
*/
|
|
21
21
|
p10Ms: number;
|
|
@@ -49,7 +49,7 @@ export interface KpiGroup {
|
|
|
49
49
|
* **품질 판정이 있었던 건수만** 센다 (2026-08-19).
|
|
50
50
|
*
|
|
51
51
|
* 판정이 없는 작업(이동·체류·중간 스테이션)은 어느 쪽에도 세지 않는다 — 모름을 양품으로 세면 양품률이
|
|
52
|
-
*
|
|
52
|
+
* 알리지 않고 100% 로 올라간다. 그래서 `good + scrap` 은 `tasks` 보다 작을 수 있고, 그것이 정상이다.
|
|
53
53
|
*
|
|
54
54
|
* 판정이 하나도 없으면 **필드를 만들지 않는다**(0/0 을 「양품률 0%」로 읽히게 두지 않는다).
|
|
55
55
|
*/
|
|
@@ -98,9 +98,109 @@ export interface KpiResult {
|
|
|
98
98
|
*/
|
|
99
99
|
peakGrossKW?: number;
|
|
100
100
|
shavedKW?: number;
|
|
101
|
+
/**
|
|
102
|
+
* 이 창의 **발전량** — 커널이 마감한 기간 사실(`energy.generation.period`)에서 온다.
|
|
103
|
+
*
|
|
104
|
+
* 부하(`kWh`)와 더하지 않는다: 발전은 설비가 만든 양이고 부하는 계량 지점을 지난 양이다. 두 수를
|
|
105
|
+
* 더하면 어느 쪽도 아닌 숫자가 된다.
|
|
106
|
+
*
|
|
107
|
+
* 성과지표(ISO 50001 EnPI)와 태양광 성능비가 이 값을 분자로 쓴다. 발전 사실이 없으면 `undefined`
|
|
108
|
+
* 다(0 이 아니다 — 「발전이 없다」와 「발전 설비가 없다」는 다르다).
|
|
109
|
+
*/
|
|
110
|
+
generated?: {
|
|
111
|
+
/** 창에 온전히 든 기간들의 합(kWh). */
|
|
112
|
+
kWh: number;
|
|
113
|
+
/** 센 기간 수. */
|
|
114
|
+
counted: number;
|
|
115
|
+
/**
|
|
116
|
+
* 그중 **관측이 기간을 다 덮지 못한** 기간 수.
|
|
117
|
+
*
|
|
118
|
+
* 커널이 기간마다 관측 구간을 함께 낸다. 마지막 표본이 기간 끝보다 이르면 그만큼 재지 않은
|
|
119
|
+
* 것이고, 그 수를 감추면 화면이 덜 잰 합을 온전한 합으로 말한다.
|
|
120
|
+
*/
|
|
121
|
+
partial: number;
|
|
122
|
+
/** 설비별 몫 — 이것은 **측정**이다(배분이 아니다). 각 설비의 적산에서 왔다. */
|
|
123
|
+
byEquipment: {
|
|
124
|
+
id: string;
|
|
125
|
+
kWh: number;
|
|
126
|
+
}[];
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* `kWh` 가 어느 회계에서 왔나.
|
|
130
|
+
*
|
|
131
|
+
* 'demand-window' 우리가 표본에서 마감한 구간 (계량이 오는 현장)
|
|
132
|
+
* 'usage-period' 밖에서 마감되어 온 구간 (계량 표본이 없는 현장)
|
|
133
|
+
*
|
|
134
|
+
* 둘을 더하지 않는다 — 같은 전기를 두 번 센다. 둘 다 있으면 계량 쪽을 쓰고 그 사실을 낸다.
|
|
135
|
+
*/
|
|
136
|
+
kWhBasis?: 'demand-window' | 'usage-period';
|
|
137
|
+
/**
|
|
138
|
+
* 기본요금을 매긴 **요금적용전력과 그 근거**.
|
|
139
|
+
*
|
|
140
|
+
* 이 값은 매달 다시 정해진다. 선언에 박아 두면 사람이 갱신해야 하고, 안 하면 낡은 값으로
|
|
141
|
+
* 기본요금이 조용히 틀린다. 그래서 **청구서가 먼저다** — 원본이 매일 실어 보낸다.
|
|
142
|
+
*
|
|
143
|
+
* 'bill' 이 창을 덮는 청구서가 말한 값
|
|
144
|
+
* 'carried-forward' 가장 최근 청구서의 값을 이어 쓴다(요금 제도가 실제로 그렇게 군다)
|
|
145
|
+
* 'declaration' 청구가 오지 않는 현장 — 모델의 선언
|
|
146
|
+
*
|
|
147
|
+
* `declaredKW` 와 다르면 **선언이 낡았다**(`stale`). 그 사실을 감추면 아무도 다시 읽지 않는다.
|
|
148
|
+
*/
|
|
149
|
+
billingDemand?: {
|
|
150
|
+
kW: number;
|
|
151
|
+
from: 'bill' | 'carried-forward' | 'declaration';
|
|
152
|
+
declaredKW?: number;
|
|
153
|
+
stale?: boolean;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* 밖에서 마감되어 온 사용 구간 — 계량 표본이 없는 현장의 유일한 근거다.
|
|
157
|
+
*
|
|
158
|
+
* `basis` 별 건수를 함께 낸다: 같은 kWh 라도 계량기가 잰 것과 공급자 화면에서 긁어온 것은 무게가
|
|
159
|
+
* 다르고, 실제로 긁어온 값이 망가져 있는 것을 본 적이 있다.
|
|
160
|
+
*/
|
|
161
|
+
usage?: {
|
|
162
|
+
kWh: number;
|
|
163
|
+
counted: number;
|
|
164
|
+
/** 구간마다 제 단가로 매긴 사용량 요금 — 모든 구간이 단가를 실었을 때만. */
|
|
165
|
+
charge?: number;
|
|
166
|
+
currency?: string;
|
|
167
|
+
/** 단가가 없어 요금을 매기지 못한 구간 수 — 조용히 빼지 않는다. */
|
|
168
|
+
unpriced: number;
|
|
169
|
+
byBasis: {
|
|
170
|
+
basis: string;
|
|
171
|
+
counted: number;
|
|
172
|
+
}[];
|
|
173
|
+
};
|
|
174
|
+
/**
|
|
175
|
+
* 이 창에 온전히 든 **청구서** — 공급자가 확정한 금액.
|
|
176
|
+
*
|
|
177
|
+
* 우리 계산을 덮지 않는다. 창이 청구 주기와 정확히 같을 때만 견줄 수 있으므로 그 사실을
|
|
178
|
+
* `matchesWindow` 로 함께 낸다 — 다른 창의 두 수를 빼면 그 차이는 아무것도 뜻하지 않는다.
|
|
179
|
+
*/
|
|
180
|
+
bill?: {
|
|
181
|
+
from: string;
|
|
182
|
+
to: string;
|
|
183
|
+
total?: number;
|
|
184
|
+
energyCharge?: number;
|
|
185
|
+
demandCharge?: number;
|
|
186
|
+
currency?: string;
|
|
187
|
+
billingDemandKW?: number;
|
|
188
|
+
matchesWindow: boolean;
|
|
189
|
+
/**
|
|
190
|
+
* 창이 같을 때, **우리 계산에서 청구액을 뺀 값** — 0 이 아니면 우리 계산이 틀렸다는 뜻이다.
|
|
191
|
+
*
|
|
192
|
+
* 항목별로도 낸다(2026-08-29). 총액만 견주면 원본이 총액을 주지 않는 청구서에서는 비교가
|
|
193
|
+
* 영영 나오지 않는다 — 실제로 첫 청구서에 기본요금만 있었다. 양쪽에 있는 항목만 견준다.
|
|
194
|
+
*/
|
|
195
|
+
diff?: {
|
|
196
|
+
total?: number;
|
|
197
|
+
demandCharge?: number;
|
|
198
|
+
energyCharge?: number;
|
|
199
|
+
};
|
|
200
|
+
};
|
|
101
201
|
/** 파생의 근거 — 구간 평균부하 × 길이(`mean-kw`). */
|
|
102
202
|
basis: string;
|
|
103
|
-
/** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) —
|
|
203
|
+
/** 센 구간 수 · 못 센 구간 수(계측이 끊긴 구간) — 알리지 않고 자르지 않는다. */
|
|
104
204
|
counted: number;
|
|
105
205
|
skipped: number;
|
|
106
206
|
/** 완료 오더당 전력량 — 답할 수 없으면 `null` 과 이유. */
|
|
@@ -124,7 +224,7 @@ export interface KpiResult {
|
|
|
124
224
|
* 붙지 않는 부하도 그 비율에 섞인다 — 화면이 그 문장을 함께 말해야 한다.
|
|
125
225
|
*/
|
|
126
226
|
byFeeder?: {
|
|
127
|
-
/** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(
|
|
227
|
+
/** 자리(분기·수전) id. 자리를 모르는 자원은 `unknown` 으로 모인다(알리지 않고 버리지 않는다). */
|
|
128
228
|
key: string;
|
|
129
229
|
kWh: number;
|
|
130
230
|
/**
|
|
@@ -153,7 +253,7 @@ export interface KpiResult {
|
|
|
153
253
|
peakKW?: number;
|
|
154
254
|
}[];
|
|
155
255
|
/**
|
|
156
|
-
* 나누지 못한 양과 그 이유 —
|
|
256
|
+
* 나누지 못한 양과 그 이유 — **알리지 않고 0 으로 만들지 않는다.**
|
|
157
257
|
* `no-weights` = 그 창에 돌아간 자원이 없다(가동시간이 전부 0). 그러면 총량은 그대로 남는다.
|
|
158
258
|
*/
|
|
159
259
|
unattributedKWh?: number;
|
|
@@ -193,7 +293,7 @@ export interface KpiResult {
|
|
|
193
293
|
}[];
|
|
194
294
|
/**
|
|
195
295
|
* **셈에서 빠진 것** — 완료는 창 안이지만 시작 이벤트를 못 찾은 건수.
|
|
196
|
-
* 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이
|
|
296
|
+
* 조회 구간이 짧아 시작이 잘렸거나 저널이 유실된 경우다. 이 값을 숨기면 평균이 알리지 않고 거짓이 된다.
|
|
197
297
|
*/
|
|
198
298
|
unpaired: {
|
|
199
299
|
leadTime: number;
|
|
@@ -251,9 +351,19 @@ export interface KpiResult {
|
|
|
251
351
|
/** 폴드 옵션 — 관점 축과, 그 축을 만들기 위해 이벤트 밖에서 와야 하는 지도. */
|
|
252
352
|
export interface KpiFoldOptions {
|
|
253
353
|
groupBy?: KpiGroupBy;
|
|
354
|
+
/**
|
|
355
|
+
* 이 트윈이 이어받고 있는 **마지막 청구서** — 요금적용전력의 우선 근거(§`billingDemandOf`).
|
|
356
|
+
*
|
|
357
|
+
* 창 밖의 청구서는 저널 조회에 걸리지 않는다. 그런데 그 값은 이 창에도 적용되므로 호출부가
|
|
358
|
+
* 커널 상태에서 읽어 넘긴다.
|
|
359
|
+
*/
|
|
360
|
+
lastBill?: {
|
|
361
|
+
to: string;
|
|
362
|
+
billingDemandKW?: number;
|
|
363
|
+
};
|
|
254
364
|
/**
|
|
255
365
|
* 노드 → 구역 지도. **이벤트에는 구역이 없다**(노드까지만 있다) — 스냅샷·마스터가 아는 값이라
|
|
256
|
-
* 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(
|
|
366
|
+
* 호출부가 넘긴다. 없으면 구역 축은 'unknown' 으로 모인다(알리지 않고 다른 축으로 바꾸지 않는다).
|
|
257
367
|
*/
|
|
258
368
|
locationArea?: Record<string, string>;
|
|
259
369
|
/**
|
|
@@ -283,7 +393,7 @@ export interface KpiFoldOptions {
|
|
|
283
393
|
tariff?: TariffDeclaration;
|
|
284
394
|
/**
|
|
285
395
|
* 교대 선언 — **이벤트에는 교대가 없다**(시각만 있다). 현장 마스터가 아는 값이라 호출부가 넘긴다.
|
|
286
|
-
* 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로
|
|
396
|
+
* 없으면 교대 축은 'unknown' 으로 모인다(다른 축으로 알리지 않고 바꾸지 않는다).
|
|
287
397
|
*/
|
|
288
398
|
shifts?: WorkCalendarEntry[];
|
|
289
399
|
/**
|
|
@@ -300,11 +410,11 @@ export interface KpiFoldOptions {
|
|
|
300
410
|
groupLimit?: number;
|
|
301
411
|
}
|
|
302
412
|
/**
|
|
303
|
-
* 이벤트를
|
|
413
|
+
* 이벤트를 계산해 시간창 KPI 를 만든다.
|
|
304
414
|
*
|
|
305
415
|
* **호출부 규약**: `events` 는 창보다 **넉넉히 앞까지** 담아 넘긴다(예: 창 시작 − 리드타임 상한).
|
|
306
416
|
* 완료된 작업의 시작 이벤트가 창 밖에 있을 수 있기 때문이다. 못 찾은 것은 `unpaired` 로 알린다 —
|
|
307
|
-
* 평균에서
|
|
417
|
+
* 평균에서 오류 없이 빠지면 지표가 거짓이 된다.
|
|
308
418
|
*
|
|
309
419
|
* 시간축은 `eventTime`(ISO) 차분이다. sim 클록이 실제 시각과 오프셋이 있어도 **차분은 정확**하다
|
|
310
420
|
* (고정 오프셋은 상쇄된다). 파싱 불가한 줄은 버린다.
|
|
@@ -363,7 +473,7 @@ export interface KpiBucket {
|
|
|
363
473
|
workP50Ms: number;
|
|
364
474
|
}
|
|
365
475
|
/**
|
|
366
|
-
* 창을 N 등분해 구간별로
|
|
476
|
+
* 창을 N 등분해 구간별로 처리한다 — **같은 이벤트를 창만 바꿔** 다시 계산한다(조회 추가 없음).
|
|
367
477
|
*
|
|
368
478
|
* 마지막 구간은 나머지를 흡수한다(부동소수 나눗셈으로 끝이 밀리면 마지막 완료가 빠진다).
|
|
369
479
|
* `count <= 1` 이면 쪼갤 것이 없으므로 빈 배열 — 호출부가 "추세 없음" 으로 다룬다.
|