critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

File lib/features/annual_summary/domain/models/annual_summary.dart (or equivalent domain path) contains AnnualSummary, AnnualSummaryPeriod, and AnnualSummaryStatus
AnnualSummary fields: id (String), peerMentorId (String), year (int), period (AnnualSummaryPeriod), computedData (AnnualSummaryComputedData), status (AnnualSummaryStatus), createdAt (DateTime), updatedAt (DateTime)
AnnualSummaryPeriod is an immutable value object with periodStart (DateTime) and periodEnd (DateTime); includes a duration getter returning the span as a Duration
AnnualSummaryStatus is a Dart enum with values: pending, computing, ready, error — matches the database CHECK constraint values exactly
AnnualSummaryComputedData is a typed value object with at minimum: totalHours (double), uniqueContacts (int), activityTypeDistribution (Map<String, double>), currentStreak (int), topMilestones (List<String>)
AnnualSummary.fromJson(Map<String, dynamic>) correctly deserialises a Supabase row including nested JSONB computed_data field
AnnualSummary.toJson() produces a Map that round-trips through fromJson with no data loss
AnnualSummary.copyWith() allows partial updates of all fields without mutating the original instance
AnnualSummary overrides == and hashCode based on id field; two instances with the same id are equal regardless of other field values
AnnualSummary.empty() factory constructor returns a valid instance with zero-value computed data and status pending — used as initial state
All classes are immutable (final fields, no setters)
No dependency on Flutter widgets — domain models are pure Dart

Technical Requirements

frameworks
Dart
apis
Supabase (JSON row contract)
data models
AnnualSummary
AnnualSummaryPeriod
AnnualSummaryStatus
AnnualSummaryComputedData
performance requirements
fromJson/toJson must handle null computed_data gracefully — return AnnualSummaryComputedData.empty() when the JSONB field is null or empty
security requirements
No PII fields (e.g., contact names) in the domain model — only aggregated counts and durations
peerMentorId is a UUID string — validate format in fromJson with an assertion or factory guard

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Keep domain models in lib/features/annual_summary/domain/ following clean architecture conventions already present in the project. Do not use code generation (freezed, json_serializable) unless it is already used in the project — manual implementation is preferred for a canonical data contract to avoid generator version drift. For AnnualSummaryStatus.fromString(), use a switch expression with a default to AnnualSummaryStatus.error for unknown values — this makes the model resilient to future database value additions. Document the expected JSON shape of computed_data with a comment or inline example, as this is the single source of truth for all downstream consumers (aggregation service, milestone detection service, visualization widgets).

Ensure DateTime fields use UTC: DateTime.parse(json['created_at'] as String).toUtc().

Testing Requirements

Unit tests in test/features/annual_summary/domain/annual_summary_test.dart using flutter_test. Test groups: (1) fromJson — valid full payload, null computed_data, missing optional fields, unknown status string falls back to AnnualSummaryStatus.error. (2) toJson — round-trip equality (fromJson(toJson(model)) == model). (3) copyWith — verify each field can be independently updated without mutating original.

(4) equality — two instances with same id are equal; instances with different ids are not equal. (5) AnnualSummaryPeriod.duration getter returns correct Duration. (6) AnnualSummaryComputedData — activityTypeDistribution values sum to ~100.0, zero-value empty() constructor. Achieve ≥90% line coverage.

Component
Annual Summary Repository
data medium
Epic Risks (3)
medium impact medium prob dependency

Rive animation files may not be available at implementation time, blocking the wrapped-animation-controller from being fully tested. If asset delivery is delayed, the controller cannot be validated for memory-leak-free disposal.

Mitigation & Contingency

Mitigation: Implement the animation controller with stub/placeholder AnimationController instances first so the lifecycle and disposal logic can be unit-tested independently of Rive assets. Define a named animation registry interface early so UI components can reference animations by name without coupling to specific Rive files.

Contingency: If Rive assets are not delivered before Epic 3 begins, replace Rive animations with Flutter implicit animations (AnimatedOpacity, ScaleTransition) as a drop-in and schedule Rive integration as a follow-on task once assets arrive.

high impact medium prob technical

The annual_summaries Supabase RPC aggregating 12 months of activity records per mentor may exceed acceptable query latency (>2s) for mentors with high activity volumes such as the HLF mentor with 380 registrations cited in workshop notes.

Mitigation & Contingency

Mitigation: Design the RPC to materialise summary results into the annual_summaries table via a scheduled edge function rather than computing on demand. The repository reads pre-computed rows, keeping query latency constant regardless of activity volume.

Contingency: If on-demand queries are required for real-time period switching, add a PostgreSQL partial index on (mentor_id, activity_date) and implement a client-side loading skeleton so slow queries degrade gracefully rather than blocking the UI.

medium impact low prob technical

iOS requires photo library permission before saving a screenshot to the gallery. If the permission prompt is triggered at an unexpected point in the share flow, the UX breaks and users may deny permission permanently, making gallery save unavailable.

Mitigation & Contingency

Mitigation: Trigger the permission request only when the user explicitly chooses 'Save to gallery' in the share overlay, not on screen load. Implement a pre-prompt explanation screen following Apple HIG so users understand why the permission is needed before the system dialog appears.

Contingency: If permission is denied, gracefully fall back to clipboard copy and system share sheet options which do not require photo library access, and surface a non-blocking snackbar explaining the limitation.