# Doris Schoolhouse — Technical Spec

## Goal

Build a desktop-first local web app on NOMAD for school intake and tracking.

Primary jobs:

- upload assignment turn-ins
- upload class recordings
- suggest metadata from D2L
- confirm metadata before Paperless submission
- transcribe and summarize recordings
- track assignment lifecycle through grading
- append grades and professor comments into Paperless notes

## Deployment shape

- **Source path:** `home/doris-schoolhouse`
- **Live NOMAD runtime target:** `/opt/doris-schoolhouse`
- **Access:** local + Pangolin-authenticated
- **Networks:** `pangolin`, `ix-databases_shared-databases`
- **DB:** shared Postgres
- **Archive/document system:** Paperless-NGX
- **Workflow helpers:** existing n8n workflows for school intake and class recording processing when appropriate

## Source of truth

| Domain | Source of truth |
|---|---|
| Course roster | D2L sync cache |
| Assignment roster / due dates | D2L sync cache with manual override support |
| Submission workflow state | Doris Schoolhouse DB |
| Uploaded files / transcripts / summaries | Paperless |
| Grades / professor comments | D2L on refresh, cached in Doris Schoolhouse DB |

The app DB owns workflow state and linkage. Paperless owns archived artifacts.

## Existing reusable assets

### D2L

Existing workspace assets already prove the login/scrape path:

- `~/.openclaw/workspace/school/d2l_scraper_final.js`
- `~/.openclaw/workspace/school/d2l_school_watcher.js`
- `~/.openclaw/workspace/school/d2l_current_snapshot.json`
- `~/.openclaw/workspace/school/d2l_grades.json`

Current confirmed D2L coverage:

- courses
- grades
- grade items
- some material/date counts
- calendar/ICS-derived schedule

Gap to close for Schoolhouse:

- normalize D2L data into app DB tables
- stronger assignment/date extraction
- deterministic matching between D2L assignment rows and Paperless-backed submissions

### Paperless / n8n

Existing repo assets:

- `n8n-workflows/08-class-recording-rag-v1.2.json`
- `n8n-workflows/18-school-paperless-intake-v1.2.json`
- `n8n-workflows/19-paperless-school-metadata-enrichment-v1.1.json`
- `docs/operations/SCHOOL_PAPERLESS_INTAKE.md`
- `docs/reference/SCHOOL_INTAKE_POSTGRES_SCHEMA.sql`

Current confirmed behavior from docs/testing notes:

- school uploads can already be pushed to Paperless with deterministic filename/title correlation
- class recording pipeline already has a webhook path and downstream Paperless/RAG behavior
- workflow 19 already does deterministic post-consumption enrichment

## App architecture

Use a server-rendered Python app for the first build:

- **Backend:** FastAPI
- **Templates:** Jinja2
- **Frontend:** vanilla JS + server-rendered forms/pages
- **DB access:** SQLAlchemy / psycopg
- **Background work:** app jobs can hand off long-running audio processing to existing n8n recording/transcription pipeline

Why this split:

- easier large-upload handling
- easy form-heavy UI
- less frontend build nonsense
- plays nicely with existing Python-heavy repo bits

## Core pages

### 1. Dashboard `/`

Shows:

- upcoming assignments
- unsubmitted assignments
- recent uploads pending confirmation
- newly graded work
- recording processing status
- sync health

### 2. Assignment Upload `/assignments/upload`

Form fields:

- class
- assignment
- submitted date
- version
- notes
- file upload / drag-drop / paste

Behavior:

1. choose file
2. app suggests class + assignment from D2L cache
3. user confirms/edits metadata
4. upload goes to Paperless intake path
5. app stores linkage row
6. assignment state becomes `submitted`

### 3. Recording Upload `/recordings/upload`

Form fields:

- class
- session date
- optional related assignment
- notes
- `.wav` upload

Behavior:

1. accept large WAV
2. persist upload job row
3. convert WAV → MP3
4. delete WAV only after MP3 success
5. hand MP3 to transcription/summarization flow
6. send transcript + summary to Paperless
7. store resulting Paperless document links

### 4. Classes `/classes` and `/classes/{id}`

Shows:

- assignments
- due dates
- grades
- comments
- linked submissions
- linked recordings

### 5. D2L Sync `/admin/d2l`

Shows:

- last sync time
- sync status
- changed courses/assignments/grades
- unmatched rows requiring manual mapping

## API surface

### Health
- `GET /api/health`

### D2L
- `POST /api/d2l/sync`
- `GET /api/d2l/courses`
- `GET /api/d2l/assignments?course_id=`
- `GET /api/d2l/grades?course_id=`

### Assignments
- `GET /api/assignments`
- `POST /api/assignments/intake`
- `POST /api/assignments/{id}/confirm-paperless`
- `POST /api/assignments/{id}/status`

### Recordings
- `GET /api/recordings`
- `POST /api/recordings/intake`
- `POST /api/recordings/{id}/reprocess`

### Paperless linkage
- `POST /api/paperless/webhook/intake-enriched`
- `POST /api/paperless/webhook/recording-complete`
- `POST /api/paperless/assignments/{id}/append-grade-note`

## Data model

### `schoolhouse_course`

- id
- d2l_course_id
- name
- code
- semester
- instructor_name
- active
- first_seen_at
- last_synced_at

### `schoolhouse_assignment`

- id
- course_id
- d2l_assignment_key
- title
- category
- due_at
- status (`assigned|in_progress|submitted|graded|late|missing`)
- grade_text
- grade_numeric
- professor_comments
- source (`d2l|manual|template`)
- created_at
- updated_at

### `schoolhouse_submission`

- id
- assignment_id
- version_label
- original_filename
- mime_type
- checksum_sha256
- submitted_at
- suggested_metadata_json
- confirmed_metadata_json
- paperless_document_id
- paperless_title
- paperless_note_appended_at
- status (`draft|uploaded|confirmed|failed`)

### `schoolhouse_recording`

- id
- course_id
- assignment_id nullable
- session_date
- original_filename
- mp3_filename
- duration_seconds
- transcription_status (`queued|processing|ready|failed`)
- summary_status (`queued|processing|ready|failed`)
- transcript_paperless_document_id
- summary_paperless_document_id
- recording_paperless_document_id nullable
- notes

### `schoolhouse_d2l_sync_run`

- id
- started_at
- completed_at
- status
- raw_snapshot_path
- changes_summary_json
- error_text

### `schoolhouse_assignment_mapping`

- id
- course_id
- d2l_grade_item_name
- normalized_key
- assignment_id
- confidence
- mapping_source (`auto|manual`)

This table is the bastard that keeps D2L grade items tied to internal assignments cleanly.

## Matching rules

### Assignment matching priority

1. exact D2L assignment key when available
2. normalized title within course
3. filename heuristics
4. latest due date proximity
5. manual confirmation

### Grade/comment update policy

- D2L refresh updates cached grade/comment fields for mapped assignments
- if a submission has a Paperless document ID and grade/comment changed, append note once per distinct change hash
- never overwrite user notes in Paperless; append a dated Schoolhouse block

## Recording pipeline contract

### Input limits

- accept WAV up to at least 1 GB
- support up to 90 minutes
- reject unsupported formats with clear message

### Processing steps

1. store upload metadata row
2. convert WAV → MP3
3. delete WAV only after MP3 success
4. submit MP3 to recording workflow
5. receive transcript/summary callback
6. archive transcript + summary into Paperless

## Secrets/config

Required env:

- `SCHOOLHOUSE_HOST`
- `SCHOOLHOUSE_PORT`
- `DATABASE_URL`
- `PAPERLESS_BASE_URL`
- `PAPERLESS_API_TOKEN`
- `N8N_BASE_URL`
- `SCHOOLHOUSE_N8N_ASSIGNMENT_WEBHOOK`
- `SCHOOLHOUSE_N8N_RECORDING_WEBHOOK`
- `D2L_SCRAPER_WORKDIR`
- `D2L_SCRAPER_COMMAND`
- `UPLOAD_TMP_DIR`
- `MAX_UPLOAD_MB`
- `SESSION_SECRET`

Do not commit live values. Use `.env.example` only.

## Repo / deployment plan

Repo source path:

- `home/doris-schoolhouse`

Expected live NOMAD runtime path later:

- `/opt/doris-schoolhouse`

Expected live data path later:

- `/opt/doris-schoolhouse/data`

Expected compose validation before deploy:

```bash
cd /opt/doris-schoolhouse
docker compose --env-file .env config
```

## Build phases

### Phase 1

- app scaffold
- schema
- D2L import command/service
- assignment upload UI
- Paperless confirmation flow

### Phase 2

- recording upload flow
- MP3 conversion
- recording job tracking
- transcript/summary callbacks

### Phase 3

- grade sync
- Paperless note append logic
- dashboard views

### Phase 4

- manual mappings UI
- recurring assignment templates
- mobile cleanup

## Verification checklist

- Python modules compile
- schema file present and readable
- compose file parses
- route map loads without import failure
- README explains runtime/deploy expectations
- no secrets committed
