50 Commits

Author SHA1 Message Date
CC Worker
44ccba2151 fix(exam): guarantee auto-map child rows reference an inserted question
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
On papers where band detection yields few/no questions but opencv/gemma still emit response
regions, those regions referenced a synthetic default_qid that was never inserted -> FK violation
(exam_response_areas/exam_boundaries -> exam_questions). Ensure the fallback container question
exists and reattach orphan child rows to it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 18:45:09 +00:00
CC Worker
e83873e822 fix(exam): dedupe all AI auto-map rows by id before insert
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
B1-4 live-route validation: continuation bands re-emit the same stable AI id for
response_areas/boundaries/layout (not just questions), causing duplicate-pkey insert
failures. Add _dedupe_rows_by_id applied to all four tables in _refresh_ai_rows.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 18:02:51 +00:00
150b915282 [verified] fix exam auto-map duplicate continued parts
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
(cherry picked from commit 31c51cb7aa33d7f2e1102cea4ffabfefee259faa)
2026-06-08 17:47:56 +00:00
CC Worker
34fc7edd68 [verified] add exam-board signed URL endpoint
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
(cherry picked from commit c65d18ca6badab193469d88e8e8b32279cca8f98)
2026-06-08 01:51:55 +00:00
c69451fba2 [verified] add upload size and MIME guards
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
(cherry picked from commit f5e05376f637f55b73e474cac8199529682ca398)
2026-06-08 01:18:39 +00:00
e98fed661f [verified] fix files list owner scoping
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
2026-06-08 02:08:38 +01:00
5da108df13 docs(reset): clarify exam-corpus scope
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
2026-06-08 00:57:57 +01:00
CC Worker
cdc105ae54 feat(seed): expand corpus to 1178 papers + download-only/unseed/granular reset
PRIMARY — corpus breadth (505->1178 papers, 18->60 specs, all URLs HEAD-verified):
- AQA (enumerated): Maths, English Lang/Lit, Geography, Computer Science, Business,
  Psychology, MFL (French/Spanish/German), GCSE + A-level, on top of round-1 sciences.
- Edexcel + OCR (confirmed direct URLs via research): Maths, English, Geography, History,
  Business, Computer Science, GCSE + A-level.
- generate_corpus_manifest.py: _subj/_mfl AQA builders, Edexcel/OCR spec+URL tables,
  derived exam_code (_mk_exam_code) matching the locked convention, concurrent re-verify.
Verified on dev .94: eb_specifications=60, eb_exams=1178, QP=469, doc_type all 'pdf',
seed idempotent (uploaded=673 new, skipped=505), failed=0.

SECONDARY:
- --download-only + persistent bucket-shaped local store (manifests/_corpus_store/, gitignored):
  download-once, seed-many, offline-repeatable; --store-dir/--no-store. (_store_path/_item_bytes/
  download_corpus). Verified: store populated, seed reads offline (download_cached).
- --unseed [--board/--spec]: inverse loader — storage objects (Storage API; protect_delete blocks
  raw SQL), first-sweep seed templates, eb_exams, eb_specifications. Verified reversible on .94.
- Granular admin reset: POST /admin/reset?scope=all|exam-corpus|timetable. reset_environment.reset(scope)
  adds EXAM_CORPUS_TABLES (10) + cc.examboards storage cleanup + TIMETABLE_TABLES (13); 'all' now also
  clears the exam subsystem the legacy reset missed. No schema migration required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 23:33:20 +00:00
CC Worker
e6be762f0c fix(timetable): enrollment_requests uses requested_at, not created_at
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
get_class selected a non-existent enrollment_requests.created_at column,
causing a PostgREST 42703 -> 500 on /database/timetable/classes/{id}
(class detail / ResultsWidget). The table column is requested_at.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 20:34:39 +00:00
CC Worker
a01a25cc2e fix(exam): response_model=None on auto-map route (Union[dict,JSONResponse])
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
The auto-map endpoint returns dict (sync 200) or JSONResponse (202 async OCR);
FastAPI cannot build a response model from that Union. Fixes import-time
FastAPIError introduced with the S5-2 endpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 19:52:05 +00:00
2678d0be42 [verified] add exam template auto-map endpoint 2026-06-07 20:48:08 +01:00
43f0a9104c [verified] round-trip S5 exam layout fields 2026-06-07 20:05:47 +01:00
CC Worker
9cc986a3f1 fix(exam): allow any institute teacher to fetch template source PDF
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
Removed the teacher_id ownership check from _require_source_visibility_or_404.
RLS already ensures a teacher can only see templates in their institute;
the ownership gate was blocking shared templates (e.g. board-uploaded AQA papers)
for any teacher who didn't personally create them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 09:55:03 +00:00
28aafaa60f feat(exam): add metadata patch for templates 2026-06-07 00:33:01 +01:00
CC Worker
a37bcaa935 fix(exam): source-pdf download reads files row via service role (S4-8.1 merge-gate fix 2)
Pre-merge smoke caught a second issue: the source_file_id download path read `files`
as-the-user, tripping a PRE-EXISTING broken RLS policy on cabinet_memberships
(42P17 infinite recursion). Authz is already enforced (template fetch + source
visibility), and source_file_id is the template's own file, so resolve the row via
service role (documented exception, same as the catalogue lookup). Flagged the
cabinet_memberships RLS recursion separately as infra bug E8.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:54:24 +00:00
CC Worker
c0775f3be1 fix(exam): source-PDF upload uses shared cc.users bucket (S4-8.1 merge-gate fix)
Pre-merge live smoke on .94 caught 'Bucket not found': the upload wrote to a
per-institute bucket cc.institutes.<id>.private that isn't provisioned on dev.
Use the shared SOURCE_BUCKET_FALLBACK (cc.users); institute is namespaced in the
storage path + enforced by the files-row RLS. Per-institute buckets are a future
multi-tenant concern. Catalogue path + cross-institute 404 already verified green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:51:51 +00:00
CC Worker
c58df6715c feat(exam): template source PDF at create + GET /templates/{id}/source-pdf (S4-8.1)
Recovered from cc-worker WIP that was left uncommitted in the dev-centre clone
(card t_0055b89b). Multipart source_pdf upload at create -> source_file_id;
source-pdf download endpoint resolves from exam_id (catalogue) or source_file_id.
NOT yet human-reviewed/merged; preserving + verifying so it isn't clobbered.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 22:29:32 +00:00
CC Worker
9c1aee28e2 feat(exam): persist S4-9 region kinds + Part geometry; keep metadata out of graph
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
Backend follow-on to migration 73:
- schemas: ResponseAreaPayload.kind extended to response|context|question_number|
  mark_area|reference|furniture + context_type; QuestionPayload gains bounds+page.
- PUT serialization persists Part bounds/page and region context_type.
- Neo4j projection only emits Region nodes for response/context regions; the
  metadata kinds (question_number/mark_area/reference/furniture) are physical-layer
  only and stay out of cc.public.exams.
- Unit test: new kinds + Part geometry + context_type round-trip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 21:14:20 +00:00
CC Worker
e269e67f27 fix(exam): block destructive template PUT once marks recorded (review #1)
PUT full-replace deletes exam_questions, and mark_entries.question_id cascades
ON DELETE — so re-saving the setup canvas after marking began would silently
wipe recorded marks. Guard: 409 if any mark_entry exists for the template's
batches. Mark-scheme edits (PATCH /questions/{id}) are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:15:35 +00:00
CC Worker
77bb0766ff feat(exam): Neo4j projection on template save + neo4j-sync (S4-7)
modules/database/services/exam_projection.py projects a saved template into
cc.public.exams: ExamPaper -> Question/Part -> Region + Part-[:ASSESSES]->
SpecPoint, joined by shared UUIDs (exam_questions.id, exam_response_areas.id,
exam_code, spec_code). Full re-sync per exam_code (idempotent). Reads via
service role + writes via system Neo4j driver (R3.5.1 documented graph-writer).

Wiring (R3.5.4/R5.3):
- PUT /templates/{id} enqueues project_template_safe via BackgroundTasks
  (swallows failures so a graph hiccup never fails the canvas save).
- POST /templates/{id}/neo4j-sync — manual trigger, as-user auth + owner check,
  runs synchronously and returns projection counts.

Unit tests: projection scheduled on PUT; neo4j-sync owner/403/404.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 19:02:18 +00:00
CC Worker
62234dbbcb fix(exam): blank total only for absent AND unmarked; flip status on mark
A roster student starts 'absent' and a direct mark would otherwise still show a
blank total. Now total is blank only when absent with no marks; recording a mark
advances the submission out of absent/unmatched to 'marking'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:43:09 +00:00
CC Worker
5ad9c01cde feat(exam): batches, scans, marks, results, CSV (S4-6)
Adds routers/exam/batches.py (mounted alongside templates under /api/exam):
- POST/GET /batches — batch creation seeds the cohort from class_students AS
  THE USER (cs_read requires caller teaches/admins the class); each active
  enrollee becomes a student_submissions row (status='absent') so no student
  is ever dropped from results (A7). Display names denormalised via a
  documented service-role profiles read (deny-all as-user, E4).
- GET /batches/{id}/queue — submissions + per-submission mark counts + progress.
- GET /batches/{id}/results + /csv — every roster student incl. absent (blank
  marks/total); CSV row always present (A7 baked into the contract).
- PUT /marks/{id} — upsert; batch_id derived server-side from the submission
  (client never supplies the RLS scoping key).
- POST /batches/{id}/scans — E3 guards: MIME check, hard size ceiling (chunked
  read), %PDF magic-byte sniff; owner-only; stores via service-role storage;
  manual/ordered matching (QR-decode is a follow-on, no QR fixtures yet).

Unit tests cover batch/roster-seed/list, queue, results+CSV A7, mark upsert
round-trip, and all scan guards + owner check.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 18:40:10 +00:00
CC Worker
f52c3267ca feat(exam): /api/exam template CRUD router (as-user RLS, E1 fix)
S4-5: new routers/exam/ package mounted at /api/exam (R5.1/E5, not under
/database/). Template CRUD with hybrid persistence (R5.2):

- POST/GET/GET{id}/PUT{id}/DELETE{id} /templates + PATCH /questions/{qid}
- Calls Supabase AS THE USER via SupabaseAnonClient.for_user (E1 fix), so the
  RLS in 72-exam-marker.sql is enforced; no service-role for user-facing ops.
- Institute resolved/validated via the user_institute_ids() SECURITY DEFINER
  RPC (institute_memberships is deny-all as-user per E4); client-supplied
  institute_id is validated, never trusted (R5.5).
- Ownership pre-checked before writes (E2); out-of-scope ids read back as 404
  under RLS (IDOR-safe). Soft-delete archives, never hard-deletes.
- PUT full-replace preserves client UUIDs as Neo4j join keys (spec §2).
- eb_exams.exam_code denormalised via a documented service-role catalogue
  lookup (eb_exams is shared reference data, deny-all as-user per E4).

Unit tests cover auth, CRUD, ownership/IDOR, institute validation, soft-delete.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 17:49:58 +00:00
CC Worker
d3465eca7b R6-D: add GET /database/timetable/timetables endpoint
- New router at routers/database/timetable/timetables.py
- Accepts optional class_id, type, active query params
- Returns {"timetables": [...]} scoped to caller's school
- Fixed broken import path in run/routers.py (tools → timetable module)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 01:14:55 +00:00
CC Worker
9de949d212 R6-E2: return empty collections when user has no school
- _require_institute returns Optional[str] instead of raising 400
- list_classes / my_teaching_classes / my_student_classes / list_school_students
  now return empty arrays when school_id is missing
2026-06-02 23:36:05 +00:00
39ad1818ae Merge branch agent/p0-correctness-security
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
# Conflicts:
#	modules/database/supabase/utils/client.py
2026-05-28 19:17:22 +01:00
1738af0e3d Merge branch agent/tlsync-token-t_a69128a1 (TLSync JWT token endpoint)
Some checks failed
api-ci-deploy / test-build-deploy (push) Has been cancelled
2026-05-28 18:04:25 +01:00
7808a0ae56 Add TLSync token endpoint 2026-05-28 17:55:37 +01:00
4f6634e088 Implement Supabase-first me bootstrap 2026-05-28 14:14:35 +01:00
54760083b5 fix: tighten API P0 auth and route handling 2026-05-28 12:42:42 +01:00
ef75f08392 fix(redis): connect during health checks 2026-05-27 16:50:25 +01:00
0d828315bb fix(graph-tree): class_code column, SubjectClass expansion handler
- _query_teacher_classes: fix code -> class_code (Supabase column name), add section_id param
- _build_timetable_section: tag class nodes with section_id=timetable
- _build_classes_section: tag class nodes with section_id=classes
- SubjectClass handler: section=timetable -> taught_lessons for class; section=classes -> enrolled students

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 13:30:53 +01:00
b71995f4fb fix(graph-tree): switch class/timetable data source to Supabase
- _query_teacher_classes: now queries class_teachers + class_students tables instead of non-existent Neo4j TEACHER_HAS_CLASS relationship
- _build_classes_section: updated signature to (user_id, institute_id, institute_db)
- _build_timetable_section: updated signature; loads classes from Supabase, not Neo4j TIMETABLE_HAS_CLASS
- TeacherTimetable lazy handler: simplified (classes pre-loaded in section builder)
- AcademicWeek timetable-term: Supabase taught_lessons query by date range instead of Neo4j
- expose supabase_institute_id from _resolve_institute call

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:58:47 +01:00
bf3df05632 fix(nav): fix AcademicWeek Cypher bug, add TeacherTimetable tree handlers, timetable-term view
- Fix uuid_string: $id Cypher bug in AcademicWeek handler (days were never loading)
- Pre-load SubjectClass children in _build_timetable_section (By Class view)
- Add TeacherTimetable handler: By Class (TIMETABLE_HAS_CLASS) + By Term (ACADEMIC_TIMETABLE_HAS_ACADEMIC_YEAR chain)
- Add timetable-term context propagation through AcademicTerm -> AcademicWeek -> TaughtLesson
- AcademicWeek in timetable-term context returns TaughtLessons filtered by teacher email
- Pass user_email from credentials to _get_children_for_node
- Propagate section_id on AcademicWeek nodes so week expansion stays in timetable context

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 12:12:46 +01:00
b42b409bb2 fix(nav): AcademicWeek children + days support in graph tree API
- AcademicWeek now returns has_children=true in both /calendar/academic
  endpoint and _get_children_for_node for AcademicTerm case
- Added AcademicWeek case to _get_children_for_node: queries
  ACADEMIC_WEEK_HAS_ACADEMIC_DAY relationships to return AcademicDay children
- Academic calendar can now expand weeks to show individual days

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 10:54:23 +01:00
0596ee5e2c feat(seed): Greenfield full timetable seed with classes and student enrollment
seed_greenfield_timetable.py creates the complete school data set:
- POST /timetable/setup: school_timetables, academic_years, terms, weeks, days
- POST /timetable/materialize-periods: 1624 academic_periods (203 days x 8 periods)
- 17 classes (Physics/Maths/English/History/Science, Yr7-12) with correct metadata
- class_teachers links (primary teacher per class)
- teacher timetable init + slot assignments (class_id FK patched onto slots)
- class_students enrollment: student1->Yr9 (5 classes), student2->Yr10 (4), student3->Yr11 (2)
- POST /timetable/materialize: 1462 taught_lessons all with class_id populated
- POST /admin/seed-timetable endpoint wired in platform_admin_router

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 05:59:06 +01:00
52532ce00f feat(phase-c): lesson plans library backend — CRUD, delivery linking, AI suggest
Adds lesson_plans_router.py with 10 endpoints under /lessons/plans:
GET/POST /plans, GET/PATCH/DELETE /plans/{id}, POST /plans/{id}/deliver,
GET /plans/{id}/deliveries, POST/DELETE /plans/{id}/collaborators,
POST /plans/{id}/suggest (Ollama-backed per-field AI suggestions).

objectives and activities stored as JSONB arrays with Bloom taxonomy support.
Registers router in run/routers.py. Adds seed_test_environment.py for
platform-admin triggered reset + seed of demo users and Neo4j.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 03:59:26 +01:00
abf8d05ca1 feat(phase-b): Supabase-first timetable, classes, enrollment, and student views
- timetable_builder_router: Supabase-primary slot write (POST /timetable/slots),
  week_cycle support, GET /slots reads from Supabase, materialize-periods endpoint,
  rebuild-neo4j endpoint, sync-lessons endpoint (Track B: TaughtLesson Neo4j nodes),
  _sync_teacher_timetables_to_neo4j and _sync_taught_lessons_to_neo4j helpers
- classes_router: GET /{class_id} enriched with profiles + enrollment_requests,
  GET /school/students for admin search, PATCH /enrollment-requests/{id} approve/reject
- taught_lessons_router: GET /student/lessons student week view with enrichment
- school_router: academic_periods sync, day-type management
- platform_admin_router + platform_admin: POST /admin/reset and /admin/seed endpoints
- invitations_router: teacher invite scaffolding
- reset_environment + seed_environment: idempotent dev environment scripts
- graph_tree_router: Supabase-first institute resolution
- provisioning_service: neo4j_private_db_name column support
- main.py + run/routers.py: register new routers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 02:55:44 +01:00
e42cd09dea feat(phase-b): GAIS Supabase loader + school search/register endpoints
- gais_data.py: rewrite to load Edubase CSV into Supabase gais_schools +
  gais_local_authorities via two-pass batch upsert (LAs first for FK integrity)
- school_router.py: add GET /school/search (trigram ilike on name, URN exact),
  POST /school/register (create institute + Neo4j provision + membership link)
- Encoding: handles Windows-1252 (cp1252) Edubase CSV format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 01:51:45 +01:00
fe3d7a12c8 feat(phase-b): school/timetable API routers + graph nav tree
New routers (all previously untracked):
- graph_tree_router: /graph/tree, /graph/node/children, /graph/calendar/academic
  Supabase-driven tree builder; institute DB resolved by teacher email scan
- school_router: /school/status (role + calendar flags), /school/info PATCH
  Self-heals profiles.school_id from institute_memberships if null
- timetable_builder_router: /timetable/setup (AcademicYear/Term/Week + SchoolTimetable),
  /timetable/slots (read/write TimetableSlot nodes), /timetable/init (TeacherTimetable)
- user_init_router: /user/init (provision user node in institute DB)

routers.py: register all new routers with correct prefixes
users.py: add JournalNode and PlannerNode schema classes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 01:24:44 +01:00
7ca21ef538 feat: implement export endpoint for transcription sessions (Phase 3E)
- Add POST /transcribe/sessions/{id}/export endpoint
- Generate SRT (SubRip subtitle format) with timestamps
- Generate TXT (plain text with [HH:MM:SS,mmm] timestamps)
- Generate JSON (structured data: session, segments, summaries, canvas events)
- Return as FileResponse download with Content-Disposition headers
- Filenames include sanitized session title + date
- No API keys stored or logged during export
2026-05-20 22:25:36 +00:00
36ae76143f Phase 3B: Implement pluggable LLM client for summary generation
- Create llm_client.py with 5 provider implementations (Anthropic, OpenAI, Ollama, OpenRouter, Google)
- Add build_prompt() helper to construct system/user prompts from templates
- Wire up POST /transcribe/sessions/{id}/summaries endpoint to call LLM client
- Return generated content + token counts (input_tokens, output_tokens)
- API keys passed per-request, never stored or logged
- Uses prompt templates from prompts.py based on summary_type
2026-05-20 22:20:19 +00:00
fd8d2a537d feat(cis): implement /database/timetables/current-period endpoint with Neo4j query
- Query Neo4j for Academic/Registration periods where now() is between start_time and end_time
- Return period_id, event_type, event_label, start_time, end_time
- Handles missing teacher or Neo4j connection gracefully
2026-05-20 22:06:46 +00:00
b47c7c252d feat(transcription): add Supabase schema and API endpoints for CIS 2026-05-20 21:03:00 +00:00
Classroom Copilot Dev
cf9bdca813 fix: Add filters parameter to BaseCRUD.get_multi() method
- Fixed signature mismatch where enrollment_requests router was passing
  filters parameter to get_multi() but method didn't accept it
- get_multi() now accepts optional filters dict and passes it to get_all()
2026-02-25 22:53:52 +00:00
2b7d52b5ae chore: update env config, docker-compose, requirements and bucket init 2026-02-21 16:29:26 +00:00
787fd86260 module updates 2025-11-19 19:38:09 +00:00
3758c7572a latest 2025-11-14 14:47:19 +00:00
2a85845835 Environment methods 2025-08-23 19:01:36 +01:00
e0c489f625 Initial commit 2025-07-11 13:52:19 +00:00