Skip to content

Commit a922aba

Browse files
authored
feat(SensorThreshold): add DerivedTag — continuous (X,Y) signal from N parents (#104)
Implements the 5th concrete Tag class — DerivedTag — closing the documented gap in the Tag hierarchy: continuous-output derivation from N parent Tags via a function-handle or compute-object strategy. Mirrors MonitorTag for listener wiring, lazy cache invalidation, and two-phase serialization, generalized to N parents and continuous output. Per SPEC §10 acceptance criteria 1–10: - libs/SensorThreshold/DerivedTag.m (622 lines): full implementation, lazy-memoized getXY, valueAt with right-biased binary_search ZOH (mirrors StateTag exactly — replaces SPEC §8 skeleton's naive find(...,'last')), DFS cycle detection at construction time using strcmp(Key, Key) for Octave SIGILL safety (Pitfall 3), all 11 SPEC §4 error IDs. - libs/SensorThreshold/TagRegistry.m: case 'derived' added to instantiateByKind dispatch + valid-kinds error message. - tests/suite/TestDerivedTag.m (484 lines, 35 test methods): construction (10) / computation (6) / compute-validation (2) / observer (4) / serialization (6) / integration (4) / grep gates (3). - tests/suite/ComputeAddStub.m (54 lines): test-only object-compute helper exercising the toStruct/fromStruct round-trip path. - docs/DerivedTag-spec.md: full specification (audience: implementer). Octave-specific fixes vs. SPEC §8 skeleton: - addListener: ismethod() throws on plain struct() in Octave (unlike MATLAB which returns false). Guard with isobject||class-exists check before ismethod to catch DerivedTag:invalidListener correctly. - fromStruct object-compute path: Octave does not accept feval('Class.method', ...) — replaced with str2func([cls '.fromStruct']). Both runtimes accept the str2func form on static methods. Cross-cutting audit findings (no edits applied): - DashboardSerializer.m dispatches on widget-type and source-type, never on Tag-kind. DerivedTag-bound widgets flow through the existing 'fastsense' / source.type='sensor' path transparently via TagRegistry.get(name). No code change needed. - LiveTagPipeline.m:315 + BatchTagPipeline.m:153 narrow isa(SensorTag)||isa(StateTag) checks gate raw-data ingestion via RawSource. DerivedTag has no RawSource (computes from parents), so it correctly does NOT participate in raw-data ingestion. Narrow-by-design; not modified. Verification: - Full Octave 11.1.0 repo suite: 96/96 tests pass (zero regressions). - TestDerivedTag flat-driver on Octave: 35/35 test methods pass. - MISS_HIT mh_style + mh_lint: 4 file(s) analysed, everything seems fine. - Grep gates: 0 `methods (Abstract)` blocks, 1 `classdef DerivedTag < Tag`, all 11 error IDs match SPEC §4 locked list. - Line-length ≤160 across all new/modified files. Out of scope (deferred to v2 per SPEC §11): Persist + DataStore caching, appendData streaming-tail, MinDuration debouncing logic, OnDataAvailable callback, multi-output, alignParentsZOH helper.
1 parent dece66b commit a922aba

5 files changed

Lines changed: 1928 additions & 1 deletion

File tree

0 commit comments

Comments
 (0)