Problem / motivation
PlantLogStore (libs/PlantLog/PlantLogStore.m) is the in-memory store for imported plant-log entries, but it exposes exactly one query method — getEntriesInRange(t0, t1) (:196), a time-window lookup. Its full public method set is just addEntries / mergeEntries / clear / getEntries / getEntriesInRange / getCount (header :14–21).
There is no content or metadata search. Yet a plant log is fundamentally a searchable operator record: each PlantLogEntry carries a char Message and a dynamic-field Metadata struct (e.g. MachineId, Operator) — PlantLogEntry.m:38–45. The core analysis workflow is "what was logged around this alarm / by this operator / about this pump?" — e.g. all entries whose message mentions trip, everything Operator = jdoe logged, all MachineId = M1 events.
Today the caller must pull the whole getEntries() array and hand-roll a loop over e.Message / e.Metadata.(field) — exactly the boilerplate getEntriesInRange already spares them on the time axis. This is a query asymmetry: the store can filter by when but not by what.
(Verified gap: grep -niE "filter|search|find\(|query|matches|contains" libs/PlantLog/*.m over non-comment lines returns only find(strcmpi(...)) parsing helpers — no message/metadata search anywhere in the lib.)
Proposed feature
A content/metadata search method on PlantLogStore, complementing the existing time-window query:
entries = store.findEntries('pump'); % Message substring, case-insensitive
entries = store.findEntries('jdoe', 'Field', 'Operator'); % match a Metadata field value
entries = store.findEntries('^trip', 'Regexp', true); % regex on Message
It returns a PlantLogEntry array — the same return shape as getEntries / getEntriesInRange — and [] when nothing matches, mirroring the lib's []-empty convention (PlantLogStore.m:50–58). It composes with getEntriesInRange (search, then range-window, or vice-versa).
Rough sketch
- Lib / class:
libs/PlantLog/PlantLogStore.m (single new public method).
- Public API:
entries = findEntries(obj, pattern, varargin)
pattern — char/string matched against Message (case-insensitive substring by default).
'Field', name — match against the named Metadata field's value instead of Message (skip entries lacking the field).
'IgnoreCase', tf (default true).
'Regexp', tf (default false → plain substring via contains/strfind; true → regexpi/regexp).
- Impl: guard
isempty(obj.entries_) → return [] (same pattern as getEntriesInRange :208), build a logical mask over {obj.entries_.Message} (or the chosen metadata field) using base contains / regexpi / strcmpi, return obj.entries_(mask). Validate pattern and throw the lib's existing PlantLogStore:invalidInput / PlantLogStore:unknownOption IDs to match house error style.
- Test:
findEntries('pump') matches the two seeded "Pump on/off" entries; 'Field','Operator' filters by metadata; 'Regexp',true honours an anchored pattern; no-match and empty-store both return [].
Value
High. Finding the relevant entries in a long plant log is a primary sensor-analysis workflow, and the store already owns all the data in memory. The method removes per-caller boilerplate, pairs with the existing time-window query, and feeds the Plant-Log hover/live-tail UI and any future log widget.
Constraints check
- Toolbox-free: ✅
contains, regexpi, strcmpi, strfind, cellfun are all base MATLAB/Octave.
- Backward-compatible: ✅ brand-new method — no change to the constructor, properties, serialization, or any existing method; existing scripts and stores are untouched.
- Pure MATLAB/Octave: ✅ respects the lib's
[]-empty convention (documented for Octave value-class compatibility at PlantLogStore.m:50–58).
- Contracts: ✅ no
DashboardWidget / Tag contract involved; self-contained in PlantLogStore.
Effort estimate
S — one method in one file plus one test.
Dedup
Clean. gh issue list --state all --search "PlantLog" → empty; --search "plant log search filter" → empty; no open PR touches PlantLog. The entire libs/PlantLog/ tree is unmined. Distinct from the event-query issues (#225 getEventsInRange, #228 by-severity/category, #245 summarize) — those are on EventStore, a separate store that "no plant-log entry ever crosses into" (PLOG-ST-01, PlantLogStore.m:9–10).
AI-proposed via /feature-scout — needs a human product decision before implementation.
Problem / motivation
PlantLogStore(libs/PlantLog/PlantLogStore.m) is the in-memory store for imported plant-log entries, but it exposes exactly one query method —getEntriesInRange(t0, t1)(:196), a time-window lookup. Its full public method set is justaddEntries / mergeEntries / clear / getEntries / getEntriesInRange / getCount(header:14–21).There is no content or metadata search. Yet a plant log is fundamentally a searchable operator record: each
PlantLogEntrycarries a charMessageand a dynamic-fieldMetadatastruct (e.g.MachineId,Operator) —PlantLogEntry.m:38–45. The core analysis workflow is "what was logged around this alarm / by this operator / about this pump?" — e.g. all entries whose message mentions trip, everythingOperator = jdoelogged, allMachineId = M1events.Today the caller must pull the whole
getEntries()array and hand-roll a loop overe.Message/e.Metadata.(field)— exactly the boilerplategetEntriesInRangealready spares them on the time axis. This is a query asymmetry: the store can filter by when but not by what.(Verified gap:
grep -niE "filter|search|find\(|query|matches|contains" libs/PlantLog/*.mover non-comment lines returns onlyfind(strcmpi(...))parsing helpers — no message/metadata search anywhere in the lib.)Proposed feature
A content/metadata search method on
PlantLogStore, complementing the existing time-window query:It returns a
PlantLogEntryarray — the same return shape asgetEntries/getEntriesInRange— and[]when nothing matches, mirroring the lib's[]-empty convention (PlantLogStore.m:50–58). It composes withgetEntriesInRange(search, then range-window, or vice-versa).Rough sketch
libs/PlantLog/PlantLogStore.m(single new public method).entries = findEntries(obj, pattern, varargin)pattern— char/string matched againstMessage(case-insensitive substring by default).'Field', name— match against the namedMetadatafield's value instead ofMessage(skip entries lacking the field).'IgnoreCase', tf(defaulttrue).'Regexp', tf(defaultfalse→ plain substring viacontains/strfind;true→regexpi/regexp).isempty(obj.entries_)→ return[](same pattern asgetEntriesInRange:208), build a logical mask over{obj.entries_.Message}(or the chosen metadata field) using basecontains/regexpi/strcmpi, returnobj.entries_(mask). Validatepatternand throw the lib's existingPlantLogStore:invalidInput/PlantLogStore:unknownOptionIDs to match house error style.findEntries('pump')matches the two seeded "Pump on/off" entries;'Field','Operator'filters by metadata;'Regexp',truehonours an anchored pattern; no-match and empty-store both return[].Value
High. Finding the relevant entries in a long plant log is a primary sensor-analysis workflow, and the store already owns all the data in memory. The method removes per-caller boilerplate, pairs with the existing time-window query, and feeds the Plant-Log hover/live-tail UI and any future log widget.
Constraints check
contains,regexpi,strcmpi,strfind,cellfunare all base MATLAB/Octave.[]-empty convention (documented for Octave value-class compatibility atPlantLogStore.m:50–58).DashboardWidget/Tagcontract involved; self-contained inPlantLogStore.Effort estimate
S — one method in one file plus one test.
Dedup
Clean.
gh issue list --state all --search "PlantLog"→ empty;--search "plant log search filter"→ empty; no open PR touches PlantLog. The entirelibs/PlantLog/tree is unmined. Distinct from the event-query issues (#225getEventsInRange, #228 by-severity/category, #245summarize) — those are onEventStore, a separate store that "no plant-log entry ever crosses into" (PLOG-ST-01,PlantLogStore.m:9–10).AI-proposed via /feature-scout — needs a human product decision before implementation.