critical priority low complexity backend pending backend specialist Tier 0

Acceptance Criteria

A `PeriodCalculatorService` class exists in `lib/services/period_calculator_service.dart` with no external package dependencies beyond Dart core
`getCurrentPeriod(PeriodType type)` returns a `Period` object with `start` and `end` as `DateTime` in UTC, covering the period that contains `DateTime.now()` in the Norwegian timezone (Europe/Oslo)
`getPreviousPeriod(PeriodType type)` returns the period immediately before `getCurrentPeriod` with no gap or overlap
`getPeriodsInRange(DateTime start, DateTime end, PeriodType type)` returns an ordered list of non-overlapping `Period` objects covering the full range; partial periods at boundaries are included
`isDateInPeriod(DateTime date, Period period)` returns true iff `date >= period.start && date < period.end` (exclusive end)
Weekly periods use ISO 8601: week starts on Monday, week 1 is the week containing the first Thursday of the year
Monthly periods: start = first millisecond of the first day of the month, end = first millisecond of the first day of the next month (exclusive)
Quarterly periods: Q1 = Jan–Mar, Q2 = Apr–Jun, Q3 = Jul–Sep, Q4 = Oct–Dec aligned to calendar year (no fiscal-year offset unless configured)
All returned `DateTime` values are UTC — the service accepts an optional `referenceDate` parameter (defaults to `DateTime.now()`) to support deterministic testing
A `PeriodType` enum exists with values `weekly`, `monthly`, `quarterly`
A `Period` value object exists with `start`, `end`, `type`, and a `label` string (e.g., 'Week 12 2025', 'March 2025', 'Q1 2025')

Technical Requirements

frameworks
Dart core
Riverpod (provider registration)
flutter_test (unit testing)
data models
Period (value object)
PeriodType (enum)
performance requirements
All period calculation methods must complete in <1ms on a mid-range device — pure arithmetic, no I/O
`getPeriodsInRange` must handle a 5-year range without performance degradation
security requirements
Service must be pure/stateless — no mutable state that could cause race conditions in async contexts
No external HTTP calls or filesystem access — pure computation

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use the `intl` package (already likely in the Flutter project) only for label formatting — do NOT use it for date arithmetic to avoid locale-dependency bugs. Implement period arithmetic using pure Dart `DateTime` UTC math: `DateTime.utc(year, month, 1)` for month boundaries is reliable and DST-safe. For ISO week numbers, implement the algorithm from ISO 8601 directly (week 1 = week containing January 4th) rather than relying on platform locale. Store all internal state as UTC `DateTime`; convert to local time only at the display layer.

Register as a Riverpod `Provider` (not auto-dispose — it is stateless and cheap to keep alive). Consider making `referenceDate` an injected dependency (via constructor default = `DateTime.now()`) to make the service fully testable without `mockito`.

Testing Requirements

Unit tests in `test/services/period_calculator_service_test.dart` using `flutter_test`. Required test cases: (1) `getCurrentPeriod(weekly)` for a known reference date returns the correct ISO week start/end, (2) week boundary crossing at year-end (e.g., 2024-12-30 is in ISO week 1 of 2025), (3) `getCurrentPeriod(monthly)` on the last day of a month returns the correct month, (4) `getCurrentPeriod(quarterly)` for each of the 12 months maps to the correct quarter, (5) `getPeriodsInRange` spanning a year-end returns contiguous non-overlapping periods, (6) `isDateInPeriod` is false for a date exactly equal to `period.end` (exclusive boundary), (7) `getPreviousPeriod` adjacent to `getCurrentPeriod` has no gap. Target 100% branch coverage on the service class.

Component
Period Calculator Service
service low
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for aggregation views are more complex than single-table policies. A misconfigured policy could silently allow a coordinator in one organisation to see data from another, causing a data breach and breaking trust with participating organisations.

Mitigation & Contingency

Mitigation: Write automated RLS integration tests that create two separate organisations with distinct data, then assert that queries authenticated as org-A users return only org-A rows. Run these tests in CI on every PR touching the database layer.

Contingency: If an RLS bypass is discovered post-deployment, immediately disable the periodic summaries feature flag, revoke affected sessions, audit access logs, notify affected organisations, and patch the policy before re-enabling.

medium impact medium prob technical

Activity records may span multiple sessions types, proxy registrations, and coordinator bulk entries. Incorrect JOIN logic or missing filters in the aggregation query could double-count sessions or omit activity types, producing inaccurate summaries that erode user trust.

Mitigation & Contingency

Mitigation: Build a fixture dataset covering all activity registration paths (direct, proxy, bulk) and assert expected aggregated counts in integration tests before any UI consumes the repository.

Contingency: If inaccurate counts are reported post-launch, mark affected summaries as invalidated in the database and re-trigger generation once the query is corrected. Communicate transparently to affected users via an in-app banner.

low impact low prob integration

The local cache must be invalidated when a new summary arrives via push notification. If the push token is stale or the FCM/APNs delivery is delayed, the device may show an outdated summary for an extended period, confusing users who see different numbers online versus offline.

Mitigation & Contingency

Mitigation: Implement a TTL on cached summaries (max 48 hours) so stale data is auto-cleared even without a push notification. Also trigger cache refresh on app foreground if the current period's summary is older than 24 hours.

Contingency: Provide a manual pull-to-refresh on the summary card that bypasses the cache and fetches directly from Supabase when a network connection is available.