@things-factory/headless-twin 10.0.15 → 10.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/attention-digest.d.ts +4 -4
- package/dist-server/engine/attention-digest.js +7 -7
- package/dist-server/engine/attention-digest.js.map +1 -1
- package/dist-server/engine/canonical-ingest.d.ts +18 -82
- package/dist-server/engine/canonical-ingest.js +183 -18
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.js +1 -1
- package/dist-server/engine/declared-stimulus.js.map +1 -1
- package/dist-server/engine/energy-topology.js +4 -4
- package/dist-server/engine/energy-topology.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +38 -0
- package/dist-server/engine/ingest-dedupe.js +145 -0
- package/dist-server/engine/ingest-dedupe.js.map +1 -0
- package/dist-server/engine/ingest-health.d.ts +90 -10
- package/dist-server/engine/ingest-health.js +125 -16
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/integration-probes.d.ts +3 -3
- package/dist-server/engine/integration-probes.js +6 -6
- package/dist-server/engine/integration-probes.js.map +1 -1
- package/dist-server/engine/kpi-baseline.js +4 -4
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +212 -14
- package/dist-server/engine/kpi-fold.js +374 -21
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +11 -11
- package/dist-server/engine/kpi-query.js +281 -36
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +1 -1
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +7 -7
- package/dist-server/engine/local-declarations.js +15 -14
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +1 -1
- package/dist-server/engine/log.js +1 -1
- package/dist-server/engine/log.js.map +1 -1
- package/dist-server/engine/measured-estimator.js +1 -1
- package/dist-server/engine/measured-estimator.js.map +1 -1
- package/dist-server/engine/measured-yield.js +1 -1
- package/dist-server/engine/measured-yield.js.map +1 -1
- package/dist-server/engine/model-basis.d.ts +1 -1
- package/dist-server/engine/model-basis.js +1 -1
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-gap.d.ts +3 -3
- package/dist-server/engine/model-gap.js +1 -1
- package/dist-server/engine/model-gap.js.map +1 -1
- package/dist-server/engine/model-vocabulary.js +3 -3
- package/dist-server/engine/model-vocabulary.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +6 -5
- package/dist-server/engine/oee-accumulator.js +6 -5
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/property-effects.js +2 -2
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/read-time.d.ts +22 -0
- package/dist-server/engine/read-time.js +81 -0
- package/dist-server/engine/read-time.js.map +1 -0
- package/dist-server/engine/restart-policy.js +1 -1
- package/dist-server/engine/restart-policy.js.map +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +2 -2
- package/dist-server/engine/spec-coverage.js +1 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +4 -4
- package/dist-server/engine/stage-path.js +1 -1
- package/dist-server/engine/stage-path.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +1 -1
- package/dist-server/engine/state-axes.js +2 -2
- package/dist-server/engine/state-axes.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +2 -2
- package/dist-server/engine/travel-estimator.js +1 -1
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +307 -54
- package/dist-server/engine/twin-engine.js +822 -196
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +2 -2
- package/dist-server/engine/warm-start.js +5 -5
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.js +1 -1
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +3 -3
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -1
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +2 -2
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -1
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.d.ts +5 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js +54 -0
- package/dist-server/migrations/1786300000000-IndexEventTypeByTime.js.map +1 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.d.ts +5 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js +44 -0
- package/dist-server/migrations/1786400000000-IndexEventCreatedAt.js.map +1 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.d.ts +6 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js +84 -0
- package/dist-server/migrations/1786500000000-CreateTwinSubjectEvents.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.d.ts +1 -0
- package/dist-server/routes.js +69 -5
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/service/index.js +30 -24
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +1 -1
- package/dist-server/service/reference/control-routing.js +1 -1
- package/dist-server/service/reference/control-routing.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +2 -2
- package/dist-server/service/reference/discovery-result.js +3 -3
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/hook-contract.d.ts +43 -0
- package/dist-server/service/reference/hook-contract.js +59 -0
- package/dist-server/service/reference/hook-contract.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +1 -1
- package/dist-server/service/reference/ingest-space.js +4 -4
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/knob-defaults.d.ts +1 -1
- package/dist-server/service/reference/knob-defaults.js +4 -4
- package/dist-server/service/reference/knob-defaults.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +116 -9
- package/dist-server/service/reference/reference-adapter.js +27 -0
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-hook.d.ts +20 -0
- package/dist-server/service/reference/reference-hook.js +133 -0
- package/dist-server/service/reference/reference-hook.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +2 -2
- package/dist-server/service/reference/reference-live.js +76 -13
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +25 -16
- package/dist-server/service/reference/reference-master.js +35 -13
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +1 -1
- package/dist-server/service/reference/reference-resolver.js +12 -12
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +1 -1
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +3 -3
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +1 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js +1 -1
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -1
- package/dist-server/service/twin-backfill/backfill-period-facts.d.ts +24 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js +96 -0
- package/dist-server/service/twin-backfill/backfill-period-facts.js.map +1 -0
- package/dist-server/service/twin-backfill/backfill-shape.d.ts +34 -0
- package/dist-server/service/twin-backfill/backfill-shape.js +75 -0
- package/dist-server/service/twin-backfill/backfill-shape.js.map +1 -0
- package/dist-server/service/twin-backfill/index.d.ts +2 -0
- package/dist-server/service/twin-backfill/index.js +6 -0
- package/dist-server/service/twin-backfill/index.js.map +1 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.d.ts +10 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js +44 -0
- package/dist-server/service/twin-backfill/twin-backfill-resolver.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +3 -3
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js +2 -2
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event-type.js +8 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +2 -2
- package/dist-server/service/twin-event/twin-event.js +32 -4
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +1 -1
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +29 -11
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +1 -1
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/event-type-split-shape.d.ts +50 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js +111 -0
- package/dist-server/service/twin-journal/event-type-split-shape.js.map +1 -0
- package/dist-server/service/twin-journal/event-type-split.d.ts +15 -0
- package/dist-server/service/twin-journal/event-type-split.js +102 -0
- package/dist-server/service/twin-journal/event-type-split.js.map +1 -0
- package/dist-server/service/twin-journal/journal-order.d.ts +15 -0
- package/dist-server/service/twin-journal/journal-order.js +17 -0
- package/dist-server/service/twin-journal/journal-order.js.map +1 -0
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -5
- package/dist-server/service/twin-journal/twin-journal-query.js +232 -21
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js +2 -1
- package/dist-server/service/twin-lifecycle/domain-catalog.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +27 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +59 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js +14 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +34 -3
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +12 -9
- package/dist-server/service/twin-model/isa95-coverage.js +33 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.js +2 -2
- package/dist-server/service/twin-model/item-ref.js.map +1 -1
- package/dist-server/service/twin-model/name-index.d.ts +1 -1
- package/dist-server/service/twin-model/name-index.js +2 -2
- package/dist-server/service/twin-model/name-index.js.map +1 -1
- package/dist-server/service/twin-model/project-structure.d.ts +1 -1
- package/dist-server/service/twin-model/project-structure.js +7 -6
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +2 -2
- package/dist-server/service/twin-model/twin-equipment.js +3 -3
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +16 -5
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-location.d.ts +1 -1
- package/dist-server/service/twin-model/twin-location.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +3 -3
- package/dist-server/service/twin-model/twin-model-item-query.js +15 -15
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +2 -2
- package/dist-server/service/twin-model/twin-model-mutation.js +4 -4
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +51 -30
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +23 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-operation.d.ts +1 -1
- package/dist-server/service/twin-model/twin-operation.js +1 -1
- package/dist-server/service/twin-model/twin-operation.js.map +1 -1
- package/dist-server/service/twin-space/move-space.js +2 -2
- package/dist-server/service/twin-space/move-space.js.map +1 -1
- package/dist-server/service/twin-space/representation-role.d.ts +2 -0
- package/dist-server/service/twin-space/representation-role.js +18 -0
- package/dist-server/service/twin-space/representation-role.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +1 -1
- package/dist-server/service/twin-space/space-integrity.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.d.ts +2 -0
- package/dist-server/service/twin-space/twin-space-representation.js +16 -0
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +37 -8
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +3 -3
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +43 -0
- package/dist-server/service/twin-subject/event-subjects.js +137 -0
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -0
- package/dist-server/service/twin-subject/index.d.ts +9 -0
- package/dist-server/service/twin-subject/index.js +16 -0
- package/dist-server/service/twin-subject/index.js.map +1 -0
- package/dist-server/service/twin-subject/item-resolver.d.ts +10 -0
- package/dist-server/service/twin-subject/item-resolver.js +93 -0
- package/dist-server/service/twin-subject/item-resolver.js.map +1 -0
- package/dist-server/service/twin-subject/subject-query-shape.d.ts +22 -0
- package/dist-server/service/twin-subject/subject-query-shape.js +88 -0
- package/dist-server/service/twin-subject/subject-query-shape.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.d.ts +23 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js +159 -0
- package/dist-server/service/twin-subject/twin-subject-backfill.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-event.d.ts +14 -0
- package/dist-server/service/twin-subject/twin-subject-event.js +111 -0
- package/dist-server/service/twin-subject/twin-subject-event.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.d.ts +39 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js +295 -0
- package/dist-server/service/twin-subject/twin-subject-fast-path.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-query.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-query.js +232 -0
- package/dist-server/service/twin-subject/twin-subject-query.js.map +1 -0
- package/dist-server/service/twin-subject/twin-subject-writer.d.ts +27 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js +166 -0
- package/dist-server/service/twin-subject/twin-subject-writer.js.map +1 -0
- package/dist-server/service/twin-target/twin-target-resolver.js +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +1 -1
- package/dist-shared/entity-delta.js +4 -4
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/touched-items.js +2 -2
- package/dist-shared/touched-items.js.map +1 -1
- package/dist-shared/twin-level.js +1 -1
- package/dist-shared/twin-level.js.map +1 -1
- package/package.json +7 -6
- package/server/engine/attention-digest.ts +9 -9
- package/server/engine/canonical-ingest.ts +202 -87
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +1 -1
- package/server/engine/energy-topology.ts +2 -2
- package/server/engine/ingest-dedupe.ts +149 -0
- package/server/engine/ingest-health.ts +171 -22
- package/server/engine/integration-probes.ts +7 -7
- package/server/engine/kpi-baseline.ts +4 -4
- package/server/engine/kpi-fold.ts +592 -41
- package/server/engine/kpi-query.ts +307 -48
- package/server/engine/live-feed-registry.ts +1 -1
- package/server/engine/load-meter.ts +1 -1
- package/server/engine/local-declarations.ts +15 -14
- package/server/engine/log.ts +1 -1
- package/server/engine/measured-estimator.ts +1 -1
- package/server/engine/measured-yield.ts +1 -1
- package/server/engine/model-basis.ts +2 -2
- package/server/engine/model-gap.ts +3 -3
- package/server/engine/model-vocabulary.ts +2 -2
- package/server/engine/oee-accumulator.ts +8 -6
- package/server/engine/property-effects.ts +2 -2
- package/server/engine/read-time.ts +77 -0
- package/server/engine/restart-policy.ts +1 -1
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/stage-path.ts +5 -5
- package/server/engine/state-axes.ts +2 -2
- package/server/engine/travel-estimator.ts +2 -2
- package/server/engine/twin-engine.ts +906 -205
- package/server/engine/warm-start.ts +6 -6
- package/server/index.ts +1 -1
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +1 -1
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +3 -3
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +2 -2
- package/server/migrations/1786300000000-IndexEventTypeByTime.ts +48 -0
- package/server/migrations/1786400000000-IndexEventCreatedAt.ts +38 -0
- package/server/migrations/1786500000000-CreateTwinSubjectEvents.ts +93 -0
- package/server/migrations/index.ts +7 -1
- package/server/routes.ts +73 -5
- package/server/service/index.ts +7 -1
- package/server/service/reference/control-routing.ts +1 -1
- package/server/service/reference/discovery-result.ts +3 -3
- package/server/service/reference/hook-contract.ts +103 -0
- package/server/service/reference/ingest-space.ts +4 -4
- package/server/service/reference/knob-defaults.ts +4 -4
- package/server/service/reference/reference-adapter.ts +145 -10
- package/server/service/reference/reference-hook.ts +145 -0
- package/server/service/reference/reference-live.ts +87 -15
- package/server/service/reference/reference-master.ts +51 -20
- package/server/service/reference/reference-resolver.ts +11 -11
- package/server/service/reference/template-registry.ts +1 -1
- package/server/service/reference/twin-reference.ts +3 -3
- package/server/service/twin-attention/twin-attention-query.ts +1 -1
- package/server/service/twin-audit/command-audit.ts +1 -1
- package/server/service/twin-audit/twin-audit-event.ts +2 -2
- package/server/service/twin-audit/twin-audit-query.ts +1 -1
- package/server/service/twin-backfill/backfill-period-facts.ts +168 -0
- package/server/service/twin-backfill/backfill-shape.ts +86 -0
- package/server/service/twin-backfill/index.ts +3 -0
- package/server/service/twin-backfill/twin-backfill-resolver.ts +35 -0
- package/server/service/twin-control/twin-control-mutation.ts +2 -2
- package/server/service/twin-event/twin-event-keys.ts +2 -2
- package/server/service/twin-event/twin-event-type.ts +18 -1
- package/server/service/twin-event/twin-event.ts +32 -6
- package/server/service/twin-forecast/forecast-metrics.ts +1 -1
- package/server/service/twin-forecast/gap-analytics.ts +1 -1
- package/server/service/twin-forecast/twin-forecast-query.ts +30 -11
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +1 -1
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +1 -1
- package/server/service/twin-instance/twin-instance.ts +4 -4
- package/server/service/twin-journal/event-type-split-shape.ts +112 -0
- package/server/service/twin-journal/event-type-split.ts +144 -0
- package/server/service/twin-journal/journal-order.ts +20 -0
- package/server/service/twin-journal/twin-journal-query.ts +224 -20
- package/server/service/twin-lifecycle/domain-catalog.ts +2 -1
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +58 -4
- package/server/service/twin-metrics/twin-metrics-query.ts +14 -1
- package/server/service/twin-model/axis-journal-evidence.ts +1 -1
- package/server/service/twin-model/epcis-coverage.ts +34 -3
- package/server/service/twin-model/iec61850-coverage.ts +1 -1
- package/server/service/twin-model/isa95-coverage.ts +43 -16
- package/server/service/twin-model/item-ref.ts +2 -2
- package/server/service/twin-model/name-index.ts +2 -2
- package/server/service/twin-model/project-structure.ts +7 -6
- package/server/service/twin-model/twin-equipment.ts +16 -4
- package/server/service/twin-model/twin-lineage-query.ts +16 -5
- package/server/service/twin-model/twin-location.ts +1 -1
- package/server/service/twin-model/twin-model-item-query.ts +15 -15
- package/server/service/twin-model/twin-model-mutation.ts +4 -4
- package/server/service/twin-model/twin-model-query.ts +49 -28
- package/server/service/twin-model/twin-model-tree-query.ts +26 -2
- package/server/service/twin-model/twin-operation.ts +2 -2
- package/server/service/twin-space/move-space.ts +3 -3
- package/server/service/twin-space/representation-role.ts +15 -0
- package/server/service/twin-space/space-integrity.ts +1 -1
- package/server/service/twin-space/twin-space-representation.ts +37 -0
- package/server/service/twin-space/twin-space-resolver.ts +40 -8
- package/server/service/twin-structure/twin-structure.ts +3 -3
- package/server/service/twin-subject/event-subjects.ts +155 -0
- package/server/service/twin-subject/index.ts +10 -0
- package/server/service/twin-subject/item-resolver.ts +92 -0
- package/server/service/twin-subject/subject-query-shape.ts +84 -0
- package/server/service/twin-subject/twin-subject-backfill.ts +196 -0
- package/server/service/twin-subject/twin-subject-event.ts +101 -0
- package/server/service/twin-subject/twin-subject-fast-path.ts +346 -0
- package/server/service/twin-subject/twin-subject-query.ts +210 -0
- package/server/service/twin-subject/twin-subject-writer.ts +189 -0
- package/server/service/twin-target/twin-target-resolver.ts +1 -1
- package/shared/entity-delta.ts +4 -4
- package/shared/touched-items.ts +2 -2
- package/shared/twin-level.ts +1 -1
- package/test/adopt-structure-live.test.ts +3 -3
- package/test/aggregate-not-findone.test.ts +112 -0
- package/test/attention-digest.test.ts +6 -6
- package/test/axis-read.test.ts +6 -5
- package/test/backfill-shape.test.ts +89 -0
- package/test/boot-resume.test.ts +20 -7
- package/test/broadcast-cost-baseline.test.ts +2 -1
- package/test/broadcast-period.test.ts +2 -2
- package/test/broadcast-queue-overflow.test.ts +244 -0
- package/test/canonical-ingest-vocabularies.test.ts +259 -8
- package/test/canonical-quantity-door.test.ts +2 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/checkpoint-refuses-empty.test.ts +2 -2
- package/test/connector-capability-declaration.test.ts +88 -0
- package/test/contract-layer-guard.test.ts +48 -0
- package/test/control-capability.test.ts +1 -1
- package/test/cursor-stall-not-read-failure.test.ts +2 -2
- package/test/declaration-reaches-model.test.ts +7 -5
- package/test/declared-stimulus.test.ts +4 -4
- package/test/discovery-result.test.ts +2 -2
- package/test/entity-delta.test.ts +4 -4
- package/test/equipment-identity-reaches.test.ts +57 -0
- package/test/event-subjects.test.ts +147 -0
- package/test/event-time-column.test.ts +3 -3
- package/test/event-type-split.test.ts +155 -0
- package/test/fact-scope-wiring.test.ts +96 -0
- package/test/fold-gap-speaks.test.ts +53 -0
- package/test/forecast-metrics.test.ts +1 -1
- package/test/forecast-tuning.test.ts +1 -1
- package/test/generated-not-counted.test.ts +99 -0
- package/test/ingest-bench.test.ts +2 -1
- package/test/ingest-health-engine.test.ts +2 -2
- package/test/ingest-health-wiring.test.ts +7 -5
- package/test/ingest-health.test.ts +6 -6
- package/test/ingest-history.test.ts +2 -2
- package/test/ingest-idempotent.test.ts +132 -0
- package/test/ingest-space.test.ts +3 -3
- package/test/ingest-wiring-guard.test.ts +197 -0
- package/test/integration-probes.test.ts +3 -3
- package/test/integration-runner.test.ts +1 -1
- package/test/item-ref.test.ts +1 -1
- package/test/journal-order-axis.test.ts +80 -0
- package/test/journal-read-discipline.test.ts +16 -6
- package/test/journal-retention.test.ts +171 -29
- package/test/journal-sort-axis.test.ts +1 -1
- package/test/journal-write-door.test.ts +3 -3
- package/test/journal-write-trend.test.ts +1 -1
- package/test/kernel-kind-guard.test.ts +3 -3
- package/test/knob-defaults.test.ts +1 -1
- package/test/kpi-baseline-db.test.ts +2 -2
- package/test/kpi-fold.test.ts +266 -14
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/live-cursor-wiring.test.ts +6 -6
- package/test/live-kernel-facts.test.ts +11 -11
- package/test/live-mirror-parity.test.ts +9 -6
- package/test/load-meter.test.ts +1 -1
- package/test/local-declarations.test.ts +13 -12
- package/test/log-stamp.test.ts +1 -1
- package/test/master-to-twin.test.ts +2 -2
- package/test/measured-yield.test.ts +2 -2
- package/test/merge-streams.test.ts +70 -0
- package/test/mirror-resumes-from-checkpoint.test.ts +13 -13
- package/test/model-basis.test.ts +2 -2
- package/test/model-gap.test.ts +4 -4
- package/test/model-vocabulary.test.ts +2 -2
- package/test/move-space.test.ts +1 -1
- package/test/oee-accumulator.test.ts +20 -5
- package/test/operational-vocabulary.test.ts +2 -2
- package/test/project-structure-db.test.ts +2 -2
- package/test/projection-reaches-screen.test.ts +7 -2
- package/test/projection-reads-declared.test.ts +109 -0
- package/test/property-effects.test.ts +10 -6
- package/test/read-failure-ledger.test.ts +106 -0
- package/test/read-failure-visible.test.ts +7 -7
- package/test/read-time.test.ts +118 -0
- package/test/reference-grounding.test.ts +1 -1
- package/test/reference-hook.test.ts +86 -0
- package/test/registry-key-guard.test.ts +1 -1
- package/test/rename-twin.test.ts +81 -0
- package/test/restart-policy.test.ts +3 -3
- package/test/resync-origin-site.test.ts +3 -3
- package/test/revision-axis.test.ts +14 -8
- package/test/runtime-key.test.ts +3 -3
- package/test/scale-twin-bench.test.ts +2 -1
- package/test/snapshot-freshness.test.ts +4 -4
- package/test/source-outcome-audit.test.ts +1 -1
- package/test/space-integrity.test.ts +1 -1
- package/test/space-representation.test.ts +66 -0
- package/test/space-scope-includes-stopped.test.ts +55 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/standard-coverage.test.ts +19 -21
- package/test/status-tally.test.ts +1 -1
- package/test/structure-revision-db.test.ts +7 -7
- package/test/subject-fast-path.test.ts +148 -0
- package/test/subject-read-model.test.ts +175 -0
- package/test/touched-items.test.ts +2 -2
- package/test/twin-audit.test.ts +1 -1
- package/test/twin-event-keys.test.ts +1 -1
- package/test/twin-model-item-db.test.ts +5 -5
- package/test/twin-model-tree-db.test.ts +35 -2
- package/test/twin-origin-resync.test.ts +1 -1
- package/test/vocabulary-guard.test.ts +1 -1
- package/test/warm-start-seam.test.ts +1 -1
- package/test/warm-start.test.ts +6 -6
- package/test/withheld-door.test.ts +2 -2
- package/test/yield-loop.test.ts +4 -4
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
type EnergyEquipmentRecord, type OperationalRecord, type IngestResult } from '@operato/twin-kernel'
|
|
1
|
+
import { type CanonicalRecord, type CanonicalAggregationRecord, type CanonicalTransformationRecord, type AdapterRule, type EnergyRecord, type EnergyEquipmentRecord, type EnergyGenerationRecord, type EnergyUsagePeriodRecord, type EnergyBillRecord, type EnergyTariffBasisRecord, type EnergyGenerationPriceRecord, type EnergyGenerationPeriodRecord, type OperationalRecord, type IngestResult, type MasterDataRecord } from '@operato/ops-contract'
|
|
2
|
+
import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, ingestEnergyGenerationRecords, ingestEnergyUsagePeriodRecords, ingestEnergyBillRecords, ingestEnergyTariffBasisRecords, ingestEnergyGenerationPriceRecords, ingestEnergyGenerationPeriodRecords, ingestOperationalRecords, isAggregationRecord, isTransformationRecord, isEnergyRecord, isEnergyEquipmentRecord, isEnergyGenerationRecord, isEnergyUsagePeriodRecord, isEnergyBillRecord, isEnergyTariffBasisRecord, isEnergyGenerationPriceRecord, isEnergyGenerationPeriodRecord, isOperationalRecord, isMasterDataRecord, ingestMasterData } from '@operato/ops-contract'
|
|
4
3
|
|
|
5
4
|
/*
|
|
6
5
|
* 정규 레코드 → 커널 검증 ACL → CanonicalEnvelope (P1 seam 코어).
|
|
@@ -9,6 +8,9 @@ import { ingest, ingestEnergyRecords, ingestEnergyEquipmentRecords, ingestOperat
|
|
|
9
8
|
* 여기 도착하는 레코드는 이미 정규 필드다 — 어휘마다 자기 필드 이름이 계약이다:
|
|
10
9
|
* EPCIS 품목 { epc, action, bizStep, disposition?, readPoint?, bizLocation? }
|
|
11
10
|
* 에너지 계량 { meterId, kW?, kWh?, … } · 설비 에너지 { equipmentId, generatedKW?, soc?, … }
|
|
11
|
+
* 발전 적산 { equipmentId, kWh, kWhSince?, … }
|
|
12
|
+
* 사용 구간 { meterId, from, to, kWh, unitPrice?, basis?, … } · 청구서 { from, to, total?, … }
|
|
13
|
+
* 요금 기준 { from, to, billingDemandKW?, demandChargePerKW?, currency }
|
|
12
14
|
* 담김(적재) { parentID, childEPCs[] | childQuantityList[], action, bizStep, … }
|
|
13
15
|
* 운영 사실 { taskId | moverId | personId | assetId | orderId, status, … } // vocabulary-guard: allow 저널 와이어 필드
|
|
14
16
|
* 커널은 objectEvent 구성 + EPCIS 검증(위반=오염 차단) + 봉투 래핑만(방언 없음).
|
|
@@ -38,10 +40,25 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
|
|
|
38
40
|
* 재고 950건·59,785kg 이 어디에도 나타나지 않았고, 빈 화면이 「이상 없음」으로 읽혔다.
|
|
39
41
|
*
|
|
40
42
|
* ★ **표준이 `action: 'ADD'` 일 때만 허용한다**(또는 변환). 마스터데이터는 로트가 **생길 때**
|
|
41
|
-
* 말하는 사실이고, 주기 관측마다 실으면 커널이 거부한다 —
|
|
43
|
+
* 말하는 사실이고, 주기 관측마다 실으면 커널이 거부한다 — 알리지 않고 통과시키지 않는 것이 옳다
|
|
42
44
|
* (커넥터가 첫 목격에 `ADD`, 그 뒤로 `OBSERVE` 를 내야 한다).
|
|
43
45
|
*/
|
|
44
46
|
ilmd: '$.ilmd',
|
|
47
|
+
/*
|
|
48
|
+
* **정정 선언** — 「이 사건은 없던 일이 되었다」(커널 0.7.65 에서 이 자리가 열렸다).
|
|
49
|
+
*
|
|
50
|
+
* 세 값이 함께여야 뜻이 있어 묶음으로 온다. 커널이 선언 시각을 요구하고, 없으면 사건 시각으로
|
|
51
|
+
* 메우지 않고 이유와 함께 돌려준다 — 「언제 정정했나」와 「언제 일어났나」가 같아지면 둘을 다시
|
|
52
|
+
* 갈라낼 수 없다.
|
|
53
|
+
*
|
|
54
|
+
* 상태는 이것으로 흔들리지 않는다. 커널은 정정을 목록에 적고 재고를 그대로 둔다 — 무엇을 어떻게
|
|
55
|
+
* 되돌릴지는 도메인 판단이고, 참 재고는 그 뒤의 잔량 관측이 다시 말해 준다.
|
|
56
|
+
*/
|
|
57
|
+
errorDeclaration: {
|
|
58
|
+
declarationTime: '$.errorDeclaration.declarationTime',
|
|
59
|
+
reason: '$.errorDeclaration.reason',
|
|
60
|
+
correctiveEventIDs: '$.errorDeclaration.correctiveEventIDs'
|
|
61
|
+
},
|
|
45
62
|
readPoint: '$.readPoint',
|
|
46
63
|
bizLocation: '$.bizLocation'
|
|
47
64
|
}
|
|
@@ -58,6 +75,11 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
|
|
|
58
75
|
outputEPCList: '$.outputEPCList',
|
|
59
76
|
outputQuantityList: '$.outputQuantityList',
|
|
60
77
|
transformationID: '$.transformationID',
|
|
78
|
+
errorDeclaration: {
|
|
79
|
+
declarationTime: '$.errorDeclaration.declarationTime',
|
|
80
|
+
reason: '$.errorDeclaration.reason',
|
|
81
|
+
correctiveEventIDs: '$.errorDeclaration.correctiveEventIDs'
|
|
82
|
+
},
|
|
61
83
|
readPoint: '$.readPoint',
|
|
62
84
|
bizLocation: '$.bizLocation'
|
|
63
85
|
}
|
|
@@ -78,84 +100,27 @@ const TWIN_INGEST_RULES: AdapterRule[] = [
|
|
|
78
100
|
childEPCs: '$.childEPCs',
|
|
79
101
|
/* 낱개 식별자 없이 **수량으로** 담긴 자식(「이 품번 40개」) — 입고·포장이 이 모양으로 온다. */
|
|
80
102
|
childQuantityList: '$.childQuantityList',
|
|
103
|
+
errorDeclaration: {
|
|
104
|
+
declarationTime: '$.errorDeclaration.declarationTime',
|
|
105
|
+
reason: '$.errorDeclaration.reason',
|
|
106
|
+
correctiveEventIDs: '$.errorDeclaration.correctiveEventIDs'
|
|
107
|
+
},
|
|
81
108
|
readPoint: '$.readPoint',
|
|
82
109
|
bizLocation: '$.bizLocation'
|
|
83
110
|
}
|
|
84
111
|
}
|
|
85
112
|
]
|
|
86
113
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* 개체 하나의 식별자. **`quantityList` 를 쓰면 없어도 된다** — 낱개 번호가 없는 자재(밀가루 3.5kg)는
|
|
91
|
-
* 개체가 없다. 둘 다 없으면 무엇을 관측했는지 말하지 않은 것이므로 커널 매핑이 거부한다.
|
|
92
|
-
*/
|
|
93
|
-
epc?: string
|
|
94
|
-
/**
|
|
95
|
-
* 클래스와 수량 — `{ epcClass, quantity, uom? }`. **잔량 절대값을 싣는다**(「얼마 뺐다」가 아니라
|
|
96
|
-
* 「얼마 남았다」). 그래야 사건 하나를 놓쳐도 다음 사건이 정답을 다시 말해 주고 오차가 쌓이지 않는다.
|
|
97
|
-
*
|
|
98
|
-
* 담김(`childQuantityList`)이 하루 먼저 이 자리를 받았다 — 그때와 같은 이유다. 원본이 비직렬 수량을
|
|
99
|
-
* 말할 길이 없어서, 그것이 주된 경로인 시스템(식품 제조 MES)은 라이브를 낼 수 없었다. 커널의
|
|
100
|
-
* 관측 리듀서는 처음부터 이 값을 읽었고 문만 없었다.
|
|
101
|
-
*/
|
|
102
|
-
quantityList?: { epcClass: string; quantity?: number; uom?: string }[]
|
|
103
|
-
action: string // ADD | OBSERVE | DELETE
|
|
104
|
-
bizStep: string
|
|
105
|
-
disposition?: string
|
|
106
|
-
readPoint?: string
|
|
107
|
-
bizLocation?: string
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* 담김 관측 — **부모와 자식**으로 말한다. 자리 필드는 개체 관측과 같은 이름이다(문자열).
|
|
112
|
-
*
|
|
113
|
-
* 이름은 표준의 것을 그대로 쓴다(`parentID`·`childEPCs`) — 여기서 다시 지으면 매핑이 번역이 되고,
|
|
114
|
-
* 같은 사실에 두 어휘가 생긴다.
|
|
115
|
-
*/
|
|
116
|
-
/**
|
|
117
|
-
* 변환 관측 — **무엇으로 무엇이 되었나.** 입력과 출력이 **한 사실에 함께** 있어야 계보가 남는다.
|
|
118
|
-
*
|
|
119
|
-
* 따로 보내면(「이 로트가 없어졌다」와 「이 제품이 생겼다」) 그 둘을 이을 근거가 없다. 그래서 표준이
|
|
120
|
-
* 한 사건에 둘을 담고, 우리 레코드도 같은 모양이다.
|
|
121
|
-
*
|
|
122
|
-
* 이름은 **표준의 것을 그대로** 쓴다(`inputQuantityList` 등). 갈래 판정이 이 이름으로 이루어지므로
|
|
123
|
-
* 커넥터 낱말(`consumed`·`produced`)로 담으면 판정되지 않는다 — 커넥터가 정규 이름으로 담아 보내고
|
|
124
|
-
* 매핑은 그 안에서 값을 고른다.
|
|
114
|
+
/*
|
|
115
|
+
* 정규 레코드 셋(`CanonicalRecord` · `CanonicalTransformationRecord` · `CanonicalAggregationRecord`)은
|
|
116
|
+
* **계약으로 옮겼다**(2026-08-30, `@operato/ops-contract` 0.2.0).
|
|
125
117
|
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
118
|
+
* 여기 있던 이유는 소비 방식 때문이었다 — EPCIS 는 커널의 범용 어댑터를 지나서 타입을 요구하지 않았다.
|
|
119
|
+
* 그런데 모양 자체는 EPCIS 2.0 의 필드 이름이고 호스트의 것이 아니다. 이제 MES 가 이 모양 그대로
|
|
120
|
+
* 보낸다.
|
|
128
121
|
*
|
|
129
|
-
* `
|
|
130
|
-
* 그 둘을 묶는 유일한 자리다.
|
|
122
|
+
* **매핑 룰(`TWIN_INGEST_RULES`)은 여기 남는다** — 이 모양을 EPCIS 사건으로 옮기는 정책이지 모양이 아니다.
|
|
131
123
|
*/
|
|
132
|
-
export interface CanonicalTransformationRecord {
|
|
133
|
-
/** 개체로 소비된 것. */
|
|
134
|
-
inputEPCList?: string[]
|
|
135
|
-
/** 클래스와 수량으로 소비된 것 — 로트 단위 소비가 이 자리다. */
|
|
136
|
-
inputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
|
|
137
|
-
outputEPCList?: string[]
|
|
138
|
-
outputQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
|
|
139
|
-
/** 한 오더의 여러 단계를 잇는 식별자. */
|
|
140
|
-
transformationID?: string
|
|
141
|
-
bizStep: string
|
|
142
|
-
disposition?: string
|
|
143
|
-
readPoint?: string
|
|
144
|
-
bizLocation?: string
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export interface CanonicalAggregationRecord {
|
|
148
|
-
parentID: string
|
|
149
|
-
/** 개체로 담긴 자식. 수량으로만 담겼으면 없다(둘 중 하나는 있어야 한다). */
|
|
150
|
-
childEPCs?: string[]
|
|
151
|
-
/** 수량으로 담긴 자식 — `{ epcClass, quantity, uom? }`. 값의 옳고 그름은 커널 검증이 본다. */
|
|
152
|
-
childQuantityList?: { epcClass: string; quantity?: number; uom?: string }[]
|
|
153
|
-
action: string // ADD(담다) | DELETE(풀다) | OBSERVE
|
|
154
|
-
bizStep: string
|
|
155
|
-
disposition?: string
|
|
156
|
-
readPoint?: string
|
|
157
|
-
bizLocation?: string
|
|
158
|
-
}
|
|
159
124
|
|
|
160
125
|
/** 정규 레코드[] → 검증된 CanonicalEnvelope[] (+ 거부분). 순수 함수. 단일→객체도 배열로 정규화. */
|
|
161
126
|
export function ingestCanonicalRecords(
|
|
@@ -166,13 +131,50 @@ export function ingestCanonicalRecords(
|
|
|
166
131
|
| EnergyRecord
|
|
167
132
|
| EnergyEquipmentRecord
|
|
168
133
|
| OperationalRecord
|
|
169
|
-
|
|
134
|
+
/* 다섯째 어휘 — 사건이 아닌 것(변하지 않는 속성). 저널로 가지 않는다(§`CanonicalIngestResult`). */
|
|
135
|
+
| MasterDataRecord
|
|
136
|
+
| (
|
|
137
|
+
| CanonicalRecord
|
|
138
|
+
| CanonicalAggregationRecord
|
|
139
|
+
| CanonicalTransformationRecord
|
|
140
|
+
| EnergyRecord
|
|
141
|
+
| EnergyEquipmentRecord
|
|
142
|
+
| OperationalRecord
|
|
143
|
+
| MasterDataRecord
|
|
144
|
+
)[]
|
|
170
145
|
| undefined
|
|
171
146
|
| null,
|
|
172
147
|
tenantId: string,
|
|
173
|
-
defaultEventTime: string
|
|
174
|
-
|
|
148
|
+
defaultEventTime: string,
|
|
149
|
+
/**
|
|
150
|
+
* **이 사실들이 어느 트윈의 것인가** — 마감된 구간 사실의 이름에 쓰인다(커널 §`SubjectBasis`).
|
|
151
|
+
*
|
|
152
|
+
* 커널은 도메인으로 서므로 자기가 어느 트윈인지 모른다. 그런데 설비 번호·계량 지점 번호는 트윈
|
|
153
|
+
* 안에서만 통하는 이름표라, 한 도메인에 현장이 둘이면 서로 다른 설비의 같은 날이 한 사실이 된다.
|
|
154
|
+
* 실측으로 그 일이 났다 — 두 발전소의 `002` 가 겹쳤다.
|
|
155
|
+
*
|
|
156
|
+
* 주지 않으면 이름표를 그대로 쓰고 `twin-local` 로 표시된다 — 겹칠 수 있다는 뜻이다.
|
|
157
|
+
*/
|
|
158
|
+
scope?: { scopeId?: string; identityOf?: (kind: 'equipment' | 'meter', localId: string) => string | undefined }
|
|
159
|
+
): CanonicalIngestResult {
|
|
175
160
|
const arr = Array.isArray(records) ? records : records ? [records] : []
|
|
161
|
+
/*
|
|
162
|
+
* ── **다섯째 어휘는 사건이 아니다** (2026-08-28) ────────────────────────────
|
|
163
|
+
*
|
|
164
|
+
* 마스터데이터 — 변하지 않는 속성(유통기한·로트번호·자리의 성질). 표준은 이것을 사건이 아니라
|
|
165
|
+
* **어휘 요소의 속성**으로 담고, 커널에 그 자리가 없어서 지금까지 사건에 실려 왔다.
|
|
166
|
+
*
|
|
167
|
+
* 그 결과가 유입에서 이렇게 났다. 전량을 읽는 원본이 유통기한을 실으려고 **들어오지 않은 것을
|
|
168
|
+
* `ADD`** 라고 말했고, `ilmd` 가 사건 단위라 자리별로 묶을 수도 없어 낱개로 나갔다 — 재기동 한 번에
|
|
169
|
+
* 2,665건이다.
|
|
170
|
+
*
|
|
171
|
+
* 그래서 여기서 갈라 낸다. 이것은 봉투가 되지 않고 저널에도 적히지 않는다(시각축이 없는 값이다).
|
|
172
|
+
* 갈래 판정은 커널의 것을 부른다 — 다른 넷과 같은 규율이다.
|
|
173
|
+
*
|
|
174
|
+
* 설계: `operato-twin/design/plans/master-data.md`
|
|
175
|
+
*/
|
|
176
|
+
const masterCandidates = arr.filter(isMasterDataRecord)
|
|
177
|
+
const master = masterCandidates.length ? ingestMasterData(masterCandidates) : { accepted: [], rejected: [] }
|
|
176
178
|
/*
|
|
177
179
|
* **어휘가 넷이다** — 물류(EPCIS)·에너지 계량·설비 에너지·운영 사실. 예전에는 이 함수가 무조건
|
|
178
180
|
* `ObjectEvent` 를 만들었고, 그래서 계측을 이 길로 넣으면 `epc` 가 없어 거부되거나(그나마 나은 쪽)
|
|
@@ -184,17 +186,79 @@ export function ingestCanonicalRecords(
|
|
|
184
186
|
* 소비처마다 답이 달라진다. 봉투는 같은 것을 쓰므로 아래 소비처(미러·저널·브로드캐스팅)는 이 갈림을 알
|
|
185
187
|
* 필요가 없다.
|
|
186
188
|
*/
|
|
187
|
-
|
|
189
|
+
/*
|
|
190
|
+
* 여섯째·일곱째 어휘 — **마감된 사용 구간**과 **청구서**. 커널 0.7.72 가 이 문들을 열었다.
|
|
191
|
+
*
|
|
192
|
+
* ── ★ 사용 구간을 계량보다 **먼저** 가른다 ─────────────────────────────────
|
|
193
|
+
* `isEnergyRecord` 는 `meterId` 가 있으면 참이므로 사용 구간에도 참이다. 순서를 정하지 않으면
|
|
194
|
+
* 시간별 사용량이 계량 표본으로 들어가고, 그 문은 `kWh` 를 **적산 레지스터**로 읽는다. 그러면
|
|
195
|
+
* 커널이 구간 전력량을 차분으로 구하는데 우리가 넣은 값이 이미 구간의 양이다 — 차분의 차분이 되어
|
|
196
|
+
* 값이 조용히 틀린다. 거부되지도 않는다.
|
|
197
|
+
*/
|
|
198
|
+
const usagePeriods = arr.filter(isEnergyUsagePeriodRecord) as unknown as EnergyUsagePeriodRecord[]
|
|
199
|
+
const bills = arr.filter(isEnergyBillRecord) as unknown as EnergyBillRecord[]
|
|
200
|
+
/*
|
|
201
|
+
* 여덟째 어휘 — **그 주기에 적용되는 요금 기준**. 커널 0.7.76 이 이 문을 열었다.
|
|
202
|
+
*
|
|
203
|
+
* ★ 청구서와 겹친다. 청구서가 `billingDemandKW` 를 실을 수 있고, 그러면 두 판정이 다 참이다.
|
|
204
|
+
* 그때 요금 기준 문으로 보내면 「금액은 이 문이 받지 않는다」로 거부된다 — 정산이 통째로
|
|
205
|
+
* 사라진다. 그래서 청구서인 것을 뺀다. 커널에서 배타로 만들어 달라고 올렸다.
|
|
206
|
+
*/
|
|
207
|
+
const tariffBases = arr.filter(r => isEnergyTariffBasisRecord(r) && !isEnergyBillRecord(r)) as unknown as EnergyTariffBasisRecord[]
|
|
208
|
+
/*
|
|
209
|
+
* 아홉째 어휘 — **발전 단가**. 낸 것에 매겨지는 1kWh 당 값이고 날마다 바뀐다(커널 0.7.79).
|
|
210
|
+
*
|
|
211
|
+
* 다른 문과 겹치지 않는다: 계량 지점이나 설비 id 가 있으면 이 문이 아니고(커널이 배타로 막는다),
|
|
212
|
+
* 요금적용전력·기본요금 단가가 없으므로 요금 기준도 아니다.
|
|
213
|
+
*/
|
|
214
|
+
const generationPrices = arr.filter(isEnergyGenerationPriceRecord) as unknown as EnergyGenerationPriceRecord[]
|
|
215
|
+
/* 마감된 발전 기간 — 지난 기록 채우기가 쓴다. 적산의 문과 배타다(구간이 있으면 이쪽). */
|
|
216
|
+
const generationPeriods = arr.filter(isEnergyGenerationPeriodRecord) as unknown as EnergyGenerationPeriodRecord[]
|
|
217
|
+
const energy = arr.filter(r => isEnergyRecord(r) && !isEnergyUsagePeriodRecord(r)) as EnergyRecord[]
|
|
188
218
|
/* 세 번째 어휘 — 설비가 낸 자기 에너지 상태(발전·저장·감축 여지·개폐 위치). 계량과 갈라 두는
|
|
189
219
|
이유는 커널에 적혀 있다: 계량은 구간에 누적되고, 이것들은 그 설비의 지금이다. */
|
|
190
|
-
|
|
220
|
+
/*
|
|
221
|
+
* 다섯 번째 어휘 — **발전 적산**(설비가 지금까지 만든 양). 커널 0.7.69 가 이 문을 열었다.
|
|
222
|
+
*
|
|
223
|
+
* ── ★ 설비 상태보다 **먼저** 가른다 ──────────────────────────────────────────
|
|
224
|
+
* 두 판정이 겹친다. `isEnergyEquipmentRecord` 는 `equipmentId` 가 있고 물류·계량 어휘가 아니면
|
|
225
|
+
* 참이므로 발전 적산도 함께 참이다. 그래서 순서가 갈림을 정한다.
|
|
226
|
+
*
|
|
227
|
+
* 순서를 잘못 두면 조용히 사라지는 것이 아니라 **엉뚱한 이유로 거부된다**: 설비 상태 문은 적산을
|
|
228
|
+
* 값으로 세지 않아 「바꿀 상태가 하나도 없다」로 돌려보낸다. 커넥터는 보냈다고 믿고, 화면에는
|
|
229
|
+
* 발전량이 없다.
|
|
230
|
+
*/
|
|
231
|
+
const energyGeneration = arr.filter(isEnergyGenerationRecord) as unknown as EnergyGenerationRecord[]
|
|
232
|
+
const energyEquipment = arr.filter(r => isEnergyEquipmentRecord(r) && !isEnergyGenerationRecord(r)) as unknown as EnergyEquipmentRecord[]
|
|
191
233
|
/*
|
|
192
234
|
* 네 번째 어휘 — **운영 사실**(작업·설비·사람·자산·오더·품질). 관측 리듀서는 이것을 오래전부터
|
|
193
|
-
*
|
|
235
|
+
* 계산했는데 **들어올 문이 없었다**: 원본이 「이 작업이 끝났다」를 말할 길이 없어 시뮬 커널만 그것을
|
|
194
236
|
* 아는 상태가 남았다(같은 화면이 두 구동에서 다른 것을 말한다).
|
|
195
237
|
*/
|
|
196
238
|
const operational = arr.filter(isOperationalRecord) as OperationalRecord[]
|
|
197
|
-
|
|
239
|
+
/*
|
|
240
|
+
* ── 이 목록은 **어휘를 더할 때마다 함께 늘어야 한다** (2026-08-30) ─────────
|
|
241
|
+
*
|
|
242
|
+
* EPCIS 갈래는 「나머지 전부」다. 새 어휘를 빼 주지 않으면 그 레코드가 제 문으로 가면서
|
|
243
|
+
* **EPCIS 문으로도 들어가** 거부된다. 받아들여지기도 하고 거부되기도 하는 상태가 되어,
|
|
244
|
+
* 유입 장부의 거부 수가 늘고 사람이 그것을 진짜 결함으로 읽는다.
|
|
245
|
+
*
|
|
246
|
+
* 실제로 발전 단가를 더할 때 그렇게 됐다. 목록을 손으로 지키는 대신 시험이 지킨다
|
|
247
|
+
* (§`canonical-ingest-vocabularies` — 어휘마다 한 문으로만 가는지).
|
|
248
|
+
*/
|
|
249
|
+
const epcis = arr.filter(
|
|
250
|
+
r =>
|
|
251
|
+
!isEnergyRecord(r) &&
|
|
252
|
+
!isEnergyEquipmentRecord(r) &&
|
|
253
|
+
!isEnergyGenerationRecord(r) &&
|
|
254
|
+
!isEnergyUsagePeriodRecord(r) &&
|
|
255
|
+
!isEnergyBillRecord(r) &&
|
|
256
|
+
!isEnergyTariffBasisRecord(r) &&
|
|
257
|
+
!isEnergyGenerationPriceRecord(r) &&
|
|
258
|
+
!isEnergyGenerationPeriodRecord(r) &&
|
|
259
|
+
!isOperationalRecord(r) &&
|
|
260
|
+
!isMasterDataRecord(r)
|
|
261
|
+
) as (CanonicalRecord | CanonicalAggregationRecord | CanonicalTransformationRecord)[]
|
|
198
262
|
|
|
199
263
|
/* 품목 어휘 안에서도 갈림이 있다(개체 관측 / 담김) — 그 판정도 커널의 것을 부른다. */
|
|
200
264
|
const epcisResult = epcis.length
|
|
@@ -232,30 +296,81 @@ export function ingestCanonicalRecords(
|
|
|
232
296
|
* **레코드가 시각을 말하면 그것을 쓴다** (2026-08-23).
|
|
233
297
|
*
|
|
234
298
|
* `eventTimePath` 를 주지 않던 동안 이 경로는 **언제나 폴링 시각**을 썼다(`defaultEventTime`).
|
|
235
|
-
* 그래서 커넥터가 이미 싣고 있던 `eventTime`(변환 레코드의 `finishedAt` 등)이
|
|
299
|
+
* 그래서 커넥터가 이미 싣고 있던 `eventTime`(변환 레코드의 `finishedAt` 등)이 **알리지 않고
|
|
236
300
|
* 무시됐다** — 커넥터를 붙인 쪽은 그것이 먹는다고 믿고 있었고, 어디에도 오류가 나지 않았다.
|
|
237
301
|
*
|
|
238
302
|
* 무엇이 걸렸나: 계보(변환)의 시각이다. 4월에 만든 로트가 8월에 만들어진 것으로 저널에 남았다.
|
|
239
303
|
* 그리고 실측 소요를 재는 폴드가 봉투 시각을 쓰므로(§`kpi-fold`: `workMs = 완료 − 첫 착수`),
|
|
240
304
|
* 폴링 간격을 작업시간으로 배울 수 있었다 — 못 배우는 것보다 나쁘다.
|
|
241
305
|
*
|
|
242
|
-
* 레코드에 `eventTime` 이 없으면 커널이
|
|
306
|
+
* 레코드에 `eventTime` 이 없으면 커널이 알리지 않고 기본값으로 떨어진다(§`resolve` — 없는 값은
|
|
243
307
|
* 오류가 아니다). 그래서 이 한 줄이 기존 경로를 막지 않는다: 시각을 말하는 레코드만 사실이 된다.
|
|
244
308
|
*/
|
|
245
309
|
{ tenantId, defaultEventTime, eventTimePath: 'eventTime' }
|
|
246
310
|
)
|
|
247
311
|
: { accepted: [], rejected: [] }
|
|
248
|
-
const energyResult = energy.length ? ingestEnergyRecords(energy, { tenantId, defaultEventTime }) : { accepted: [], rejected: [] }
|
|
312
|
+
const energyResult = energy.length ? ingestEnergyRecords(energy, { tenantId, defaultEventTime, ...scope }) : { accepted: [], rejected: [] }
|
|
249
313
|
const equipmentResult = energyEquipment.length
|
|
250
|
-
? ingestEnergyEquipmentRecords(energyEquipment, { tenantId, defaultEventTime })
|
|
314
|
+
? ingestEnergyEquipmentRecords(energyEquipment, { tenantId, defaultEventTime, ...scope })
|
|
315
|
+
: { accepted: [], rejected: [] }
|
|
316
|
+
const usageResult = usagePeriods.length
|
|
317
|
+
? ingestEnergyUsagePeriodRecords(usagePeriods, { tenantId, defaultEventTime, ...scope })
|
|
318
|
+
: { accepted: [], rejected: [] }
|
|
319
|
+
const billResult = bills.length ? ingestEnergyBillRecords(bills, { tenantId, defaultEventTime, ...scope }) : { accepted: [], rejected: [] }
|
|
320
|
+
const tariffBasisResult = tariffBases.length
|
|
321
|
+
? ingestEnergyTariffBasisRecords(tariffBases, { tenantId, defaultEventTime, ...scope })
|
|
322
|
+
: { accepted: [], rejected: [] }
|
|
323
|
+
const generationPeriodResult = generationPeriods.length
|
|
324
|
+
? ingestEnergyGenerationPeriodRecords(generationPeriods, { tenantId, defaultEventTime, ...scope })
|
|
325
|
+
: { accepted: [], rejected: [] }
|
|
326
|
+
const generationPriceResult = generationPrices.length
|
|
327
|
+
? ingestEnergyGenerationPriceRecords(generationPrices, { tenantId, defaultEventTime, ...scope })
|
|
328
|
+
: { accepted: [], rejected: [] }
|
|
329
|
+
const generationResult = energyGeneration.length
|
|
330
|
+
? ingestEnergyGenerationRecords(energyGeneration, { tenantId, defaultEventTime, ...scope })
|
|
251
331
|
: { accepted: [], rejected: [] }
|
|
252
332
|
const operationalResult = operational.length
|
|
253
|
-
? ingestOperationalRecords(operational, { tenantId, defaultEventTime })
|
|
333
|
+
? ingestOperationalRecords(operational, { tenantId, defaultEventTime, ...scope })
|
|
254
334
|
: { accepted: [], rejected: [] }
|
|
255
335
|
|
|
256
336
|
/* 거부분을 합쳐 돌려준다 — 어느 어휘에서 거부됐는지는 이유 문구가 말한다. */
|
|
257
337
|
return {
|
|
258
|
-
accepted: [
|
|
259
|
-
|
|
338
|
+
accepted: [
|
|
339
|
+
...epcisResult.accepted,
|
|
340
|
+
...energyResult.accepted,
|
|
341
|
+
...equipmentResult.accepted,
|
|
342
|
+
...generationResult.accepted,
|
|
343
|
+
...usageResult.accepted,
|
|
344
|
+
...generationPriceResult.accepted,
|
|
345
|
+
...generationPeriodResult.accepted,
|
|
346
|
+
...billResult.accepted,
|
|
347
|
+
...tariffBasisResult.accepted,
|
|
348
|
+
...operationalResult.accepted
|
|
349
|
+
],
|
|
350
|
+
rejected: [
|
|
351
|
+
...epcisResult.rejected,
|
|
352
|
+
...energyResult.rejected,
|
|
353
|
+
...equipmentResult.rejected,
|
|
354
|
+
...generationResult.rejected,
|
|
355
|
+
...usageResult.rejected,
|
|
356
|
+
...billResult.rejected,
|
|
357
|
+
...tariffBasisResult.rejected,
|
|
358
|
+
...generationPriceResult.rejected,
|
|
359
|
+
...generationPeriodResult.rejected,
|
|
360
|
+
...operationalResult.rejected,
|
|
361
|
+
...master.rejected
|
|
362
|
+
],
|
|
363
|
+
/* 사건이 아닌 것은 따로 낸다 — 부르는 쪽이 상태 세우는 문으로 보낸다(저널로 가지 않게). */
|
|
364
|
+
masterData: master.accepted
|
|
260
365
|
}
|
|
261
366
|
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 유입 결과 — 사건(봉투)과 **사건이 아닌 것**을 갈라 낸다.
|
|
370
|
+
*
|
|
371
|
+
* 마스터데이터를 `accepted` 에 섞지 않는 이유: 부르는 쪽이 그것을 저널에 적게 되고, 시각축이 없는 값이
|
|
372
|
+
* 「그때 일어난 일」로 남는다. 갈래를 타입으로 갈라 두면 그 실수를 할 수 없다.
|
|
373
|
+
*/
|
|
374
|
+
export interface CanonicalIngestResult extends IngestResult {
|
|
375
|
+
masterData: MasterDataRecord[]
|
|
376
|
+
}
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* | 주석(확인 ack) | 커널 런타임 | 관측 커널 |
|
|
17
17
|
*
|
|
18
18
|
* 미러에서 액추에이션을 그냥 허용하면 **새 거짓말이 생긴다**: 트윈은 "보류됨" 이라 말하고 실 시스템은
|
|
19
|
-
* 모른다.
|
|
19
|
+
* 모른다. 알리지 않고 커널만 바꾸지 않고, 이유를 밝혀 거절한다.
|
|
20
20
|
*
|
|
21
21
|
* 반면 확인(ack)은 액추에이션이 아니다 — 신호를 봤다는 트윈 쪽 주석이라 현실을 건드리지 않는다.
|
|
22
22
|
* 미러에서도 성립해야 하므로 관측 커널로 보낸다.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* ── 판정을 순수 함수로 (이 파일) ────────────────────────────────────────────
|
|
18
18
|
* 「이 트윈에 저장된 자극을 실을 것인가」에는 사람이 틀리기 쉬운 갈림이 셋 있다: 미러에 실으면 **현실을
|
|
19
19
|
* 오염**시키고, 잘못된 선언을 실으면 **다음 틱에서 서버가 내려가고**(실제로 그 이력이 있다), 시나리오
|
|
20
|
-
* 엔진이 없는 런타임에 실으면
|
|
20
|
+
* 엔진이 없는 런타임에 실으면 알리지 않고 아무 일도 없다. 그래서 판정을 값으로 만들고 시험한다.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
/** 자극을 실을지 말지 — 그리고 왜. 부르는 쪽은 이 답만 보고 움직인다. */
|
|
@@ -13,11 +13,11 @@
|
|
|
13
13
|
* 단선도를 그리면 갖지 않은 공학적 충실도를 주장하게 된다. 여기서 그리는 것은 **트윈이 선언한 관계**
|
|
14
14
|
* (자리의 계층 + 설비의 소속)와 **계량이 실제로 덮는 범위**뿐이다. 한계는 소비처가 화면에 적는다.
|
|
15
15
|
*
|
|
16
|
-
* ──
|
|
16
|
+
* ── 알리지 않고 빠뜨리지 않는다 ─────────────────────────────────────────────────
|
|
17
17
|
* 자리를 모르는 설비, 계량기가 없는 자리, 계량이 덮지 않는 몫은 **각자 자기 칸으로** 나온다. 그림에서
|
|
18
18
|
* 사라지면 사용자는 자기 현장이 다 그려졌다고 믿는다.
|
|
19
19
|
*/
|
|
20
|
-
import { EMS_LOCATION_TYPES, electricalUpstreamOf } from '@operato/
|
|
20
|
+
import { EMS_LOCATION_TYPES, electricalUpstreamOf } from '@operato/ops-contract'
|
|
21
21
|
|
|
22
22
|
/** 계통의 한 구간(수전·분기·구역) — 자리 하나가 곧 구간이다. */
|
|
23
23
|
export interface TopologySegment {
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 같은 사실을 두 번 받으면 한 번만 반영한다 — **유입 경계 한 곳에서.**
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 필요한가 (2026-08-26) ────────────────────────────────────────────────
|
|
5
|
+
* 지금 중복을 막는 것은 커넥터마다 각자 하고 있다. 그래서 커넥터가 그것을 잊으면 트윈이 그대로 두 번
|
|
6
|
+
* 센다. 실제로 그랬다: 태양광 커넥터가 10초마다 물으면서 10분마다 바뀌는 값을 매번 다시 보냈고,
|
|
7
|
+
* 한 주기에 76건이 되풀이됐다(커넥터에서 고쳐서 2건이 됐다).
|
|
8
|
+
*
|
|
9
|
+
* 그리고 이것은 웹훅으로 받을 때 더 자주 난다. 밀어 주는 방식은 재전송이 정상이다 — 그쪽이 응답을
|
|
10
|
+
* 못 받으면 같은 것을 다시 보낸다.
|
|
11
|
+
*
|
|
12
|
+
* ── 무엇이 두 번 세어지나 ──────────────────────────────────────────────────
|
|
13
|
+
* 절대값으로 말하는 사실은 여러 번 반영해도 결과가 같다(잔량 · 상태 · 적산 · 순간 kW). 그런데 세는
|
|
14
|
+
* 값은 그렇지 않다.
|
|
15
|
+
*
|
|
16
|
+
* 구간의 표본 수 · 평균 부하(표본 수로 나눈다) · OEE 카운터 · 유입 계수
|
|
17
|
+
* 그리고 지난 기록에 같은 사실이 두 줄로 남는다 — 나중에 그 기록으로 다시 계산하면 또 두 번 센다
|
|
18
|
+
*
|
|
19
|
+
* ── 정체를 무엇으로 보나 ────────────────────────────────────────────────────
|
|
20
|
+
* 봉투의 `eventId` 로는 판단할 수 없다. 보낸 쪽이 매번 새로 만드는 순번이고(`…-energy-1` 처럼 호출마다
|
|
21
|
+
* 1부터 다시 시작한다), 같은 사실을 두 번 보내면 다른 번호가 붙는다. 다른 사실에 같은 번호가 붙는 일도
|
|
22
|
+
* 생긴다.
|
|
23
|
+
*
|
|
24
|
+
* 그래서 **사실 자체로** 판단한다.
|
|
25
|
+
*
|
|
26
|
+
* 무엇에 대한 것인가 설비 · 계량 지점 · 물품 · 작업 · 오더 · 자리
|
|
27
|
+
* 어떤 종류인가 사건 종류(eventType)
|
|
28
|
+
* 언제 잰 것인가 연결된 시스템이 준 시각
|
|
29
|
+
*
|
|
30
|
+
* 이 셋이 같으면 같은 사실이다. 태양광 커넥터가 `collectedAt` 으로 판단한 것과 같은 방식이고, 그것을
|
|
31
|
+
* 커넥터마다 하지 않도록 여기로 올린다.
|
|
32
|
+
*
|
|
33
|
+
* ── 정확히 같은 것만 버린다 ─────────────────────────────────────────────────
|
|
34
|
+
* 늦게 도착한 옛 사실은 버리지 않는다. 그것이 왔다는 것 자체가 사실이고, 상태에 반영되지 않는 것은
|
|
35
|
+
* 커널의 순서 판단이 이미 맡는다. 여기서 버리는 것은 **정체가 완전히 같은 것**뿐이다.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/** 시각을 밀리초로 — 연결된 시스템이 준 시각이 먼저, 없으면 봉투의 시각. */
|
|
39
|
+
function timeOf(envelope: any): number | undefined {
|
|
40
|
+
for (const v of [envelope?.data?.at, envelope?.data?.eventTime, envelope?.eventTime]) {
|
|
41
|
+
const t = Date.parse(String(v ?? ''))
|
|
42
|
+
if (Number.isFinite(t)) return t
|
|
43
|
+
}
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 이 봉투가 말하는 대상 — 어휘마다 이름이 다르므로 아는 이름을 모두 본다.
|
|
49
|
+
*
|
|
50
|
+
* 순서가 뜻을 갖는다: 주체를 먼저 본다. 설비 사실은 「지금 붙어 있는 작업」을 함께 싣고, 작업 사실은
|
|
51
|
+
* 「소속 오더」를 함께 싣는다. 아무 이름이나 먼저 보면 참조를 주체로 읽는다(승격 키에서 겪은 것과 같다).
|
|
52
|
+
*/
|
|
53
|
+
function targetOf(envelope: any): string | undefined {
|
|
54
|
+
const d = envelope?.data ?? {}
|
|
55
|
+
const first = (...vals: unknown[]) => {
|
|
56
|
+
for (const v of vals) {
|
|
57
|
+
const s = String(v ?? '').trim()
|
|
58
|
+
if (s) return s
|
|
59
|
+
}
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
return first(
|
|
63
|
+
d.meterId,
|
|
64
|
+
d.equipmentId,
|
|
65
|
+
d.moverId,
|
|
66
|
+
d.taskId,
|
|
67
|
+
d.orderId,
|
|
68
|
+
d.personId,
|
|
69
|
+
d.assetId,
|
|
70
|
+
d.locationId,
|
|
71
|
+
d.epcList?.[0],
|
|
72
|
+
d.parentID,
|
|
73
|
+
d.quantityList?.[0]?.epcClass,
|
|
74
|
+
d.transformationID
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 이 봉투가 말하는 사실의 정체 — 같으면 같은 사실이다.
|
|
80
|
+
*
|
|
81
|
+
* 대상이나 시각을 알 수 없으면 **정체를 만들지 않는다**(`undefined`). 그때는 거르지 않고 통과시킨다 —
|
|
82
|
+
* 모르는 것을 같다고 판단하면 서로 다른 사실이 하나로 합쳐진다. 알리지 않고 잃는 것보다 두 번 세는 것이 낫다.
|
|
83
|
+
*/
|
|
84
|
+
export function factIdentity(envelope: any): string | undefined {
|
|
85
|
+
const type = String(envelope?.eventType ?? '').trim()
|
|
86
|
+
if (!type) return undefined
|
|
87
|
+
const target = targetOf(envelope)
|
|
88
|
+
if (!target) return undefined
|
|
89
|
+
const at = timeOf(envelope)
|
|
90
|
+
if (at === undefined) return undefined
|
|
91
|
+
return `${type}|${target}|${at}`
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 최근에 본 정체를 기억한다 — **크기에 상한을 둔다.**
|
|
96
|
+
*
|
|
97
|
+
* 전부 기억할 수는 없다(트윈 하나가 수천만 사건을 갖는다). 창을 넘으면 오래된 것부터 잊는다. 창 밖의
|
|
98
|
+
* 재전송은 다시 통과하는데, 그것은 절대값 사실이라 상태에 해가 없고 지난 기록에 한 줄 더 남는다.
|
|
99
|
+
* 창을 크게 잡으면 그만큼 메모리를 쓴다 — 그 값을 여기서 이름으로 못 박는다.
|
|
100
|
+
*
|
|
101
|
+
* **재기동을 건너지 못한다.** 프로세스가 새로 뜨면 기억이 비어 있으므로, 재기동 직후 첫 주기가 같은
|
|
102
|
+
* 값을 다시 보내면 걸러지지 않는다. 그것을 막으려면 이 기억이 저장돼야 하고, 그것은 다음 걸음이다.
|
|
103
|
+
* 지금은 그 사실을 여기 적어 둔다 — 적어 두지 않으면 「중복은 이제 없다」로 읽힌다.
|
|
104
|
+
*/
|
|
105
|
+
export class FactDeduper {
|
|
106
|
+
/** 기억하는 정체의 수 — 창. */
|
|
107
|
+
static WINDOW = 20_000
|
|
108
|
+
|
|
109
|
+
private seen = new Set<string>()
|
|
110
|
+
private order: string[] = []
|
|
111
|
+
|
|
112
|
+
/** 이 사실을 본 적이 있나 — 없으면 기억하고 `false`. */
|
|
113
|
+
private known(identity: string): boolean {
|
|
114
|
+
if (this.seen.has(identity)) return true
|
|
115
|
+
this.seen.add(identity)
|
|
116
|
+
this.order.push(identity)
|
|
117
|
+
if (this.order.length > FactDeduper.WINDOW) {
|
|
118
|
+
const gone = this.order.splice(0, this.order.length - FactDeduper.WINDOW)
|
|
119
|
+
for (const g of gone) this.seen.delete(g)
|
|
120
|
+
}
|
|
121
|
+
return false
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* 처음 보는 것만 돌려준다 — 그리고 몇 개를 버렸는지 함께 낸다.
|
|
126
|
+
*
|
|
127
|
+
* 버린 수를 함께 내는 이유: 알리지 않고 버리면 「보낸 수」와 「반영된 수」가 어긋나는데 아무도 그
|
|
128
|
+
* 이유를 모른다. 그 수가 크면 보내는 쪽이 되풀이 보내고 있다는 뜻이고, 그것은 고칠 일이다.
|
|
129
|
+
*/
|
|
130
|
+
filter<T>(envelopes: readonly T[]): { fresh: T[]; duplicates: number } {
|
|
131
|
+
const fresh: T[] = []
|
|
132
|
+
let duplicates = 0
|
|
133
|
+
for (const e of envelopes) {
|
|
134
|
+
const id = factIdentity(e)
|
|
135
|
+
if (id === undefined) {
|
|
136
|
+
fresh.push(e)
|
|
137
|
+
continue
|
|
138
|
+
}
|
|
139
|
+
if (this.known(id)) duplicates++
|
|
140
|
+
else fresh.push(e)
|
|
141
|
+
}
|
|
142
|
+
return { fresh, duplicates }
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** 기억하고 있는 정체의 수 — 계기가 읽는다. */
|
|
146
|
+
size(): number {
|
|
147
|
+
return this.seen.size
|
|
148
|
+
}
|
|
149
|
+
}
|