critical priority medium complexity infrastructure pending frontend specialist Tier 2

Acceptance Criteria

generateStatCardAnnouncement(value, label) returns a non-empty string in the format '<value> <label>' where value is the formatted numeric string and label is the human-readable descriptor
generateMilestoneAnnouncement(milestone, isEarned) returns 'Milestone earned: <description>' when isEarned is true and 'Milestone locked: <description>' when isEarned is false
generateActivityBreakdownAnnouncement(segments) returns a comma-separated string listing each segment as '<percentage>% <organisation-specific label>'
Organisation-specific terminology is sourced from the existing organisation labels system — no hard-coded strings in the generator
All generated strings pass WCAG 2.2 AA criterion 1.3.1 (Info and Relationships): they include enough context for a screen reader user to understand the widget without visual context
Strings do not contain HTML, markdown, or special characters that would be vocalised literally by VoiceOver or TalkBack
Numeric values in stat card strings use locale-aware formatting (e.g. thousand separators appropriate for the device locale)
Unit tests cover all three generator methods with at least three input variants each, including zero/empty edge cases

Technical Requirements

frameworks
Flutter
flutter_test
apis
intl (NumberFormat for locale-aware formatting)
Organisation labels system (dynamic terminology)
data models
MilestoneModel
ActivitySegmentModel
OrganisationLabels
performance requirements
String generation must be synchronous and return within < 1 ms; no async operations permitted inside announcement generators
security requirements
Announcement strings must not include personally identifiable information (names, contact details) — only aggregate statistics and milestone metadata

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Implement the three generators as pure methods on SummaryAccessibilityProvider (or a dedicated SummaryAnnouncementGenerator helper class invoked by the provider). Keep generators stateless — they receive all data as parameters and return strings. Use the intl package's NumberFormat.decimalPattern(locale) for numeric formatting so strings sound natural when read aloud (e.g. '1,234 activities' not '1234 activities').

Source all descriptive labels from the existing organisation labels system to ensure terminology matches what each organisation's users expect (NHF, Blindeforbundet, HLF all use different terminology for the same concepts). Do not concatenate raw enum names or internal IDs into announcement strings — always map through the label system. For activity type breakdowns, round percentages to whole numbers in the announcement string to keep them audible and concise.

Testing Requirements

Write unit tests in flutter_test for each of the three generator methods. For stat card: test integer value, decimal value, zero value, and very large value (> 1 000). For milestone: test earned state, locked state, and empty description edge case. For activity breakdown: test single segment, multiple segments, all-zero percentages, and segments whose labels come from different organisation label sets.

Verify that strings are non-empty for all valid inputs and that organisation-specific labels are resolved correctly by mocking the label provider.

Component
Summary Accessibility Provider
infrastructure medium
Epic Risks (2)
medium impact medium prob technical

Simultaneous count-up animations across multiple stat cards and chart draw-in animations on lower-end Android devices may cause frame drops below 60fps, degrading the premium Wrapped experience and making the feature feel unpolished.

Mitigation & Contingency

Mitigation: Stagger animation starts using AnimationController with staggered intervals rather than starting all animations simultaneously. Use RepaintBoundary around each animated widget to isolate rasterisation. Profile on a mid-range Android device (e.g., equivalent to Pixel 4a) during development, not just at QA.

Contingency: If frame rate targets cannot be met on low-end devices, implement a device-capability check at startup and substitute simpler fade-in animations for the count-up and chart draw-in on devices below a CPU performance threshold.

medium impact low prob integration

The activity-type-breakdown-widget must render organisation-specific activity type labels sourced from the terminology system. If the terminology provider is not yet integrated at the time this widget is built, the widget will display hardcoded system labels, which is a regression risk for multi-org support.

Mitigation & Contingency

Mitigation: Accept activity type labels as a typed parameter in the widget constructor rather than reading from the terminology provider directly inside the widget. The BLoC or repository layer resolves labels before passing them to the widget, maintaining clean separation and testability.

Contingency: If terminology resolution is unavailable at widget integration time, display internal activity type keys as a temporary fallback with a localised suffix '(label pending)' visible only in non-production builds so QA can identify unresolved labels.