Commit a922aba
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
- docs
- libs/SensorThreshold
- tests/suite
0 commit comments