critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

ActivitySummaryAggregator implements IActivitySummaryRepository and provides getActivitySummaryForMentor(String mentorId, DateRange dateRange).
Supabase query selects only id, duration_minutes, completed_at from the activities table with filters: mentor_id = mentorId, status = 'completed', completed_at >= dateRange.start, completed_at <= dateRange.end.
sessionCount equals the number of returned rows.
totalDurationMinutes equals the sum of all duration_minutes values.
averageDurationMinutes equals totalDurationMinutes / sessionCount (not computed by Supabase — computed in Dart).
When the query returns zero rows, ActivitySummary.empty() is returned with periodStart and periodEnd matching the requested DateRange.
periodStart and periodEnd in the returned ActivitySummary match the requested DateRange boundaries.
If Supabase returns a row with null duration_minutes, it is treated as 0 for aggregation with a debug-level log warning.
PostgrestException is caught and re-thrown as a domain-specific ActivitySummaryFetchException with a human-readable message.
Unit tests using mocktail achieve 90%+ branch coverage for all scenarios including empty result, null duration, and Supabase error.

Technical Requirements

frameworks
Flutter
BLoC
flutter_test
mocktail
apis
Supabase REST API — activities table (select, filter by mentor_id, status, completed_at range)
data models
ActivitySummary
DateRange
performance requirements
Supabase query uses column selection (not select *) to minimize payload.
In-memory aggregation for up to 500 rows completes in under 50ms.
No N+1 queries — all data fetched in a single Supabase call.
security requirements
Supabase RLS must enforce that a peer mentor can only query their own activity records — the mentorId filter is a secondary defense, not the primary one.
Do not log individual activity IDs or durations at INFO level in production builds.
DateRange boundaries must be validated (end >= start) before constructing the Supabase query.

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement in lib/features/benefit_calculator/data/services/activity_summary_aggregator.dart. Inject the Supabase client via constructor. Use supabase.from('activities').select('id, duration_minutes, completed_at').eq('mentor_id', mentorId).eq('status', 'completed').gte('completed_at', dateRange.start.toIso8601String()).lte('completed_at', dateRange.end.toIso8601String()). Cast result to List> and iterate to compute aggregates.

Use null-aware arithmetic: (row['duration_minutes'] as num?)?.toDouble() ?? 0.0. For averageDurationMinutes, guard against division by zero: sessionCount > 0 ? totalDurationMinutes / sessionCount : 0.0.

Register as a Riverpod provider or inject via constructor in the BLoC that uses it. Define ActivitySummaryFetchException in the domain layer with a message and optional Supabase error code field.

Testing Requirements

Unit tests using flutter_test and mocktail. Mock the Supabase client to return predefined row lists. Test file: test/services/activity_summary_aggregator_test.dart. Scenarios: (1) multiple completed activities returns correct sessionCount, totalDurationMinutes, averageDurationMinutes, (2) empty result set returns ActivitySummary.empty() with correct period dates, (3) row with null duration_minutes treated as 0 and logged, (4) PostgrestException mapped to ActivitySummaryFetchException, (5) single-row result (averageDurationMinutes equals totalDurationMinutes).

Verify that the Supabase query mock was called with the exact expected filters using mocktail's verify().

Component
Activity Summary Aggregator
service low
Epic Risks (2)
medium impact medium prob integration

Supabase organisation configuration table may not yet have the five multiplier columns, requiring a migration. If the migration is not coordinated with other teams touching the same table, schema conflicts could delay delivery.

Mitigation & Contingency

Mitigation: Add multiplier columns in a dedicated, non-destructive ALTER TABLE migration script. Review the organisation config table schema with the backend team before writing the migration. Use nullable columns with defaults so existing rows are unaffected.

Contingency: If the migration cannot be deployed in time, stub the repository to return hardcoded default multiplier values from a local config file, allowing parallel development. Swap in the real Supabase fetch once the migration is live.

low impact medium prob scope

The ActivitySummaryAggregator depends on activity records already persisted in the database. For newly onboarded peer mentors with no activity history, the aggregator will return zero counts, which could make the calculator appear broken on first use.

Mitigation & Contingency

Mitigation: Design the pre-fill value object to distinguish between 'no data yet' and 'zero activities'. The calculator input panel should display empty inputs (not zero) when no history exists, with placeholder text guiding the user to enter values manually.

Contingency: If the distinction cannot be surfaced cleanly in the UI timeline, fall back to always showing empty inputs and document the manual-entry path as the primary UX until activity data accumulates.