Skip to content

Commit e1936c4

Browse files
committed
fix(statistics): use separate functions for SAC by tank role
1 parent 85c0bc3 commit e1936c4

4 files changed

Lines changed: 112 additions & 10 deletions

File tree

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

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,10 +309,62 @@ class StatisticsRepository {
309309
}
310310
}
311311

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

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/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: 48 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,51 @@ 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+
296+
// ---------------------------------------------------------------------------
297+
// SAC In Pressure By Tank Role
298+
// ---------------------------------------------------------------------------
299+
300+
group('getSacPressureByTankRole', () {
301+
test('groups SAC in pressure by tank role using runtime', () async {
302+
await insertDiveWithTank(
303+
id: 'dive-back',
304+
bottomTimeSeconds: 35 * 60,
305+
runtimeSeconds: 42 * 60,
306+
avgDepth: 20.0,
307+
startPressure: 200,
308+
endPressure: 50,
309+
tankRole: 'backGas',
310+
);
311+
312+
final sacByRole = await repository.getSacPressureByTankRole();
313+
314+
expect(sacByRole, isNotEmpty);
315+
expect(sacByRole.containsKey('backGas'), isTrue);
316+
expect(sacByRole['backGas']!, greaterThan(0));
317+
});
318+
319+
test('falls back to bottom_time when runtime null', () async {
320+
await insertDiveWithTank(
321+
id: 'dive-norunt',
322+
bottomTimeSeconds: 40 * 60,
323+
runtimeSeconds: null,
324+
avgDepth: 20.0,
325+
startPressure: 200,
326+
endPressure: 50,
327+
tankRole: 'backGas',
328+
);
329+
330+
final sacByRole = await repository.getSacPressureByTankRole();
288331

289332
expect(sacByRole, isNotEmpty);
290333
expect(sacByRole['backGas']!, greaterThan(0));

0 commit comments

Comments
 (0)