high priority low complexity backend pending backend specialist Tier 1

Acceptance Criteria

`getCurrentHalfYearPeriod(DateTime ref)` returns correct H1 (Jan 1 – Jun 30) or H2 (Jul 1 – Dec 31) boundaries for any reference date
`getCurrentQuarterPeriod(DateTime ref)` returns correct Q1–Q4 boundaries for any reference date
`getPreviousPeriod(PeriodType, DateTime ref)` returns the same period type from the previous year (e.g., H1 2024 for a ref in H1 2025)
`isDateInPeriod(DateTime date, Period period)` returns true only when the date falls within [periodStart, periodEnd] inclusive
All boundary dates are returned as UTC midnight values to avoid DST ambiguity
Service is pure and deterministic — given the same inputs it always returns the same outputs
Service handles year-boundary edge cases: Dec 31 belongs to H2/Q4 of the current year, Jan 1 belongs to H1/Q1 of the current year
Service has no Supabase or Flutter dependencies — it is a pure Dart utility class

Technical Requirements

frameworks
Dart (pure, no Flutter dependency)
data models
Period
PeriodType (enum: halfYear, quarterly)
SummaryPeriod
performance requirements
All calculations are synchronous and complete in microseconds
security requirements
No sensitive data flows through this service — no security concerns

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Define a `Period` value object with `start`, `end` (UTC DateTime), and `type` (PeriodType enum). Define `PeriodType` as an enum with values `h1`, `h2`, `q1`, `q2`, `q3`, `q4`. Implement as a stateless `final class PeriodCalculatorService` with only static methods — no instantiation needed. Use `DateTime.utc(year, month, day)` for all boundary construction to avoid timezone issues.

For `getPreviousPeriod`, simply subtract 1 year from the reference date and re-derive the period — do not attempt to track 'previous' by cycling through enum values, as that won't handle year boundaries correctly. Export this class from a `period_calculator` library so it can be used by both the SummaryGenerationService and the ActivityAggregationRepository without circular imports.

Testing Requirements

Comprehensive unit tests with flutter_test covering: all 4 quarters and both half-years for a standard year; boundary dates (Jan 1, Mar 31, Apr 1, Jun 30, Jul 1, Sep 30, Oct 1, Dec 31); leap year handling (Feb 29 in Q1); year transition (Dec 31 2024 → Jan 1 2025); `getPreviousPeriod` for all period types; `isDateInPeriod` for dates before, within, and after a period. Target 100% branch coverage — this is pure logic with no side effects.

Component
Period Calculator Service
service low
Epic Risks (4)
high impact medium prob technical

Supabase pg_cron or Edge Function retries could trigger multiple concurrent generation runs for the same period and organisation, producing duplicate summaries and sending multiple push notifications to users — a serious UX regression.

Mitigation & Contingency

Mitigation: Implement a database-level run-lock using an INSERT … ON CONFLICT DO NOTHING pattern keyed on (organisation_id, period_type, period_start). Only the first successful insert proceeds; subsequent attempts read the existing lock and exit early. Test with concurrent invocations in a Deno test suite.

Contingency: If duplicate summaries are detected post-deployment, add a deduplication cleanup job that removes all but the most recent summary per (user_id, period_type, period_start) and sends a corrective push notification.

medium impact low prob integration

FCM and APNs have different payload structures and size limits. An oversized or malformed payload could cause silent notification drops on iOS or delivery failures on Android, meaning mentors never learn their summary is ready.

Mitigation & Contingency

Mitigation: Build the PushNotificationDispatcher with separate FCM and APNs payload constructors, enforce a 256-byte body limit on the preview text, and run integration tests against the Firebase Emulator and a test APNs sandbox.

Contingency: Fall back to a generic 'Your periodic summary is ready' message if personalised preview text construction fails, ensuring delivery even when the personalisation pipeline encounters an error.

medium impact high prob scope

Outlier thresholds that are too tight will flag most mentors as outliers (alert fatigue for coordinators), while thresholds that are too loose will miss genuinely underactive mentors — directly undermining HLF's follow-up goal.

Mitigation & Contingency

Mitigation: Implement thresholds as configurable per-organisation database settings rather than hardcoded constants. Provide sensible defaults (underactive < 2 sessions/period, overloaded > 20 sessions/period) and document the tuning process for coordinators in the admin portal.

Contingency: If coordinators report threshold miscalibration after launch, expose a threshold configuration UI in the coordinator admin screen and allow real-time threshold adjustment without requiring a code deployment.

low impact high prob scope

The app may not have 12 months of historical activity data for all organisations at launch, making year-over-year comparison impossible for most users and rendering the comparison widget empty, which could disappoint users expecting Wrapped-style insights.

Mitigation & Contingency

Mitigation: Design the generation service to gracefully handle missing prior-year data by setting the yoy_delta field to null rather than zero. The UI must treat null as 'no comparison available' with appropriate placeholder copy rather than showing a misleading 0% delta.

Contingency: If historical data import from legacy Excel/Word sources becomes feasible, add a one-time backfill Edge Function that populates prior-year activity records from imported spreadsheets. Until then, explicitly communicate the data-availability limitation in the first summary each user receives.