Implement parameterized RPC call wrappers
epic-bufdir-data-aggregation-data-layer-task-003 — Implement typed Dart function signatures in the Aggregation Query Builder that wrap each Supabase RPC function call (activity counts, contact counts, event counts, geographic distribution, participant deduplication). Each wrapper must accept a reporting period, organization filter, and optional category filter, then return the appropriate strongly typed Dart model. Apply the org-isolation filter on every call.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Define the five wrapper methods as public async methods on `AggregationQueryBuilder`. Use a private helper `_callRpc
For the `categoryFilter`, define an `allowlist` constant Set of valid Bufdir category code strings and validate at the top of each wrapper that accepts a category. Define `AggregationQueryException` in the shared exceptions file with fields: `String rpcName`, `String message`, `Object? cause`. Note that Supabase RPCs return `List
Testing Requirements
Unit tests using `flutter_test` and mocktail must cover: (1) Happy-path test for each of the five wrappers: stub the Supabase client mock to return a valid JSON list, assert the returned Dart model equals the expected deserialized value. (2) Org filter application: verify `applyOrgFilter` was called exactly once per wrapper invocation using mock verification. (3) RPC name correctness: verify the exact RPC function name string is passed to `supabase.rpc()` for each wrapper. (4) Category filter pass-through: verify the category parameter is included in the RPC params when provided and omitted when null.
(5) Empty result handling: stub the mock to return an empty list, assert the method returns a zero-value model without throwing. (6) Error handling: stub the mock to throw a `PostgrestException`, assert the wrapper rethrows as `AggregationQueryException` with the original message. (7) Timeout behavior: stub the mock to never complete, assert the Future completes with a timeout error within 11 seconds. Test coverage target: 100% of all five wrapper method branches.
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.
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.