feat: widgets and ai chat
This commit is contained in:
@@ -82,36 +82,39 @@ When a course is created or updated with type `COMPLIANCE`, the following settin
|
||||
| `maxRetakeAttempts` | integer \| null | — | `null` | Max attempts per retake cycle. `null` = unlimited |
|
||||
| `passingScore` | integer | — | `80` | Minimum score (0–100) to count as a valid completion |
|
||||
|
||||
**CCT-3: Completion Validity Window**
|
||||
- When a learner completes a compliance course (meets passing score), their completion is **valid for `retakeIntervalMonths` months**
|
||||
**CCT-3: Due Date Ownership**
|
||||
- The initial `dueDate` for cycle 1 is set by the course admin/teacher when learners are assigned or enrolled into a compliance course
|
||||
- `dueDate` is the operational deadline used for reminder scheduling, grace period handling, extensions, and overdue status
|
||||
- For renewal cycles, the system automatically creates the next cycle with `dueDate = validUntil` from the previous compliant cycle
|
||||
- Admin actions such as extend or reset may override the current cycle's `dueDate`
|
||||
|
||||
**CCT-4: Completion Validity Window**
|
||||
- When a learner completes a compliance course, their completion is valid for `retakeIntervalMonths` months
|
||||
- `validUntil` = `completedAt` + `retakeIntervalMonths`
|
||||
- `validUntil` is the date that triggers renewal into the next cycle
|
||||
- The system tracks this validity window per learner per course in the `course_completion_record` table
|
||||
- A valid completion satisfies compliance; an expired completion does not
|
||||
|
||||
**CCT-4: Retake Lifecycle**
|
||||
**CCT-5: Retake Lifecycle**
|
||||
A learner's relationship to a compliance course follows this cycle:
|
||||
|
||||
```
|
||||
Enrolled → In Progress → Completed (Compliant)
|
||||
↓
|
||||
[retakeIntervalMonths pass]
|
||||
↓
|
||||
Expiring Soon ← reminder notifications sent
|
||||
↓
|
||||
[validity expires]
|
||||
↓
|
||||
In Grace Period ← if gracePeriodDays > 0
|
||||
↓
|
||||
[grace period ends]
|
||||
↓
|
||||
Non-Compliant ← escalation triggers
|
||||
↓
|
||||
[learner re-enrolled automatically]
|
||||
↓
|
||||
In Progress → Completed (Compliant) → ...
|
||||
Cycle 1 created → Not Started → In Progress → Completed (Compliant)
|
||||
↓
|
||||
[validUntil reaches current date]
|
||||
↓
|
||||
Cycle 2 created automatically as new active cycle
|
||||
↓
|
||||
Not Started → Expiring Soon → In Grace Period → Non-Compliant → Completed
|
||||
```
|
||||
|
||||
**CCT-5: Compliance Status per Learner**
|
||||
- There is exactly one active cycle per learner per compliance course
|
||||
- The active cycle owns the learner's current `status`, `dueDate`, reminder schedule, and overdue state
|
||||
- When a learner completes a cycle, that cycle becomes historical and remains visible in history
|
||||
- When `validUntil` is reached, the system creates the next cycle as the new active record with status `not_started`
|
||||
- The previous cycle is preserved for audit/history and is no longer used as the source of current reminder or overdue state
|
||||
|
||||
**CCT-6: Compliance Status per Learner**
|
||||
Each learner enrolled in a compliance course has one of these statuses (tracked on `course_completion_record`):
|
||||
|
||||
| Status | Condition |
|
||||
@@ -124,17 +127,23 @@ Each learner enrolled in a compliance course has one of these statuses (tracked
|
||||
| `non_compliant` | Past expiry + grace period with no valid completion |
|
||||
| `waived` | Exempted by admin (with reason and optional expiration) |
|
||||
|
||||
**CCT-6: Automatic Re-enrollment**
|
||||
**CCT-7: Automatic Re-enrollment**
|
||||
- When a learner's completion validity expires, the system automatically creates a new completion record with status `not_started` and a new `dueDate`
|
||||
- Previous completions are preserved in history (never deleted)
|
||||
- The learner sees the course reappear in their "Due" list
|
||||
- A background job (`compliance-expiry-checker`) runs daily to process expirations
|
||||
|
||||
**CCT-7: Auto-grading Behavior**
|
||||
- Like `SELF_PACED` courses, `COMPLIANCE` courses auto-grade submissions when all questions are auto-gradable
|
||||
- If the learner's score meets or exceeds `passingScore`, the completion is recorded as valid
|
||||
**CCT-8: Completion Rule**
|
||||
- Compliance completion is determined by the course completion rule, not by any single passing submission in isolation
|
||||
- For v1, a compliance cycle is complete when the learner satisfies the course's configured completion requirements and achieves `passingScore` on the designated final assessment for the cycle
|
||||
- Submission and lesson events may trigger a re-check, but a centralized compliance completion service decides whether the cycle becomes `compliant`
|
||||
- If below `passingScore` and `maxRetakeAttempts` is not reached, the learner may retry
|
||||
|
||||
**CCT-9: Auto-grading Behavior**
|
||||
- Like `SELF_PACED` courses, `COMPLIANCE` courses auto-grade submissions when all questions are auto-gradable
|
||||
- Auto-graded results feed into the centralized compliance completion evaluation
|
||||
- Manually graded assessments may also complete the cycle once grading is finished and the completion rule is satisfied
|
||||
|
||||
---
|
||||
|
||||
### 3.2 Notifications & Reminders
|
||||
@@ -161,6 +170,11 @@ Automatically remind learners before their compliance course deadlines and notif
|
||||
- When a new retake cycle is created (automatic re-enrollment), notify the learner
|
||||
- Email includes: course name, new due date, link to start
|
||||
|
||||
**NR-5: Notification Idempotency**
|
||||
- Reminder and alert delivery is persisted per cycle so scheduled jobs can be re-run safely
|
||||
- The system records reminder events such as `reminder_30d`, `reminder_7d`, `reminder_1d`, `renewal_created`, and `became_non_compliant`
|
||||
- A given reminder or alert may be sent at most once per cycle per channel unless an admin explicitly triggers a manual resend
|
||||
|
||||
> **Note**: The notification system PRD (`prd/notification-system`) covers the general delivery infrastructure (templates, channels, preferences). This section only defines the compliance-specific triggers.
|
||||
|
||||
---
|
||||
@@ -185,7 +199,10 @@ Certificates issued for compliance courses should carry an expiration date match
|
||||
- Certificate PDF shows: completion date, expiration date, cycle number, framework tag (if set)
|
||||
- Certificate list view in dashboard shows validity status badge (valid/expiring/expired)
|
||||
|
||||
> **Note**: This builds on the existing certificate system (`course.certificate` JSONB settings). No new certificate tables are needed — the `course_completion_record.cycle_number` links each certificate to its retake cycle.
|
||||
**CL-4: Certificate Persistence**
|
||||
- Compliance certificates are stored per cycle in a dedicated issuance table linked to `course_completion_record`
|
||||
- Each issuance stores `issuedAt`, `expiresAt`, `status`, and the associated `cycleNumber`
|
||||
- The existing course-level certificate settings remain the configuration source, but per-cycle issuance history is persisted separately so learners and admins can view all historical certificates
|
||||
|
||||
---
|
||||
|
||||
@@ -278,7 +295,9 @@ On the organization dashboard, show a summary card for compliance courses:
|
||||
- Course card turns orange: "In Grace Period" (if grace period > 0)
|
||||
- OR red: "Non-Compliant" (if no grace period)
|
||||
↓
|
||||
5. Student clicks course → Sees their previous completion history:
|
||||
5. Student clicks the same course → Sees the active cycle plus prior cycle history:
|
||||
- This is not a separate course and not a course-switcher in nav
|
||||
- The learner stays in the same course shell and sees a "Current Cycle" panel plus a "Past Cycles" history list
|
||||
"Cycle 1: Completed Jan 2025 (Score: 92%) — Expired"
|
||||
"Cycle 2: Due by Jan 2026 — [Start Retake]"
|
||||
↓
|
||||
@@ -371,6 +390,36 @@ CREATE INDEX idx_ccr_due_date ON course_completion_record(due_date);
|
||||
CREATE INDEX idx_ccr_valid_until ON course_completion_record(valid_until);
|
||||
```
|
||||
|
||||
### New Table: `course_completion_notification_event`
|
||||
```sql
|
||||
CREATE TABLE course_completion_notification_event (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
course_completion_record_id UUID NOT NULL REFERENCES course_completion_record(id) ON DELETE CASCADE,
|
||||
channel VARCHAR NOT NULL, -- 'email', 'in_app'
|
||||
event_type VARCHAR NOT NULL, -- 'reminder_30d', 'renewal_created', 'became_non_compliant', etc.
|
||||
sent_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
UNIQUE(course_completion_record_id, channel, event_type)
|
||||
);
|
||||
```
|
||||
|
||||
### New Table: `course_certificate_issue`
|
||||
```sql
|
||||
CREATE TABLE course_certificate_issue (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
course_id UUID NOT NULL REFERENCES course(id) ON DELETE CASCADE,
|
||||
profile_id UUID NOT NULL REFERENCES profile(id) ON DELETE CASCADE,
|
||||
course_completion_record_id UUID NOT NULL REFERENCES course_completion_record(id) ON DELETE CASCADE,
|
||||
cycle_number INTEGER NOT NULL,
|
||||
issued_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
status VARCHAR NOT NULL DEFAULT 'valid', -- 'valid', 'expiring_soon', 'expired', 'renewed'
|
||||
file_url TEXT,
|
||||
|
||||
UNIQUE(course_completion_record_id)
|
||||
);
|
||||
```
|
||||
|
||||
### Drizzle Schema (TypeScript)
|
||||
|
||||
```typescript
|
||||
@@ -527,8 +576,8 @@ Two new routes under `/internal/`, protected by the existing `PRIVATE_SERVER_KEY
|
||||
```
|
||||
POST /internal/compliance/check-expiry
|
||||
→ Checks valid_until dates across all compliance courses
|
||||
→ Transitions statuses: compliant → expiring_soon → in_grace_period → non_compliant
|
||||
→ Creates new cycle records (automatic re-enrollment) when expired
|
||||
→ Creates the next cycle when a compliant cycle reaches valid_until
|
||||
→ Transitions only the active cycle through: not_started/in_progress → expiring_soon → in_grace_period → non_compliant
|
||||
→ Returns: { processed: number, transitioned: number, reEnrolled: number }
|
||||
|
||||
POST /internal/compliance/send-reminders
|
||||
@@ -617,6 +666,7 @@ apps/dashboard/src/lib/features/course/
|
||||
apps/course-app/ (learner-facing)
|
||||
# Updated: show compliance status badge, due date, retake history on course page
|
||||
# Updated: course card shows "Due by" and color-coded status
|
||||
# Updated: same course page shows "Current Cycle" and "Past Cycles" history instead of separate course entries
|
||||
```
|
||||
|
||||
### Files That Need Code Changes
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
# Compliance UX
|
||||
|
||||
This document describes the compliance user journey that is implemented today. It is intentionally limited to the current behavior in code and dashboard UI.
|
||||
|
||||
## Scope
|
||||
|
||||
- Applies only to courses whose type is `COMPLIANCE`
|
||||
- The `Compliance` nav item only appears for `COMPLIANCE` courses
|
||||
- The current implementation uses one course with compliance history inside it
|
||||
- It does not create separate retake courses or a course-switching UI
|
||||
|
||||
## Key prerequisites
|
||||
|
||||
- A teacher/admin must create or configure the course as `COMPLIANCE`
|
||||
- The course must have `certificate.deadline` set before student enrollment can create the initial compliance cycle
|
||||
- Student completion is currently tied to the existing course certification rule, not a separate compliance-only completion rule
|
||||
|
||||
## Teacher/Admin Journey
|
||||
|
||||
### 1. Configure the course
|
||||
|
||||
The teacher/admin creates or updates a course to use the `COMPLIANCE` type.
|
||||
|
||||
For the compliance lifecycle to begin for learners, the course must also have a certificate deadline configured. That deadline is used as the initial `dueDate` for the first compliance cycle.
|
||||
|
||||
### 2. Enroll learners
|
||||
|
||||
When a student is enrolled into the course, the system attempts to create that learner's first compliance record automatically.
|
||||
|
||||
This automatic record creation currently runs when enrollment happens through:
|
||||
|
||||
- direct course member add
|
||||
- bulk course member add
|
||||
- course invite acceptance
|
||||
- organization invite acceptance when courses are assigned
|
||||
- audience-based course enrollment
|
||||
|
||||
If the course has no certificate deadline, compliance record creation is blocked.
|
||||
|
||||
### 3. View course compliance status
|
||||
|
||||
Staff can open the course `Compliance` page.
|
||||
|
||||
The page currently shows:
|
||||
|
||||
- summary cards for learner counts by status
|
||||
- a learner table with current status, due date, validity date, and score
|
||||
|
||||
This is an overview of the latest compliance record per learner, not a multi-cycle drilldown UI.
|
||||
|
||||
### 4. Take bulk staff actions
|
||||
|
||||
On the course `Compliance` page, staff can select learners and apply bulk actions:
|
||||
|
||||
- `Reset`
|
||||
- `Extend`
|
||||
- `Waive`
|
||||
|
||||
Current behavior:
|
||||
|
||||
- `Reset` creates a first record if none exists, or resets the learner's latest record back to `not_started` with a new due date
|
||||
- `Extend` updates the due date on the learner's current incomplete record; if the learner has no current incomplete record and also has no completed record, it creates a new `not_started` record
|
||||
- `Waive` marks the learner's current incomplete record as `waived`
|
||||
|
||||
The page supports:
|
||||
|
||||
- select all visible learners
|
||||
- clear selection
|
||||
- staff-entered due date for reset/extend
|
||||
- optional waiver reason
|
||||
- optional waiver expiration date
|
||||
|
||||
### 5. Refresh data
|
||||
|
||||
The `Compliance` page has a refresh action. It clears the local compliance state and reloads the course data.
|
||||
|
||||
## Student Journey
|
||||
|
||||
### 1. Join the course
|
||||
|
||||
The student joins the `COMPLIANCE` course through one of the supported enrollment flows.
|
||||
|
||||
If the course is correctly configured, the system creates the student's first compliance cycle automatically with:
|
||||
|
||||
- status `not_started`
|
||||
- cycle number `1`
|
||||
- due date from `certificate.deadline`
|
||||
|
||||
### 2. Take the course normally
|
||||
|
||||
The student experience remains inside the same course shell. There is no separate retake course.
|
||||
|
||||
The student works through lessons and exercises using the existing course flow.
|
||||
|
||||
### 3. Open the Compliance page
|
||||
|
||||
Students see a `Compliance` tab for compliance courses.
|
||||
|
||||
The page currently shows:
|
||||
|
||||
- `Current Cycle`
|
||||
- `History`
|
||||
|
||||
`Current Cycle` displays the latest active record fields that are currently available in the system:
|
||||
|
||||
- cycle number
|
||||
- status
|
||||
- due date
|
||||
- completed at
|
||||
- valid until
|
||||
- score
|
||||
- attempts
|
||||
|
||||
`History` shows previous and current cycle records in a table.
|
||||
|
||||
### 4. Complete the course
|
||||
|
||||
Compliance completion is not triggered by a dedicated "mark complete" action on the compliance page.
|
||||
|
||||
Instead, the system evaluates compliance after submission activity. When the student becomes eligible under the existing course certification rule:
|
||||
|
||||
- the active compliance record is marked `compliant`
|
||||
- `completedAt` is stored
|
||||
- `validUntil` is calculated from the course retake interval
|
||||
- a per-cycle certificate issue record is created if certificates are downloadable for the course
|
||||
|
||||
If the student is not yet eligible for the course certificate, the compliance record remains incomplete.
|
||||
|
||||
## System-driven lifecycle
|
||||
|
||||
Some parts of the journey are implemented as backend jobs rather than dashboard actions.
|
||||
|
||||
### Reminder and status scan
|
||||
|
||||
The system has an internal reminder/status scan that can:
|
||||
|
||||
- mark a record `expiring_soon`
|
||||
- move a learner into `in_grace_period`
|
||||
- move a learner into `non_compliant`
|
||||
- create reminder notification-event records for configured reminder offsets
|
||||
|
||||
This logic is implemented in the backend. There is no teacher-facing reminder management UI yet.
|
||||
|
||||
### Renewal cycle creation
|
||||
|
||||
When a compliant cycle reaches `validUntil`, the expiry job:
|
||||
|
||||
- marks the old cycle as expired
|
||||
- marks its certificate issue as expired
|
||||
- creates the next cycle as `not_started`
|
||||
- sets the next cycle's due date to the previous cycle's `validUntil`
|
||||
|
||||
There is no separate student retake selector in the UI. The renewal appears as another cycle in the same course history.
|
||||
|
||||
## Current UI boundaries
|
||||
|
||||
The following are implemented in the backend but not yet exposed as full dashboard workflows:
|
||||
|
||||
- staff drilldown into a specific learner's compliance history from the staff overview page
|
||||
- reminder management UI
|
||||
- explicit certificate history UI tied to compliance cycles
|
||||
|
||||
The current dashboard UX is therefore:
|
||||
|
||||
- staff: overview + learner table + bulk actions
|
||||
- student: current cycle + cycle history
|
||||
Reference in New Issue
Block a user