@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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-metrics-query.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/twin-metrics-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Directive, Query, Resolver } from 'type-graphql'\n\nimport { In } from 'typeorm'\n\nimport { Domain, getRepository, ScalarObject } from '@things-factory/shell'\nimport { PrivilegeObject, checkPermission } from '@things-factory/auth-base'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { newCpuSampler, sampleCpu } from '../../engine/load-meter.js'\nimport { modelGapOf } from '../../engine/model-gap.js'\n\n/*\n * 트윈 처리량 계측(모니터, ④-1) — 라이브 동기 인터페이스 부하를 읽는 일급 지표.\n * 유입률(ingestRate)·브로드캐스팅률(broadcastRate)·저널률(journalRate)·백로그(backlog)·누적. 폴링으로 라이브 관측.\n * (부하가 화면에 안 보인다던 문제의 정공법 — \"얼마나 들어오고 얼마나 브로드캐스팅/기록되나\"를 수치로.)\n */\n/* CPU 는 누적값이라 **표본 사이의 차이**로만 점유율이 나온다. 그래서 호출 사이에 상태가 남아야 한다. */\nconst CPU_SAMPLER = newCpuSampler()\n\n/**\n * 거부된 **레코드 원문**을 볼 수 있는 자격.\n *\n * 트윈을 고칠 수 있는 사람과 같은 자격으로 둔다 — 매핑을 고치려면 원천이 실제로 보낸 모양을 봐야 하므로\n * 권한이 그 일과 맞는다. 도메인 소유자·슈퍼유저는 통과한다(프레임워크 규약과 같다).\n *\n * 판정·추이·사유는 이 자격과 무관하게 낸다. 「맞춰지고 있나」는 감출 일이 아니다.\n */\nconst RAW_RECORD_PRIVILEGE: PrivilegeObject = { category: 'twin', privilege: 'mutation', owner: true, super: true }\n\n@Resolver()\nexport class TwinMetricsQuery {\n @Query(returns => ScalarObject, { nullable: true, description: 'Live throughput metrics of one twin instance (ingest/broadcast/journal rate, backlog). Null if not a live instance or not in the current domain.' })\n twinMetrics(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): any {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return null // 테넌트 격리\n return TwinEngine.metrics(context.state.domain.id, instanceId)\n }\n\n @Query(returns => [ScalarObject], {\n description:\n 'Throughput metrics of every running twin instance in the current domain — simulations as well as mirrors (monitor dashboard). Simulations report ingestRate 0 because they receive nothing from a source: they emit their own facts, and that zero is a fact rather than a gap. Until 2026-08-20 simulations carried no meter at all and were silently absent from this list, which made a host full of running simulations look idle.'\n })\n async twinMetricsAll(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.allMetrics(context.state.domain.id) // 도메인 스코프\n }\n\n /*\n * ── 동기화 건강 — 「이 트윈이 현장과 맞춰지고 있나」 ──────────────────────────\n *\n * `twinMetrics` 는 **통과한** 이벤트의 초당 수를 답한다. 여기서 답하는 것은 **제시된 것 중 얼마가\n * 통과했나**이고, 통과하지 못한 것의 이유와 실제 모양까지 낸다.\n *\n * 왜 따로 있나: 거부된 레코드는 **저널에 없다**(거부의 뜻이 그것이다). 저널을 접어 나오는 것은 통과한\n * 쪽뿐이라, 거부의 추이는 세는 자리를 따로 두어야 나온다.\n *\n * 왜 도메인으로 자르나: 이것은 **테넌트의 데이터**다. 거부 표본에는 그 고객의 레코드 원문이 들어\n * 있으므로 남의 것을 보여 줄 수 없다(부하 계기판이 프로세스 전체를 내는 것과 반대다 — 그쪽은\n * 이벤트 루프가 프로세스 공유라서 그렇게 했다).\n *\n * ── 수치와 원문은 민감도가 다르다 ───────────────────────────────────────────\n * 판정·추이·사유는 「맞춰지고 있나」를 알려면 누구나 봐야 한다. 반면 **거부된 레코드 원문**은 그 고객의\n * 업무 데이터다(품목·수량·거래처가 그대로 들어 있다). 같은 도메인이라는 것만으로 원문까지 열면 필요\n * 없는 사람에게도 열린다.\n *\n * 그래서 원문만 한 겹 뒤에 둔다 — 트윈을 **고칠 수 있는 사람**에게만. 매핑을 고치려면 원천이 실제로\n * 무엇을 보냈는지 봐야 하므로 권한이 그 일과 맞는다. 감출 때는 **감췄다고 말한다**(`samplesWithheld`);\n * 빈 배열로 두면 「거부된 것이 없다」로 읽힌다.\n */\n @Query(returns => ScalarObject, {\n description:\n 'Answers one question: is this twin keeping in step with the floor? Returns a verdict — syncing | mapping-blocked | no-traffic | not-connected | not-applicable | unknown — together with the threshold it was judged by, so a screen can say why. `recent` is the last **closed** 10-minute window (null while the first window is still filling: a partially filled window under-counts, so it is not called \"recent\"), and `trend` carries up to 36 closed windows (6 hours) oldest-first with offered / accepted / rejected per window. Accepted and rejected are reported together because counting only rejections makes \"nothing is arriving\" and \"everything is fine\" both look like zero. `reasons` groups rejections by the reason the kernel gave, most frequent first, each with the last time that reason occurred — a reason whose time has stopped is one you already fixed. `samples` holds one rejected record per reason (the raw record, truncated with recordTruncated when long) because a reason alone does not tell you what the source actually sent. Counts that could not be shown are reported rather than dropped: sampleDropped, trendDropped, and reasonsCapped when distinct reasons hit their limit and the rest were folded into \"(other reasons)\". A twin that has never received anything reports null figures rather than zeros, since never-measured and measured-as-zero are different facts. This ledger lives in memory: it covers roughly the last 6 hours and resets when the process restarts. The verdict, trend and reasons are returned to anyone in the owning domain, but the raw records in `samples` are business data of that tenant, so they require the same privilege as changing the twin (twin/mutation, with domain owner and superuser granted); when the caller lacks it, `samples` comes back empty and `samplesWithheld` says how many were held back — an empty list alone would read as \"nothing was rejected\". `modelGap` answers a different question from the verdict: the verdict is about the feed (is anything arriving, does it pass), while modelGap is about the model (did we declare what the source knows). The ingest gate deliberately does not reject a record that references an id the master never declared — a source may legitimately report a new place before the structure sync catches up, and rejecting it would lose that measurement. The kernel absorbs it and marks it `origin: \"observed\"` instead, and modelGap counts those per axis (locations / equipment / persons) with a capped id list plus how many more were left out. It also carries the kernel\\'s `unhandled` tally — event kinds the twin could not apply at all — and, for energy twins, how many state reports arrived from equipment the model does not know. `notCovered` names axes this cannot answer yet: meters carry no origin mark in the kernel, so a mistyped meterId silently becomes a new meter whose kW is accumulated into the site demand. Null while the twin is not running: there is nothing to compare against, and never-compared is not the same as none. Null if the instance is not in the current domain.'\n })\n async twinIngestHealth(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain.id\n /*\n * 테넌트 격리 — 다만 `owns` 는 **도는 트윈**만 참이다. 멈춘 트윈의 장부도 볼 수 있어야 하므로\n * (「고치려고 멈춰 뒀는데 마지막에 뭐가 거부됐나」), 등록 여부로도 판정한다. 도는 트윈이면 DB 를\n * 묻지 않는다(대부분의 조회가 그쪽이다).\n */\n if (!TwinEngine.owns(domainId, instanceId) && !(await TwinEngine.ownsRegistered(domainId, instanceId))) return null\n\n const view = TwinEngine.ingestHealthOf(domainId, instanceId)\n\n /*\n * ── 「원본이 말한 적 없는 것」을 함께 낸다 (2026-08-20) ──────────────────────\n * 유입 문은 참조 오류를 거부하지 않는다(실측: 모르는 로케이션·bizStep·계량기가 모두 통과). 거부하지\n * 않는 것이 옳다 — 원천이 모델보다 먼저 새 자리를 말하는 것은 정상이고, 거부하면 그 선행 계측을\n * 잃는다. 대신 커널이 **흡수하고 표시한다**(`origin: 'observed'`).\n *\n * 그 표시를 읽는 곳이 모델 인스펙션 화면 하나였다. 그런데 「이 트윈이 현장과 맞춰지고 있나」를\n * 보는 사람이 바로 그것을 알아야 한다 — 원천이 우리가 모르는 로케이션만 보내면 트윈은 도는데\n * 스키매틱은 비어 있고, 그게 우리가 「빈 공장」이라 불러 온 상태다.\n *\n * 판정에는 섞지 않는다. 판정은 **피드**를 말하고 이것은 **모델**을 말한다(`model-gap.ts`).\n */\n const modelGap = modelGapOf(TwinEngine.snapshot(domainId, instanceId))\n\n /* 원문을 볼 수 있나 — 못 보면 **몇 건을 감췄는지** 함께 낸다(빈 배열은 「없었다」로 읽힌다). */\n const maySeeRecords = await checkPermission(\n RAW_RECORD_PRIVILEGE,\n context.state.user,\n context.state.domain\n )\n if (maySeeRecords) return { ...view, modelGap }\n\n const { samples, ...rest } = view\n return { ...rest, modelGap, samples: [], samplesWithheld: samples.length }\n }\n\n /*\n * ── 이벤트 루프 부하 — **운영 계기판이다. 테넌트 기능이 아니다.** ─────────────\n *\n * `twinMetrics` 는 \"얼마나 들어오고 나가나\"(초당 이벤트)를 답한다. 여기서 묻는 것은 \"그 일을 하느라\n * 이벤트 루프를 얼마나 붙잡고 있나\" 다. 시뮬 틱은 트윈마다 setInterval 로 **메인 루프에서 동기로**\n * 돌기 때문에, 한 트윈이 느려지면 HTTP·구독이 함께 느려진다. 실제로 그 상태를 겪었는데 볼 계기판이\n * 없어서 \"포트는 열렸는데 응답이 없다\" 로만 보였다.\n *\n * ── 왜 도메인으로 자르지 않나 ───────────────────────────────────────────────\n * 처음에는 호출자의 도메인만 냈다. 테넌트 격리로는 맞아 보이지만 **이벤트 루프는 프로세스 하나가\n * 공유한다.** 실측으로 드러났다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안, 다른 도메인 화면은\n * `실행 중 0 · 루프 점유 —` 였다. \"내 도메인은 한가한데 왜 느리지\" 를 설명할 수 없었다.\n *\n * 그렇다고 테넌트에게 남의 부하를 조금 보여 주는 것도 답이 아니다 — 쓸 수 없는 정보이고 봐서도 안 된다.\n * 층이 잘못이었다: 이것은 **프로세스를 운영하는 사람의 계기판**이다. 그래서 도메인으로 자르지 않고\n * (전 테넌트 합산) **슈퍼유저에게만** 연다.\n */\n @Directive('@privilege(superUserGranted: true)')\n @Query(returns => ScalarObject, {\n description:\n 'Event-loop load of every twin in this process, for system operators (superuser only). Deliberately NOT scoped to the caller\\'s domain: the event loop is shared by the whole process, so a tenant-scoped view reports an idle domain while the machine is saturated by someone else. Reports how many instances are running (by mode), how many forecast forks have been created, and per instance how long each kind of work takes (kernel tick, state snapshot, broadcast payload, publish, journal write, live ingest, Monte-Carlo forecast, fork trajectory). Each instance reports both `recent` (the last closed sampling window — what it is doing now) and `total` (cumulative since boot), with `busyRatio` telling how much of the window that twin held the loop. `fleetBusyRatio` above 1 means the twins together cannot finish inside one tick interval, which is when HTTP and subscriptions start to stall. Work that has never been measured is reported as null rather than zero, because zero would read as \"instant\".'\n })\n async twinLoad(@Ctx() context: ResolverContext): Promise<any> {\n const fleet = TwinEngine.fleet() // 프로세스 전체 — 도메인으로 자르지 않는다(위 주석)\n\n /* 도메인 id 는 UUID 라 사람이 못 읽는다. 읽을 수 있는 이름을 채운다 — **모르면 id 를 그대로 둔다**\n (지어내지 않는다). 라이브 인스턴스에는 이미 붙어 있고, 시뮬은 여기서 한 번 조회한다. */\n const ids = [...new Set((fleet.byDomain ?? []).map((d: any) => d.domainId).filter((v: any) => v && v !== '(unknown)'))]\n if (ids.length) {\n const found = await getRepository(Domain).find({ where: { id: In(ids as string[]) } })\n const nameOf = new Map(found.map(d => [d.id, d.subdomain || d.name]))\n for (const d of fleet.byDomain) if (nameOf.get(d.domainId)) d.label = nameOf.get(d.domainId) as string\n for (const r of fleet.slowest ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n for (const r of fleet.details ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n }\n\n /*\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자와 뜻이 다르다.**\n *\n * 위의 값들은 트윈의 계측된 작업만 잰 것이라, 그 밖의 일(GraphQL·TypeORM·GC·webpack·HTTP)은\n * 분모에도 분자에도 없다. \"루프 점유 <1%\" 인데 CPU 는 110% 였던 상태를 실제로 겪었다. 두 숫자를\n * 나란히 두어야 **트윈이 범인인지** 가릴 수 있다.\n *\n * 표본 간 차이라 첫 호출은 `null` 이다 — 0 이 아니다.\n */\n const usage = process.cpuUsage()\n const cpu = sampleCpu(CPU_SAMPLER, usage.user + usage.system, Date.now())\n\n return { ...fleet, processCpuRatio: cpu?.ratio ?? null, processCpuSpanMs: cpu?.spanMs ?? null }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-metrics-query.js","sourceRoot":"","sources":["../../../server/service/twin-metrics/twin-metrics-query.ts"],"names":[],"mappings":";;;;AAAA,+CAAmE;AAEnE,qCAA4B;AAE5B,iDAA2E;AAC3E,yDAA4E;AAE5E,oDAAkD;AAClD,8DAAqE;AACrE,4DAAsD;AAEtD;;;;GAIG;AACH,kEAAkE;AAClE,MAAM,WAAW,GAAG,IAAA,6BAAa,GAAE,CAAA;AAEnC;;;;;;;GAOG;AACH,MAAM,oBAAoB,GAAoB,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;AAG5G,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAE3B,WAAW,CAAoB,UAAkB,EAAS,OAAwB;QAChF,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC;YAAE,OAAO,IAAI,CAAA,CAAC,SAAS;QAChF,OAAO,qBAAU,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;IAChE,CAAC;IAMK,AAAN,KAAK,CAAC,cAAc,CAAQ,OAAwB;QAClD,OAAO,qBAAU,CAAC,UAAU,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA,CAAC,UAAU;IAClE,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;OAqBG;IAkBG,AAAN,KAAK,CAAC,gBAAgB,CAAoB,UAAkB,EAAS,OAAwB;QAC3F,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAA;QACxC;;;;WAIG;QACH,IAAI,CAAC,qBAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,qBAAU,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;QAEnH,MAAM,IAAI,GAAG,qBAAU,CAAC,cAAc,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAA;QAE5D;;;;;;;;;;;WAWG;QACH,MAAM,QAAQ,GAAG,IAAA,yBAAU,EAAC,qBAAU,CAAC,QAAQ,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAA;QAEtE,8DAA8D;QAC9D,MAAM,aAAa,GAAG,MAAM,IAAA,2BAAe,EACzC,oBAAoB,EACpB,OAAO,CAAC,KAAK,CAAC,IAAI,EAClB,OAAO,CAAC,KAAK,CAAC,MAAM,CACrB,CAAA;QACD,IAAI,aAAa;YAAE,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,CAAA;QAE/C,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,IAAI,CAAA;QACjC,OAAO,EAAE,GAAG,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,eAAe,EAAE,OAAO,CAAC,MAAM,EAAE,CAAA;IAC5E,CAAC;IAED;;;;;;;;;;;;;;;;OAgBG;IAMG,AAAN,KAAK,CAAC,QAAQ,CAAQ,OAAwB;QAC5C,MAAM,KAAK,GAAG,qBAAU,CAAC,KAAK,EAAE,CAAA,CAAC,gCAAgC;QAEjE;gEACwD;QACxD,MAAM,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC,CAAA;QACvH,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACf,MAAM,KAAK,GAAG,MAAM,IAAA,qBAAa,EAAC,cAAM,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,IAAA,YAAE,EAAC,GAAe,CAAC,EAAE,EAAE,CAAC,CAAA;YACtF,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;YACrE,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,QAAQ;gBAAE,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;oBAAE,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAW,CAAA;YACtG,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE;gBAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;oBAAE,CAAC,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;YACrH,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,IAAI,EAAE;gBAAE,IAAI,CAAC,CAAC,QAAQ,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC;oBAAE,CAAC,CAAC,WAAW,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;QACvH,CAAC;QAED;;;;;;;;WAQG;QACH,MAAM,KAAK,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAA;QAChC,MAAM,GAAG,GAAG,IAAA,yBAAS,EAAC,WAAW,EAAE,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CAAA;QAEzE,OAAO,EAAE,GAAG,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,KAAK,IAAI,IAAI,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI,EAAE,CAAA;IACjG,CAAC;CACF,CAAA;AA7IY,4CAAgB;AAE3B;IADC,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,kJAAkJ,EAAE,CAAC;IACvM,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;mDAGxD;AAMK;IAJL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,CAAC,oBAAY,CAAC,EAAE;QAChC,WAAW,EACT,waAAwa;KAC3a,CAAC;IACoB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;sDAE1B;AAyCK;IAjBL,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B;;;;;;;;;;;WAWG;QACH,QAAQ,EAAE,IAAI;QACd,WAAW,EACT,ggGAAggG;KACngG,CAAC;IACsB,mBAAA,IAAA,kBAAG,EAAC,YAAY,CAAC,CAAA;IAAsB,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;wDAmCnE;AAwBK;IALL,IAAA,wBAAS,EAAC,oCAAoC,CAAC;IAC/C,IAAA,oBAAK,EAAC,OAAO,CAAC,EAAE,CAAC,oBAAY,EAAE;QAC9B,WAAW,EACT,2+BAA2+B;KAC9+B,CAAC;IACc,mBAAA,IAAA,kBAAG,GAAE,CAAA;;;;gDA2BpB;2BA5IU,gBAAgB;IAD5B,IAAA,uBAAQ,GAAE;GACE,gBAAgB,CA6I5B","sourcesContent":["import { Arg, Ctx, Directive, Query, Resolver } from 'type-graphql'\n\nimport { In } from 'typeorm'\n\nimport { Domain, getRepository, ScalarObject } from '@things-factory/shell'\nimport { PrivilegeObject, checkPermission } from '@things-factory/auth-base'\n\nimport { TwinEngine } from '../../engine/index.js'\nimport { newCpuSampler, sampleCpu } from '../../engine/load-meter.js'\nimport { modelGapOf } from '../../engine/model-gap.js'\n\n/*\n * 트윈 처리량 계측(모니터, ④-1) — 라이브 동기 인터페이스 부하를 읽는 일급 지표.\n * 유입률(ingestRate)·브로드캐스팅률(broadcastRate)·저널률(journalRate)·백로그(backlog)·누적. 폴링으로 라이브 관측.\n * (부하가 화면에 안 보인다던 문제의 정공법 — \"얼마나 들어오고 얼마나 브로드캐스팅/기록되나\"를 수치로.)\n */\n/* CPU 는 누적값이라 **표본 사이의 차이**로만 점유율이 나온다. 그래서 호출 사이에 상태가 남아야 한다. */\nconst CPU_SAMPLER = newCpuSampler()\n\n/**\n * 거부된 **레코드 원문**을 볼 수 있는 자격.\n *\n * 트윈을 고칠 수 있는 사람과 같은 자격으로 둔다 — 매핑을 고치려면 원천이 실제로 보낸 모양을 봐야 하므로\n * 권한이 그 일과 맞는다. 도메인 소유자·슈퍼유저는 통과한다(프레임워크 규약과 같다).\n *\n * 판정·추이·사유는 이 자격과 무관하게 낸다. 「맞춰지고 있나」는 감출 일이 아니다.\n */\nconst RAW_RECORD_PRIVILEGE: PrivilegeObject = { category: 'twin', privilege: 'mutation', owner: true, super: true }\n\n@Resolver()\nexport class TwinMetricsQuery {\n @Query(returns => ScalarObject, { nullable: true, description: 'Live throughput metrics of one twin instance (ingest/broadcast/journal rate, backlog). Null if not a live instance or not in the current domain.' })\n twinMetrics(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): any {\n if (!TwinEngine.owns(context.state.domain.id, instanceId)) return null // 테넌트 격리\n return TwinEngine.metrics(context.state.domain.id, instanceId)\n }\n\n @Query(returns => [ScalarObject], {\n description:\n 'Throughput metrics of every running twin instance in the current domain — simulations as well as mirrors (monitor dashboard). Simulations report ingestRate 0 because they receive nothing from a source: they emit their own facts, and that zero is a fact rather than a gap. Until 2026-08-20 simulations carried no meter at all and were silently absent from this list, which made a host full of running simulations look idle.'\n })\n async twinMetricsAll(@Ctx() context: ResolverContext): Promise<any[]> {\n return TwinEngine.allMetrics(context.state.domain.id) // 도메인 스코프\n }\n\n /*\n * ── 동기화 건강 — 「이 트윈이 현장과 맞춰지고 있나」 ──────────────────────────\n *\n * `twinMetrics` 는 **통과한** 이벤트의 초당 수를 답한다. 여기서 답하는 것은 **제시된 것 중 얼마가\n * 통과했나**이고, 통과하지 못한 것의 이유와 실제 모양까지 낸다.\n *\n * 왜 따로 있나: 거부된 레코드는 **저널에 없다**(거부의 뜻이 그것이다). 저널을 계산해 나오는 것은 통과한\n * 쪽뿐이라, 거부의 추이는 세는 자리를 따로 두어야 나온다.\n *\n * 왜 도메인으로 자르나: 이것은 **테넌트의 데이터**다. 거부 표본에는 그 고객의 레코드 원문이 들어\n * 있으므로 남의 것을 보여 줄 수 없다(부하 계기판이 프로세스 전체를 내는 것과 반대다 — 그쪽은\n * 이벤트 루프가 프로세스 공유라서 그렇게 했다).\n *\n * ── 수치와 원문은 민감도가 다르다 ───────────────────────────────────────────\n * 판정·추이·사유는 「맞춰지고 있나」를 알려면 누구나 봐야 한다. 반면 **거부된 레코드 원문**은 그 고객의\n * 업무 데이터다(품목·수량·거래처가 그대로 들어 있다). 같은 도메인이라는 것만으로 원문까지 열면 필요\n * 없는 사람에게도 열린다.\n *\n * 그래서 원문만 한 겹 뒤에 둔다 — 트윈을 **고칠 수 있는 사람**에게만. 매핑을 고치려면 원천이 실제로\n * 무엇을 보냈는지 봐야 하므로 권한이 그 일과 맞는다. 감출 때는 **감췄다고 말한다**(`samplesWithheld`);\n * 빈 배열로 두면 「거부된 것이 없다」로 읽힌다.\n */\n @Query(returns => ScalarObject, {\n /*\n * ── `null` 이 정답인 자리다 (2026-08-29 실측) ───────────────────────────────\n * 이 조회는 세 경우에 `null` 을 낸다: 다른 도메인의 트윈 · 등록되지 않은 트윈 · 아직 돌지 않는\n * 트윈. 설명문에도 그렇게 적혀 있었는데 **스키마가 그것을 금지하고 있었다.**\n *\n * 그래서 화면이 아직 돌지 않는 트윈을 열면 조회 전체가 이 오류로 죽었다.\n *\n * Cannot return null for non-nullable field Query.twinIngestHealth.\n *\n * 「없다」를 낼 수 있어야 화면이 「아직 잰 것이 없다」를 말한다. 빈 객체로 바꾸면 그것이\n * 「재 봤는데 아무것도 없다」로 읽힌다 — 두 사실이 다르다.\n */\n nullable: true,\n description:\n 'Answers one question: is this twin keeping in step with the floor? Returns a verdict — syncing | mapping-blocked | no-traffic | not-connected | not-applicable | unknown — together with the threshold it was judged by, so a screen can say why. `recent` is the last **closed** 10-minute window (null while the first window is still filling: a partially filled window under-counts, so it is not called \"recent\"), and `trend` carries up to 36 closed windows (6 hours) oldest-first with offered / accepted / rejected per window. Accepted and rejected are reported together because counting only rejections makes \"nothing is arriving\" and \"everything is fine\" both look like zero. `reasons` groups rejections by the reason the kernel gave, most frequent first, each with the last time that reason occurred — a reason whose time has stopped is one you already fixed. `samples` holds one rejected record per reason (the raw record, truncated with recordTruncated when long) because a reason alone does not tell you what the source actually sent. Counts that could not be shown are reported rather than dropped: sampleDropped, trendDropped, and reasonsCapped when distinct reasons hit their limit and the rest were folded into \"(other reasons)\". A twin that has never received anything reports null figures rather than zeros, since never-measured and measured-as-zero are different facts. This ledger lives in memory: it covers roughly the last 6 hours and resets when the process restarts. The verdict, trend and reasons are returned to anyone in the owning domain, but the raw records in `samples` are business data of that tenant, so they require the same privilege as changing the twin (twin/mutation, with domain owner and superuser granted); when the caller lacks it, `samples` comes back empty and `samplesWithheld` says how many were held back — an empty list alone would read as \"nothing was rejected\". `modelGap` answers a different question from the verdict: the verdict is about the feed (is anything arriving, does it pass), while modelGap is about the model (did we declare what the source knows). The ingest gate deliberately does not reject a record that references an id the master never declared — a source may legitimately report a new place before the structure sync catches up, and rejecting it would lose that measurement. The kernel absorbs it and marks it `origin: \"observed\"` instead, and modelGap counts those per axis (locations / equipment / persons) with a capped id list plus how many more were left out. It also carries the kernel\\'s `unhandled` tally — event kinds the twin could not apply at all — and, for energy twins, how many state reports arrived from equipment the model does not know. `notCovered` names axes this cannot answer yet: meters carry no origin mark in the kernel, so a mistyped meterId silently becomes a new meter whose kW is accumulated into the site demand. Null while the twin is not running: there is nothing to compare against, and never-compared is not the same as none. Null if the instance is not in the current domain.'\n })\n async twinIngestHealth(@Arg('instanceId') instanceId: string, @Ctx() context: ResolverContext): Promise<any> {\n const domainId = context.state.domain.id\n /*\n * 테넌트 격리 — 다만 `owns` 는 **도는 트윈**만 참이다. 멈춘 트윈의 장부도 볼 수 있어야 하므로\n * (「고치려고 멈춰 뒀는데 마지막에 뭐가 거부됐나」), 등록 여부로도 판정한다. 도는 트윈이면 DB 를\n * 묻지 않는다(대부분의 조회가 그쪽이다).\n */\n if (!TwinEngine.owns(domainId, instanceId) && !(await TwinEngine.ownsRegistered(domainId, instanceId))) return null\n\n const view = TwinEngine.ingestHealthOf(domainId, instanceId)\n\n /*\n * ── 「원본이 말한 적 없는 것」을 함께 낸다 (2026-08-20) ──────────────────────\n * 유입 문은 참조 오류를 거부하지 않는다(실측: 모르는 로케이션·bizStep·계량기가 모두 통과). 거부하지\n * 않는 것이 옳다 — 원천이 모델보다 먼저 새 자리를 말하는 것은 정상이고, 거부하면 그 선행 계측을\n * 잃는다. 대신 커널이 **흡수하고 표시한다**(`origin: 'observed'`).\n *\n * 그 표시를 읽는 곳이 모델 인스펙션 화면 하나였다. 그런데 「이 트윈이 현장과 맞춰지고 있나」를\n * 보는 사람이 바로 그것을 알아야 한다 — 원천이 우리가 모르는 로케이션만 보내면 트윈은 도는데\n * 스키매틱은 비어 있고, 그게 우리가 「빈 공장」이라 불러 온 상태다.\n *\n * 판정에는 섞지 않는다. 판정은 **피드**를 말하고 이것은 **모델**을 말한다(`model-gap.ts`).\n */\n const modelGap = modelGapOf(TwinEngine.snapshot(domainId, instanceId))\n\n /* 원문을 볼 수 있나 — 못 보면 **몇 건을 감췄는지** 함께 낸다(빈 배열은 「없었다」로 읽힌다). */\n const maySeeRecords = await checkPermission(\n RAW_RECORD_PRIVILEGE,\n context.state.user,\n context.state.domain\n )\n if (maySeeRecords) return { ...view, modelGap }\n\n const { samples, ...rest } = view\n return { ...rest, modelGap, samples: [], samplesWithheld: samples.length }\n }\n\n /*\n * ── 이벤트 루프 부하 — **운영 계기판이다. 테넌트 기능이 아니다.** ─────────────\n *\n * `twinMetrics` 는 \"얼마나 들어오고 나가나\"(초당 이벤트)를 답한다. 여기서 묻는 것은 \"그 일을 하느라\n * 이벤트 루프를 얼마나 붙잡고 있나\" 다. 시뮬 틱은 트윈마다 setInterval 로 **메인 루프에서 동기로**\n * 돌기 때문에, 한 트윈이 느려지면 HTTP·구독이 함께 느려진다. 실제로 그 상태를 겪었는데 볼 계기판이\n * 없어서 \"포트는 열렸는데 응답이 없다\" 로만 보였다.\n *\n * ── 왜 도메인으로 자르지 않나 ───────────────────────────────────────────────\n * 처음에는 호출자의 도메인만 냈다. 테넌트 격리로는 맞아 보이지만 **이벤트 루프는 프로세스 하나가\n * 공유한다.** 실측으로 드러났다: 한 도메인 트윈 22개가 CPU 를 110% 쓰는 동안, 다른 도메인 화면은\n * `실행 중 0 · 루프 점유 —` 였다. \"내 도메인은 한가한데 왜 느리지\" 를 설명할 수 없었다.\n *\n * 그렇다고 테넌트에게 남의 부하를 조금 보여 주는 것도 답이 아니다 — 쓸 수 없는 정보이고 봐서도 안 된다.\n * 층이 잘못이었다: 이것은 **프로세스를 운영하는 사람의 계기판**이다. 그래서 도메인으로 자르지 않고\n * (전 테넌트 합산) **슈퍼유저에게만** 연다.\n */\n @Directive('@privilege(superUserGranted: true)')\n @Query(returns => ScalarObject, {\n description:\n 'Event-loop load of every twin in this process, for system operators (superuser only). Deliberately NOT scoped to the caller\\'s domain: the event loop is shared by the whole process, so a tenant-scoped view reports an idle domain while the machine is saturated by someone else. Reports how many instances are running (by mode), how many forecast forks have been created, and per instance how long each kind of work takes (kernel tick, state snapshot, broadcast payload, publish, journal write, live ingest, Monte-Carlo forecast, fork trajectory). Each instance reports both `recent` (the last closed sampling window — what it is doing now) and `total` (cumulative since boot), with `busyRatio` telling how much of the window that twin held the loop. `fleetBusyRatio` above 1 means the twins together cannot finish inside one tick interval, which is when HTTP and subscriptions start to stall. Work that has never been measured is reported as null rather than zero, because zero would read as \"instant\".'\n })\n async twinLoad(@Ctx() context: ResolverContext): Promise<any> {\n const fleet = TwinEngine.fleet() // 프로세스 전체 — 도메인으로 자르지 않는다(위 주석)\n\n /* 도메인 id 는 UUID 라 사람이 못 읽는다. 읽을 수 있는 이름을 채운다 — **모르면 id 를 그대로 둔다**\n (지어내지 않는다). 라이브 인스턴스에는 이미 붙어 있고, 시뮬은 여기서 한 번 조회한다. */\n const ids = [...new Set((fleet.byDomain ?? []).map((d: any) => d.domainId).filter((v: any) => v && v !== '(unknown)'))]\n if (ids.length) {\n const found = await getRepository(Domain).find({ where: { id: In(ids as string[]) } })\n const nameOf = new Map(found.map(d => [d.id, d.subdomain || d.name]))\n for (const d of fleet.byDomain) if (nameOf.get(d.domainId)) d.label = nameOf.get(d.domainId) as string\n for (const r of fleet.slowest ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n for (const r of fleet.details ?? []) if (r.domainId && nameOf.get(r.domainId)) r.domainLabel = nameOf.get(r.domainId)\n }\n\n /*\n * 프로세스가 실제로 쓴 CPU — **계기판의 다른 숫자와 뜻이 다르다.**\n *\n * 위의 값들은 트윈의 계측된 작업만 잰 것이라, 그 밖의 일(GraphQL·TypeORM·GC·webpack·HTTP)은\n * 분모에도 분자에도 없다. \"루프 점유 <1%\" 인데 CPU 는 110% 였던 상태를 실제로 겪었다. 두 숫자를\n * 나란히 두어야 **트윈이 범인인지** 가릴 수 있다.\n *\n * 표본 간 차이라 첫 호출은 `null` 이다 — 0 이 아니다.\n */\n const usage = process.cpuUsage()\n const cpu = sampleCpu(CPU_SAMPLER, usage.user + usage.system, Date.now())\n\n return { ...fleet, processCpuRatio: cpu?.ratio ?? null, processCpuSpanMs: cpu?.spanMs ?? null }\n }\n}\n"]}
|
|
@@ -81,7 +81,7 @@ async function axisSeenInJournal(repo, where, axis) {
|
|
|
81
81
|
/*
|
|
82
82
|
* 접두사로 찾는 축(물품) — 한 건만 있으면 된다. `Like` 를 쓰는 대신 **낱말 목록**을 쓰지 않는 이유는
|
|
83
83
|
* EPCIS 사건 이름이 커널에서 자라기 때문이다(객체·집약·변환·거래…). 이름을 여기 베끼면 커널이
|
|
84
|
-
* 하나 늘릴 때 이 자리가
|
|
84
|
+
* 하나 늘릴 때 이 자리가 알리지 않고 낡는다.
|
|
85
85
|
*/
|
|
86
86
|
const { Like } = await Promise.resolve().then(() => __importStar(require('typeorm')));
|
|
87
87
|
return (0, journal_count_js_1.anyJournalRow)(repo, { ...where, eventType: Like(`${spec.prefix}%`) });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"axis-journal-evidence.js","sourceRoot":"","sources":["../../../server/service/twin-model/axis-journal-evidence.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,8CAqBC;AAxDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qEAA8D;AAE9D;;;;GAIG;AACH,MAAM,WAAW,GAA0D;IACzE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,EAAE;IACjC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE;IACnC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC3B,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,sBAAsB,CAAC,EAAE;CACnD,CAAA;AAED,qDAAqD;AAC9C,KAAK,UAAU,iBAAiB,CACrC,IAAS,EACT,KAA8B,EAC9B,IAAY;IAEZ,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,MAAM,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAA;QACxE,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD;;;;OAIG;IACH,MAAM,EAAE,IAAI,EAAE,GAAG,wDAAa,SAAS,GAAC,CAAA;IACxC,OAAO,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["/*\n * **저널에 그 축의 사실이 있나** — 「아직 없다」와 「왔는데 상태에 없다」를 가른다 (2026-08-22).\n *\n * ── 왜 필요했나 (실측) ──────────────────────────────────────────────────────\n * 화면이 「작업 — 아직 하나도 없습니다」라고 말하는데, 그 트윈의 저널에는 `task.status` 5,786건이\n * 있었고 **마지막 상태가 `in-progress`** 였다. 사용자가 「작업 지시 835건인데 작업이 0인 이유」를\n * 물어 반나절을 조사해 원인을 찾았다: 관측 모드 커널이 관측기의 작업을 스냅샷으로 내보내지 않는다\n * (커널 자리이고 그 레인에 넘겼다).\n *\n * 화면 몫은 다른 것이다. **카드가 단정하면 안 된다.** 「아직 하나도 없습니다」는 「선언은 있고 사실이\n * 아직 일어나지 않았다」는 뜻인데, 실제로는 「사실이 일어났는데 상태에 오지 못했다」였다 — 그 차이가\n * 사용자를 반나절 조사로 보낸다.\n *\n * 그래서 상태가 빈 축에 대해 **저널을 한 건만 물어본다.** 있으면 화면이 그렇게 말한다.\n *\n * ── 어휘의 집 ───────────────────────────────────────────────────────────────\n * 어느 사건이 어느 축을 채우는지는 **커널이 선언한 낱말**에서 온다(`OP_EVENT`·`ENERGY_EVENT`, 그리고\n * EPCIS 접두사). 여기서 이름을 새로 만들지 않는다 — 커널이 낱말을 늘리면 이 표에 한 줄이 늘고,\n * 표에 없는 축은 **아무 말도 하지 않는다**(모르는 것을 지어내지 않는다).\n */\nimport { anyJournalRow } from '../twin-event/journal-count.js'\n\n/**\n * 상태 축 → 그 축을 채우는 사건 낱말. 표에 없으면 판단하지 않는다.\n *\n * 물품은 EPCIS 로 온다(사건 이름이 여러 개다) — 그래서 접두사로 본다.\n */\nconst AXIS_EVENTS: Record<string, { types?: string[]; prefix?: string }> = {\n tasks: { types: ['task.status'] },\n orders: { types: ['order.status'] },\n items: { prefix: 'epcis.' },\n demandWindows: { types: ['energy.demand.window'] }\n}\n\n/** 이 축의 사실이 저널에 한 건이라도 있나. 판단할 수 없으면 `null`(모른다). */\nexport async function axisSeenInJournal(\n repo: any,\n where: Record<string, unknown>,\n axis: string\n): Promise<boolean | null> {\n const spec = AXIS_EVENTS[axis]\n if (!spec) return null\n\n if (spec.types) {\n for (const t of spec.types) {\n if (await anyJournalRow(repo, { ...where, eventType: t })) return true\n }\n return false\n }\n /*\n * 접두사로 찾는 축(물품) — 한 건만 있으면 된다. `Like` 를 쓰는 대신 **낱말 목록**을 쓰지 않는 이유는\n * EPCIS 사건 이름이 커널에서 자라기 때문이다(객체·집약·변환·거래…). 이름을 여기 베끼면 커널이\n * 하나 늘릴 때 이 자리가
|
|
1
|
+
{"version":3,"file":"axis-journal-evidence.js","sourceRoot":"","sources":["../../../server/service/twin-model/axis-journal-evidence.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,8CAqBC;AAxDD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qEAA8D;AAE9D;;;;GAIG;AACH,MAAM,WAAW,GAA0D;IACzE,KAAK,EAAE,EAAE,KAAK,EAAE,CAAC,aAAa,CAAC,EAAE;IACjC,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE;IACnC,KAAK,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE;IAC3B,aAAa,EAAE,EAAE,KAAK,EAAE,CAAC,sBAAsB,CAAC,EAAE;CACnD,CAAA;AAED,qDAAqD;AAC9C,KAAK,UAAU,iBAAiB,CACrC,IAAS,EACT,KAA8B,EAC9B,IAAY;IAEZ,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAA;IAC9B,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAA;IAEtB,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,MAAM,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAA;QACxE,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC;IACD;;;;OAIG;IACH,MAAM,EAAE,IAAI,EAAE,GAAG,wDAAa,SAAS,GAAC,CAAA;IACxC,OAAO,IAAA,gCAAa,EAAC,IAAI,EAAE,EAAE,GAAG,KAAK,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,CAAA;AAC9E,CAAC","sourcesContent":["/*\n * **저널에 그 축의 사실이 있나** — 「아직 없다」와 「왔는데 상태에 없다」를 가른다 (2026-08-22).\n *\n * ── 왜 필요했나 (실측) ──────────────────────────────────────────────────────\n * 화면이 「작업 — 아직 하나도 없습니다」라고 말하는데, 그 트윈의 저널에는 `task.status` 5,786건이\n * 있었고 **마지막 상태가 `in-progress`** 였다. 사용자가 「작업 지시 835건인데 작업이 0인 이유」를\n * 물어 반나절을 조사해 원인을 찾았다: 관측 모드 커널이 관측기의 작업을 스냅샷으로 내보내지 않는다\n * (커널 자리이고 그 레인에 넘겼다).\n *\n * 화면 몫은 다른 것이다. **카드가 단정하면 안 된다.** 「아직 하나도 없습니다」는 「선언은 있고 사실이\n * 아직 일어나지 않았다」는 뜻인데, 실제로는 「사실이 일어났는데 상태에 오지 못했다」였다 — 그 차이가\n * 사용자를 반나절 조사로 보낸다.\n *\n * 그래서 상태가 빈 축에 대해 **저널을 한 건만 물어본다.** 있으면 화면이 그렇게 말한다.\n *\n * ── 어휘의 집 ───────────────────────────────────────────────────────────────\n * 어느 사건이 어느 축을 채우는지는 **커널이 선언한 낱말**에서 온다(`OP_EVENT`·`ENERGY_EVENT`, 그리고\n * EPCIS 접두사). 여기서 이름을 새로 만들지 않는다 — 커널이 낱말을 늘리면 이 표에 한 줄이 늘고,\n * 표에 없는 축은 **아무 말도 하지 않는다**(모르는 것을 지어내지 않는다).\n */\nimport { anyJournalRow } from '../twin-event/journal-count.js'\n\n/**\n * 상태 축 → 그 축을 채우는 사건 낱말. 표에 없으면 판단하지 않는다.\n *\n * 물품은 EPCIS 로 온다(사건 이름이 여러 개다) — 그래서 접두사로 본다.\n */\nconst AXIS_EVENTS: Record<string, { types?: string[]; prefix?: string }> = {\n tasks: { types: ['task.status'] },\n orders: { types: ['order.status'] },\n items: { prefix: 'epcis.' },\n demandWindows: { types: ['energy.demand.window'] }\n}\n\n/** 이 축의 사실이 저널에 한 건이라도 있나. 판단할 수 없으면 `null`(모른다). */\nexport async function axisSeenInJournal(\n repo: any,\n where: Record<string, unknown>,\n axis: string\n): Promise<boolean | null> {\n const spec = AXIS_EVENTS[axis]\n if (!spec) return null\n\n if (spec.types) {\n for (const t of spec.types) {\n if (await anyJournalRow(repo, { ...where, eventType: t })) return true\n }\n return false\n }\n /*\n * 접두사로 찾는 축(물품) — 한 건만 있으면 된다. `Like` 를 쓰는 대신 **낱말 목록**을 쓰지 않는 이유는\n * EPCIS 사건 이름이 커널에서 자라기 때문이다(객체·집약·변환·거래…). 이름을 여기 베끼면 커널이\n * 하나 늘릴 때 이 자리가 알리지 않고 낡는다.\n */\n const { Like } = await import('typeorm')\n return anyJournalRow(repo, { ...where, eventType: Like(`${spec.prefix}%`) })\n}\n"]}
|
|
@@ -78,6 +78,31 @@ const EVENTS = [
|
|
|
78
78
|
];
|
|
79
79
|
/* Part: 사건이 답하는 다섯 가지 — 무엇을·어디서·언제·왜, 그리고 어떤 거래로. */
|
|
80
80
|
const FIELDS = [
|
|
81
|
+
{
|
|
82
|
+
/*
|
|
83
|
+
* **무엇을 한 것인가** — 표준 `action`: `ADD`(생겼다) · `OBSERVE`(보았다) · `DELETE`(없어졌다).
|
|
84
|
+
*
|
|
85
|
+
* 이 줄이 표에 없었다. 표준 필드이고 우리가 계약·유입·검증에서 다 쓰는데도 세지 않았습니다.
|
|
86
|
+
*
|
|
87
|
+
* ── 2026-08-25 그 뜻이 커졌다 ─────────────────────────────────────────────
|
|
88
|
+
* `DELETE` 를 낱개 번호가 있는 물품에만 적용하고 있었다. 그래서 **낱개 번호가 없는 현장에서는
|
|
89
|
+
* 재고를 지울 방법이 하나도 없었다** — 승화푸드는 모든 재고가 수량으로 오는 현장이고, 한 번 본
|
|
90
|
+
* 재고가 영원히 남았다(실제 1,704건인데 트윈이 10,038건).
|
|
91
|
+
*
|
|
92
|
+
* 이제 수량으로 온 재고도 없어질 수 있다. 자리를 함께 말하면 그 자리의 것만, 말하지 않으면
|
|
93
|
+
* 그 로트를 모든 자리에서 지운다 — 짐작이 아니라 진술의 범위를 읽는다.
|
|
94
|
+
*
|
|
95
|
+
* 화면은 아직 없다. 「이것이 없어졌다」를 사람에게 보이는 자리를 만들지 않았다.
|
|
96
|
+
*/
|
|
97
|
+
std: 'action',
|
|
98
|
+
part: 'fields',
|
|
99
|
+
label: 'twin.epcis.action',
|
|
100
|
+
axis: null,
|
|
101
|
+
shownOn: ['items'],
|
|
102
|
+
structure: 'full',
|
|
103
|
+
behavior: 'full',
|
|
104
|
+
surface: 'none'
|
|
105
|
+
},
|
|
81
106
|
{
|
|
82
107
|
std: 'epcList / quantityList',
|
|
83
108
|
part: 'fields',
|
|
@@ -146,10 +171,10 @@ const FIELDS = [
|
|
|
146
171
|
},
|
|
147
172
|
{
|
|
148
173
|
/*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
* `surface: 'none'` 이면 갈 곳을 적지 않는다(§`shownOn` 규칙).
|
|
174
|
+
* `shownOn` 은 「어디에 속하나」다 — 정정 선언은 물품의 사실이다. 화면에 그려지는지는 `surface` 가
|
|
175
|
+
* 따로 답한다(지금은 `none` 이고 `note` 가 그 사실을 말한다). 둘은 다른 물음이다(§`shownOn`).
|
|
152
176
|
*/
|
|
177
|
+
shownOn: ['items'],
|
|
153
178
|
std: 'errorDeclaration',
|
|
154
179
|
part: 'fields',
|
|
155
180
|
label: 'twin.epcis.errorDeclaration',
|
|
@@ -161,6 +186,8 @@ const FIELDS = [
|
|
|
161
186
|
},
|
|
162
187
|
/* 모양만 있고 아무도 채우지 않는 것들 — 구조만 있음. */
|
|
163
188
|
{
|
|
189
|
+
/* 어디에 속하나 — 소유·보관의 이전은 그 물품의 사실이다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */
|
|
190
|
+
shownOn: ['items'],
|
|
164
191
|
std: 'sourceList / destinationList',
|
|
165
192
|
part: 'fields',
|
|
166
193
|
label: 'twin.epcis.sourceDestination',
|
|
@@ -171,6 +198,8 @@ const FIELDS = [
|
|
|
171
198
|
note: 'twin.epcis.note.sourceDestination'
|
|
172
199
|
},
|
|
173
200
|
{
|
|
201
|
+
/* 어디에 속하나 — 지속 처분은 그 물품에 붙는다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */
|
|
202
|
+
shownOn: ['items'],
|
|
174
203
|
std: 'persistentDisposition',
|
|
175
204
|
part: 'fields',
|
|
176
205
|
label: 'twin.epcis.persistentDisposition',
|
|
@@ -194,6 +223,8 @@ const FIELDS = [
|
|
|
194
223
|
* 그 구별을 표가 말하지 않으면 「센서를 못 받는 트윈」으로 읽힌다.
|
|
195
224
|
*/
|
|
196
225
|
{
|
|
226
|
+
/* 어디에 속하나 — 표준에서 센서 관측은 **개체에 붙는다** — 우리가 그것을 자리로 옮긴 것이 다른 이야기다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */
|
|
227
|
+
shownOn: ['items'],
|
|
197
228
|
std: 'sensorElementList',
|
|
198
229
|
part: 'fields',
|
|
199
230
|
label: 'twin.epcis.sensorElement',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"epcis-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/epcis-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * GS1 EPCIS 2.0 적합성 기준 — **물류·제조 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 적합성 패널이 ISA-95 하나만 말하고 있었다. 그런데 창고·야드·공장 트윈에서 「무슨 일이 있었나」와\n * 「이 번호가 무엇을 가리키나」를 정하는 것은 EPCIS 다. ISA-95 표만 보이면 그 트윈이 기대는 기준의\n * 절반이 화면에 없다.\n *\n * ── 수준은 근거를 보고 적었다 ───────────────────────────────────────────────\n * 「있다/없다」를 기억으로 적지 않는다. 각 줄의 판단 근거:\n * · 타입 선언 — 커널 `epcis.ts`(이벤트·필드의 모양)\n * · 실제 사용 — 커널 도메인(`mes-kernel`·`face2-adapter`·`observed-reducer`)과 호스트 인제스트\n * · 화면 노출 — 저널·계보·엔티티 360\n * 모양만 있고 아무도 채우지 않는 필드는 **구조만 있음**으로 적는다(그게 사실이다).\n *\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/* Part: EPCIS 2.0 의 사건 종류 — 무엇이 일어났는가. */\nconst EVENTS: Isa95Concept[] = [\n {\n std: 'ObjectEvent',\n part: 'events',\n label: 'twin.epcis.ObjectEvent',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.ObjectEvent'\n },\n {\n std: 'AggregationEvent',\n part: 'events',\n label: 'twin.epcis.AggregationEvent',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.AggregationEvent'\n },\n {\n std: 'TransactionEvent',\n part: 'events',\n label: 'twin.epcis.TransactionEvent',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.TransactionEvent'\n },\n {\n std: 'TransformationEvent',\n part: 'events',\n label: 'twin.epcis.TransformationEvent',\n axis: null,\n shownOn: ['items', 'operations'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.TransformationEvent'\n },\n /* EPCIS 2.0 이 더한 사건 — 우리는 타입조차 두지 않았다. 없는 것은 없다고 적는다. */\n {\n std: 'AssociationEvent',\n part: 'events',\n label: 'twin.epcis.AssociationEvent',\n axis: null,\n structure: 'none',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.AssociationEvent'\n }\n]\n\n/* Part: 사건이 답하는 다섯 가지 — 무엇을·어디서·언제·왜, 그리고 어떤 거래로. */\nconst FIELDS: Isa95Concept[] = [\n {\n std: 'epcList / quantityList',\n part: 'fields',\n label: 'twin.epcis.identity',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.identity'\n },\n /*\n * ── 2026-08-24 주장 근거가 달라졌다 ────────────────────────────────────────\n * 이 줄은 오래 `full` 이었지만 근거가 **우리 선언**이었다. 이제 **정본 원문 대조**다:\n * CBV Standard Release 2.0(Ratified Jun 2022) §7.2.3 의 처분 38개를 전수 확인했다.\n *\n * 그 대조가 실제로 셋을 바꿨다.\n * · `expired` — 있는 낱말인데 우리가 쓰지 않고 있었다(기한 경과를 처분으로 말할 수 있게 됐다)\n * · `conformant` / `non_conformant` — 검사 판정을 처분으로 남기는 낱말. 함께 `inspecting` bizStep\n * · `non_sellable_expired` 는 표준이 **폐기**하고 `expired` 로 대체한 것을 확인\n *\n * ★ 그리고 **검증 방법에 함정이 있다**: `ref.gs1.org/cbv/…` 로 URN 을 조회하면 **지어낸 값에도\n * 똑같은 응답**이 온다. 그것으로 확인했다고 여기면 없는 낱말을 발행한다(한 번 그렇게 했다).\n * 확인은 정본 문서로만 한다.\n */\n {\n std: 'bizStep / disposition (CBV)',\n part: 'fields',\n label: 'twin.epcis.cbv',\n axis: null,\n shownOn: ['items', 'tasks'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.cbv'\n },\n {\n std: 'readPoint / bizLocation',\n part: 'fields',\n label: 'twin.epcis.where',\n axis: 'locations',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.where'\n },\n {\n std: 'bizTransactionList',\n part: 'fields',\n label: 'twin.epcis.bizTransaction',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.bizTransaction'\n },\n {\n std: 'ilmd',\n part: 'fields',\n label: 'twin.epcis.ilmd',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.ilmd'\n },\n {\n /*\n * 2026-08-24 — **`shownOn` 을 걷었다.** 같은 줄의 `note` 가 「화면에 보이는 곳은 아직 없습니다」라고\n * 말하는데 배지는 「물품에서 표시됩니다」라고 말하고 있었다 — 한 줄이 서로 반대를 말했다.\n * `surface: 'none'` 이면 갈 곳을 적지 않는다(§`shownOn` 규칙).\n */\n std: 'errorDeclaration',\n part: 'fields',\n label: 'twin.epcis.errorDeclaration',\n axis: null,\n structure: 'full',\n behavior: 'partial',\n surface: 'none',\n note: 'twin.epcis.note.errorDeclaration'\n },\n /* 모양만 있고 아무도 채우지 않는 것들 — 구조만 있음. */\n {\n std: 'sourceList / destinationList',\n part: 'fields',\n label: 'twin.epcis.sourceDestination',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sourceDestination'\n },\n {\n std: 'persistentDisposition',\n part: 'fields',\n label: 'twin.epcis.persistentDisposition',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.persistentDisposition'\n },\n /*\n * ── 2026-08-24 계측이 어디로 들어오는지 확정됐다 ──────────────────────────\n * 「계측은 EPCIS 센서 필드로 오지 않는다」는 사실이 이제 **자리를 갖는다**: 자리의 상시 관측은\n * ISA-95 `OperationsEvent` 로 들어오고(커널 `location.measured` 채널), 에너지는 그 위의 누적기가\n * 받는다. ISA-95 표의 `OperationsEvent` 줄이 그 판정을 든다.\n *\n * 왜 EPCIS 가 아닌가: `SensorElement` 는 **개체에 붙는** 관측이고, 냉장실의 온도는 그 안의 물건\n * 수백 개와 관계되며 그 수백 개는 시간에 따라 바뀐다. 개체마다 붙이면 같은 사실이 수백 벌이 되고,\n * 물건이 떠나면 그 방의 온도 이력이 함께 사라진다. 주인은 **자리**다.\n *\n * 그래서 이 줄은 `behavior: 'none'` 으로 남는다 — 결손이 아니라 **다른 층으로 들어온다는 사실**이다.\n * 그 구별을 표가 말하지 않으면 「센서를 못 받는 트윈」으로 읽힌다.\n */\n {\n std: 'sensorElementList',\n part: 'fields',\n label: 'twin.epcis.sensorElement',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sensorElement'\n }\n]\n\nexport const EPCIS_CONCEPTS: Isa95Concept[] = [...EVENTS, ...FIELDS]\n\nexport function epcisCoverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = EPCIS_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'GS1 EPCIS 2.0',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"epcis-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/epcis-coverage.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * GS1 EPCIS 2.0 적합성 기준 — **물류·제조 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 적합성 패널이 ISA-95 하나만 말하고 있었다. 그런데 창고·야드·공장 트윈에서 「무슨 일이 있었나」와\n * 「이 번호가 무엇을 가리키나」를 정하는 것은 EPCIS 다. ISA-95 표만 보이면 그 트윈이 기대는 기준의\n * 절반이 화면에 없다.\n *\n * ── 수준은 근거를 보고 적었다 ───────────────────────────────────────────────\n * 「있다/없다」를 기억으로 적지 않는다. 각 줄의 판단 근거:\n * · 타입 선언 — 커널 `epcis.ts`(이벤트·필드의 모양)\n * · 실제 사용 — 커널 도메인(`mes-kernel`·`face2-adapter`·`observed-reducer`)과 호스트 인제스트\n * · 화면 노출 — 저널·계보·엔티티 360\n * 모양만 있고 아무도 채우지 않는 필드는 **구조만 있음**으로 적는다(그게 사실이다).\n *\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/* Part: EPCIS 2.0 의 사건 종류 — 무엇이 일어났는가. */\nconst EVENTS: Isa95Concept[] = [\n {\n std: 'ObjectEvent',\n part: 'events',\n label: 'twin.epcis.ObjectEvent',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.ObjectEvent'\n },\n {\n std: 'AggregationEvent',\n part: 'events',\n label: 'twin.epcis.AggregationEvent',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.AggregationEvent'\n },\n {\n std: 'TransactionEvent',\n part: 'events',\n label: 'twin.epcis.TransactionEvent',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.TransactionEvent'\n },\n {\n std: 'TransformationEvent',\n part: 'events',\n label: 'twin.epcis.TransformationEvent',\n axis: null,\n shownOn: ['items', 'operations'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.TransformationEvent'\n },\n /* EPCIS 2.0 이 더한 사건 — 우리는 타입조차 두지 않았다. 없는 것은 없다고 적는다. */\n {\n std: 'AssociationEvent',\n part: 'events',\n label: 'twin.epcis.AssociationEvent',\n axis: null,\n structure: 'none',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.AssociationEvent'\n }\n]\n\n/* Part: 사건이 답하는 다섯 가지 — 무엇을·어디서·언제·왜, 그리고 어떤 거래로. */\nconst FIELDS: Isa95Concept[] = [\n {\n /*\n * **무엇을 한 것인가** — 표준 `action`: `ADD`(생겼다) · `OBSERVE`(보았다) · `DELETE`(없어졌다).\n *\n * 이 줄이 표에 없었다. 표준 필드이고 우리가 계약·유입·검증에서 다 쓰는데도 세지 않았습니다.\n *\n * ── 2026-08-25 그 뜻이 커졌다 ─────────────────────────────────────────────\n * `DELETE` 를 낱개 번호가 있는 물품에만 적용하고 있었다. 그래서 **낱개 번호가 없는 현장에서는\n * 재고를 지울 방법이 하나도 없었다** — 승화푸드는 모든 재고가 수량으로 오는 현장이고, 한 번 본\n * 재고가 영원히 남았다(실제 1,704건인데 트윈이 10,038건).\n *\n * 이제 수량으로 온 재고도 없어질 수 있다. 자리를 함께 말하면 그 자리의 것만, 말하지 않으면\n * 그 로트를 모든 자리에서 지운다 — 짐작이 아니라 진술의 범위를 읽는다.\n *\n * 화면은 아직 없다. 「이것이 없어졌다」를 사람에게 보이는 자리를 만들지 않았다.\n */\n std: 'action',\n part: 'fields',\n label: 'twin.epcis.action',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'none'\n },\n {\n std: 'epcList / quantityList',\n part: 'fields',\n label: 'twin.epcis.identity',\n axis: 'items',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.identity'\n },\n /*\n * ── 2026-08-24 주장 근거가 달라졌다 ────────────────────────────────────────\n * 이 줄은 오래 `full` 이었지만 근거가 **우리 선언**이었다. 이제 **정본 원문 대조**다:\n * CBV Standard Release 2.0(Ratified Jun 2022) §7.2.3 의 처분 38개를 전수 확인했다.\n *\n * 그 대조가 실제로 셋을 바꿨다.\n * · `expired` — 있는 낱말인데 우리가 쓰지 않고 있었다(기한 경과를 처분으로 말할 수 있게 됐다)\n * · `conformant` / `non_conformant` — 검사 판정을 처분으로 남기는 낱말. 함께 `inspecting` bizStep\n * · `non_sellable_expired` 는 표준이 **폐기**하고 `expired` 로 대체한 것을 확인\n *\n * ★ 그리고 **검증 방법에 함정이 있다**: `ref.gs1.org/cbv/…` 로 URN 을 조회하면 **지어낸 값에도\n * 똑같은 응답**이 온다. 그것으로 확인했다고 여기면 없는 낱말을 발행한다(한 번 그렇게 했다).\n * 확인은 정본 문서로만 한다.\n */\n {\n std: 'bizStep / disposition (CBV)',\n part: 'fields',\n label: 'twin.epcis.cbv',\n axis: null,\n shownOn: ['items', 'tasks'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.cbv'\n },\n {\n std: 'readPoint / bizLocation',\n part: 'fields',\n label: 'twin.epcis.where',\n axis: 'locations',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.epcis.note.where'\n },\n {\n std: 'bizTransactionList',\n part: 'fields',\n label: 'twin.epcis.bizTransaction',\n axis: 'orders',\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.bizTransaction'\n },\n {\n std: 'ilmd',\n part: 'fields',\n label: 'twin.epcis.ilmd',\n axis: null,\n shownOn: ['items'],\n structure: 'full',\n behavior: 'full',\n surface: 'partial',\n note: 'twin.epcis.note.ilmd'\n },\n {\n /*\n * `shownOn` 은 「어디에 속하나」다 — 정정 선언은 물품의 사실이다. 화면에 그려지는지는 `surface` 가\n * 따로 답한다(지금은 `none` 이고 `note` 가 그 사실을 말한다). 둘은 다른 물음이다(§`shownOn`).\n */\n shownOn: ['items'],\n std: 'errorDeclaration',\n part: 'fields',\n label: 'twin.epcis.errorDeclaration',\n axis: null,\n structure: 'full',\n behavior: 'partial',\n surface: 'none',\n note: 'twin.epcis.note.errorDeclaration'\n },\n /* 모양만 있고 아무도 채우지 않는 것들 — 구조만 있음. */\n {\n /* 어디에 속하나 — 소유·보관의 이전은 그 물품의 사실이다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'sourceList / destinationList',\n part: 'fields',\n label: 'twin.epcis.sourceDestination',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sourceDestination'\n },\n {\n /* 어디에 속하나 — 지속 처분은 그 물품에 붙는다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'persistentDisposition',\n part: 'fields',\n label: 'twin.epcis.persistentDisposition',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.persistentDisposition'\n },\n /*\n * ── 2026-08-24 계측이 어디로 들어오는지 확정됐다 ──────────────────────────\n * 「계측은 EPCIS 센서 필드로 오지 않는다」는 사실이 이제 **자리를 갖는다**: 자리의 상시 관측은\n * ISA-95 `OperationsEvent` 로 들어오고(커널 `location.measured` 채널), 에너지는 그 위의 누적기가\n * 받는다. ISA-95 표의 `OperationsEvent` 줄이 그 판정을 든다.\n *\n * 왜 EPCIS 가 아닌가: `SensorElement` 는 **개체에 붙는** 관측이고, 냉장실의 온도는 그 안의 물건\n * 수백 개와 관계되며 그 수백 개는 시간에 따라 바뀐다. 개체마다 붙이면 같은 사실이 수백 벌이 되고,\n * 물건이 떠나면 그 방의 온도 이력이 함께 사라진다. 주인은 **자리**다.\n *\n * 그래서 이 줄은 `behavior: 'none'` 으로 남는다 — 결손이 아니라 **다른 층으로 들어온다는 사실**이다.\n * 그 구별을 표가 말하지 않으면 「센서를 못 받는 트윈」으로 읽힌다.\n */\n {\n /* 어디에 속하나 — 표준에서 센서 관측은 **개체에 붙는다** — 우리가 그것을 자리로 옮긴 것이 다른 이야기다. 화면에 그려지는지는 `surface` 가 따로 답한다(§`shownOn`). */\n shownOn: ['items'],\n std: 'sensorElementList',\n part: 'fields',\n label: 'twin.epcis.sensorElement',\n axis: null,\n structure: 'partial',\n behavior: 'none',\n surface: 'none',\n note: 'twin.epcis.note.sensorElement'\n }\n]\n\nexport const EPCIS_CONCEPTS: Isa95Concept[] = [...EVENTS, ...FIELDS]\n\nexport function epcisCoverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = EPCIS_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n model: 'GS1 EPCIS 2.0',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"iec61850-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/iec61850-coverage.ts"],"names":[],"mappings":";;;AAiGA,4CAaC;AA9GD;;;;;;;;;;;;;;;GAeG;AACH,2DAA0F;AAE1F;;;GAGG;AACH,MAAM,aAAa,GAAmB;IACpC;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;;;;;;OAQG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;CACF,CAAA;AAEY,QAAA,iBAAiB,GAAmB,CAAC,GAAG,aAAa,CAAC,CAAA,CAAC,wCAAwC;AAE5G,SAAgB,gBAAgB,CAAC,IAAc;IAK7C,MAAM,QAAQ,GAAG,yBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAA,iCAAa,EAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,KAAK,GAAG,CAAC,CAAuC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IACvG,OAAO;QACL,mDAAmD;QACnD,KAAK,EAAE,wBAAwB;QAC/B,QAAQ;QACR,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;KAC7H,CAAA;AACH,CAAC","sourcesContent":["/*\n * IEC 61850 적합성 기준 — **에너지 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 인스펙션의 적합성 패널은 트윈 종류와 무관하게 ISA-95 하나만 말하고 있었다. 에너지 트윈에서 그것은\n * 거짓은 아니지만(설비·계층·속성은 에너지에서도 ISA-95 자원이다) **부족하다** — 계량기·차단기·태양광·\n * 축전지의 어휘를 주는 것은 IEC 61850 이고, 무엇이 비었는지도 그 목록으로만 셀 수 있다.\n *\n * ── 주장 수준을 못 박는다 ───────────────────────────────────────────────────\n * 여기서 재는 것은 **어휘와 모델**이다. 프로토콜(MMS·GOOSE·샘플드값)을 구현했다는 뜻이 아니며,\n * 그 주장은 적합성 시험이 뒷받침해야 한다. 표 이름을 「IEC 61850 (vocabulary)」로 쓰는 이유다.\n *\n * ── 값은 관측이 아니라 선언이다 ─────────────────────────────────────────────\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n * 자동으로 알 수 있는 것 하나만 자동으로 한다 — 축 이름이 커널 축 목록에 없으면 구조가 없는 것이다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/*\n * Part 7-4 — 논리 노드 클래스. 이 프로파일이 쓰는 것만 적는다(표준 전체를 옮기지 않는다):\n * 계량·계측·개폐·발전·저장.\n */\nconst LOGICAL_NODES: Isa95Concept[] = [ // vocabulary-guard: allow — IEC 61850 의 「논리 노드(logical node)」는 표준 용어다(우리가 은퇴시킨 node 어휘가 아니다)\n /*\n * 계량(적산) — 커넥터가 누적 전력량을 실어 오고 상태가 지점별로 들고 있다. 화면은 계량 컴포넌트가\n * 지점의 값을 보인다.\n */\n {\n std: 'MMTR',\n part: '7-4',\n label: 'twin.iec61850.MMTR',\n axis: 'equipment',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.MMTR'\n },\n /*\n * 계측(유효전력·역률) — 표본이 수요
|
|
1
|
+
{"version":3,"file":"iec61850-coverage.js","sourceRoot":"","sources":["../../../server/service/twin-model/iec61850-coverage.ts"],"names":[],"mappings":";;;AAiGA,4CAaC;AA9GD;;;;;;;;;;;;;;;GAeG;AACH,2DAA0F;AAE1F;;;GAGG;AACH,MAAM,aAAa,GAAmB;IACpC;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,WAAW;QACjB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;OAGG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,eAAe;QACrB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;;;;;;;;OAQG;IACH;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;IACD;QACE,GAAG,EAAE,MAAM;QACX,IAAI,EAAE,KAAK;QACX,KAAK,EAAE,oBAAoB;QAC3B,IAAI,EAAE,IAAI;QACV,OAAO,EAAE,CAAC,WAAW,CAAC;QACtB,SAAS,EAAE,MAAM;QACjB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,MAAM;QACf,IAAI,EAAE,yBAAyB;KAChC;CACF,CAAA;AAEY,QAAA,iBAAiB,GAAmB,CAAC,GAAG,aAAa,CAAC,CAAA,CAAC,wCAAwC;AAE5G,SAAgB,gBAAgB,CAAC,IAAc;IAK7C,MAAM,QAAQ,GAAG,yBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAA,iCAAa,EAAC,CAAC,EAAE,IAAI,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAC5E,MAAM,KAAK,GAAG,CAAC,CAAuC,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,MAAM,CAAA;IACvG,OAAO;QACL,mDAAmD;QACnD,KAAK,EAAE,wBAAwB;QAC/B,QAAQ;QACR,MAAM,EAAE,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC,EAAE;KAC7H,CAAA;AACH,CAAC","sourcesContent":["/*\n * IEC 61850 적합성 기준 — **에너지 트윈이 표준의 어디를 채우고 있나.**\n *\n * ── 왜 따로 있나 ────────────────────────────────────────────────────────────\n * 인스펙션의 적합성 패널은 트윈 종류와 무관하게 ISA-95 하나만 말하고 있었다. 에너지 트윈에서 그것은\n * 거짓은 아니지만(설비·계층·속성은 에너지에서도 ISA-95 자원이다) **부족하다** — 계량기·차단기·태양광·\n * 축전지의 어휘를 주는 것은 IEC 61850 이고, 무엇이 비었는지도 그 목록으로만 셀 수 있다.\n *\n * ── 주장 수준을 못 박는다 ───────────────────────────────────────────────────\n * 여기서 재는 것은 **어휘와 모델**이다. 프로토콜(MMS·GOOSE·샘플드값)을 구현했다는 뜻이 아니며,\n * 그 주장은 적합성 시험이 뒷받침해야 한다. 표 이름을 「IEC 61850 (vocabulary)」로 쓰는 이유다.\n *\n * ── 값은 관측이 아니라 선언이다 ─────────────────────────────────────────────\n * ISA-95 표와 같은 규율이다(`isa95-coverage.ts` 머리말): 사람이 판단해 적고, 틀리면 여기를 고친다.\n * 자동으로 알 수 있는 것 하나만 자동으로 한다 — 축 이름이 커널 축 목록에 없으면 구조가 없는 것이다.\n */\nimport { reconcileAxes, type Isa95Concept, type CoverageLevel } from './isa95-coverage.js'\n\n/*\n * Part 7-4 — 논리 노드 클래스. 이 프로파일이 쓰는 것만 적는다(표준 전체를 옮기지 않는다):\n * 계량·계측·개폐·발전·저장.\n */\nconst LOGICAL_NODES: Isa95Concept[] = [ // vocabulary-guard: allow — IEC 61850 의 「논리 노드(logical node)」는 표준 용어다(우리가 은퇴시킨 node 어휘가 아니다)\n /*\n * 계량(적산) — 커넥터가 누적 전력량을 실어 오고 상태가 지점별로 들고 있다. 화면은 계량 컴포넌트가\n * 지점의 값을 보인다.\n */\n {\n std: 'MMTR',\n part: '7-4',\n label: 'twin.iec61850.MMTR',\n axis: 'equipment',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.MMTR'\n },\n /*\n * 계측(유효전력·역률) — 표본이 수요 구간에 합쳐지고, 구간·피크·계약 대비가 그 위에 선다.\n * 이 프로파일에서 가장 두껍게 채운 자리다.\n */\n {\n std: 'MMXU',\n part: '7-4',\n label: 'twin.iec61850.MMXU',\n axis: 'demandWindows',\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.MMXU'\n },\n /*\n * ── 셋이 함께 올라갔다 (2026-08-15) ──────────────────────────────────────\n * 개폐·발전·저장은 오래 「구조만 있음」이었다. 능력이 상태 필드를 선언해 두었는데 **값을 받는 문이\n * 없었기 때문**이다. 이제 셋 다 실제로 돈다: 설비 상태가 계약에 서고(`EquipmentState`), 커널이\n * `energy.equipment` 로 받아 그 설비에 적고, 씬 컴포넌트가 그린다.\n *\n * 세 층을 함께 올리는 이유: 값이 없는데 화면만 만들면 영원히 「—」인 카드가 되고, 화면이 없는데\n * 기능만 올리면 아무도 그 값을 보지 못한다. 「값이 먼저, 컴포넌트는 그 다음」의 결과다.\n */\n {\n std: 'XCBR',\n part: '7-4',\n label: 'twin.iec61850.XCBR',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.XCBR'\n },\n {\n std: 'DPVA',\n part: '7-4',\n label: 'twin.iec61850.DPVA',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.DPVA'\n },\n {\n std: 'ZBAT',\n part: '7-4',\n label: 'twin.iec61850.ZBAT',\n axis: null,\n shownOn: ['equipment'],\n structure: 'full',\n behavior: 'full',\n surface: 'full',\n note: 'twin.iec61850.note.ZBAT'\n }\n]\n\nexport const IEC61850_CONCEPTS: Isa95Concept[] = [...LOGICAL_NODES] // vocabulary-guard: allow — 표준 용어(위 주석)\n\nexport function iec61850Coverage(axes: string[]): {\n model: string\n concepts: (Isa95Concept & { axisMissing?: boolean })[]\n totals: { concepts: number; structure: number; behavior: number; surface: number }\n} {\n const concepts = IEC61850_CONCEPTS.map(c => reconcileAxes(c, new Set(axes)))\n const count = (k: 'structure' | 'behavior' | 'surface') => concepts.filter(c => c[k] === 'full').length\n return {\n /* 이름에 (vocabulary) 를 달아 둔다 — 프로토콜 적합성으로 읽히지 않게. */\n model: 'IEC 61850 (vocabulary)',\n concepts,\n totals: { concepts: concepts.length, structure: count('structure'), behavior: count('behavior'), surface: count('surface') }\n }\n}\n"]}
|
|
@@ -24,15 +24,18 @@ export interface Isa95Concept {
|
|
|
24
24
|
*
|
|
25
25
|
* 그래서 축일 수 없는 것은 갈 곳을 가리킨다. 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다.
|
|
26
26
|
*
|
|
27
|
-
* ── ★
|
|
28
|
-
* 이
|
|
29
|
-
*
|
|
27
|
+
* ── ★ 뜻을 고쳐 적는다 (2026-08-24, 사용자 지적) ──────────────────────────
|
|
28
|
+
* 한때 이 칸을 「**어디서 보이나**」로 읽고, 화면이 그것을 「해당 축 항목에 표시됩니다」라는 문장으로
|
|
29
|
+
* 냈다. 두 가지가 잘못이었다.
|
|
30
30
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
31
|
+
* ① 그 문장은 **한국어가 아니었다**(사용자 지적). 그리고 칩이 이미 갈 곳을 보이므로 **군더더기**다.
|
|
32
|
+
* ② 뜻이 틀렸다. 이 칸은 「**어디에 속하나**」다 — 그 개념이 자기 목록을 갖지 않고 **어느 축의 안에
|
|
33
|
+
* 들어 있나**. 화면에 그려지는지는 `surface` 가 따로 답한다. 둘은 다른 물음이다.
|
|
33
34
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* 그래서 **`surface` 와 짝을 맞추지 않는다.** 자기 축이 없으면 언제나 갈 곳을 적는다 — 아직 그려지지
|
|
36
|
+
* 않는 개념도 **어디에 속하는지는 사실**이고, 그것을 비우면 「어디에도 없는 것」으로 읽힌다.
|
|
37
|
+
*
|
|
38
|
+
* 여기가 비어 있고 축도 없으면 그것은 **진짜 빈칸**이다(위 규칙).
|
|
36
39
|
*/
|
|
37
40
|
shownOn?: string[];
|
|
38
41
|
}
|
|
@@ -42,10 +45,10 @@ export declare const ISA95_CONCEPTS: Isa95Concept[];
|
|
|
42
45
|
* 적합성 보고 — **채운 것과 빠진 것을 함께.**
|
|
43
46
|
*
|
|
44
47
|
* `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로
|
|
45
|
-
* **구조 없음으로 낮추고 그 사실을 말한다**(
|
|
48
|
+
* **구조 없음으로 낮추고 그 사실을 말한다**(알리지 않고 통과시키면 이 표가 거짓이 된다).
|
|
46
49
|
*/
|
|
47
50
|
/**
|
|
48
|
-
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을
|
|
51
|
+
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 알리지 않고 지우지 않는다.**
|
|
49
52
|
*
|
|
50
53
|
* 두 자리가 어긋날 수 있다.
|
|
51
54
|
* · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.
|
|
@@ -86,7 +86,17 @@ const PART2 = [
|
|
|
86
86
|
* `TestSpecificationID`(로트별 기준 참조 — 품목 정의가 대신 답하므로 두지 않았다) · `Status` ·
|
|
87
87
|
* `HierarchyScope` · `Version`. 채운 것만 세는 것이 이 표의 규율이다.
|
|
88
88
|
*/
|
|
89
|
-
|
|
89
|
+
/*
|
|
90
|
+
* ── 2026-08-25 — 목록이 쌓이기만 하던 것을 고쳤다 ──────────────────────────
|
|
91
|
+
* 연결된 시스템은 매 주기 현재 재고를 전부 보내는데, 트윈은 그것을 낱낱의 관측으로 받아 **더하기만
|
|
92
|
+
* 했다.** 「그리고 이것 말고는 없다」를 받는 곳이 없어서 목록에서 빠진 줄이 영원히 남았다.
|
|
93
|
+
* 실측: 실제 1,704건인데 트윈이 10,038건.
|
|
94
|
+
*
|
|
95
|
+
* 그동안 이 줄은 「기능 있음」이었다. **현재 재고를 맞게 유지하지 못하는 상태로 그렇게 적을 수
|
|
96
|
+
* 없다.** 오늘 「이 목록이 전부다」를 받는 길을 냈고(커널 0.7.63), 수량으로 온 재고도 없어질 수
|
|
97
|
+
* 있게 했다. 이제 사실이다.
|
|
98
|
+
*/
|
|
99
|
+
{ std: 'MaterialLot', part: '2', label: 'twin.isa95.MaterialLot', axis: 'items', structure: 'partial', behavior: 'full', surface: 'partial', note: 'twin.isa95.note.lot' },
|
|
90
100
|
/*
|
|
91
101
|
* ── 2026-08-24 판정 정정: `none` 이 아니었다 ──────────────────────────────
|
|
92
102
|
* 이 줄은 셋 다 `none` 이었는데 **표가 코드보다 뒤처져 있었다.** 커널은 서브로트를 든다:
|
|
@@ -96,7 +106,7 @@ const PART2 = [
|
|
|
96
106
|
* `itemKeyOf(item)` **개체의 정체성이 이 값이다** — `subLotId ?? epc`
|
|
97
107
|
*
|
|
98
108
|
* 기능이 `full` 인 근거: 이 축이 없으면 같은 로트를 rack-1 에 100개·rack-2 에 60개 관측했을 때
|
|
99
|
-
* **뒤에 온 관측이 앞을 덮어 100개가
|
|
109
|
+
* **뒤에 온 관측이 앞을 덮어 100개가 오류 없이 사라진다**(합계 160 → 60). 실제로 그 결함을 이 축으로
|
|
100
110
|
* 고쳤다. 즉 장식이 아니라 정체성을 지탱한다.
|
|
101
111
|
*
|
|
102
112
|
* 구조가 `partial` 인 이유: 서브로트는 로트와 같은 모양이고(위 참조) 그 로트가 `partial` 이다.
|
|
@@ -116,7 +126,7 @@ const PART2 = [
|
|
|
116
126
|
* 기능은 **부분**이다: 호스트의 주행 추정기가 `speed` 를 읽어 이동 시간을 만드는 것이 유일한 소비처다
|
|
117
127
|
* (다른 속성은 아직 아무도 읽지 않는다). 그 사실을 `full` 로 올리면 표가 거짓이 된다.
|
|
118
128
|
*/
|
|
119
|
-
{ std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
|
|
129
|
+
{ std: 'Property', part: '2', label: 'twin.isa95.Property', axis: null, shownOn: ['equipment', 'persons', 'assets'], structure: 'full', behavior: 'partial', surface: 'full', note: 'twin.isa95.note.property' },
|
|
120
130
|
/*
|
|
121
131
|
* **자리의 상시 관측** — 2026-08-24 신설(커널 0.7.56). 표에 이 개념이 아예 없었다.
|
|
122
132
|
*
|
|
@@ -134,12 +144,26 @@ const PART2 = [
|
|
|
134
144
|
* 계층이고 냉장실은 `StorageZone` 수준의 설비다. `OperationalLocationType` 에는 이 축이 없다.
|
|
135
145
|
*
|
|
136
146
|
* 구조가 `partial`: 봉투 전체(`ID`·`Description`·사건 분류·`OperationsRecord` 구조)가 아니라 관측
|
|
137
|
-
* 하나를 담는 데 필요한 칸만 든다.
|
|
147
|
+
* 하나를 담는 데 필요한 칸만 든다. 2026-08-25 에 지난 기록의 봉투가 사람이 적은 문장을 함께
|
|
148
|
+
* 나르게 됐지만(`CanonicalEnvelope.description`), 그것은 **기록에 남는 말**이고 이 타입의
|
|
149
|
+
* `Description` 칸이 아니다 — 상태에 담기지 않으므로 이 줄의 등급을 올리지 않는다.
|
|
138
150
|
* 기능이 `full`: 사건 채널(`location.measured`)이 상태를 만들고, 선언된 기준으로 판정해
|
|
139
151
|
* `observation-out-of-limit` 신호를 세운다.
|
|
140
152
|
* 화면이 `none`: 클라이언트에 이 축을 그리는 곳이 없다(실측 0곳).
|
|
141
153
|
*/
|
|
142
|
-
|
|
154
|
+
/*
|
|
155
|
+
* ── 2026-08-25 — 이 줄이 실제보다 높게 적혀 있었다 ────────────────────────
|
|
156
|
+
* 「기능 있음」으로 적어 두었다. 커널이 관측을 반영하고 기준으로 판단하는 것은 사실이었으므로
|
|
157
|
+
* 그렇게 적었는데, **연결된 시스템이 그 값을 보낼 길이 없었다** — 유입 경로가 `locationId` 를
|
|
158
|
+
* 보지 않아 「어느 사실인지 모른다」로 거부했다.
|
|
159
|
+
*
|
|
160
|
+
* **아무도 먹일 수 없는 기능은 동작하는 기능이 아니다.** 오늘 그 경로를 냈고(커널 0.7.62),
|
|
161
|
+
* 이제 「기능 있음」이 사실이다. 그때까지는 「일부」였어야 했다.
|
|
162
|
+
*
|
|
163
|
+
* 같은 부류를 오늘 세 번 만났다 — 표가 **할 수 있는 일을 못 한다고** 적은 것이 여섯 줄이었고,
|
|
164
|
+
* 이것은 반대로 **못 하는 일을 할 수 있다고** 적은 것이다. 뒤쪽이 더 위험하다.
|
|
165
|
+
*/
|
|
166
|
+
{ std: 'OperationsEvent', part: '2', label: 'twin.isa95.OperationsEvent', axis: null, shownOn: ['locations'], structure: 'partial', behavior: 'full', surface: 'none' }
|
|
143
167
|
];
|
|
144
168
|
/*
|
|
145
169
|
* Part 4 — 일정과 실적.
|
|
@@ -160,7 +184,7 @@ const PART4 = [
|
|
|
160
184
|
*
|
|
161
185
|
* `surface` 도 `full` 이다(같은 날 두 번째 단계): 목록·상세·관계에 더해 **과거로 다시 계산하는 길**이 화면에
|
|
162
186
|
* 있다 — 시각 커서를 옮기면 그 시점의 사실이 나오고, 무엇을 근거로 답했는지도 화면이 말한다
|
|
163
|
-
* (라이브=권위 · 저널=파생 ·
|
|
187
|
+
* (라이브=권위 · 저널=파생 · 계산한 리비전). 실측: 이른 시각 0건 · 하루 뒤 504건.
|
|
164
188
|
*/
|
|
165
189
|
{ std: 'OperationsSchedule', part: '4', label: 'twin.isa95.OperationsSchedule', axis: 'orders', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.schedule' },
|
|
166
190
|
{ std: 'OperationsPerformance', part: '4', label: 'twin.isa95.OperationsPerformance', axis: 'tasks', structure: 'full', behavior: 'full', surface: 'full', note: 'twin.isa95.note.performance' },
|
|
@@ -203,7 +227,7 @@ const PART4 = [
|
|
|
203
227
|
*
|
|
204
228
|
* 화면은 `none` 이다 — 클라이언트에 이 축을 그리는 곳이 없다(실측: `operato-twin/client` 에 0곳).
|
|
205
229
|
*/
|
|
206
|
-
{ std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, structure: 'partial', behavior: 'partial', surface: 'none' },
|
|
230
|
+
{ std: 'TestResult', part: '4', label: 'twin.isa95.TestResult', axis: null, shownOn: ['items'], structure: 'partial', behavior: 'partial', surface: 'none' },
|
|
207
231
|
{ std: 'WorkMaster', part: '4', label: 'twin.isa95.WorkMaster', axis: 'recipes', structure: 'full', behavior: 'full', surface: 'full' },
|
|
208
232
|
{ std: 'WorkDirective', part: '4', label: 'twin.isa95.WorkDirective', axis: null, structure: 'none', behavior: 'none', surface: 'none' }
|
|
209
233
|
];
|
|
@@ -213,10 +237,10 @@ exports.ISA95_CONCEPTS = [...PART2, ...PART4];
|
|
|
213
237
|
* 적합성 보고 — **채운 것과 빠진 것을 함께.**
|
|
214
238
|
*
|
|
215
239
|
* `axis` 를 커널 축 목록과 맞춰 본다: 축이 있다고 적혀 있는데 커널에 없으면 그 선언이 낡은 것이므로
|
|
216
|
-
* **구조 없음으로 낮추고 그 사실을 말한다**(
|
|
240
|
+
* **구조 없음으로 낮추고 그 사실을 말한다**(알리지 않고 통과시키면 이 표가 거짓이 된다).
|
|
217
241
|
*/
|
|
218
242
|
/**
|
|
219
|
-
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을
|
|
243
|
+
* 선언한 축을 커널의 축 목록과 맞춘다 — **어긋난 것을 알리지 않고 지우지 않는다.**
|
|
220
244
|
*
|
|
221
245
|
* 두 자리가 어긋날 수 있다.
|
|
222
246
|
* · `axis` — 그 개념 자신의 문. 커널에 없으면 구조를 「없음」으로 낮추고 그 사실을 말한다.
|