critical priority medium complexity backend pending backend specialist Tier 2

Acceptance Criteria

The generated CSV contains exactly the columns required by Bufdir's reporting schema in the specified column order β€” no extra columns, no missing columns
All column headers are in the exact string format Bufdir requires (Norwegian or English per Bufdir spec); values are correctly encoded per Bufdir's data type rules
Activities are filtered to the requesting admin's org subtree only β€” an admin from org node X cannot export activities from sibling node Y
Date range filter correctly uses activity start_date (inclusive on both ends); activities with null start_date are excluded from export
The CSV uses UTF-8 BOM encoding so it opens correctly in Norwegian Excel installations without character corruption
Numeric fields (hours, participant count) use period as decimal separator per Bufdir spec, not comma
Empty optional fields are rendered as empty strings, not 'null' or 'undefined'
The export query is paginated internally (max 1000 rows per DB batch) so exports of 10 000+ activities do not cause memory exhaustion
A completed export is persisted to the export_history table with: admin_id, org_node_id, date_range, row_count, file_size_bytes, export_type='csv', created_at
If zero activities match the filter criteria, a valid CSV with only the header row is returned rather than an error
The `bufdir-format-serializer` is the only code path that knows the column mapping β€” AdminExportService only passes domain objects to it; no column names are hardcoded in the service

Technical Requirements

frameworks
Flutter
Dart
Riverpod
apis
Supabase Postgres (activities table, org_nodes, RLS)
OrgHierarchyService.getSubtreeIds()
Bufdir format serializer (bufdir-format-serializer Dart package or internal utility)
data models
Activity
OrgNode
UserProfile
ExportHistory
BufdirReportRow
performance requirements
CSV generation for 10 000 activity rows completes in under 30 seconds
Memory usage does not exceed 100MB during export β€” use streaming/chunked CSV writing
Database query uses indexed columns (org_node_id, start_date) to avoid full table scans
security requirements
Export endpoint enforces admin authentication and org-scope validation before any data query
Generated CSV is never written to a publicly accessible storage path β€” use signed URLs with 15-minute expiry
Activity data in the CSV must not include PII fields not required by Bufdir schema (e.g., internal user IDs should be replaced with anonymized identifiers if Bufdir does not require them)
Export history records who exported what and when for GDPR accountability

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Define a `BufdirReportRow` data class that mirrors Bufdir's column structure exactly β€” this is the boundary between domain model and export format. The serializer maps `Activity β†’ BufdirReportRow β†’ CSV row`. Keeping this mapping in one class makes it easy to update when Bufdir changes their schema. For streaming CSV generation, build the CSV line-by-line using a `StringBuffer` per batch, then flush to a `List` (bytes) β€” avoid building the entire CSV string in memory at once.

Write the BOM (`ο»Ώ`) as the first bytes before any CSV content. Store the Bufdir column order as a `const List` at the top of the serializer file so it's immediately visible to reviewers. When Bufdir's required columns include computed values (e.g., total hours = sum of sub-activities), compute them in a SQL aggregate query rather than in Dart to avoid loading unnecessary rows. Log the export parameters (org node, date range, row count) at INFO level for operational visibility.

Testing Requirements

Unit tests for bufdir-format-serializer: provide a list of known Activity objects and assert the exact CSV string output matches Bufdir's expected format byte-for-byte, including BOM, line endings (CRLF per CSV standard), and special character escaping (commas in text fields wrapped in double-quotes). Unit tests for AdminExportService: mock Supabase and OrgHierarchyService; assert that org-scope filter is applied, date range filter is applied, and export_history record is written with correct metadata. Edge case tests: zero results returns header-only CSV; activities with Norwegian special characters (æøΓ₯) are correctly encoded in UTF-8 BOM. Integration test: run against Supabase local with seeded activity data; download the CSV and open in a CSV parser, asserting row count and column values.

Performance test: seed 15 000 activities and assert export completes under 30 seconds without OOM.

Component
Admin Export Service
service medium
Epic Risks (4)
medium impact high prob technical

OrgHierarchyNavigator rendering NHF's full 1,400-chapter tree in a single widget may cause Flutter frame-rate drops below 60 fps on mid-range devices, making the navigator unusable for NHF national admins.

Mitigation & Contingency

Mitigation: Implement lazy expansion: only load immediate children on node expand rather than the full tree upfront. Use virtual scrolling for long sibling lists. Test with a synthetic 1,400-node dataset on a low-end Android device during development.

Contingency: If lazy expansion is insufficient, replace the tree widget with a paginated drill-down navigator (select level β†’ select child) that avoids rendering more than 50 nodes at a time.

medium impact medium prob dependency

Bufdir may update their required export column structure or file format during or after development. If the AdminExportService hardcodes the current Bufdir schema, any format change requires a code release rather than a config update.

Mitigation & Contingency

Mitigation: Drive the Bufdir column mapping from a configuration repository rather than hardcoded constants. Abstract column definitions into a named schema config so that format changes require only a config update and re-deployment without service logic changes.

Contingency: If Bufdir format changes post-launch, release a config update within one sprint. If the change is structural (new required sections), scope a targeted service update and communicate timeline to partner organisations.

high impact medium prob integration

Role transition side-effects in UserManagementService (e.g., certification expiry removing mentor from chapter listing, pause triggering coordinator notification) may interact with external services like HLF's website sync. Incomplete side-effect handling could leave the system in an inconsistent state.

Mitigation & Contingency

Mitigation: Model side-effects as explicit domain events published after the primary state change is persisted. Implement event handlers as idempotent operations so re-processing is safe. Write integration tests that assert all side-effects fire correctly for each role transition type.

Contingency: If a side-effect fails after the primary change is persisted, log the failure with full context and trigger a manual reconciliation alert to the on-call team. Provide an admin-accessible re-trigger action for failed side-effects.

medium impact medium prob scope

If AdminStatisticsService cache TTL is set too long, org_admin may see significantly stale KPI values (e.g., a mentor newly paused an hour ago still appears as active), undermining trust in the dashboard.

Mitigation & Contingency

Mitigation: Default cache TTL to 5 minutes with a manual refresh action on the dashboard. Implement cache invalidation triggered by UserManagementService write operations that affect counted entities.

Contingency: If staleness causes org admin complaints post-launch, reduce TTL to 60 seconds and introduce a real-time Supabase subscription for high-impact counters (paused mentors, expiring certifications).