@things-factory/headless-twin 10.0.8 → 10.0.10
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 +52 -0
- package/dist-server/engine/attention-digest.js +76 -0
- package/dist-server/engine/attention-digest.js.map +1 -0
- package/dist-server/engine/canonical-ingest.d.ts +2 -2
- package/dist-server/engine/canonical-ingest.js +24 -2
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.d.ts +33 -0
- package/dist-server/engine/command-routing.js +53 -0
- package/dist-server/engine/command-routing.js.map +1 -0
- package/dist-server/engine/energy-topology.d.ts +86 -0
- package/dist-server/engine/energy-topology.js +144 -0
- package/dist-server/engine/energy-topology.js.map +1 -0
- package/dist-server/engine/index.d.ts +11 -0
- package/dist-server/engine/index.js +17 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-baseline.d.ts +78 -0
- package/dist-server/engine/kpi-baseline.js +123 -0
- package/dist-server/engine/kpi-baseline.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +137 -1
- package/dist-server/engine/kpi-fold.js +225 -0
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +32 -1
- package/dist-server/engine/kpi-query.js +262 -20
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/kpi-target.d.ts +17 -0
- package/dist-server/engine/kpi-target.js +24 -2
- package/dist-server/engine/kpi-target.js.map +1 -1
- package/dist-server/engine/live-attentions.d.ts +1 -0
- package/dist-server/engine/live-attentions.js +7 -1
- package/dist-server/engine/live-attentions.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +25 -0
- package/dist-server/engine/live-feed-registry.js +51 -0
- package/dist-server/engine/live-feed-registry.js.map +1 -0
- package/dist-server/engine/load-meter.d.ts +181 -0
- package/dist-server/engine/load-meter.js +267 -0
- package/dist-server/engine/load-meter.js.map +1 -0
- package/dist-server/engine/local-declarations.d.ts +262 -0
- package/dist-server/engine/local-declarations.js +528 -0
- package/dist-server/engine/local-declarations.js.map +1 -0
- package/dist-server/engine/model-basis.d.ts +39 -6
- package/dist-server/engine/model-basis.js +69 -9
- package/dist-server/engine/model-basis.js.map +1 -1
- package/dist-server/engine/model-vocabulary.d.ts +17 -0
- package/dist-server/engine/model-vocabulary.js +81 -0
- package/dist-server/engine/model-vocabulary.js.map +1 -0
- package/dist-server/engine/oee-accumulator.d.ts +28 -0
- package/dist-server/engine/oee-accumulator.js +26 -1
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/operation-basis.d.ts +32 -0
- package/dist-server/engine/operation-basis.js +87 -0
- package/dist-server/engine/operation-basis.js.map +1 -0
- package/dist-server/engine/property-effects.d.ts +30 -0
- package/dist-server/engine/property-effects.js +192 -0
- package/dist-server/engine/property-effects.js.map +1 -0
- package/dist-server/engine/runtime-key.d.ts +15 -0
- package/dist-server/engine/runtime-key.js +64 -0
- package/dist-server/engine/runtime-key.js.map +1 -0
- package/dist-server/engine/spec-coverage.js +3 -3
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +21 -0
- package/dist-server/engine/state-axes.js +59 -0
- package/dist-server/engine/state-axes.js.map +1 -0
- package/dist-server/engine/structure-diff.js +1 -1
- package/dist-server/engine/structure-diff.js.map +1 -1
- package/dist-server/engine/travel-estimator.d.ts +9 -2
- package/dist-server/engine/travel-estimator.js +11 -6
- package/dist-server/engine/travel-estimator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +418 -57
- package/dist-server/engine/twin-engine.js +1599 -306
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +93 -13
- package/dist-server/engine/warm-start.js +114 -14
- package/dist-server/engine/warm-start.js.map +1 -1
- package/dist-server/index.d.ts +1 -0
- package/dist-server/index.js +17 -11
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.d.ts +5 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js +48 -0
- package/dist-server/migrations/1786000000000-RenameTwinInstanceBoardToModel.js.map +1 -0
- package/dist-server/migrations/index.d.ts +2 -0
- package/dist-server/migrations/index.js +10 -0
- package/dist-server/migrations/index.js.map +1 -0
- package/dist-server/service/index.d.ts +3 -2
- package/dist-server/service/index.js +25 -17
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/discovery-result.d.ts +34 -0
- package/dist-server/service/reference/discovery-result.js +84 -0
- package/dist-server/service/reference/discovery-result.js.map +1 -0
- package/dist-server/service/reference/ingest-space.d.ts +53 -0
- package/dist-server/service/reference/ingest-space.js +79 -0
- package/dist-server/service/reference/ingest-space.js.map +1 -0
- package/dist-server/service/reference/knob-defaults.d.ts +20 -0
- package/dist-server/service/reference/knob-defaults.js +59 -0
- package/dist-server/service/reference/knob-defaults.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +23 -0
- package/dist-server/service/reference/reference-live.js +123 -6
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +227 -11
- package/dist-server/service/reference/reference-master.js +206 -25
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +33 -3
- package/dist-server/service/reference/reference-resolver.js +290 -23
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/template-registry.d.ts +19 -3
- package/dist-server/service/reference/template-registry.js.map +1 -1
- package/dist-server/service/twin-attention/twin-attention-query.d.ts +8 -1
- package/dist-server/service/twin-attention/twin-attention-query.js +39 -8
- package/dist-server/service/twin-attention/twin-attention-query.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +37 -0
- package/dist-server/service/twin-audit/command-audit.js +53 -0
- package/dist-server/service/twin-audit/command-audit.js.map +1 -0
- package/dist-server/service/twin-audit/index.d.ts +4 -0
- package/dist-server/service/twin-audit/index.js +8 -0
- package/dist-server/service/twin-audit/index.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +24 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +125 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -0
- package/dist-server/service/twin-audit/twin-audit-query.d.ts +4 -0
- package/dist-server/service/twin-audit/twin-audit-query.js +76 -0
- package/dist-server/service/twin-audit/twin-audit-query.js.map +1 -0
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +2 -0
- package/dist-server/service/twin-control/twin-control-mutation.js +50 -11
- 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 +1 -1
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +14 -5
- package/dist-server/service/twin-event/twin-event.js +46 -14
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/forecast-metrics.d.ts +13 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js +57 -0
- package/dist-server/service/twin-forecast/forecast-metrics.js.map +1 -0
- package/dist-server/service/twin-forecast/forecast-tuning.d.ts +12 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js +44 -0
- package/dist-server/service/twin-forecast/forecast-tuning.js.map +1 -0
- package/dist-server/service/twin-forecast/gap-analytics.d.ts +28 -2
- package/dist-server/service/twin-forecast/gap-analytics.js +43 -17
- package/dist-server/service/twin-forecast/gap-analytics.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +219 -39
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-instance/twin-instance.d.ts +38 -5
- package/dist-server/service/twin-instance/twin-instance.js +66 -18
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +11 -4
- package/dist-server/service/twin-journal/twin-journal-query.js +95 -15
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +11 -5
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +46 -27
- 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 +63 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/epcis-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/epcis-coverage.js +174 -0
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -0
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/iec61850-coverage.js +98 -0
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -0
- package/dist-server/service/twin-model/index.d.ts +13 -0
- package/dist-server/service/twin-model/index.js +29 -0
- package/dist-server/service/twin-model/index.js.map +1 -0
- package/dist-server/service/twin-model/isa95-coverage.d.ts +48 -0
- package/dist-server/service/twin-model/isa95-coverage.js +155 -0
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.d.ts +16 -0
- package/dist-server/service/twin-model/project-structure.js +173 -0
- package/dist-server/service/twin-model/project-structure.js.map +1 -0
- package/dist-server/service/twin-model/standard-coverage.d.ts +9 -0
- package/dist-server/service/twin-model/standard-coverage.js +37 -0
- package/dist-server/service/twin-model/standard-coverage.js.map +1 -0
- package/dist-server/service/twin-model/twin-equipment.d.ts +64 -0
- package/dist-server/service/twin-model/twin-equipment.js +134 -0
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +206 -0
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-location.d.ts +58 -0
- package/dist-server/service/twin-model/twin-location.js +130 -0
- package/dist-server/service/twin-model/twin-location.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-item-query.d.ts +141 -0
- package/dist-server/service/twin-model/twin-model-item-query.js +852 -0
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-mutation.d.ts +57 -0
- package/dist-server/service/twin-model/twin-model-mutation.js +490 -0
- package/dist-server/service/twin-model/twin-model-mutation.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-query.d.ts +43 -0
- package/dist-server/service/twin-model/twin-model-query.js +551 -0
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-model-tree-query.d.ts +3 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js +158 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -0
- package/dist-server/service/twin-model/twin-operation.d.ts +61 -0
- package/dist-server/service/twin-model/twin-operation.js +137 -0
- package/dist-server/service/twin-model/twin-operation.js.map +1 -0
- package/dist-server/service/twin-space/move-space.d.ts +65 -0
- package/dist-server/service/twin-space/move-space.js +166 -0
- package/dist-server/service/twin-space/move-space.js.map +1 -0
- package/dist-server/service/twin-space/space-integrity.d.ts +97 -0
- package/dist-server/service/twin-space/space-integrity.js +182 -0
- package/dist-server/service/twin-space/space-integrity.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-area.js +4 -4
- package/dist-server/service/twin-space/twin-space-area.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-representation.js +6 -6
- package/dist-server/service/twin-space/twin-space-representation.js.map +1 -1
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +63 -1
- package/dist-server/service/twin-space/twin-space-resolver.js +365 -18
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +17 -1
- package/dist-server/service/twin-space/twin-space.js +15 -5
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js +1 -1
- package/dist-server/service/twin-state/twin-state-subscription.js.map +1 -1
- package/dist-server/service/twin-structure/twin-structure.d.ts +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +8 -7
- package/dist-server/service/twin-structure/twin-structure.js.map +1 -1
- package/dist-server/service/twin-target/twin-target-resolver.js +21 -4
- package/dist-server/service/twin-target/twin-target-resolver.js.map +1 -1
- package/dist-shared/axis-read.d.ts +12 -0
- package/dist-shared/axis-read.js +32 -0
- package/dist-shared/axis-read.js.map +1 -0
- package/dist-shared/entity-delta.d.ts +29 -0
- package/dist-shared/entity-delta.js +287 -0
- package/dist-shared/entity-delta.js.map +1 -0
- package/dist-shared/kpi-broadcast.d.ts +4 -0
- package/dist-shared/kpi-broadcast.js +16 -0
- package/dist-shared/kpi-broadcast.js.map +1 -0
- package/dist-shared/twin-level.d.ts +23 -0
- package/dist-shared/twin-level.js +52 -0
- package/dist-shared/twin-level.js.map +1 -0
- package/package.json +14 -12
- package/server/engine/attention-digest.ts +102 -0
- package/server/engine/canonical-ingest.ts +36 -4
- package/server/engine/command-routing.ts +67 -0
- package/server/engine/energy-topology.ts +189 -0
- package/server/engine/index.ts +17 -0
- package/server/engine/kpi-baseline.ts +202 -0
- package/server/engine/kpi-fold.ts +389 -1
- package/server/engine/kpi-query.ts +295 -22
- package/server/engine/kpi-target.ts +24 -2
- package/server/engine/live-attentions.ts +7 -2
- package/server/engine/live-feed-registry.ts +58 -0
- package/server/engine/load-meter.ts +384 -0
- package/server/engine/local-declarations.ts +700 -0
- package/server/engine/model-basis.ts +78 -10
- package/server/engine/model-vocabulary.ts +82 -0
- package/server/engine/oee-accumulator.ts +34 -1
- package/server/engine/operation-basis.ts +100 -0
- package/server/engine/property-effects.ts +199 -0
- package/server/engine/runtime-key.ts +58 -0
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/state-axes.ts +60 -0
- package/server/engine/structure-diff.ts +1 -1
- package/server/engine/travel-estimator.ts +11 -6
- package/server/engine/twin-engine.ts +1710 -297
- package/server/engine/warm-start.ts +207 -22
- package/server/index.ts +13 -10
- package/server/migrations/1786000000000-RenameTwinInstanceBoardToModel.ts +43 -0
- package/server/migrations/index.ts +7 -0
- package/server/service/index.ts +9 -1
- package/server/service/reference/discovery-result.ts +95 -0
- package/server/service/reference/ingest-space.ts +104 -0
- package/server/service/reference/knob-defaults.ts +59 -0
- package/server/service/reference/reference-live.ts +121 -6
- package/server/service/reference/reference-master.ts +362 -35
- package/server/service/reference/reference-resolver.ts +307 -23
- package/server/service/reference/template-registry.ts +20 -5
- package/server/service/twin-attention/twin-attention-query.ts +43 -6
- package/server/service/twin-audit/command-audit.ts +81 -0
- package/server/service/twin-audit/index.ts +5 -0
- package/server/service/twin-audit/twin-audit-event.ts +112 -0
- package/server/service/twin-audit/twin-audit-query.ts +72 -0
- package/server/service/twin-control/twin-control-mutation.ts +53 -13
- package/server/service/twin-event/twin-event-keys.ts +1 -1
- package/server/service/twin-event/twin-event.ts +52 -15
- package/server/service/twin-forecast/forecast-metrics.ts +60 -0
- package/server/service/twin-forecast/forecast-tuning.ts +40 -0
- package/server/service/twin-forecast/gap-analytics.ts +48 -9
- package/server/service/twin-forecast/twin-forecast-query.ts +211 -38
- package/server/service/twin-instance/twin-instance.ts +127 -22
- package/server/service/twin-journal/twin-journal-query.ts +108 -14
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +40 -23
- package/server/service/twin-metrics/twin-metrics-query.ts +60 -3
- package/server/service/twin-model/epcis-coverage.ts +195 -0
- package/server/service/twin-model/iec61850-coverage.ts +117 -0
- package/server/service/twin-model/index.ts +25 -0
- package/server/service/twin-model/isa95-coverage.ts +190 -0
- package/server/service/twin-model/project-structure.ts +199 -0
- package/server/service/twin-model/standard-coverage.ts +35 -0
- package/server/service/twin-model/twin-equipment.ts +150 -0
- package/server/service/twin-model/twin-lineage-query.ts +193 -0
- package/server/service/twin-model/twin-location.ts +140 -0
- package/server/service/twin-model/twin-model-item-query.ts +848 -0
- package/server/service/twin-model/twin-model-mutation.ts +503 -0
- package/server/service/twin-model/twin-model-query.ts +537 -0
- package/server/service/twin-model/twin-model-tree-query.ts +180 -0
- package/server/service/twin-model/twin-operation.ts +150 -0
- package/server/service/twin-space/move-space.ts +262 -0
- package/server/service/twin-space/space-integrity.ts +293 -0
- package/server/service/twin-space/twin-space-area.ts +4 -4
- package/server/service/twin-space/twin-space-representation.ts +6 -6
- package/server/service/twin-space/twin-space-resolver.ts +365 -22
- package/server/service/twin-space/twin-space.ts +40 -6
- package/server/service/twin-state/twin-state-subscription.ts +1 -1
- package/server/service/twin-structure/twin-structure.ts +10 -7
- package/server/service/twin-target/twin-target-resolver.ts +22 -4
- package/shared/axis-read.ts +28 -0
- package/shared/entity-delta.ts +286 -0
- package/shared/kpi-broadcast.ts +13 -0
- package/shared/twin-level.ts +48 -0
- package/test/adopt-structure-live.test.ts +133 -0
- package/test/attention-digest.test.ts +135 -0
- package/test/axis-read.test.ts +87 -0
- package/test/boot-resume.test.ts +205 -0
- package/test/canonical-ingest-vocabularies.test.ts +118 -0
- package/test/capability-mapping.test.ts +5 -5
- package/test/command-routing.test.ts +61 -0
- package/test/declaration-reaches-model.test.ts +178 -0
- package/test/discovery-result.test.ts +75 -0
- package/test/energy-topology.test.ts +113 -0
- package/test/entity-delta.test.ts +195 -25
- package/test/event-time-column.test.ts +67 -0
- package/test/forecast-metrics.test.ts +71 -0
- package/test/forecast-tuning.test.ts +42 -0
- package/test/gap-analytics.test.ts +34 -7
- package/test/ingest-bench.test.ts +9 -9
- package/test/ingest-running-guard.test.ts +136 -0
- package/test/ingest-space.test.ts +78 -0
- package/test/instance-cache-lifecycle.test.ts +110 -0
- package/test/kernel-kind-guard.test.ts +96 -0
- package/test/knob-defaults.test.ts +72 -0
- package/test/kpi-baseline-db.test.ts +216 -0
- package/test/kpi-baseline.test.ts +196 -0
- package/test/kpi-fold.test.ts +393 -2
- package/test/kpi-query-bench.test.ts +130 -0
- package/test/lineage-survives-restart.test.ts +203 -0
- package/test/live-feed-registry.test.ts +67 -0
- package/test/live-kernel-facts.test.ts +161 -0
- package/test/live-mirror-parity.test.ts +54 -14
- package/test/load-meter.test.ts +314 -0
- package/test/local-declarations.test.ts +554 -0
- package/test/master-to-twin.test.ts +128 -36
- package/test/model-basis.test.ts +56 -1
- package/test/model-vocabulary.test.ts +114 -0
- package/test/move-space.test.ts +149 -0
- package/test/mutation-gate.test.ts +122 -0
- package/test/oee-accumulator.test.ts +90 -7
- package/test/operation-basis.test.ts +100 -0
- package/test/operations-capability-db.test.ts +165 -0
- package/test/project-structure-db.test.ts +226 -0
- package/test/projection-reaches-screen.test.ts +272 -0
- package/test/property-effects.test.ts +96 -0
- package/test/registry-key-guard.test.ts +80 -0
- package/test/resync-origin-site.test.ts +77 -0
- package/test/runtime-key.test.ts +66 -0
- package/test/scale-twin-bench.test.ts +7 -7
- package/test/snapshot-freshness.test.ts +60 -0
- package/test/space-integrity.test.ts +223 -0
- package/test/standard-coverage.test.ts +94 -0
- package/test/state-axes.test.ts +74 -0
- package/test/streamline-e2e.test.ts +16 -16
- package/test/structure-revision-db.test.ts +16 -15
- package/test/tenant-registry-db.test.ts +149 -0
- package/test/twin-audit.test.ts +83 -0
- package/test/twin-model-item-db.test.ts +275 -0
- package/test/twin-model-tree-db.test.ts +176 -0
- package/test/twin-origin-resync.test.ts +114 -0
- package/test/warm-start-seam.test.ts +140 -0
- package/test/warm-start.test.ts +223 -4
- package/tsconfig.json +6 -1
- package/tsconfig.shared.json +23 -0
- package/tsconfig.shared.tsbuildinfo +1 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist-server/engine/entity-delta.d.ts +0 -19
- package/dist-server/engine/entity-delta.js +0 -161
- package/dist-server/engine/entity-delta.js.map +0 -1
- package/dist-server/service/twin-event/backfill-keys.d.ts +0 -11
- package/dist-server/service/twin-event/backfill-keys.js +0 -63
- package/dist-server/service/twin-event/backfill-keys.js.map +0 -1
- package/dist-server/tsconfig.tsbuildinfo +0 -1
- package/server/engine/entity-delta.ts +0 -169
- package/server/service/twin-event/backfill-keys.ts +0 -72
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* Face2 마스터 인제스트(ADR-0018 반영 모드) — 레퍼런스 시스템(실 WMS/YMS/MES 또는 가상 레퍼런스)의
|
|
3
|
-
* 마스터를 읽어 트윈 구조(
|
|
3
|
+
* 마스터를 읽어 트윈 구조(TwinModelDef) + 공간(Space content)을 생성. 손배선이 아니라 인제스트로 엔티티가 흐른다.
|
|
4
4
|
*
|
|
5
5
|
* 규율(ADR-0018): 도메인 타입(location.type=노드용)은 커널 카탈로그 어휘를 그대로 반영(발명 금지).
|
|
6
|
-
* location 은 role 로 노드(운영점)·area(공간영역)·both 를 구분 — 노드→
|
|
6
|
+
* location 은 role 로 노드(운영점)·area(공간영역)·both 를 구분 — 노드→TwinModelDef, area→Space.areas.
|
|
7
7
|
* 좌표·representations 는 트윈 공간층(보완/추가). 실 시스템이 좌표를 안 주면 layout 없이 위상만 인제스트.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import type { EquipmentLevel, WorkCalendarEntry } from '@operato/twin-kernel'
|
|
10
|
+
import type { DomainSystem, EquipmentLevel, WorkCalendarEntry } from '@operato/twin-kernel'
|
|
11
11
|
|
|
12
12
|
export interface RefLocation {
|
|
13
13
|
id: string
|
|
14
14
|
type: string // 노드는 커널 노드타입(dock/storage/yard-slot…), area 는 공간 region 타입
|
|
15
15
|
name?: string
|
|
16
|
+
description?: string
|
|
16
17
|
role: 'location' | 'area' | 'both'
|
|
17
18
|
parentId?: string
|
|
19
|
+
/**
|
|
20
|
+
* **전기적으로 어디서 받는가** — 이 자리에 전기를 주는 상류 자리(커널 계약의 `upstreamId`).
|
|
21
|
+
*
|
|
22
|
+
* `parentId`(공간 포함)와 따로 두는 이유는 커널 계약에 적혀 있다. 여기서 빠뜨리면 마스터가 말한 계통이
|
|
23
|
+
* 모델까지 오지 못하고, 뿌리 계량 판정이 분기 계량기까지 뿌리로 세어 **부하가 두 배로** 보인다.
|
|
24
|
+
*/
|
|
25
|
+
upstreamId?: string
|
|
18
26
|
/** 저장 용량 — 이 자리에 동시에 놓일 수 있는 물품 수. */
|
|
19
27
|
capacity?: number
|
|
20
28
|
/**
|
|
@@ -30,6 +38,14 @@ export interface RefLocation {
|
|
|
30
38
|
* 짐작해 채우지 않는다.
|
|
31
39
|
*/
|
|
32
40
|
level?: EquipmentLevel
|
|
41
|
+
/**
|
|
42
|
+
* 자리의 **열린 속성** — 표준 `OperationalLocationProperty`(설비·사람·자산과 같은 모양).
|
|
43
|
+
*
|
|
44
|
+
* 무엇이 여기 실리나: **자리의 물리량**이다. 에너지의 계약전력(`contract.kW`)이 첫 사례다 —
|
|
45
|
+
* 그 값이 앉을 자리가 없어서 EMS 커널은 계약 대비 판정을 할 수 없었다(자리가 없으면 사실이
|
|
46
|
+
* 들어오지 못한다). 커널은 자기가 **읽는** 속성 id 만 선언하고(`EMS_PROPERTY`) 나머지는 나른다.
|
|
47
|
+
*/
|
|
48
|
+
properties?: RefResourceProperty[]
|
|
33
49
|
footprint?: { x: number; y: number; w: number; h: number } // 있으면 area 기하 + 노드 중심좌표
|
|
34
50
|
gs1Id?: string // 정식 GS1 정체성(§J) — 위치=SGLN. 결합(tag)/추적 canonical id. 받아들이기 경계서 부여(로컬 id=name/xref 로 보존).
|
|
35
51
|
}
|
|
@@ -85,9 +101,42 @@ export interface RefResourceClass {
|
|
|
85
101
|
effectiveEnd?: string
|
|
86
102
|
}
|
|
87
103
|
|
|
104
|
+
/**
|
|
105
|
+
* 검사 명세 — **자원이 가리키는 대상**(ISA-95 `TestSpecification`).
|
|
106
|
+
*
|
|
107
|
+
* 표준 필드는 셋만 담는다(`ID`·`Description`·`Version`) — 재는 속성과 합격 기준은 아직 1차 출처와
|
|
108
|
+
* 대조하지 않았다(커널 계약의 머리말이 그 사실을 적어 두었다).
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* 시험 결과 — **"이 개체가 그 시험을 통과했다"** 는 기록(ISA-95 결과).
|
|
112
|
+
*
|
|
113
|
+
* 결과를 낳는 곳은 트윈이 아니다(인사·정비 시스템이 기록한다) — 그래서 관측이면서 **선언 경로로** 온다.
|
|
114
|
+
* 커널은 이 값이 있을 때만 자격을 판정한다: 없으면 제약이 아니고(라인이 굶지 않는다), 있으면 만료·
|
|
115
|
+
* 불합격이 배정을 막는다.
|
|
116
|
+
*/
|
|
117
|
+
export interface RefTestResult {
|
|
118
|
+
specId: string
|
|
119
|
+
result: 'pass' | 'fail'
|
|
120
|
+
at?: string
|
|
121
|
+
/** 없으면 무기한 — 짐작으로 기한을 만들면 멀쩡한 자격자가 조용히 사라진다. */
|
|
122
|
+
expiresAt?: string
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface RefTestSpecification {
|
|
126
|
+
id: string
|
|
127
|
+
/** 표시명 — i18n 키일 수 있다. 읽는 어휘를 하나로 맞추려고 인제스트가 `name` 으로 옮긴다. */
|
|
128
|
+
description?: string
|
|
129
|
+
version?: string
|
|
130
|
+
}
|
|
131
|
+
|
|
88
132
|
export interface RefEquipment {
|
|
89
133
|
id: string
|
|
90
134
|
kind: string
|
|
135
|
+
/** 사람이 부르는 이름 — 없으면 화면이 id 를 보인다(id 를 이름으로 베끼지 않는다). */
|
|
136
|
+
name?: string
|
|
137
|
+
/** 시험 결과 — 자격이 성립하는지는 이것이 말한다(§RefTestResult). */
|
|
138
|
+
testResults?: RefTestResult[]
|
|
139
|
+
description?: string
|
|
91
140
|
homeLocationId: string
|
|
92
141
|
mtbfMs?: number // 평균 고장 간격(확률적 고장) — 지정 시 커널이 breakdown 시뮬 → 주목(고장) 발생
|
|
93
142
|
mttrMs?: number // 평균 수리 시간
|
|
@@ -165,7 +214,7 @@ export interface RefOperationVariability {
|
|
|
165
214
|
/**
|
|
166
215
|
* 오퍼레이션 명세 — ISA-95 `OperationsSegment`. 커널 `OperationDef` 와 필드 이름이 1:1이다.
|
|
167
216
|
*
|
|
168
|
-
* **이것이 없으면 시뮬레이션의 시간은 우리가 코드에
|
|
217
|
+
* **이것이 없으면 시뮬레이션의 시간은 우리가 코드에 고정한 상수다.** 원 시스템(또는 가상 레퍼런스)이
|
|
169
218
|
* 공정별 소요·변동·수율을 말해 줘야 예측이 그 현장의 예측이 된다. 주지 않으면 커널 기본값으로
|
|
170
219
|
* 굴러가고, **기본값을 썼다는 사실은 커널 `specCoverage()` 가 밝힌다**(조용히 넘어가지 않는다).
|
|
171
220
|
*/
|
|
@@ -203,6 +252,8 @@ export interface RefOperation {
|
|
|
203
252
|
export interface RefPerson {
|
|
204
253
|
id: string
|
|
205
254
|
name?: string
|
|
255
|
+
/** 시험 결과 — 자격이 성립하는지는 이것이 말한다(§RefTestResult). */
|
|
256
|
+
testResults?: RefTestResult[]
|
|
206
257
|
/**
|
|
207
258
|
* 속한 등급들 — **표준 `Person.PersonnelClassID`, `maxOccurs="unbounded"`**.
|
|
208
259
|
* 복수인 것이 표준이고 그것이 자격 표현이다: 한 사람이 용접 자격과 지게차 자격을 함께 갖는다.
|
|
@@ -240,6 +291,8 @@ export interface RefPerson {
|
|
|
240
291
|
export interface RefAsset {
|
|
241
292
|
id: string
|
|
242
293
|
name?: string
|
|
294
|
+
/** 시험 결과 — 자격이 성립하는지는 이것이 말한다(§RefTestResult). */
|
|
295
|
+
testResults?: RefTestResult[]
|
|
243
296
|
/**
|
|
244
297
|
* 속한 등급들 — 표준 `PhysicalAssetClassID`(복수). 팔레트가 'euro-pallet' 이면서 'food-grade' 일 수 있다.
|
|
245
298
|
* 공정의 요구는 등급 하나 + 수량이다(표준 `PhysicalAssetSpecification`).
|
|
@@ -280,10 +333,52 @@ export interface RefMaterialDefinition {
|
|
|
280
333
|
effectiveEnd?: string
|
|
281
334
|
}
|
|
282
335
|
|
|
336
|
+
/**
|
|
337
|
+
* **이 마스터를 다시 만들 수 있는 방법** — 트윈이 자기 원본을 기억하는 자리.
|
|
338
|
+
*
|
|
339
|
+
* 트윈은 원본을 비출 뿐이므로(ADR-0028), 원본이 달라지면 **다시 읽어야** 합니다. 그런데 지금까지
|
|
340
|
+
* 트윈은 "내가 무엇에서 나왔나" 를 남기지 않았다 — 어댑터로 들어온 트윈은 `TwinReference` 행이
|
|
341
|
+
* 있었지만 템플릿으로 세운 트윈에는 아무것도 없었고, 그래서 **다시 읽을 대상이 없는 트윈**이
|
|
342
|
+
* 만들어졌다. 원본을 잃은 트윈은 첫날의 모습에 영원히 머문다.
|
|
343
|
+
*
|
|
344
|
+
* 그래서 원천이 무엇이든 여기 한 자리에 남긴다. 다시 읽기는 이 값 하나만 보면 된다.
|
|
345
|
+
*/
|
|
346
|
+
export type MasterOrigin =
|
|
347
|
+
/**
|
|
348
|
+
* 등록된 레퍼런스(어댑터)에서 왔다 — 다시 읽기 = 그 어댑터로 마스터를 다시 가져온다.
|
|
349
|
+
*
|
|
350
|
+
* `source` 는 **레퍼런스 등록 이름**이고 그 참조가 보고하는 사이트 전부에 같다. 그래서 어느 사이트에서
|
|
351
|
+
* 온 트윈인지는 `siteId` 가 있어야 안다 — 없으면 다시 읽기가 사이트를 찾지 못한다(2026-08-18 실측:
|
|
352
|
+
* 사이트가 여러 개인 참조에서 온 트윈은 재동기가 전부 거절됐다. 참조 이름을 사이트별 마스터의
|
|
353
|
+
* `source`(=인스턴스 id)와 견주고 있었다 — 서로 다른 것을 비교한 것이다).
|
|
354
|
+
*/
|
|
355
|
+
| { kind: 'reference'; source: string; siteId?: string; spaceId?: string }
|
|
356
|
+
/**
|
|
357
|
+
* 템플릿 + 노브로 합성했다 — 다시 읽기 = 같은 노브로 다시 짓는다(합성은 결정적이다).
|
|
358
|
+
*
|
|
359
|
+
* `spaceId` 는 **만들 때 고른 현장**이다(합쳐 넣기). 기억하지 않으면 다시 읽을 때 트윈이 조용히 자기
|
|
360
|
+
* 현장을 떠난다 — 템플릿이 낸 기본 공간으로 돌아가기 때문이다. 그러면 그 현장의 값(요금 등)도 함께
|
|
361
|
+
* 잃는다(2026-08-18 실측: 한 현장에 합쳐 둔 EMS 트윈 여덟이 재동기 뒤 각자 공간으로 흩어졌다).
|
|
362
|
+
*/
|
|
363
|
+
| { kind: 'template'; templateId: string; params: Record<string, unknown>; spaceId?: string }
|
|
364
|
+
|
|
283
365
|
export interface ReferenceMaster {
|
|
284
366
|
source: string
|
|
285
|
-
|
|
367
|
+
/**
|
|
368
|
+
* 어느 종류의 트윈인가 — **커널 카탈로그가 정본**이다(`DomainSystem`).
|
|
369
|
+
*
|
|
370
|
+
* 예전에는 `'wms' | 'yms' | 'mes'` 를 여기 적어 뒀다. 커널에 종류가 하나 늘 때(에너지) 이 줄이
|
|
371
|
+
* 함께 움직여야 하는데, 손으로 적힌 목록은 그것을 알려 주지 않는다 — 어휘의 집은 한 곳이어야 한다.
|
|
372
|
+
*/
|
|
373
|
+
system: DomainSystem
|
|
286
374
|
siteName: string
|
|
375
|
+
/** 검사 명세 — 자원·등급의 `testSpecificationIds` 가 가리키는 대상. */
|
|
376
|
+
testSpecifications?: RefTestSpecification[]
|
|
377
|
+
/**
|
|
378
|
+
* 이 마스터가 어디서 나왔나 — **다시 읽기의 유일한 근거.** 없으면 그 트윈은 다시 읽을 수 없고,
|
|
379
|
+
* 조회 화면이 그 사실을 그대로 말한다(없는 것을 있는 척하지 않는다).
|
|
380
|
+
*/
|
|
381
|
+
origin?: MasterOrigin
|
|
287
382
|
/* spaceId 명시 시 그 공간을 참조(여러 트윈이 한 공간 공유, N:1). 없으면 source 에서 파생(1:1). */
|
|
288
383
|
/**
|
|
289
384
|
* 현장(공간). **시각 기준(`timezone`)이 여기 있는 이유**: 한 테넌트가 Rosarito(태평양)와 한국
|
|
@@ -301,12 +396,19 @@ export interface ReferenceMaster {
|
|
|
301
396
|
/**
|
|
302
397
|
* **생산 정의**(레시피·라우트·자재 바인딩) — 커널의 정의-구동 MES 모드 입구.
|
|
303
398
|
*
|
|
304
|
-
* 없으면 커널은 레거시(하드코딩) 흐름으로
|
|
305
|
-
* 커널은 그것을 보지 못한다** — 선언과 실행이
|
|
399
|
+
* 없으면 커널은 레거시(하드코딩) 흐름으로 동작한다. 즉 **현장 레시피를 적어도 이 자리에 실리지 않으면
|
|
400
|
+
* 커널은 그것을 보지 못한다** — 선언과 실행이 어긋나는 지점이라 자리를 연다.
|
|
401
|
+
*/
|
|
402
|
+
/**
|
|
403
|
+
* 생산 선언 — `TwinModelDef.productionSpec` 으로 통과한다(ISA-95 OperationsSegment + BOM).
|
|
404
|
+
*
|
|
405
|
+
* 예전 이름은 `mesSpec` 이었다. 담긴 것은 MES 만의 것이 아니라 "자재를 소비해 자재를 산출하는
|
|
406
|
+
* 공정" 이고, 이름이 MES 였기 때문에 창고 트윈은 이 자리를 쓸 생각조차 하지 못했다(유통가공 창고가
|
|
407
|
+
* BOM 을 설명 문서에만 적어 둔 채 키트를 못 만드는 트윈을 만든 원인). 근거는 커널 계약 주석에.
|
|
306
408
|
*/
|
|
307
|
-
|
|
409
|
+
productionSpec?: unknown
|
|
308
410
|
/**
|
|
309
|
-
* 사람 — 선언하지 않으면 **인원 제약이 없는 트윈**이 된다(설비만 있으면 언제나
|
|
411
|
+
* 사람 — 선언하지 않으면 **인원 제약이 없는 트윈**이 된다(설비만 있으면 언제나 가동되는 공장).
|
|
310
412
|
* 공정이 인원을 요구하는데 사람이 없으면 그 작업은 시작되지 않는다 — 그것이 사실이므로 그대로 둔다.
|
|
311
413
|
*/
|
|
312
414
|
persons?: RefPerson[]
|
|
@@ -317,7 +419,7 @@ export interface ReferenceMaster {
|
|
|
317
419
|
assets?: RefAsset[]
|
|
318
420
|
/**
|
|
319
421
|
* 등급 정의 — 표준 `PersonnelClass`·`EquipmentClass`·`PhysicalAssetClass`.
|
|
320
|
-
* 안 실어도 트윈은
|
|
422
|
+
* 안 실어도 트윈은 동작한다(소속 문자열 그대로 판정). 실으면 상속·유효기간이 살아난다.
|
|
321
423
|
*/
|
|
322
424
|
personnelClasses?: RefResourceClass[]
|
|
323
425
|
equipmentClasses?: RefResourceClass[]
|
|
@@ -325,12 +427,20 @@ export interface ReferenceMaster {
|
|
|
325
427
|
/** 품목 정의·등급 — 표준 `MaterialDefinition` / `MaterialClass`. 없으면 단위 환산을 할 수 없다. */
|
|
326
428
|
materialDefinitions?: RefMaterialDefinition[]
|
|
327
429
|
materialClasses?: RefResourceClass[]
|
|
430
|
+
/**
|
|
431
|
+
* 커넥터가 **옮기는 동안 알게 된 것.** 외부 시스템의 값을 우리 어휘로 못 옮겨 기본값을 넣었다면
|
|
432
|
+
* 여기에 담아 보낸다 — 그러면 `masterToTwin` 의 경고와 한 줄기로 합쳐져 화면까지 간다.
|
|
433
|
+
*
|
|
434
|
+
* 이 자리가 없던 동안 커넥터는 할 말이 있어도 **말할 곳이 없었다.** 그래서 도크를 랙으로 바꿔
|
|
435
|
+
* 놓고도 조용히 성공했다. 옮기는 사람만 아는 사실은 옮기는 자리에서 실어야 한다.
|
|
436
|
+
*/
|
|
437
|
+
warnings?: IngestWarning[]
|
|
328
438
|
}
|
|
329
439
|
|
|
330
440
|
export interface IngestedTwin {
|
|
331
|
-
|
|
441
|
+
model: any // TwinModelDef(+layout+spaceId) — 커널 소비
|
|
332
442
|
spaceContent: any // TwinSpace content — 뷰 소비
|
|
333
|
-
warnings:
|
|
443
|
+
warnings: IngestWarning[]
|
|
334
444
|
}
|
|
335
445
|
|
|
336
446
|
/**
|
|
@@ -344,7 +454,7 @@ export interface IngestedTwin {
|
|
|
344
454
|
* 값이다. 그래서 8월의 `America/Tijuana` 는 −420(PDT), 1월은 −480(PST)이 된다.
|
|
345
455
|
*
|
|
346
456
|
* **한계를 밝힌다**: 여기서 나오는 것은 **한 시점의** 오프셋이다. 커널은 고정 오프셋으로 하루 중 분을
|
|
347
|
-
* 읽으므로,
|
|
457
|
+
* 읽으므로, 모델을 만든 계절과 다른 계절을 길게 예측하면 한 시간 어긋난다. 표준에 충실한 끝 모습은
|
|
348
458
|
* 호스트가 절대 구간(`StartDateTime`/`FinishDateTime`)을 계산해 넣는 것이고, 그때 이 함수는 사라진다.
|
|
349
459
|
*
|
|
350
460
|
* 모르는 이름에는 **0 을 답하지 않는다**(`undefined`) — 0 은 "UTC 라고 선언했다" 는 뜻이고, 모르는 것을
|
|
@@ -365,21 +475,193 @@ export function utcOffsetOf(timezone?: string, at: Date = new Date()): number |
|
|
|
365
475
|
}
|
|
366
476
|
}
|
|
367
477
|
|
|
478
|
+
|
|
479
|
+
/* ── 인제스트 경고 ──────────────────────────────────────────────────────────
|
|
480
|
+
* 인제스트는 구조에 문제가 있어도 트윈을 만들어 준다(막지 않는 것이 옳다 — 부분적으로라도 세우고
|
|
481
|
+
* 고쳐 나가는 것이 이 시스템의 방식이다). 그래서 **경고가 유일한 신호**인데, 그 경고를 화면이 하나도
|
|
482
|
+
* 보여주지 않고 있었다: 유통가공 창고 템플릿은 커널이 모르는 자리 타입(`vas-station`)으로 트윈을
|
|
483
|
+
* 만들면서 경고를 냈지만, 위자드는 그것을 버렸고 사용자는 아무 말 없이 막히는 트윈을 받았다.
|
|
484
|
+
*
|
|
485
|
+
* 문장이 아니라 코드로 실어 보낸다. 예전 경고는 프로즈였고 그중 하나에는 한글이 섞여 있었다
|
|
486
|
+
* (`— 카탈로그에 없음`) — 다섯 언어 화면에 그대로 내보내면 이번에 걷어낸 문제를 다시 만든다.
|
|
487
|
+
* 이 프로젝트가 이미 쓰는 규약과 같은 모양이다: 주의 신호(kind+params)·커맨드 거절(errorCode+params)·
|
|
488
|
+
* 만만함 판정(code+영어 canonical message).
|
|
489
|
+
*
|
|
490
|
+
* code 언어 중립 — 화면이 `twin.ingest.<code>` 로 사람 말을 만든다
|
|
491
|
+
* params 문장에 끼울 값 — 번역이 언어마다 어순을 바꿀 수 있게
|
|
492
|
+
* message 영어 canonical — 로그에 남고, 번역이 없을 때 그대로 보인다(빈칸보다 낫다)
|
|
493
|
+
*
|
|
494
|
+
* **여기 사는 이유**: 이 파일이 경고의 생산자이고, 테스트가 소스에서 직접 읽는 모듈이라
|
|
495
|
+
* 런타임 상대 import 를 가질 수 없다(이 저장소의 규약 — 그런 모듈은 자기완결이다).
|
|
496
|
+
* ────────────────────────────────────────────────────────────────────────── */
|
|
497
|
+
/** 인제스트가 알리는 한 가지. */
|
|
498
|
+
export interface IngestWarning {
|
|
499
|
+
/** 언어 중립 코드. 화면은 `twin.ingest.<code>` 로 렌더한다. */
|
|
500
|
+
code: string
|
|
501
|
+
/** 문장에 끼울 값. 번역이 어순을 바꿀 수 있게 이름으로 넘긴다. */
|
|
502
|
+
params?: Record<string, string | number>
|
|
503
|
+
/** 영어 canonical 문장 — 로그·폴백용. 번역이 없어도 읽을 수 있어야 한다. */
|
|
504
|
+
message: string
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* **시각을 모르는 채 시각으로 판정하려 한다** — 발전 형상은 하루의 시각을 읽는데 이 트윈의 공간이
|
|
509
|
+
* 시간대를 말하지 않았다.
|
|
510
|
+
*
|
|
511
|
+
* ── 왜 경고인가 (2026-08-18) ────────────────────────────────────────────────
|
|
512
|
+
* 커널은 시간대를 모르면 UTC 로 읽는다(계약에 밝혀 둔 규율 — 모르는 값을 지어내지 않는다). 그런데 발전
|
|
513
|
+
* 형상은 **시각별 비율**이라, 시간대가 없으면 현장 정오가 아니라 UTC 정오에 피크가 선다(KST 현장이면
|
|
514
|
+
* 밤 9시). 값이 그럴듯하게 나오므로 아무도 알아채지 못한다.
|
|
515
|
+
*
|
|
516
|
+
* 그래서 시간대를 **지어내지 않고**(서버 로컬이나 임의 기본값을 넣으면 그 트윈의 모든 시각 판정이 조용히
|
|
517
|
+
* 그 가정 위에 선다) 대신 **그 조합을 말한다** — 공간에 시간대를 선언하면 사라지는 경고다.
|
|
518
|
+
*/
|
|
519
|
+
export const generationWithoutTimeZone = (spaceId: string, equipmentId: string): IngestWarning => ({
|
|
520
|
+
code: 'generation-without-time-zone',
|
|
521
|
+
params: { spaceId, equipmentId },
|
|
522
|
+
message:
|
|
523
|
+
`"${equipmentId}" declares a daily generation shape but space "${spaceId}" declares no time zone — the hourly fractions will be read in UTC, ` +
|
|
524
|
+
'so the generation peak will not land at local noon. Declare the space time zone (we do not guess one).'
|
|
525
|
+
})
|
|
526
|
+
|
|
527
|
+
/** 커널 카탈로그가 모르는 자리 타입 — 그 자리는 흐름에 참여하지 못할 수 있다. */
|
|
528
|
+
export const unknownLocationType = (system: string, type: string, id: string): IngestWarning => ({
|
|
529
|
+
code: 'unknown-location-type',
|
|
530
|
+
params: { system, type, id },
|
|
531
|
+
message: `"${type}" is not a known ${system} location type (${id}) — the kernel may not include this location in the flow`
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
/** 시간대 문자열을 못 읽었다 — 시각 해석이 UTC 로 떨어진다(조용히 현지 시각으로 가정하지 않는다). */
|
|
535
|
+
export const unknownTimezone = (timezone: string): IngestWarning => ({
|
|
536
|
+
code: 'unknown-timezone',
|
|
537
|
+
params: { timezone },
|
|
538
|
+
message: `space timezone "${timezone}" is not a known IANA zone — the twin will read times as UTC`
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
/** 작업 정의에 소요시간·파라미터가 없다 — 커널 기본값으로 동작한다. */
|
|
542
|
+
export const operationWithoutDuration = (operation: string): IngestWarning => ({
|
|
543
|
+
code: 'operation-without-duration',
|
|
544
|
+
params: { operation },
|
|
545
|
+
message: `operation "${operation}" has no duration or parameters — kernel defaults will be used`
|
|
546
|
+
})
|
|
547
|
+
|
|
548
|
+
/** 볼 수 있는 표현이 없다 — 현장이 화면에 뜨지 않는다. */
|
|
549
|
+
export const noViewableRepresentation = (): IngestWarning => ({
|
|
550
|
+
code: 'no-viewable-representation',
|
|
551
|
+
message: 'the space has no viewable representation (no representations and no geo) — add one, or provide geo/board'
|
|
552
|
+
})
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* 구조 투영에서 **가리키는 대상을 못 찾았다.**
|
|
556
|
+
*
|
|
557
|
+
* 예전에는 이런 참조가 model JSON 안의 맨 문자열이라 화면만 경고할 수 있었고, 다른 경로로 들어온
|
|
558
|
+
* 데이터는 아무도 안 봤다. 이제 행으로 풀면서 **인제스트가 직접 안다** — 그러니 인제스트가 말한다.
|
|
559
|
+
* 막지는 않는다(트윈은 model 로 이미 동작한다). 다만 조용히 넘기지 않는다.
|
|
560
|
+
*/
|
|
561
|
+
export const unresolvedReference = (field: string, ref: string): IngestWarning => ({
|
|
562
|
+
code: 'unresolved-reference',
|
|
563
|
+
params: { field, ref },
|
|
564
|
+
message: `${field} points at something that is not in this twin (${ref}) — the link was left empty`
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* 구조 투영 자체가 실패했다 — 조회는 못 하지만 **트윈은 동작한다**(커널은 model 를 이미 받았다).
|
|
569
|
+
* 행은 원본에서 다시 그릴 수 있으므로 인제스트를 실패로 만들지 않되, 침묵하지도 않는다.
|
|
570
|
+
*/
|
|
571
|
+
export const projectionFailed = (reason: string): IngestWarning => ({
|
|
572
|
+
code: 'projection-failed',
|
|
573
|
+
params: { reason },
|
|
574
|
+
message: `structure projection failed (${reason}) — the twin runs, but model inspection will be stale until the next ingest`
|
|
575
|
+
})
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* **동작 중에 공장을 전환했다** — 미러는 멈추지 않으므로, 무엇이 사라졌는지 여기서 말한다.
|
|
579
|
+
*
|
|
580
|
+
* 멈췄다 세우는 구조 전환이라면 사용자가 "다시 세웠다" 는 것을 안다. 미러는 그 마디가 없어서
|
|
581
|
+
* 자리 하나·설비 한 대가 조용히 없어질 수 있다 — 자리가 수천인 현장에서 화면의 수를 세어
|
|
582
|
+
* 눈치채기를 기대할 수는 없다. 늘어난 것도 함께 말한다(무엇이 반영되었나가 곧 인제스트의 답이다).
|
|
583
|
+
*/
|
|
584
|
+
export const structureAdopted = (rev: number, shift: { locationsAdded: number; locationsDropped: number; equipmentDropped: number; personsDropped: number; assetsDropped: number }): IngestWarning => ({
|
|
585
|
+
code: 'structure-adopted',
|
|
586
|
+
params: { rev, ...shift },
|
|
587
|
+
message:
|
|
588
|
+
`the running mirror adopted a new structure (revision ${rev}) — ` +
|
|
589
|
+
`+${shift.locationsAdded} location(s), -${shift.locationsDropped} location(s), ` +
|
|
590
|
+
`-${shift.equipmentDropped} equipment, -${shift.personsDropped} person(s), -${shift.assetsDropped} asset(s)`
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
/**
|
|
594
|
+
* 커넥터가 외부 시스템의 값을 **우리 어휘로 옮기지 못해 기본값을 넣었다.**
|
|
595
|
+
*
|
|
596
|
+
* 예: Oracle WMS 가 우리 표에 없는 `locn_type` 을 주면 그 자리는 `storage` 가 된다 — 도크가 랙으로
|
|
597
|
+
* 모델링되는 것이다. 인제스트를 통째로 실패시킬 일은 아니지만(로케이션 하나 때문에 현장 전체가
|
|
598
|
+
* 안 들어오는 편이 더 나쁘다), **조용히 넘어가면 안 된다.** 트윈이 실물과 다르게 생겼는데
|
|
599
|
+
* 아무도 그 사실을 모른 채 그 위에서 흐름을 논하게 된다.
|
|
600
|
+
*
|
|
601
|
+
* 값을 나열하는 이유: "3건" 만으로는 커넥터 매핑에 무엇을 더해야 하는지 알 수 없다.
|
|
602
|
+
*/
|
|
603
|
+
export const connectorMappingFallback = (field: string, fallback: string, values: readonly string[], count: number): IngestWarning => ({
|
|
604
|
+
code: 'connector-mapping-fallback',
|
|
605
|
+
params: { field, fallback, values: values.join(', '), count },
|
|
606
|
+
message: `${count} ${field} value(s) had no mapping and fell back to "${fallback}" (${values.join(', ')}) — the twin's shape differs from the source system here`
|
|
607
|
+
})
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
/** 로그 한 줄 — 개발자가 읽는 것이므로 영어 canonical 문장을 쓴다. */
|
|
611
|
+
export const describeWarnings = (warnings: readonly IngestWarning[]): string =>
|
|
612
|
+
warnings.map(w => `${w.code}: ${w.message}`).join(' | ')
|
|
613
|
+
|
|
614
|
+
/**
|
|
615
|
+
* 서버가 기본값으로 채운 노브들 — 사용자가 준 적 없는 값으로 트윈이 만들어졌음을 알린다.
|
|
616
|
+
*
|
|
617
|
+
* 조용히 채우면 나중에 "왜 이 수치인가" 를 아무도 답할 수 없다. 막을 일은 아니다(기본값은 템플릿이
|
|
618
|
+
* 확신을 갖고 선언한 값이다) — 다만 밝힌다.
|
|
619
|
+
*/
|
|
620
|
+
export const knobDefaultsFilled = (keys: readonly string[]): IngestWarning => ({
|
|
621
|
+
code: 'knob-defaults-filled',
|
|
622
|
+
params: { keys: keys.join(', '), count: keys.length },
|
|
623
|
+
message: `values you did not provide were filled from the template defaults: ${keys.join(', ')}`
|
|
624
|
+
})
|
|
625
|
+
|
|
626
|
+
/**
|
|
627
|
+
* 이 현장의 설비를 노브 값으로 채웠다 — **조용히 채우지 않는다.**
|
|
628
|
+
*
|
|
629
|
+
* 에너지 트윈의 부하를 이웃 설비 id 로 선언할 때 쓴다. 몇 개를 가져왔는지 말해 주지 않으면, 사용자는
|
|
630
|
+
* 자기가 고르지 않은 설비들이 부하로 잡힌 것을 나중에 화면에서야 알게 된다.
|
|
631
|
+
*/
|
|
632
|
+
export const siteEquipmentAdopted = (key: string, count: number): IngestWarning => ({
|
|
633
|
+
code: 'site-equipment-adopted',
|
|
634
|
+
params: { key, count },
|
|
635
|
+
message: `${count} piece(s) of equipment already on this site were adopted as loads (knob "${key}") — set their coefficients before trusting the numbers`
|
|
636
|
+
})
|
|
637
|
+
|
|
368
638
|
export function masterToTwin(master: ReferenceMaster, catalog?: any): IngestedTwin {
|
|
369
|
-
|
|
639
|
+
/* 커넥터가 옮기며 알게 된 것을 먼저 싣는다 — 원인이 앞에 오는 편이 읽기 쉽다(대체가 먼저, 그 결과가 뒤). */
|
|
640
|
+
const warnings: IngestWarning[] = [...(master.warnings ?? [])]
|
|
370
641
|
const known: string[] = catalog?.[master.system]?.locationTypes ?? []
|
|
371
642
|
|
|
372
643
|
const locationEntries = master.locations.filter(l => l.role === 'location' || l.role === 'both')
|
|
373
644
|
const areaLocs = master.locations.filter(l => l.role === 'area' || l.role === 'both')
|
|
374
645
|
|
|
375
646
|
const locations = locationEntries.map(l => {
|
|
376
|
-
if (known.length && !known.includes(l.type)) warnings.push(
|
|
377
|
-
// gs1Id(정식 정체성)를
|
|
378
|
-
|
|
647
|
+
if (known.length && !known.includes(l.type)) warnings.push(unknownLocationType(master.system, l.type, l.id))
|
|
648
|
+
// gs1Id(정식 정체성)를 모델의 자리로 통과 — 결합(tag)/추적이 GS1 로(§J). 로컬 id 는 그대로 유지(name/xref).
|
|
649
|
+
/*
|
|
650
|
+
* **이름을 통과시킨다.** 여기서 버리고 있었다 — 그래서 모델에도, 투영한 행에도 이름이 없고
|
|
651
|
+
* 화면은 자리 id 를 그대로 찍었다(`cut-station`). 목록·표·관계 그림에서 같은 결함이 따로
|
|
652
|
+
* 보였던 이유가 이 한 줄이다. 이름 없는 원본은 이름 없이 통과한다(id 로 메우지 않는다).
|
|
653
|
+
*/
|
|
654
|
+
/*
|
|
655
|
+
* **속성도 통과시킨다** — 설비와 같은 규율(손대지 않고 나른다). 여기가 없으면 자리에 실은 값이
|
|
656
|
+
* 커널에 닿지 않는다: 에너지의 계약전력(`contract.kW`)이 그 경로이고, 통과하지 않으면 커널이
|
|
657
|
+
* 계약을 모르는 채로 서서 **판정을 아예 하지 않는다**(오류 없이 조용히).
|
|
658
|
+
*/
|
|
659
|
+
return { id: l.id, type: l.type, ...(l.name ? { name: l.name } : {}), ...(l.description ? { description: l.description } : {}), capacity: l.capacity ?? 0, ...(l.parallelism !== undefined ? { parallelism: l.parallelism } : {}), ...(l.level ? { level: l.level } : {}), ...(l.properties?.length ? { properties: l.properties } : {}), parentId: l.parentId, ...(l.upstreamId ? { upstreamId: l.upstreamId } : {}), gs1Id: l.gs1Id }
|
|
379
660
|
})
|
|
380
661
|
|
|
381
662
|
const equipment = (master.equipment ?? []).map(e => ({
|
|
382
|
-
id: e.id, kind: e.kind,
|
|
663
|
+
id: e.id, kind: e.kind, ...(e.name ? { name: e.name } : {}), ...(e.description ? { description: e.description } : {}), ...(e.testResults?.length ? { testResults: e.testResults } : {}),
|
|
664
|
+
homeLocation: e.homeLocationId, mtbfMs: e.mtbfMs, mttrMs: e.mttrMs, gs1Id: e.gs1Id,
|
|
383
665
|
...(e.window ? { window: e.window } : {}),
|
|
384
666
|
/* 속성은 손대지 않고 통과 — 이동시간 추정기가 kind 별 속도를 여기서 읽는다(단위 코드 그대로). */
|
|
385
667
|
...(e.properties?.length ? { properties: e.properties } : {}), ...(e.workCalendar?.length ? { workCalendar: e.workCalendar } : {}), ...(e.testSpecificationIds?.length ? { testSpecificationIds: e.testSpecificationIds } : {}),
|
|
@@ -394,56 +676,84 @@ export function masterToTwin(master: ReferenceMaster, catalog?: any): IngestedTw
|
|
|
394
676
|
const spaceId = master.space.spaceId ?? `${master.source}-site`
|
|
395
677
|
/*
|
|
396
678
|
* 시각 기준을 커널이 읽을 형태로 푼다 — 커널은 zero-dep 이라 시간대 데이터베이스를 들이지 않고
|
|
397
|
-
* **분 오프셋**만 받는다(계약 `
|
|
679
|
+
* **분 오프셋**만 받는다(계약 `TwinModelDef.utcOffsetMinutes`). 푸는 일은 호스트의 몫이다.
|
|
398
680
|
*
|
|
399
681
|
* 선언이 없으면 **필드를 만들지 않는다** — 그러면 커널이 UTC 로 읽고, 그 기본값은 계약에 밝혀져 있다.
|
|
400
682
|
* 여기서 0 을 넣으면 "UTC 라고 선언했다" 와 "선언하지 않았다" 가 구별되지 않는다.
|
|
401
683
|
*/
|
|
402
684
|
const utcOffsetMinutes = utcOffsetOf(master.space.timezone)
|
|
403
685
|
if (master.space.timezone && utcOffsetMinutes === undefined) {
|
|
404
|
-
warnings.push(
|
|
686
|
+
warnings.push(unknownTimezone(String(master.space.timezone)))
|
|
405
687
|
}
|
|
406
688
|
/*
|
|
407
|
-
* 오퍼레이션 명세를
|
|
689
|
+
* 오퍼레이션 명세를 model 에 통과시킨다 — 커널이 `loadOperations` 로 소비한다(TwinModelDef 확장 필드,
|
|
408
690
|
* 모르는 커널은 무시). 여기서 값을 손대지 않는다: 기간은 표준 표기(ISO 8601)로 커널까지 그대로 간다.
|
|
409
691
|
*/
|
|
410
692
|
const operations = master.operations
|
|
411
693
|
if (operations?.length) {
|
|
412
694
|
for (const o of operations) {
|
|
413
|
-
if (!o.duration && !o.parameters?.length) warnings.push(
|
|
695
|
+
if (!o.duration && !o.parameters?.length) warnings.push(operationWithoutDuration(o.key))
|
|
414
696
|
}
|
|
415
697
|
}
|
|
416
698
|
/* 사람도 손대지 않고 통과 — 등급·교대 그대로 커널까지 간다(경계에서 어휘를 갈지 않는다). */
|
|
417
|
-
const persons = master.persons?.map(p => ({ id: p.id, personnelClassIds: p.personnelClassIds, ...(p.window ? { window: p.window } : {}), ...(p.workCalendar?.length ? { workCalendar: p.workCalendar } : {}), ...(p.homeLocationId ? { homeLocation: p.homeLocationId } : {}), ...(p.properties?.length ? { properties: p.properties } : {}), ...(p.testSpecificationIds?.length ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.effectiveStart ? { effectiveStart: p.effectiveStart } : {}), ...(p.effectiveEnd ? { effectiveEnd: p.effectiveEnd } : {}) }))
|
|
699
|
+
const persons = master.persons?.map(p => ({ id: p.id, ...(p.name ? { name: p.name } : {}), ...(p.testResults?.length ? { testResults: p.testResults } : {}), personnelClassIds: p.personnelClassIds, ...(p.window ? { window: p.window } : {}), ...(p.workCalendar?.length ? { workCalendar: p.workCalendar } : {}), ...(p.homeLocationId ? { homeLocation: p.homeLocationId } : {}), ...(p.properties?.length ? { properties: p.properties } : {}), ...(p.testSpecificationIds?.length ? { testSpecificationIds: p.testSpecificationIds } : {}), ...(p.effectiveStart ? { effectiveStart: p.effectiveStart } : {}), ...(p.effectiveEnd ? { effectiveEnd: p.effectiveEnd } : {}) }))
|
|
418
700
|
/* 자산도 손대지 않고 통과 — 등급·자리 그대로 커널까지. */
|
|
419
|
-
const assets = master.assets?.map(a => ({ id: a.id, assetClassIds: a.assetClassIds, homeLocation: a.homeLocationId, gs1Id: a.gs1Id, ...(a.properties?.length ? { properties: a.properties } : {}), ...(a.testSpecificationIds?.length ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.effectiveStart ? { effectiveStart: a.effectiveStart } : {}), ...(a.effectiveEnd ? { effectiveEnd: a.effectiveEnd } : {}) }))
|
|
701
|
+
const assets = master.assets?.map(a => ({ id: a.id, ...(a.name ? { name: a.name } : {}), ...(a.testResults?.length ? { testResults: a.testResults } : {}), assetClassIds: a.assetClassIds, homeLocation: a.homeLocationId, gs1Id: a.gs1Id, ...(a.properties?.length ? { properties: a.properties } : {}), ...(a.testSpecificationIds?.length ? { testSpecificationIds: a.testSpecificationIds } : {}), ...(a.effectiveStart ? { effectiveStart: a.effectiveStart } : {}), ...(a.effectiveEnd ? { effectiveEnd: a.effectiveEnd } : {}) }))
|
|
420
702
|
/* 품목 정의·등급도 손대지 않고 통과 — 경계에서 어휘를 갈지 않는다(환산 계수는 커널이 읽는다). */
|
|
421
703
|
const materialDefinitions = master.materialDefinitions?.map(d => ({
|
|
422
704
|
id: d.id,
|
|
423
|
-
|
|
705
|
+
/* 이름은 이름 자리에 — 예전에는 `name` 을 `description` 으로 옮겨 담았다(어휘가 뒤집혀 있었다). */
|
|
706
|
+
...(d.name ? { name: d.name } : {}),
|
|
424
707
|
...(d.materialClassIds?.length ? { materialClassIds: d.materialClassIds } : {}),
|
|
425
708
|
...(d.properties?.length ? { properties: d.properties } : {}),
|
|
426
709
|
...(d.testSpecificationIds?.length ? { testSpecificationIds: d.testSpecificationIds } : {}),
|
|
427
710
|
...(d.effectiveStart ? { effectiveStart: d.effectiveStart } : {}),
|
|
428
711
|
...(d.effectiveEnd ? { effectiveEnd: d.effectiveEnd } : {})
|
|
429
712
|
}))
|
|
430
|
-
|
|
713
|
+
/*
|
|
714
|
+
* **등급의 표시명은 `name` 이다.**
|
|
715
|
+
*
|
|
716
|
+
* 표준 마스터는 등급의 표시명을 `description` 에 담는다(`RefResourceClass.description` = i18n 키).
|
|
717
|
+
* 그것을 그대로 모델에 실으면 화면의 "설명" 열에 이름이 앉는다 — 사용자에게는 이름인데 머리글은
|
|
718
|
+
* 설명이라, 같은 것을 축마다 다른 이름으로 부르게 된다.
|
|
719
|
+
*
|
|
720
|
+
* 그래서 **문 하나에서 어휘를 맞춘다**: 표시명은 어느 축이든 `name`. 원래의 `description` 은
|
|
721
|
+
* 남겨 두지 않는다 — 두 자리에 같은 값이 있으면 어느 것이 정본인지 다시 헷갈린다.
|
|
722
|
+
*/
|
|
723
|
+
const asName = (list?: RefResourceClass[]) =>
|
|
724
|
+
list?.map(({ description, ...rest }) => ({ ...rest, ...(description ? { name: description } : {}) }))
|
|
725
|
+
/*
|
|
726
|
+
* 검사 명세도 **표시명은 `name`** 이다 — 등급과 같은 규칙을 쓴다(어휘를 하나로). 표준 마스터는
|
|
727
|
+
* 표시명을 `description` 에 담으므로 문 하나에서 옮긴다. 그러지 않으면 화면의 "설명" 열에 이름이
|
|
728
|
+
* 앉아 축마다 다른 이름으로 같은 것을 부르게 된다(등급에서 이미 겪었다).
|
|
729
|
+
*/
|
|
730
|
+
const testSpecifications = master.testSpecifications?.map(({ description, ...rest }) => ({
|
|
731
|
+
...rest,
|
|
732
|
+
...(description ? { name: description } : {})
|
|
733
|
+
}))
|
|
734
|
+
const personnelClasses = asName(master.personnelClasses)
|
|
735
|
+
const equipmentClasses = asName(master.equipmentClasses)
|
|
736
|
+
const assetClasses = asName(master.assetClasses)
|
|
737
|
+
const materialClasses = asName(master.materialClasses)
|
|
738
|
+
|
|
739
|
+
const model = {
|
|
431
740
|
locations, equipment, layout, spaceId,
|
|
432
741
|
/* 생산 정의도 손대지 않고 통과 — 경계에서 어휘를 갈지 않는다(커널이 그대로 소비한다). */
|
|
433
|
-
...(master.
|
|
742
|
+
...(master.productionSpec ? { productionSpec: master.productionSpec } : {}),
|
|
434
743
|
...(materialDefinitions?.length ? { materialDefinitions } : {}),
|
|
435
|
-
...(
|
|
744
|
+
...(materialClasses?.length ? { materialClasses } : {}),
|
|
436
745
|
...(utcOffsetMinutes !== undefined ? { utcOffsetMinutes } : {}),
|
|
437
746
|
...(operations?.length ? { operations } : {}),
|
|
438
|
-
...(
|
|
439
|
-
...(
|
|
440
|
-
...(
|
|
747
|
+
...(testSpecifications?.length ? { testSpecifications } : {}),
|
|
748
|
+
...(personnelClasses?.length ? { personnelClasses } : {}),
|
|
749
|
+
...(equipmentClasses?.length ? { equipmentClasses } : {}),
|
|
750
|
+
...(assetClasses?.length ? { assetClasses } : {}),
|
|
441
751
|
...(persons?.length ? { persons } : {}),
|
|
442
752
|
...(assets?.length ? { assets } : {})
|
|
443
753
|
}
|
|
444
754
|
|
|
445
755
|
const areas = areaLocs.map(l => ({
|
|
446
|
-
id: l.id, type: l.type, name: l.name ?? l.id, parentId: l.parentId,
|
|
756
|
+
id: l.id, type: l.type, name: l.name ?? l.id, parentId: l.parentId, ...(l.upstreamId ? { upstreamId: l.upstreamId } : {}),
|
|
447
757
|
x: l.footprint?.x ?? 0, y: l.footprint?.y ?? 0, w: l.footprint?.w ?? 10, h: l.footprint?.h ?? 10
|
|
448
758
|
}))
|
|
449
759
|
// 인제스트 현장 물질화(space facet #4) — 표현이 없고 geo 가 있으면 기본 map 표현 합성.
|
|
@@ -458,13 +768,30 @@ export function masterToTwin(master: ReferenceMaster, catalog?: any): IngestedTw
|
|
|
458
768
|
? [{ id: 'r-map', type: 'map', name: '위성 지도', primary: true, center: { lat: geo.lat, lon: geo.lon }, zoom: 17 }]
|
|
459
769
|
: []
|
|
460
770
|
if (representations.length === 0) {
|
|
461
|
-
warnings.push(
|
|
771
|
+
warnings.push(noViewableRepresentation())
|
|
462
772
|
}
|
|
463
773
|
const spaceContent = {
|
|
464
774
|
width: master.space.width, depth: master.space.depth, unit: master.space.unit ?? 'm',
|
|
465
775
|
geo, representations, primaryId: master.space.primaryId ?? representations[0]?.id,
|
|
466
|
-
areas
|
|
776
|
+
areas
|
|
467
777
|
}
|
|
468
778
|
|
|
469
|
-
return {
|
|
779
|
+
return { model, spaceContent, warnings }
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* 다시 읽을 사이트를 고른다 — **기억해 둔 사이트 하나만**.
|
|
784
|
+
*
|
|
785
|
+
* 못 찾으면 **첫 사이트로 때우지 않는다**: 다른 현장의 구조를 이 트윈에 덮어쓰는 일이고, 그것은 오류가
|
|
786
|
+
* 아니라 **조용히 틀린 공장**으로 나타난다. 기억이 없는 옛 트윈은 `undefined` 를 돌려주고, 부르는 쪽이
|
|
787
|
+
* 마스터의 정체(`source` = 인스턴스 id)로 찾는 길을 택한다.
|
|
788
|
+
*/
|
|
789
|
+
export function pickOriginSite<T extends { siteId: string }>(
|
|
790
|
+
sites: readonly T[],
|
|
791
|
+
origin: { siteId?: string } | undefined | null
|
|
792
|
+
): { site?: T; reason?: 'no-remembered-site' | 'site-gone' } {
|
|
793
|
+
const wanted = String(origin?.siteId ?? '').trim()
|
|
794
|
+
if (!wanted) return { reason: 'no-remembered-site' }
|
|
795
|
+
const site = sites.find(s => String(s.siteId) === wanted)
|
|
796
|
+
return site ? { site } : { reason: 'site-gone' }
|
|
470
797
|
}
|