FeedForward uses SQLite as its database engine with FastLite as the ORM layer. The schema is designed around educational workflows with a focus on data privacy, soft deletion patterns, and clear hierarchical relationships between instructors, courses, assignments, and students.
Database Engine: SQLite 3
Database File: data/users.db
ORM: FastLite (FastHTML's database layer)
Connection Management: Connection pooling with WAL mode
Backup Strategy: Daily snapshots with 30-day retention
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ users │ │ courses │ │ assignments │
│─────────────│ │─────────────│ │─────────────│
│ email (PK) │←────────│instructor_ │ │ id (PK) │
│ name │ │ email(FK) │←────────│ course_id │
│ role │ │ id (PK) │ │ (FK) │
│ status │ │ code │ │ title │
└─────────────┘ │ title │ │ status │
↑ └─────────────┘ └─────────────┘
│ ↑ ↑
│ │ │
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ enrollments │ │ drafts │ │ rubrics │
│─────────────│ │─────────────│ │─────────────│
│ id (PK) │ │ id (PK) │ │ id (PK) │
│ course_id │ │ assignment_ │ │ assignment_ │
│ (FK) │ │ id (FK) │ │ id (FK) │
│ student_ │ │ student_ │ └─────────────┘
│ email(FK) │ │ email(FK) │ ↑
└─────────────┘ │ content │ │
│ status │ ┌─────────────┐
└─────────────┘ │ rubric_ │
↑ │ categories │
│ │─────────────│
┌─────────────┐ │ id (PK) │
│ model_runs │ │ rubric_id │
│─────────────│ │ (FK) │
│ id (PK) │ │ name │
│ draft_id │ │ weight │
│ (FK) │ └─────────────┘
│ model_id │
│ (FK) │
└─────────────┘
Primary user table supporting three roles: ADMIN, INSTRUCTOR, and STUDENT.
| Column | Type | Constraints | Description |
|---|---|---|---|
| str | PRIMARY KEY | User's email address | |
| name | str | NOT NULL | User's full name |
| password | str | NOT NULL | Bcrypt hashed password |
| role | str | NOT NULL | STUDENT, INSTRUCTOR, or ADMIN |
| verified | bool | DEFAULT false | Email verification status |
| verification_token | str | Token for email verification | |
| approved | bool | DEFAULT false | Admin approval status |
| department | str | User's department | |
| reset_token | str | Password reset token | |
| reset_token_expiry | str | Token expiration (ISO timestamp) | |
| status | str | DEFAULT 'active' | active, inactive, archived, deleted |
| last_active | str | Last activity timestamp | |
| tos_accepted | bool | DEFAULT false | Terms of Service acceptance |
| privacy_accepted | bool | DEFAULT false | Privacy Policy acceptance |
| acceptance_date | str | ToS/Privacy acceptance timestamp |
Indexes: - PRIMARY KEY: email - INDEX: role, status - INDEX: verification_token - INDEX: reset_token
Stores course information managed by instructors.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| code | str | NOT NULL | Course code (e.g., CS101) |
| title | str | NOT NULL | Course title |
| term | str | Academic term | |
| department | str | Department offering course | |
| instructor_email | str | FOREIGN KEY | References users.email |
| status | str | DEFAULT 'active' | active, closed, archived, deleted |
| created_at | str | Creation timestamp | |
| updated_at | str | Last update timestamp |
Indexes: - PRIMARY KEY: id - INDEX: instructor_email - INDEX: status - UNIQUE: code, term, instructor_email
Stores assignment details within courses.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| course_id | int | FOREIGN KEY | References courses.id |
| title | str | NOT NULL | Assignment title |
| description | str | Assignment instructions | |
| due_date | str | Due date (ISO format) | |
| max_drafts | int | DEFAULT 3 | Maximum draft submissions |
| created_by | str | FOREIGN KEY | Instructor email |
| status | str | DEFAULT 'draft' | draft, active, closed, archived, deleted |
| created_at | str | Creation timestamp | |
| updated_at | str | Last update timestamp |
Indexes: - PRIMARY KEY: id - INDEX: course_id, status - INDEX: due_date
Junction table linking students to courses.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| course_id | int | FOREIGN KEY | References courses.id |
| student_email | str | FOREIGN KEY | References users.email |
Indexes: - PRIMARY KEY: id - UNIQUE: course_id, student_email
Stores student draft submissions with privacy-focused design.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| assignment_id | int | FOREIGN KEY | References assignments.id |
| student_email | str | FOREIGN KEY | References users.email |
| version | int | NOT NULL | Draft version number |
| content | str | Draft content (temporarily stored) | |
| content_preserved | bool | DEFAULT false | Flag to preserve content |
| submission_date | str | Submission timestamp | |
| word_count | int | Word count for statistics | |
| status | str | DEFAULT 'submitted' | submitted, processing, feedback_ready, archived |
| content_removed_date | str | When content was removed | |
| hidden_by_student | bool | DEFAULT false | Soft delete by student |
Indexes: - PRIMARY KEY: id - INDEX: assignment_id, student_email - INDEX: status - UNIQUE: assignment_id, student_email, version
Main rubric configuration per assignment.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| assignment_id | int | FOREIGN KEY | References assignments.id |
Indexes: - PRIMARY KEY: id - UNIQUE: assignment_id
Individual rubric evaluation categories.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| rubric_id | int | FOREIGN KEY | References rubrics.id |
| name | str | NOT NULL | Category name |
| description | str | Category description | |
| weight | float | NOT NULL | Category weight (0-1) |
Indexes: - PRIMARY KEY: id - INDEX: rubric_id
Stores AI model configurations.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| name | str | NOT NULL | Display name |
| provider | str | NOT NULL | OpenAI, Anthropic, Ollama, etc |
| model_id | str | NOT NULL | Model identifier (gpt-4, claude-3) |
| model_version | str | Model version | |
| description | str | Model description | |
| api_config | str | JSON configuration | |
| owner_type | str | DEFAULT 'system' | system or instructor |
| owner_id | str/int | Owner identifier | |
| capabilities | str | JSON array of capabilities | |
| max_context | int | Maximum context length | |
| active | bool | DEFAULT true | Model availability |
| created_at | str | Creation timestamp | |
| updated_at | str | Last update timestamp |
Indexes: - PRIMARY KEY: id - INDEX: provider, active - INDEX: owner_type, owner_id
Assignment-specific AI configuration.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| assignment_id | int | FOREIGN KEY | References assignments.id |
| primary_ai_model_id | int | FOREIGN KEY | Primary model for aggregation |
| feedback_level | str | DEFAULT 'both' | overall, criterion, or both |
| num_runs | int | DEFAULT 3 | Number of AI runs |
| aggregation_method_id | int | FOREIGN KEY | Aggregation method |
| feedback_style_id | int | FOREIGN KEY | Feedback presentation style |
| require_review | bool | DEFAULT true | Instructor review required |
| mark_display_option_id | int | FOREIGN KEY | Score display option |
Indexes: - PRIMARY KEY: id - UNIQUE: assignment_id
Links AI models to assignments with run configuration.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| assignment_setting_id | int | FOREIGN KEY | References assignment_settings.id |
| ai_model_id | int | FOREIGN KEY | References ai_models.id |
| num_runs | int | DEFAULT 3 | Runs for this model |
Indexes: - PRIMARY KEY: id - INDEX: assignment_setting_id
Individual AI model execution records.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| draft_id | int | FOREIGN KEY | References drafts.id |
| model_id | int | FOREIGN KEY | References ai_models.id |
| run_number | int | Run sequence number | |
| timestamp | str | Execution timestamp | |
| prompt | str | Prompt used | |
| raw_response | str | Raw AI response | |
| status | str | DEFAULT 'pending' | pending, complete, error |
Indexes: - PRIMARY KEY: id - INDEX: draft_id, status - INDEX: model_id
Scores for each rubric category per model run.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| model_run_id | int | FOREIGN KEY | References model_runs.id |
| category_id | int | FOREIGN KEY | References rubric_categories.id |
| score | float | NOT NULL | Category score |
| confidence | float | Score confidence (0-1) |
Indexes: - PRIMARY KEY: id - INDEX: model_run_id - INDEX: category_id
Individual feedback points from model runs.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| model_run_id | int | FOREIGN KEY | References model_runs.id |
| category_id | int | FOREIGN KEY | References rubric_categories.id |
| type | str | NOT NULL | strength, improvement, general |
| content | str | NOT NULL | Feedback text |
| is_strength | bool | DEFAULT false | Positive feedback flag |
| is_aggregated | bool | DEFAULT false | Included in aggregation |
Indexes: - PRIMARY KEY: id - INDEX: model_run_id, category_id - INDEX: type, is_aggregated
Final aggregated feedback for student viewing.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| draft_id | int | FOREIGN KEY | References drafts.id |
| category_id | int | FOREIGN KEY | References rubric_categories.id |
| aggregated_score | float | NOT NULL | Final score |
| feedback_text | str | NOT NULL | Aggregated feedback |
| edited_by_instructor | bool | DEFAULT false | Instructor modified |
| instructor_email | str | Editor's email | |
| release_date | str | When released to student | |
| status | str | DEFAULT 'pending_review' | pending_review, approved, released |
Indexes: - PRIMARY KEY: id - INDEX: draft_id, status - UNIQUE: draft_id, category_id
System-wide configuration settings.
| Column | Type | Constraints | Description |
|---|---|---|---|
| key | str | PRIMARY KEY | Configuration key |
| value | str | NOT NULL | Configuration value |
| description | str | Setting description |
Approved email domains for registration.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| domain | str | NOT NULL UNIQUE | Email domain |
| auto_approve_instructor | bool | DEFAULT false | Auto-approve instructors |
| created_at | str | Creation timestamp | |
| updated_at | str | Last update timestamp |
Available feedback aggregation methods.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| name | str | NOT NULL | Method name |
| description | str | Method description | |
| is_active | bool | DEFAULT true | Method availability |
Default Methods: - Average - Weighted Average - Maximum - Median
Feedback presentation styles.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| name | str | NOT NULL | Style name |
| description | str | Style description | |
| is_active | bool | DEFAULT true | Style availability |
Options for displaying scores to students.
| Column | Type | Constraints | Description |
|---|---|---|---|
| id | int | PRIMARY KEY AUTOINCREMENT | Unique identifier |
| display_type | str | NOT NULL | numeric, hidden, icon |
| name | str | NOT NULL | Option name |
| description | str | Option description | |
| icon_type | str | Icon type if applicable | |
| is_active | bool | DEFAULT true | Option availability |
Most tables implement soft deletion using a status field:
-- Example status transitions
'draft' → 'active' → 'inactive' → 'archived' → 'deleted'
-- Query pattern for active records
SELECT * FROM table WHERE status != 'deleted';
All timestamps use ISO 8601 format:
YYYY-MM-DDTHH:MM:SS.sssZ
Example: 2024-10-15T14:30:00.000Z
Complex configurations stored as JSON strings:
# api_config example
{
"api_key": "encrypted_key",
"endpoint": "https://api.openai.com/v1",
"organization": "org-123"
}
# capabilities example
["text", "vision", "code", "audio"]
content_preserved flag prevents automatic removalcontent_removed_date tracks deletion for complianceApplication-level cascade handling: - Deleting course → soft delete assignments → soft delete drafts - Deleting user → handle based on role and data retention policy - Deleting assignment → preserve drafts for record keeping
-- Vacuum database (monthly)
VACUUM;
-- Analyze tables for query optimization
ANALYZE;
-- Clean old soft-deleted records (quarterly)
DELETE FROM drafts
WHERE status = 'deleted'
AND DATE(updated_at) < DATE('now', '-90 days');
# Daily backup script
sqlite3 data/users.db ".backup /backups/users_$(date +%Y%m%d).db"
# Verify backup integrity
sqlite3 /backups/users_20241015.db "PRAGMA integrity_check;"
Key indexes for common queries: 1. User lookups by email and role 2. Course listings by instructor 3. Assignment queries by course and status 4. Draft searches by student and assignment 5. Feedback retrieval by draft status
For scaling beyond SQLite limits:
-- Example PostgreSQL equivalents
-- SQLite: AUTOINCREMENT
-- PostgreSQL: SERIAL or IDENTITY
-- SQLite: TEXT for all strings
-- PostgreSQL: VARCHAR(n) with specific limits
-- SQLite: INTEGER for booleans
-- PostgreSQL: BOOLEAN native type
Track schema changes:
-- system_config table
INSERT INTO system_config (key, value, description)
VALUES ('schema_version', '1.0.0', 'Current database schema version');
This schema is optimized for educational workflows with privacy-conscious design. Regular maintenance and monitoring ensure optimal performance.