Implement unresolved count query for nav badge display
epic-duplicate-activity-detection-core-logic-task-006 — Implement the getUnresolvedCount() method in DeduplicationQueueService that returns an integer count of unresolved pairs scoped to the coordinator's chapters. This count drives the navigation badge on the coordinator queue entry point. Optimize the query to avoid fetching full records — use a COUNT aggregate via the repository. Cache the result with a short TTL to avoid redundant requests on tab focus.
Acceptance Criteria
Technical Requirements
Execution Context
Tier 2 - 518 tasks
Can start after Tier 1 completes
Implementation Notes
Supabase Dart supports `.count()` via `CountOption` — use `CountOption.exact` for precision. Example: `.from('duplicate_queue').select('', const FetchOptions(count: CountOption.exact)).eq('status', 'unresolved').in('chapter_id', chapterIds)` — the response `.count` field gives the integer without fetching rows. Implement the cache as a simple Riverpod `StateProvider<({int count, DateTime fetchedAt})?>` keyed to the user session. The provider is invalidated (set to null) by calling `ref.invalidate()` from forceResolve().
Use a Riverpod `FutureProvider` that checks the cache first and falls through to the repository only on miss or expiry. Ensure the nav badge widget uses `ref.watch` on this provider so it rebuilds reactively when the cache is invalidated.
Testing Requirements
Unit tests: verify COUNT query is issued (not a full-row fetch), cache returns stale value within TTL and fetches fresh value after TTL expires, cache invalidation triggers a fresh fetch. Test zero count returns 0 (not null). Test that cache is keyed per user (simulate two user sessions). Integration test: confirm count matches the actual number of unresolved rows in the test database.
Test that resolving a pair via forceResolve() reduces count by 1 on next call.
If the duplicate check RPC fails due to a network error or Supabase outage, the service must decide whether to block submission entirely (safe but disruptive) or allow submission to proceed silently (functional but risks data duplication). An incorrect choice leads to either user frustration or data quality issues.
Mitigation & Contingency
Mitigation: Define an explicit error policy in the service: RPC failures result in a DuplicateCheckResult with status: 'check_failed' and no candidates. The caller treats this as 'allow submission, flag for async review'. Document this as the intended graceful degradation behaviour in the service interface contract.
Contingency: If stakeholders require blocking on RPC failure, expose a configurable `failMode` parameter in the service that can be toggled per organisation via the feature flag system without a code deployment.
The DuplicateComparisonPanel must handle varying activity schemas across organisations (NHF, HLF, Blindeforbundet each have different activity fields). A rigid layout may not accommodate all field variations, causing truncation or missing data in the comparison view.
Mitigation & Contingency
Mitigation: Design the panel to render a dynamic list of key-value pairs rather than a fixed-column layout. Define a `ComparisonField` model that each service populates with only the fields relevant to the activity type and organisation, allowing the panel to adapt without schema knowledge.
Contingency: If dynamic rendering proves too complex within the timeline, ship a simplified panel showing only the five most critical fields (peer mentor, activity type, date, chapter, submitter) and log a follow-up ticket for full field rendering in a later sprint.