GitHub - izetg/SignalFusionKit: A reference pattern for fusing watchOS HealthKit + CoreMotion signals into a risk classification. Extracted and generalized from Ember's anomaly-detection architecture. · GitHub
/" data-turbo-transient="true" />
Skip to content
Type / to search
Sign in<br>Sign upAppearance settings
You signed in with another tab or window. Reload to refresh your session.<br>You signed out in another tab or window. Reload to refresh your session.<br>You switched accounts on another tab or window. Reload to refresh your session.
Dismiss alert
{{ message }}
izetg
SignalFusionKit
Public
Notifications<br>You must be signed in to change notification settings
Fork
Star
master
BranchesTags
Go to file
CodeOpen more actions menu
Folders and files<br>NameNameLast commit message<br>Last commit date<br>Latest commit
History<br>1 Commit<br>1 Commit
Sources/SignalFusionKit
Sources/SignalFusionKit
Tests/SignalFusionKitTests
Tests/SignalFusionKitTests
.gitignore
.gitignore
LICENSE
LICENSE
Package.swift
Package.swift
README.md
README.md
View all files
Repository files navigation
SignalFusionKit
A reference pattern for fusing multiple watchOS sensor signals — HealthKit<br>vitals, CoreMotion accelerometer data, optional CoreLocation speed — into a<br>single risk classification, with a debounce/cooldown primitive for the<br>notification side.
Extracted and generalized from the anomaly-detection pipeline built for<br>Ember, a biometric dead man's switch app. This<br>package shares the architecture , not Ember's production configuration —<br>see What this is / isn't below.
Why
Most watchOS "something bad might be happening" features (fall detection,<br>crash detection, panic triggers) end up solving the same three problems:
Combine several independent, asynchronously-arriving signals into one<br>decision, without letting a single ambiguous signal drown out a<br>single unambiguous one (e.g. a confirmed fall shouldn't be diluted by<br>averaging it against calm vitals).
Detect a sudden motion anomaly from raw accelerometer data using both a<br>"sustained" check (filters noise) and a "sharp delta" check (catches<br>instantaneous impacts a duration gate would smooth over).
Debounce the result so the user isn't re-alerted every few seconds while<br>the underlying condition persists.
SignalFusionKit is those three pieces, written as small, pure, unit-tested<br>types with zero dependency on HealthKit/CoreMotion in the parts that matter —<br>plus one thin, documented (but untested) adapter showing how to wire them to<br>real sensors.
Install
Swift Package Manager:
.package(url: "https://github.com/izetg/SignalFusionKit.git", from: "1.0.0")
Usage
.high (SpO2 below the example "low" threshold)
// 2. Feed raw accelerometer magnitude samples to detect motion anomalies.<br>let detector = MotionAnomalyDetector(config: .example)<br>let anomalyDetected = detector.ingest(magnitudeG: 4.2, at: Date())
// 3. Debounce repeated triggers.<br>let cooldown = CooldownGate(window: 60) // seconds<br>if cooldown.attempt() {<br>// safe to alert / act<br>}">import SignalFusionKit
// 1. Combine signals into a risk level.<br>let snapshot = SignalSnapshot(<br>oxygenSaturation: 93,<br>heartRateVariability: 40,<br>fallDetected: false,<br>motionAnomalyDetected: false<br>let assessment = RiskEngine.evaluate(snapshot, config: .example)<br>// assessment.level -> .high (SpO2 below the example "low" threshold)
// 2. Feed raw accelerometer magnitude samples to detect motion anomalies.<br>let detector = MotionAnomalyDetector(config: .example)<br>let anomalyDetected = detector.ingest(magnitudeG: 4.2, at: Date())
// 3. Debounce repeated triggers.<br>let cooldown = CooldownGate(window: 60) // seconds<br>if cooldown.attempt() {<br>// safe to alert / act
See WatchKitAdapter.swift<br>for a worked (but untested — see below) example wiring this to real<br>HealthKit + CoreMotion callbacks in a watchOS app.
What this is / isn't
Is: a reusable architecture for combining multi-sensor watchOS signals<br>into a risk decision, with the interesting logic (RiskEngine,<br>MotionAnomalyDetector, CooldownGate) fully unit-tested and free of Apple<br>framework dependencies.
Isn't: Ember's production configuration. The threshold values in<br>RiskEngineConfig.example and MotionAnomalyConfig.example are round,<br>illustrative numbers picked for readability in tests and docs — not the<br>tuned, validated values Ember actually ships with. Real thresholds need<br>real-world data collection, false-positive/negative tradeoffs specific to<br>your use case, and ideally domain-expert review. Treat the example configs<br>as the seam where your own calibration plugs in, not as a recommendation.
Isn't tested end-to-end on a Watch. RiskEngine, MotionAnomalyDetector,<br>and CooldownGate are pure Swift and covered by swift test.<br>WatchKitAdapter is real HealthKit/CoreMotion glue that needs Xcode and a<br>device/simulator to exercise — verify it there before relying on it.
Testing
swift test
Runs on any platform with the Swift toolchain — the tested types don't<br>import HealthKit or...