Period boundary calculator core logic
epic-periodic-summaries-foundation-task-003 — Implement the PeriodCalculatorService in Dart that computes the exact start and end timestamps for weekly, monthly, and quarterly periods given a reference date and a Norwegian locale. Handle fiscal-year edge cases, week numbering (ISO 8601), and month/quarter boundaries. Expose methods: getCurrentPeriod(type), getPreviousPeriod(type), getPeriodsInRange(start, end, type), and isDateInPeriod(date, period).
Acceptance Criteria
Technical Requirements
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
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.
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.
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.
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.