critical priority medium complexity backend pending backend specialist Tier 4

Acceptance Criteria

Concrete BufdirMetricsRepository class implements the abstract IBufdirMetricsRepository interface with all five read methods: fetchRawActivities, fetchRawEvents, fetchRawContacts, fetchParticipantCount, and fetchGeographicDistribution
Every read method delegates to the AggregationQueryBuilder without containing inline SQL or direct Supabase client calls
All methods return Either<DomainFailure, T> (or equivalent typed result) — never raw exceptions propagate to callers
Supabase PostgrestException is mapped to a typed QueryFailure with the original error code preserved
Network/timeout errors from Supabase are mapped to a typed NetworkFailure domain error
Organization isolation is enforced: every fetch call receives the org_id parameter and the query builder applies it — a missing org_id causes an ArgumentError before any network call
fetchGeographicDistribution returns municipality-level precision only (no exact GPS coordinates) in compliance with PostGIS privacy requirements
All returned domain models are validated against their Dart type contracts before being wrapped in Right()
Repository constructor accepts AggregationQueryBuilder as a dependency injection parameter (not constructed internally)
No raw Supabase client or PostgreSQL credentials are referenced in this file

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase PostgreSQL 15 RPC functions
Supabase REST API via supabase_flutter SDK
data models
activity
annual_summary
contact
assignment
bufdir_export_audit_log
performance requirements
Each read method must complete within 5 seconds under normal network conditions
Repository must not block the UI thread — all operations must be async/await
No redundant Supabase round-trips: each method performs exactly one query builder call
security requirements
org_id must be validated as non-null, non-empty UUID before any Supabase call
JWT from Supabase Auth session must be attached to every request (handled by supabase_flutter client automatically — verify it is not bypassed)
RLS policies on Supabase enforce org isolation server-side; the repository must also enforce it client-side as defense-in-depth
No PII fields (names, contact details) returned in geographic distribution queries — only counts and region codes
Service role key must never appear in Flutter source code

Execution Context

Execution Tier
Tier 4

Tier 4 - 323 tasks

Can start after Tier 3 completes

Implementation Notes

Follow the existing repository pattern in the codebase — look at any existing SupabaseRepository implementations for error-mapping conventions before writing new code. Use a sealed class hierarchy for DomainFailure (QueryFailure, NetworkFailure, UnauthorizedFailure) if not already present. The Either pattern should use dartz or the project's existing Result type — do not introduce a new dependency. For geographic distribution, verify PostGIS returns municipality-level codes (kommunenummer) and not raw ST_Point coordinates before passing to the domain model.

Inject AggregationQueryBuilder via Riverpod Provider so tests can override it. Keep the repository thin — zero business logic, only delegation and error mapping.

Testing Requirements

Unit tests (flutter_test + mockito) must cover: (1) each of the five read methods delegates to MockAggregationQueryBuilder with correct parameters, (2) Supabase PostgrestException is mapped to QueryFailure with correct code, (3) generic Exception is mapped to NetworkFailure, (4) null/empty org_id causes ArgumentError before any builder call, (5) valid response is wrapped in Right() with correctly parsed domain model, (6) repository can be injected as IBufdirMetricsRepository mock in service-layer tests. Minimum 90% line coverage on the repository class. No real Supabase connections in unit tests.

Component
Bufdir Metrics Repository
data medium
Epic Risks (2)
high impact medium prob technical

Supabase RPC functions return JSON with PostgreSQL numeric types (bigint, numeric) that do not map cleanly to Dart int/double. Silent truncation or JSON parsing errors could corrupt participant counts in the final Bufdir submission without any runtime exception.

Mitigation & Contingency

Mitigation: Define explicit Dart fromJson factories for all RPC result models with type-safe parsing and assertion checks. Add a contract test that compares raw RPC JSON output against expected Dart model values using a known seed dataset.

Contingency: If type mismatches are found in production metrics, expose a validation endpoint in BufdirMetricsRepository that re-fetches and compares raw RPC output against the persisted snapshot, flagging any discrepancies before export proceeds.

medium impact high prob scope

Persisted metric snapshots can become stale if additional activities are registered after the snapshot is saved but before the export is finalized. Coordinators might unknowingly export data that does not reflect the latest activity registrations.

Mitigation & Contingency

Mitigation: Store a snapshot_generated_at timestamp and a record_count_at_generation field in the snapshot. When the coordinator views cached results, compare the current activity count for the period against the snapshot value and display a 'Data updated since last aggregation — re-run?' warning if counts differ.

Contingency: Add a mandatory staleness check before the export confirmation dialog can proceed: if the snapshot is more than 24 hours old or the record count has changed, require the coordinator to re-run aggregation before the export button is enabled.