@things-factory/headless-twin 10.0.11 → 10.0.13
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/canonical-ingest.d.ts +81 -3
- package/dist-server/engine/canonical-ingest.js +89 -10
- 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.d.ts +43 -0
- package/dist-server/engine/declared-stimulus.js +57 -0
- package/dist-server/engine/declared-stimulus.js.map +1 -0
- package/dist-server/engine/index.d.ts +5 -0
- package/dist-server/engine/index.js +6 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +335 -0
- package/dist-server/engine/ingest-health.js +434 -0
- package/dist-server/engine/ingest-health.js.map +1 -0
- package/dist-server/engine/integration-coverage.d.ts +76 -0
- package/dist-server/engine/integration-coverage.js +73 -0
- package/dist-server/engine/integration-coverage.js.map +1 -0
- package/dist-server/engine/integration-probes.d.ts +65 -0
- package/dist-server/engine/integration-probes.js +100 -0
- package/dist-server/engine/integration-probes.js.map +1 -0
- package/dist-server/engine/integration-runner.d.ts +45 -0
- package/dist-server/engine/integration-runner.js +59 -0
- package/dist-server/engine/integration-runner.js.map +1 -0
- package/dist-server/engine/integration-target-profile.d.ts +57 -0
- package/dist-server/engine/integration-target-profile.js +79 -0
- package/dist-server/engine/integration-target-profile.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +39 -0
- package/dist-server/engine/kpi-fold.js +68 -5
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +3 -3
- package/dist-server/engine/kpi-query.js +132 -18
- 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 +3 -17
- 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 +12 -3
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +3 -6
- package/dist-server/engine/local-declarations.js +97 -16
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +18 -0
- package/dist-server/engine/log.js +80 -0
- package/dist-server/engine/log.js.map +1 -0
- package/dist-server/engine/loop-lag.d.ts +54 -0
- package/dist-server/engine/loop-lag.js +87 -0
- package/dist-server/engine/loop-lag.js.map +1 -0
- package/dist-server/engine/measured-yield.d.ts +42 -0
- package/dist-server/engine/measured-yield.js +75 -0
- package/dist-server/engine/measured-yield.js.map +1 -0
- package/dist-server/engine/model-gap.d.ts +108 -0
- package/dist-server/engine/model-gap.js +95 -0
- package/dist-server/engine/model-gap.js.map +1 -0
- package/dist-server/engine/operation-basis.d.ts +16 -0
- package/dist-server/engine/operation-basis.js +20 -2
- package/dist-server/engine/operation-basis.js.map +1 -1
- package/dist-server/engine/property-effects.js +17 -0
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/restart-policy.d.ts +13 -0
- package/dist-server/engine/restart-policy.js +52 -0
- package/dist-server/engine/restart-policy.js.map +1 -0
- 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 +8 -0
- package/dist-server/engine/spec-coverage.js +3 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +83 -0
- package/dist-server/engine/stage-path.js +118 -0
- package/dist-server/engine/stage-path.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +400 -31
- package/dist-server/engine/twin-engine.js +1198 -187
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +35 -3
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +18 -7
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +14 -0
- package/dist-server/service/reference/control-routing.js +64 -0
- package/dist-server/service/reference/control-routing.js.map +1 -0
- package/dist-server/service/reference/discovery-result.d.ts +1 -1
- package/dist-server/service/reference/discovery-result.js +1 -1
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/index.d.ts +5 -1
- package/dist-server/service/reference/index.js +8 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +251 -3
- package/dist-server/service/reference/reference-adapter.js +43 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-assessment.d.ts +68 -0
- package/dist-server/service/reference/reference-assessment.js +136 -0
- package/dist-server/service/reference/reference-assessment.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +12 -1
- package/dist-server/service/reference/reference-live.js +119 -14
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +47 -1
- package/dist-server/service/reference/reference-master.js +34 -4
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-probe.d.ts +92 -0
- package/dist-server/service/reference/reference-probe.js +186 -0
- package/dist-server/service/reference/reference-probe.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
- package/dist-server/service/reference/reference-progress-subscription.js +94 -0
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
- package/dist-server/service/reference/reference-progress.d.ts +38 -0
- package/dist-server/service/reference/reference-progress.js +71 -0
- package/dist-server/service/reference/reference-progress.js.map +1 -0
- package/dist-server/service/reference/reference-resolver.d.ts +3 -3
- package/dist-server/service/reference/reference-resolver.js +242 -58
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +32 -0
- package/dist-server/service/reference/twin-reference.js +10 -0
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +34 -0
- package/dist-server/service/twin-audit/command-audit.js +15 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +3 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +10 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +15 -2
- package/dist-server/service/twin-control/twin-control-mutation.js +88 -36
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/journal-count.d.ts +21 -0
- package/dist-server/service/twin-event/journal-count.js +26 -0
- package/dist-server/service/twin-event/journal-count.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
- package/dist-server/service/twin-event/twin-event-keys.js +15 -18
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
- package/dist-server/service/twin-event/twin-event-type.js +18 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event.js +9 -1
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
- package/dist-server/service/twin-ingest-window/index.js +10 -0
- package/dist-server/service/twin-ingest-window/index.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.d.ts +1 -1
- package/dist-server/service/twin-instance/twin-instance.js +2 -2
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +74 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
- package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +19 -7
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/isa95-coverage.js +23 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.d.ts +24 -0
- package/dist-server/service/twin-model/item-ref.js +88 -0
- package/dist-server/service/twin-model/item-ref.js.map +1 -0
- package/dist-server/service/twin-model/name-index.d.ts +36 -0
- package/dist-server/service/twin-model/name-index.js +116 -0
- package/dist-server/service/twin-model/name-index.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +1 -1
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/status-tally.d.ts +9 -0
- package/dist-server/service/twin-model/status-tally.js +37 -0
- package/dist-server/service/twin-model/status-tally.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +157 -5
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-readiness/index.d.ts +2 -0
- package/dist-server/service/twin-readiness/index.js +6 -0
- package/dist-server/service/twin-readiness/index.js.map +1 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +23 -3
- package/dist-shared/entity-delta.js +12 -8
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/kpi-broadcast.js +1 -1
- package/dist-shared/kpi-broadcast.js.map +1 -1
- package/dist-shared/touched-items.d.ts +7 -0
- package/dist-shared/touched-items.js +80 -0
- package/dist-shared/touched-items.js.map +1 -0
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +164 -15
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +66 -0
- package/server/engine/index.ts +6 -0
- package/server/engine/ingest-health.ts +704 -0
- package/server/engine/integration-coverage.ts +147 -0
- package/server/engine/integration-probes.ts +144 -0
- package/server/engine/integration-runner.ts +95 -0
- package/server/engine/integration-target-profile.ts +103 -0
- package/server/engine/kpi-fold.ts +101 -5
- package/server/engine/kpi-query.ts +137 -23
- package/server/engine/live-feed-registry.ts +4 -3
- package/server/engine/load-meter.ts +12 -3
- package/server/engine/local-declarations.ts +97 -19
- package/server/engine/log.ts +72 -0
- package/server/engine/loop-lag.ts +120 -0
- package/server/engine/measured-yield.ts +89 -0
- package/server/engine/model-gap.ts +168 -0
- package/server/engine/operation-basis.ts +33 -2
- package/server/engine/property-effects.ts +17 -0
- package/server/engine/restart-policy.ts +55 -0
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +23 -3
- package/server/engine/stage-path.ts +172 -0
- package/server/engine/twin-engine.ts +1318 -186
- package/server/index.ts +36 -3
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
- package/server/migrations/index.ts +7 -1
- package/server/service/index.ts +11 -0
- package/server/service/reference/control-routing.ts +62 -0
- package/server/service/reference/discovery-result.ts +1 -1
- package/server/service/reference/index.ts +7 -1
- package/server/service/reference/reference-adapter.ts +275 -5
- package/server/service/reference/reference-assessment.ts +215 -0
- package/server/service/reference/reference-live.ts +126 -14
- package/server/service/reference/reference-master.ts +64 -4
- package/server/service/reference/reference-probe.ts +264 -0
- package/server/service/reference/reference-progress-subscription.ts +73 -0
- package/server/service/reference/reference-progress.ts +95 -0
- package/server/service/reference/reference-resolver.ts +246 -19
- package/server/service/reference/twin-reference.ts +39 -1
- package/server/service/twin-audit/command-audit.ts +34 -1
- package/server/service/twin-audit/twin-audit-event.ts +20 -0
- package/server/service/twin-control/twin-control-mutation.ts +78 -30
- package/server/service/twin-event/journal-count.ts +53 -0
- package/server/service/twin-event/twin-event-keys.ts +16 -1
- package/server/service/twin-event/twin-event-type.ts +26 -2
- package/server/service/twin-event/twin-event.ts +7 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
- package/server/service/twin-ingest-window/index.ts +7 -0
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
- package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
- package/server/service/twin-instance/twin-instance.ts +16 -10
- package/server/service/twin-journal/twin-journal-query.ts +59 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +79 -4
- package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
- package/server/service/twin-model/axis-journal-evidence.ts +57 -0
- package/server/service/twin-model/epcis-coverage.ts +2 -8
- package/server/service/twin-model/iec61850-coverage.ts +2 -8
- package/server/service/twin-model/isa95-coverage.ts +22 -9
- package/server/service/twin-model/item-ref.ts +80 -0
- package/server/service/twin-model/name-index.ts +94 -0
- package/server/service/twin-model/project-structure.ts +1 -1
- package/server/service/twin-model/status-tally.ts +37 -0
- package/server/service/twin-model/twin-lineage-query.ts +38 -9
- package/server/service/twin-model/twin-model-item-query.ts +26 -27
- package/server/service/twin-model/twin-model-query.ts +156 -5
- package/server/service/twin-model/twin-model-tree-query.ts +7 -0
- package/server/service/twin-readiness/index.ts +3 -0
- package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
- package/server/service/twin-space/twin-space-resolver.ts +5 -4
- package/shared/entity-delta.ts +31 -7
- package/shared/kpi-broadcast.ts +1 -1
- package/shared/touched-items.ts +73 -0
- package/test/adopt-structure-live.test.ts +7 -7
- package/test/axis-journal-evidence.test.ts +71 -0
- package/test/axis-read.test.ts +101 -1
- package/test/boot-resume.test.ts +93 -27
- package/test/broadcast-cost-baseline.test.ts +200 -0
- package/test/broadcast-period.test.ts +58 -0
- package/test/canonical-ingest-vocabularies.test.ts +36 -1
- package/test/canonical-quantity-door.test.ts +61 -0
- package/test/command-routing.test.ts +1 -1
- package/test/control-capability.test.ts +103 -0
- package/test/declared-location-types.test.ts +89 -0
- package/test/declared-stimulus.test.ts +88 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/duration-estimators.test.ts +1 -1
- package/test/entity-delta.test.ts +2 -2
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-health-engine.test.ts +248 -0
- package/test/ingest-health-wiring.test.ts +119 -0
- package/test/ingest-health.test.ts +306 -0
- package/test/ingest-history.test.ts +247 -0
- package/test/ingest-running-guard.test.ts +5 -5
- package/test/instance-cache-lifecycle.test.ts +1 -1
- package/test/integration-probes.test.ts +103 -0
- package/test/integration-runner.test.ts +95 -0
- package/test/item-ref.test.ts +78 -0
- package/test/journal-read-discipline.test.ts +177 -0
- package/test/journal-retention.test.ts +133 -0
- package/test/journal-sort-axis.test.ts +142 -0
- package/test/journal-write-door.test.ts +110 -0
- package/test/journal-write-trend.test.ts +115 -0
- package/test/kernel-kind-guard.test.ts +4 -4
- package/test/kpi-baseline-db.test.ts +1 -1
- package/test/kpi-fold.test.ts +90 -3
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/lineage-survives-restart.test.ts +20 -4
- package/test/live-cursor-wiring.test.ts +87 -0
- package/test/live-feed-registry.test.ts +15 -7
- package/test/live-kernel-facts.test.ts +7 -7
- package/test/live-mirror-parity.test.ts +6 -0
- package/test/load-meter.test.ts +29 -15
- package/test/local-declarations.test.ts +109 -2
- package/test/log-stamp.test.ts +59 -0
- package/test/loop-lag.test.ts +82 -0
- package/test/measured-yield.test.ts +90 -0
- package/test/model-gap.test.ts +153 -0
- package/test/oee-accumulator.test.ts +4 -0
- package/test/operation-basis.test.ts +28 -1
- package/test/operational-vocabulary.test.ts +108 -0
- package/test/operations-capability-db.test.ts +4 -4
- package/test/project-structure-db.test.ts +1 -1
- package/test/projection-reaches-screen.test.ts +1 -1
- package/test/property-effects.test.ts +28 -0
- package/test/read-failure-visible.test.ts +145 -0
- package/test/reference-grounding.test.ts +70 -0
- package/test/resolve-ts-siblings.mjs +52 -0
- package/test/restart-policy.test.ts +111 -0
- package/test/resync-origin-site.test.ts +7 -1
- package/test/revision-axis.test.ts +93 -0
- package/test/runtime-key.test.ts +2 -2
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/source-outcome-audit.test.ts +104 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/stage-path.test.ts +95 -0
- package/test/standard-coverage.test.ts +15 -3
- package/test/status-tally.test.ts +55 -0
- package/test/structure-revision-db.test.ts +31 -27
- package/test/tenant-registry-db.test.ts +3 -3
- package/test/time-range.test.ts +152 -0
- package/test/touched-items.test.ts +61 -0
- package/test/twin-event-keys.test.ts +10 -2
- package/test/twin-model-item-db.test.ts +5 -3
- package/test/twin-model-tree-db.test.ts +7 -7
- package/test/twin-origin-resync.test.ts +4 -4
- package/test/warm-start-seam.test.ts +4 -0
- package/test/yield-loop.test.ts +199 -0
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"project-structure.js","sourceRoot":"","sources":["../../../server/service/twin-model/project-structure.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinArea } from '../twin-space/twin-area.js'\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\nimport { readBoardEquipment } from '@operato/twin-kernel'\n\n/*\n * 구조 투영 — `model`(커널 입력 문서) → **표준 엔티티 행**(ADR-0032).\n *\n * ── 왜 전량 교체인가 ────────────────────────────────────────────────────────\n * 이 행들은 **원본에서 온 캐시**다(ADR-0032-D). 원본이 진실이므로 diff 로 아끼는 것보다\n * **매번 그대로 다시 그리는 것**이 맞다 — diff 는 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를\n * 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * 규모가 커지면 서명(`TwinStructure.signature`)이 같을 때 건너뛰는 장치가 이미 있으므로\n * 그것을 앞에 둔다. 여기서 diff 를 발명하지 않는다.\n *\n * ── 순서가 중요하다 ────────────────────────────────────────────────────────\n * 설비가 자리를 FK 로 가리키므로 **자리를 먼저** 넣는다. 지우는 것은 반대 순서다.\n * (자리 삭제 시 설비의 `homeLocation` 은 `SET NULL` 이지만, 전량 교체에서는 둘 다 지운다.)\n */\nexport interface ProjectionReport {\n locations: number\n equipment: number\n operations: number\n /** 자리를 못 찾아 기준 자리를 비운 설비 — **조용히 넘기지 않는다.** */\n unresolvedHome: string[]\n /** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */\n unresolvedArea: string[]\n}\n\n/**\n * 이 트윈의 구조 행을 **원본 그대로 다시 그린다.**\n *\n * `sourceRef` 는 어느 원본에서 왔는지다(`ReferenceMaster.source` = instanceId). 없는 행은\n * 만들 수 없다 — 캐시라는 사실을 구조로 지킨다.\n */\nexport async function projectStructure(domainId: string, instanceId: string, model: any, sourceRef: string): Promise<ProjectionReport> {\n const instance = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!instance) throw new Error(`cannot project structure: twin instance \"${instanceId}\" is not registered`)\n\n /*\n * **같은 id 가 둘이면 그 사실을 말한다** — 제약 위반 문구를 사용자에게 던지지 않는다.\n *\n * 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은\n * `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도\n * 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야\n * 한다). 조용히 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는\n * 모른다.\n */\n const dupOf = (list: any[] | undefined, what: string): string | undefined => {\n const seen = new Set<string>()\n for (const e of list ?? []) {\n const id = String(e?.id ?? '')\n if (!id) continue\n if (seen.has(id)) return `twin \"${instanceId}\" has two ${what} with id \"${id}\" — ids must be unique within a twin, so the model cannot be projected`\n seen.add(id)\n }\n return undefined\n }\n const dupMsg = dupOf(model?.locations, 'locations') ?? dupOf(readBoardEquipment(model), 'equipment')\n if (dupMsg) throw new Error(dupMsg)\n\n const locRepo = getRepository(TwinLocation)\n const eqRepo = getRepository(TwinEquipment)\n const opRepo = getRepository(TwinOperation)\n const scope = { domain: { id: domainId } as any, instance: { id: instance.id } as any }\n const ingestedAt = new Date()\n\n /*\n * 전량 교체 — 이 행들은 **원본에서 온 캐시**이므로 매번 그대로 다시 그린다.\n * diff 로 아끼는 것은 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * **관측으로 자란 자원은 여기 쓰지 않는다.** 커널은 마스터에 없던 설비도 이벤트로 알게 되지만\n * (`observed-reducer` 의 `origin: 'observed'`), 그것을 이 표에 밀어 넣으면 **쓰기 경로가 둘**이 되고\n * 틱 루프에서 DB 로 쓰는 길이 생긴다(ADR-0032-D 위반). 그 차이는 **조회 시점에 비교해 보고**한다 —\n * 숨겨야 할 불일치가 아니라 \"원본이 모르는 설비가 실행 중이다\" 는 사실이다.\n */\n await eqRepo.delete(scope)\n await opRepo.delete(scope)\n await locRepo.delete(scope)\n\n /* 구역은 model 밖에 산다(호스트의 TwinArea). 이름으로 찾아 FK 를 잇는다. */\n const areas = await getRepository(TwinArea).find({ where: { domain: { id: domainId } as any } })\n const areaOf = new Map(areas.map(a => [a.areaId, a]))\n const unresolvedArea: string[] = []\n\n /*\n * ── 전기 상류를 「구역」으로 읽지 않는다 (2026-08-18) ────────────────────────\n *\n * 자리의 부모가 **다른 자리**일 수 있다(분기의 상류는 수전이다). 그 부모를 구역 표에서 찾으면 당연히\n * 없고, 예전에는 그것을 매번 「구역이 이 트윈에 없다」로 경고했다 — 에너지 트윈을 인제스트할 때마다\n * 분기 수만큼 거짓 경보가 났다. 경보가 소음이 되면 **사람이 경보 자체를 읽지 않게 된다.**\n *\n * 자리를 가리키는 부모는 계층(자리 ⊃ 자리)이고, 구역은 model 밖의 다른 표다. 그래서 부모가 이 모델의\n * 자리이면 구역 해석을 **시도하지 않는다**(없는 것을 못 찾았다고 말하지 않는다).\n */\n const locationIds = new Set((model?.locations ?? []).filter((l: any) => l?.id).map((l: any) => String(l.id)))\n\n const locations: TwinLocation[] = (model?.locations ?? [])\n .filter((l: any) => l?.id)\n .map((l: any) => {\n const parentIsLocation = l.parentId ? locationIds.has(String(l.parentId)) : false\n const area = l.parentId && !parentIsLocation ? areaOf.get(l.parentId) : undefined\n if (l.parentId && !parentIsLocation && !area) unresolvedArea.push(`${l.id}→${l.parentId}`)\n return locRepo.create({\n ...scope,\n locationId: l.id,\n /* **이름을 옮긴다.** 버리고 있었다 — 그래서 화면에 자리 id 만 남았다(§name 주석 참조). */\n name: l.name ?? null,\n description: l.description ?? null,\n type: l.type,\n capacity: l.capacity ?? null,\n parallelism: l.parallelism ?? null,\n area: area ? ({ id: area.id } as any) : null,\n gs1Id: l.gs1Id ?? null,\n /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(조용한 누락). */\n properties: l.properties ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n const savedLocations = locations.length ? await locRepo.save(locations, { chunk: 500 }) : []\n const locOf = new Map(savedLocations.map(l => [l.locationId, l]))\n\n const unresolvedHome: string[] = []\n const equipment: TwinEquipment[] = (model?.equipment ?? [])\n .filter((m: any) => m?.id)\n .map((m: any) => {\n const home = m.homeLocation ? locOf.get(m.homeLocation) : undefined\n /* 끊어진 기준 자리는 **이름을 대고** 보고한다. 예전에는 화면이 경고할 뿐이었고\n 다른 경로로 들어온 데이터는 아무도 안 봤다. */\n if (m.homeLocation && !home) unresolvedHome.push(`${m.id}→${m.homeLocation}`)\n return eqRepo.create({\n ...scope,\n equipmentId: m.id,\n name: m.name ?? null,\n description: m.description ?? null,\n kind: m.kind,\n /* 속성도 옮긴다 — 행에 자리가 없어 흘리고 있었다(소비처가 실제로 읽는 사실이다: 주행 속도). */\n properties: m.properties ?? null,\n /* 시험 참조도 함께 — 흘리면 명세 쪽에서 \"누가 나를 가리키나\" 에 이 설비가 빠진다. */\n testSpecificationIds: m.testSpecificationIds ?? null,\n /* 결과까지 — 참조만 있으면 \"무엇으로 검증한다\" 까지고, 자격 성립은 결과가 말한다. */\n testResults: m.testResults ?? null,\n homeLocation: home ? ({ id: home.id } as any) : null,\n mtbfMs: m.mtbfMs ?? null,\n mttrMs: m.mttrMs ?? null,\n gs1Id: m.gs1Id ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n if (equipment.length) await eqRepo.save(equipment, { chunk: 500 })\n\n const operations: TwinOperation[] = (model?.operations ?? [])\n .filter((o: any) => o?.key)\n .map((o: any, i: number) =>\n opRepo.create({\n ...scope,\n operationKey: o.key,\n /*\n * 공정은 마스터가 `label` 로 말한다 — 행에서는 **규약의 이름 하나만** 둔다.\n *\n * 한동안 `label` 컬럼도 함께 남겨 뒀더니 조회 화면에 \"이름\" 열이 **두 개** 떴다(같은 값이\n * 두 번). 어휘를 하나로 한다고 하면서 둘을 남기면 반드시 이렇게 새어 나온다.\n */\n name: o.name ?? o.label ?? null,\n description: o.description ?? null,\n intent: o.intent ?? null,\n locationType: o.locationType ?? null,\n resourceType: o.resourceType ?? null,\n /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 접으면 초가 조용히 사라진다. */\n duration: o.duration ?? null,\n variability: o.variability ?? null,\n parameters: o.parameters ?? null,\n /* **네 자원 명세를 함께 옮긴다.** 셋이 빠져 있어서 조회 화면이 \"원본이 말한 값\" 이라 적고도\n 넷 중 하나만 보였다 — 사람이 필요한 공정을 보고 사람이 필요 없다고 결론 내게 된다. */\n personnelSpecification: o.personnelSpecification ?? null,\n physicalAssetSpecification: o.physicalAssetSpecification ?? null,\n materialSpecification: o.materialSpecification ?? null,\n seq: i,\n sourceRef,\n ingestedAt\n } as any)\n )\n if (operations.length) await opRepo.save(operations, { chunk: 500 })\n\n return {\n locations: savedLocations.length,\n equipment: equipment.length,\n operations: operations.length,\n unresolvedHome,\n unresolvedArea\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"project-structure.js","sourceRoot":"","sources":["../../../server/service/twin-model/project-structure.ts"],"names":[],"mappings":"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","sourcesContent":["import { getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinArea } from '../twin-space/twin-area.js'\nimport { TwinLocation } from './twin-location.js'\nimport { TwinEquipment } from './twin-equipment.js'\nimport { TwinOperation } from './twin-operation.js'\nimport { readBoardEquipment } from '@operato/twin-kernel'\n\n/*\n * 구조 투영 — `model`(커널 입력 문서) → **표준 엔티티 행**(ADR-0032).\n *\n * ── 왜 전량 교체인가 ────────────────────────────────────────────────────────\n * 이 행들은 **원본에서 온 캐시**다(ADR-0032-D). 원본이 진실이므로 diff 로 아끼는 것보다\n * **매번 그대로 다시 그리는 것**이 맞다 — diff 는 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를\n * 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * 규모가 커지면 서명(`TwinStructure.signature`)이 같을 때 건너뛰는 장치가 이미 있으므로\n * 그것을 앞에 둔다. 여기서 diff 를 발명하지 않는다.\n *\n * ── 순서가 중요하다 ────────────────────────────────────────────────────────\n * 설비가 자리를 FK 로 가리키므로 **자리를 먼저** 넣는다. 지우는 것은 반대 순서다.\n * (자리 삭제 시 설비의 `homeLocation` 은 `SET NULL` 이지만, 전량 교체에서는 둘 다 지운다.)\n */\nexport interface ProjectionReport {\n locations: number\n equipment: number\n operations: number\n /** 자리를 못 찾아 기준 자리를 비운 설비 — **조용히 넘기지 않는다.** */\n unresolvedHome: string[]\n /** 구역을 못 찾아 비운 자리. 구역은 model 밖(호스트 개념)이라 어긋날 수 있다. */\n unresolvedArea: string[]\n}\n\n/**\n * 이 트윈의 구조 행을 **원본 그대로 다시 그린다.**\n *\n * `sourceRef` 는 어느 원본에서 왔는지다(`ReferenceMaster.source` = instanceId). 없는 행은\n * 만들 수 없다 — 캐시라는 사실을 구조로 지킨다.\n */\nexport async function projectStructure(domainId: string, instanceId: string, model: any, sourceRef: string): Promise<ProjectionReport> {\n const instance = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!instance) throw new Error(`cannot project structure: twin instance \"${instanceId}\" is not registered`)\n\n /*\n * **같은 id 가 둘이면 그 사실을 말한다** — 제약 위반 문구를 사용자에게 보이지 않는다.\n *\n * 실측(2026-08-18): 현장 값을 그 현장의 트윈 전부에 얹다가 한 트윈에서 투영이 터졌다. 올라온 것은\n * `SQLITE_CONSTRAINT: UNIQUE constraint failed: twin_equipment...` 뿐이라 **무엇이 잘못됐는지 아무도\n * 알 수 없었다.** 원인은 그 모델에 설비 `load-1` 이 두 번 들어 있던 것이다(id 는 트윈 안에서 유일해야\n * 한다). 조용히 하나만 남기지 않는다 — 그것은 모델링 오류를 감추는 일이고, 어느 쪽이 맞는지 우리는\n * 모른다.\n */\n const dupOf = (list: any[] | undefined, what: string): string | undefined => {\n const seen = new Set<string>()\n for (const e of list ?? []) {\n const id = String(e?.id ?? '')\n if (!id) continue\n if (seen.has(id)) return `twin \"${instanceId}\" has two ${what} with id \"${id}\" — ids must be unique within a twin, so the model cannot be projected`\n seen.add(id)\n }\n return undefined\n }\n const dupMsg = dupOf(model?.locations, 'locations') ?? dupOf(readBoardEquipment(model), 'equipment')\n if (dupMsg) throw new Error(dupMsg)\n\n const locRepo = getRepository(TwinLocation)\n const eqRepo = getRepository(TwinEquipment)\n const opRepo = getRepository(TwinOperation)\n const scope = { domain: { id: domainId } as any, instance: { id: instance.id } as any }\n const ingestedAt = new Date()\n\n /*\n * 전량 교체 — 이 행들은 **원본에서 온 캐시**이므로 매번 그대로 다시 그린다.\n * diff 로 아끼는 것은 \"우리 쪽에 남겨야 할 것이 있다\" 는 전제를 깔고, 그 전제가 소유 침범의 시작이다.\n *\n * **관측으로 자란 자원은 여기 쓰지 않는다.** 커널은 마스터에 없던 설비도 이벤트로 알게 되지만\n * (`observed-reducer` 의 `origin: 'observed'`), 그것을 이 표에 밀어 넣으면 **쓰기 경로가 둘**이 되고\n * 틱 루프에서 DB 로 쓰는 길이 생긴다(ADR-0032-D 위반). 그 차이는 **조회 시점에 비교해 보고**한다 —\n * 숨겨야 할 불일치가 아니라 \"원본이 모르는 설비가 실행 중이다\" 는 사실이다.\n */\n await eqRepo.delete(scope)\n await opRepo.delete(scope)\n await locRepo.delete(scope)\n\n /* 구역은 model 밖에 산다(호스트의 TwinArea). 이름으로 찾아 FK 를 잇는다. */\n const areas = await getRepository(TwinArea).find({ where: { domain: { id: domainId } as any } })\n const areaOf = new Map(areas.map(a => [a.areaId, a]))\n const unresolvedArea: string[] = []\n\n /*\n * ── 전기 상류를 「구역」으로 읽지 않는다 (2026-08-18) ────────────────────────\n *\n * 자리의 부모가 **다른 자리**일 수 있다(분기의 상류는 수전이다). 그 부모를 구역 표에서 찾으면 당연히\n * 없고, 예전에는 그것을 매번 「구역이 이 트윈에 없다」로 경고했다 — 에너지 트윈을 인제스트할 때마다\n * 분기 수만큼 거짓 경보가 났다. 경보가 소음이 되면 **사람이 경보 자체를 읽지 않게 된다.**\n *\n * 자리를 가리키는 부모는 계층(자리 ⊃ 자리)이고, 구역은 model 밖의 다른 표다. 그래서 부모가 이 모델의\n * 자리이면 구역 해석을 **시도하지 않는다**(없는 것을 못 찾았다고 말하지 않는다).\n */\n const locationIds = new Set((model?.locations ?? []).filter((l: any) => l?.id).map((l: any) => String(l.id)))\n\n const locations: TwinLocation[] = (model?.locations ?? [])\n .filter((l: any) => l?.id)\n .map((l: any) => {\n const parentIsLocation = l.parentId ? locationIds.has(String(l.parentId)) : false\n const area = l.parentId && !parentIsLocation ? areaOf.get(l.parentId) : undefined\n if (l.parentId && !parentIsLocation && !area) unresolvedArea.push(`${l.id}→${l.parentId}`)\n return locRepo.create({\n ...scope,\n locationId: l.id,\n /* **이름을 옮긴다.** 버리고 있었다 — 그래서 화면에 자리 id 만 남았다(§name 주석 참조). */\n name: l.name ?? null,\n description: l.description ?? null,\n type: l.type,\n capacity: l.capacity ?? null,\n parallelism: l.parallelism ?? null,\n area: area ? ({ id: area.id } as any) : null,\n gs1Id: l.gs1Id ?? null,\n /* 자리의 속성도 옮긴다 — 옮기지 않으면 계약전력·요금이 항목 패널에서 사라진다(조용한 누락). */\n properties: l.properties ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n const savedLocations = locations.length ? await locRepo.save(locations, { chunk: 500 }) : []\n const locOf = new Map(savedLocations.map(l => [l.locationId, l]))\n\n const unresolvedHome: string[] = []\n const equipment: TwinEquipment[] = (model?.equipment ?? [])\n .filter((m: any) => m?.id)\n .map((m: any) => {\n const home = m.homeLocation ? locOf.get(m.homeLocation) : undefined\n /* 끊어진 기준 자리는 **이름을 대고** 보고한다. 예전에는 화면이 경고할 뿐이었고\n 다른 경로로 들어온 데이터는 아무도 안 봤다. */\n if (m.homeLocation && !home) unresolvedHome.push(`${m.id}→${m.homeLocation}`)\n return eqRepo.create({\n ...scope,\n equipmentId: m.id,\n name: m.name ?? null,\n description: m.description ?? null,\n kind: m.kind,\n /* 속성도 옮긴다 — 행에 자리가 없어 흘리고 있었다(소비처가 실제로 읽는 사실이다: 주행 속도). */\n properties: m.properties ?? null,\n /* 시험 참조도 함께 — 흘리면 명세 쪽에서 \"누가 나를 가리키나\" 에 이 설비가 빠진다. */\n testSpecificationIds: m.testSpecificationIds ?? null,\n /* 결과까지 — 참조만 있으면 \"무엇으로 검증한다\" 까지고, 자격 성립은 결과가 말한다. */\n testResults: m.testResults ?? null,\n homeLocation: home ? ({ id: home.id } as any) : null,\n mtbfMs: m.mtbfMs ?? null,\n mttrMs: m.mttrMs ?? null,\n gs1Id: m.gs1Id ?? null,\n sourceRef,\n ingestedAt\n } as any)\n })\n if (equipment.length) await eqRepo.save(equipment, { chunk: 500 })\n\n const operations: TwinOperation[] = (model?.operations ?? [])\n .filter((o: any) => o?.key)\n .map((o: any, i: number) =>\n opRepo.create({\n ...scope,\n operationKey: o.key,\n /*\n * 공정은 마스터가 `label` 로 말한다 — 행에서는 **규약의 이름 하나만** 둔다.\n *\n * 한동안 `label` 컬럼도 함께 남겨 뒀더니 조회 화면에 \"이름\" 열이 **두 개** 떴다(같은 값이\n * 두 번). 어휘를 하나로 한다고 하면서 둘을 남기면 반드시 이렇게 새어 나온다.\n */\n name: o.name ?? o.label ?? null,\n description: o.description ?? null,\n intent: o.intent ?? null,\n locationType: o.locationType ?? null,\n resourceType: o.resourceType ?? null,\n /* 소요시간은 ISO 8601 문자열이 정본 — 분으로 접으면 초가 조용히 사라진다. */\n duration: o.duration ?? null,\n variability: o.variability ?? null,\n parameters: o.parameters ?? null,\n /* **네 자원 명세를 함께 옮긴다.** 셋이 빠져 있어서 조회 화면이 \"원본이 말한 값\" 이라 적고도\n 넷 중 하나만 보였다 — 사람이 필요한 공정을 보고 사람이 필요 없다고 결론 내게 된다. */\n personnelSpecification: o.personnelSpecification ?? null,\n physicalAssetSpecification: o.physicalAssetSpecification ?? null,\n materialSpecification: o.materialSpecification ?? null,\n seq: i,\n sourceRef,\n ingestedAt\n } as any)\n )\n if (operations.length) await opRepo.save(operations, { chunk: 500 })\n\n return {\n locations: savedLocations.length,\n equipment: equipment.length,\n operations: operations.length,\n unresolvedHome,\n unresolvedArea\n }\n}\n"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* **수 하나가 뜻을 바꾸는 자리** — 상태별로 갈라 센다 (2026-08-23).
|
|
4
|
+
*
|
|
5
|
+
* ── 무엇이 있었나 ───────────────────────────────────────────────────────────
|
|
6
|
+
* 미러가 완료된 작업까지 상태에 들고 있게 되면서(관측 흡수에서 `completed` 를 떨어뜨리지 않게 커널을
|
|
7
|
+
* 고친 뒤) 개념 지도의 작업 카드가 「0」에서 「2,820」이 되었다. 이 카드는 **상태의 인구조사**이므로
|
|
8
|
+
* 그 수가 맞다 — 그런데 사람은 그것을 「지금 할 일 2,820건」으로 읽는다.
|
|
9
|
+
*
|
|
10
|
+
* 수를 줄여 맞는 척하게 만들지 않는다(그러면 인구조사가 거짓이 된다). 대신 **무엇으로 이루어진
|
|
11
|
+
* 수인지** 함께 낸다: 「진행 중 28 · 완료 2,792」.
|
|
12
|
+
*
|
|
13
|
+
* ── 지키는 것 ───────────────────────────────────────────────────────────────
|
|
14
|
+
* · 낱말은 **커널이 실어 보낸 그대로**다. 여기서 묶거나 고쳐 부르지 않는다(화면이 라벨을 입히고,
|
|
15
|
+
* 라벨이 없으면 낱말이 그대로 보여 누락이 드러난다).
|
|
16
|
+
* · 상태를 말하지 않는 축은 **아무것도 내지 않는다.** 빈 표를 내면 화면이 「상태가 없는 축」과
|
|
17
|
+
* 「상태를 못 읽은 축」을 구별할 수 없다.
|
|
18
|
+
* · 한 번만 훑는다. 카드가 이미 같은 배열의 길이를 세므로 셈이 한 벌 늘지 않는다.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.statusTally = statusTally;
|
|
22
|
+
/** 이 축의 개체를 상태별로 센다. 상태가 없으면 빈 객체(필드를 만들지 않는다). */
|
|
23
|
+
function statusTally(bucket) {
|
|
24
|
+
if (!Array.isArray(bucket) || !bucket.length)
|
|
25
|
+
return {};
|
|
26
|
+
const tally = new Map();
|
|
27
|
+
for (const e of bucket) {
|
|
28
|
+
const s = e?.status;
|
|
29
|
+
if (typeof s !== 'string' || !s)
|
|
30
|
+
continue;
|
|
31
|
+
tally.set(s, (tally.get(s) ?? 0) + 1);
|
|
32
|
+
}
|
|
33
|
+
if (!tally.size)
|
|
34
|
+
return {};
|
|
35
|
+
return { statusCounts: [...tally].map(([status, count]) => ({ status, count })).sort((x, y) => y.count - x.count) };
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=status-tally.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status-tally.js","sourceRoot":"","sources":["../../../server/service/twin-model/status-tally.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;AASH,kCAUC;AAXD,mDAAmD;AACnD,SAAgB,WAAW,CAAC,MAAe;IACzC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM;QAAE,OAAO,EAAE,CAAA;IACvD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAA;IACvC,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;QACvB,MAAM,CAAC,GAAI,CAAS,EAAE,MAAM,CAAA;QAC5B,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC;YAAE,SAAQ;QACzC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACvC,CAAC;IACD,IAAI,CAAC,KAAK,CAAC,IAAI;QAAE,OAAO,EAAE,CAAA;IAC1B,OAAO,EAAE,YAAY,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,EAAE,CAAA;AACrH,CAAC","sourcesContent":["/*\n * **수 하나가 뜻을 바꾸는 자리** — 상태별로 갈라 센다 (2026-08-23).\n *\n * ── 무엇이 있었나 ───────────────────────────────────────────────────────────\n * 미러가 완료된 작업까지 상태에 들고 있게 되면서(관측 흡수에서 `completed` 를 떨어뜨리지 않게 커널을\n * 고친 뒤) 개념 지도의 작업 카드가 「0」에서 「2,820」이 되었다. 이 카드는 **상태의 인구조사**이므로\n * 그 수가 맞다 — 그런데 사람은 그것을 「지금 할 일 2,820건」으로 읽는다.\n *\n * 수를 줄여 맞는 척하게 만들지 않는다(그러면 인구조사가 거짓이 된다). 대신 **무엇으로 이루어진\n * 수인지** 함께 낸다: 「진행 중 28 · 완료 2,792」.\n *\n * ── 지키는 것 ───────────────────────────────────────────────────────────────\n * · 낱말은 **커널이 실어 보낸 그대로**다. 여기서 묶거나 고쳐 부르지 않는다(화면이 라벨을 입히고,\n * 라벨이 없으면 낱말이 그대로 보여 누락이 드러난다).\n * · 상태를 말하지 않는 축은 **아무것도 내지 않는다.** 빈 표를 내면 화면이 「상태가 없는 축」과\n * 「상태를 못 읽은 축」을 구별할 수 없다.\n * · 한 번만 훑는다. 카드가 이미 같은 배열의 길이를 세므로 셈이 한 벌 늘지 않는다.\n */\n\n/** 상태 한 갈래와 그 건수. */\nexport interface StatusCount {\n status: string\n count: number\n}\n\n/** 이 축의 개체를 상태별로 센다. 상태가 없으면 빈 객체(필드를 만들지 않는다). */\nexport function statusTally(bucket: unknown): { statusCounts?: StatusCount[] } {\n if (!Array.isArray(bucket) || !bucket.length) return {}\n const tally = new Map<string, number>()\n for (const e of bucket) {\n const s = (e as any)?.status\n if (typeof s !== 'string' || !s) continue\n tally.set(s, (tally.get(s) ?? 0) + 1)\n }\n if (!tally.size) return {}\n return { statusCounts: [...tally].map(([status, count]) => ({ status, count })).sort((x, y) => y.count - x.count) }\n}\n"]}
|
|
@@ -4,6 +4,7 @@ exports.TwinLineageQuery = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const type_graphql_1 = require("type-graphql");
|
|
6
6
|
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const item_ref_js_1 = require("./item-ref.js");
|
|
7
8
|
const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
|
|
8
9
|
const twin_event_js_1 = require("../twin-event/twin-event.js");
|
|
9
10
|
/*
|
|
@@ -24,11 +25,6 @@ const twin_event_js_1 = require("../twin-event/twin-event.js");
|
|
|
24
25
|
* 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 "이 개체는 원자재가 없다" 로
|
|
25
26
|
* 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.
|
|
26
27
|
*/
|
|
27
|
-
/** GS1 SGTIN 의 **품목 참조** — 개체(serial)를 떼고 품목만 남긴다(`…sgtin:0614141.077101.42` → `077101`). */
|
|
28
|
-
function itemRefOf(epc) {
|
|
29
|
-
const m = /sgtin:[^.]+\.([^.]+)\./.exec(epc);
|
|
30
|
-
return m ? m[1] : null;
|
|
31
|
-
}
|
|
32
28
|
let TwinLineageQuery = class TwinLineageQuery {
|
|
33
29
|
async twinItemLineage(instanceId, epc, context, maxDepth, window) {
|
|
34
30
|
const domainId = context.state.domain.id;
|
|
@@ -61,7 +57,7 @@ let TwinLineageQuery = class TwinLineageQuery {
|
|
|
61
57
|
}
|
|
62
58
|
}
|
|
63
59
|
/* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */
|
|
64
|
-
const items = [{ epc, itemRef: itemRefOf(epc), depth: 0 }];
|
|
60
|
+
const items = [{ epc, itemRef: (0, item_ref_js_1.itemRefOf)(epc), depth: 0 }];
|
|
65
61
|
const edges = [];
|
|
66
62
|
const seen = new Set([epc]);
|
|
67
63
|
let depthTruncated = false;
|
|
@@ -79,7 +75,7 @@ let TwinLineageQuery = class TwinLineageQuery {
|
|
|
79
75
|
if (seen.has(input))
|
|
80
76
|
continue;
|
|
81
77
|
seen.add(input);
|
|
82
|
-
items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 });
|
|
78
|
+
items.push({ epc: input, itemRef: (0, item_ref_js_1.itemRefOf)(input), depth: cur.depth + 1 });
|
|
83
79
|
next.push({ epc: input, depth: cur.depth + 1 });
|
|
84
80
|
}
|
|
85
81
|
}
|
|
@@ -97,17 +93,49 @@ let TwinLineageQuery = class TwinLineageQuery {
|
|
|
97
93
|
const spec = inst.model?.productionSpec;
|
|
98
94
|
const binding = spec?.binding ?? {};
|
|
99
95
|
const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]));
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
/*
|
|
97
|
+
* ── **화면은 끝마디로 묻는다** (2026-08-23) ─────────────────────────────────
|
|
98
|
+
*
|
|
99
|
+
* 서랍의 포커스 식별자는 클래스의 끝마디다(`net/class/product-M0000438`). 대응 표의 값은 전체
|
|
100
|
+
* 식별자(`https://…/class/product-M0000438`)이므로 곧바로는 맞지 않고, 그래서 「선언된 자재가
|
|
101
|
+
* 아닙니다」가 떴다 — 그 품목은 대응 표에 있었다.
|
|
102
|
+
*
|
|
103
|
+
* 끝마디로도 찾되 **하나뿐일 때만** 인정한다. 둘 이상이 같은 끝마디를 가지면 어느 것인지 모르므로
|
|
104
|
+
* 아무 말도 하지 않는다 — 짐작으로 이으면 남의 레시피와 견주게 되고, 그것이 「설계와 다르다」는
|
|
105
|
+
* 거짓 진단이 된다.
|
|
106
|
+
*/
|
|
107
|
+
const materialOf = (e) => (0, item_ref_js_1.resolveByDeclaration)((0, item_ref_js_1.itemRefOf)(e), refToMaterial) ?? null;
|
|
104
108
|
const myMaterial = materialOf(epc);
|
|
105
109
|
const recipes = spec?.definition?.recipes ?? [];
|
|
106
110
|
const recipe = myMaterial ? recipes.find((r) => (r.outputs ?? []).some((o) => o.material === myMaterial)) : undefined;
|
|
111
|
+
/*
|
|
112
|
+
* ── 「모르는 개체」와 「대응이 없는 개체」는 다른 사실이다 (2026-08-23) ──────
|
|
113
|
+
*
|
|
114
|
+
* 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데,
|
|
115
|
+
* 그 품목은 원본의 품목 선언에 **이름까지 있었다**(「냉동그린빈스」). 바인딩 표가 그 품목을 담지
|
|
116
|
+
* 못한 것이고, 그것은 「중간 산물」과 전혀 다른 이야기다 — 사용자가 할 일도 다르다(원본을 볼 일이
|
|
117
|
+
* 아니라 대응을 고칠 일이다).
|
|
118
|
+
*
|
|
119
|
+
* 그래서 개체가 가리키는 품목이 **모델의 품목 선언에 있는지** 함께 본다. 이름은 그 자리에 있으니
|
|
120
|
+
* 함께 낸다(화면이 사람에게 무엇을 말하는지 알 수 있게).
|
|
121
|
+
*/
|
|
122
|
+
const definitions = inst.model?.materialDefinitions ?? [];
|
|
123
|
+
const defByRef = new Map(definitions.filter(d => d?.id).map(d => [String(d.id), d]));
|
|
124
|
+
/* 정의도 같은 문으로 되찾는다 — 규칙 하나, 후보가 둘 이상이면 답하지 않는다. */
|
|
125
|
+
const myRef = (0, item_ref_js_1.itemRefOf)(epc);
|
|
126
|
+
const myDef = (0, item_ref_js_1.resolveByDeclaration)(myRef, defByRef);
|
|
107
127
|
let conformance = null;
|
|
108
128
|
if (!Object.keys(binding).length) {
|
|
109
129
|
conformance = { comparable: false, reason: 'no-binding' };
|
|
110
130
|
}
|
|
131
|
+
else if (!myMaterial && myDef) {
|
|
132
|
+
/* 원본은 이 품목을 안다 — 개체를 자재에 잇는 대응이 없어서 비교하지 못한다. */
|
|
133
|
+
conformance = {
|
|
134
|
+
comparable: false,
|
|
135
|
+
reason: 'item-known-not-bound',
|
|
136
|
+
...(typeof myDef.name === 'string' && myDef.name ? { itemName: myDef.name } : {})
|
|
137
|
+
};
|
|
138
|
+
}
|
|
111
139
|
else if (!myMaterial) {
|
|
112
140
|
conformance = { comparable: false, reason: 'item-not-bound' };
|
|
113
141
|
}
|
|
@@ -175,7 +203,7 @@ let TwinLineageQuery = class TwinLineageQuery {
|
|
|
175
203
|
return {
|
|
176
204
|
instanceId,
|
|
177
205
|
epc,
|
|
178
|
-
itemRef: itemRefOf(epc),
|
|
206
|
+
itemRef: (0, item_ref_js_1.itemRefOf)(epc),
|
|
179
207
|
material: myMaterial,
|
|
180
208
|
items,
|
|
181
209
|
edges,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"twin-lineage-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-lineage-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\n/*\n * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**\n *\n * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────\n * 구성(BOM)의 주어는 품목 **정의**다 — \"트레일러는 프레임 1개로 만든다\"(선언).\n * 계보의 주어는 품목 **개체**다 — \"이 트레일러는 저 프레임 로트에서 나왔다\"(관측).\n * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: \"설계는 1개인데 이건 2개를 먹었다\".\n * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.\n *\n * ── 어디서 읽나 ────────────────────────────────────────────────────────────────\n * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는\n * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.\n *\n * ── 규모 ───────────────────────────────────────────────────────────────────────\n * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만\n * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 \"이 개체는 원자재가 없다\" 로\n * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.\n */\n\n/** GS1 SGTIN 의 **품목 참조** — 개체(serial)를 떼고 품목만 남긴다(`…sgtin:0614141.077101.42` → `077101`). */\nfunction itemRefOf(epc: string): string | null {\n const m = /sgtin:[^.]+\\.([^.]+)\\./.exec(epc)\n return m ? m[1] : null\n}\n\n@Resolver()\nexport class TwinLineageQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: \"the recipe says one frame, this one consumed two\".'\n })\n async twinItemLineage(\n @Arg('instanceId') instanceId: string,\n @Arg('epc') epc: string,\n @Ctx() context: ResolverContext,\n @Arg('maxDepth', type => Int, { nullable: true }) maxDepth?: number,\n @Arg('window', type => Int, { nullable: true }) window?: number\n ): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12)\n const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000)\n\n /*\n * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,\n * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.\n */\n const rows: any[] = await getRepository(TwinEvent).find({\n where: { domain: { id: domainId } as any, instanceId, eventType: 'epcis.TransformationEvent' } as any,\n order: { eventTime: 'DESC' } as any,\n take: windowCap\n })\n const truncated = rows.length >= windowCap\n\n /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */\n const madeBy = new Map<string, { inputs: string[]; at?: string; tx?: string }[]>()\n for (const r of rows) {\n const d = r.payload?.data ?? {}\n const outs: string[] = d.outputEPCList ?? []\n const ins: string[] = d.inputEPCList ?? []\n const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction }\n for (const o of outs) {\n const list = madeBy.get(o) ?? []\n list.push(step)\n madeBy.set(o, list)\n }\n }\n\n /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */\n const items: any[] = [{ epc, itemRef: itemRefOf(epc), depth: 0 }]\n const edges: any[] = []\n const seen = new Set<string>([epc])\n let depthTruncated = false\n let frontier = [{ epc, depth: 0 }]\n while (frontier.length) {\n const next: typeof frontier = []\n for (const cur of frontier) {\n if (cur.depth >= depthCap) { depthTruncated = true; continue }\n for (const step of madeBy.get(cur.epc) ?? []) {\n for (const input of step.inputs) {\n edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 })\n if (seen.has(input)) continue\n seen.add(input)\n items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 })\n next.push({ epc: input, depth: cur.depth + 1 })\n }\n }\n }\n frontier = next\n }\n\n /*\n * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로\n * 소비된 것을 자재별로 나란히 센다.\n *\n * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.\n * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 \"설계와 다르다\" 는 거짓 진단이 나오고,\n * 그건 아무 말도 안 하는 것보다 나쁘다.\n */\n const spec: any = (inst.model as any)?.productionSpec\n const binding: Record<string, string> = spec?.binding ?? {}\n const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]))\n const materialOf = (e: string) => {\n const ref = itemRefOf(e)\n return ref ? refToMaterial.get(ref) ?? null : null\n }\n\n const myMaterial = materialOf(epc)\n const recipes: any[] = spec?.definition?.recipes ?? []\n const recipe = myMaterial ? recipes.find((r: any) => (r.outputs ?? []).some((o: any) => o.material === myMaterial)) : undefined\n\n let conformance: any = null\n if (!Object.keys(binding).length) {\n conformance = { comparable: false, reason: 'no-binding' }\n } else if (!myMaterial) {\n conformance = { comparable: false, reason: 'item-not-bound' }\n } else if (!recipe) {\n conformance = { comparable: false, reason: 'no-recipe-for-material' }\n } else {\n /*\n * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.\n *\n * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서\n * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와\n * **영원히 안 맞는다**(검사가 이것을 잡았다).\n *\n * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도\n * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.\n *\n * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도\n * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).\n */\n const observed = new Map<string, number>()\n let unbound = 0\n const parentsOf = (e: string) => edges.filter(x => x.from === e).map(x => x.to)\n const walked = new Set<string>([epc])\n let layer = parentsOf(epc)\n while (layer.length) {\n const deeper: string[] = []\n for (const e of layer) {\n if (walked.has(e)) continue\n walked.add(e)\n const mat = materialOf(e)\n if (mat) { observed.set(mat, (observed.get(mat) ?? 0) + 1); continue }\n const up = parentsOf(e)\n /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */\n if (!up.length) { unbound++; continue }\n deeper.push(...up)\n }\n layer = deeper\n }\n const declared = new Map<string, number>((recipe.inputs ?? []).map((i: any) => [i.material, i.qty ?? null]))\n const keys = [...new Set([...declared.keys(), ...observed.keys()])]\n conformance = {\n comparable: true,\n material: myMaterial,\n recipe: recipe.key,\n /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */\n lines: keys.map(m => ({\n material: m,\n declared: declared.has(m) ? declared.get(m) : null,\n observed: observed.get(m) ?? 0,\n matches: declared.get(m) === (observed.get(m) ?? 0)\n })),\n /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.\n 어느 쪽인지 우리는 모르므로 **수만 낸다.** */\n unboundInputs: unbound\n }\n }\n\n return {\n instanceId,\n epc,\n itemRef: itemRefOf(epc),\n material: myMaterial,\n items,\n edges,\n conformance,\n /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */\n basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }\n }\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"twin-lineage-query.js","sourceRoot":"","sources":["../../../server/service/twin-model/twin-lineage-query.ts"],"names":[],"mappings":"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","sourcesContent":["import { Arg, Ctx, Int, Query, Resolver } from 'type-graphql'\n\nimport { ScalarObject, getRepository } from '@things-factory/shell'\n\nimport { itemRefOf, resolveByDeclaration } from './item-ref.js'\n\nimport { TwinInstance } from '../twin-instance/twin-instance.js'\nimport { TwinEvent } from '../twin-event/twin-event.js'\n\n/*\n * 계보 — **이 개체는 무엇에서 나왔나**, 그리고 **설계대로 만들어졌나.**\n *\n * ── 구성과 계보는 주어가 다르다 ────────────────────────────────────────────────\n * 구성(BOM)의 주어는 품목 **정의**다 — \"트레일러는 프레임 1개로 만든다\"(선언).\n * 계보의 주어는 품목 **개체**다 — \"이 트레일러는 저 프레임 로트에서 나왔다\"(관측).\n * 둘은 같은 모양이라 겹쳐 볼 수 있고, **그 차이가 곧 진단**이다: \"설계는 1개인데 이건 2개를 먹었다\".\n * 트윈이 아니면 못 하는 답이고, 이 질의가 그 답의 재료다.\n *\n * ── 어디서 읽나 ────────────────────────────────────────────────────────────────\n * 계보는 상태가 아니라 **저널**에 있다(EPCIS `TransformationEvent` 의 입력·산출 목록). 상태는\n * 지금 무엇이 있는지만 알고, 없어진 원자재가 무엇이었는지는 이벤트만 안다.\n *\n * ── 규모 ───────────────────────────────────────────────────────────────────────\n * 입력 목록은 **컬럼이 아니라 봉투 안**에 있어서 인덱스로 좁힐 수 없다. 그래서 변환 이벤트만\n * 창으로 끊어 읽고, **끊었으면 끊었다고 말한다.** 그 사실을 감추면 \"이 개체는 원자재가 없다\" 로\n * 읽히고, 계보에서 그 오해는 리콜 범위를 좁게 잡는 결과로 이어진다.\n */\n\n\n@Resolver()\nexport class TwinLineageQuery {\n @Query(returns => ScalarObject, {\n nullable: true,\n description:\n 'Where one item came from (observed) laid next to what its type declares (BOM). Walks TransformationEvent input/output lists in the journal; reports the window it read and whether it was truncated. The declared|observed difference is the diagnosis: \"the recipe says one frame, this one consumed two\".'\n })\n async twinItemLineage(\n @Arg('instanceId') instanceId: string,\n @Arg('epc') epc: string,\n @Ctx() context: ResolverContext,\n @Arg('maxDepth', type => Int, { nullable: true }) maxDepth?: number,\n @Arg('window', type => Int, { nullable: true }) window?: number\n ): Promise<any | null> {\n const domainId = context.state.domain.id\n const inst = await getRepository(TwinInstance).findOne({ where: { domain: { id: domainId }, instanceId } })\n if (!inst) return null\n\n const depthCap = Math.min(Math.max(maxDepth ?? 6, 1), 12)\n const windowCap = Math.min(Math.max(window ?? 5000, 1), 50000)\n\n /*\n * 변환 이벤트만 읽는다. **최신부터** 읽는다 — 계보를 묻는 개체는 대개 최근에 만들어진 것이고,\n * 창을 넘겨 잘릴 때 잃는 쪽이 오래된 것이어야 덜 아프다.\n */\n const rows: any[] = await getRepository(TwinEvent).find({\n where: { domain: { id: domainId } as any, instanceId, eventType: 'epcis.TransformationEvent' } as any,\n order: { eventTime: 'DESC' } as any,\n take: windowCap\n })\n const truncated = rows.length >= windowCap\n\n /* 산출 → 그 변환. 한 개체는 한 번만 만들어지지만, 데이터가 그렇지 않을 수 있으므로 목록으로 든다. */\n const madeBy = new Map<string, { inputs: string[]; at?: string; tx?: string }[]>()\n for (const r of rows) {\n const d = r.payload?.data ?? {}\n const outs: string[] = d.outputEPCList ?? []\n const ins: string[] = d.inputEPCList ?? []\n const step = { inputs: ins, at: r.eventTime, tx: d.bizTransactionList?.[0]?.bizTransaction }\n for (const o of outs) {\n const list = madeBy.get(o) ?? []\n list.push(step)\n madeBy.set(o, list)\n }\n }\n\n /* 거슬러 올라간다 — 너비 우선, 지나온 것은 다시 파지 않는다(사이클·공유 부품). */\n const items: any[] = [{ epc, itemRef: itemRefOf(epc), depth: 0 }]\n const edges: any[] = []\n const seen = new Set<string>([epc])\n let depthTruncated = false\n let frontier = [{ epc, depth: 0 }]\n while (frontier.length) {\n const next: typeof frontier = []\n for (const cur of frontier) {\n if (cur.depth >= depthCap) { depthTruncated = true; continue }\n for (const step of madeBy.get(cur.epc) ?? []) {\n for (const input of step.inputs) {\n edges.push({ from: cur.epc, to: input, at: step.at, tx: step.tx, depth: cur.depth + 1 })\n if (seen.has(input)) continue\n seen.add(input)\n items.push({ epc: input, itemRef: itemRefOf(input), depth: cur.depth + 1 })\n next.push({ epc: input, depth: cur.depth + 1 })\n }\n }\n }\n frontier = next\n }\n\n /*\n * **겹쳐 본다.** 이 개체의 품목이 어느 레시피의 산출이면, 그 레시피가 선언한 투입과 실제로\n * 소비된 것을 자재별로 나란히 센다.\n *\n * 개체(EPC)와 자재 키를 잇는 것은 `productionSpec.binding`(자재 키 → GS1 품목 참조)이다.\n * 바인딩이 없으면 **비교하지 않는다** — 짐작으로 이으면 \"설계와 다르다\" 는 거짓 진단이 나오고,\n * 그건 아무 말도 안 하는 것보다 나쁘다.\n */\n const spec: any = (inst.model as any)?.productionSpec\n const binding: Record<string, string> = spec?.binding ?? {}\n const refToMaterial = new Map(Object.entries(binding).map(([mat, ref]) => [String(ref), mat]))\n /*\n * ── **화면은 끝마디로 묻는다** (2026-08-23) ─────────────────────────────────\n *\n * 서랍의 포커스 식별자는 클래스의 끝마디다(`net/class/product-M0000438`). 대응 표의 값은 전체\n * 식별자(`https://…/class/product-M0000438`)이므로 곧바로는 맞지 않고, 그래서 「선언된 자재가\n * 아닙니다」가 떴다 — 그 품목은 대응 표에 있었다.\n *\n * 끝마디로도 찾되 **하나뿐일 때만** 인정한다. 둘 이상이 같은 끝마디를 가지면 어느 것인지 모르므로\n * 아무 말도 하지 않는다 — 짐작으로 이으면 남의 레시피와 견주게 되고, 그것이 「설계와 다르다」는\n * 거짓 진단이 된다.\n */\n const materialOf = (e: string) => resolveByDeclaration(itemRefOf(e), refToMaterial) ?? null\n\n const myMaterial = materialOf(epc)\n const recipes: any[] = spec?.definition?.recipes ?? []\n const recipe = myMaterial ? recipes.find((r: any) => (r.outputs ?? []).some((o: any) => o.material === myMaterial)) : undefined\n\n /*\n * ── 「모르는 개체」와 「대응이 없는 개체」는 다른 사실이다 (2026-08-23) ──────\n *\n * 화면이 「이 개체는 선언된 자재가 아닙니다(라인 내부의 중간 산물일 수 있습니다)」라고 말했는데,\n * 그 품목은 원본의 품목 선언에 **이름까지 있었다**(「냉동그린빈스」). 바인딩 표가 그 품목을 담지\n * 못한 것이고, 그것은 「중간 산물」과 전혀 다른 이야기다 — 사용자가 할 일도 다르다(원본을 볼 일이\n * 아니라 대응을 고칠 일이다).\n *\n * 그래서 개체가 가리키는 품목이 **모델의 품목 선언에 있는지** 함께 본다. 이름은 그 자리에 있으니\n * 함께 낸다(화면이 사람에게 무엇을 말하는지 알 수 있게).\n */\n const definitions: any[] = (inst.model as any)?.materialDefinitions ?? []\n const defByRef = new Map(definitions.filter(d => d?.id).map(d => [String(d.id), d]))\n /* 정의도 같은 문으로 되찾는다 — 규칙 하나, 후보가 둘 이상이면 답하지 않는다. */\n const myRef = itemRefOf(epc)\n const myDef = resolveByDeclaration(myRef, defByRef)\n\n let conformance: any = null\n if (!Object.keys(binding).length) {\n conformance = { comparable: false, reason: 'no-binding' }\n } else if (!myMaterial && myDef) {\n /* 원본은 이 품목을 안다 — 개체를 자재에 잇는 대응이 없어서 비교하지 못한다. */\n conformance = {\n comparable: false,\n reason: 'item-known-not-bound',\n ...(typeof myDef.name === 'string' && myDef.name ? { itemName: myDef.name } : {})\n }\n } else if (!myMaterial) {\n conformance = { comparable: false, reason: 'item-not-bound' }\n } else if (!recipe) {\n conformance = { comparable: false, reason: 'no-recipe-for-material' }\n } else {\n /*\n * 관측: **바인딩된 자재까지 내려가 센다** — 중간 산물(WIP)은 통과한다.\n *\n * 다단 라인에서 완제품의 직접 투입은 원자재가 아니라 **WIP** 다: 원자재는 첫 공정에서\n * 소비되고 완제품은 마지막 공정에서 나온다. 그래서 직접 투입만 세면 레시피가 선언한 자재와\n * **영원히 안 맞는다**(검사가 이것을 잡았다).\n *\n * WIP 은 라우트의 내부 산물이고 선언된 자재가 아니다 — 레시피는 자재로 말하므로 비교도\n * 자재로 해야 한다. 바인딩에 있으면 거기서 멈추고, 없으면 그 위로 계속 따라간다.\n *\n * 다만 **다른 완제품까지 넘어가지 않는다**: 바인딩된 것을 만나면 멈추므로, 하위조립도\n * 자재로 선언돼 있으면 그 자리에서 세어진다(그 아래는 그 자재 자신의 레시피가 볼 일이다).\n */\n const observed = new Map<string, number>()\n let unbound = 0\n const parentsOf = (e: string) => edges.filter(x => x.from === e).map(x => x.to)\n const walked = new Set<string>([epc])\n let layer = parentsOf(epc)\n while (layer.length) {\n const deeper: string[] = []\n for (const e of layer) {\n if (walked.has(e)) continue\n walked.add(e)\n const mat = materialOf(e)\n if (mat) { observed.set(mat, (observed.get(mat) ?? 0) + 1); continue }\n const up = parentsOf(e)\n /* 바인딩에도 없고 더 올라갈 곳도 없다 — 정말 모르는 투입이다(수만 낸다). */\n if (!up.length) { unbound++; continue }\n deeper.push(...up)\n }\n layer = deeper\n }\n const declared = new Map<string, number>((recipe.inputs ?? []).map((i: any) => [i.material, i.qty ?? null]))\n const keys = [...new Set([...declared.keys(), ...observed.keys()])]\n conformance = {\n comparable: true,\n material: myMaterial,\n recipe: recipe.key,\n /* 자재마다 선언·관측을 나란히. 어느 한쪽에만 있는 것도 사실이다(빠뜨리지 않는다). */\n lines: keys.map(m => ({\n material: m,\n declared: declared.has(m) ? declared.get(m) : null,\n observed: observed.get(m) ?? 0,\n matches: declared.get(m) === (observed.get(m) ?? 0)\n })),\n /* 바인딩에 없는 투입 — 설계에 없는 것이 들어갔을 수도, 바인딩이 덜 선언됐을 수도 있다.\n 어느 쪽인지 우리는 모르므로 **수만 낸다.** */\n unboundInputs: unbound\n }\n }\n\n return {\n instanceId,\n epc,\n itemRef: itemRefOf(epc),\n material: myMaterial,\n items,\n edges,\n conformance,\n /* 무엇을 근거로 답했나 — 창을 넘겨 끊겼으면 계보가 실제보다 얕게 보인다. */\n basis: { source: 'journal', transformationsRead: rows.length, truncated, depthTruncated, depthCap, windowCap }\n }\n }\n}\n"]}
|
|
@@ -4,14 +4,18 @@ exports.TwinModelItemQuery = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const type_graphql_1 = require("type-graphql");
|
|
6
6
|
const shell_1 = require("@things-factory/shell");
|
|
7
|
+
const item_ref_js_1 = require("./item-ref.js");
|
|
7
8
|
const index_js_1 = require("../../engine/index.js");
|
|
8
9
|
const warm_start_js_1 = require("../../engine/warm-start.js");
|
|
9
10
|
const property_effects_js_1 = require("../../engine/property-effects.js");
|
|
11
|
+
/* 축이 사는 자리를 읽는 **한 곳** — 카드(`twin-model-query`)와 같은 규칙을 쓴다. */
|
|
12
|
+
const axis_read_js_1 = require("@things-factory/headless-twin/dist-shared/axis-read.js");
|
|
10
13
|
const twin_instance_js_1 = require("../twin-instance/twin-instance.js");
|
|
11
14
|
const twin_location_js_1 = require("./twin-location.js");
|
|
12
15
|
const twin_equipment_js_1 = require("./twin-equipment.js");
|
|
13
16
|
const twin_operation_js_1 = require("./twin-operation.js");
|
|
14
17
|
const twin_area_js_1 = require("../twin-space/twin-area.js");
|
|
18
|
+
const name_index_js_1 = require("./name-index.js");
|
|
15
19
|
/* 커널 런타임 로드 — 축·관계·속성 선언의 SSOT. 이 파일은 도메인 모양을 알지 않는다. */
|
|
16
20
|
const { TWIN_AXES, TWIN_RELATIONS, axisInfo, axisSource, documentPath, relationsFrom, relationsTo, propertiesOf } = require('@operato/twin-kernel');
|
|
17
21
|
/**
|
|
@@ -25,16 +29,6 @@ const ROW_BACKED = {
|
|
|
25
29
|
equipment: { entity: twin_equipment_js_1.TwinEquipment, idColumn: 'equipmentId', order: { equipmentId: 'ASC' } },
|
|
26
30
|
operations: { entity: twin_operation_js_1.TwinOperation, idColumn: 'operationKey', order: { seq: 'ASC' } }
|
|
27
31
|
};
|
|
28
|
-
/** `productionSpec.definition.recipes` 같은 경로를 문서에서 따라간다. */
|
|
29
|
-
const at = (o, path) => path.split('.').reduce((a, k) => (a == null ? undefined : a[k]), o);
|
|
30
|
-
/**
|
|
31
|
-
* 문서 항목의 **식별자** — 축마다 이름이 다르다(자리는 `id`, 공정은 `key`, 레시피도 `key`).
|
|
32
|
-
*
|
|
33
|
-
* 도메인 모양을 코드가 아는 것이 아니라 **식별자 관례**를 아는 것이다. 후보를 순서대로 보고 첫
|
|
34
|
-
* 값을 쓴다. 하나도 없으면 그 항목은 가리킬 수 없으므로 `null` — 목록에서 빼지 않고 **id 없음**으로
|
|
35
|
-
* 낸다(조용히 빠지면 건수가 안 맞는다).
|
|
36
|
-
*/
|
|
37
|
-
const idOf = (o) => (typeof o === 'string' ? o : o?.id ?? o?.key ?? o?.gtin ?? null);
|
|
38
32
|
/** 검색 — 항목의 값 어디든 걸리면 맞는 것으로 본다. 축마다 검색 컬럼을 정해 두면 축이 늘 때마다 여기를 고쳐야 한다. */
|
|
39
33
|
function matches(o, q) {
|
|
40
34
|
if (!q)
|
|
@@ -121,7 +115,7 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
121
115
|
truncated: rows.length >= SCAN_MAX,
|
|
122
116
|
...(() => {
|
|
123
117
|
const items = hit.slice(skip, skip + take).map((r) => this.rowItem(axis, r));
|
|
124
|
-
return { items, names:
|
|
118
|
+
return { items, names: (0, name_index_js_1.namesFor)(items, (0, name_index_js_1.nameIndex)(inst.model ?? {})) };
|
|
125
119
|
})()
|
|
126
120
|
};
|
|
127
121
|
}
|
|
@@ -167,12 +161,15 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
167
161
|
if (!snap) {
|
|
168
162
|
return { axis, label: info.label, basis: 'state', total: null, observed: false, offset: skip, items: [] };
|
|
169
163
|
}
|
|
170
|
-
|
|
164
|
+
/* 축이 사는 자리는 **커널 선언이 말한다**(`path`) — 축 이름으로만 읽으면 중첩된 축이 언제나
|
|
165
|
+
비어 보인다. 카드는 `axisValueOf` 로 읽는데 여기가 그 규칙을 지나지 않아, 「42」를 센 개념의
|
|
166
|
+
목록이 「0건」으로 나왔다(에너지의 수요 구간). 규칙의 집은 `axisValueOf` 하나다. */
|
|
167
|
+
const bucket = (0, axis_read_js_1.axisValueOf)(snap, info);
|
|
171
168
|
const list = Array.isArray(bucket) ? bucket : [];
|
|
172
169
|
const hitState = q ? list.filter(o => matches(o, q)) : list;
|
|
173
170
|
const items = hitState
|
|
174
171
|
.slice(skip, skip + take)
|
|
175
|
-
.map(o => ({ id: idOf(o), fields: this.withOrderMaterial(axis, inst.model ?? {}, o) }));
|
|
172
|
+
.map(o => ({ id: (0, name_index_js_1.idOf)(o, info), fields: this.withOrderMaterial(axis, inst.model ?? {}, o) }));
|
|
176
173
|
return {
|
|
177
174
|
axis,
|
|
178
175
|
label: info.label,
|
|
@@ -185,11 +182,11 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
185
182
|
offset: skip,
|
|
186
183
|
resourceClassLabels: this.classLabels(inst.model ?? {}),
|
|
187
184
|
items,
|
|
188
|
-
names:
|
|
185
|
+
names: (0, name_index_js_1.namesFor)(items, (0, name_index_js_1.nameIndex)(inst.model ?? {}))
|
|
189
186
|
};
|
|
190
187
|
}
|
|
191
188
|
/* 아직 문서 안에 사는 축 — 문서를 읽어 자른다. 규모 한계를 `basis` 로 드러낸다. */
|
|
192
|
-
const raw = at(inst.model ?? {}, documentPath(axis));
|
|
189
|
+
const raw = (0, name_index_js_1.at)(inst.model ?? {}, documentPath(axis));
|
|
193
190
|
const all = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
194
191
|
const hit = q ? all.filter(o => matches(o, q)) : all;
|
|
195
192
|
return {
|
|
@@ -210,8 +207,8 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
210
207
|
const singular = !Array.isArray(raw) && !!raw;
|
|
211
208
|
const items = hit
|
|
212
209
|
.slice(skip, skip + take)
|
|
213
|
-
.map(o => ({ id: idOf(o) ?? (singular ? axis : null), fields: this.withBinding(axis, inst.model ?? {}, o) }));
|
|
214
|
-
return { items, names:
|
|
210
|
+
.map(o => ({ id: (0, name_index_js_1.idOf)(o) ?? (singular ? axis : null), fields: this.withBinding(axis, inst.model ?? {}, o) }));
|
|
211
|
+
return { items, names: (0, name_index_js_1.namesFor)(items, (0, name_index_js_1.nameIndex)(inst.model ?? {})), singular };
|
|
215
212
|
})()
|
|
216
213
|
};
|
|
217
214
|
}
|
|
@@ -278,10 +275,9 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
278
275
|
const binding = model?.productionSpec?.binding ?? {};
|
|
279
276
|
const refToMaterial = new Map(Object.entries(binding).map(([mat, r]) => [String(r), mat]));
|
|
280
277
|
/* 단일 품목 작업지시는 `gtin`, 여러 품목이면 라인마다 말한다 — 첫 라인으로 대표하지 않는다. */
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
};
|
|
278
|
+
/* 정체성 모양은 하나가 아니다 — 규칙의 집은 `item-ref` 하나다(웹 URI 원본에서 이 자리가 언제나
|
|
279
|
+
빈칸이었다). */
|
|
280
|
+
const refOf = item_ref_js_1.itemRefOf;
|
|
285
281
|
const mat = refToMaterial.get(String(refOf(fields.gtin) ?? ''));
|
|
286
282
|
if (mat)
|
|
287
283
|
return { ...fields, material: mat };
|
|
@@ -328,8 +324,9 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
328
324
|
/* 다시 계산할 것이 없다 — 아래에서 "모른다" 를 낸다(0 으로 메우지 않는다). */
|
|
329
325
|
}
|
|
330
326
|
}
|
|
331
|
-
|
|
332
|
-
const
|
|
327
|
+
/* 같은 규칙 — 축 이름이 아니라 선언된 자리에서 읽는다(§axisValueOf). */
|
|
328
|
+
const bucket = snap ? (0, axis_read_js_1.axisValueOf)(snap, info) : null;
|
|
329
|
+
const found = Array.isArray(bucket) ? bucket.find((o) => (0, name_index_js_1.idOf)(o, info) === itemId) : undefined;
|
|
333
330
|
if (!snap || !found) {
|
|
334
331
|
return {
|
|
335
332
|
instanceId, axis, label: info.label, itemId,
|
|
@@ -342,7 +339,7 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
342
339
|
names: {}, provenance: { basis: 'state' }
|
|
343
340
|
};
|
|
344
341
|
}
|
|
345
|
-
const names =
|
|
342
|
+
const names = (0, name_index_js_1.namesFor)([{ id: itemId, fields: found }], (0, name_index_js_1.nameIndex)(inst.model ?? {}));
|
|
346
343
|
return {
|
|
347
344
|
instanceId, axis, label: info.label, itemId,
|
|
348
345
|
identity: { standardClass: info.standardClass ?? null, typeRole: info.typeRole ?? null, kind: info.kind, gs1Scheme: info.gs1Scheme ?? null },
|
|
@@ -383,10 +380,10 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
383
380
|
* "없다" 고 답하면, 문서에는 있는 자리가 화면에서 사라진다(이 프로젝트가 내내 잡아 온 부류).
|
|
384
381
|
*/
|
|
385
382
|
if (!declared) {
|
|
386
|
-
const raw = at(model, info.path);
|
|
383
|
+
const raw = (0, name_index_js_1.at)(model, info.path);
|
|
387
384
|
const all = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
388
385
|
/* 한 벌뿐인 선언은 축 이름으로 찾는다 — 목록이 그 이름으로 가리켰기 때문이다. */
|
|
389
|
-
const found = all.find(o => idOf(o) === itemId) ?? (!Array.isArray(raw) && raw && itemId === axis ? raw : undefined);
|
|
386
|
+
const found = all.find(o => (0, name_index_js_1.idOf)(o) === itemId) ?? (!Array.isArray(raw) && raw && itemId === axis ? raw : undefined);
|
|
390
387
|
if (found) {
|
|
391
388
|
declared = typeof found === 'string' ? { id: found } : this.withBinding(axis, model, found);
|
|
392
389
|
provenance = { basis: 'document' };
|
|
@@ -433,7 +430,7 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
433
430
|
* 뜻). 화면이 `kind` 로 갈랐더니 **라우트 상세가 그릇 틀로 그려져** 가장 중요한 정보(공정 순서)가
|
|
434
431
|
* 개수 칩 하나로 줄어들었다. 그릇인지 아닌지는 모양이 말한다.
|
|
435
432
|
*/
|
|
436
|
-
container: axisSource(axis) === 'document' && !Array.isArray(at(model, info.path)) && at(model, info.path) != null,
|
|
433
|
+
container: axisSource(axis) === 'document' && !Array.isArray((0, name_index_js_1.at)(model, info.path)) && (0, name_index_js_1.at)(model, info.path) != null,
|
|
437
434
|
/* 두 열 — 원본이 말한 값, 트윈이 관측한 값. 한쪽이 없으면 **없다고 낸다**(0 으로 메우지 않는다). */
|
|
438
435
|
declared,
|
|
439
436
|
observed,
|
|
@@ -474,7 +471,7 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
474
471
|
* **이웃의 이름도 함께 싣는다.** 관계 그림의 이웃은 대개 *들어오는* 링크(이 자리를 기준 자리로
|
|
475
472
|
* 삼는 설비·사람)라, 이 항목 자신의 필드에는 그 id 가 없다 — 필드만 훑으면 그림이 id 로 채워진다.
|
|
476
473
|
*/
|
|
477
|
-
names:
|
|
474
|
+
names: (0, name_index_js_1.namesFor)([{ id: itemId, fields: declared }, { fields: observed }, { fields: links }], { ...(0, name_index_js_1.nameIndex)(model), ...areaNames }),
|
|
478
475
|
provenance
|
|
479
476
|
};
|
|
480
477
|
}
|
|
@@ -500,10 +497,10 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
500
497
|
/* 관측 축(상태·저널)은 문서에 없다 — 경로가 없는 축을 읽으면 모델 루트를 훑는다. */
|
|
501
498
|
if (axisSource(info.axis) !== 'document')
|
|
502
499
|
continue;
|
|
503
|
-
const raw = at(model ?? {}, info.path);
|
|
500
|
+
const raw = (0, name_index_js_1.at)(model ?? {}, info.path);
|
|
504
501
|
const list = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
505
502
|
for (const o of list) {
|
|
506
|
-
const id = idOf(o);
|
|
503
|
+
const id = (0, name_index_js_1.idOf)(o);
|
|
507
504
|
if (!id || typeof o !== 'object')
|
|
508
505
|
continue;
|
|
509
506
|
const label = o.name ?? o.label ?? o.description;
|
|
@@ -633,7 +630,8 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
633
630
|
return { absence: 'not-running' };
|
|
634
631
|
}
|
|
635
632
|
}
|
|
636
|
-
|
|
633
|
+
/* 같은 규칙 — 선언된 자리에서 읽는다. 축 이름으로 읽으면 중첩된 축이 언제나 「관측 없음」이 된다. */
|
|
634
|
+
const bucket = state ? (0, axis_read_js_1.axisValueOf)(state, axisInfo(axis) ?? { axis }) : null;
|
|
637
635
|
/*
|
|
638
636
|
* **왜 관측이 없는지 말한다.** 예전에는 그냥 `null` 이었고 화면은 그것을 "트윈이 실행 중이지 않아"
|
|
639
637
|
* 로 읽었다 — 실행 중인데도 그렇게 말했다(공정 상세에서 관측됨). 공정·레시피처럼 **관측 축이
|
|
@@ -641,7 +639,8 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
641
639
|
*/
|
|
642
640
|
if (!Array.isArray(bucket))
|
|
643
641
|
return { absence: 'not-observed' };
|
|
644
|
-
|
|
642
|
+
/* 같은 규칙 — 선언이 말하는 자리로 찾는다. `id` 로만 찾으면 수요 구간을 열 수 없다. */
|
|
643
|
+
const value = bucket.find((x) => (0, name_index_js_1.idOf)(x, axisInfo(axis)) === itemId);
|
|
645
644
|
if (!value)
|
|
646
645
|
return { absence: 'not-observed' };
|
|
647
646
|
return { value, basis: live ? 'state' : 'journal', ...(folded != null ? { foldedRevision: folded } : {}) };
|
|
@@ -695,9 +694,9 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
695
694
|
* 푼다.** 행에만 있고 문서에 없는 항목(있을 수 있다)은 이어진 것이 비지만, 그것은 사실이다.
|
|
696
695
|
*/
|
|
697
696
|
const axisPath = axisSource(axis) === 'document' ? axisInfo(axis)?.path : undefined;
|
|
698
|
-
const rawSelf = axisPath ? at(model, axisPath) : undefined;
|
|
697
|
+
const rawSelf = axisPath ? (0, name_index_js_1.at)(model, axisPath) : undefined;
|
|
699
698
|
const selfAll = Array.isArray(rawSelf) ? rawSelf : rawSelf ? [rawSelf] : [];
|
|
700
|
-
const self = self0 ?? selfAll.find((o) => idOf(o) === itemId);
|
|
699
|
+
const self = self0 ?? selfAll.find((o) => (0, name_index_js_1.idOf)(o) === itemId);
|
|
701
700
|
/* 나가는 쪽 — 내 필드가 가리키는 것. 선언이 필드 이름을 알려 준다. */
|
|
702
701
|
const out = relationsFrom(axis).map((r) => {
|
|
703
702
|
const raw = self ? this.fieldValue(self, r.field) : [];
|
|
@@ -756,11 +755,11 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
756
755
|
* 규모: 상태는 지금의 사실만 담아 유한하고, 상한과 총계는 문서 축과 같은 규율을 쓴다
|
|
757
756
|
* (넘치면 몇이 더 있는지 말한다). 저널 전체를 전수 조회하는 일은 여기서 하지 않는다.
|
|
758
757
|
*/
|
|
759
|
-
const raw = axisSource(r.from) === 'document' ? at(model, fromInfo.path) : (await observedBucket(r.from));
|
|
758
|
+
const raw = axisSource(r.from) === 'document' ? (0, name_index_js_1.at)(model, fromInfo.path) : (await observedBucket(r.from));
|
|
760
759
|
const all = Array.isArray(raw) ? raw : raw ? [raw] : [];
|
|
761
760
|
for (const x of all)
|
|
762
761
|
if (this.fieldValue(x, r.field).map(String).includes(itemId)) {
|
|
763
|
-
const id = idOf(x);
|
|
762
|
+
const id = (0, name_index_js_1.idOf)(x);
|
|
764
763
|
if (id)
|
|
765
764
|
hits.push(id);
|
|
766
765
|
}
|
|
@@ -793,7 +792,7 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
793
792
|
}
|
|
794
793
|
cur = next;
|
|
795
794
|
}
|
|
796
|
-
return cur.map(v => (typeof v === 'object' && v !== null ? idOf(v) ?? v : v)).filter(v => v != null);
|
|
795
|
+
return cur.map(v => (typeof v === 'object' && v !== null ? (0, name_index_js_1.idOf)(v) ?? v : v)).filter(v => v != null);
|
|
797
796
|
}
|
|
798
797
|
/**
|
|
799
798
|
* 행에서 관계 값을 꺼낸다 — FK 는 객체(관계 미로딩이면 없음)이고 문서 필드명과 컬럼명이 다를 수 있다.
|
|
@@ -809,10 +808,10 @@ let TwinModelItemQuery = class TwinModelItemQuery {
|
|
|
809
808
|
if (typeof v === 'string')
|
|
810
809
|
return [v];
|
|
811
810
|
if (Array.isArray(v))
|
|
812
|
-
return v.map(x => (typeof x === 'string' ? x : idOf(x) ?? '')).filter(Boolean);
|
|
811
|
+
return v.map(x => (typeof x === 'string' ? x : (0, name_index_js_1.idOf)(x) ?? '')).filter(Boolean);
|
|
813
812
|
/* FK 객체 — 대상 축의 id 컬럼으로 읽는다(관계를 로딩하지 않았으면 값이 없고, 그것도 사실이다). */
|
|
814
813
|
const idCol = ROW_BACKED[targetAxis]?.idColumn;
|
|
815
|
-
const got = idCol ? v[idCol] : idOf(v);
|
|
814
|
+
const got = idCol ? v[idCol] : (0, name_index_js_1.idOf)(v);
|
|
816
815
|
return got ? [String(got)] : [];
|
|
817
816
|
}
|
|
818
817
|
};
|