critical priority medium complexity backend pending backend specialist Tier 1

Acceptance Criteria

OrganizationUnitType is a Dart enum with values: national, region, chapter; includes a fromString factory that throws ArgumentError on unknown values and a toJson() method returning the lowercase string value
OrganizationUnit is an immutable Dart class with fields: id (String), parentId (String?), name (String), unitType (OrganizationUnitType), createdAt (DateTime), updatedAt (DateTime), deletedAt (DateTime?); all non-nullable fields are required in the constructor
OrganizationUnit.fromJson(Map<String, dynamic>) correctly deserializes all fields including nullable deletedAt and the unitType enum
OrganizationUnit.toJson() returns a Map<String, dynamic> with snake_case keys matching Supabase column names (id, parent_id, name, unit_type, created_at, updated_at, deleted_at)
OrganizationUnit.copyWith() allows overriding any subset of fields and returns a new immutable instance
OrganizationUnit overrides == and hashCode based on the id field
OrganizationUnitTree is a class with fields: root (OrganizationUnit) and children (List<OrganizationUnitTree>); includes a recursive findById(String id) method returning OrganizationUnit?
OrganizationUnitTree.fromFlatList(List<OrganizationUnit> units) correctly builds a tree from a flat list using parent_id relationships
All model files are located in the correct domain layer directory following existing project conventions
Unit tests pass for all fromJson/toJson round-trips including edge cases (null deletedAt, all unit type values)

Technical Requirements

frameworks
Flutter (Dart)
Riverpod (for provider integration if needed at this layer)
data models
OrganizationUnit
OrganizationUnitType
OrganizationUnitTree
performance requirements
OrganizationUnitTree.fromFlatList must build a tree of 500 nodes in under 5ms (use a Map<String, OrganizationUnitTree> index by id for O(n) construction, not O(n²) nested loops)
security requirements
Do not expose deletedAt in any UI serialization path — treat it as an internal field
fromJson must not throw unhandled exceptions on unexpected JSON shapes — validate and throw domain-specific exceptions

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

Follow the existing project model conventions — inspect neighboring model files before writing to match constructor style (named parameters, required keyword), file naming, and directory placement. Use Dart's const constructor where all fields are final for compile-time immutability. For equality and hashCode, using only id is intentional and consistent with entity identity semantics. For OrganizationUnitTree.fromFlatList, build a Map keyed by id first, then iterate once to attach each node to its parent's children list — this is O(n) vs the naive O(n²) approach.

The root of the tree is the node with parentId == null. Handle the edge case where multiple roots exist (should not occur in valid data, but log a warning rather than throwing). DateTime parsing from Supabase returns ISO 8601 strings with timezone — use DateTime.parse() which handles this correctly. Do not use code generation (json_serializable, freezed) unless the rest of the project already uses it — hand-write serialization to match existing patterns.

Testing Requirements

Write unit tests using flutter_test. Test file location: test/models/organization_unit_test.dart. Scenarios: (1) fromJson round-trip with all fields populated. (2) fromJson with deletedAt = null.

(3) fromJson with an unknown unit_type string throws ArgumentError. (4) toJson produces correct snake_case keys. (5) copyWith produces a new instance with updated fields, original unchanged. (6) Two OrganizationUnit instances with the same id are == regardless of other field differences.

(7) OrganizationUnitTree.fromFlatList with 1 national + 3 regions + 9 chapters builds correct tree structure. (8) findById returns correct node for leaf, intermediate, and root. (9) findById returns null for non-existent id. Aim for 100% branch coverage on model logic.

Component
Organization Unit Repository
data high
Epic Risks (3)
high impact medium prob technical

Recursive CTE queries for large hierarchies (1,400+ nodes) may exceed Supabase query timeouts or produce unacceptably slow responses, degrading tree load time beyond the 1-second target.

Mitigation & Contingency

Mitigation: Implement Supabase RPC functions for subtree fetches rather than client-side recursive calls. Use materialized path or closure table as a supplemental index for depth-first traversal. Benchmark with realistic NHF data volumes during development.

Contingency: Fall back to a pre-computed flat unit list stored in the hierarchy cache with client-side tree reconstruction, trading freshness for speed. Add a background refresh job to keep the cache warm.

medium impact low prob technical

Concurrent writes from multiple admin sessions could cause cache staleness, leading to stale tree views and incorrect ancestor path computations that corrupt aggregation results.

Mitigation & Contingency

Mitigation: Use optimistic versioning on cache entries with a short TTL (5 minutes) as a safety net. Subscribe to Supabase Realtime on the organization_units table to push invalidation events to all connected clients.

Contingency: Provide a manual 'Refresh Hierarchy' action in the admin portal that forces a full cache bust, and display a staleness warning banner when the cache age exceeds the TTL.

high impact low prob security

Persisting the flat unit list to local storage may expose organization structure data if the device is compromised or the storage is not properly encrypted, violating data protection requirements.

Mitigation & Contingency

Mitigation: Use flutter_secure_storage (AES-256 backed by Keychain/Keystore) for the local unit list cache rather than SharedPreferences. Include only unit IDs, names, and types — no member PII.

Contingency: Disable local-storage persistence entirely and rely on in-memory cache only. Accept the trade-off of no offline hierarchy access for the security guarantee.