The _get_profile select list omitted school_id, causing
/me/bootstrap to always return null for that field even after
the column was populated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /database/timetable/timetables with optional filters (R6-D)
- Return empty collections instead of 400 when user has no school (R6-E)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
supabase-py injects apikey internally via create_client(url, key). Manually
setting headers['apikey'] caused PostgREST to log "Duplicate API key found /
JSON could not be generated" on every bootstrap request.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add init service behind --profile init with cc-api-dev:latest image
- Remove build sections from backend-dev and backend-test (pre-built images)
- Add env var comments referencing .env.example
- Standardize INIT_MODE=infra default, RUN_INIT=true for init service
- _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>
- 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>
- 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>
- reset_environment: profiles PATCH now sets only school_id=null (removing invalid
user_type='platform_admin' that violated profiles_user_type_check constraint)
- seed_environment: same profiles PATCH fix; admin_profiles upsert now uses correct
column names (admin_role, is_super_admin, display_name) matching 002_schema.sql
- Platform admin status is correctly tracked via admin_profiles.is_super_admin=true
and JWT user_metadata.user_type='platform_admin', not profiles.user_type
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
Supabase Auth admin API requires PUT /auth/v1/admin/users/{id} to update
a user record — PATCH returns 405. Corrects the seed step that sets
kcar's user_type to 'platform_admin'.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two root causes fixed:
1. seed_environment.py: KevlarAI website was 'https://kevlarai.com' (real
domain) instead of 'https://kevlarai.test'. Also, seed step 8 now patches
kcar's auth user_metadata to set user_type='platform_admin' on every
reset+seed, so the fix is self-healing and doesn't require manual DB edits.
2. provisioning_service.py: user_type_map now maps 'platform_admin' to
('superadmin', 'superadmin'), so _ensure_membership() is never called for
platform admin accounts and they are never silently enrolled in the
default institute.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
- 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>
get_global_driver() now sets _driver_unavailable=True when the initial
connection fails, so subsequent calls fail immediately instead of
spending 60s retrying each time. Added reset_global_driver() to allow
manual reconnection after Neo4j comes back up.
Also fixes APP_BOLT_URL in .env: was bolt://bolt.classroomcopilot.ai
(public IP, port not exposed), now bolt://192.168.0.209:7687 (LAN).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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
- 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
- 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
- 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()