@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
package/test/kpi-fold.test.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* 업무 KPI 폴드 검증 — 저널을
|
|
2
|
+
* 업무 KPI 폴드 검증 — 저널을 계산해 시간창 지표를 만드는 규칙을 고정한다.
|
|
3
3
|
*
|
|
4
4
|
* 무엇을 지키는가:
|
|
5
5
|
* ① **성과는 완료 시점으로 센다** — 시작 시점으로 세면 진행 중인 것이 섞여 처리량이 부풀려진다.
|
|
6
|
-
* ② **셈에서 빠진 것을 밝힌다**(unpaired) — 시작 이벤트를 못 찾은 완료를
|
|
6
|
+
* ② **셈에서 빠진 것을 밝힌다**(unpaired) — 시작 이벤트를 못 찾은 완료를 알리지 않고 버리면 평균이 거짓이 된다.
|
|
7
7
|
* ③ **없는 것을 0 으로 꾸미지 않는다** — 저널이 비었으면 eventsSeen 0 으로 그 사실이 보인다.
|
|
8
8
|
* ④ 시간축은 eventTime 차분 — sim 클록 오프셋이 있어도 차분은 정확하다.
|
|
9
9
|
*/
|
|
@@ -82,7 +82,7 @@ test('분위수: p50·p90 을 낸다(작은 표본에서 과장하지 않는다)
|
|
|
82
82
|
assert.equal(r.workTime.p90Ms, 10000)
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
-
test('시작 이벤트를 못 찾은 완료는 unpaired 로 밝힌다 — 평균에서
|
|
85
|
+
test('시작 이벤트를 못 찾은 완료는 unpaired 로 밝힌다 — 평균에서 오류 없이 빠지지 않게', () => {
|
|
86
86
|
const r = foldKpi([task('t1', 'completed', 30)], WINDOW)
|
|
87
87
|
assert.equal(r.throughput.tasks, 1, '완료는 센다')
|
|
88
88
|
assert.equal(r.leadTime.count, 0, '리드타임은 계산할 수 없다')
|
|
@@ -159,7 +159,7 @@ test('오더 완료: 양이 답한다 — 낱말이 진행 중이라 말해도',
|
|
|
159
159
|
test('오더 완료: 같은 오더를 한 번만 센다 — 폴링 원본이 다시 보내도', () => {
|
|
160
160
|
/*
|
|
161
161
|
* 폴링하는 원본은 같은 오더의 종결을 여러 주기에 걸쳐 다시 보낼 수 있다(결함이 아니라 폴링의 성질).
|
|
162
|
-
* 사건마다 세면 처리량이
|
|
162
|
+
* 사건마다 세면 처리량이 알리지 않고 부풀려진다 — 작업 쪽은 커널의 짝맞춤이 이미 그 규율을 갖고 있었고
|
|
163
163
|
* 오더만 없었다.
|
|
164
164
|
*/
|
|
165
165
|
const r = foldKpi([order('o1', 'completed', 10), order('o1', 'completed', 20), order('o2', 'completed', 30)], WINDOW)
|
|
@@ -233,7 +233,7 @@ test('같은 전이가 두 번 와도 착수는 처음 것, 생성은 마지막
|
|
|
233
233
|
})
|
|
234
234
|
|
|
235
235
|
/* ── 구간 쪼개기(추세) ─────────────────────────────────────────────────────
|
|
236
|
-
* 화면의 추세선이 여기서 나온다. 조회는 한 번이고 창만 바꿔 다시
|
|
236
|
+
* 화면의 추세선이 여기서 나온다. 조회는 한 번이고 창만 바꿔 다시 계산한다.
|
|
237
237
|
*/
|
|
238
238
|
test('구간 쪼개기: N 등분해 구간별 처리량을 낸다', () => {
|
|
239
239
|
/* 1시간 창을 4등분(15분씩). 각 구간에 한 건씩 완료되게 놓는다. */
|
|
@@ -336,7 +336,7 @@ test('축의 합이 전체 처리량과 같다 — 어느 축에서도 건이
|
|
|
336
336
|
}
|
|
337
337
|
})
|
|
338
338
|
|
|
339
|
-
test('축의 값이 없던 건은 unknown 으로 모인다 —
|
|
339
|
+
test('축의 값이 없던 건은 unknown 으로 모인다 — 알리지 않고 버리지 않는다', () => {
|
|
340
340
|
const events = [...three('a', 100, { kind: 'move', resourceRef: 'fk-1' }), ...three('b', 200, { kind: 'move' })]
|
|
341
341
|
const r = foldKpi(events, WINDOW, { groupBy: 'resource' })
|
|
342
342
|
assert.deepEqual(r.groups?.items.map(g => g.key).sort(), ['fk-1', 'unknown'])
|
|
@@ -374,7 +374,7 @@ test('구역 축은 노드→구역 지도를 받아야 낸다 — 지도가 없
|
|
|
374
374
|
assert.deepEqual(mapped.groups?.items.map(g => [g.key, g.tasks]), [['zone-a', 2]], '두 노드가 한 구역으로 모인다')
|
|
375
375
|
})
|
|
376
376
|
|
|
377
|
-
test('축이 많으면 잘라내고 잘린 축의 수와 그 안의 건수를 함께 알린다 —
|
|
377
|
+
test('축이 많으면 잘라내고 잘린 축의 수와 그 안의 건수를 함께 알린다 — 들어온 것이 없는 절단 금지', () => {
|
|
378
378
|
/* 축마다 건수를 다르게 둔다: 축의 수만 알리면 "그 외 20개" 가 20건인지 60건인지 알 수 없다. */
|
|
379
379
|
const events: KpiEvent[] = []
|
|
380
380
|
let t = 0
|
|
@@ -403,7 +403,7 @@ test('짝을 못 찾은 건도 축에서 센다 — 건수는 세고 소요만
|
|
|
403
403
|
})
|
|
404
404
|
|
|
405
405
|
/* ── 시간 비교 ──────────────────────────────────────────────────────────────
|
|
406
|
-
* "29초" 만으로는 좋아졌는지 나빠졌는지 알 수 없다. 창을 옮겨 한 번 더
|
|
406
|
+
* "29초" 만으로는 좋아졌는지 나빠졌는지 알 수 없다. 창을 옮겨 한 번 더 계산 규칙을 고정한다.
|
|
407
407
|
*/
|
|
408
408
|
test('직전 구간: 같은 길이로 바로 앞 — 경계가 맞물린다(겹치거나 벌어지지 않게)', () => {
|
|
409
409
|
const w = shiftWindow(WINDOW.fromMs, WINDOW.toMs, 'previous')
|
|
@@ -431,7 +431,7 @@ test('중앙값 차이: 한쪽이 결측이면 차이는 없다 — 0 으로 내
|
|
|
431
431
|
assert.equal(deltaP50(missing, measured), undefined)
|
|
432
432
|
})
|
|
433
433
|
|
|
434
|
-
test('비교: 옮긴 창으로
|
|
434
|
+
test('비교: 옮긴 창으로 계산하면 앞 구간의 성과가 나온다 — 같은 폴드, 창만 다르다', () => {
|
|
435
435
|
/* 현재 구간에 2건, 직전 구간에 1건. */
|
|
436
436
|
const events = [
|
|
437
437
|
...three('now-1', 100, { kind: 'move' }),
|
|
@@ -500,7 +500,7 @@ test('교차: 지금만 있는 축은 isNew — delta 를 건수와 같게 적
|
|
|
500
500
|
assert.equal(nw.prevTasks, undefined)
|
|
501
501
|
})
|
|
502
502
|
|
|
503
|
-
test('교차: 사라진 축을 따로 알린다 — 멈춘 구역이 표에서
|
|
503
|
+
test('교차: 사라진 축을 따로 알린다 — 멈춘 구역이 표에서 오류 없이 빠지지 않게', () => {
|
|
504
504
|
const c = crossGroups([g('pick', 5)], [g('pick', 5), g('putaway', 12)])
|
|
505
505
|
assert.deepEqual(c.disappeared, [{ key: 'putaway', tasks: 12 }])
|
|
506
506
|
})
|
|
@@ -547,7 +547,7 @@ test('교대별로 쪼갠다 — 완료 시각에서 계산한다(저널에 없
|
|
|
547
547
|
assert.equal(r.groups?.items.reduce((s, g) => s + g.tasks, 0), r.throughput.tasks, '축의 합이 전체와 같다')
|
|
548
548
|
})
|
|
549
549
|
|
|
550
|
-
test('현장의 시각 기준으로 읽는다 — UTC 로
|
|
550
|
+
test('현장의 시각 기준으로 읽는다 — UTC 로 알리지 않고 가정하지 않는다', () => {
|
|
551
551
|
const events = [...doneAtHour('t1', 20)] // 절대 20시
|
|
552
552
|
const utc = foldKpi(events, DAY, { groupBy: 'shift', shifts: SHIFTS_3 })
|
|
553
553
|
const local = foldKpi(events, DAY, { groupBy: 'shift', shifts: SHIFTS_3, utcOffsetMinutes: -420 })
|
|
@@ -630,7 +630,7 @@ test('모르는 지표 이름은 짐작해 매핑하지 않는다', () => {
|
|
|
630
630
|
/* ── 목표의 범위 (공간 vs 인스턴스) ──────────────────────────────────────────
|
|
631
631
|
*
|
|
632
632
|
* 이 규칙은 원래 DB 접근 뒤에 숨어 있어 **화면을 눌러 봐야만** 확인할 수 있었다. 로그인 뒤 화면을
|
|
633
|
-
* 자동으로 검증할 방법이 없는 프로젝트라 그런 규칙은
|
|
633
|
+
* 자동으로 검증할 방법이 없는 프로젝트라 그런 규칙은 알리지 않고 미검증으로 쌓인다 — 그래서 밖으로 뺐다.
|
|
634
634
|
*/
|
|
635
635
|
const row = (metric: string, target: number, scope: { spaceId?: string; instanceId?: string }) => ({
|
|
636
636
|
metric,
|
|
@@ -788,7 +788,7 @@ test('그 창에 완료가 없으면 원단위는 정의되지 않는다 — 전
|
|
|
788
788
|
test('계측이 끊긴 구간은 세지 않고 몇 개를 못 셌는지 말한다', () => {
|
|
789
789
|
const r = foldKpi([win(T, T + q, 400), win(T + q, T + 2 * q), orderDone(T + 60_000)], { fromMs: T, toMs: T + 2 * q })
|
|
790
790
|
assert.equal(r.energy?.counted, 1)
|
|
791
|
-
assert.equal(r.energy?.skipped, 1, '
|
|
791
|
+
assert.equal(r.energy?.skipped, 1, '알리지 않고 빼면 이 합이 그 창의 전부인 줄 읽는다')
|
|
792
792
|
})
|
|
793
793
|
|
|
794
794
|
test('창에 걸친 구간은 세지 않는다 — 부분을 비례로 자르면 추정이 하나 더 생긴다', () => {
|
|
@@ -1134,7 +1134,7 @@ test('피크 구간에 표본이 없던 분기는 피크 값을 만들지 않는
|
|
|
1134
1134
|
* ── 완료로 읽지 못한 오더 상태 낱말을 **센다** (2026-08-23) ──────────────────
|
|
1135
1135
|
*
|
|
1136
1136
|
* 오더의 상태 어휘는 계약이 도메인에 넘긴 것이라(`picking`·`shipped`·`FINISHED`…) 이 폴드는 「완료로
|
|
1137
|
-
* 읽히는 표현」을 동의어 목록으로 받는다. 그 대가는 **목록에 없는 낱말이
|
|
1137
|
+
* 읽히는 표현」을 동의어 목록으로 받는다. 그 대가는 **목록에 없는 낱말이 알리지 않고 0 으로 세어진다**는 것이다.
|
|
1138
1138
|
*
|
|
1139
1139
|
* 실제로 그랬다: `finished` 가 빠져 있어 첫 실 시스템의 완료 오더가 전부 0 이었고, 사람이 화면을 보고
|
|
1140
1140
|
* 의심해서야 드러났다. 목록을 넓히는 것으로는 다음 원본을 막지 못한다 — 그래서 **못 읽은 것을 센다.**
|
|
@@ -1174,3 +1174,174 @@ test('상태를 말하지 않은 사건은 여기 오지 않는다 — 그것은
|
|
|
1174
1174
|
const r = foldKpi([{ eventType: 'order.status', eventTime: at(10), payload: { data: { orderId: 'o-1' } } }], WINDOW)
|
|
1175
1175
|
assert.deepEqual(r.orderStatusNotCounted, {})
|
|
1176
1176
|
})
|
|
1177
|
+
|
|
1178
|
+
/* ── 밖에서 마감되어 온 사용 구간과 청구서 (2026-08-29) ────────────────────
|
|
1179
|
+
*
|
|
1180
|
+
* ★ 계량 표본이 오지 않는 현장이 있다. 원본이 「이 구간에 X 썼다」를 이미 마감해 준다.
|
|
1181
|
+
* 그 값을 쓰지 않으면 그 현장의 화면은 영영 빈손이다.
|
|
1182
|
+
*
|
|
1183
|
+
* 두 회계를 **더하지 않는다** — 같은 전기를 두 번 센다.
|
|
1184
|
+
*/
|
|
1185
|
+
|
|
1186
|
+
const usagePeriod = (kWh: number, fromSec: number, toSec: number, extra: Record<string, unknown> = {}): KpiEvent => ({
|
|
1187
|
+
eventType: 'energy.usage.period',
|
|
1188
|
+
eventTime: at(toSec),
|
|
1189
|
+
payload: { data: { meterId: 'incoming-1', from: at(fromSec), to: at(toSec), kWh, ...extra } }
|
|
1190
|
+
})
|
|
1191
|
+
|
|
1192
|
+
test('★ 계량 표본이 없어도 사용 구간으로 답한다 — 근거를 밝힌다', () => {
|
|
1193
|
+
const k = foldKpi([usagePeriod(474.48, 0, 1800, { basis: 'provider' }), usagePeriod(471.6, 1800, 3600, { basis: 'provider' })], WINDOW)
|
|
1194
|
+
assert.equal(k.energy?.kWh, 474.48 + 471.6)
|
|
1195
|
+
assert.equal(k.energy?.kWhBasis, 'usage-period')
|
|
1196
|
+
assert.equal(k.energy?.usage?.counted, 2)
|
|
1197
|
+
assert.deepEqual(k.energy?.usage?.byBasis, [{ basis: 'provider', counted: 2 }])
|
|
1198
|
+
})
|
|
1199
|
+
|
|
1200
|
+
test('창에 걸친 구간은 세지 않는다 — 반만 세면 합이 창을 넘는다', () => {
|
|
1201
|
+
const k = foldKpi([usagePeriod(100, -1800, 1800)], WINDOW)
|
|
1202
|
+
assert.equal(k.energy?.usage, undefined)
|
|
1203
|
+
})
|
|
1204
|
+
|
|
1205
|
+
test('★ 구간마다 제 단가로 매긴다 — 단일 단가로 접지 않는다', () => {
|
|
1206
|
+
/* 새벽 87.3 · 낮 222.3 — 하나로 접으면 「피크를 깎아 얼마를 아끼나」가 사라진다. */
|
|
1207
|
+
const k = foldKpi(
|
|
1208
|
+
[
|
|
1209
|
+
usagePeriod(100, 0, 1800, { unitPrice: 87.3, currency: 'KRW', basis: 'provider' }),
|
|
1210
|
+
usagePeriod(100, 1800, 3600, { unitPrice: 222.3, currency: 'KRW', basis: 'provider' })
|
|
1211
|
+
],
|
|
1212
|
+
WINDOW,
|
|
1213
|
+
{ tariff: { energyChargePerKWh: 140.2, currency: 'KRW' } } as any
|
|
1214
|
+
)
|
|
1215
|
+
assert.equal(k.energy?.usage?.charge, 100 * 87.3 + 100 * 222.3)
|
|
1216
|
+
assert.equal((k.energy?.cost as any)?.energyCharge, 100 * 87.3 + 100 * 222.3, '구간별 단가가 이긴다')
|
|
1217
|
+
assert.ok(!(k.energy?.cost as any)?.notModeled.includes('tou'))
|
|
1218
|
+
})
|
|
1219
|
+
|
|
1220
|
+
test('일부만 단가를 실었으면 요금을 만들지 않고 그 수를 낸다', () => {
|
|
1221
|
+
const k = foldKpi(
|
|
1222
|
+
[usagePeriod(100, 0, 1800, { unitPrice: 87.3, currency: 'KRW' }), usagePeriod(100, 1800, 3600)],
|
|
1223
|
+
WINDOW
|
|
1224
|
+
)
|
|
1225
|
+
assert.equal(k.energy?.usage?.charge, undefined, '있는 것만 더하면 그 합이 창의 요금인 척한다')
|
|
1226
|
+
assert.equal(k.energy?.usage?.unpriced, 1)
|
|
1227
|
+
})
|
|
1228
|
+
|
|
1229
|
+
test('★ 청구서는 사실로 나란히 둔다 — 창이 다르면 견주지 않는다', () => {
|
|
1230
|
+
const bill: KpiEvent = {
|
|
1231
|
+
eventType: 'energy.bill',
|
|
1232
|
+
eventTime: at(3600),
|
|
1233
|
+
payload: { data: { from: at(600), to: at(3000), total: 1_000_000, currency: 'KRW', billingDemandKW: 1650 } }
|
|
1234
|
+
}
|
|
1235
|
+
const k = foldKpi([bill], WINDOW)
|
|
1236
|
+
assert.equal(k.energy?.bill?.total, 1_000_000)
|
|
1237
|
+
assert.equal(k.energy?.bill?.billingDemandKW, 1650)
|
|
1238
|
+
assert.equal(k.energy?.bill?.matchesWindow, false, '한 달 청구서를 하루 창의 값과 빼면 아무것도 뜻하지 않는다')
|
|
1239
|
+
assert.equal(k.energy?.bill?.ourTotalMinusBilled, undefined)
|
|
1240
|
+
})
|
|
1241
|
+
|
|
1242
|
+
test('★ 창이 청구 주기와 같으면 우리 계산과의 차이를 낸다 — 0 이 아니면 우리가 틀렸다', () => {
|
|
1243
|
+
const bill: KpiEvent = {
|
|
1244
|
+
eventType: 'energy.bill',
|
|
1245
|
+
eventTime: at(3600),
|
|
1246
|
+
payload: { data: { from: at(0), to: at(3600), total: 100_000, currency: 'KRW' } }
|
|
1247
|
+
}
|
|
1248
|
+
const k = foldKpi([bill, usagePeriod(1_000, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
|
|
1249
|
+
tariff: { energyChargePerKWh: 87.3, currency: 'KRW' }
|
|
1250
|
+
} as any)
|
|
1251
|
+
assert.equal(k.energy?.bill?.matchesWindow, true)
|
|
1252
|
+
assert.equal(k.energy?.bill?.diff?.total, 1_000 * 87.3 - 100_000)
|
|
1253
|
+
})
|
|
1254
|
+
|
|
1255
|
+
test('★ 총액이 없는 청구서도 항목별로 견준다 — 실제 첫 청구서에 기본요금만 있었다', () => {
|
|
1256
|
+
const bill: KpiEvent = {
|
|
1257
|
+
eventType: 'energy.bill',
|
|
1258
|
+
eventTime: at(3600),
|
|
1259
|
+
payload: { data: { from: at(0), to: at(3600), demandCharge: 13_728_000, currency: 'KRW', billingDemandKW: 1650 } }
|
|
1260
|
+
}
|
|
1261
|
+
const k = foldKpi([bill], WINDOW, {
|
|
1262
|
+
tariff: { demandChargePerKW: 8_320, billingDemandKW: 1_650, currency: 'KRW' }
|
|
1263
|
+
} as any)
|
|
1264
|
+
assert.equal(k.energy?.bill?.diff?.demandCharge, 0, '요금적용전력으로 매기면 청구서와 같다')
|
|
1265
|
+
assert.equal(k.energy?.bill?.diff?.total, undefined, '한쪽에 없는 항목은 견주지 않는다')
|
|
1266
|
+
})
|
|
1267
|
+
|
|
1268
|
+
test('요금적용전력을 선언하지 않으면 기본요금이 어긋나는 것이 드러난다', () => {
|
|
1269
|
+
const bill: KpiEvent = {
|
|
1270
|
+
eventType: 'energy.bill',
|
|
1271
|
+
eventTime: at(3600),
|
|
1272
|
+
payload: { data: { from: at(0), to: at(3600), demandCharge: 13_728_000, currency: 'KRW' } }
|
|
1273
|
+
}
|
|
1274
|
+
/* 선언이 없으면 커널은 창의 피크로 조건부 값을 낸다 — 그 차이가 그대로 나온다. */
|
|
1275
|
+
const peakWindow: KpiEvent = {
|
|
1276
|
+
eventType: 'energy.demand.window',
|
|
1277
|
+
eventTime: at(1800),
|
|
1278
|
+
payload: { data: { startMs: T0 + 900_000, endMs: T0 + 1_800_000, maxKW: 600, meanKW: 600, samples: 4 } }
|
|
1279
|
+
}
|
|
1280
|
+
const k = foldKpi([bill, peakWindow], WINDOW, {
|
|
1281
|
+
tariff: { demandChargePerKW: 8_320, currency: 'KRW' }
|
|
1282
|
+
} as any)
|
|
1283
|
+
const d = k.energy?.bill?.diff?.demandCharge
|
|
1284
|
+
assert.ok(d !== undefined && d !== 0, `어긋남이 드러나야 한다: ${d}`)
|
|
1285
|
+
})
|
|
1286
|
+
|
|
1287
|
+
/* ── 요금적용전력은 청구서가 먼저다 (2026-08-29) ───────────────────────────
|
|
1288
|
+
*
|
|
1289
|
+
* ★ 그 값은 최근 몇 달의 최고 수요로 **매달 다시 정해진다.** 선언에 박아 두면 사람이 매달
|
|
1290
|
+
* 갱신해야 하고, 안 하면 낡은 값으로 기본요금이 조용히 틀린다.
|
|
1291
|
+
*/
|
|
1292
|
+
|
|
1293
|
+
const billEvent = (fromSec: number, toSec: number, data: Record<string, unknown>): KpiEvent => ({
|
|
1294
|
+
eventType: 'energy.bill',
|
|
1295
|
+
eventTime: at(toSec),
|
|
1296
|
+
payload: { data: { from: at(fromSec), to: at(toSec), currency: 'KRW', ...data } }
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
test('★ 창을 덮는 청구서가 있으면 그 값으로 매긴다', () => {
|
|
1300
|
+
const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
|
|
1301
|
+
tariff: { demandChargePerKW: 8_320, billingDemandKW: 1200, currency: 'KRW' }
|
|
1302
|
+
} as any)
|
|
1303
|
+
assert.equal(k.energy?.billingDemand?.kW, 1650)
|
|
1304
|
+
assert.equal(k.energy?.billingDemand?.from, 'bill')
|
|
1305
|
+
assert.equal((k.energy?.cost as any)?.demandCharge, 1650 * 8_320, '선언 1200 이 아니라 청구서 1650 으로 매긴다')
|
|
1306
|
+
})
|
|
1307
|
+
|
|
1308
|
+
test('★ 선언이 청구서와 다르면 낡았다고 말한다 — 감추면 아무도 다시 읽지 않는다', () => {
|
|
1309
|
+
const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
|
|
1310
|
+
tariff: { demandChargePerKW: 8_320, billingDemandKW: 1200, currency: 'KRW' }
|
|
1311
|
+
} as any)
|
|
1312
|
+
assert.equal(k.energy?.billingDemand?.declaredKW, 1200)
|
|
1313
|
+
assert.equal(k.energy?.billingDemand?.stale, true)
|
|
1314
|
+
})
|
|
1315
|
+
|
|
1316
|
+
test('선언과 청구서가 같으면 낡았다고 하지 않는다', () => {
|
|
1317
|
+
const k = foldKpi([billEvent(0, 3600, { demandCharge: 1, billingDemandKW: 1650 })], WINDOW, {
|
|
1318
|
+
tariff: { demandChargePerKW: 8_320, billingDemandKW: 1650, currency: 'KRW' }
|
|
1319
|
+
} as any)
|
|
1320
|
+
assert.equal(k.energy?.billingDemand?.stale, undefined)
|
|
1321
|
+
})
|
|
1322
|
+
|
|
1323
|
+
test('★ 창에 청구서가 없으면 마지막 청구서의 값을 이어 쓴다 — 요금 제도가 그렇게 군다', () => {
|
|
1324
|
+
const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
|
|
1325
|
+
tariff: { demandChargePerKW: 8_320, currency: 'KRW' },
|
|
1326
|
+
lastBill: { to: at(-86400), billingDemandKW: 1650 }
|
|
1327
|
+
} as any)
|
|
1328
|
+
assert.equal(k.energy?.billingDemand?.kW, 1650)
|
|
1329
|
+
assert.equal(k.energy?.billingDemand?.from, 'carried-forward')
|
|
1330
|
+
assert.equal((k.energy?.cost as any)?.demandBasis, 'billing-demand')
|
|
1331
|
+
})
|
|
1332
|
+
|
|
1333
|
+
test('청구가 오지 않는 현장은 선언을 쓴다', () => {
|
|
1334
|
+
const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
|
|
1335
|
+
tariff: { demandChargePerKW: 8_320, billingDemandKW: 900, currency: 'KRW' }
|
|
1336
|
+
} as any)
|
|
1337
|
+
assert.equal(k.energy?.billingDemand?.from, 'declaration')
|
|
1338
|
+
assert.equal(k.energy?.billingDemand?.kW, 900)
|
|
1339
|
+
assert.equal(k.energy?.billingDemand?.stale, undefined)
|
|
1340
|
+
})
|
|
1341
|
+
|
|
1342
|
+
test('셋 다 없으면 이 칸을 만들지 않는다 — 빈 값을 지어내지 않는다', () => {
|
|
1343
|
+
const k = foldKpi([usagePeriod(100, 0, 3600, { unitPrice: 87.3, currency: 'KRW' })], WINDOW, {
|
|
1344
|
+
tariff: { demandChargePerKW: 8_320, currency: 'KRW' }
|
|
1345
|
+
} as any)
|
|
1346
|
+
assert.equal(k.energy?.billingDemand, undefined)
|
|
1347
|
+
})
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* · 브로드캐스팅 — 서버가 주기마다 전 단계를 계산해 밀어 준다. 자리가 트윈당 최대 200개고 지표가 6개라
|
|
8
8
|
* 태그가 1,200개가 된다. **모델에 카드를 하나도 안 놓아도 계산은 돈다.**
|
|
9
9
|
* · 질의 — 카드가 필요할 때 묻는다. **모델에 놓인 카드 수만큼만** 든다. 시각(`toTime`)도 물을 수 있어
|
|
10
|
-
* 타임라인과 맞고, 공간 단위 합산(여러 트윈을 한 번에
|
|
10
|
+
* 타임라인과 맞고, 공간 단위 합산(여러 트윈을 한 번에 계산)은 질의에만 있다.
|
|
11
11
|
*
|
|
12
12
|
* 질의로 정하려면 **한 번의 비용**을 알아야 한다. 카드 20장이 30초마다 물으면 감당되는지가 결정을
|
|
13
13
|
* 가른다. 짐작하지 않고 잰다.
|
|
@@ -82,7 +82,7 @@ before(async () => {
|
|
|
82
82
|
const d = await ds.getRepository(Domain).save({ name: 'kpi-bench', subdomain: 'kpi-bench' } as any)
|
|
83
83
|
domainId = d.id
|
|
84
84
|
|
|
85
|
-
/* 자리 계층 — 자리 200개를 구역 4개에 나눠 붙인다(구역 단위
|
|
85
|
+
/* 자리 계층 — 자리 200개를 구역 4개에 나눠 붙인다(구역 단위 계산 비용도 재려면 부모가 있어야 한다). */
|
|
86
86
|
const locations = Array.from({ length: LOCATIONS }, (_, i) => ({
|
|
87
87
|
id: `loc-${i}`,
|
|
88
88
|
type: 'storage',
|
|
@@ -125,6 +125,6 @@ test('bench: 구간 성과 폴드 한 번의 비용 vs 창 안의 작업 수', a
|
|
|
125
125
|
console.log(` 작업 ${count.toLocaleString()}건(이벤트 ${(count * 2).toLocaleString()}줄) · 자리 ${LOCATIONS}개`)
|
|
126
126
|
console.log(` 폴드 1회 ${plain.toFixed(1)}ms`)
|
|
127
127
|
console.log(` 구역별 폴드 1회 ${byArea.toFixed(1)}ms`)
|
|
128
|
-
console.log(` 카드 20장 동시 ${(plain * 20).toFixed(0)}ms (직렬 상한 — 실제로는 한 번
|
|
128
|
+
console.log(` 카드 20장 동시 ${(plain * 20).toFixed(0)}ms (직렬 상한 — 실제로는 한 번 계산해 나눠 쓸 수 있다)`)
|
|
129
129
|
}
|
|
130
130
|
})
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* ★ 읽기 커서는 **부르는 쪽이 자리를 가리켜야** 이어진다 — 커넥터는 그 이름을 알 수 없다.
|
|
3
3
|
*
|
|
4
|
-
* ── 무엇이
|
|
4
|
+
* ── 무엇이 알리지 않고 끊겨 있었나 (2026-08-23) ──────────────────────────────────
|
|
5
5
|
* 커서를 저장할 컬럼과 계약을 만들고 배선까지 했는데, **한 번도 저장되지 않았다.** 저장할 자리를
|
|
6
6
|
* `cfg`·`site` 에서 찾으려 했기 때문이다.
|
|
7
7
|
*
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* 원본은 그것을 모른다. 커넥터가 채울 수 없는 값을 커넥터에서 읽으려 한 것이 잘못이었다.
|
|
13
13
|
*
|
|
14
14
|
* 그리고 그 실패는 **로그로는 말했다** — 「this feed does not say which reference it reads from」.
|
|
15
|
-
* 그 한 줄로 다른 레인이 바로 짚었다(
|
|
15
|
+
* 그 한 줄로 다른 레인이 바로 짚었다(알리지 않고 넘기지 않은 것이 값을 했다). 그러나 로그는 사람이 볼 때만
|
|
16
16
|
* 값이 있다. 배선이 다시 끊기면 이 시험이 먼저 말한다.
|
|
17
17
|
*
|
|
18
18
|
* ── 왜 「늘었나」로 확인하면 안 되나 ────────────────────────────────────────
|
|
@@ -44,10 +44,10 @@ test('커서를 저장할 자리는 부르는 쪽이 가리킨다 — cfg·site
|
|
|
44
44
|
assert.match(code, /startReferenceLiveFeed/, '대상 함수가 남아 있다')
|
|
45
45
|
})
|
|
46
46
|
|
|
47
|
-
test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그 경로는
|
|
47
|
+
test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그 경로는 알리지 않고 창이 미끄러진다', () => {
|
|
48
48
|
/*
|
|
49
49
|
* 호출처가 둘이고 **둘 다** `TwinReference` 를 들고 있다(부팅 재개 · 인제스트 직후 기동).
|
|
50
|
-
* 하나만 넘기면 그 경로로 세워진 트윈만 커서를 잃고, 그 차이는 오류 없이
|
|
50
|
+
* 하나만 넘기면 그 경로로 세워진 트윈만 커서를 잃고, 그 차이는 오류 없이 들어온 것이 없다.
|
|
51
51
|
*/
|
|
52
52
|
const calls = [...codeOf(LIVE).matchAll(/startReferenceLiveFeed\(([^)]*)\)/g)].map(m => m[1])
|
|
53
53
|
const resolverCalls = [...codeOf(RESOLVER).matchAll(/startReferenceLiveFeed\([\s\S]{0,200}?\)/g)].map(m => m[0])
|
|
@@ -58,7 +58,7 @@ test('모든 호출처가 그 이름을 넘긴다 — 한 곳만 빠져도 그
|
|
|
58
58
|
}
|
|
59
59
|
})
|
|
60
60
|
|
|
61
|
-
test('이름이 없으면
|
|
61
|
+
test('이름이 없으면 알리지 않고 돌지 않는다 — 말한다', () => {
|
|
62
62
|
/*
|
|
63
63
|
* 커서 없이 도는 것은 「창이 매번 미끄러진다」는 뜻이고, 그 손실은 오류 없이 사라진다. 그래서 그
|
|
64
64
|
* 사실을 로그로 말한다 — 실제로 그 한 줄이 이 결함을 찾아냈다.
|
|
@@ -82,6 +82,6 @@ test('커서는 레퍼런스의 것이다 — 인스턴스마다 두지 않는
|
|
|
82
82
|
})
|
|
83
83
|
|
|
84
84
|
test('저장 실패를 삼키지 않는다', () => {
|
|
85
|
-
/*
|
|
85
|
+
/* 오류 없이 실패하면 다음 재기동에서 창이 다시 미끄러지고, 이 컬럼을 만든 이유가 사라진다. */
|
|
86
86
|
assert.match(codeOf(LIVE), /live cursor not saved/, '저장 실패를 말한다')
|
|
87
87
|
})
|
|
@@ -65,7 +65,7 @@ test('커널이 낸 사실도 브로드캐스팅을 예약한다 — 피크·마
|
|
|
65
65
|
|
|
66
66
|
/*
|
|
67
67
|
* ── 구조 전환도 브로드캐스팅한다 (2026-08-18) ───────────────────────────────────────
|
|
68
|
-
* 화면이 보는 것 상당수가 구조에서 파생된다(계약 대비 판정·주목 신호·자리 색). 구조 전환이
|
|
68
|
+
* 화면이 보는 것 상당수가 구조에서 파생된다(계약 대비 판정·주목 신호·자리 색). 구조 전환이 알리지 않고
|
|
69
69
|
* 끝나면 지도 레일은 다음 계측 표본이 올 때까지 옛 화면을 들고 있고, 헤더 배지는 폴링으로 먼저 알아
|
|
70
70
|
* 「배지엔 있고 목록엔 없는」 어긋난 화면이 된다(실화면에서 봤다).
|
|
71
71
|
*/
|
|
@@ -101,18 +101,18 @@ test('과거 화면의 「지금」은 잘라 낸 뒤의 마지막 사실이다
|
|
|
101
101
|
const body = methodOf('recover')
|
|
102
102
|
/* 예전에는 전량(rows)의 마지막 시각을 썼다 — 물어본 시점보다 뒤의 이벤트라 과거 화면이 미래를
|
|
103
103
|
기준으로 「늦었다」를 판정할 수 있었다. 이제 rows 는 이미 잘린 것이다. */
|
|
104
|
-
assert.match(body, /const wanted = rows/, '자른 것과
|
|
104
|
+
assert.match(body, /const wanted = rows/, '자른 것과 계산하는 것이 같아야 한다')
|
|
105
105
|
})
|
|
106
106
|
|
|
107
107
|
/*
|
|
108
|
-
* ── 재개점에서
|
|
109
|
-
*
|
|
110
|
-
* 이벤트가 하나만 늘어도 0부터 다시
|
|
111
|
-
* 여기서는 **호스트가 그 씨앗을 실제로 쓰는지**를 지킨다 — 배선이 사라지면 지름길은
|
|
108
|
+
* ── 재개점에서 이어서 계산한다 (2026-08-18) ──────────────────────────────────────
|
|
109
|
+
* 계산한 결과는 이미 남기고 있었지만, 그 지점의 **재개점**(리듀서 내부 상태 전부 + 가동 누적기)이 없어서
|
|
110
|
+
* 이벤트가 하나만 늘어도 0부터 다시 계산해야 했다. 동치는 커널 시험이 증명한다(0부터 == 재개점 + 꼬리).
|
|
111
|
+
* 여기서는 **호스트가 그 씨앗을 실제로 쓰는지**를 지킨다 — 배선이 사라지면 지름길은 오류 없이 사라진다.
|
|
112
112
|
*/
|
|
113
113
|
test('폴드가 재개점을 쓰고, 새 재개점을 함께 남긴다', () => {
|
|
114
114
|
const body = methodOf('recover')
|
|
115
|
-
assert.match(body, /replayFrom\(model, resume\.fold\.reducer/, '씨앗이 있으면 꼬리만
|
|
115
|
+
assert.match(body, /replayFrom\(model, resume\.fold\.reducer/, '씨앗이 있으면 꼬리만 계산한다')
|
|
116
116
|
assert.match(body, /revision: MoreThan\(from\)/, '읽는 것도 그 뒤만')
|
|
117
117
|
assert.match(body, /oee\.restore\(resume\.fold\.oee\)/, '가동 누적기도 이어받는다(없으면 가용률이 작아진다)')
|
|
118
118
|
assert.match(body, /replayWithCheckpoint\(/, '씨앗이 없을 때도 새 재개점을 만든다')
|
|
@@ -122,7 +122,7 @@ test('폴드가 재개점을 쓰고, 새 재개점을 함께 남긴다', () => {
|
|
|
122
122
|
test('씨앗을 좁게 쓴다 — 캐시가 사실을 이기지 않는다', () => {
|
|
123
123
|
const body = methodOf('recover')
|
|
124
124
|
/* 지금을 물었을 때만·구조가 그대로일 때만. 시간여행은 목표 이전 재개점이 필요하고(사슬은 다음 단계),
|
|
125
|
-
구조가 바뀌면 그 경계에서 갈라
|
|
125
|
+
구조가 바뀌면 그 경계에서 갈라 계산해야 한다. */
|
|
126
126
|
assert.match(body, /asOfNowRead && tip && cachedForResume && cachedForResume\.fold/, '지금 읽기에만 쓴다')
|
|
127
127
|
assert.match(body, /\(cachedForResume\.structureRev \?\? null\) === tip\.structureRev/, '구조가 같을 때만 쓴다')
|
|
128
128
|
assert.match(body, /segments\.length <= 1/, '구조가 갈린 폴드에는 쓰지 않는다')
|
|
@@ -131,10 +131,10 @@ test('씨앗을 좁게 쓴다 — 캐시가 사실을 이기지 않는다', () =
|
|
|
131
131
|
/*
|
|
132
132
|
* ── 재개점 **사슬** (2026-08-18) ─────────────────────────────────────────────
|
|
133
133
|
* 최신 재개점 하나로는 과거를 도울 수 없다 — 그것은 언제나 목표보다 뒤에 있다. 그래서 지점을 여러 개
|
|
134
|
-
* 남기고, 과거를 물으면 **목표 직전** 지점에서
|
|
135
|
-
* 눈금(리비전 간격)을 넘을 때만 남기고 최근 몇 개만 든다 — 더 과거는 0부터
|
|
134
|
+
* 남기고, 과거를 물으면 **목표 직전** 지점에서 계산한다. 지점은 각각 상태 전체를 들고 있어 무겁기 때문에
|
|
135
|
+
* 눈금(리비전 간격)을 넘을 때만 남기고 최근 몇 개만 든다 — 더 과거는 0부터 계산한다(알려진 한계).
|
|
136
136
|
*/
|
|
137
|
-
test('과거 읽기는 목표 직전 지점에서
|
|
137
|
+
test('과거 읽기는 목표 직전 지점에서 계산한다 — 사슬을 쓴다', () => {
|
|
138
138
|
const body = methodOf('recover')
|
|
139
139
|
assert.match(body, /this\.chainSeedFor\(domainId, instanceId, \{/, '과거 씨앗을 사슬에서 고른다')
|
|
140
140
|
assert.match(body, /structureCount <= 1/, '구조가 갈린 트윈에서는 쓰지 않는다')
|
|
@@ -247,7 +247,7 @@ test('★ withLiveAttentions 는 채널을 하나도 떨어뜨리지 않는다(
|
|
|
247
247
|
|
|
248
248
|
/*
|
|
249
249
|
* ── 되짚은 상태에도 신호가 있어야 한다 (2026-08-05) ────────────────────────
|
|
250
|
-
* 라이브·시뮬은 커널이 신호를 스스로 내지만, 저널을
|
|
250
|
+
* 라이브·시뮬은 커널이 신호를 스스로 내지만, 저널을 계산하는 경로는 프로젝터 상태만 낸다 — 신호 키가
|
|
251
251
|
* 아예 없었다. 지도는 `snap.attentions` 를 읽으므로 **과거를 되짚으면 주의 레일이 텅 비었고**,
|
|
252
252
|
* 기동돼 있지 않은 트윈을 보는 화면도 같았다. 실 저널로 재 보니 `rosarito-wms` 는 246건이 비어 있었다.
|
|
253
253
|
*
|
|
@@ -260,7 +260,7 @@ test('★ 프로젝터 폴드 상태에는 신호 키가 없다 — 보강이
|
|
|
260
260
|
const proj = new StateProjector(model)
|
|
261
261
|
for (const env of events) proj.apply(env)
|
|
262
262
|
const raw: any = proj.snapshot()
|
|
263
|
-
assert.equal(raw.attentions, undefined, '
|
|
263
|
+
assert.equal(raw.attentions, undefined, '계산한 상태는 신호를 스스로 내지 않는다(커널이 아니다)')
|
|
264
264
|
})
|
|
265
265
|
|
|
266
266
|
test('★ 보강하면 다시 계산한 상태에도 신호가 있다 — 지도의 주의 레일이 비지 않게', () => {
|
|
@@ -274,6 +274,6 @@ test('★ 보강하면 다시 계산한 상태에도 신호가 있다 — 지도
|
|
|
274
274
|
tasks: []
|
|
275
275
|
}
|
|
276
276
|
const out = withLiveAttentions(st)
|
|
277
|
-
assert.ok((out.attentions ?? []).length > 0, '
|
|
277
|
+
assert.ok((out.attentions ?? []).length > 0, '계산한 상태에서도 규칙이 신호를 낸다')
|
|
278
278
|
assert.equal(out.attentions![0].kind, 'bottleneck')
|
|
279
279
|
})
|
package/test/load-meter.test.ts
CHANGED
|
@@ -84,7 +84,7 @@ test('경고는 쿨다운을 둔다 — 느린 트윈이 매 틱 경고하면
|
|
|
84
84
|
const slow = 900
|
|
85
85
|
|
|
86
86
|
assert.equal(judgeCycle(m, slow, TICK, 1000).warn, true, '처음엔 말한다')
|
|
87
|
-
assert.equal(judgeCycle(m, slow, TICK, 2000).warn, false, '쿨다운 안에는
|
|
87
|
+
assert.equal(judgeCycle(m, slow, TICK, 2000).warn, false, '쿨다운 안에는 알리지 않고')
|
|
88
88
|
assert.equal(judgeCycle(m, slow, TICK, 1000 + WARN_COOLDOWN_MS).warn, true, '쿨다운이 지나면 다시')
|
|
89
89
|
|
|
90
90
|
assert.equal(m.overBudget, 3, '말하지 않아도 세기는 한다')
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* 현장 선언 — **구조는 원천이, 수는 현장이.**
|
|
3
3
|
*
|
|
4
|
-
* 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이
|
|
4
|
+
* 이 시험이 지키는 것: 사용자가 넣은 수가 ① 검사를 거치고(단위를 잘못 읽은 값이 알리지 않고 들어가지 않는다)
|
|
5
5
|
* ② 원천에서 다시 그려도 살아남고 ③ 갈 곳이 없으면 그 사실이 남는다.
|
|
6
6
|
*/
|
|
7
7
|
import { test } from 'node:test'
|
|
@@ -122,7 +122,7 @@ test('같은 속성을 다시 넣으면 새 값이 이긴다 — 현장이 값
|
|
|
122
122
|
const second = mergeLocalDeclarations(first, [
|
|
123
123
|
{ target: 'location', id: 'incoming-1', properties: [{ id: 'tariff.demandChargePerKW', value: '24' }, { id: 'tariff.currency', value: 'USD' }] }
|
|
124
124
|
])
|
|
125
|
-
assert.equal(second.length, 1, '같은 대상은 한 줄로
|
|
125
|
+
assert.equal(second.length, 1, '같은 대상은 한 줄로 합쳐진다')
|
|
126
126
|
assert.deepEqual(second[0].properties.map(p => [p.id, p.value]), [
|
|
127
127
|
['tariff.demandChargePerKW', '24'],
|
|
128
128
|
['tariff.currency', 'USD']
|
|
@@ -154,7 +154,7 @@ test('현장 값이 원천 값을 덮는다 — 고쳐 넣었다는 것은 원
|
|
|
154
154
|
assert.equal(p.source, 'local')
|
|
155
155
|
})
|
|
156
156
|
|
|
157
|
-
test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을
|
|
157
|
+
test('대상이 사라졌으면 그 사실을 낸다 — 갈 곳 없는 선언을 알리지 않고 버리지 않는다', () => {
|
|
158
158
|
const { missingTargets } = applyLocalDeclarations(emsModel(), [
|
|
159
159
|
{ target: 'equipment', id: 'ess-gone', properties: [{ id: 'storage.capacityKWh', value: '500' }] }
|
|
160
160
|
])
|
|
@@ -191,7 +191,7 @@ test('경고 문장이 무엇을 해야 하는지 말한다 — 우리가 시간
|
|
|
191
191
|
* 필드 이름을 틀린 호출(예: id 대신 targetId)이 「받아준 선언이 없다」는 답만 받고 rejected 는 비어
|
|
192
192
|
* 있었다. 사용자는 값을 넣었다고 믿는데 화면은 아무것도 바뀌지 않고, 고칠 단서도 없다.
|
|
193
193
|
*/
|
|
194
|
-
test('대상 id 가 없으면 이유를 말한다 —
|
|
194
|
+
test('대상 id 가 없으면 이유를 말한다 — 알리지 않고 건너뛰지 않는다', () => {
|
|
195
195
|
const { accepted, rejected } = validateLocalDeclarations('ems', [
|
|
196
196
|
{ properties: [{ id: 'contract.kW', value: '3500' }] } as any
|
|
197
197
|
])
|
|
@@ -289,7 +289,7 @@ test('기억이 없는 옛 선언은 손대지 않고 그렇게 말한다 —
|
|
|
289
289
|
assert.equal((back.model.locations[0] as any).properties[0].value, '600', '값은 그대로다 — 다음 재읽기가 바로잡는다')
|
|
290
290
|
})
|
|
291
291
|
|
|
292
|
-
test('겹에 없는 것을 뺐다고 하지 않는다 —
|
|
292
|
+
test('겹에 없는 것을 뺐다고 하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
293
293
|
const { revoked, notFound } = revokeLocalDeclarations(
|
|
294
294
|
[{ target: 'location', id: 'incoming-1', properties: [{ id: 'contract.kW', value: '600', replaced: '3500' }] }],
|
|
295
295
|
[
|
|
@@ -309,7 +309,7 @@ test('겹에 없는 것을 뺐다고 하지 않는다 — 조용한 성공이
|
|
|
309
309
|
* 이고(SAP 는 `P3`, Oracle 은 `W01` 로 같은 현장을 부른다), 여기 담기는 것은 그 시스템이 **모르는**
|
|
310
310
|
* 값이다 — 이름이 내용과 정반대였다. 축은 「누가 말했나」이고 답은 `reference` ↔ `local` 둘이다.
|
|
311
311
|
*
|
|
312
|
-
* 낱말은
|
|
312
|
+
* 낱말은 알리지 않고 되돌아간다(복사·자동완성·옛 문서). 그래서 검사로 못 박는다.
|
|
313
313
|
*/
|
|
314
314
|
test('이 겹의 어휘는 local 이다 — site·facility 로 되돌아가지 않는다', () => {
|
|
315
315
|
const src = readFileSync(fileURLToPath(new URL('../server/engine/local-declarations.ts', import.meta.url)), 'utf-8')
|
|
@@ -430,7 +430,7 @@ test('구조 투영이 중복 id 를 이름으로 말한다 — 제약 위반
|
|
|
430
430
|
const src = readFileSync(fileURLToPath(new URL('../server/service/twin-model/project-structure.ts', import.meta.url)), 'utf-8')
|
|
431
431
|
assert.match(src, /has two \$\{what\} with id/, '무엇이 겹쳤는지 이름으로 말한다')
|
|
432
432
|
assert.match(src, /ids must be unique within a twin/, '규칙도 함께 말한다')
|
|
433
|
-
/*
|
|
433
|
+
/* 알리지 않고 하나만 남기지 않는다 — 어느 쪽이 맞는지 우리는 모른다. */
|
|
434
434
|
assert.equal(/dedupe|distinctBy/.test(src), false, '중복을 소리 없이 지우지 않는다')
|
|
435
435
|
})
|
|
436
436
|
|
|
@@ -508,7 +508,7 @@ test('지게차에 계약전력을 넣던 구멍이 막혔다 — 읽는 코드
|
|
|
508
508
|
assert.equal(rejected[0]?.reason, 'wrong-target')
|
|
509
509
|
})
|
|
510
510
|
|
|
511
|
-
test('확인할 수 있으면 오타 공정 이름을 거절한다 —
|
|
511
|
+
test('확인할 수 있으면 오타 공정 이름을 거절한다 — 알리지 않고 아무 일도 안 하지 않게', () => {
|
|
512
512
|
const decl = [{ target: 'operation' as const, id: 'put-away', properties: [{ id: 'operation.duration', value: '4', uom: 'MIN' }] }]
|
|
513
513
|
const checked = validateLocalDeclarations('wms', decl, { knownOperations: ['putaway', 'pick'] })
|
|
514
514
|
assert.deepEqual(checked.accepted, [])
|
|
@@ -534,7 +534,7 @@ test('철회하면 표에서 빠진다 — 원천 명세나 커널 상수가 다
|
|
|
534
534
|
assert.deepEqual(back.restored.length, 1)
|
|
535
535
|
})
|
|
536
536
|
|
|
537
|
-
test('없는 공정을 철회했다고 말하지 않는다 —
|
|
537
|
+
test('없는 공정을 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
538
538
|
const back = restoreRevoked({ locations: [], equipment: [], localDurations: { pick: 'PT60S' } }, [
|
|
539
539
|
{ target: 'operation', id: 'putaway', propertyId: 'operation.duration', replaced: null }
|
|
540
540
|
])
|
|
@@ -642,7 +642,7 @@ test('모수 철회는 모수 표에서 뺀다 — 시간 선언은 그대로
|
|
|
642
642
|
assert.deepEqual((back.model as any).localDurations, { weld: 'PT180S' }, '시간은 그대로')
|
|
643
643
|
})
|
|
644
644
|
|
|
645
|
-
test('없는 모수를 철회했다고 말하지 않는다 —
|
|
645
|
+
test('없는 모수를 철회했다고 말하지 않는다 — 성공이라 답하면서 아무 일도 하지 않는 것이 가장 비싸다', () => {
|
|
646
646
|
const back = restoreRevoked({ locations: [], equipment: [], localParams: { weld: { yield: '0.9' } } }, [
|
|
647
647
|
{ target: 'operation', id: 'weld', propertyId: 'operation.setupDuration', replaced: null }
|
|
648
648
|
])
|
package/test/log-stamp.test.ts
CHANGED
|
@@ -34,7 +34,7 @@ test('오류는 스택까지 남는다 — 「어디서 났나」를 잃지 않
|
|
|
34
34
|
assert.ok(out.includes('log-stamp.test'), '스택이 있어야 어느 자리에서 났는지 알 수 있다')
|
|
35
35
|
})
|
|
36
36
|
|
|
37
|
-
test('값이 없어도 있었다는 사실은 남긴다 —
|
|
37
|
+
test('값이 없어도 있었다는 사실은 남긴다 — 알리지 않고 지우지 않는다', () => {
|
|
38
38
|
assert.equal(line(['a', undefined, null, 0]), 'a undefined null 0')
|
|
39
39
|
const circular: any = { name: 'ㄱ' }
|
|
40
40
|
circular.self = circular
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* ② 표본이 모자라면 **배우지 않는다** — 비율은 드문 사건을 재는 값이라 작은 표본이 잡음을 만든다.
|
|
12
12
|
* ③ 0 도 사실이다 — 전량 불량이면 0 을 배운다(「값이 없다」와 다르다).
|
|
13
13
|
* ④ 모르는 종류에는 `undefined` — 그러면 커널이 선언값·상수로 내려간다.
|
|
14
|
-
* ⑤ 배우지 못한 것을 **밝힌다** —
|
|
14
|
+
* ⑤ 배우지 못한 것을 **밝힌다** — 알리지 않고 일부만 반영하지 않는다.
|
|
15
15
|
*/
|
|
16
16
|
import { test } from 'node:test'
|
|
17
17
|
import assert from 'node:assert/strict'
|
|
@@ -84,7 +84,7 @@ test('사슬 넷이 이어져 있다 — 판정→축→집계→비율', () =>
|
|
|
84
84
|
assert.match(hostSrc('engine/kpi-fold.ts'), /facets\.outcome/, 'KPI 가 판정 축을 읽는다')
|
|
85
85
|
assert.match(hostSrc('engine/kpi-fold.ts'), /quality\?: \{ good: number; scrap: number \}/, '종류별로 센다')
|
|
86
86
|
const engine = hostSrc('engine/twin-engine.ts')
|
|
87
|
-
assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번
|
|
87
|
+
assert.match(engine, /buildYieldEstimator\(kpi\?\.groups\?\.items/, '같은 폴드에서 배운다(두 번 계산하지 않는다)')
|
|
88
88
|
assert.match(engine, /kernel\.yieldEstimator = yields\.estimator/, '커널 시임에 넣는다')
|
|
89
89
|
assert.match(engine, /yieldOf missing/, '옛 커널이면 그 사실을 말한다')
|
|
90
90
|
})
|