Define typed Dart models for stats rows
epic-coordinator-statistics-dashboard-foundation-task-001 — Create all typed Dart model classes that represent the stats data contract consumed by higher tiers. This includes StatsRow, PeriodStats, ActivityTypeStats, CoordinatorStats, and PeerMentorStats models with proper JSON serialization, equality, and copyWith methods. These models form the stable data contract for all dashboard widgets.
Acceptance Criteria
Technical Requirements
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.
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.
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.
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.