high priority low complexity frontend pending frontend specialist Tier 1

Acceptance Criteria

AggregationSummaryData is defined as an immutable Dart class with Equatable
totalParticipantCount field is of type int
deduplicatedParticipantCount field is of type int — must always be less than or equal to totalParticipantCount
activityHoursByCategory field is of type Map<String, double> where keys are Bufdir category names and values are total hours
geographicDistribution field is of type List<GeographicRegionSummary> where each entry has regionName (String), chapterName (String), and participantCount (int)
unmappedActivityTypes field is of type List<UnmappedActivitySummary> where each entry has activityTypeName (String) and count (int)
deduplicationAnomalySummary field is of type DeduplicationAnomalySummary with anomalyCount (int) and description (String)
AggregationSummaryData.fromJson(Map<String, dynamic>) factory constructor parses all fields from the RPC response payload
AggregationSummaryData.toJson() method serialises back to Map<String, dynamic> (round-trip fidelity)
fromJson handles null or missing optional fields gracefully — no unhandled null pointer exceptions
All nested classes (GeographicRegionSummary, UnmappedActivitySummary, DeduplicationAnomalySummary) implement Equatable
Unit tests verify: fromJson round-trip, missing field handling, Equatable equality

Technical Requirements

frameworks
flutter
equatable
apis
generate_bufdir_report RPC response schema
data models
AggregationSummaryData
GeographicRegionSummary
UnmappedActivitySummary
DeduplicationAnomalySummary
bufdir_category_mappings
participants
performance requirements
fromJson must complete in under 1ms for typical response sizes (fewer than 100 category entries)
All fields must use const constructors where possible to minimise allocation
security requirements
Model must contain only aggregate statistics — no participant names, personal identifiers, or addresses
Validation: deduplicatedParticipantCount must be asserted to be <= totalParticipantCount in the constructor (assert statement in debug builds)

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Place all model classes in lib/features/bufdir/aggregation/models/. Use a separate file per class for clarity, with a barrel export from models.dart. Align the fromJson field names exactly with the column names returned by the generate_bufdir_report RPC — coordinate with the backend task owner (task-003 / task-006) to confirm the exact JSON shape before finalising the parser. Use List.from(json['field'] ??

[]) for list fields to safely handle null or absent arrays. For Map parsing of activityHoursByCategory, cast via (json['activity_hours_by_category'] as Map).map((k, v) => MapEntry(k, (v as num).toDouble())) — this handles both int and double values from the JSON. If the project uses freezed, use @freezed with fromJson/toJson support to eliminate boilerplate — this is the preferred approach for consistency with other models in the codebase.

Testing Requirements

Unit tests using flutter_test. Test file: test/features/bufdir/aggregation/models/aggregation_summary_data_test.dart. Test cases: (1) fromJson with a complete valid payload — all fields populated correctly; (2) fromJson with missing optional fields — defaults to empty lists or zero counts without throwing; (3) toJson produces a map that fromJson can consume to produce an equal object (round-trip); (4) two AggregationSummaryData instances with identical fields are equal via Equatable; (5) deduplicatedParticipantCount > totalParticipantCount triggers assert in debug mode (use test with expectLater and throwsAssertionError); (6) activityHoursByCategory with multiple categories is parsed into the correct map. Achieve 100% line coverage on all model classes.

Component
Aggregation Summary Widget
ui medium
Epic Risks (2)
medium impact high prob scope

For NHF with 1,400 local chapters, rendering a geographic breakdown as a flat list in the AggregationSummaryWidget would produce an unscrollable wall of data that is unusable on a mobile screen. Coordinators need to verify geographic completeness before export, but the raw chapter list is too long to review.

Mitigation & Contingency

Mitigation: Design the geographic distribution section as a collapsible two-level hierarchy: regions are shown expanded by default with their aggregate count, individual chapters are collapsed under each region and expandable on tap. Show only non-zero regions by default with a 'show empty regions' toggle.

Contingency: If the hierarchical display is too complex to implement before the submission deadline, fall back to a region-only summary view with a total count of active chapters per region and a note that chapter-level detail is available in the full export file.

medium impact medium prob technical

The aggregation BLoC must handle a multi-stage async pipeline with partial success states, warning accumulation across stages, and retry logic for individual failed stages. If the state model is too simplistic (e.g., loading/success/error), warning states and partial results will be lost on retry, confusing coordinators who see warnings disappear and reappear.

Mitigation & Contingency

Mitigation: Model the BLoC state as a rich sealed class hierarchy: AggregationIdle, AggregationRunning(stage, completedStages, accumulatedWarnings), AggregationComplete(result, warnings), AggregationFailed(failedStage, accumulatedWarnings, error). Accumulated warnings persist across retry attempts so coordinators see the full warning history.

Contingency: If state management complexity causes repeated bugs near the deadline, simplify to a single AggregationResult value object that captures success, warnings, and error as nullable fields, and drive both UI components from that single object rather than a live BLoC stream.