AAttendrix Docsv1.0
Domain Systems
ExplanationImplemented

Attendance Engine & Inverted Persistence

Inverted attendance ledger, default-to-present failure modes, optimistic UI rollbacks, and foreign key cascade semantics.

Attendance Engine & Inverted Persistence

The Attendrix Attendance Engine tracks academic lecture attendance for thousands of students while maintaining sub-second client responsiveness and eliminating over 80% of database write overhead.

ATTENDANCE INVERSION INVARIANT
INV-ATT-01

Absences are explicitly recorded in public.absences. Presence is derived dynamically when no absence record exists for an active class slot. Absences cannot be logged for canceled or holiday-flagged occurrences.


1. The Inverted Persistence Model

In standard university enterprise systems, an attendance record is created every time a student attends a lecture. Because Indian university regulations mandate 75–80% attendance, approximately 80–90% of all student-lecture pairs are positive.

Attendrix inverts this assumption:

State Definition:
- Row exists in public.absences for (user_id, class_id)  --> Student is ABSENT
- No row exists in public.absences for (user_id, class_id) --> Student is PRESENT

Architectural Advantages

  1. 80%+ Write Reduction: The database writes records only when a student skips a class.
  2. Instant Course Initialization: Enrolling in a course requires zero row inserts in the attendance table; the student immediately starts with 100% attendance.
  3. Sparse Index Density: Indexes on public.absences (user_id, class_id) remain exceptionally compact, fitting entirely in RAM buffer pools.

2. Concurrency Hazards: "Default to Present" Failure Modes

While the inverted model saves millions of database rows, it introduces an asymmetric failure hazard:

The Default-to-Present Concurrency Hazard

If a student marks themselves absent in the mobile app, but the network request drops or fails before reaching PostgreSQL, the database defaults to Present.

In an academic context where attendance fraud or unverified records carry disciplinary risks, an unhandled failure silently awards unearned presence.

Mitigation Architecture in Mobile Client

The Attendrix client implements a multi-layer mitigation strategy in mark_absent.dart and sync_app_data.dart:

Absence Mutation & Offline Resilience Lifecycle
Sequence / Flow
sequenceDiagram
    autonumber
    participant UI as Flutter Student UI
    participant AppState as FFAppState (Local Cache)
    participant SecStore as FlutterSecureStorage (Pending Queue)
    participant PostgREST as Supabase RPC (mark_absent)
    participant DB as PostgreSQL public.absences

    UI->>AppState: Student taps "Mark Absent"
    AppState->>AppState: Optimistic UI update: is_absent = true
    AppState->>SecStore: Enqueue pending absence mutation {class_id, timestamp}
    
    AppState->>PostgREST: Dispatch mark_absent(p_class_id, p_source, p_reason)
    alt RPC Succeeded
        PostgREST->>DB: INSERT INTO public.absences (user_id, class_id)
        DB-->>PostgREST: Returns updated attendance stats
        PostgREST-->>AppState: Success Response
        AppState->>SecStore: Dequeue pending mutation
        AppState->>AppState: Confirm local attendance percentage
    else RPC Network Timeout / Offline
        AppState->>UI: Show Toast: "Absence saved offline. Will sync when online."
        Note over AppState,SecStore: Pending queue persists across app restarts
    else RPC Rejected (e.g. CLASS_CANCELLED)
        PostgREST-->>AppState: Error: CLASS_CANCELLED
        AppState->>SecStore: Dequeue invalid mutation
        AppState->>AppState: Rollback is_absent = false
        AppState->>UI: Show Alert: "Cannot mark absence: Class was cancelled."
    end
  1. Pending Mutation Queue: Mutations are immediately written to encrypted local flash storage (FlutterSecureStorage) before the HTTP request is initiated.
  2. Reconciliation Loop: Upon reconnecting to WiFi or cellular data, syncAppData replays all pending mutations before fetching fresh server states.
  3. Explicit Server Error Rollback: If the server rejects an absence (e.g., class cancelled by faculty, semester ended), the client automatically rolls back the optimistic UI state and notifies the student.

3. Class Reconciliation & Foreign Key Cascade Behavior

A critical architectural pitfall in university scheduling is handling class cancellations, room changes, and timetable regeneration.

Why Soft Deletes Are Mandatory

In the Attendrix database:

-- In public.classes:
is_cancelled boolean NOT NULL DEFAULT false,
cancelled_at timestamptz,
cancellation_reason text
Catastrophic Data Loss Hazard: Physical Deletions

The foreign key on public.absences.class_id references public.classes.class_id.

If timetable regeneration were implemented via physical SQL DELETE FROM classes WHERE ..., a cascade (ON DELETE CASCADE) would permanently wipe all historical student absences logged for those classes!

Even without cascade, a RESTRICT rule would cause the administrative schedule sync to crash.

The Soft-Delete Guarantee

To preserve historical attendance integrity:

  1. Never Physically Delete Occurrences: Classes are marked with is_cancelled = true.
  2. Exclusion from Calculation: The calculate_user_attendance_v2 RPC explicitly filters out cancelled classes:
SELECT count(*) 
FROM classes c
WHERE c.semester_id = p_semester_id
  AND c.is_cancelled = false
  AND c.scheduled_start <= now();
  1. Absence Ledger Invariance: Absences associated with cancelled classes are ignored during percentage calculation but preserved in the audit history.

4. Margin-of-Safety & Projection Mathematics

Students track attendance primarily to avoid falling below institutional mandatory thresholds (typically 80%80\% or 75%75\%). The attendance projection algorithm computes live and projected compliance:

P=Cattended+Cfuture_presentCheld+Cfuture_total×100P = \frac{C_{\text{attended}} + C_{\text{future\_present}}}{C_{\text{held}} + C_{\text{future\_total}}} \times 100

where Cheld=Cscheduled_pastCcancelledCholidayC_{\text{held}} = C_{\text{scheduled\_past}} - C_{\text{cancelled}} - C_{\text{holiday}} and Cattended=CheldAloggedC_{\text{attended}} = C_{\text{held}} - A_{\text{logged}}.

The custom action calculateProjectedAttendance calculates the bunk margin (BB) and recovery deficit (RR):

Case 1: Attendance Above Threshold (PTP \ge T)

The student can safely miss BB upcoming lectures before dropping below threshold TT:

B=Cattended(Cheld×T)TB = \left\lfloor \frac{C_{\text{attended}} - (C_{\text{held}} \times T)}{T} \right\rfloor

Case 2: Attendance Below Threshold (P<TP < T)

The student must attend RR consecutive lectures without absence to recover to threshold TT:

R=(Cheld×T)Cattended1TR = \left\lceil \frac{(C_{\text{held}} \times T) - C_{\text{attended}}}{1 - T} \right\rceil

Was this page helpful?

Your feedback directly guides the engineering documentation roadmap.

On this page