Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit b391638

Browse files
committed
feat: Add commodity sensing unit tests and fix feature extractor bugs
Add comprehensive test suite (36 tests) for the ADR-013 commodity sensing module covering all components: RingBuffer, SimulatedCollector determinism, feature extraction (time-domain stats, FFT spectral analysis, band power isolation), CUSUM change-point detection, presence/motion classification, and end-to-end CommodityBackend pipeline. Fix feature_extractor.py: add missing _trim_to_window method that caused AttributeError on the WifiSample extraction path, add post-trim sample count guard, and handle constant-signal edge case in skewness/kurtosis computation to prevent scipy RuntimeWarning. https://claude.ai/code/session_01Ki7pvEZtJDvqJkmyn6B714
1 parent 5210ef4 commit b391638

2 files changed

Lines changed: 725 additions & 2 deletions

File tree

v1/src/sensing/feature_extractor.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ def extract(self, samples: List[WifiSample]) -> RssiFeatures:
103103

104104
# Trim to window
105105
samples = self._trim_to_window(samples)
106+
if len(samples) < 4:
107+
return RssiFeatures(n_samples=len(samples))
106108
rssi = np.array([s.rssi_dbm for s in samples], dtype=np.float64)
107109
timestamps = np.array([s.timestamp for s in samples], dtype=np.float64)
108110

@@ -158,17 +160,34 @@ def extract_from_array(
158160

159161
return features
160162

163+
# -- window trimming -----------------------------------------------------
164+
165+
def _trim_to_window(self, samples: List[WifiSample]) -> List[WifiSample]:
166+
"""Keep only samples within the most recent ``window_seconds``."""
167+
if not samples:
168+
return samples
169+
latest_ts = samples[-1].timestamp
170+
cutoff = latest_ts - self._window_seconds
171+
trimmed = [s for s in samples if s.timestamp >= cutoff]
172+
return trimmed
173+
161174
# -- time-domain ---------------------------------------------------------
162175

163176
@staticmethod
164177
def _compute_time_domain(rssi: NDArray[np.float64], features: RssiFeatures) -> None:
165178
features.mean = float(np.mean(rssi))
166179
features.variance = float(np.var(rssi, ddof=1)) if len(rssi) > 1 else 0.0
167180
features.std = float(np.std(rssi, ddof=1)) if len(rssi) > 1 else 0.0
168-
features.skewness = float(scipy_stats.skew(rssi, bias=False)) if len(rssi) > 2 else 0.0
169-
features.kurtosis = float(scipy_stats.kurtosis(rssi, bias=False)) if len(rssi) > 3 else 0.0
170181
features.range = float(np.ptp(rssi))
171182

183+
# Guard against constant signals where higher moments are undefined
184+
if features.std < 1e-12:
185+
features.skewness = 0.0
186+
features.kurtosis = 0.0
187+
else:
188+
features.skewness = float(scipy_stats.skew(rssi, bias=False)) if len(rssi) > 2 else 0.0
189+
features.kurtosis = float(scipy_stats.kurtosis(rssi, bias=False)) if len(rssi) > 3 else 0.0
190+
172191
q75, q25 = np.percentile(rssi, [75, 25])
173192
features.iqr = float(q75 - q25)
174193

0 commit comments

Comments
 (0)