critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

BulkRegistrationService.registerBulk accepts an optional CoordinatorDuplicateDecision parameter (enum: include_all, exclude_flagged) defaulting to exclude_flagged
Before any database write, the service calls Proxy Duplicate Detector for each mentor ID in the input list, passing the ActivityTemplate (activity_type_id, date) to check for existing activities on the same date
Duplicate detection calls are made in parallel (Future.wait) to minimise latency, not sequentially
Each mentor flagged as a duplicate has their BulkRegistrationEntry.status set to skipped_duplicate when CoordinatorDuplicateDecision is exclude_flagged
When CoordinatorDuplicateDecision is include_all, duplicate-flagged mentors are passed through to the write step with status not overridden
BulkRegistrationResult includes the full list of entries including skipped_duplicate entries so the UI can display a summary
If Proxy Duplicate Detector is unavailable (timeout, error), the service logs the error, marks the affected mentor entries with status failure and error_reason 'duplicate_check_failed', and continues processing remaining mentors
Duplicate detection does not block the service from processing mentors that were not flagged
The duplicate detection step accounts for organisation-scoped activity uniqueness (same mentor + same date + same activity_type in the same organisation)

Technical Requirements

frameworks
Flutter
BLoC
apis
Supabase PostgreSQL 15 (activity table duplicate query)
Supabase Edge Functions (if duplicate detection is server-side)
data models
activity
activity_type
assignment
performance requirements
Duplicate detection for a batch of 50 mentors must complete within 5 seconds
Use Future.wait for parallel duplicate checks — sequential checks are not acceptable for batches >5
Implement a timeout of 3 seconds per individual duplicate check call
security requirements
Duplicate detection queries must be scoped to the coordinator's organisation via RLS — a coordinator cannot detect duplicates across other organisations
Service role key must not be used for duplicate detection — use the coordinator's JWT
No mentor personal data (names, contacts) should be included in duplicate detection payloads — use UUIDs only

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Implement the duplicate detection as a separate ProxyDuplicateDetector class injected into BulkRegistrationService via constructor (dependency injection pattern for testability). The detector should query the Supabase activity table for existing records matching (peer_mentor_id, activity_type_id, date::date, organization_id). Use a single batch query (IN clause on mentor IDs) rather than N individual queries to avoid N+1 performance issues — return a Set of mentor IDs that have duplicates. The CoordinatorDuplicateDecision parameter allows the UI (confirmation screen in task-010) to pass the coordinator's choice after showing warnings.

Design the service so duplicate detection is idempotent — calling it twice with the same inputs produces the same result without side effects. Handle Future.wait errors with Future.wait(..., eagerError: false) to collect all results rather than failing fast on the first error.

Testing Requirements

Unit tests: test that all mentors are checked in parallel (mock the duplicate detector and assert Future.wait usage). Test that mentors flagged as duplicates are skipped when decision is exclude_flagged. Test that all mentors proceed when decision is include_all. Test partial failure: 1 of 5 duplicate checks fails — the other 4 should still proceed.

Test that BulkRegistrationResult correctly counts skipped_duplicate entries separately from failure entries. Test timeout handling: duplicate detector does not respond within 3s — verify mentor is marked failure with 'duplicate_check_failed'. Integration test: against a Supabase test schema, insert a known activity and verify the duplicate detector correctly identifies it. Coverage target: ≥90% on the duplicate detection integration path.

Component
Bulk Registration Service
service high
Epic Risks (3)
medium impact medium prob scope

If the batch insert RPC returns a mix of successes and failures (e.g., 3 of 10 mentors fail due to constraint violations that slipped through application-level duplicate detection), the confirmation screen result state becomes ambiguous. A coordinator who sees '7 of 10 succeeded' may not know whether to manually register the 3 failures, retry, or escalate — leading to either duplicate registrations or silent underreporting.

Mitigation & Contingency

Mitigation: Design the Bulk Registration Service to return a strongly typed BulkRegistrationResult with per-mentor RegistrationOutcome (success | duplicate_detected | constraint_violation | permission_denied). Design the result screen to list each failed mentor with a specific, plain-language reason and a one-tap 'Retry for this mentor' action that pre-fills the activity wizard with the batch template for that individual.

Contingency: If per-mentor retry UI is too complex to deliver within the epic scope, fall back to displaying failed mentors with their error codes and instructing coordinators to use single-proxy mode for the failures. Document this as a known limitation in release notes and create a follow-up ticket for per-mentor retry in the next sprint.

medium impact medium prob dependency

The Proxy Activity Wizard must reuse the existing activity wizard step widgets (type, date, duration, notes) while injecting a proxy attribution banner and a different submission payload builder. If the existing wizard is not designed for composability, the proxy variant may require forking the widget tree, creating two maintenance-diverging codebases that will drift out of sync when the base wizard is updated (e.g., new activity types added, new mandatory fields).

Mitigation & Contingency

Mitigation: Before implementing the Proxy Activity Wizard, audit the existing activity wizard's architecture. If steps are already extracted as independent StatelessWidget/ConsumerWidget classes, compose them directly with a wrapping Column that injects the attribution banner. If they are tightly coupled inside a parent widget, refactor the existing wizard to accept a nullable ProxyContext parameter before starting the proxy variant — this refactor should be a prerequisite task in this epic.

Contingency: If refactoring the base wizard is blocked by unrelated in-flight work on that component, implement the proxy wizard as a full fork but create a shared StepWidgets library file that both the base wizard and proxy wizard import. Schedule a deduplication refactor as a tech-debt ticket in the next planning cycle.

medium impact medium prob technical

The bulk registration flow spans three sequential screens (multi-select → activity form → confirmation → result) with shared mutable state: the selected mentor list, the activity template, the per-mentor duplicate warnings, and the final submission result. Managing this state across screens without a well-designed Bloc risks state leaks, stale duplicate warning data after mentor removal, and confirmation screen inconsistencies if the user navigates back and changes the mentor selection.

Mitigation & Contingency

Mitigation: Define a single BulkRegistrationBloc (or Cubit) with explicit state transitions covering: MentorsSelected → ActivityTemplateCompleted → DuplicatesChecked → ConfirmationReady → Submitting → SubmissionResult. Each backward navigation event (e.g., 'Back' from confirmation to mentor selection) dispatches a ResetToMentorSelection event that clears downstream state. Unit test every state transition with edge cases including empty mentor list, all mentors having duplicates, and network failure during submission.

Contingency: If state management complexity causes persistent bugs in testing, simplify by passing state explicitly through Navigator arguments (immutable snapshots per screen) rather than a shared Bloc. This reduces flexibility but eliminates cross-screen state mutation bugs.