Skip to content

Commit dd6dc84

Browse files
committed
fix(macdive): emit sample tank pressure via allTankPressures key
MacDive XML parser wrote profile sample pressure as point['pressure'], but UddfEntityImporter._storeTankPressures reads it exclusively from point['allTankPressures'] (a list of {pressure, tankIndex} maps) — the convention shared by Subsurface, Shearwater, UDDF, and CSV parsers. The singular `pressure` key was silently dropped, so every MacDive XML import landed with zero rows in tank_pressure and the cylinder pressure trace missing from the profile chart. Invisible to the synthetic parser test (only asserted timestamp), so it slipped through review. New regression guards in both the synthetic and real-sample suites now exercise the path end-to-end.
1 parent cb8b422 commit dd6dc84

3 files changed

Lines changed: 61 additions & 1 deletion

File tree

lib/features/universal_import/data/parsers/macdive_xml_parser.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,16 @@ class MacDiveXmlParser implements ImportParser {
280280
for (final s in d.samples) {
281281
final point = <String, dynamic>{'timestamp': s.time.inSeconds};
282282
if (s.depthMeters != null) point['depth'] = s.depthMeters;
283-
if (s.pressureBar != null) point['pressure'] = s.pressureBar;
283+
if (s.pressureBar != null) {
284+
// UddfEntityImporter._storeTankPressures reads sample pressure
285+
// exclusively from `allTankPressures` (a list of
286+
// {pressure, tankIndex} maps); the singular `pressure` key it
287+
// ignores. MacDive XML emits one <pressure> per sample for the
288+
// primary tank, so map to tankIndex 0 to match Subsurface/UDDF/CSV.
289+
point['allTankPressures'] = [
290+
<String, dynamic>{'pressure': s.pressureBar, 'tankIndex': 0},
291+
];
292+
}
284293
if (s.temperatureCelsius != null) {
285294
point['temperature'] = s.temperatureCelsius;
286295
}

test/features/universal_import/data/parsers/macdive_xml_parser_test.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,30 @@ void main() {
101101
expect((profile[1] as Map)['timestamp'], 60);
102102
});
103103

104+
test('profile sample pressure exposed via allTankPressures', () async {
105+
// UddfEntityImporter._storeTankPressures only reads the
106+
// `allTankPressures` key on each profile point (a list of
107+
// {pressure, tankIndex} maps). Writing the legacy `pressure` key
108+
// silently drops the cylinder pressure trace from the profile chart.
109+
// MacDive XML emits one <pressure> per sample for the primary tank,
110+
// so we map it to tankIndex 0.
111+
final payload = await const MacDiveXmlParser().parse(bytes);
112+
final dive = payload.entitiesOf(ImportEntityType.dives).first;
113+
final profile = (dive['profile'] as List).cast<Map<String, dynamic>>();
114+
115+
final firstSample = profile[0];
116+
final allTP = firstSample['allTankPressures'] as List?;
117+
expect(
118+
allTP,
119+
isNotNull,
120+
reason: 'importer reads sample pressure from allTankPressures only',
121+
);
122+
expect(allTP!.length, 1, reason: 'single-gas dive has one tank entry');
123+
final tp0 = allTP.first as Map<String, dynamic>;
124+
expect(tp0['tankIndex'], 0);
125+
expect(tp0['pressure'], 200);
126+
});
127+
104128
test('dive links gear via equipmentRefs with matching uddfId', () async {
105129
final payload = await const MacDiveXmlParser().parse(bytes);
106130
final dive = payload.entitiesOf(ImportEntityType.dives).first;

test/features/universal_import/data/parsers/macdive_xml_real_sample_test.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,33 @@ void main() {
134134
expect(withProfile, isNotEmpty);
135135
});
136136

137+
test('profile samples carry tank pressure via allTankPressures', () async {
138+
// Regression guard: MacDive XML samples emit <pressure> per primary
139+
// tank, but the importer reads pressure exclusively from
140+
// `allTankPressures` (a list of {pressure, tankIndex} maps). Writing
141+
// the singular `pressure` key drops the cylinder pressure trace
142+
// entirely — counts would still tally but the profile chart would
143+
// not render the cylinder pressure line.
144+
if (skipIfNoFixture()) return;
145+
final payload = await const MacDiveXmlParser().parse(bytes);
146+
final dives = payload.entitiesOf(ImportEntityType.dives);
147+
final samplesWithPressure = dives
148+
.map((d) => (d['profile'] as List?) ?? const [])
149+
.expand((p) => p)
150+
.where(
151+
(p) =>
152+
((p as Map)['allTankPressures'] as List?)?.isNotEmpty ?? false,
153+
);
154+
expect(
155+
samplesWithPressure,
156+
isNotEmpty,
157+
reason:
158+
'real MacDive exports include <pressure> in samples; if no '
159+
'allTankPressures entries appear, the profile chart will be '
160+
'missing its cylinder pressure trace',
161+
);
162+
});
163+
137164
test('profile timestamps span the dive (not all zero)', () async {
138165
// Regression guard: real MacDive exports format <time> as decimal
139166
// seconds ("10.00"), which int.tryParse silently rejects. The bug

0 commit comments

Comments
 (0)