@winkjs/composer 0.0.2 → 0.4.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/LICENSE +1 -1
- package/README.md +42 -103
- package/package.json +53 -29
- package/src/composer.js +75 -0
- package/src/core/batch/aoo-encoder.js +148 -0
- package/src/core/batch/index.js +12 -0
- package/src/core/batch/sliding-window.js +91 -0
- package/src/core/codec/index.js +137 -0
- package/src/core/emitter-manager/mqtt/constants.js +102 -0
- package/src/core/emitter-manager/mqtt/emitter.js +780 -0
- package/src/core/emitter-manager/mqtt/index.js +190 -0
- package/src/core/emitter-manager/mqtt/mqtt-store.js +538 -0
- package/src/core/emitter-manager/terminal/assert-columns.js +98 -0
- package/src/core/emitter-manager/terminal/formatters.js +160 -0
- package/src/core/emitter-manager/terminal/index.js +341 -0
- package/src/core/env-vars.js +245 -0
- package/src/core/mqtt-protocol.js +47 -0
- package/src/core/partition-manager/index.js +7 -0
- package/src/core/partition-manager/init.js +44 -0
- package/src/core/partition-manager/introspect.js +15 -0
- package/src/core/partition-manager/publish-to.js +6 -0
- package/src/core/partition-manager/recompute.js +5 -0
- package/src/core/partition-manager/reset.js +5 -0
- package/src/core/partition-manager/update.js +264 -0
- package/src/core/partition-manager/validate-flow.js +157 -0
- package/src/core/semantics/digest.js +128 -0
- package/src/core/semantics/index.js +83 -0
- package/src/core/semantics/loader.js +571 -0
- package/src/core/semantics/schemas/asset-class-schema.js +218 -0
- package/src/core/semantics/schemas/column-schema.js +651 -0
- package/src/core/semantics/schemas/enum-schema.js +153 -0
- package/src/core/semantics/schemas/index.js +51 -0
- package/src/core/semantics/warnings.js +141 -0
- package/src/core/shutdown-manager/index.js +289 -0
- package/src/core/source-manager/csv/index.js +171 -0
- package/src/core/source-manager/csv/start.js +456 -0
- package/src/core/source-manager/mqtt/client.js +406 -0
- package/src/core/source-manager/mqtt/constants.js +147 -0
- package/src/core/source-manager/mqtt/dedup.js +244 -0
- package/src/core/source-manager/mqtt/index.js +260 -0
- package/src/core/source-manager/mqtt/status.js +546 -0
- package/src/core/source-manager/test-harness/comparator.js +226 -0
- package/src/core/source-manager/test-harness/field-generator.js +85 -0
- package/src/core/source-manager/test-harness/fuzz.js +87 -0
- package/src/core/source-manager/test-harness/index.js +115 -0
- package/src/core/source-manager/test-harness/prng.js +57 -0
- package/src/core/source-manager/test-harness/start.js +232 -0
- package/src/core/source-manager/test-harness/validate.js +213 -0
- package/src/core/storage-manager/questdb/assert-columns.js +120 -0
- package/src/core/storage-manager/questdb/ensure-tables.js +187 -0
- package/src/core/storage-manager/questdb/index.js +1251 -0
- package/src/core/storage-manager/questdb/persist-plan.js +488 -0
- package/src/core/storage-manager/questdb/writers.js +142 -0
- package/src/core/test-utils/sealed-record.js +80 -0
- package/src/core/test-utils/soak-signature.js +149 -0
- package/src/core/test-utils/tcp-proxy.js +106 -0
- package/src/core/tunable/helpers.js +254 -0
- package/src/core/tunable/index.js +87 -0
- package/src/core/utils/console-formatter/index.js +207 -0
- package/src/core/utils/create-smart-store-as/index.js +98 -0
- package/src/core/utils/create-smart-store-as/sanitize-field.js +46 -0
- package/src/core/utils/create-smart-store-as/smart-truncate.js +93 -0
- package/src/core/utils/flow/index.js +5 -0
- package/src/core/utils/flow/make-collision-checker.js +23 -0
- package/src/core/utils/flow/to-kebab.js +20 -0
- package/src/core/utils/half-life/index.js +111 -0
- package/src/core/utils/node/disable.js +11 -0
- package/src/core/utils/node/enable.js +11 -0
- package/src/core/utils/node/execute-triggers.js +40 -0
- package/src/core/utils/node/index.js +10 -0
- package/src/core/utils/node/pause.js +11 -0
- package/src/core/utils/node/populate-predicate-inputs.js +14 -0
- package/src/core/utils/node/publish-nan.js +19 -0
- package/src/core/utils/node/reset-predicate-inputs.js +11 -0
- package/src/core/utils/node/unpause.js +11 -0
- package/src/core/utils/node/validate-spec.js +22 -0
- package/src/core/utils/options/resolve-field-keyed.js +115 -0
- package/src/core/utils/quantize/index.js +75 -0
- package/src/core/utils/stats/formulas.js +67 -0
- package/src/core/utils/stats/index.js +3 -0
- package/src/core/utils/validate/composer-validators.js +141 -0
- package/src/core/utils/validate/helpers.js +206 -0
- package/src/core/utils/validate/index.js +12 -0
- package/src/core/utils/validate/type-validators.js +538 -0
- package/src/core/utils/validate/validate.js +107 -0
- package/src/core/utils/validate/validators.js +177 -0
- package/src/core/wiring/assert-handle.js +57 -0
- package/src/core/wiring/assert-module.js +63 -0
- package/src/core/wiring/index.js +5 -0
- package/src/core/wiring/node-type-to-module.js +17 -0
- package/src/core/wiring/wire-emitters.js +256 -0
- package/src/core/wiring/wire-linear-graph.js +25 -0
- package/src/core/wiring/wire-node.js +157 -0
- package/src/core/wiring/wire-semantics.js +173 -0
- package/src/core/wiring/wire-storages.js +344 -0
- package/src/flow/check-output-collisions.js +93 -0
- package/src/flow/consts.js +9 -0
- package/src/flow/deep-clone-spec.js +64 -0
- package/src/flow/driver.js +171 -0
- package/src/flow/expand-single-input-array.js +76 -0
- package/src/flow/expand-template-spec.js +93 -0
- package/src/flow/flow-api-schemas.js +300 -0
- package/src/flow/flow.js +658 -0
- package/src/flow/for-each.js +225 -0
- package/src/flow/get-signature-pattern.js +31 -0
- package/src/flow/get-target-array.js +46 -0
- package/src/flow/inspect.js +67 -0
- package/src/flow/load-node-module.js +81 -0
- package/src/flow/make-node-method.js +112 -0
- package/src/flow/resolve-pick-by-field.js +77 -0
- package/src/flow/resolve-tunables.js +76 -0
- package/src/flow/run.js +392 -0
- package/src/flow/serialize.js +161 -0
- package/src/flow/signatures-schema.js +222 -0
- package/src/flow/spec-builder.js +65 -0
- package/src/flow/validate-triggers.js +250 -0
- package/src/flow/validate.js +261 -0
- package/src/nodes/accumulate/index.js +21 -0
- package/src/nodes/accumulate/init.js +48 -0
- package/src/nodes/accumulate/introspect.js +123 -0
- package/src/nodes/accumulate/publish-to.js +34 -0
- package/src/nodes/accumulate/recompute.js +27 -0
- package/src/nodes/accumulate/reset.js +25 -0
- package/src/nodes/accumulate/update.js +42 -0
- package/src/nodes/appraise/calibrate.js +64 -0
- package/src/nodes/appraise/decision.js +66 -0
- package/src/nodes/appraise/deviation.js +140 -0
- package/src/nodes/appraise/index.js +23 -0
- package/src/nodes/appraise/init.js +165 -0
- package/src/nodes/appraise/integrate.js +45 -0
- package/src/nodes/appraise/introspect.js +352 -0
- package/src/nodes/appraise/publish-to.js +93 -0
- package/src/nodes/appraise/receptor.js +69 -0
- package/src/nodes/appraise/recompute.js +79 -0
- package/src/nodes/appraise/reset.js +53 -0
- package/src/nodes/appraise/update.js +130 -0
- package/src/nodes/archive/csv/index.js +6 -0
- package/src/nodes/archive/csv/init.js +36 -0
- package/src/nodes/archive/csv/load.js +70 -0
- package/src/nodes/archive/csv/reset.js +14 -0
- package/src/nodes/archive/csv/simple-parser.js +216 -0
- package/src/nodes/archive/csv/test.js +173 -0
- package/src/nodes/archive/csv/update.js +63 -0
- package/src/nodes/butterworth-filter/index.js +18 -0
- package/src/nodes/butterworth-filter/init.js +184 -0
- package/src/nodes/butterworth-filter/introspect.js +228 -0
- package/src/nodes/butterworth-filter/publish-to.js +21 -0
- package/src/nodes/butterworth-filter/recompute.js +13 -0
- package/src/nodes/butterworth-filter/reset.js +30 -0
- package/src/nodes/butterworth-filter/update.js +38 -0
- package/src/nodes/categorize/index.js +19 -0
- package/src/nodes/categorize/init.js +58 -0
- package/src/nodes/categorize/introspect.js +177 -0
- package/src/nodes/categorize/publish-to.js +29 -0
- package/src/nodes/categorize/recompute.js +14 -0
- package/src/nodes/categorize/reset.js +16 -0
- package/src/nodes/categorize/update.js +53 -0
- package/src/nodes/controller/index.js +9 -0
- package/src/nodes/controller/init.js +55 -0
- package/src/nodes/controller/introspect.js +91 -0
- package/src/nodes/controller/publish-to.js +8 -0
- package/src/nodes/controller/recompute.js +9 -0
- package/src/nodes/controller/reset.js +17 -0
- package/src/nodes/controller/update.js +63 -0
- package/src/nodes/diff/index.js +19 -0
- package/src/nodes/diff/init.js +37 -0
- package/src/nodes/diff/introspect.js +132 -0
- package/src/nodes/diff/publish-to.js +21 -0
- package/src/nodes/diff/recompute.js +12 -0
- package/src/nodes/diff/reset.js +12 -0
- package/src/nodes/diff/update.js +37 -0
- package/src/nodes/digest-moments/formulas.js +12 -0
- package/src/nodes/digest-moments/index.js +14 -0
- package/src/nodes/digest-moments/init.js +67 -0
- package/src/nodes/digest-moments/introspect.js +154 -0
- package/src/nodes/digest-moments/publish-to.js +34 -0
- package/src/nodes/digest-moments/recompute.js +14 -0
- package/src/nodes/digest-moments/reset.js +14 -0
- package/src/nodes/digest-moments/update.js +120 -0
- package/src/nodes/dwell-time-tracker/index.js +14 -0
- package/src/nodes/dwell-time-tracker/init.js +59 -0
- package/src/nodes/dwell-time-tracker/introspect.js +137 -0
- package/src/nodes/dwell-time-tracker/publish-to.js +22 -0
- package/src/nodes/dwell-time-tracker/recompute.js +8 -0
- package/src/nodes/dwell-time-tracker/reset.js +23 -0
- package/src/nodes/dwell-time-tracker/update.js +103 -0
- package/src/nodes/emit-if/helpers.js +94 -0
- package/src/nodes/emit-if/index.js +16 -0
- package/src/nodes/emit-if/init.js +67 -0
- package/src/nodes/emit-if/introspect.js +102 -0
- package/src/nodes/emit-if/publish-to.js +13 -0
- package/src/nodes/emit-if/recompute.js +12 -0
- package/src/nodes/emit-if/reset.js +27 -0
- package/src/nodes/emit-if/update.js +93 -0
- package/src/nodes/es-correlation/index.js +19 -0
- package/src/nodes/es-correlation/init.js +73 -0
- package/src/nodes/es-correlation/introspect.js +176 -0
- package/src/nodes/es-correlation/publish-to.js +32 -0
- package/src/nodes/es-correlation/recompute.js +56 -0
- package/src/nodes/es-correlation/reset.js +27 -0
- package/src/nodes/es-correlation/update.js +110 -0
- package/src/nodes/es-mean/index.js +20 -0
- package/src/nodes/es-mean/init.js +58 -0
- package/src/nodes/es-mean/introspect.js +129 -0
- package/src/nodes/es-mean/publish-to.js +19 -0
- package/src/nodes/es-mean/recompute.js +16 -0
- package/src/nodes/es-mean/reset.js +24 -0
- package/src/nodes/es-mean/update.js +69 -0
- package/src/nodes/es-pairwise-correlation/index.js +19 -0
- package/src/nodes/es-pairwise-correlation/init.js +98 -0
- package/src/nodes/es-pairwise-correlation/introspect.js +187 -0
- package/src/nodes/es-pairwise-correlation/publish-to.js +31 -0
- package/src/nodes/es-pairwise-correlation/recompute.js +69 -0
- package/src/nodes/es-pairwise-correlation/reset.js +29 -0
- package/src/nodes/es-pairwise-correlation/update.js +89 -0
- package/src/nodes/es-stats/index.js +21 -0
- package/src/nodes/es-stats/init.js +105 -0
- package/src/nodes/es-stats/introspect.js +157 -0
- package/src/nodes/es-stats/publish-to.js +42 -0
- package/src/nodes/es-stats/recompute.js +74 -0
- package/src/nodes/es-stats/reset.js +49 -0
- package/src/nodes/es-stats/update-envelope.js +44 -0
- package/src/nodes/es-stats/update-welford.js +64 -0
- package/src/nodes/es-stats/update.js +59 -0
- package/src/nodes/index.js +83 -0
- package/src/nodes/invert-flag/index.js +20 -0
- package/src/nodes/invert-flag/init.js +36 -0
- package/src/nodes/invert-flag/introspect.js +115 -0
- package/src/nodes/invert-flag/publish-to.js +23 -0
- package/src/nodes/invert-flag/recompute.js +12 -0
- package/src/nodes/invert-flag/reset.js +12 -0
- package/src/nodes/invert-flag/update.js +32 -0
- package/src/nodes/kalman1d/index.js +20 -0
- package/src/nodes/kalman1d/init.js +103 -0
- package/src/nodes/kalman1d/introspect.js +250 -0
- package/src/nodes/kalman1d/publish-to.js +34 -0
- package/src/nodes/kalman1d/recompute.js +23 -0
- package/src/nodes/kalman1d/reset.js +29 -0
- package/src/nodes/kalman1d/update.js +144 -0
- package/src/nodes/kernel/index.js +18 -0
- package/src/nodes/kernel/init.js +66 -0
- package/src/nodes/kernel/introspect.js +163 -0
- package/src/nodes/kernel/presets.js +74 -0
- package/src/nodes/kernel/publish-to.js +25 -0
- package/src/nodes/kernel/recompute.js +14 -0
- package/src/nodes/kernel/reset.js +20 -0
- package/src/nodes/kernel/update.js +69 -0
- package/src/nodes/lag/index.js +21 -0
- package/src/nodes/lag/init.js +87 -0
- package/src/nodes/lag/introspect.js +202 -0
- package/src/nodes/lag/publish-to.js +64 -0
- package/src/nodes/lag/recompute.js +33 -0
- package/src/nodes/lag/reset.js +49 -0
- package/src/nodes/lag/update.js +133 -0
- package/src/nodes/median3/index.js +19 -0
- package/src/nodes/median3/init.js +40 -0
- package/src/nodes/median3/introspect.js +116 -0
- package/src/nodes/median3/publish-to.js +21 -0
- package/src/nodes/median3/recompute.js +12 -0
- package/src/nodes/median3/reset.js +15 -0
- package/src/nodes/median3/update.js +58 -0
- package/src/nodes/moments-digest/copy-from.js +12 -0
- package/src/nodes/moments-digest/flush.js +17 -0
- package/src/nodes/moments-digest/index.js +132 -0
- package/src/nodes/moments-digest/init.js +84 -0
- package/src/nodes/moments-digest/introspect.js +122 -0
- package/src/nodes/moments-digest/prelude.js +28 -0
- package/src/nodes/moments-digest/publish-to.js +31 -0
- package/src/nodes/moments-digest/recompute.js +9 -0
- package/src/nodes/moments-digest/reset.js +20 -0
- package/src/nodes/moments-digest/update.js +182 -0
- package/src/nodes/node-names.js +67 -0
- package/src/nodes/page-hinkley/index.js +20 -0
- package/src/nodes/page-hinkley/init.js +66 -0
- package/src/nodes/page-hinkley/introspect.js +193 -0
- package/src/nodes/page-hinkley/publish-to.js +28 -0
- package/src/nodes/page-hinkley/recompute.js +14 -0
- package/src/nodes/page-hinkley/reset.js +23 -0
- package/src/nodes/page-hinkley/update.js +105 -0
- package/src/nodes/pass-if/index.js +21 -0
- package/src/nodes/pass-if/init.js +37 -0
- package/src/nodes/pass-if/introspect.js +93 -0
- package/src/nodes/pass-if/publish-to.js +15 -0
- package/src/nodes/pass-if/recompute.js +15 -0
- package/src/nodes/pass-if/reset.js +18 -0
- package/src/nodes/pass-if/update.js +37 -0
- package/src/nodes/persist-if/helpers.js +111 -0
- package/src/nodes/persist-if/index.js +9 -0
- package/src/nodes/persist-if/init.js +98 -0
- package/src/nodes/persist-if/introspect.js +106 -0
- package/src/nodes/persist-if/publish-to.js +21 -0
- package/src/nodes/persist-if/recompute.js +20 -0
- package/src/nodes/persist-if/reset.js +35 -0
- package/src/nodes/persist-if/update.js +112 -0
- package/src/nodes/persistence-check/index.js +14 -0
- package/src/nodes/persistence-check/init.js +38 -0
- package/src/nodes/persistence-check/introspect.js +144 -0
- package/src/nodes/persistence-check/publish-to.js +11 -0
- package/src/nodes/persistence-check/recompute.js +7 -0
- package/src/nodes/persistence-check/reset.js +12 -0
- package/src/nodes/persistence-check/update.js +57 -0
- package/src/nodes/process-index/index.js +15 -0
- package/src/nodes/process-index/init.js +48 -0
- package/src/nodes/process-index/introspect.js +182 -0
- package/src/nodes/process-index/publish-to.js +29 -0
- package/src/nodes/process-index/recompute.js +8 -0
- package/src/nodes/process-index/reset.js +8 -0
- package/src/nodes/process-index/update.js +75 -0
- package/src/nodes/ratio/index.js +14 -0
- package/src/nodes/ratio/init.js +37 -0
- package/src/nodes/ratio/introspect.js +155 -0
- package/src/nodes/ratio/publish-to.js +15 -0
- package/src/nodes/ratio/recompute.js +7 -0
- package/src/nodes/ratio/reset.js +7 -0
- package/src/nodes/ratio/update.js +47 -0
- package/src/nodes/sanitize/index.js +82 -0
- package/src/nodes/sanitize/init.js +82 -0
- package/src/nodes/sanitize/introspect.js +194 -0
- package/src/nodes/sanitize/publish-to.js +15 -0
- package/src/nodes/sanitize/recompute.js +8 -0
- package/src/nodes/sanitize/reset.js +14 -0
- package/src/nodes/sanitize/update.js +102 -0
- package/src/nodes/spike-guard/index.js +14 -0
- package/src/nodes/spike-guard/init.js +47 -0
- package/src/nodes/spike-guard/introspect.js +130 -0
- package/src/nodes/spike-guard/publish-to.js +35 -0
- package/src/nodes/spike-guard/recompute.js +14 -0
- package/src/nodes/spike-guard/reset.js +19 -0
- package/src/nodes/spike-guard/update.js +89 -0
- package/src/nodes/sse-emitter/index.js +7 -0
- package/src/nodes/sse-emitter/init.js +105 -0
- package/src/nodes/sse-emitter/introspect.js +134 -0
- package/src/nodes/sse-emitter/publish-to.js +6 -0
- package/src/nodes/sse-emitter/recompute.js +16 -0
- package/src/nodes/sse-emitter/reset.js +19 -0
- package/src/nodes/sse-emitter/update.js +69 -0
- package/src/nodes/sse-emitter/utils/broadcast-heartbeat.js +36 -0
- package/src/nodes/sse-emitter/utils/build-message.js +31 -0
- package/src/nodes/sse-emitter/utils/connection-manager.js +45 -0
- package/src/nodes/sse-emitter/utils/endpoint-initializer.js +20 -0
- package/src/nodes/sse-emitter/utils/health-monitor.js +38 -0
- package/src/nodes/sse-emitter/utils/qos-handler.js +41 -0
- package/src/nodes/state-change-detector/helpers.js +35 -0
- package/src/nodes/state-change-detector/index.js +12 -0
- package/src/nodes/state-change-detector/init.js +56 -0
- package/src/nodes/state-change-detector/introspect.js +146 -0
- package/src/nodes/state-change-detector/publish-to.js +20 -0
- package/src/nodes/state-change-detector/recompute.js +6 -0
- package/src/nodes/state-change-detector/reset.js +20 -0
- package/src/nodes/state-change-detector/update.js +97 -0
- package/src/nodes/sw-stats/index.js +14 -0
- package/src/nodes/sw-stats/init.js +51 -0
- package/src/nodes/sw-stats/introspect.js +131 -0
- package/src/nodes/sw-stats/publish-to.js +45 -0
- package/src/nodes/sw-stats/recompute.js +33 -0
- package/src/nodes/sw-stats/reset.js +15 -0
- package/src/nodes/sw-stats/update.js +39 -0
- package/src/nodes/swing-watch/index.js +17 -0
- package/src/nodes/swing-watch/init.js +144 -0
- package/src/nodes/swing-watch/introspect.js +188 -0
- package/src/nodes/swing-watch/publish-to.js +53 -0
- package/src/nodes/swing-watch/recompute.js +22 -0
- package/src/nodes/swing-watch/reset.js +55 -0
- package/src/nodes/swing-watch/update.js +767 -0
- package/src/nodes/tally/index.js +15 -0
- package/src/nodes/tally/init.js +32 -0
- package/src/nodes/tally/introspect.js +116 -0
- package/src/nodes/tally/publish-to.js +28 -0
- package/src/nodes/tally/recompute.js +10 -0
- package/src/nodes/tally/reset.js +10 -0
- package/src/nodes/tally/update.js +44 -0
- package/src/nodes/threshold/index.js +14 -0
- package/src/nodes/threshold/init.js +121 -0
- package/src/nodes/threshold/introspect.js +192 -0
- package/src/nodes/threshold/publish-to.js +11 -0
- package/src/nodes/threshold/recompute.js +8 -0
- package/src/nodes/threshold/reset.js +14 -0
- package/src/nodes/threshold/update.js +54 -0
- package/src/nodes/transform/helpers.js +39 -0
- package/src/nodes/transform/index.js +15 -0
- package/src/nodes/transform/init.js +35 -0
- package/src/nodes/transform/introspect.js +118 -0
- package/src/nodes/transform/publish-to.js +23 -0
- package/src/nodes/transform/recompute.js +12 -0
- package/src/nodes/transform/reset.js +11 -0
- package/src/nodes/transform/update.js +32 -0
- package/src/nodes/trend/compute-confidence.js +112 -0
- package/src/nodes/trend/index.js +14 -0
- package/src/nodes/trend/init.js +81 -0
- package/src/nodes/trend/introspect.js +151 -0
- package/src/nodes/trend/publish-to.js +22 -0
- package/src/nodes/trend/recompute.js +15 -0
- package/src/nodes/trend/reset.js +26 -0
- package/src/nodes/trend/update.js +93 -0
- package/src/nodes/tw-stats/copy-from.js +16 -0
- package/src/nodes/tw-stats/flush.js +12 -0
- package/src/nodes/tw-stats/index.js +41 -0
- package/src/nodes/tw-stats/init.js +92 -0
- package/src/nodes/tw-stats/introspect.js +171 -0
- package/src/nodes/tw-stats/publish-to.js +108 -0
- package/src/nodes/tw-stats/recompute.js +9 -0
- package/src/nodes/tw-stats/reset.js +16 -0
- package/src/nodes/tw-stats/update.js +113 -0
- package/src/nodes/unbalance/index.js +15 -0
- package/src/nodes/unbalance/init.js +59 -0
- package/src/nodes/unbalance/introspect.js +167 -0
- package/src/nodes/unbalance/publish-to.js +52 -0
- package/src/nodes/unbalance/recompute.js +11 -0
- package/src/nodes/unbalance/reset.js +10 -0
- package/src/nodes/unbalance/update.js +83 -0
- package/src/nodes/vector-distance/index.js +14 -0
- package/src/nodes/vector-distance/init.js +54 -0
- package/src/nodes/vector-distance/introspect.js +149 -0
- package/src/nodes/vector-distance/publish-to.js +35 -0
- package/src/nodes/vector-distance/recompute.js +11 -0
- package/src/nodes/vector-distance/reset.js +20 -0
- package/src/nodes/vector-distance/update.js +139 -0
- package/src/nodes/winnow/index.js +12 -0
- package/src/nodes/winnow/init.js +82 -0
- package/src/nodes/winnow/introspect.js +253 -0
- package/src/nodes/winnow/publish-to.js +51 -0
- package/src/nodes/winnow/recompute.js +14 -0
- package/src/nodes/winnow/reset.js +34 -0
- package/src/nodes/winnow/update.js +203 -0
- package/src/tools/training/features.js +227 -0
- package/src/tools/training/index.js +21 -0
- package/src/tools/training/logistic.js +238 -0
- package/src/tools/training/metrics.js +126 -0
- package/src/tools/training/scale.js +239 -0
- package/src/tools/training/validate.js +103 -0
- package/src/windowing/count-sliding/create.js +16 -0
- package/src/windowing/count-sliding/get-reverse-chronological-index.js +19 -0
- package/src/windowing/count-sliding/index.js +6 -0
- package/src/windowing/count-sliding/is-not-full.js +5 -0
- package/src/windowing/count-sliding/push.js +15 -0
- package/src/windowing/count-sliding/reset.js +11 -0
- package/src/windowing/count-sliding/size.js +5 -0
- package/src/windowing/time-sliding/create.js +25 -0
- package/src/windowing/time-sliding/push.js +42 -0
- package/CODE_OF_CONDUCT.md +0 -46
- package/CONTRIBUTING.md +0 -132
- package/docs/.nojekyll +0 -0
- package/docs/CONTRIBUTING.md +0 -134
- package/docs/README.md +0 -132
- package/docs/_coverpage.md +0 -26
- package/docs/_media/bg.png +0 -0
- package/docs/_media/wink-logo.png +0 -0
- package/docs/_navbar.md +0 -14
- package/docs/_sidebar.md +0 -2
- package/docs/index.html +0 -44
- package/eslint.config.js +0 -283
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Introspection metadata for kalman1d node.
|
|
3
|
+
*
|
|
4
|
+
* Defines supported stats, control methods, capabilities, DSL schema,
|
|
5
|
+
* and default options. All getters return defensive copies.
|
|
6
|
+
*
|
|
7
|
+
* The kalman1d node is a 1-D Kalman filter for model-based state estimation
|
|
8
|
+
* with statistical outlier detection via an innovation gate (chi-squared test).
|
|
9
|
+
*
|
|
10
|
+
* **Compositional value — the innovation signal:**
|
|
11
|
+
* The node's primary differentiator is publishing the innovation (prediction
|
|
12
|
+
* error) as a first-class stat. This enables powerful downstream compositions:
|
|
13
|
+
*
|
|
14
|
+
* - **innovation → appraise**: Detect "temperature is SURPRISING given the
|
|
15
|
+
* current draw" rather than "temperature is high" — the Kalman model
|
|
16
|
+
* accounts for expected variation from control inputs, leaving only the
|
|
17
|
+
* unexplained component as anomaly evidence.
|
|
18
|
+
*
|
|
19
|
+
* - **innovation → esStats → threshold**: Monitor mean(|innovation|) over
|
|
20
|
+
* time to detect model mismatch and trigger recalibration.
|
|
21
|
+
*
|
|
22
|
+
* - **innovationGate → predict**: Classify the PATTERN of normalized
|
|
23
|
+
* surprises across multiple signals for fault diagnosis.
|
|
24
|
+
*
|
|
25
|
+
* - **ghost + innovation**: In shadow partitions, innovation grows as
|
|
26
|
+
* simulated degradation diverges — enabling data-driven prognosis.
|
|
27
|
+
*
|
|
28
|
+
* **Control input generalization:**
|
|
29
|
+
* "Control" is not limited to actuator commands — it is any measurable causal
|
|
30
|
+
* influence: fuel rate → tank level, occupancy → CO2, current → temperature.
|
|
31
|
+
* This makes Kalman applicable wherever an approximate physical model exists.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
35
|
+
|
|
36
|
+
// ── Supported Features ──────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
const SUPPORTED_STATS = [ 'filtered', 'variance', 'innovation', 'innovationGate' ];
|
|
39
|
+
|
|
40
|
+
const STAT_DESCRIPTIONS = {
|
|
41
|
+
filtered: 'Optimal state estimate (Kalman-filtered value)',
|
|
42
|
+
variance: 'Estimation error covariance — quantifies uncertainty in the estimate',
|
|
43
|
+
innovation: 'Prediction error (signed): z - H·xHat. The anomaly signal — ' +
|
|
44
|
+
'feeds appraise for surprise detection, esStats for model monitoring',
|
|
45
|
+
innovationGate: 'Normalized innovation squared (chi-squared(1) statistic). ' +
|
|
46
|
+
'Unitless anomaly score — directly thresholdable with known false-alarm rates'
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
50
|
+
reset: 'Clears accumulated state; next measurement auto-initializes',
|
|
51
|
+
enable: 'Enables node processing',
|
|
52
|
+
disable: 'Disables node processing',
|
|
53
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
54
|
+
unpause: 'Resumes node processing after pause'
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const NODE_TYPE = 'Kalman 1d';
|
|
58
|
+
|
|
59
|
+
const CAPABILITIES = {
|
|
60
|
+
description: 'Model-based state estimation with statistical outlier detection via innovation gating',
|
|
61
|
+
features: [
|
|
62
|
+
'Optimal state estimation for linear systems with Gaussian noise',
|
|
63
|
+
'Control-aware prediction using any causal influence as input',
|
|
64
|
+
'Innovation signal as first-class output for downstream anomaly detection',
|
|
65
|
+
'Statistical outlier detection via Mahalanobis distance (chi-squared gate)',
|
|
66
|
+
'Dual-mode operation: exclude outliers OR track sudden changes (follow mode)',
|
|
67
|
+
'Gap-tolerant prediction with bounded uncertainty growth',
|
|
68
|
+
'Auto-initialization from first measurement',
|
|
69
|
+
'Variance floor prevents filter lock on long-running streams'
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ── Getter Functions (defensive copies) ─────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
76
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
77
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
78
|
+
export const getNodeType = () => NODE_TYPE;
|
|
79
|
+
export const getCapabilities = () => ( {
|
|
80
|
+
...CAPABILITIES,
|
|
81
|
+
features: CAPABILITIES.features.slice()
|
|
82
|
+
} );
|
|
83
|
+
|
|
84
|
+
// ── Default Options ─────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
export const DEFAULT_OPTIONS = {
|
|
87
|
+
sensorVariance: 1,
|
|
88
|
+
processVariance: 0.01,
|
|
89
|
+
chi2Threshold: 6.63,
|
|
90
|
+
followMode: false,
|
|
91
|
+
stateTransition: 1,
|
|
92
|
+
measurement: 1,
|
|
93
|
+
controlModel: 0,
|
|
94
|
+
varianceLimit: 100
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// ── DSL Metadata ────────────────────────────────────────────────────────────
|
|
98
|
+
|
|
99
|
+
const DSL_METADATA = {
|
|
100
|
+
specSchema: {
|
|
101
|
+
nodeType: {
|
|
102
|
+
type: 'string',
|
|
103
|
+
required: true,
|
|
104
|
+
value: NODE_TYPE
|
|
105
|
+
},
|
|
106
|
+
name: {
|
|
107
|
+
type: 'string',
|
|
108
|
+
required: true,
|
|
109
|
+
validator: validators.identifier,
|
|
110
|
+
error: 'name must be a valid identifier'
|
|
111
|
+
},
|
|
112
|
+
from: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
required: true,
|
|
115
|
+
properties: {
|
|
116
|
+
x: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
required: true,
|
|
119
|
+
validator: validators.noSpaces,
|
|
120
|
+
error: 'from.x must not contain spaces'
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// ── Control input field (optional) ──────────────────────────
|
|
126
|
+
// Any measurable causal influence: heater power, fuel rate,
|
|
127
|
+
// motor current, occupancy. Not just actuator commands.
|
|
128
|
+
control: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
required: false,
|
|
131
|
+
validator: validators.noSpaces,
|
|
132
|
+
error: 'control field name must not contain spaces'
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
// ── Noise covariances ───────────────────────────────────────
|
|
136
|
+
// R: measurement noise variance, in measurement² units. The
|
|
137
|
+
// expected variance of the sensor noise on each measurement.
|
|
138
|
+
// Per-sensor via field-keyed objects.
|
|
139
|
+
sensorVariance: {
|
|
140
|
+
type: 'numberOrFieldKeyed',
|
|
141
|
+
required: false,
|
|
142
|
+
default: DEFAULT_OPTIONS.sensorVariance,
|
|
143
|
+
validator: validators.positive,
|
|
144
|
+
error: 'sensorVariance must be positive'
|
|
145
|
+
},
|
|
146
|
+
// Q: process noise variance, in state² units. The expected
|
|
147
|
+
// variance of the unmodeled change in state from one step to
|
|
148
|
+
// the next. Used directly in the predict step (PPred = F·P·F + Q).
|
|
149
|
+
// Larger Q → filter trusts new measurements more (responsive).
|
|
150
|
+
// Smaller Q → filter trusts its model more (smoother).
|
|
151
|
+
processVariance: {
|
|
152
|
+
type: 'numberOrFieldKeyed',
|
|
153
|
+
required: false,
|
|
154
|
+
default: DEFAULT_OPTIONS.processVariance,
|
|
155
|
+
validator: validators.positive,
|
|
156
|
+
error: 'processVariance must be positive'
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
// ── Outlier detection ───────────────────────────────────────
|
|
160
|
+
// Chi-squared(1) threshold for innovation gate.
|
|
161
|
+
// 3.84 = 95%, 6.63 = 99%, 10.84 = 99.9%
|
|
162
|
+
chi2Threshold: {
|
|
163
|
+
type: 'numberOrFieldKeyed',
|
|
164
|
+
required: false,
|
|
165
|
+
default: DEFAULT_OPTIONS.chi2Threshold,
|
|
166
|
+
validator: validators.positive,
|
|
167
|
+
error: 'chi2Threshold must be positive'
|
|
168
|
+
},
|
|
169
|
+
// Follow mode tracks jumps; exclude mode rejects outliers.
|
|
170
|
+
followMode: {
|
|
171
|
+
type: 'boolean',
|
|
172
|
+
required: false,
|
|
173
|
+
default: DEFAULT_OPTIONS.followMode
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
// ── State-space model coefficients ──────────────────────────
|
|
177
|
+
// F: state transition (how state evolves between measurements).
|
|
178
|
+
// Default 1 = random walk / constant-state model.
|
|
179
|
+
stateTransition: {
|
|
180
|
+
type: 'number',
|
|
181
|
+
required: false,
|
|
182
|
+
default: DEFAULT_OPTIONS.stateTransition
|
|
183
|
+
},
|
|
184
|
+
// H: measurement model (how state appears in measurements).
|
|
185
|
+
// Default 1 = direct observation. H=0 is never valid — makes
|
|
186
|
+
// the state unobservable and causes division by zero in update.
|
|
187
|
+
measurement: {
|
|
188
|
+
type: 'number',
|
|
189
|
+
required: false,
|
|
190
|
+
default: DEFAULT_OPTIONS.measurement,
|
|
191
|
+
validator: validators.nonZero,
|
|
192
|
+
error: 'measurement (H) must be nonzero'
|
|
193
|
+
},
|
|
194
|
+
// G: control coefficient. Can be zero (no control), positive
|
|
195
|
+
// (e.g., heater adding heat), or negative (e.g., fuel consumption
|
|
196
|
+
// depleting tank level). Per-sensor via field-keyed objects.
|
|
197
|
+
controlModel: {
|
|
198
|
+
type: 'numberOrFieldKeyed',
|
|
199
|
+
required: false,
|
|
200
|
+
default: DEFAULT_OPTIONS.controlModel
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// ── Numerical safeguards ────────────────────────────────────
|
|
204
|
+
// Pmax = varianceLimit * R. Bounds uncertainty growth during
|
|
205
|
+
// prediction-only periods (missing data, gaps).
|
|
206
|
+
varianceLimit: {
|
|
207
|
+
type: 'number',
|
|
208
|
+
required: false,
|
|
209
|
+
default: DEFAULT_OPTIONS.varianceLimit,
|
|
210
|
+
validator: validators.positive,
|
|
211
|
+
error: 'varianceLimit must be positive'
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
// ── Output configuration ────────────────────────────────────
|
|
215
|
+
stats: {
|
|
216
|
+
type: 'object',
|
|
217
|
+
required: true,
|
|
218
|
+
propertyNames: SUPPORTED_STATS,
|
|
219
|
+
propertySchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
required: true,
|
|
222
|
+
properties: {
|
|
223
|
+
storeAs: {
|
|
224
|
+
type: 'string',
|
|
225
|
+
required: true,
|
|
226
|
+
validator: validators.identifier,
|
|
227
|
+
error: 'storeAs must be a valid identifier'
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
// No cross-field constraints needed:
|
|
235
|
+
// - controlModel != 0 without `control` is valid (control defaults to 0)
|
|
236
|
+
// - All parameter combinations are mathematically valid
|
|
237
|
+
crossFieldValidators: [],
|
|
238
|
+
|
|
239
|
+
// Pattern: nameXOutputsOptions
|
|
240
|
+
// DSL: .kalman1d( name, field, { filtered: 'est' }, { control: 'power' } )
|
|
241
|
+
buildSpec: ( name, x, stats, options ) => ( {
|
|
242
|
+
nodeType: NODE_TYPE,
|
|
243
|
+
name,
|
|
244
|
+
from: { x },
|
|
245
|
+
stats,
|
|
246
|
+
...options
|
|
247
|
+
} )
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Publish function for kalman1d node.
|
|
3
|
+
*
|
|
4
|
+
* Copies computed state values to the output message, or propagates NaN
|
|
5
|
+
* if the last input was invalid. Skipped entirely when disabled.
|
|
6
|
+
*
|
|
7
|
+
* All four stats (filtered, variance, innovation, innovationGate) are
|
|
8
|
+
* published on every tick — including after outlier exclusion. This is
|
|
9
|
+
* intentional: innovation reflects what reality looked like (measurement-
|
|
10
|
+
* space), while filtered/variance reflect the predict-only state. Downstream
|
|
11
|
+
* nodes (appraise, esStats, predict) need the innovation from excluded
|
|
12
|
+
* measurements to detect model mismatch and build anomaly conviction.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { publishNaN } from '../../core/utils/node/index.js';
|
|
16
|
+
|
|
17
|
+
const publishTo = function ( state, msg ) {
|
|
18
|
+
if ( state.disable ) return;
|
|
19
|
+
|
|
20
|
+
// Propagate NaN if unhealthy (fault isolation)
|
|
21
|
+
if ( state.inputValidationFailed ) {
|
|
22
|
+
publishNaN( state, msg );
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Publish only the stats the user requested
|
|
27
|
+
const stats = state.stats;
|
|
28
|
+
if ( stats.filtered ) msg[ stats.filtered.storeAs ] = state.xHat;
|
|
29
|
+
if ( stats.variance ) msg[ stats.variance.storeAs ] = state.P;
|
|
30
|
+
if ( stats.innovation ) msg[ stats.innovation.storeAs ] = state.innovation;
|
|
31
|
+
if ( stats.innovationGate ) msg[ stats.innovationGate.storeAs ] = state.innovationGate;
|
|
32
|
+
}; // publishTo()
|
|
33
|
+
|
|
34
|
+
export default publishTo;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recompute for kalman1d node.
|
|
3
|
+
*
|
|
4
|
+
* Clamps the estimation error covariance P to [Pmin, Pmax]. The update()
|
|
5
|
+
* function already clamps after prediction, but recompute provides a periodic
|
|
6
|
+
* safety net for very long-running pipelines where floating-point drift could
|
|
7
|
+
* push P outside its valid bounds.
|
|
8
|
+
*
|
|
9
|
+
* Unlike butterworth-filter (which is a no-op), Kalman's recompute is active
|
|
10
|
+
* because the covariance evolves recursively and is susceptible to long-term
|
|
11
|
+
* drift in edge cases (e.g., extended prediction-only periods with near-zero
|
|
12
|
+
* process noise).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const recompute = function ( state ) {
|
|
16
|
+
// Clamp covariance to valid range
|
|
17
|
+
if ( state.P > state.Pmax ) state.P = state.Pmax;
|
|
18
|
+
if ( state.P < state.Pmin ) state.P = state.Pmin;
|
|
19
|
+
|
|
20
|
+
return true;
|
|
21
|
+
}; // recompute()
|
|
22
|
+
|
|
23
|
+
export default recompute;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reset for kalman1d node.
|
|
3
|
+
*
|
|
4
|
+
* Clears all accumulated state to pre-first-measurement condition. The next
|
|
5
|
+
* valid measurement will auto-initialize the filter (xHat = z/H, P = R/H²).
|
|
6
|
+
*
|
|
7
|
+
* Idempotent: multiple reset() calls produce the same state as a single call.
|
|
8
|
+
* Model parameters (R, Q, F, G, H, thresholds) are preserved — only the
|
|
9
|
+
* estimation state is cleared.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const reset = function ( state ) {
|
|
13
|
+
// ── Estimation state ────────────────────────────────────────────────
|
|
14
|
+
state.xHat = 0;
|
|
15
|
+
state.P = 0;
|
|
16
|
+
state.isInitialized = false;
|
|
17
|
+
|
|
18
|
+
// ── Innovation outputs ──────────────────────────────────────────────
|
|
19
|
+
state.innovation = 0;
|
|
20
|
+
state.innovationGate = 0;
|
|
21
|
+
|
|
22
|
+
// ── Diagnostics ─────────────────────────────────────────────────────
|
|
23
|
+
state.updateCount = 0;
|
|
24
|
+
state.outlierCount = 0;
|
|
25
|
+
|
|
26
|
+
return true;
|
|
27
|
+
}; // reset()
|
|
28
|
+
|
|
29
|
+
export default reset;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Core Kalman filter algorithm for kalman1d node (hot path).
|
|
3
|
+
*
|
|
4
|
+
* Implements the standard 1-D Kalman filter: predict → innovate → gate →
|
|
5
|
+
* update/exclude/follow. Zero allocations — all scalar arithmetic.
|
|
6
|
+
*
|
|
7
|
+
* **Why innovation is stored BEFORE the gate branch:**
|
|
8
|
+
*
|
|
9
|
+
* Innovation (prediction error) is a measurement-space signal about reality,
|
|
10
|
+
* not a filter-state signal about the filter's decision. When the filter
|
|
11
|
+
* excludes an outlier, the innovation that triggered the exclusion is itself
|
|
12
|
+
* valuable diagnostic information for downstream nodes:
|
|
13
|
+
*
|
|
14
|
+
* 1. **innovation → appraise**: "Temperature is SURPRISING given the heater
|
|
15
|
+
* power" — the Kalman model accounts for expected heating; only the
|
|
16
|
+
* UNEXPLAINED component triggers appraise conviction. Excluded outlier
|
|
17
|
+
* innovation tells appraise about a real event the filter chose to ignore.
|
|
18
|
+
*
|
|
19
|
+
* 2. **innovation → esStats → threshold**: Sustained mean(|innovation|)
|
|
20
|
+
* detects model mismatch. If we suppressed innovation on excluded
|
|
21
|
+
* measurements, esStats would underestimate the mismatch.
|
|
22
|
+
*
|
|
23
|
+
* 3. **innovation → predict (fault classification)**: Classify the PATTERN
|
|
24
|
+
* of surprises across multiple Kalman-filtered signals. Missing the
|
|
25
|
+
* outlier innovation breaks the pattern.
|
|
26
|
+
*
|
|
27
|
+
* 4. **ghost + innovation**: In shadow partitions running degradation
|
|
28
|
+
* scenarios, innovation grows as the perturbation diverges from the
|
|
29
|
+
* real model — enabling data-driven prognosis without a physics model.
|
|
30
|
+
*
|
|
31
|
+
* 5. **Signed innovation + control direction**: "Positive innovation with
|
|
32
|
+
* negative control input = external heat source" — directional
|
|
33
|
+
* diagnostics require the raw signed innovation even on excluded ticks.
|
|
34
|
+
*
|
|
35
|
+
* References:
|
|
36
|
+
* - Bar-Shalom, Li, Kirubarajan, "Estimation with Applications to Tracking
|
|
37
|
+
* and Navigation" (Wiley, 2001), Ch. 5: 1-D Kalman filter, innovation gate.
|
|
38
|
+
* - Mahalanobis distance ≡ |innovation|/√S; innovationGate = D² ~ χ²(1).
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const DENORMAL_THRESHOLD = 1e-30;
|
|
42
|
+
|
|
43
|
+
const update = function ( state, msg ) {
|
|
44
|
+
// ── Guard ───────────────────────────────────────────────────────────
|
|
45
|
+
if ( state.disable || state.pause ) return state;
|
|
46
|
+
|
|
47
|
+
const z = msg[ state.x ];
|
|
48
|
+
|
|
49
|
+
// Reset health flag on each update
|
|
50
|
+
state.inputValidationFailed = false;
|
|
51
|
+
|
|
52
|
+
// ── Fault handling: NaN, Infinity, undefined ────────────────────────
|
|
53
|
+
// Missing data → prediction-only: covariance grows (via the predict
|
|
54
|
+
// step on the next valid measurement), innovation is NaN downstream.
|
|
55
|
+
if ( !Number.isFinite( z ) ) {
|
|
56
|
+
state.inputValidationFailed = true;
|
|
57
|
+
return state;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ── Control input (optional, defaults to 0) ─────────────────────────
|
|
61
|
+
// When absent or invalid, no control influence this tick.
|
|
62
|
+
// "Control" = any measurable causal influence, not just actuators:
|
|
63
|
+
// fuel rate → tank level, occupancy → CO₂, current → temperature.
|
|
64
|
+
const u = state.controlField ? ( msg[ state.controlField ] ?? 0 ) : 0;
|
|
65
|
+
const uSafe = Number.isFinite( u ) ? u : 0;
|
|
66
|
+
|
|
67
|
+
// ── Auto-initialize from first measurement ──────────────────────────
|
|
68
|
+
// No prior estimate needed: x₀ = z/H, P₀ = R/H².
|
|
69
|
+
// Innovation is 0 (no prediction to compare against).
|
|
70
|
+
if ( !state.isInitialized ) {
|
|
71
|
+
state.xHat = z / state.H;
|
|
72
|
+
state.P = state.R / ( state.H * state.H );
|
|
73
|
+
state.innovation = 0;
|
|
74
|
+
state.innovationGate = 0;
|
|
75
|
+
state.isInitialized = true;
|
|
76
|
+
state.updateCount += 1;
|
|
77
|
+
return state;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── PREDICT ─────────────────────────────────────────────────────────
|
|
81
|
+
// State prediction: x̂(k|k-1) = F·x̂(k-1) + G·u(k)
|
|
82
|
+
// Covariance prediction: P(k|k-1) = F·P(k-1)·F + Q
|
|
83
|
+
const xPred = ( state.F * state.xHat ) + ( state.G * uSafe );
|
|
84
|
+
let PPred = ( state.F * state.P * state.F ) + state.Q;
|
|
85
|
+
|
|
86
|
+
// Clamp covariance to valid range
|
|
87
|
+
if ( PPred > state.Pmax ) PPred = state.Pmax;
|
|
88
|
+
if ( PPred < state.Pmin ) PPred = state.Pmin;
|
|
89
|
+
|
|
90
|
+
// ── INNOVATE (always computed, always stored) ───────────────────────
|
|
91
|
+
// Innovation: ν = z - H·x̂(k|k-1)
|
|
92
|
+
// Innovation covariance: S = H·P(k|k-1)·H + R
|
|
93
|
+
// Innovation gate: ν²/S ~ χ²(1) when model is correct
|
|
94
|
+
const innovation = z - ( state.H * xPred );
|
|
95
|
+
const S = ( state.H * PPred * state.H ) + state.R;
|
|
96
|
+
const innovationGate = ( innovation * innovation ) / S;
|
|
97
|
+
|
|
98
|
+
// Store BEFORE branching — downstream always sees what reality
|
|
99
|
+
// looked like, regardless of the filter's gate decision.
|
|
100
|
+
state.innovation = innovation;
|
|
101
|
+
state.innovationGate = innovationGate;
|
|
102
|
+
|
|
103
|
+
// ── OUTLIER GATE ────────────────────────────────────────────────────
|
|
104
|
+
// Mahalanobis distance² > χ²(1) threshold → statistically unlikely
|
|
105
|
+
if ( innovationGate > state.chi2Threshold ) {
|
|
106
|
+
state.outlierCount += 1;
|
|
107
|
+
|
|
108
|
+
if ( state.followMode ) {
|
|
109
|
+
// Follow mode: reset estimate to track the jump.
|
|
110
|
+
// Use case: systems with legitimate step changes (valve
|
|
111
|
+
// positions, load changes, operating mode switches).
|
|
112
|
+
state.xHat = z / state.H;
|
|
113
|
+
state.P = state.R / ( state.H * state.H );
|
|
114
|
+
} else {
|
|
115
|
+
// Exclude mode: reject measurement, advance with prediction.
|
|
116
|
+
// Time marches on — xHat and P reflect the predict-only state.
|
|
117
|
+
// Innovation IS published (stored above) so downstream sees
|
|
118
|
+
// the anomalous measurement even though the filter ignored it.
|
|
119
|
+
// Use case: continuous processes where outliers are sensor
|
|
120
|
+
// glitches (temperature, pressure, tank level).
|
|
121
|
+
state.xHat = xPred;
|
|
122
|
+
state.P = PPred;
|
|
123
|
+
state.updateCount += 1;
|
|
124
|
+
return state;
|
|
125
|
+
}
|
|
126
|
+
} else {
|
|
127
|
+
// ── NORMAL KALMAN UPDATE ────────────────────────────────────
|
|
128
|
+
// Kalman gain: K = P(k|k-1)·H / S
|
|
129
|
+
// State update: x̂(k) = x̂(k|k-1) + K·ν
|
|
130
|
+
// Covariance update: P(k) = (1 - K·H)·P(k|k-1)
|
|
131
|
+
const K = ( PPred * state.H ) / S;
|
|
132
|
+
state.xHat = xPred + ( K * innovation );
|
|
133
|
+
state.P = ( 1 - ( K * state.H ) ) * PPred;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Denormal flushing ───────────────────────────────────────────────
|
|
137
|
+
// Prevents 100x slowdown on x86/ARM when P approaches zero.
|
|
138
|
+
if ( Math.abs( state.P ) < DENORMAL_THRESHOLD ) state.P = 0;
|
|
139
|
+
|
|
140
|
+
state.updateCount += 1;
|
|
141
|
+
return state;
|
|
142
|
+
}; // update()
|
|
143
|
+
|
|
144
|
+
export default update;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Public API for kernel node.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports all lifecycle functions and shared control signals.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { default as init } from './init.js';
|
|
8
|
+
export { default as update } from './update.js';
|
|
9
|
+
export { default as reset } from './reset.js';
|
|
10
|
+
export { default as recompute } from './recompute.js';
|
|
11
|
+
export { default as publishTo } from './publish-to.js';
|
|
12
|
+
// Direct imports for common control functions
|
|
13
|
+
export { default as disable } from '../../core/utils/node/disable.js';
|
|
14
|
+
export { default as enable } from '../../core/utils/node/enable.js';
|
|
15
|
+
export { default as pause } from '../../core/utils/node/pause.js';
|
|
16
|
+
export { default as unpause } from '../../core/utils/node/unpause.js';
|
|
17
|
+
// Re-export everything from introspection.js
|
|
18
|
+
export * from './introspect.js';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Initialization for kernel node.
|
|
3
|
+
*
|
|
4
|
+
* Validates the spec, resolves the kernel (from preset name or custom array),
|
|
5
|
+
* reverses it for efficient sequential memory access during convolution,
|
|
6
|
+
* allocates a doubled ring buffer (2 × kernelLength) that enables a
|
|
7
|
+
* branch-free contiguous read window during convolution (see update.js for
|
|
8
|
+
* the "duplicated head" pattern), and returns the fully initialized state.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { validateSpec } from '../../core/utils/node/index.js';
|
|
12
|
+
import * as introspect from './introspect.js';
|
|
13
|
+
import PRESETS from './presets.js';
|
|
14
|
+
import { resolveScalar, resolveArray } from '../../core/utils/options/resolve-field-keyed.js';
|
|
15
|
+
|
|
16
|
+
const init = function ( spec ) {
|
|
17
|
+
// 1. Validate
|
|
18
|
+
validateSpec( spec, introspect );
|
|
19
|
+
|
|
20
|
+
// 2. Initialize state
|
|
21
|
+
const state = Object.create( null );
|
|
22
|
+
// Standard input parameter health flag during update: false means healthy
|
|
23
|
+
state.inputValidationFailed = false;
|
|
24
|
+
// Ensure node is enabled by default
|
|
25
|
+
state.disable = false;
|
|
26
|
+
state.pause = false;
|
|
27
|
+
|
|
28
|
+
// Store field name and output configuration
|
|
29
|
+
state.x = spec.from.x;
|
|
30
|
+
state.stats = spec.stats;
|
|
31
|
+
|
|
32
|
+
// Get kernel from preset or custom kernel
|
|
33
|
+
// Supports both direct and field-keyed specification
|
|
34
|
+
const presetSpec = resolveScalar( spec.preset, state.x );
|
|
35
|
+
const kernelSpec = resolveArray( spec.kernel, state.x );
|
|
36
|
+
|
|
37
|
+
// Reverse kernel for efficient sequential memory access during convolution
|
|
38
|
+
state.kernel = presetSpec ?
|
|
39
|
+
PRESETS[ presetSpec ].slice().reverse() :
|
|
40
|
+
kernelSpec.slice().reverse();
|
|
41
|
+
// For debugging/introspection
|
|
42
|
+
state.presetName = presetSpec || 'userDefined';
|
|
43
|
+
|
|
44
|
+
// Pre-compute kernel length for hot path
|
|
45
|
+
state.kernelLength = state.kernel.length;
|
|
46
|
+
|
|
47
|
+
// Doubled ring buffer: size is the logical window length, buffer holds
|
|
48
|
+
// 2 × size doubles so every push writes to both halves (see update.js).
|
|
49
|
+
// Preserves the { size, buffer, head, used } shape expected by
|
|
50
|
+
// publishTo (via isNotFull) and reset (via ring.reset).
|
|
51
|
+
state.ring = Object.create( null );
|
|
52
|
+
state.ring.size = state.kernelLength;
|
|
53
|
+
state.ring.buffer = new Float64Array( state.kernelLength * 2 );
|
|
54
|
+
state.ring.head = 0;
|
|
55
|
+
state.ring.used = 0;
|
|
56
|
+
|
|
57
|
+
// Result storage
|
|
58
|
+
state.result = 0;
|
|
59
|
+
|
|
60
|
+
// Node type for debugging
|
|
61
|
+
state.nodeType = introspect.getNodeType();
|
|
62
|
+
|
|
63
|
+
return state;
|
|
64
|
+
}; // init()
|
|
65
|
+
|
|
66
|
+
export default init;
|