high priority medium complexity database pending database specialist Tier 1

Acceptance Criteria

A `post_session_reports` table exists in Supabase PostgreSQL with columns: id (uuid PK), org_id (uuid NOT NULL FK), peer_mentor_id (uuid NOT NULL FK users), assignment_id (uuid FK assignments), schema_version (integer NOT NULL), field_values (jsonb NOT NULL default '{}'), status (enum: draft/submitted/archived), submitted_at (timestamptz nullable), created_at (timestamptz), updated_at (timestamptz)
RLS: peer mentor can INSERT drafts and SELECT/UPDATE their own reports where status = 'draft'; cannot update submitted reports
RLS: coordinator can SELECT all reports in their org; cannot mutate
RLS: org admin can perform all CRUD operations within their org_id
saveDraft(PostSessionReport) upserts the record with status='draft', updating field_values and updated_at
submit(String reportId) transitions status from 'draft' to 'submitted' and sets submitted_at to now(); throws an exception if status is already 'submitted'
fetchById(String id) returns the full report including related way_forward_items (joined query or two-step fetch)
field_values JSONB column stores a Map<String, dynamic> validated against the org's schema version at save time — mismatched schema versions throw a SchemaVersionMismatchException
Attempting to submit a report with required fields missing (per schema) throws a ValidationException listing missing field keys
Database migration is idempotent and includes foreign key constraints with ON DELETE CASCADE from assignments to post_session_reports

Technical Requirements

frameworks
Flutter
Supabase Dart SDK
freezed
flutter_test
Riverpod
apis
Supabase PostgreSQL 15
Supabase Auth
data models
activity
assignment
contact
performance requirements
saveDraft() must complete in under 1 second on a standard LTE connection — use upsert (not insert + update) to minimise round trips
fetchById() including way_forward_items join must return in under 800 ms
JSONB field_values column must have a GIN index to support fast schema-version filtering queries
security requirements
field_values may contain health-related free text (e.g., Blindeforbundet's helsetilstand field) — classified as sensitive personal data under GDPR; ensure RLS boundary is strict
schema_version must be validated server-side via Edge Function on submit to prevent client-side version spoofing
Service role key never used in mobile client — all operations use user JWT with RLS
submitted reports are immutable — enforce via RLS UPDATE policy that blocks changes when status = 'submitted'

Execution Context

Execution Tier
Tier 1

Tier 1 - 540 tasks

Can start after Tier 0 completes

Implementation Notes

field_values is a free-form Map — use jsonb in Postgres and Map in Dart. Serialise with json.encode() before sending to Supabase and json.decode() on retrieval. The schema_version field must match the version in the cached ReportSchemaCache (task-002) — validate this in the repository before calling Supabase to give fast local feedback. For the submit() transition, use a Supabase RPC (stored procedure) rather than a direct UPDATE to enforce the status machine server-side: `CREATE OR REPLACE FUNCTION submit_post_session_report(report_id uuid)` that validates current status and sets submitted_at atomically.

This prevents race conditions where two clients submit the same draft simultaneously. The Blindeforbundet formalisert rapportstruktur (helsetilstand, kursinteresse, hjelpemiddelsituasjon, veien videre) should be representable as keys within field_values — no separate columns needed, keeping the schema flexible across orgs. Document the expected field_value key conventions in a code comment.

Testing Requirements

Unit tests with mocked SupabaseClient: saveDraft() calls upsert with correct payload; submit() sends correct status transition; fetchById() deserialises nested way_forward_items; SchemaVersionMismatchException thrown on version conflict; ValidationException lists correct missing fields. Integration tests (local Supabase emulator): verify RLS — peer mentor cannot read another mentor's report; coordinator can read all; submitted report cannot be updated by mentor. Verify that submitted_at is set server-side (not by client). Target 100% method coverage on repository class.

Include a test for concurrent saveDraft() calls (optimistic locking or last-write-wins — document the chosen strategy).

Component
Post-Session Report Repository
data medium
Epic Risks (3)
high impact medium prob technical

Dynamically rendered form fields built from runtime JSON schema are significantly harder to make accessible than statically declared widgets — Flutter's Semantics tree must be correct for every possible field type and every validation state. Failures here block the entire feature for Blindeforbundet's visually impaired peer mentors.

Mitigation & Contingency

Mitigation: Define WCAG 2.2 AA semantics requirements for each field type before implementation and write widget tests using Flutter's SemanticsController for every type. Include a real-device VoiceOver test session in the acceptance gate for this epic before marking it done.

Contingency: If dynamic semantics prove too difficult to get right generically, implement field-type-specific Semantics wrappers (one per supported field type) instead of a single generic renderer, accepting slightly more code duplication in exchange for reliable accessibility.

high impact medium prob technical

The report-form-orchestrator must manage a complex state machine — schema loading, draft persistence, per-field validation, submission retries, and error recovery — across multiple async operations. Incorrect state transitions could result in lost user data, double submissions, or UI freezes.

Mitigation & Contingency

Mitigation: Define all Bloc states and events explicitly as sealed classes before writing any logic. Use a state machine diagram reviewed by the team before implementation. Write exhaustive Bloc unit tests covering every state transition, including concurrent events and network interruption mid-submission.

Contingency: If Bloc complexity becomes unmanageable, extract draft persistence into a separate DraftManagerCubit and keep report-form-orchestrator focused solely on the submit workflow. The additional granularity makes each component independently testable.

medium impact low prob scope

Organisations may require field types beyond the five currently specified (text, multiline, checkbox group, radio, date). If a new type is discovered during pilot testing, the dynamic-field-renderer must be extended, potentially requiring changes across multiple layers.

Mitigation & Contingency

Mitigation: Design dynamic-field-renderer as a registry of field-type renderers with a clear extension point. Document the pattern for adding a new field type so that it can be done in one file without touching existing renderers.

Contingency: If an unhandled field type is encountered at runtime, dynamic-field-renderer renders a labelled plain-text fallback widget and logs a warning so the missing type is surfaced in monitoring, preventing a crash while making the gap visible.