critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

ReportingPeriod is a Dart @immutable value class with startDate (DateTime, UTC), endDate (DateTime, UTC), type (PeriodType enum), and a stable id (String, derived from type+startDate)
PeriodType enum defines: monthly, quarterly, annual, custom — all values have a human-readable display label in Norwegian (nb) and English (en)
ReportingPeriod enforces startDate < endDate via assert in the constructor; constructing with equal or reversed dates throws AssertionError in debug and ArgumentError in release mode
ReportingPeriod implements == and hashCode based on all fields so value equality works correctly in collections
ReportingPeriod.contains(DateTime date) returns true if date falls within [startDate, endDate) (inclusive start, exclusive end) — consistent with Bufdir's closed-open interval convention
ReportingPeriod.overlaps(ReportingPeriod other) returns true iff the periods share at least one millisecond
PeriodBoundaryFilter interface defines filterByPeriod(List<T> items, ReportingPeriod period) returning a filtered list — generic type T must have a DateTime timestamp field expressed via a lambda accessor
All date arithmetic uses UTC throughout; no local timezone conversions inside the model layer
The model file exports only public API symbols; internal helpers are private
A unit test file co-located in test/ covers all public methods with at least 2 valid and 1 invalid input each

Technical Requirements

frameworks
flutter_test (for unit tests)
dart:core (DateTime, Duration)
data models
ReportingPeriod
PeriodType
PeriodBoundaryFilter<T>
performance requirements
ReportingPeriod construction and equality checks must complete in O(1)
No external I/O in the domain model — pure in-memory value objects
security requirements
No user-supplied date strings are parsed inside the model — callers must pass typed DateTime objects to prevent injection of malformed ISO strings

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Model this as a pure Dart value object in a dedicated lib/domain/reporting_period/ directory to keep domain logic separate from infrastructure (Supabase) and UI layers. Use Dart's @immutable annotation and make all fields final. Derive the id as '${type.name}-${startDate.toIso8601String()}' to produce a deterministic, human-readable identifier useful for Supabase row keys. For PeriodBoundaryFilter, prefer a functional interface (typedef + extension method) over an abstract class to keep it lightweight and composable.

Norwegian grant reporting cycles follow calendar boundaries: monthly = first to last day of month, quarterly = Jan-Mar/Apr-Jun/Jul-Sep/Oct-Dec, annual = Jan 1 to Dec 31 — bake these as named constructors (ReportingPeriod.monthly(year, month), etc.) to prevent off-by-one errors downstream.

Testing Requirements

Pure Dart unit tests (no Flutter widgets, no Supabase). One test file per public class. Test cases must cover: valid construction for each PeriodType, invalid construction (reversed dates, equal dates), equality and hash consistency, contains() with date on boundary (startDate inclusive, endDate exclusive), contains() with date outside period, overlaps() for adjacent/overlapping/non-overlapping/identical periods, and PeriodBoundaryFilter with an empty list, a list where all items fall inside the period, and a list where some items fall outside. Run with `flutter test` in the package root.

Component
Reporting Period Service
service medium
Epic Risks (4)
high impact high prob integration

NHF members can belong to up to 5 local chapters. When a participant has activities registered under different chapter IDs within the same reporting period, deduplication requires a reliable cross-chapter identity key. If national IDs are absent for some members (a known data quality issue in NHF's systems), the deduplication service may fail to identify duplicates, resulting in inflated counts submitted to Bufdir.

Mitigation & Contingency

Mitigation: Implement a multi-attribute identity matching strategy: primary match on national_id, fallback to (full_name + birth_year + municipality) composite key. Expose a low-confidence match list in DeduplicationAnomalyReport that coordinators can review and manually resolve before submission.

Contingency: If identity data quality is too poor for reliable automated deduplication for specific organisations, add an organisation-level config flag that disables cross-chapter deduplication for that org and requires coordinators to manually review the anomaly report before submitting.

high impact medium prob integration

The geographic distribution algorithm must resolve NHF's 1,400 local chapter hierarchy to regional aggregates. If the organizational unit hierarchy in the database is incomplete (missing parent-child relationships for some chapters), the geographic service will silently drop activities from unmapped chapters, producing an understated geographic breakdown.

Mitigation & Contingency

Mitigation: Add a hierarchy completeness validation step in GeographicDistributionService that counts activities without a resolvable region assignment and surfaces them as an 'unmapped_activities' field in the distribution result. Block export if unmapped_activities > 0.

Contingency: Provide a 'national' fallback bucket for activities from chapters with no region assignment, clearly labelled in the preview screen so coordinators are alerted to fix the org hierarchy data before re-running aggregation.

high impact low prob technical

BufdirAggregationService orchestrates four dependent services. If one service (e.g., GeographicDistributionService) throws mid-pipeline, the partially assembled metrics payload may be silently cached or returned as if complete, resulting in a Bufdir submission missing the geographic breakdown section.

Mitigation & Contingency

Mitigation: Implement the orchestrator as a transactional pipeline using Dart's Result type pattern: each stage returns Either<AggregationError, PartialResult>, and the orchestrator only proceeds if all stages succeed. The final payload is only assembled and persisted when all stages return success.

Contingency: If a partial failure state reaches the UI, the AggregationProgressIndicator must display a specific stage failure message with a retry option that re-runs only the failed stage rather than the full pipeline.

medium impact medium prob scope

Internal activity types that have no corresponding Bufdir category in the mapping configuration will cause the aggregation to silently exclude those activities from the final counts. Coordinators may not notice the omission until Bufdir queries why submission totals are lower than expected.

Mitigation & Contingency

Mitigation: BufdirAggregationService must produce an unmapped_activity_types list as part of its output. If any internal activity types are unmapped, display a blocking warning in the AggregationSummaryWidget listing the unmapped types before allowing the coordinator to proceed to export.

Contingency: Allow coordinators to temporarily assign unmapped activity types to a Bufdir 'other' catch-all category as an emergency workaround, with an audit flag indicating manual override was applied for that submission.