-- ============================================================ -- Classroom Copilot — deterministic development seed -- Migration 067: small, repeatable fixtures for Supabase dev/staging -- Run after: 066-taught-lessons-nullable.sql -- -- This intentionally excludes the full GAIS open-data import. It creates a -- compact school, users, classes, timetable, lessons, and storage bucket -- fixtures suitable for local/dev smoke tests without sensitive live data. -- ============================================================ CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- Stable dev identities. Password for all fixture users is "devpassword". INSERT INTO auth.users ( instance_id, id, aud, role, email, encrypted_password, email_confirmed_at, raw_app_meta_data, raw_user_meta_data, created_at, updated_at, confirmation_token, recovery_token, email_change_token_new, email_change ) VALUES ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000001', 'authenticated', 'authenticated', 'platform.admin@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Platform Admin","user_type":"admin"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000002', 'authenticated', 'authenticated', 'school.admin@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"School Admin","user_type":"admin"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000011', 'authenticated', 'authenticated', 'ada.teacher@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Ada Teacher","user_type":"teacher"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000012', 'authenticated', 'authenticated', 'alan.teacher@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Alan Teacher","user_type":"teacher"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000101', 'authenticated', 'authenticated', 's1.student@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Student One","user_type":"student"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000102', 'authenticated', 'authenticated', 's2.student@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Student Two","user_type":"student"}', now(), now(), '', '', '', ''), ('00000000-0000-0000-0000-000000000000', '00000000-0000-4000-8000-000000000103', 'authenticated', 'authenticated', 's3.student@classroomcopilot.dev', crypt('devpassword', gen_salt('bf')), now(), '{"provider":"email","providers":["email"]}', '{"display_name":"Student Three","user_type":"student"}', now(), now(), '', '', '', '') ON CONFLICT (id) DO NOTHING; INSERT INTO public.profiles (id, email, user_type, username, full_name, display_name, school_id, metadata) VALUES ('00000000-0000-4000-8000-000000000001', 'platform.admin@classroomcopilot.dev', 'admin', 'platform-admin', 'Platform Admin', 'Platform Admin', NULL, '{"seed":"dev","role":"platform_admin"}'), ('00000000-0000-4000-8000-000000000002', 'school.admin@classroomcopilot.dev', 'admin', 'school-admin', 'School Admin', 'School Admin', NULL, '{"seed":"dev","role":"school_admin"}'), ('00000000-0000-4000-8000-000000000011', 'ada.teacher@classroomcopilot.dev', 'teacher', 'ada-teacher', 'Ada Teacher', 'Ada Teacher', NULL, '{"seed":"dev","department":"Science"}'), ('00000000-0000-4000-8000-000000000012', 'alan.teacher@classroomcopilot.dev', 'teacher', 'alan-teacher', 'Alan Teacher', 'Alan Teacher', NULL, '{"seed":"dev","department":"Science"}'), ('00000000-0000-4000-8000-000000000101', 's1.student@classroomcopilot.dev', 'student', 'student-one', 'Student One', 'Student One', NULL, '{"seed":"dev","year_group":"9"}'), ('00000000-0000-4000-8000-000000000102', 's2.student@classroomcopilot.dev', 'student', 'student-two', 'Student Two', 'Student Two', NULL, '{"seed":"dev","year_group":"9"}'), ('00000000-0000-4000-8000-000000000103', 's3.student@classroomcopilot.dev', 'student', 'student-three', 'Student Three', 'Student Three', NULL, '{"seed":"dev","year_group":"10"}') ON CONFLICT (id) DO UPDATE SET email = EXCLUDED.email, user_type = EXCLUDED.user_type, username = EXCLUDED.username, full_name = EXCLUDED.full_name, display_name = EXCLUDED.display_name, school_id = EXCLUDED.school_id, metadata = EXCLUDED.metadata; INSERT INTO public.admin_profiles (id, email, display_name, admin_role, is_super_admin, metadata) VALUES ('00000000-0000-4000-8000-000000000001', 'platform.admin@classroomcopilot.dev', 'Platform Admin', 'platform_admin', true, '{"seed":"dev"}') ON CONFLICT (id) DO UPDATE SET is_super_admin = true, metadata = EXCLUDED.metadata; INSERT INTO public.institutes (id, name, urn, status, address, website, metadata, geo_coordinates) VALUES ( '00000000-0000-4000-8000-000000000201', 'Classroom Copilot Dev School', 'DEV0001', 'active', '{"line1":"1 Fixture Road","town":"Dev Town","postcode":"CC1 1DV","country":"GB"}', 'https://classroomcopilot.dev', '{"seed":"dev","local_authority":"Fixture LA"}', '{"lat":51.5007,"lon":-0.1246}' ) ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, metadata = EXCLUDED.metadata; UPDATE public.profiles SET school_id = '00000000-0000-4000-8000-000000000201' WHERE id <> '00000000-0000-4000-8000-000000000001'; INSERT INTO public.institute_memberships (profile_id, institute_id, role, metadata) VALUES ('00000000-0000-4000-8000-000000000002', '00000000-0000-4000-8000-000000000201', 'school_admin', '{"seed":"dev"}'), ('00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', 'teacher', '{"seed":"dev"}'), ('00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', 'teacher', '{"seed":"dev"}'), ('00000000-0000-4000-8000-000000000101', '00000000-0000-4000-8000-000000000201', 'student', '{"seed":"dev"}'), ('00000000-0000-4000-8000-000000000102', '00000000-0000-4000-8000-000000000201', 'student', '{"seed":"dev"}'), ('00000000-0000-4000-8000-000000000103', '00000000-0000-4000-8000-000000000201', 'student', '{"seed":"dev"}') ON CONFLICT (profile_id, institute_id) DO UPDATE SET role = EXCLUDED.role, metadata = EXCLUDED.metadata; INSERT INTO public.classes (id, institute_id, name, class_code, subject, key_stage, year_group, academic_year, description, created_by) VALUES ('00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000201', '9 Science A', '9SCI-A', 'Science', '3', '9', '2026-2027', 'Deterministic dev Year 9 science class', '00000000-0000-4000-8000-000000000002'), ('00000000-0000-4000-8000-000000000302', '00000000-0000-4000-8000-000000000201', '10 Physics B', '10PHY-B', 'Physics', '4', '10', '2026-2027', 'Deterministic dev Year 10 physics class', '00000000-0000-4000-8000-000000000002') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, class_code = EXCLUDED.class_code; INSERT INTO public.class_teachers (class_id, teacher_id, is_primary, assigned_by) VALUES ('00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000011', true, '00000000-0000-4000-8000-000000000002'), ('00000000-0000-4000-8000-000000000302', '00000000-0000-4000-8000-000000000012', true, '00000000-0000-4000-8000-000000000002') ON CONFLICT (class_id, teacher_id) DO UPDATE SET is_primary = EXCLUDED.is_primary; INSERT INTO public.class_students (class_id, student_id, enrolled_by) VALUES ('00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000101', '00000000-0000-4000-8000-000000000002'), ('00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000102', '00000000-0000-4000-8000-000000000002'), ('00000000-0000-4000-8000-000000000302', '00000000-0000-4000-8000-000000000103', '00000000-0000-4000-8000-000000000002') ON CONFLICT (class_id, student_id) DO UPDATE SET status = 'active'; INSERT INTO public.whiteboard_rooms (id, user_id, institute_id, name, context_type, context_id, is_default, storage_path, node_type) VALUES ('00000000-0000-4000-8000-000000000401', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', 'Ada Teacher Workspace', 'profile', '00000000-0000-4000-8000-000000000011', true, 'cc.users/00000000-0000-4000-8000-000000000011/tldraw/default.json', 'profile_workspace'), ('00000000-0000-4000-8000-000000000402', '00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', 'Alan Teacher Workspace', 'profile', '00000000-0000-4000-8000-000000000012', true, 'cc.users/00000000-0000-4000-8000-000000000012/tldraw/default.json', 'profile_workspace') ON CONFLICT (id) DO UPDATE SET storage_path = EXCLUDED.storage_path, is_default = EXCLUDED.is_default; INSERT INTO public.school_timetables (id, institute_id, year_label, start_date, end_date, periods_template) VALUES ( '00000000-0000-4000-8000-000000000501', '00000000-0000-4000-8000-000000000201', '2026-2027', '2026-09-01', '2026-09-07', '[{"code":"P1","name":"Period 1","type":"lesson","start_time":"09:00","end_time":"10:00"},{"code":"P2","name":"Period 2","type":"lesson","start_time":"10:05","end_time":"11:05"},{"code":"BR","name":"Break","type":"break","start_time":"11:05","end_time":"11:25"},{"code":"P3","name":"Period 3","type":"lesson","start_time":"11:25","end_time":"12:25"}]' ) ON CONFLICT (institute_id, year_label) DO UPDATE SET periods_template = EXCLUDED.periods_template; INSERT INTO public.academic_years (id, school_timetable_id, institute_id, year_label) VALUES ('00000000-0000-4000-8000-000000000511', '00000000-0000-4000-8000-000000000501', '00000000-0000-4000-8000-000000000201', '2026-2027') ON CONFLICT (school_timetable_id, year_label) DO NOTHING; INSERT INTO public.academic_terms (id, academic_year_id, institute_id, term_name, term_number, start_date, end_date, notes, tags) VALUES ('00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000511', '00000000-0000-4000-8000-000000000201', 'Autumn fixture week', 1, '2026-09-01', '2026-09-07', 'Single deterministic week for dev smoke tests', '{dev}') ON CONFLICT (academic_year_id, term_number) DO UPDATE SET start_date = EXCLUDED.start_date, end_date = EXCLUDED.end_date; INSERT INTO public.academic_weeks (id, academic_term_id, institute_id, week_number, start_date, week_cycle, notes, tags) VALUES ('00000000-0000-4000-8000-000000000531', '00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000201', 1, '2026-09-01', 'A', 'Deterministic week A', '{dev}') ON CONFLICT (academic_term_id, week_number) DO UPDATE SET week_cycle = EXCLUDED.week_cycle; INSERT INTO public.academic_days (id, academic_week_id, academic_term_id, institute_id, date, day_of_week, academic_day_number, notes, tags) VALUES ('00000000-0000-4000-8000-000000000541', '00000000-0000-4000-8000-000000000531', '00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000201', '2026-09-01', 'Tuesday', 1, 'Fixture day 1', '{dev}'), ('00000000-0000-4000-8000-000000000542', '00000000-0000-4000-8000-000000000531', '00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000201', '2026-09-02', 'Wednesday', 2, 'Fixture day 2', '{dev}'), ('00000000-0000-4000-8000-000000000543', '00000000-0000-4000-8000-000000000531', '00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000201', '2026-09-03', 'Thursday', 3, 'Fixture day 3', '{dev}'), ('00000000-0000-4000-8000-000000000544', '00000000-0000-4000-8000-000000000531', '00000000-0000-4000-8000-000000000521', '00000000-0000-4000-8000-000000000201', '2026-09-04', 'Friday', 4, 'Fixture day 4', '{dev}') ON CONFLICT (institute_id, date) DO UPDATE SET academic_day_number = EXCLUDED.academic_day_number; INSERT INTO public.academic_periods (id, academic_day_id, institute_id, period_code, period_name, period_type, start_time, end_time) SELECT ('00000000-0000-4000-8000-' || lpad((600 + d.day_no * 10 + p.period_no)::text, 12, '0'))::uuid, d.id, '00000000-0000-4000-8000-000000000201'::uuid, p.period_code, p.period_name, p.period_type, p.start_time::time, p.end_time::time FROM (VALUES ('00000000-0000-4000-8000-000000000541'::uuid, 1), ('00000000-0000-4000-8000-000000000542'::uuid, 2), ('00000000-0000-4000-8000-000000000543'::uuid, 3), ('00000000-0000-4000-8000-000000000544'::uuid, 4) ) AS d(id, day_no) CROSS JOIN (VALUES (1, 'P1', 'Period 1', 'lesson', '09:00', '10:00'), (2, 'P2', 'Period 2', 'lesson', '10:05', '11:05'), (3, 'BR', 'Break', 'break', '11:05', '11:25'), (4, 'P3', 'Period 3', 'lesson', '11:25', '12:25') ) AS p(period_no, period_code, period_name, period_type, start_time, end_time) ON CONFLICT (academic_day_id, period_code) DO UPDATE SET period_name = EXCLUDED.period_name; INSERT INTO public.teacher_timetables (id, profile_id, institute_id, school_timetable_id, start_date, end_date) VALUES ('00000000-0000-4000-8000-000000000701', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', '00000000-0000-4000-8000-000000000501', '2026-09-01', '2026-09-07'), ('00000000-0000-4000-8000-000000000702', '00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', '00000000-0000-4000-8000-000000000501', '2026-09-01', '2026-09-07') ON CONFLICT (profile_id, school_timetable_id) DO UPDATE SET start_date = EXCLUDED.start_date, end_date = EXCLUDED.end_date; INSERT INTO public.teacher_timetable_slots (id, teacher_timetable_id, profile_id, institute_id, day_of_week, period_code, subject_class, start_time, end_time, week_cycle, class_id) VALUES ('00000000-0000-4000-8000-000000000711', '00000000-0000-4000-8000-000000000701', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', 'Tuesday', 'P1', '9 Science A', '09:00', '10:00', 'A', '00000000-0000-4000-8000-000000000301'), ('00000000-0000-4000-8000-000000000712', '00000000-0000-4000-8000-000000000701', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', 'Wednesday', 'P2', '9 Science A', '10:05', '11:05', 'A', '00000000-0000-4000-8000-000000000301'), ('00000000-0000-4000-8000-000000000713', '00000000-0000-4000-8000-000000000702', '00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', 'Thursday', 'P1', '10 Physics B', '09:00', '10:00', 'A', '00000000-0000-4000-8000-000000000302') ON CONFLICT (teacher_timetable_id, week_cycle, day_of_week, period_code) DO UPDATE SET class_id = EXCLUDED.class_id, subject_class = EXCLUDED.subject_class; INSERT INTO public.taught_lessons (id, academic_period_id, teacher_timetable_slot_id, class_id, teacher_id, institute_id, date, period_code, week_cycle, day_of_week, lesson_plan, whiteboard_room_id, status, notes, tags) VALUES ('00000000-0000-4000-8000-000000000801', '00000000-0000-4000-8000-000000000611', '00000000-0000-4000-8000-000000000711', '00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', '2026-09-01', 'P1', 'A', 'Tuesday', '{"title":"Forces baseline","objectives":["Describe balanced and unbalanced forces"]}', '00000000-0000-4000-8000-000000000401', 'planned', 'Dev fixture taught lesson', '{dev,science}'), ('00000000-0000-4000-8000-000000000802', '00000000-0000-4000-8000-000000000622', '00000000-0000-4000-8000-000000000712', '00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', '2026-09-02', 'P2', 'A', 'Wednesday', '{"title":"Particle model recap","objectives":["Compare solids liquids and gases"]}', '00000000-0000-4000-8000-000000000401', 'planned', 'Dev fixture taught lesson', '{dev,science}'), ('00000000-0000-4000-8000-000000000803', '00000000-0000-4000-8000-000000000631', '00000000-0000-4000-8000-000000000713', '00000000-0000-4000-8000-000000000302', '00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', '2026-09-03', 'P1', 'A', 'Thursday', '{"title":"Energy stores","objectives":["Identify energy stores in examples"]}', '00000000-0000-4000-8000-000000000402', 'planned', 'Dev fixture taught lesson', '{dev,physics}') ON CONFLICT (academic_period_id, teacher_id) DO UPDATE SET lesson_plan = EXCLUDED.lesson_plan, status = EXCLUDED.status; INSERT INTO public.curriculum_topics (id, title, subject, key_stage, year_group, topic_type, total_lessons, department) VALUES ('DEV-SCI-FORCES', 'Forces baseline', 'Science', '3', '9', 'Standard', 1, 'Science'), ('DEV-PHY-ENERGY', 'Energy stores', 'Physics', '4', '10', 'Standard', 1, 'Science') ON CONFLICT (id) DO UPDATE SET title = EXCLUDED.title; INSERT INTO public.planned_lessons (id, created_by, institute_id, class_id, whiteboard_room_id, topic_code, title, subject, year_group, estimated_duration_minutes, objectives, activities, status, tags) VALUES ('00000000-0000-4000-8000-000000000901', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000201', '00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000401', 'DEV-SCI-FORCES', 'Forces baseline planned lesson', 'Science', '9', 60, '["Describe balanced and unbalanced forces"]', '[{"type":"starter","title":"Force diagrams"}]', 'ready', '{dev,science}'), ('00000000-0000-4000-8000-000000000902', '00000000-0000-4000-8000-000000000012', '00000000-0000-4000-8000-000000000201', '00000000-0000-4000-8000-000000000302', '00000000-0000-4000-8000-000000000402', 'DEV-PHY-ENERGY', 'Energy stores planned lesson', 'Physics', '10', 60, '["Identify energy stores"]', '[{"type":"main","title":"Energy transfer circus"}]', 'ready', '{dev,physics}') ON CONFLICT (id) DO UPDATE SET status = EXCLUDED.status, objectives = EXCLUDED.objectives; INSERT INTO public.lesson_deliveries (id, planned_lesson_id, taught_lesson_id, delivered_by, class_id, institute_id, whiteboard_room_id, started_at, ended_at, notes) VALUES ('00000000-0000-4000-8000-000000000911', '00000000-0000-4000-8000-000000000901', '00000000-0000-4000-8000-000000000801', '00000000-0000-4000-8000-000000000011', '00000000-0000-4000-8000-000000000301', '00000000-0000-4000-8000-000000000201', '00000000-0000-4000-8000-000000000401', '2026-09-01 09:00:00+00', '2026-09-01 10:00:00+00', 'Delivered lesson fixture') ON CONFLICT (id) DO UPDATE SET taught_lesson_id = EXCLUDED.taught_lesson_id; -- Dev storage buckets expected by app/API. Object rows are intentionally omitted; -- TLDraw paths above point at where empty/default snapshots should be written. INSERT INTO storage.buckets (id, name, public) VALUES ('cc.users', 'cc.users', false), ('cc.public.snapshots', 'cc.public.snapshots', false), ('cc.examboards', 'cc.examboards', true) ON CONFLICT (id) DO UPDATE SET public = EXCLUDED.public; -- Lightweight verification breadcrumbs for SQL-level smoke checks. DO $$ DECLARE profile_count integer; institute_count integer; class_count integer; taught_count integer; BEGIN SELECT count(*) INTO profile_count FROM public.profiles WHERE metadata->>'seed' = 'dev'; SELECT count(*) INTO institute_count FROM public.institutes WHERE metadata->>'seed' = 'dev'; SELECT count(*) INTO class_count FROM public.classes WHERE academic_year = '2026-2027'; SELECT count(*) INTO taught_count FROM public.taught_lessons WHERE tags @> ARRAY['dev']; IF profile_count <> 7 OR institute_count <> 1 OR class_count <> 2 OR taught_count <> 3 THEN RAISE EXCEPTION 'Dev seed verification failed: profiles=%, institutes=%, classes=%, taught_lessons=%', profile_count, institute_count, class_count, taught_count; END IF; END $$;