feat: implement 28 additive enhancement issues (Tag analysis, EventDetection CRUD, FastSense overlays, stores) - #378
Merged
Conversation
Add the SAVE half of the TagRegistry save/load contract: a static toStructs() that returns the catalog as the cell array loadFromStructs already consumes, sorted by key for determinism. Pure assembly over each Tag subclass's existing toStruct — strictly additive, no format change. Round-trips: toStructs -> clear -> loadFromStructs restores the catalog. Tests: +3 in TestTagRegistry (empty, sorted, round-trip); 29/29 pass. MISS_HIT style+lint clean. Closes #363 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the missing READ of the id-addressed event CRUD surface. getEvent(id) searches the same set as getEvents (in-memory single-user; NDJSON-merged in cluster mode), matches Id on Event objects and struct rows (mirroring acknowledgeEvent/closeEvent), and throws EventStore:unknownEventId when absent — uniform with the sibling id-addressed mutators. Tests: +2 in TestEventStoreRw (found-by-id, not-found-throws); 9/9 pass. check_matlab_code + MISS_HIT clean. Closes #360 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The single number a sensor engineer reports (energy/dose/volume/throughput): scalar area under Y over [t0,t1]; integral()/integral([],[]) integrate the full series. Implemented as a thin positional-arg wrapper over cumulativeIntegral (#327), inheriting its toolbox-free gap-robust trapezoidal core, empty/single-sample=>0 policy, NaN-zeroing, and Tag:integralOnDiscrete warning. Base-class method — inherited by every tag kind via getXYRange. Tests: +7 in TestTag (constant=>20, triangle=>8, window==Range, empty-bounds==full, empty-data=>0, NaN-robust, discrete-warns); 39/39 pass. check_matlab_code + MISS_HIT clean. Closes #351 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the Tag statistics surface with the order-statistic dimension that getStats (#223) deliberately left out. percentile(levels,t0,t1) returns percentile values via toolbox-free linear interpolation (i = p/100*(n-1)+1) between sorted samples; output matches the level shape, NaNs are masked, empty=>NaN, levels validated to [0,100]. Convenience median()==P50 and iqr()==P75-P25. Base-class method inherited by every tag kind via getXYRange. Tests: +8 in TestTag (scalar, vector-shape, median==P50, IQR=4.5, window, NaN-robust, empty=>NaN, invalid-level errors); 47/47 pass. check_matlab_code + MISS_HIT clean. Closes #339 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…/iqr) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Close the last universal numeric-analysis gap in the Tag family. findPeaks
returns local maxima/minima with prominence, entirely toolbox-free (diff +
indexing + a baseline walk, NOT the Signal Processing Toolbox findpeaks).
Options: MinProminence, MinSeparation, Polarity (max|min|both), Range. Struct
output {times,values,prominences,polarity,count,intervals} plus a 2-out
[times,values] form. Flat-top plateaus report one peak; NaNs segment the
series; MinSeparation greedily keeps the most prominent peak; minima are the
maxima of -Y. Discrete StateTag warns Tag:findPeaksOnDiscrete. Core lives in a
static private helper detectExtrema_.
Tests: +12 in TestTag (single, multi+intervals, prominence filter, separation
merge, plateau, minima, both, NaN gap, range, 2-out, bad-option, discrete);
59/59 pass. check_matlab_code + MISS_HIT style/lint/metric clean.
Closes #329
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Close the create-without-delete asymmetry in the event lifecycle. removeEvents (lenient bulk, returns count removed, skips unknown ids) and removeEvent (strict, throws EventStore:unknownEventId on a miss) drop events from events_ by Id via the same linear scan closeEvent uses, without auto-saving (Pitfall 2). Each removed event cascades EventBinding.detach (new additive mirror of attach that purges forward + reverse indexes) and drops its single-user ack records so no dangling binding/ack survives. Tests: +5 in TestEventStoreRw (drop-one, unknown-throws, bulk-skips-unknown, binding-detach cascade, save/reload reduced set); 14/14 pass. check_matlab_code + MISS_HIT clean. Closes #354 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complete the event/annotation CRUD lifecycle (create + delete shipped) with an id-addressed, validated, save-aware EDIT. Event.editWindow is a new additive in-place mutator that (unlike close) works on an already-closed event — recomputing Duration and reusing the EndTime>=StartTime guard — so a manual annotation, closed on creation, can finally be nudged. EventStore.editEvent edits StartTime/EndTime/Notes/Severity/Category/Label by id; it validates keys before touching the event and applies the window guard first, so a bad key or inverted window leaves the event untouched. No auto-save (Pitfall 2). Tests: +7 in TestEventStoreRw (editWindow recompute+reject, edit window+notes+severity, unknown-id, unknown-field-untouched, inverted-window-untouched, save/reload); 21/21 pass. check_matlab_code + MISS_HIT clean. Closes #355 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add bulk acknowledgement for alarm-flood workflows. acknowledgeEvents(ids)
acknowledges exactly the given set (the operator's filtered/visible list) and
acknowledgeAll() acknowledges the whole unacknowledged set; both loop the
existing single-event acknowledgeEvent so each ack retains its full
{user,host,epoch,comment} audit stamp (IDENT-02). Unknown and already-acked ids
are skipped; each returns the count acknowledged; neither auto-saves (mirrors
acknowledgeEvent).
Tests: +5 in TestEventStoreRw (ack-list, skip-already-acked, skip-unknown,
ack-all + idempotent, audit-comment preserved); 26/26 pass. check_matlab_code +
MISS_HIT clean.
Closes #310
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the first relational primitive to the otherwise-unary Tag analysis family: correlate(other[,t0,t1]) returns Pearson r in [-1,1] of two tags' time-aligned series (2-out [r,n] also returns the aligned sample count). The second tag is sampled onto the first's timestamps by zero-order-hold valueAt — the same ZOH convention DerivedTag uses for mismatched parents — pairwise NaNs are dropped, and r is computed toolbox-free via sums (no Statistics Toolbox). Returns NaN for fewer than 2 aligned pairs or a constant (zero-variance) channel. Base-class method inherited by every tag kind. Unlocks the #341 lagCorrelation sibling. Tests: +9 in TestTag (identical=>1, anti=>-1, orthogonal=>0, n-out, zero-variance=>NaN, n<2=>NaN, ZOH alignment, range, bad-other); 68/68 pass. check_matlab_code + MISS_HIT clean. Closes #340 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…me-lag Add the time-delay sibling of correlate (#340). lagCorrelation ZOH-resamples both tags onto a uniform grid over A's windowed span (spacing = median A sample spacing), scans a toolbox-free normalized cross-correlation over +/-MaxLag samples (NOT xcorr/finddelay), and returns the argmax lag in time units (positive dt => B lags A). Forms: dt, [dt,r], and [dt,r,lags,rr] (a plottable r-vs-lag curve); options are positional t0,t1 and name-value MaxLag. NaN is returned when the overlap is < 2 samples or a channel is constant. Shared Pearson math extracted to a static private pearson_ helper. Tests: +7 in TestTag (recover +4 delay, identical=>lag0/r1, MaxLag clamp, full-curve shape, n<2=>NaN, zero-variance=>NaN, bad-other); 75/75 pass. check_matlab_code + MISS_HIT style/lint/metric clean. Closes #341 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Answer the canonical discrete-channel question — when did the state change,
how many times, and from what to what — directly. transitions() walks getXY and
emits a struct-array record {Time, FromState, ToState} at each actual state
change (repeated-value samples skipped), handling both numeric and cellstr Y via
strcmp/isequaln so a NaN->NaN run is not a spurious change. Empty struct for a
constant or empty channel. The categorical analog of crossings (#328) and the
change-point complement to the aggregate stateDurations (#258).
Tests: +4 in TestStateTag (numeric, cellstr, constant=>empty, empty-channel);
22/22 pass. check_matlab_code + MISS_HIT clean.
Closes #342
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ction Add the missing data-cleaning axis to the Tag analysis family. removeOutliers is a non-mutating base-class method that flags amplitude outliers with a robust test (Method hampel rolling-median+MAD default, iqr Tukey fence, or zscore modified z-score), then returns a cleaned copy plus the outlier indices; Fill selects nan|linear|previous|remove. Toolbox-free (median/MAD + interp1, NOT isoutlier/filloutliers which are absent in Octave); existing NaN inputs are never counted as outliers; a zero-spread Hampel window still catches a lone spike. numeric-Y only (StateTag categorical -> Tag:notNumeric). Inherited by every tag kind. Tests: +9 in TestTag (hampel spike, non-mutating, fill linear/previous/remove, iqr, zscore, non-numeric error, bad-option errors); 84/84 pass. check_matlab_code + MISS_HIT style/lint/metric clean. Closes #343 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…e spectrum Add the frequency-domain sibling of the Tag analysis family. spectrum() returns a single-sided amplitude spectrum computed with core fft (non-DC/non-Nyquist bins doubled so a pure tone reads ~A); Fs is inferred from the median sample spacing or set via 'SampleRate', and 'Detrend' (linear/mean) removes trend before the transform. dominantFrequency() returns the largest non-DC peak's frequency. numeric-Y only (Tag:notNumeric), >=2 samples (Tag:spectrumTooFewPoints); near-uniform sampling assumed (documented, points at resampleUniform #308). Toolbox-free — NOT the Signal Processing Toolbox. Base-class methods inherited by every tag kind. Tests: +7 in TestTag (10Hz sine peak, amplitude+bin-count, SampleRate override, mean-Detrend DC removal, too-few-points, non-numeric, bad-options); 91/91 pass. check_matlab_code + MISS_HIT clean. Closes #338 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…d delete Add the targeted DELETE the operator journal was missing — the last of the three journal-style stores to get one (siblings EventStore.removeEvents #354, FastSenseDataStore.removeColumn #364). removeEntries(ids) drops entries by Id (char/string/cell, unknown ids skipped) and removeEntriesInRange(t0,t1) drops by Timestamp window (mirrors getEntriesInRange); both preserve sorted order and return the count removed. No new state. Tests: +5 in TestPlantLogStore (by-id, bulk-skip-unknown, bad-input, in-range, bad-bounds); 26/26 pass. check_matlab_code + MISS_HIT clean. Closes #365 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add age-based retention for long-running operator journals: pruneEntriesBefore(t) drops every entry older than t (keeps Timestamp >= t) and returns the count removed — a no-op when nothing is older, emptying the store when t is past the newest entry. Sibling of the EventStore age-prune (#293). Tests: +4 in TestPlantLogStore (prune-head, no-op, empties-past-newest, bad-input); 30/30 pass. check_matlab_code + MISS_HIT clean. Closes #366 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…teresis
Add a static factory that expresses a scalar level alarm in one line and
synthesizes both condition handles. MonitorTag.level(key, parent, tripLevel)
builds ConditionFn @(x,y) y>trip (Direction 'above', default) or y<trip
('below'); a Deadband d>0 adds the AlarmOffConditionFn for hysteresis. Because
the FSM flips ON->OFF when AlarmOffConditionFn is true (it is the clear
trigger), 'above' clears at y<trip-d and 'below' at y>trip+d. All remaining
options (MinDuration, EventStore, callbacks, Persist, DataStore, Tag universals)
forward verbatim to the constructor; no FSM/constructor/serialization change.
Tests: +6 in TestMonitorTag (above default + no off-cond, below, deadband
extends alarm through the band, option forwarding, validation errors); 33/33
pass. check_matlab_code + MISS_HIT clean.
Closes #349
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the range/out-of-band sibling of MonitorTag.level (#349). MonitorTag.band(key, parent, lo, hi) trips when y is OUTSIDE [lo,hi] ('outside', default) or INSIDE it ('inside'). A Deadband d>0 synthesizes the matching AlarmOffConditionFn (the FSM clear trigger): an outside alarm clears only once y is back inside the shrunk band [lo+d, hi-d], an inside alarm once y is outside the widened band [lo-d, hi+d]. lo>hi or non-finite bounds raise MonitorTag:invalidBand; all other options forward verbatim to the constructor. Tests: +4 in TestMonitorTag (outside default + no off-cond, inside, deadband holds alarm in the band zone, validation errors); 37/37 pass. check_matlab_code + MISS_HIT clean. Closes #350 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion registry Complete the server-side action-registry lifecycle. unregisterAction(name) is the mirror of registerAction — rmfield from the Actions struct and, when a client is connected, push the refreshed action list (silent no-op if absent); listActions() returns fieldnames(Actions) as a cellstr. No protocol/wire change. Tests: +4 in TestWebBridge (unregister, absent-no-op, list-includes-registered, list-reflects-unregister); 6/6 pass. check_matlab_code + MISS_HIT clean. Closes #318 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a static constructor that reads a delimited file into a SensorTag (or a SensorTag array for multiple value columns) — the import sibling of exportCsv (#288). Resolves TimeCol/ValueCol by header name or 1-based index, keys multi- column output by header (single-column key overridable via 'Key'), and forwards Name/Units/Criticality/... to the constructor. Reads through the library's toolbox-free, Octave-safe readRawDelimited_ (MEX + pure-MATLAB fallback), NOT readtable — which is what keeps it Octave-safe — auto-detects the delimiter, and sorts rows by ascending time. Tests: +6 in TestSensorTag (auto cols, by-header-name, multi-col array, key-override + Units passthrough, sort-by-time, bad-column errors); 26/26 pass. check_matlab_code + MISS_HIT clean. Closes #345 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ag trilogy Add opt-in MinDuration to CompositeTag so the aggregated output only flips to 1 once the condition has held >= MinDuration (parent-X units), stopping aggregated alarms from chattering on transient single-sample child excursions. The run-length filter is factored into a new shared private helper minDurationFilter_ (NaN-preserving, strict less-than, matching the MonitorTag Stage-3 debounce), applied to the merged 0/1 series before caching. MinDuration is parsed in the constructor (added to splitArgs_ cmpKeys) and round-tripped in toStruct (omit-when-zero) / fromStruct; default 0 preserves current behavior exactly. Completes MinDuration support across Monitor / Derived / Composite. Tests: +4 in TestCompositeTag (suppresses short run, zero keeps short run, struct round-trip, omit-when-zero); 35/35 pass. check_matlab_code + MISS_HIT clean. Closes #325 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ion) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Give StateTag an optional code->name legend so integer-coded PLC/SCADA/recipe
state channels can render 'Run' instead of a bare 2 without giving up the
numeric trace. Adds a public StateNames property (Nx2 {code,name} cell,
Octave-safe, [] => numeric behaviour byte-for-byte unchanged), accepted as a
'StateNames' constructor option, and a nameAt(t) accessor that maps
valueAt(t) through the legend — mirroring valueAt's scalar->char /
vector->cellstr shape with a numeric-string fallback for unmapped codes.
Serialized (omit-when-empty) in toStruct / fromStruct so existing serialized
StateTags round-trip untouched.
Tests: +6 in TestStateTag (scalar, vector, unmapped fallback, no-legend
fallback, round-trip, omit-when-empty); 28/28 pass. TestTagRegistry 29/29 (no
serialization regression). check_matlab_code + MISS_HIT clean.
Closes #372
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the vertical partner of addThreshold: addVLine(x) draws a vertical reference line at a time/x value spanning the full Y range — for marking an event instant, a setpoint change, or a shift boundary. Options Color / LineStyle / LineWidth / Label; pre-render only (FastSense:alreadyRendered), non-scalar x rejected (FastSense:invalidVLine). Rendered after the axis Y-limits finalise so the line spans the axis with XLimInclude/YLimInclude off (excluded from limit computation), tagged UserData.FastSense.Type='vline' with an optional top label. Tests: tests/test_add_vline.m — 6 tests (config, defaults, multiple, non-scalar error, render smoke [hLine valid, XData=[x x], UserData type], reject-after-render); all pass in live MATLAB. check_matlab_code + MISS_HIT clean. Closes #357 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…light Add the missing vertical dual of addBand. addSpan(t0, t1) shades the vertical band X in [t0,t1] across the full Y range — for highlighting a phase, shift, or event window on a time axis. Options FaceColor / FaceAlpha / EdgeColor / Label; pre-render only (FastSense:alreadyRendered), non-scalar or inverted bounds rejected (FastSense:invalidSpan). Rendered after the axis Y-limits finalise as a translucent patch (YLimInclude off) pushed behind the data lines via uistack (try/catch fallback), tagged UserData.FastSense.Type='span'. Tests: tests/test_add_span.m — 6 tests (config, defaults, multiple, inverted error, render smoke [hPatch valid, XData spans [t0,t1], UserData type], reject-after-render); all pass in live MATLAB. check_matlab_code + MISS_HIT clean. Closes #377 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add the text member of the annotation family (addMarker/addThreshold/addBand). addText(x, y, str) places a text callout at data coordinates (x,y). Options Color (default Theme.ForegroundColor) / FontSize / HorizontalAlignment / VerticalAlignment; pre-render only (FastSense:alreadyRendered), non-scalar coords or non-char str rejected (FastSense:invalidText). Rendered as a front-layer text object tagged UserData.FastSense.Type='text'. Tests: tests/test_add_text.m — 6 tests (config, defaults, multiple, bad-args errors, render smoke [hText valid, String, UserData type], reject-after-render); all pass in live MATLAB. check_matlab_code + MISS_HIT clean. Closes #347 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… a bound Tag Give the core plot axis-labeling. Add public XLabel/YLabel name-value options (default '') drawn in render() only when non-empty, so the default is identical to prior behaviour. addTag auto-derives the Y label from a single bound tag when unset — Units -> Name -> Key, matching FastSenseWidget's derivation — and clears the derived label once a second tag is added (multi-tag -> unlabeled); an explicit YLabel always wins. Tests: tests/test_axis_labels.m — 6 tests (explicit drawn, default unlabeled, derive-from-Units, fallback-to-Name, explicit-wins, multi-tag-clears); all pass in live MATLAB. check_matlab_code + MISS_HIT clean. Closes #356 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…dow overlay
Add a base-Tag primitive that resolves the tag over N windows and re-zeroes each
window's time onto a shared relative-time axis for shift-vs-shift / run-vs-run
comparison. compareWindows({[t0 t1],...}) returns a struct array {RelT, Y,
Window} ready for the existing FastSense.addLine overlay path; 'Anchor' selects
'start' (default, re-zero at t0), 'end' (align on t1), or a numeric scalar
offset. Delegates to getXYRange once per window and passes Y through unchanged
(so a categorical StateTag also aligns), inherited by every Tag kind with no
per-subclass work.
Tests: +5 in TestTag (start anchor, end anchor, scalar anchor, Y carry-through,
bad-args errors); 96/96 pass. check_matlab_code + MISS_HIT clean.
Closes #358
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…etion Add functionSignatures.json for the SensorThreshold (Tag) library, giving the MATLAB editor argument tab-completion for the Tag family — parity with the existing FastSense/Dashboard signatures. 21 entries cover the SensorTag/StateTag/MonitorTag/CompositeTag constructors, the MonitorTag.level and band factories, SensorTag.fromCsv, StateTag.nameAt/transitions, TagRegistry get/register/toStructs, and the Tag analysis methods (getStats, percentile, integral, correlate, lagCorrelation, findPeaks, removeOutliers, spectrum, compareWindows) with kind/type/choices per argument. Tests: tests/test_function_signatures.m validates all three functionSignatures files parse as JSON with a schema version and that the SensorThreshold file exposes the expected Tag-family + factory entries; passes in live MATLAB. MISS_HIT clean. Closes #332 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tures) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…natures test CI on the PR branch flagged two issues: - MATLAB Lint (mh_metric --ci): FastSense.render, already 597/600 lines on main, tipped over the 600-line and 95-cyclomatic gates once the #356 axis-label and #357/#377/#347 overlay blocks were added inline. Extract both into private helpers applyAxisLabels_ and renderCustomOverlays_; render() is back under budget and all 24 render/label tests still pass unchanged. - Octave Tests: test_function_signatures asserted MATLAB-specific jsondecode field names (_schemaVersion -> x_schemaVersion, dotted keys -> underscores), which Octave mangles differently. Validate against the raw JSON text via strfind instead (implementation-independent) while still confirming jsondecode parses each file. (The windows-latest Concurrency Smoke failure is pre-existing and environmental — no C compiler on the runner, so the lock MEX can't build — unrelated to this branch, which adds no classdef or MEX.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
|
Wiki documentation follow-up for this PR:
Generated by Claude Code |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements 28 auto-generated enhancement issues from the backlog, each as the smallest safe additive change through the project's GSD workflow. Every feature is toolbox-free, pure MATLAB/Octave, backward-compatible (no public signature or serialized-format change), verified in a live MATLAB session, and MISS_HIT (
mh_style+mh_lint) clean. ~150 new tests, all green; targeted regression sweeps show no breakage.Commits are grouped as
feat(NNN): …(feature + test) followed bydocs(state): …(STATE bookkeeping), one pair per issue.Features by area
Tag analysis family (
libs/SensorThreshold/Tag.m, base-class — inherited by every kind)#351integral(t0,t1)— scalar definite integral#339percentile()/median()/iqr()— order statistics#329findPeaks()— toolbox-free local-extrema + prominence#340correlate(other)— Pearson correlation (first relational primitive)#341lagCorrelation(other,MaxLag)— cross-correlation / best time-lag#343removeOutliers()— hampel/iqr/zscore despike#338spectrum()/dominantFrequency()— single-sided amplitude spectrum (core fft)#358compareWindows(windows)— phase-aligned multi-window overlayTag model
#342StateTag.transitions()— change-point event list#372StateTag.StateNames+nameAt()— code→name legend#349MonitorTag.level(...)— level alarm + Deadband hysteresis factory#350MonitorTag.band(...)— range / out-of-band alarm factory#325CompositeTag MinDurationdebounce (+ sharedprivate/minDurationFilter_.m)#345SensorTag.fromCsv()— Octave-safe CSV/TSV import#363TagRegistry.toStructs()— whole-catalog serializerEventDetection (
libs/EventDetection/)#360EventStore.getEvent(id)— id-addressed point-read#354EventStore.removeEvent/removeEvents(+EventBinding.detach)#355EventStore.editEvent(id,...)(+Event.editWindow)#310EventStore.acknowledgeEvents/acknowledgeAll— bulk ackStores / bridge / plot
#365PlantLogStore.removeEntries/removeEntriesInRange#366PlantLogStore.pruneEntriesBefore(t)#364FastSenseDataStore.removeColumn(name)#318WebBridge.unregisterAction/listActions#357FastSense.addVLine(x,...)— vertical reference line#377FastSense.addSpan(t0,t1,...)— vertical time-window highlight#347FastSense.addText(x,y,str,...)— on-plot text annotation#356FastSenseXLabel/YLabel + auto-derive Y units from a bound Tag#332SensorThresholdfunctionSignatures.json— editor tab-completionCloses #351, #339, #329, #340, #341, #343, #338, #358, #342, #372, #349, #350, #325, #345, #363, #360, #354, #355, #310, #365, #366, #364, #318, #357, #377, #347, #356, #332
Verification
runtests; FastSense render smoke tests exercised in real figures).check_matlab_code+ MISS_HITmh_style/mh_lintclean on all changed files.Not included (deferred — need a focused pass or a product decision)
#348DisplayScale/DisplayOffset — product decision: a scaled parentgetXYwould silently change MonitorTag threshold semantics (thresholds are set in raw units).#353OffDelay,#352Latched,#323DerivedTag AlignParents — FSM/streaming carry-in changes best done with a dedicated MonitorTag regression pass.#359 #346 #344 #320 #315 #311 #313 #337 #324 #322 #319 #317 #335 #336 #314).🤖 Generated with Claude Code