@things-factory/headless-twin 10.0.9 → 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/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.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- 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 +8 -3
- package/dist-server/engine/index.js +10 -3
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/kpi-baseline.d.ts +1 -1
- package/dist-server/engine/kpi-baseline.js.map +1 -1
- 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 +1 -1
- package/dist-server/engine/kpi-query.js +212 -18
- 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-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/{board-vocabulary.d.ts → model-vocabulary.d.ts} +4 -4
- package/dist-server/engine/{board-vocabulary.js → model-vocabulary.js} +37 -19
- 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 +1 -1
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.js +3 -3
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/state-axes.d.ts +7 -2
- package/dist-server/engine/state-axes.js +8 -3
- package/dist-server/engine/state-axes.js.map +1 -1
- 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 +332 -47
- package/dist-server/engine/twin-engine.js +1314 -279
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/engine/warm-start.d.ts +38 -4
- package/dist-server/engine/warm-start.js +38 -9
- 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 +1 -1
- package/dist-server/service/reference/discovery-result.js +2 -2
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/ingest-space.d.ts +24 -1
- package/dist-server/service/reference/ingest-space.js +17 -1
- package/dist-server/service/reference/ingest-space.js.map +1 -1
- package/dist-server/service/reference/reference-live.d.ts +23 -0
- package/dist-server/service/reference/reference-live.js +121 -4
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +194 -13
- package/dist-server/service/reference/reference-master.js +159 -28
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +31 -1
- package/dist-server/service/reference/reference-resolver.js +223 -12
- 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-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.js +21 -1
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +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 +217 -38
- 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 -20
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.d.ts +5 -2
- package/dist-server/service/twin-journal/twin-journal-query.js +71 -9
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +3 -5
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +24 -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 +62 -0
- 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 +356 -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-structure/twin-structure.d.ts +1 -1
- package/dist-server/service/twin-structure/twin-structure.js +8 -8
- package/dist-server/service/twin-structure/twin-structure.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-server/engine → dist-shared}/entity-delta.d.ts +3 -0
- package/{dist-server/engine → dist-shared}/entity-delta.js +102 -2
- package/dist-shared/entity-delta.js.map +1 -0
- package/dist-shared/kpi-broadcast.js.map +1 -0
- package/dist-shared/twin-level.js.map +1 -0
- package/package.json +14 -12
- package/server/engine/canonical-ingest.ts +36 -4
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/energy-topology.ts +189 -0
- package/server/engine/index.ts +10 -3
- package/server/engine/kpi-baseline.ts +1 -1
- package/server/engine/kpi-fold.ts +389 -1
- package/server/engine/kpi-query.ts +214 -19
- package/server/engine/kpi-target.ts +24 -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/{board-vocabulary.ts → model-vocabulary.ts} +36 -15
- 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 +1 -1
- package/server/engine/spec-coverage.ts +3 -3
- package/server/engine/state-axes.ts +8 -3
- package/server/engine/structure-diff.ts +1 -1
- package/server/engine/travel-estimator.ts +11 -6
- package/server/engine/twin-engine.ts +1414 -274
- package/server/engine/warm-start.ts +83 -12
- 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 +2 -2
- package/server/service/reference/ingest-space.ts +35 -1
- package/server/service/reference/reference-live.ts +119 -4
- package/server/service/reference/reference-master.ts +276 -35
- package/server/service/reference/reference-resolver.ts +232 -12
- package/server/service/reference/template-registry.ts +20 -5
- 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 +22 -1
- 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 +208 -36
- package/server/service/twin-instance/twin-instance.ts +126 -25
- package/server/service/twin-journal/twin-journal-query.ts +74 -10
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +23 -22
- package/server/service/twin-metrics/twin-metrics-query.ts +59 -2
- 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 +355 -21
- package/server/service/twin-space/twin-space.ts +40 -6
- package/server/service/twin-structure/twin-structure.ts +10 -10
- package/shared/axis-read.ts +28 -0
- package/{server/engine → shared}/entity-delta.ts +96 -1
- package/test/adopt-structure-live.test.ts +133 -0
- package/test/attention-digest.test.ts +3 -3
- 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 +1 -1
- package/test/command-routing.test.ts +1 -1
- package/test/declaration-reaches-model.test.ts +178 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/energy-topology.test.ts +113 -0
- package/test/entity-delta.test.ts +171 -1
- 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 +6 -6
- package/test/ingest-running-guard.test.ts +136 -0
- package/test/ingest-space.test.ts +29 -1
- package/test/instance-cache-lifecycle.test.ts +110 -0
- package/test/kernel-kind-guard.test.ts +96 -0
- package/test/kpi-baseline-db.test.ts +3 -1
- package/test/kpi-baseline.test.ts +3 -3
- package/test/kpi-fold.test.ts +393 -2
- package/test/kpi-query-bench.test.ts +5 -3
- 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 +22 -15
- package/test/load-meter.test.ts +314 -0
- package/test/local-declarations.test.ts +554 -0
- package/test/master-to-twin.test.ts +121 -33
- package/test/model-basis.test.ts +56 -1
- package/test/{board-vocabulary.test.ts → model-vocabulary.test.ts} +25 -25
- package/test/move-space.test.ts +149 -0
- package/test/mutation-gate.test.ts +19 -5
- package/test/oee-accumulator.test.ts +26 -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/resync-origin-site.test.ts +77 -0
- package/test/scale-twin-bench.test.ts +5 -5
- 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 +1 -1
- package/test/streamline-e2e.test.ts +14 -14
- package/test/structure-revision-db.test.ts +11 -11
- package/test/tenant-registry-db.test.ts +5 -5
- 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 +6 -6
- package/test/warm-start.test.ts +88 -3
- 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/board-vocabulary.js.map +0 -1
- package/dist-server/engine/entity-delta.js.map +0 -1
- package/dist-server/engine/kpi-broadcast.js.map +0 -1
- package/dist-server/engine/twin-level.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/service/twin-event/backfill-keys.ts +0 -72
- /package/{dist-server/engine → dist-shared}/kpi-broadcast.d.ts +0 -0
- /package/{dist-server/engine → dist-shared}/kpi-broadcast.js +0 -0
- /package/{dist-server/engine → dist-shared}/twin-level.d.ts +0 -0
- /package/{dist-server/engine → dist-shared}/twin-level.js +0 -0
- /package/{server/engine → shared}/kpi-broadcast.ts +0 -0
- /package/{server/engine → shared}/twin-level.ts +0 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { Args, Ctx, Query, Resolver } from 'type-graphql'
|
|
2
|
+
|
|
3
|
+
import { ListParam, getQueryBuilderFromListParams, getRepository, ScalarObject } from '@things-factory/shell'
|
|
4
|
+
|
|
5
|
+
import { TwinAuditEvent } from './twin-audit-event.js'
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* 승인 기록 조회 — **감사의 기본 질문에 답한다.**
|
|
9
|
+
*
|
|
10
|
+
* "요즘 무슨 조치가 나갔나" → 최근순 목록
|
|
11
|
+
* "김현장이 승인한 것 전부" → 행위자 검색(인덱스 있음)
|
|
12
|
+
* "AI 가 제안한 것만" → origin 필터
|
|
13
|
+
* "거절된 시도" → accepted 필터
|
|
14
|
+
*
|
|
15
|
+
* 저널에 섞여 있을 때는 이 중 어느 것도 못 했다 — 행위자가 payload JSON 안이라 인덱스를 걸 수 없었다.
|
|
16
|
+
*/
|
|
17
|
+
/** 시각 필터 값을 날짜로 — 못 읽는 값은 그대로 둔다(지어내지 않는다). */
|
|
18
|
+
function toDateValue(v: any): any {
|
|
19
|
+
const one = (x: any) => {
|
|
20
|
+
if (x instanceof Date) return x
|
|
21
|
+
const t = typeof x === 'string' ? Date.parse(x) : NaN
|
|
22
|
+
return Number.isNaN(t) ? x : new Date(t)
|
|
23
|
+
}
|
|
24
|
+
return Array.isArray(v) ? v.map(one) : one(v)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@Resolver()
|
|
28
|
+
export class TwinAuditQuery {
|
|
29
|
+
/* 조회 게이트는 달지 않는다 — 이 저장소의 규약이고(mutation-gate 검사가 정본을 지킨다),
|
|
30
|
+
도메인 필터가 테넌트 경계를 이미 지킨다. 테넌트를 넘어 읽는 계기판만 OPS_GATE 를 쓴다. */
|
|
31
|
+
@Query(returns => ScalarObject, {
|
|
32
|
+
nullable: true,
|
|
33
|
+
description:
|
|
34
|
+
'List twin command audit records (who approved what, and what the kernel answered) with the standard list contract, plus the total match count.'
|
|
35
|
+
})
|
|
36
|
+
async twinAuditList(
|
|
37
|
+
/* 표준 목록 계약은 `@Args` 로 펼쳐 받는다 — `@Arg` 로 받으면 type-graphql 이 입력 타입을
|
|
38
|
+
결정하지 못해 **스키마 조립 자체가 실패**한다(서버가 아예 안 뜬다). */
|
|
39
|
+
@Args(type => ListParam) params: ListParam,
|
|
40
|
+
@Ctx() context: ResolverContext
|
|
41
|
+
): Promise<any> {
|
|
42
|
+
const domainId = context.state.domain?.id
|
|
43
|
+
if (!domainId) return { items: [], total: 0 }
|
|
44
|
+
|
|
45
|
+
const effective: ListParam = {
|
|
46
|
+
...(params ?? ({} as ListParam)),
|
|
47
|
+
/* 기본 정렬 — 최근 것부터. 감사는 "요즘" 에서 시작한다. */
|
|
48
|
+
sortings: params?.sortings?.length ? params.sortings : [{ name: 'eventTime', desc: true }],
|
|
49
|
+
/* 시각 필터는 날짜로 옮겨서 넘긴다 — 문자열 그대로면 조용히 0건이 된다(저널과 같은 이유). */
|
|
50
|
+
filters: (params?.filters ?? []).map(f => (f.name === 'eventTime' ? { ...f, value: toDateValue(f.value) } : f))
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const qb = getQueryBuilderFromListParams({
|
|
54
|
+
repository: getRepository(TwinAuditEvent),
|
|
55
|
+
params: effective,
|
|
56
|
+
domain: context.state.domain,
|
|
57
|
+
alias: 'a',
|
|
58
|
+
/*
|
|
59
|
+
* `searchables` 는 전부 **실컬럼**이다 — 프레임워크 질의 빌더는 여기 없는 컬럼의 LIKE 를
|
|
60
|
+
* 경고 후 무시한다(인덱스 없는 전체 스캔 방지). 행위자로 찾는 것이 이 테이블을 구분한 이유다.
|
|
61
|
+
*/
|
|
62
|
+
searchables: ['actorEmail', 'actorName', 'commandType', 'origin', 'errorCode', 'reason'],
|
|
63
|
+
sortables: ['eventTime', 'commandType', 'actorEmail', 'origin', 'accepted'],
|
|
64
|
+
/* 감사 목록은 사람이 읽는 속도로 본다 — 크게 퍼 오는 것이 이득이 아니다. */
|
|
65
|
+
defaultLimit: 50,
|
|
66
|
+
maxLimit: 200
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
const [items, total] = await qb.getManyAndCount()
|
|
70
|
+
return { items, total }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -4,7 +4,11 @@ import { validateScenario } from '@operato/twin-kernel'
|
|
|
4
4
|
|
|
5
5
|
import { ScalarObject } from '@things-factory/shell'
|
|
6
6
|
|
|
7
|
+
import { getRepository } from '@things-factory/shell'
|
|
8
|
+
|
|
7
9
|
import { TwinEngine } from '../../engine/index.js'
|
|
10
|
+
import { TwinAuditEvent } from '../twin-audit/twin-audit-event.js'
|
|
11
|
+
import { buildCommandAudit } from '../twin-audit/command-audit.js'
|
|
8
12
|
|
|
9
13
|
/*
|
|
10
14
|
* 트윈 Command·Scenario 채널 — 상향 mutation.
|
|
@@ -31,11 +35,28 @@ export class TwinControlMutation {
|
|
|
31
35
|
* 시뮬에만 있어서 미러 트윈에서는 이 한 줄이 TypeError 로 터졌다(깔끔한 거절이 아니라 실패).
|
|
32
36
|
* 권위(모드) × 성격(주석/액추에이션) 판정은 한 곳에만 둔다.
|
|
33
37
|
*/
|
|
38
|
+
/* 원본을 그대로 감사에 넘긴다 — 무엇이 들어왔는지가 기록의 대상이다(정규화는 감사 모듈이 한다). */
|
|
39
|
+
const cmdWithOrigin = command
|
|
34
40
|
const ack = await TwinEngine.dispatchCommand(domainId, instanceId, command)
|
|
35
|
-
/* 라이브 구조 변이(resource.add)는 저장
|
|
41
|
+
/* 라이브 구조 변이(resource.add)는 저장 model 에도 반영 — 프로비저닝 편집기·재기동이 추가분을 잃지 않게. */
|
|
36
42
|
if (ack?.accepted && command?.type === 'resource.add') {
|
|
37
43
|
await TwinEngine.syncBoardEquipment(domainId, instanceId).catch(err => console.error('twin syncBoardEquipment fail', err))
|
|
38
44
|
}
|
|
45
|
+
/*
|
|
46
|
+
* 감사 기록 — 승인 게이트가 앞을 막고, 이 줄이 뒤를 설명한다. 둘 다 있어야 "사람 손을 거쳤다" 를
|
|
47
|
+
* 증명할 수 있다. 커널이 거절한 것도 남긴다 — 무엇을 시도했는지가 감사의 절반이다.
|
|
48
|
+
*
|
|
49
|
+
* 기록이 실패해도 커맨드 결과는 그대로 돌려준다. 감사가 본업을 막으면 안 되지만, 실패를 삼키지도
|
|
50
|
+
* 않는다(로그에 남긴다) — 조용히 안 남으면 나중에 "기록이 없다" 와 "일이 없었다" 를 구분 못 한다.
|
|
51
|
+
*/
|
|
52
|
+
try {
|
|
53
|
+
const audit = buildCommandAudit({ command: cmdWithOrigin, ack, actor: context.state.user, at: new Date() })
|
|
54
|
+
const repo = getRepository(TwinAuditEvent)
|
|
55
|
+
await repo.save(repo.create({ domain: context.state.domain, instanceId, ...audit }))
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(`[twin-control] command audit not recorded for "${instanceId}"`, err)
|
|
58
|
+
}
|
|
59
|
+
|
|
39
60
|
/* 거절 사유는 **언어 중립 코드**로 함께 돌려준다 — 화면이 `twin.cmderr.<code>` 로 사람 말을 만든다. */
|
|
40
61
|
return { accepted: !!ack?.accepted, error: ack?.error, errorCode: ack?.errorCode, errorParams: ack?.errorParams }
|
|
41
62
|
}
|
|
@@ -83,7 +83,7 @@ export function locationOf(envelope: any): string | undefined {
|
|
|
83
83
|
* 설비·무버 — 운영 델타(equipment.status·task.status)가 대상을 가리키는 축.
|
|
84
84
|
*
|
|
85
85
|
* EPCIS 어휘가 아니라서 다른 축 어디에도 안 잡힌다. 이게 없으면 "이 지게차가 오늘 무엇을 했나" 를
|
|
86
|
-
* 서버에서 물을 방법이 없어, 화면이 저널을 통째로 받아
|
|
86
|
+
* 서버에서 물을 방법이 없어, 화면이 저널을 통째로 받아 전수 조회하는 수밖에 없다.
|
|
87
87
|
*/
|
|
88
88
|
export function equipmentIdOf(envelope: any): string | undefined {
|
|
89
89
|
const d = envelope?.data ?? envelope ?? {}
|
|
@@ -23,7 +23,7 @@ import { Domain, ScalarObject } from '@things-factory/shell'
|
|
|
23
23
|
*
|
|
24
24
|
* bizStep·locationId·moverId 는 컬럼만 두고 인덱스는 두지 않는다 — 한 트윈 안에서 카디널리티가
|
|
25
25
|
* 낮아(업무단계 몇 개, 위치 수백, 설비 수십) (domain,instanceId) 로 이미 좁혀진 뒤의 잔여 필터로
|
|
26
|
-
* 충분하고, 뜨거운 표에 인덱스를 더 얹을
|
|
26
|
+
* 충분하고, 뜨거운 표에 인덱스를 더 얹을 가치가 없다. 저널 하나가 아주 커져서 이 축들의 조회가
|
|
27
27
|
* 느려지면 그때 측정을 근거로 인덱스를 추가할 일이지, 지레 얹어 쓰기를 무겁게 할 일은 아니다.
|
|
28
28
|
*/
|
|
29
29
|
@Entity()
|
|
@@ -32,6 +32,8 @@ import { Domain, ScalarObject } from '@things-factory/shell'
|
|
|
32
32
|
@Index('ix_twin_event_2', (e: TwinEvent) => [e.domain, e.instanceId, e.eventType, e.revision], { unique: false })
|
|
33
33
|
@Index('ix_twin_event_3', (e: TwinEvent) => [e.domain, e.instanceId, e.epc], { unique: false })
|
|
34
34
|
@Index('ix_twin_event_4', (e: TwinEvent) => [e.domain, e.instanceId, e.orderId], { unique: false })
|
|
35
|
+
/* ix_5 — "이 지시가 무엇을 일으켰나". 감사 기록의 commandId 로 저널을 다시 계산하는 축. */
|
|
36
|
+
@Index('ix_twin_event_5', (e: TwinEvent) => [e.domain, e.correlationId], { unique: false })
|
|
35
37
|
@ObjectType({ description: 'Append-only twin event journal record (EPCIS event or operational delta).' })
|
|
36
38
|
export class TwinEvent {
|
|
37
39
|
@PrimaryGeneratedColumn('uuid')
|
|
@@ -45,17 +47,19 @@ export class TwinEvent {
|
|
|
45
47
|
@RelationId((e: TwinEvent) => e.domain)
|
|
46
48
|
domainId?: string
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
@
|
|
54
|
-
|
|
50
|
+
/*
|
|
51
|
+
* 이 사실을 낸 트윈. **빌 수 없다** — 저널 행을 만드는 곳은 `journalRow` 하나뿐이고 거기서
|
|
52
|
+
* 필수 인자로 받는다. nullable 로 두면 읽는 쪽이 매번 `?? ''` 를 달게 되고, 그 방어가 실제로는
|
|
53
|
+
* 일어나지 않는 경우를 대비하느라 "빈 인스턴스의 이벤트" 라는 없는 개념을 코드에 남긴다.
|
|
54
|
+
*/
|
|
55
|
+
@Column()
|
|
56
|
+
@Field({ description: 'Twin runtime instance id that emitted the event.' })
|
|
57
|
+
instanceId: string
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
@
|
|
58
|
-
|
|
59
|
+
/* 커널 봉투의 필수 필드(`CanonicalEnvelope.eventType`)라 빌 수 없다 — 계약을 그대로 옮긴다. */
|
|
60
|
+
@Column()
|
|
61
|
+
@Field({ description: 'Event type: epcis.* | task.status | equipment.status | order.status.' })
|
|
62
|
+
eventType: string
|
|
59
63
|
|
|
60
64
|
@Column({ type: 'int', nullable: true })
|
|
61
65
|
@Field(type => Int, { nullable: true, description: 'Monotonic state revision at which the event was emitted.' })
|
|
@@ -66,7 +70,7 @@ export class TwinEvent {
|
|
|
66
70
|
*
|
|
67
71
|
* 이것이 없던 시절에는 구조가 바뀌면 저널을 통째로 지우는 것이 유일한 길이었다(안 지우면 옛
|
|
68
72
|
* 이벤트를 새 공장에 대고 접어 이력이 거짓말을 한다). 이 한 칸이 셋째 길을 연다 — 재생이
|
|
69
|
-
* 구조가 바뀌는 지점에서
|
|
73
|
+
* 구조가 바뀌는 지점에서 전환한 뒤 이어 접는다.
|
|
70
74
|
*
|
|
71
75
|
* **비어 있을 수 있다**: 컬럼이 생기기 전에 쓰인 행이다. 그 행들은 **가장 오래된 리비전**에
|
|
72
76
|
* 속한다(0 을 채워 넣지 않는다 — 0 은 "모름" 이 아니라 유효한 번호처럼 보인다).
|
|
@@ -75,9 +79,18 @@ export class TwinEvent {
|
|
|
75
79
|
@Field(type => Int, { nullable: true, description: 'Structure revision (TwinStructure.rev) in force when this event happened. Absent on rows written before structure revisions existed; those belong to the oldest revision.' })
|
|
76
80
|
structureRev?: number
|
|
77
81
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
/*
|
|
83
|
+
* 이 사실이 일어난 시각(트윈의 시계). **날짜 타입이다.**
|
|
84
|
+
*
|
|
85
|
+
* 예전에는 문자열이었다. 읽을 때마다 `Date.parse` 했고, 정렬은 UTC ISO 만 들어온다는 전제 위에서
|
|
86
|
+
* 사전식 비교가 우연히 맞았다 — `+09:00` 오프셋 표기가 하나 섞이면 조용히 순서가 틀린다.
|
|
87
|
+
* 그 전제는 컬럼 어디에도 적혀 있지 않았다.
|
|
88
|
+
*
|
|
89
|
+
* `datetime` 을 쓴다. `timestamp` 는 sqlite 가 지원하지 않아 5개 드라이버 공통이 아니다.
|
|
90
|
+
*/
|
|
91
|
+
@Column({ type: 'datetime', nullable: true })
|
|
92
|
+
@Field({ nullable: true, description: 'Time at which the fact happened on the twin clock.' })
|
|
93
|
+
eventTime?: Date
|
|
81
94
|
|
|
82
95
|
/*
|
|
83
96
|
* ── 승격된 검색 축 ────────────────────────────────────────────────────────
|
|
@@ -86,7 +99,31 @@ export class TwinEvent {
|
|
|
86
99
|
* simple-json(TEXT) 안의 값은 5개 DB 드라이버 공통으로 거를 방법이 없어서(멀티DB 호환 규칙상
|
|
87
100
|
* DB별 JSON 연산자 금지) 승격 외의 선택지가 없다.
|
|
88
101
|
* 길이 상한 255 는 GS1 식별자 규격 대비 충분하며, 넘치는 값은 조용히 잘리지 않고 경고를 남긴다.
|
|
102
|
+
*
|
|
103
|
+
* ── 축을 새로 추가할 때 ──────────────────────────────────────────────────
|
|
104
|
+
* 새 승격 컬럼을 만들면 **이미 쌓인 행은 비어 있다.** 그 행들은 새 축으로 검색되지 않고,
|
|
105
|
+
* 사용자에게는 "과거가 없는 것" 으로 보인다(조용히 빠진 데이터).
|
|
106
|
+
*
|
|
107
|
+
* 예전에는 기동 훅이 매번 테이블을 살펴 채웠다. 그건 두 가지로 틀렸다 — 저널이 커질수록 부팅이
|
|
108
|
+
* 느려지고, "bizStep 이 비었으면 옛 행" 이라는 전제를 코드에 고정해 그 전제가 깨지는 순간
|
|
109
|
+
* (감사 행처럼 업무 단계가 없는 행) 매 부팅마다 헛돌았다. 그래서 걷어냈다(git: backfill-keys.ts).
|
|
110
|
+
*
|
|
111
|
+
* 채우는 일은 **마이그레이션 한 번**이 맞다(`yarn migration`). 그 컬럼에 맞는 것을 그때 쓴다 —
|
|
112
|
+
* 범용 스캐너를 되살리지 말 것. 개발 데이타라면 그냥 버리는 것도 답이다.
|
|
113
|
+
*/
|
|
114
|
+
/**
|
|
115
|
+
* 이 사실을 일으킨 **지시** — 커널이 커맨드에서 이어 준 상관값(기본은 commandId).
|
|
116
|
+
*
|
|
117
|
+
* 이것이 감사 기록(TwinAuditEvent.commandId)과 저널을 잇는 유일한 다리다. 없으면 승인 기록은
|
|
118
|
+
* "사람이 허락했다" 까지이고, 그 뒤 공장이 실제로 어떻게 움직였는지와 이어지지 않는다.
|
|
119
|
+
*
|
|
120
|
+
* **대부분의 행은 비어 있다** — 커맨드가 아니라 시뮬 시간이 낳은 사실이기 때문이다. 그것이 정상이고,
|
|
121
|
+
* 비어 있다고 "덜 채워진 옛 행" 으로 보면 안 된다(같은 오해를 bizStep 에서 이미 한 번 했다).
|
|
89
122
|
*/
|
|
123
|
+
@Column({ length: 255, nullable: true })
|
|
124
|
+
@Field({ nullable: true, description: 'Id of the command that caused this fact (kernel correlation id; defaults to the commandId). Empty for facts that simulation time produced rather than a command.' })
|
|
125
|
+
correlationId?: string
|
|
126
|
+
|
|
90
127
|
@Column({ length: 255, nullable: true })
|
|
91
128
|
@Field({ nullable: true, description: 'Business step (CBV bizStep tail), promoted from the payload for indexed filtering.' })
|
|
92
129
|
bizStep?: string
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 예측 지표 — **스냅샷에서 수 하나를 뽑는 규칙.**
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 순수 모듈인가 (2026-08-15) ───────────────────────────────────────────
|
|
5
|
+
* 이 표가 리졸버 안에 있어서 시험이 불러올 수 없었다(type-graphql·shell 을 물고 있다). 그런데 여기서
|
|
6
|
+
* 틀리면 예측 그래프 전체가 조용히 다른 것을 그린다 — 오류 없이. 규칙은 시험이 닿는 자리에 둔다.
|
|
7
|
+
*
|
|
8
|
+
* ── 에너지 지표를 더한 이유 ─────────────────────────────────────────────────
|
|
9
|
+
* 예측 화면의 지표가 물류 여섯(재고·점유·작업·오더·출고·백로그)으로 고정돼 있어서, 에너지 트윈을
|
|
10
|
+
* 고르면 **남의 질문에 답하는 그래프**가 떴다. 에너지 트윈에는 재고도 오더도 없으므로 그 값들은
|
|
11
|
+
* 전부 0 이고, 0 은 「없다」가 아니라 「이 트윈의 질문이 아니다」이다.
|
|
12
|
+
*
|
|
13
|
+
* 여기 더한 셋은 **커널이 이미 상태로 들고 있는 것**이다 — 새 계산을 만들지 않았다. 특히 부하 합은
|
|
14
|
+
* 커널이 뿌리 계량기로만 세는데(계층 계량의 이중 계상 방지), 그 규칙을 여기서 다시 쓰면 두 벌이 된다.
|
|
15
|
+
* 그래서 지점을 직접 더하지 않고 **구간 값**을 읽는다.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 스냅샷 → 수 하나. 값이 없으면 0 이 아니라 **그 지표를 그릴 수 없다**는 뜻이지만, 그래프 계약이
|
|
19
|
+
* 수를 요구하므로 0 을 낸다 — 그래서 에너지 지표는 「없음」이 0 과 헷갈리지 않는 것만 골랐다. */
|
|
20
|
+
export type MetricFn = (s: any) => number
|
|
21
|
+
|
|
22
|
+
export const FORECAST_METRICS: Record<string, MetricFn> = {
|
|
23
|
+
items: s => (s?.items || []).length,
|
|
24
|
+
occupancy: s => (s?.locations || []).reduce((a: number, n: any) => a + (n.occupancy || 0), 0),
|
|
25
|
+
tasks: s => (s?.tasks || []).length,
|
|
26
|
+
orders: s => (s?.orders || []).length,
|
|
27
|
+
// 이행 지표 — 라이브 예측의 핵심 질문("언제 다 나가나·백로그 언제 풀리나").
|
|
28
|
+
shipped: s => (s?.orders || []).filter((o: any) => o.status === 'shipped').length,
|
|
29
|
+
backlog: s => (s?.orders || []).filter((o: any) => o.status !== 'shipped' && o.status !== 'cancelled').length,
|
|
30
|
+
|
|
31
|
+
/*
|
|
32
|
+
* ── 에너지 ────────────────────────────────────────────────────────────────
|
|
33
|
+
* `peakKW` 는 **마감된 구간의 최대**다 — 단조 증가라 지평선 위에서 「최대수요가 어디까지 오르나」로
|
|
34
|
+
* 읽힌다. 열린 구간은 넣지 않는다(아직 확정이 아니다 — 커널이 지키는 규율을 여기서도 지킨다).
|
|
35
|
+
*/
|
|
36
|
+
peakKW: s => Number(s?.energy?.peakSince?.kW) || 0,
|
|
37
|
+
/*
|
|
38
|
+
* 이번 구간이 이대로 가면 얼마로 마감되나 — 커널이 낸 투영(`mean-so-far`)을 그대로 읽는다.
|
|
39
|
+
* 우리가 다시 계산하지 않는다: 투영 규칙이 두 벌이 되면 화면과 판정이 다른 말을 한다.
|
|
40
|
+
*/
|
|
41
|
+
projectedKW: s => Number(s?.energy?.open?.projectedKW ?? s?.energy?.open?.meanKW) || 0,
|
|
42
|
+
/*
|
|
43
|
+
* 계약을 넘긴 구간 수 — 「몇 번 넘나」는 요금이 걸린 질문이라 최대치와 따로 본다.
|
|
44
|
+
* 계약을 모르는 트윈에서는 늘 0 이다(넘김을 판정할 근거가 없다 — 지어내지 않는다).
|
|
45
|
+
*/
|
|
46
|
+
overWindows: s => (s?.energy?.closed || []).filter((w: any) => w?.overContract === true).length
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 이 지표가 그 종류의 트윈에서 뜻이 있나 — 화면이 목록을 고를 때 쓴다. */
|
|
50
|
+
export const ENERGY_METRICS = ['projectedKW', 'peakKW', 'overWindows'] as const
|
|
51
|
+
export const FLOW_METRICS = ['items', 'occupancy', 'tasks', 'orders', 'shipped', 'backlog'] as const
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 트윈 종류에 맞는 지표 목록 — **한 곳에서 정한다.**
|
|
55
|
+
*
|
|
56
|
+
* 화면이 각자 목록을 들고 있으면 종류가 늘 때 어느 화면이 뒤처졌는지 알 수 없다.
|
|
57
|
+
*/
|
|
58
|
+
export function metricsForKind(kind?: string): string[] {
|
|
59
|
+
return kind === 'ems' ? [...ENERGY_METRICS] : [...FLOW_METRICS]
|
|
60
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 예측을 얼마나 잘게 굴릴 것인가 — **한 회차의 틱 수를 묶는다.**
|
|
3
|
+
*
|
|
4
|
+
* ── 왜 필요한가 (2026-08-17 실측) ───────────────────────────────────────────
|
|
5
|
+
* 예측 비용의 거의 전부가 틱이다. 트윈 하나의 예측 한 번이 30회차 × 300틱 = 9,000틱, 한 틱 3.2ms →
|
|
6
|
+
* 28.5초였다. 그런데 몬테카를로는 지평선 **끝에서** 지표를 한 번 읽는다 — 중간을 1초 단위로 볼 이유가
|
|
7
|
+
* 없다면 성글게 굴려도 답이 같다.
|
|
8
|
+
*
|
|
9
|
+
* 정말 같은지는 재서 확인했다(`operato-twin/test/fork-cost.test.ts`, 10회차·지평선 5분):
|
|
10
|
+
*
|
|
11
|
+
* step 1s → 3,048ms · 재고 p50 325 · 완료작업 8 · 완료오더 0
|
|
12
|
+
* step 5s → 628ms · 재고 p50 325 · 완료작업 8 · 완료오더 0 ← 답이 같고 5배 싸다
|
|
13
|
+
* step 10s → 286ms · 재고 p50 326 · … ← 여기서부터 흔들린다
|
|
14
|
+
*
|
|
15
|
+
* ── 왜 고정 step 이 아니라 틱 수 상한인가 ───────────────────────────────────
|
|
16
|
+
* step 을 5초로 못 박으면 1분짜리 지평선은 12틱이 되어 너무 성글고, 2시간짜리는 1,440틱이라 여전히
|
|
17
|
+
* 비싸다. 묶어야 하는 것은 step 이 아니라 **한 회차가 도는 횟수**다.
|
|
18
|
+
*
|
|
19
|
+
* 상한(10초)을 따로 두는 이유: 지평선이 길다고 무한정 성글게 굴리면 짧은 작업(초 단위 피킹)이 통째로
|
|
20
|
+
* 한 틱에 삼켜진다. 그 지점을 넘지 않는다.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** 한 회차가 도는 틱 수의 목표 — 이보다 잘게 굴려야 할 이유가 생기면 여기를 고친다. */
|
|
24
|
+
export const TARGET_TICKS_PER_RUN = 60
|
|
25
|
+
/** 아무리 지평선이 길어도 이보다 성글게 굴리지 않는다 — 짧은 작업이 한 틱에 삼켜진다. */
|
|
26
|
+
export const MAX_STEP_MS = 10_000
|
|
27
|
+
/** 커널의 시각 단위 — 이보다 잘게 굴릴 이유는 없다. */
|
|
28
|
+
export const MIN_STEP_MS = 1_000
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 이 지평선을 굴릴 틱 크기.
|
|
32
|
+
*
|
|
33
|
+
* 초 단위로 떨어뜨린다 — 3,333ms 같은 값은 사람이 읽을 수도, 화면이 설명할 수도 없다.
|
|
34
|
+
*/
|
|
35
|
+
export function forecastStepMs(horizonMs: number): number {
|
|
36
|
+
if (!Number.isFinite(horizonMs) || horizonMs <= 0) return MIN_STEP_MS
|
|
37
|
+
const raw = horizonMs / TARGET_TICKS_PER_RUN
|
|
38
|
+
const rounded = Math.round(raw / MIN_STEP_MS) * MIN_STEP_MS
|
|
39
|
+
return Math.min(MAX_STEP_MS, Math.max(MIN_STEP_MS, rounded))
|
|
40
|
+
}
|
|
@@ -23,19 +23,58 @@ export interface GapPoint {
|
|
|
23
23
|
modelAbsError: number
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
export
|
|
26
|
+
/** 합산 결과 + **무엇을 못 넣었는지.** 조용히 줄이지 않는다. */
|
|
27
|
+
export interface AggregateResult {
|
|
28
|
+
points: GapPoint[]
|
|
29
|
+
/** 한 트윈이라도 재지 못해 통째로 뺀 vantage 수. */
|
|
30
|
+
droppedVantages: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 공동배치(co-located) 트윈들의 점 배열을 공장 단위 한 시계열로 합산.
|
|
35
|
+
*
|
|
36
|
+
* ── 예전에 무엇이 어긋나 있었나 (2026-08-08) ─────────────────────────────────
|
|
37
|
+
* "동일 부팅 → vantage 정렬" 을 **가정**하고 배열 인덱스끼리 더했다. 그 가정은 세 곳에서 깨졌다:
|
|
38
|
+
*
|
|
39
|
+
* ① 격자를 트윈마다 자기 저널의 첫·마지막 이벤트로 만들었다. 저널 구간이 다르면 같은 인덱스가
|
|
40
|
+
* 서로 다른 시각이다. **부팅이 같아도** 어긋난다.
|
|
41
|
+
* ② 한 vantage 를 못 재면 `continue` 로 건너뛰고 밀어 담았다. 그 순간부터 인덱스가 한 칸씩 밀린다.
|
|
42
|
+
* ③ 길이를 `max` 로 잡아, 뒤쪽 인덱스는 남은 트윈만 더해졌다 — 공장 합계 곡선이 꼬리에서
|
|
43
|
+
* 아래로 꺾인다. 공장과 아무 상관 없는 이유로.
|
|
44
|
+
*
|
|
45
|
+
* 셋 다 오류를 내지 않았다. 그럴듯한 곡선이 나오고, 그 곡선으로 예측 정확도를 판정했다.
|
|
46
|
+
*
|
|
47
|
+
* ── 지금 ────────────────────────────────────────────────────────────────────
|
|
48
|
+
* 격자는 밖에서 하나를 만들어 모두에게 같은 것을 주고(`sharedGrid`), 못 잰 자리는 `null` 로
|
|
49
|
+
* 남긴다. 여기서는 **모든 트윈이 값을 가진 vantage 만** 더한다. 하나라도 비면 그 시각의
|
|
50
|
+
* 공장 총량은 알 수 없는 것이지, 그 트윈이 0 을 낸 것이 아니다.
|
|
51
|
+
*
|
|
52
|
+
* 뺀 개수는 돌려준다 — 화면이 "24점 중 18점" 이라고 말할 수 있어야 한다.
|
|
53
|
+
*/
|
|
54
|
+
export function aggregatePoints(per: (GapPoint | null)[][]): AggregateResult {
|
|
55
|
+
if (!per.length) return { points: [], droppedVantages: 0 }
|
|
28
56
|
const n = Math.max(0, ...per.map(pts => pts.length))
|
|
29
57
|
const out: GapPoint[] = []
|
|
58
|
+
let droppedVantages = 0
|
|
30
59
|
for (let i = 0; i < n; i++) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
60
|
+
const slot = per.map(pts => pts[i])
|
|
61
|
+
if (slot.some(pt => !pt)) { droppedVantages++; continue }
|
|
62
|
+
const pts = slot as GapPoint[]
|
|
63
|
+
const sum = (f: (p: GapPoint) => number) => pts.reduce((a, p) => a + f(p), 0)
|
|
64
|
+
const p50 = sum(p => p.p50)
|
|
65
|
+
const min = sum(p => p.min)
|
|
66
|
+
const max = sum(p => p.max)
|
|
67
|
+
const actual = sum(p => p.actual)
|
|
68
|
+
out.push({
|
|
69
|
+
t: pts[0].t, // 격자가 하나이므로 어느 트윈에서 읽어도 같다.
|
|
70
|
+
p50, min, max, actual,
|
|
71
|
+
error: actual - p50,
|
|
72
|
+
inBand: actual >= min && actual <= max,
|
|
73
|
+
naiveAbsError: sum(p => p.naiveAbsError),
|
|
74
|
+
modelAbsError: sum(p => p.modelAbsError)
|
|
75
|
+
})
|
|
37
76
|
}
|
|
38
|
-
return out
|
|
77
|
+
return { points: out, droppedVantages }
|
|
39
78
|
}
|
|
40
79
|
|
|
41
80
|
/** 안정성 축 + 편향 축 판정(관리도). errors = 실제−예측 시계열. */
|