@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,352 @@
|
|
|
1
|
+
// nodes/appraise/introspect.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Introspection metadata for the appraise node.
|
|
5
|
+
*
|
|
6
|
+
* Defines supported stats, control methods, capabilities, and DSL schema
|
|
7
|
+
* for a two-layer SNN that accumulates evidence from multiple detection
|
|
8
|
+
* signals and produces a stable conviction score.
|
|
9
|
+
*
|
|
10
|
+
* L1 receptor neurons (per source) provide LIF spiking, BLI intensity,
|
|
11
|
+
* and rate persistence. L2 decision neuron accumulates weighted spikes
|
|
12
|
+
* with MM readout. Signed weights enable excitatory/inhibitory synapses.
|
|
13
|
+
*
|
|
14
|
+
* Key differences from standard nodes:
|
|
15
|
+
* - `from.x`: string array of source field names (like esPairwiseCorrelation)
|
|
16
|
+
* - `sources`: keyed object with per-source config (signed weights)
|
|
17
|
+
* - `thresholds`: monitor / degraded / critical classification
|
|
18
|
+
* - `l2HalfLife`: optional L2 membrane decay (defaults to max L1 tau)
|
|
19
|
+
* - `messageRate`: optional messages per timestamp unit (for warmup calc)
|
|
20
|
+
*
|
|
21
|
+
* @see ADR-004
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { validators } from '../../core/utils/validate/index.js';
|
|
25
|
+
import { DEVIATION_TYPES } from './deviation.js';
|
|
26
|
+
|
|
27
|
+
// ── Supported Statistics ────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
const SUPPORTED_STATS = [
|
|
30
|
+
'combined', 'state', 'charge',
|
|
31
|
+
'rate', 'membrane', 'calibrating'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const STAT_DESCRIPTIONS = {
|
|
35
|
+
combined: 'Conviction score via MM readout [0, 1)',
|
|
36
|
+
state: 'Threshold classification label (Normal/Monitor/Degraded/Critical)',
|
|
37
|
+
charge: 'Per-source BLI charge (published as {storeAs}_{sourceField} scalars)',
|
|
38
|
+
rate: 'Per-source firing rate (published as {storeAs}_{sourceField} scalars)',
|
|
39
|
+
membrane: 'L2 decision neuron raw membrane potential',
|
|
40
|
+
calibrating: 'Burn-in calibration active flag (boolean)'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
// ── Control Methods ─────────────────────────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
const SUPPORTED_CONTROL_METHODS = {
|
|
46
|
+
reset: 'Clears all charges and resets state to Normal',
|
|
47
|
+
enable: 'Enables node processing',
|
|
48
|
+
disable: 'Disables node processing',
|
|
49
|
+
pause: 'Pauses node processing while keeping state visible',
|
|
50
|
+
unpause: 'Resumes node processing after pause'
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ── Node Identity ───────────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const NODE_TYPE = 'Appraise';
|
|
56
|
+
|
|
57
|
+
// ── Capabilities ────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
const CAPABILITIES = {
|
|
60
|
+
description: 'Two-layer SNN that accumulates evidence from multiple detection signals into a stable conviction score',
|
|
61
|
+
features: [
|
|
62
|
+
'L1 receptor neurons: LIF spiking + BLI intensity + Rate persistence per source',
|
|
63
|
+
'L2 decision neuron: unbounded leaky accumulation with MM readout',
|
|
64
|
+
'Excitatory/inhibitory synapses via signed weights',
|
|
65
|
+
'Burn-in calibration: Theta learned from warmup baseline',
|
|
66
|
+
'Michaelis-Menten normalization — one theta per source',
|
|
67
|
+
'Five deviation types: identity, absolute, highExceedance, lowExceedance, bandExceedance',
|
|
68
|
+
'Exponential decay with configurable half-life (global default, per-source override)',
|
|
69
|
+
'Per-source charge and rate published as named scalars — compose categorize downstream for labels',
|
|
70
|
+
'Zero allocation in hot path'
|
|
71
|
+
]
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// ── Default Options ─────────────────────────────────────────────────────────
|
|
75
|
+
|
|
76
|
+
const DEFAULT_OPTIONS = {};
|
|
77
|
+
|
|
78
|
+
// ── Validators ──────────────────────────────────────────────────────────────
|
|
79
|
+
|
|
80
|
+
const deviationTypes = Array.from( DEVIATION_TYPES );
|
|
81
|
+
const isDeviationType = validators.oneOf( deviationTypes );
|
|
82
|
+
|
|
83
|
+
// Inline validator: non-zero finite number (allows negative for inhibitory synapses)
|
|
84
|
+
const nonZeroFinite = ( v ) => ( Number.isFinite( v ) && ( v !== 0 ) );
|
|
85
|
+
|
|
86
|
+
// ── DSL Metadata ────────────────────────────────────────────────────────────
|
|
87
|
+
|
|
88
|
+
const DSL_METADATA = {
|
|
89
|
+
specSchema: {
|
|
90
|
+
nodeType: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
required: true,
|
|
93
|
+
value: NODE_TYPE
|
|
94
|
+
},
|
|
95
|
+
name: {
|
|
96
|
+
type: 'string',
|
|
97
|
+
required: true,
|
|
98
|
+
validator: validators.identifier,
|
|
99
|
+
error: 'name must be a valid identifier'
|
|
100
|
+
},
|
|
101
|
+
from: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
required: true,
|
|
104
|
+
properties: {
|
|
105
|
+
x: {
|
|
106
|
+
type: 'array',
|
|
107
|
+
required: true,
|
|
108
|
+
minItems: 1,
|
|
109
|
+
maxItems: 20,
|
|
110
|
+
itemSchema: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
validator: validators.noSpaces,
|
|
113
|
+
error: 'source field name must not contain spaces'
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
sources: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
required: true,
|
|
121
|
+
minProperties: 1,
|
|
122
|
+
maxProperties: 20,
|
|
123
|
+
propertySchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
required: true,
|
|
126
|
+
properties: {
|
|
127
|
+
deviation: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
required: true,
|
|
130
|
+
validator: isDeviationType,
|
|
131
|
+
error: `source.deviation must be one of: ${deviationTypes.join( ', ' )}`
|
|
132
|
+
},
|
|
133
|
+
theta: {
|
|
134
|
+
type: 'number',
|
|
135
|
+
required: true,
|
|
136
|
+
validator: validators.positive,
|
|
137
|
+
error: 'source.theta must be a positive number'
|
|
138
|
+
},
|
|
139
|
+
weight: {
|
|
140
|
+
type: 'number',
|
|
141
|
+
required: true,
|
|
142
|
+
validator: nonZeroFinite,
|
|
143
|
+
error: 'source.weight must be a non-zero finite number'
|
|
144
|
+
},
|
|
145
|
+
halfLife: {
|
|
146
|
+
type: 'number',
|
|
147
|
+
required: false,
|
|
148
|
+
validator: validators.positive,
|
|
149
|
+
error: 'source.halfLife must be a positive number'
|
|
150
|
+
},
|
|
151
|
+
baseline: {
|
|
152
|
+
type: 'number',
|
|
153
|
+
required: false,
|
|
154
|
+
validator: validators.isFinite,
|
|
155
|
+
error: 'source.baseline must be a finite number'
|
|
156
|
+
},
|
|
157
|
+
band: {
|
|
158
|
+
type: 'object',
|
|
159
|
+
required: false,
|
|
160
|
+
properties: {
|
|
161
|
+
lower: {
|
|
162
|
+
type: 'number',
|
|
163
|
+
required: true,
|
|
164
|
+
validator: validators.isFinite,
|
|
165
|
+
error: 'band.lower must be a finite number'
|
|
166
|
+
},
|
|
167
|
+
upper: {
|
|
168
|
+
type: 'number',
|
|
169
|
+
required: true,
|
|
170
|
+
validator: validators.isFinite,
|
|
171
|
+
error: 'band.upper must be a finite number'
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
halfLife: {
|
|
179
|
+
type: 'number',
|
|
180
|
+
required: true,
|
|
181
|
+
validator: validators.positive,
|
|
182
|
+
error: 'halfLife must be a positive number'
|
|
183
|
+
},
|
|
184
|
+
l2HalfLife: {
|
|
185
|
+
type: 'number',
|
|
186
|
+
required: false,
|
|
187
|
+
validator: validators.positive,
|
|
188
|
+
error: 'l2HalfLife must be a positive number'
|
|
189
|
+
},
|
|
190
|
+
messageRate: {
|
|
191
|
+
type: 'number',
|
|
192
|
+
required: false,
|
|
193
|
+
validator: validators.positive,
|
|
194
|
+
error: 'messageRate must be a positive number'
|
|
195
|
+
},
|
|
196
|
+
thresholds: {
|
|
197
|
+
type: 'object',
|
|
198
|
+
required: true,
|
|
199
|
+
properties: {
|
|
200
|
+
monitor: {
|
|
201
|
+
type: 'object',
|
|
202
|
+
required: true,
|
|
203
|
+
properties: {
|
|
204
|
+
at: {
|
|
205
|
+
type: 'number',
|
|
206
|
+
required: true,
|
|
207
|
+
validator: validators.positive,
|
|
208
|
+
error: 'thresholds.monitor.at must be a positive number'
|
|
209
|
+
},
|
|
210
|
+
action: {
|
|
211
|
+
type: 'string',
|
|
212
|
+
required: true,
|
|
213
|
+
validator: validators.nonEmptyString,
|
|
214
|
+
error: 'thresholds.monitor.action must be a non-empty string'
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
degraded: {
|
|
219
|
+
type: 'object',
|
|
220
|
+
required: true,
|
|
221
|
+
properties: {
|
|
222
|
+
at: {
|
|
223
|
+
type: 'number',
|
|
224
|
+
required: true,
|
|
225
|
+
validator: validators.positive,
|
|
226
|
+
error: 'thresholds.degraded.at must be a positive number'
|
|
227
|
+
},
|
|
228
|
+
action: {
|
|
229
|
+
type: 'string',
|
|
230
|
+
required: true,
|
|
231
|
+
validator: validators.nonEmptyString,
|
|
232
|
+
error: 'thresholds.degraded.action must be a non-empty string'
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
critical: {
|
|
237
|
+
type: 'object',
|
|
238
|
+
required: true,
|
|
239
|
+
properties: {
|
|
240
|
+
at: {
|
|
241
|
+
type: 'number',
|
|
242
|
+
required: true,
|
|
243
|
+
validator: validators.positive,
|
|
244
|
+
error: 'thresholds.critical.at must be a positive number'
|
|
245
|
+
},
|
|
246
|
+
action: {
|
|
247
|
+
type: 'string',
|
|
248
|
+
required: true,
|
|
249
|
+
validator: validators.nonEmptyString,
|
|
250
|
+
error: 'thresholds.critical.action must be a non-empty string'
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
stats: {
|
|
257
|
+
type: 'object',
|
|
258
|
+
required: true,
|
|
259
|
+
minProperties: 1,
|
|
260
|
+
propertyNames: SUPPORTED_STATS,
|
|
261
|
+
propertySchema: {
|
|
262
|
+
type: 'object',
|
|
263
|
+
required: true,
|
|
264
|
+
properties: {
|
|
265
|
+
storeAs: {
|
|
266
|
+
type: 'string',
|
|
267
|
+
required: true,
|
|
268
|
+
validator: validators.identifier,
|
|
269
|
+
error: 'storeAs must be a valid identifier'
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
},
|
|
275
|
+
|
|
276
|
+
crossFieldValidators: [
|
|
277
|
+
{
|
|
278
|
+
fields: [ 'thresholds' ],
|
|
279
|
+
validator: ( spec ) => {
|
|
280
|
+
const { monitor, degraded, critical } = spec.thresholds;
|
|
281
|
+
return ( monitor.at < degraded.at ) && ( degraded.at < critical.at );
|
|
282
|
+
},
|
|
283
|
+
error: 'Threshold ordering must be: monitor.at < degraded.at < critical.at'
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
fields: [ 'from.x' ],
|
|
287
|
+
validator: ( spec ) => {
|
|
288
|
+
const fields = spec.from.x;
|
|
289
|
+
return ( new Set( fields ) ).size === fields.length;
|
|
290
|
+
},
|
|
291
|
+
error: 'Source field names in from.x must be unique'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
fields: [ 'from.x', 'sources' ],
|
|
295
|
+
validator: ( spec ) => {
|
|
296
|
+
const fields = spec.from.x;
|
|
297
|
+
const keys = Object.keys( spec.sources );
|
|
298
|
+
if ( fields.length !== keys.length ) return false;
|
|
299
|
+
return fields.every( ( f ) => spec.sources[ f ] !== undefined );
|
|
300
|
+
},
|
|
301
|
+
error: 'Every from.x entry must have a matching key in sources, and vice versa'
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
fields: [ 'sources' ],
|
|
305
|
+
validator: ( spec ) => Object.values( spec.sources ).every( ( cfg ) => {
|
|
306
|
+
if ( cfg.deviation === 'highExceedance' || cfg.deviation === 'lowExceedance' ) {
|
|
307
|
+
return cfg.baseline !== undefined;
|
|
308
|
+
}
|
|
309
|
+
return true;
|
|
310
|
+
} ),
|
|
311
|
+
error: 'highExceedance and lowExceedance deviation types require a baseline value'
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
fields: [ 'sources' ],
|
|
315
|
+
validator: ( spec ) => Object.values( spec.sources ).every( ( cfg ) => {
|
|
316
|
+
if ( cfg.deviation === 'bandExceedance' ) {
|
|
317
|
+
return ( cfg.band !== undefined ) &&
|
|
318
|
+
( typeof cfg.band === 'object' ) &&
|
|
319
|
+
( cfg.band.lower < cfg.band.upper );
|
|
320
|
+
}
|
|
321
|
+
return true;
|
|
322
|
+
} ),
|
|
323
|
+
error: 'bandExceedance deviation type requires a band with lower < upper'
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
fields: [ 'sources' ],
|
|
327
|
+
validator: ( spec ) => Object.values( spec.sources ).some( ( cfg ) => cfg.weight > 0 ),
|
|
328
|
+
error: 'At least one source must have a positive (excitatory) weight'
|
|
329
|
+
}
|
|
330
|
+
],
|
|
331
|
+
|
|
332
|
+
buildSpec: ( name, x, stats, options ) => ( {
|
|
333
|
+
nodeType: NODE_TYPE,
|
|
334
|
+
name,
|
|
335
|
+
from: { x },
|
|
336
|
+
stats,
|
|
337
|
+
...options
|
|
338
|
+
} )
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// ── Getter Functions (defensive copies) ─────────────────────────────────────
|
|
342
|
+
|
|
343
|
+
export const getSupportedStats = () => SUPPORTED_STATS.slice();
|
|
344
|
+
export const getStatDescriptions = () => ( { ...STAT_DESCRIPTIONS } );
|
|
345
|
+
export const getSupportedControlMethods = () => ( { ...SUPPORTED_CONTROL_METHODS } );
|
|
346
|
+
export const getNodeType = () => NODE_TYPE;
|
|
347
|
+
export const getCapabilities = () => ( {
|
|
348
|
+
...CAPABILITIES,
|
|
349
|
+
features: CAPABILITIES.features.slice()
|
|
350
|
+
} );
|
|
351
|
+
export { DEFAULT_OPTIONS };
|
|
352
|
+
export const getDSLMetadata = () => ( { ...DSL_METADATA } );
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// nodes/appraise/publish-to.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Publishes appraise node outputs to the message.
|
|
5
|
+
*
|
|
6
|
+
* Six stats: combined (number), state (string), charge (per-source scalars),
|
|
7
|
+
* rate (per-source scalars), membrane (number), calibrating (boolean).
|
|
8
|
+
*
|
|
9
|
+
* charge and rate are published as per-source named scalars using pre-built
|
|
10
|
+
* field names ({storeAs}_{sourceField}), enabling downstream categorize
|
|
11
|
+
* nodes and proper float64 QuestDB columns per source.
|
|
12
|
+
*
|
|
13
|
+
* On validation failure, all numeric stats get NaN — combined, membrane,
|
|
14
|
+
* and all per-source charge/rate fields. String and boolean stats are skipped.
|
|
15
|
+
*
|
|
16
|
+
* @see ADR-004
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Publishes computed values to the output message.
|
|
21
|
+
*
|
|
22
|
+
* @param {Object} state - Node state
|
|
23
|
+
* @param {Object} msg - Output message to populate
|
|
24
|
+
*/
|
|
25
|
+
const publishTo = function ( state, msg ) {
|
|
26
|
+
// Guard: skip if disabled
|
|
27
|
+
if ( state.disable ) return;
|
|
28
|
+
|
|
29
|
+
const stats = state.stats;
|
|
30
|
+
const n = state.sourceCount;
|
|
31
|
+
|
|
32
|
+
// Custom NaN publishing — cannot use publishNaN helper because charge
|
|
33
|
+
// and rate are per-source named scalars (chargeFields/rateFields arrays),
|
|
34
|
+
// not scalar entries in state.stats.
|
|
35
|
+
if ( state.inputValidationFailed ) {
|
|
36
|
+
if ( stats.combined ) {
|
|
37
|
+
msg[ stats.combined.storeAs ] = NaN;
|
|
38
|
+
}
|
|
39
|
+
if ( stats.membrane ) {
|
|
40
|
+
msg[ stats.membrane.storeAs ] = NaN;
|
|
41
|
+
}
|
|
42
|
+
if ( stats.charge ) {
|
|
43
|
+
const fields = state.chargeFields;
|
|
44
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
45
|
+
msg[ fields[ i ] ] = NaN;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if ( stats.rate ) {
|
|
49
|
+
const fields = state.rateFields;
|
|
50
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
51
|
+
msg[ fields[ i ] ] = NaN;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Publish configured stats
|
|
58
|
+
if ( stats.combined ) {
|
|
59
|
+
msg[ stats.combined.storeAs ] = state.combined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if ( stats.state ) {
|
|
63
|
+
msg[ stats.state.storeAs ] = state.stateName;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Per-source charge scalars (pre-built field names from init)
|
|
67
|
+
if ( stats.charge ) {
|
|
68
|
+
const fields = state.chargeFields;
|
|
69
|
+
const charges = state.charges;
|
|
70
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
71
|
+
msg[ fields[ i ] ] = charges[ i ];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if ( stats.membrane ) {
|
|
76
|
+
msg[ stats.membrane.storeAs ] = state.l2Membrane;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Per-source rate scalars (pre-built field names from init)
|
|
80
|
+
if ( stats.rate ) {
|
|
81
|
+
const fields = state.rateFields;
|
|
82
|
+
const rates = state.rates;
|
|
83
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
84
|
+
msg[ fields[ i ] ] = rates[ i ];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if ( stats.calibrating ) {
|
|
89
|
+
msg[ stats.calibrating.storeAs ] = state.calibrating;
|
|
90
|
+
}
|
|
91
|
+
}; // publishTo()
|
|
92
|
+
|
|
93
|
+
export default publishTo;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// nodes/appraise/receptor.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview L1 receptor neuron processing for the SNN appraise architecture.
|
|
5
|
+
*
|
|
6
|
+
* Each source has three parallel channels driven from the same MM-normalised input:
|
|
7
|
+
* LIF (Leaky Integrate-and-Fire) — graded spikes forwarded to L2 decision neuron
|
|
8
|
+
* BLI (Bounded Leaky Integrator) — charge in [0, 1] for intensity explanation
|
|
9
|
+
* Rate — smoothed firing count for persistence explanation
|
|
10
|
+
*
|
|
11
|
+
* LIF uses reset-by-subtraction: on spike, V -= Vth, carrying excess potential
|
|
12
|
+
* into the next cycle. The graded spike value equals V at the moment of firing
|
|
13
|
+
* (before subtraction), preserving magnitude information through to L2.
|
|
14
|
+
*
|
|
15
|
+
* Both functions are stateless, zero-alloc, and hot-path safe.
|
|
16
|
+
*
|
|
17
|
+
* @see Gerstner, W. & Kistler, W.M. (2002). Spiking Neuron Models. Cambridge.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { integrate } from './integrate.js';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Processes one receptor neuron: LIF spike + BLI integration + Rate tracking.
|
|
24
|
+
* Called when a valid, finite input is available for source i.
|
|
25
|
+
*
|
|
26
|
+
* @param {Object} state - Node state (mutated: membranes, spikes, fired, charges, rates)
|
|
27
|
+
* @param {number} i - Source index
|
|
28
|
+
* @param {number} norm - MM-normalised input from [0, 1)
|
|
29
|
+
* @param {number} decayFactor - exp( -dt / tau_i ), from [0, 1]
|
|
30
|
+
*/
|
|
31
|
+
const processReceptor = function ( state, i, norm, decayFactor ) {
|
|
32
|
+
// ── LIF Channel ─────────────────────────────────────────────────────────
|
|
33
|
+
let v = ( state.membranes[ i ] * decayFactor ) + norm;
|
|
34
|
+
|
|
35
|
+
if ( v >= state.VTH ) {
|
|
36
|
+
state.spikes[ i ] = v;
|
|
37
|
+
state.fired[ i ] = 1;
|
|
38
|
+
v -= state.VTH;
|
|
39
|
+
} else {
|
|
40
|
+
state.spikes[ i ] = 0;
|
|
41
|
+
state.fired[ i ] = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
state.membranes[ i ] = v;
|
|
45
|
+
|
|
46
|
+
// ── BLI Channel (intensity) ─────────────────────────────────────────────
|
|
47
|
+
state.charges[ i ] = integrate( state.charges[ i ], norm, decayFactor );
|
|
48
|
+
|
|
49
|
+
// ── Rate Channel (persistence) ──────────────────────────────────────────
|
|
50
|
+
state.rates[ i ] = ( state.rates[ i ] * decayFactor ) + state.fired[ i ];
|
|
51
|
+
}; // processReceptor()
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Pure decay on all channels when no valid input is available for source i.
|
|
55
|
+
* No spike is emitted, and the fired flag is cleared.
|
|
56
|
+
*
|
|
57
|
+
* @param {Object} state - Node state (mutated: membranes, spikes, fired, charges, rates)
|
|
58
|
+
* @param {number} i - Source index
|
|
59
|
+
* @param {number} decayFactor - exp( -dt / tau_i ), from [0, 1]
|
|
60
|
+
*/
|
|
61
|
+
const decayReceptor = function ( state, i, decayFactor ) {
|
|
62
|
+
state.membranes[ i ] *= decayFactor;
|
|
63
|
+
state.charges[ i ] *= decayFactor;
|
|
64
|
+
state.rates[ i ] *= decayFactor;
|
|
65
|
+
state.spikes[ i ] = 0;
|
|
66
|
+
state.fired[ i ] = 0;
|
|
67
|
+
}; // decayReceptor()
|
|
68
|
+
|
|
69
|
+
export { processReceptor, decayReceptor };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// nodes/appraise/recompute.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Numerical stability handler for two-layer SNN appraise node.
|
|
5
|
+
*
|
|
6
|
+
* Checks for NaN in L1 membranes, charges, and L2 membrane. If any NaN
|
|
7
|
+
* is detected (catastrophic numerical failure), triggers a full reset.
|
|
8
|
+
* Otherwise, clamps L1 charges to [0, 1], floors L1 membranes and L2
|
|
9
|
+
* membrane at 0, and recomputes conviction via MM readout.
|
|
10
|
+
*
|
|
11
|
+
* @see ADR-004
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import reset from './reset.js';
|
|
15
|
+
import { readout } from './decision.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Clamps state values and recomputes combined conviction.
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} state - Node state
|
|
21
|
+
* @returns {boolean} Always returns true
|
|
22
|
+
*/
|
|
23
|
+
const recompute = function ( state ) {
|
|
24
|
+
const charges = state.charges;
|
|
25
|
+
const membranes = state.membranes;
|
|
26
|
+
const n = state.sourceCount;
|
|
27
|
+
|
|
28
|
+
// ── NaN Catastrophe Check ───────────────────────────────────────────────
|
|
29
|
+
// Check L1 charges, L1 membranes, and L2 membrane
|
|
30
|
+
if ( Number.isNaN( state.l2Membrane ) ) {
|
|
31
|
+
reset( state );
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
36
|
+
if ( Number.isNaN( charges[ i ] ) || Number.isNaN( membranes[ i ] ) ) {
|
|
37
|
+
reset( state );
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ── Clamp L1 Charges to [0, 1] ─────────────────────────────────────────
|
|
43
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
44
|
+
if ( charges[ i ] > 1 ) {
|
|
45
|
+
charges[ i ] = 1;
|
|
46
|
+
} else if ( charges[ i ] < 0 ) {
|
|
47
|
+
charges[ i ] = 0;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ── Floor L1 Membranes at 0 ─────────────────────────────────────────────
|
|
52
|
+
for ( let i = 0; i < n; i += 1 ) {
|
|
53
|
+
if ( membranes[ i ] < 0 ) {
|
|
54
|
+
membranes[ i ] = 0;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Floor L2 Membrane at 0 ──────────────────────────────────────────────
|
|
59
|
+
if ( state.l2Membrane < 0 ) {
|
|
60
|
+
state.l2Membrane = 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Recompute Conviction via MM Readout ─────────────────────────────────
|
|
64
|
+
state.combined = readout( state.l2Membrane, state.l2Theta );
|
|
65
|
+
|
|
66
|
+
// ── Reclassify State ────────────────────────────────────────────────────
|
|
67
|
+
const levels = state.thresholdLevels;
|
|
68
|
+
state.stateName = 'Normal';
|
|
69
|
+
for ( let i = levels.length - 1; i >= 0; i -= 1 ) {
|
|
70
|
+
if ( state.combined >= levels[ i ].at ) {
|
|
71
|
+
state.stateName = levels[ i ].name;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return true;
|
|
77
|
+
}; // recompute()
|
|
78
|
+
|
|
79
|
+
export default recompute;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// nodes/appraise/reset.js
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Resets the two-layer SNN appraise node state.
|
|
5
|
+
*
|
|
6
|
+
* Clears all L1 state (membranes, spikes, fired, charges, rates) and L2
|
|
7
|
+
* state (membrane, combined, classification). Zeros the timestamp so the
|
|
8
|
+
* next message behaves as a cold start (no decay on first message after reset).
|
|
9
|
+
*
|
|
10
|
+
* Preserves Theta if already calibrated — the learned baseline does not
|
|
11
|
+
* change on analytical reset. Same principle as lag node ADR-008.
|
|
12
|
+
* Only re-enters calibration if warmup was still in progress.
|
|
13
|
+
*
|
|
14
|
+
* @see ADR-004
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Resets all appraise computation state to initial values.
|
|
19
|
+
* Preserves l2Theta and calibrating=false if calibration was complete.
|
|
20
|
+
*
|
|
21
|
+
* @param {Object} state - Node state to reset
|
|
22
|
+
* @returns {boolean} Always returns true (success)
|
|
23
|
+
*/
|
|
24
|
+
const reset = function ( state ) {
|
|
25
|
+
// ── L1 State ────────────────────────────────────────────────────────────
|
|
26
|
+
state.membranes.fill( 0 );
|
|
27
|
+
state.spikes.fill( 0 );
|
|
28
|
+
state.fired.fill( 0 );
|
|
29
|
+
state.charges.fill( 0 );
|
|
30
|
+
state.rates.fill( 0 );
|
|
31
|
+
|
|
32
|
+
// ── L2 State ────────────────────────────────────────────────────────────
|
|
33
|
+
state.l2Membrane = 0;
|
|
34
|
+
|
|
35
|
+
// ── Calibration ─────────────────────────────────────────────────────────
|
|
36
|
+
// Preserve Theta if already calibrated; reset warmup counter if still calibrating
|
|
37
|
+
if ( state.calibrating ) {
|
|
38
|
+
state.messageCount = 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Combined Score & Classification ─────────────────────────────────────
|
|
42
|
+
state.combined = 0;
|
|
43
|
+
state.stateName = 'Normal';
|
|
44
|
+
|
|
45
|
+
// ── Timing ──────────────────────────────────────────────────────────────
|
|
46
|
+
state.lastTimestamp = 0;
|
|
47
|
+
state.hasReceivedMessage = false;
|
|
48
|
+
state.inputValidationFailed = false;
|
|
49
|
+
|
|
50
|
+
return true;
|
|
51
|
+
}; // reset()
|
|
52
|
+
|
|
53
|
+
export default reset;
|