-- ============================================================ -- GeoGraSim V2 — additive Schema-Erweiterungen -- Idempotent: kann mehrfach laufen, kein DROP, kein RENAME. -- V1-Tabellen (students, classes, teachers, licenses) bleiben unangetastet. -- ============================================================ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS=0; -- ------------------------------------------------------------ -- Locales (Sprache + Land + Variante) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS locales_v2 ( code VARCHAR(32) PRIMARY KEY, language CHAR(2) NOT NULL, country CHAR(2) NOT NULL, variant VARCHAR(16) NOT NULL, display_name VARCHAR(100) NOT NULL, is_easy BOOLEAN NOT NULL DEFAULT FALSE, is_active BOOLEAN NOT NULL DEFAULT TRUE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT IGNORE INTO locales_v2 (code, language, country, variant, display_name, is_easy) VALUES ('de-AT-standard', 'de', 'AT', 'standard', 'Deutsch (Österreich)', FALSE), ('de-AT-easy', 'de', 'AT', 'easy', 'Deutsch (Österreich) — Leichte Sprache', TRUE); -- ------------------------------------------------------------ -- Lehrplan-Anker-Pool (Land-präfixiert) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS lehrplan_anchors_v2 ( code VARCHAR(64) PRIMARY KEY, country CHAR(2) NOT NULL, scheme VARCHAR(32) NOT NULL, level VARCHAR(100) NOT NULL, topic VARCHAR(255) NOT NULL, competence TEXT NOT NULL, display_order INT DEFAULT 0, is_active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_country_scheme (country, scheme) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Modul-Registrierung (von Atlas beim Integrieren befüllt) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS module_info_v2 ( slug VARCHAR(64) PRIMARY KEY, display_name VARCHAR(255) NOT NULL, display_name_easy VARCHAR(255) NOT NULL, version VARCHAR(20) NOT NULL, spec_version VARCHAR(20) NOT NULL, icon VARCHAR(20) DEFAULT '', short_description TEXT NOT NULL, short_description_easy TEXT NOT NULL, long_description TEXT NOT NULL, long_description_easy TEXT NOT NULL, estimated_duration_min INT NOT NULL DEFAULT 25, card_image_url VARCHAR(500) DEFAULT NULL, entry_url VARCHAR(500) NOT NULL, maintainer_instance VARCHAR(64) NOT NULL, status ENUM('draft', 'beta', 'stable', 'archived') NOT NULL DEFAULT 'draft', installed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Modul ↔ Lehrplan-Anker (Many-to-Many) CREATE TABLE IF NOT EXISTS module_lehrplan_v2 ( module_slug VARCHAR(64) NOT NULL, anker_code VARCHAR(64) NOT NULL, PRIMARY KEY (module_slug, anker_code), KEY idx_anker (anker_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Modul ↔ Glossar-Schwerpunkte (Many-to-Many) CREATE TABLE IF NOT EXISTS module_glossar_v2 ( module_slug VARCHAR(64) NOT NULL, glossar_slug VARCHAR(64) NOT NULL, PRIMARY KEY (module_slug, glossar_slug), KEY idx_glossar (glossar_slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Modul-Difficulty-Levels CREATE TABLE IF NOT EXISTS module_levels_v2 ( module_slug VARCHAR(64) NOT NULL, level_id VARCHAR(20) NOT NULL, label VARCHAR(100) NOT NULL, label_easy VARCHAR(100) NOT NULL, display_order INT DEFAULT 0, PRIMARY KEY (module_slug, level_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Modul-Telemetry-Milestones (vorab deklariert) CREATE TABLE IF NOT EXISTS module_milestones_v2 ( module_slug VARCHAR(64) NOT NULL, milestone_id VARCHAR(64) NOT NULL, label VARCHAR(255) NOT NULL, display_order INT DEFAULT 0, PRIMARY KEY (module_slug, milestone_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Resume-State pro Schüler*in × Modul (opake JSON-Blob) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS module_state_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, state_json LONGTEXT NOT NULL, summary VARCHAR(500) DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, created_by_version ENUM('v1','v2') DEFAULT 'v2', UNIQUE KEY uniq_student_module (student_id, module_slug), KEY idx_updated (updated_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Endergebnisse (POST /api/result) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS assessments_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, class_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, module_version VARCHAR(20) NOT NULL, spec_version VARCHAR(20) NOT NULL, completed BOOLEAN NOT NULL, abandoned BOOLEAN NOT NULL DEFAULT FALSE, score INT NOT NULL, score_max INT NOT NULL, duration_s INT NOT NULL, difficulty VARCHAR(20) NOT NULL, mode ENUM('free','teacher_started','assessment','demo') NOT NULL, module_specific_data LONGTEXT DEFAULT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by_version ENUM('v1','v2') DEFAULT 'v2', KEY idx_student (student_id), KEY idx_class_module (class_id, module_slug), KEY idx_created (created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Milestones erreicht pro Assessment CREATE TABLE IF NOT EXISTS assessment_milestones_v2 ( assessment_id BIGINT NOT NULL, milestone_id VARCHAR(64) NOT NULL, PRIMARY KEY (assessment_id, milestone_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Lehrplan-Coverage pro Assessment CREATE TABLE IF NOT EXISTS assessment_lehrplan_v2 ( assessment_id BIGINT NOT NULL, anker_code VARCHAR(64) NOT NULL, coverage ENUM('full','partial','none') NOT NULL, PRIMARY KEY (assessment_id, anker_code) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Freitext-Antworten pro Assessment CREATE TABLE IF NOT EXISTS assessment_freetext_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, assessment_id BIGINT NOT NULL, prompt_id VARCHAR(64) NOT NULL, prompt_text TEXT NOT NULL, answer_text TEXT NOT NULL, KEY idx_assessment (assessment_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Telemetry-Events (Heartbeat / Milestone / Stuck / a11y-Blocker) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS telemetry_events_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, class_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, module_version VARCHAR(20) NOT NULL, event_type ENUM('heartbeat','milestone','stuck','accessibility-blocker') NOT NULL, phase VARCHAR(64) DEFAULT NULL, phase_label VARCHAR(255) DEFAULT NULL, step VARCHAR(20) DEFAULT NULL, current_score INT DEFAULT NULL, score_max INT DEFAULT NULL, time_in_phase_s INT DEFAULT NULL, total_time_s INT DEFAULT NULL, milestone_id VARCHAR(64) DEFAULT NULL, milestone_label VARCHAR(255) DEFAULT NULL, score_delta INT DEFAULT NULL, idle_s INT DEFAULT NULL, last_action_label VARCHAR(255) DEFAULT NULL, hint VARCHAR(500) DEFAULT NULL, blocker_type VARCHAR(64) DEFAULT NULL, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_student_ts (student_id, ts), KEY idx_class_ts (class_id, ts), KEY idx_module_ts (module_slug, ts), KEY idx_type (event_type) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Schüler-Modul-Fortschritt (für Lerngeschichte) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS student_progress_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, total_attempts INT DEFAULT 0, best_score INT DEFAULT 0, best_score_max INT DEFAULT 0, last_completed_at TIMESTAMP NULL DEFAULT NULL, total_duration_s BIGINT DEFAULT 0, UNIQUE KEY uniq_student_module (student_id, module_slug), KEY idx_student (student_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Schüler-Lehrplan-Coverage (welche Anker hat Schüler*in abgedeckt) CREATE TABLE IF NOT EXISTS lehrplan_progress_v2 ( student_id INT NOT NULL, anker_code VARCHAR(64) NOT NULL, best_coverage ENUM('full','partial','none') NOT NULL, reached_via_module VARCHAR(64) NOT NULL, reached_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (student_id, anker_code), KEY idx_student (student_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Hochgeladene Artefakte (Bilder, Karten, Audio aus Modulen) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS artifacts_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, student_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, mime_type VARCHAR(100) NOT NULL, url VARCHAR(500) NOT NULL, thumbnail_url VARCHAR(500) DEFAULT NULL, label VARCHAR(255) DEFAULT NULL, bytes BIGINT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_student (student_id), KEY idx_module (module_slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- Verknüpfung Artefakt ↔ Assessment CREATE TABLE IF NOT EXISTS assessment_artifacts_v2 ( assessment_id BIGINT NOT NULL, artifact_id BIGINT NOT NULL, PRIMARY KEY (assessment_id, artifact_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- A11y-Profil-Erweiterung (additiv zu V1-students) -- Neue Spalten in der bestehenden students-Tabelle, additiv mit DEFAULTs. -- ------------------------------------------------------------ ALTER TABLE students ADD COLUMN IF NOT EXISTS easy_language BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS high_contrast BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS reduced_motion BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS screen_reader BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS text_scale ENUM('normal','large','xlarge') NOT NULL DEFAULT 'normal', ADD COLUMN IF NOT EXISTS locale_pref VARCHAR(32) DEFAULT NULL, ADD COLUMN IF NOT EXISTS country_pref CHAR(2) DEFAULT 'AT', ADD COLUMN IF NOT EXISTS nachteilsausgleich BOOLEAN NOT NULL DEFAULT FALSE, ADD COLUMN IF NOT EXISTS avatar_slug VARCHAR(64) DEFAULT 'fuchs-explorer'; -- Lehrperson-Erweiterung: is_admin-Flag für V2-Plattform-Config ALTER TABLE teachers ADD COLUMN IF NOT EXISTS is_admin BOOLEAN NOT NULL DEFAULT FALSE; -- ------------------------------------------------------------ -- Plattform-Config (Heartbeat-Intervall, Token-TTL, Rate-Limits) -- Lehrperson mit Admin-Rolle kann das im Admin-Board justieren. -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS platform_config_v2 ( config_key VARCHAR(64) PRIMARY KEY, config_value VARCHAR(255) NOT NULL, value_type ENUM('int', 'float', 'string', 'bool', 'json') NOT NULL DEFAULT 'string', min_value VARCHAR(255) DEFAULT NULL, max_value VARCHAR(255) DEFAULT NULL, label VARCHAR(255) NOT NULL, label_easy VARCHAR(255) DEFAULT NULL, help_text TEXT DEFAULT NULL, group_name VARCHAR(64) NOT NULL DEFAULT 'general', display_order INT DEFAULT 0, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, updated_by INT DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT IGNORE INTO platform_config_v2 (config_key, config_value, value_type, min_value, max_value, label, help_text, group_name, display_order) VALUES ('heartbeat_interval_min_s', '10', 'int', '5', '60', 'Heartbeat — Min-Intervall (Sek.)', 'Untergrenze für Modul-Telemetrie. Schnellere Module dürfen nicht öfter pingen.', 'telemetry', 10), ('heartbeat_interval_max_s', '30', 'int', '15', '120', 'Heartbeat — Max-Intervall (Sek.)', 'Obergrenze. Ruhige Module dürfen seltener pingen.', 'telemetry', 20), ('stuck_threshold_s', '90', 'int', '30', '300', 'Stuck-Schwellwert (Sek.)', 'Nach wieviel Sek. Inaktivität wird ein Stuck-Event gefeuert.', 'telemetry', 30), ('telemetry_rate_limit_per_s','1','int', '1', '10', 'Telemetry Rate-Limit (Events/Sek.)','Max. Events pro Sekunde pro Schüler*in.', 'telemetry', 40), ('telemetry_event_cap_per_session','2000','int','100','10000','Telemetry Event-Cap pro Sitzung','Nach dieser Anzahl werden Heartbeats verworfen (Milestones bleiben).', 'telemetry', 50), ('token_ttl_s', '3600', 'int', '300', '28800','Token-Lebensdauer (Sek.)', 'Wie lange ein Login-Token gültig ist. Default 1h.', 'auth', 10), ('token_refresh_window_s', '300', 'int', '60', '1800', 'Token-Refresh-Fenster (Sek.)', 'Wieviele Sek. vor Ablauf ein Token erneuert wird.', 'auth', 20), ('telemetry_retention_days', '30', 'int', '7', '365', 'Telemetry-Aufbewahrung (Tage)', 'Wie lange Telemetry-Events in der DB bleiben, bevor sie gelöscht werden.', 'telemetry', 60); -- ------------------------------------------------------------ -- Health-Metriken (pro API-Endpoint, für Auto-Tuning Phase 2) -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS health_metrics_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, endpoint VARCHAR(128) NOT NULL, method VARCHAR(10) NOT NULL, duration_ms INT NOT NULL, status_code INT NOT NULL, ts TIMESTAMP DEFAULT CURRENT_TIMESTAMP, KEY idx_endpoint_ts (endpoint, ts), KEY idx_ts (ts) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Klassen-Aufträge (V2-Variante, additiv zu V1-class_assignments) -- Lehrperson vergibt ein Modul mit Schwierigkeit (+ Settings) an die Klasse. -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS class_assignments_v2 ( id BIGINT AUTO_INCREMENT PRIMARY KEY, class_id INT NOT NULL, teacher_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, difficulty VARCHAR(20) NOT NULL, custom_params JSON DEFAULT NULL, -- V3: Lehrperson-eigene Parameter-Overrides starts_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, ends_at TIMESTAMP NULL DEFAULT NULL, active BOOLEAN NOT NULL DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_by_version ENUM('v1','v2') DEFAULT 'v2', KEY idx_class_active (class_id, active), KEY idx_module (module_slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Schüler-Level-Unlocks pro Modul -- Im freien Modus: Schüler*in sieht nur Stufen, die freigeschaltet sind. -- Im Auftragsmodus: Pflicht-Stufe vorgegeben (außer Nachteilsausgleich). -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS student_level_unlock_v2 ( student_id INT NOT NULL, module_slug VARCHAR(64) NOT NULL, highest_unlocked_level VARCHAR(20) NOT NULL DEFAULT 'L1', highest_completed_level VARCHAR(20) DEFAULT NULL, best_score_per_level JSON DEFAULT NULL, -- {"L1": {"score": 95, "max": 100}, "L2": {...}} attempts_per_level JSON DEFAULT NULL, -- {"L1": 3, "L2": 1} last_played_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (student_id, module_slug), KEY idx_student (student_id), KEY idx_module (module_slug) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- ------------------------------------------------------------ -- Sentry für Spec-Version-Tracking -- ------------------------------------------------------------ CREATE TABLE IF NOT EXISTS spec_history_v2 ( id INT AUTO_INCREMENT PRIMARY KEY, spec_version VARCHAR(20) NOT NULL, applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, notes TEXT DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO spec_history_v2 (spec_version, notes) VALUES ('1.0-draft', 'Initial V2 schema, Atlas Nachtschicht 2026-05-16'); SET FOREIGN_KEY_CHECKS=1; -- ============================================================ -- Ende init.sql -- ============================================================