Implement verified curriculum, video review, watch sessions, ledger safeguards, and remove demo data

This commit is contained in:
Hamza-Ayed
2026-09-09 17:04:08 +03:00
parent 0dc5df7b5e
commit 5af684a892
49 changed files with 1809 additions and 3005 deletions
@@ -0,0 +1,74 @@
-- Curriculum publication identity. Apply after taking a schema backup.
-- This migration is additive: it does not rewrite `lessons` or local assets.
CREATE TABLE IF NOT EXISTS `curriculum_lessons` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`grade_key` VARCHAR(64) NOT NULL,
`subject_key` VARCHAR(96) NOT NULL,
`semester_key` VARCHAR(64) NOT NULL,
`unit_key` VARCHAR(96) NOT NULL,
`lesson_key` VARCHAR(128) NOT NULL,
`curriculum_version` VARCHAR(64) NOT NULL,
`title` VARCHAR(500) NOT NULL,
`source_manifest_path` VARCHAR(500) NOT NULL,
`source_status` ENUM('unverified','reviewed','approved','withdrawn') NOT NULL DEFAULT 'unverified',
`reviewed_at` TIMESTAMP NULL DEFAULT NULL,
`reviewed_by` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_curriculum_lesson_uuid` (`uuid`),
UNIQUE KEY `uq_curriculum_lesson_identity` (`grade_key`,`subject_key`,`semester_key`,`unit_key`,`lesson_key`,`curriculum_version`),
KEY `idx_curriculum_lesson_manifest` (`source_manifest_path`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `publication_bundles` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`curriculum_lesson_id` BIGINT UNSIGNED NOT NULL,
`bundle_version` VARCHAR(64) NOT NULL,
`status` ENUM('draft','review','published','withdrawn') NOT NULL DEFAULT 'draft',
`published_at` TIMESTAMP NULL DEFAULT NULL,
`withdrawn_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_bundle_uuid` (`uuid`),
UNIQUE KEY `uq_bundle_lesson_version` (`curriculum_lesson_id`,`bundle_version`),
KEY `idx_bundle_publication` (`curriculum_lesson_id`,`status`,`published_at`),
CONSTRAINT `fk_bundle_curriculum_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `content_assets` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`asset_type` ENUM('lesson_markdown','worksheet_markdown','textbook_pdf','lesson_mindmap_image','unit_mindmap_image','audio','transcript','other') NOT NULL,
`storage_driver` ENUM('local','r2') NOT NULL DEFAULT 'local',
`storage_key` VARCHAR(700) NOT NULL,
`mime_type` VARCHAR(127) NOT NULL,
`byte_size` BIGINT UNSIGNED NOT NULL,
`sha256` CHAR(64) NOT NULL,
`source_reference` VARCHAR(700) NULL,
`rights_status` ENUM('unknown','review_required','cleared','restricted') NOT NULL DEFAULT 'unknown',
`review_status` ENUM('draft','review','approved','withdrawn') NOT NULL DEFAULT 'draft',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_content_asset_uuid` (`uuid`),
UNIQUE KEY `uq_content_asset_integrity` (`storage_driver`,`storage_key`,`sha256`),
KEY `idx_content_asset_status` (`review_status`,`asset_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `publication_bundle_assets` (
`publication_bundle_id` BIGINT UNSIGNED NOT NULL,
`content_asset_id` BIGINT UNSIGNED NOT NULL,
`role` ENUM('primary_lesson','worksheet','textbook','lesson_mindmap','unit_mindmap','audio','transcript','supporting') NOT NULL,
`sort_order` SMALLINT UNSIGNED NOT NULL DEFAULT 0,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`publication_bundle_id`,`content_asset_id`,`role`),
KEY `idx_bundle_assets_role` (`publication_bundle_id`,`role`,`sort_order`),
CONSTRAINT `fk_bundle_asset_bundle` FOREIGN KEY (`publication_bundle_id`) REFERENCES `publication_bundles` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_bundle_asset_asset` FOREIGN KEY (`content_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Rollback: do not drop published records. Mark a bundle withdrawn and preserve
-- its assets, then restore the preceding published bundle if one exists.
@@ -0,0 +1,28 @@
-- Financial ledger foundation. Amounts are integer fils (1 JOD = 1000 fils).
CREATE TABLE IF NOT EXISTS `ledger_accounts` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `code` VARCHAR(80) NOT NULL,
`owner_type` ENUM('platform','teacher','school') NOT NULL, `owner_id` BIGINT UNSIGNED NULL,
`currency` CHAR(3) NOT NULL DEFAULT 'JOD', `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_ledger_account` (`code`,`owner_type`,`owner_id`,`currency`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `ledger_entries` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL,
`debit_account_id` BIGINT UNSIGNED NOT NULL, `credit_account_id` BIGINT UNSIGNED NOT NULL,
`amount_fils` BIGINT UNSIGNED NOT NULL, `currency` CHAR(3) NOT NULL DEFAULT 'JOD',
`reference_type` VARCHAR(64) NOT NULL, `reference_id` VARCHAR(128) NOT NULL,
`reason` VARCHAR(500) NOT NULL, `policy_version` VARCHAR(64) NOT NULL,
`reversal_of_entry_id` BIGINT UNSIGNED NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_ledger_entry_uuid` (`uuid`),
UNIQUE KEY `uq_ledger_reference` (`reference_type`,`reference_id`,`debit_account_id`,`credit_account_id`),
KEY `idx_ledger_debit` (`debit_account_id`), KEY `idx_ledger_credit` (`credit_account_id`),
CONSTRAINT `fk_ledger_debit` FOREIGN KEY (`debit_account_id`) REFERENCES `ledger_accounts` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_ledger_credit` FOREIGN KEY (`credit_account_id`) REFERENCES `ledger_accounts` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_ledger_reversal` FOREIGN KEY (`reversal_of_entry_id`) REFERENCES `ledger_entries` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `teacher_withdrawal_holds` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `teacher_id` BIGINT UNSIGNED NOT NULL,
`amount_fils` BIGINT UNSIGNED NOT NULL, `status` ENUM('held','released','settled','cancelled') NOT NULL DEFAULT 'held',
`idempotency_key` VARCHAR(128) NOT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `released_at` TIMESTAMP NULL,
PRIMARY KEY (`id`), UNIQUE KEY `uq_withdrawal_hold_uuid` (`uuid`), UNIQUE KEY `uq_withdrawal_idempotency` (`teacher_id`,`idempotency_key`),
KEY `idx_withdrawal_teacher_status` (`teacher_id`,`status`), CONSTRAINT `fk_withdrawal_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teachers` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,33 @@
CREATE TABLE IF NOT EXISTS `lesson_learning_packages` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `curriculum_lesson_id` BIGINT UNSIGNED NOT NULL, `bundle_id` BIGINT UNSIGNED NOT NULL,
`package_type` ENUM('general','english') NOT NULL DEFAULT 'general', `status` ENUM('draft','review','published','withdrawn') NOT NULL DEFAULT 'draft',
`structure_json` JSON NOT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_lesson_package` (`curriculum_lesson_id`,`bundle_id`,`package_type`),
CONSTRAINT `fk_learning_package_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_learning_package_bundle` FOREIGN KEY (`bundle_id`) REFERENCES `publication_bundles` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `english_lexical_entries` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `lesson_learning_package_id` BIGINT UNSIGNED NOT NULL,
`lemma` VARCHAR(255) NOT NULL, `part_of_speech` VARCHAR(64) NULL, `meaning_ar` TEXT NOT NULL,
`ipa_verified` VARCHAR(255) NULL, `audio_asset_id` BIGINT UNSIGNED NULL, `source_reference` VARCHAR(500) NOT NULL,
`review_status` ENUM('draft','approved','withdrawn') NOT NULL DEFAULT 'draft',
PRIMARY KEY (`id`), UNIQUE KEY `uq_english_lemma_package` (`lesson_learning_package_id`,`lemma`),
CONSTRAINT `fk_lexical_package` FOREIGN KEY (`lesson_learning_package_id`) REFERENCES `lesson_learning_packages` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_lexical_audio` FOREIGN KEY (`audio_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `learning_attempts` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `student_id` BIGINT UNSIGNED NOT NULL,
`curriculum_lesson_id` BIGINT UNSIGNED NOT NULL, `activity_type` VARCHAR(64) NOT NULL, `activity_id` VARCHAR(128) NOT NULL,
`result_status` ENUM('correct','incorrect','partial','ungraded') NOT NULL, `help_level` TINYINT UNSIGNED NOT NULL DEFAULT 0,
`submitted_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uq_learning_attempt_uuid` (`uuid`),
KEY `idx_attempt_student_lesson` (`student_id`,`curriculum_lesson_id`,`submitted_at`),
CONSTRAINT `fk_attempt_student` FOREIGN KEY (`student_id`) REFERENCES `students` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_attempt_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `remediation_plans` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `student_id` BIGINT UNSIGNED NOT NULL, `curriculum_lesson_id` BIGINT UNSIGNED NOT NULL,
`status` ENUM('active','completed','expired') NOT NULL DEFAULT 'active', `plan_json` JSON NOT NULL, `due_at` TIMESTAMP NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), KEY `idx_remediation_student` (`student_id`,`status`),
CONSTRAINT `fk_remediation_student` FOREIGN KEY (`student_id`) REFERENCES `students` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_remediation_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,40 @@
-- Payment safety hold: apply this migration before deploying the P0 payment changes.
-- No balance is created or reconciled by this migration. Existing records remain
-- historical evidence and must be reconciled against a real provider later.
CREATE TABLE IF NOT EXISTS `cliq_payments` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL UNIQUE,
`student_id` BIGINT UNSIGNED NOT NULL,
`course_id` BIGINT UNSIGNED NOT NULL,
`amount_jod` DECIMAL(8,2) NOT NULL,
`cliq_alias` VARCHAR(100) NOT NULL,
`reference_code` VARCHAR(50) NOT NULL UNIQUE,
`bank_transaction_id` VARCHAR(100) DEFAULT NULL UNIQUE,
`receipt_image_path` VARCHAR(500) DEFAULT NULL,
`verification_status` ENUM('pending','verified','rejected','manual_review') NOT NULL DEFAULT 'pending',
`ai_extracted_json` JSON DEFAULT NULL,
`verified_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_cliq_student` (`student_id`),
KEY `idx_cliq_ref` (`reference_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `payout_queue` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL UNIQUE,
`teacher_id` BIGINT UNSIGNED NOT NULL,
`amount_jod` DECIMAL(8,2) NOT NULL,
`teacher_cliq_alias` VARCHAR(150) NOT NULL,
`status` ENUM('queued','processing','completed','failed','cancelled') NOT NULL DEFAULT 'queued',
`transaction_reference` VARCHAR(100) DEFAULT NULL,
`failure_reason` TEXT DEFAULT NULL,
`processed_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_payout_teacher` (`teacher_id`),
KEY `idx_payout_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Rollback: do not drop either table. They may contain financial evidence.
@@ -0,0 +1,15 @@
-- Must run before revenue periods because verified ratings reference watch_sessions.
CREATE TABLE IF NOT EXISTS `watch_sessions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL, `student_id` BIGINT UNSIGNED NOT NULL, `video_version_id` BIGINT UNSIGNED NOT NULL,
`funding_source` ENUM('school','marketplace','none') NOT NULL, `server_started_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `server_ended_at` TIMESTAMP NULL, `status` ENUM('active','ended','invalidated') NOT NULL DEFAULT 'active',
PRIMARY KEY (`id`), UNIQUE KEY `uq_watch_session_uuid` (`uuid`), KEY `idx_watch_student_version` (`student_id`,`video_version_id`,`status`),
CONSTRAINT `fk_watch_student` FOREIGN KEY (`student_id`) REFERENCES `students` (`id`) ON DELETE RESTRICT, CONSTRAINT `fk_watch_version` FOREIGN KEY (`video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `watch_events` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `watch_session_id` BIGINT UNSIGNED NOT NULL, `sequence_no` INT UNSIGNED NOT NULL, `event_type` ENUM('start','heartbeat','pause','seek','resume','end','buffer') NOT NULL, `client_position_ms` BIGINT UNSIGNED NOT NULL, `server_received_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, `payload_json` JSON NULL,
PRIMARY KEY (`id`), UNIQUE KEY `uq_watch_event_sequence` (`watch_session_id`,`sequence_no`), CONSTRAINT `fk_watch_event_session` FOREIGN KEY (`watch_session_id`) REFERENCES `watch_sessions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `eligible_watch_intervals` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `watch_session_id` BIGINT UNSIGNED NOT NULL, `start_ms` BIGINT UNSIGNED NOT NULL, `end_ms` BIGINT UNSIGNED NOT NULL, `eligible_seconds` INT UNSIGNED NOT NULL, `reason_code` VARCHAR(80) NOT NULL, `policy_version` VARCHAR(64) NOT NULL, `status` ENUM('measured','excluded','review') NOT NULL DEFAULT 'measured',
PRIMARY KEY (`id`), UNIQUE KEY `uq_eligible_interval` (`watch_session_id`,`start_ms`,`end_ms`,`policy_version`), CONSTRAINT `fk_eligible_session` FOREIGN KEY (`watch_session_id`) REFERENCES `watch_sessions` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,30 @@
CREATE TABLE IF NOT EXISTS `revenue_periods` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `uuid` CHAR(36) NOT NULL,
`starts_at` TIMESTAMP NOT NULL, `ends_at` TIMESTAMP NOT NULL,
`policy_version` VARCHAR(64) NOT NULL, `status` ENUM('open','closing','closed','reopened') NOT NULL DEFAULT 'open',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_revenue_period_uuid` (`uuid`), UNIQUE KEY `uq_revenue_period_dates` (`starts_at`,`ends_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `teacher_revenue_allocations` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `revenue_period_id` BIGINT UNSIGNED NOT NULL,
`teacher_id` BIGINT UNSIGNED NOT NULL, `video_version_id` BIGINT UNSIGNED NOT NULL,
`eligible_seconds` BIGINT UNSIGNED NOT NULL, `amount_fils` BIGINT UNSIGNED NOT NULL,
`status` ENUM('estimated','held','approved','reversed') NOT NULL DEFAULT 'estimated',
`ledger_entry_id` BIGINT UNSIGNED NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_teacher_allocation` (`revenue_period_id`,`teacher_id`,`video_version_id`),
CONSTRAINT `fk_allocation_period` FOREIGN KEY (`revenue_period_id`) REFERENCES `revenue_periods` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_allocation_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teachers` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_allocation_version` FOREIGN KEY (`video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_allocation_ledger` FOREIGN KEY (`ledger_entry_id`) REFERENCES `ledger_entries` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `lesson_video_ratings` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, `student_id` BIGINT UNSIGNED NOT NULL,
`video_version_id` BIGINT UNSIGNED NOT NULL, `rating` TINYINT UNSIGNED NOT NULL,
`watch_session_id` BIGINT UNSIGNED NOT NULL, `status` ENUM('pending','verified','flagged','rejected') NOT NULL DEFAULT 'pending',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_student_video_rating` (`student_id`,`video_version_id`),
CONSTRAINT `fk_lvr_student` FOREIGN KEY (`student_id`) REFERENCES `students` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_lvr_version` FOREIGN KEY (`video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_lvr_session` FOREIGN KEY (`watch_session_id`) REFERENCES `watch_sessions` (`id`) ON DELETE RESTRICT,
CONSTRAINT `chk_lvr_rating` CHECK (`rating` BETWEEN 1 AND 5)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
@@ -0,0 +1,62 @@
-- Teacher lesson submissions and immutable video candidates.
-- Requires 20260909_curriculum_publication_identity.sql.
CREATE TABLE IF NOT EXISTS `teacher_submissions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`teacher_id` BIGINT UNSIGNED NOT NULL,
`curriculum_lesson_id` BIGINT UNSIGNED NOT NULL,
`status` ENUM('draft','uploading','review','published','withdrawn','archived') NOT NULL DEFAULT 'draft',
`current_published_video_version_id` BIGINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_teacher_submission_uuid` (`uuid`),
UNIQUE KEY `uq_teacher_curriculum_submission` (`teacher_id`,`curriculum_lesson_id`),
KEY `idx_submission_lesson_status` (`curriculum_lesson_id`,`status`),
CONSTRAINT `fk_submission_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teachers` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_submission_curriculum_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `video_versions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`teacher_submission_id` BIGINT UNSIGNED NOT NULL,
`version_number` INT UNSIGNED NOT NULL,
`status` ENUM('draft','uploading','review','rejected','published','superseded','withdrawn') NOT NULL DEFAULT 'draft',
`replaces_video_version_id` BIGINT UNSIGNED NULL,
`source_lesson_id` BIGINT UNSIGNED NULL,
`upload_audit_id` BIGINT UNSIGNED NULL,
`content_sha256` CHAR(64) NULL,
`review_report_json` JSON NULL,
`published_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_video_version_uuid` (`uuid`),
UNIQUE KEY `uq_submission_version` (`teacher_submission_id`,`version_number`),
KEY `idx_video_version_status` (`teacher_submission_id`,`status`),
CONSTRAINT `fk_video_version_submission` FOREIGN KEY (`teacher_submission_id`) REFERENCES `teacher_submissions` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_video_version_replacement` FOREIGN KEY (`replaces_video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
ALTER TABLE `teacher_submissions`
ADD CONSTRAINT `fk_submission_current_version`
FOREIGN KEY (`current_published_video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT;
CREATE TABLE IF NOT EXISTS `submission_idempotency_keys` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`teacher_id` BIGINT UNSIGNED NOT NULL,
`operation` VARCHAR(64) NOT NULL,
`idempotency_key` VARCHAR(128) NOT NULL,
`request_sha256` CHAR(64) NOT NULL,
`response_json` JSON NULL,
`http_status` SMALLINT UNSIGNED NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_submission_idempotency` (`teacher_id`,`operation`,`idempotency_key`),
CONSTRAINT `fk_submission_idempotency_teacher` FOREIGN KEY (`teacher_id`) REFERENCES `teachers` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
-- Rollback: preserve submissions and versions. Withdraw affected publications;
-- do not drop records that may be referenced by student progress or review data.
@@ -0,0 +1,31 @@
CREATE TABLE IF NOT EXISTS `video_review_jobs` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`uuid` CHAR(36) NOT NULL,
`video_version_id` BIGINT UNSIGNED NOT NULL,
`curriculum_lesson_id` BIGINT UNSIGNED NOT NULL,
`prompt_version` VARCHAR(64) NOT NULL,
`schema_version` VARCHAR(64) NOT NULL,
`video_sha256` CHAR(64) NOT NULL,
`markdown_sha256` CHAR(64) NOT NULL,
`status` ENUM('queued','collecting_evidence','needs_evidence','ready_for_human_review','rejected','approved','failed','superseded') NOT NULL DEFAULT 'queued',
`input_manifest_json` JSON NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`completed_at` TIMESTAMP NULL DEFAULT NULL,
PRIMARY KEY (`id`), UNIQUE KEY `uq_review_job_uuid` (`uuid`),
KEY `idx_review_job_version` (`video_version_id`,`status`),
CONSTRAINT `fk_review_job_version` FOREIGN KEY (`video_version_id`) REFERENCES `video_versions` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_review_job_lesson` FOREIGN KEY (`curriculum_lesson_id`) REFERENCES `curriculum_lessons` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
CREATE TABLE IF NOT EXISTS `video_review_reports` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`video_review_job_id` BIGINT UNSIGNED NOT NULL,
`recommendation` ENUM('reject','revise','manual_review','ready_for_human_review') NOT NULL,
`report_json` JSON NOT NULL,
`reviewer_id` BIGINT UNSIGNED NULL,
`human_decision` ENUM('pending','approved','rejected') NOT NULL DEFAULT 'pending',
`reviewed_at` TIMESTAMP NULL DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`), UNIQUE KEY `uq_review_report_job` (`video_review_job_id`),
CONSTRAINT `fk_review_report_job` FOREIGN KEY (`video_review_job_id`) REFERENCES `video_review_jobs` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
@@ -0,0 +1,18 @@
-- Removes presentation-grade synthetic reputation defaults. Existing measurements
-- are retained; unknown measurements remain NULL until backed by verified data.
ALTER TABLE `teacher_performance_metrics`
MODIFY `avg_response_minutes` INT NULL DEFAULT NULL,
MODIFY `response_rate_percentage` DECIMAL(5,2) NULL DEFAULT NULL,
MODIFY `raw_avg_rating` DECIMAL(3,2) NULL DEFAULT NULL,
MODIFY `weighted_student_rating` DECIMAL(3,2) NULL DEFAULT NULL,
MODIFY `ai_engagement_score` DECIMAL(5,2) NULL DEFAULT NULL,
MODIFY `sla_speed_score` DECIMAL(5,2) NULL DEFAULT NULL,
MODIFY `mastery_impact_score` DECIMAL(5,2) NULL DEFAULT NULL,
MODIFY `composite_merit_score` DECIMAL(5,2) NULL DEFAULT NULL,
MODIFY `star_equivalent` DECIMAL(3,2) NULL DEFAULT NULL,
MODIFY `reputation_tier` VARCHAR(100) NULL DEFAULT NULL;
ALTER TABLE `teacher_reviews`
ALTER `student_watch_percentage` SET DEFAULT 0.00,
ALTER `socratic_accuracy_rate` SET DEFAULT 0.00,
ALTER `is_verified` SET DEFAULT 0;
@@ -0,0 +1,15 @@
-- Immutable evidence records are required before a reviewer can approve a video.
CREATE TABLE IF NOT EXISTS `video_review_evidence` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`video_review_job_id` BIGINT UNSIGNED NOT NULL,
`transcript_asset_id` BIGINT UNSIGNED NOT NULL,
`visual_evidence_asset_id` BIGINT UNSIGNED NOT NULL,
`coverage_json` JSON NOT NULL,
`submitted_by` BIGINT UNSIGNED NOT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_review_evidence_job` (`video_review_job_id`),
CONSTRAINT `fk_review_evidence_job` FOREIGN KEY (`video_review_job_id`) REFERENCES `video_review_jobs` (`id`) ON DELETE CASCADE,
CONSTRAINT `fk_review_evidence_transcript` FOREIGN KEY (`transcript_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT,
CONSTRAINT `fk_review_evidence_visual` FOREIGN KEY (`visual_evidence_asset_id`) REFERENCES `content_assets` (`id`) ON DELETE RESTRICT
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;