Skip to content

Commit cef3344

Browse files
authored
Merge pull request #274 from mgoodness/fix-sac-by-tank-role
Fix SAC by Tank Role
2 parents f5eb86c + c405cca commit cef3344

7 files changed

Lines changed: 174 additions & 10 deletions

File tree

lib/features/statistics/data/repositories/statistics_repository.dart

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -311,10 +311,63 @@ class StatisticsRepository {
311311
}
312312
}
313313

314-
/// Get average SAC by tank role (back gas, stage, deco, etc.)
314+
/// Get volume-based average SAC by tank role (back gas, stage, deco, etc.)
315+
///
316+
/// Returns a map of tank role to average SAC in L/min.
317+
/// Requires tank volume data.
318+
Future<Map<String, double>> getSacVolumeByTankRole({String? diverId}) async {
319+
try {
320+
final diverFilter = diverId != null ? 'AND d.diver_id = ?' : '';
321+
final params = diverId != null ? [diverId] : [];
322+
323+
final results = await _db.customSelect('''
324+
SELECT
325+
t.tank_role,
326+
AVG(
327+
CASE
328+
WHEN COALESCE(d.runtime, d.bottom_time) > 0 AND d.avg_depth > 0 AND t.start_pressure > t.end_pressure THEN
329+
((t.start_pressure - t.end_pressure) * t.volume) / (COALESCE(d.runtime, d.bottom_time) / 60.0) / ((d.avg_depth / 10.0) + 1)
330+
ELSE NULL
331+
END
332+
) AS avg_sac
333+
FROM dives d
334+
INNER JOIN dive_tanks t ON t.dive_id = d.id
335+
WHERE t.start_pressure IS NOT NULL
336+
AND t.end_pressure IS NOT NULL
337+
AND COALESCE(d.runtime, d.bottom_time) > 0
338+
AND d.avg_depth > 0
339+
AND t.volume > 0
340+
$diverFilter
341+
GROUP BY t.tank_role
342+
HAVING avg_sac IS NOT NULL
343+
ORDER BY avg_sac ASC
344+
''', variables: params.map((p) => Variable(p)).toList()).get();
345+
346+
final Map<String, double> sacByRole = {};
347+
for (final row in results) {
348+
final role = row.read<String>('tank_role');
349+
final sac = row.read<double>('avg_sac');
350+
sacByRole[role] = sac;
351+
}
352+
353+
return sacByRole;
354+
} catch (e, stackTrace) {
355+
_log.error(
356+
'Failed to get SAC in volume by tank role',
357+
error: e,
358+
stackTrace: stackTrace,
359+
);
360+
return {};
361+
}
362+
}
363+
364+
/// Get pressure-based average SAC by tank role (back gas, stage, deco, etc.)
315365
///
316366
/// Returns a map of tank role to average SAC in bar/min.
317-
Future<Map<String, double>> getSacByTankRole({String? diverId}) async {
367+
/// Does not require tank volume.
368+
Future<Map<String, double>> getSacPressureByTankRole({
369+
String? diverId,
370+
}) async {
318371
try {
319372
final diverFilter = diverId != null ? 'AND d.diver_id = ?' : '';
320373
final params = diverId != null ? [diverId] : [];
@@ -351,7 +404,7 @@ class StatisticsRepository {
351404
return sacByRole;
352405
} catch (e, stackTrace) {
353406
_log.error(
354-
'Failed to get SAC by tank role',
407+
'Failed to get SAC in pressure by tank role',
355408
error: e,
356409
stackTrace: stackTrace,
357410
);

lib/features/statistics/presentation/providers/statistics_providers.dart

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,13 @@ final sacByTankRoleProvider = FutureProvider<Map<String, double>>((ref) async {
7373
_keepAliveWithExpiry(ref);
7474
final repository = ref.watch(statisticsRepositoryProvider);
7575
final currentDiverId = ref.watch(currentDiverIdProvider);
76-
return repository.getSacByTankRole(diverId: currentDiverId);
76+
final sacUnit = ref.watch(sacUnitProvider);
77+
78+
if (sacUnit == SacUnit.litersPerMin) {
79+
return repository.getSacVolumeByTankRole(diverId: currentDiverId);
80+
} else {
81+
return repository.getSacPressureByTankRole(diverId: currentDiverId);
82+
}
7783
});
7884

7985
// ============================================================================

test/features/media/data/services/network_credentials_service_test.mocks.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,4 +407,13 @@ class MockNetworkCredentialsRepository extends _i1.Mock
407407
returnValueForMissingStub: _i4.Future<void>.value(),
408408
)
409409
as _i4.Future<void>);
410+
411+
@override
412+
_i4.Future<void> updateDisplayName(String? id, String? displayName) =>
413+
(super.noSuchMethod(
414+
Invocation.method(#updateDisplayName, [id, displayName]),
415+
returnValue: _i4.Future<void>.value(),
416+
returnValueForMissingStub: _i4.Future<void>.value(),
417+
)
418+
as _i4.Future<void>);
410419
}

test/features/media/data/services/network_scan_service_test.mocks.dart

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,15 @@ class MockNetworkCredentialsService extends _i1.Mock
305305
),
306306
)
307307
as _i5.Future<List<_i8.NetworkCredentialHost>>);
308+
309+
@override
310+
_i5.Future<void> updateDisplayName(String? id, String? displayName) =>
311+
(super.noSuchMethod(
312+
Invocation.method(#updateDisplayName, [id, displayName]),
313+
returnValue: _i5.Future<void>.value(),
314+
returnValueForMissingStub: _i5.Future<void>.value(),
315+
)
316+
as _i5.Future<void>);
308317
}
309318

310319
/// A class which mocks [ManifestSubscriptionRepository].
@@ -450,6 +459,23 @@ class MockManifestSubscriptionRepository extends _i1.Mock
450459
)
451460
as _i5.Future<void>);
452461

462+
@override
463+
_i5.Future<void> updateUrlAndDisplayName(
464+
String? id, {
465+
required String? manifestUrl,
466+
required String? displayName,
467+
}) =>
468+
(super.noSuchMethod(
469+
Invocation.method(
470+
#updateUrlAndDisplayName,
471+
[id],
472+
{#manifestUrl: manifestUrl, #displayName: displayName},
473+
),
474+
returnValue: _i5.Future<void>.value(),
475+
returnValueForMissingStub: _i5.Future<void>.value(),
476+
)
477+
as _i5.Future<void>);
478+
453479
@override
454480
_i5.Future<void> deleteById(String? id) =>
455481
(super.noSuchMethod(

test/features/media/presentation/widgets/url_tab_test.mocks.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ class MockNetworkCredentialsService extends _i1.Mock
139139
),
140140
)
141141
as _i4.Future<List<_i7.NetworkCredentialHost>>);
142+
143+
@override
144+
_i4.Future<void> updateDisplayName(String? id, String? displayName) =>
145+
(super.noSuchMethod(
146+
Invocation.method(#updateDisplayName, [id, displayName]),
147+
returnValue: _i4.Future<void>.value(),
148+
returnValueForMissingStub: _i4.Future<void>.value(),
149+
)
150+
as _i4.Future<void>);
142151
}
143152

144153
/// A class which mocks [MediaRepository].

test/features/statistics/data/repositories/statistics_repository_error_test.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ void main() {
6262
expect(sacPressureRecords.worst, isNull);
6363

6464
// Methods that return empty map
65-
expect(await repository.getSacByTankRole(), isEmpty);
65+
expect(await repository.getSacVolumeByTankRole(), isEmpty);
66+
expect(await repository.getSacPressureByTankRole(), isEmpty);
6667

6768
// Methods that return tuple defaults
6869
final soloVsBuddy = await repository.getSoloVsBuddyCount();

test/features/statistics/data/repositories/statistics_repository_sac_test.dart

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,11 +251,11 @@ void main() {
251251
});
252252

253253
// ---------------------------------------------------------------------------
254-
// SAC By Tank Role
254+
// SAC In Volume By Tank Role
255255
// ---------------------------------------------------------------------------
256256

257-
group('getSacByTankRole', () {
258-
test('groups SAC by tank role using runtime', () async {
257+
group('getSacVolumeByTankRole', () {
258+
test('groups SAC in volume by tank role using runtime', () async {
259259
await insertDiveWithTank(
260260
id: 'dive-back',
261261
bottomTimeSeconds: 35 * 60,
@@ -264,9 +264,10 @@ void main() {
264264
startPressure: 200,
265265
endPressure: 50,
266266
tankRole: 'backGas',
267+
volume: 12.0,
267268
);
268269

269-
final sacByRole = await repository.getSacByTankRole();
270+
final sacByRole = await repository.getSacVolumeByTankRole();
270271

271272
expect(sacByRole, isNotEmpty);
272273
expect(sacByRole.containsKey('backGas'), isTrue);
@@ -282,9 +283,68 @@ void main() {
282283
startPressure: 200,
283284
endPressure: 50,
284285
tankRole: 'backGas',
286+
volume: 12.0,
285287
);
286288

287-
final sacByRole = await repository.getSacByTankRole();
289+
final sacByRole = await repository.getSacVolumeByTankRole();
290+
291+
expect(sacByRole, isNotEmpty);
292+
expect(sacByRole['backGas']!, greaterThan(0));
293+
});
294+
295+
test('excludes tanks with zero volume', () async {
296+
await insertDiveWithTank(
297+
id: 'dive-zerovol',
298+
bottomTimeSeconds: 40 * 60,
299+
runtimeSeconds: 42 * 60,
300+
avgDepth: 20.0,
301+
startPressure: 200,
302+
endPressure: 50,
303+
tankRole: 'backGas',
304+
volume: 0,
305+
);
306+
307+
final sacByRole = await repository.getSacVolumeByTankRole();
308+
309+
expect(sacByRole, isEmpty);
310+
});
311+
});
312+
313+
// ---------------------------------------------------------------------------
314+
// SAC In Pressure By Tank Role
315+
// ---------------------------------------------------------------------------
316+
317+
group('getSacPressureByTankRole', () {
318+
test('groups SAC in pressure by tank role using runtime', () async {
319+
await insertDiveWithTank(
320+
id: 'dive-back',
321+
bottomTimeSeconds: 35 * 60,
322+
runtimeSeconds: 42 * 60,
323+
avgDepth: 20.0,
324+
startPressure: 200,
325+
endPressure: 50,
326+
tankRole: 'backGas',
327+
);
328+
329+
final sacByRole = await repository.getSacPressureByTankRole();
330+
331+
expect(sacByRole, isNotEmpty);
332+
expect(sacByRole.containsKey('backGas'), isTrue);
333+
expect(sacByRole['backGas']!, greaterThan(0));
334+
});
335+
336+
test('falls back to bottom_time when runtime null', () async {
337+
await insertDiveWithTank(
338+
id: 'dive-norunt',
339+
bottomTimeSeconds: 40 * 60,
340+
runtimeSeconds: null,
341+
avgDepth: 20.0,
342+
startPressure: 200,
343+
endPressure: 50,
344+
tankRole: 'backGas',
345+
);
346+
347+
final sacByRole = await repository.getSacPressureByTankRole();
288348

289349
expect(sacByRole, isNotEmpty);
290350
expect(sacByRole['backGas']!, greaterThan(0));

0 commit comments

Comments
 (0)