critical priority medium complexity backend pending backend specialist Tier 0

Acceptance Criteria

Dart model classes are defined for all six types: ActivityRecord, EventRecord, ContactRecord, MetricSnapshot, GeographicDistributionResult, and ParticipantCount
Each model implements `fromJson(Map<String, dynamic> json)` factory constructor that correctly deserializes all fields including nested objects and enums
Each model implements `toJson()` method that produces a Map representation suitable for Supabase insertion and Bufdir payload construction
Each model overrides `==` and `hashCode` using all meaningful fields so equality-based test assertions and deduplication logic work correctly
Each model includes a `copyWith()` method that returns a new instance with selectively overridden fields, with all other fields preserved
All date/time fields use `DateTime` (UTC-normalized) rather than raw strings
Nullable fields are explicitly typed as `T?` and non-nullable fields as `T` with no late initializers
All enum values used within models are defined as Dart enums with `fromJson` string-mapping support
Models compile cleanly with `dart analyze` with zero warnings or errors
Unit tests verify round-trip serialization: `fromJson(model.toJson()) == model` for all model types
All field names in `fromJson`/`toJson` exactly match the Supabase column names and RPC return field names

Technical Requirements

frameworks
flutter_test (for unit tests)
Dart core library only — no code generation packages unless already in pubspec
apis
Supabase RPC return schemas (activity_counts, contact_counts, event_counts, geographic_distribution, participant_deduplication)
Bufdir submission payload schema (field name mapping)
data models
ActivityRecord
EventRecord
ContactRecord
MetricSnapshot
GeographicDistributionResult
ParticipantCount
performance requirements
Model instantiation from JSON must complete in under 1ms for typical payloads
Models must be immutable (all fields final) to prevent unintended mutation during aggregation
security requirements
Models must not store organization_id as a mutable field — it must be set at construction time and cannot be changed
No raw dynamic fields: all JSON keys must be mapped to typed Dart properties

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Place all model classes under `lib/src/features/bufdir/data/models/` using one file per model class for discoverability. Define a base `BufdirModel` abstract class with `toJson()` declared abstract and a static helper `_parseDateTime(dynamic value)` for consistent UTC normalization of all date fields from Supabase. Use `@immutable` annotation from `package:meta` on every model class. For enums (e.g., activity category types), define them in a shared `lib/src/features/bufdir/data/models/enums.dart` file with extension methods for `fromString` and `toJson` string conversion.

Avoid json_serializable or freezed unless already a project dependency — hand-written serialization is easier to audit for field-name correctness against Supabase column names. Keep field names in `fromJson` as string literals matching the Supabase schema exactly, and document any discrepancies with a comment. These models are the foundational contract for the entire data layer epic — treat them as a public API: any breaking change requires updating all consumers.

Testing Requirements

Unit tests using `flutter_test` must cover: (1) `fromJson` deserialization for each model with a complete, valid JSON fixture. (2) `toJson` serialization producing the expected Map structure for each model. (3) Round-trip test: `fromJson(model.toJson()) == model` asserts true for each model type. (4) Equality tests: two instances with identical field values are equal; changing any single field produces inequality.

(5) `copyWith` tests: verify unchanged fields are preserved and the overridden field reflects the new value. (6) Null-safety boundary tests: optional fields set to null deserialize without error; required fields missing from JSON throw a typed exception. Test coverage target: 100% of model methods.

Component
Aggregation Query Builder
data high
Epic Risks (2)
high impact medium prob technical

Supabase RPC functions return JSON with PostgreSQL numeric types (bigint, numeric) that do not map cleanly to Dart int/double. Silent truncation or JSON parsing errors could corrupt participant counts in the final Bufdir submission without any runtime exception.

Mitigation & Contingency

Mitigation: Define explicit Dart fromJson factories for all RPC result models with type-safe parsing and assertion checks. Add a contract test that compares raw RPC JSON output against expected Dart model values using a known seed dataset.

Contingency: If type mismatches are found in production metrics, expose a validation endpoint in BufdirMetricsRepository that re-fetches and compares raw RPC output against the persisted snapshot, flagging any discrepancies before export proceeds.

medium impact high prob scope

Persisted metric snapshots can become stale if additional activities are registered after the snapshot is saved but before the export is finalized. Coordinators might unknowingly export data that does not reflect the latest activity registrations.

Mitigation & Contingency

Mitigation: Store a snapshot_generated_at timestamp and a record_count_at_generation field in the snapshot. When the coordinator views cached results, compare the current activity count for the period against the snapshot value and display a 'Data updated since last aggregation — re-run?' warning if counts differ.

Contingency: Add a mandatory staleness check before the export confirmation dialog can proceed: if the snapshot is more than 24 hours old or the record count has changed, require the coordinator to re-run aggregation before the export button is enabled.