@winkjs/composer 0.0.2 → 0.4.1
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 +106 -80
- package/package.json +56 -28
- 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,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Introspection metadata for the winnow node.
|
|
3
|
+
*
|
|
4
|
+
* Winnow is a trajectory-aware significance detector. It tracks a
|
|
5
|
+
* projected trajectory from the last significant point and fires when
|
|
6
|
+
* the signal deviates beyond an adaptive, self-tightening threshold.
|
|
7
|
+
*
|
|
8
|
+
* The name comes from agriculture: winnowing separates the grain
|
|
9
|
+
* (significant samples) from the chaff (redundant ones). Pair with
|
|
10
|
+
* passIf for compression, emitIf for edge-to-cloud gating, or
|
|
11
|
+
* threshold on deviation for adaptive alarming.
|
|
12
|
+
*
|
|
13
|
+
* Reads slope, noise, direction, and gate from upstream nodes via
|
|
14
|
+
* configurable field names — does not compute them itself.
|
|
15
|
+
*
|
|
16
|
+
* Validated through 10 compression experiments on synthetic signals
|
|
17
|
+
* and real 20 kHz bearing vibration data.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
21
|
+
|
|
22
|
+
// ── Supported Features ──────────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
const SUPPORTED_STATS = [ 'deviation', 'predicted', 'significant', 'xPrev', 'tPrev' ];
|
|
25
|
+
|
|
26
|
+
const STAT_DESCRIPTIONS = {
|
|
27
|
+
deviation: 'Distance from projected trajectory (always published)',
|
|
28
|
+
predicted: 'Where the trajectory says the signal should be',
|
|
29
|
+
significant: 'Boolean: did the signal stray beyond the adaptive threshold?',
|
|
30
|
+
xPrev: 'Previous tick input value (from 1-sample buffer). Non-NaN only on chi-squared gate-fire keeps. Requires bufferPrev: true.',
|
|
31
|
+
tPrev: 'Previous tick timestamp (from 1-sample buffer). Non-NaN only on chi-squared gate-fire keeps. Requires bufferPrev: true and timestampField.'
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
35
|
+
reset: 'Clears anchor, counter, and accumulated state',
|
|
36
|
+
enable: 'Enables node processing',
|
|
37
|
+
disable: 'Disables node processing',
|
|
38
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
39
|
+
unpause: 'Resumes node processing after pause'
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const NODE_TYPE = 'Winnow';
|
|
43
|
+
|
|
44
|
+
const CAPABILITIES = {
|
|
45
|
+
description: 'Trajectory-aware significance detector — separates the grain from the chaff',
|
|
46
|
+
features: [
|
|
47
|
+
'Slope-aware deadband: projects anchor forward using the rate of change',
|
|
48
|
+
'Progressive tightening: threshold narrows as segment grows',
|
|
49
|
+
'Step detection via chi-squared innovation gate from upstream kalman1d',
|
|
50
|
+
'Trend reversal detection from upstream trend node',
|
|
51
|
+
'Adaptive threshold: K × noise floor (zero per-signal tuning)',
|
|
52
|
+
'Gap prevention: forces a point through after maxGap samples',
|
|
53
|
+
'Graceful degradation: works without slope/gate/direction (reduces to flat deadband)',
|
|
54
|
+
'Publishes deviation and predicted for downstream analytics',
|
|
55
|
+
'Optional 1-sample buffer (bufferPrev): publishes xPrev/tPrev on gate-fire keeps for spike-region anchoring'
|
|
56
|
+
]
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// ── Default Options ─────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
export const DEFAULT_OPTIONS = {
|
|
62
|
+
K: 2,
|
|
63
|
+
tightenBase: 100,
|
|
64
|
+
maxGap: 500,
|
|
65
|
+
slopeField: 'roc',
|
|
66
|
+
noiseField: 'stdev',
|
|
67
|
+
dirField: 'trendDir',
|
|
68
|
+
gateField: 'gate',
|
|
69
|
+
chi2Threshold: 6.63,
|
|
70
|
+
bufferPrev: false
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// ── Tunable Parameters ──────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export const TUNABLE_PARAMS = [ 'K' ];
|
|
76
|
+
|
|
77
|
+
// ── Getter Functions (defensive copies) ─────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
80
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
81
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
82
|
+
export const getNodeType = () => NODE_TYPE;
|
|
83
|
+
export const getCapabilities = () => ( {
|
|
84
|
+
...CAPABILITIES,
|
|
85
|
+
features: CAPABILITIES.features.slice()
|
|
86
|
+
} );
|
|
87
|
+
|
|
88
|
+
// ── DSL Metadata ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
const DSL_METADATA = {
|
|
91
|
+
specSchema: {
|
|
92
|
+
nodeType: {
|
|
93
|
+
type: 'string',
|
|
94
|
+
required: true,
|
|
95
|
+
value: NODE_TYPE
|
|
96
|
+
},
|
|
97
|
+
name: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
required: true,
|
|
100
|
+
validator: validators.identifier,
|
|
101
|
+
error: 'name must be a valid identifier'
|
|
102
|
+
},
|
|
103
|
+
from: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
required: true,
|
|
106
|
+
properties: {
|
|
107
|
+
x: {
|
|
108
|
+
type: 'string',
|
|
109
|
+
required: true,
|
|
110
|
+
validator: validators.noSpaces,
|
|
111
|
+
error: 'from.x must not contain spaces'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
stats: {
|
|
116
|
+
type: 'object',
|
|
117
|
+
required: true,
|
|
118
|
+
minProperties: 1,
|
|
119
|
+
propertyNames: SUPPORTED_STATS,
|
|
120
|
+
propertySchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
required: true,
|
|
123
|
+
properties: {
|
|
124
|
+
storeAs: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
required: true,
|
|
127
|
+
validator: validators.identifier,
|
|
128
|
+
error: 'storeAs must be a valid identifier'
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// ── Sensitivity ─────────────────────────────────────────
|
|
135
|
+
K: {
|
|
136
|
+
type: 'numberOrFunctionOrFieldKeyed',
|
|
137
|
+
required: false,
|
|
138
|
+
default: DEFAULT_OPTIONS.K,
|
|
139
|
+
validator: validators.positiveOrFunction,
|
|
140
|
+
error: 'K must be positive or a function'
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// ── Tightening ──────────────────────────────────────────
|
|
144
|
+
tightenBase: {
|
|
145
|
+
type: 'numberOrFieldKeyed',
|
|
146
|
+
required: false,
|
|
147
|
+
default: DEFAULT_OPTIONS.tightenBase,
|
|
148
|
+
validator: validators.positive,
|
|
149
|
+
error: 'tightenBase must be positive'
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// ── Gap prevention ──────────────────────────────────────
|
|
153
|
+
maxGap: {
|
|
154
|
+
type: 'numberOrFieldKeyed',
|
|
155
|
+
required: false,
|
|
156
|
+
default: DEFAULT_OPTIONS.maxGap,
|
|
157
|
+
integer: true,
|
|
158
|
+
validator: validators.positiveInteger,
|
|
159
|
+
error: 'maxGap must be a positive integer'
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
// ── Upstream field names ─────────────────────────────────
|
|
163
|
+
slopeField: {
|
|
164
|
+
type: 'string',
|
|
165
|
+
required: false,
|
|
166
|
+
default: DEFAULT_OPTIONS.slopeField,
|
|
167
|
+
validator: validators.noSpaces,
|
|
168
|
+
error: 'slopeField must not contain spaces'
|
|
169
|
+
},
|
|
170
|
+
noiseField: {
|
|
171
|
+
type: 'string',
|
|
172
|
+
required: false,
|
|
173
|
+
default: DEFAULT_OPTIONS.noiseField,
|
|
174
|
+
validator: validators.noSpaces,
|
|
175
|
+
error: 'noiseField must not contain spaces'
|
|
176
|
+
},
|
|
177
|
+
dirField: {
|
|
178
|
+
type: 'string',
|
|
179
|
+
required: false,
|
|
180
|
+
default: DEFAULT_OPTIONS.dirField,
|
|
181
|
+
validator: validators.noSpaces,
|
|
182
|
+
error: 'dirField must not contain spaces'
|
|
183
|
+
},
|
|
184
|
+
gateField: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
required: false,
|
|
187
|
+
default: DEFAULT_OPTIONS.gateField,
|
|
188
|
+
validator: validators.noSpaces,
|
|
189
|
+
error: 'gateField must not contain spaces'
|
|
190
|
+
},
|
|
191
|
+
|
|
192
|
+
// ── Step detection threshold ─────────────────────────────
|
|
193
|
+
// numberOrFieldKeyed matches its siblings tightenBase/maxGap and the
|
|
194
|
+
// resolveScalar call in init.js, so a per-field value is accepted.
|
|
195
|
+
chi2Threshold: {
|
|
196
|
+
type: 'numberOrFieldKeyed',
|
|
197
|
+
required: false,
|
|
198
|
+
default: DEFAULT_OPTIONS.chi2Threshold,
|
|
199
|
+
validator: validators.positive,
|
|
200
|
+
error: 'chi2Threshold must be positive'
|
|
201
|
+
},
|
|
202
|
+
|
|
203
|
+
// ── 1-sample buffer for spike-region anchoring ──────────
|
|
204
|
+
bufferPrev: {
|
|
205
|
+
type: 'boolean',
|
|
206
|
+
required: false,
|
|
207
|
+
default: DEFAULT_OPTIONS.bufferPrev
|
|
208
|
+
},
|
|
209
|
+
timestampField: {
|
|
210
|
+
type: 'string',
|
|
211
|
+
required: false,
|
|
212
|
+
validator: validators.noSpaces,
|
|
213
|
+
error: 'timestampField must not contain spaces'
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
crossFieldValidators: [
|
|
218
|
+
{
|
|
219
|
+
fields: [ 'stats', 'bufferPrev' ],
|
|
220
|
+
validator: ( spec ) => {
|
|
221
|
+
// xPrev and tPrev stats require bufferPrev to be true
|
|
222
|
+
if ( ( spec.stats?.xPrev !== undefined || spec.stats?.tPrev !== undefined ) &&
|
|
223
|
+
!spec.bufferPrev ) {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
},
|
|
228
|
+
error: 'xPrev and tPrev stats require bufferPrev: true'
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
fields: [ 'stats', 'timestampField' ],
|
|
232
|
+
validator: ( spec ) => {
|
|
233
|
+
// tPrev stat requires timestampField
|
|
234
|
+
if ( spec.stats?.tPrev !== undefined && spec.timestampField === undefined ) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
return true;
|
|
238
|
+
},
|
|
239
|
+
error: 'tPrev stat requires timestampField to be specified'
|
|
240
|
+
}
|
|
241
|
+
],
|
|
242
|
+
|
|
243
|
+
// Pattern: NAME_X_OUTPUTS_OPTIONS -> ( name, x, stats, options )
|
|
244
|
+
buildSpec: ( name, x, stats, options ) => ( {
|
|
245
|
+
nodeType: NODE_TYPE,
|
|
246
|
+
name,
|
|
247
|
+
from: { x },
|
|
248
|
+
stats,
|
|
249
|
+
...options
|
|
250
|
+
} )
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Publish winnow outputs to the message.
|
|
3
|
+
*
|
|
4
|
+
* Copies deviation, predicted, and significant to the message for
|
|
5
|
+
* downstream consumption. All three are always computed by update()
|
|
6
|
+
* — publishTo iterates only over stats the user configured.
|
|
7
|
+
*
|
|
8
|
+
* When input validation has failed, publishes NaN for numeric stats
|
|
9
|
+
* and false for significant — downstream nodes see invalid data,
|
|
10
|
+
* not stale values.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { publishNaN } from '../../core/utils/node/index.js';
|
|
14
|
+
|
|
15
|
+
const publishTo = function ( state, msg ) {
|
|
16
|
+
if ( state.disable ) return;
|
|
17
|
+
|
|
18
|
+
if ( state.inputValidationFailed ) {
|
|
19
|
+
publishNaN( state, msg );
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const stats = state.stats;
|
|
24
|
+
|
|
25
|
+
if ( stats.deviation ) {
|
|
26
|
+
msg[ stats.deviation.storeAs ] = state.deviation;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if ( stats.predicted ) {
|
|
30
|
+
msg[ stats.predicted.storeAs ] = state.predicted;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if ( stats.significant ) {
|
|
34
|
+
msg[ stats.significant.storeAs ] = state.significant;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ── Buffer-backed stats (conditional on gate-fire keep) ─────────
|
|
38
|
+
// xPrev/tPrev are published as the buffered (k-1) values only when
|
|
39
|
+
// the current sample was kept by Check 2 (chi-squared gate fire).
|
|
40
|
+
// On non-gate keeps and on non-kept messages, they publish NaN —
|
|
41
|
+
// signalling "no lookback anchor for this sample." This prevents
|
|
42
|
+
// noisy non-gate prevValues from degrading the kfSmoothed trajectory.
|
|
43
|
+
if ( state.hasXPrev ) {
|
|
44
|
+
msg[ stats.xPrev.storeAs ] = state.keptByGate ? state.xPrev : NaN;
|
|
45
|
+
}
|
|
46
|
+
if ( state.hasTPrev ) {
|
|
47
|
+
msg[ stats.tPrev.storeAs ] = state.keptByGate ? state.tPrev : NaN;
|
|
48
|
+
}
|
|
49
|
+
}; // publishTo()
|
|
50
|
+
|
|
51
|
+
export default publishTo;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Recompute for winnow — no-op.
|
|
3
|
+
*
|
|
4
|
+
* Winnow's state is a reference trajectory (anchor + slope) and a
|
|
5
|
+
* counter. No accumulated sums or running averages that could drift
|
|
6
|
+
* due to floating-point error. Counter overflow is safe to 2^53
|
|
7
|
+
* (at 20 kHz, that is 14 million years).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const recompute = function () {
|
|
11
|
+
return true;
|
|
12
|
+
}; // recompute()
|
|
13
|
+
|
|
14
|
+
export default recompute;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Reset winnow to initial state.
|
|
3
|
+
*
|
|
4
|
+
* Clears the anchor, counter, and all accumulated state. The next
|
|
5
|
+
* message will trigger the warmup path (anchor is null), establishing
|
|
6
|
+
* a fresh reference trajectory.
|
|
7
|
+
*
|
|
8
|
+
* Called by the controller node when a downstream condition fires
|
|
9
|
+
* (e.g., after a step change detected by kalman1d's innovation gate).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const reset = function ( state ) {
|
|
13
|
+
state.anchor = null;
|
|
14
|
+
state.anchorSlope = 0;
|
|
15
|
+
state.anchorTime = 0;
|
|
16
|
+
state.lastPassedAt = 0;
|
|
17
|
+
state.prevDirection = null;
|
|
18
|
+
state.counter = 0;
|
|
19
|
+
state.deviation = 0;
|
|
20
|
+
state.predicted = 0;
|
|
21
|
+
state.significant = false;
|
|
22
|
+
state.tunableErrorLogged = false;
|
|
23
|
+
|
|
24
|
+
// ── Buffer state ───────────────────────────────────────────────
|
|
25
|
+
state.bufferedX = NaN;
|
|
26
|
+
state.bufferedT = NaN;
|
|
27
|
+
state.keptByGate = false;
|
|
28
|
+
state.xPrev = NaN;
|
|
29
|
+
state.tPrev = NaN;
|
|
30
|
+
|
|
31
|
+
return true;
|
|
32
|
+
}; // reset()
|
|
33
|
+
|
|
34
|
+
export default reset;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Update function for the winnow node (hot path).
|
|
3
|
+
*
|
|
4
|
+
* Winnow answers one question per message: "Has this signal strayed
|
|
5
|
+
* from where it was heading?" Five checks run in order — the first
|
|
6
|
+
* one that fires wins. If none fire, the signal is on trajectory
|
|
7
|
+
* and `significant` is set to false.
|
|
8
|
+
*
|
|
9
|
+
* The anchor is the last significant point — its value, slope, and
|
|
10
|
+
* timestamp. Between significant points, the anchor projects forward
|
|
11
|
+
* using its slope: `predicted = anchor + slope × elapsed`. The
|
|
12
|
+
* deviation from this projection, measured against the local noise
|
|
13
|
+
* floor, is the core compression signal.
|
|
14
|
+
*
|
|
15
|
+
* Zero allocation. All state mutations are scalar assignments.
|
|
16
|
+
* prevDirection updates unconditionally on every message — not only
|
|
17
|
+
* when significant (design audit finding: the closure does this on
|
|
18
|
+
* line 86, before the deadband check; missing it causes trend
|
|
19
|
+
* reversals during non-significant periods to be lost).
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
// Extracted helper: set anchor and mark significant.
|
|
23
|
+
// Called from each of the five checks. Zero allocation — scalar
|
|
24
|
+
// assignments only. Reduces update() cyclomatic complexity.
|
|
25
|
+
const setAnchor = function ( state, xVal, slope ) {
|
|
26
|
+
state.anchor = xVal;
|
|
27
|
+
state.anchorSlope = Number.isFinite( slope ) ? slope : 0;
|
|
28
|
+
state.anchorTime = state.counter;
|
|
29
|
+
state.lastPassedAt = state.counter;
|
|
30
|
+
state.significant = true;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const update = function ( state, msg ) {
|
|
34
|
+
// ── Guards ──────────────────────────────────────────────────────
|
|
35
|
+
// Disabled: skip everything. Paused: skip update, publishTo still
|
|
36
|
+
// runs (last-known values stay visible downstream).
|
|
37
|
+
if ( state.disable || state.pause ) return state;
|
|
38
|
+
|
|
39
|
+
// Every message increments the counter — used for elapsed time
|
|
40
|
+
// computation and gap prevention.
|
|
41
|
+
state.counter += 1;
|
|
42
|
+
|
|
43
|
+
// ── Read primary input ──────────────────────────────────────────
|
|
44
|
+
// The signal being tracked. Upstream median3 or sanitize should
|
|
45
|
+
// have cleaned it, but we guard anyway.
|
|
46
|
+
const xVal = msg[ state.x ];
|
|
47
|
+
|
|
48
|
+
if ( !Number.isFinite( xVal ) ) {
|
|
49
|
+
state.inputValidationFailed = true;
|
|
50
|
+
return state;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
state.inputValidationFailed = false;
|
|
54
|
+
|
|
55
|
+
// ── Buffer previous tick (when enabled) ─────────────────────────
|
|
56
|
+
// Copy buffer → output, then overwrite buffer with current values.
|
|
57
|
+
// O(1), zero allocation. The output (xPrev/tPrev) holds the k-1
|
|
58
|
+
// value until publishTo decides whether to publish it.
|
|
59
|
+
if ( state.bufferPrev ) {
|
|
60
|
+
state.xPrev = state.bufferedX;
|
|
61
|
+
state.tPrev = state.bufferedT;
|
|
62
|
+
state.bufferedX = xVal;
|
|
63
|
+
if ( state.timestampField ) {
|
|
64
|
+
state.bufferedT = msg[ state.timestampField ];
|
|
65
|
+
}
|
|
66
|
+
state.keptByGate = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ── Resolve tunable K ───────────────────────────────────────────
|
|
70
|
+
// K is the sensitivity multiplier: "store when deviation exceeds
|
|
71
|
+
// K × noise floor." It can be a static number or a function that
|
|
72
|
+
// reads from the message (e.g., scaleBy('signalQuality', 2)).
|
|
73
|
+
// On error, the last good value is retained (assignment fails
|
|
74
|
+
// because RHS throws before LHS is written).
|
|
75
|
+
try {
|
|
76
|
+
state.K = state.KFn( msg ); // eslint-disable-line new-cap
|
|
77
|
+
if ( state.tunableErrorLogged ) {
|
|
78
|
+
state.tunableErrorLogged = false;
|
|
79
|
+
}
|
|
80
|
+
} catch ( error ) {
|
|
81
|
+
if ( !state.tunableErrorLogged ) {
|
|
82
|
+
state.tunableErrorLogged = true;
|
|
83
|
+
console.error( `WinkComposer/${state.nodeType}: tunable threw: ${error.message}` );
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ── Read supporting fields from upstream nodes ───────────────────
|
|
88
|
+
// These are optional — winnow degrades gracefully without them:
|
|
89
|
+
// noise undefined → warmup fires forever (safe, visible)
|
|
90
|
+
// slope undefined → anchorSlope stays 0 (flat deadband)
|
|
91
|
+
// direction undefined → no trend reversal triggers
|
|
92
|
+
// gate undefined → no step detection
|
|
93
|
+
const noise = msg[ state.noiseField ];
|
|
94
|
+
const slope = msg[ state.slopeField ];
|
|
95
|
+
const direction = msg[ state.dirField ];
|
|
96
|
+
const gate = msg[ state.gateField ];
|
|
97
|
+
|
|
98
|
+
// ── Detect trend reversal ───────────────────────────────────────
|
|
99
|
+
// Check BEFORE updating prevDirection — we need to compare the
|
|
100
|
+
// current direction against what we saw last time.
|
|
101
|
+
// Exclude 'learning' transitions (trend node warmup) — these are
|
|
102
|
+
// not genuine reversals.
|
|
103
|
+
const dirChanged = (
|
|
104
|
+
state.prevDirection !== null &&
|
|
105
|
+
direction !== state.prevDirection &&
|
|
106
|
+
direction !== 'learning' &&
|
|
107
|
+
state.prevDirection !== 'learning'
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
// Update unconditionally — on EVERY message, not only significant
|
|
111
|
+
// ones. If we only update on significant events, we miss reversals
|
|
112
|
+
// that develop during quiet (non-significant) periods. This was
|
|
113
|
+
// caught in the design audit: the original closure updates
|
|
114
|
+
// prevTrendDir on every call at line 86, before the deadband check.
|
|
115
|
+
state.prevDirection = direction;
|
|
116
|
+
|
|
117
|
+
// ── Compute trajectory projection ───────────────────────────────
|
|
118
|
+
// The anchor projects forward using its slope at the time it was
|
|
119
|
+
// set. For a linear ramp, the projection matches the signal →
|
|
120
|
+
// deviation ≈ 0 → high compression. For a sinusoidal peak, the
|
|
121
|
+
// projection overshoots → deviation grows → significant fires.
|
|
122
|
+
const elapsed = state.counter - state.anchorTime;
|
|
123
|
+
const predicted = state.anchor === null ?
|
|
124
|
+
xVal :
|
|
125
|
+
state.anchor + ( state.anchorSlope * elapsed );
|
|
126
|
+
const deviation = Math.abs( xVal - predicted );
|
|
127
|
+
|
|
128
|
+
// Always publish deviation and predicted — useful for downstream
|
|
129
|
+
// analytics (adaptive alarming, process monitoring) regardless of
|
|
130
|
+
// whether the point is significant.
|
|
131
|
+
state.deviation = deviation;
|
|
132
|
+
state.predicted = predicted;
|
|
133
|
+
|
|
134
|
+
// ── Check 1: Warmup ─────────────────────────────────────────────
|
|
135
|
+
// Anchor not yet set (first message) or noise estimate not
|
|
136
|
+
// available (esStats needs 3 samples to publish). Accept the point
|
|
137
|
+
// and establish the anchor. Every partition starts here.
|
|
138
|
+
if ( state.anchor === null || !Number.isFinite( noise ) ) {
|
|
139
|
+
setAnchor( state, xVal, slope );
|
|
140
|
+
return state;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Check 2: Step change ────────────────────────────────────────
|
|
144
|
+
// The upstream kalman1d publishes an innovation gate — a
|
|
145
|
+
// chi-squared(1) statistic that measures how surprising the
|
|
146
|
+
// current sample is relative to the model's prediction. When
|
|
147
|
+
// it exceeds the threshold (6.63 = 99% confidence), the signal
|
|
148
|
+
// did something the model could not predict. This is a structural
|
|
149
|
+
// break — accept immediately and reset the anchor.
|
|
150
|
+
if ( Number.isFinite( gate ) && gate > state.chi2Threshold ) {
|
|
151
|
+
state.keptByGate = true;
|
|
152
|
+
setAnchor( state, xVal, slope );
|
|
153
|
+
return state;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ── Check 3: Trend reversal ─────────────────────────────────────
|
|
157
|
+
// The upstream trend node classifies the signal as learning,
|
|
158
|
+
// stable, rising, or falling. When the direction changes (rising →
|
|
159
|
+
// falling at a peak, stable → rising at a ramp onset), the signal
|
|
160
|
+
// reached an inflection point. Store this point to preserve the
|
|
161
|
+
// shape. The dirChanged flag was computed above, before
|
|
162
|
+
// prevDirection was updated.
|
|
163
|
+
if ( dirChanged ) {
|
|
164
|
+
setAnchor( state, xVal, slope );
|
|
165
|
+
return state;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── Check 4: Slope-aware deadband with tightening ───────────────
|
|
169
|
+
// The threshold starts at K × noise and narrows as elapsed grows.
|
|
170
|
+
// At elapsed = tightenBase: full threshold (K × noise).
|
|
171
|
+
// At elapsed = 2 × tightenBase: half threshold.
|
|
172
|
+
// At elapsed = 4 × tightenBase: quarter threshold.
|
|
173
|
+
// This progressive tightening ensures that long segments without
|
|
174
|
+
// a stored point become increasingly likely to trigger — placing
|
|
175
|
+
// intermediate points in regions that would otherwise be long gaps.
|
|
176
|
+
const tightenFactor = state.tightenBase / Math.max( state.tightenBase, elapsed );
|
|
177
|
+
const threshold = state.K * noise * tightenFactor;
|
|
178
|
+
|
|
179
|
+
if ( deviation > threshold ) {
|
|
180
|
+
setAnchor( state, xVal, slope );
|
|
181
|
+
return state;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// ── Check 5: Gap prevention ─────────────────────────────────────
|
|
185
|
+
// If none of the above fired and it has been maxGap samples since
|
|
186
|
+
// the last significant point, force one through. This prevents
|
|
187
|
+
// data gaps in storage or transmission. In streaming mode there is
|
|
188
|
+
// no "last point"; maxGap serves as the tail anchor.
|
|
189
|
+
if ( ( state.counter - state.lastPassedAt ) >= state.maxGap ) {
|
|
190
|
+
setAnchor( state, xVal, slope );
|
|
191
|
+
return state;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── Not significant ─────────────────────────────────────────────
|
|
195
|
+
// The signal is on trajectory. deviation and predicted are already
|
|
196
|
+
// set above — publishTo will write them downstream. The point is
|
|
197
|
+
// redundant: it can be reconstructed by linear interpolation
|
|
198
|
+
// between the previous and next significant points.
|
|
199
|
+
state.significant = false;
|
|
200
|
+
return state;
|
|
201
|
+
}; // update()
|
|
202
|
+
|
|
203
|
+
export default update;
|