@things-factory/headless-twin 10.0.11 → 10.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-server/engine/canonical-ingest.d.ts +81 -3
- package/dist-server/engine/canonical-ingest.js +89 -10
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/command-routing.js +1 -1
- package/dist-server/engine/command-routing.js.map +1 -1
- package/dist-server/engine/declared-stimulus.d.ts +43 -0
- package/dist-server/engine/declared-stimulus.js +57 -0
- package/dist-server/engine/declared-stimulus.js.map +1 -0
- package/dist-server/engine/index.d.ts +5 -0
- package/dist-server/engine/index.js +6 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +335 -0
- package/dist-server/engine/ingest-health.js +434 -0
- package/dist-server/engine/ingest-health.js.map +1 -0
- package/dist-server/engine/integration-coverage.d.ts +76 -0
- package/dist-server/engine/integration-coverage.js +73 -0
- package/dist-server/engine/integration-coverage.js.map +1 -0
- package/dist-server/engine/integration-probes.d.ts +65 -0
- package/dist-server/engine/integration-probes.js +100 -0
- package/dist-server/engine/integration-probes.js.map +1 -0
- package/dist-server/engine/integration-runner.d.ts +45 -0
- package/dist-server/engine/integration-runner.js +59 -0
- package/dist-server/engine/integration-runner.js.map +1 -0
- package/dist-server/engine/integration-target-profile.d.ts +57 -0
- package/dist-server/engine/integration-target-profile.js +79 -0
- package/dist-server/engine/integration-target-profile.js.map +1 -0
- package/dist-server/engine/kpi-fold.d.ts +39 -0
- package/dist-server/engine/kpi-fold.js +68 -5
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.d.ts +3 -3
- package/dist-server/engine/kpi-query.js +132 -18
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/live-feed-registry.d.ts +1 -1
- package/dist-server/engine/live-feed-registry.js +3 -17
- package/dist-server/engine/live-feed-registry.js.map +1 -1
- package/dist-server/engine/load-meter.d.ts +1 -1
- package/dist-server/engine/load-meter.js +12 -3
- package/dist-server/engine/load-meter.js.map +1 -1
- package/dist-server/engine/local-declarations.d.ts +3 -6
- package/dist-server/engine/local-declarations.js +97 -16
- package/dist-server/engine/local-declarations.js.map +1 -1
- package/dist-server/engine/log.d.ts +18 -0
- package/dist-server/engine/log.js +80 -0
- package/dist-server/engine/log.js.map +1 -0
- package/dist-server/engine/loop-lag.d.ts +54 -0
- package/dist-server/engine/loop-lag.js +87 -0
- package/dist-server/engine/loop-lag.js.map +1 -0
- package/dist-server/engine/measured-yield.d.ts +42 -0
- package/dist-server/engine/measured-yield.js +75 -0
- package/dist-server/engine/measured-yield.js.map +1 -0
- package/dist-server/engine/model-gap.d.ts +108 -0
- package/dist-server/engine/model-gap.js +95 -0
- package/dist-server/engine/model-gap.js.map +1 -0
- package/dist-server/engine/operation-basis.d.ts +16 -0
- package/dist-server/engine/operation-basis.js +20 -2
- package/dist-server/engine/operation-basis.js.map +1 -1
- package/dist-server/engine/property-effects.js +17 -0
- package/dist-server/engine/property-effects.js.map +1 -1
- package/dist-server/engine/restart-policy.d.ts +13 -0
- package/dist-server/engine/restart-policy.js +52 -0
- package/dist-server/engine/restart-policy.js.map +1 -0
- package/dist-server/engine/runtime-key.js +1 -1
- package/dist-server/engine/runtime-key.js.map +1 -1
- package/dist-server/engine/spec-coverage.d.ts +8 -0
- package/dist-server/engine/spec-coverage.js +3 -1
- package/dist-server/engine/spec-coverage.js.map +1 -1
- package/dist-server/engine/stage-path.d.ts +83 -0
- package/dist-server/engine/stage-path.js +118 -0
- package/dist-server/engine/stage-path.js.map +1 -0
- package/dist-server/engine/twin-engine.d.ts +400 -31
- package/dist-server/engine/twin-engine.js +1198 -187
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +35 -3
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.d.ts +5 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js +58 -0
- package/dist-server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.js.map +1 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.d.ts +5 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js +55 -0
- package/dist-server/migrations/1786200000000-CarryLiveFeedCursor.js.map +1 -0
- package/dist-server/migrations/index.js +7 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +18 -7
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/control-routing.d.ts +14 -0
- package/dist-server/service/reference/control-routing.js +64 -0
- package/dist-server/service/reference/control-routing.js.map +1 -0
- package/dist-server/service/reference/discovery-result.d.ts +1 -1
- package/dist-server/service/reference/discovery-result.js +1 -1
- package/dist-server/service/reference/discovery-result.js.map +1 -1
- package/dist-server/service/reference/index.d.ts +5 -1
- package/dist-server/service/reference/index.js +8 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/reference-adapter.d.ts +251 -3
- package/dist-server/service/reference/reference-adapter.js +43 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-assessment.d.ts +68 -0
- package/dist-server/service/reference/reference-assessment.js +136 -0
- package/dist-server/service/reference/reference-assessment.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +12 -1
- package/dist-server/service/reference/reference-live.js +119 -14
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +47 -1
- package/dist-server/service/reference/reference-master.js +34 -4
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-probe.d.ts +92 -0
- package/dist-server/service/reference/reference-probe.js +186 -0
- package/dist-server/service/reference/reference-probe.js.map +1 -0
- package/dist-server/service/reference/reference-progress-subscription.d.ts +17 -0
- package/dist-server/service/reference/reference-progress-subscription.js +94 -0
- package/dist-server/service/reference/reference-progress-subscription.js.map +1 -0
- package/dist-server/service/reference/reference-progress.d.ts +38 -0
- package/dist-server/service/reference/reference-progress.js +71 -0
- package/dist-server/service/reference/reference-progress.js.map +1 -0
- package/dist-server/service/reference/reference-resolver.d.ts +3 -3
- package/dist-server/service/reference/reference-resolver.js +242 -58
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/reference/twin-reference.d.ts +32 -0
- package/dist-server/service/reference/twin-reference.js +10 -0
- package/dist-server/service/reference/twin-reference.js.map +1 -1
- package/dist-server/service/twin-audit/command-audit.d.ts +34 -0
- package/dist-server/service/twin-audit/command-audit.js +15 -1
- package/dist-server/service/twin-audit/command-audit.js.map +1 -1
- package/dist-server/service/twin-audit/twin-audit-event.d.ts +3 -0
- package/dist-server/service/twin-audit/twin-audit-event.js +10 -0
- package/dist-server/service/twin-audit/twin-audit-event.js.map +1 -1
- package/dist-server/service/twin-control/twin-control-mutation.d.ts +15 -2
- package/dist-server/service/twin-control/twin-control-mutation.js +88 -36
- package/dist-server/service/twin-control/twin-control-mutation.js.map +1 -1
- package/dist-server/service/twin-event/journal-count.d.ts +21 -0
- package/dist-server/service/twin-event/journal-count.js +26 -0
- package/dist-server/service/twin-event/journal-count.js.map +1 -0
- package/dist-server/service/twin-event/twin-event-keys.d.ts +9 -0
- package/dist-server/service/twin-event/twin-event-keys.js +15 -18
- package/dist-server/service/twin-event/twin-event-keys.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-type.d.ts +3 -1
- package/dist-server/service/twin-event/twin-event-type.js +18 -1
- package/dist-server/service/twin-event/twin-event-type.js.map +1 -1
- package/dist-server/service/twin-event/twin-event.d.ts +1 -0
- package/dist-server/service/twin-event/twin-event.js +9 -1
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js +2 -1
- package/dist-server/service/twin-forecast/twin-forecast-query.js.map +1 -1
- package/dist-server/service/twin-ingest-window/index.d.ts +5 -0
- package/dist-server/service/twin-ingest-window/index.js +10 -0
- package/dist-server/service/twin-ingest-window/index.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.d.ts +12 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js +123 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-query.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.d.ts +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js +51 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window-writer.js.map +1 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.d.ts +16 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js +108 -0
- package/dist-server/service/twin-ingest-window/twin-ingest-window.js.map +1 -0
- package/dist-server/service/twin-instance/twin-instance.d.ts +1 -1
- package/dist-server/service/twin-instance/twin-instance.js +2 -2
- package/dist-server/service/twin-instance/twin-instance.js.map +1 -1
- package/dist-server/service/twin-journal/twin-journal-query.js +58 -5
- package/dist-server/service/twin-journal/twin-journal-query.js.map +1 -1
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.d.ts +14 -0
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js +74 -3
- package/dist-server/service/twin-lifecycle/twin-lifecycle-mutation.js.map +1 -1
- package/dist-server/service/twin-metrics/twin-metrics-query.d.ts +1 -0
- package/dist-server/service/twin-metrics/twin-metrics-query.js +78 -3
- package/dist-server/service/twin-metrics/twin-metrics-query.js.map +1 -1
- package/dist-server/service/twin-model/axis-journal-evidence.d.ts +2 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js +89 -0
- package/dist-server/service/twin-model/axis-journal-evidence.js.map +1 -0
- package/dist-server/service/twin-model/epcis-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/epcis-coverage.js +19 -7
- package/dist-server/service/twin-model/epcis-coverage.js.map +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.d.ts +1 -1
- package/dist-server/service/twin-model/iec61850-coverage.js +18 -7
- package/dist-server/service/twin-model/iec61850-coverage.js.map +1 -1
- package/dist-server/service/twin-model/isa95-coverage.d.ts +14 -0
- package/dist-server/service/twin-model/isa95-coverage.js +23 -9
- package/dist-server/service/twin-model/isa95-coverage.js.map +1 -1
- package/dist-server/service/twin-model/item-ref.d.ts +24 -0
- package/dist-server/service/twin-model/item-ref.js +88 -0
- package/dist-server/service/twin-model/item-ref.js.map +1 -0
- package/dist-server/service/twin-model/name-index.d.ts +36 -0
- package/dist-server/service/twin-model/name-index.js +116 -0
- package/dist-server/service/twin-model/name-index.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +1 -1
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/status-tally.d.ts +9 -0
- package/dist-server/service/twin-model/status-tally.js +37 -0
- package/dist-server/service/twin-model/status-tally.js.map +1 -0
- package/dist-server/service/twin-model/twin-lineage-query.js +40 -12
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +38 -39
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-query.js +157 -5
- package/dist-server/service/twin-model/twin-model-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js +7 -0
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-readiness/index.d.ts +2 -0
- package/dist-server/service/twin-readiness/index.js +6 -0
- package/dist-server/service/twin-readiness/index.js.map +1 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.d.ts +3 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js +103 -0
- package/dist-server/service/twin-readiness/twin-readiness-query.js.map +1 -0
- package/dist-server/service/twin-space/twin-space-resolver.d.ts +2 -2
- package/dist-server/service/twin-space/twin-space-resolver.js +5 -4
- package/dist-server/service/twin-space/twin-space-resolver.js.map +1 -1
- package/dist-shared/entity-delta.d.ts +23 -3
- package/dist-shared/entity-delta.js +12 -8
- package/dist-shared/entity-delta.js.map +1 -1
- package/dist-shared/kpi-broadcast.js +1 -1
- package/dist-shared/kpi-broadcast.js.map +1 -1
- package/dist-shared/touched-items.d.ts +7 -0
- package/dist-shared/touched-items.js +80 -0
- package/dist-shared/touched-items.js.map +1 -0
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +164 -15
- package/server/engine/command-routing.ts +1 -1
- package/server/engine/declared-stimulus.ts +66 -0
- package/server/engine/index.ts +6 -0
- package/server/engine/ingest-health.ts +704 -0
- package/server/engine/integration-coverage.ts +147 -0
- package/server/engine/integration-probes.ts +144 -0
- package/server/engine/integration-runner.ts +95 -0
- package/server/engine/integration-target-profile.ts +103 -0
- package/server/engine/kpi-fold.ts +101 -5
- package/server/engine/kpi-query.ts +137 -23
- package/server/engine/live-feed-registry.ts +4 -3
- package/server/engine/load-meter.ts +12 -3
- package/server/engine/local-declarations.ts +97 -19
- package/server/engine/log.ts +72 -0
- package/server/engine/loop-lag.ts +120 -0
- package/server/engine/measured-yield.ts +89 -0
- package/server/engine/model-gap.ts +168 -0
- package/server/engine/operation-basis.ts +33 -2
- package/server/engine/property-effects.ts +17 -0
- package/server/engine/restart-policy.ts +55 -0
- package/server/engine/runtime-key.ts +1 -1
- package/server/engine/spec-coverage.ts +23 -3
- package/server/engine/stage-path.ts +172 -0
- package/server/engine/twin-engine.ts +1318 -186
- package/server/index.ts +36 -3
- package/server/migrations/1786100000000-PromoteEventActionAndSyncWarnings.ts +54 -0
- package/server/migrations/1786200000000-CarryLiveFeedCursor.ts +53 -0
- package/server/migrations/index.ts +7 -1
- package/server/service/index.ts +11 -0
- package/server/service/reference/control-routing.ts +62 -0
- package/server/service/reference/discovery-result.ts +1 -1
- package/server/service/reference/index.ts +7 -1
- package/server/service/reference/reference-adapter.ts +275 -5
- package/server/service/reference/reference-assessment.ts +215 -0
- package/server/service/reference/reference-live.ts +126 -14
- package/server/service/reference/reference-master.ts +64 -4
- package/server/service/reference/reference-probe.ts +264 -0
- package/server/service/reference/reference-progress-subscription.ts +73 -0
- package/server/service/reference/reference-progress.ts +95 -0
- package/server/service/reference/reference-resolver.ts +246 -19
- package/server/service/reference/twin-reference.ts +39 -1
- package/server/service/twin-audit/command-audit.ts +34 -1
- package/server/service/twin-audit/twin-audit-event.ts +20 -0
- package/server/service/twin-control/twin-control-mutation.ts +78 -30
- package/server/service/twin-event/journal-count.ts +53 -0
- package/server/service/twin-event/twin-event-keys.ts +16 -1
- package/server/service/twin-event/twin-event-type.ts +26 -2
- package/server/service/twin-event/twin-event.ts +7 -0
- package/server/service/twin-forecast/twin-forecast-query.ts +2 -1
- package/server/service/twin-ingest-window/index.ts +7 -0
- package/server/service/twin-ingest-window/twin-ingest-window-query.ts +125 -0
- package/server/service/twin-ingest-window/twin-ingest-window-writer.ts +57 -0
- package/server/service/twin-ingest-window/twin-ingest-window.ts +113 -0
- package/server/service/twin-instance/twin-instance.ts +16 -10
- package/server/service/twin-journal/twin-journal-query.ts +59 -5
- package/server/service/twin-lifecycle/twin-lifecycle-mutation.ts +79 -4
- package/server/service/twin-metrics/twin-metrics-query.ts +81 -3
- package/server/service/twin-model/axis-journal-evidence.ts +57 -0
- package/server/service/twin-model/epcis-coverage.ts +2 -8
- package/server/service/twin-model/iec61850-coverage.ts +2 -8
- package/server/service/twin-model/isa95-coverage.ts +22 -9
- package/server/service/twin-model/item-ref.ts +80 -0
- package/server/service/twin-model/name-index.ts +94 -0
- package/server/service/twin-model/project-structure.ts +1 -1
- package/server/service/twin-model/status-tally.ts +37 -0
- package/server/service/twin-model/twin-lineage-query.ts +38 -9
- package/server/service/twin-model/twin-model-item-query.ts +26 -27
- package/server/service/twin-model/twin-model-query.ts +156 -5
- package/server/service/twin-model/twin-model-tree-query.ts +7 -0
- package/server/service/twin-readiness/index.ts +3 -0
- package/server/service/twin-readiness/twin-readiness-query.ts +96 -0
- package/server/service/twin-space/twin-space-resolver.ts +5 -4
- package/shared/entity-delta.ts +31 -7
- package/shared/kpi-broadcast.ts +1 -1
- package/shared/touched-items.ts +73 -0
- package/test/adopt-structure-live.test.ts +7 -7
- package/test/axis-journal-evidence.test.ts +71 -0
- package/test/axis-read.test.ts +101 -1
- package/test/boot-resume.test.ts +93 -27
- package/test/broadcast-cost-baseline.test.ts +200 -0
- package/test/broadcast-period.test.ts +58 -0
- package/test/canonical-ingest-vocabularies.test.ts +36 -1
- package/test/canonical-quantity-door.test.ts +61 -0
- package/test/command-routing.test.ts +1 -1
- package/test/control-capability.test.ts +103 -0
- package/test/declared-location-types.test.ts +89 -0
- package/test/declared-stimulus.test.ts +88 -0
- package/test/discovery-result.test.ts +1 -1
- package/test/duration-estimators.test.ts +1 -1
- package/test/entity-delta.test.ts +2 -2
- package/test/ingest-bench.test.ts +3 -3
- package/test/ingest-health-engine.test.ts +248 -0
- package/test/ingest-health-wiring.test.ts +119 -0
- package/test/ingest-health.test.ts +306 -0
- package/test/ingest-history.test.ts +247 -0
- package/test/ingest-running-guard.test.ts +5 -5
- package/test/instance-cache-lifecycle.test.ts +1 -1
- package/test/integration-probes.test.ts +103 -0
- package/test/integration-runner.test.ts +95 -0
- package/test/item-ref.test.ts +78 -0
- package/test/journal-read-discipline.test.ts +177 -0
- package/test/journal-retention.test.ts +133 -0
- package/test/journal-sort-axis.test.ts +142 -0
- package/test/journal-write-door.test.ts +110 -0
- package/test/journal-write-trend.test.ts +115 -0
- package/test/kernel-kind-guard.test.ts +4 -4
- package/test/kpi-baseline-db.test.ts +1 -1
- package/test/kpi-fold.test.ts +90 -3
- package/test/kpi-query-bench.test.ts +3 -3
- package/test/lineage-survives-restart.test.ts +20 -4
- package/test/live-cursor-wiring.test.ts +87 -0
- package/test/live-feed-registry.test.ts +15 -7
- package/test/live-kernel-facts.test.ts +7 -7
- package/test/live-mirror-parity.test.ts +6 -0
- package/test/load-meter.test.ts +29 -15
- package/test/local-declarations.test.ts +109 -2
- package/test/log-stamp.test.ts +59 -0
- package/test/loop-lag.test.ts +82 -0
- package/test/measured-yield.test.ts +90 -0
- package/test/model-gap.test.ts +153 -0
- package/test/oee-accumulator.test.ts +4 -0
- package/test/operation-basis.test.ts +28 -1
- package/test/operational-vocabulary.test.ts +108 -0
- package/test/operations-capability-db.test.ts +4 -4
- package/test/project-structure-db.test.ts +1 -1
- package/test/projection-reaches-screen.test.ts +1 -1
- package/test/property-effects.test.ts +28 -0
- package/test/read-failure-visible.test.ts +145 -0
- package/test/reference-grounding.test.ts +70 -0
- package/test/resolve-ts-siblings.mjs +52 -0
- package/test/restart-policy.test.ts +111 -0
- package/test/resync-origin-site.test.ts +7 -1
- package/test/revision-axis.test.ts +93 -0
- package/test/runtime-key.test.ts +2 -2
- package/test/scale-twin-bench.test.ts +2 -2
- package/test/source-outcome-audit.test.ts +104 -0
- package/test/spec-coverage.test.ts +1 -1
- package/test/stage-path.test.ts +95 -0
- package/test/standard-coverage.test.ts +15 -3
- package/test/status-tally.test.ts +55 -0
- package/test/structure-revision-db.test.ts +31 -27
- package/test/tenant-registry-db.test.ts +3 -3
- package/test/time-range.test.ts +152 -0
- package/test/touched-items.test.ts +61 -0
- package/test/twin-event-keys.test.ts +10 -2
- package/test/twin-model-item-db.test.ts +5 -3
- package/test/twin-model-tree-db.test.ts +7 -7
- package/test/twin-origin-resync.test.ts +4 -4
- package/test/warm-start-seam.test.ts +4 -0
- package/test/yield-loop.test.ts +199 -0
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* 실측 양품률 추정기 — **이력에서 배운 수율이 선언값·상수를 이긴다.**
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 필요했나 (2026-08-19) ────────────────────────────────────────────────
|
|
6
|
+
* 소요시간에는 이력 층이 있었다(`measured-estimator`): 저널이 「그 현장에서 실제로 얼마 걸렸나」를 안다.
|
|
7
|
+
* 수율에는 그 층이 없었다 — 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목
|
|
8
|
+
* 신호까지 만들었다.** 초기 라인이든 성숙한 라인이든 같은 수로 판정된다는 뜻이다.
|
|
9
|
+
*
|
|
10
|
+
* 그런데 이력을 만들 재료도 없었다: 양품/불량은 설비 누적 카운터(종류를 모른다)와 EPCIS disposition
|
|
11
|
+
* (종류를 모른다)에만 남았다. 그래서 커널이 **판정을 일어난 자리(작업 완료)에 적게** 했고
|
|
12
|
+
* (`TaskStatusDelta.outcome`), 폴드가 축으로 들고(`TaskFacets.outcome`), KPI 가 종류별로 센다
|
|
13
|
+
* (`KpiGroup.quality`). 이 파일은 그 수를 비율로 바꿔 커널 시임에 넣는다.
|
|
14
|
+
*
|
|
15
|
+
* ── 정직 규율 (소요 추정기와 같은 것) ───────────────────────────────────────
|
|
16
|
+
* - **표본이 적으면 쓰지 않는다**(`minSamples`). 세 건 중 한 건 불량은 「불량률 33%」가 아니다.
|
|
17
|
+
* - `'unknown'` 축은 쓰지 않는다 — 종류를 모르는 기록을 특정 종류의 근거로 삼을 수 없다.
|
|
18
|
+
* - **판정이 없는 작업은 분모에 넣지 않는다.** KPI 가 이미 그렇게 세지만(`quality` 는 판정된 것만),
|
|
19
|
+
* 여기서도 `good + scrap` 만 쓴다 — `tasks` 를 분모로 쓰면 이동·체류가 섞여 양품률이 낮게 나온다.
|
|
20
|
+
* - **0 도 사실이다.** 전부 불량이면 0 을 배운다(그 현장이 그랬다). 「값이 없다」와 구별해 다룬다 —
|
|
21
|
+
* 커널이 `0` 을 받으면 전량 불량으로 굴러가고, 그것이 관측과 같은 결론이다.
|
|
22
|
+
* - 무엇을 배웠고 무엇을 표본 부족으로 버렸는지 함께 낸다(조용히 일부만 반영하지 않는다).
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.DEFAULT_YIELD_MIN_SAMPLES = void 0;
|
|
26
|
+
exports.buildYieldEstimator = buildYieldEstimator;
|
|
27
|
+
/**
|
|
28
|
+
* 기본 표본 하한 — 판정 20건 미만이면 그 종류는 배우지 않는다.
|
|
29
|
+
*
|
|
30
|
+
* 소요(5건)보다 높게 잡는 이유: 비율은 **드문 사건**을 재는 값이다. 불량률 5% 인 공정에서 20건을 봐도
|
|
31
|
+
* 불량은 한 건이고, 그 한 건이 있고 없음으로 비율이 0% 와 5% 사이를 오간다. 낮게 잡으면 추정기가
|
|
32
|
+
* 이력이라는 이름으로 잡음을 실어 보낸다.
|
|
33
|
+
*/
|
|
34
|
+
exports.DEFAULT_YIELD_MIN_SAMPLES = 20;
|
|
35
|
+
/**
|
|
36
|
+
* 작업 종류별 KPI 그룹에서 양품률 추정기를 만든다(순수 — DB·커널 모름).
|
|
37
|
+
* `groups` 는 `computeTwinKpi({ groupBy: 'taskKind' })` 의 `groups.items` 를 그대로 넘긴다.
|
|
38
|
+
*/
|
|
39
|
+
function buildYieldEstimator(groups, opts = {}) {
|
|
40
|
+
const minSamples = opts.minSamples ?? exports.DEFAULT_YIELD_MIN_SAMPLES;
|
|
41
|
+
const learned = {};
|
|
42
|
+
const samples = {};
|
|
43
|
+
const skipped = [];
|
|
44
|
+
for (const g of groups ?? []) {
|
|
45
|
+
if (!g?.key || g.key === 'unknown')
|
|
46
|
+
continue;
|
|
47
|
+
const good = g.quality?.good ?? 0;
|
|
48
|
+
const scrap = g.quality?.scrap ?? 0;
|
|
49
|
+
const judged = good + scrap;
|
|
50
|
+
/* 판정이 아예 없는 종류는 **건너뛴 것이 아니다** — 품질을 가리지 않는 공정(이동·체류)이 정상이다. */
|
|
51
|
+
if (judged === 0)
|
|
52
|
+
continue;
|
|
53
|
+
if (judged < minSamples) {
|
|
54
|
+
skipped.push({ kind: g.key, judged });
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
learned[g.key] = good / judged;
|
|
58
|
+
samples[g.key] = { good, scrap };
|
|
59
|
+
}
|
|
60
|
+
const kinds = Object.keys(learned);
|
|
61
|
+
if (!kinds.length)
|
|
62
|
+
return { learned, samples, skipped, minSamples };
|
|
63
|
+
return {
|
|
64
|
+
/*
|
|
65
|
+
* 시임은 **무상태**다(커널이 fork 할 때 참조를 공유한다 — 소요 추정기와 같은 규약).
|
|
66
|
+
* 배우지 않은 종류에는 `undefined` 를 답한다: 그러면 커널이 선언값 → 상수로 내려간다.
|
|
67
|
+
*/
|
|
68
|
+
estimator: { estimate: ({ kind }) => learned[kind] },
|
|
69
|
+
learned,
|
|
70
|
+
samples,
|
|
71
|
+
skipped,
|
|
72
|
+
minSamples
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=measured-yield.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"measured-yield.js","sourceRoot":"","sources":["../../server/engine/measured-yield.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AA6BH,kDAsCC;AAnDD;;;;;;GAMG;AACU,QAAA,yBAAyB,GAAG,EAAE,CAAA;AAE3C;;;GAGG;AACH,SAAgB,mBAAmB,CACjC,MAAgF,EAChF,OAAgC,EAAE;IAElC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,iCAAyB,CAAA;IAC/D,MAAM,OAAO,GAA2B,EAAE,CAAA;IAC1C,MAAM,OAAO,GAAoD,EAAE,CAAA;IACnE,MAAM,OAAO,GAAuC,EAAE,CAAA;IAEtD,KAAK,MAAM,CAAC,IAAI,MAAM,IAAI,EAAE,EAAE,CAAC;QAC7B,IAAI,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,CAAC,GAAG,KAAK,SAAS;YAAE,SAAQ;QAC5C,MAAM,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,IAAI,CAAC,CAAA;QACjC,MAAM,KAAK,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,IAAI,CAAC,CAAA;QACnC,MAAM,MAAM,GAAG,IAAI,GAAG,KAAK,CAAA;QAC3B,gEAAgE;QAChE,IAAI,MAAM,KAAK,CAAC;YAAE,SAAQ;QAC1B,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,CAAA;YACrC,SAAQ;QACV,CAAC;QACD,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,MAAM,CAAA;QAC9B,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;IAClC,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IAClC,IAAI,CAAC,KAAK,CAAC,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,CAAA;IAEnE,OAAO;QACL;;;WAGG;QACH,SAAS,EAAE,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;QACpD,OAAO;QACP,OAAO;QACP,OAAO;QACP,UAAU;KACX,CAAA;AACH,CAAC","sourcesContent":["/*\n * 실측 양품률 추정기 — **이력에서 배운 수율이 선언값·상수를 이긴다.**\n *\n * ── 왜 필요했나 (2026-08-19) ────────────────────────────────────────────────\n * 소요시간에는 이력 층이 있었다(`measured-estimator`): 저널이 「그 현장에서 실제로 얼마 걸렸나」를 안다.\n * 수율에는 그 층이 없었다 — 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목\n * 신호까지 만들었다.** 초기 라인이든 성숙한 라인이든 같은 수로 판정된다는 뜻이다.\n *\n * 그런데 이력을 만들 재료도 없었다: 양품/불량은 설비 누적 카운터(종류를 모른다)와 EPCIS disposition\n * (종류를 모른다)에만 남았다. 그래서 커널이 **판정을 일어난 자리(작업 완료)에 적게** 했고\n * (`TaskStatusDelta.outcome`), 폴드가 축으로 들고(`TaskFacets.outcome`), KPI 가 종류별로 센다\n * (`KpiGroup.quality`). 이 파일은 그 수를 비율로 바꿔 커널 시임에 넣는다.\n *\n * ── 정직 규율 (소요 추정기와 같은 것) ───────────────────────────────────────\n * - **표본이 적으면 쓰지 않는다**(`minSamples`). 세 건 중 한 건 불량은 「불량률 33%」가 아니다.\n * - `'unknown'` 축은 쓰지 않는다 — 종류를 모르는 기록을 특정 종류의 근거로 삼을 수 없다.\n * - **판정이 없는 작업은 분모에 넣지 않는다.** KPI 가 이미 그렇게 세지만(`quality` 는 판정된 것만),\n * 여기서도 `good + scrap` 만 쓴다 — `tasks` 를 분모로 쓰면 이동·체류가 섞여 양품률이 낮게 나온다.\n * - **0 도 사실이다.** 전부 불량이면 0 을 배운다(그 현장이 그랬다). 「값이 없다」와 구별해 다룬다 —\n * 커널이 `0` 을 받으면 전량 불량으로 굴러가고, 그것이 관측과 같은 결론이다.\n * - 무엇을 배웠고 무엇을 표본 부족으로 버렸는지 함께 낸다(조용히 일부만 반영하지 않는다).\n */\n\n/** 커널 `YieldEstimator` 와 같은 모양(커널 타입에 의존하지 않기 위해 구조로 맞춘다). */\nexport interface YieldEstimatorResult {\n estimator?: {\n estimate(ctx: { kind: string }): number | undefined\n }\n /** 작업 종류 → 실측 양품률(0..1). 추정기가 답하는 종류들. */\n learned: Record<string, number>\n /** 종류별 표본 — 배운 값의 근거 크기(화면·로그가 「몇 건에서 배웠나」를 말할 수 있게). */\n samples: Record<string, { good: number; scrap: number }>\n /** 표본이 모자라 쓰지 않은 종류와 판정 건수 — 배우지 못한 것을 밝힌다. */\n skipped: { kind: string; judged: number }[]\n minSamples: number\n}\n\n/**\n * 기본 표본 하한 — 판정 20건 미만이면 그 종류는 배우지 않는다.\n *\n * 소요(5건)보다 높게 잡는 이유: 비율은 **드문 사건**을 재는 값이다. 불량률 5% 인 공정에서 20건을 봐도\n * 불량은 한 건이고, 그 한 건이 있고 없음으로 비율이 0% 와 5% 사이를 오간다. 낮게 잡으면 추정기가\n * 이력이라는 이름으로 잡음을 실어 보낸다.\n */\nexport const DEFAULT_YIELD_MIN_SAMPLES = 20\n\n/**\n * 작업 종류별 KPI 그룹에서 양품률 추정기를 만든다(순수 — DB·커널 모름).\n * `groups` 는 `computeTwinKpi({ groupBy: 'taskKind' })` 의 `groups.items` 를 그대로 넘긴다.\n */\nexport function buildYieldEstimator(\n groups: { key: string; quality?: { good: number; scrap: number } }[] | undefined,\n opts: { minSamples?: number } = {}\n): YieldEstimatorResult {\n const minSamples = opts.minSamples ?? DEFAULT_YIELD_MIN_SAMPLES\n const learned: Record<string, number> = {}\n const samples: Record<string, { good: number; scrap: number }> = {}\n const skipped: { kind: string; judged: number }[] = []\n\n for (const g of groups ?? []) {\n if (!g?.key || g.key === 'unknown') continue\n const good = g.quality?.good ?? 0\n const scrap = g.quality?.scrap ?? 0\n const judged = good + scrap\n /* 판정이 아예 없는 종류는 **건너뛴 것이 아니다** — 품질을 가리지 않는 공정(이동·체류)이 정상이다. */\n if (judged === 0) continue\n if (judged < minSamples) {\n skipped.push({ kind: g.key, judged })\n continue\n }\n learned[g.key] = good / judged\n samples[g.key] = { good, scrap }\n }\n\n const kinds = Object.keys(learned)\n if (!kinds.length) return { learned, samples, skipped, minSamples }\n\n return {\n /*\n * 시임은 **무상태**다(커널이 fork 할 때 참조를 공유한다 — 소요 추정기와 같은 규약).\n * 배우지 않은 종류에는 `undefined` 를 답한다: 그러면 커널이 선언값 → 상수로 내려간다.\n */\n estimator: { estimate: ({ kind }) => learned[kind] },\n learned,\n samples,\n skipped,\n minSamples\n }\n}\n"]}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/** 커널 스냅샷에서 우리가 읽는 것만. 나머지 필드는 알 필요가 없다. */
|
|
2
|
+
export interface ModelGapSource {
|
|
3
|
+
locations?: {
|
|
4
|
+
id?: string;
|
|
5
|
+
origin?: string;
|
|
6
|
+
}[];
|
|
7
|
+
equipment?: {
|
|
8
|
+
id?: string;
|
|
9
|
+
origin?: string;
|
|
10
|
+
}[];
|
|
11
|
+
persons?: {
|
|
12
|
+
id?: string;
|
|
13
|
+
origin?: string;
|
|
14
|
+
}[];
|
|
15
|
+
/** 커널이 담을 줄 몰라 세어 둔 사건 — 종류별 개수와 처음·마지막 시각. */
|
|
16
|
+
unhandled?: {
|
|
17
|
+
eventType: string;
|
|
18
|
+
count: number;
|
|
19
|
+
firstAtMs?: number;
|
|
20
|
+
lastAtMs?: number;
|
|
21
|
+
}[];
|
|
22
|
+
/**
|
|
23
|
+
* EMS 상태 — **`energy` 안에 있다.**
|
|
24
|
+
*
|
|
25
|
+
* `unknownEquipment` 를 루트에서 읽고 있었다(2026-08-20에 잡음). 커널은 그것을 `EnergyState` 안에
|
|
26
|
+
* 담으므로, 루트에서 읽으면 **영원히 `null`** 이다 — 오류도 없이 「에너지 트윈이 아니다」로 읽힌다.
|
|
27
|
+
* 나만 읽는 필드를 잘못된 경로에서 읽으면 이렇게 조용히 사라진다.
|
|
28
|
+
*
|
|
29
|
+
* 계량 지점의 `origin` 은 **없을 수 있다**(옛 스냅샷에서 이어받은 지점). 그때는 「선언되었다」가
|
|
30
|
+
* 아니라 **모른다**다.
|
|
31
|
+
*/
|
|
32
|
+
energy?: {
|
|
33
|
+
points?: {
|
|
34
|
+
id?: string;
|
|
35
|
+
origin?: string;
|
|
36
|
+
}[];
|
|
37
|
+
/** 모델이 모르는 설비가 에너지 상태를 보내 온 횟수. 0 이면 커널이 이 칸을 만들지 않는다. */
|
|
38
|
+
unknownEquipment?: number;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 화면에 내는 id 수 상한.
|
|
43
|
+
*
|
|
44
|
+
* 오타 하나를 찾는 데 스무 개면 충분하고, 원천이 수천 개를 보내는 경우(모델을 아예 안 맞춘 경우)에
|
|
45
|
+
* 목록으로 화면을 채울 이유가 없다. **넘친 수는 함께 낸다** — 조용히 자르지 않는다.
|
|
46
|
+
*/
|
|
47
|
+
export declare const GAP_ID_LIMIT = 20;
|
|
48
|
+
export interface ModelGapAxis {
|
|
49
|
+
/** 관측으로만 알게 된 id 수. */
|
|
50
|
+
count: number;
|
|
51
|
+
/** 그중 화면에 싣는 것(최대 `GAP_ID_LIMIT`). */
|
|
52
|
+
ids: string[];
|
|
53
|
+
/** 상한에서 밀려난 수. 0 이면 전부 실었다. */
|
|
54
|
+
more: number;
|
|
55
|
+
/**
|
|
56
|
+
* 출처를 **모르는** 수 — 표시가 없는 항목.
|
|
57
|
+
*
|
|
58
|
+
* 「선언되었다」로 세지 않는다. 계량기는 이 표시가 늦게 생겼으므로 옛 스냅샷에서 이어받은 지점에는
|
|
59
|
+
* 칸이 없다(`undefined`). 그것을 마스터로 세면 오타를 놓치고, 관측으로 세면 정상을 문제로 만든다.
|
|
60
|
+
* 둘 다 틀리므로 **모른다고 센다.**
|
|
61
|
+
*/
|
|
62
|
+
unknown: number;
|
|
63
|
+
}
|
|
64
|
+
export interface ModelGapView {
|
|
65
|
+
locations: ModelGapAxis;
|
|
66
|
+
equipment: ModelGapAxis;
|
|
67
|
+
persons: ModelGapAxis;
|
|
68
|
+
/**
|
|
69
|
+
* EMS 계량 지점.
|
|
70
|
+
*
|
|
71
|
+
* ── 이 축이 가장 무겁다 ────────────────────────────────────────────────────
|
|
72
|
+
* 모르는 `meterId` 는 조용히 새 계량기가 되고 그 kW 가 그 현장의 **수요 구간·피크·요금 판정**에
|
|
73
|
+
* 더해진다. 오타 하나면 진짜 계량기는 멈추고 유령이 누적된다 — 숫자는 그럴듯하고 틀린다. 자리 오타는
|
|
74
|
+
* 스키매틱이 비는 것으로 드러나지만, 이건 그럴듯한 숫자로 나온다.
|
|
75
|
+
*
|
|
76
|
+
* 커널이 `origin` 을 **표본마다 다시 맞춘다** — 선언의 정본은 모델이고 모델은 채택으로 늘어나므로,
|
|
77
|
+
* 한 번 찍고 두면 나중에 선언된 계량기가 영원히 `observed` 로 남는다. 그래서 이 값은 「지금 모델에
|
|
78
|
+
* 있는가」의 답이고, 「한 번 오타였다」의 답이 아니다.
|
|
79
|
+
*/
|
|
80
|
+
meters: ModelGapAxis;
|
|
81
|
+
/** 세 축의 합 — 화면이 「있나 없나」를 한 번에 묻기 위한 값. */
|
|
82
|
+
total: number;
|
|
83
|
+
/** 커널이 담을 줄 모른 사건들(종류별). 참조 문제와 다른 사실이므로 따로 낸다. */
|
|
84
|
+
unhandled: {
|
|
85
|
+
eventType: string;
|
|
86
|
+
count: number;
|
|
87
|
+
lastAt: string | null;
|
|
88
|
+
}[];
|
|
89
|
+
/** EMS: 모델이 모르는 설비의 에너지 보고 횟수. 없으면 `null`(0 과 구별한다). */
|
|
90
|
+
unknownEquipmentReports: number | null;
|
|
91
|
+
/**
|
|
92
|
+
* 아직 덮지 못하는 축 — **모르는 것을 모른다고 말한다.**
|
|
93
|
+
*
|
|
94
|
+
* 계량기는 커널 0.7.39 부터 덮인다(그 전에는 `origin` 표시가 없어 오타를 찾아 줄 수 없었다).
|
|
95
|
+
* 비어 있는 것이 정상이고, 새 축이 표시 없이 들어오면 여기에 이름을 넣는다 — 화면이 「다 찾아
|
|
96
|
+
* 준다」고 말하지 않게.
|
|
97
|
+
*/
|
|
98
|
+
notCovered: string[];
|
|
99
|
+
}
|
|
100
|
+
/** 계량기 축이 아직 덮이지 않는 이유 — 화면이 그대로 말할 수 있게 이름을 여기 둔다. */
|
|
101
|
+
export declare const NOT_COVERED_METERS = "meters";
|
|
102
|
+
/**
|
|
103
|
+
* 스냅샷 → 모델 갭.
|
|
104
|
+
*
|
|
105
|
+
* 스냅샷이 없으면 `null` 이다 — **0 이 아니다.** 트윈이 돌지 않으면 비교할 상대가 없고, 「없다」와
|
|
106
|
+
* 「세어 보지 못했다」는 다른 사실이다(모델 인스펙션이 같은 규율을 쓴다).
|
|
107
|
+
*/
|
|
108
|
+
export declare function modelGapOf(snapshot: ModelGapSource | null | undefined): ModelGapView | null;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* **원본이 말한 적 없는 것** — 미해결 참조를 세는 한 자리. 순수(import 없음).
|
|
4
|
+
*
|
|
5
|
+
* ── 무엇을 답하나 ───────────────────────────────────────────────────────────
|
|
6
|
+
* 원천이 우리 모델에 없는 id 를 보내면 어떻게 되나. 유입 문(`ingestCanonicalRecords`)은 **거부하지
|
|
7
|
+
* 않는다** — 실측으로 확인했다(2026-08-20):
|
|
8
|
+
*
|
|
9
|
+
* 모르는 로케이션 「오타-99」 → 통과
|
|
10
|
+
* 모르는 bizStep 「PUTAWAY_X17」 → 통과
|
|
11
|
+
* 모르는 계량기 「MMXU-오타-99」 → 통과
|
|
12
|
+
*
|
|
13
|
+
* 거부하지 않는 것이 옳다. 원천이 모델보다 먼저 새 로케이션을 말하는 것은 정상이고(구조 갱신은 늦게 온다),
|
|
14
|
+
* 거부하면 그 선행 계측을 잃는다. 그래서 커널은 **흡수하고 표시한다** — 마스터에 없던 로케이션은
|
|
15
|
+
* `origin: 'observed'` 로 승격되어 스냅샷에 실린다(`ObservedReducer.touchLocation`).
|
|
16
|
+
*
|
|
17
|
+
* ── 왜 이 파일이 있나 ───────────────────────────────────────────────────────
|
|
18
|
+
* 그 표시를 읽는 곳이 **모델 인스펙션 화면 하나**였다. 그런데 이 사실을 필요로 하는 사람은 「이 트윈이
|
|
19
|
+
* 현장과 맞춰지고 있나」를 보는 사람이다 — 원천이 우리가 모르는 로케이션만 보내고 있으면 트윈은 도는데
|
|
20
|
+
* 스키매틱은 비어 있고, 그것이 우리가 「빈 공장」이라 불러 온 상태다.
|
|
21
|
+
*
|
|
22
|
+
* 도출을 두 곳에 적으면 두 화면이 다른 수를 말하게 된다. 그래서 여기 한 벌만 둔다.
|
|
23
|
+
*
|
|
24
|
+
* ── 판정을 바꾸지 않는다 ────────────────────────────────────────────────────
|
|
25
|
+
* 동기화 판정(`syncVerdictOf`)은 **피드**를 말한다: 오고 있나, 통과하나. 이 갭은 **모델**을 말한다:
|
|
26
|
+
* 원천이 아는 것을 우리가 선언했나. 둘을 한 판정에 섞으면 「유입은 정상인데 모델이 뒤처졌다」와
|
|
27
|
+
* 「유입이 막혔다」가 같은 색으로 보이고, 처방이 완전히 다르다.
|
|
28
|
+
*/
|
|
29
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
+
exports.NOT_COVERED_METERS = exports.GAP_ID_LIMIT = void 0;
|
|
31
|
+
exports.modelGapOf = modelGapOf;
|
|
32
|
+
/**
|
|
33
|
+
* 화면에 내는 id 수 상한.
|
|
34
|
+
*
|
|
35
|
+
* 오타 하나를 찾는 데 스무 개면 충분하고, 원천이 수천 개를 보내는 경우(모델을 아예 안 맞춘 경우)에
|
|
36
|
+
* 목록으로 화면을 채울 이유가 없다. **넘친 수는 함께 낸다** — 조용히 자르지 않는다.
|
|
37
|
+
*/
|
|
38
|
+
exports.GAP_ID_LIMIT = 20;
|
|
39
|
+
/** 계량기 축이 아직 덮이지 않는 이유 — 화면이 그대로 말할 수 있게 이름을 여기 둔다. */
|
|
40
|
+
exports.NOT_COVERED_METERS = 'meters';
|
|
41
|
+
function axisOf(rows) {
|
|
42
|
+
const ids = [];
|
|
43
|
+
let count = 0;
|
|
44
|
+
let unknown = 0;
|
|
45
|
+
for (const r of rows ?? []) {
|
|
46
|
+
if (r?.origin === 'observed') {
|
|
47
|
+
count++;
|
|
48
|
+
if (ids.length < exports.GAP_ID_LIMIT)
|
|
49
|
+
ids.push(String(r.id ?? ''));
|
|
50
|
+
}
|
|
51
|
+
else if (r?.origin !== 'master') {
|
|
52
|
+
/* 표시가 없다 — 마스터로도 관측으로도 세지 않는다. 모르는 것은 모른다고 센다. */
|
|
53
|
+
unknown++;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { count, ids, more: Math.max(0, count - ids.length), unknown };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 스냅샷 → 모델 갭.
|
|
60
|
+
*
|
|
61
|
+
* 스냅샷이 없으면 `null` 이다 — **0 이 아니다.** 트윈이 돌지 않으면 비교할 상대가 없고, 「없다」와
|
|
62
|
+
* 「세어 보지 못했다」는 다른 사실이다(모델 인스펙션이 같은 규율을 쓴다).
|
|
63
|
+
*/
|
|
64
|
+
function modelGapOf(snapshot) {
|
|
65
|
+
if (!snapshot)
|
|
66
|
+
return null;
|
|
67
|
+
const locations = axisOf(snapshot.locations);
|
|
68
|
+
const equipment = axisOf(snapshot.equipment);
|
|
69
|
+
const persons = axisOf(snapshot.persons);
|
|
70
|
+
const meters = axisOf(snapshot.energy?.points);
|
|
71
|
+
return {
|
|
72
|
+
locations,
|
|
73
|
+
equipment,
|
|
74
|
+
persons,
|
|
75
|
+
meters,
|
|
76
|
+
total: locations.count + equipment.count + persons.count + meters.count,
|
|
77
|
+
unhandled: (snapshot.unhandled ?? []).map(u => ({
|
|
78
|
+
eventType: u.eventType,
|
|
79
|
+
count: u.count,
|
|
80
|
+
/* 마지막 시각이 멈춘 종류는 이미 고쳐진 것이다. 없으면 지어내지 않는다. */
|
|
81
|
+
lastAt: Number.isFinite(u.lastAtMs) ? new Date(u.lastAtMs).toISOString() : null
|
|
82
|
+
})),
|
|
83
|
+
/*
|
|
84
|
+
* 커널은 0 이면 이 칸을 만들지 않는다. 그래서 「없음」은 두 뜻이다 — 에너지 트윈이 아니거나, 세었고
|
|
85
|
+
* 0 이었다. 둘을 가르려면 `energy` 자체가 있는지를 본다: 있으면 세었고 0 이다.
|
|
86
|
+
*/
|
|
87
|
+
unknownEquipmentReports: typeof snapshot.energy?.unknownEquipment === 'number' ? snapshot.energy.unknownEquipment : snapshot.energy ? 0 : null,
|
|
88
|
+
/*
|
|
89
|
+
* 이제 계량기도 덮는다(커널 0.7.39 가 `MeterPointState.origin` 을 낸다). 덮지 못하는 축이 생기면
|
|
90
|
+
* 다시 여기에 이름을 넣는다 — 화면이 「다 찾아 준다」고 말하지 않게.
|
|
91
|
+
*/
|
|
92
|
+
notCovered: []
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=model-gap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model-gap.js","sourceRoot":"","sources":["../../server/engine/model-gap.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;;;AA6GH,gCAgCC;AAnHD;;;;;GAKG;AACU,QAAA,YAAY,GAAG,EAAE,CAAA;AAoD9B,uDAAuD;AAC1C,QAAA,kBAAkB,GAAG,QAAQ,CAAA;AAE1C,SAAS,MAAM,CAAC,IAAoD;IAClE,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,IAAI,KAAK,GAAG,CAAC,CAAA;IACb,IAAI,OAAO,GAAG,CAAC,CAAA;IACf,KAAK,MAAM,CAAC,IAAI,IAAI,IAAI,EAAE,EAAE,CAAC;QAC3B,IAAI,CAAC,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;YAC7B,KAAK,EAAE,CAAA;YACP,IAAI,GAAG,CAAC,MAAM,GAAG,oBAAY;gBAAE,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;QAC7D,CAAC;aAAM,IAAI,CAAC,EAAE,MAAM,KAAK,QAAQ,EAAE,CAAC;YAClC,kDAAkD;YAClD,OAAO,EAAE,CAAA;QACX,CAAC;IACH,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAA;AACvE,CAAC;AAED;;;;;GAKG;AACH,SAAgB,UAAU,CAAC,QAA2C;IACpE,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAA;IAE1B,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAA;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;IACxC,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAE9C,OAAO;QACL,SAAS;QACT,SAAS;QACT,OAAO;QACP,MAAM;QACN,KAAK,EAAE,SAAS,CAAC,KAAK,GAAG,SAAS,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK;QACvE,SAAS,EAAE,CAAC,QAAQ,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YAC9C,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,8CAA8C;YAC9C,MAAM,EAAE,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,QAAkB,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;SAC1F,CAAC,CAAC;QACH;;;WAGG;QACH,uBAAuB,EACrB,OAAO,QAAQ,CAAC,MAAM,EAAE,gBAAgB,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI;QACvH;;;WAGG;QACH,UAAU,EAAE,EAAE;KACf,CAAA;AACH,CAAC","sourcesContent":["/*\n * **원본이 말한 적 없는 것** — 미해결 참조를 세는 한 자리. 순수(import 없음).\n *\n * ── 무엇을 답하나 ───────────────────────────────────────────────────────────\n * 원천이 우리 모델에 없는 id 를 보내면 어떻게 되나. 유입 문(`ingestCanonicalRecords`)은 **거부하지\n * 않는다** — 실측으로 확인했다(2026-08-20):\n *\n * 모르는 로케이션 「오타-99」 → 통과\n * 모르는 bizStep 「PUTAWAY_X17」 → 통과\n * 모르는 계량기 「MMXU-오타-99」 → 통과\n *\n * 거부하지 않는 것이 옳다. 원천이 모델보다 먼저 새 로케이션을 말하는 것은 정상이고(구조 갱신은 늦게 온다),\n * 거부하면 그 선행 계측을 잃는다. 그래서 커널은 **흡수하고 표시한다** — 마스터에 없던 로케이션은\n * `origin: 'observed'` 로 승격되어 스냅샷에 실린다(`ObservedReducer.touchLocation`).\n *\n * ── 왜 이 파일이 있나 ───────────────────────────────────────────────────────\n * 그 표시를 읽는 곳이 **모델 인스펙션 화면 하나**였다. 그런데 이 사실을 필요로 하는 사람은 「이 트윈이\n * 현장과 맞춰지고 있나」를 보는 사람이다 — 원천이 우리가 모르는 로케이션만 보내고 있으면 트윈은 도는데\n * 스키매틱은 비어 있고, 그것이 우리가 「빈 공장」이라 불러 온 상태다.\n *\n * 도출을 두 곳에 적으면 두 화면이 다른 수를 말하게 된다. 그래서 여기 한 벌만 둔다.\n *\n * ── 판정을 바꾸지 않는다 ────────────────────────────────────────────────────\n * 동기화 판정(`syncVerdictOf`)은 **피드**를 말한다: 오고 있나, 통과하나. 이 갭은 **모델**을 말한다:\n * 원천이 아는 것을 우리가 선언했나. 둘을 한 판정에 섞으면 「유입은 정상인데 모델이 뒤처졌다」와\n * 「유입이 막혔다」가 같은 색으로 보이고, 처방이 완전히 다르다.\n */\n\n/** 커널 스냅샷에서 우리가 읽는 것만. 나머지 필드는 알 필요가 없다. */\nexport interface ModelGapSource {\n locations?: { id?: string; origin?: string }[]\n equipment?: { id?: string; origin?: string }[]\n persons?: { id?: string; origin?: string }[]\n /** 커널이 담을 줄 몰라 세어 둔 사건 — 종류별 개수와 처음·마지막 시각. */\n unhandled?: { eventType: string; count: number; firstAtMs?: number; lastAtMs?: number }[]\n /**\n * EMS 상태 — **`energy` 안에 있다.**\n *\n * `unknownEquipment` 를 루트에서 읽고 있었다(2026-08-20에 잡음). 커널은 그것을 `EnergyState` 안에\n * 담으므로, 루트에서 읽으면 **영원히 `null`** 이다 — 오류도 없이 「에너지 트윈이 아니다」로 읽힌다.\n * 나만 읽는 필드를 잘못된 경로에서 읽으면 이렇게 조용히 사라진다.\n *\n * 계량 지점의 `origin` 은 **없을 수 있다**(옛 스냅샷에서 이어받은 지점). 그때는 「선언되었다」가\n * 아니라 **모른다**다.\n */\n energy?: {\n points?: { id?: string; origin?: string }[]\n /** 모델이 모르는 설비가 에너지 상태를 보내 온 횟수. 0 이면 커널이 이 칸을 만들지 않는다. */\n unknownEquipment?: number\n }\n}\n\n/**\n * 화면에 내는 id 수 상한.\n *\n * 오타 하나를 찾는 데 스무 개면 충분하고, 원천이 수천 개를 보내는 경우(모델을 아예 안 맞춘 경우)에\n * 목록으로 화면을 채울 이유가 없다. **넘친 수는 함께 낸다** — 조용히 자르지 않는다.\n */\nexport const GAP_ID_LIMIT = 20\n\nexport interface ModelGapAxis {\n /** 관측으로만 알게 된 id 수. */\n count: number\n /** 그중 화면에 싣는 것(최대 `GAP_ID_LIMIT`). */\n ids: string[]\n /** 상한에서 밀려난 수. 0 이면 전부 실었다. */\n more: number\n /**\n * 출처를 **모르는** 수 — 표시가 없는 항목.\n *\n * 「선언되었다」로 세지 않는다. 계량기는 이 표시가 늦게 생겼으므로 옛 스냅샷에서 이어받은 지점에는\n * 칸이 없다(`undefined`). 그것을 마스터로 세면 오타를 놓치고, 관측으로 세면 정상을 문제로 만든다.\n * 둘 다 틀리므로 **모른다고 센다.**\n */\n unknown: number\n}\n\nexport interface ModelGapView {\n locations: ModelGapAxis\n equipment: ModelGapAxis\n persons: ModelGapAxis\n /**\n * EMS 계량 지점.\n *\n * ── 이 축이 가장 무겁다 ────────────────────────────────────────────────────\n * 모르는 `meterId` 는 조용히 새 계량기가 되고 그 kW 가 그 현장의 **수요 구간·피크·요금 판정**에\n * 더해진다. 오타 하나면 진짜 계량기는 멈추고 유령이 누적된다 — 숫자는 그럴듯하고 틀린다. 자리 오타는\n * 스키매틱이 비는 것으로 드러나지만, 이건 그럴듯한 숫자로 나온다.\n *\n * 커널이 `origin` 을 **표본마다 다시 맞춘다** — 선언의 정본은 모델이고 모델은 채택으로 늘어나므로,\n * 한 번 찍고 두면 나중에 선언된 계량기가 영원히 `observed` 로 남는다. 그래서 이 값은 「지금 모델에\n * 있는가」의 답이고, 「한 번 오타였다」의 답이 아니다.\n */\n meters: ModelGapAxis\n /** 세 축의 합 — 화면이 「있나 없나」를 한 번에 묻기 위한 값. */\n total: number\n /** 커널이 담을 줄 모른 사건들(종류별). 참조 문제와 다른 사실이므로 따로 낸다. */\n unhandled: { eventType: string; count: number; lastAt: string | null }[]\n /** EMS: 모델이 모르는 설비의 에너지 보고 횟수. 없으면 `null`(0 과 구별한다). */\n unknownEquipmentReports: number | null\n /**\n * 아직 덮지 못하는 축 — **모르는 것을 모른다고 말한다.**\n *\n * 계량기는 커널 0.7.39 부터 덮인다(그 전에는 `origin` 표시가 없어 오타를 찾아 줄 수 없었다).\n * 비어 있는 것이 정상이고, 새 축이 표시 없이 들어오면 여기에 이름을 넣는다 — 화면이 「다 찾아\n * 준다」고 말하지 않게.\n */\n notCovered: string[]\n}\n\n/** 계량기 축이 아직 덮이지 않는 이유 — 화면이 그대로 말할 수 있게 이름을 여기 둔다. */\nexport const NOT_COVERED_METERS = 'meters'\n\nfunction axisOf(rows: { id?: string; origin?: string }[] | undefined): ModelGapAxis {\n const ids: string[] = []\n let count = 0\n let unknown = 0\n for (const r of rows ?? []) {\n if (r?.origin === 'observed') {\n count++\n if (ids.length < GAP_ID_LIMIT) ids.push(String(r.id ?? ''))\n } else if (r?.origin !== 'master') {\n /* 표시가 없다 — 마스터로도 관측으로도 세지 않는다. 모르는 것은 모른다고 센다. */\n unknown++\n }\n }\n return { count, ids, more: Math.max(0, count - ids.length), unknown }\n}\n\n/**\n * 스냅샷 → 모델 갭.\n *\n * 스냅샷이 없으면 `null` 이다 — **0 이 아니다.** 트윈이 돌지 않으면 비교할 상대가 없고, 「없다」와\n * 「세어 보지 못했다」는 다른 사실이다(모델 인스펙션이 같은 규율을 쓴다).\n */\nexport function modelGapOf(snapshot: ModelGapSource | null | undefined): ModelGapView | null {\n if (!snapshot) return null\n\n const locations = axisOf(snapshot.locations)\n const equipment = axisOf(snapshot.equipment)\n const persons = axisOf(snapshot.persons)\n const meters = axisOf(snapshot.energy?.points)\n\n return {\n locations,\n equipment,\n persons,\n meters,\n total: locations.count + equipment.count + persons.count + meters.count,\n unhandled: (snapshot.unhandled ?? []).map(u => ({\n eventType: u.eventType,\n count: u.count,\n /* 마지막 시각이 멈춘 종류는 이미 고쳐진 것이다. 없으면 지어내지 않는다. */\n lastAt: Number.isFinite(u.lastAtMs) ? new Date(u.lastAtMs as number).toISOString() : null\n })),\n /*\n * 커널은 0 이면 이 칸을 만들지 않는다. 그래서 「없음」은 두 뜻이다 — 에너지 트윈이 아니거나, 세었고\n * 0 이었다. 둘을 가르려면 `energy` 자체가 있는지를 본다: 있으면 세었고 0 이다.\n */\n unknownEquipmentReports:\n typeof snapshot.energy?.unknownEquipment === 'number' ? snapshot.energy.unknownEquipment : snapshot.energy ? 0 : null,\n /*\n * 이제 계량기도 덮는다(커널 0.7.39 가 `MeterPointState.origin` 을 낸다). 덮지 못하는 축이 생기면\n * 다시 여기에 이름을 넣는다 — 화면이 「다 찾아 준다」고 말하지 않게.\n */\n notCovered: []\n }\n}\n"]}
|
|
@@ -15,6 +15,22 @@ export interface OperationBasisRow {
|
|
|
15
15
|
referenceDuration?: string;
|
|
16
16
|
/** 우리가 선언해 둔 시간(ISO 8601) — 있으면. */
|
|
17
17
|
localDuration?: string;
|
|
18
|
+
/**
|
|
19
|
+
* 우리가 선언해 둔 공정 모수 — **커널이 읽는 표기 그대로**(수율 0..1, 셋업 ISO 8601).
|
|
20
|
+
*
|
|
21
|
+
* 사람 단위(퍼센트·분)로 되돌리지 않는다: 화면은 선언 겹에서 사용자가 넣은 값을 그대로 들고 있고,
|
|
22
|
+
* 여기 있는 것은 **실제로 커널에 실린 값**이다 — 둘을 견주면 환산이 잘못된 것도 드러난다.
|
|
23
|
+
*/
|
|
24
|
+
localParams?: Record<string, string>;
|
|
25
|
+
/** 원천 명세 행이 말한 모수 — 있으면(우리 선언이 이것을 이긴다). */
|
|
26
|
+
referenceParams?: Record<string, string>;
|
|
27
|
+
/**
|
|
28
|
+
* 모수를 **무엇으로 읽었나** — 커널의 자기보고(`id → measured|declared`) (2026-08-19).
|
|
29
|
+
*
|
|
30
|
+
* 수율이 이력에서 온 값이면 선언값은 예비로만 남는다(소요시간과 같은 규율) — 화면이 그 사실을 말해야
|
|
31
|
+
* 사람이 헛일을 하지 않는다. 커널이 아직 그 공정을 돌리지 않았으면 이 칸이 빈다(모른다).
|
|
32
|
+
*/
|
|
33
|
+
paramBasis?: Record<string, 'measured' | 'declared'>;
|
|
18
34
|
}
|
|
19
35
|
/**
|
|
20
36
|
* 공정 목록을 모은다 — **넷의 합집합**, 순서는 원천 행 → 돌린 것 → 이력에서 배운 것 → 선언한 것.
|
|
@@ -14,7 +14,8 @@ exports.knownOperationKinds = knownOperationKinds;
|
|
|
14
14
|
* ① 원천 명세 행(`model.operations`) — 원 시스템이 말한 공정.
|
|
15
15
|
* ② 커널의 자기보고(`specCoverage()`) — **실제로 돌린** 공정과 그 시간의 출처.
|
|
16
16
|
* ③ 이력에서 배운 종류(`measuredOperationKinds`) — 재기동에도 남는 근거.
|
|
17
|
-
* ④ 우리가 이미 선언한 것(`model.localDurations`) — 선언해 둔 공정이 목록에서
|
|
17
|
+
* ④ 우리가 이미 선언한 것(`model.localDurations`·`model.localParams`) — 선언해 둔 공정이 목록에서
|
|
18
|
+
* 사라지지 않게(사라지면 철회할 자리가 없다).
|
|
18
19
|
*
|
|
19
20
|
* ③이 필요한 이유는 실측으로 드러났다: ②는 **작업이 새로 생길 때** 채워지므로, 이력이 풍부한 창고
|
|
20
21
|
* 트윈도 재기동 직후에는 목록이 비었다 — 값을 채울 곳을 보여야 하는 화면이 정작 그때 아무 말도 못 한다.
|
|
@@ -47,12 +48,21 @@ function operationBasis(model, kernel, measuredKinds) {
|
|
|
47
48
|
row.label = String(o.label);
|
|
48
49
|
if (o.duration)
|
|
49
50
|
row.referenceDuration = String(o.duration);
|
|
51
|
+
/* 원천이 말한 모수도 함께 든다 — 우리 선언과 견줄 수 있어야 「무엇을 고쳤나」가 보인다. */
|
|
52
|
+
for (const p of (o.parameters ?? [])) {
|
|
53
|
+
if (!p?.id || p.value === undefined)
|
|
54
|
+
continue;
|
|
55
|
+
row.referenceParams = { ...(row.referenceParams ?? {}), [String(p.id)]: String(p.value) };
|
|
56
|
+
}
|
|
50
57
|
}
|
|
51
58
|
const coverage = kernel ? (0, spec_coverage_ts_1.readSpecCoverage)(kernel) : undefined;
|
|
52
59
|
for (const o of coverage?.operations ?? []) {
|
|
53
60
|
if (!o?.kind)
|
|
54
61
|
continue;
|
|
55
|
-
at(String(o.kind))
|
|
62
|
+
const row = at(String(o.kind));
|
|
63
|
+
row.basis = o.duration;
|
|
64
|
+
if (o.parameterBasis && Object.keys(o.parameterBasis).length)
|
|
65
|
+
row.paramBasis = { ...o.parameterBasis };
|
|
56
66
|
}
|
|
57
67
|
/*
|
|
58
68
|
* 이력에서 배운 종류 — 커널이 아직 이 종류를 돌리지 않았어도 **추정기가 답할 수 있다**. 그래서 근거를
|
|
@@ -72,6 +82,14 @@ function operationBasis(model, kernel, measuredKinds) {
|
|
|
72
82
|
continue;
|
|
73
83
|
at(kind).localDuration = String(iso);
|
|
74
84
|
}
|
|
85
|
+
const params = (model?.localParams ?? {});
|
|
86
|
+
for (const [kind, entries] of Object.entries(params)) {
|
|
87
|
+
if (!kind)
|
|
88
|
+
continue;
|
|
89
|
+
const row = at(kind);
|
|
90
|
+
for (const [id, value] of Object.entries(entries ?? {}))
|
|
91
|
+
row.localParams = { ...(row.localParams ?? {}), [id]: String(value) };
|
|
92
|
+
}
|
|
75
93
|
return [...rows.values()];
|
|
76
94
|
}
|
|
77
95
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-basis.js","sourceRoot":"","sources":["../../server/engine/operation-basis.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"operation-basis.js","sourceRoot":"","sources":["../../server/engine/operation-basis.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 이 트윈이 **어떤 공정을 돌리고, 그 시간이 어디서 오나.**\n *\n * ── 왜 이 목록이 필요한가 (2026-08-18) ─────────────────────────────────────\n * 「측정이 없을 때의 표준 시간」을 현장이 선언하려면 먼저 **무엇에 선언할지** 보여야 한다. 그런데 공정은\n * 자리·설비와 달리 모델에 행이 없을 수 있다 — 실측: 운영 중인 트윈 35개 중 창고·야드는 공정 명세 행이\n * 0개였고(MES만 4개), 그 트윈들의 시간은 전부 커널 상수에서 왔다.\n *\n * 그래서 목록을 **지어내지 않고 네 곳에서 모은다**:\n * ① 원천 명세 행(`model.operations`) — 원 시스템이 말한 공정.\n * ② 커널의 자기보고(`specCoverage()`) — **실제로 돌린** 공정과 그 시간의 출처.\n * ③ 이력에서 배운 종류(`measuredOperationKinds`) — 재기동에도 남는 근거.\n * ④ 우리가 이미 선언한 것(`model.localDurations`·`model.localParams`) — 선언해 둔 공정이 목록에서\n * 사라지지 않게(사라지면 철회할 자리가 없다).\n *\n * ③이 필요한 이유는 실측으로 드러났다: ②는 **작업이 새로 생길 때** 채워지므로, 이력이 풍부한 창고\n * 트윈도 재기동 직후에는 목록이 비었다 — 값을 채울 곳을 보여야 하는 화면이 정작 그때 아무 말도 못 한다.\n *\n * 그래도 셋 다 비는 트윈은 있다(한 번도 돌지 않고 명세 행도 없는 새 트윈). 그때 「공정이 없다」고 말하지\n * 않는다(모르는 것이다) — 부르는 쪽이 그 사실을 그대로 화면에 옮긴다.\n */\nimport { readSpecCoverage } from './spec-coverage.ts'\n\n/** 공정 한 종류 — 이름과 「지금 무엇으로 도나」. */\nexport interface OperationBasisRow {\n /** 작업 종류(`FlowTask.kind` = `OperationDef.key`) — 선언이 겨누는 id 다. */\n kind: string\n /** 사람이 읽는 이름 — 원천 명세 행이 있을 때만(없으면 종류 이름이 곧 이름이다). */\n label?: string\n /**\n * 지금 이 종류의 시간이 어디서 오나 — 커널의 자기보고, 또는 **이력에서 배운 사실**. 없으면 모른다.\n * · `measured` — 이 현장의 이력에서 배운 값(가장 강한 근거). **선언보다 이긴다.**\n * · `declared` — 선언된 값(현장이 정한 것 또는 원천 명세 행).\n * · `default` — 커널 소스의 상수. 이 목록의 존재 이유다.\n */\n basis?: 'measured' | 'declared' | 'default'\n /** 원천 명세 행이 말한 시간(ISO 8601) — 있으면. 우리 선언이 이것을 이긴다. */\n referenceDuration?: string\n /** 우리가 선언해 둔 시간(ISO 8601) — 있으면. */\n localDuration?: string\n /**\n * 우리가 선언해 둔 공정 모수 — **커널이 읽는 표기 그대로**(수율 0..1, 셋업 ISO 8601).\n *\n * 사람 단위(퍼센트·분)로 되돌리지 않는다: 화면은 선언 겹에서 사용자가 넣은 값을 그대로 들고 있고,\n * 여기 있는 것은 **실제로 커널에 실린 값**이다 — 둘을 견주면 환산이 잘못된 것도 드러난다.\n */\n localParams?: Record<string, string>\n /** 원천 명세 행이 말한 모수 — 있으면(우리 선언이 이것을 이긴다). */\n referenceParams?: Record<string, string>\n /**\n * 모수를 **무엇으로 읽었나** — 커널의 자기보고(`id → measured|declared`) (2026-08-19).\n *\n * 수율이 이력에서 온 값이면 선언값은 예비로만 남는다(소요시간과 같은 규율) — 화면이 그 사실을 말해야\n * 사람이 헛일을 하지 않는다. 커널이 아직 그 공정을 돌리지 않았으면 이 칸이 빈다(모른다).\n */\n paramBasis?: Record<string, 'measured' | 'declared'>\n}\n\n/**\n * 공정 목록을 모은다 — **넷의 합집합**, 순서는 원천 행 → 돌린 것 → 이력에서 배운 것 → 선언한 것.\n *\n * `kernel` 이 없으면(정지 트윈) ②가 빠지고, `measuredKinds` 를 주지 않으면 ③이 빠진다: 그 사실은\n * `basis` 가 비는 것으로 드러난다(빈칸이 곧 「모른다」다).\n */\nexport function operationBasis(model: any, kernel?: any, measuredKinds?: readonly string[]): OperationBasisRow[] {\n const rows = new Map<string, OperationBasisRow>()\n const at = (kind: string): OperationBasisRow => {\n const cur = rows.get(kind)\n if (cur) return cur\n const row: OperationBasisRow = { kind }\n rows.set(kind, row)\n return row\n }\n\n for (const o of (model?.operations ?? []) as any[]) {\n if (!o?.key) continue\n const row = at(String(o.key))\n if (o.label) row.label = String(o.label)\n if (o.duration) row.referenceDuration = String(o.duration)\n /* 원천이 말한 모수도 함께 든다 — 우리 선언과 견줄 수 있어야 「무엇을 고쳤나」가 보인다. */\n for (const p of (o.parameters ?? []) as any[]) {\n if (!p?.id || p.value === undefined) continue\n row.referenceParams = { ...(row.referenceParams ?? {}), [String(p.id)]: String(p.value) }\n }\n }\n\n const coverage = kernel ? readSpecCoverage(kernel) : undefined\n for (const o of coverage?.operations ?? []) {\n if (!o?.kind) continue\n const row = at(String(o.kind))\n row.basis = o.duration\n if (o.parameterBasis && Object.keys(o.parameterBasis).length) row.paramBasis = { ...o.parameterBasis }\n }\n\n /*\n * 이력에서 배운 종류 — 커널이 아직 이 종류를 돌리지 않았어도 **추정기가 답할 수 있다**. 그래서 근거를\n * `measured` 로 채운다: 짐작이 아니라 설치된 추정기가 그 종류에 실측값을 줄 것이라는 사실이다.\n * 커널이 이미 보고한 종류는 **덮지 않는다** — 실제로 돌린 것이 더 강한 사실이다.\n */\n for (const kind of measuredKinds ?? []) {\n if (!kind) continue\n const row = at(String(kind))\n if (!row.basis) row.basis = 'measured'\n }\n\n const declared = (model?.localDurations ?? {}) as Record<string, string>\n for (const [kind, iso] of Object.entries(declared)) {\n if (!kind) continue\n at(kind).localDuration = String(iso)\n }\n\n const params = (model?.localParams ?? {}) as Record<string, Record<string, string>>\n for (const [kind, entries] of Object.entries(params)) {\n if (!kind) continue\n const row = at(kind)\n for (const [id, value] of Object.entries(entries ?? {})) row.localParams = { ...(row.localParams ?? {}), [id]: String(value) }\n }\n\n return [...rows.values()]\n}\n\n/**\n * 선언을 받아도 되는 공정 이름들 — **하나도 확인할 수 없으면 `undefined`**(모른다).\n *\n * 빈 배열로 답하지 않는 이유: 빈 배열은 「어떤 이름도 안 된다」로 읽히고, 그러면 아직 돌지 않은 트윈에는\n * 아무것도 선언할 수 없다. 모르는 것과 없는 것은 다르다 — 모를 때는 받아 주고 그 사실을 말한다.\n */\nexport function knownOperationKinds(model: any, kernel?: any, measuredKinds?: readonly string[]): string[] | undefined {\n const kinds = operationBasis(model, kernel, measuredKinds).map(r => r.kind)\n return kinds.length ? kinds : undefined\n}\n"]}
|
|
@@ -150,6 +150,23 @@ exports.PROPERTY_EFFECTS = [
|
|
|
150
150
|
effect: 'twin.propeffect.operationDuration',
|
|
151
151
|
readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'
|
|
152
152
|
},
|
|
153
|
+
/*
|
|
154
|
+
* ── 공정 모수 (2026-08-19) ────────────────────────────────────────────────
|
|
155
|
+
* 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목 신호까지 만들었다** — 초기
|
|
156
|
+
* 라인이든 성숙한 라인이든 같은 수로 판정된다. 셋업도 상수였다. 둘 다 현장이 말할 수 있어야 한다.
|
|
157
|
+
*/
|
|
158
|
+
{
|
|
159
|
+
id: 'operation.yield',
|
|
160
|
+
axes: ['operations'],
|
|
161
|
+
effect: 'twin.propeffect.operationYield',
|
|
162
|
+
readBy: '@operato/twin-kernel MesKernel(OP_PARAM.yield) ← declareParameters (양품/불량 판정과 산출량)'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'operation.setupDuration',
|
|
166
|
+
axes: ['operations'],
|
|
167
|
+
effect: 'twin.propeffect.operationSetup',
|
|
168
|
+
readBy: '@operato/twin-kernel MesKernel(OP_PARAM.setupDuration) ← declareParameters (가동 앞에 붙는 셋업)'
|
|
169
|
+
},
|
|
153
170
|
{
|
|
154
171
|
id: 'tariff.currency',
|
|
155
172
|
axes: ['locations'],
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"property-effects.js","sourceRoot":"","sources":["../../server/engine/property-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 자원 속성의 **효과 등록부** — 이 속성이 무엇을 바꾸나.\n *\n * ── 왜 있나 ─────────────────────────────────────────────────────────────────\n * 속성은 **열려 있다**: 템플릿·마스터가 아무 id 로나 사실을 실어 보낼 수 있고, 커널은 그것을 그대로\n * 나르고 화면은 단위와 함께 보인다(표준이 속성 어휘를 정하지 않으므로 그것이 옳다).\n *\n * 그런데 **효과는 닫혀 있다**: 값이 계산·배정·판정을 바꾸려면 그 규칙을 코드가 알아야 한다. 지금\n * 효과가 있는 것은 둘뿐이다. 문제는 그 사실이 **어디에도 드러나지 않는다**는 것이었다 — 사용자는\n * 속성을 넣으면 무언가 달라질 것으로 기대하는데, 실제로는 표에 한 줄이 늘 뿐이다.\n *\n * 그래서 이 등록부는 **지어내지 않고 드러낸다**: 읽는 코드가 있는 것만 적고, 없는 것은 \"표시만 된다\"\n * 고 화면이 말한다. 새 효과를 코드로 붙이는 사람은 **여기 한 줄을 더한다** — 그러지 않으면 사용자는\n * 그 효과가 있는지 알 수 없다.\n *\n * ⚠️ 이 파일은 등록부이고 판정이 아니다. 값을 실제로 읽는 곳은 각자다(아래 `readBy` 가 그 자리를\n * 가리킨다) — 여기에 계산을 옮겨 오면 규칙이 두 벌이 된다.\n */\n\n/** 효과가 있는 속성 하나. */\nexport interface PropertyEffect {\n /** 속성 id — 선언에 적히는 그 이름(`ResourceProperty.id`). */\n id: string\n /** 어느 축의 속성인가 — 그 축의 항목에만 이 효과를 말한다. */\n axes: string[]\n /** 무엇을 바꾸나 — 화면이 읽는 i18n 키(언어중립: 서버는 사람 말을 만들지 않는다). */\n effect: string\n /** 실제로 읽는 코드 — 지어낸 효과를 등록하지 않기 위한 표식이다(없는 자리를 가리키면 리뷰가 잡는다). */\n readBy: string\n}\n\nexport const PROPERTY_EFFECTS: PropertyEffect[] = [\n {\n id: 'speed',\n axes: ['equipment'],\n effect: 'twin.propeffect.speed',\n readBy: 'headless-twin/server/engine/travel-estimator.ts (buildTravelEstimator)'\n },\n {\n id: 'perBaseUnit',\n axes: ['materialDefinitions'],\n effect: 'twin.propeffect.perBaseUnit',\n readBy: '@operato/twin-kernel conversionFactorOf'\n },\n /*\n * ── 에너지 속성 (2026-08-18) ──────────────────────────────────────────────\n *\n * 이 값들은 **오래전부터 읽히고 있었다** — 커널이 부하·피크·배터리를 그것으로 계산하고, 호스트가\n * 금액을 그것으로 만든다. 그런데 등록부에 없어서 화면은 그 값들에 대해 「표시만 됩니다」라고 말했다.\n * 조용한 거짓말이다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다.\n *\n * 효과를 새로 만든 것이 아니라 **있는 효과를 드러낸다.** `readBy` 는 실제로 읽는 자리를 가리킨다.\n */\n {\n id: 'contract.kW',\n axes: ['locations'],\n effect: 'twin.propeffect.contractKW',\n readBy: '@operato/twin-kernel EmsKernel.declaredContractKW (계약 대비 판정·초과 주의)'\n },\n {\n id: 'power.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.ratedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (가동 중 부하)'\n },\n {\n id: 'power.standbyKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.standbyKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (대기 중 부하)'\n },\n {\n id: 'storage.capacityKWh',\n axes: ['equipment'],\n effect: 'twin.propeffect.capacityKWh',\n readBy: '@operato/twin-kernel EmsKernel (축전지 방전 가능량)'\n },\n {\n id: 'dispatch.dischargeAboveKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.dischargeAboveKW',\n readBy: '@operato/twin-kernel EmsKernel (방전 시작 문턱)'\n },\n {\n id: 'dispatch.maxDischargeKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.maxDischargeKW',\n readBy: '@operato/twin-kernel EmsKernel (인버터 방전 상한)'\n },\n {\n id: 'dispatch.reserveSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.reserveSoc',\n readBy: '@operato/twin-kernel EmsKernel (이 아래로는 방전하지 않는다)'\n },\n {\n id: 'storage.initialSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.initialSoc',\n readBy: '@operato/twin-kernel EmsKernel (시작 충전율 — 예측·what-if 의 씨앗)'\n },\n {\n id: 'generation.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.genRatedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveGeneration (정격 발전)'\n },\n {\n id: 'generation.dailyProfile',\n axes: ['equipment'],\n effect: 'twin.propeffect.genDailyProfile',\n readBy: '@operato/twin-kernel generationFractionAt (시각별 발전 비율)'\n },\n /* 요금은 커널이 읽지 않는다 — 금액은 저널을 접는 호스트가 만든다. readBy 가 그 사실을 말한다. */\n {\n id: 'tariff.demandChargePerKW',\n axes: ['locations'],\n effect: 'twin.propeffect.demandChargePerKW',\n readBy: 'headless-twin/server/engine/kpi-query.ts (수요요금 계산)'\n },\n {\n id: 'tariff.energyChargePerKWh',\n axes: ['locations'],\n effect: 'twin.propeffect.energyChargePerKWh',\n readBy: 'headless-twin/server/engine/kpi-query.ts (전력량 요금 계산)'\n },\n /* 판정 기준 — 값이 경보의 유무를 바꾼다(가장 눈에 보이는 효과다). */\n {\n id: 'attention.congestionRatio',\n axes: ['locations'],\n effect: 'twin.propeffect.congestionRatio',\n readBy: '@operato/twin-kernel deriveAttentions (병목 판정 기준)'\n },\n {\n id: 'attention.scrapRate',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapRate',\n readBy: '@operato/twin-kernel deriveAttentions (불량 과다 판정 기준)'\n },\n {\n id: 'attention.scrapMinSamples',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapMinSamples',\n readBy: '@operato/twin-kernel deriveAttentions (불량률을 재기 시작할 표본 수)'\n },\n /*\n * ── 공정 소요시간 (2026-08-18) ────────────────────────────────────────────\n *\n * 실측: 운영 중인 트윈 35개 중 **창고·야드는 공정 명세 행이 0개**였다 — 그 트윈들의 시간은 전부 커널\n * 상수에서 왔고, 예측은 「같은 조건에서의 상대 비교」에 머물렀다(절대 시각 주장 불가). 이 선언이\n * 그 사다리를 한 칸 올린다: 실측이 없어도 **현장이 정한 시간**으로 굴러간다.\n *\n * 실측(이력에서 배운 값)은 여전히 이 선언을 이긴다 — 화면이 그 사실을 함께 말한다.\n */\n {\n id: 'operation.duration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationDuration',\n readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'\n },\n {\n id: 'tariff.currency',\n axes: ['locations'],\n effect: 'twin.propeffect.currency',\n readBy: 'headless-twin/server/engine/kpi-query.ts (금액 표기 통화)'\n }\n]\n\n/**\n * 이 축의 속성 효과 — `속성 id → i18n 키`.\n *\n * 없는 속성은 **여기 없다**(빈 값으로 채우지 않는다) — 화면이 \"등록된 효과가 없다\" 를 스스로 말한다.\n */\nexport function propertyEffectsOf(axis: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const e of PROPERTY_EFFECTS) if (e.axes.includes(axis)) out[e.id] = e.effect\n return out\n}\n\n/** 등록부의 축 이름 → 선언이 겨누는 대상 이름. 대상이 아닌 축(자재 정의 등)은 여기 없다. */\nconst AXIS_TO_TARGET: Record<string, string> = { locations: 'location', equipment: 'equipment', operations: 'operation' }\n\n/**\n * 이 속성을 **어느 대상에 얹을 수 있나** — 등록부의 축이 그대로 답한다.\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 지게차에 계약전력을 선언할 수 있었다. 문은 받아 주고 화면은 「넣었습니다」라고 말하는데, 그 값을 읽는\n * 코드는 자리(수전)만 본다 — 아무 일도 일어나지 않고 그 사실이 어디에도 나오지 않았다. 효과가 어느 축의\n * 것인지는 이미 등록부가 알고 있으므로, 그 지식을 검사에 쓴다(두 곳에 적지 않는다).\n *\n * 등록부에 없는 속성은 **비어 있는 목록이 아니라 `undefined`** 다: 「어디에도 못 얹는다」와 「우리가\n * 제한을 모른다」는 다르다(후자는 받아 준다 — 속성은 열려 있다는 규약을 지킨다).\n */\nexport function declarationTargetsOf(propertyId: string): string[] | undefined {\n const e = PROPERTY_EFFECTS.find(x => x.id === propertyId)\n if (!e) return undefined\n const targets = e.axes.map(a => AXIS_TO_TARGET[a]).filter((t): t is string => !!t)\n return targets.length ? targets : undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"property-effects.js","sourceRoot":"","sources":["../../server/engine/property-effects.ts"],"names":[],"mappings":"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","sourcesContent":["/*\n * 자원 속성의 **효과 등록부** — 이 속성이 무엇을 바꾸나.\n *\n * ── 왜 있나 ─────────────────────────────────────────────────────────────────\n * 속성은 **열려 있다**: 템플릿·마스터가 아무 id 로나 사실을 실어 보낼 수 있고, 커널은 그것을 그대로\n * 나르고 화면은 단위와 함께 보인다(표준이 속성 어휘를 정하지 않으므로 그것이 옳다).\n *\n * 그런데 **효과는 닫혀 있다**: 값이 계산·배정·판정을 바꾸려면 그 규칙을 코드가 알아야 한다. 지금\n * 효과가 있는 것은 둘뿐이다. 문제는 그 사실이 **어디에도 드러나지 않는다**는 것이었다 — 사용자는\n * 속성을 넣으면 무언가 달라질 것으로 기대하는데, 실제로는 표에 한 줄이 늘 뿐이다.\n *\n * 그래서 이 등록부는 **지어내지 않고 드러낸다**: 읽는 코드가 있는 것만 적고, 없는 것은 \"표시만 된다\"\n * 고 화면이 말한다. 새 효과를 코드로 붙이는 사람은 **여기 한 줄을 더한다** — 그러지 않으면 사용자는\n * 그 효과가 있는지 알 수 없다.\n *\n * ⚠️ 이 파일은 등록부이고 판정이 아니다. 값을 실제로 읽는 곳은 각자다(아래 `readBy` 가 그 자리를\n * 가리킨다) — 여기에 계산을 옮겨 오면 규칙이 두 벌이 된다.\n */\n\n/** 효과가 있는 속성 하나. */\nexport interface PropertyEffect {\n /** 속성 id — 선언에 적히는 그 이름(`ResourceProperty.id`). */\n id: string\n /** 어느 축의 속성인가 — 그 축의 항목에만 이 효과를 말한다. */\n axes: string[]\n /** 무엇을 바꾸나 — 화면이 읽는 i18n 키(언어중립: 서버는 사람 말을 만들지 않는다). */\n effect: string\n /** 실제로 읽는 코드 — 지어낸 효과를 등록하지 않기 위한 표식이다(없는 자리를 가리키면 리뷰가 잡는다). */\n readBy: string\n}\n\nexport const PROPERTY_EFFECTS: PropertyEffect[] = [\n {\n id: 'speed',\n axes: ['equipment'],\n effect: 'twin.propeffect.speed',\n readBy: 'headless-twin/server/engine/travel-estimator.ts (buildTravelEstimator)'\n },\n {\n id: 'perBaseUnit',\n axes: ['materialDefinitions'],\n effect: 'twin.propeffect.perBaseUnit',\n readBy: '@operato/twin-kernel conversionFactorOf'\n },\n /*\n * ── 에너지 속성 (2026-08-18) ──────────────────────────────────────────────\n *\n * 이 값들은 **오래전부터 읽히고 있었다** — 커널이 부하·피크·배터리를 그것으로 계산하고, 호스트가\n * 금액을 그것으로 만든다. 그런데 등록부에 없어서 화면은 그 값들에 대해 「표시만 됩니다」라고 말했다.\n * 조용한 거짓말이다: 사용자는 계약전력을 넣고도 「이 값은 아무것도 바꾸지 않는다」는 문장을 읽었다.\n *\n * 효과를 새로 만든 것이 아니라 **있는 효과를 드러낸다.** `readBy` 는 실제로 읽는 자리를 가리킨다.\n */\n {\n id: 'contract.kW',\n axes: ['locations'],\n effect: 'twin.propeffect.contractKW',\n readBy: '@operato/twin-kernel EmsKernel.declaredContractKW (계약 대비 판정·초과 주의)'\n },\n {\n id: 'power.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.ratedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (가동 중 부하)'\n },\n {\n id: 'power.standbyKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.standbyKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveEquipmentLoad (대기 중 부하)'\n },\n {\n id: 'storage.capacityKWh',\n axes: ['equipment'],\n effect: 'twin.propeffect.capacityKWh',\n readBy: '@operato/twin-kernel EmsKernel (축전지 방전 가능량)'\n },\n {\n id: 'dispatch.dischargeAboveKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.dischargeAboveKW',\n readBy: '@operato/twin-kernel EmsKernel (방전 시작 문턱)'\n },\n {\n id: 'dispatch.maxDischargeKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.maxDischargeKW',\n readBy: '@operato/twin-kernel EmsKernel (인버터 방전 상한)'\n },\n {\n id: 'dispatch.reserveSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.reserveSoc',\n readBy: '@operato/twin-kernel EmsKernel (이 아래로는 방전하지 않는다)'\n },\n {\n id: 'storage.initialSoc',\n axes: ['equipment'],\n effect: 'twin.propeffect.initialSoc',\n readBy: '@operato/twin-kernel EmsKernel (시작 충전율 — 예측·what-if 의 씨앗)'\n },\n {\n id: 'generation.ratedKW',\n axes: ['equipment'],\n effect: 'twin.propeffect.genRatedKW',\n readBy: '@operato/twin-kernel EmsKernel.deriveGeneration (정격 발전)'\n },\n {\n id: 'generation.dailyProfile',\n axes: ['equipment'],\n effect: 'twin.propeffect.genDailyProfile',\n readBy: '@operato/twin-kernel generationFractionAt (시각별 발전 비율)'\n },\n /* 요금은 커널이 읽지 않는다 — 금액은 저널을 접는 호스트가 만든다. readBy 가 그 사실을 말한다. */\n {\n id: 'tariff.demandChargePerKW',\n axes: ['locations'],\n effect: 'twin.propeffect.demandChargePerKW',\n readBy: 'headless-twin/server/engine/kpi-query.ts (수요요금 계산)'\n },\n {\n id: 'tariff.energyChargePerKWh',\n axes: ['locations'],\n effect: 'twin.propeffect.energyChargePerKWh',\n readBy: 'headless-twin/server/engine/kpi-query.ts (전력량 요금 계산)'\n },\n /* 판정 기준 — 값이 경보의 유무를 바꾼다(가장 눈에 보이는 효과다). */\n {\n id: 'attention.congestionRatio',\n axes: ['locations'],\n effect: 'twin.propeffect.congestionRatio',\n readBy: '@operato/twin-kernel deriveAttentions (병목 판정 기준)'\n },\n {\n id: 'attention.scrapRate',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapRate',\n readBy: '@operato/twin-kernel deriveAttentions (불량 과다 판정 기준)'\n },\n {\n id: 'attention.scrapMinSamples',\n axes: ['equipment'],\n effect: 'twin.propeffect.scrapMinSamples',\n readBy: '@operato/twin-kernel deriveAttentions (불량률을 재기 시작할 표본 수)'\n },\n /*\n * ── 공정 소요시간 (2026-08-18) ────────────────────────────────────────────\n *\n * 실측: 운영 중인 트윈 35개 중 **창고·야드는 공정 명세 행이 0개**였다 — 그 트윈들의 시간은 전부 커널\n * 상수에서 왔고, 예측은 「같은 조건에서의 상대 비교」에 머물렀다(절대 시각 주장 불가). 이 선언이\n * 그 사다리를 한 칸 올린다: 실측이 없어도 **현장이 정한 시간**으로 굴러간다.\n *\n * 실측(이력에서 배운 값)은 여전히 이 선언을 이긴다 — 화면이 그 사실을 함께 말한다.\n */\n {\n id: 'operation.duration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationDuration',\n readBy: '@operato/twin-kernel FlowEngine.durationOf ← declareDurations (engine/local-declarations.ts 가 실어 보낸다)'\n },\n /*\n * ── 공정 모수 (2026-08-19) ────────────────────────────────────────────────\n * 양품률의 기본값은 커널 소스의 상수(0.8)였고, **그 상수가 불량 과다 주목 신호까지 만들었다** — 초기\n * 라인이든 성숙한 라인이든 같은 수로 판정된다. 셋업도 상수였다. 둘 다 현장이 말할 수 있어야 한다.\n */\n {\n id: 'operation.yield',\n axes: ['operations'],\n effect: 'twin.propeffect.operationYield',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.yield) ← declareParameters (양품/불량 판정과 산출량)'\n },\n {\n id: 'operation.setupDuration',\n axes: ['operations'],\n effect: 'twin.propeffect.operationSetup',\n readBy: '@operato/twin-kernel MesKernel(OP_PARAM.setupDuration) ← declareParameters (가동 앞에 붙는 셋업)'\n },\n {\n id: 'tariff.currency',\n axes: ['locations'],\n effect: 'twin.propeffect.currency',\n readBy: 'headless-twin/server/engine/kpi-query.ts (금액 표기 통화)'\n }\n]\n\n/**\n * 이 축의 속성 효과 — `속성 id → i18n 키`.\n *\n * 없는 속성은 **여기 없다**(빈 값으로 채우지 않는다) — 화면이 \"등록된 효과가 없다\" 를 스스로 말한다.\n */\nexport function propertyEffectsOf(axis: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const e of PROPERTY_EFFECTS) if (e.axes.includes(axis)) out[e.id] = e.effect\n return out\n}\n\n/** 등록부의 축 이름 → 선언이 겨누는 대상 이름. 대상이 아닌 축(자재 정의 등)은 여기 없다. */\nconst AXIS_TO_TARGET: Record<string, string> = { locations: 'location', equipment: 'equipment', operations: 'operation' }\n\n/**\n * 이 속성을 **어느 대상에 얹을 수 있나** — 등록부의 축이 그대로 답한다.\n *\n * ── 왜 필요한가 (2026-08-18) ────────────────────────────────────────────────\n * 지게차에 계약전력을 선언할 수 있었다. 문은 받아 주고 화면은 「넣었습니다」라고 말하는데, 그 값을 읽는\n * 코드는 자리(수전)만 본다 — 아무 일도 일어나지 않고 그 사실이 어디에도 나오지 않았다. 효과가 어느 축의\n * 것인지는 이미 등록부가 알고 있으므로, 그 지식을 검사에 쓴다(두 곳에 적지 않는다).\n *\n * 등록부에 없는 속성은 **비어 있는 목록이 아니라 `undefined`** 다: 「어디에도 못 얹는다」와 「우리가\n * 제한을 모른다」는 다르다(후자는 받아 준다 — 속성은 열려 있다는 규약을 지킨다).\n */\nexport function declarationTargetsOf(propertyId: string): string[] | undefined {\n const e = PROPERTY_EFFECTS.find(x => x.id === propertyId)\n if (!e) return undefined\n const targets = e.axes.map(a => AXIS_TO_TARGET[a]).filter((t): t is string => !!t)\n return targets.length ? targets : undefined\n}\n"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** 재기동 정책 — 셋뿐이다. */
|
|
2
|
+
export type RestartPolicy = 'resync' | 'resume' | 'reset';
|
|
3
|
+
export declare const RESTART_POLICIES: readonly RestartPolicy[];
|
|
4
|
+
/**
|
|
5
|
+
* 저장된 값을 정책으로 읽는다 — **모르면 오류를 낸다**(기본값으로 메우지 않는다).
|
|
6
|
+
*
|
|
7
|
+
* 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,
|
|
8
|
+
* 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.
|
|
9
|
+
* 그래서 이 함수는 옛 값에 대해서도 오류를 내며, 무엇으로 옮겨야 하는지 문장으로 말한다.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readRestartPolicy(raw: unknown, where: string): RestartPolicy;
|
|
12
|
+
/** 이 값이 정책인가 — 오류를 내지 않고 묻는 자리(화면·검사)를 위해. */
|
|
13
|
+
export declare function isRestartPolicy(raw: unknown): raw is RestartPolicy;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나.
|
|
4
|
+
*
|
|
5
|
+
* ── 왜 이 이름인가 (ADR-0029 · plans/simulator-as-source.md §2) ─────────────
|
|
6
|
+
* 옛 이름은 `realityMode` 였고 값은 `mirror`·`sim-world`·`sim-experiment` 였다. 그 축은 「현실이 어디서
|
|
7
|
+
* 오나」를 말하는 것처럼 보였지만, 코드가 그것으로 가른 것은 **재기동 거동 하나**였다. 그리고 「현실이
|
|
8
|
+
* 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) — 트윈은 한
|
|
9
|
+
* 종류이고 언제나 원본을 반영한다.
|
|
10
|
+
*
|
|
11
|
+
* 그래서 이 축은 사라지지 않고 **트윈에 남아 이름을 바꿨다.** 남는 이유: 「자기 저널을 잇는가」는 원본의
|
|
12
|
+
* 성질이 아니라 **트윈과 자기 과거의 관계**다. 같은 시뮬레이터 원본을 보는 두 트윈이 하나는 이어가고
|
|
13
|
+
* 하나는 매번 처음부터 재현할 수 있다 — 원본은 그 둘을 구별하지 못한다.
|
|
14
|
+
*
|
|
15
|
+
* resync — 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다).
|
|
16
|
+
* resume — 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).
|
|
17
|
+
* reset — 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험).
|
|
18
|
+
*
|
|
19
|
+
* ── 기본값을 두지 않는다 ────────────────────────────────────────────────────
|
|
20
|
+
* 예전에는 미선언이 조용히 `sim-experiment`(=저널 초기화)로 떨어졌다. 그 관용은 값이 하나 어긋나는
|
|
21
|
+
* 순간 **저널을 지우는 길**이 된다 — 개명 뒤 옛 값이 남아 있는 행을 그 길로 읽으면 재기동 한 번에
|
|
22
|
+
* 이력이 사라진다. 그래서 모르는 값은 **오류를 낸다.** 부르는 쪽이 선언하게 만드는 것이 이 축의 요점이다.
|
|
23
|
+
*/
|
|
24
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
|
+
exports.RESTART_POLICIES = void 0;
|
|
26
|
+
exports.readRestartPolicy = readRestartPolicy;
|
|
27
|
+
exports.isRestartPolicy = isRestartPolicy;
|
|
28
|
+
exports.RESTART_POLICIES = ['resync', 'resume', 'reset'];
|
|
29
|
+
/**
|
|
30
|
+
* 저장된 값을 정책으로 읽는다 — **모르면 오류를 낸다**(기본값으로 메우지 않는다).
|
|
31
|
+
*
|
|
32
|
+
* 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,
|
|
33
|
+
* 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.
|
|
34
|
+
* 그래서 이 함수는 옛 값에 대해서도 오류를 내며, 무엇으로 옮겨야 하는지 문장으로 말한다.
|
|
35
|
+
*/
|
|
36
|
+
function readRestartPolicy(raw, where) {
|
|
37
|
+
const value = typeof raw === 'string' ? raw.trim() : '';
|
|
38
|
+
if (exports.RESTART_POLICIES.includes(value))
|
|
39
|
+
return value;
|
|
40
|
+
const legacy = { mirror: 'resync', 'sim-world': 'resume', 'sim-experiment': 'reset' };
|
|
41
|
+
if (legacy[value]) {
|
|
42
|
+
throw new Error(`${where}: restart policy "${value}" is the old vocabulary — move the stored value to "${legacy[value]}" ` +
|
|
43
|
+
'(ADR-0029 · plans/simulator-as-source.md §2). Nothing is translated at read time on purpose: a translation would keep the old value alive.');
|
|
44
|
+
}
|
|
45
|
+
throw new Error(`${where}: restart policy ${value ? `"${value}"` : 'is missing'} — declare one of ${exports.RESTART_POLICIES.join(' | ')}. ` +
|
|
46
|
+
'There is no default: an unspecified policy used to fall through to "reset", which erases the journal on the next reboot.');
|
|
47
|
+
}
|
|
48
|
+
/** 이 값이 정책인가 — 오류를 내지 않고 묻는 자리(화면·검사)를 위해. */
|
|
49
|
+
function isRestartPolicy(raw) {
|
|
50
|
+
return typeof raw === 'string' && exports.RESTART_POLICIES.includes(raw);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=restart-policy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"restart-policy.js","sourceRoot":"","sources":["../../server/engine/restart-policy.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAcH,8CAcC;AAGD,0CAEC;AA5BY,QAAA,gBAAgB,GAA6B,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAU,CAAA;AAEhG;;;;;;GAMG;AACH,SAAgB,iBAAiB,CAAC,GAAY,EAAE,KAAa;IAC3D,MAAM,KAAK,GAAG,OAAO,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;IACvD,IAAK,wBAAsC,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO,KAAsB,CAAA;IAC1F,MAAM,MAAM,GAAkC,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,QAAQ,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAA;IACpH,IAAI,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,qBAAqB,KAAK,uDAAuD,MAAM,CAAC,KAAK,CAAC,IAAI;YACxG,4IAA4I,CAC/I,CAAA;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CACb,GAAG,KAAK,oBAAoB,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,qBAAqB,wBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI;QAClH,0HAA0H,CAC7H,CAAA;AACH,CAAC;AAED,8CAA8C;AAC9C,SAAgB,eAAe,CAAC,GAAY;IAC1C,OAAO,OAAO,GAAG,KAAK,QAAQ,IAAK,wBAAsC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAA;AACzF,CAAC","sourcesContent":["/*\n * **재기동 정책** — 이 트윈은 다시 세워질 때 자기 과거를 어떻게 대하나.\n *\n * ── 왜 이 이름인가 (ADR-0029 · plans/simulator-as-source.md §2) ─────────────\n * 옛 이름은 `realityMode` 였고 값은 `mirror`·`sim-world`·`sim-experiment` 였다. 그 축은 「현실이 어디서\n * 오나」를 말하는 것처럼 보였지만, 코드가 그것으로 가른 것은 **재기동 거동 하나**였다. 그리고 「현실이\n * 어디서 오나」는 ADR-0029 가 **원본의 종류**로 옮긴 물음이다(`TwinReference.adapterType`) — 트윈은 한\n * 종류이고 언제나 원본을 반영한다.\n *\n * 그래서 이 축은 사라지지 않고 **트윈에 남아 이름을 바꿨다.** 남는 이유: 「자기 저널을 잇는가」는 원본의\n * 성질이 아니라 **트윈과 자기 과거의 관계**다. 같은 시뮬레이터 원본을 보는 두 트윈이 하나는 이어가고\n * 하나는 매번 처음부터 재현할 수 있다 — 원본은 그 둘을 구별하지 못한다.\n *\n * resync — 외부 실물에서 다시 읽는다(원본이 진실이므로 우리 과거를 이어 붙이지 않는다).\n * resume — 저널을 이어 세운다(이 트윈이 낳은 타임라인이 곧 사실이다).\n * reset — 저널을 비우고 씨앗부터 다시 돌린다(재현 가능한 실험).\n *\n * ── 기본값을 두지 않는다 ────────────────────────────────────────────────────\n * 예전에는 미선언이 조용히 `sim-experiment`(=저널 초기화)로 떨어졌다. 그 관용은 값이 하나 어긋나는\n * 순간 **저널을 지우는 길**이 된다 — 개명 뒤 옛 값이 남아 있는 행을 그 길로 읽으면 재기동 한 번에\n * 이력이 사라진다. 그래서 모르는 값은 **오류를 낸다.** 부르는 쪽이 선언하게 만드는 것이 이 축의 요점이다.\n */\n\n/** 재기동 정책 — 셋뿐이다. */\nexport type RestartPolicy = 'resync' | 'resume' | 'reset'\n\nexport const RESTART_POLICIES: readonly RestartPolicy[] = ['resync', 'resume', 'reset'] as const\n\n/**\n * 저장된 값을 정책으로 읽는다 — **모르면 오류를 낸다**(기본값으로 메우지 않는다).\n *\n * 옛 어휘(`mirror`·`sim-world`·`sim-experiment`)를 여기서 번역하지 않는다: 번역을 두면 옛 값이 살아남고,\n * 그 값이 다시 어딘가에 저장된다. 저장된 값은 **한 번 옮기고**(데이터 이관) 코드는 새 어휘만 안다.\n * 그래서 이 함수는 옛 값에 대해서도 오류를 내며, 무엇으로 옮겨야 하는지 문장으로 말한다.\n */\nexport function readRestartPolicy(raw: unknown, where: string): RestartPolicy {\n const value = typeof raw === 'string' ? raw.trim() : ''\n if ((RESTART_POLICIES as readonly string[]).includes(value)) return value as RestartPolicy\n const legacy: Record<string, RestartPolicy> = { mirror: 'resync', 'sim-world': 'resume', 'sim-experiment': 'reset' }\n if (legacy[value]) {\n throw new Error(\n `${where}: restart policy \"${value}\" is the old vocabulary — move the stored value to \"${legacy[value]}\" ` +\n '(ADR-0029 · plans/simulator-as-source.md §2). Nothing is translated at read time on purpose: a translation would keep the old value alive.'\n )\n }\n throw new Error(\n `${where}: restart policy ${value ? `\"${value}\"` : 'is missing'} — declare one of ${RESTART_POLICIES.join(' | ')}. ` +\n 'There is no default: an unspecified policy used to fall through to \"reset\", which erases the journal on the next reboot.'\n )\n}\n\n/** 이 값이 정책인가 — 오류를 내지 않고 묻는 자리(화면·검사)를 위해. */\nexport function isRestartPolicy(raw: unknown): raw is RestartPolicy {\n return typeof raw === 'string' && (RESTART_POLICIES as readonly string[]).includes(raw)\n}\n"]}
|