critical priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

An abstract Dart class `BufdirMetricsRepository` is defined in `lib/src/features/bufdir/domain/repositories/bufdir_metrics_repository.dart`
The interface declares exactly eight abstract methods: `fetchRawActivities`, `fetchRawEvents`, `fetchRawContacts`, `fetchParticipantCount`, `fetchGeographicDistribution`, `saveMetricSnapshot`, `getMetricSnapshot`, and `deleteMetricSnapshot`
Each method has a fully typed signature using the model classes from task-001: correct return types (`Future<List<ActivityRecord>>`, `Future<ParticipantCount>`, etc.) and correct parameter types (`DateTimeRange`, `OrgFilter`, `String snapshotId`, etc.)
The interface includes Dart doc comments on every method explaining its contract, expected behavior, and what exception types it may throw
The interface compiles cleanly with `dart analyze` with zero warnings
A mock implementation (`MockBufdirMetricsRepository`) is generated or hand-written and placed in `test/mocks/` for use in service-layer tests
No concrete implementation is included in this task — the abstract class declares only method signatures with no method bodies
The interface does not import any Supabase or Flutter framework packages — it depends only on Dart core and the domain model classes
A canary unit test verifies that `MockBufdirMetricsRepository` compiles and can be instantiated, ensuring the mock is valid before service-layer tests depend on it

Technical Requirements

frameworks
flutter_test
Mockito or mocktail (for mock generation)
data models
ActivityRecord
EventRecord
ContactRecord
MetricSnapshot
GeographicDistributionResult
ParticipantCount
OrgFilter
DateTimeRange
performance requirements
The interface imposes no performance constraints — these are declared by concrete implementations
security requirements
The interface must not expose organization_id as a standalone parameter — it must always be encapsulated within an `OrgFilter` typed parameter to prevent accidental omission in concrete implementations

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place the abstract class in the `domain/repositories/` layer (not `data/`), following clean architecture conventions — the domain layer defines the contract, the data layer implements it. Method signatures should use `DateTimeRange` from `dart:ui` or a project-defined equivalent rather than raw `DateTime` start/end pairs, to make the period boundary unambiguous. For `saveMetricSnapshot` and `deleteMetricSnapshot`, use `String snapshotId` as the identifier rather than a full model object for `delete`. For `getMetricSnapshot`, return `Future` (nullable) to indicate that a snapshot may not exist for the requested ID — this is the repository query convention used throughout the project.

When generating the mock with Mockito's `@GenerateMocks`, add `@GenerateMocks([BufdirMetricsRepository])` to the test file and run `dart run build_runner build` — or hand-write the mock using `mocktail`'s `class MockBufdirMetricsRepository extends Mock implements BufdirMetricsRepository {}` pattern, which requires no code generation. This interface is the single most important artifact produced by the data layer epic — its stability directly determines the velocity of all service-layer development and testing that follows.

Testing Requirements

A single canary unit test file (`test/features/bufdir/domain/bufdir_metrics_repository_test.dart`) must: (1) Instantiate `MockBufdirMetricsRepository` (or the hand-written mock) and verify it compiles without error. (2) Stub one method (e.g., `fetchRawActivities`) and assert the stub returns the configured value — confirming the mock framework integration is working correctly. (3) Verify that calling an unstubbed method on the mock throws a `MissingStubError` (Mockito) or equivalent, confirming strict mock behavior is enforced. This canary test is intentionally minimal — its purpose is to validate that the interface and mock are valid Dart before service-layer tests are written against them.

Coverage target: canary test only; implementation tests belong in later tasks.

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.