critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

StatsRow model includes: id (String), activityType (ActivityType enum), coordinatorId (String), orgId (String), chapterId (String), occurredAt (DateTime), durationMinutes (int), and optional notes (String?)
PeriodStats model includes: startDate (DateTime), endDate (DateTime), totalActivities (int), totalDurationMinutes (int), and breakdownByType (Map<ActivityType, int>)
ActivityTypeStats model includes: activityType (ActivityType), count (int), totalDurationMinutes (int), and percentageOfTotal (double)
CoordinatorStats model includes: coordinatorId (String), orgId (String), chapterId (String), period (PeriodStats), and activityBreakdown (List<ActivityTypeStats>)
PeerMentorStats model includes: mentorId (String), period (PeriodStats), activityBreakdown (List<ActivityTypeStats>), and assignedCoordinatorId (String)
All models implement fromJson(Map<String, dynamic>) and toJson() with correct DateTime ISO 8601 handling
All models implement == and hashCode using all fields
All models implement copyWith() that accepts nullable overrides for every field
ActivityType enum covers all activity types defined in the existing activity domain (no hardcoded strings in models)
All model files include Dart doc comments on every public field describing its business meaning
No circular imports — models depend only on the ActivityType enum and core Dart types
flutter test on model files passes with zero errors

Technical Requirements

frameworks
Flutter
Dart
data models
StatsRow
PeriodStats
ActivityTypeStats
CoordinatorStats
PeerMentorStats
ActivityType
performance requirements
fromJson() for a list of 1000 StatsRow instances must complete in under 50ms on a mid-range device
security requirements
Models must not include authentication tokens or raw Supabase row metadata beyond what is listed above
coordinatorId and mentorId must be non-nullable strings — null IDs indicate a data integrity error and must throw during fromJson()

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place all models in lib/features/stats/domain/models/ to keep them in the domain layer. Use a shared ActivityType enum imported from lib/features/activity/domain/enums/activity_type.dart — do not duplicate the enum. Implement copyWith() manually (not via code generation) to avoid build_runner dependency at this layer; the number of fields is manageable. For percentageOfTotal in ActivityTypeStats, compute it as a derived field in fromJson() or a getter rather than storing it separately to prevent inconsistency.

Use DateTime.parse() for JSON deserialisation and .toIso8601String() for serialisation — handle both UTC and local time by always storing UTC internally. These models are the lowest-level contract; any schema changes here will cascade to all consumers, so document breaking change policy in a comment at the top of each file.

Testing Requirements

Unit tests in flutter_test. For each model: test fromJson round-trip for fully populated instance, test fromJson round-trip for instance with optional fields absent, test copyWith changes only targeted fields, test == returns true for identical instances and false after changing each field individually. Test that fromJson throws ArgumentError or FormatException when required fields are missing. Test that fromJson parses ISO 8601 DateTime strings with timezone offsets correctly.

Minimum 95% line coverage on all model files.

Component
Stats Repository
data medium
Epic Risks (3)
high impact medium prob technical

Pre-aggregated Supabase views may still be slow for orgs with very large activity datasets (NHF with 1,400 chapters). If the view query plan performs sequential scans, dashboard load times could exceed acceptable thresholds and degrade the perceived value of the feature.

Mitigation & Contingency

Mitigation: Design views with composite indexes on (org_id, coordinator_id, month) from the start. Run EXPLAIN ANALYZE during development against a seeded dataset of realistic scale. Add materialized view refresh strategy if needed.

Contingency: If live view performance is insufficient, convert to materialized views refreshed on a schedule or on activity-write triggers. Expose the refresh delay transparently in the UI with a 'last updated' timestamp.

high impact low prob security

Supabase RLS policies for the stats views may not be configured correctly during initial migration, potentially allowing cross-coordinator data leakage before the RoleAccessValidator layer is reached. This is a security and compliance risk.

Mitigation & Contingency

Mitigation: Write RLS integration tests as part of this epic that explicitly verify a coordinator JWT cannot read another coordinator's stats rows. Apply RLS policies in the migration script itself, not as a manual step.

Contingency: If an RLS gap is discovered post-deployment, immediately disable the stats screen via a feature flag, apply the corrected RLS migration, and re-enable after verification. Log and audit all queries that ran during the gap window.

medium impact medium prob integration

Cache invalidation logic may not be triggered correctly when a new activity is registered by a peer mentor or when an expense approval is granted. Stale data could cause coordinators to make decisions based on outdated KPIs, undermining trust in the dashboard.

Mitigation & Contingency

Mitigation: Define explicit invalidation event contracts with the activity registration and expense approval pipelines. Implement an event bus subscription within StatsCacheManager. Document the invalidation contract in code.

Contingency: If event-driven invalidation proves unreliable, add a manual 'Refresh' pull-to-refresh gesture on the dashboard and reduce TTL to 5 minutes as a fallback degradation strategy.