Skip to content

Commit 03faeeb

Browse files
HanSur94claude
andcommitted
refactor: return registry instead of cell array from load functions
Both loadModuleData and loadModuleMetadata now take registry as first argument and return it for chaining. Sensors are modified in-place via handle semantics. Simplifies caller code — no need to manage a separate sensors cell array. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6c0c19a commit 03faeeb

4 files changed

Lines changed: 112 additions & 153 deletions

File tree

libs/SensorThreshold/loadModuleData.m

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
1-
function sensors = loadModuleData(registry, moduleStruct)
1+
function registry = loadModuleData(registry, moduleStruct)
22
%LOADMODULEDATA Match module struct fields to registered sensors and assign X/Y.
3-
% sensors = loadModuleData(registry, moduleStruct) takes an
3+
% registry = loadModuleData(registry, moduleStruct) takes an
44
% ExternalSensorRegistry and a module struct loaded from the external
55
% system. The struct must contain a .doc field where each sub-field has
66
% .name and .datum properties. The .datum value names the shared
77
% datenum field. Each struct field whose name matches a registered
88
% sensor key gets its data assigned as sensor.Y, with the shared
99
% datenum as sensor.X.
1010
%
11-
% Returns a 1xN cell array of filled Sensor handles (empty 1x0 if no
12-
% matches). Output order follows fieldnames(moduleStruct).
13-
%
14-
% Repeated calls overwrite sensor.X and sensor.Y in-place (handle
15-
% semantics).
11+
% Returns the registry (handle) for chaining convenience. Matched
12+
% sensors are modified in-place via handle semantics.
1613
%
1714
% See also ExternalSensorRegistry, Sensor.
1815

@@ -29,7 +26,6 @@
2926
registeredKeys = registry.keys();
3027

3128
if isempty(registeredKeys)
32-
sensors = cell(1, 0);
3329
return;
3430
end
3531

@@ -43,11 +39,9 @@
4339
nMatched = numel(matchedFields);
4440

4541
% --- Assign X/Y to each matched sensor ---
46-
sensors = cell(1, nMatched);
4742
for i = 1:nMatched
4843
s = registry.get(matchedFields{i});
4944
s.X = X;
5045
s.Y = moduleStruct.(matchedFields{i});
51-
sensors{i} = s;
5246
end
5347
end

libs/SensorThreshold/loadModuleMetadata.m

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
function sensors = loadModuleMetadata(metadataTable, sensors)
1+
function registry = loadModuleMetadata(registry, metadataTable)
22
%LOADMODULEMETADATA Attach state channels from metadata table to sensors.
3-
% sensors = loadModuleMetadata(metadataTable, sensors) reads discrete
3+
% registry = loadModuleMetadata(registry, metadataTable) reads discrete
44
% state signals from a MATLAB table, compresses them from dense to
55
% sparse transitions, and attaches StateChannel objects to each sensor
6-
% whose ThresholdRules reference matching state column names.
6+
% in the registry whose ThresholdRules reference matching state column
7+
% names.
78
%
89
% metadataTable must be a MATLAB table with a 'Date' column (datetime)
910
% and one or more state columns. The Date column is converted to
@@ -18,8 +19,8 @@
1819
% Each sensor receives its own StateChannel instance (no shared
1920
% handles). Compressed data is cached so each column is processed once.
2021
%
21-
% Repeated calls add additional StateChannels without clearing existing
22-
% ones. Caller is responsible for avoiding duplicates.
22+
% Returns the registry (handle) for chaining convenience. Sensors are
23+
% modified in-place via handle semantics.
2324
%
2425
% See also loadModuleData, StateChannel, ThresholdRule, Sensor.
2526

@@ -28,7 +29,7 @@
2829
% --- Validate table input ---
2930
if ~istable(metadataTable)
3031
error('loadModuleMetadata:notTable', ...
31-
'First argument must be a table, got %s.', class(metadataTable));
32+
'Second argument must be a table, got %s.', class(metadataTable));
3233
end
3334

3435
colNames = metadataTable.Properties.VariableNames;
@@ -38,8 +39,9 @@
3839
'Metadata table must contain a ''Date'' column.');
3940
end
4041

41-
% --- Early exit for empty sensors ---
42-
if isempty(sensors)
42+
% --- Get all sensors from registry ---
43+
allKeys = registry.keys();
44+
if isempty(allKeys)
4345
return;
4446
end
4547

@@ -53,8 +55,8 @@
5355
cache = struct();
5456

5557
% --- Attach state channels to each sensor ---
56-
for i = 1:numel(sensors)
57-
s = sensors{i};
58+
for i = 1:numel(allKeys)
59+
s = registry.get(allKeys{i});
5860

5961
% Skip sensors with no threshold rules
6062
if isempty(s.ThresholdRules)
@@ -82,12 +84,8 @@
8284
% Compress on first access, cache for reuse
8385
if ~isfield(cache, key)
8486
colData = metadataTable.(key);
85-
% Table columns are column vectors — transpose for row
86-
if isnumeric(colData)
87-
colData = reshape(colData, 1, []);
88-
elseif iscell(colData)
89-
colData = reshape(colData, 1, []);
90-
end
87+
% Table columns are column vectors — reshape to row
88+
colData = reshape(colData, 1, []);
9189
cache.(key) = compressTransitions(X, colData);
9290
end
9391
cached = cache.(key);

tests/suite/TestLoadModuleData.m

Lines changed: 27 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,12 @@ function testBasicMatch(testCase)
2828
reg.register('flow', Sensor('flow'));
2929

3030
ms = TestLoadModuleData.makeModuleStruct({'temp', 'press', 'flow'}, 100);
31-
sensors = loadModuleData(reg, ms);
31+
out = loadModuleData(reg, ms);
3232

33-
testCase.verifyEqual(numel(sensors), 3, 'all_matched');
34-
for i = 1:numel(sensors)
35-
testCase.verifyEqual(numel(sensors{i}.X), 100, 'X_length');
36-
testCase.verifyEqual(numel(sensors{i}.Y), 100, 'Y_length');
37-
end
33+
testCase.verifyTrue(isa(out, 'ExternalSensorRegistry'), 'returns_registry');
34+
testCase.verifyEqual(numel(reg.get('temp').X), 100, 'temp_X');
35+
testCase.verifyEqual(numel(reg.get('press').Y), 100, 'press_Y');
36+
testCase.verifyEqual(numel(reg.get('flow').Y), 100, 'flow_Y');
3837
end
3938

4039
function testPartialMatch(testCase)
@@ -44,30 +43,30 @@ function testPartialMatch(testCase)
4443
reg.register('press', Sensor('press'));
4544

4645
ms = TestLoadModuleData.makeModuleStruct({'temp', 'press', 'flow'}, 50);
47-
sensors = loadModuleData(reg, ms);
46+
loadModuleData(reg, ms);
4847

49-
testCase.verifyEqual(numel(sensors), 2, 'partial_match');
48+
testCase.verifyEqual(numel(reg.get('temp').X), 50, 'temp_filled');
49+
testCase.verifyEqual(numel(reg.get('press').X), 50, 'press_filled');
5050
end
5151

5252
function testNoMatch(testCase)
53-
% Registry has sensors not in struct
53+
% Registry has sensors not in struct — sensor stays empty
5454
reg = ExternalSensorRegistry('Test');
5555
reg.register('voltage', Sensor('voltage'));
5656

5757
ms = TestLoadModuleData.makeModuleStruct({'temp', 'press'}, 50);
58-
sensors = loadModuleData(reg, ms);
58+
loadModuleData(reg, ms);
5959

60-
testCase.verifyTrue(isempty(sensors), 'no_match_empty');
61-
testCase.verifyEqual(size(sensors), [1 0], 'empty_1x0');
60+
testCase.verifyTrue(isempty(reg.get('voltage').X), 'voltage_empty');
6261
end
6362

6463
function testEmptyRegistry(testCase)
6564
reg = ExternalSensorRegistry('Test');
6665
ms = TestLoadModuleData.makeModuleStruct({'temp'}, 50);
67-
sensors = loadModuleData(reg, ms);
66+
out = loadModuleData(reg, ms);
6867

69-
testCase.verifyTrue(isempty(sensors), 'empty_registry');
70-
testCase.verifyEqual(size(sensors), [1 0], 'empty_registry_1x0');
68+
testCase.verifyTrue(isa(out, 'ExternalSensorRegistry'), 'returns_registry');
69+
testCase.verifyEqual(reg.count(), 0, 'still_empty');
7170
end
7271

7372
function testSharedXValues(testCase)
@@ -77,24 +76,10 @@ function testSharedXValues(testCase)
7776
reg.register('b', Sensor('b'));
7877

7978
ms = TestLoadModuleData.makeModuleStruct({'a', 'b'}, 100);
80-
sensors = loadModuleData(reg, ms);
79+
loadModuleData(reg, ms);
8180

82-
testCase.verifyEqual(sensors{1}.X, sensors{2}.X, 'shared_X');
83-
testCase.verifyEqual(sensors{1}.X, ms.time_utc, 'X_matches_datenum');
84-
end
85-
86-
function testOutputOrderFollowsFieldnames(testCase)
87-
% Output order matches fieldnames(moduleStruct), not registry order
88-
reg = ExternalSensorRegistry('Test');
89-
reg.register('beta', Sensor('beta'));
90-
reg.register('alpha', Sensor('alpha'));
91-
92-
% Struct fields: doc, time_utc, alpha, beta
93-
ms = TestLoadModuleData.makeModuleStruct({'alpha', 'beta'}, 10);
94-
sensors = loadModuleData(reg, ms);
95-
96-
testCase.verifyEqual(sensors{1}.Key, 'alpha', 'first_is_alpha');
97-
testCase.verifyEqual(sensors{2}.Key, 'beta', 'second_is_beta');
81+
testCase.verifyEqual(reg.get('a').X, reg.get('b').X, 'shared_X');
82+
testCase.verifyEqual(reg.get('a').X, ms.time_utc, 'X_matches_datenum');
9883
end
9984

10085
function testDocFieldExcluded(testCase)
@@ -107,10 +92,10 @@ function testDocFieldExcluded(testCase)
10792
ms.doc.temp.datum = 'time_utc';
10893
ms.time_utc = linspace(datenum(2024,1,1), datenum(2024,1,2), 50);
10994
ms.temp = randn(1, 50);
110-
sensors = loadModuleData(reg, ms);
95+
loadModuleData(reg, ms);
11196

112-
testCase.verifyEqual(numel(sensors), 1, 'doc_excluded');
113-
testCase.verifyEqual(sensors{1}.Key, 'temp', 'only_temp');
97+
testCase.verifyEqual(numel(reg.get('temp').X), 50, 'temp_filled');
98+
testCase.verifyTrue(isempty(reg.get('doc').X), 'doc_excluded');
11499
end
115100

116101
function testDatenumFieldExcluded(testCase)
@@ -120,10 +105,10 @@ function testDatenumFieldExcluded(testCase)
120105
reg.register('temp', Sensor('temp'));
121106

122107
ms = TestLoadModuleData.makeModuleStruct({'temp'}, 50);
123-
sensors = loadModuleData(reg, ms);
108+
loadModuleData(reg, ms);
124109

125-
testCase.verifyEqual(numel(sensors), 1, 'datenum_excluded');
126-
testCase.verifyEqual(sensors{1}.Key, 'temp', 'only_temp');
110+
testCase.verifyEqual(numel(reg.get('temp').X), 50, 'temp_filled');
111+
testCase.verifyTrue(isempty(reg.get('time_utc').X), 'datenum_excluded');
127112
end
128113

129114
function testMissingDocFieldErrors(testCase)
@@ -181,31 +166,18 @@ function testDatumNotCharErrors(testCase)
181166
testCase.verifyTrue(threw, 'non_char_datum_throws');
182167
end
183168

184-
function testOutputIsRowCell(testCase)
185-
reg = ExternalSensorRegistry('Test');
186-
reg.register('temp', Sensor('temp'));
187-
188-
ms = TestLoadModuleData.makeModuleStruct({'temp'}, 10);
189-
sensors = loadModuleData(reg, ms);
190-
191-
testCase.verifyEqual(size(sensors, 1), 1, 'row_cell');
192-
end
193-
194169
function testOverwriteOnRepeatedCall(testCase)
195170
% Calling twice overwrites sensor data (handle semantics)
196171
reg = ExternalSensorRegistry('Test');
197172
reg.register('temp', Sensor('temp'));
198173

199174
ms1 = TestLoadModuleData.makeModuleStruct({'temp'}, 50);
200-
sensors1 = loadModuleData(reg, ms1);
175+
loadModuleData(reg, ms1);
176+
testCase.verifyEqual(numel(reg.get('temp').Y), 50, 'first_call');
201177

202178
ms2 = TestLoadModuleData.makeModuleStruct({'temp'}, 100);
203-
sensors2 = loadModuleData(reg, ms2);
204-
205-
% Same handle, new data — verify via both references
206-
testCase.verifyEqual(numel(sensors2{1}.Y), 100, 'overwritten_Y');
207-
testCase.verifyEqual(numel(sensors1{1}.Y), 100, 'sensors1_also_overwritten');
208-
testCase.verifyTrue(sensors1{1} == sensors2{1}, 'same_handle');
179+
loadModuleData(reg, ms2);
180+
testCase.verifyEqual(numel(reg.get('temp').Y), 100, 'overwritten');
209181
end
210182
end
211183
end

0 commit comments

Comments
 (0)