@things-factory/headless-twin 10.0.18 β 10.1.0
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 +10 -1
- package/dist-server/engine/canonical-ingest.js +110 -33
- package/dist-server/engine/canonical-ingest.js.map +1 -1
- package/dist-server/engine/event-time.d.ts +25 -0
- package/dist-server/engine/event-time.js +98 -0
- package/dist-server/engine/event-time.js.map +1 -0
- package/dist-server/engine/index.d.ts +2 -0
- package/dist-server/engine/index.js +23 -0
- package/dist-server/engine/index.js.map +1 -1
- package/dist-server/engine/ingest-dedupe.d.ts +18 -6
- package/dist-server/engine/ingest-dedupe.js +201 -15
- package/dist-server/engine/ingest-dedupe.js.map +1 -1
- package/dist-server/engine/ingest-health.d.ts +60 -1
- package/dist-server/engine/ingest-health.js +43 -8
- package/dist-server/engine/ingest-health.js.map +1 -1
- package/dist-server/engine/kpi-fold.d.ts +19 -0
- package/dist-server/engine/kpi-fold.js +4 -1
- package/dist-server/engine/kpi-fold.js.map +1 -1
- package/dist-server/engine/kpi-query.js +23 -3
- package/dist-server/engine/kpi-query.js.map +1 -1
- package/dist-server/engine/oee-accumulator.d.ts +35 -2
- package/dist-server/engine/oee-accumulator.js +56 -4
- package/dist-server/engine/oee-accumulator.js.map +1 -1
- package/dist-server/engine/twin-engine.d.ts +72 -3
- package/dist-server/engine/twin-engine.js +118 -5
- package/dist-server/engine/twin-engine.js.map +1 -1
- package/dist-server/index.js +21 -0
- package/dist-server/index.js.map +1 -1
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.d.ts +5 -0
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js +66 -0
- package/dist-server/migrations/1786600000000-RenameOperatoMesAdapterType.js.map +1 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.d.ts +5 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js +79 -0
- package/dist-server/migrations/1786700000000-AddDispatchRetryColumns.js.map +1 -0
- package/dist-server/migrations/index.js +5 -1
- package/dist-server/migrations/index.js.map +1 -1
- package/dist-server/routes.js +19 -16
- package/dist-server/routes.js.map +1 -1
- package/dist-server/service/actuation/actuation-advisor.d.ts +60 -0
- package/dist-server/service/actuation/actuation-advisor.js +53 -0
- package/dist-server/service/actuation/actuation-advisor.js.map +1 -0
- package/dist-server/service/actuation/actuation-outcome.d.ts +52 -0
- package/dist-server/service/actuation/actuation-outcome.js +34 -0
- package/dist-server/service/actuation/actuation-outcome.js.map +1 -0
- package/dist-server/service/actuation/actuation-rule-resolver.d.ts +19 -0
- package/dist-server/service/actuation/actuation-rule-resolver.js +161 -0
- package/dist-server/service/actuation/actuation-rule-resolver.js.map +1 -0
- package/dist-server/service/actuation/actuation-rule.d.ts +55 -0
- package/dist-server/service/actuation/actuation-rule.js +130 -0
- package/dist-server/service/actuation/actuation-rule.js.map +1 -0
- package/dist-server/service/actuation/approval-gateway.d.ts +20 -0
- package/dist-server/service/actuation/approval-gateway.js +14 -0
- package/dist-server/service/actuation/approval-gateway.js.map +1 -0
- package/dist-server/service/actuation/catch-up-effects.d.ts +16 -0
- package/dist-server/service/actuation/catch-up-effects.js +120 -0
- package/dist-server/service/actuation/catch-up-effects.js.map +1 -0
- package/dist-server/service/actuation/command-dispatcher.d.ts +230 -0
- package/dist-server/service/actuation/command-dispatcher.js +269 -0
- package/dist-server/service/actuation/command-dispatcher.js.map +1 -0
- package/dist-server/service/actuation/command-store.d.ts +11 -0
- package/dist-server/service/actuation/command-store.js +219 -0
- package/dist-server/service/actuation/command-store.js.map +1 -0
- package/dist-server/service/actuation/dispatch-after-approval.d.ts +11 -0
- package/dist-server/service/actuation/dispatch-after-approval.js +73 -0
- package/dist-server/service/actuation/dispatch-after-approval.js.map +1 -0
- package/dist-server/service/actuation/effect-match.d.ts +53 -0
- package/dist-server/service/actuation/effect-match.js +65 -0
- package/dist-server/service/actuation/effect-match.js.map +1 -0
- package/dist-server/service/actuation/index.d.ts +25 -0
- package/dist-server/service/actuation/index.js +30 -0
- package/dist-server/service/actuation/index.js.map +1 -0
- package/dist-server/service/actuation/retry-plan.d.ts +63 -0
- package/dist-server/service/actuation/retry-plan.js +99 -0
- package/dist-server/service/actuation/retry-plan.js.map +1 -0
- package/dist-server/service/actuation/rule-evaluate.d.ts +127 -0
- package/dist-server/service/actuation/rule-evaluate.js +205 -0
- package/dist-server/service/actuation/rule-evaluate.js.map +1 -0
- package/dist-server/service/actuation/rule-loop.d.ts +5 -0
- package/dist-server/service/actuation/rule-loop.js +156 -0
- package/dist-server/service/actuation/rule-loop.js.map +1 -0
- package/dist-server/service/actuation/rule-runner.d.ts +52 -0
- package/dist-server/service/actuation/rule-runner.js +135 -0
- package/dist-server/service/actuation/rule-runner.js.map +1 -0
- package/dist-server/service/actuation/rule-wiring.d.ts +24 -0
- package/dist-server/service/actuation/rule-wiring.js +80 -0
- package/dist-server/service/actuation/rule-wiring.js.map +1 -0
- package/dist-server/service/actuation/settle-effects.d.ts +21 -0
- package/dist-server/service/actuation/settle-effects.js +101 -0
- package/dist-server/service/actuation/settle-effects.js.map +1 -0
- package/dist-server/service/actuation/sweep-dispatch.d.ts +37 -0
- package/dist-server/service/actuation/sweep-dispatch.js +125 -0
- package/dist-server/service/actuation/sweep-dispatch.js.map +1 -0
- package/dist-server/service/actuation/twin-command-resolver.d.ts +120 -0
- package/dist-server/service/actuation/twin-command-resolver.js +406 -0
- package/dist-server/service/actuation/twin-command-resolver.js.map +1 -0
- package/dist-server/service/actuation/twin-command.d.ts +111 -0
- package/dist-server/service/actuation/twin-command.js +170 -0
- package/dist-server/service/actuation/twin-command.js.map +1 -0
- package/dist-server/service/actuation/twin-now.d.ts +41 -0
- package/dist-server/service/actuation/twin-now.js +65 -0
- package/dist-server/service/actuation/twin-now.js.map +1 -0
- package/dist-server/service/index.d.ts +5 -2
- package/dist-server/service/index.js +28 -17
- package/dist-server/service/index.js.map +1 -1
- package/dist-server/service/reference/actuation-routing.d.ts +60 -0
- package/dist-server/service/reference/actuation-routing.js +126 -0
- package/dist-server/service/reference/actuation-routing.js.map +1 -0
- package/dist-server/service/reference/actuation-target.d.ts +65 -0
- package/dist-server/service/reference/actuation-target.js +94 -0
- package/dist-server/service/reference/actuation-target.js.map +1 -0
- package/dist-server/service/reference/connection-portability-resolver.d.ts +29 -0
- package/dist-server/service/reference/connection-portability-resolver.js +181 -0
- package/dist-server/service/reference/connection-portability-resolver.js.map +1 -0
- package/dist-server/service/reference/connection-portability.d.ts +138 -0
- package/dist-server/service/reference/connection-portability.js +230 -0
- package/dist-server/service/reference/connection-portability.js.map +1 -0
- package/dist-server/service/reference/fill-cadence.d.ts +17 -0
- package/dist-server/service/reference/fill-cadence.js +31 -0
- package/dist-server/service/reference/fill-cadence.js.map +1 -0
- package/dist-server/service/reference/fill-dedupe.d.ts +16 -0
- package/dist-server/service/reference/fill-dedupe.js +63 -0
- package/dist-server/service/reference/fill-dedupe.js.map +1 -0
- package/dist-server/service/reference/fill-loop.d.ts +35 -0
- package/dist-server/service/reference/fill-loop.js +187 -0
- package/dist-server/service/reference/fill-loop.js.map +1 -0
- package/dist-server/service/reference/fill-span.d.ts +10 -0
- package/dist-server/service/reference/fill-span.js +37 -0
- package/dist-server/service/reference/fill-span.js.map +1 -0
- package/dist-server/service/reference/hook-contract.d.ts +149 -22
- package/dist-server/service/reference/hook-contract.js +236 -23
- package/dist-server/service/reference/hook-contract.js.map +1 -1
- package/dist-server/service/reference/hook-rejected.d.ts +25 -0
- package/dist-server/service/reference/hook-rejected.js +48 -0
- package/dist-server/service/reference/hook-rejected.js.map +1 -0
- package/dist-server/service/reference/hook-store.d.ts +3 -0
- package/dist-server/service/reference/hook-store.js +28 -0
- package/dist-server/service/reference/hook-store.js.map +1 -0
- package/dist-server/service/reference/index.d.ts +4 -1
- package/dist-server/service/reference/index.js +5 -1
- package/dist-server/service/reference/index.js.map +1 -1
- package/dist-server/service/reference/live-cadence.d.ts +68 -0
- package/dist-server/service/reference/live-cadence.js +44 -0
- package/dist-server/service/reference/live-cadence.js.map +1 -0
- package/dist-server/service/reference/live-feed-lease.d.ts +15 -0
- package/dist-server/service/reference/live-feed-lease.js +56 -0
- package/dist-server/service/reference/live-feed-lease.js.map +1 -0
- package/dist-server/service/reference/live-ingest.d.ts +21 -0
- package/dist-server/service/reference/live-ingest.js +66 -0
- package/dist-server/service/reference/live-ingest.js.map +1 -0
- package/dist-server/service/reference/reference-adapter.d.ts +355 -2
- package/dist-server/service/reference/reference-adapter.js +19 -1
- package/dist-server/service/reference/reference-adapter.js.map +1 -1
- package/dist-server/service/reference/reference-fill-run.d.ts +50 -0
- package/dist-server/service/reference/reference-fill-run.js +142 -0
- package/dist-server/service/reference/reference-fill-run.js.map +1 -0
- package/dist-server/service/reference/reference-fill.d.ts +90 -0
- package/dist-server/service/reference/reference-fill.js +141 -0
- package/dist-server/service/reference/reference-fill.js.map +1 -0
- package/dist-server/service/reference/reference-hook.d.ts +64 -6
- package/dist-server/service/reference/reference-hook.js +265 -27
- package/dist-server/service/reference/reference-hook.js.map +1 -1
- package/dist-server/service/reference/reference-link-groups.d.ts +10 -0
- package/dist-server/service/reference/reference-link-groups.js +15 -0
- package/dist-server/service/reference/reference-link-groups.js.map +1 -0
- package/dist-server/service/reference/reference-links.d.ts +14 -0
- package/dist-server/service/reference/reference-links.js +73 -0
- package/dist-server/service/reference/reference-links.js.map +1 -0
- package/dist-server/service/reference/reference-live.d.ts +1 -0
- package/dist-server/service/reference/reference-live.js +32 -6
- package/dist-server/service/reference/reference-live.js.map +1 -1
- package/dist-server/service/reference/reference-master.d.ts +56 -2
- package/dist-server/service/reference/reference-master.js +33 -3
- package/dist-server/service/reference/reference-master.js.map +1 -1
- package/dist-server/service/reference/reference-resolver.d.ts +27 -0
- package/dist-server/service/reference/reference-resolver.js +79 -1
- package/dist-server/service/reference/reference-resolver.js.map +1 -1
- package/dist-server/service/twin-event/twin-event-keys.d.ts +31 -3
- package/dist-server/service/twin-event/twin-event-keys.js +36 -4
- package/dist-server/service/twin-event/twin-event-keys.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 +11 -2
- package/dist-server/service/twin-event/twin-event.js.map +1 -1
- package/dist-server/service/twin-model/choose-recipe.d.ts +15 -0
- package/dist-server/service/twin-model/choose-recipe.js +33 -0
- package/dist-server/service/twin-model/choose-recipe.js.map +1 -0
- package/dist-server/service/twin-model/external-resolver.d.ts +76 -0
- package/dist-server/service/twin-model/external-resolver.js +109 -0
- package/dist-server/service/twin-model/external-resolver.js.map +1 -0
- package/dist-server/service/twin-model/project-structure.js +16 -3
- package/dist-server/service/twin-model/project-structure.js.map +1 -1
- package/dist-server/service/twin-model/twin-equipment.d.ts +17 -0
- package/dist-server/service/twin-model/twin-equipment.js +10 -0
- package/dist-server/service/twin-model/twin-equipment.js.map +1 -1
- package/dist-server/service/twin-model/twin-lineage-query.js +75 -1
- package/dist-server/service/twin-model/twin-lineage-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-item-query.js +94 -5
- package/dist-server/service/twin-model/twin-model-item-query.js.map +1 -1
- package/dist-server/service/twin-model/twin-model-tree-query.js.map +1 -1
- package/dist-server/service/twin-space/twin-space.d.ts +29 -0
- package/dist-server/service/twin-space/twin-space.js +13 -0
- package/dist-server/service/twin-space/twin-space.js.map +1 -1
- package/dist-server/service/twin-subject/event-subjects.d.ts +3 -1
- package/dist-server/service/twin-subject/event-subjects.js +8 -1
- package/dist-server/service/twin-subject/event-subjects.js.map +1 -1
- package/dist-shared/entity-delta.js +1 -1
- package/dist-shared/entity-delta.js.map +1 -1
- package/package.json +7 -7
- package/server/engine/canonical-ingest.ts +119 -33
- package/server/engine/event-time.ts +96 -0
- package/server/engine/index.ts +20 -0
- package/server/engine/ingest-dedupe.ts +203 -27
- package/server/engine/ingest-health.ts +91 -6
- package/server/engine/kpi-fold.ts +24 -1
- package/server/engine/kpi-query.ts +23 -4
- package/server/engine/oee-accumulator.ts +80 -8
- package/server/engine/twin-engine.ts +119 -5
- package/server/index.ts +23 -0
- package/server/migrations/1786600000000-RenameOperatoMesAdapterType.ts +66 -0
- package/server/migrations/1786700000000-AddDispatchRetryColumns.ts +82 -0
- package/server/migrations/index.ts +5 -1
- package/server/routes.ts +19 -20
- package/server/service/actuation/actuation-advisor.ts +124 -0
- package/server/service/actuation/actuation-outcome.ts +97 -0
- package/server/service/actuation/actuation-rule-resolver.ts +147 -0
- package/server/service/actuation/actuation-rule.ts +139 -0
- package/server/service/actuation/approval-gateway.ts +44 -0
- package/server/service/actuation/catch-up-effects.ts +152 -0
- package/server/service/actuation/command-dispatcher.ts +511 -0
- package/server/service/actuation/command-store.ts +236 -0
- package/server/service/actuation/dispatch-after-approval.ts +71 -0
- package/server/service/actuation/effect-match.ts +124 -0
- package/server/service/actuation/index.ts +27 -0
- package/server/service/actuation/retry-plan.ts +145 -0
- package/server/service/actuation/rule-evaluate.ts +352 -0
- package/server/service/actuation/rule-loop.ts +160 -0
- package/server/service/actuation/rule-runner.ts +216 -0
- package/server/service/actuation/rule-wiring.ts +84 -0
- package/server/service/actuation/settle-effects.ts +115 -0
- package/server/service/actuation/sweep-dispatch.ts +179 -0
- package/server/service/actuation/twin-command-resolver.ts +387 -0
- package/server/service/actuation/twin-command.ts +232 -0
- package/server/service/actuation/twin-now.ts +97 -0
- package/server/service/index.ts +11 -0
- package/server/service/reference/actuation-routing.ts +165 -0
- package/server/service/reference/actuation-target.ts +163 -0
- package/server/service/reference/connection-portability-resolver.ts +171 -0
- package/server/service/reference/connection-portability.ts +285 -0
- package/server/service/reference/fill-cadence.ts +29 -0
- package/server/service/reference/fill-dedupe.ts +66 -0
- package/server/service/reference/fill-loop.ts +153 -0
- package/server/service/reference/fill-span.ts +31 -0
- package/server/service/reference/hook-contract.ts +280 -22
- package/server/service/reference/hook-rejected.ts +53 -0
- package/server/service/reference/hook-store.ts +28 -0
- package/server/service/reference/index.ts +5 -1
- package/server/service/reference/live-cadence.ts +129 -0
- package/server/service/reference/live-feed-lease.ts +56 -0
- package/server/service/reference/live-ingest.ts +94 -0
- package/server/service/reference/reference-adapter.ts +392 -5
- package/server/service/reference/reference-fill-run.ts +165 -0
- package/server/service/reference/reference-fill.ts +223 -0
- package/server/service/reference/reference-hook.ts +355 -32
- package/server/service/reference/reference-link-groups.ts +21 -0
- package/server/service/reference/reference-links.ts +77 -0
- package/server/service/reference/reference-live.ts +30 -5
- package/server/service/reference/reference-master.ts +90 -5
- package/server/service/reference/reference-resolver.ts +77 -2
- package/server/service/twin-event/twin-event-keys.ts +37 -4
- package/server/service/twin-event/twin-event.ts +18 -1
- package/server/service/twin-model/choose-recipe.ts +31 -0
- package/server/service/twin-model/external-resolver.ts +161 -0
- package/server/service/twin-model/project-structure.ts +16 -3
- package/server/service/twin-model/twin-equipment.ts +22 -0
- package/server/service/twin-model/twin-lineage-query.ts +82 -1
- package/server/service/twin-model/twin-model-item-query.ts +95 -4
- package/server/service/twin-model/twin-model-tree-query.ts +1 -1
- package/server/service/twin-space/twin-space.ts +39 -0
- package/server/service/twin-subject/event-subjects.ts +8 -1
- package/shared/entity-delta.ts +1 -1
- package/test/ack-shape-alignment.test.ts +97 -0
- package/test/actuation-approval-door.test.ts +421 -0
- package/test/actuation-dispatch.test.ts +209 -0
- package/test/actuation-effect.test.ts +162 -0
- package/test/actuation-outcome.test.ts +152 -0
- package/test/actuation-rule-runner.test.ts +281 -0
- package/test/actuation-rule.test.ts +266 -0
- package/test/actuation-seam.test.ts +249 -0
- package/test/actuation-settle.test.ts +164 -0
- package/test/actuation-target.test.ts +145 -0
- package/test/canonical-ingest-vocabularies.test.ts +49 -2
- package/test/capability-mapping.test.ts +1 -1
- package/test/choose-recipe.test.ts +117 -0
- package/test/command-store-writes-columns.test.ts +126 -0
- package/test/connection-portability-doors.test.ts +87 -0
- package/test/connector-capability-declaration.test.ts +54 -4
- package/test/declaration-reaches-model.test.ts +43 -0
- package/test/dedupe-transformation.test.ts +112 -0
- package/test/entity-delta.test.ts +1 -1
- package/test/event-subjects.test.ts +2 -1
- package/test/external-resolver.test.ts +171 -0
- package/test/fill-dedupe.test.ts +59 -0
- package/test/fill-loop.test.ts +35 -0
- package/test/first-envelope.test.ts +153 -0
- package/test/fold-shift-dst.test.ts +110 -0
- package/test/home-in-area.test.ts +90 -0
- package/test/hook-rejected-shape.test.ts +86 -0
- package/test/hook-response-sum.test.ts +193 -0
- package/test/hook-sequence.test.ts +333 -0
- package/test/ingest-dedupe.test.ts +56 -0
- package/test/ingest-expected-quiet.test.ts +111 -0
- package/test/ingest-idempotent.test.ts +79 -0
- package/test/ingest-reconcile-four-ways.test.ts +97 -0
- package/test/ingest-rules-header.test.ts +100 -0
- package/test/ingest-window-rolls-unobserved.test.ts +72 -0
- package/test/intake-mapping-parity.test.ts +175 -0
- package/test/live-cadence.test.ts +170 -0
- package/test/live-feed-lease.test.ts +63 -0
- package/test/live-mirror-parity.test.ts +1 -1
- package/test/oee-accumulator.test.ts +3 -3
- package/test/oee-site-calendar.test.ts +146 -0
- package/test/order-and-transaction-split.test.ts +107 -0
- package/test/reference-fill.test.ts +235 -0
- package/test/reference-hook.test.ts +114 -23
- package/test/reference-links.test.ts +56 -0
- package/test/retry-plan.test.ts +139 -0
- package/test/sweep-dispatch.test.ts +191 -0
- package/test/twin-event-keys.test.ts +20 -6
- package/test/twin-model-item-db.test.ts +6 -6
- package/test/twin-model-tree-db.test.ts +6 -6
- package/tsconfig.shared.tsbuildinfo +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* **κ΄μΈ‘μμ μ‘°μΉλ₯Ό λΈλ€** β κ·Έ νμ νλ.
|
|
3
|
+
*
|
|
4
|
+
* ββ 무μμ΄ μμλ (2026-09-01) βββββββββββββββββββββββββββββββββββββββββββ
|
|
5
|
+
* μΉμΈ κ²μ΄νΈλ₯Ό μΈμ°κ³ λλ **λΌ μ μλ κ²μ΄ μ¬λλΏ**μ΄μλ€. κ³μ½μ λΈ μͺ½μ λ€ κ°μ§λ‘ λλλ°
|
|
6
|
+
* (`operator` Β· `rule` Β· `ai` Β· `schedule`) μ¬λ λ§κ³ λ κΈΈμ΄ μμλ€. κ²μ΄νΈμ κ°μ΄μΉλ νΈμμ΄ μ€μ€λ‘
|
|
7
|
+
* μμμ± κ²μ μ¬λμ΄ νλ¨νλ λ° μλλ°, μμμ± κ²μ μ¬λ¦΄ μλ¦¬κ° μμλ€.
|
|
8
|
+
*
|
|
9
|
+
* ββ 쑰건μ μλ‘ λ§λ€μ§ μλλ€ ββββββββββββββββββββββββββββββββββββββββββββ
|
|
10
|
+
* 컀λμ΄ μ΄λ―Έ μνμμ γλ΄μΌ ν κ²γμ νλ¨ν΄ `Attention` μΌλ‘ λΈλ€ β μμ λ μ’
λ₯ μ½λ(`kind`),
|
|
11
|
+
* 무μμ λν κ²μΈμ§(`anchor`), μΈμ λΆν°μΈμ§(`timeStamp`), ννμ© κ°(`params`). 쑰건 μΈμ΄λ₯Ό μλ‘ λ§λ€λ©΄
|
|
12
|
+
* κ°μ νλ¨μ΄ λ λ²μ΄ λκ³ , νμͺ½λ§ κ³ μ³μ§λ λ μ΄ μ¨λ€.
|
|
13
|
+
*
|
|
14
|
+
* κ·Έλμ κ·μΉμ **μ΄λ μ£Όμμ μ΄λ μ§μλ₯Ό λΆμ΄λκ°**λ§ μ νλ€.
|
|
15
|
+
*
|
|
16
|
+
* ββ μκ·Ήκ³Ό μ§μλ₯Ό μμ§ μλλ€ ββββββββββββββββββββββββββββββββββββββββββββ
|
|
17
|
+
* μ£Όμμλ `suggestedAction.command` κ° μ€λ € μμ§λ§ κ·Έκ²μ **컀λ μμμ λλ μκ·Ή**μ΄λ€
|
|
18
|
+
* (`resource.repair` = λͺ¨λΈμμ μ리λ κ²μΌλ‘ λλ€). μ¬κΈ°μ λ΄λ κ²μ νμ₯μ λκ°λ μ§μλ€. κ·Έ κ°μ
|
|
19
|
+
* κ·Έλλ‘ μ°λ©΄ γλͺ¨λΈμ κ·Έλ κ² λλΌγκ° γμ¬λμ 보λ΄λΌγκ° λλ€(Β§`propose` μ κ±°μ ).
|
|
20
|
+
*
|
|
21
|
+
* ββ 무λμ§λ κ² λ· βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
22
|
+
* ν볡ν κΈΈμ μ§§λ€. μ€μ λ‘ λ¬΄λμ§λ κ²μ μ΄κ²λ€μ΄λ€.
|
|
23
|
+
*
|
|
24
|
+
* κ°μ μ‘°κ±΄μ΄ λ§€ μ£ΌκΈ° λ€μ μ¬λΌμ¨λ€ λκΈ° μ€μ κ°μ μ§μκ° μμ κ° μ λ€
|
|
25
|
+
* λ°©κΈ μκΈ΄ 쑰건μ λ°λ‘ μ¬λ¦°λ€ κΉλΉ‘μΈ κ²μ νμ₯ μ§μκ° λκ°λ€
|
|
26
|
+
* μ‘°κ±΄μ΄ μ¬λΌμ§ λ€μλ λκΈ°μ λ¨λλ€ μΉμΈνλ μ¬λμ΄ μλ λ¬Έμ λ₯Ό νλ¨νλ€
|
|
27
|
+
* ν λ²μ μλ°± κ°κ° μ¬λΌμ¨λ€ μ¬λμ΄ μ½μ μ μκ³ , μ½μ§ μμΌλ©΄ κ²μ΄νΈκ° μλμ΄ λλ€
|
|
28
|
+
*
|
|
29
|
+
* λ· λ€ μ€λ₯κ° λμ§ μλλ€. κ·Έλμ μ΄ νμΌμ΄ λ·μ λ€λ£¬λ€.
|
|
30
|
+
*/
|
|
31
|
+
import type { CommandTypeSpec } from '@operato/ops-contract'
|
|
32
|
+
|
|
33
|
+
/** μ΄λ μ£Όμμ μ΄λ μ§μλ₯Ό λΆμ΄λκ° β μ μΈ ν μ€. */
|
|
34
|
+
export interface ActuationRuleSpec {
|
|
35
|
+
id: string
|
|
36
|
+
/** μ΄ νΈμμλ§ μ μ©λλ€. νΈμλ§λ€ λΌμΈμ΄ λ€λ₯΄λ―λ‘ μ μ κ·μΉμ λμ§ μλλ€. */
|
|
37
|
+
instanceId: string
|
|
38
|
+
/** μ΄λ μ£ΌμμΈκ° β 컀λμ μμ μ½λ(`Attention.kind`). */
|
|
39
|
+
attentionKind: string
|
|
40
|
+
/** μ΄ μ¬κ°λ μ΄μμΌ λλ§ β μμΌλ©΄ μ’
λ₯λ§ λ³Έλ€. */
|
|
41
|
+
minSeverity?: 'low' | 'medium' | 'high' | 'critical'
|
|
42
|
+
/**
|
|
43
|
+
* **μ‘°κ±΄μ΄ μ΄λ§νΌ μ΄μ΄μ‘μ λ** μ¬λ¦°λ€.
|
|
44
|
+
*
|
|
45
|
+
* μμΌλ©΄ μ²μ λ³Έ μ£ΌκΈ°μ λ°λ‘ μ¬λ¦°λ€. κΉλΉ‘μ΄λ 쑰건μ νμ₯ μ§μκ° λκ°λ κ²μ λ§μΌλ €λ©΄ μ΄ κ°μ΄ νμνλ€
|
|
46
|
+
* β 컀λμ΄ μ‘°κ±΄μ΄ μ²μ μ±λ¦½ν μκ°μ κΈ°μ΅νλ―λ‘(`Attention.timeStamp`) μ¬κΈ°μ μ΄ μ μλ€.
|
|
47
|
+
*/
|
|
48
|
+
minHeldMs?: number
|
|
49
|
+
/** 무μμ λ΄λκ° β μ μΈλ μ‘°μΉ μ’
λ₯(`CommandTypeSpec.type`). 컀λ μκ·Ήμ΄ μλλ€. */
|
|
50
|
+
commandType: string
|
|
51
|
+
/**
|
|
52
|
+
* μ§μμ λ΄μ© β `{μ΄λ¦}` μ μ£Όμμ κ°μΌλ‘ λ°κΏ λ£λλ€.
|
|
53
|
+
*
|
|
54
|
+
* λ°κΏ κ°μ `Attention.params` μ `anchor` μμ μ¨λ€(`{locationId}` Β· `{ratioPct}`). ν κ²Ή μ€κ΄νΈλ₯Ό
|
|
55
|
+
* μ°λ κ²μ μ΄ μ μ₯μμ i18n κ΄λ‘μ κ°λ€.
|
|
56
|
+
*
|
|
57
|
+
* λ°κΏ κ²μ΄ μμΌλ©΄ **κ·Έ μ리λ₯Ό λΉμ΄λ€** β μ§μ΄λΈ κ°μ λ£μ§ μλλ€. μꡬνλ μΉΈμ΄ λΉλ©΄ κ³μ½μ΄ κ±°μ νκ³
|
|
58
|
+
* (Β§`missingCommandPayload`) κ·Έ κ±°μ μ΄ μ¬λμκ² μ ν΄μ§λ€.
|
|
59
|
+
*/
|
|
60
|
+
payloadTemplate?: Record<string, unknown>
|
|
61
|
+
/**
|
|
62
|
+
* **ν λ² λΈ κ·Όκ±°μ λ€μ λ΄κΈ°κΉμ§ κΈ°λ€λ¦¬λ μκ°.**
|
|
63
|
+
*
|
|
64
|
+
* μ΄κ²μ΄ μμΌλ©΄ μΉμΈνλ μκ° κ·μΉμ΄ κ°μ κ²μ λ λΈλ€ β μ€μ λ‘ κ·Έλ¬λ€. γκ²°μ μ κΈ°λ€λ¦¬λ κ²γλ§ λ³΄κ³
|
|
65
|
+
* μ€λ³΅μ λ§μλλ°, μΉμΈνλ©΄ κ·Έ 컀맨λκ° λκΈ°μμ λΉ μ§κ³ 쑰건μ κ·Έλλ‘ μ μκΈ° λλ¬Έμ΄λ€. μ¬λμ΄
|
|
66
|
+
* μΉμΈν μλ‘ λκΈ° μ€μ΄ λ€μ μ°¨λ λͺ¨μμ΄ λλ€.
|
|
67
|
+
*
|
|
68
|
+
* 쑰건μ μ΄μ΄μ§λ κ²μ΄κ³ μ‘°μΉλ μκ°μ΄ κ±Έλ¦°λ€. λΈ μ‘°μΉκ° ν¨κ³Όλ₯Ό λΌ λκΉμ§ κΈ°λ€λ¦¬λ μκ°μ΄ μ΄ κ°μ΄λ€.
|
|
69
|
+
*/
|
|
70
|
+
cooldownMs?: number
|
|
71
|
+
/** κΊΌ λ κ·μΉμ νμ νμ§ μλλ€. μ§μ°μ§ μκ³ λ μ μμ΄μΌ νλ€. */
|
|
72
|
+
enabled?: boolean
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** νμ μ λ€μ΄κ°λ μ£Όμ β 컀λμ΄ λΈ λͺ¨μ κ·Έλλ‘ μ€ νμν κ²λ§. */
|
|
76
|
+
export interface AttentionLike {
|
|
77
|
+
id: string
|
|
78
|
+
kind: string
|
|
79
|
+
severity: string
|
|
80
|
+
state?: string
|
|
81
|
+
anchor?: { locationId?: string; moverId?: string; orderId?: string; operation?: string }
|
|
82
|
+
timeStamp?: string
|
|
83
|
+
params?: Record<string, string | number>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** μ¬λ¦΄ κ² νλ. */
|
|
87
|
+
export interface RuleProposal {
|
|
88
|
+
ruleId: string
|
|
89
|
+
attentionId: string
|
|
90
|
+
attentionKind: string
|
|
91
|
+
commandType: string
|
|
92
|
+
payload: Record<string, unknown>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** μ¬λ¦¬μ§ μμ κ²κ³Ό κ·Έ μ΄μ β **λ§νμ§ μμΌλ©΄ μ¬λμ κ·μΉμ΄ λλ μ€ μλ€.** */
|
|
96
|
+
export interface RuleSkip {
|
|
97
|
+
ruleId: string
|
|
98
|
+
attentionId?: string
|
|
99
|
+
reason: string
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface RuleVerdict {
|
|
103
|
+
proposals: RuleProposal[]
|
|
104
|
+
/** μ μ μ¬λ Έλ. μ‘°μ©ν λμ΄κ°μ§ μλλ€. */
|
|
105
|
+
skips: RuleSkip[]
|
|
106
|
+
/** μνμ λΏμ μλλ β μλμΌλ©΄ λͺ κ°λ₯Ό λκ³ μλ. */
|
|
107
|
+
withheld?: number
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const SEVERITY_ORDER = ['low', 'medium', 'high', 'critical'] as const
|
|
111
|
+
|
|
112
|
+
/** μ¬κ°λκ° λ¬Έν±μ λΏλ β λͺ¨λ₯΄λ κ°μ λΏμ§ μλ κ²μΌλ‘ λ³Έλ€(λͺ¨λ¦μ ν΅κ³Όλ‘ μ½μ§ μλλ€). */
|
|
113
|
+
function severityMeets(actual: string, min: string | undefined): boolean {
|
|
114
|
+
if (!min) return true
|
|
115
|
+
const a = SEVERITY_ORDER.indexOf(actual as any)
|
|
116
|
+
const m = SEVERITY_ORDER.indexOf(min as any)
|
|
117
|
+
if (a < 0 || m < 0) return false
|
|
118
|
+
return a >= m
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* `{μ΄λ¦}` μ λ°κΏ λ£λλ€ β λ°κΏ κ°μ΄ μμΌλ©΄ **κ·Έ μΉΈμ λΊλ€.**
|
|
123
|
+
*
|
|
124
|
+
* μλ λ¬Έμμ΄μ κ·Έλλ‘ λλ©΄ `{quantity}` κ°μ κΈμκ° μ§μμ μλμΌλ‘ λ€μ΄κ°κ³ , κ·Έ μ§μκ° νμ₯μ λκ°λ€.
|
|
125
|
+
* λΉΌλ©΄ κ³μ½μ΄ γμꡬνλ μΉΈμ΄ μλ€γλ‘ κ±°μ νκ³ κ·Έ κ±°μ μ΄ μ¬λμκ² μ ν΄μ§λ€.
|
|
126
|
+
*/
|
|
127
|
+
function fill(template: Record<string, unknown> | undefined, values: Record<string, string | number>): {
|
|
128
|
+
payload: Record<string, unknown>
|
|
129
|
+
missing: string[]
|
|
130
|
+
} {
|
|
131
|
+
const payload: Record<string, unknown> = {}
|
|
132
|
+
const missing: string[] = []
|
|
133
|
+
if (!template) return { payload, missing }
|
|
134
|
+
|
|
135
|
+
for (const [key, raw] of Object.entries(template)) {
|
|
136
|
+
if (typeof raw !== 'string') {
|
|
137
|
+
payload[key] = raw
|
|
138
|
+
continue
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/* κ° νλλ₯Ό κ·Έλλ‘ λλ₯΄λ μ리 β μλ₯Ό λ¬Έμμ΄λ‘ λ§λ€μ§ μλλ€(μλμ΄ λ¬Έμμ΄μ΄λ©΄ κ³μ°μ΄ 무λμ§λ€). */
|
|
142
|
+
const whole = /^\{([A-Za-z0-9_.]+)\}$/.exec(raw)
|
|
143
|
+
if (whole) {
|
|
144
|
+
const name = whole[1]!
|
|
145
|
+
if (!(name in values)) {
|
|
146
|
+
missing.push(name)
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
payload[key] = values[name]
|
|
150
|
+
continue
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
let gap = false
|
|
154
|
+
const text = raw.replace(/\{([A-Za-z0-9_.]+)\}/g, (_m, name: string) => {
|
|
155
|
+
if (name in values) return String(values[name])
|
|
156
|
+
missing.push(name)
|
|
157
|
+
gap = true
|
|
158
|
+
return ''
|
|
159
|
+
})
|
|
160
|
+
if (!gap) payload[key] = text
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return { payload, missing }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** λ°κΏ λ£μ κ° β μ£Όμμ `params` μ `anchor` μμ. */
|
|
167
|
+
function valuesOf(attention: AttentionLike): Record<string, string | number> {
|
|
168
|
+
const out: Record<string, string | number> = { ...(attention.params ?? {}) }
|
|
169
|
+
for (const [key, value] of Object.entries(attention.anchor ?? {})) {
|
|
170
|
+
if (value !== undefined && value !== null && value !== '') out[key] = value as string
|
|
171
|
+
}
|
|
172
|
+
out.attentionId = attention.id
|
|
173
|
+
out.attentionKind = attention.kind
|
|
174
|
+
return out
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* μ΄λ²μ 무μμ μ¬λ¦΄μ§ μ νλ€ β **μμ ν¨μ**. μ μ₯μλ μκ³λ λ³΄μ§ μλλ€.
|
|
179
|
+
*
|
|
180
|
+
* μνμ΄ μ¬κΈ°λκ³Ό μνκ³Ό μ€λ³΅μ λ€ κ±Έμ μ μμ΄μΌ νκ³ , κ·Έλ¬λ €λ©΄ μ΄ νμ μ΄ λ°μμ κ°μ λ°μμΌ νλ€.
|
|
181
|
+
* μ΄ μ μ₯μμμ κ°μ₯ μμ£Ό λλ κ²°ν¨μ΄ γμ리λ μκ³ κΈΈμ΄ μλ€γμ΄κ³ , κ²μ΄νΈμ 물리λ κ²μ΄λ―λ‘ μ¬κΈ°μ
|
|
182
|
+
* λλ©΄ κ²°κ³Όκ° νμ₯μ μ§μλ€.
|
|
183
|
+
*/
|
|
184
|
+
export function evaluateRules(args: {
|
|
185
|
+
rules: readonly ActuationRuleSpec[]
|
|
186
|
+
attentions: readonly AttentionLike[]
|
|
187
|
+
/** μ μΈλ μ‘°μΉ μ’
λ₯ β μλ μ’
λ₯λ₯Ό κ°λ¦¬ν€λ κ·μΉμ μ¬κΈ°μ κ±Έλ¦°λ€. */
|
|
188
|
+
specs: readonly CommandTypeSpec[]
|
|
189
|
+
/**
|
|
190
|
+
* **μ΄λ―Έ λ€λ£¨κ³ μλ κ·Όκ±°λ€** β κ·Όκ±°λ§λ€ λ§μ§λ§μΌλ‘ μ‘°μΉλ₯Ό λΈ μκ°.
|
|
191
|
+
*
|
|
192
|
+
* κ²°μ μ κΈ°λ€λ¦¬λ κ²λ§ 보면 λΆμ‘±νλ€. μΉμΈνλ μκ° κ·Έ 컀맨λκ° λκΈ°μμ λΉ μ§κ³ 쑰건μ κ·Έλλ‘
|
|
193
|
+
* μ μμ΄μ, λ€μ λ°ν΄κ° κ°μ κ²μ λ λΈλ€ β κ±Έμ΄ λ³΄κ³ κ·Έ μνλ₯Ό μ€μ λ‘ λ§λ€μλ€.
|
|
194
|
+
*
|
|
195
|
+
* κ·Έλμ **λΈ μκ°**μ λ°λλ€. κ²°μ μ κΈ°λ€λ¦¬λ κ²μ μκ°κ³Ό 무κ΄νκ² λ§λλ€(Β§`openCauses`).
|
|
196
|
+
* μ μ₯μμμ μ½μ΄ λκΈ΄λ€ β νλ‘μΈμ€ κΈ°μ΅μ λλ©΄ μ¬κΈ°λμ΄ κ³§ μ€λ³΅μ΄ λλ€.
|
|
197
|
+
*/
|
|
198
|
+
openCauses: ReadonlySet<string>
|
|
199
|
+
/** κ·Όκ±° β λ§μ§λ§μΌλ‘ μ‘°μΉλ₯Ό λΈ μκ°(ms). μμΌλ©΄ λΈ μ μ΄ μλ€. */
|
|
200
|
+
lastProposedAt?: ReadonlyMap<string, number>
|
|
201
|
+
/** μ§κΈ β μ‘°κ±΄μ΄ μΌλ§λ μ΄μ΄μ‘λμ§ μ¬λ κΈ°μ€. */
|
|
202
|
+
nowMs: number
|
|
203
|
+
/** ν λ²μ λͺ κ°κΉμ§ μ¬λ¦¬λ. λλ κ²μ λκ³ μ€κ³ , λκ³ μ¨ μλ₯Ό λ§νλ€. */
|
|
204
|
+
maxPerRun?: number
|
|
205
|
+
}): RuleVerdict {
|
|
206
|
+
const { rules, attentions, specs, openCauses, nowMs } = args
|
|
207
|
+
const maxPerRun = args.maxPerRun ?? DEFAULT_MAX_PER_RUN
|
|
208
|
+
|
|
209
|
+
const proposals: RuleProposal[] = []
|
|
210
|
+
const skips: RuleSkip[] = []
|
|
211
|
+
let withheld = 0
|
|
212
|
+
|
|
213
|
+
/* ν μ£Όμμ κ·μΉμ΄ λ λΆμ΄ μμ΄λ μ‘°μΉλ νλλ§ μ¬λ¦°λ€ β κ°μ 쑰건μ λ μ§μκ° λκ°λ©΄ νμ₯μ΄ λ λ² μμ§μΈλ€. */
|
|
214
|
+
const claimed = new Set<string>(openCauses)
|
|
215
|
+
|
|
216
|
+
for (const rule of rules) {
|
|
217
|
+
if (rule.enabled === false) continue
|
|
218
|
+
|
|
219
|
+
const gap = commandTypeGap(rule.commandType, specs)
|
|
220
|
+
if (gap) {
|
|
221
|
+
skips.push({ ruleId: rule.id, reason: gap })
|
|
222
|
+
continue
|
|
223
|
+
}
|
|
224
|
+
const spec = specs.find(s => s.type === rule.commandType)!
|
|
225
|
+
|
|
226
|
+
for (const attention of attentions) {
|
|
227
|
+
if (attention.kind !== rule.attentionKind) continue
|
|
228
|
+
|
|
229
|
+
/* νμΈλ κ²κ³Ό μ¬λΌμ§ κ²μλ μ¬λ¦¬μ§ μλλ€ β μ¬λμ΄ μ΄λ―Έ λ³Έ κ²μ΄λ€. */
|
|
230
|
+
if (attention.state && attention.state !== 'active') continue
|
|
231
|
+
|
|
232
|
+
if (!severityMeets(attention.severity, rule.minSeverity)) continue
|
|
233
|
+
|
|
234
|
+
if (claimed.has(attention.id)) continue
|
|
235
|
+
|
|
236
|
+
/*
|
|
237
|
+
* λΈ μ§ μΌλ§ μ λμΌλ©΄ κΈ°λ€λ¦°λ€. 쑰건μ μ΄μ΄μ§λ κ²μ΄κ³ μ‘°μΉλ μκ°μ΄ κ±Έλ¦°λ€ β ν¨κ³Όλ₯Ό 보기 μ μ
|
|
238
|
+
* λ λ΄λ©΄ λκΈ° μ€μ΄ κ°μ μ§μλ‘ μ°¬λ€.
|
|
239
|
+
*/
|
|
240
|
+
const lastAt = args.lastProposedAt?.get(attention.id)
|
|
241
|
+
const cooldown = rule.cooldownMs ?? DEFAULT_COOLDOWN_MS
|
|
242
|
+
if (lastAt !== undefined && cooldown > 0 && nowMs - lastAt < cooldown) continue
|
|
243
|
+
|
|
244
|
+
const held = heldMs(attention, nowMs)
|
|
245
|
+
if (rule.minHeldMs && rule.minHeldMs > 0) {
|
|
246
|
+
if (held === undefined) {
|
|
247
|
+
/*
|
|
248
|
+
* μΈμ λΆν°μΈμ§ λͺ¨λ₯΄λ μ£Όμλ€. **λͺ¨λ¦μ γμΆ©λΆν μ€λλλ€γλ‘ μ½μ§ μλλ€** β κ·Έλ κ² μ½μΌλ©΄
|
|
249
|
+
* λ°©κΈ μκΈ΄ κΉλΉ‘μμ νμ₯ μ§μκ° λκ°λ€.
|
|
250
|
+
*/
|
|
251
|
+
skips.push({
|
|
252
|
+
ruleId: rule.id,
|
|
253
|
+
attentionId: attention.id,
|
|
254
|
+
reason: `μ΄ μ£Όμλ μΈμ λΆν°μΈμ§ λ§νμ§ μλλ€ β ${rule.minHeldMs}ms μ΄μ΄μ‘λμ§ μ μ μμ΄ μ¬λ¦¬μ§ μμλ€`
|
|
255
|
+
})
|
|
256
|
+
continue
|
|
257
|
+
}
|
|
258
|
+
if (held < rule.minHeldMs) continue
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (proposals.length >= maxPerRun) {
|
|
262
|
+
withheld++
|
|
263
|
+
continue
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const { payload, missing } = fill(rule.payloadTemplate, valuesOf(attention))
|
|
267
|
+
if (missing.length) {
|
|
268
|
+
skips.push({
|
|
269
|
+
ruleId: rule.id,
|
|
270
|
+
attentionId: attention.id,
|
|
271
|
+
reason: `μ§μμ λ£μ κ°μ΄ μ£Όμμ μλ€ (${[...new Set(missing)].join(', ')}) β μ§μ΄λΈ κ°μΌλ‘ νμ₯ μ§μλ₯Ό λ§λ€μ§ μμλ€`
|
|
272
|
+
})
|
|
273
|
+
continue
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const gaps = missingRequired(spec, payload)
|
|
277
|
+
if (gaps.length) {
|
|
278
|
+
skips.push({
|
|
279
|
+
ruleId: rule.id,
|
|
280
|
+
attentionId: attention.id,
|
|
281
|
+
reason: `μ§μκ° μꡬνλ μΉΈμ΄ λΉμλ€ (${gaps.join(', ')}) β 무μμ μ§μνλμ§ λͺ¨λ₯΄λ©΄ μΉμΈν κ·Όκ±°κ° μλ€`
|
|
282
|
+
})
|
|
283
|
+
continue
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
claimed.add(attention.id)
|
|
287
|
+
proposals.push({
|
|
288
|
+
ruleId: rule.id,
|
|
289
|
+
attentionId: attention.id,
|
|
290
|
+
attentionKind: attention.kind,
|
|
291
|
+
commandType: rule.commandType,
|
|
292
|
+
payload
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
return { proposals, skips, ...(withheld ? { withheld } : {}) }
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** ν λ²μ μ¬λ¦¬λ μν β μ¬λμ΄ μ½μ μ μλ μ. μ½μ§ μλ λκΈ° μ€μ κ²μ΄νΈκ° μλλ€. */
|
|
301
|
+
export const DEFAULT_MAX_PER_RUN = 20
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* κ°μ κ·Όκ±°μ λ€μ λ΄κΈ°κΉμ§ β **μ μΈνμ§ μμλ κΈ°λ€λ¦°λ€.**
|
|
305
|
+
*
|
|
306
|
+
* κΈ°λ³Έμ 0 μΌλ‘ λλ©΄ μ μΈνμ§ μμ κ·μΉμ΄ μΉμΈν λλ§λ€ κ°μ κ²μ λ λΈλ€. κ·Έ κ²°ν¨μ κΈ°λ³Έκ°μΌλ‘ λμ§
|
|
307
|
+
* μλλ€. λ 빨리 λ΄μΌ νλ κ·μΉμ μ€μ€λ‘ μ§§κ² μ λλ€.
|
|
308
|
+
*/
|
|
309
|
+
export const DEFAULT_COOLDOWN_MS = 60 * 60_000
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* μ΄ κ·μΉμ΄ λΌ μ μλ μ’
λ₯μΈκ° β **νμ μ΄ ν μ리μλ§ μκ².**
|
|
313
|
+
*
|
|
314
|
+
* μ μ₯ν λμ λ λκ° κ°μ κ²μ λ΄μΌ νλ€. λ λ²μ΄λ©΄ μ μ₯μ λλλ° μ λλ κ·μΉμ΄ μκΈ°κ³ , μ¬λμ κ·μΉμ΄
|
|
315
|
+
* κ±Έλ¦° μ€λ‘ μ½λλ€.
|
|
316
|
+
*
|
|
317
|
+
* `stimuli` λ₯Ό λ°λ μ΄μ : 컀λ μκ·Ή(`CMD`)μ μ μμ λ **μ μ λλμ§**λ₯Ό λ§νλ €λ©΄ κ·Έ μ΄νλ₯Ό μμμΌ νλ€.
|
|
318
|
+
* γμ μΈλμ§ μμλ€γλ‘λ§ λ΅νλ©΄ λΆλ₯΄λ μͺ½μ μ μΈμ΄ λΉ μ§ μ€λ‘ μ½κ³ μ μΈμ μΆκ°νλ€ β κ·Έ μκ° λͺ¨λΈ μμ
|
|
319
|
+
* μκ·Ήμ΄ νμ₯ μ§μκ° λλ€.
|
|
320
|
+
*/
|
|
321
|
+
export function commandTypeGap(
|
|
322
|
+
commandType: string,
|
|
323
|
+
specs: readonly CommandTypeSpec[],
|
|
324
|
+
stimuli?: readonly string[]
|
|
325
|
+
): string | undefined {
|
|
326
|
+
if (specs.some(s => s.type === commandType)) return undefined
|
|
327
|
+
|
|
328
|
+
if (stimuli?.includes(commandType)) {
|
|
329
|
+
return (
|
|
330
|
+
`κ·Έκ²μ 컀λ μμμ λλ μκ·Ήμ΄λ€ (${commandType}) β νμ₯μ λκ°λ μ§μκ° μλλ€. ` +
|
|
331
|
+
'λͺ¨λΈ μνλ₯Ό λ°κΎΈλ €λ©΄ 컀λμ μκ·Ήμ λ£κ³ , νμ₯μ 무μΈκ°λ₯Ό μν€λ €λ©΄ κ·Έ μ§μλ₯Ό μ‘°μΉ μ’
λ₯λ‘ μ μΈνλ€'
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return `μ μΈλμ§ μμ μ‘°μΉ μ’
λ₯λ₯Ό κ°λ¦¬ν¨λ€ (${commandType}) β μ μΈλ κ²: ${specs.map(s => s.type).join(', ') || '(μμ)'}`
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** μ‘°κ±΄μ΄ μΌλ§λ μ΄μ΄μ‘λ β λͺ¨λ₯΄λ©΄ `undefined`(0 μ΄ μλλ€). */
|
|
339
|
+
function heldMs(attention: AttentionLike, nowMs: number): number | undefined {
|
|
340
|
+
if (!attention.timeStamp) return undefined
|
|
341
|
+
const at = Date.parse(attention.timeStamp)
|
|
342
|
+
if (!Number.isFinite(at)) return undefined
|
|
343
|
+
return Math.max(0, nowMs - at)
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** κ³μ½μ μꡬλ₯Ό κ·Έλλ‘ λ³Έλ€ β νμ μ μ¬κΈ°μ λ€μ μ°μ§ μλλ€. */
|
|
347
|
+
function missingRequired(spec: CommandTypeSpec, payload: Record<string, unknown>): string[] {
|
|
348
|
+
return (spec.required ?? []).filter(key => {
|
|
349
|
+
const v = payload[key]
|
|
350
|
+
return v === undefined || v === null || v === ''
|
|
351
|
+
})
|
|
352
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* κ·μΉμ μ£ΌκΈ°λ‘ λ리λ νμ΄λ¨Έ νλ.
|
|
3
|
+
*
|
|
4
|
+
* ββ μ 컀λ ν±μ΄ μλκ° ββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
5
|
+
* `tickGuarded` λ μμΈκ° λλ©΄ **κ·Έ νΈμμ λ©μΆλ€.** κ·μΉ νλκ° μλͺ»λλ€κ³ 곡μ₯μ΄ λ©μΆλ©΄ μ λλ€.
|
|
6
|
+
* κ·Έλ¦¬κ³ κ΅ΆκΉ κ°μκ° λλ¦° ν± μ
μ νΈμμ λ΄λ¦¬λλ°, κ·μΉμ DB λ₯Ό λ λ² κ±΄λ리λ―λ‘ κ·Έ κ°μμ κ±Έλ¦°λ€.
|
|
7
|
+
*
|
|
8
|
+
* ββ μ μ΄ κ°κ²©μΈκ° βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
9
|
+
* κ·μΉμ΄ 보λ κ²μ **μ€λ μ΄μ΄μ§ 쑰건**μ΄λ€. γλ³λͺ©μ΄ μΈ μκ°μ§Έγλ₯Ό 1μ΄λ§λ€ λ€μ λ¬Όμ μ΄μ κ° μλ€.
|
|
10
|
+
* μ§§κ² μ‘μΌλ©΄ μ»λ κ² μμ΄ DB λ₯Ό λ λ²μ© λ μΉλ€.
|
|
11
|
+
*
|
|
12
|
+
* ββ κ²Ήμ³ λμ§ μλλ€ βββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
13
|
+
* ν λ°ν΄κ° κ°κ²©λ³΄λ€ μ€λ 걸리면 λ€μ λ°νκ° κ²ΉμΉλ€. κ²ΉμΉλ©΄ λ λ°ν΄κ° κ°μ κ·Όκ±° μ§ν©μ μ½κ³ **κ°μ
|
|
14
|
+
* μ‘°μΉλ₯Ό λ λ² λΈλ€** β μ€λ³΅μ λ§λ μ₯μΉκ° κ·Έ μκ° ν΅κ³Όλλ€.
|
|
15
|
+
*/
|
|
16
|
+
import { MES_COMMANDS } from '@operato/ops-contract'
|
|
17
|
+
|
|
18
|
+
import { twinError, twinLog } from '../../engine/log.js'
|
|
19
|
+
import { TwinEngine } from '../../engine/twin-engine.js'
|
|
20
|
+
|
|
21
|
+
import { capabilitiesOf, getAdapter } from '../reference/reference-adapter.js'
|
|
22
|
+
import { actuationReadinessOf, actuationTargetOf, routeActuation } from '../reference/actuation-routing.js'
|
|
23
|
+
import { sweepPendingDispatch, type SweepReport } from './sweep-dispatch.js'
|
|
24
|
+
import { catchUpEffects } from './catch-up-effects.js'
|
|
25
|
+
import { databaseCommandStore } from './command-store.js'
|
|
26
|
+
import { runRulesOnce } from './rule-runner.js'
|
|
27
|
+
import { attentionsOf, modeOf, noteRun, rulesOf } from './rule-wiring.js'
|
|
28
|
+
|
|
29
|
+
/** λͺ μ΄λ§λ€ λλ. μ€λ μ΄μ΄μ§ 쑰건μ 보λ μΌμ΄λΌ μ΄μ΄ν μ΄μ κ° μλ€. */
|
|
30
|
+
export const RULE_PERIOD_MS = 30_000
|
|
31
|
+
|
|
32
|
+
let timer: any
|
|
33
|
+
let running = false
|
|
34
|
+
|
|
35
|
+
/** μ£ΌκΈ°λ₯Ό μΈμ΄λ€ β λ λ² λΆλ¬λ νλλ§ μ λ€. */
|
|
36
|
+
export function startActuationRuleLoop(periodMs = RULE_PERIOD_MS): void {
|
|
37
|
+
if (timer) return
|
|
38
|
+
|
|
39
|
+
timer = setInterval(() => {
|
|
40
|
+
/* μ λ°ν΄κ° μμ§ λλ€ β κ²ΉμΉλ©΄ κ°μ μ‘°μΉλ₯Ό λ λ² λΈλ€. 건λλ°λ κ²μ΄ λ§λ€. */
|
|
41
|
+
if (running) return
|
|
42
|
+
running = true
|
|
43
|
+
|
|
44
|
+
tickRules()
|
|
45
|
+
.catch(err => twinError(`[twin-rule] κ·μΉ μ£ΌκΈ°κ° μ€ν¨νλ€ β ${err?.message ?? err}`))
|
|
46
|
+
.finally(() => {
|
|
47
|
+
running = false
|
|
48
|
+
})
|
|
49
|
+
}, periodMs)
|
|
50
|
+
|
|
51
|
+
/* μ΄ νμ΄λ¨Έ λλ¬Έμ νλ‘μΈμ€κ° μ μ£½μ§ μκ². */
|
|
52
|
+
if (typeof timer?.unref === 'function') timer.unref()
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function stopActuationRuleLoop(): void {
|
|
56
|
+
if (!timer) return
|
|
57
|
+
clearInterval(timer)
|
|
58
|
+
timer = undefined
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* μΌκΎΌμ΄ ν λ°ν΄μ 무μμ νλ β **λ°λμμ λλ§** μ λλ€.
|
|
63
|
+
*
|
|
64
|
+
* ββ μ νμνκ° (2026-09-04) ββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
65
|
+
* μ²μμ λ³΄κ³ λ₯Ό λ°κ³ λ²λ Έλ€. κ·Έλ¬λ λκΈ° μ€μ μ‘°μΉκ° μμ μλλ° μ무 μΌλ μ μΌμ΄λ λ
|
|
66
|
+
* **μ΄μ λ₯Ό μ κΈΈμ΄ μμλ€** β μ€μ λ‘ κ·Έ μνλ₯Ό λ§λ 30λΆμ λ€μ‘λ€. μ§μ§ μλ κ²μ λκ° μ³μ
|
|
67
|
+
* νλ¨μ΄μ§λ§, μ³μ νλ¨λ 보μ΄μ§ μμΌλ©΄ κ²°ν¨κ³Ό ꡬλ³λμ§ μλλ€.
|
|
68
|
+
*
|
|
69
|
+
* ββ μ λ°λμμ λλ§μΈκ° ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
70
|
+
* 30μ΄λ§λ€ κ°μ μ€μ μ μΌλ©΄ ν루 2,880μ€μ΄κ³ κ·Έ μ¬μ΄μ μ§μ§ λ³νκ° λ¬»νλ€. λκΈ° μ€μ λͺ¨μμ΄
|
|
71
|
+
* κ·Έλλ‘λ©΄ μλ‘ λ§ν κ²μ΄ μλ€ β `noteBlocked` μμ μ§ν¨ κ²κ³Ό κ°μ κ·μ¨μ΄λ€.
|
|
72
|
+
*/
|
|
73
|
+
const lastSweep = new Map<string, string>()
|
|
74
|
+
|
|
75
|
+
function noteSweep(domainId: string, report: SweepReport): void {
|
|
76
|
+
if (!report.seen) {
|
|
77
|
+
lastSweep.delete(domainId)
|
|
78
|
+
return
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const skips = Object.entries(report.skipped)
|
|
82
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
83
|
+
.map(([why, n]) => `${why} ${n}`)
|
|
84
|
+
.join(' Β· ')
|
|
85
|
+
const line = `${report.seen}건 λκΈ° Β· λκΉ ${report.dispatched}${skips ? ` Β· ${skips}` : ''}`
|
|
86
|
+
|
|
87
|
+
if (lastSweep.get(domainId) === line) return
|
|
88
|
+
lastSweep.set(domainId, line)
|
|
89
|
+
twinLog(`[twin-sweep] ${line}`)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function tickRules(): Promise<void> {
|
|
93
|
+
/*
|
|
94
|
+
* ββ μ μ
μ°½μ λ¨Όμ κ΅΄λ¦°λ€ (2026-09-05) βββββββββββββββββββββββββββββββββββββ
|
|
95
|
+
* μ°½μ΄ λ«νλ μ΄μ λ **μκ°μ΄ μ§λ κ²**μ΄λ€. κ·Έλ°λ° μμ μλ γλ€μ μ¬κ±΄μ΄ μμ λγμ γλκ°
|
|
96
|
+
* λ¬Όμ΄λ³Ό λγλ§ κ΅΄λ λ€ β λ λ€ μ μ¬ μ μλ€.
|
|
97
|
+
*
|
|
98
|
+
* κ·Έλμ μ¬μ€μ΄ λλ¬Έ νΈμμ νμ μ무κ²λ μ λ¨μλ€. μ€μΈ‘: 46κ±΄μ΄ 62λΆμ ν©μ΄μ Έ μ¨ νΈμμ μ°½μ΄
|
|
99
|
+
* 1κ°, μ¬μ€ 11건λΏμΈ νΈμμ μ°½μ΄ 48κ° β **μ¬μ€ μμ μ°½ μκ° κ±°κΎΈλ‘**μλ€. μ°½μ λ§λ κ²μ΄ μ μ
μ΄
|
|
100
|
+
* μλλΌ λκ° λ¬Όμ΄λ³Έ κ²μ΄μκΈ° λλ¬Έμ΄λ€.
|
|
101
|
+
*
|
|
102
|
+
* **μ무λ μ 보λ νΈμμΌμλ‘ κΈ°λ‘μ΄ μμλ€.** μ¬μ€μ΄ μ‘°μ©ν μ¬λΌμ§λ κ²μ μ‘μΌλ €λ μ₯λΆκ° μ νν
|
|
103
|
+
* κ·Έ λ°λλ‘ λκ³ μμλ€(137λ΄ν¬κ° μ₯λΆ μμ΄ μ§λκ°λ€ β μΈν°κ·Έλ μ΄μ
λ μΈ μ€μΈ‘).
|
|
104
|
+
*
|
|
105
|
+
* μμ ν νΈμμ΄ μμ΄λ κ΅΄λ¦°λ€ β μ₯λΆλ νλ‘μΈμ€κ° μ¬λ λμ μ΄μ΄μ§κ³ , κ·μΉμ λ νΈμμ΄ μλ€λ
|
|
106
|
+
* κ²κ³Ό κΈ°λ‘ν μ°½μ΄ μλ€λ κ²μ λ€λ₯Έ μ¬μ€μ΄λ€.
|
|
107
|
+
*/
|
|
108
|
+
const rolled = TwinEngine.rollAllIngestWindows()
|
|
109
|
+
if (rolled) twinLog(`[twin-ingest] μ°½ ${rolled}κ°λ₯Ό λ«μ μ μλ€`)
|
|
110
|
+
|
|
111
|
+
const owned = TwinEngine.ownedTwins()
|
|
112
|
+
if (!owned.length) return
|
|
113
|
+
|
|
114
|
+
/*
|
|
115
|
+
* νΈλμμ
μ λκΈ°μ§ μλλ€ β μ¬λ¬ νΈμμ λκ³ , ν νΈμμ΄ μ€ν¨ν΄λ μμ νΈμμ΄ λΈ κ²μ λ¨μμΌ νλ€.
|
|
116
|
+
* ν λ©μ΄λ¦¬λ‘ λ¬ΆμΌλ©΄ λ§μ§λ§ νΈμμ μ€ν¨κ° μμ κ²°κ³Όλ₯Ό μ λΆ λ¬Όλ¦°λ€.
|
|
117
|
+
*/
|
|
118
|
+
/*
|
|
119
|
+
* **λμΉ λ³΄κ³ λ₯Ό ν¨κ» backfill νλ€** β κ΄μΈ‘ β μ‘°μΉμ λ°λ λ°©ν₯μ΄λ€.
|
|
120
|
+
*
|
|
121
|
+
* κ°μ μ£ΌκΈ°λ₯Ό μ°λ μ΄μ λ κ°μ΄λ€: λ λ€ λͺ μ΄λ§λ€ λ³Ό μΌμ΄κ³ , νμ΄λ¨Έλ₯Ό λ λλ©΄ λ΄λ¦¬κ³ μΈμΈ μ리κ°
|
|
122
|
+
* λμ΄ λλ€. λ€λ§ **μλ‘ λ§μ§ μλλ€** β κ·μΉμ΄ ν°μ Έλ backfill μ λμμΌ νλ€. μ‘°μΉλ₯Ό λͺ» λ΄λ κ²κ³Ό
|
|
123
|
+
* λκ° μ‘°μΉμ κ²°κ³Όλ₯Ό λͺ¨λ₯΄λ κ²μ λ€λ₯Έ μ¬κ³ λ€.
|
|
124
|
+
*/
|
|
125
|
+
for (const domainId of new Set(owned.map(t => t.domainId))) {
|
|
126
|
+
await catchUpEffects(domainId, databaseCommandStore())
|
|
127
|
+
/*
|
|
128
|
+
* **λκΈΈ μ°¨λ‘λ₯Ό κΈ°λ€λ¦¬λ κ²λ€λ ν¨κ» μ€λλ€** β μΉμΈλ§ λκ³ μ λκ° κ², μ€ν¨ν΄μ λ€μ ν΄ λ³Ό κ².
|
|
129
|
+
*
|
|
130
|
+
* μ μ κ²½λ‘λ μΉμΈ μ§νμ μ΄λ―Έ λλλ€(Β§`dispatchAfterApproval`, μ€μΈ‘ 282ms). μ΄ μ리λ **κ·Έ
|
|
131
|
+
* μ¬μ΄μ νλ‘μΈμ€κ° μ£½μ κ²½μ°**μ μ¬μλλ§ λ³Έλ€. κ·Έλμ 30μ΄λ‘ μΆ©λΆνκ³ μ무λ μ κΈ°λ€λ¦°λ€.
|
|
132
|
+
*/
|
|
133
|
+
const swept = await sweepPendingDispatch(domainId, {
|
|
134
|
+
store: databaseCommandStore(),
|
|
135
|
+
actuate: command => routeActuation(domainId, command.instanceId, command),
|
|
136
|
+
idempotent: async instanceId => {
|
|
137
|
+
const target = await actuationTargetOf(domainId, instanceId).catch(() => undefined)
|
|
138
|
+
const type = (target as any)?.adapterType
|
|
139
|
+
return type ? capabilitiesOf(getAdapter(type)).includes('idempotent-actuation') : false
|
|
140
|
+
},
|
|
141
|
+
localReadiness: async instanceId => {
|
|
142
|
+
const r = await actuationReadinessOf(domainId, instanceId).catch(() => undefined)
|
|
143
|
+
/* μ격μΌλ‘ λ¬Όμ΄ λ³Έ λ΅μ μ°μ§ μλλ€ β μ΄ μ리λ μ μͺ½μ 건λλ¦¬μ§ μλ κ²μ΄ κ·μ½μ΄λ€. */
|
|
144
|
+
return r && (r as any).checked === 'local' ? (r as any) : undefined
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
noteSweep(domainId, swept)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
await runRulesOnce({
|
|
151
|
+
ownedTwins: () => owned,
|
|
152
|
+
rulesOf,
|
|
153
|
+
attentionsOf,
|
|
154
|
+
modeOf,
|
|
155
|
+
store: databaseCommandStore(),
|
|
156
|
+
specs: MES_COMMANDS,
|
|
157
|
+
nowMs: () => Date.now(),
|
|
158
|
+
noteRun
|
|
159
|
+
})
|
|
160
|
+
}
|