high priority low complexity backend pending backend specialist Tier 2

Acceptance Criteria

Every `evaluate()` call emits a structured log entry at DEBUG level containing: assignment_id (string), days_since_contact (int or 'never'), remind_threshold (int), escalate_threshold (int), result_variant (string: 'none' | 'remind' | 'escalate')
The no-contact-ever edge case emits an additional INFO-level log entry with a distinct `event` key (e.g., `event: 'reminder_no_contact_ever'`) for easy filtering
Log entries use a consistent key schema across the reminders module (snake_case keys)
No PII (user names, addresses, contact content) appears in any log entry — only opaque IDs
Logging does not alter control flow; exceptions from the logging layer are caught and suppressed with a fallback warning
Logs are emitted via the project's existing logger abstraction (not direct `print` or `debugPrint`)
Log level is configurable (DEBUG in dev, INFO in production) without code changes

Technical Requirements

frameworks
Flutter
Dart (logging package or project logger abstraction)
data models
ReminderEvaluationResult
performance requirements
Log serialization must not block the async evaluation — use synchronous string interpolation only
No log entry should exceed 512 bytes to remain compatible with standard log aggregation pipelines
security requirements
Strictly no PII in logs — assignment_id is acceptable as an opaque identifier only
Log output must be disabled or redacted in release builds unless explicitly opted-in via build config
Audit log entries (escalate results) must include a timestamp for forensic traceability

Execution Context

Execution Tier
Tier 2

Tier 2 - 518 tasks

Can start after Tier 1 completes

Implementation Notes

Use the project's existing logger abstraction (check `lib/core/logging/` or equivalent). If no abstraction exists, wrap the Dart `logging` package in a thin `AppLogger` class so log output can be redirected in tests. Define a private `_logEvaluation()` helper inside `ReminderEvaluationServiceImpl` that accepts the evaluation context as named parameters and formats the log map — this keeps the main `evaluate()` method readable. For the no-contact-ever case, emit at INFO (not DEBUG) because it represents an operationally significant state that coordinators may want to monitor.

Never log `OrgReminderThresholds` values in production builds as they reveal internal org configuration.

Testing Requirements

Unit tests should verify that: (1) a DEBUG log entry is emitted for each evaluate() call, (2) the no-contact-ever path emits the dedicated INFO event, (3) no PII fields appear in log output. Use a mock/fake logger that captures log entries as a list for assertion. Verify log entries are NOT emitted when the logger is set to WARN level.

Component
Reminder Evaluation Service
service medium
Epic Risks (3)
medium impact high prob scope

The idempotency window (how long after a reminder is sent before another can be sent for the same assignment) is not explicitly specified. An incorrect window — too short, duplicate reminders appear; too long, a resolved and re-opened situation is not re-notified. This ambiguity could result in user-visible bugs post-launch.

Mitigation & Contingency

Mitigation: Before implementation, define the idempotency window explicitly with stakeholders: a reminder is suppressed if a same-type notification record exists with sent_at within the last (reminder_days - 1) days. Document this rule as a named constant in the service with a comment referencing the decision.

Contingency: If the window is wrong in production, it is a single constant change with a hotfix deployment. The notification_log table allows re-processing without data migration.

high impact medium prob technical

For organisations with thousands of open assignments (e.g., NHF with 1,400 chapters), the daily scheduler query over all open assignments could time out or consume excessive Supabase compute units, especially if the contact tracking query lacks proper indexing.

Mitigation & Contingency

Mitigation: Add a composite index on assignments(status, last_contact_date) before running performance tests. Use cursor-based pagination in the scheduler (query 500 rows at a time). Run a load test with 10,000 synthetic assignments as described in the feature documentation before merging.

Contingency: If the query is too slow for synchronous execution, move the evaluation to the Edge Function (cron trigger epic) and use Supabase's built-in parallelism. The service interface does not change, only the execution context.

medium impact medium prob integration

If the push notification service fails (FCM outage, invalid device token) during dispatch, the in-app notification may already be persisted but the push is silently lost. Inconsistent state makes it impossible to report accurate delivery status.

Mitigation & Contingency

Mitigation: Implement push dispatch and in-app persistence as separate operations with independent error handling. Record delivery_status as 'pending', 'delivered', or 'failed' on the notification_log row. Retry failed push deliveries up to 3 times with exponential backoff.

Contingency: If FCM is consistently unavailable, the in-app notification is still visible to the user, providing a degraded but functional fallback. Alert on consecutive push failures via the cron trigger's error logging.