Prechádzať zdrojové kódy

update: subida de proyecto

jordi 4 týždňov pred
commit
5d92b5938b
100 zmenil súbory, kde vykonal 13869 pridanie a 0 odobranie
  1. 19 0
      .claude/settings.local.json
  2. 27 0
      .env
  3. 6 0
      .gitignore
  4. 140 0
      compose.yaml
  5. 9 0
      internal/.claude/settings.local.json
  6. 14 0
      internal/Dockerfile.backend
  7. 14 0
      internal/requirements.txt
  8. 58 0
      internal/schemas/001_admins_table_setup.schema.sql
  9. BIN
      internal/schemas/admins_table.pdf
  10. 121 0
      internal/scripts/seed_admin.py
  11. 0 0
      internal/src/backend/__init__.py
  12. 71 0
      internal/src/backend/app.py
  13. 0 0
      internal/src/backend/core/__init__.py
  14. 23 0
      internal/src/backend/core/config.py
  15. 31 0
      internal/src/backend/core/database.py
  16. 33 0
      internal/src/backend/core/security.py
  17. 0 0
      internal/src/backend/db_models/__init__.py
  18. 59 0
      internal/src/backend/db_models/internal.py
  19. 154 0
      internal/src/backend/db_models/platform.py
  20. 67 0
      internal/src/backend/dependencies.py
  21. 0 0
      internal/src/backend/routers/__init__.py
  22. 143 0
      internal/src/backend/routers/auth.py
  23. 94 0
      internal/src/backend/routers/dataset.py
  24. 130 0
      internal/src/backend/routers/inference.py
  25. 267 0
      internal/src/backend/routers/platform.py
  26. 127 0
      internal/src/backend/routers/predictions.py
  27. 57 0
      internal/src/backend/routers/statistics.py
  28. 655 0
      internal/src/backend/routers/web.py
  29. 0 0
      internal/src/backend/schemas/__init__.py
  30. 26 0
      internal/src/backend/schemas/auth.py
  31. 93 0
      internal/src/backend/schemas/inference.py
  32. 111 0
      internal/src/backend/schemas/platform.py
  33. 308 0
      internal/src/backend/static/style.css
  34. 72 0
      internal/src/backend/templates/base.html
  35. 79 0
      internal/src/backend/templates/estadisticas.html
  36. 117 0
      internal/src/backend/templates/estudiante.html
  37. 137 0
      internal/src/backend/templates/estudiantes.html
  38. 38 0
      internal/src/backend/templates/login.html
  39. 110 0
      internal/src/backend/templates/modelos.html
  40. 134 0
      internal/src/backend/templates/panel.html
  41. 114 0
      internal/src/backend/templates/plataforma.html
  42. 133 0
      internal/src/backend/templates/predicciones.html
  43. 0 0
      internal/src/models/datasets/clean_dataset.csv
  44. 0 0
      internal/src/models/datasets/dataset.csv
  45. 501 0
      internal/src/models/datasets/math_risk_scores.csv
  46. 501 0
      internal/src/models/datasets/nlp_pred_career_recommendation.csv
  47. 501 0
      internal/src/models/datasets/nlp_pred_computer_science.csv
  48. 501 0
      internal/src/models/datasets/nlp_pred_risk_alert.csv
  49. 501 0
      internal/src/models/datasets/nlp_pred_technical_drawing.csv
  50. 501 0
      internal/src/models/datasets/support_level_predictions.csv
  51. BIN
      internal/src/models/models-pkl/linear_avg_final.pkl
  52. BIN
      internal/src/models/models-pkl/linear_mathematics_final.pkl
  53. BIN
      internal/src/models/models-pkl/logistic_grade_tendency.pkl
  54. BIN
      internal/src/models/models-pkl/logistic_mathematics_pass.pkl
  55. BIN
      internal/src/models/models-pkl/logistic_support_level.pkl
  56. BIN
      internal/src/models/models-pkl/scaler_grade_tendency.pkl
  57. BIN
      internal/src/models/models-pkl/scaler_mathematics_pass.pkl
  58. BIN
      internal/src/models/models-pkl/scaler_support_level.pkl
  59. 0 0
      internal/src/models/predictions/dashboard_ml_avg_final.json
  60. 0 0
      internal/src/models/predictions/dashboard_ml_grade_tendency.json
  61. 0 0
      internal/src/models/predictions/dashboard_ml_math_final.json
  62. 0 0
      internal/src/models/predictions/dashboard_ml_math_pass.json
  63. 0 0
      internal/src/models/predictions/dashboard_ml_models.json
  64. 0 0
      internal/src/models/predictions/dashboard_ml_support_level.json
  65. 3200 0
      internal/src/models/predictions/ml-preds.ipynb
  66. 964 0
      internal/src/models/predictions/nlp-preds.ipynb
  67. 0 0
      internal/src/models/statistics/dashboard_attendance.json
  68. 0 0
      internal/src/models/statistics/dashboard_demographics.json
  69. 0 0
      internal/src/models/statistics/dashboard_school_group.json
  70. 0 0
      internal/src/models/statistics/dashboard_student_performance.json
  71. 0 0
      internal/src/models/statistics/dashboard_subject_performance.json
  72. 0 0
      internal/src/models/statistics/dashboard_support.json
  73. 0 0
      internal/src/models/statistics/dashboard_term_progression.json
  74. 18 0
      internal/src/models/statistics/iframe_figures/figure_10.html
  75. 18 0
      internal/src/models/statistics/iframe_figures/figure_12.html
  76. 18 0
      internal/src/models/statistics/iframe_figures/figure_14.html
  77. 18 0
      internal/src/models/statistics/iframe_figures/figure_16.html
  78. 18 0
      internal/src/models/statistics/iframe_figures/figure_163.html
  79. 18 0
      internal/src/models/statistics/iframe_figures/figure_171.html
  80. 18 0
      internal/src/models/statistics/iframe_figures/figure_18.html
  81. 18 0
      internal/src/models/statistics/iframe_figures/figure_20.html
  82. 18 0
      internal/src/models/statistics/iframe_figures/figure_8.html
  83. 227 0
      internal/src/models/statistics/stats.ipynb
  84. 13 0
      user_platform/Dockerfile
  85. 211 0
      user_platform/schemas/001_platform_table_setup.schema.sql
  86. BIN
      user_platform/schemas/platform_table.pdf
  87. 619 0
      user_platform/seed.py
  88. 47 0
      user_platform/src/backend/app.py
  89. 23 0
      user_platform/src/backend/database.py
  90. 157 0
      user_platform/src/backend/models.py
  91. 11 0
      user_platform/src/backend/requirements.txt
  92. 1 0
      user_platform/src/backend/routers/__init__.py
  93. 75 0
      user_platform/src/backend/routers/auth.py
  94. 244 0
      user_platform/src/backend/routers/student.py
  95. 240 0
      user_platform/src/frontend/css/style.css
  96. 273 0
      user_platform/src/frontend/dashboard.html
  97. 56 0
      user_platform/src/frontend/index.html
  98. 442 0
      user_platform/src/frontend/js/app.js
  99. 45 0
      user_platform/src/frontend/js/login.js
  100. 62 0
      user_platform/src/frontend/login.html

+ 19 - 0
.claude/settings.local.json

@@ -0,0 +1,19 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(\"/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final/internal/internal_venv/bin/pip\" freeze)",
+      "Bash(internal_venv/bin/python3 -c ' *)",
+      "Bash(internal_venv/bin/python3 -c \"import sklearn; print\\(sklearn.__version__\\)\")",
+      "Bash(internal_venv/bin/pip install *)",
+      "Bash(INTERNAL_DB_URL=postgresql://x:x@localhost:5555/x PLATFORM_DB_URL=postgresql://x:x@localhost:55557/x JWT_SECRET_KEY=test internal_venv/bin/python3 -c ' *)",
+      "Bash(\"/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final/user_platform/user-platform-venv/bin/pip\" freeze)",
+      "Bash(\"/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final/user_platform/user-platform-venv/bin/pip\" install psycopg2-binary sqlalchemy passlib bcrypt)",
+      "Bash(user-platform-venv/bin/python3 -c ' *)",
+      "Bash(docker compose *)",
+      "Bash(find '/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final' -not -path */internal_venv/* -not -path */user-platform-venv/* -not -path */__pycache__/* -not -path */.git/* -not -path */.DS_Store -type f)",
+      "Bash(find '/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final' -not -path */internal_venv/* -not -path */user-platform-venv/* -not -path */__pycache__/* -not -path */.git/* -not -name .DS_Store -not -path */.claude/* -type f)",
+      "WebFetch(domain:diqueran.net)",
+      "Bash(INTERNAL_DB_URL=postgresql://internal-database-user:v2E61F75zUgdu0Of@localhost:5555/internal-database PLATFORM_DB_URL=postgresql://x:x@localhost:55557/x JWT_SECRET_KEY=3f8a9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a internal_venv/bin/python3 -c ' *)"
+    ]
+  }
+}

+ 27 - 0
.env

@@ -0,0 +1,27 @@
+# ══════════════════════════════════════════════════════════════════
+#  OpenData II — Variables de entorno unificadas
+#  Usado por: compose.yaml raíz
+# ══════════════════════════════════════════════════════════════════
+
+# ── Base de datos interna (admins) ─────────────────────────────────
+INTERNAL_DB_USER=internal-database-user
+INTERNAL_DB_PASSWORD=v2E61F75zUgdu0Of
+INTERNAL_DB_NAME=internal-database
+INTERNAL_DB_PORT=5555
+
+# ── Base de datos de plataforma de usuarios ─────────────────────────
+PLATFORM_DB_USER=user-database-user
+PLATFORM_DB_PASSWORD=M9He5G2ggpfyxyAU
+PLATFORM_DB_NAME=user-database
+PLATFORM_DB_PORT=55557
+
+# ── MinIO ────────────────────────────────────────────────────────────
+MINIO_ROOT_USER=user-minio-database
+MINIO_ROOT_PASSWORD=ocR4bo7A60phIy5W
+MINIO_PORT=9876
+MINIO_CONSOLE_PORT=9871
+
+# ── Backend FastAPI ───────────────────────────────────────────────────
+BACKEND_PORT=8000
+JWT_SECRET_KEY=3f8a9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a
+JWT_ACCESS_TOKEN_EXPIRE_MINUTES=480

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+__pycache__/
+*.py[cod]
+*$py.class
+.vscode/
+.idea/
+.DS_Store

+ 140 - 0
compose.yaml

@@ -0,0 +1,140 @@
+services:
+
+  # ── Base de datos interna (admins, sesiones, logs) ─────────────────────────────
+  internal_db:
+    image: postgres:16
+    container_name: internal_postgres_db
+    restart: unless-stopped
+
+    environment:
+      POSTGRES_USER: ${INTERNAL_DB_USER}
+      POSTGRES_PASSWORD: ${INTERNAL_DB_PASSWORD}
+      POSTGRES_DB: ${INTERNAL_DB_NAME}
+
+    ports:
+      - "${INTERNAL_DB_PORT}:5432"
+
+    volumes:
+      - internal_db_data:/var/lib/postgresql/data
+      - ./internal/schemas/001_admins_table_setup.schema.sql:/docker-entrypoint-initdb.d/001_init.sql:ro
+
+    networks:
+      - opendata_network
+
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U ${INTERNAL_DB_USER} -d ${INTERNAL_DB_NAME}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+
+  # ── Base de datos de plataforma de usuarios ────────────────────────────────────
+  platform_db:
+    image: postgres:16
+    container_name: user_postgres_db
+    restart: unless-stopped
+
+    environment:
+      POSTGRES_USER: ${PLATFORM_DB_USER}
+      POSTGRES_PASSWORD: ${PLATFORM_DB_PASSWORD}
+      POSTGRES_DB: ${PLATFORM_DB_NAME}
+
+    ports:
+      - "${PLATFORM_DB_PORT}:5432"
+
+    volumes:
+      - platform_db_data:/var/lib/postgresql/data
+      - ./user_platform/schemas/001_platform_table_setup.schema.sql:/docker-entrypoint-initdb.d/001_init.sql:ro
+
+    networks:
+      - opendata_network
+
+    healthcheck:
+      test: ["CMD-SHELL", "pg_isready -U ${PLATFORM_DB_USER} -d ${PLATFORM_DB_NAME}"]
+      interval: 10s
+      timeout: 5s
+      retries: 5
+
+  # ── MinIO (almacenamiento de archivos) ─────────────────────────────────────────
+  minio:
+    image: minio/minio:latest
+    container_name: minio
+    restart: unless-stopped
+
+    command: server /data --console-address ":9001"
+
+    environment:
+      MINIO_ROOT_USER: ${MINIO_ROOT_USER}
+      MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD}
+
+    ports:
+      - "${MINIO_PORT}:9000"
+      - "${MINIO_CONSOLE_PORT}:9001"
+
+    volumes:
+      - minio_data:/data
+
+    networks:
+      - opendata_network
+
+    healthcheck:
+      test: ["CMD", "mc", "ready", "local"]
+      interval: 15s
+      timeout: 5s
+      retries: 5
+
+  # ── Backend interno (FastAPI) ──────────────────────────────────────────────────
+  backend:
+    build:
+      context: ./internal
+      dockerfile: Dockerfile.backend
+    container_name: internal_backend
+    restart: unless-stopped
+
+    environment:
+      JWT_SECRET_KEY: ${JWT_SECRET_KEY}
+      JWT_ACCESS_TOKEN_EXPIRE_MINUTES: ${JWT_ACCESS_TOKEN_EXPIRE_MINUTES}
+      INTERNAL_DB_URL: postgresql://${INTERNAL_DB_USER}:${INTERNAL_DB_PASSWORD}@internal_db:5432/${INTERNAL_DB_NAME}
+      PLATFORM_DB_URL: postgresql://${PLATFORM_DB_USER}:${PLATFORM_DB_PASSWORD}@platform_db:5432/${PLATFORM_DB_NAME}
+      MODELS_BASE_PATH: /app/models
+
+    ports:
+      - "${BACKEND_PORT}:8000"
+
+    depends_on:
+      internal_db:
+        condition: service_healthy
+      platform_db:
+        condition: service_healthy
+
+    networks:
+      - opendata_network
+
+  # ── Backend de plataforma de usuarios (FastAPI + Frontend) ─────────────────────
+  user_backend:
+    build:
+      context: ./user_platform
+      dockerfile: Dockerfile
+    container_name: user_platform_backend
+    restart: unless-stopped
+
+    environment:
+      PLATFORM_DB_URL: postgresql://${PLATFORM_DB_USER}:${PLATFORM_DB_PASSWORD}@platform_db:5432/${PLATFORM_DB_NAME}
+
+    ports:
+      - "8001:8000"
+
+    depends_on:
+      platform_db:
+        condition: service_healthy
+
+    networks:
+      - opendata_network
+
+volumes:
+  internal_db_data:
+  platform_db_data:
+  minio_data:
+
+networks:
+  opendata_network:
+    driver: bridge

+ 9 - 0
internal/.claude/settings.local.json

@@ -0,0 +1,9 @@
+{
+  "permissions": {
+    "allow": [
+      "Bash(python3 -c \"import json; nb=json.load\\(open\\('/Users/diqueran1/Desktop/Universidad/2do año/2do Cuatri/Open Data II/Proyecto Final/internal/models/statistics/prelim.ipynb'\\)\\); print\\([c['id'] for c in nb['cells']]\\)\")",
+      "Bash(python3 -c ' *)",
+      "Bash(python3 -c \"import sys,csv; r=csv.reader\\(sys.stdin\\); print\\(next\\(r\\)\\)\")"
+    ]
+  }
+}

+ 14 - 0
internal/Dockerfile.backend

@@ -0,0 +1,14 @@
+FROM python:3.12-slim
+
+WORKDIR /app
+
+RUN apt-get update && apt-get install -y --no-install-recommends \
+    gcc libpq-dev \
+    && rm -rf /var/lib/apt/lists/*
+
+COPY requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+COPY src/ ./
+
+CMD ["uvicorn", "backend.app:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

+ 14 - 0
internal/requirements.txt

@@ -0,0 +1,14 @@
+fastapi==0.115.12
+uvicorn[standard]==0.32.1
+python-jose[cryptography]==3.3.0
+bcrypt==4.2.1
+psycopg2-binary==2.9.10
+sqlalchemy==2.0.36
+python-multipart==0.0.20
+pydantic-settings==2.7.0
+jinja2==3.1.6
+aiofiles==24.1.0
+pandas==3.0.2
+scikit-learn==1.8.0
+joblib==1.4.2
+numpy>=2.0

+ 58 - 0
internal/schemas/001_admins_table_setup.schema.sql

@@ -0,0 +1,58 @@
+DROP SCHEMA public CASCADE;
+CREATE SCHEMA public;
+
+CREATE TABLE roles_admin (
+    id_rol SERIAL PRIMARY KEY,
+    nombre VARCHAR(50) UNIQUE NOT NULL
+);
+
+CREATE TABLE usuarios_admin (
+    id_usuario SERIAL PRIMARY KEY,
+    username VARCHAR(100) UNIQUE NOT NULL,
+    email VARCHAR(150) UNIQUE NOT NULL,
+    password_hash TEXT NOT NULL,
+    nombre VARCHAR(100),
+    activo BOOLEAN DEFAULT TRUE,
+    ultimo_login TIMESTAMP,
+    fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    id_rol INT NOT NULL,
+
+    CONSTRAINT fk_admin_rol
+        FOREIGN KEY(id_rol)
+        REFERENCES roles_admin(id_rol)
+);
+
+CREATE TABLE sesiones_admin (
+    id_sesion SERIAL PRIMARY KEY,
+    id_usuario INT NOT NULL,
+    token TEXT NOT NULL,
+    ip VARCHAR(100),
+    user_agent TEXT,
+    fecha_inicio TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    fecha_expiracion TIMESTAMP,
+
+    CONSTRAINT fk_sesion_usuario
+        FOREIGN KEY(id_usuario)
+        REFERENCES usuarios_admin(id_usuario)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE logs_admin (
+    id_log SERIAL PRIMARY KEY,
+    id_usuario INT,
+    accion VARCHAR(255),
+    descripcion TEXT,
+    fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+
+    CONSTRAINT fk_log_usuario
+        FOREIGN KEY(id_usuario)
+        REFERENCES usuarios_admin(id_usuario)
+        ON DELETE SET NULL
+);
+
+INSERT INTO roles_admin(nombre)
+VALUES
+('superadmin'),
+('admin'),
+('moderador'),
+('viewer');

BIN
internal/schemas/admins_table.pdf


+ 121 - 0
internal/scripts/seed_admin.py

@@ -0,0 +1,121 @@
+#!/usr/bin/env python3
+"""
+Crea el primer usuario superadmin en la base de datos interna.
+
+Uso (desde internal/):
+    python scripts/seed_admin.py
+
+Requiere que las variables de entorno estén configuradas (lee .env automáticamente).
+La base de datos PostgreSQL debe estar corriendo.
+"""
+
+import sys
+import os
+
+# Permite ejecutar el script desde internal/ o desde la raíz
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+
+# Lee el .env manualmente para conexión local (sin depender de python-dotenv)
+def _read_env(path: str) -> dict[str, str]:
+    result: dict[str, str] = {}
+    if os.path.exists(path):
+        with open(path) as f:
+            for line in f:
+                line = line.strip()
+                if line and not line.startswith("#") and "=" in line:
+                    key, _, val = line.partition("=")
+                    result[key.strip()] = val.strip()
+    return result
+
+# Lee el .env raíz del proyecto
+_here = os.path.dirname(os.path.abspath(__file__))
+env = _read_env(os.path.join(_here, "..", "..", ".env"))
+
+pg_user = env.get("INTERNAL_DB_USER", "internal-database-user")
+pg_pass = env.get("INTERNAL_DB_PASSWORD", "")
+pg_db   = env.get("INTERNAL_DB_NAME", "internal-database")
+pg_port = env.get("INTERNAL_DB_PORT", "5555")
+jwt_key = env.get("JWT_SECRET_KEY", "seed-only")
+plat_user = env.get("PLATFORM_DB_USER", "x")
+plat_pass = env.get("PLATFORM_DB_PASSWORD", "x")
+plat_db   = env.get("PLATFORM_DB_NAME", "x")
+plat_port = env.get("PLATFORM_DB_PORT", "55557")
+
+os.environ.setdefault("INTERNAL_DB_URL", f"postgresql://{pg_user}:{pg_pass}@localhost:{pg_port}/{pg_db}")
+os.environ.setdefault("PLATFORM_DB_URL", f"postgresql://{plat_user}:{plat_pass}@localhost:{plat_port}/{plat_db}")
+os.environ.setdefault("JWT_SECRET_KEY", jwt_key)
+
+from src.backend.core.database import internal_engine, InternalSession  # noqa: E402
+from src.backend.core.security import hash_password  # noqa: E402
+from src.backend.db_models.internal import InternalBase, LogAdmin, RolAdmin, UsuarioAdmin  # noqa: E402
+
+
+def seed():
+    print("\n=== Seed: Administrador Interno ===\n")
+
+    # Crea las tablas si no existen
+    InternalBase.metadata.create_all(bind=internal_engine)
+    print("✓ Esquema verificado")
+
+    db = InternalSession()
+    try:
+        # Roles
+        roles_existentes = db.query(RolAdmin).count()
+        if roles_existentes == 0:
+            for nombre in ("superadmin", "admin", "moderador", "viewer"):
+                db.add(RolAdmin(nombre=nombre))
+            db.commit()
+            print("✓ Roles creados: superadmin, admin, moderador, viewer")
+        else:
+            print(f"  Roles ya existentes ({roles_existentes})")
+
+        # Superadmin
+        username = input("\nUsername [admin]: ").strip() or "admin"
+        existing = db.query(UsuarioAdmin).filter(UsuarioAdmin.username == username).first()
+        if existing:
+            print(f"\n⚠ El usuario '{username}' ya existe. Abortando.")
+            return
+
+        email = input("Email: ").strip()
+        while not email:
+            email = input("Email (requerido): ").strip()
+
+        password = input("Contraseña: ").strip()
+        while len(password) < 8:
+            password = input("Contraseña (mínimo 8 caracteres): ").strip()
+
+        nombre = input("Nombre completo [Administrador]: ").strip() or "Administrador"
+
+        rol = db.query(RolAdmin).filter(RolAdmin.nombre == "superadmin").first()
+
+        user = UsuarioAdmin(
+            username=username,
+            email=email,
+            password_hash=hash_password(password),
+            nombre=nombre,
+            id_rol=rol.id_rol,
+        )
+        db.add(user)
+        db.commit()
+        db.refresh(user)
+
+        db.add(LogAdmin(
+            id_usuario=user.id_usuario,
+            accion="SEED",
+            descripcion="Usuario superadmin creado via seed script",
+        ))
+        db.commit()
+
+        print(f"\n✓ Usuario '{username}' ({email}) creado con rol superadmin")
+        print("  Ya puedes hacer login en POST /auth/login\n")
+
+    except Exception as e:
+        db.rollback()
+        print(f"\n✗ Error: {e}")
+        raise
+    finally:
+        db.close()
+
+
+if __name__ == "__main__":
+    seed()

+ 0 - 0
internal/src/backend/__init__.py


+ 71 - 0
internal/src/backend/app.py

@@ -0,0 +1,71 @@
+from contextlib import asynccontextmanager
+from pathlib import Path
+
+from fastapi import FastAPI
+from fastapi.middleware.cors import CORSMiddleware
+from fastapi.staticfiles import StaticFiles
+
+from .core.config import get_settings
+from .routers import auth, dataset, inference, platform, predictions, statistics, web
+
+settings = get_settings()
+
+
+@asynccontextmanager
+async def lifespan(app: FastAPI):
+    # Pre-load ML models on startup to avoid cold-start latency
+    from .routers.inference import _load_models
+    _load_models()
+    yield
+
+
+app = FastAPI(
+    title="OpenData II — Panel Interno",
+    description=(
+        "API interna para administradores: estadísticas descriptivas, "
+        "dashboards ML/NLP, inferencia de modelos en tiempo real y "
+        "acceso completo a la base de datos de la plataforma de usuarios."
+    ),
+    version="1.0.0",
+    lifespan=lifespan,
+    docs_url="/docs",
+    redoc_url="/redoc",
+)
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+app.mount("/static", StaticFiles(directory=Path(__file__).parent / "static"), name="static")
+
+# Web panel (HTML — cookie auth) — must be included BEFORE JSON API routers
+# so that GET "/" is handled by the web router, not the JSON root.
+app.include_router(web.router)
+
+# JSON API routers
+app.include_router(auth.router)
+app.include_router(statistics.router)
+app.include_router(predictions.router)
+app.include_router(inference.router)
+app.include_router(dataset.router)
+app.include_router(platform.router)
+
+
+@app.get("/health", tags=["Root"], summary="Estado del servicio")
+def health():
+    return {"status": "ok"}
+
+
+@app.get("/api", tags=["Root"], summary="Información de la API JSON")
+def api_info():
+    return {
+        "service": "OpenData II — Internal API",
+        "version": "1.0.0",
+        "docs": "/docs",
+        "redoc": "/redoc",
+        "panel": "/panel",
+    }

+ 0 - 0
internal/src/backend/core/__init__.py


+ 23 - 0
internal/src/backend/core/config.py

@@ -0,0 +1,23 @@
+from functools import lru_cache
+from pydantic_settings import BaseSettings, SettingsConfigDict
+
+
+class Settings(BaseSettings):
+    # JWT
+    jwt_secret_key: str = "changeme-use-a-strong-secret-in-production"
+    jwt_algorithm: str = "HS256"
+    jwt_access_token_expire_minutes: int = 480  # 8 hours
+
+    # Database URLs
+    internal_db_url: str = "postgresql://internal-database-user:v2E61F75zUgdu0Of@localhost:5555/internal-database"
+    platform_db_url: str = "postgresql://user-database-user:M9He5G2ggpfyxyAU@localhost:55557/user-database"
+
+    # Paths (override via env var in Docker)
+    models_base_path: str = "/app/models"
+
+    model_config = SettingsConfigDict(env_file=".env", extra="ignore")
+
+
+@lru_cache
+def get_settings() -> Settings:
+    return Settings()

+ 31 - 0
internal/src/backend/core/database.py

@@ -0,0 +1,31 @@
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker, DeclarativeBase
+
+from .config import get_settings
+
+settings = get_settings()
+
+internal_engine = create_engine(
+    settings.internal_db_url,
+    pool_pre_ping=True,
+    pool_size=5,
+    max_overflow=10,
+)
+
+platform_engine = create_engine(
+    settings.platform_db_url,
+    pool_pre_ping=True,
+    pool_size=5,
+    max_overflow=10,
+)
+
+InternalSession = sessionmaker(bind=internal_engine, autoflush=False, autocommit=False)
+PlatformSession = sessionmaker(bind=platform_engine, autoflush=False, autocommit=False)
+
+
+class InternalBase(DeclarativeBase):
+    pass
+
+
+class PlatformBase(DeclarativeBase):
+    pass

+ 33 - 0
internal/src/backend/core/security.py

@@ -0,0 +1,33 @@
+from datetime import datetime, timedelta, timezone
+from typing import Optional
+
+import bcrypt
+from jose import JWTError, jwt
+
+from .config import get_settings
+
+settings = get_settings()
+
+
+def verify_password(plain: str, hashed: str) -> bool:
+    return bcrypt.checkpw(plain.encode(), hashed.encode())
+
+
+def hash_password(password: str) -> str:
+    return bcrypt.hashpw(password.encode(), bcrypt.gensalt()).decode()
+
+
+def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
+    to_encode = data.copy()
+    expire = datetime.now(timezone.utc) + (
+        expires_delta or timedelta(minutes=settings.jwt_access_token_expire_minutes)
+    )
+    to_encode["exp"] = expire
+    return jwt.encode(to_encode, settings.jwt_secret_key, algorithm=settings.jwt_algorithm)
+
+
+def decode_access_token(token: str) -> Optional[dict]:
+    try:
+        return jwt.decode(token, settings.jwt_secret_key, algorithms=[settings.jwt_algorithm])
+    except JWTError:
+        return None

+ 0 - 0
internal/src/backend/db_models/__init__.py


+ 59 - 0
internal/src/backend/db_models/internal.py

@@ -0,0 +1,59 @@
+from datetime import datetime
+
+from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, String, Text
+from sqlalchemy.orm import relationship
+
+from ..core.database import InternalBase
+
+
+class RolAdmin(InternalBase):
+    __tablename__ = "roles_admin"
+
+    id_rol = Column(Integer, primary_key=True, index=True)
+    nombre = Column(String(50), unique=True, nullable=False)
+
+    usuarios = relationship("UsuarioAdmin", back_populates="rol")
+
+
+class UsuarioAdmin(InternalBase):
+    __tablename__ = "usuarios_admin"
+
+    id_usuario = Column(Integer, primary_key=True, index=True)
+    username = Column(String(100), unique=True, nullable=False, index=True)
+    email = Column(String(150), unique=True, nullable=False)
+    password_hash = Column(Text, nullable=False)
+    nombre = Column(String(100))
+    activo = Column(Boolean, default=True)
+    ultimo_login = Column(DateTime, nullable=True)
+    fecha_creacion = Column(DateTime, default=datetime.utcnow)
+    id_rol = Column(Integer, ForeignKey("roles_admin.id_rol"), nullable=False)
+
+    rol = relationship("RolAdmin", back_populates="usuarios")
+    sesiones = relationship("SesionAdmin", back_populates="usuario", cascade="all, delete-orphan")
+    logs = relationship("LogAdmin", back_populates="usuario")
+
+
+class SesionAdmin(InternalBase):
+    __tablename__ = "sesiones_admin"
+
+    id_sesion = Column(Integer, primary_key=True, index=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios_admin.id_usuario", ondelete="CASCADE"), nullable=False)
+    token = Column(Text, nullable=False, index=True)
+    ip = Column(String(100), nullable=True)
+    user_agent = Column(Text, nullable=True)
+    fecha_inicio = Column(DateTime, default=datetime.utcnow)
+    fecha_expiracion = Column(DateTime, nullable=True)
+
+    usuario = relationship("UsuarioAdmin", back_populates="sesiones")
+
+
+class LogAdmin(InternalBase):
+    __tablename__ = "logs_admin"
+
+    id_log = Column(Integer, primary_key=True, index=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios_admin.id_usuario", ondelete="SET NULL"), nullable=True)
+    accion = Column(String(255))
+    descripcion = Column(Text, nullable=True)
+    fecha = Column(DateTime, default=datetime.utcnow)
+
+    usuario = relationship("UsuarioAdmin", back_populates="logs")

+ 154 - 0
internal/src/backend/db_models/platform.py

@@ -0,0 +1,154 @@
+from datetime import datetime
+from decimal import Decimal
+
+from sqlalchemy import (
+    Boolean, Column, DateTime, ForeignKey, Integer,
+    Numeric, String, Text, UniqueConstraint,
+)
+from sqlalchemy.orm import relationship
+
+from ..core.database import PlatformBase
+
+
+class Rol(PlatformBase):
+    __tablename__ = "roles"
+
+    id_rol = Column(Integer, primary_key=True, index=True)
+    nombre = Column(String(50), unique=True, nullable=False)
+
+    usuarios = relationship("Usuario", back_populates="rol")
+
+
+class Usuario(PlatformBase):
+    __tablename__ = "usuarios"
+
+    id_usuario = Column(Integer, primary_key=True, index=True)
+    nombre = Column(String(100), nullable=False)
+    apellidos = Column(String(150), nullable=True)
+    email = Column(String(150), unique=True, nullable=False)
+    fecha_creacion = Column(DateTime, default=datetime.utcnow)
+    activo = Column(Boolean, default=True)
+    id_rol = Column(Integer, ForeignKey("roles.id_rol"), nullable=False)
+
+    rol = relationship("Rol", back_populates="usuarios")
+    credencial = relationship("Credencial", back_populates="usuario", uselist=False)
+    profesor = relationship("Profesor", back_populates="usuario", uselist=False)
+    alumno = relationship("Alumno", back_populates="usuario", uselist=False)
+
+
+class Credencial(PlatformBase):
+    __tablename__ = "credenciales"
+
+    id_credencial = Column(Integer, primary_key=True, index=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True, nullable=False)
+    password_hash = Column(Text, nullable=False)
+    ultimo_login = Column(DateTime, nullable=True)
+
+    usuario = relationship("Usuario", back_populates="credencial")
+
+
+class Profesor(PlatformBase):
+    __tablename__ = "profesores"
+
+    id_profesor = Column(Integer, primary_key=True, index=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True, nullable=False)
+    especialidad = Column(String(100), nullable=True)
+
+    usuario = relationship("Usuario", back_populates="profesor")
+    cursos = relationship("Curso", back_populates="profesor")
+
+
+class Alumno(PlatformBase):
+    __tablename__ = "alumnos"
+
+    id_alumno = Column(Integer, primary_key=True, index=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True, nullable=False)
+    nivel = Column(String(50), nullable=True)
+
+    usuario = relationship("Usuario", back_populates="alumno")
+    matriculas = relationship("Matricula", back_populates="alumno")
+    entregas = relationship("Entrega", back_populates="alumno")
+    progreso = relationship("Progreso", back_populates="alumno")
+
+
+class Curso(PlatformBase):
+    __tablename__ = "cursos"
+
+    id_curso = Column(Integer, primary_key=True, index=True)
+    nombre = Column(String(150), nullable=False)
+    descripcion = Column(Text, nullable=True)
+    fecha_creacion = Column(DateTime, default=datetime.utcnow)
+    id_profesor = Column(Integer, ForeignKey("profesores.id_profesor"), nullable=False)
+
+    profesor = relationship("Profesor", back_populates="cursos")
+    matriculas = relationship("Matricula", back_populates="curso")
+    actividades = relationship("Actividad", back_populates="curso")
+    progreso = relationship("Progreso", back_populates="curso")
+
+
+class Matricula(PlatformBase):
+    __tablename__ = "matriculas"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+
+    id_matricula = Column(Integer, primary_key=True, index=True)
+    id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), nullable=False)
+    id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"), nullable=False)
+    fecha_matricula = Column(DateTime, default=datetime.utcnow)
+
+    alumno = relationship("Alumno", back_populates="matriculas")
+    curso = relationship("Curso", back_populates="matriculas")
+
+
+class Actividad(PlatformBase):
+    __tablename__ = "actividades"
+
+    id_actividad = Column(Integer, primary_key=True, index=True)
+    titulo = Column(String(200), nullable=False)
+    descripcion = Column(Text, nullable=True)
+    fecha_publicacion = Column(DateTime, default=datetime.utcnow)
+    fecha_entrega = Column(DateTime, nullable=True)
+    puntuacion_maxima = Column(Numeric(5, 2), nullable=True)
+    id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"), nullable=False)
+
+    curso = relationship("Curso", back_populates="actividades")
+    entregas = relationship("Entrega", back_populates="actividad")
+
+
+class Entrega(PlatformBase):
+    __tablename__ = "entregas"
+
+    id_entrega = Column(Integer, primary_key=True, index=True)
+    id_actividad = Column(Integer, ForeignKey("actividades.id_actividad", ondelete="CASCADE"), nullable=False)
+    id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), nullable=False)
+    fecha_entrega = Column(DateTime, default=datetime.utcnow)
+    contenido = Column(Text, nullable=True)
+    estado = Column(String(50), default="pendiente")
+
+    actividad = relationship("Actividad", back_populates="entregas")
+    alumno = relationship("Alumno", back_populates="entregas")
+    calificacion = relationship("Calificacion", back_populates="entrega", uselist=False)
+
+
+class Calificacion(PlatformBase):
+    __tablename__ = "calificaciones"
+
+    id_calificacion = Column(Integer, primary_key=True, index=True)
+    id_entrega = Column(Integer, ForeignKey("entregas.id_entrega", ondelete="CASCADE"), unique=True, nullable=False)
+    nota = Column(Numeric(5, 2), nullable=True)
+    observaciones = Column(Text, nullable=True)
+    fecha_calificacion = Column(DateTime, default=datetime.utcnow)
+
+    entrega = relationship("Entrega", back_populates="calificacion")
+
+
+class Progreso(PlatformBase):
+    __tablename__ = "progreso"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+
+    id_progreso = Column(Integer, primary_key=True, index=True)
+    id_alumno = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), nullable=False)
+    id_curso = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"), nullable=False)
+    porcentaje = Column(Numeric(5, 2), default=0)
+
+    alumno = relationship("Alumno", back_populates="progreso")
+    curso = relationship("Curso", back_populates="progreso")

+ 67 - 0
internal/src/backend/dependencies.py

@@ -0,0 +1,67 @@
+from datetime import datetime, timezone
+
+from fastapi import Depends, HTTPException, status
+from fastapi.security import OAuth2PasswordBearer
+from sqlalchemy.orm import Session
+
+from .core.database import InternalSession, PlatformSession
+from .core.security import decode_access_token
+from .db_models.internal import SesionAdmin, UsuarioAdmin
+
+oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
+
+
+def get_internal_db():
+    db = InternalSession()
+    try:
+        yield db
+    finally:
+        db.close()
+
+
+def get_platform_db():
+    db = PlatformSession()
+    try:
+        yield db
+    finally:
+        db.close()
+
+
+def get_current_admin(
+    token: str = Depends(oauth2_scheme),
+    db: Session = Depends(get_internal_db),
+) -> UsuarioAdmin:
+    credentials_exception = HTTPException(
+        status_code=status.HTTP_401_UNAUTHORIZED,
+        detail="Credenciales inválidas o expiradas",
+        headers={"WWW-Authenticate": "Bearer"},
+    )
+
+    payload = decode_access_token(token)
+    if payload is None:
+        raise credentials_exception
+
+    user_id = payload.get("sub")
+    if user_id is None:
+        raise credentials_exception
+
+    session = (
+        db.query(SesionAdmin)
+        .filter(
+            SesionAdmin.token == token,
+            SesionAdmin.fecha_expiracion > datetime.now(timezone.utc),
+        )
+        .first()
+    )
+    if session is None:
+        raise credentials_exception
+
+    user = (
+        db.query(UsuarioAdmin)
+        .filter(UsuarioAdmin.id_usuario == user_id, UsuarioAdmin.activo == True)
+        .first()
+    )
+    if user is None:
+        raise credentials_exception
+
+    return user

+ 0 - 0
internal/src/backend/routers/__init__.py


+ 143 - 0
internal/src/backend/routers/auth.py

@@ -0,0 +1,143 @@
+from datetime import datetime, timedelta, timezone
+
+from fastapi import APIRouter, Depends, HTTPException, Request, status
+from sqlalchemy.orm import Session
+
+from ..core.config import get_settings
+from ..core.security import create_access_token, verify_password
+from ..db_models.internal import LogAdmin, SesionAdmin, UsuarioAdmin
+from ..dependencies import get_current_admin, get_internal_db, oauth2_scheme
+from ..schemas.auth import AdminInfo, LoginRequest, TokenResponse
+
+router = APIRouter(prefix="/auth", tags=["Autenticación"])
+settings = get_settings()
+
+
+@router.post("/login", response_model=TokenResponse, summary="Iniciar sesión")
+def login(request: Request, body: LoginRequest, db: Session = Depends(get_internal_db)):
+    user = (
+        db.query(UsuarioAdmin)
+        .filter(
+            (UsuarioAdmin.username == body.username)
+            | (UsuarioAdmin.email == body.username)
+        )
+        .first()
+    )
+
+    if not user or not verify_password(body.password, user.password_hash):
+        raise HTTPException(
+            status_code=status.HTTP_401_UNAUTHORIZED,
+            detail="Usuario o contraseña incorrectos",
+        )
+
+    if not user.activo:
+        raise HTTPException(
+            status_code=status.HTTP_403_FORBIDDEN,
+            detail="Cuenta desactivada",
+        )
+
+    expires = timedelta(minutes=settings.jwt_access_token_expire_minutes)
+    token = create_access_token({"sub": str(user.id_usuario), "username": user.username}, expires)
+
+    client_ip = request.client.host if request.client else None
+    session = SesionAdmin(
+        id_usuario=user.id_usuario,
+        token=token,
+        ip=client_ip,
+        user_agent=request.headers.get("user-agent"),
+        fecha_expiracion=datetime.now(timezone.utc) + expires,
+    )
+    db.add(session)
+
+    user.ultimo_login = datetime.now(timezone.utc)
+
+    db.add(LogAdmin(
+        id_usuario=user.id_usuario,
+        accion="LOGIN",
+        descripcion=f"Login desde {client_ip or 'desconocido'}",
+    ))
+    db.commit()
+
+    return TokenResponse(
+        access_token=token,
+        expires_in=settings.jwt_access_token_expire_minutes * 60,
+    )
+
+
+@router.post("/logout", summary="Cerrar sesión")
+def logout(
+    token: str = Depends(oauth2_scheme),
+    current_admin: UsuarioAdmin = Depends(get_current_admin),
+    db: Session = Depends(get_internal_db),
+):
+    db.query(SesionAdmin).filter(SesionAdmin.token == token).delete()
+    db.add(LogAdmin(
+        id_usuario=current_admin.id_usuario,
+        accion="LOGOUT",
+        descripcion="Sesión cerrada",
+    ))
+    db.commit()
+    return {"message": "Sesión cerrada correctamente"}
+
+
+@router.get("/me", response_model=AdminInfo, summary="Información del administrador actual")
+def me(current_admin: UsuarioAdmin = Depends(get_current_admin)):
+    return AdminInfo(
+        id_usuario=current_admin.id_usuario,
+        username=current_admin.username,
+        email=current_admin.email,
+        nombre=current_admin.nombre,
+        rol=current_admin.rol.nombre,
+        activo=current_admin.activo,
+        ultimo_login=current_admin.ultimo_login,
+        fecha_creacion=current_admin.fecha_creacion,
+    )
+
+
+@router.get("/sessions", summary="Sesiones activas del administrador actual")
+def get_sessions(
+    current_admin: UsuarioAdmin = Depends(get_current_admin),
+    db: Session = Depends(get_internal_db),
+):
+    sessions = (
+        db.query(SesionAdmin)
+        .filter(
+            SesionAdmin.id_usuario == current_admin.id_usuario,
+            SesionAdmin.fecha_expiracion > datetime.now(timezone.utc),
+        )
+        .all()
+    )
+    return [
+        {
+            "id_sesion": s.id_sesion,
+            "ip": s.ip,
+            "user_agent": s.user_agent,
+            "fecha_inicio": s.fecha_inicio,
+            "fecha_expiracion": s.fecha_expiracion,
+        }
+        for s in sessions
+    ]
+
+
+@router.get("/logs", summary="Registro de actividad del administrador actual")
+def get_logs(
+    current_admin: UsuarioAdmin = Depends(get_current_admin),
+    db: Session = Depends(get_internal_db),
+    limit: int = 100,
+):
+    logs = (
+        db.query(LogAdmin)
+        .filter(LogAdmin.id_usuario == current_admin.id_usuario)
+        .order_by(LogAdmin.fecha.desc())
+        .limit(limit)
+        .all()
+    )
+    return [
+        {
+            "id_log": log.id_log,
+            "accion": log.accion,
+            "descripcion": log.descripcion,
+            "fecha": log.fecha,
+        }
+        for log in logs
+    ]

+ 94 - 0
internal/src/backend/routers/dataset.py

@@ -0,0 +1,94 @@
+from pathlib import Path
+from typing import Optional
+
+import pandas as pd
+from fastapi import APIRouter, Depends, HTTPException, Query
+
+from ..core.config import get_settings
+from ..db_models.internal import UsuarioAdmin
+from ..dependencies import get_current_admin
+
+router = APIRouter(prefix="/dataset", tags=["Dataset de Estudiantes"])
+settings = get_settings()
+
+DATASETS_DIR = Path(settings.models_base_path) / "datasets"
+CLEAN_CSV = DATASETS_DIR / "clean_dataset.csv"
+
+
+def _load_clean() -> pd.DataFrame:
+    return pd.read_csv(CLEAN_CSV)
+
+
+@router.get("/summary", summary="Resumen estadístico del dataset")
+def get_summary(_: UsuarioAdmin = Depends(get_current_admin)):
+    df = _load_clean()
+    return {
+        "total_students": len(df),
+        "countries": df["country"].value_counts().to_dict(),
+        "courses": df["course"].value_counts().to_dict(),
+        "gender_distribution": df["gender"].value_counts().to_dict(),
+        "support_level_distribution": df["support_level"].value_counts().to_dict(),
+        "grade_tendency_distribution": df["grade_tendency"].value_counts().to_dict(),
+        "avg_final": {
+            "mean": round(df["avg_final"].mean(), 3),
+            "std": round(df["avg_final"].std(), 3),
+            "min": round(df["avg_final"].min(), 3),
+            "max": round(df["avg_final"].max(), 3),
+        },
+        "attendance_absences": {
+            "mean": round(df["attendance_absences_total"].mean(), 2),
+            "max": int(df["attendance_absences_total"].max()),
+        },
+        "pass_rate_mean": round(df["passed_rate_percent"].mean(), 2),
+    }
+
+
+@router.get("/students", summary="Lista de estudiantes con filtros opcionales")
+def get_students(
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=500),
+    country: Optional[str] = Query(None),
+    course: Optional[str] = Query(None),
+    gender: Optional[str] = Query(None),
+    support_level: Optional[str] = Query(None),
+    grade_tendency: Optional[str] = Query(None),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    df = _load_clean()
+
+    if country:
+        df = df[df["country"].str.lower() == country.lower()]
+    if course:
+        df = df[df["course"].str.lower() == course.lower()]
+    if gender:
+        df = df[df["gender"].str.upper() == gender.upper()]
+    if support_level:
+        df = df[df["support_level"].str.lower() == support_level.lower()]
+    if grade_tendency:
+        df = df[df["grade_tendency"].str.lower() == grade_tendency.lower()]
+
+    total = len(df)
+    start = (page - 1) * page_size
+    page_df = df.iloc[start : start + page_size]
+
+    return {
+        "total": total,
+        "page": page,
+        "page_size": page_size,
+        "items": page_df.fillna("").to_dict(orient="records"),
+    }
+
+
+@router.get("/students/{student_id}", summary="Datos completos de un estudiante")
+def get_student(student_id: str, _: UsuarioAdmin = Depends(get_current_admin)):
+    df = _load_clean()
+    row = df[df["student_id"] == student_id]
+    if row.empty:
+        raise HTTPException(status_code=404, detail=f"Estudiante '{student_id}' no encontrado")
+    return row.fillna("").to_dict(orient="records")[0]
+
+
+@router.get("/fields", summary="Lista de columnas del dataset")
+def get_fields(_: UsuarioAdmin = Depends(get_current_admin)):
+    df = _load_clean()
+    return {"fields": list(df.columns)}

+ 130 - 0
internal/src/backend/routers/inference.py

@@ -0,0 +1,130 @@
+from functools import lru_cache
+from pathlib import Path
+from typing import Any
+
+import joblib
+import numpy as np
+from fastapi import APIRouter, Depends, HTTPException
+
+from ..core.config import get_settings
+from ..db_models.internal import UsuarioAdmin
+from ..dependencies import get_current_admin
+from ..schemas.inference import (
+    AvgFinalInput, AvgFinalOutput,
+    GradeTendencyInput, GradeTendencyOutput,
+    MathFinalInput, MathFinalOutput,
+    MathPassInput, MathPassOutput,
+    SupportLevelInput, SupportLevelOutput,
+)
+
+router = APIRouter(prefix="/inference", tags=["Inferencia de Modelos"])
+settings = get_settings()
+
+ENCODING_INFO = {
+    "gender_encoded": {"F": 0, "M": 1},
+    "course_encoded": {
+        "Primary 6": 0, "Secondary 1": 1, "Secondary 2": 2,
+        "Secondary 3": 3, "Secondary 4": 4, "Secondary 5": 5, "Secondary 6": 6,
+    },
+    "support_level_num": {"Low": 0, "Medium": 1, "High": 2},
+    "grade_tendency_encoded": {"Downtrend": 0, "Stable": 0, "Uptrend": 1},
+}
+
+
+@lru_cache(maxsize=1)
+def _load_models() -> dict[str, Any]:
+    pkl_dir = Path(settings.models_base_path) / "models-pkl"
+    return {
+        "linear_avg_final": joblib.load(pkl_dir / "linear_avg_final.pkl"),
+        "linear_math_final": joblib.load(pkl_dir / "linear_mathematics_final.pkl"),
+        "logistic_grade_tendency": joblib.load(pkl_dir / "logistic_grade_tendency.pkl"),
+        "logistic_math_pass": joblib.load(pkl_dir / "logistic_mathematics_pass.pkl"),
+        "logistic_support_level": joblib.load(pkl_dir / "logistic_support_level.pkl"),
+    }
+
+
+@router.get("/encodings", summary="Tabla de codificaciones para variables categóricas")
+def get_encodings(_: UsuarioAdmin = Depends(get_current_admin)):
+    return ENCODING_INFO
+
+
+@router.post("/avg-final", response_model=AvgFinalOutput, summary="M1 — Predice el promedio final del estudiante")
+def predict_avg_final(body: AvgFinalInput, _: UsuarioAdmin = Depends(get_current_admin)):
+    model = _load_models()["linear_avg_final"]
+    X = np.array([[
+        body.age, body.gender_encoded, body.course_encoded,
+        body.attendance_absences_total, body.support_level_num,
+        body.total_subjects, body.term1_avg,
+    ]])
+    pred = float(model.predict(X)[0])
+    return AvgFinalOutput(predicted_avg_final=round(pred, 4))
+
+
+@router.post("/math-final", response_model=MathFinalOutput, summary="M4 — Predice la nota final de Matemáticas")
+def predict_math_final(body: MathFinalInput, _: UsuarioAdmin = Depends(get_current_admin)):
+    model = _load_models()["linear_math_final"]
+    X = np.array([[
+        body.Mathematics_term1, body.Mathematics_term2,
+        body.Mathematics_term3, body.attendance_absences_total,
+    ]])
+    pred = float(model.predict(X)[0])
+    return MathFinalOutput(predicted_math_final=round(pred, 4))
+
+
+@router.post("/grade-tendency", response_model=GradeTendencyOutput, summary="M2 — Predice la tendencia de notas")
+def predict_grade_tendency(body: GradeTendencyInput, _: UsuarioAdmin = Depends(get_current_admin)):
+    scaler, model = _load_models()["logistic_grade_tendency"]
+    X = np.array([[
+        body.term1_avg, body.term2_avg, body.term3_avg,
+        body.attendance_absences_total, body.support_level_num,
+        body.age, body.gender_encoded,
+    ]])
+    X_scaled = scaler.transform(X)
+    pred_class = int(model.predict(X_scaled)[0])
+    proba = model.predict_proba(X_scaled)[0]
+    return GradeTendencyOutput(
+        predicted_class=pred_class,
+        label="Uptrend" if pred_class == 1 else "Downtrend",
+        probability_downtrend=round(float(proba[0]), 4),
+        probability_uptrend=round(float(proba[1]), 4),
+    )
+
+
+@router.post("/math-pass", response_model=MathPassOutput, summary="M3 — Predice aprobado/suspenso en Matemáticas")
+def predict_math_pass(body: MathPassInput, _: UsuarioAdmin = Depends(get_current_admin)):
+    scaler, model = _load_models()["logistic_math_pass"]
+    X = np.array([[
+        body.Mathematics_term1, body.Mathematics_term2,
+        body.Mathematics_term3, body.attendance_absences_total,
+        body.support_level_num,
+    ]])
+    X_scaled = scaler.transform(X)
+    pred_class = int(model.predict(X_scaled)[0])
+    proba = model.predict_proba(X_scaled)[0]
+    return MathPassOutput(
+        predicted_class=pred_class,
+        label="Pass" if pred_class == 1 else "Fail",
+        probability_fail=round(float(proba[0]), 4),
+        probability_pass=round(float(proba[1]), 4),
+    )
+
+
+@router.post("/support-level", response_model=SupportLevelOutput, summary="M5 — Predice el nivel de soporte necesario")
+def predict_support_level(body: SupportLevelInput, _: UsuarioAdmin = Depends(get_current_admin)):
+    scaler, model = _load_models()["logistic_support_level"]
+    X = np.array([[
+        body.term1_avg, body.attendance_absences_total,
+        body.failed_subjects, body.grade_tendency_encoded,
+        body.age, body.gender_encoded,
+    ]])
+    X_scaled = scaler.transform(X)
+    pred_class = int(model.predict(X_scaled)[0])
+    proba = model.predict_proba(X_scaled)[0]
+    labels = {0: "Low", 1: "Medium", 2: "High"}
+    return SupportLevelOutput(
+        predicted_class=pred_class,
+        label=labels[pred_class],
+        probability_low=round(float(proba[0]), 4),
+        probability_medium=round(float(proba[1]), 4),
+        probability_high=round(float(proba[2]), 4),
+    )

+ 267 - 0
internal/src/backend/routers/platform.py

@@ -0,0 +1,267 @@
+from typing import Optional
+
+from fastapi import APIRouter, Depends, HTTPException, Query
+from sqlalchemy.orm import Session, joinedload
+
+from ..db_models.internal import UsuarioAdmin
+from ..db_models.platform import (
+    Actividad, Alumno, Calificacion, Curso, Entrega,
+    Matricula, Profesor, Progreso, Rol, Usuario,
+)
+from ..dependencies import get_current_admin, get_platform_db
+from ..schemas.platform import (
+    ActividadOut, AlumnoOut, CursoOut, EntregaOut,
+    MatriculaOut, PlatformSummary, ProfesorOut, ProgresoOut, UsuarioOut,
+)
+
+router = APIRouter(prefix="/platform", tags=["Plataforma de Usuarios"])
+
+
+# ── Summary ─────────────────────────────────────────────────────────────────────
+
+@router.get("/summary", response_model=PlatformSummary, summary="Resumen de la plataforma de usuarios")
+def platform_summary(
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    return PlatformSummary(
+        total_users=db.query(Usuario).count(),
+        active_users=db.query(Usuario).filter(Usuario.activo == True).count(),
+        total_students=db.query(Alumno).count(),
+        total_teachers=db.query(Profesor).count(),
+        total_courses=db.query(Curso).count(),
+        total_enrollments=db.query(Matricula).count(),
+        total_activities=db.query(Actividad).count(),
+        total_submissions=db.query(Entrega).count(),
+    )
+
+
+# ── Users ───────────────────────────────────────────────────────────────────────
+
+@router.get("/users", summary="Lista de usuarios de la plataforma")
+def get_users(
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    rol: Optional[str] = Query(None, description="Filtrar por nombre de rol"),
+    activo: Optional[bool] = Query(None),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Usuario).options(joinedload(Usuario.rol))
+    if rol:
+        q = q.join(Rol).filter(Rol.nombre == rol)
+    if activo is not None:
+        q = q.filter(Usuario.activo == activo)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [UsuarioOut.model_validate(u) for u in items],
+    }
+
+
+@router.get("/users/{user_id}", response_model=UsuarioOut, summary="Datos de un usuario específico")
+def get_user(
+    user_id: int,
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    u = db.query(Usuario).options(joinedload(Usuario.rol)).filter(Usuario.id_usuario == user_id).first()
+    if not u:
+        raise HTTPException(status_code=404, detail="Usuario no encontrado")
+    return UsuarioOut.model_validate(u)
+
+
+# ── Students ─────────────────────────────────────────────────────────────────────
+
+@router.get("/students", summary="Lista de alumnos")
+def get_students(
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    nivel: Optional[str] = Query(None),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Alumno).options(joinedload(Alumno.usuario).joinedload(Usuario.rol))
+    if nivel:
+        q = q.filter(Alumno.nivel == nivel)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [AlumnoOut.model_validate(a) for a in items],
+    }
+
+
+@router.get("/students/{student_id}", response_model=AlumnoOut, summary="Datos de un alumno")
+def get_student(
+    student_id: int,
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    a = (
+        db.query(Alumno)
+        .options(joinedload(Alumno.usuario).joinedload(Usuario.rol))
+        .filter(Alumno.id_alumno == student_id)
+        .first()
+    )
+    if not a:
+        raise HTTPException(status_code=404, detail="Alumno no encontrado")
+    return AlumnoOut.model_validate(a)
+
+
+# ── Teachers ──────────────────────────────────────────────────────────────────────
+
+@router.get("/teachers", summary="Lista de profesores")
+def get_teachers(
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Profesor).options(joinedload(Profesor.usuario).joinedload(Usuario.rol))
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [ProfesorOut.model_validate(p) for p in items],
+    }
+
+
+# ── Courses ───────────────────────────────────────────────────────────────────────
+
+@router.get("/courses", summary="Lista de cursos")
+def get_courses(
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Curso).options(
+        joinedload(Curso.profesor).joinedload(Profesor.usuario).joinedload(Usuario.rol)
+    )
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [CursoOut.model_validate(c) for c in items],
+    }
+
+
+@router.get("/courses/{course_id}", summary="Datos de un curso con actividades")
+def get_course(
+    course_id: int,
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    c = (
+        db.query(Curso)
+        .options(
+            joinedload(Curso.profesor).joinedload(Profesor.usuario),
+            joinedload(Curso.actividades),
+        )
+        .filter(Curso.id_curso == course_id)
+        .first()
+    )
+    if not c:
+        raise HTTPException(status_code=404, detail="Curso no encontrado")
+    return {
+        **CursoOut.model_validate(c).model_dump(),
+        "actividades": [ActividadOut.model_validate(a) for a in c.actividades],
+    }
+
+
+# ── Enrollments ───────────────────────────────────────────────────────────────────
+
+@router.get("/enrollments", summary="Lista de matrículas")
+def get_enrollments(
+    course_id: Optional[int] = Query(None),
+    student_id: Optional[int] = Query(None),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Matricula)
+    if course_id:
+        q = q.filter(Matricula.id_curso == course_id)
+    if student_id:
+        q = q.filter(Matricula.id_alumno == student_id)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [MatriculaOut.model_validate(m) for m in items],
+    }
+
+
+# ── Activities ────────────────────────────────────────────────────────────────────
+
+@router.get("/activities", summary="Lista de actividades")
+def get_activities(
+    course_id: Optional[int] = Query(None),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Actividad)
+    if course_id:
+        q = q.filter(Actividad.id_curso == course_id)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [ActividadOut.model_validate(a) for a in items],
+    }
+
+
+# ── Submissions ───────────────────────────────────────────────────────────────────
+
+@router.get("/submissions", summary="Lista de entregas")
+def get_submissions(
+    activity_id: Optional[int] = Query(None),
+    student_id: Optional[int] = Query(None),
+    estado: Optional[str] = Query(None, description="pendiente | entregado | calificado"),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Entrega).options(joinedload(Entrega.calificacion))
+    if activity_id:
+        q = q.filter(Entrega.id_actividad == activity_id)
+    if student_id:
+        q = q.filter(Entrega.id_alumno == student_id)
+    if estado:
+        q = q.filter(Entrega.estado == estado)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [EntregaOut.model_validate(e) for e in items],
+    }
+
+
+# ── Progress ──────────────────────────────────────────────────────────────────────
+
+@router.get("/progress", summary="Progreso de alumnos en cursos")
+def get_progress(
+    course_id: Optional[int] = Query(None),
+    student_id: Optional[int] = Query(None),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=200),
+    db: Session = Depends(get_platform_db),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    q = db.query(Progreso)
+    if course_id:
+        q = q.filter(Progreso.id_curso == course_id)
+    if student_id:
+        q = q.filter(Progreso.id_alumno == student_id)
+    total = q.count()
+    items = q.offset((page - 1) * page_size).limit(page_size).all()
+    return {
+        "total": total, "page": page, "page_size": page_size,
+        "items": [ProgresoOut.model_validate(p) for p in items],
+    }

+ 127 - 0
internal/src/backend/routers/predictions.py

@@ -0,0 +1,127 @@
+import json
+from pathlib import Path
+from typing import Optional
+
+import pandas as pd
+from fastapi import APIRouter, Depends, HTTPException, Query
+
+from ..core.config import get_settings
+from ..db_models.internal import UsuarioAdmin
+from ..dependencies import get_current_admin
+
+router = APIRouter(prefix="/predictions", tags=["Predicciones"])
+settings = get_settings()
+
+PREDS_DIR = Path(settings.models_base_path) / "predictions"
+DATASETS_DIR = Path(settings.models_base_path) / "datasets"
+
+ML_DASHBOARDS: dict[str, str] = {
+    "avg_final": "dashboard_ml_avg_final.json",
+    "grade_tendency": "dashboard_ml_grade_tendency.json",
+    "math_final": "dashboard_ml_math_final.json",
+    "math_pass": "dashboard_ml_math_pass.json",
+    "models_overview": "dashboard_ml_models.json",
+    "support_level": "dashboard_ml_support_level.json",
+}
+
+NLP_DATASETS: dict[str, str] = {
+    "career_recommendation": "nlp_pred_career_recommendation.csv",
+    "risk_alert": "nlp_pred_risk_alert.csv",
+    "computer_science": "nlp_pred_computer_science.csv",
+    "technical_drawing": "nlp_pred_technical_drawing.csv",
+}
+
+ML_DATASETS: dict[str, str] = {
+    "math_risk": "math_risk_scores.csv",
+    "support_level": "support_level_predictions.csv",
+}
+
+
+def _paginate(df: pd.DataFrame, page: int, page_size: int) -> dict:
+    total = len(df)
+    start = (page - 1) * page_size
+    rows = df.iloc[start : start + page_size].fillna("").to_dict(orient="records")
+    return {"total": total, "page": page, "page_size": page_size, "items": rows}
+
+
+# ── ML Dashboards ───────────────────────────────────────────────────────────────
+
+@router.get("/dashboards", summary="Lista de dashboards ML disponibles")
+def list_ml_dashboards(_: UsuarioAdmin = Depends(get_current_admin)):
+    return {"ml_dashboards": list(ML_DASHBOARDS.keys())}
+
+
+@router.get("/dashboards/{name}", summary="Datos Plotly JSON de un dashboard de modelo ML")
+def get_ml_dashboard(name: str, _: UsuarioAdmin = Depends(get_current_admin)):
+    if name not in ML_DASHBOARDS:
+        raise HTTPException(
+            status_code=404,
+            detail=f"Dashboard '{name}' no encontrado. Disponibles: {list(ML_DASHBOARDS.keys())}",
+        )
+    path = PREDS_DIR / ML_DASHBOARDS[name]
+    return json.loads(path.read_text(encoding="utf-8"))
+
+
+# ── NLP predictions ─────────────────────────────────────────────────────────────
+
+@router.get("/nlp/{name}", summary="Predicciones NLP por dataset")
+def get_nlp_predictions(
+    name: str,
+    student_id: Optional[str] = Query(None, description="Filtrar por student_id"),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=500),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    if name not in NLP_DATASETS:
+        raise HTTPException(
+            status_code=404,
+            detail=f"Dataset '{name}' no encontrado. Disponibles: {list(NLP_DATASETS.keys())}",
+        )
+    df = pd.read_csv(DATASETS_DIR / NLP_DATASETS[name])
+    if student_id:
+        df = df[df["student_id"] == student_id]
+    return _paginate(df, page, page_size)
+
+
+# ── ML predictions ──────────────────────────────────────────────────────────────
+
+@router.get("/ml/{name}", summary="Predicciones ML por dataset")
+def get_ml_predictions(
+    name: str,
+    student_id: Optional[str] = Query(None, description="Filtrar por student_id"),
+    risk_level: Optional[str] = Query(None, description="Filtrar por nivel de riesgo"),
+    page: int = Query(1, ge=1),
+    page_size: int = Query(50, ge=1, le=500),
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    if name not in ML_DATASETS:
+        raise HTTPException(
+            status_code=404,
+            detail=f"Dataset '{name}' no encontrado. Disponibles: {list(ML_DATASETS.keys())}",
+        )
+    df = pd.read_csv(DATASETS_DIR / ML_DATASETS[name])
+    if student_id:
+        df = df[df["student_id"] == student_id]
+    if risk_level and "risk_level" in df.columns:
+        df = df[df["risk_level"].str.lower() == risk_level.lower()]
+    return _paginate(df, page, page_size)
+
+
+# ── Per-student aggregation ──────────────────────────────────────────────────────
+
+@router.get("/students/{student_id}", summary="Todas las predicciones para un estudiante")
+def get_student_predictions(
+    student_id: str,
+    _: UsuarioAdmin = Depends(get_current_admin),
+):
+    result: dict = {"student_id": student_id}
+    all_datasets = {**NLP_DATASETS, **ML_DATASETS}
+    for key, filename in all_datasets.items():
+        path = DATASETS_DIR / filename
+        try:
+            df = pd.read_csv(path)
+            rows = df[df["student_id"] == student_id]
+            result[key] = rows.fillna("").to_dict(orient="records")
+        except Exception:
+            result[key] = []
+    return result

+ 57 - 0
internal/src/backend/routers/statistics.py

@@ -0,0 +1,57 @@
+import json
+from pathlib import Path
+
+from fastapi import APIRouter, Depends, HTTPException
+from fastapi.responses import HTMLResponse
+
+from ..core.config import get_settings
+from ..db_models.internal import UsuarioAdmin
+from ..dependencies import get_current_admin
+
+router = APIRouter(prefix="/statistics", tags=["Estadísticas"])
+settings = get_settings()
+
+STATS_DIR = Path(settings.models_base_path) / "statistics"
+FIGURES_DIR = STATS_DIR / "iframe_figures"
+
+DASHBOARDS: dict[str, str] = {
+    "attendance": "dashboard_attendance.json",
+    "demographics": "dashboard_demographics.json",
+    "school_group": "dashboard_school_group.json",
+    "student_performance": "dashboard_student_performance.json",
+    "subject_performance": "dashboard_subject_performance.json",
+    "support": "dashboard_support.json",
+    "term_progression": "dashboard_term_progression.json",
+}
+
+
+@router.get("/", summary="Lista de dashboards y figuras disponibles")
+def list_statistics(_: UsuarioAdmin = Depends(get_current_admin)):
+    figures = sorted(f.stem for f in FIGURES_DIR.glob("*.html")) if FIGURES_DIR.exists() else []
+    return {
+        "dashboards": list(DASHBOARDS.keys()),
+        "iframe_figures": figures,
+    }
+
+
+@router.get("/{name}", summary="Datos Plotly JSON de un dashboard estadístico")
+def get_dashboard(name: str, _: UsuarioAdmin = Depends(get_current_admin)):
+    if name not in DASHBOARDS:
+        raise HTTPException(
+            status_code=404,
+            detail=f"Dashboard '{name}' no encontrado. Disponibles: {list(DASHBOARDS.keys())}",
+        )
+    path = STATS_DIR / DASHBOARDS[name]
+    return json.loads(path.read_text(encoding="utf-8"))
+
+
+@router.get("/figures/{name}", response_class=HTMLResponse, summary="Figura interactiva HTML (iframe)")
+def get_figure(name: str, _: UsuarioAdmin = Depends(get_current_admin)):
+    path = FIGURES_DIR / f"{name}.html"
+    if not path.exists():
+        available = sorted(f.stem for f in FIGURES_DIR.glob("*.html"))
+        raise HTTPException(
+            status_code=404,
+            detail=f"Figura '{name}' no encontrada. Disponibles: {available}",
+        )
+    return HTMLResponse(content=path.read_text(encoding="utf-8"))

+ 655 - 0
internal/src/backend/routers/web.py

@@ -0,0 +1,655 @@
+import json
+import math
+from datetime import datetime, timedelta, timezone
+from pathlib import Path
+from typing import Optional
+
+import pandas as pd
+from fastapi import APIRouter, Depends, Form, Request
+from fastapi.responses import HTMLResponse, RedirectResponse
+from fastapi.templating import Jinja2Templates
+from sqlalchemy.orm import Session, joinedload
+
+from ..core.config import get_settings
+from ..core.database import InternalSession, PlatformSession
+from ..core.security import create_access_token, decode_access_token, verify_password
+from ..db_models.internal import LogAdmin, SesionAdmin, UsuarioAdmin
+from ..db_models.platform import (
+    Actividad, Alumno, Curso, Entrega, Matricula, Profesor, Progreso, Rol, Usuario,
+)
+
+router = APIRouter(tags=["Web Panel"])
+settings = get_settings()
+
+templates = Jinja2Templates(directory=Path(__file__).parent.parent / "templates")
+
+MODELS_DIR  = Path(settings.models_base_path)
+DATASETS_DIR = MODELS_DIR / "datasets"
+STATS_DIR    = MODELS_DIR / "statistics"
+PREDS_DIR    = MODELS_DIR / "predictions"
+FIGURES_DIR  = STATS_DIR / "iframe_figures"
+
+STAT_DASHBOARDS = {
+    "attendance":          "Asistencia",
+    "demographics":        "Demografía",
+    "school_group":        "Grupo Escolar",
+    "student_performance": "Rendimiento Estudiantil",
+    "subject_performance": "Rendimiento por Asignatura",
+    "support":             "Soporte",
+    "term_progression":    "Progresión Trimestral",
+}
+ML_DASHBOARDS = {
+    "avg_final":      "Promedio Final",
+    "grade_tendency": "Tendencia de Notas",
+    "math_final":     "Notas Matemáticas",
+    "math_pass":      "Aprobado Matemáticas",
+    "models_overview": "Vista General Modelos",
+    "support_level":  "Nivel de Soporte",
+}
+NLP_DATASETS = {
+    "career_recommendation": "Recomendación de Carrera",
+    "risk_alert":            "Alertas de Riesgo",
+    "computer_science":      "Predicción Computación",
+    "technical_drawing":     "Predicción Dibujo Técnico",
+}
+ML_PRED_DATASETS = {
+    "math_risk":    "Riesgo Matemáticas",
+    "support_level": "Nivel de Soporte",
+}
+ALL_PRED_DATASETS = {**NLP_DATASETS, **ML_PRED_DATASETS}
+
+MODEL_CONFIGS: dict = {
+    "avg-final": {
+        "name": "M1 — Promedio Final (Regresión Lineal)",
+        "fields": [
+            {"name": "age",                      "label": "Edad",              "type": "number", "min": 11,  "max": 25,  "default": 15,  "step": 1},
+            {"name": "gender_encoded",           "label": "Género",            "type": "select", "options": {"0": "Femenino (0)", "1": "Masculino (1)"}},
+            {"name": "course_encoded",           "label": "Curso",             "type": "select", "options": {"0":"Primary 6","1":"Secondary 1","2":"Secondary 2","3":"Secondary 3","4":"Secondary 4","5":"Secondary 5","6":"Secondary 6"}},
+            {"name": "attendance_absences_total","label": "Total Ausencias",   "type": "number", "min": 0,   "max": 200, "default": 5,   "step": 1},
+            {"name": "support_level_num",        "label": "Nivel Apoyo",       "type": "select", "options": {"0":"Low","1":"Medium","2":"High"}},
+            {"name": "total_subjects",           "label": "Nº Asignaturas",    "type": "number", "min": 1,   "max": 20,  "default": 16,  "step": 1},
+            {"name": "term1_avg",                "label": "Promedio Trim. 1",  "type": "number", "min": 0,   "max": 100, "default": 60,  "step": 0.1},
+        ],
+    },
+    "math-final": {
+        "name": "M4 — Nota Final Matemáticas (Regresión Lineal)",
+        "fields": [
+            {"name": "Mathematics_term1",        "label": "Matemáticas T1",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "Mathematics_term2",        "label": "Matemáticas T2",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "Mathematics_term3",        "label": "Matemáticas T3",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "attendance_absences_total","label": "Total Ausencias",   "type": "number", "min": 0, "max": 200, "default": 5,  "step": 1},
+        ],
+    },
+    "grade-tendency": {
+        "name": "M2 — Tendencia de Notas (Regresión Logística)",
+        "fields": [
+            {"name": "term1_avg",                "label": "Promedio T1",       "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "term2_avg",                "label": "Promedio T2",       "type": "number", "min": 0, "max": 100, "default": 62, "step": 0.1},
+            {"name": "term3_avg",                "label": "Promedio T3",       "type": "number", "min": 0, "max": 100, "default": 65, "step": 0.1},
+            {"name": "attendance_absences_total","label": "Total Ausencias",   "type": "number", "min": 0, "max": 200, "default": 5,  "step": 1},
+            {"name": "support_level_num",        "label": "Nivel Apoyo",       "type": "select", "options": {"0":"Low","1":"Medium","2":"High"}},
+            {"name": "age",                      "label": "Edad",              "type": "number", "min": 11, "max": 25, "default": 15, "step": 1},
+            {"name": "gender_encoded",           "label": "Género",            "type": "select", "options": {"0":"Femenino (0)","1":"Masculino (1)"}},
+        ],
+    },
+    "math-pass": {
+        "name": "M3 — Aprobado/Suspenso Matemáticas (Logística)",
+        "fields": [
+            {"name": "Mathematics_term1",        "label": "Matemáticas T1",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "Mathematics_term2",        "label": "Matemáticas T2",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "Mathematics_term3",        "label": "Matemáticas T3",    "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "attendance_absences_total","label": "Total Ausencias",   "type": "number", "min": 0, "max": 200, "default": 5,  "step": 1},
+            {"name": "support_level_num",        "label": "Nivel Apoyo",       "type": "select", "options": {"0":"Low","1":"Medium","2":"High"}},
+        ],
+    },
+    "support-level": {
+        "name": "M5 — Nivel de Soporte Necesario (Multinomial)",
+        "fields": [
+            {"name": "term1_avg",                "label": "Promedio T1",       "type": "number", "min": 0, "max": 100, "default": 60, "step": 0.1},
+            {"name": "attendance_absences_total","label": "Total Ausencias",   "type": "number", "min": 0, "max": 200, "default": 5,  "step": 1},
+            {"name": "failed_subjects",          "label": "Asig. Suspendidas", "type": "number", "min": 0, "max": 20,  "default": 0,  "step": 1},
+            {"name": "grade_tendency_encoded",   "label": "Tendencia",         "type": "select", "options": {"0":"Downtrend/Stable","1":"Uptrend"}},
+            {"name": "age",                      "label": "Edad",              "type": "number", "min": 11, "max": 25, "default": 15, "step": 1},
+            {"name": "gender_encoded",           "label": "Género",            "type": "select", "options": {"0":"Femenino (0)","1":"Masculino (1)"}},
+        ],
+    },
+}
+
+PRED_CSV_MAP = {
+    "career_recommendation": "nlp_pred_career_recommendation.csv",
+    "risk_alert":            "nlp_pred_risk_alert.csv",
+    "computer_science":      "nlp_pred_computer_science.csv",
+    "technical_drawing":     "nlp_pred_technical_drawing.csv",
+    "math_risk":             "math_risk_scores.csv",
+    "support_level":         "support_level_predictions.csv",
+}
+
+STUDENT_PRED_FILES = {
+    "Alerta de Riesgo":        "nlp_pred_risk_alert.csv",
+    "Recomendación Carrera":   "nlp_pred_career_recommendation.csv",
+    "Predicción Computación":  "nlp_pred_computer_science.csv",
+    "Predicción Dibujo Técnico": "nlp_pred_technical_drawing.csv",
+    "Riesgo Matemáticas":      "math_risk_scores.csv",
+    "Nivel de Soporte":        "support_level_predictions.csv",
+}
+
+SUBJECTS = [
+    "Mathematics","English","Science","ICT","Geography","History","Civics",
+    "Agriculture","Business_Studies","Physical_Education","Swahili","French",
+    "Biology","Chemistry","Physics","Entrepreneurship","Fine_Arts","Music",
+    "Additional_Math","Religious_Education",
+]
+
+# ── Helpers ──────────────────────────────────────────────────────────────────────
+
+def get_internal_db():
+    db = InternalSession()
+    try:
+        yield db
+    finally:
+        db.close()
+
+def get_platform_db():
+    db = PlatformSession()
+    try:
+        yield db
+    finally:
+        db.close()
+
+def _get_user(request: Request, db: Session) -> Optional[UsuarioAdmin]:
+    token = request.cookies.get("access_token")
+    if not token:
+        print("DEBUG: No token in cookies")
+        return None
+    try:
+        payload = decode_access_token(token)
+        if not payload:
+            print("DEBUG: decode_access_token returned None")
+            return None
+        user_id = payload.get("sub")
+        if user_id is None:
+            print("DEBUG: no sub in payload")
+            return None
+        print(f"DEBUG: fetching user {user_id}")
+        user = db.query(UsuarioAdmin).filter(
+            UsuarioAdmin.id_usuario == int(user_id),
+            UsuarioAdmin.activo == True,
+        ).first()
+        if not user:
+            print("DEBUG: user not found or not activo in db")
+        return user
+    except Exception as e:
+        print(f"DEBUG: Exception in _get_user: {e}")
+        return None
+
+def _redirect_login() -> RedirectResponse:
+    return RedirectResponse("/login", status_code=303)
+
+def _load_csv(name: str) -> pd.DataFrame:
+    return pd.read_csv(DATASETS_DIR / name)
+
+# ── Auth ──────────────────────────────────────────────────────────────────────────
+
+@router.get("/login", response_class=HTMLResponse)
+async def login_page(request: Request):
+    return templates.TemplateResponse("login.html", {"request": request, "error": None})
+
+@router.post("/login")
+async def login_submit(
+    request: Request,
+    username: str = Form(...),
+    password: str = Form(...),
+    db: Session = Depends(get_internal_db),
+):
+    user = db.query(UsuarioAdmin).filter(
+        (UsuarioAdmin.username == username) | (UsuarioAdmin.email == username)
+    ).first()
+
+    if not user or not verify_password(password, user.password_hash) or not user.activo:
+        return templates.TemplateResponse("login.html", {
+            "request": request,
+            "error": "Usuario o contraseña incorrectos",
+        })
+
+    expires = timedelta(minutes=settings.jwt_access_token_expire_minutes)
+    token = create_access_token({"sub": str(user.id_usuario), "username": user.username}, expires)
+
+    db.add(SesionAdmin(
+        id_usuario=user.id_usuario, token=token,
+        ip=request.client.host if request.client else None,
+        user_agent=request.headers.get("user-agent"),
+        fecha_expiracion=datetime.now(timezone.utc) + expires,
+    ))
+    user.ultimo_login = datetime.now(timezone.utc)
+    db.add(LogAdmin(id_usuario=user.id_usuario, accion="WEB_LOGIN", descripcion="Login web panel"))
+    db.commit()
+
+    response = RedirectResponse("/panel", status_code=303)
+    response.set_cookie(
+        "access_token", token,
+        httponly=True, samesite="lax",
+        max_age=settings.jwt_access_token_expire_minutes * 60,
+    )
+    return response
+
+@router.post("/logout")
+async def logout(request: Request, db: Session = Depends(get_internal_db)):
+    token = request.cookies.get("access_token")
+    if token:
+        db.query(SesionAdmin).filter(SesionAdmin.token == token).delete()
+        db.commit()
+    response = RedirectResponse("/login", status_code=303)
+    response.delete_cookie("access_token")
+    return response
+
+# ── Root ──────────────────────────────────────────────────────────────────────────
+
+@router.get("/", response_class=HTMLResponse)
+async def root(request: Request, db: Session = Depends(get_internal_db)):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+    return RedirectResponse("/panel", status_code=303)
+
+# ── Panel overview ────────────────────────────────────────────────────────────────
+
+@router.get("/panel", response_class=HTMLResponse)
+async def panel_overview(request: Request, db: Session = Depends(get_internal_db)):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    df = _load_csv("clean_dataset.csv")
+    risk_df = _load_csv("nlp_pred_risk_alert.csv")
+
+    kpis = {
+        "total_students": len(df),
+        "avg_final":      round(float(df["avg_final"].mean()), 1),
+        "pass_rate":      round(float(df["passed_rate_percent"].mean()), 1),
+        "high_risk":      int((risk_df["risk_level"] == "High").sum()),
+        "low_support":    int((df["support_level"] == "Low").sum()),
+    }
+
+    chart_json = (STATS_DIR / "dashboard_demographics.json").read_text(encoding="utf-8")
+
+    return templates.TemplateResponse("panel.html", {
+        "request": request, "user": user, "active": "panel",
+        "kpis": kpis, "chart_json": chart_json,
+        "stat_dashboards": STAT_DASHBOARDS, "ml_dashboards": ML_DASHBOARDS,
+    })
+
+# ── Estadísticas ──────────────────────────────────────────────────────────────────
+
+@router.get("/panel/estadisticas", response_class=HTMLResponse)
+async def estadisticas(
+    request: Request,
+    dashboard: str = "demographics",
+    db: Session = Depends(get_internal_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    if dashboard not in STAT_DASHBOARDS:
+        dashboard = "demographics"
+
+    chart_json = (STATS_DIR / f"dashboard_{dashboard}.json").read_text(encoding="utf-8")
+    figures = sorted(f.stem for f in FIGURES_DIR.glob("*.html")) if FIGURES_DIR.exists() else []
+
+    return templates.TemplateResponse("estadisticas.html", {
+        "request": request, "user": user, "active": "estadisticas",
+        "stat_dashboards": STAT_DASHBOARDS,
+        "current_dashboard": dashboard,
+        "current_name": STAT_DASHBOARDS[dashboard],
+        "chart_json": chart_json, "figures": figures,
+    })
+
+@router.get("/panel/figuras/{name}", response_class=HTMLResponse)
+async def figura_html(name: str, request: Request, db: Session = Depends(get_internal_db)):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+    path = FIGURES_DIR / f"{name}.html"
+    if not path.exists():
+        return HTMLResponse("<p>Figura no encontrada</p>", status_code=404)
+    return HTMLResponse(path.read_text(encoding="utf-8"))
+
+# ── Predicciones ──────────────────────────────────────────────────────────────────
+
+@router.get("/panel/predicciones", response_class=HTMLResponse)
+async def predicciones(
+    request: Request,
+    tipo: str = "risk_alert",
+    page: int = 1,
+    db: Session = Depends(get_internal_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    if tipo not in ALL_PRED_DATASETS:
+        tipo = "risk_alert"
+
+    df = _load_csv(PRED_CSV_MAP[tipo])
+    page_size = 30
+    total = len(df)
+    total_pages = max(1, math.ceil(total / page_size))
+    page = max(1, min(page, total_pages))
+    rows = df.iloc[(page-1)*page_size : page*page_size].fillna("").to_dict(orient="records")
+
+    ml_key_map = {"math_risk": "math_pass", "support_level": "support_level"}
+    ml_dashboard_json = None
+    if tipo in ml_key_map:
+        p = PREDS_DIR / f"dashboard_ml_{ml_key_map[tipo]}.json"
+        if p.exists():
+            ml_dashboard_json = p.read_text(encoding="utf-8")
+
+    return templates.TemplateResponse("predicciones.html", {
+        "request": request, "user": user, "active": "predicciones",
+        "all_datasets": ALL_PRED_DATASETS,
+        "current_tipo": tipo, "current_name": ALL_PRED_DATASETS[tipo],
+        "columns": list(df.columns), "rows": rows,
+        "total": total, "page": page, "total_pages": total_pages,
+        "ml_dashboard_json": ml_dashboard_json, "ml_dashboards": ML_DASHBOARDS,
+    })
+
+@router.get("/panel/predicciones/dashboard/{name}", response_class=HTMLResponse)
+async def pred_dashboard(name: str, request: Request, db: Session = Depends(get_internal_db)):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+    if name not in ML_DASHBOARDS:
+        return _redirect_login()
+    chart_json = (PREDS_DIR / f"dashboard_ml_{name}.json").read_text(encoding="utf-8")
+    return templates.TemplateResponse("estadisticas.html", {
+        "request": request, "user": user, "active": "predicciones",
+        "stat_dashboards": ML_DASHBOARDS,
+        "current_dashboard": name,
+        "current_name": ML_DASHBOARDS[name],
+        "chart_json": chart_json, "figures": [],
+    })
+
+# ── Estudiantes ───────────────────────────────────────────────────────────────────
+
+@router.get("/panel/estudiantes", response_class=HTMLResponse)
+async def estudiantes(
+    request: Request,
+    country: str = "", course: str = "", gender: str = "", support_level: str = "",
+    page: int = 1,
+    db: Session = Depends(get_internal_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    df = _load_csv("clean_dataset.csv")
+    if country:       df = df[df["country"].str.lower() == country.lower()]
+    if course:        df = df[df["course"].str.lower() == course.lower()]
+    if gender:        df = df[df["gender"].str.upper() == gender.upper()]
+    if support_level: df = df[df["support_level"].str.lower() == support_level.lower()]
+
+    page_size = 40
+    total = len(df)
+    total_pages = max(1, math.ceil(total / page_size))
+    page = max(1, min(page, total_pages))
+
+    cols = ["student_id","gender","age","country","course","support_level","avg_final","grade_tendency","passed_rate_percent"]
+    rows = df[cols].iloc[(page-1)*page_size : page*page_size].fillna("").to_dict(orient="records")
+
+    full_df = _load_csv("clean_dataset.csv")
+    countries = sorted(full_df["country"].dropna().unique())
+    courses   = sorted(full_df["course"].dropna().unique())
+
+    return templates.TemplateResponse("estudiantes.html", {
+        "request": request, "user": user, "active": "estudiantes",
+        "rows": rows, "total": total, "page": page, "total_pages": total_pages,
+        "filters": {"country": country, "course": course, "gender": gender, "support_level": support_level},
+        "countries": countries, "courses": courses,
+    })
+
+@router.get("/panel/estudiantes/{student_id}", response_class=HTMLResponse)
+async def estudiante_detail(
+    request: Request, student_id: str,
+    db: Session = Depends(get_internal_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    df = _load_csv("clean_dataset.csv")
+    row = df[df["student_id"] == student_id]
+    if row.empty:
+        return RedirectResponse("/panel/estudiantes", status_code=303)
+
+    student = row.fillna("").to_dict(orient="records")[0]
+
+    predictions: dict = {}
+    for label, fname in STUDENT_PRED_FILES.items():
+        try:
+            pf = _load_csv(fname)
+            prow = pf[pf["student_id"] == student_id]
+            if not prow.empty:
+                predictions[label] = prow.fillna("").to_dict(orient="records")[0]
+        except Exception:
+            pass
+
+    grades = []
+    for subj in SUBJECTS:
+        final_key  = f"{subj}_final"
+        status_key = f"{subj}_status"
+        if final_key in student and student[final_key] != "":
+            grades.append({
+                "subject": subj.replace("_", " "),
+                "final":   student[final_key],
+                "status":  student.get(status_key, ""),
+            })
+
+    return templates.TemplateResponse("estudiante.html", {
+        "request": request, "user": user, "active": "estudiantes",
+        "student": student, "predictions": predictions, "grades": grades,
+    })
+
+# ── Modelos / Inferencia ──────────────────────────────────────────────────────────
+
+@router.get("/panel/modelos", response_class=HTMLResponse)
+async def modelos_page(
+    request: Request,
+    model: str = "avg-final",
+    db: Session = Depends(get_internal_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+    if model not in MODEL_CONFIGS:
+        model = "avg-final"
+    return templates.TemplateResponse("modelos.html", {
+        "request": request, "user": user, "active": "modelos",
+        "model_configs": MODEL_CONFIGS, "current_model": model,
+        "result": None, "error": None, "form_data": {},
+    })
+
+@router.post("/panel/modelos", response_class=HTMLResponse)
+async def modelos_infer(request: Request, db: Session = Depends(get_internal_db)):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    form = await request.form()
+    model = str(form.get("model_key", "avg-final"))
+    if model not in MODEL_CONFIGS:
+        model = "avg-final"
+
+    result: Optional[dict] = None
+    error: Optional[str]   = None
+
+    try:
+        import joblib
+        import numpy as np
+
+        PKL = MODELS_DIR / "models-pkl"
+        fv = lambda k: float(form[k])
+
+        if model == "avg-final":
+            m = joblib.load(PKL / "linear_avg_final.pkl")
+            X = np.array([[fv("age"), fv("gender_encoded"), fv("course_encoded"),
+                           fv("attendance_absences_total"), fv("support_level_num"),
+                           fv("total_subjects"), fv("term1_avg")]])
+            result = {"Promedio Final Predicho": f"{m.predict(X)[0]:.2f}"}
+
+        elif model == "math-final":
+            m = joblib.load(PKL / "linear_mathematics_final.pkl")
+            X = np.array([[fv("Mathematics_term1"), fv("Mathematics_term2"),
+                           fv("Mathematics_term3"), fv("attendance_absences_total")]])
+            result = {"Nota Final Matemáticas": f"{m.predict(X)[0]:.2f}"}
+
+        elif model == "grade-tendency":
+            scaler, m = joblib.load(PKL / "logistic_grade_tendency.pkl")
+            X = np.array([[fv("term1_avg"), fv("term2_avg"), fv("term3_avg"),
+                           fv("attendance_absences_total"), fv("support_level_num"),
+                           fv("age"), fv("gender_encoded")]])
+            pred = int(m.predict(scaler.transform(X))[0])
+            proba = m.predict_proba(scaler.transform(X))[0]
+            result = {
+                "Predicción":    "⬆ Uptrend" if pred == 1 else "⬇ Downtrend",
+                "P(Downtrend)": f"{proba[0]:.2%}",
+                "P(Uptrend)":   f"{proba[1]:.2%}",
+            }
+
+        elif model == "math-pass":
+            scaler, m = joblib.load(PKL / "logistic_mathematics_pass.pkl")
+            X = np.array([[fv("Mathematics_term1"), fv("Mathematics_term2"),
+                           fv("Mathematics_term3"), fv("attendance_absences_total"),
+                           fv("support_level_num")]])
+            pred = int(m.predict(scaler.transform(X))[0])
+            proba = m.predict_proba(scaler.transform(X))[0]
+            result = {
+                "Predicción": "✅ Pass" if pred == 1 else "❌ Fail",
+                "P(Fail)":   f"{proba[0]:.2%}",
+                "P(Pass)":   f"{proba[1]:.2%}",
+            }
+
+        elif model == "support-level":
+            scaler, m = joblib.load(PKL / "logistic_support_level.pkl")
+            X = np.array([[fv("term1_avg"), fv("attendance_absences_total"),
+                           fv("failed_subjects"), fv("grade_tendency_encoded"),
+                           fv("age"), fv("gender_encoded")]])
+            pred = int(m.predict(scaler.transform(X))[0])
+            proba = m.predict_proba(scaler.transform(X))[0]
+            labels = {0: "🟢 Low", 1: "🟡 Medium", 2: "🔴 High"}
+            result = {
+                "Nivel de Soporte": labels[pred],
+                "P(Low)":   f"{proba[0]:.2%}",
+                "P(Medium)":f"{proba[1]:.2%}",
+                "P(High)":  f"{proba[2]:.2%}",
+            }
+
+    except Exception as e:
+        error = str(e)
+
+    return templates.TemplateResponse("modelos.html", {
+        "request": request, "user": user, "active": "modelos",
+        "model_configs": MODEL_CONFIGS, "current_model": model,
+        "result": result, "error": error, "form_data": dict(form),
+    })
+
+# ── Plataforma ────────────────────────────────────────────────────────────────────
+
+@router.get("/panel/plataforma", response_class=HTMLResponse)
+async def plataforma(
+    request: Request,
+    tab: str = "resumen",
+    page: int = 1,
+    db: Session = Depends(get_internal_db),
+    platform_db: Session = Depends(get_platform_db),
+):
+    user = _get_user(request, db)
+    if not user:
+        return _redirect_login()
+
+    platform_error = None
+    summary: dict = {}
+    items: list = []
+    columns: list = []
+    total = 0
+    total_pages = 1
+    PAGE_SIZE = 30
+
+    try:
+        summary = {
+            "total_users":       platform_db.query(Usuario).count(),
+            "active_users":      platform_db.query(Usuario).filter(Usuario.activo == True).count(),
+            "total_students":    platform_db.query(Alumno).count(),
+            "total_teachers":    platform_db.query(Profesor).count(),
+            "total_courses":     platform_db.query(Curso).count(),
+            "total_enrollments": platform_db.query(Matricula).count(),
+            "total_activities":  platform_db.query(Actividad).count(),
+            "total_submissions": platform_db.query(Entrega).count(),
+        }
+
+        if tab == "usuarios":
+            q = platform_db.query(Usuario).options(joinedload(Usuario.rol))
+            total = q.count()
+            total_pages = max(1, math.ceil(total / PAGE_SIZE))
+            page = max(1, min(page, total_pages))
+            rows = q.offset((page-1)*PAGE_SIZE).limit(PAGE_SIZE).all()
+            columns = ["ID","Nombre","Email","Rol","Activo","Creado"]
+            items = [{
+                "ID": u.id_usuario,
+                "Nombre": f"{u.nombre} {u.apellidos or ''}".strip(),
+                "Email": u.email,
+                "Rol": u.rol.nombre if u.rol else "",
+                "Activo": "✅" if u.activo else "❌",
+                "Creado": u.fecha_creacion.strftime("%d/%m/%Y") if u.fecha_creacion else "",
+            } for u in rows]
+
+        elif tab == "alumnos":
+            q = platform_db.query(Alumno).options(joinedload(Alumno.usuario))
+            total = q.count()
+            total_pages = max(1, math.ceil(total / PAGE_SIZE))
+            page = max(1, min(page, total_pages))
+            rows = q.offset((page-1)*PAGE_SIZE).limit(PAGE_SIZE).all()
+            columns = ["ID","Nombre","Email","Nivel"]
+            items = [{
+                "ID": a.id_alumno,
+                "Nombre": f"{a.usuario.nombre} {a.usuario.apellidos or ''}".strip() if a.usuario else "",
+                "Email": a.usuario.email if a.usuario else "",
+                "Nivel": a.nivel or "",
+            } for a in rows]
+
+        elif tab == "profesores":
+            q = platform_db.query(Profesor).options(joinedload(Profesor.usuario))
+            total = q.count()
+            total_pages = max(1, math.ceil(total / PAGE_SIZE))
+            page = max(1, min(page, total_pages))
+            rows = q.offset((page-1)*PAGE_SIZE).limit(PAGE_SIZE).all()
+            columns = ["ID","Nombre","Email","Especialidad"]
+            items = [{
+                "ID": p.id_profesor,
+                "Nombre": f"{p.usuario.nombre} {p.usuario.apellidos or ''}".strip() if p.usuario else "",
+                "Email": p.usuario.email if p.usuario else "",
+                "Especialidad": p.especialidad or "",
+            } for p in rows]
+
+        elif tab == "cursos":
+            q = platform_db.query(Curso).options(joinedload(Curso.profesor).joinedload(Profesor.usuario))
+            total = q.count()
+            total_pages = max(1, math.ceil(total / PAGE_SIZE))
+            page = max(1, min(page, total_pages))
+            rows = q.offset((page-1)*PAGE_SIZE).limit(PAGE_SIZE).all()
+            columns = ["ID","Nombre","Profesor","Creado"]
+            items = [{
+                "ID": c.id_curso, "Nombre": c.nombre,
+                "Profesor": f"{c.profesor.usuario.nombre} {c.profesor.usuario.apellidos or ''}".strip() if c.profesor and c.profesor.usuario else "",
+                "Creado": c.fecha_creacion.strftime("%d/%m/%Y") if c.fecha_creacion else "",
+            } for c in rows]
+
+    except Exception as e:
+        platform_error = f"Base de datos de plataforma no disponible: {e}"
+
+    return templates.TemplateResponse("plataforma.html", {
+        "request": request, "user": user, "active": "plataforma",
+        "tab": tab, "summary": summary,
+        "columns": columns, "items": items,
+        "total": total, "page": page, "total_pages": total_pages,
+        "platform_error": platform_error,
+    })

+ 0 - 0
internal/src/backend/schemas/__init__.py


+ 26 - 0
internal/src/backend/schemas/auth.py

@@ -0,0 +1,26 @@
+from datetime import datetime
+from typing import Optional
+
+from pydantic import BaseModel
+
+
+class LoginRequest(BaseModel):
+    username: str
+    password: str
+
+
+class TokenResponse(BaseModel):
+    access_token: str
+    token_type: str = "bearer"
+    expires_in: int
+
+
+class AdminInfo(BaseModel):
+    id_usuario: int
+    username: str
+    email: str
+    nombre: Optional[str]
+    rol: str
+    activo: bool
+    ultimo_login: Optional[datetime]
+    fecha_creacion: datetime

+ 93 - 0
internal/src/backend/schemas/inference.py

@@ -0,0 +1,93 @@
+from pydantic import BaseModel, Field
+
+
+# ── M1: Linear Regression — avg_final ─────────────────────────────────────────
+
+class AvgFinalInput(BaseModel):
+    age: int = Field(..., ge=11, le=25, description="Edad del estudiante")
+    gender_encoded: int = Field(..., ge=0, le=1, description="Género: 0=F, 1=M")
+    course_encoded: int = Field(
+        ..., ge=0, le=6,
+        description="Curso: 0=Primary6, 1=Sec1, 2=Sec2, 3=Sec3, 4=Sec4, 5=Sec5, 6=Sec6",
+    )
+    attendance_absences_total: int = Field(..., ge=0, description="Total de ausencias")
+    support_level_num: int = Field(..., ge=0, le=2, description="Apoyo: 0=Low, 1=Medium, 2=High")
+    total_subjects: int = Field(..., ge=1, le=20, description="Nº de asignaturas cursadas")
+    term1_avg: float = Field(..., ge=0.0, le=100.0, description="Promedio de notas del trimestre 1")
+
+
+class AvgFinalOutput(BaseModel):
+    predicted_avg_final: float
+    model: str = "M1 — Linear Regression (avg_final)"
+
+
+# ── M4: Linear Regression — Mathematics_final ─────────────────────────────────
+
+class MathFinalInput(BaseModel):
+    Mathematics_term1: float = Field(..., ge=0.0, le=100.0)
+    Mathematics_term2: float = Field(..., ge=0.0, le=100.0)
+    Mathematics_term3: float = Field(..., ge=0.0, le=100.0)
+    attendance_absences_total: int = Field(..., ge=0)
+
+
+class MathFinalOutput(BaseModel):
+    predicted_math_final: float
+    model: str = "M4 — Linear Regression (Mathematics_final)"
+
+
+# ── M2: Logistic Regression — grade_tendency ──────────────────────────────────
+
+class GradeTendencyInput(BaseModel):
+    term1_avg: float = Field(..., ge=0.0, le=100.0, description="Promedio trimestre 1")
+    term2_avg: float = Field(..., ge=0.0, le=100.0, description="Promedio trimestre 2")
+    term3_avg: float = Field(..., ge=0.0, le=100.0, description="Promedio trimestre 3")
+    attendance_absences_total: int = Field(..., ge=0)
+    support_level_num: int = Field(..., ge=0, le=2, description="0=Low, 1=Medium, 2=High")
+    age: int = Field(..., ge=11, le=25)
+    gender_encoded: int = Field(..., ge=0, le=1, description="0=F, 1=M")
+
+
+class GradeTendencyOutput(BaseModel):
+    predicted_class: int
+    label: str
+    probability_downtrend: float
+    probability_uptrend: float
+    model: str = "M2 — Logistic Regression (grade_tendency)"
+
+
+# ── M3: Logistic Regression — Mathematics pass/fail ───────────────────────────
+
+class MathPassInput(BaseModel):
+    Mathematics_term1: float = Field(..., ge=0.0, le=100.0)
+    Mathematics_term2: float = Field(..., ge=0.0, le=100.0)
+    Mathematics_term3: float = Field(..., ge=0.0, le=100.0)
+    attendance_absences_total: int = Field(..., ge=0)
+    support_level_num: int = Field(..., ge=0, le=2, description="0=Low, 1=Medium, 2=High")
+
+
+class MathPassOutput(BaseModel):
+    predicted_class: int
+    label: str
+    probability_fail: float
+    probability_pass: float
+    model: str = "M3 — Logistic Regression (Mathematics pass/fail)"
+
+
+# ── M5: Multinomial Logistic — support_level ──────────────────────────────────
+
+class SupportLevelInput(BaseModel):
+    term1_avg: float = Field(..., ge=0.0, le=100.0)
+    attendance_absences_total: int = Field(..., ge=0)
+    failed_subjects: int = Field(..., ge=0)
+    grade_tendency_encoded: int = Field(..., ge=0, le=1, description="0=Downtrend/Stable, 1=Uptrend")
+    age: int = Field(..., ge=11, le=25)
+    gender_encoded: int = Field(..., ge=0, le=1, description="0=F, 1=M")
+
+
+class SupportLevelOutput(BaseModel):
+    predicted_class: int
+    label: str
+    probability_low: float
+    probability_medium: float
+    probability_high: float
+    model: str = "M5 — Multinomial Logistic Regression (support_level)"

+ 111 - 0
internal/src/backend/schemas/platform.py

@@ -0,0 +1,111 @@
+from datetime import datetime
+from decimal import Decimal
+from typing import List, Optional
+
+from pydantic import BaseModel
+
+
+class RolOut(BaseModel):
+    id_rol: int
+    nombre: str
+
+    model_config = {"from_attributes": True}
+
+
+class UsuarioOut(BaseModel):
+    id_usuario: int
+    nombre: str
+    apellidos: Optional[str]
+    email: str
+    fecha_creacion: datetime
+    activo: bool
+    rol: RolOut
+
+    model_config = {"from_attributes": True}
+
+
+class AlumnoOut(BaseModel):
+    id_alumno: int
+    nivel: Optional[str]
+    usuario: UsuarioOut
+
+    model_config = {"from_attributes": True}
+
+
+class ProfesorOut(BaseModel):
+    id_profesor: int
+    especialidad: Optional[str]
+    usuario: UsuarioOut
+
+    model_config = {"from_attributes": True}
+
+
+class CursoOut(BaseModel):
+    id_curso: int
+    nombre: str
+    descripcion: Optional[str]
+    fecha_creacion: datetime
+    profesor: ProfesorOut
+
+    model_config = {"from_attributes": True}
+
+
+class MatriculaOut(BaseModel):
+    id_matricula: int
+    id_alumno: int
+    id_curso: int
+    fecha_matricula: datetime
+
+    model_config = {"from_attributes": True}
+
+
+class ActividadOut(BaseModel):
+    id_actividad: int
+    titulo: str
+    descripcion: Optional[str]
+    fecha_publicacion: datetime
+    fecha_entrega: Optional[datetime]
+    puntuacion_maxima: Optional[Decimal]
+    id_curso: int
+
+    model_config = {"from_attributes": True}
+
+
+class CalificacionOut(BaseModel):
+    id_calificacion: int
+    nota: Optional[Decimal]
+    observaciones: Optional[str]
+    fecha_calificacion: datetime
+
+    model_config = {"from_attributes": True}
+
+
+class EntregaOut(BaseModel):
+    id_entrega: int
+    id_actividad: int
+    id_alumno: int
+    fecha_entrega: datetime
+    estado: str
+    calificacion: Optional[CalificacionOut]
+
+    model_config = {"from_attributes": True}
+
+
+class ProgresoOut(BaseModel):
+    id_progreso: int
+    id_alumno: int
+    id_curso: int
+    porcentaje: Optional[Decimal]
+
+    model_config = {"from_attributes": True}
+
+
+class PlatformSummary(BaseModel):
+    total_users: int
+    active_users: int
+    total_students: int
+    total_teachers: int
+    total_courses: int
+    total_enrollments: int
+    total_activities: int
+    total_submissions: int

+ 308 - 0
internal/src/backend/static/style.css

@@ -0,0 +1,308 @@
+@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700&display=swap');
+
+/* ── Variables ─────────────────────────────────────────────────────────────── */
+:root {
+  --bg:         #eef2f7;
+  --bg-soft:    #f7f9fc;
+  --ink:        #142236;
+  --muted:      #5e7188;
+  --line:       #d8e1ec;
+  --card:       #ffffff;
+  --sidebar:    #11263a;
+  --sidebar-2:  #1b344d;
+  --teal:       #00796B; /* Updated to match BridgeLearn Demo */
+  --teal-soft:  #E0F2F1;
+  --teal-dark:  #004D40;
+  --blue:       #1d4ed8;
+  --blue-soft:  #dce8ff;
+  --amber:      #d97706;
+  --amber-soft: #ffedd4;
+  --rose:       #c62849;
+  --rose-soft:  #ffdce4;
+  --ok:         #0f9f6f;
+  --ok-soft:    #d8f8eb;
+  --radius-xl:  24px;
+  --radius-lg:  16px;
+  --radius-md:  12px;
+  --radius-sm:  8px;
+  --shadow-sm:  0 4px 12px rgba(0, 0, 0, 0.05);
+  --shadow-md:  0 10px 30px rgba(0, 0, 0, 0.08);
+  --shadow-lg:  0 20px 40px rgba(0, 0, 0, 0.12);
+  --transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+}
+
+/* ── Animations ────────────────────────────────────────────────────────────── */
+@keyframes slideUpFade {
+  0% { opacity: 0; transform: translateY(20px); }
+  100% { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
+.animate-slide-up {
+  animation: slideUpFade 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+.animate-fade-in {
+  animation: fadeIn 0.5s ease-out forwards;
+}
+.delay-100 { animation-delay: 100ms; opacity: 0; }
+.delay-200 { animation-delay: 200ms; opacity: 0; }
+.delay-300 { animation-delay: 300ms; opacity: 0; }
+
+/* ── Reset ─────────────────────────────────────────────────────────────────── */
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+body { font-family: 'Manrope', sans-serif; background: var(--bg); color: var(--ink); display: flex; min-height: 100vh; font-size: 15px; -webkit-font-smoothing: antialiased; }
+h1,h2,h3,h4,h5 { font-family: 'Sora', sans-serif; }
+a { color: inherit; text-decoration: none; }
+
+/* ── Sidebar ───────────────────────────────────────────────────────────────── */
+.sidebar {
+  width: 258px; flex-shrink: 0;
+  background: var(--sidebar);
+  display: flex; flex-direction: column;
+  position: fixed; top: 0; left: 0; bottom: 0;
+  z-index: 100; overflow: hidden;
+  box-shadow: 4px 0 24px rgba(0,0,0,0.1);
+}
+.sidebar-brand { padding: 26px 20px 20px; border-bottom: 1px solid rgba(255,255,255,0.05); }
+.logo-text { font-family: 'Sora', sans-serif; font-size: 1.15rem; font-weight: 700; color: #fff; letter-spacing: -0.01em; }
+.logo-sub  { font-size: 0.7rem; color: rgba(255,255,255,0.5); margin-top: 4px; }
+
+.sidebar-nav { flex: 1; padding: 18px 12px; overflow-y: auto; }
+.nav-section { font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.1em; color: rgba(255,255,255,0.3); padding: 14px 10px 8px; font-weight: 700; }
+.nav-link {
+  display: flex; align-items: center; gap: 12px;
+  padding: 10px 14px; border-radius: var(--radius-sm);
+  color: rgba(255,255,255,0.65); font-size: 0.88rem; font-weight: 600;
+  transition: var(--transition); margin-bottom: 4px;
+}
+.nav-link:hover  { background: rgba(255,255,255,0.08); color: #fff; transform: translateX(4px); }
+.nav-link.active { background: linear-gradient(135deg, var(--teal), var(--teal-dark)); color: #fff; box-shadow: 0 4px 12px rgba(0, 121, 107, 0.3); }
+.nav-link .icon  { font-size: 1.1rem; width: 22px; text-align: center; flex-shrink: 0; }
+
+.sidebar-footer { padding: 16px 20px; border-top: 1px solid rgba(255,255,255,0.05); background: rgba(0,0,0,0.1); }
+.user-pill { display: flex; align-items: center; gap: 12px; }
+.user-avatar {
+  width: 38px; height: 38px; border-radius: 50%;
+  background: linear-gradient(135deg, var(--teal), var(--teal-dark)); color: #fff;
+  display: flex; align-items: center; justify-content: center;
+  font-weight: 700; font-size: 0.9rem; flex-shrink: 0;
+  box-shadow: 0 4px 10px rgba(0,0,0,0.2);
+}
+.user-name { font-size: 0.85rem; color: rgba(255,255,255,0.9); font-weight: 600; line-height: 1.2; }
+.user-role { font-size: 0.7rem; color: rgba(255,255,255,0.4); margin-top: 2px;}
+.logout-btn {
+  margin-left: auto; background: rgba(255,255,255,0.05); border: none;
+  color: rgba(255,255,255,0.5); cursor: pointer; font-size: 1.1rem;
+  padding: 6px 8px; border-radius: 8px; transition: var(--transition);
+}
+.logout-btn:hover { background: rgba(198,40,73,0.2); color: var(--rose); transform: scale(1.05); }
+
+/* ── Workspace ─────────────────────────────────────────────────────────────── */
+.workspace { margin-left: 258px; flex: 1; display: flex; flex-direction: column; min-height: 100vh; background: linear-gradient(180deg, #eef2f7 0%, #f4f7fa 100%); }
+.topbar {
+  background: rgba(255, 255, 255, 0.8); border-bottom: 1px solid rgba(216, 225, 236, 0.6);
+  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
+  padding: 0 32px; height: 64px;
+  display: flex; align-items: center; gap: 12px;
+  position: sticky; top: 0; z-index: 50;
+}
+.topbar-title { font-family: 'Sora', sans-serif; font-size: 1.05rem; font-weight: 700; color: var(--ink); }
+.topbar-sub   { font-size: 0.8rem; color: var(--muted); font-weight: 500; }
+.topbar-spacer { flex: 1; }
+.content { padding: 32px; flex: 1; max-width: 1600px; margin: 0 auto; width: 100%; }
+
+/* ── Cards ─────────────────────────────────────────────────────────────────── */
+.card { 
+  background: var(--card); border-radius: var(--radius-lg); 
+  box-shadow: var(--shadow-sm); padding: 24px; 
+  transition: var(--transition);
+  border: 1px solid rgba(216, 225, 236, 0.5);
+}
+.card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); border-color: rgba(0, 121, 107, 0.1); }
+.card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 18px; }
+.card-title { font-family: 'Sora', sans-serif; font-size: 1rem; font-weight: 700; color: var(--ink); }
+.card-sub   { font-size: 0.8rem; color: var(--muted); margin-top: 4px; }
+
+/* ── KPI Cards ─────────────────────────────────────────────────────────────── */
+.kpi-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 18px; margin-bottom: 28px; }
+.kpi-card {
+  background: var(--card); border-radius: var(--radius-lg);
+  box-shadow: var(--shadow-sm); padding: 24px;
+  border-top: 4px solid transparent;
+  transition: var(--transition);
+  position: relative; overflow: hidden;
+}
+.kpi-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
+.kpi-card::after {
+  content: ''; position: absolute; top: 0; right: 0; width: 60px; height: 60px;
+  background: radial-gradient(circle, rgba(0,121,107,0.05) 0%, rgba(255,255,255,0) 70%);
+  border-radius: 50%; transform: translate(20%, -20%);
+}
+.kpi-card.teal   { border-top-color: var(--teal); }
+.kpi-card.blue   { border-top-color: var(--blue); }
+.kpi-card.amber  { border-top-color: var(--amber); }
+.kpi-card.rose   { border-top-color: var(--rose); }
+.kpi-card.ok     { border-top-color: var(--ok); }
+.kpi-label { font-size: 0.75rem; font-weight: 700; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 10px; }
+.kpi-value { font-family: 'Sora', sans-serif; font-size: 2.4rem; font-weight: 700; color: var(--ink); line-height: 1; letter-spacing: -0.02em; }
+.kpi-note  { font-size: 0.75rem; color: var(--muted); margin-top: 8px; font-weight: 500;}
+
+/* ── Grids ─────────────────────────────────────────────────────────────────── */
+.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
+.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 24px; }
+.gap-top { margin-top: 24px; }
+
+/* ── Section header ────────────────────────────────────────────────────────── */
+.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+.section-title  { font-family: 'Sora', sans-serif; font-size: 1.2rem; font-weight: 700; color: var(--ink); }
+.section-sub    { font-size: 0.85rem; color: var(--muted); margin-top: 4px; }
+
+/* ── Tables ────────────────────────────────────────────────────────────────── */
+.table-wrap { overflow-x: auto; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); background: var(--card); border: 1px solid var(--line); }
+table { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
+thead th {
+  background: var(--bg-soft); color: var(--muted);
+  font-weight: 700; font-size: 0.75rem;
+  text-transform: uppercase; letter-spacing: 0.05em;
+  padding: 14px 18px; text-align: left;
+  border-bottom: 1px solid var(--line);
+  white-space: nowrap;
+}
+tbody td { padding: 14px 18px; border-bottom: 1px solid var(--line); color: var(--ink); font-weight: 500; }
+tbody tr:last-child td { border-bottom: none; }
+tbody tr:hover { background: rgba(0, 121, 107, 0.02); }
+tbody tr.clickable { cursor: pointer; transition: background 0.2s; }
+
+/* ── Badges ────────────────────────────────────────────────────────────────── */
+.badge { display: inline-flex; align-items: center; padding: 4px 10px; border-radius: 999px; font-size: 0.75rem; font-weight: 700; white-space: nowrap; }
+.badge-ok    { background: var(--ok-soft);    color: #0a6b4a; }
+.badge-rose  { background: var(--rose-soft);  color: var(--rose); }
+.badge-amber { background: var(--amber-soft); color: #8a4e08; }
+.badge-blue  { background: var(--blue-soft);  color: var(--blue); }
+.badge-teal  { background: var(--teal-soft);  color: var(--teal); }
+.badge-muted { background: var(--bg);         color: var(--muted); border: 1px solid var(--line); }
+
+/* ── Buttons ───────────────────────────────────────────────────────────────── */
+.btn {
+  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
+  padding: 10px 20px; border-radius: var(--radius-sm);
+  font-size: 0.88rem; font-weight: 600; cursor: pointer;
+  border: none; transition: var(--transition); font-family: inherit;
+}
+.btn-primary   { background: linear-gradient(135deg, var(--teal), var(--teal-dark)); color: #fff; box-shadow: 0 4px 12px rgba(0, 121, 107, 0.2); }
+.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(0, 121, 107, 0.3); }
+.btn-primary:active { transform: translateY(0); }
+.btn-secondary { background: var(--card); color: var(--ink); border: 1px solid var(--line); box-shadow: 0 2px 4px rgba(0,0,0,0.02); }
+.btn-secondary:hover { background: var(--bg-soft); border-color: #cbd5e1; transform: translateY(-1px); }
+.btn-sm  { padding: 6px 14px; font-size: 0.8rem; }
+.btn-block { width: 100%; }
+
+/* ── Forms ─────────────────────────────────────────────────────────────────── */
+.form-group { margin-bottom: 18px; }
+.form-label { display: block; font-size: 0.8rem; font-weight: 600; color: var(--ink); margin-bottom: 6px; }
+.form-control {
+  width: 100%; padding: 10px 14px;
+  border: 1px solid var(--line); border-radius: var(--radius-sm);
+  font-size: 0.9rem; font-family: inherit; color: var(--ink);
+  background: var(--card); transition: var(--transition); outline: none;
+}
+.form-control:focus { border-color: var(--teal); box-shadow: 0 0 0 4px rgba(0, 121, 107, 0.1); }
+select.form-control { cursor: pointer; }
+
+/* ── Filter bar ────────────────────────────────────────────────────────────── */
+.filter-bar { display: flex; gap: 14px; flex-wrap: wrap; align-items: flex-end; margin-bottom: 24px; padding: 20px; background: var(--card); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); border: 1px solid rgba(216, 225, 236, 0.5); }
+.filter-bar .form-group { margin-bottom: 0; min-width: 160px; }
+
+/* ── Pagination ────────────────────────────────────────────────────────────── */
+.pagination { display: flex; gap: 6px; align-items: center; justify-content: flex-end; margin-top: 20px; flex-wrap: wrap; }
+.page-btn {
+  padding: 8px 14px; border: 1px solid var(--line);
+  border-radius: var(--radius-sm); background: var(--card);
+  color: var(--muted); font-size: 0.85rem; font-weight: 600;
+  cursor: pointer; text-decoration: none; transition: var(--transition);
+}
+.page-btn.active { background: var(--teal); color: #fff; border-color: var(--teal); box-shadow: 0 2px 8px rgba(0, 121, 107, 0.2); }
+.page-btn:hover:not(.active) { background: var(--bg-soft); color: var(--ink); border-color: #cbd5e1; }
+.page-info { font-size: 0.8rem; color: var(--muted); margin-right: auto; font-weight: 500;}
+
+/* ── Tabs ──────────────────────────────────────────────────────────────────── */
+.tabs { 
+  display: flex; gap: 4px; border-bottom: 2px solid var(--line); 
+  margin-bottom: 24px; overflow-x: auto; scrollbar-width: none; 
+}
+.tabs::-webkit-scrollbar { display: none; }
+.tab-btn {
+  padding: 12px 20px; border: none; background: none;
+  font-size: 0.9rem; font-weight: 600; color: var(--muted);
+  cursor: pointer; border-bottom: 3px solid transparent;
+  margin-bottom: -2px; transition: var(--transition); white-space: nowrap;
+  text-decoration: none; font-family: inherit; border-radius: 6px 6px 0 0;
+}
+.tab-btn.active { color: var(--teal); border-bottom-color: var(--teal); background: rgba(0, 121, 107, 0.04); }
+.tab-btn:hover:not(.active)  { color: var(--ink); background: rgba(0,0,0,0.02); }
+
+/* ── Chart ─────────────────────────────────────────────────────────────────── */
+.chart-wrap { width: 100%; min-height: 500px; border-radius: var(--radius-md); overflow: hidden; }
+.fig-frame  { width: 100%; height: 520px; border: none; border-radius: var(--radius-md); background: #fafafa; }
+
+/* ── Result card ───────────────────────────────────────────────────────────── */
+.result-card { background: linear-gradient(135deg, var(--teal-soft), #fff); border: 1px solid rgba(0, 121, 107, 0.2); border-radius: var(--radius-lg); padding: 24px; margin-top: 24px; box-shadow: var(--shadow-sm); }
+.result-card h3 { font-family: 'Sora', sans-serif; color: var(--teal-dark); font-size: 1.05rem; margin-bottom: 16px; font-weight: 700; }
+.result-row { display: flex; justify-content: space-between; align-items: center; padding: 10px 0; border-bottom: 1px dashed rgba(0, 121, 107, 0.2); }
+.result-row:last-child { border-bottom: none; }
+.result-label { font-size: 0.85rem; color: var(--muted); font-weight: 500; }
+.result-value { font-weight: 700; color: var(--ink); font-size: 0.95rem; }
+
+/* ── Alerts ────────────────────────────────────────────────────────────────── */
+.alert { padding: 14px 20px; border-radius: var(--radius-md); font-size: 0.9rem; margin-bottom: 18px; font-weight: 500; display: flex; align-items: center; gap: 10px; box-shadow: var(--shadow-sm); }
+.alert-error   { background: var(--rose-soft);  color: var(--rose);  border: 1px solid rgba(198,40,73,0.3); }
+.alert-success { background: var(--ok-soft);    color: #0a6b4a;      border: 1px solid rgba(15,159,111,0.3); }
+.alert-info    { background: var(--blue-soft);  color: var(--blue);  border: 1px solid rgba(29,78,216,0.3); }
+
+/* ── Login ─────────────────────────────────────────────────────────────────── */
+.login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; background: radial-gradient(circle at top right, var(--sidebar-2), var(--sidebar)); margin-left: 0; }
+.login-box  { background: var(--card); border-radius: var(--radius-xl); box-shadow: var(--shadow-lg); padding: 48px; width: 100%; max-width: 440px; text-align: center; }
+.login-logo { font-family: 'Sora', sans-serif; font-size: 1.8rem; font-weight: 700; color: var(--teal); letter-spacing: -0.02em; }
+.login-app  { font-family: 'Sora', sans-serif; font-size: 1.1rem; font-weight: 600; color: var(--ink); margin-top: 8px; }
+.login-sub  { font-size: 0.85rem; color: var(--muted); margin-top: 6px; margin-bottom: 32px; }
+
+/* ── Detail grid ───────────────────────────────────────────────────────────── */
+.detail-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; }
+.detail-item { padding: 16px 20px; background: var(--bg-soft); border-radius: var(--radius-md); border: 1px solid var(--line); transition: var(--transition); }
+.detail-item:hover { border-color: rgba(0, 121, 107, 0.3); background: #fff; box-shadow: var(--shadow-sm); }
+.detail-item label { font-size: 0.72rem; color: var(--muted); font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; display: block; margin-bottom: 6px; }
+.detail-item span  { font-size: 0.95rem; color: var(--ink); font-weight: 700; }
+
+/* ── Progress bar ──────────────────────────────────────────────────────────── */
+.progress { height: 8px; background: var(--bg); border-radius: 999px; overflow: hidden; border: 1px solid var(--line); }
+.progress-fill { height: 100%; background: linear-gradient(90deg, var(--teal), #26A69A); border-radius: 999px; transition: width 1s cubic-bezier(0.16, 1, 0.3, 1); }
+
+/* ── Quick links ───────────────────────────────────────────────────────────── */
+.quick-links { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 16px; }
+.quick-link {
+  display: flex; align-items: center; gap: 16px;
+  padding: 20px; background: var(--card);
+  border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
+  border: 1px solid rgba(216, 225, 236, 0.5); transition: var(--transition);
+}
+.quick-link:hover { border-color: var(--teal); box-shadow: var(--shadow-md); transform: translateY(-3px); }
+.quick-link .ql-icon { font-size: 1.6rem; background: var(--bg-soft); width: 48px; height: 48px; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: var(--teal); transition: var(--transition); }
+.quick-link:hover .ql-icon { background: var(--teal); color: #fff; }
+.quick-link .ql-title { font-weight: 700; font-size: 0.95rem; color: var(--ink); }
+.quick-link .ql-sub   { font-size: 0.8rem; color: var(--muted); margin-top: 3px; }
+
+/* ── Responsive ────────────────────────────────────────────────────────────── */
+@media (max-width: 900px) {
+  .sidebar { transform: translateX(-100%); }
+  .workspace { margin-left: 0; }
+  .grid-2, .grid-3 { grid-template-columns: 1fr; }
+  .kpi-grid { grid-template-columns: 1fr 1fr; }
+}
+@media (max-width: 560px) {
+  .kpi-grid { grid-template-columns: 1fr; }
+  .content  { padding: 20px; }
+}

+ 72 - 0
internal/src/backend/templates/base.html

@@ -0,0 +1,72 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>{% block title %}Panel{% endblock %} — OpenData II</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="/static/style.css">
+  {% block head %}{% endblock %}
+</head>
+<body>
+
+<nav class="sidebar">
+  <div class="sidebar-brand">
+    <div class="logo-text">OpenData II</div>
+    <div class="logo-sub">Panel de Administración Interna</div>
+  </div>
+
+  <div class="sidebar-nav">
+    <a href="/panel" class="nav-link {% if active == 'panel' %}active{% endif %}">
+      <span class="icon">📊</span> Panel
+    </a>
+
+    <div class="nav-section">Análisis</div>
+    <a href="/panel/estadisticas" class="nav-link {% if active == 'estadisticas' %}active{% endif %}">
+      <span class="icon">📈</span> Estadísticas
+    </a>
+    <a href="/panel/predicciones" class="nav-link {% if active == 'predicciones' %}active{% endif %}">
+      <span class="icon">🤖</span> Predicciones ML/NLP
+    </a>
+    <a href="/panel/estudiantes" class="nav-link {% if active == 'estudiantes' %}active{% endif %}">
+      <span class="icon">👥</span> Estudiantes
+    </a>
+    <a href="/panel/modelos" class="nav-link {% if active == 'modelos' %}active{% endif %}">
+      <span class="icon">🔬</span> Inferencia
+    </a>
+
+    <div class="nav-section">Base de Datos</div>
+    <a href="/panel/plataforma" class="nav-link {% if active == 'plataforma' %}active{% endif %}">
+      <span class="icon">🏫</span> Plataforma
+    </a>
+  </div>
+
+  <div class="sidebar-footer">
+    <div class="user-pill">
+      <div class="user-avatar">{{ user.username[0].upper() }}</div>
+      <div>
+        <div class="user-name">{{ user.nombre or user.username }}</div>
+        <div class="user-role">{{ user.rol.nombre }}</div>
+      </div>
+      <form action="/logout" method="post" style="margin-left:auto">
+        <button type="submit" class="logout-btn" title="Cerrar sesión">⎋</button>
+      </form>
+    </div>
+  </div>
+</nav>
+
+<main class="workspace">
+  <div class="topbar">
+    <span class="topbar-title">{% block topbar_title %}Panel{% endblock %}</span>
+    {% block topbar_extra %}{% endblock %}
+  </div>
+  <div class="content">
+    {% block content %}{% endblock %}
+  </div>
+</main>
+
+{% block scripts %}{% endblock %}
+</body>
+</html>

+ 79 - 0
internal/src/backend/templates/estadisticas.html

@@ -0,0 +1,79 @@
+{% extends "base.html" %}
+{% block title %}Estadísticas{% endblock %}
+{% block topbar_title %}Estadísticas{% endblock %}
+{% block topbar_extra %}
+<span class="topbar-sub">{{ current_name }}</span>
+{% endblock %}
+
+{% block head %}
+<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
+{% endblock %}
+
+{% block content %}
+
+<!-- Dashboard selector tabs -->
+<div class="tabs animate-slide-up">
+  {% for key, name in stat_dashboards.items() %}
+  <a href="/panel/estadisticas?dashboard={{ key }}"
+     class="tab-btn {% if current_dashboard == key %}active{% endif %}">{{ name }}</a>
+  {% endfor %}
+</div>
+
+<!-- Chart -->
+<div class="card animate-slide-up delay-100" style="margin-bottom:28px">
+  <div class="card-header">
+    <div>
+      <div class="card-title">{{ current_name }}</div>
+      <div class="card-sub">Dashboard estadístico interactivo con visualizaciones Plotly de alta resolución</div>
+    </div>
+  </div>
+  {% if chart_json %}
+  <div id="chart-main" class="chart-wrap animate-fade-in delay-200"></div>
+  {% else %}
+  <div class="alert alert-info animate-fade-in delay-200">
+    <span style="font-size:1.2rem">ℹ️</span> Selecciona un dashboard en las pestañas superiores para visualizar los datos.
+  </div>
+  {% endif %}
+</div>
+
+<!-- Iframe figures -->
+{% if figures %}
+<div class="section-header animate-slide-up delay-200">
+  <div>
+    <div class="section-title">Figuras interactivas adicionales</div>
+    <div class="section-sub">Explora los modelos dinámicos detallados generados a partir de los últimos datos de la plataforma ({{ figures|length }} gráficos)</div>
+  </div>
+</div>
+
+<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(320px,1fr));gap:24px" class="animate-slide-up delay-300">
+  {% for fig in figures %}
+  <div class="card" style="padding:0; overflow:hidden;">
+    <div class="card-header" style="margin-bottom:0; padding:16px 20px; border-bottom: 1px solid var(--line); background: var(--bg-soft);">
+      <div class="card-title" style="font-size:0.88rem; font-family: 'Manrope', sans-serif;">{{ fig|replace('_', ' ')|title }}</div>
+      <a href="/panel/figuras/{{ fig }}" target="_blank" class="btn btn-secondary btn-sm" style="background:white;">
+        ↗ Ampliar
+      </a>
+    </div>
+    <iframe src="/panel/figuras/{{ fig }}" class="fig-frame" style="height:360px; border-radius:0;"></iframe>
+  </div>
+  {% endfor %}
+</div>
+{% endif %}
+
+{% endblock %}
+
+{% block scripts %}
+{% if chart_json %}
+<script>
+  const fig = {{ chart_json | safe }};
+  Plotly.react('chart-main', fig.data, {
+    ...fig.layout,
+    height: 500,
+    margin: {t:40,r:20,b:40,l:50},
+    paper_bgcolor:'rgba(0,0,0,0)',
+    plot_bgcolor:'rgba(0,0,0,0)',
+    font: { family: 'Manrope, sans-serif', color: '#5e7188' },
+  }, {responsive:true, displayModeBar:true, modeBarButtonsToRemove:['toImage', 'sendDataToCloud']});
+</script>
+{% endif %}
+{% endblock %}

+ 117 - 0
internal/src/backend/templates/estudiante.html

@@ -0,0 +1,117 @@
+{% extends "base.html" %}
+{% block title %}{{ student.student_id }}{% endblock %}
+{% block topbar_title %}Perfil de Estudiante{% endblock %}
+{% block topbar_extra %}
+<span class="topbar-sub">{{ student.student_id }}</span>
+<span class="topbar-spacer"></span>
+<a href="/panel/estudiantes" class="btn btn-secondary btn-sm">← Volver</a>
+{% endblock %}
+
+{% block content %}
+
+<!-- Student info -->
+<div class="card" style="margin-bottom:18px">
+  <div class="card-header">
+    <div>
+      <div class="card-title">{{ student.student_id }}</div>
+      <div class="card-sub">{{ student.school_name }} · {{ student.city }}, {{ student.country }}</div>
+    </div>
+    <div style="display:flex;gap:8px;flex-wrap:wrap">
+      {% if student.support_level == 'High' %}
+        <span class="badge badge-rose">Apoyo {{ student.support_level }}</span>
+      {% elif student.support_level == 'Medium' %}
+        <span class="badge badge-amber">Apoyo {{ student.support_level }}</span>
+      {% else %}
+        <span class="badge badge-ok">Apoyo {{ student.support_level }}</span>
+      {% endif %}
+      {% if student.grade_tendency == 'Uptrend' %}
+        <span class="badge badge-ok">⬆ Uptrend</span>
+      {% elif student.grade_tendency == 'Downtrend' %}
+        <span class="badge badge-rose">⬇ Downtrend</span>
+      {% else %}
+        <span class="badge badge-muted">→ Stable</span>
+      {% endif %}
+    </div>
+  </div>
+
+  <div class="detail-grid">
+    <div class="detail-item"><label>Género</label><span>{{ student.gender }}</span></div>
+    <div class="detail-item"><label>Edad</label><span>{{ student.age }}</span></div>
+    <div class="detail-item"><label>Curso</label><span>{{ student.course }}</span></div>
+    <div class="detail-item"><label>Año Académico</label><span>{{ student.academic_year }}</span></div>
+    <div class="detail-item"><label>Grupo</label><span>{{ student.class_group }}</span></div>
+    <div class="detail-item"><label>Tutor</label><span>{{ student.homeroom_teacher }}</span></div>
+    <div class="detail-item"><label>Ausencias</label><span>{{ student.attendance_absences_total }}</span></div>
+    <div class="detail-item"><label>Promedio Final</label><span style="color:var(--teal);font-weight:700">{{ student.avg_final }}</span></div>
+    <div class="detail-item"><label>Asig. Aprobadas</label><span>{{ student.passed_subjects }} / {{ student.total_subjects }}</span></div>
+    <div class="detail-item"><label>% Aprobación</label><span>{{ "%.1f"|format(student.passed_rate_percent) }}%</span></div>
+    <div class="detail-item"><label>Notas (min/max)</label><span>{{ student.min_grade }} / {{ student.max_grade }}</span></div>
+    <div class="detail-item"><label>Notas</label><span style="font-size:0.78rem">{{ student.notes }}</span></div>
+  </div>
+</div>
+
+<!-- Grades + Predictions -->
+<div class="grid-2" style="gap:18px">
+
+  <!-- Subject grades -->
+  <div class="card">
+    <div class="card-header">
+      <div class="card-title">Notas por asignatura</div>
+      <div class="card-sub">{{ grades|length }} asignaturas</div>
+    </div>
+    {% if grades %}
+    <div class="table-wrap">
+      <table>
+        <thead>
+          <tr><th>Asignatura</th><th>Nota Final</th><th>Estado</th></tr>
+        </thead>
+        <tbody>
+          {% for g in grades %}
+          <tr>
+            <td>{{ g.subject }}</td>
+            <td style="font-weight:700">{{ g.final }}</td>
+            <td>
+              {% if g.status == 'Pass' %}
+                <span class="badge badge-ok">Pass</span>
+              {% elif g.status == 'Fail' %}
+                <span class="badge badge-rose">Fail</span>
+              {% else %}
+                <span class="badge badge-muted">—</span>
+              {% endif %}
+            </td>
+          </tr>
+          {% endfor %}
+        </tbody>
+      </table>
+    </div>
+    {% else %}
+    <div class="alert alert-info">No hay datos de asignaturas.</div>
+    {% endif %}
+  </div>
+
+  <!-- Predictions -->
+  <div class="card">
+    <div class="card-header">
+      <div class="card-title">Predicciones ML / NLP</div>
+      <div class="card-sub">Resultados de todos los modelos</div>
+    </div>
+    {% if predictions %}
+      {% for label, pred in predictions.items() %}
+      <div style="margin-bottom:14px;padding:14px;background:var(--bg-soft);border-radius:var(--radius-sm)">
+        <div style="font-size:0.75rem;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:0.05em;margin-bottom:8px">{{ label }}</div>
+        {% for k, v in pred.items() %}
+        {% if k != 'student_id' %}
+        <div style="display:flex;justify-content:space-between;padding:4px 0;border-bottom:1px solid var(--line);font-size:0.82rem">
+          <span style="color:var(--muted)">{{ k }}</span>
+          <span style="font-weight:600">{{ v }}</span>
+        </div>
+        {% endif %}
+        {% endfor %}
+      </div>
+      {% endfor %}
+    {% else %}
+    <div class="alert alert-info">No hay predicciones disponibles para este estudiante.</div>
+    {% endif %}
+  </div>
+</div>
+{% endblock %}

+ 137 - 0
internal/src/backend/templates/estudiantes.html

@@ -0,0 +1,137 @@
+{% extends "base.html" %}
+{% block title %}Estudiantes{% endblock %}
+{% block topbar_title %}Estudiantes{% endblock %}
+{% block topbar_extra %}
+<span class="topbar-sub">{{ total }} registros</span>
+{% endblock %}
+
+{% block content %}
+
+<!-- Filters -->
+<form method="get" action="/panel/estudiantes">
+  <div class="filter-bar">
+    <div class="form-group">
+      <label class="form-label">País</label>
+      <select name="country" class="form-control">
+        <option value="">Todos</option>
+        {% for c in countries %}
+        <option value="{{ c }}" {% if filters.country == c %}selected{% endif %}>{{ c }}</option>
+        {% endfor %}
+      </select>
+    </div>
+    <div class="form-group">
+      <label class="form-label">Curso</label>
+      <select name="course" class="form-control">
+        <option value="">Todos</option>
+        {% for c in courses %}
+        <option value="{{ c }}" {% if filters.course == c %}selected{% endif %}>{{ c }}</option>
+        {% endfor %}
+      </select>
+    </div>
+    <div class="form-group">
+      <label class="form-label">Género</label>
+      <select name="gender" class="form-control">
+        <option value="">Todos</option>
+        <option value="F" {% if filters.gender == 'F' %}selected{% endif %}>Femenino</option>
+        <option value="M" {% if filters.gender == 'M' %}selected{% endif %}>Masculino</option>
+      </select>
+    </div>
+    <div class="form-group">
+      <label class="form-label">Nivel de Apoyo</label>
+      <select name="support_level" class="form-control">
+        <option value="">Todos</option>
+        <option value="Low"    {% if filters.support_level == 'Low'    %}selected{% endif %}>Low</option>
+        <option value="Medium" {% if filters.support_level == 'Medium' %}selected{% endif %}>Medium</option>
+        <option value="High"   {% if filters.support_level == 'High'   %}selected{% endif %}>High</option>
+      </select>
+    </div>
+    <div style="display:flex;gap:8px;align-items:flex-end">
+      <button type="submit" class="btn btn-primary">Filtrar</button>
+      <a href="/panel/estudiantes" class="btn btn-secondary">✕ Limpiar</a>
+    </div>
+  </div>
+</form>
+
+<!-- Table -->
+<div class="card">
+  <div class="card-header">
+    <div class="card-title">Lista de estudiantes</div>
+    <span class="card-sub">Clic en un estudiante para ver su perfil completo</span>
+  </div>
+
+  {% if rows %}
+  <div class="table-wrap">
+    <table>
+      <thead>
+        <tr>
+          <th>ID</th>
+          <th>Género</th>
+          <th>Edad</th>
+          <th>País</th>
+          <th>Curso</th>
+          <th>Apoyo</th>
+          <th>Promedio Final</th>
+          <th>Tendencia</th>
+          <th>Aprobación %</th>
+        </tr>
+      </thead>
+      <tbody>
+        {% for row in rows %}
+        <tr class="clickable" onclick="window.location='/panel/estudiantes/{{ row.student_id }}'">
+          <td style="font-weight:700;color:var(--teal)">{{ row.student_id }}</td>
+          <td>{{ row.gender }}</td>
+          <td>{{ row.age }}</td>
+          <td>{{ row.country }}</td>
+          <td>{{ row.course }}</td>
+          <td>
+            {% if row.support_level == 'High' %}
+              <span class="badge badge-rose">High</span>
+            {% elif row.support_level == 'Medium' %}
+              <span class="badge badge-amber">Medium</span>
+            {% else %}
+              <span class="badge badge-ok">Low</span>
+            {% endif %}
+          </td>
+          <td style="font-weight:700">{{ "%.1f"|format(row.avg_final) }}</td>
+          <td>
+            {% if row.grade_tendency == 'Uptrend' %}
+              <span class="badge badge-ok">⬆ Uptrend</span>
+            {% elif row.grade_tendency == 'Downtrend' %}
+              <span class="badge badge-rose">⬇ Downtrend</span>
+            {% else %}
+              <span class="badge badge-muted">→ Stable</span>
+            {% endif %}
+          </td>
+          <td>
+            {% set pct = row.passed_rate_percent %}
+            <div style="display:flex;align-items:center;gap:8px">
+              <div class="progress" style="flex:1;min-width:60px">
+                <div class="progress-fill" style="width:{{ pct }}%"></div>
+              </div>
+              <span style="font-size:0.78rem;color:var(--muted);min-width:38px">{{ "%.0f"|format(pct) }}%</span>
+            </div>
+          </td>
+        </tr>
+        {% endfor %}
+      </tbody>
+    </table>
+  </div>
+
+  <div class="pagination">
+    <span class="page-info">{{ total }} estudiantes · página {{ page }} de {{ total_pages }}</span>
+    {% if page > 1 %}
+    <a href="/panel/estudiantes?country={{ filters.country }}&course={{ filters.course }}&gender={{ filters.gender }}&support_level={{ filters.support_level }}&page={{ page-1 }}" class="page-btn">‹</a>
+    {% endif %}
+    {% for p in range([1, page-2]|max, [total_pages+1, page+3]|min) %}
+    <a href="/panel/estudiantes?country={{ filters.country }}&course={{ filters.course }}&gender={{ filters.gender }}&support_level={{ filters.support_level }}&page={{ p }}"
+       class="page-btn {% if p == page %}active{% endif %}">{{ p }}</a>
+    {% endfor %}
+    {% if page < total_pages %}
+    <a href="/panel/estudiantes?country={{ filters.country }}&course={{ filters.course }}&gender={{ filters.gender }}&support_level={{ filters.support_level }}&page={{ page+1 }}" class="page-btn">›</a>
+    {% endif %}
+  </div>
+  {% else %}
+  <div class="alert alert-info">No hay estudiantes que coincidan con los filtros.</div>
+  {% endif %}
+</div>
+{% endblock %}

+ 38 - 0
internal/src/backend/templates/login.html

@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Acceder — OpenData II</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="/static/style.css">
+</head>
+<body>
+<div class="login-page">
+  <div class="login-box">
+    <div class="login-logo">OpenData II</div>
+    <div class="login-app">Panel Interno</div>
+    <div class="login-sub">Acceso restringido a administradores</div>
+
+    {% if error %}
+    <div class="alert alert-error">{{ error }}</div>
+    {% endif %}
+
+    <form method="post" action="/login">
+      <div class="form-group">
+        <label class="form-label" for="username">Usuario o email</label>
+        <input id="username" name="username" type="text" class="form-control" placeholder="admin" autofocus required>
+      </div>
+      <div class="form-group">
+        <label class="form-label" for="password">Contraseña</label>
+        <input id="password" name="password" type="password" class="form-control" placeholder="••••••••" required>
+      </div>
+      <button type="submit" class="btn btn-primary btn-block" style="margin-top:8px">
+        Acceder →
+      </button>
+    </form>
+  </div>
+</div>
+</body>
+</html>

+ 110 - 0
internal/src/backend/templates/modelos.html

@@ -0,0 +1,110 @@
+{% extends "base.html" %}
+{% block title %}Inferencia{% endblock %}
+{% block topbar_title %}Inferencia de Modelos{% endblock %}
+{% block topbar_extra %}
+<span class="topbar-sub">5 modelos ML disponibles</span>
+{% endblock %}
+
+{% block content %}
+
+<!-- Model selector -->
+<div class="tabs">
+  {% for key, cfg in model_configs.items() %}
+  <a href="/panel/modelos?model={{ key }}"
+     class="tab-btn {% if current_model == key %}active{% endif %}">{{ cfg.name.split('—')[0].strip() }}</a>
+  {% endfor %}
+</div>
+
+{% set cfg = model_configs[current_model] %}
+
+<div class="grid-2" style="gap:18px">
+
+  <!-- Form -->
+  <div class="card">
+    <div class="card-header">
+      <div>
+        <div class="card-title">{{ cfg.name }}</div>
+        <div class="card-sub">Introduce los valores y obtén la predicción</div>
+      </div>
+    </div>
+
+    {% if error %}
+    <div class="alert alert-error">{{ error }}</div>
+    {% endif %}
+
+    <form method="post" action="/panel/modelos">
+      <input type="hidden" name="model_key" value="{{ current_model }}">
+
+      {% for field in cfg.fields %}
+      <div class="form-group">
+        <label class="form-label">{{ field.label }}</label>
+        {% if field.type == 'select' %}
+        <select name="{{ field.name }}" class="form-control">
+          {% for val, label in field.options.items() %}
+          <option value="{{ val }}"
+            {% if form_data and form_data.get(field.name) == val %}selected{% endif %}>
+            {{ label }}
+          </option>
+          {% endfor %}
+        </select>
+        {% else %}
+        <input
+          type="number" name="{{ field.name }}" class="form-control"
+          min="{{ field.min }}" max="{{ field.max }}" step="0.01"
+          value="{{ form_data[field.name] if form_data and field.name in form_data else field.default }}"
+          required>
+        {% endif %}
+      </div>
+      {% endfor %}
+
+      <button type="submit" class="btn btn-primary btn-block" style="margin-top:8px">
+        🔬 Ejecutar predicción
+      </button>
+    </form>
+  </div>
+
+  <!-- Result + Info -->
+  <div>
+    {% if result %}
+    <div class="result-card" style="margin-bottom:18px">
+      <h3>✅ Resultado de la predicción</h3>
+      {% for k, v in result.items() %}
+      <div class="result-row">
+        <span class="result-label">{{ k }}</span>
+        <span class="result-value">{{ v }}</span>
+      </div>
+      {% endfor %}
+    </div>
+    {% endif %}
+
+    <div class="card">
+      <div class="card-header">
+        <div class="card-title">Modelos disponibles</div>
+      </div>
+      {% for key, cfg_item in model_configs.items() %}
+      <div style="padding:12px 0;border-bottom:1px solid var(--line);{% if loop.last %}border-bottom:none{% endif %}">
+        <div style="display:flex;justify-content:space-between;align-items:center">
+          <div>
+            <div style="font-size:0.83rem;font-weight:700;color:{% if key == current_model %}var(--teal){% else %}var(--ink){% endif %}">{{ cfg_item.name }}</div>
+            <div style="font-size:0.73rem;color:var(--muted);margin-top:2px">{{ cfg_item.fields|length }} variables de entrada</div>
+          </div>
+          <a href="/panel/modelos?model={{ key }}" class="btn btn-secondary btn-sm">Usar</a>
+        </div>
+      </div>
+      {% endfor %}
+    </div>
+
+    <div class="card gap-top">
+      <div class="card-header">
+        <div class="card-title">Codificaciones</div>
+      </div>
+      <div style="font-size:0.8rem;color:var(--muted);line-height:1.8">
+        <div><strong>gender_encoded:</strong> 0 = Femenino · 1 = Masculino</div>
+        <div><strong>course_encoded:</strong> 0=Primary6 · 1=Sec1 · 2=Sec2 · 3=Sec3 · 4=Sec4 · 5=Sec5 · 6=Sec6</div>
+        <div><strong>support_level_num:</strong> 0=Low · 1=Medium · 2=High</div>
+        <div><strong>grade_tendency_encoded:</strong> 0=Downtrend/Stable · 1=Uptrend</div>
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}

+ 134 - 0
internal/src/backend/templates/panel.html

@@ -0,0 +1,134 @@
+{% extends "base.html" %}
+{% block title %}Panel{% endblock %}
+{% block topbar_title %}Panel de Control{% endblock %}
+
+{% block head %}
+<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
+{% endblock %}
+
+{% block content %}
+
+<!-- KPIs -->
+<div class="kpi-grid">
+  <div class="kpi-card teal">
+    <div class="kpi-label">Total Estudiantes</div>
+    <div class="kpi-value">{{ kpis.total_students }}</div>
+    <div class="kpi-note">Dataset completo</div>
+  </div>
+  <div class="kpi-card blue">
+    <div class="kpi-label">Promedio Final</div>
+    <div class="kpi-value">{{ kpis.avg_final }}</div>
+    <div class="kpi-note">Sobre 100 puntos</div>
+  </div>
+  <div class="kpi-card ok">
+    <div class="kpi-label">Tasa de Aprobación</div>
+    <div class="kpi-value">{{ kpis.pass_rate }}%</div>
+    <div class="kpi-note">Promedio global</div>
+  </div>
+  <div class="kpi-card rose">
+    <div class="kpi-label">Riesgo Alto (NLP)</div>
+    <div class="kpi-value">{{ kpis.high_risk }}</div>
+    <div class="kpi-note">Requieren atención</div>
+  </div>
+  <div class="kpi-card amber">
+    <div class="kpi-label">Apoyo Bajo</div>
+    <div class="kpi-value">{{ kpis.low_support }}</div>
+    <div class="kpi-note">Nivel soporte bajo</div>
+  </div>
+</div>
+
+<!-- Chart + Quick links -->
+<div class="grid-2" style="gap:20px">
+  <div class="card">
+    <div class="card-header">
+      <div>
+        <div class="card-title">Demografía del Dataset</div>
+        <div class="card-sub">Distribución por género, país, edad y curso</div>
+      </div>
+      <a href="/panel/estadisticas?dashboard=demographics" class="btn btn-secondary btn-sm">Ver más →</a>
+    </div>
+    <div id="chart-demo" class="chart-wrap"></div>
+  </div>
+
+  <div>
+    <div class="card" style="margin-bottom:18px">
+      <div class="card-header">
+        <div>
+          <div class="card-title">Acceso rápido</div>
+          <div class="card-sub">Secciones del panel</div>
+        </div>
+      </div>
+      <div class="quick-links">
+        <a href="/panel/estadisticas" class="quick-link">
+          <span class="ql-icon">📈</span>
+          <div>
+            <div class="ql-title">Estadísticas</div>
+            <div class="ql-sub">{{ stat_dashboards|length }} dashboards</div>
+          </div>
+        </a>
+        <a href="/panel/predicciones?tipo=risk_alert" class="quick-link">
+          <span class="ql-icon">⚠️</span>
+          <div>
+            <div class="ql-title">Alertas de Riesgo</div>
+            <div class="ql-sub">NLP risk_alert</div>
+          </div>
+        </a>
+        <a href="/panel/predicciones?tipo=career_recommendation" class="quick-link">
+          <span class="ql-icon">🎯</span>
+          <div>
+            <div class="ql-title">Recomendación</div>
+            <div class="ql-sub">NLP career</div>
+          </div>
+        </a>
+        <a href="/panel/predicciones?tipo=math_risk" class="quick-link">
+          <span class="ql-icon">📐</span>
+          <div>
+            <div class="ql-title">Riesgo Matemáticas</div>
+            <div class="ql-sub">ML math_risk</div>
+          </div>
+        </a>
+        <a href="/panel/modelos" class="quick-link">
+          <span class="ql-icon">🔬</span>
+          <div>
+            <div class="ql-title">Inferencia</div>
+            <div class="ql-sub">5 modelos ML</div>
+          </div>
+        </a>
+        <a href="/panel/plataforma" class="quick-link">
+          <span class="ql-icon">🏫</span>
+          <div>
+            <div class="ql-title">Plataforma</div>
+            <div class="ql-sub">Usuarios y cursos</div>
+          </div>
+        </a>
+      </div>
+    </div>
+
+    <div class="card">
+      <div class="card-header">
+        <div class="card-title">Dashboards ML disponibles</div>
+      </div>
+      <div style="display:flex;flex-direction:column;gap:6px">
+        {% for key, name in ml_dashboards.items() %}
+        <a href="/panel/predicciones/dashboard/{{ key }}" class="btn btn-secondary btn-sm" style="justify-content:flex-start">
+          {{ name }}
+        </a>
+        {% endfor %}
+      </div>
+    </div>
+  </div>
+</div>
+{% endblock %}
+
+{% block scripts %}
+<script>
+  const fig = {{ chart_json | safe }};
+  Plotly.react('chart-demo', fig.data, {
+    ...fig.layout,
+    height: 420,
+    margin: {t:30,r:10,b:40,l:40},
+    paper_bgcolor:'rgba(0,0,0,0)',
+    plot_bgcolor:'rgba(0,0,0,0)',
+  }, {responsive:true, displayModeBar:false});
+</script>
+{% endblock %}

+ 114 - 0
internal/src/backend/templates/plataforma.html

@@ -0,0 +1,114 @@
+{% extends "base.html" %}
+{% block title %}Plataforma{% endblock %}
+{% block topbar_title %}Plataforma de Usuarios{% endblock %}
+{% block topbar_extra %}
+{% if summary %}
+<span class="topbar-sub">{{ summary.total_users }} usuarios · {{ summary.total_courses }} cursos</span>
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+{% if platform_error %}
+<div class="alert alert-error">{{ platform_error }}</div>
+{% endif %}
+
+{% if summary %}
+<!-- KPIs -->
+<div class="kpi-grid" style="margin-bottom:20px">
+  <div class="kpi-card teal">
+    <div class="kpi-label">Usuarios Totales</div>
+    <div class="kpi-value">{{ summary.total_users }}</div>
+    <div class="kpi-note">{{ summary.active_users }} activos</div>
+  </div>
+  <div class="kpi-card blue">
+    <div class="kpi-label">Alumnos</div>
+    <div class="kpi-value">{{ summary.total_students }}</div>
+  </div>
+  <div class="kpi-card ok">
+    <div class="kpi-label">Profesores</div>
+    <div class="kpi-value">{{ summary.total_teachers }}</div>
+  </div>
+  <div class="kpi-card amber">
+    <div class="kpi-label">Cursos</div>
+    <div class="kpi-value">{{ summary.total_courses }}</div>
+  </div>
+  <div class="kpi-card rose">
+    <div class="kpi-label">Matrículas</div>
+    <div class="kpi-value">{{ summary.total_enrollments }}</div>
+  </div>
+</div>
+{% endif %}
+
+<!-- Tabs -->
+<div class="tabs">
+  <a href="/panel/plataforma?tab=resumen"   class="tab-btn {% if tab == 'resumen'   %}active{% endif %}">Resumen</a>
+  <a href="/panel/plataforma?tab=usuarios"  class="tab-btn {% if tab == 'usuarios'  %}active{% endif %}">Usuarios</a>
+  <a href="/panel/plataforma?tab=alumnos"   class="tab-btn {% if tab == 'alumnos'   %}active{% endif %}">Alumnos</a>
+  <a href="/panel/plataforma?tab=profesores"class="tab-btn {% if tab == 'profesores'}active{% endif %}">Profesores</a>
+  <a href="/panel/plataforma?tab=cursos"    class="tab-btn {% if tab == 'cursos'    %}active{% endif %}">Cursos</a>
+</div>
+
+{% if tab == 'resumen' and summary %}
+<div class="card">
+  <div class="card-header"><div class="card-title">Resumen de la plataforma</div></div>
+  <div class="detail-grid">
+    <div class="detail-item"><label>Usuarios totales</label><span>{{ summary.total_users }}</span></div>
+    <div class="detail-item"><label>Usuarios activos</label><span>{{ summary.active_users }}</span></div>
+    <div class="detail-item"><label>Alumnos</label><span>{{ summary.total_students }}</span></div>
+    <div class="detail-item"><label>Profesores</label><span>{{ summary.total_teachers }}</span></div>
+    <div class="detail-item"><label>Cursos</label><span>{{ summary.total_courses }}</span></div>
+    <div class="detail-item"><label>Matrículas</label><span>{{ summary.total_enrollments }}</span></div>
+    <div class="detail-item"><label>Actividades</label><span>{{ summary.total_activities }}</span></div>
+    <div class="detail-item"><label>Entregas</label><span>{{ summary.total_submissions }}</span></div>
+  </div>
+</div>
+
+{% else %}
+<!-- Data table -->
+<div class="card">
+  <div class="card-header">
+    <div class="card-title">
+      {% if tab == 'usuarios' %}Usuarios
+      {% elif tab == 'alumnos' %}Alumnos
+      {% elif tab == 'profesores' %}Profesores
+      {% elif tab == 'cursos' %}Cursos
+      {% endif %}
+    </div>
+    <span class="card-sub">{{ total }} registros</span>
+  </div>
+
+  {% if items %}
+  <div class="table-wrap">
+    <table>
+      <thead>
+        <tr>{% for col in columns %}<th>{{ col }}</th>{% endfor %}</tr>
+      </thead>
+      <tbody>
+        {% for item in items %}
+        <tr>{% for col in columns %}<td>{{ item[col] }}</td>{% endfor %}</tr>
+        {% endfor %}
+      </tbody>
+    </table>
+  </div>
+
+  <div class="pagination">
+    <span class="page-info">{{ total }} registros · página {{ page }} de {{ total_pages }}</span>
+    {% if page > 1 %}
+    <a href="/panel/plataforma?tab={{ tab }}&page={{ page-1 }}" class="page-btn">‹</a>
+    {% endif %}
+    {% for p in range([1, page-2]|max, [total_pages+1, page+3]|min) %}
+    <a href="/panel/plataforma?tab={{ tab }}&page={{ p }}"
+       class="page-btn {% if p == page %}active{% endif %}">{{ p }}</a>
+    {% endfor %}
+    {% if page < total_pages %}
+    <a href="/panel/plataforma?tab={{ tab }}&page={{ page+1 }}" class="page-btn">›</a>
+    {% endif %}
+  </div>
+  {% elif not platform_error %}
+  <div class="alert alert-info">No hay datos disponibles. Ejecuta el seed de la plataforma primero.</div>
+  {% endif %}
+</div>
+{% endif %}
+
+{% endblock %}

+ 133 - 0
internal/src/backend/templates/predicciones.html

@@ -0,0 +1,133 @@
+{% extends "base.html" %}
+{% block title %}Predicciones{% endblock %}
+{% block topbar_title %}Predicciones ML / NLP{% endblock %}
+{% block topbar_extra %}
+<span class="topbar-sub">{{ current_name }} — {{ total }} registros</span>
+{% endblock %}
+
+{% block head %}
+{% if ml_dashboard_json %}
+<script src="https://cdn.plot.ly/plotly-2.35.2.min.js" charset="utf-8"></script>
+{% endif %}
+{% endblock %}
+
+{% block content %}
+
+<!-- Dataset selector -->
+<div class="tabs">
+  {% for key, name in all_datasets.items() %}
+  <a href="/panel/predicciones?tipo={{ key }}"
+     class="tab-btn {% if current_tipo == key %}active{% endif %}">{{ name }}</a>
+  {% endfor %}
+</div>
+
+<!-- ML Dashboard for this dataset (if exists) -->
+{% if ml_dashboard_json %}
+<div class="card" style="margin-bottom:20px">
+  <div class="card-header">
+    <div class="card-title">Dashboard del modelo — {{ current_name }}</div>
+    <a href="/panel/predicciones?tipo={{ current_tipo }}" class="btn btn-secondary btn-sm">↺ Recargar</a>
+  </div>
+  <div id="chart-ml" class="chart-wrap"></div>
+</div>
+{% endif %}
+
+<!-- Data table -->
+<div class="card">
+  <div class="card-header">
+    <div>
+      <div class="card-title">Tabla de predicciones</div>
+      <div class="card-sub">{{ current_name }} · mostrando {{ rows|length }} de {{ total }}</div>
+    </div>
+    <div style="display:flex;gap:8px">
+      {% for key, name in ml_dashboards.items() %}
+      {% if loop.first %}
+      <a href="/panel/predicciones/dashboard/{{ key }}" class="btn btn-secondary btn-sm">📊 Ver dashboards ML</a>
+      {% endif %}
+      {% endfor %}
+    </div>
+  </div>
+
+  {% if rows %}
+  <div class="table-wrap">
+    <table>
+      <thead>
+        <tr>
+          {% for col in columns %}
+          <th>{{ col }}</th>
+          {% endfor %}
+        </tr>
+      </thead>
+      <tbody>
+        {% for row in rows %}
+        <tr>
+          {% for col in columns %}
+          <td>
+            {% set val = row[col] %}
+            {% if col == 'risk_level' or col == 'risk_level' %}
+              {% if val == 'High' %}
+                <span class="badge badge-rose">{{ val }}</span>
+              {% elif val == 'Medium' %}
+                <span class="badge badge-amber">{{ val }}</span>
+              {% elif val == 'Low' %}
+                <span class="badge badge-ok">{{ val }}</span>
+              {% else %}
+                {{ val }}
+              {% endif %}
+            {% elif col == 'real_status' or col == 'real_support' or col == 'predicted' %}
+              {% if val in ['Pass', 'Low'] %}
+                <span class="badge badge-ok">{{ val }}</span>
+              {% elif val in ['Fail', 'High'] %}
+                <span class="badge badge-rose">{{ val }}</span>
+              {% elif val == 'Medium' %}
+                <span class="badge badge-amber">{{ val }}</span>
+              {% else %}
+                {{ val }}
+              {% endif %}
+            {% elif col == 'student_id' %}
+              <a href="/panel/estudiantes/{{ val }}" style="color:var(--teal);font-weight:600">{{ val }}</a>
+            {% else %}
+              {{ val }}
+            {% endif %}
+          </td>
+          {% endfor %}
+        </tr>
+        {% endfor %}
+      </tbody>
+    </table>
+  </div>
+
+  <!-- Pagination -->
+  <div class="pagination">
+    <span class="page-info">{{ total }} registros · página {{ page }} de {{ total_pages }}</span>
+    {% if page > 1 %}
+    <a href="/panel/predicciones?tipo={{ current_tipo }}&page={{ page - 1 }}" class="page-btn">‹</a>
+    {% endif %}
+    {% for p in range([1, page-2]|max, [total_pages+1, page+3]|min) %}
+    <a href="/panel/predicciones?tipo={{ current_tipo }}&page={{ p }}"
+       class="page-btn {% if p == page %}active{% endif %}">{{ p }}</a>
+    {% endfor %}
+    {% if page < total_pages %}
+    <a href="/panel/predicciones?tipo={{ current_tipo }}&page={{ page + 1 }}" class="page-btn">›</a>
+    {% endif %}
+  </div>
+  {% else %}
+  <div class="alert alert-info">No hay datos disponibles para este dataset.</div>
+  {% endif %}
+</div>
+
+{% endblock %}
+
+{% block scripts %}
+{% if ml_dashboard_json %}
+<script>
+  const fig = {{ ml_dashboard_json | safe }};
+  Plotly.react('chart-ml', fig.data, {
+    ...fig.layout,
+    height: 520,
+    paper_bgcolor:'rgba(0,0,0,0)',
+    plot_bgcolor:'rgba(0,0,0,0)',
+  }, {responsive:true, displayModeBar:false});
+</script>
+{% endif %}
+{% endblock %}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/datasets/clean_dataset.csv


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/datasets/dataset.csv


+ 501 - 0
internal/src/models/datasets/math_risk_scores.csv

@@ -0,0 +1,501 @@
+student_id,Math_term1,Math_term2,Math_term3,Math_final,real_status,prob_fail,prob_pass,risk_level
+EA00001,78.5,47.9,56.2,60.9,Pass,0.0001,0.9999,Bajo
+EA00002,65.6,53.8,38.9,52.8,Pass,0.0555,0.9445,Bajo
+EA00003,43.5,46.0,63.4,51.0,Pass,0.3667,0.6333,Medio
+EA00004,71.2,48.2,61.2,60.2,Pass,0.0002,0.9998,Bajo
+EA00005,52.0,84.0,59.0,65.0,Pass,0.0,1.0,Bajo
+EA00006,52.3,44.5,43.4,46.7,Fail,0.8868,0.1132,Alto
+EA00007,54.3,43.5,62.9,53.6,Pass,0.0461,0.9539,Bajo
+EA00008,53.2,49.0,45.4,49.2,Fail,0.6338,0.3662,Alto
+EA00009,44.4,60.2,53.7,52.8,Pass,0.0954,0.9046,Bajo
+EA00010,52.0,72.5,51.0,58.5,Pass,0.0008,0.9992,Bajo
+EA00011,64.0,71.4,55.3,63.6,Pass,0.0,1.0,Bajo
+EA00012,48.4,54.6,68.9,57.3,Pass,0.0033,0.9967,Bajo
+EA00013,46.6,49.0,75.0,56.9,Pass,0.0047,0.9953,Bajo
+EA00014,49.4,46.3,39.6,45.1,Fail,0.9727,0.0273,Alto
+EA00015,43.2,78.1,74.7,65.3,Pass,0.0,1.0,Bajo
+EA00016,59.8,67.1,65.9,64.3,Pass,0.0,1.0,Bajo
+EA00017,53.2,54.8,61.8,56.6,Pass,0.0034,0.9966,Bajo
+EA00018,59.1,55.6,79.1,64.6,Pass,0.0,1.0,Bajo
+EA00019,58.8,70.9,44.8,58.2,Pass,0.001,0.999,Bajo
+EA00020,52.3,38.7,53.6,48.2,Fail,0.7544,0.2456,Alto
+EA00021,71.9,56.3,56.9,61.7,Pass,0.0001,0.9999,Bajo
+EA00022,45.9,54.4,79.4,59.9,Pass,0.0005,0.9995,Bajo
+EA00023,45.5,63.4,60.2,56.4,Pass,0.0054,0.9946,Bajo
+EA00024,52.8,51.8,72.3,59.0,Pass,0.0007,0.9993,Bajo
+EA00025,49.0,59.9,61.7,56.9,Pass,0.0036,0.9964,Bajo
+EA00026,70.4,52.3,57.5,60.1,Pass,0.0001,0.9999,Bajo
+EA00027,67.6,52.1,65.6,61.8,Pass,0.0001,0.9999,Bajo
+EA00028,35.3,52.0,61.3,49.5,Fail,0.6549,0.3451,Alto
+EA00029,39.7,62.7,46.7,49.7,Fail,0.4755,0.5245,Medio
+EA00030,47.9,55.8,56.6,53.4,Pass,0.0409,0.9591,Bajo
+EA00031,65.9,47.7,42.3,52.0,Pass,0.1023,0.8977,Bajo
+EA00032,72.6,72.9,72.8,72.8,Pass,0.0,1.0,Bajo
+EA00033,59.9,52.2,61.7,57.9,Pass,0.0014,0.9986,Bajo
+EA00034,43.4,42.9,51.6,46.0,Fail,0.9566,0.0434,Alto
+EA00035,50.7,63.5,48.4,54.2,Pass,0.0259,0.9741,Bajo
+EA00036,51.6,52.2,54.8,52.9,Pass,0.084,0.916,Bajo
+EA00037,43.1,47.6,47.3,46.0,Fail,0.9638,0.0362,Alto
+EA00038,68.1,66.1,67.4,67.2,Pass,0.0,1.0,Bajo
+EA00039,45.7,44.3,27.5,39.2,Fail,0.9997,0.0003,Alto
+EA00040,52.0,65.0,71.5,62.8,Pass,0.0,1.0,Bajo
+EA00041,47.3,54.5,41.7,47.8,Fail,0.777,0.223,Alto
+EA00042,50.1,57.0,68.4,58.5,Pass,0.001,0.999,Bajo
+EA00043,63.7,55.2,63.8,60.9,Pass,0.0001,0.9999,Bajo
+EA00044,53.5,66.5,50.8,56.9,Pass,0.0035,0.9965,Bajo
+EA00045,26.4,60.8,60.7,49.3,Fail,0.709,0.291,Alto
+EA00046,67.7,67.5,41.1,58.8,Pass,0.0005,0.9995,Bajo
+EA00047,49.4,46.1,56.4,50.6,Pass,0.2641,0.7359,Medio
+EA00048,70.0,64.2,66.8,67.0,Pass,0.0,1.0,Bajo
+EA00049,54.4,60.5,64.4,59.8,Pass,0.0004,0.9996,Bajo
+EA00050,28.3,55.3,69.6,51.1,Pass,0.3302,0.6698,Medio
+EA00051,56.9,75.9,51.4,61.4,Pass,0.0001,0.9999,Bajo
+EA00052,56.5,46.5,62.2,55.1,Pass,0.0131,0.9869,Bajo
+EA00053,34.2,54.7,63.2,50.7,Pass,0.3992,0.6008,Medio
+EA00054,52.9,47.7,68.2,56.3,Pass,0.0057,0.9943,Bajo
+EA00055,46.1,56.4,46.3,49.6,Fail,0.5457,0.4543,Alto
+EA00056,67.8,65.4,42.7,58.6,Pass,0.0004,0.9996,Bajo
+EA00057,68.4,54.1,67.2,63.2,Pass,0.0,1.0,Bajo
+EA00058,55.7,77.6,64.0,65.8,Pass,0.0,1.0,Bajo
+EA00059,66.7,50.3,59.0,58.7,Pass,0.0005,0.9995,Bajo
+EA00060,65.6,55.4,67.7,62.9,Pass,0.0,1.0,Bajo
+EA00061,70.3,60.1,75.7,68.7,Pass,0.0,1.0,Bajo
+EA00062,50.9,62.6,41.0,51.5,Pass,0.1868,0.8132,Bajo
+EA00063,38.5,69.1,55.2,54.3,Pass,0.0233,0.9767,Bajo
+EA00064,52.2,44.2,68.4,54.9,Pass,0.0195,0.9805,Bajo
+EA00065,77.7,51.8,59.5,63.0,Pass,0.0,1.0,Bajo
+EA00066,62.2,64.7,91.6,72.8,Pass,0.0,1.0,Bajo
+EA00067,60.8,67.9,57.9,62.2,Pass,0.0,1.0,Bajo
+EA00068,54.8,53.0,50.8,52.9,Pass,0.0597,0.9403,Bajo
+EA00069,67.5,56.6,56.7,60.3,Pass,0.0002,0.9998,Bajo
+EA00070,81.0,50.1,64.6,65.2,Pass,0.0,1.0,Bajo
+EA00071,48.8,80.3,79.9,69.7,Pass,0.0,1.0,Bajo
+EA00072,41.9,51.7,68.2,53.9,Pass,0.0387,0.9613,Bajo
+EA00073,37.8,39.4,60.0,45.7,Fail,0.9654,0.0346,Alto
+EA00074,79.9,70.2,64.9,71.7,Pass,0.0,1.0,Bajo
+EA00075,51.0,58.5,56.4,55.3,Pass,0.0114,0.9886,Bajo
+EA00076,43.7,74.4,59.5,59.2,Pass,0.0006,0.9994,Bajo
+EA00077,63.5,65.1,53.2,60.6,Pass,0.0001,0.9999,Bajo
+EA00078,46.6,49.0,62.1,52.6,Pass,0.1064,0.8936,Bajo
+EA00079,45.1,65.2,46.9,52.4,Pass,0.0981,0.9019,Bajo
+EA00080,57.5,47.4,63.1,56.0,Pass,0.0071,0.9929,Bajo
+EA00081,49.0,48.3,62.1,53.1,Pass,0.0805,0.9195,Bajo
+EA00082,38.1,63.3,58.8,53.4,Pass,0.0494,0.9506,Bajo
+EA00083,48.8,64.3,56.6,56.6,Pass,0.0042,0.9958,Bajo
+EA00084,71.5,79.8,71.7,74.3,Pass,0.0,1.0,Bajo
+EA00085,41.3,57.8,71.9,57.0,Pass,0.0046,0.9954,Bajo
+EA00086,53.4,69.3,51.4,58.0,Pass,0.0012,0.9988,Bajo
+EA00087,59.7,52.1,67.0,59.6,Pass,0.0003,0.9997,Bajo
+EA00088,62.0,60.8,69.9,64.2,Pass,0.0,1.0,Bajo
+EA00089,53.4,60.5,62.1,58.7,Pass,0.0007,0.9993,Bajo
+EA00090,46.0,40.4,56.0,47.5,Fail,0.8775,0.1225,Alto
+EA00091,50.8,67.9,50.4,56.4,Pass,0.0036,0.9964,Bajo
+EA00092,71.9,54.2,51.5,59.2,Pass,0.0003,0.9997,Bajo
+EA00093,49.9,68.1,53.6,57.2,Pass,0.0021,0.9979,Bajo
+EA00094,49.5,41.3,69.3,53.4,Pass,0.0557,0.9443,Bajo
+EA00095,72.5,48.5,43.1,54.7,Pass,0.0112,0.9888,Bajo
+EA00096,67.1,40.4,67.2,58.2,Pass,0.0014,0.9986,Bajo
+EA00097,47.1,66.6,64.6,59.4,Pass,0.0006,0.9994,Bajo
+EA00098,41.0,61.1,53.5,51.9,Pass,0.1735,0.8265,Bajo
+EA00099,47.8,45.7,49.4,47.6,Fail,0.7979,0.2021,Alto
+EA00100,62.7,58.0,48.2,56.3,Pass,0.0033,0.9967,Bajo
+EA00101,71.4,73.5,60.3,68.4,Pass,0.0,1.0,Bajo
+EA00102,46.6,66.0,40.4,51.0,Pass,0.2711,0.7289,Medio
+EA00103,38.0,46.1,59.1,47.7,Fail,0.863,0.137,Alto
+EA00104,35.3,63.6,43.2,47.4,Fail,0.8702,0.1298,Alto
+EA00105,58.9,48.8,44.0,50.6,Pass,0.3335,0.6665,Medio
+EA00106,73.9,62.4,73.6,70.0,Pass,0.0,1.0,Bajo
+EA00107,47.5,57.7,65.7,57.0,Pass,0.0028,0.9972,Bajo
+EA00108,53.4,49.2,62.6,55.1,Pass,0.0151,0.9849,Bajo
+EA00109,52.0,68.3,49.9,56.7,Pass,0.004,0.996,Bajo
+EA00110,68.2,52.1,39.8,53.4,Pass,0.0262,0.9738,Bajo
+EA00111,56.9,60.2,55.1,57.4,Pass,0.0027,0.9973,Bajo
+EA00112,63.4,39.9,55.1,52.8,Pass,0.0577,0.9423,Bajo
+EA00113,63.4,61.4,50.8,58.5,Pass,0.0007,0.9993,Bajo
+EA00114,38.3,64.4,58.8,53.8,Pass,0.0343,0.9657,Bajo
+EA00115,55.2,58.4,53.3,55.6,Pass,0.0078,0.9922,Bajo
+EA00116,49.0,31.0,61.2,47.1,Fail,0.9109,0.0891,Alto
+EA00117,48.3,29.3,48.1,41.9,Fail,0.9973,0.0027,Alto
+EA00118,43.3,49.1,55.1,49.2,Fail,0.6304,0.3696,Alto
+EA00119,65.6,49.5,62.1,59.1,Pass,0.0004,0.9996,Bajo
+EA00120,49.5,55.0,64.6,56.4,Pass,0.0048,0.9952,Bajo
+EA00121,61.4,62.9,55.8,60.0,Pass,0.0002,0.9998,Bajo
+EA00122,43.6,76.7,45.3,55.2,Pass,0.0135,0.9865,Bajo
+EA00123,73.5,69.2,58.4,67.0,Pass,0.0,1.0,Bajo
+EA00124,36.4,35.9,49.0,40.4,Fail,0.9994,0.0006,Alto
+EA00125,64.8,49.4,62.6,58.9,Pass,0.0004,0.9996,Bajo
+EA00126,71.2,62.7,57.9,63.9,Pass,0.0,1.0,Bajo
+EA00127,60.4,38.7,53.9,51.0,Pass,0.2329,0.7671,Medio
+EA00128,45.7,61.7,58.2,55.2,Pass,0.0164,0.9836,Bajo
+EA00129,67.6,68.6,71.0,69.1,Pass,0.0,1.0,Bajo
+EA00130,58.5,58.4,84.7,67.2,Pass,0.0,1.0,Bajo
+EA00131,53.6,49.0,64.2,55.6,Pass,0.0103,0.9897,Bajo
+EA00132,63.3,59.1,71.7,64.7,Pass,0.0,1.0,Bajo
+EA00133,48.8,54.3,59.6,54.2,Pass,0.0234,0.9766,Bajo
+EA00134,65.3,57.0,75.1,65.8,Pass,0.0,1.0,Bajo
+EA00135,69.8,49.0,55.0,57.9,Pass,0.0009,0.9991,Bajo
+EA00136,64.0,56.1,42.7,54.3,Pass,0.019,0.981,Bajo
+EA00137,62.3,44.8,46.3,51.1,Pass,0.2133,0.7867,Medio
+EA00138,78.3,57.6,56.1,64.0,Pass,0.0,1.0,Bajo
+EA00139,32.3,55.3,43.4,43.7,Fail,0.9903,0.0097,Alto
+EA00140,55.2,75.4,63.2,64.6,Pass,0.0,1.0,Bajo
+EA00141,43.8,42.7,53.7,46.7,Fail,0.9292,0.0708,Alto
+EA00142,68.7,46.3,41.9,52.3,Pass,0.0865,0.9135,Bajo
+EA00143,62.2,46.6,74.4,61.1,Pass,0.0001,0.9999,Bajo
+EA00144,46.4,53.5,72.1,57.3,Pass,0.0037,0.9963,Bajo
+EA00145,57.9,46.2,67.2,57.1,Pass,0.0029,0.9971,Bajo
+EA00146,50.2,53.2,50.5,51.3,Pass,0.2115,0.7885,Medio
+EA00147,67.3,52.3,74.3,64.6,Pass,0.0,1.0,Bajo
+EA00148,57.4,59.6,70.1,62.4,Pass,0.0,1.0,Bajo
+EA00149,31.7,48.7,47.5,42.6,Fail,0.9963,0.0037,Alto
+EA00150,38.6,66.4,56.2,53.7,Pass,0.0475,0.9525,Bajo
+EA00151,82.7,55.8,57.0,65.2,Pass,0.0,1.0,Bajo
+EA00152,77.2,42.2,66.4,61.9,Pass,0.0,1.0,Bajo
+EA00153,62.3,35.2,48.7,48.7,Fail,0.5703,0.4297,Alto
+EA00154,62.7,60.2,47.1,56.7,Pass,0.0033,0.9967,Bajo
+EA00155,42.2,70.3,60.0,57.5,Pass,0.0035,0.9965,Bajo
+EA00156,70.9,41.9,59.7,57.5,Pass,0.0013,0.9987,Bajo
+EA00157,64.5,48.2,79.9,64.2,Pass,0.0,1.0,Bajo
+EA00158,69.3,39.2,51.3,53.3,Pass,0.0409,0.9591,Bajo
+EA00159,65.1,66.6,50.6,60.8,Pass,0.0001,0.9999,Bajo
+EA00160,64.3,64.0,50.0,59.4,Pass,0.0004,0.9996,Bajo
+EA00161,49.7,67.4,39.1,52.1,Pass,0.1449,0.8551,Bajo
+EA00162,59.4,53.3,61.8,58.2,Pass,0.0012,0.9988,Bajo
+EA00163,70.0,63.0,65.5,66.2,Pass,0.0,1.0,Bajo
+EA00164,64.1,43.2,58.6,55.3,Pass,0.0088,0.9912,Bajo
+EA00165,62.3,60.2,56.5,59.7,Pass,0.0002,0.9998,Bajo
+EA00166,38.4,55.5,49.7,47.9,Fail,0.8696,0.1304,Alto
+EA00167,55.9,66.2,47.0,56.4,Pass,0.0035,0.9965,Bajo
+EA00168,36.9,69.0,70.5,58.8,Pass,0.0015,0.9985,Bajo
+EA00169,47.6,46.4,45.3,46.4,Fail,0.9336,0.0664,Alto
+EA00170,57.5,64.8,59.6,60.6,Pass,0.0001,0.9999,Bajo
+EA00171,38.5,41.7,57.6,45.9,Fail,0.9653,0.0347,Alto
+EA00172,59.1,51.2,71.7,60.7,Pass,0.0002,0.9998,Bajo
+EA00173,53.4,65.7,62.4,60.5,Pass,0.0002,0.9998,Bajo
+EA00174,39.7,70.3,58.9,56.3,Pass,0.0053,0.9947,Bajo
+EA00175,43.7,70.8,36.9,50.5,Pass,0.2752,0.7248,Medio
+EA00176,68.6,47.7,67.1,61.1,Pass,0.0001,0.9999,Bajo
+EA00177,62.5,49.5,61.0,57.7,Pass,0.0016,0.9984,Bajo
+EA00178,58.2,68.1,47.1,57.8,Pass,0.0013,0.9987,Bajo
+EA00179,70.1,58.2,76.8,68.4,Pass,0.0,1.0,Bajo
+EA00180,86.4,45.5,65.8,65.9,Pass,0.0,1.0,Bajo
+EA00181,75.4,50.0,41.7,55.7,Pass,0.0049,0.9951,Bajo
+EA00182,39.6,44.8,65.1,49.8,Fail,0.598,0.402,Alto
+EA00183,31.8,52.8,50.6,45.1,Fail,0.9788,0.0212,Alto
+EA00184,57.6,47.6,64.4,56.5,Pass,0.0037,0.9963,Bajo
+EA00185,44.5,49.6,58.5,50.9,Pass,0.3767,0.6233,Medio
+EA00186,54.1,51.1,55.7,53.6,Pass,0.0468,0.9532,Bajo
+EA00187,48.2,49.9,44.4,47.5,Fail,0.8023,0.1977,Alto
+EA00188,59.1,47.2,63.2,56.5,Pass,0.0046,0.9954,Bajo
+EA00189,41.1,67.1,50.9,53.0,Pass,0.0584,0.9416,Bajo
+EA00190,52.5,48.1,47.0,49.2,Fail,0.6143,0.3857,Alto
+EA00191,65.9,78.0,58.5,67.5,Pass,0.0,1.0,Bajo
+EA00192,49.4,48.6,44.9,47.6,Fail,0.8436,0.1564,Alto
+EA00193,56.0,65.4,57.4,59.6,Pass,0.0003,0.9997,Bajo
+EA00194,46.6,57.8,58.5,54.3,Pass,0.0235,0.9765,Bajo
+EA00195,43.8,64.2,63.7,57.2,Pass,0.003,0.997,Bajo
+EA00196,40.0,74.0,60.9,58.3,Pass,0.0014,0.9986,Bajo
+EA00197,57.4,51.5,43.5,50.8,Pass,0.2414,0.7586,Medio
+EA00198,75.5,60.9,62.9,66.4,Pass,0.0,1.0,Bajo
+EA00199,54.4,52.0,36.9,47.8,Fail,0.77,0.23,Alto
+EA00200,33.2,61.9,54.3,49.8,Fail,0.5905,0.4095,Alto
+EA00201,31.8,44.1,48.3,41.4,Fail,0.999,0.001,Alto
+EA00202,46.0,54.3,51.8,50.7,Pass,0.2965,0.7035,Medio
+EA00203,53.8,50.3,52.2,52.1,Pass,0.1313,0.8687,Bajo
+EA00204,60.8,66.9,49.4,59.0,Pass,0.0004,0.9996,Bajo
+EA00205,65.8,59.1,66.1,63.7,Pass,0.0,1.0,Bajo
+EA00206,51.5,42.8,58.5,50.9,Pass,0.2401,0.7599,Medio
+EA00207,46.6,54.3,69.0,56.6,Pass,0.0057,0.9943,Bajo
+EA00208,45.9,45.6,48.2,46.6,Fail,0.927,0.073,Alto
+EA00209,67.3,56.9,58.0,60.7,Pass,0.0001,0.9999,Bajo
+EA00210,41.1,49.1,66.1,52.1,Pass,0.1925,0.8075,Bajo
+EA00211,56.8,36.0,53.0,48.6,Fail,0.7043,0.2957,Alto
+EA00212,59.2,59.7,72.9,63.9,Pass,0.0,1.0,Bajo
+EA00213,30.1,52.7,66.4,49.7,Fail,0.5766,0.4234,Alto
+EA00214,67.2,66.9,59.7,64.6,Pass,0.0,1.0,Bajo
+EA00215,40.4,74.9,58.8,58.0,Pass,0.0019,0.9981,Bajo
+EA00216,64.6,41.3,44.7,50.2,Pass,0.3004,0.6996,Medio
+EA00217,36.3,77.6,47.6,53.8,Pass,0.0401,0.9599,Bajo
+EA00218,44.4,54.5,70.8,56.6,Pass,0.0057,0.9943,Bajo
+EA00219,50.4,67.0,65.9,61.1,Pass,0.0001,0.9999,Bajo
+EA00220,63.1,43.9,63.6,56.9,Pass,0.0026,0.9974,Bajo
+EA00221,49.3,46.4,57.4,51.0,Pass,0.302,0.698,Medio
+EA00222,34.0,55.9,64.5,51.5,Pass,0.3005,0.6995,Medio
+EA00223,68.1,37.0,64.4,56.5,Pass,0.0026,0.9974,Bajo
+EA00224,23.7,49.0,65.2,46.0,Fail,0.9671,0.0329,Alto
+EA00225,50.2,38.0,57.9,48.7,Fail,0.6984,0.3016,Alto
+EA00226,34.6,48.7,75.4,52.9,Pass,0.1256,0.8744,Bajo
+EA00227,61.5,34.3,57.4,51.1,Pass,0.2349,0.7651,Medio
+EA00228,53.0,49.5,75.0,59.2,Pass,0.0008,0.9992,Bajo
+EA00229,74.9,47.3,59.1,60.4,Pass,0.0001,0.9999,Bajo
+EA00230,38.0,41.6,43.7,41.1,Fail,0.9989,0.0011,Alto
+EA00231,45.7,79.7,64.7,63.4,Pass,0.0,1.0,Bajo
+EA00232,45.7,71.9,70.7,62.8,Pass,0.0,1.0,Bajo
+EA00233,81.0,63.4,71.1,71.8,Pass,0.0,1.0,Bajo
+EA00234,70.3,60.4,49.6,60.1,Pass,0.0002,0.9998,Bajo
+EA00235,52.0,58.4,73.1,61.2,Pass,0.0001,0.9999,Bajo
+EA00236,55.4,64.6,69.1,63.0,Pass,0.0,1.0,Bajo
+EA00237,48.8,71.6,75.8,65.4,Pass,0.0,1.0,Bajo
+EA00238,47.6,57.9,59.9,55.1,Pass,0.0169,0.9831,Bajo
+EA00239,47.4,49.5,63.0,53.3,Pass,0.0603,0.9397,Bajo
+EA00240,46.0,49.8,39.1,45.0,Fail,0.9774,0.0226,Alto
+EA00241,56.7,55.6,55.9,56.1,Pass,0.0056,0.9944,Bajo
+EA00242,69.0,61.2,50.8,60.3,Pass,0.0002,0.9998,Bajo
+EA00243,47.6,60.8,55.6,54.7,Pass,0.0191,0.9809,Bajo
+EA00244,63.4,54.1,46.7,54.7,Pass,0.0099,0.9901,Bajo
+EA00245,68.4,42.4,74.4,61.7,Pass,0.0001,0.9999,Bajo
+EA00246,62.2,58.2,65.7,62.0,Pass,0.0001,0.9999,Bajo
+EA00247,49.1,77.4,56.5,61.0,Pass,0.0001,0.9999,Bajo
+EA00248,65.4,38.4,46.5,50.1,Pass,0.3385,0.6615,Medio
+EA00249,68.1,67.3,61.2,65.5,Pass,0.0,1.0,Bajo
+EA00250,66.3,56.9,56.3,59.8,Pass,0.0003,0.9997,Bajo
+EA00251,69.2,58.4,45.8,57.8,Pass,0.0012,0.9988,Bajo
+EA00252,44.4,53.1,43.1,46.9,Fail,0.9122,0.0878,Alto
+EA00253,57.0,65.7,65.2,62.6,Pass,0.0,1.0,Bajo
+EA00254,44.0,56.1,58.9,53.0,Pass,0.0743,0.9257,Bajo
+EA00255,63.1,54.6,68.3,62.0,Pass,0.0,1.0,Bajo
+EA00256,62.7,56.4,57.7,58.9,Pass,0.0005,0.9995,Bajo
+EA00257,69.5,67.8,62.9,66.7,Pass,0.0,1.0,Bajo
+EA00258,43.5,35.4,59.0,46.0,Fail,0.9531,0.0469,Alto
+EA00259,56.0,73.1,66.4,65.2,Pass,0.0,1.0,Bajo
+EA00260,55.4,43.2,59.3,52.6,Pass,0.0763,0.9237,Bajo
+EA00261,38.7,23.4,45.6,35.9,Fail,1.0,0.0,Alto
+EA00262,54.1,47.0,51.0,50.7,Pass,0.2821,0.7179,Medio
+EA00263,45.1,57.3,74.6,59.0,Pass,0.0008,0.9992,Bajo
+EA00264,51.0,45.5,61.7,52.7,Pass,0.0946,0.9054,Bajo
+EA00265,43.2,52.0,54.6,49.9,Fail,0.4504,0.5496,Medio
+EA00266,37.0,61.4,64.8,54.4,Pass,0.0271,0.9729,Bajo
+EA00267,63.8,47.4,56.2,55.8,Pass,0.0049,0.9951,Bajo
+EA00268,66.6,52.0,54.4,57.7,Pass,0.0013,0.9987,Bajo
+EA00269,33.4,32.2,53.4,39.7,Fail,0.9997,0.0003,Alto
+EA00270,46.4,56.1,59.2,53.9,Pass,0.037,0.963,Bajo
+EA00271,55.0,72.7,41.0,56.2,Pass,0.0036,0.9964,Bajo
+EA00272,55.4,69.0,42.7,55.7,Pass,0.0065,0.9935,Bajo
+EA00273,47.7,76.8,53.9,59.5,Pass,0.0003,0.9997,Bajo
+EA00274,58.3,47.4,72.4,59.4,Pass,0.0003,0.9997,Bajo
+EA00275,57.7,44.7,75.9,59.4,Pass,0.0005,0.9995,Bajo
+EA00276,70.5,40.6,60.1,57.1,Pass,0.0026,0.9974,Bajo
+EA00277,63.7,64.9,47.4,58.7,Pass,0.0004,0.9996,Bajo
+EA00278,53.4,48.8,53.6,51.9,Pass,0.1571,0.8429,Bajo
+EA00279,83.2,36.8,67.0,62.3,Pass,0.0,1.0,Bajo
+EA00280,54.0,65.4,60.3,59.9,Pass,0.0003,0.9997,Bajo
+EA00281,52.9,37.3,58.6,49.6,Fail,0.5593,0.4407,Alto
+EA00282,57.7,52.8,49.7,53.4,Pass,0.0433,0.9567,Bajo
+EA00283,57.9,65.2,51.8,58.3,Pass,0.0007,0.9993,Bajo
+EA00284,64.9,66.4,57.8,63.0,Pass,0.0,1.0,Bajo
+EA00285,49.8,39.7,51.4,47.0,Fail,0.8787,0.1213,Alto
+EA00286,51.3,68.3,60.6,60.1,Pass,0.0004,0.9996,Bajo
+EA00287,48.8,39.4,75.9,54.7,Pass,0.0262,0.9738,Bajo
+EA00288,39.1,45.6,48.7,44.5,Fail,0.9896,0.0104,Alto
+EA00289,67.4,69.3,43.0,59.9,Pass,0.0002,0.9998,Bajo
+EA00290,54.9,50.6,54.1,53.2,Pass,0.0588,0.9412,Bajo
+EA00291,77.5,68.4,62.3,69.4,Pass,0.0,1.0,Bajo
+EA00292,91.4,62.2,67.3,73.6,Pass,0.0,1.0,Bajo
+EA00293,74.9,54.0,55.3,61.4,Pass,0.0,1.0,Bajo
+EA00294,71.6,36.8,78.6,62.3,Pass,0.0,1.0,Bajo
+EA00295,69.2,57.7,62.7,63.2,Pass,0.0,1.0,Bajo
+EA00296,63.4,73.6,81.1,72.7,Pass,0.0,1.0,Bajo
+EA00297,52.9,58.5,58.1,56.5,Pass,0.0058,0.9942,Bajo
+EA00298,73.7,64.1,41.3,59.7,Pass,0.0002,0.9998,Bajo
+EA00299,70.2,68.0,54.8,64.3,Pass,0.0,1.0,Bajo
+EA00300,55.2,47.0,45.9,49.4,Fail,0.5084,0.4916,Alto
+EA00301,40.0,68.9,52.0,53.6,Pass,0.0365,0.9635,Bajo
+EA00302,50.7,64.6,35.9,50.4,Pass,0.3136,0.6864,Medio
+EA00303,60.3,43.6,52.5,52.1,Pass,0.1164,0.8836,Bajo
+EA00304,38.2,42.6,48.0,42.9,Fail,0.9962,0.0038,Alto
+EA00305,76.4,48.9,64.6,63.3,Pass,0.0,1.0,Bajo
+EA00306,49.7,53.4,61.4,54.8,Pass,0.013,0.987,Bajo
+EA00307,75.0,57.0,57.6,63.2,Pass,0.0,1.0,Bajo
+EA00308,78.7,66.0,56.7,67.1,Pass,0.0,1.0,Bajo
+EA00309,60.5,61.9,71.9,64.8,Pass,0.0,1.0,Bajo
+EA00310,69.8,59.6,61.1,63.5,Pass,0.0,1.0,Bajo
+EA00311,46.1,44.2,42.1,44.1,Fail,0.9906,0.0094,Alto
+EA00312,54.8,60.2,62.8,59.3,Pass,0.0005,0.9995,Bajo
+EA00313,50.8,62.3,35.5,49.5,Fail,0.5377,0.4623,Alto
+EA00314,72.9,55.7,66.6,65.1,Pass,0.0,1.0,Bajo
+EA00315,100.0,74.8,58.3,77.7,Pass,0.0,1.0,Bajo
+EA00316,31.0,64.9,53.2,49.7,Fail,0.4949,0.5051,Medio
+EA00317,62.0,65.8,46.1,58.0,Pass,0.0011,0.9989,Bajo
+EA00318,68.1,47.7,57.6,57.8,Pass,0.001,0.999,Bajo
+EA00319,73.8,75.7,64.1,71.2,Pass,0.0,1.0,Bajo
+EA00320,62.1,83.2,65.6,70.3,Pass,0.0,1.0,Bajo
+EA00321,62.5,53.8,54.3,56.9,Pass,0.0026,0.9974,Bajo
+EA00322,39.0,58.1,55.6,50.9,Pass,0.2969,0.7031,Medio
+EA00323,39.4,55.3,58.0,50.9,Pass,0.386,0.614,Medio
+EA00324,51.4,49.7,65.6,55.6,Pass,0.0089,0.9911,Bajo
+EA00325,33.0,36.5,48.3,39.3,Fail,0.9997,0.0003,Alto
+EA00326,75.0,74.4,57.4,68.9,Pass,0.0,1.0,Bajo
+EA00327,66.2,59.5,36.0,53.9,Pass,0.0225,0.9775,Bajo
+EA00328,48.6,68.4,55.3,57.4,Pass,0.0019,0.9981,Bajo
+EA00329,47.2,38.9,58.0,48.0,Fail,0.8238,0.1762,Alto
+EA00330,53.8,66.1,67.3,62.4,Pass,0.0,1.0,Bajo
+EA00331,60.6,85.2,65.4,70.4,Pass,0.0,1.0,Bajo
+EA00332,77.8,92.5,56.8,75.7,Pass,0.0,1.0,Bajo
+EA00333,62.9,59.2,60.1,60.7,Pass,0.0001,0.9999,Bajo
+EA00334,53.4,37.0,65.4,51.9,Pass,0.1709,0.8291,Bajo
+EA00335,54.3,45.7,52.7,50.9,Pass,0.2713,0.7287,Medio
+EA00336,51.2,68.9,58.5,59.5,Pass,0.0004,0.9996,Bajo
+EA00337,57.4,52.8,59.6,56.6,Pass,0.0053,0.9947,Bajo
+EA00338,53.0,72.1,60.1,61.7,Pass,0.0001,0.9999,Bajo
+EA00339,33.3,43.4,42.6,39.8,Fail,0.9996,0.0004,Alto
+EA00340,72.4,44.9,52.8,56.7,Pass,0.0021,0.9979,Bajo
+EA00341,71.6,62.4,56.3,63.4,Pass,0.0,1.0,Bajo
+EA00342,57.9,57.4,66.6,60.6,Pass,0.0001,0.9999,Bajo
+EA00343,47.1,48.7,60.5,52.1,Pass,0.1392,0.8608,Bajo
+EA00344,80.1,71.5,53.3,68.3,Pass,0.0,1.0,Bajo
+EA00345,65.7,54.5,72.8,64.3,Pass,0.0,1.0,Bajo
+EA00346,45.1,58.8,39.6,47.8,Fail,0.7955,0.2045,Alto
+EA00347,87.9,35.3,57.5,60.2,Pass,0.0001,0.9999,Bajo
+EA00348,44.2,52.6,65.5,54.1,Pass,0.0354,0.9646,Bajo
+EA00349,51.9,53.8,45.2,50.3,Pass,0.2969,0.7031,Medio
+EA00350,34.7,54.1,32.1,40.3,Fail,0.9993,0.0007,Alto
+EA00351,65.3,56.2,54.9,58.8,Pass,0.0005,0.9995,Bajo
+EA00352,53.0,60.6,58.4,57.3,Pass,0.0024,0.9976,Bajo
+EA00353,43.7,48.3,58.5,50.2,Pass,0.4453,0.5547,Medio
+EA00354,58.9,78.1,54.0,63.7,Pass,0.0,1.0,Bajo
+EA00355,48.8,57.4,75.8,60.7,Pass,0.0003,0.9997,Bajo
+EA00356,57.9,52.0,72.0,60.6,Pass,0.0002,0.9998,Bajo
+EA00357,60.4,80.0,66.3,68.9,Pass,0.0,1.0,Bajo
+EA00358,54.4,61.5,43.5,53.1,Pass,0.054,0.946,Bajo
+EA00359,48.2,64.0,54.5,55.6,Pass,0.0076,0.9924,Bajo
+EA00360,55.5,71.2,42.9,56.5,Pass,0.0024,0.9976,Bajo
+EA00361,60.2,47.9,71.4,59.8,Pass,0.0003,0.9997,Bajo
+EA00362,64.0,59.3,46.3,56.5,Pass,0.003,0.997,Bajo
+EA00363,74.5,74.5,63.9,71.0,Pass,0.0,1.0,Bajo
+EA00364,58.1,50.9,54.6,54.5,Pass,0.0145,0.9855,Bajo
+EA00365,76.1,60.3,66.5,67.6,Pass,0.0,1.0,Bajo
+EA00366,87.5,46.5,61.3,65.1,Pass,0.0,1.0,Bajo
+EA00367,54.1,59.9,64.2,59.4,Pass,0.0004,0.9996,Bajo
+EA00368,76.3,60.5,65.3,67.4,Pass,0.0,1.0,Bajo
+EA00369,38.5,61.4,67.6,55.8,Pass,0.0123,0.9877,Bajo
+EA00370,51.8,54.0,70.9,58.9,Pass,0.0008,0.9992,Bajo
+EA00371,44.4,31.8,60.1,45.4,Fail,0.9633,0.0367,Alto
+EA00372,45.2,66.2,58.1,56.5,Pass,0.0066,0.9934,Bajo
+EA00373,77.6,63.2,45.1,62.0,Pass,0.0,1.0,Bajo
+EA00374,54.6,60.8,50.6,55.3,Pass,0.007,0.993,Bajo
+EA00375,49.6,69.3,43.5,54.1,Pass,0.0221,0.9779,Bajo
+EA00376,41.9,50.0,52.2,48.0,Fail,0.7435,0.2565,Alto
+EA00377,31.2,59.6,52.7,47.8,Fail,0.8835,0.1165,Alto
+EA00378,68.7,66.0,76.0,70.2,Pass,0.0,1.0,Bajo
+EA00379,50.3,49.4,50.7,50.1,Pass,0.4085,0.5915,Medio
+EA00380,53.1,39.3,66.3,52.9,Pass,0.066,0.934,Bajo
+EA00381,86.9,78.2,57.9,74.3,Pass,0.0,1.0,Bajo
+EA00382,48.0,63.9,59.9,57.3,Pass,0.0022,0.9978,Bajo
+EA00383,50.6,52.3,51.2,51.4,Pass,0.1749,0.8251,Bajo
+EA00384,65.8,76.6,66.7,69.7,Pass,0.0,1.0,Bajo
+EA00385,51.6,57.2,40.8,49.9,Fail,0.3997,0.6003,Medio
+EA00386,45.4,71.8,57.2,58.1,Pass,0.0011,0.9989,Bajo
+EA00387,45.1,60.8,65.6,57.2,Pass,0.0031,0.9969,Bajo
+EA00388,48.0,72.0,66.7,62.2,Pass,0.0001,0.9999,Bajo
+EA00389,56.3,58.7,66.8,60.6,Pass,0.0002,0.9998,Bajo
+EA00390,47.4,42.5,31.1,40.3,Fail,0.9991,0.0009,Alto
+EA00391,59.5,48.5,67.3,58.4,Pass,0.0007,0.9993,Bajo
+EA00392,52.1,75.1,63.6,63.6,Pass,0.0,1.0,Bajo
+EA00393,72.8,48.2,49.6,56.9,Pass,0.0019,0.9981,Bajo
+EA00394,44.0,41.8,42.7,42.8,Fail,0.9952,0.0048,Alto
+EA00395,63.1,56.8,48.9,56.3,Pass,0.0038,0.9962,Bajo
+EA00396,39.2,55.4,27.7,40.8,Fail,0.9989,0.0011,Alto
+EA00397,49.9,52.0,88.7,63.5,Pass,0.0,1.0,Bajo
+EA00398,53.9,52.0,44.3,50.1,Pass,0.3009,0.6991,Medio
+EA00399,52.5,32.6,52.1,45.7,Fail,0.9566,0.0434,Alto
+EA00400,64.8,63.9,49.6,59.4,Pass,0.0003,0.9997,Bajo
+EA00401,43.4,79.6,64.4,62.5,Pass,0.0001,0.9999,Bajo
+EA00402,31.5,57.1,61.4,50.0,Pass,0.4666,0.5334,Medio
+EA00403,68.0,56.3,57.1,60.5,Pass,0.0001,0.9999,Bajo
+EA00404,55.1,45.9,63.6,54.9,Pass,0.0168,0.9832,Bajo
+EA00405,69.8,51.6,49.5,57.0,Pass,0.0017,0.9983,Bajo
+EA00406,69.5,61.3,61.3,64.0,Pass,0.0,1.0,Bajo
+EA00407,70.0,50.4,36.4,52.3,Pass,0.0735,0.9265,Bajo
+EA00408,53.3,53.0,67.3,57.9,Pass,0.0014,0.9986,Bajo
+EA00409,22.9,63.2,60.6,48.9,Fail,0.7208,0.2792,Alto
+EA00410,61.7,66.7,69.1,65.8,Pass,0.0,1.0,Bajo
+EA00411,65.5,65.5,72.1,67.7,Pass,0.0,1.0,Bajo
+EA00412,51.6,54.5,55.5,53.9,Pass,0.0382,0.9618,Bajo
+EA00413,43.0,60.2,41.3,48.2,Fail,0.7147,0.2853,Alto
+EA00414,53.6,52.5,56.2,54.1,Pass,0.0236,0.9764,Bajo
+EA00415,55.9,71.7,96.0,74.5,Pass,0.0,1.0,Bajo
+EA00416,33.2,52.0,62.2,49.1,Fail,0.627,0.373,Alto
+EA00417,59.7,39.6,55.4,51.6,Pass,0.151,0.849,Bajo
+EA00418,76.8,46.8,60.1,61.2,Pass,0.0001,0.9999,Bajo
+EA00419,52.9,51.6,57.5,54.0,Pass,0.0341,0.9659,Bajo
+EA00420,44.2,65.6,82.2,64.0,Pass,0.0,1.0,Bajo
+EA00421,40.9,46.6,44.9,44.1,Fail,0.9866,0.0134,Alto
+EA00422,78.4,57.3,72.6,69.4,Pass,0.0,1.0,Bajo
+EA00423,49.5,69.0,77.5,65.3,Pass,0.0,1.0,Bajo
+EA00424,68.0,55.6,53.6,59.1,Pass,0.0004,0.9996,Bajo
+EA00425,50.1,40.9,67.4,52.8,Pass,0.0648,0.9352,Bajo
+EA00426,55.8,63.0,69.6,62.8,Pass,0.0,1.0,Bajo
+EA00427,56.0,64.2,55.5,58.6,Pass,0.0009,0.9991,Bajo
+EA00428,41.0,55.5,59.7,52.1,Pass,0.1262,0.8738,Bajo
+EA00429,56.1,23.8,63.5,47.8,Fail,0.7906,0.2094,Alto
+EA00430,64.8,45.6,57.3,55.9,Pass,0.0065,0.9935,Bajo
+EA00431,68.1,47.8,72.4,62.8,Pass,0.0,1.0,Bajo
+EA00432,50.2,56.0,73.9,60.0,Pass,0.0003,0.9997,Bajo
+EA00433,48.0,51.5,53.8,51.1,Pass,0.2716,0.7284,Medio
+EA00434,64.9,41.7,63.7,56.8,Pass,0.0025,0.9975,Bajo
+EA00435,50.1,69.1,59.2,59.5,Pass,0.0005,0.9995,Bajo
+EA00436,66.0,31.8,49.2,49.0,Fail,0.5661,0.4339,Alto
+EA00437,51.9,69.3,49.7,57.0,Pass,0.0028,0.9972,Bajo
+EA00438,31.8,53.9,53.7,46.5,Fail,0.9494,0.0506,Alto
+EA00439,59.5,51.2,62.8,57.8,Pass,0.0014,0.9986,Bajo
+EA00440,31.2,64.9,45.9,47.3,Fail,0.8761,0.1239,Alto
+EA00441,67.2,54.3,63.0,61.5,Pass,0.0001,0.9999,Bajo
+EA00442,75.0,55.9,61.9,64.3,Pass,0.0,1.0,Bajo
+EA00443,49.5,37.8,63.6,50.3,Pass,0.3372,0.6628,Medio
+EA00444,70.4,67.8,54.2,64.1,Pass,0.0,1.0,Bajo
+EA00445,55.3,54.4,66.0,58.6,Pass,0.0007,0.9993,Bajo
+EA00446,52.2,38.8,54.5,48.5,Fail,0.7071,0.2929,Alto
+EA00447,78.4,68.3,65.4,70.7,Pass,0.0,1.0,Bajo
+EA00448,51.8,55.3,57.3,54.8,Pass,0.0161,0.9839,Bajo
+EA00449,49.3,39.1,64.7,51.0,Pass,0.3207,0.6793,Medio
+EA00450,48.8,52.5,49.7,50.3,Pass,0.2915,0.7085,Medio
+EA00451,78.5,59.1,67.5,68.4,Pass,0.0,1.0,Bajo
+EA00452,73.1,65.9,72.6,70.5,Pass,0.0,1.0,Bajo
+EA00453,45.6,35.8,62.8,48.1,Fail,0.8038,0.1962,Alto
+EA00454,66.6,47.3,48.1,54.0,Pass,0.0218,0.9782,Bajo
+EA00455,45.4,62.8,55.6,54.6,Pass,0.0219,0.9781,Bajo
+EA00456,46.9,56.1,63.1,55.4,Pass,0.0149,0.9851,Bajo
+EA00457,63.3,57.2,55.4,58.6,Pass,0.0006,0.9994,Bajo
+EA00458,52.4,65.2,60.7,59.4,Pass,0.0004,0.9996,Bajo
+EA00459,64.6,45.7,75.1,61.8,Pass,0.0001,0.9999,Bajo
+EA00460,52.6,46.7,64.3,54.5,Pass,0.025,0.975,Bajo
+EA00461,56.2,46.2,66.5,56.3,Pass,0.0038,0.9962,Bajo
+EA00462,37.1,70.4,63.5,57.0,Pass,0.0043,0.9957,Bajo
+EA00463,51.4,56.4,64.0,57.3,Pass,0.0027,0.9973,Bajo
+EA00464,51.1,58.4,60.2,56.6,Pass,0.0045,0.9955,Bajo
+EA00465,39.1,51.2,53.3,47.9,Fail,0.807,0.193,Alto
+EA00466,59.9,59.9,28.5,49.4,Fail,0.3614,0.6386,Medio
+EA00467,35.4,79.1,51.6,55.4,Pass,0.0102,0.9898,Bajo
+EA00468,58.8,57.8,78.9,65.2,Pass,0.0,1.0,Bajo
+EA00469,33.9,49.7,48.7,44.1,Fail,0.9902,0.0098,Alto
+EA00470,63.9,60.3,57.3,60.5,Pass,0.0002,0.9998,Bajo
+EA00471,44.1,51.1,59.0,51.4,Pass,0.2285,0.7715,Medio
+EA00472,80.9,66.8,61.9,69.9,Pass,0.0,1.0,Bajo
+EA00473,69.1,59.9,52.7,60.6,Pass,0.0002,0.9998,Bajo
+EA00474,51.8,41.3,45.3,46.1,Fail,0.944,0.056,Alto
+EA00475,55.6,47.8,58.4,53.9,Pass,0.0227,0.9773,Bajo
+EA00476,75.8,68.2,81.6,75.2,Pass,0.0,1.0,Bajo
+EA00477,43.5,53.7,37.9,45.0,Fail,0.9678,0.0322,Alto
+EA00478,45.5,70.5,72.0,62.7,Pass,0.0,1.0,Bajo
+EA00479,61.4,80.5,59.2,67.0,Pass,0.0,1.0,Bajo
+EA00480,53.9,51.3,61.7,55.6,Pass,0.008,0.992,Bajo
+EA00481,69.1,58.6,60.9,62.9,Pass,0.0,1.0,Bajo
+EA00482,69.0,61.0,42.8,57.6,Pass,0.0013,0.9987,Bajo
+EA00483,38.7,54.7,37.2,43.5,Fail,0.9907,0.0093,Alto
+EA00484,42.6,55.5,48.8,49.0,Fail,0.7217,0.2783,Alto
+EA00485,68.7,52.8,59.9,60.5,Pass,0.0002,0.9998,Bajo
+EA00486,60.0,50.2,50.5,53.6,Pass,0.045,0.955,Bajo
+EA00487,51.4,33.3,60.6,48.4,Fail,0.6752,0.3248,Alto
+EA00488,61.0,52.4,47.7,53.7,Pass,0.0287,0.9713,Bajo
+EA00489,52.5,55.0,69.8,59.1,Pass,0.0007,0.9993,Bajo
+EA00490,49.7,46.7,70.4,55.6,Pass,0.0107,0.9893,Bajo
+EA00491,50.9,37.6,52.1,46.9,Fail,0.8974,0.1026,Alto
+EA00492,68.1,56.5,53.2,59.3,Pass,0.0004,0.9996,Bajo
+EA00493,71.7,67.6,50.9,63.4,Pass,0.0,1.0,Bajo
+EA00494,66.9,71.3,71.7,70.0,Pass,0.0,1.0,Bajo
+EA00495,69.8,60.8,74.4,68.3,Pass,0.0,1.0,Bajo
+EA00496,77.1,48.1,69.1,64.8,Pass,0.0,1.0,Bajo
+EA00497,63.4,84.9,55.2,67.8,Pass,0.0,1.0,Bajo
+EA00498,67.5,41.3,39.6,49.5,Fail,0.4625,0.5375,Medio
+EA00499,50.8,51.6,51.8,51.4,Pass,0.1972,0.8028,Bajo
+EA00500,62.9,73.9,67.8,68.2,Pass,0.0,1.0,Bajo

+ 501 - 0
internal/src/models/datasets/nlp_pred_career_recommendation.csv

@@ -0,0 +1,501 @@
+student_id,career_area,confidence,top_subjects,score_stem,score_humanities,score_business,score_arts,avg_final
+EA00001,Humanities,0.2794,"Biology, French, Geography",52.87,61.2,54.5,50.5,55.818
+EA00002,Arts,0.2599,"Business_Studies, Biology, Swahili",59.9,61.33,63.55,64.9,61.519
+EA00003,Arts,0.2616,"Geography, Music, Civics",58.77,59.42,59.33,62.9,59.582
+EA00004,Arts,0.2727,"Civics, Entrepreneurship, Physical_Education",56.57,61.4,65.23,68.7,60.762
+EA00005,Business,0.2685,"English, Music, History",63.93,70.55,76.2,73.1,69.094
+EA00006,Arts,0.2903,"Fine_Arts, Physical_Education, Business_Studies",51.3,57.18,62.9,70.1,56.953
+EA00007,Arts,0.2564,"Civics, Business_Studies, Geography",63.08,66.02,64.6,66.8,64.933
+EA00008,Humanities,0.2588,"ICT, Fine_Arts, Swahili",57.97,62.63,60.7,60.73,60.424
+EA00009,STEM,0.2635,"Science, Physics, Biology",58.8,52.37,53.3,58.7,55.547
+EA00010,Arts,0.2635,"Biology, ICT, Business_Studies",60.12,57.27,57.87,62.7,59.018
+EA00011,Arts,0.2746,"Fine_Arts, Civics, French",63.15,64.67,62.1,71.9,64.682
+EA00012,Business,0.258,"Business_Studies, ICT, French",66.32,66.16,69.87,68.5,67.006
+EA00013,Business,0.2657,"Civics, Music, Business_Studies",59.17,59.2,66.6,65.65,60.818
+EA00014,Business,0.276,"Civics, Entrepreneurship, Business_Studies",56.43,59.03,64.6,54.0,58.788
+EA00015,Business,0.2615,"History, Agriculture, English",60.74,65.96,67.0,62.5,63.729
+EA00016,Business,0.262,"Music, Swahili, Business_Studies",66.87,68.35,71.55,66.35,67.882
+EA00017,Arts,0.2847,"Physical_Education, Music, Science",56.35,57.97,59.7,69.25,58.972
+EA00018,Business,0.2696,"Business_Studies, Civics, Biology",63.02,67.65,73.0,67.15,66.519
+EA00019,Arts,0.3008,"Physical_Education, ICT, Science",56.7,55.74,55.63,72.3,57.035
+EA00020,Arts,0.2779,"English, Physical_Education, Agriculture",53.88,55.6,60.6,65.45,56.741
+EA00021,Arts,0.2805,"English, Physical_Education, History",61.7,68.37,65.43,76.2,65.565
+EA00022,Business,0.2599,"Business_Studies, Physical_Education, Science",61.79,64.53,68.17,67.8,64.235
+EA00023,Arts,0.3059,"Physical_Education, Agriculture, Business_Studies",58.0,63.72,66.83,83.1,63.053
+EA00024,Arts,0.2744,"Music, Physical_Education, Swahili",61.93,61.28,64.03,70.8,63.118
+EA00025,Humanities,0.2608,"Biology, French, Civics",63.74,68.38,64.17,65.9,65.582
+EA00026,Arts,0.276,"ICT, Civics, Physical_Education",56.75,57.08,56.4,64.9,57.319
+EA00027,Arts,0.2735,"Fine_Arts, Biology, Geography",60.99,61.57,62.55,69.7,62.4
+EA00028,Business,0.2869,"Business_Studies, Civics, English",57.95,65.36,73.25,58.75,62.894
+EA00029,Arts,0.2819,"Physical_Education, Civics, Fine_Arts",53.72,57.9,58.07,66.6,57.476
+EA00030,Arts,0.2709,"Civics, Fine_Arts, Science",58.6,57.73,56.85,64.35,58.775
+EA00031,Humanities,0.2765,"Geography, French, Science",55.3,60.22,42.2,60.1,55.806
+EA00032,Arts,0.2615,"Civics, Fine_Arts, ICT",67.83,73.39,73.35,75.97,71.961
+EA00033,Arts,0.2819,"Biology, Physical_Education, Fine_Arts",54.1,55.75,53.95,64.3,56.465
+EA00034,Humanities,0.2738,"Geography, Civics, Biology",51.63,58.96,51.6,53.1,54.925
+EA00035,Arts,0.2652,"Music, Business_Studies, Swahili",60.1,61.3,63.85,66.87,62.044
+EA00036,Arts,0.2734,"Physical_Education, Civics, Science",64.47,64.46,63.45,72.4,65.276
+EA00037,Business,0.2637,"Music, Agriculture, Biology",60.47,60.41,67.05,66.37,62.161
+EA00038,Business,0.2761,"Mathematics, Business_Studies, Geography",50.44,56.68,62.0,55.4,54.538
+EA00039,Arts,0.2906,"Physical_Education, Swahili, Entrepreneurship",51.08,56.98,61.8,69.6,57.235
+EA00040,Business,0.2769,"History, Biology, Agriculture",54.3,59.11,64.85,55.95,57.528
+EA00041,Arts,0.2675,"Biology, Geography, Music",51.92,58.27,58.25,61.5,56.594
+EA00042,Arts,0.2817,"Physical_Education, Agriculture, History",55.64,60.24,61.03,69.4,59.094
+EA00043,Arts,0.2783,"Religious_Education, Physical_Education, Biology",61.75,62.79,66.07,73.5,63.629
+EA00044,Business,0.2627,"Civics, Science, Business_Studies",67.33,68.98,74.3,72.2,69.583
+EA00045,Arts,0.2798,"Physical_Education, Business_Studies, ICT",56.07,55.17,61.75,67.2,57.081
+EA00046,Humanities,0.2776,"Civics, Swahili, ICT",53.6,63.6,53.7,58.2,57.711
+EA00047,Business,0.2712,"Business_Studies, French, ICT",58.63,59.35,67.23,62.7,60.756
+EA00048,Arts,0.2605,"Music, Swahili, Biology",64.96,69.33,69.15,71.65,67.782
+EA00049,Business,0.2638,"Geography, Agriculture, Biology",63.59,65.47,68.5,62.1,64.812
+EA00050,Arts,0.2586,"Physics, Swahili, Physical_Education",58.62,59.2,57.8,61.25,59.071
+EA00051,Arts,0.2597,"Geography, Music, Biology",60.6,59.78,58.95,62.9,60.524
+EA00052,Arts,0.2995,"Physical_Education, ICT, Civics",59.37,61.62,64.05,79.1,62.031
+EA00053,Arts,0.2847,"Physical_Education, Music, French",54.74,58.61,52.85,66.15,57.306
+EA00054,Business,0.2754,"History, Agriculture, Business_Studies",56.84,60.4,68.0,61.7,59.906
+EA00055,Arts,0.2702,"English, Physical_Education, Agriculture",57.98,62.18,61.23,67.15,61.118
+EA00056,Arts,0.2573,"Geography, Civics, Science",62.92,69.51,68.43,69.6,67.0
+EA00057,Humanities,0.2521,"Biology, Geography, Agriculture",66.05,66.74,65.6,66.35,66.278
+EA00058,Humanities,0.2594,"Science, English, French",66.02,72.35,70.35,70.2,69.456
+EA00059,Arts,0.2596,"Music, English, Biology",57.87,57.75,55.95,60.15,57.871
+EA00060,Business,0.2575,"Biology, Civics, ICT",67.8,67.62,69.0,63.5,67.344
+EA00061,Arts,0.2714,"English, Biology, Physical_Education",64.85,69.85,69.57,76.1,68.771
+EA00062,Business,0.2722,"Business_Studies, Swahili, Physical_Education",54.84,58.07,66.75,65.6,58.617
+EA00063,Humanities,0.2543,"Chemistry, History, Music",67.41,68.7,68.2,65.85,67.776
+EA00064,Arts,0.2805,"Physical_Education, Business_Studies, Civics",58.58,61.43,61.6,70.8,61.006
+EA00065,Humanities,0.2682,"Swahili, English, Geography",57.03,63.27,60.83,54.8,59.944
+EA00066,Business,0.2671,"Civics, Mathematics, Business_Studies",61.65,68.87,71.3,65.15,66.0
+EA00067,Business,0.2713,"Business_Studies, Agriculture, Fine_Arts",64.1,61.18,73.23,71.45,65.547
+EA00068,Arts,0.2649,"Music, Business_Studies, French",63.37,67.52,68.1,71.7,66.556
+EA00069,Arts,0.2616,"Science, Additional_Math, French",61.17,63.52,61.0,65.8,62.319
+EA00070,Arts,0.2566,"Swahili, French, Geography",62.3,65.27,65.05,66.5,64.281
+EA00071,Arts,0.2727,"Additional_Math, History, Physical_Education",62.5,64.88,62.2,71.1,63.894
+EA00072,Business,0.266,"Geography, ICT, Business_Studies",59.02,64.25,66.45,60.13,61.935
+EA00073,Arts,0.2634,"Swahili, Physical_Education, Physics",52.0,57.05,57.05,59.4,55.247
+EA00074,Arts,0.2639,"Geography, Civics, Biology",66.76,65.27,58.75,68.4,65.482
+EA00075,Humanities,0.2606,"Civics, Business_Studies, Fine_Arts",57.11,64.62,61.7,64.5,61.171
+EA00076,Arts,0.2626,"Science, Swahili, French",67.64,68.92,68.1,72.9,68.506
+EA00077,Arts,0.2829,"French, Fine_Arts, Biology",66.43,67.46,62.95,77.65,67.765
+EA00078,Business,0.2646,"Business_Studies, Swahili, Science",59.17,61.8,64.85,59.25,60.929
+EA00079,Business,0.2619,"History, Physics, Agriculture",64.14,66.33,69.05,64.15,65.539
+EA00080,Arts,0.2673,"Physical_Education, Science, History",67.6,65.92,64.3,72.17,67.424
+EA00081,Arts,0.2912,"Music, ICT, Fine_Arts",58.08,60.25,58.85,72.8,61.535
+EA00082,STEM,0.2584,"Science, ICT, Swahili",61.33,57.67,60.9,57.45,59.428
+EA00083,Business,0.2699,"Agriculture, Civics, Religious_Education",54.18,56.67,62.9,59.3,56.681
+EA00084,Humanities,0.2548,"Civics, Mathematics, Agriculture",67.2,67.6,66.9,63.6,66.671
+EA00085,Arts,0.2576,"Science, French, Music",67.17,69.5,70.23,71.8,69.076
+EA00086,Arts,0.307,"Physical_Education, Civics, English",53.27,54.87,51.67,70.8,54.662
+EA00087,Arts,0.2625,"ICT, French, Science",61.27,64.97,59.2,66.0,63.106
+EA00088,Arts,0.2565,"History, ICT, Swahili",66.84,68.07,64.35,68.75,67.206
+EA00089,Arts,0.2885,"Civics, Physical_Education, Science",58.53,62.27,62.4,74.3,61.637
+EA00090,Arts,0.2971,"Fine_Arts, Civics, Physical_Education",54.2,61.3,60.75,74.5,59.865
+EA00091,Arts,0.2733,"Fine_Arts, French, Business_Studies",59.37,61.2,63.5,69.23,62.241
+EA00092,Arts,0.2679,"Entrepreneurship, Physical_Education, English",52.02,57.67,52.27,59.25,54.906
+EA00093,Humanities,0.2805,"Civics, English, Geography",60.69,72.87,61.65,64.6,65.559
+EA00094,Arts,0.2721,"Chemistry, Fine_Arts, Geography",61.01,63.79,62.45,70.0,63.25
+EA00095,Business,0.2639,"Business_Studies, Civics, Physical_Education",64.67,68.17,73.5,72.2,68.025
+EA00096,Humanities,0.2576,"Fine_Arts, French, Religious_Education",53.46,58.46,56.65,58.35,56.3
+EA00097,Arts,0.2915,"Music, English, Physical_Education",60.97,66.53,64.0,78.8,65.565
+EA00098,Arts,0.2837,"Physical_Education, Geography, Science",56.61,61.41,64.5,72.3,60.441
+EA00099,Arts,0.2937,"Physical_Education, Civics, Biology",58.0,59.51,55.65,72.0,59.244
+EA00100,Business,0.2686,"Entrepreneurship, Business_Studies, English",67.3,68.9,76.73,72.7,69.941
+EA00101,Arts,0.2636,"Civics, Fine_Arts, Mathematics",63.02,61.3,64.4,67.57,63.376
+EA00102,Humanities,0.2559,"Business_Studies, Science, English",54.2,57.81,56.15,57.8,56.222
+EA00103,Business,0.2623,"Entrepreneurship, Civics, Business_Studies",63.97,68.39,72.03,70.2,67.576
+EA00104,Arts,0.3102,"Physical_Education, French, Physics",51.37,53.42,51.1,70.1,53.275
+EA00105,Business,0.2587,"ICT, History, Science",64.0,64.63,68.25,66.95,65.138
+EA00106,Arts,0.2699,"Civics, Fine_Arts, Swahili",62.5,70.77,68.95,74.75,67.794
+EA00107,Arts,0.2858,"ICT, Physical_Education, Science",66.33,63.81,59.5,75.9,64.653
+EA00108,Business,0.2605,"Swahili, Agriculture, Biology",54.7,59.28,60.8,58.6,57.669
+EA00109,Arts,0.2823,"Civics, Physical_Education, Business_Studies",54.05,57.27,59.05,67.0,56.894
+EA00110,Business,0.2544,"English, History, Science",61.73,63.17,64.57,64.3,63.041
+EA00111,STEM,0.2564,"English, ICT, Science",63.17,61.59,60.55,61.1,61.965
+EA00112,Arts,0.2857,"Music, Physical_Education, Geography",53.62,56.9,60.15,68.25,57.494
+EA00113,Arts,0.2597,"Civics, ICT, English",62.02,66.33,62.67,67.0,64.241
+EA00114,Business,0.2661,"Agriculture, Business_Studies, Swahili",60.17,65.1,69.8,67.2,63.662
+EA00115,STEM,0.2618,"History, Biology, ICT",62.55,59.88,58.7,57.8,60.371
+EA00116,Business,0.268,"Biology, Entrepreneurship, English",55.12,56.05,63.8,63.1,57.918
+EA00117,Arts,0.2719,"History, Physical_Education, Civics",54.45,58.0,51.8,61.33,56.606
+EA00118,Arts,0.2821,"Civics, Additional_Math, Physical_Education",55.99,59.75,56.33,67.6,58.059
+EA00119,Business,0.2621,"Biology, Swahili, Chemistry",60.63,63.78,66.75,63.5,62.756
+EA00120,Arts,0.2716,"Civics, Physics, Science",59.46,63.35,57.93,67.4,61.029
+EA00121,Arts,0.2812,"Physical_Education, Biology, English",62.27,68.0,66.6,77.0,65.881
+EA00122,Humanities,0.2617,"Civics, Agriculture, Swahili",55.42,60.57,57.65,57.77,57.912
+EA00123,Arts,0.2845,"Music, ICT, Science",70.41,65.9,65.6,80.3,69.418
+EA00124,Humanities,0.2701,"Civics, Science, History",50.1,58.9,55.07,54.0,54.312
+EA00125,Business,0.2604,"Swahili, Agriculture, Religious_Education",61.59,65.49,66.63,62.2,63.978
+EA00126,Arts,0.2859,"Civics, Physical_Education, Biology",66.34,72.08,61.75,80.15,69.453
+EA00127,Business,0.2607,"ICT, Biology, Agriculture",54.07,55.32,58.97,57.8,55.717
+EA00128,Humanities,0.2594,"History, Civics, Business_Studies",56.0,62.26,60.65,61.1,59.724
+EA00129,Arts,0.2743,"Fine_Arts, Physical_Education, Civics",63.73,68.78,66.25,75.13,67.824
+EA00130,Arts,0.2619,"ICT, Civics, Fine_Arts",61.27,64.62,57.3,65.0,62.424
+EA00131,Arts,0.3018,"Physical_Education, ICT, French",64.98,67.57,68.97,87.1,68.053
+EA00132,Arts,0.2631,"Music, Civics, English",62.98,68.53,67.5,71.05,66.638
+EA00133,Business,0.2665,"Civics, Geography, Business_Studies",57.62,65.85,68.1,63.93,62.871
+EA00134,Business,0.2608,"Swahili, Physical_Education, Biology",62.69,64.2,69.15,69.1,64.735
+EA00135,Arts,0.2674,"French, Civics, Fine_Arts",56.62,61.18,55.77,63.35,58.871
+EA00136,Arts,0.2966,"Physical_Education, Civics, Chemistry",59.66,62.1,62.3,77.6,62.025
+EA00137,Humanities,0.2627,"Religious_Education, ICT, Chemistry",58.0,58.99,52.13,55.4,57.117
+EA00138,Business,0.2545,"ICT, English, Business_Studies",64.8,65.47,66.97,65.9,65.547
+EA00139,Arts,0.2638,"ICT, Fine_Arts, Geography",55.05,57.04,61.95,62.35,57.541
+EA00140,Humanities,0.2657,"ICT, Civics, English",60.48,64.75,62.0,56.5,61.788
+EA00141,Business,0.2698,"French, ICT, Entrepreneurship",53.12,56.4,62.23,58.9,56.418
+EA00142,Arts,0.2803,"Geography, Physical_Education, ICT",52.15,52.36,61.6,64.7,54.641
+EA00143,Business,0.2581,"Biology, Physical_Education, Business_Studies",62.38,65.8,68.15,67.7,65.094
+EA00144,Arts,0.276,"Music, Agriculture, Geography",54.86,59.75,60.35,66.7,58.624
+EA00145,Arts,0.2797,"History, Fine_Arts, Biology",58.47,59.23,52.3,66.0,58.917
+EA00146,Arts,0.2634,"Physical_Education, Science, Civics",60.73,62.13,60.73,65.65,61.822
+EA00147,Arts,0.2547,"ICT, History, Physics",61.84,63.51,64.55,64.9,63.029
+EA00148,Arts,0.2614,"Physics, Civics, Business_Studies",65.43,65.63,66.05,69.75,66.082
+EA00149,Arts,0.3042,"Physical_Education, Music, Science",54.0,56.38,54.65,72.15,57.053
+EA00150,Arts,0.2764,"ICT, Agriculture, Physical_Education",56.67,61.92,67.8,71.2,61.631
+EA00151,Arts,0.2537,"ICT, Mathematics, French",59.33,60.07,59.2,60.7,59.789
+EA00152,Arts,0.2751,"Music, Science, English",62.26,63.29,60.4,70.55,63.372
+EA00153,Business,0.2764,"Business_Studies, Civics, Physics",58.07,59.08,68.05,61.0,60.062
+EA00154,Arts,0.274,"Religious_Education, Physical_Education, Music",63.53,70.21,68.87,76.45,68.456
+EA00155,Arts,0.2574,"Biology, English, Business_Studies",66.65,67.92,68.8,70.5,67.769
+EA00156,Business,0.2683,"French, Religious_Education, Entrepreneurship",54.72,61.54,63.33,56.5,59.006
+EA00157,Arts,0.2947,"Physical_Education, Music, Civics",61.69,61.82,59.35,76.4,63.188
+EA00158,Humanities,0.2687,"Geography, Science, Swahili",60.56,65.92,53.53,65.3,61.7
+EA00159,Arts,0.2618,"Civics, Agriculture, Music",58.33,62.55,65.97,66.25,62.1
+EA00160,Arts,0.2644,"ICT, Additional_Math, Civics",64.91,64.23,58.4,67.4,64.0
+EA00161,Business,0.2736,"Agriculture, Civics, Swahili",55.99,63.06,68.15,61.85,60.739
+EA00162,Humanities,0.2612,"History, Geography, Physical_Education",55.62,60.92,57.0,59.7,58.212
+EA00163,Arts,0.2535,"Civics, Physical_Education, French",67.75,67.42,68.47,69.15,67.924
+EA00164,Arts,0.2668,"Physical_Education, Biology, Civics",66.15,60.28,66.3,70.15,64.576
+EA00165,Business,0.2551,"Business_Studies, Physics, Science",62.22,63.05,64.9,64.2,63.218
+EA00166,Arts,0.271,"Civics, Music, ICT",55.43,59.19,58.05,64.2,58.318
+EA00167,Arts,0.2792,"Physical_Education, Physics, History",61.87,65.39,67.93,75.6,65.194
+EA00168,Arts,0.2658,"Civics, Physical_Education, History",59.18,63.08,62.35,66.85,62.0
+EA00169,Arts,0.2729,"Chemistry, ICT, Swahili",59.17,58.47,53.27,64.15,58.506
+EA00170,Arts,0.2956,"Physical_Education, Civics, Geography",63.6,68.55,65.6,83.0,66.841
+EA00171,Arts,0.2646,"Civics, Geography, Fine_Arts",53.13,61.5,60.35,62.97,58.828
+EA00172,Arts,0.2943,"Physical_Education, Civics, ICT",67.19,69.63,59.35,81.8,68.038
+EA00173,Arts,0.2906,"Fine_Arts, ICT, Physical_Education",68.46,68.79,65.65,83.1,69.9
+EA00174,Business,0.2658,"Civics, Entrepreneurship, English",67.57,71.2,74.2,66.17,69.65
+EA00175,Arts,0.2827,"Physical_Education, Swahili, Civics",50.53,62.33,60.2,68.2,57.269
+EA00176,Arts,0.2661,"Music, Civics, French",60.84,65.35,58.03,66.8,62.539
+EA00177,Arts,0.2619,"French, Civics, Music",57.32,60.41,53.75,60.85,58.588
+EA00178,Arts,0.2697,"Physical_Education, English, Music",54.65,54.36,56.4,61.1,55.494
+EA00179,Arts,0.2667,"Science, Physical_Education, Swahili",65.99,64.68,61.5,69.9,65.459
+EA00180,Arts,0.285,"ICT, Music, Physical_Education",59.24,65.78,55.1,71.8,62.541
+EA00181,Business,0.2615,"Business_Studies, Civics, Chemistry",67.57,65.11,72.03,70.8,67.535
+EA00182,Arts,0.276,"English, Geography, Physical_Education",52.58,54.81,53.7,61.4,54.25
+EA00183,Business,0.2676,"Civics, English, Agriculture",53.52,53.92,58.95,53.9,54.394
+EA00184,Arts,0.271,"Fine_Arts, Physics, Biology",64.0,66.87,64.85,72.77,66.511
+EA00185,Arts,0.2611,"Entrepreneurship, ICT, Fine_Arts",51.4,55.43,58.17,58.3,55.022
+EA00186,Arts,0.2687,"Business_Studies, Science, Music",64.6,66.4,74.8,75.6,67.925
+EA00187,Arts,0.2836,"Geography, Physical_Education, Biology",52.03,61.58,55.0,66.73,57.994
+EA00188,Arts,0.2713,"Civics, Biology, Physical_Education",64.73,67.5,71.95,76.0,67.375
+EA00189,Arts,0.2832,"Physical_Education, ICT, Swahili",56.82,58.08,63.0,70.3,59.75
+EA00190,Arts,0.2796,"Music, Geography, Physical_Education",53.78,56.33,49.55,61.97,55.629
+EA00191,Arts,0.2602,"Civics, ICT, Science",67.54,67.27,57.37,67.6,65.653
+EA00192,Arts,0.2597,"French, Business_Studies, Civics",58.05,61.4,59.85,62.9,60.138
+EA00193,Business,0.2668,"Fine_Arts, Biology, Swahili",58.46,58.43,64.15,59.4,59.239
+EA00194,Arts,0.2858,"Fine_Arts, English, Physical_Education",62.27,68.38,66.0,78.7,67.081
+EA00195,Arts,0.26,"Science, English, Entrepreneurship",65.84,64.57,69.63,70.3,66.324
+EA00196,Arts,0.3002,"Physical_Education, Science, Biology",64.77,62.24,59.45,80.0,64.0
+EA00197,Business,0.2721,"Agriculture, Business_Studies, Biology",53.43,56.18,61.73,55.55,55.967
+EA00198,Arts,0.2888,"Physical_Education, Civics, History",64.63,67.27,57.45,76.9,66.222
+EA00199,Arts,0.2668,"Civics, English, ICT",52.93,59.2,57.45,61.7,56.394
+EA00200,Arts,0.2766,"Entrepreneurship, Physical_Education, English",50.52,58.81,59.47,64.55,56.794
+EA00201,Business,0.2752,"Entrepreneurship, Business_Studies, Physical_Education",52.2,57.33,65.43,62.8,57.294
+EA00202,Arts,0.2643,"Fine_Arts, Swahili, English",60.88,63.02,55.0,64.27,61.541
+EA00203,Arts,0.2625,"Civics, Swahili, ICT",56.02,58.89,57.6,61.4,57.806
+EA00204,Business,0.2777,"Business_Studies, Religious_Education, Agriculture",61.03,64.44,72.2,62.35,63.744
+EA00205,Arts,0.2808,"Fine_Arts, Civics, Agriculture",62.78,63.52,62.87,73.85,64.359
+EA00206,Humanities,0.2669,"History, Music, English",55.97,64.18,56.45,63.85,59.853
+EA00207,Business,0.2554,"History, Agriculture, ICT",54.97,57.27,58.3,57.75,56.594
+EA00208,Arts,0.2938,"Fine_Arts, Biology, ICT",61.63,63.95,61.5,77.85,64.335
+EA00209,Humanities,0.262,"Civics, Agriculture, English",61.87,70.87,69.8,68.0,67.112
+EA00210,Arts,0.2598,"History, Biology, Agriculture",57.5,61.2,63.8,64.05,60.318
+EA00211,Arts,0.2804,"Physical_Education, English, Geography",53.52,59.45,57.4,66.4,57.812
+EA00212,Arts,0.2637,"Fine_Arts, Science, Civics",66.28,69.8,63.43,71.45,67.629
+EA00213,Arts,0.2772,"Civics, History, Physical_Education",53.17,55.51,56.1,63.2,55.241
+EA00214,Arts,0.2682,"Civics, Fine_Arts, Physical_Education",60.57,64.93,67.0,70.55,64.206
+EA00215,Arts,0.2832,"Business_Studies, Physical_Education, Physics",59.45,60.39,66.2,73.5,61.853
+EA00216,STEM,0.2518,"History, Biology, ICT",54.78,54.07,54.1,54.6,54.359
+EA00217,Humanities,0.2596,"Geography, Science, History",61.24,65.82,62.45,64.0,63.281
+EA00218,Business,0.2699,"Business_Studies, ICT, French",56.57,63.0,69.65,68.85,62.15
+EA00219,Humanities,0.2584,"Physical_Education, Geography, Business_Studies",65.39,70.32,67.1,69.33,67.878
+EA00220,Arts,0.2842,"Fine_Arts, Physical_Education, Geography",55.87,61.8,60.63,70.8,60.559
+EA00221,Business,0.2685,"Civics, Biology, Entrepreneurship",58.2,66.28,68.43,62.0,63.022
+EA00222,Arts,0.2664,"Science, Civics, Fine_Arts",60.03,64.22,67.3,69.55,63.482
+EA00223,Business,0.2631,"Civics, Business_Studies, Agriculture",60.28,62.4,66.15,62.6,62.088
+EA00224,Arts,0.2606,"Geography, Civics, French",55.62,60.56,56.6,60.9,58.135
+EA00225,Arts,0.2652,"Physical_Education, Geography, Business_Studies",56.38,58.42,62.15,63.87,59.1
+EA00226,Arts,0.2697,"English, Music, Physical_Education",59.92,67.26,67.45,71.87,65.6
+EA00227,Arts,0.2857,"ICT, Physical_Education, Swahili",58.72,57.15,57.5,69.35,59.306
+EA00228,Arts,0.2745,"Music, Geography, ICT",60.93,62.96,61.55,70.15,62.924
+EA00229,Humanities,0.258,"Civics, English, Business_Studies",58.52,65.37,65.15,64.35,62.644
+EA00230,Humanities,0.2694,"Swahili, ICT, French",52.3,60.5,55.95,55.8,56.035
+EA00231,Arts,0.2582,"Civics, Music, Geography",56.53,61.99,59.75,62.05,59.622
+EA00232,Arts,0.2731,"Fine_Arts, Science, Geography",64.07,65.62,60.2,71.35,64.788
+EA00233,Arts,0.2829,"Civics, Physical_Education, Agriculture",65.33,66.01,69.7,79.3,67.206
+EA00234,Humanities,0.2629,"Civics, Physical_Education, English",60.7,66.9,61.95,64.95,63.535
+EA00235,Humanities,0.2681,"French, Science, Geography",58.06,61.57,55.0,55.0,58.965
+EA00236,Arts,0.2677,"ICT, Physical_Education, History",64.25,64.67,66.3,71.35,65.55
+EA00237,Arts,0.2585,"Civics, Mathematics, Swahili",56.04,61.37,60.3,61.95,59.118
+EA00238,STEM,0.2533,"Agriculture, French, ICT",56.63,55.84,56.4,54.65,56.078
+EA00239,Arts,0.2625,"Science, Physical_Education, Civics",57.51,59.32,54.25,60.9,58.165
+EA00240,Humanities,0.2777,"English, Geography, French",55.37,64.68,54.15,58.7,58.906
+EA00241,Business,0.2777,"ICT, Religious_Education, Biology",61.63,62.21,67.95,52.9,61.589
+EA00242,Business,0.2719,"Agriculture, English, Music",60.22,60.03,68.75,63.85,61.669
+EA00243,Business,0.2543,"Science, History, Entrepreneurship",58.85,59.25,60.8,60.2,59.494
+EA00244,Business,0.2629,"Agriculture, History, Physical_Education",59.4,62.1,66.95,66.23,62.278
+EA00245,Arts,0.2837,"Biology, Physical_Education, Entrepreneurship",63.39,65.04,68.0,77.8,65.6
+EA00246,Business,0.256,"Chemistry, ICT, English",64.9,63.42,67.15,66.8,64.744
+EA00247,Arts,0.2693,"Geography, Swahili, Entrepreneurship",57.49,59.56,62.03,66.0,59.522
+EA00248,Arts,0.2931,"Physical_Education, Civics, Music",53.44,59.05,50.65,67.65,56.765
+EA00249,Business,0.2755,"Business_Studies, English, Chemistry",64.27,64.46,75.5,69.8,66.1
+EA00250,Business,0.2772,"Agriculture, ICT, Swahili",55.76,57.57,69.15,67.0,58.812
+EA00251,Arts,0.2714,"ICT, Physical_Education, Business_Studies",60.18,61.26,60.87,67.9,61.2
+EA00252,Arts,0.2815,"History, Physical_Education, Fine_Arts",57.0,61.57,57.4,68.95,60.256
+EA00253,Arts,0.2595,"History, Swahili, Science",54.62,57.75,54.6,58.5,56.176
+EA00254,Arts,0.2968,"Fine_Arts, Physical_Education, ICT",56.32,60.62,55.65,72.85,59.912
+EA00255,Business,0.2691,"Religious_Education, Agriculture, Geography",65.82,69.76,74.45,66.6,68.669
+EA00256,Humanities,0.2614,"Science, Biology, Geography",60.98,62.54,57.1,58.6,61.031
+EA00257,Arts,0.2762,"Physical_Education, Geography, ICT",62.47,63.13,63.05,72.0,63.981
+EA00258,Arts,0.259,"Swahili, Physical_Education, Civics",57.27,61.65,59.85,62.5,59.887
+EA00259,Business,0.27,"Business_Studies, Geography, Civics",64.25,66.76,73.75,68.4,66.888
+EA00260,Business,0.2753,"Entrepreneurship, Fine_Arts, Science",55.83,54.52,64.7,59.93,57.556
+EA00261,Arts,0.2657,"Biology, Science, Physical_Education",55.3,54.0,60.95,61.6,56.171
+EA00262,Business,0.2687,"Science, English, Civics",62.51,67.46,70.7,62.4,65.506
+EA00263,Arts,0.2704,"Music, Science, Civics",59.05,59.31,61.35,66.6,60.318
+EA00264,Arts,0.2694,"Swahili, Civics, Fine_Arts",51.6,61.38,59.9,63.75,57.825
+EA00265,Arts,0.2698,"Music, French, English",51.57,56.6,56.25,60.75,55.271
+EA00266,Arts,0.2752,"Music, Biology, Civics",52.22,58.31,58.2,64.05,56.824
+EA00267,Arts,0.2594,"Physical_Education, Science, ICT",57.85,55.9,60.3,60.95,57.7
+EA00268,Arts,0.2741,"Fine_Arts, Music, Civics",61.65,64.52,65.7,72.43,65.041
+EA00269,Arts,0.2806,"English, Geography, Music",53.42,52.3,50.0,60.75,53.488
+EA00270,Business,0.2615,"Agriculture, Swahili, Science",62.56,67.09,70.45,69.35,65.95
+EA00271,Arts,0.2693,"Physical_Education, Agriculture, French",60.23,61.48,62.0,67.7,61.856
+EA00272,Arts,0.282,"Civics, Fine_Arts, Music",54.88,58.53,51.95,64.93,57.65
+EA00273,Business,0.2626,"Agriculture, English, ICT",54.51,56.6,59.2,55.1,56.112
+EA00274,Arts,0.2574,"French, Civics, Physics",58.17,56.63,53.15,58.2,56.947
+EA00275,Business,0.2696,"French, Business_Studies, English",54.93,61.2,65.65,61.7,59.044
+EA00276,Humanities,0.2786,"Civics, French, Science",54.72,64.58,50.43,62.1,58.522
+EA00277,Humanities,0.2581,"Music, History, Business_Studies",60.38,65.79,65.1,63.63,63.55
+EA00278,Humanities,0.2559,"Additional_Math, Science, French",60.23,61.57,59.2,59.63,60.461
+EA00279,Arts,0.2632,"Civics, English, Physical_Education",60.42,59.61,58.27,63.7,60.111
+EA00280,Arts,0.2772,"Music, Physical_Education, Business_Studies",55.97,56.63,65.0,68.1,58.578
+EA00281,Arts,0.2704,"Music, Civics, Business_Studies",51.22,61.98,61.13,64.6,58.689
+EA00282,Arts,0.2799,"English, Swahili, Biology",57.22,62.17,49.5,65.65,59.341
+EA00283,Humanities,0.2677,"French, Physics, History",66.36,70.52,61.35,65.2,66.994
+EA00284,Humanities,0.2624,"Civics, English, Geography",59.68,67.2,65.6,63.6,63.856
+EA00285,Business,0.263,"Physical_Education, Agriculture, Civics",58.05,61.9,66.43,66.2,61.85
+EA00286,Business,0.2547,"Biology, Business_Studies, Swahili",57.07,60.72,61.13,61.1,59.312
+EA00287,STEM,0.2568,"Physics, Geography, ICT",61.07,61.01,60.07,55.65,60.278
+EA00288,STEM,0.2594,"ICT, Science, Civics",56.44,55.69,53.2,52.25,55.322
+EA00289,Humanities,0.262,"Civics, Agriculture, Swahili",62.39,70.93,70.6,66.8,67.322
+EA00290,Humanities,0.2621,"Civics, Agriculture, History",57.62,66.13,65.93,62.65,62.682
+EA00291,Humanities,0.2667,"Geography, Entrepreneurship, Civics",66.01,72.45,69.1,64.1,68.718
+EA00292,Arts,0.2753,"Civics, ICT, Physical_Education",64.23,66.03,70.85,76.4,66.606
+EA00293,Humanities,0.2693,"Physics, History, Swahili",59.77,66.3,56.43,63.7,61.838
+EA00294,Business,0.2679,"Music, History, Business_Studies",65.88,65.7,72.85,67.5,66.924
+EA00295,Business,0.2615,"Swahili, Civics, Agriculture",65.4,70.1,72.5,69.2,68.572
+EA00296,Arts,0.2689,"Mathematics, Physical_Education, Fine_Arts",62.43,64.03,64.03,70.05,64.078
+EA00297,Arts,0.2799,"Science, Music, Physical_Education",62.05,64.4,64.83,74.35,64.818
+EA00298,Arts,0.2773,"Physical_Education, History, French",57.82,64.03,55.5,68.03,61.541
+EA00299,Arts,0.2725,"Swahili, ICT, Physical_Education",64.17,65.25,58.1,70.25,64.553
+EA00300,Arts,0.2875,"Physical_Education, Civics, Music",53.95,60.07,53.27,67.5,57.582
+EA00301,Arts,0.2698,"Civics, Swahili, Geography",55.07,57.93,54.95,62.05,56.944
+EA00302,Arts,0.2639,"Fine_Arts, Civics, Geography",57.73,55.32,58.2,61.4,57.344
+EA00303,Arts,0.2977,"Physical_Education, Geography, English",54.22,57.73,54.05,70.35,57.531
+EA00304,Arts,0.271,"English, Business_Studies, Physical_Education",56.12,62.39,59.03,66.0,59.794
+EA00305,Arts,0.2721,"Music, English, Biology",60.42,61.78,63.2,69.3,62.794
+EA00306,Arts,0.2701,"History, Agriculture, Physical_Education",59.37,63.41,66.4,70.0,62.488
+EA00307,Business,0.2655,"ICT, Business_Studies, French",66.4,66.2,69.47,59.6,66.456
+EA00308,Business,0.265,"Business_Studies, Mathematics, Civics",55.1,58.02,62.8,61.1,58.012
+EA00309,Arts,0.2968,"Physical_Education, Mathematics, History",56.93,56.4,54.17,70.7,57.075
+EA00310,Business,0.2626,"Agriculture, French, Civics",66.55,68.43,70.77,63.75,67.629
+EA00311,Business,0.2709,"Civics, Business_Studies, English",50.97,59.53,60.7,52.9,55.753
+EA00312,Arts,0.2901,"Physical_Education, Music, ICT",63.42,67.95,63.3,79.55,67.119
+EA00313,Business,0.2719,"Entrepreneurship, Biology, Civics",57.72,56.1,61.97,52.1,57.556
+EA00314,Business,0.2571,"Biology, Religious_Education, Geography",58.66,60.46,62.2,60.6,59.929
+EA00315,Arts,0.256,"Swahili, Mathematics, English",66.03,65.99,68.2,68.9,66.462
+EA00316,Humanities,0.2565,"Civics, Agriculture, Physical_Education",57.48,60.98,59.7,59.55,59.353
+EA00317,Arts,0.2786,"ICT, Business_Studies, Civics",57.58,54.45,59.17,66.1,57.238
+EA00318,Arts,0.2662,"French, Religious_Education, Physical_Education",58.37,59.83,59.6,64.5,59.544
+EA00319,Humanities,0.27,"Mathematics, Civics, Geography",54.99,61.4,54.5,56.5,57.659
+EA00320,Arts,0.2545,"Music, French, Mathematics",62.57,63.13,61.35,63.85,62.788
+EA00321,Business,0.2602,"History, Swahili, Civics",58.27,64.8,66.75,66.7,62.947
+EA00322,Humanities,0.2742,"Civics, Swahili, English",57.02,70.46,62.9,66.55,64.365
+EA00323,Humanities,0.2698,"History, Music, Biology",68.12,73.58,62.7,68.3,69.067
+EA00324,Business,0.2758,"Agriculture, Swahili, Business_Studies",57.38,57.51,66.77,60.45,59.339
+EA00325,Arts,0.2706,"ICT, English, Agriculture",51.84,59.45,53.67,61.2,55.4
+EA00326,STEM,0.2623,"Civics, Science, ICT",71.23,67.08,64.3,69.0,68.531
+EA00327,Arts,0.2622,"Physical_Education, ICT, Business_Studies",58.55,58.49,61.63,63.5,59.589
+EA00328,Arts,0.3105,"Fine_Arts, Physical_Education, Music",55.88,55.0,49.3,72.13,57.665
+EA00329,Arts,0.2836,"Physical_Education, Music, Civics",49.73,56.56,59.4,65.6,55.547
+EA00330,Arts,0.2659,"Biology, French, Mathematics",57.35,54.38,55.6,60.6,56.112
+EA00331,Arts,0.2674,"Agriculture, Music, Physical_Education",68.02,67.53,67.83,74.25,68.547
+EA00332,Business,0.2681,"Mathematics, Agriculture, Geography",60.33,58.99,66.65,62.6,60.653
+EA00333,Arts,0.2653,"Biology, History, Agriculture",63.03,66.65,70.5,72.3,66.171
+EA00334,Arts,0.2893,"Physical_Education, Fine_Arts, Business_Studies",60.21,63.75,70.4,79.1,64.882
+EA00335,Business,0.2667,"Business_Studies, French, Fine_Arts",61.48,69.27,73.4,71.1,67.329
+EA00336,Humanities,0.2628,"Swahili, ICT, Business_Studies",61.95,64.4,60.13,58.6,62.1
+EA00337,Business,0.2725,"English, Geography, Agriculture",53.96,57.61,62.85,56.2,56.641
+EA00338,Business,0.2725,"Business_Studies, Civics, Biology",57.63,60.13,64.9,55.5,59.694
+EA00339,Humanities,0.2682,"Swahili, Religious_Education, English",51.0,63.43,61.8,60.27,58.578
+EA00340,Humanities,0.2598,"Civics, Science, Physical_Education",56.03,60.15,55.2,60.1,57.818
+EA00341,Arts,0.2636,"French, ICT, Physical_Education",63.24,67.6,70.0,71.9,66.262
+EA00342,Arts,0.2575,"Swahili, Chemistry, Physics",67.8,69.52,67.3,70.95,68.775
+EA00343,Arts,0.2727,"Swahili, Religious_Education, Physics",56.07,55.93,53.65,62.1,56.081
+EA00344,Business,0.2766,"Business_Studies, Civics, History",56.7,66.12,71.3,63.65,62.559
+EA00345,Arts,0.2812,"Physical_Education, History, Geography",58.56,62.63,58.5,70.3,60.812
+EA00346,Business,0.2783,"Business_Studies, Agriculture, Additional_Math",54.51,57.3,66.05,59.5,57.312
+EA00347,Arts,0.2569,"French, Swahili, Civics",57.84,58.93,55.3,59.5,58.088
+EA00348,Humanities,0.2589,"History, Geography, Physical_Education",62.93,68.88,65.8,68.4,66.012
+EA00349,Arts,0.2801,"Business_Studies, Physical_Education, History",49.86,51.65,58.9,62.4,52.444
+EA00350,Business,0.2686,"Science, Biology, Agriculture",56.25,55.14,63.2,60.7,57.333
+EA00351,Arts,0.268,"Entrepreneurship, Science, Geography",63.65,65.35,63.83,70.6,65.1
+EA00352,Arts,0.318,"Physical_Education, History, Geography",59.31,65.97,57.67,85.3,62.9
+EA00353,Arts,0.2763,"Fine_Arts, ICT, French",55.11,60.58,59.53,66.9,58.983
+EA00354,Arts,0.2942,"Physical_Education, Mathematics, French",53.46,58.67,57.03,70.5,57.028
+EA00355,Arts,0.2565,"History, Physical_Education, ICT",60.22,63.93,64.9,65.23,62.965
+EA00356,Arts,0.2631,"ICT, Civics, French",62.83,66.59,65.77,69.7,65.3
+EA00357,Arts,0.2798,"Physical_Education, Business_Studies, Science",62.78,64.12,69.27,76.2,65.338
+EA00358,Arts,0.2755,"Physical_Education, Biology, Business_Studies",55.73,53.81,61.9,65.2,56.722
+EA00359,Business,0.2958,"Business_Studies, Agriculture, Fine_Arts",55.27,55.02,69.55,55.3,56.865
+EA00360,Arts,0.2634,"Civics, Swahili, Entrepreneurship",60.65,66.49,66.9,69.4,64.671
+EA00361,Arts,0.2618,"Physical_Education, English, Civics",60.52,66.68,65.2,68.25,64.381
+EA00362,Arts,0.283,"Fine_Arts, History, Science",66.34,66.76,59.7,76.1,66.85
+EA00363,Arts,0.2791,"Music, ICT, History",65.73,67.61,65.05,76.8,67.729
+EA00364,Arts,0.2831,"Physical_Education, Agriculture, Entrepreneurship",61.31,64.72,71.77,78.1,65.347
+EA00365,Business,0.2682,"Agriculture, French, Mathematics",56.93,57.53,64.35,61.13,58.759
+EA00366,Business,0.2755,"Agriculture, Civics, Swahili",59.24,61.27,69.0,60.95,61.306
+EA00367,Arts,0.2975,"Physical_Education, ICT, French",62.16,67.18,60.23,80.3,64.659
+EA00368,Business,0.2585,"Science, Physics, Business_Studies",65.11,60.59,66.2,64.2,63.483
+EA00369,Business,0.2722,"Civics, Agriculture, French",58.6,60.23,64.97,54.9,60.089
+EA00370,Business,0.2562,"Biology, Physical_Education, Geography",67.58,65.27,69.57,69.15,67.189
+EA00371,Arts,0.2743,"Religious_Education, Physical_Education, Entrepreneurship",56.08,58.17,62.2,66.7,58.647
+EA00372,Arts,0.2727,"Fine_Arts, Geography, English",56.76,65.47,64.45,70.0,62.294
+EA00373,Arts,0.2824,"Physical_Education, Fine_Arts, English",62.62,71.18,66.2,78.7,68.288
+EA00374,Arts,0.2659,"Biology, Physical_Education, English",60.62,62.22,67.9,69.1,63.188
+EA00375,Business,0.2727,"Business_Studies, Civics, Entrepreneurship",55.02,55.03,61.2,53.15,55.894
+EA00376,Business,0.2718,"Biology, Agriculture, Civics",55.01,57.87,63.4,57.0,57.278
+EA00377,Arts,0.2832,"Civics, English, Physical_Education",53.3,61.14,54.4,66.7,57.512
+EA00378,Arts,0.2661,"Business_Studies, Physical_Education, French",60.25,65.38,67.25,69.95,64.262
+EA00379,Business,0.2619,"ICT, Physics, Physical_Education",63.98,63.53,68.05,64.23,64.347
+EA00380,Humanities,0.2558,"Science, English, Civics",65.38,68.19,66.6,66.35,66.794
+EA00381,Humanities,0.2689,"French, History, Swahili",66.18,72.65,67.6,63.7,68.475
+EA00382,Arts,0.2761,"Physical_Education, English, Religious_Education",64.27,66.79,67.73,75.8,66.594
+EA00383,Business,0.2666,"ICT, History, Business_Studies",59.29,58.37,63.85,58.0,59.347
+EA00384,Humanities,0.2643,"Civics, Biology, English",66.65,69.9,65.05,62.83,66.935
+EA00385,Arts,0.292,"Physical_Education, Agriculture, ICT",54.7,55.43,59.63,70.0,56.729
+EA00386,Humanities,0.2654,"Swahili, History, Business_Studies",58.66,63.35,59.4,57.3,60.425
+EA00387,Arts,0.2836,"Fine_Arts, Physical_Education, Physics",60.17,59.34,63.6,72.5,61.6
+EA00388,Business,0.256,"ICT, English, Science",65.83,63.5,66.6,64.27,64.824
+EA00389,Arts,0.2596,"Science, Music, History",55.95,57.83,60.3,61.05,57.838
+EA00390,Humanities,0.266,"Religious_Education, Geography, Biology",60.73,69.17,67.17,63.0,65.339
+EA00391,Business,0.2643,"Agriculture, Geography, ICT",57.6,54.09,61.83,60.4,57.065
+EA00392,Business,0.2594,"ICT, Agriculture, Civics",62.3,68.67,70.13,69.3,66.753
+EA00393,Arts,0.2714,"Physical_Education, English, Physics",56.73,56.5,56.15,63.1,57.369
+EA00394,Arts,0.2976,"Music, Physical_Education, Science",51.45,53.2,52.65,66.65,54.1
+EA00395,Arts,0.2712,"Physical_Education, Business_Studies, Civics",63.97,68.53,73.37,76.6,68.247
+EA00396,Business,0.2633,"Physical_Education, Civics, Business_Studies",50.03,58.29,60.05,59.7,55.967
+EA00397,Arts,0.2763,"Science, ICT, Physical_Education",69.84,64.5,59.7,74.1,66.7
+EA00398,Arts,0.2735,"Civics, Business_Studies, Fine_Arts",58.4,58.57,61.97,67.35,60.141
+EA00399,Humanities,0.2837,"History, Civics, Geography",53.65,61.73,48.8,53.4,56.044
+EA00400,Arts,0.2734,"Physical_Education, Physics, Business_Studies",60.2,57.66,61.8,67.6,60.212
+EA00401,Arts,0.2678,"Music, Civics, Fine_Arts",63.78,67.4,71.0,73.97,67.706
+EA00402,Humanities,0.2585,"Entrepreneurship, ICT, Swahili",58.36,63.53,61.4,62.45,61.044
+EA00403,Humanities,0.2689,"French, Biology, ICT",61.01,65.0,61.1,54.6,62.119
+EA00404,Business,0.2886,"Business_Studies, Agriculture, ICT",57.8,58.85,73.45,64.4,60.975
+EA00405,Arts,0.2926,"Swahili, Music, Physical_Education",57.28,63.51,62.15,75.67,63.311
+EA00406,Arts,0.2776,"Physics, Music, Physical_Education",63.99,64.67,65.1,74.45,65.539
+EA00407,Arts,0.2887,"ICT, English, Fine_Arts",56.63,59.35,58.1,70.65,59.5
+EA00408,Arts,0.2979,"Physical_Education, Swahili, History",65.14,68.78,68.57,85.9,68.253
+EA00409,Arts,0.273,"Agriculture, French, Physical_Education",53.05,59.43,59.25,64.5,57.331
+EA00410,Business,0.2739,"Swahili, English, Biology",61.89,66.22,72.5,64.05,64.918
+EA00411,Business,0.2639,"ICT, Physics, Agriculture",68.81,69.53,75.25,71.6,70.029
+EA00412,Arts,0.2836,"Agriculture, Physical_Education, Science",55.49,54.08,63.2,68.4,57.112
+EA00413,Arts,0.267,"Business_Studies, Civics, Physical_Education",60.0,69.77,72.0,73.5,67.506
+EA00414,STEM,0.2531,"Civics, ICT, Biology",58.3,57.57,56.25,58.2,57.788
+EA00415,Business,0.2665,"Business_Studies, Mathematics, Physical_Education",63.8,65.97,72.35,69.33,66.547
+EA00416,Arts,0.2853,"Physical_Education, ICT, Business_Studies",56.3,61.93,59.65,71.0,60.1
+EA00417,Arts,0.269,"English, Business_Studies, Civics",56.82,64.0,59.05,66.2,61.141
+EA00418,Arts,0.2598,"Music, History, English",56.46,56.4,58.75,60.23,57.322
+EA00419,Humanities,0.2645,"Civics, French, History",56.83,63.28,62.4,56.7,59.931
+EA00420,Humanities,0.2638,"French, Geography, Civics",60.37,69.03,66.05,66.25,64.788
+EA00421,Arts,0.2864,"Physical_Education, Geography, Business_Studies",55.59,55.15,65.4,70.7,57.594
+EA00422,Arts,0.26,"Entrepreneurship, Civics, Physical_Education",64.98,65.39,65.47,68.8,65.644
+EA00423,Arts,0.273,"ICT, Religious_Education, Civics",57.85,63.84,60.95,68.6,61.531
+EA00424,Humanities,0.2603,"ICT, History, Fine_Arts",59.58,62.1,55.8,61.13,60.3
+EA00425,Arts,0.2844,"Physical_Education, Business_Studies, Music",58.65,57.66,64.23,71.75,60.65
+EA00426,Humanities,0.2586,"Geography, Biology, Swahili",66.28,70.35,68.33,67.1,68.244
+EA00427,Arts,0.2822,"Physical_Education, French, History",56.6,62.35,58.25,69.65,60.359
+EA00428,Arts,0.2649,"Civics, Chemistry, English",58.1,62.09,58.9,64.55,60.594
+EA00429,Business,0.2687,"Science, Business_Studies, Civics",56.13,55.29,63.3,60.9,57.329
+EA00430,Arts,0.2598,"Science, Civics, Physical_Education",57.1,55.53,55.85,59.15,56.612
+EA00431,Arts,0.2656,"Physical_Education, Civics, Mathematics",54.5,58.2,59.13,62.13,57.778
+EA00432,Arts,0.2738,"Music, Civics, Biology",66.14,70.87,64.65,76.05,68.8
+EA00433,Arts,0.2878,"Physical_Education, Fine_Arts, Civics",57.87,62.37,59.65,72.7,61.631
+EA00434,Arts,0.2666,"Agriculture, Physical_Education, ICT",66.57,68.82,66.53,73.4,68.072
+EA00435,Business,0.2564,"History, Entrepreneurship, Fine_Arts",66.47,69.78,70.13,67.2,68.371
+EA00436,Arts,0.2873,"Religious_Education, Physical_Education, ICT",57.09,63.09,61.9,73.4,61.128
+EA00437,Arts,0.2725,"English, Geography, History",60.0,68.78,56.35,69.35,64.006
+EA00438,Humanities,0.2581,"ICT, Civics, Physics",57.98,58.73,56.8,54.0,57.406
+EA00439,Business,0.2626,"Science, Civics, Biology",58.8,59.89,65.25,64.5,60.572
+EA00440,Arts,0.2676,"History, Physical_Education, Science",54.97,57.23,57.8,62.1,57.062
+EA00441,Humanities,0.252,"ICT, English, Religious_Education",60.4,61.33,61.2,60.4,60.85
+EA00442,Business,0.2684,"Business_Studies, Music, Agriculture",60.68,61.3,69.03,66.17,63.194
+EA00443,Humanities,0.2558,"Business_Studies, Civics, Science",66.18,68.72,68.57,65.17,67.256
+EA00444,Business,0.2561,"History, Civics, Agriculture",58.9,61.77,61.95,59.25,60.312
+EA00445,Arts,0.2683,"Fine_Arts, History, Chemistry",65.3,65.88,65.2,72.0,66.676
+EA00446,Arts,0.2885,"Physical_Education, History, English",55.33,62.08,55.97,70.3,58.919
+EA00447,Arts,0.2884,"Physical_Education, ICT, Mathematics",64.31,63.5,64.4,77.9,64.788
+EA00448,Arts,0.2982,"Physical_Education, Music, ICT",64.54,64.4,69.65,84.4,67.261
+EA00449,Humanities,0.2605,"English, Swahili, Agriculture",59.63,65.64,65.6,61.1,63.1
+EA00450,Arts,0.2879,"Physical_Education, Business_Studies, Science",55.52,60.86,59.35,71.05,59.994
+EA00451,Arts,0.2762,"Physical_Education, History, Music",62.65,67.93,64.9,74.6,66.406
+EA00452,Humanities,0.2591,"Music, Science, Civics",67.35,70.7,67.35,67.45,68.619
+EA00453,Arts,0.2743,"Biology, ICT, Physical_Education",59.86,62.65,62.45,69.9,61.856
+EA00454,Business,0.2665,"Swahili, Agriculture, Geography",62.74,69.3,70.45,61.9,66.056
+EA00455,Arts,0.272,"ICT, Biology, Physical_Education",63.09,63.23,70.63,73.6,65.088
+EA00456,Business,0.2655,"ICT, English, Business_Studies",63.38,67.8,71.9,67.7,66.65
+EA00457,Humanities,0.2545,"Civics, French, Entrepreneurship",56.47,59.14,57.97,58.85,58.022
+EA00458,STEM,0.2538,"Civics, Biology, Swahili",60.77,60.28,59.5,58.85,60.188
+EA00459,Arts,0.2708,"French, Geography, Physical_Education",61.17,63.01,63.8,69.8,62.747
+EA00460,Arts,0.269,"Fine_Arts, Science, Business_Studies",59.52,59.82,62.15,66.8,60.869
+EA00461,STEM,0.2558,"Science, Physics, Agriculture",56.49,54.6,54.35,55.35,55.389
+EA00462,Arts,0.2684,"Physics, Music, Entrepreneurship",56.06,57.02,59.47,63.3,57.75
+EA00463,Business,0.2582,"ICT, Biology, Swahili",65.78,65.26,69.15,67.67,66.267
+EA00464,Arts,0.298,"Physical_Education, Entrepreneurship, Civics",57.56,61.7,60.0,76.1,60.541
+EA00465,Arts,0.2827,"Fine_Arts, Civics, History",51.58,58.77,53.4,64.55,56.125
+EA00466,Arts,0.2612,"Science, Biology, ICT",64.17,61.75,63.5,66.97,63.729
+EA00467,Arts,0.2869,"Physical_Education, Business_Studies, History",58.53,62.12,67.03,75.5,62.531
+EA00468,Business,0.2641,"English, Science, History",67.27,69.64,72.5,65.1,68.825
+EA00469,Business,0.273,"Geography, Additional_Math, Agriculture",56.14,59.31,65.25,58.3,58.647
+EA00470,Arts,0.258,"Religious_Education, Geography, ICT",63.05,63.1,58.9,64.35,62.522
+EA00471,Business,0.2626,"Agriculture, ICT, Civics",55.36,58.82,61.4,58.2,57.588
+EA00472,Humanities,0.2574,"Biology, Swahili, Business_Studies",66.89,68.13,63.3,66.4,66.744
+EA00473,Business,0.2614,"Business_Studies, French, Civics",63.04,66.63,69.9,67.87,65.806
+EA00474,Business,0.2706,"Entrepreneurship, English, Religious_Education",53.18,57.97,61.67,55.1,56.672
+EA00475,Arts,0.2627,"Fine_Arts, English, History",55.72,60.3,60.07,62.73,59.139
+EA00476,Business,0.2617,"Civics, English, Mathematics",65.76,66.95,71.7,69.6,67.188
+EA00477,Arts,0.2639,"Business_Studies, Science, Biology",57.7,54.06,60.8,61.85,57.089
+EA00478,Arts,0.295,"Physical_Education, Science, Music",61.37,58.95,58.1,74.65,61.712
+EA00479,Humanities,0.2582,"French, ICT, Civics",61.53,68.67,67.95,67.8,65.541
+EA00480,Arts,0.2624,"Civics, French, History",63.66,66.02,58.95,67.1,64.494
+EA00481,Arts,0.2931,"Physical_Education, Swahili, History",55.87,62.02,58.43,73.1,59.506
+EA00482,Humanities,0.282,"Civics, English, History",60.23,73.27,64.25,62.1,66.182
+EA00483,Arts,0.2804,"ICT, Physical_Education, Biology",58.04,59.8,59.77,69.2,59.624
+EA00484,Humanities,0.2592,"Civics, English, ICT",58.07,61.7,59.7,58.55,59.812
+EA00485,Arts,0.2801,"Music, Civics, Science",57.48,61.05,58.35,68.83,60.847
+EA00486,Business,0.2552,"French, Physics, Swahili",60.43,61.53,61.9,58.7,60.812
+EA00487,Business,0.2699,"Physical_Education, Agriculture, Biology",57.9,58.91,66.7,63.65,60.029
+EA00488,Arts,0.2646,"Civics, Biology, Business_Studies",54.97,57.09,55.85,60.4,56.265
+EA00489,Arts,0.2836,"Fine_Arts, Physical_Education, Agriculture",60.93,64.4,64.5,75.15,64.459
+EA00490,Arts,0.2712,"Civics, Swahili, Physical_Education",58.31,63.53,65.5,69.7,61.881
+EA00491,Business,0.2621,"Civics, Religious_Education, Science",54.4,60.63,61.2,57.3,57.935
+EA00492,Arts,0.255,"History, Civics, Music",59.98,62.5,60.37,62.6,61.322
+EA00493,Business,0.258,"Geography, Physical_Education, Mathematics",58.1,58.47,61.6,60.6,58.988
+EA00494,STEM,0.2532,"ICT, Science, History",68.54,68.26,67.4,66.55,68.083
+EA00495,Humanities,0.2612,"Geography, English, Civics",63.2,69.08,65.2,67.0,66.131
+EA00496,Humanities,0.2732,"Civics, Swahili, Biology",65.38,74.03,65.57,66.0,68.7
+EA00497,STEM,0.2604,"Biology, ICT, Geography",64.9,60.4,61.85,62.1,62.481
+EA00498,Business,0.2702,"Business_Studies, Science, French",58.13,57.63,63.33,55.3,58.541
+EA00499,Humanities,0.2816,"Swahili, History, French",58.82,68.12,53.35,61.65,61.975
+EA00500,Arts,0.2947,"Physical_Education, French, Swahili",59.03,60.7,57.85,74.2,60.456

+ 501 - 0
internal/src/models/datasets/nlp_pred_computer_science.csv

@@ -0,0 +1,501 @@
+student_id,predicted_subject,predicted_value,prediction_target,status,confidence,reasoning_summary
+EA00001,Computer_Science,48.04,Computer_Science_final,Fail,0.83,"Based on ICT=46.8, Math=60.9, Physics=45.9"
+EA00002,Computer_Science,56.52,Computer_Science_final,Pass,0.92,"Based on ICT=63.1, Math=52.8, Physics=52.5"
+EA00003,Computer_Science,55.31,Computer_Science_final,Pass,0.9,"Based on ICT=60.7, Math=51.0, Physics=58.3"
+EA00004,Computer_Science,56.76,Computer_Science_final,Pass,0.92,"Based on ICT=61.2, Math=60.2, Physics=49.0"
+EA00005,Computer_Science,59.56,Computer_Science_final,Pass,0.95,"Based on ICT=62.4, Math=65.0, Physics=59.6"
+EA00006,Computer_Science,44.54,Computer_Science_final,Fail,0.8,"Based on ICT=55.7, Math=46.7, Physics=38.9"
+EA00007,Computer_Science,59.65,Computer_Science_final,Pass,0.95,"Based on ICT=64.2, Math=53.6, Physics=65.0"
+EA00008,Computer_Science,57.97,Computer_Science_final,Pass,0.93,"Based on ICT=69.0, Math=49.2, Physics=52.0"
+EA00009,Computer_Science,54.21,Computer_Science_final,Pass,0.89,"Based on ICT=57.5, Math=52.8, Physics=62.9"
+EA00010,Computer_Science,59.55,Computer_Science_final,Pass,0.95,"Based on ICT=65.6, Math=58.5, Physics=57.4"
+EA00011,Computer_Science,64.49,Computer_Science_final,Pass,0.95,"Based on ICT=68.3, Math=63.6, Physics=59.5"
+EA00012,Computer_Science,66.98,Computer_Science_final,Pass,0.95,"Based on ICT=74.4, Math=57.3, Physics=70.5"
+EA00013,Computer_Science,57.24,Computer_Science_final,Pass,0.92,"Based on ICT=58.3, Math=56.9, Physics=48.8"
+EA00014,Computer_Science,52.64,Computer_Science_final,Pass,0.88,"Based on ICT=56.0, Math=45.1, Physics=59.7"
+EA00015,Computer_Science,59.74,Computer_Science_final,Pass,0.95,"Based on ICT=63.2, Math=65.3, Physics=56.0"
+EA00016,Computer_Science,66.52,Computer_Science_final,Pass,0.95,"Based on ICT=73.0, Math=64.3, Physics=65.7"
+EA00017,Computer_Science,51.57,Computer_Science_final,Pass,0.87,"Based on ICT=50.4, Math=56.6, Physics=52.3"
+EA00018,Computer_Science,63.2,Computer_Science_final,Pass,0.95,"Based on ICT=57.7, Math=64.6, Physics=62.3"
+EA00019,Computer_Science,59.93,Computer_Science_final,Pass,0.95,"Based on ICT=69.7, Math=58.2, Physics=51.9"
+EA00020,Computer_Science,54.84,Computer_Science_final,Pass,0.9,"Based on ICT=62.3, Math=48.2, Physics=58.7"
+EA00021,Computer_Science,63.69,Computer_Science_final,Pass,0.95,"Based on ICT=65.9, Math=61.7, Physics=59.7"
+EA00022,Computer_Science,56.45,Computer_Science_final,Pass,0.91,"Based on ICT=61.9, Math=59.9, Physics=63.3"
+EA00023,Computer_Science,54.23,Computer_Science_final,Pass,0.89,"Based on ICT=56.5, Math=56.4, Physics=58.9"
+EA00024,Computer_Science,59.3,Computer_Science_final,Pass,0.94,"Based on ICT=64.5, Math=59.0, Physics=54.2"
+EA00025,Computer_Science,61.23,Computer_Science_final,Pass,0.95,"Based on ICT=70.3, Math=56.9, Physics=51.6"
+EA00026,Computer_Science,58.74,Computer_Science_final,Pass,0.94,"Based on ICT=67.7, Math=60.1, Physics=50.5"
+EA00027,Computer_Science,62.11,Computer_Science_final,Pass,0.95,"Based on ICT=66.9, Math=61.8, Physics=57.3"
+EA00028,Computer_Science,60.01,Computer_Science_final,Pass,0.95,"Based on ICT=63.7, Math=49.5, Physics=69.0"
+EA00029,Computer_Science,49.69,Computer_Science_final,Fail,0.85,"Based on ICT=56.5, Math=49.7, Physics=33.5"
+EA00030,Computer_Science,54.86,Computer_Science_final,Pass,0.9,"Based on ICT=62.1, Math=53.4, Physics=58.2"
+EA00031,Computer_Science,57.07,Computer_Science_final,Pass,0.92,"Based on ICT=60.9, Math=52.0, Physics=56.8"
+EA00032,Computer_Science,66.78,Computer_Science_final,Pass,0.95,"Based on ICT=76.6, Math=72.8, Physics=60.5"
+EA00033,Computer_Science,49.93,Computer_Science_final,Fail,0.85,"Based on ICT=59.1, Math=57.9, Physics=41.0"
+EA00034,Computer_Science,47.31,Computer_Science_final,Fail,0.82,"Based on ICT=55.4, Math=46.0, Physics=50.2"
+EA00035,Computer_Science,55.67,Computer_Science_final,Pass,0.91,"Based on ICT=63.3, Math=54.2, Physics=53.2"
+EA00036,Computer_Science,60.07,Computer_Science_final,Pass,0.95,"Based on ICT=69.5, Math=52.9, Physics=54.6"
+EA00037,Computer_Science,57.76,Computer_Science_final,Pass,0.93,"Based on ICT=63.9, Math=46.0, Physics=61.8"
+EA00038,Computer_Science,50.41,Computer_Science_final,Pass,0.85,"Based on ICT=48.1, Math=67.2, Physics=39.1"
+EA00039,Computer_Science,45.24,Computer_Science_final,Fail,0.8,"Based on ICT=46.1, Math=39.2, Physics=56.5"
+EA00040,Computer_Science,55.94,Computer_Science_final,Pass,0.91,"Based on ICT=56.2, Math=62.8, Physics=52.1"
+EA00041,Computer_Science,48.78,Computer_Science_final,Fail,0.84,"Based on ICT=50.3, Math=47.8, Physics=49.7"
+EA00042,Computer_Science,55.09,Computer_Science_final,Pass,0.9,"Based on ICT=57.3, Math=58.5, Physics=47.6"
+EA00043,Computer_Science,58.3,Computer_Science_final,Pass,0.93,"Based on ICT=61.4, Math=60.9, Physics=60.9"
+EA00044,Computer_Science,61.61,Computer_Science_final,Pass,0.95,"Based on ICT=61.4, Math=56.9, Physics=71.7"
+EA00045,Computer_Science,57.95,Computer_Science_final,Pass,0.93,"Based on ICT=64.0, Math=49.3, Physics=59.2"
+EA00046,Computer_Science,62.49,Computer_Science_final,Pass,0.95,"Based on ICT=70.0, Math=58.8, Physics=54.7"
+EA00047,Computer_Science,57.11,Computer_Science_final,Pass,0.92,"Based on ICT=68.3, Math=50.6, Physics=50.2"
+EA00048,Computer_Science,60.84,Computer_Science_final,Pass,0.95,"Based on ICT=64.3, Math=67.0, Physics=53.7"
+EA00049,Computer_Science,60.24,Computer_Science_final,Pass,0.95,"Based on ICT=61.0, Math=59.8, Physics=61.5"
+EA00050,Computer_Science,54.97,Computer_Science_final,Pass,0.9,"Based on ICT=62.5, Math=51.1, Physics=65.2"
+EA00051,Computer_Science,56.25,Computer_Science_final,Pass,0.91,"Based on ICT=60.7, Math=61.4, Physics=53.5"
+EA00052,Computer_Science,62.6,Computer_Science_final,Pass,0.95,"Based on ICT=71.9, Math=55.1, Physics=60.8"
+EA00053,Computer_Science,51.61,Computer_Science_final,Pass,0.87,"Based on ICT=57.8, Math=50.7, Physics=49.4"
+EA00054,Computer_Science,57.84,Computer_Science_final,Pass,0.93,"Based on ICT=50.9, Math=56.3, Physics=59.9"
+EA00055,Computer_Science,51.86,Computer_Science_final,Pass,0.87,"Based on ICT=58.8, Math=49.6, Physics=56.1"
+EA00056,Computer_Science,57.85,Computer_Science_final,Pass,0.93,"Based on ICT=72.9, Math=58.6, Physics=45.9"
+EA00057,Computer_Science,59.9,Computer_Science_final,Pass,0.95,"Based on ICT=61.0, Math=63.2, Physics=62.4"
+EA00058,Computer_Science,65.11,Computer_Science_final,Pass,0.95,"Based on ICT=66.1, Math=65.8, Physics=57.0"
+EA00059,Computer_Science,55.07,Computer_Science_final,Pass,0.9,"Based on ICT=58.1, Math=58.7, Physics=53.1"
+EA00060,Computer_Science,67.36,Computer_Science_final,Pass,0.95,"Based on ICT=72.7, Math=62.9, Physics=62.9"
+EA00061,Computer_Science,62.21,Computer_Science_final,Pass,0.95,"Based on ICT=59.7, Math=68.7, Physics=60.8"
+EA00062,Computer_Science,53.41,Computer_Science_final,Pass,0.88,"Based on ICT=62.6, Math=51.5, Physics=45.8"
+EA00063,Computer_Science,59.69,Computer_Science_final,Pass,0.95,"Based on ICT=70.2, Math=54.3, Physics=67.9"
+EA00064,Computer_Science,56.15,Computer_Science_final,Pass,0.91,"Based on ICT=59.7, Math=54.9, Physics=58.1"
+EA00065,Computer_Science,56.03,Computer_Science_final,Pass,0.91,"Based on ICT=52.1, Math=63.0, Physics=50.8"
+EA00066,Computer_Science,56.98,Computer_Science_final,Pass,0.92,"Based on ICT=57.0, Math=72.8, Physics=52.6"
+EA00067,Computer_Science,64.27,Computer_Science_final,Pass,0.95,"Based on ICT=70.7, Math=62.2, Physics=65.9"
+EA00068,Computer_Science,61.49,Computer_Science_final,Pass,0.95,"Based on ICT=68.3, Math=52.9, Physics=61.3"
+EA00069,Computer_Science,56.96,Computer_Science_final,Pass,0.92,"Based on ICT=56.7, Math=60.3, Physics=57.8"
+EA00070,Computer_Science,62.59,Computer_Science_final,Pass,0.95,"Based on ICT=59.7, Math=65.2, Physics=62.1"
+EA00071,Computer_Science,63.49,Computer_Science_final,Pass,0.95,"Based on ICT=69.6, Math=69.7, Physics=42.5"
+EA00072,Computer_Science,61.31,Computer_Science_final,Pass,0.95,"Based on ICT=68.7, Math=53.9, Physics=46.0"
+EA00073,Computer_Science,49.71,Computer_Science_final,Fail,0.85,"Based on ICT=51.6, Math=45.7, Physics=60.6"
+EA00074,Computer_Science,68.63,Computer_Science_final,Pass,0.95,"Based on ICT=70.8, Math=71.7, Physics=63.7"
+EA00075,Computer_Science,55.51,Computer_Science_final,Pass,0.91,"Based on ICT=61.4, Math=55.3, Physics=55.1"
+EA00076,Computer_Science,58.58,Computer_Science_final,Pass,0.94,"Based on ICT=65.0, Math=59.2, Physics=65.8"
+EA00077,Computer_Science,65.37,Computer_Science_final,Pass,0.95,"Based on ICT=71.6, Math=60.6, Physics=55.3"
+EA00078,Computer_Science,54.1,Computer_Science_final,Pass,0.89,"Based on ICT=57.8, Math=52.6, Physics=60.9"
+EA00079,Computer_Science,58.48,Computer_Science_final,Pass,0.93,"Based on ICT=62.0, Math=52.4, Physics=71.5"
+EA00080,Computer_Science,64.01,Computer_Science_final,Pass,0.95,"Based on ICT=71.5, Math=56.0, Physics=68.3"
+EA00081,Computer_Science,62.62,Computer_Science_final,Pass,0.95,"Based on ICT=76.8, Math=53.1, Physics=52.8"
+EA00082,Computer_Science,59.37,Computer_Science_final,Pass,0.94,"Based on ICT=67.1, Math=53.4, Physics=64.9"
+EA00083,Computer_Science,48.77,Computer_Science_final,Fail,0.84,"Based on ICT=55.1, Math=56.6, Physics=44.4"
+EA00084,Computer_Science,65.13,Computer_Science_final,Pass,0.95,"Based on ICT=71.2, Math=74.3, Physics=59.3"
+EA00085,Computer_Science,63.0,Computer_Science_final,Pass,0.95,"Based on ICT=70.5, Math=57.0, Physics=66.9"
+EA00086,Computer_Science,54.16,Computer_Science_final,Pass,0.89,"Based on ICT=50.1, Math=58.0, Physics=60.2"
+EA00087,Computer_Science,61.65,Computer_Science_final,Pass,0.95,"Based on ICT=71.2, Math=59.6, Physics=49.8"
+EA00088,Computer_Science,67.26,Computer_Science_final,Pass,0.95,"Based on ICT=72.8, Math=64.2, Physics=66.8"
+EA00089,Computer_Science,54.78,Computer_Science_final,Pass,0.9,"Based on ICT=58.9, Math=58.7, Physics=51.7"
+EA00090,Computer_Science,48.23,Computer_Science_final,Fail,0.83,"Based on ICT=45.4, Math=47.5, Physics=57.6"
+EA00091,Computer_Science,60.17,Computer_Science_final,Pass,0.95,"Based on ICT=61.4, Math=56.4, Physics=64.6"
+EA00092,Computer_Science,52.53,Computer_Science_final,Pass,0.88,"Based on ICT=51.3, Math=59.2, Physics=51.9"
+EA00093,Computer_Science,53.7,Computer_Science_final,Pass,0.89,"Based on ICT=56.2, Math=57.2, Physics=54.9"
+EA00094,Computer_Science,55.27,Computer_Science_final,Pass,0.9,"Based on ICT=60.0, Math=53.4, Physics=59.0"
+EA00095,Computer_Science,61.63,Computer_Science_final,Pass,0.95,"Based on ICT=64.3, Math=54.7, Physics=66.2"
+EA00096,Computer_Science,59.09,Computer_Science_final,Pass,0.94,"Based on ICT=61.2, Math=58.2, Physics=55.3"
+EA00097,Computer_Science,63.24,Computer_Science_final,Pass,0.95,"Based on ICT=65.4, Math=59.4, Physics=64.9"
+EA00098,Computer_Science,51.73,Computer_Science_final,Pass,0.87,"Based on ICT=50.0, Math=51.9, Physics=62.3"
+EA00099,Computer_Science,55.94,Computer_Science_final,Pass,0.91,"Based on ICT=60.7, Math=47.6, Physics=60.2"
+EA00100,Computer_Science,63.5,Computer_Science_final,Pass,0.95,"Based on ICT=73.7, Math=56.3, Physics=70.1"
+EA00101,Computer_Science,64.7,Computer_Science_final,Pass,0.95,"Based on ICT=63.4, Math=68.4, Physics=64.2"
+EA00102,Computer_Science,51.38,Computer_Science_final,Pass,0.86,"Based on ICT=55.7, Math=51.0, Physics=49.7"
+EA00103,Computer_Science,62.12,Computer_Science_final,Pass,0.95,"Based on ICT=73.1, Math=47.7, Physics=74.0"
+EA00104,Computer_Science,51.21,Computer_Science_final,Pass,0.86,"Based on ICT=57.6, Math=47.4, Physics=61.7"
+EA00105,Computer_Science,63.29,Computer_Science_final,Pass,0.95,"Based on ICT=75.7, Math=50.6, Physics=61.7"
+EA00106,Computer_Science,66.98,Computer_Science_final,Pass,0.95,"Based on ICT=65.4, Math=70.0, Physics=56.1"
+EA00107,Computer_Science,67.9,Computer_Science_final,Pass,0.95,"Based on ICT=80.2, Math=57.0, Physics=64.2"
+EA00108,Computer_Science,52.55,Computer_Science_final,Pass,0.88,"Based on ICT=47.9, Math=55.1, Physics=59.4"
+EA00109,Computer_Science,46.15,Computer_Science_final,Fail,0.81,"Based on ICT=48.7, Math=56.7, Physics=48.0"
+EA00110,Computer_Science,58.24,Computer_Science_final,Pass,0.93,"Based on ICT=62.5, Math=53.4, Physics=56.8"
+EA00111,Computer_Science,63.63,Computer_Science_final,Pass,0.95,"Based on ICT=72.0, Math=57.4, Physics=53.8"
+EA00112,Computer_Science,52.1,Computer_Science_final,Pass,0.87,"Based on ICT=60.5, Math=52.8, Physics=38.1"
+EA00113,Computer_Science,60.78,Computer_Science_final,Pass,0.95,"Based on ICT=70.5, Math=58.5, Physics=55.1"
+EA00114,Computer_Science,56.17,Computer_Science_final,Pass,0.91,"Based on ICT=61.4, Math=53.8, Physics=60.3"
+EA00115,Computer_Science,64.37,Computer_Science_final,Pass,0.95,"Based on ICT=65.8, Math=55.6, Physics=61.8"
+EA00116,Computer_Science,49.84,Computer_Science_final,Fail,0.85,"Based on ICT=56.1, Math=47.1, Physics=50.7"
+EA00117,Computer_Science,47.37,Computer_Science_final,Fail,0.82,"Based on ICT=56.2, Math=41.9, Physics=55.3"
+EA00118,Computer_Science,53.63,Computer_Science_final,Pass,0.89,"Based on ICT=60.2, Math=49.2, Physics=52.9"
+EA00119,Computer_Science,52.88,Computer_Science_final,Pass,0.88,"Based on ICT=51.8, Math=59.1, Physics=62.0"
+EA00120,Computer_Science,59.68,Computer_Science_final,Pass,0.95,"Based on ICT=60.3, Math=56.4, Physics=72.5"
+EA00121,Computer_Science,60.41,Computer_Science_final,Pass,0.95,"Based on ICT=61.8, Math=60.0, Physics=65.5"
+EA00122,Computer_Science,56.07,Computer_Science_final,Pass,0.91,"Based on ICT=61.9, Math=55.2, Physics=56.8"
+EA00123,Computer_Science,71.48,Computer_Science_final,Pass,0.94,"Based on ICT=79.8, Math=67.0, Physics=67.2"
+EA00124,Computer_Science,46.53,Computer_Science_final,Fail,0.82,"Based on ICT=53.7, Math=40.4, Physics=49.2"
+EA00125,Computer_Science,62.06,Computer_Science_final,Pass,0.95,"Based on ICT=66.4, Math=58.9, Physics=63.7"
+EA00126,Computer_Science,65.24,Computer_Science_final,Pass,0.95,"Based on ICT=68.1, Math=63.9, Physics=63.9"
+EA00127,Computer_Science,60.15,Computer_Science_final,Pass,0.95,"Based on ICT=65.8, Math=51.0, Physics=52.3"
+EA00128,Computer_Science,55.85,Computer_Science_final,Pass,0.91,"Based on ICT=55.1, Math=55.2, Physics=57.3"
+EA00129,Computer_Science,67.63,Computer_Science_final,Pass,0.95,"Based on ICT=66.1, Math=69.1, Physics=66.5"
+EA00130,Computer_Science,63.47,Computer_Science_final,Pass,0.95,"Based on ICT=74.5, Math=67.2, Physics=58.4"
+EA00131,Computer_Science,64.68,Computer_Science_final,Pass,0.95,"Based on ICT=74.6, Math=55.6, Physics=64.4"
+EA00132,Computer_Science,61.99,Computer_Science_final,Pass,0.95,"Based on ICT=62.5, Math=64.7, Physics=62.7"
+EA00133,Computer_Science,59.53,Computer_Science_final,Pass,0.95,"Based on ICT=69.6, Math=54.2, Physics=48.1"
+EA00134,Computer_Science,59.46,Computer_Science_final,Pass,0.94,"Based on ICT=63.0, Math=65.8, Physics=54.5"
+EA00135,Computer_Science,57.27,Computer_Science_final,Pass,0.92,"Based on ICT=59.0, Math=57.9, Physics=57.6"
+EA00136,Computer_Science,53.36,Computer_Science_final,Pass,0.88,"Based on ICT=50.6, Math=54.3, Physics=56.5"
+EA00137,Computer_Science,61.12,Computer_Science_final,Pass,0.95,"Based on ICT=66.6, Math=51.1, Physics=54.8"
+EA00138,Computer_Science,69.66,Computer_Science_final,Pass,0.95,"Based on ICT=79.4, Math=64.0, Physics=62.8"
+EA00139,Computer_Science,56.59,Computer_Science_final,Pass,0.92,"Based on ICT=76.1, Math=43.7, Physics=46.6"
+EA00140,Computer_Science,62.74,Computer_Science_final,Pass,0.95,"Based on ICT=73.1, Math=64.6, Physics=55.1"
+EA00141,Computer_Science,51.65,Computer_Science_final,Pass,0.87,"Based on ICT=65.7, Math=46.7, Physics=40.3"
+EA00142,Computer_Science,53.94,Computer_Science_final,Pass,0.89,"Based on ICT=63.1, Math=52.3, Physics=41.5"
+EA00143,Computer_Science,56.22,Computer_Science_final,Pass,0.91,"Based on ICT=57.2, Math=61.1, Physics=59.4"
+EA00144,Computer_Science,61.78,Computer_Science_final,Pass,0.95,"Based on ICT=63.0, Math=57.3, Physics=51.7"
+EA00145,Computer_Science,56.63,Computer_Science_final,Pass,0.92,"Based on ICT=62.8, Math=57.1, Physics=54.0"
+EA00146,Computer_Science,56.66,Computer_Science_final,Pass,0.92,"Based on ICT=59.5, Math=51.3, Physics=64.0"
+EA00147,Computer_Science,65.18,Computer_Science_final,Pass,0.95,"Based on ICT=72.9, Math=64.6, Physics=69.6"
+EA00148,Computer_Science,60.09,Computer_Science_final,Pass,0.95,"Based on ICT=57.2, Math=62.4, Physics=80.5"
+EA00149,Computer_Science,49.64,Computer_Science_final,Fail,0.85,"Based on ICT=57.2, Math=42.6, Physics=51.0"
+EA00150,Computer_Science,60.49,Computer_Science_final,Pass,0.95,"Based on ICT=77.1, Math=53.7, Physics=45.3"
+EA00151,Computer_Science,66.13,Computer_Science_final,Pass,0.95,"Based on ICT=65.5, Math=65.2, Physics=59.8"
+EA00152,Computer_Science,61.31,Computer_Science_final,Pass,0.95,"Based on ICT=59.7, Math=61.9, Physics=66.5"
+EA00153,Computer_Science,51.47,Computer_Science_final,Pass,0.86,"Based on ICT=58.9, Math=48.7, Physics=63.0"
+EA00154,Computer_Science,60.58,Computer_Science_final,Pass,0.95,"Based on ICT=59.8, Math=56.7, Physics=65.7"
+EA00155,Computer_Science,65.64,Computer_Science_final,Pass,0.95,"Based on ICT=75.0, Math=57.5, Physics=57.2"
+EA00156,Computer_Science,52.34,Computer_Science_final,Pass,0.87,"Based on ICT=50.4, Math=57.5, Physics=59.0"
+EA00157,Computer_Science,65.41,Computer_Science_final,Pass,0.95,"Based on ICT=74.1, Math=64.2, Physics=55.5"
+EA00158,Computer_Science,58.12,Computer_Science_final,Pass,0.93,"Based on ICT=56.5, Math=53.3, Physics=66.2"
+EA00159,Computer_Science,55.3,Computer_Science_final,Pass,0.9,"Based on ICT=57.2, Math=60.8, Physics=50.5"
+EA00160,Computer_Science,65.79,Computer_Science_final,Pass,0.95,"Based on ICT=72.5, Math=59.4, Physics=67.9"
+EA00161,Computer_Science,55.9,Computer_Science_final,Pass,0.91,"Based on ICT=56.4, Math=52.1, Physics=54.1"
+EA00162,Computer_Science,57.74,Computer_Science_final,Pass,0.93,"Based on ICT=62.6, Math=58.2, Physics=46.7"
+EA00163,Computer_Science,68.42,Computer_Science_final,Pass,0.95,"Based on ICT=74.2, Math=66.2, Physics=63.9"
+EA00164,Computer_Science,65.53,Computer_Science_final,Pass,0.95,"Based on ICT=65.2, Math=55.3, Physics=69.3"
+EA00165,Computer_Science,64.43,Computer_Science_final,Pass,0.95,"Based on ICT=69.0, Math=59.7, Physics=70.1"
+EA00166,Computer_Science,56.04,Computer_Science_final,Pass,0.91,"Based on ICT=63.2, Math=47.9, Physics=59.4"
+EA00167,Computer_Science,63.39,Computer_Science_final,Pass,0.95,"Based on ICT=65.0, Math=56.4, Physics=75.4"
+EA00168,Computer_Science,61.97,Computer_Science_final,Pass,0.95,"Based on ICT=68.1, Math=58.8, Physics=47.1"
+EA00169,Computer_Science,57.04,Computer_Science_final,Pass,0.92,"Based on ICT=67.2, Math=46.4, Physics=53.4"
+EA00170,Computer_Science,65.42,Computer_Science_final,Pass,0.95,"Based on ICT=70.7, Math=60.6, Physics=70.1"
+EA00171,Computer_Science,54.71,Computer_Science_final,Pass,0.9,"Based on ICT=59.6, Math=45.9, Physics=53.9"
+EA00172,Computer_Science,67.14,Computer_Science_final,Pass,0.95,"Based on ICT=74.0, Math=60.7, Physics=70.3"
+EA00173,Computer_Science,66.46,Computer_Science_final,Pass,0.95,"Based on ICT=86.6, Math=60.5, Physics=54.0"
+EA00174,Computer_Science,65.09,Computer_Science_final,Pass,0.95,"Based on ICT=71.5, Math=56.3, Physics=71.8"
+EA00175,Computer_Science,47.15,Computer_Science_final,Fail,0.82,"Based on ICT=49.4, Math=50.5, Physics=49.5"
+EA00176,Computer_Science,62.79,Computer_Science_final,Pass,0.95,"Based on ICT=71.3, Math=61.1, Physics=54.9"
+EA00177,Computer_Science,56.37,Computer_Science_final,Pass,0.91,"Based on ICT=55.3, Math=57.7, Physics=57.4"
+EA00178,Computer_Science,58.39,Computer_Science_final,Pass,0.93,"Based on ICT=60.3, Math=57.8, Physics=57.3"
+EA00179,Computer_Science,67.15,Computer_Science_final,Pass,0.95,"Based on ICT=71.7, Math=68.4, Physics=62.2"
+EA00180,Computer_Science,67.11,Computer_Science_final,Pass,0.95,"Based on ICT=76.6, Math=65.9, Physics=48.5"
+EA00181,Computer_Science,63.55,Computer_Science_final,Pass,0.95,"Based on ICT=70.7, Math=55.7, Physics=66.4"
+EA00182,Computer_Science,52.31,Computer_Science_final,Pass,0.87,"Based on ICT=59.2, Math=49.8, Physics=52.3"
+EA00183,Computer_Science,47.62,Computer_Science_final,Fail,0.83,"Based on ICT=49.8, Math=45.1, Physics=56.5"
+EA00184,Computer_Science,58.5,Computer_Science_final,Pass,0.94,"Based on ICT=59.1, Math=56.5, Physics=76.0"
+EA00185,Computer_Science,53.48,Computer_Science_final,Pass,0.88,"Based on ICT=66.5, Math=50.9, Physics=38.8"
+EA00186,Computer_Science,59.82,Computer_Science_final,Pass,0.95,"Based on ICT=60.3, Math=53.6, Physics=65.7"
+EA00187,Computer_Science,47.9,Computer_Science_final,Fail,0.83,"Based on ICT=51.3, Math=47.5, Physics=45.3"
+EA00188,Computer_Science,60.65,Computer_Science_final,Pass,0.95,"Based on ICT=69.6, Math=56.5, Physics=65.6"
+EA00189,Computer_Science,58.28,Computer_Science_final,Pass,0.93,"Based on ICT=68.9, Math=53.0, Physics=48.6"
+EA00190,Computer_Science,54.11,Computer_Science_final,Pass,0.89,"Based on ICT=56.7, Math=49.2, Physics=56.0"
+EA00191,Computer_Science,70.78,Computer_Science_final,Pass,0.94,"Based on ICT=76.0, Math=67.5, Physics=70.1"
+EA00192,Computer_Science,57.47,Computer_Science_final,Pass,0.92,"Based on ICT=64.0, Math=47.6, Physics=57.2"
+EA00193,Computer_Science,57.04,Computer_Science_final,Pass,0.92,"Based on ICT=55.3, Math=59.6, Physics=62.2"
+EA00194,Computer_Science,61.04,Computer_Science_final,Pass,0.95,"Based on ICT=67.2, Math=54.3, Physics=57.0"
+EA00195,Computer_Science,59.24,Computer_Science_final,Pass,0.94,"Based on ICT=69.0, Math=57.2, Physics=55.3"
+EA00196,Computer_Science,63.65,Computer_Science_final,Pass,0.95,"Based on ICT=67.3, Math=58.3, Physics=65.6"
+EA00197,Computer_Science,47.21,Computer_Science_final,Fail,0.82,"Based on ICT=50.7, Math=50.8, Physics=51.1"
+EA00198,Computer_Science,67.07,Computer_Science_final,Pass,0.95,"Based on ICT=67.7, Math=66.4, Physics=65.1"
+EA00199,Computer_Science,56.35,Computer_Science_final,Pass,0.91,"Based on ICT=61.7, Math=47.8, Physics=52.5"
+EA00200,Computer_Science,52.97,Computer_Science_final,Pass,0.88,"Based on ICT=54.5, Math=49.8, Physics=55.9"
+EA00201,Computer_Science,52.61,Computer_Science_final,Pass,0.88,"Based on ICT=58.6, Math=41.4, Physics=47.2"
+EA00202,Computer_Science,56.03,Computer_Science_final,Pass,0.91,"Based on ICT=60.2, Math=50.7, Physics=61.3"
+EA00203,Computer_Science,56.71,Computer_Science_final,Pass,0.92,"Based on ICT=66.7, Math=52.1, Physics=50.9"
+EA00204,Computer_Science,57.6,Computer_Science_final,Pass,0.93,"Based on ICT=60.1, Math=59.0, Physics=56.7"
+EA00205,Computer_Science,58.79,Computer_Science_final,Pass,0.94,"Based on ICT=62.7, Math=63.7, Physics=55.6"
+EA00206,Computer_Science,53.44,Computer_Science_final,Pass,0.88,"Based on ICT=62.2, Math=50.9, Physics=52.7"
+EA00207,Computer_Science,55.03,Computer_Science_final,Pass,0.9,"Based on ICT=62.6, Math=56.6, Physics=50.1"
+EA00208,Computer_Science,61.73,Computer_Science_final,Pass,0.95,"Based on ICT=73.2, Math=46.6, Physics=65.4"
+EA00209,Computer_Science,56.98,Computer_Science_final,Pass,0.92,"Based on ICT=65.4, Math=60.7, Physics=62.3"
+EA00210,Computer_Science,60.4,Computer_Science_final,Pass,0.95,"Based on ICT=58.3, Math=52.1, Physics=65.4"
+EA00211,Computer_Science,51.96,Computer_Science_final,Pass,0.87,"Based on ICT=54.4, Math=48.6, Physics=55.1"
+EA00212,Computer_Science,65.99,Computer_Science_final,Pass,0.95,"Based on ICT=68.0, Math=63.9, Physics=57.3"
+EA00213,Computer_Science,51.33,Computer_Science_final,Pass,0.86,"Based on ICT=61.7, Math=49.7, Physics=53.4"
+EA00214,Computer_Science,61.03,Computer_Science_final,Pass,0.95,"Based on ICT=61.1, Math=64.6, Physics=61.2"
+EA00215,Computer_Science,65.38,Computer_Science_final,Pass,0.95,"Based on ICT=63.6, Math=58.0, Physics=69.8"
+EA00216,Computer_Science,50.09,Computer_Science_final,Pass,0.85,"Based on ICT=58.3, Math=50.2, Physics=47.7"
+EA00217,Computer_Science,52.12,Computer_Science_final,Pass,0.87,"Based on ICT=56.7, Math=53.8, Physics=59.1"
+EA00218,Computer_Science,61.23,Computer_Science_final,Pass,0.95,"Based on ICT=72.3, Math=56.6, Physics=65.0"
+EA00219,Computer_Science,65.43,Computer_Science_final,Pass,0.95,"Based on ICT=68.0, Math=61.1, Physics=68.7"
+EA00220,Computer_Science,54.46,Computer_Science_final,Pass,0.89,"Based on ICT=50.3, Math=56.9, Physics=58.6"
+EA00221,Computer_Science,55.09,Computer_Science_final,Pass,0.9,"Based on ICT=56.5, Math=51.0, Physics=49.4"
+EA00222,Computer_Science,55.38,Computer_Science_final,Pass,0.9,"Based on ICT=64.5, Math=51.5, Physics=54.1"
+EA00223,Computer_Science,54.5,Computer_Science_final,Pass,0.9,"Based on ICT=61.3, Math=56.5, Physics=60.2"
+EA00224,Computer_Science,47.26,Computer_Science_final,Fail,0.82,"Based on ICT=54.6, Math=46.0, Physics=51.2"
+EA00225,Computer_Science,56.15,Computer_Science_final,Pass,0.91,"Based on ICT=63.2, Math=48.7, Physics=51.5"
+EA00226,Computer_Science,61.06,Computer_Science_final,Pass,0.95,"Based on ICT=66.9, Math=52.9, Physics=64.7"
+EA00227,Computer_Science,56.63,Computer_Science_final,Pass,0.92,"Based on ICT=72.4, Math=51.1, Physics=50.0"
+EA00228,Computer_Science,62.27,Computer_Science_final,Pass,0.95,"Based on ICT=69.3, Math=59.2, Physics=61.1"
+EA00229,Computer_Science,61.21,Computer_Science_final,Pass,0.95,"Based on ICT=60.3, Math=60.4, Physics=56.2"
+EA00230,Computer_Science,53.33,Computer_Science_final,Pass,0.88,"Based on ICT=62.0, Math=41.1, Physics=55.8"
+EA00231,Computer_Science,57.55,Computer_Science_final,Pass,0.93,"Based on ICT=56.3, Math=63.4, Physics=49.4"
+EA00232,Computer_Science,62.12,Computer_Science_final,Pass,0.95,"Based on ICT=63.5, Math=62.8, Physics=64.5"
+EA00233,Computer_Science,59.8,Computer_Science_final,Pass,0.95,"Based on ICT=55.2, Math=71.8, Physics=62.2"
+EA00234,Computer_Science,59.0,Computer_Science_final,Pass,0.94,"Based on ICT=69.4, Math=60.1, Physics=44.5"
+EA00235,Computer_Science,53.27,Computer_Science_final,Pass,0.88,"Based on ICT=55.9, Math=61.2, Physics=48.1"
+EA00236,Computer_Science,67.83,Computer_Science_final,Pass,0.95,"Based on ICT=78.3, Math=63.0, Physics=61.7"
+EA00237,Computer_Science,52.19,Computer_Science_final,Pass,0.87,"Based on ICT=45.4, Math=65.4, Physics=55.0"
+EA00238,Computer_Science,56.36,Computer_Science_final,Pass,0.91,"Based on ICT=61.4, Math=55.1, Physics=52.3"
+EA00239,Computer_Science,55.01,Computer_Science_final,Pass,0.9,"Based on ICT=55.9, Math=53.3, Physics=64.4"
+EA00240,Computer_Science,49.97,Computer_Science_final,Fail,0.85,"Based on ICT=55.9, Math=45.0, Physics=51.9"
+EA00241,Computer_Science,62.11,Computer_Science_final,Pass,0.95,"Based on ICT=74.1, Math=56.1, Physics=52.6"
+EA00242,Computer_Science,58.74,Computer_Science_final,Pass,0.94,"Based on ICT=52.4, Math=60.3, Physics=66.2"
+EA00243,Computer_Science,52.65,Computer_Science_final,Pass,0.88,"Based on ICT=62.4, Math=54.7, Physics=54.2"
+EA00244,Computer_Science,56.54,Computer_Science_final,Pass,0.92,"Based on ICT=62.5, Math=54.7, Physics=57.6"
+EA00245,Computer_Science,59.64,Computer_Science_final,Pass,0.95,"Based on ICT=61.2, Math=61.7, Physics=63.4"
+EA00246,Computer_Science,67.52,Computer_Science_final,Pass,0.95,"Based on ICT=73.1, Math=62.0, Physics=54.7"
+EA00247,Computer_Science,60.77,Computer_Science_final,Pass,0.95,"Based on ICT=64.9, Math=61.0, Physics=61.8"
+EA00248,Computer_Science,55.86,Computer_Science_final,Pass,0.91,"Based on ICT=57.6, Math=50.1, Physics=60.5"
+EA00249,Computer_Science,59.96,Computer_Science_final,Pass,0.95,"Based on ICT=61.9, Math=65.5, Physics=46.4"
+EA00250,Computer_Science,59.56,Computer_Science_final,Pass,0.95,"Based on ICT=73.0, Math=59.8, Physics=48.0"
+EA00251,Computer_Science,59.42,Computer_Science_final,Pass,0.94,"Based on ICT=70.0, Math=57.8, Physics=51.0"
+EA00252,Computer_Science,58.83,Computer_Science_final,Pass,0.94,"Based on ICT=67.0, Math=46.9, Physics=50.6"
+EA00253,Computer_Science,52.51,Computer_Science_final,Pass,0.88,"Based on ICT=49.1, Math=62.6, Physics=49.3"
+EA00254,Computer_Science,58.43,Computer_Science_final,Pass,0.93,"Based on ICT=67.4, Math=53.0, Physics=54.6"
+EA00255,Computer_Science,68.38,Computer_Science_final,Pass,0.95,"Based on ICT=65.7, Math=62.0, Physics=64.1"
+EA00256,Computer_Science,53.82,Computer_Science_final,Pass,0.89,"Based on ICT=53.9, Math=58.9, Physics=55.5"
+EA00257,Computer_Science,67.33,Computer_Science_final,Pass,0.95,"Based on ICT=70.8, Math=66.7, Physics=60.2"
+EA00258,Computer_Science,51.77,Computer_Science_final,Pass,0.87,"Based on ICT=56.2, Math=46.0, Physics=63.1"
+EA00259,Computer_Science,67.46,Computer_Science_final,Pass,0.95,"Based on ICT=69.8, Math=65.2, Physics=70.8"
+EA00260,Computer_Science,56.7,Computer_Science_final,Pass,0.92,"Based on ICT=59.5, Math=52.6, Physics=53.7"
+EA00261,Computer_Science,50.25,Computer_Science_final,Pass,0.85,"Based on ICT=57.5, Math=35.9, Physics=50.5"
+EA00262,Computer_Science,51.0,Computer_Science_final,Pass,0.86,"Based on ICT=59.1, Math=50.7, Physics=60.3"
+EA00263,Computer_Science,56.63,Computer_Science_final,Pass,0.92,"Based on ICT=58.7, Math=59.0, Physics=51.3"
+EA00264,Computer_Science,53.65,Computer_Science_final,Pass,0.89,"Based on ICT=56.0, Math=52.7, Physics=51.3"
+EA00265,Computer_Science,50.34,Computer_Science_final,Pass,0.85,"Based on ICT=52.1, Math=49.9, Physics=54.8"
+EA00266,Computer_Science,44.64,Computer_Science_final,Fail,0.8,"Based on ICT=52.8, Math=54.4, Physics=34.3"
+EA00267,Computer_Science,53.91,Computer_Science_final,Pass,0.89,"Based on ICT=62.0, Math=55.8, Physics=57.3"
+EA00268,Computer_Science,54.49,Computer_Science_final,Pass,0.89,"Based on ICT=63.0, Math=57.7, Physics=60.5"
+EA00269,Computer_Science,45.27,Computer_Science_final,Fail,0.8,"Based on ICT=53.7, Math=39.7, Physics=47.1"
+EA00270,Computer_Science,58.86,Computer_Science_final,Pass,0.94,"Based on ICT=64.8, Math=53.9, Physics=64.7"
+EA00271,Computer_Science,55.5,Computer_Science_final,Pass,0.91,"Based on ICT=59.0, Math=56.2, Physics=57.3"
+EA00272,Computer_Science,56.55,Computer_Science_final,Pass,0.92,"Based on ICT=59.0, Math=55.7, Physics=55.8"
+EA00273,Computer_Science,56.25,Computer_Science_final,Pass,0.91,"Based on ICT=65.2, Math=59.5, Physics=52.1"
+EA00274,Computer_Science,54.78,Computer_Science_final,Pass,0.9,"Based on ICT=60.1, Math=59.4, Physics=62.4"
+EA00275,Computer_Science,56.98,Computer_Science_final,Pass,0.92,"Based on ICT=58.1, Math=59.4, Physics=53.0"
+EA00276,Computer_Science,54.12,Computer_Science_final,Pass,0.89,"Based on ICT=52.8, Math=57.1, Physics=56.6"
+EA00277,Computer_Science,59.76,Computer_Science_final,Pass,0.95,"Based on ICT=65.6, Math=58.7, Physics=49.8"
+EA00278,Computer_Science,55.32,Computer_Science_final,Pass,0.9,"Based on ICT=53.8, Math=51.9, Physics=58.8"
+EA00279,Computer_Science,58.72,Computer_Science_final,Pass,0.94,"Based on ICT=64.0, Math=62.3, Physics=58.5"
+EA00280,Computer_Science,57.35,Computer_Science_final,Pass,0.92,"Based on ICT=57.4, Math=59.9, Physics=53.9"
+EA00281,Computer_Science,53.89,Computer_Science_final,Pass,0.89,"Based on ICT=62.1, Math=49.6, Physics=44.7"
+EA00282,Computer_Science,52.67,Computer_Science_final,Pass,0.88,"Based on ICT=55.3, Math=53.4, Physics=55.7"
+EA00283,Computer_Science,64.25,Computer_Science_final,Pass,0.95,"Based on ICT=70.9, Math=58.3, Physics=78.6"
+EA00284,Computer_Science,56.93,Computer_Science_final,Pass,0.92,"Based on ICT=54.2, Math=63.0, Physics=60.6"
+EA00285,Computer_Science,54.59,Computer_Science_final,Pass,0.9,"Based on ICT=66.3, Math=47.0, Physics=62.0"
+EA00286,Computer_Science,59.69,Computer_Science_final,Pass,0.95,"Based on ICT=61.7, Math=60.1, Physics=56.3"
+EA00287,Computer_Science,62.63,Computer_Science_final,Pass,0.95,"Based on ICT=67.1, Math=54.7, Physics=70.6"
+EA00288,Computer_Science,55.04,Computer_Science_final,Pass,0.9,"Based on ICT=70.3, Math=44.5, Physics=45.6"
+EA00289,Computer_Science,61.86,Computer_Science_final,Pass,0.95,"Based on ICT=71.2, Math=59.9, Physics=62.4"
+EA00290,Computer_Science,58.31,Computer_Science_final,Pass,0.93,"Based on ICT=61.1, Math=53.2, Physics=61.2"
+EA00291,Computer_Science,68.58,Computer_Science_final,Pass,0.95,"Based on ICT=63.0, Math=69.4, Physics=67.3"
+EA00292,Computer_Science,69.32,Computer_Science_final,Pass,0.95,"Based on ICT=78.2, Math=73.6, Physics=55.8"
+EA00293,Computer_Science,62.4,Computer_Science_final,Pass,0.95,"Based on ICT=66.6, Math=61.4, Physics=73.5"
+EA00294,Computer_Science,60.41,Computer_Science_final,Pass,0.95,"Based on ICT=59.3, Math=62.3, Physics=70.8"
+EA00295,Computer_Science,62.29,Computer_Science_final,Pass,0.95,"Based on ICT=63.5, Math=63.2, Physics=63.5"
+EA00296,Computer_Science,62.82,Computer_Science_final,Pass,0.95,"Based on ICT=58.2, Math=72.7, Physics=68.4"
+EA00297,Computer_Science,58.23,Computer_Science_final,Pass,0.93,"Based on ICT=62.4, Math=56.5, Physics=57.3"
+EA00298,Computer_Science,55.59,Computer_Science_final,Pass,0.91,"Based on ICT=50.4, Math=59.7, Physics=63.1"
+EA00299,Computer_Science,68.82,Computer_Science_final,Pass,0.95,"Based on ICT=74.3, Math=64.3, Physics=52.8"
+EA00300,Computer_Science,49.73,Computer_Science_final,Fail,0.85,"Based on ICT=51.2, Math=49.4, Physics=56.4"
+EA00301,Computer_Science,57.15,Computer_Science_final,Pass,0.92,"Based on ICT=62.6, Math=53.6, Physics=53.6"
+EA00302,Computer_Science,53.6,Computer_Science_final,Pass,0.89,"Based on ICT=58.7, Math=50.4, Physics=58.3"
+EA00303,Computer_Science,52.67,Computer_Science_final,Pass,0.88,"Based on ICT=49.7, Math=52.1, Physics=57.6"
+EA00304,Computer_Science,49.71,Computer_Science_final,Fail,0.85,"Based on ICT=58.9, Math=42.9, Physics=54.0"
+EA00305,Computer_Science,55.41,Computer_Science_final,Pass,0.9,"Based on ICT=49.7, Math=63.3, Physics=63.1"
+EA00306,Computer_Science,59.54,Computer_Science_final,Pass,0.95,"Based on ICT=69.8, Math=54.8, Physics=54.1"
+EA00307,Computer_Science,66.45,Computer_Science_final,Pass,0.95,"Based on ICT=78.1, Math=63.2, Physics=59.9"
+EA00308,Computer_Science,62.01,Computer_Science_final,Pass,0.95,"Based on ICT=55.2, Math=67.1, Physics=60.2"
+EA00309,Computer_Science,59.32,Computer_Science_final,Pass,0.94,"Based on ICT=60.6, Math=64.8, Physics=53.1"
+EA00310,Computer_Science,59.99,Computer_Science_final,Pass,0.95,"Based on ICT=69.1, Math=63.5, Physics=64.1"
+EA00311,Computer_Science,52.87,Computer_Science_final,Pass,0.88,"Based on ICT=58.2, Math=44.1, Physics=53.6"
+EA00312,Computer_Science,61.39,Computer_Science_final,Pass,0.95,"Based on ICT=73.0, Math=59.3, Physics=58.5"
+EA00313,Computer_Science,49.38,Computer_Science_final,Fail,0.84,"Based on ICT=47.5, Math=49.5, Physics=56.4"
+EA00314,Computer_Science,54.46,Computer_Science_final,Pass,0.89,"Based on ICT=48.9, Math=65.1, Physics=54.6"
+EA00315,Computer_Science,65.72,Computer_Science_final,Pass,0.95,"Based on ICT=59.0, Math=77.7, Physics=68.2"
+EA00316,Computer_Science,53.14,Computer_Science_final,Pass,0.88,"Based on ICT=63.3, Math=49.7, Physics=64.1"
+EA00317,Computer_Science,63.06,Computer_Science_final,Pass,0.95,"Based on ICT=74.0, Math=58.0, Physics=57.5"
+EA00318,Computer_Science,60.22,Computer_Science_final,Pass,0.95,"Based on ICT=64.3, Math=57.8, Physics=56.0"
+EA00319,Computer_Science,60.53,Computer_Science_final,Pass,0.95,"Based on ICT=58.8, Math=71.2, Physics=49.5"
+EA00320,Computer_Science,67.61,Computer_Science_final,Pass,0.95,"Based on ICT=68.7, Math=70.3, Physics=59.7"
+EA00321,Computer_Science,54.8,Computer_Science_final,Pass,0.9,"Based on ICT=58.1, Math=56.9, Physics=46.7"
+EA00322,Computer_Science,52.05,Computer_Science_final,Pass,0.87,"Based on ICT=60.6, Math=50.9, Physics=53.0"
+EA00323,Computer_Science,60.66,Computer_Science_final,Pass,0.95,"Based on ICT=67.2, Math=50.9, Physics=62.5"
+EA00324,Computer_Science,50.34,Computer_Science_final,Pass,0.85,"Based on ICT=58.7, Math=55.6, Physics=51.7"
+EA00325,Computer_Science,53.68,Computer_Science_final,Pass,0.89,"Based on ICT=62.8, Math=39.3, Physics=61.2"
+EA00326,Computer_Science,70.81,Computer_Science_final,Pass,0.94,"Based on ICT=76.9, Math=68.9, Physics=66.8"
+EA00327,Computer_Science,55.46,Computer_Science_final,Pass,0.9,"Based on ICT=66.0, Math=53.9, Physics=54.3"
+EA00328,Computer_Science,57.48,Computer_Science_final,Pass,0.92,"Based on ICT=55.5, Math=57.4, Physics=50.5"
+EA00329,Computer_Science,48.72,Computer_Science_final,Fail,0.84,"Based on ICT=56.0, Math=48.0, Physics=40.0"
+EA00330,Computer_Science,52.75,Computer_Science_final,Pass,0.88,"Based on ICT=58.2, Math=62.4, Physics=50.3"
+EA00331,Computer_Science,69.36,Computer_Science_final,Pass,0.95,"Based on ICT=71.4, Math=70.4, Physics=67.5"
+EA00332,Computer_Science,60.57,Computer_Science_final,Pass,0.95,"Based on ICT=53.1, Math=75.7, Physics=60.9"
+EA00333,Computer_Science,64.66,Computer_Science_final,Pass,0.95,"Based on ICT=72.4, Math=60.7, Physics=53.1"
+EA00334,Computer_Science,55.58,Computer_Science_final,Pass,0.91,"Based on ICT=61.5, Math=51.9, Physics=55.2"
+EA00335,Computer_Science,56.95,Computer_Science_final,Pass,0.92,"Based on ICT=59.0, Math=50.9, Physics=57.1"
+EA00336,Computer_Science,60.53,Computer_Science_final,Pass,0.95,"Based on ICT=71.0, Math=59.5, Physics=60.7"
+EA00337,Computer_Science,51.52,Computer_Science_final,Pass,0.87,"Based on ICT=49.1, Math=56.6, Physics=48.7"
+EA00338,Computer_Science,64.16,Computer_Science_final,Pass,0.95,"Based on ICT=60.0, Math=61.7, Physics=64.7"
+EA00339,Computer_Science,43.39,Computer_Science_final,Fail,0.78,"Based on ICT=54.5, Math=39.8, Physics=42.9"
+EA00340,Computer_Science,53.56,Computer_Science_final,Pass,0.89,"Based on ICT=58.7, Math=56.7, Physics=49.9"
+EA00341,Computer_Science,64.65,Computer_Science_final,Pass,0.95,"Based on ICT=74.5, Math=63.4, Physics=55.9"
+EA00342,Computer_Science,65.86,Computer_Science_final,Pass,0.95,"Based on ICT=74.1, Math=60.6, Physics=74.2"
+EA00343,Computer_Science,53.73,Computer_Science_final,Pass,0.89,"Based on ICT=53.1, Math=52.1, Physics=62.9"
+EA00344,Computer_Science,58.49,Computer_Science_final,Pass,0.93,"Based on ICT=57.5, Math=68.3, Physics=45.8"
+EA00345,Computer_Science,58.12,Computer_Science_final,Pass,0.93,"Based on ICT=64.5, Math=64.3, Physics=54.3"
+EA00346,Computer_Science,51.56,Computer_Science_final,Pass,0.87,"Based on ICT=56.9, Math=47.8, Physics=49.4"
+EA00347,Computer_Science,56.96,Computer_Science_final,Pass,0.92,"Based on ICT=58.1, Math=60.2, Physics=59.3"
+EA00348,Computer_Science,62.01,Computer_Science_final,Pass,0.95,"Based on ICT=69.7, Math=54.1, Physics=70.3"
+EA00349,Computer_Science,48.23,Computer_Science_final,Fail,0.83,"Based on ICT=56.8, Math=50.3, Physics=45.1"
+EA00350,Computer_Science,51.05,Computer_Science_final,Pass,0.86,"Based on ICT=61.9, Math=40.3, Physics=55.6"
+EA00351,Computer_Science,63.42,Computer_Science_final,Pass,0.95,"Based on ICT=66.8, Math=58.8, Physics=57.4"
+EA00352,Computer_Science,58.11,Computer_Science_final,Pass,0.93,"Based on ICT=63.7, Math=57.3, Physics=54.8"
+EA00353,Computer_Science,56.4,Computer_Science_final,Pass,0.91,"Based on ICT=65.1, Math=50.2, Physics=52.5"
+EA00354,Computer_Science,53.36,Computer_Science_final,Pass,0.88,"Based on ICT=49.9, Math=63.7, Physics=58.7"
+EA00355,Computer_Science,65.46,Computer_Science_final,Pass,0.95,"Based on ICT=72.2, Math=60.7, Physics=54.0"
+EA00356,Computer_Science,63.9,Computer_Science_final,Pass,0.95,"Based on ICT=74.2, Math=60.6, Physics=49.6"
+EA00357,Computer_Science,63.38,Computer_Science_final,Pass,0.95,"Based on ICT=63.4, Math=68.9, Physics=52.2"
+EA00358,Computer_Science,52.2,Computer_Science_final,Pass,0.87,"Based on ICT=50.5, Math=53.1, Physics=58.8"
+EA00359,Computer_Science,52.95,Computer_Science_final,Pass,0.88,"Based on ICT=58.5, Math=55.6, Physics=44.6"
+EA00360,Computer_Science,60.33,Computer_Science_final,Pass,0.95,"Based on ICT=66.5, Math=56.5, Physics=60.6"
+EA00361,Computer_Science,57.52,Computer_Science_final,Pass,0.93,"Based on ICT=64.1, Math=59.8, Physics=51.6"
+EA00362,Computer_Science,60.2,Computer_Science_final,Pass,0.95,"Based on ICT=59.3, Math=56.5, Physics=73.4"
+EA00363,Computer_Science,69.58,Computer_Science_final,Pass,0.95,"Based on ICT=75.0, Math=71.0, Physics=63.2"
+EA00364,Computer_Science,52.91,Computer_Science_final,Pass,0.88,"Based on ICT=59.7, Math=54.5, Physics=55.4"
+EA00365,Computer_Science,57.8,Computer_Science_final,Pass,0.93,"Based on ICT=60.6, Math=67.6, Physics=53.0"
+EA00366,Computer_Science,62.54,Computer_Science_final,Pass,0.95,"Based on ICT=61.6, Math=65.1, Physics=60.2"
+EA00367,Computer_Science,64.02,Computer_Science_final,Pass,0.95,"Based on ICT=70.8, Math=59.4, Physics=67.7"
+EA00368,Computer_Science,65.38,Computer_Science_final,Pass,0.95,"Based on ICT=62.6, Math=67.4, Physics=68.3"
+EA00369,Computer_Science,64.05,Computer_Science_final,Pass,0.95,"Based on ICT=68.0, Math=55.8, Physics=55.5"
+EA00370,Computer_Science,61.86,Computer_Science_final,Pass,0.95,"Based on ICT=67.7, Math=58.9, Physics=70.2"
+EA00371,Computer_Science,49.05,Computer_Science_final,Fail,0.84,"Based on ICT=56.9, Math=45.4, Physics=60.4"
+EA00372,Computer_Science,53.75,Computer_Science_final,Pass,0.89,"Based on ICT=52.8, Math=56.5, Physics=59.2"
+EA00373,Computer_Science,54.53,Computer_Science_final,Pass,0.9,"Based on ICT=48.3, Math=62.0, Physics=67.3"
+EA00374,Computer_Science,52.97,Computer_Science_final,Pass,0.88,"Based on ICT=63.8, Math=55.3, Physics=48.2"
+EA00375,Computer_Science,52.6,Computer_Science_final,Pass,0.88,"Based on ICT=59.8, Math=54.1, Physics=48.4"
+EA00376,Computer_Science,54.5,Computer_Science_final,Pass,0.9,"Based on ICT=62.2, Math=48.0, Physics=58.9"
+EA00377,Computer_Science,47.5,Computer_Science_final,Fail,0.82,"Based on ICT=48.0, Math=47.8, Physics=55.3"
+EA00378,Computer_Science,59.78,Computer_Science_final,Pass,0.95,"Based on ICT=57.3, Math=70.2, Physics=54.4"
+EA00379,Computer_Science,63.23,Computer_Science_final,Pass,0.95,"Based on ICT=74.3, Math=50.1, Physics=73.0"
+EA00380,Computer_Science,65.12,Computer_Science_final,Pass,0.95,"Based on ICT=69.5, Math=52.9, Physics=70.5"
+EA00381,Computer_Science,70.06,Computer_Science_final,Pass,0.95,"Based on ICT=73.1, Math=74.3, Physics=60.0"
+EA00382,Computer_Science,54.5,Computer_Science_final,Pass,0.9,"Based on ICT=57.0, Math=57.3, Physics=65.2"
+EA00383,Computer_Science,60.83,Computer_Science_final,Pass,0.95,"Based on ICT=72.3, Math=51.4, Physics=59.3"
+EA00384,Computer_Science,65.36,Computer_Science_final,Pass,0.95,"Based on ICT=61.3, Math=69.7, Physics=60.5"
+EA00385,Computer_Science,52.14,Computer_Science_final,Pass,0.87,"Based on ICT=59.5, Math=49.9, Physics=54.4"
+EA00386,Computer_Science,54.07,Computer_Science_final,Pass,0.89,"Based on ICT=55.0, Math=58.1, Physics=56.3"
+EA00387,Computer_Science,59.23,Computer_Science_final,Pass,0.94,"Based on ICT=62.8, Math=57.2, Physics=65.8"
+EA00388,Computer_Science,69.49,Computer_Science_final,Pass,0.95,"Based on ICT=77.7, Math=62.2, Physics=57.9"
+EA00389,Computer_Science,57.47,Computer_Science_final,Pass,0.92,"Based on ICT=63.3, Math=60.6, Physics=31.4"
+EA00390,Computer_Science,47.38,Computer_Science_final,Fail,0.82,"Based on ICT=54.3, Math=40.3, Physics=61.1"
+EA00391,Computer_Science,59.7,Computer_Science_final,Pass,0.95,"Based on ICT=63.6, Math=58.4, Physics=55.7"
+EA00392,Computer_Science,67.65,Computer_Science_final,Pass,0.95,"Based on ICT=79.7, Math=63.6, Physics=58.8"
+EA00393,Computer_Science,56.37,Computer_Science_final,Pass,0.91,"Based on ICT=59.8, Math=56.9, Physics=62.7"
+EA00394,Computer_Science,51.53,Computer_Science_final,Pass,0.87,"Based on ICT=59.6, Math=42.8, Physics=50.2"
+EA00395,Computer_Science,63.25,Computer_Science_final,Pass,0.95,"Based on ICT=68.5, Math=56.3, Physics=67.2"
+EA00396,Computer_Science,48.54,Computer_Science_final,Fail,0.84,"Based on ICT=57.1, Math=40.8, Physics=46.3"
+EA00397,Computer_Science,66.4,Computer_Science_final,Pass,0.95,"Based on ICT=74.5, Math=63.5, Physics=71.2"
+EA00398,Computer_Science,53.12,Computer_Science_final,Pass,0.88,"Based on ICT=58.1, Math=50.1, Physics=55.5"
+EA00399,Computer_Science,47.81,Computer_Science_final,Fail,0.83,"Based on ICT=58.3, Math=45.7, Physics=39.4"
+EA00400,Computer_Science,56.16,Computer_Science_final,Pass,0.91,"Based on ICT=56.8, Math=59.4, Physics=66.5"
+EA00401,Computer_Science,63.84,Computer_Science_final,Pass,0.95,"Based on ICT=70.6, Math=62.5, Physics=58.1"
+EA00402,Computer_Science,58.35,Computer_Science_final,Pass,0.93,"Based on ICT=69.0, Math=50.0, Physics=58.9"
+EA00403,Computer_Science,57.98,Computer_Science_final,Pass,0.93,"Based on ICT=68.2, Math=60.5, Physics=56.0"
+EA00404,Computer_Science,63.21,Computer_Science_final,Pass,0.95,"Based on ICT=67.4, Math=54.9, Physics=63.1"
+EA00405,Computer_Science,62.13,Computer_Science_final,Pass,0.95,"Based on ICT=73.2, Math=57.0, Physics=59.9"
+EA00406,Computer_Science,61.07,Computer_Science_final,Pass,0.95,"Based on ICT=59.8, Math=64.0, Physics=76.7"
+EA00407,Computer_Science,63.78,Computer_Science_final,Pass,0.95,"Based on ICT=74.6, Math=52.3, Physics=54.0"
+EA00408,Computer_Science,64.75,Computer_Science_final,Pass,0.95,"Based on ICT=72.2, Math=57.9, Physics=70.4"
+EA00409,Computer_Science,48.78,Computer_Science_final,Fail,0.84,"Based on ICT=56.1, Math=48.9, Physics=45.4"
+EA00410,Computer_Science,57.04,Computer_Science_final,Pass,0.92,"Based on ICT=60.0, Math=65.8, Physics=52.6"
+EA00411,Computer_Science,75.32,Computer_Science_final,Pass,0.9,"Based on ICT=81.5, Math=67.7, Physics=79.3"
+EA00412,Computer_Science,48.65,Computer_Science_final,Fail,0.84,"Based on ICT=50.8, Math=53.9, Physics=49.0"
+EA00413,Computer_Science,54.03,Computer_Science_final,Pass,0.89,"Based on ICT=62.8, Math=48.2, Physics=62.0"
+EA00414,Computer_Science,58.71,Computer_Science_final,Pass,0.94,"Based on ICT=67.9, Math=54.1, Physics=56.6"
+EA00415,Computer_Science,65.28,Computer_Science_final,Pass,0.95,"Based on ICT=64.9, Math=74.5, Physics=63.0"
+EA00416,Computer_Science,56.38,Computer_Science_final,Pass,0.91,"Based on ICT=70.4, Math=49.1, Physics=48.5"
+EA00417,Computer_Science,56.68,Computer_Science_final,Pass,0.92,"Based on ICT=65.0, Math=51.6, Physics=57.8"
+EA00418,Computer_Science,59.0,Computer_Science_final,Pass,0.94,"Based on ICT=58.7, Math=61.2, Physics=56.3"
+EA00419,Computer_Science,56.2,Computer_Science_final,Pass,0.91,"Based on ICT=57.0, Math=54.0, Physics=60.3"
+EA00420,Computer_Science,62.49,Computer_Science_final,Pass,0.95,"Based on ICT=69.6, Math=64.0, Physics=47.7"
+EA00421,Computer_Science,48.77,Computer_Science_final,Fail,0.84,"Based on ICT=59.6, Math=44.1, Physics=55.9"
+EA00422,Computer_Science,67.44,Computer_Science_final,Pass,0.95,"Based on ICT=71.2, Math=69.4, Physics=53.9"
+EA00423,Computer_Science,65.01,Computer_Science_final,Pass,0.95,"Based on ICT=72.1, Math=65.3, Physics=47.6"
+EA00424,Computer_Science,65.72,Computer_Science_final,Pass,0.95,"Based on ICT=68.7, Math=59.1, Physics=57.2"
+EA00425,Computer_Science,54.95,Computer_Science_final,Pass,0.9,"Based on ICT=59.8, Math=52.8, Physics=65.9"
+EA00426,Computer_Science,62.46,Computer_Science_final,Pass,0.95,"Based on ICT=68.4, Math=62.8, Physics=61.8"
+EA00427,Computer_Science,56.11,Computer_Science_final,Pass,0.91,"Based on ICT=57.3, Math=58.6, Physics=52.5"
+EA00428,Computer_Science,56.78,Computer_Science_final,Pass,0.92,"Based on ICT=63.8, Math=52.1, Physics=60.1"
+EA00429,Computer_Science,51.78,Computer_Science_final,Pass,0.87,"Based on ICT=62.7, Math=47.8, Physics=49.1"
+EA00430,Computer_Science,51.07,Computer_Science_final,Pass,0.86,"Based on ICT=54.9, Math=55.9, Physics=58.1"
+EA00431,Computer_Science,52.49,Computer_Science_final,Pass,0.87,"Based on ICT=45.9, Math=62.8, Physics=58.1"
+EA00432,Computer_Science,62.1,Computer_Science_final,Pass,0.95,"Based on ICT=66.1, Math=60.0, Physics=60.9"
+EA00433,Computer_Science,58.36,Computer_Science_final,Pass,0.93,"Based on ICT=65.9, Math=51.1, Physics=57.6"
+EA00434,Computer_Science,67.36,Computer_Science_final,Pass,0.95,"Based on ICT=75.0, Math=56.8, Physics=66.8"
+EA00435,Computer_Science,64.61,Computer_Science_final,Pass,0.95,"Based on ICT=69.0, Math=59.5, Physics=68.3"
+EA00436,Computer_Science,60.05,Computer_Science_final,Pass,0.95,"Based on ICT=70.7, Math=49.0, Physics=60.2"
+EA00437,Computer_Science,57.26,Computer_Science_final,Pass,0.92,"Based on ICT=62.6, Math=57.0, Physics=52.4"
+EA00438,Computer_Science,61.4,Computer_Science_final,Pass,0.95,"Based on ICT=67.2, Math=46.5, Physics=65.1"
+EA00439,Computer_Science,53.59,Computer_Science_final,Pass,0.89,"Based on ICT=66.0, Math=57.8, Physics=47.8"
+EA00440,Computer_Science,51.0,Computer_Science_final,Pass,0.86,"Based on ICT=57.6, Math=47.3, Physics=52.5"
+EA00441,Computer_Science,58.05,Computer_Science_final,Pass,0.93,"Based on ICT=71.9, Math=61.5, Physics=46.9"
+EA00442,Computer_Science,57.34,Computer_Science_final,Pass,0.92,"Based on ICT=63.2, Math=64.3, Physics=64.1"
+EA00443,Computer_Science,56.54,Computer_Science_final,Pass,0.92,"Based on ICT=72.2, Math=50.3, Physics=59.9"
+EA00444,Computer_Science,59.03,Computer_Science_final,Pass,0.94,"Based on ICT=60.8, Math=64.1, Physics=61.8"
+EA00445,Computer_Science,60.64,Computer_Science_final,Pass,0.95,"Based on ICT=65.4, Math=58.6, Physics=58.0"
+EA00446,Computer_Science,53.72,Computer_Science_final,Pass,0.89,"Based on ICT=62.1, Math=48.5, Physics=59.5"
+EA00447,Computer_Science,70.38,Computer_Science_final,Pass,0.95,"Based on ICT=75.6, Math=70.7, Physics=57.3"
+EA00448,Computer_Science,65.11,Computer_Science_final,Pass,0.95,"Based on ICT=82.3, Math=54.8, Physics=63.7"
+EA00449,Computer_Science,61.29,Computer_Science_final,Pass,0.95,"Based on ICT=70.2, Math=51.0, Physics=55.4"
+EA00450,Computer_Science,51.48,Computer_Science_final,Pass,0.86,"Based on ICT=64.1, Math=50.3, Physics=45.9"
+EA00451,Computer_Science,64.66,Computer_Science_final,Pass,0.95,"Based on ICT=63.9, Math=68.4, Physics=52.2"
+EA00452,Computer_Science,65.24,Computer_Science_final,Pass,0.95,"Based on ICT=68.0, Math=70.5, Physics=58.5"
+EA00453,Computer_Science,58.72,Computer_Science_final,Pass,0.94,"Based on ICT=70.0, Math=48.1, Physics=58.9"
+EA00454,Computer_Science,60.5,Computer_Science_final,Pass,0.95,"Based on ICT=68.8, Math=54.0, Physics=59.8"
+EA00455,Computer_Science,62.37,Computer_Science_final,Pass,0.95,"Based on ICT=74.9, Math=54.6, Physics=55.1"
+EA00456,Computer_Science,69.69,Computer_Science_final,Pass,0.95,"Based on ICT=82.0, Math=55.4, Physics=57.8"
+EA00457,Computer_Science,53.28,Computer_Science_final,Pass,0.88,"Based on ICT=52.6, Math=58.6, Physics=58.1"
+EA00458,Computer_Science,53.92,Computer_Science_final,Pass,0.89,"Based on ICT=60.5, Math=59.4, Physics=53.6"
+EA00459,Computer_Science,59.8,Computer_Science_final,Pass,0.95,"Based on ICT=64.4, Math=61.8, Physics=58.4"
+EA00460,Computer_Science,55.05,Computer_Science_final,Pass,0.9,"Based on ICT=63.6, Math=54.5, Physics=53.8"
+EA00461,Computer_Science,54.78,Computer_Science_final,Pass,0.9,"Based on ICT=54.6, Math=56.3, Physics=59.6"
+EA00462,Computer_Science,57.94,Computer_Science_final,Pass,0.93,"Based on ICT=55.0, Math=57.0, Physics=70.8"
+EA00463,Computer_Science,67.38,Computer_Science_final,Pass,0.95,"Based on ICT=78.6, Math=57.3, Physics=58.9"
+EA00464,Computer_Science,56.43,Computer_Science_final,Pass,0.91,"Based on ICT=56.4, Math=56.6, Physics=57.2"
+EA00465,Computer_Science,48.97,Computer_Science_final,Fail,0.84,"Based on ICT=61.5, Math=47.9, Physics=41.6"
+EA00466,Computer_Science,59.06,Computer_Science_final,Pass,0.94,"Based on ICT=72.2, Math=49.4, Physics=62.4"
+EA00467,Computer_Science,56.77,Computer_Science_final,Pass,0.92,"Based on ICT=63.9, Math=55.4, Physics=52.0"
+EA00468,Computer_Science,65.73,Computer_Science_final,Pass,0.95,"Based on ICT=65.3, Math=65.2, Physics=64.4"
+EA00469,Computer_Science,50.05,Computer_Science_final,Pass,0.85,"Based on ICT=54.8, Math=44.1, Physics=49.5"
+EA00470,Computer_Science,64.27,Computer_Science_final,Pass,0.95,"Based on ICT=71.3, Math=60.5, Physics=62.2"
+EA00471,Computer_Science,52.41,Computer_Science_final,Pass,0.87,"Based on ICT=66.1, Math=51.4, Physics=47.7"
+EA00472,Computer_Science,69.16,Computer_Science_final,Pass,0.95,"Based on ICT=70.8, Math=69.9, Physics=57.3"
+EA00473,Computer_Science,59.74,Computer_Science_final,Pass,0.95,"Based on ICT=62.8, Math=60.6, Physics=62.5"
+EA00474,Computer_Science,52.29,Computer_Science_final,Pass,0.87,"Based on ICT=55.0, Math=46.1, Physics=55.2"
+EA00475,Computer_Science,50.73,Computer_Science_final,Pass,0.86,"Based on ICT=61.1, Math=53.9, Physics=48.6"
+EA00476,Computer_Science,67.1,Computer_Science_final,Pass,0.95,"Based on ICT=64.3, Math=75.2, Physics=58.9"
+EA00477,Computer_Science,50.63,Computer_Science_final,Pass,0.86,"Based on ICT=60.1, Math=45.0, Physics=54.9"
+EA00478,Computer_Science,58.36,Computer_Science_final,Pass,0.93,"Based on ICT=67.9, Math=62.7, Physics=50.9"
+EA00479,Computer_Science,68.55,Computer_Science_final,Pass,0.95,"Based on ICT=75.0, Math=67.0, Physics=63.8"
+EA00480,Computer_Science,59.83,Computer_Science_final,Pass,0.95,"Based on ICT=70.3, Math=55.6, Physics=67.2"
+EA00481,Computer_Science,53.77,Computer_Science_final,Pass,0.89,"Based on ICT=47.3, Math=62.9, Physics=58.7"
+EA00482,Computer_Science,58.06,Computer_Science_final,Pass,0.93,"Based on ICT=58.8, Math=57.6, Physics=58.9"
+EA00483,Computer_Science,54.56,Computer_Science_final,Pass,0.9,"Based on ICT=71.8, Math=43.5, Physics=57.6"
+EA00484,Computer_Science,61.67,Computer_Science_final,Pass,0.95,"Based on ICT=67.1, Math=49.0, Physics=63.8"
+EA00485,Computer_Science,59.63,Computer_Science_final,Pass,0.95,"Based on ICT=62.1, Math=60.5, Physics=45.8"
+EA00486,Computer_Science,55.46,Computer_Science_final,Pass,0.9,"Based on ICT=61.0, Math=53.6, Physics=66.3"
+EA00487,Computer_Science,53.48,Computer_Science_final,Pass,0.88,"Based on ICT=65.5, Math=48.4, Physics=58.7"
+EA00488,Computer_Science,55.3,Computer_Science_final,Pass,0.9,"Based on ICT=58.4, Math=53.7, Physics=54.8"
+EA00489,Computer_Science,60.04,Computer_Science_final,Pass,0.95,"Based on ICT=67.1, Math=59.1, Physics=60.8"
+EA00490,Computer_Science,54.97,Computer_Science_final,Pass,0.9,"Based on ICT=53.4, Math=55.6, Physics=60.8"
+EA00491,Computer_Science,50.28,Computer_Science_final,Pass,0.85,"Based on ICT=57.1, Math=46.9, Physics=48.9"
+EA00492,Computer_Science,61.56,Computer_Science_final,Pass,0.95,"Based on ICT=64.0, Math=59.3, Physics=55.6"
+EA00493,Computer_Science,57.69,Computer_Science_final,Pass,0.93,"Based on ICT=60.8, Math=63.4, Physics=52.6"
+EA00494,Computer_Science,71.07,Computer_Science_final,Pass,0.94,"Based on ICT=75.9, Math=70.0, Physics=68.1"
+EA00495,Computer_Science,59.67,Computer_Science_final,Pass,0.95,"Based on ICT=64.9, Math=68.3, Physics=52.9"
+EA00496,Computer_Science,66.12,Computer_Science_final,Pass,0.95,"Based on ICT=70.5, Math=64.8, Physics=61.4"
+EA00497,Computer_Science,66.27,Computer_Science_final,Pass,0.95,"Based on ICT=69.9, Math=67.8, Physics=62.9"
+EA00498,Computer_Science,53.74,Computer_Science_final,Pass,0.89,"Based on ICT=57.2, Math=49.5, Physics=59.1"
+EA00499,Computer_Science,55.59,Computer_Science_final,Pass,0.91,"Based on ICT=63.3, Math=51.4, Physics=56.9"
+EA00500,Computer_Science,61.01,Computer_Science_final,Pass,0.95,"Based on ICT=58.2, Math=68.2, Physics=56.2"

+ 501 - 0
internal/src/models/datasets/nlp_pred_risk_alert.csv

@@ -0,0 +1,501 @@
+student_id,risk_level,risk_score,main_factors,avg_final,failed_subjects,attendance_absences_total,grade_tendency
+EA00001,High,0.635,"below average grade, multiple failures, high absences",55.818,3,24,Uptrend
+EA00002,Low,0.1071,none,61.519,0,4,Uptrend
+EA00003,Medium,0.3556,below average grade,59.582,1,7,Uptrend
+EA00004,Medium,0.4779,"high absences, downward trend",60.762,1,18,Downtrend
+EA00005,Low,0.1924,high absences,69.094,0,22,Uptrend
+EA00006,High,0.6353,"below average grade, multiple failures, high absences",56.953,4,16,Uptrend
+EA00007,Low,0.182,none,64.933,0,10,Uptrend
+EA00008,Low,0.2351,none,60.424,1,0,Uptrend
+EA00009,Medium,0.4698,below average grade,55.547,1,14,Uptrend
+EA00010,Medium,0.4011,below average grade,59.018,1,14,Uptrend
+EA00011,Low,0.1264,none,64.682,0,5,Uptrend
+EA00012,Low,0.1879,none,67.006,0,14,Uptrend
+EA00013,Medium,0.3301,multiple failures,60.818,3,1,Uptrend
+EA00014,Medium,0.3548,"below average grade, multiple failures",58.788,2,3,Uptrend
+EA00015,Low,0.1687,none,63.729,0,11,Uptrend
+EA00016,Low,0.1513,downward trend,67.882,0,1,Downtrend
+EA00017,Medium,0.5498,"below average grade, downward trend",58.972,1,15,Downtrend
+EA00018,Low,0.0284,none,66.519,0,2,Uptrend
+EA00019,High,0.5511,"below average grade, multiple failures",57.035,2,13,Uptrend
+EA00020,Medium,0.5325,"below average grade, multiple failures",56.741,3,11,Uptrend
+EA00021,Low,0.1578,none,65.565,1,0,Uptrend
+EA00022,Low,0.266,high absences,64.235,0,17,Uptrend
+EA00023,Low,0.2493,none,63.053,1,15,Uptrend
+EA00024,Low,0.2917,downward trend,63.118,0,13,Downtrend
+EA00025,Low,0.0749,none,65.582,0,13,Uptrend
+EA00026,High,0.619,"below average grade, multiple failures, high absences, downward trend",57.319,2,22,Downtrend
+EA00027,Low,0.187,none,62.4,0,11,Uptrend
+EA00028,Low,0.2418,multiple failures,62.894,2,1,Uptrend
+EA00029,High,0.6071,"below average grade, multiple failures, downward trend",57.476,2,10,Downtrend
+EA00030,Medium,0.3222,below average grade,58.775,0,15,Uptrend
+EA00031,High,0.5713,"below average grade, multiple failures",55.806,4,5,Uptrend
+EA00032,Low,0.1818,high absences,71.961,0,24,Uptrend
+EA00033,High,0.6946,"below average grade, multiple failures, downward trend",56.465,4,8,Downtrend
+EA00034,High,0.8278,"low avg grade, multiple failures, high absences",54.925,4,17,Uptrend
+EA00035,Low,0.12,none,62.044,0,7,Uptrend
+EA00036,Low,0.0894,none,65.276,0,11,Uptrend
+EA00037,Low,0.2383,none,62.161,1,1,Uptrend
+EA00038,High,0.9097,"low avg grade, multiple failures, downward trend",54.538,5,3,Downtrend
+EA00039,High,0.5698,"below average grade, multiple failures",57.235,3,10,Uptrend
+EA00040,Medium,0.4652,"below average grade, multiple failures",57.528,3,5,Uptrend
+EA00041,Medium,0.5207,"below average grade, multiple failures",56.594,3,6,Uptrend
+EA00042,Low,0.2497,below average grade,59.094,1,5,Uptrend
+EA00043,Low,0.2353,high absences,63.629,0,21,Uptrend
+EA00044,Low,0.1183,none,69.583,0,8,Uptrend
+EA00045,Medium,0.4709,"below average grade, multiple failures",57.081,3,6,Uptrend
+EA00046,Medium,0.5377,"below average grade, multiple failures",57.711,3,7,Uptrend
+EA00047,Medium,0.4891,"high absences, downward trend",60.756,1,19,Downtrend
+EA00048,Low,0.2922,"high absences, downward trend",67.782,0,21,Downtrend
+EA00049,Low,0.0774,none,64.812,0,0,Uptrend
+EA00050,Medium,0.3384,"below average grade, high absences",59.071,0,23,Uptrend
+EA00051,Low,0.2806,high absences,60.524,1,17,Uptrend
+EA00052,Low,0.2948,downward trend,62.031,0,13,Downtrend
+EA00053,Medium,0.4094,below average grade,57.306,1,11,Uptrend
+EA00054,Medium,0.3508,below average grade,59.906,1,5,Uptrend
+EA00055,Medium,0.4819,multiple failures,61.118,3,11,Uptrend
+EA00056,Medium,0.3472,"high absences, downward trend",67.0,1,23,Downtrend
+EA00057,Low,0.2624,high absences,66.278,0,22,Uptrend
+EA00058,Low,0.1574,none,69.456,0,6,Uptrend
+EA00059,Medium,0.5156,"below average grade, multiple failures, high absences",57.871,2,20,Uptrend
+EA00060,Low,0.0369,none,67.344,0,8,Uptrend
+EA00061,Low,0.248,high absences,68.771,1,18,Uptrend
+EA00062,High,0.6526,"below average grade, multiple failures, downward trend",58.617,2,10,Downtrend
+EA00063,Low,0.1226,high absences,67.776,0,19,Uptrend
+EA00064,Low,0.119,none,61.006,0,5,Uptrend
+EA00065,Low,0.2887,below average grade,59.944,0,7,Uptrend
+EA00066,Low,0.1048,high absences,66.0,0,22,Uptrend
+EA00067,Low,0.1962,high absences,65.547,0,17,Uptrend
+EA00068,Low,0.245,downward trend,66.556,0,6,Downtrend
+EA00069,Low,0.0829,none,62.319,0,5,Uptrend
+EA00070,Low,0.0696,none,64.281,0,4,Uptrend
+EA00071,Low,0.2635,none,63.894,1,14,Uptrend
+EA00072,Low,0.2171,none,61.935,1,11,Uptrend
+EA00073,High,0.5681,"below average grade, multiple failures, high absences",55.247,3,17,Uptrend
+EA00074,Low,0.0,none,65.482,0,1,Uptrend
+EA00075,Medium,0.3111,downward trend,61.171,0,11,Downtrend
+EA00076,Low,0.2541,high absences,68.506,0,23,Uptrend
+EA00077,Low,0.0225,none,67.765,0,7,Uptrend
+EA00078,Low,0.1598,none,60.929,0,11,Uptrend
+EA00079,Low,0.0593,none,65.539,0,13,Uptrend
+EA00080,Low,0.1891,none,67.424,0,15,Uptrend
+EA00081,Low,0.1532,none,61.535,1,0,Uptrend
+EA00082,Medium,0.4742,"below average grade, high absences",59.428,1,17,Uptrend
+EA00083,Medium,0.4702,"below average grade, multiple failures",56.681,2,13,Uptrend
+EA00084,Low,0.1888,high absences,66.671,0,19,Uptrend
+EA00085,Low,0.0805,none,69.076,0,8,Uptrend
+EA00086,High,0.8568,"low avg grade, multiple failures, downward trend",54.662,4,9,Downtrend
+EA00087,Medium,0.4198,"high absences, downward trend",63.106,1,20,Downtrend
+EA00088,Low,0.1051,none,67.206,0,12,Uptrend
+EA00089,Low,0.2248,high absences,61.637,0,24,Uptrend
+EA00090,Medium,0.397,"below average grade, multiple failures",59.865,2,4,Uptrend
+EA00091,Medium,0.3613,downward trend,62.241,0,15,Downtrend
+EA00092,High,0.9347,"low avg grade, multiple failures, high absences, downward trend",54.906,4,22,Downtrend
+EA00093,Low,0.1626,high absences,65.559,0,21,Uptrend
+EA00094,Medium,0.3461,downward trend,63.25,0,8,Downtrend
+EA00095,Low,0.0228,none,68.025,0,7,Uptrend
+EA00096,High,0.6101,"below average grade, multiple failures",56.3,4,1,Uptrend
+EA00097,Low,0.1227,none,65.565,0,10,Uptrend
+EA00098,Low,0.209,none,60.441,1,9,Uptrend
+EA00099,High,0.7328,"below average grade, multiple failures, high absences, downward trend",59.244,2,24,Downtrend
+EA00100,Medium,0.3047,"high absences, downward trend",69.941,0,19,Downtrend
+EA00101,Medium,0.3511,high absences,63.376,1,24,Uptrend
+EA00102,High,0.6465,"below average grade, multiple failures",56.222,4,9,Uptrend
+EA00103,Low,0.1096,none,67.576,1,10,Uptrend
+EA00104,High,0.7576,"low avg grade, multiple failures",53.275,6,13,Uptrend
+EA00105,Low,0.2002,downward trend,65.138,0,2,Downtrend
+EA00106,Low,0.0,none,67.794,0,2,Uptrend
+EA00107,Low,0.1708,high absences,64.653,0,18,Uptrend
+EA00108,High,0.5771,"below average grade, multiple failures, downward trend",57.669,2,8,Downtrend
+EA00109,High,0.6397,"below average grade, multiple failures",56.894,4,9,Uptrend
+EA00110,Medium,0.3217,downward trend,63.041,0,10,Downtrend
+EA00111,Low,0.2088,none,61.965,1,1,Uptrend
+EA00112,Medium,0.384,"below average grade, multiple failures",57.494,2,6,Uptrend
+EA00113,Low,0.1304,none,64.241,0,10,Uptrend
+EA00114,Low,0.2133,high absences,63.662,0,19,Uptrend
+EA00115,Low,0.137,none,60.371,1,1,Uptrend
+EA00116,Medium,0.3779,"below average grade, multiple failures",57.918,2,3,Uptrend
+EA00117,Medium,0.5093,"below average grade, multiple failures, high absences",56.606,2,24,Uptrend
+EA00118,High,0.7631,"below average grade, multiple failures, high absences, downward trend",58.059,3,21,Downtrend
+EA00119,Low,0.223,high absences,62.756,0,19,Uptrend
+EA00120,Medium,0.4552,"multiple failures, downward trend",61.029,2,11,Downtrend
+EA00121,Low,0.2379,downward trend,65.881,0,11,Downtrend
+EA00122,Medium,0.4072,"below average grade, multiple failures",57.912,2,4,Uptrend
+EA00123,Low,0.2433,downward trend,69.418,0,11,Downtrend
+EA00124,High,0.6802,"low avg grade, multiple failures",54.312,4,2,Uptrend
+EA00125,Low,0.1804,high absences,63.978,0,22,Uptrend
+EA00126,Low,0.1946,downward trend,69.453,0,7,Downtrend
+EA00127,Medium,0.4323,"below average grade, multiple failures",55.717,2,8,Uptrend
+EA00128,Low,0.2272,below average grade,59.724,0,1,Uptrend
+EA00129,Low,0.0741,none,67.824,0,9,Uptrend
+EA00130,Low,0.2848,high absences,62.424,0,20,Uptrend
+EA00131,Low,0.0922,none,68.053,0,7,Uptrend
+EA00132,Low,0.0906,none,66.638,0,12,Uptrend
+EA00133,Medium,0.3072,high absences,62.871,1,23,Uptrend
+EA00134,Low,0.1895,none,64.735,1,2,Uptrend
+EA00135,Medium,0.5034,"below average grade, multiple failures, high absences",58.871,2,17,Uptrend
+EA00136,Low,0.1322,none,62.025,0,4,Uptrend
+EA00137,Medium,0.4422,"below average grade, multiple failures",57.117,3,0,Uptrend
+EA00138,Medium,0.3006,downward trend,65.547,0,12,Downtrend
+EA00139,High,0.7765,"below average grade, multiple failures, high absences, downward trend",57.541,4,19,Downtrend
+EA00140,Low,0.2249,high absences,61.788,0,21,Uptrend
+EA00141,High,0.6516,"below average grade, multiple failures, downward trend",56.418,3,3,Downtrend
+EA00142,High,0.8999,"low avg grade, multiple failures, downward trend",54.641,4,7,Downtrend
+EA00143,Low,0.1449,high absences,65.094,0,21,Uptrend
+EA00144,Low,0.2653,below average grade,58.624,1,0,Uptrend
+EA00145,Medium,0.4636,"below average grade, multiple failures",58.917,2,10,Uptrend
+EA00146,Low,0.2764,downward trend,61.822,0,9,Downtrend
+EA00147,Low,0.2833,none,63.029,1,12,Uptrend
+EA00148,Low,0.2719,"high absences, downward trend",66.082,0,24,Downtrend
+EA00149,High,0.5903,"below average grade, multiple failures, downward trend",57.053,2,14,Downtrend
+EA00150,High,0.5962,"multiple failures, high absences, downward trend",61.631,2,21,Downtrend
+EA00151,Low,0.2999,below average grade,59.789,1,2,Uptrend
+EA00152,Low,0.1129,none,63.372,0,3,Uptrend
+EA00153,Medium,0.3419,high absences,60.062,1,21,Uptrend
+EA00154,Low,0.0,none,68.456,0,0,Uptrend
+EA00155,Low,0.2213,downward trend,67.769,0,1,Downtrend
+EA00156,Medium,0.4827,"below average grade, multiple failures, high absences",59.006,2,20,Uptrend
+EA00157,Low,0.1418,none,63.188,0,3,Uptrend
+EA00158,Low,0.147,none,61.7,0,6,Uptrend
+EA00159,Low,0.2001,none,62.1,0,13,Uptrend
+EA00160,Low,0.2396,none,64.0,0,13,Uptrend
+EA00161,Low,0.1944,none,60.739,1,1,Uptrend
+EA00162,High,0.6962,"below average grade, multiple failures, downward trend",58.212,4,7,Downtrend
+EA00163,Low,0.1211,none,67.924,0,7,Uptrend
+EA00164,Low,0.1194,none,64.576,0,5,Uptrend
+EA00165,Medium,0.3617,"high absences, downward trend",63.218,0,16,Downtrend
+EA00166,Medium,0.3052,below average grade,58.318,1,2,Uptrend
+EA00167,Low,0.1574,none,65.194,1,9,Uptrend
+EA00168,Medium,0.3012,multiple failures,62.0,2,4,Uptrend
+EA00169,Medium,0.3435,"below average grade, multiple failures",58.506,2,2,Uptrend
+EA00170,Low,0.1234,high absences,66.841,0,16,Uptrend
+EA00171,Medium,0.3443,"below average grade, multiple failures",58.828,2,5,Uptrend
+EA00172,Low,0.085,none,68.038,0,8,Uptrend
+EA00173,Low,0.1536,high absences,69.9,0,16,Uptrend
+EA00174,Low,0.1104,none,69.65,0,14,Uptrend
+EA00175,High,0.6202,"below average grade, multiple failures, high absences",57.269,4,23,Uptrend
+EA00176,Low,0.2416,downward trend,62.539,0,5,Downtrend
+EA00177,Medium,0.4355,"below average grade, multiple failures",58.588,2,10,Uptrend
+EA00178,Medium,0.5093,"below average grade, multiple failures, downward trend",55.494,2,9,Downtrend
+EA00179,Low,0.0054,none,65.459,0,0,Uptrend
+EA00180,Medium,0.3879,multiple failures,62.541,3,6,Uptrend
+EA00181,Low,0.0306,none,67.535,0,5,Uptrend
+EA00182,High,0.6352,"low avg grade, multiple failures",54.25,5,0,Uptrend
+EA00183,High,0.7915,"low avg grade, multiple failures, high absences",54.394,6,17,Uptrend
+EA00184,Medium,0.3105,"high absences, downward trend",66.511,0,20,Downtrend
+EA00185,High,0.6148,"below average grade, multiple failures",55.022,4,4,Uptrend
+EA00186,Low,0.0006,none,67.925,0,0,Uptrend
+EA00187,High,0.6813,"below average grade, multiple failures, downward trend",57.994,3,14,Downtrend
+EA00188,Low,0.1642,high absences,67.375,0,16,Uptrend
+EA00189,Medium,0.3975,below average grade,59.75,1,14,Uptrend
+EA00190,High,0.8027,"below average grade, multiple failures, downward trend",55.629,4,7,Downtrend
+EA00191,Low,0.0,none,65.653,0,0,Uptrend
+EA00192,Low,0.1284,none,60.138,1,0,Uptrend
+EA00193,Medium,0.4219,"below average grade, multiple failures",59.239,2,11,Uptrend
+EA00194,Low,0.2371,"high absences, downward trend",67.081,0,21,Downtrend
+EA00195,Low,0.0419,none,66.324,0,4,Uptrend
+EA00196,Low,0.2532,downward trend,64.0,0,8,Downtrend
+EA00197,High,0.593,"below average grade, high absences, downward trend",55.967,1,19,Downtrend
+EA00198,Low,0.0907,downward trend,66.222,0,0,Downtrend
+EA00199,High,0.6365,"below average grade, multiple failures, downward trend",56.394,3,10,Downtrend
+EA00200,Medium,0.5251,"below average grade, multiple failures",56.794,4,1,Uptrend
+EA00201,Medium,0.5167,"below average grade, multiple failures",57.294,4,7,Uptrend
+EA00202,Low,0.0755,none,61.541,0,6,Uptrend
+EA00203,Medium,0.5193,"below average grade, multiple failures",57.806,2,13,Uptrend
+EA00204,Low,0.1338,none,63.744,0,8,Uptrend
+EA00205,Medium,0.373,downward trend,64.359,0,10,Downtrend
+EA00206,Medium,0.3722,"below average grade, high absences",59.853,0,22,Uptrend
+EA00207,Medium,0.5297,"below average grade, multiple failures",56.594,2,15,Uptrend
+EA00208,Low,0.2491,multiple failures,64.335,2,6,Uptrend
+EA00209,Low,0.2263,high absences,67.112,1,23,Uptrend
+EA00210,Low,0.183,none,60.318,1,1,Uptrend
+EA00211,Medium,0.3585,"below average grade, multiple failures",57.812,2,2,Uptrend
+EA00212,Low,0.1629,downward trend,67.629,0,7,Downtrend
+EA00213,High,0.6831,"below average grade, multiple failures, high absences",55.241,5,21,Uptrend
+EA00214,Medium,0.4175,downward trend,64.206,1,15,Downtrend
+EA00215,Medium,0.3017,none,61.853,1,13,Uptrend
+EA00216,High,0.7645,"low avg grade, multiple failures, downward trend",54.359,2,13,Downtrend
+EA00217,Low,0.2069,none,63.281,0,12,Uptrend
+EA00218,Medium,0.4479,"multiple failures, high absences",62.15,2,22,Uptrend
+EA00219,Low,0.07,none,67.878,0,10,Uptrend
+EA00220,Medium,0.4025,downward trend,60.559,1,9,Downtrend
+EA00221,Low,0.2244,none,63.022,1,0,Uptrend
+EA00222,Low,0.2676,none,63.482,0,12,Uptrend
+EA00223,Low,0.2502,high absences,62.088,0,22,Uptrend
+EA00224,Medium,0.3963,"below average grade, high absences",58.135,1,22,Uptrend
+EA00225,High,0.5526,"below average grade, multiple failures",59.1,2,10,Uptrend
+EA00226,Low,0.178,downward trend,65.6,0,8,Downtrend
+EA00227,Low,0.2368,below average grade,59.306,0,6,Uptrend
+EA00228,Low,0.269,none,62.924,1,10,Uptrend
+EA00229,Medium,0.4068,downward trend,62.644,1,6,Downtrend
+EA00230,High,0.7226,"below average grade, multiple failures, high absences, downward trend",56.035,2,24,Downtrend
+EA00231,Medium,0.4512,"below average grade, downward trend",59.622,1,8,Downtrend
+EA00232,Medium,0.414,downward trend,64.788,1,12,Downtrend
+EA00233,Low,0.2064,downward trend,67.206,0,5,Downtrend
+EA00234,Low,0.1984,none,63.535,1,6,Uptrend
+EA00235,Medium,0.3934,"below average grade, high absences",58.965,1,23,Uptrend
+EA00236,Low,0.1942,high absences,65.55,1,17,Uptrend
+EA00237,High,0.5669,"below average grade, high absences, downward trend",59.118,1,24,Downtrend
+EA00238,Medium,0.3767,"below average grade, multiple failures",56.078,2,2,Uptrend
+EA00239,Medium,0.3684,"below average grade, multiple failures",58.165,2,3,Uptrend
+EA00240,Medium,0.5401,"below average grade, multiple failures, downward trend",58.906,2,1,Downtrend
+EA00241,Low,0.1395,none,61.589,1,1,Uptrend
+EA00242,Low,0.1861,downward trend,61.669,0,1,Downtrend
+EA00243,Low,0.2818,below average grade,59.494,0,13,Uptrend
+EA00244,Low,0.2405,none,62.278,0,15,Uptrend
+EA00245,Medium,0.3188,"high absences, downward trend",65.6,0,17,Downtrend
+EA00246,Low,0.1662,none,64.744,0,2,Uptrend
+EA00247,Medium,0.5287,"below average grade, multiple failures",59.522,2,13,Uptrend
+EA00248,Medium,0.3891,"below average grade, multiple failures",56.765,2,8,Uptrend
+EA00249,Low,0.055,none,66.1,1,0,Uptrend
+EA00250,High,0.6853,"below average grade, multiple failures, high absences",58.812,4,16,Uptrend
+EA00251,Medium,0.3087,downward trend,61.2,0,8,Downtrend
+EA00252,Low,0.2392,none,60.256,1,1,Uptrend
+EA00253,High,0.8063,"below average grade, multiple failures, high absences, downward trend",56.176,6,17,Downtrend
+EA00254,Medium,0.4124,below average grade,59.912,1,14,Uptrend
+EA00255,Low,0.2578,downward trend,68.669,0,8,Downtrend
+EA00256,Low,0.1887,none,61.031,0,14,Uptrend
+EA00257,Low,0.1221,none,63.981,0,9,Uptrend
+EA00258,High,0.5658,"below average grade, high absences, downward trend",59.887,1,18,Downtrend
+EA00259,Low,0.1929,downward trend,66.888,0,4,Downtrend
+EA00260,High,0.6103,"below average grade, multiple failures, high absences",57.556,3,18,Uptrend
+EA00261,Medium,0.4177,"below average grade, multiple failures",56.171,2,7,Uptrend
+EA00262,Low,0.158,high absences,65.506,0,21,Uptrend
+EA00263,Low,0.1133,none,60.318,0,4,Uptrend
+EA00264,Medium,0.3078,below average grade,57.825,1,6,Uptrend
+EA00265,Medium,0.5185,"below average grade, multiple failures, high absences",55.271,3,18,Uptrend
+EA00266,High,0.6391,"below average grade, multiple failures, high absences, downward trend",56.824,2,23,Downtrend
+EA00267,Medium,0.499,"below average grade, downward trend",57.7,1,14,Downtrend
+EA00268,Low,0.2176,high absences,65.041,0,21,Uptrend
+EA00269,High,0.8203,"low avg grade, multiple failures",53.488,6,14,Uptrend
+EA00270,Low,0.1598,none,65.95,0,13,Uptrend
+EA00271,Medium,0.3568,"high absences, downward trend",61.856,0,18,Downtrend
+EA00272,Medium,0.426,"below average grade, multiple failures",57.65,3,0,Uptrend
+EA00273,High,0.6107,"below average grade, multiple failures, high absences",56.112,3,21,Uptrend
+EA00274,Medium,0.5418,"below average grade, multiple failures, high absences",56.947,2,24,Uptrend
+EA00275,Medium,0.4034,"below average grade, multiple failures",59.044,2,7,Uptrend
+EA00276,Medium,0.5497,"below average grade, multiple failures",58.522,3,8,Uptrend
+EA00277,Medium,0.3462,none,63.55,1,15,Uptrend
+EA00278,Low,0.2268,downward trend,60.461,0,0,Downtrend
+EA00279,Medium,0.4254,"multiple failures, high absences",60.111,2,22,Uptrend
+EA00280,Medium,0.4836,"below average grade, multiple failures",58.578,2,15,Uptrend
+EA00281,Medium,0.4654,"below average grade, multiple failures",58.689,3,1,Uptrend
+EA00282,Medium,0.5354,"below average grade, multiple failures, downward trend",59.341,2,7,Downtrend
+EA00283,Low,0.2925,"high absences, downward trend",66.994,0,23,Downtrend
+EA00284,Low,0.235,downward trend,63.856,0,5,Downtrend
+EA00285,Medium,0.3737,downward trend,61.85,1,9,Downtrend
+EA00286,High,0.6323,"below average grade, multiple failures, downward trend",59.312,2,2,Downtrend
+EA00287,Low,0.1663,none,60.278,0,8,Uptrend
+EA00288,Medium,0.5112,"below average grade, multiple failures",55.322,3,3,Uptrend
+EA00289,Low,0.292,downward trend,67.322,0,15,Downtrend
+EA00290,Low,0.1027,none,62.682,0,4,Uptrend
+EA00291,Low,0.0635,none,68.718,0,4,Uptrend
+EA00292,Low,0.1331,high absences,66.606,0,19,Uptrend
+EA00293,Medium,0.4335,"multiple failures, high absences",61.838,2,23,Uptrend
+EA00294,Low,0.1808,downward trend,66.924,0,9,Downtrend
+EA00295,Low,0.0713,none,68.572,0,10,Uptrend
+EA00296,Low,0.2783,none,64.078,1,2,Uptrend
+EA00297,Low,0.2518,downward trend,64.818,0,4,Downtrend
+EA00298,Medium,0.3297,downward trend,61.541,0,8,Downtrend
+EA00299,Low,0.0825,none,64.553,0,1,Uptrend
+EA00300,Medium,0.4773,"below average grade, high absences",57.582,1,21,Uptrend
+EA00301,High,0.5708,"below average grade, multiple failures, high absences",56.944,3,17,Uptrend
+EA00302,Medium,0.4407,"below average grade, multiple failures",57.344,2,7,Uptrend
+EA00303,Medium,0.4294,"below average grade, multiple failures",57.531,4,3,Uptrend
+EA00304,Low,0.2705,below average grade,59.794,1,6,Uptrend
+EA00305,Low,0.287,high absences,62.794,1,18,Uptrend
+EA00306,Low,0.2311,high absences,62.488,0,21,Uptrend
+EA00307,Low,0.2112,downward trend,66.456,0,11,Downtrend
+EA00308,Medium,0.4254,"below average grade, multiple failures",58.012,2,9,Uptrend
+EA00309,Medium,0.5164,"below average grade, multiple failures",57.075,3,14,Uptrend
+EA00310,Medium,0.338,"high absences, downward trend",67.629,0,22,Downtrend
+EA00311,High,0.6146,"below average grade, multiple failures, downward trend",55.753,3,0,Downtrend
+EA00312,Low,0.2073,high absences,67.119,0,24,Uptrend
+EA00313,Medium,0.5234,"below average grade, multiple failures",57.556,3,2,Uptrend
+EA00314,Medium,0.507,"below average grade, multiple failures",59.929,2,13,Uptrend
+EA00315,Low,0.2843,downward trend,66.462,0,13,Downtrend
+EA00316,Medium,0.4205,"below average grade, high absences",59.353,1,20,Uptrend
+EA00317,Medium,0.5174,"below average grade, multiple failures",57.238,5,2,Uptrend
+EA00318,Low,0.2872,below average grade,59.544,0,12,Uptrend
+EA00319,High,0.7324,"below average grade, multiple failures, high absences, downward trend",57.659,3,18,Downtrend
+EA00320,Low,0.1771,high absences,62.788,0,18,Uptrend
+EA00321,Medium,0.3712,high absences,62.947,1,21,Uptrend
+EA00322,Low,0.1885,none,64.365,1,7,Uptrend
+EA00323,Low,0.0613,none,69.067,0,5,Uptrend
+EA00324,Medium,0.4624,"below average grade, multiple failures, high absences",59.339,2,21,Uptrend
+EA00325,High,0.6848,"below average grade, multiple failures, high absences",55.4,4,24,Uptrend
+EA00326,Low,0.1178,none,68.531,0,9,Uptrend
+EA00327,Medium,0.4685,below average grade,59.589,1,13,Uptrend
+EA00328,Medium,0.4322,"below average grade, multiple failures",57.665,2,8,Uptrend
+EA00329,High,0.6029,"below average grade, multiple failures",55.547,4,13,Uptrend
+EA00330,Medium,0.4775,"below average grade, multiple failures, high absences",56.112,2,21,Uptrend
+EA00331,Low,0.2447,downward trend,68.547,0,9,Downtrend
+EA00332,Low,0.265,multiple failures,60.653,2,6,Uptrend
+EA00333,Low,0.0,none,66.171,0,3,Uptrend
+EA00334,Low,0.1066,none,64.882,0,4,Uptrend
+EA00335,Low,0.0427,none,67.329,0,6,Uptrend
+EA00336,Low,0.1696,none,62.1,0,15,Uptrend
+EA00337,Medium,0.4962,"below average grade, multiple failures",56.641,3,2,Uptrend
+EA00338,High,0.6076,"below average grade, multiple failures, downward trend",59.694,3,4,Downtrend
+EA00339,Medium,0.4103,"below average grade, multiple failures, high absences",58.578,2,16,Uptrend
+EA00340,Medium,0.4451,"below average grade, high absences",57.818,1,20,Uptrend
+EA00341,Low,0.1117,high absences,66.262,0,17,Uptrend
+EA00342,Low,0.1891,high absences,68.775,0,20,Uptrend
+EA00343,Medium,0.5095,"below average grade, multiple failures",56.081,3,13,Uptrend
+EA00344,Medium,0.3746,"multiple failures, downward trend",62.559,2,4,Downtrend
+EA00345,Medium,0.3456,high absences,60.812,1,19,Uptrend
+EA00346,High,0.7424,"below average grade, multiple failures, high absences, downward trend",57.312,3,20,Downtrend
+EA00347,Medium,0.4609,"below average grade, high absences",58.088,1,20,Uptrend
+EA00348,Low,0.2799,downward trend,66.012,0,14,Downtrend
+EA00349,High,0.7901,"low avg grade, multiple failures, high absences",52.444,5,23,Uptrend
+EA00350,Medium,0.5223,"below average grade, multiple failures",57.333,4,9,Uptrend
+EA00351,Low,0.2116,downward trend,65.1,0,7,Downtrend
+EA00352,Low,0.1462,none,62.9,0,9,Uptrend
+EA00353,Medium,0.4065,below average grade,58.983,1,11,Uptrend
+EA00354,High,0.7037,"below average grade, multiple failures, high absences",57.028,5,17,Uptrend
+EA00355,Medium,0.4011,downward trend,62.965,1,0,Downtrend
+EA00356,Low,0.0546,none,65.3,1,3,Uptrend
+EA00357,Low,0.2213,downward trend,65.338,0,9,Downtrend
+EA00358,Medium,0.43,"below average grade, downward trend",56.722,1,4,Downtrend
+EA00359,High,0.6946,"below average grade, multiple failures, high absences, downward trend",56.865,2,24,Downtrend
+EA00360,Low,0.2547,high absences,64.671,0,21,Uptrend
+EA00361,Low,0.2306,high absences,64.381,0,16,Uptrend
+EA00362,Low,0.1347,downward trend,66.85,0,1,Downtrend
+EA00363,Low,0.0176,none,67.729,0,5,Uptrend
+EA00364,Low,0.2391,"high absences, downward trend",65.347,0,24,Downtrend
+EA00365,High,0.8553,"below average grade, multiple failures, downward trend",58.759,4,13,Downtrend
+EA00366,Low,0.1912,none,61.306,0,12,Uptrend
+EA00367,Medium,0.3342,downward trend,64.659,0,14,Downtrend
+EA00368,Low,0.27,none,63.483,0,14,Uptrend
+EA00369,Low,0.2145,none,60.089,1,4,Uptrend
+EA00370,Low,0.2293,high absences,67.189,0,20,Uptrend
+EA00371,Medium,0.4553,"below average grade, multiple failures, high absences",58.647,2,18,Uptrend
+EA00372,Low,0.2986,downward trend,62.294,0,5,Downtrend
+EA00373,Low,0.2059,none,68.288,1,15,Uptrend
+EA00374,Medium,0.3207,high absences,63.188,1,20,Uptrend
+EA00375,High,0.7547,"below average grade, multiple failures, high absences, downward trend",55.894,3,17,Downtrend
+EA00376,High,0.5597,"below average grade, multiple failures, high absences",57.278,3,22,Uptrend
+EA00377,High,0.6018,"below average grade, multiple failures",57.512,5,6,Uptrend
+EA00378,Low,0.1622,none,64.262,0,2,Uptrend
+EA00379,Low,0.1648,none,64.347,0,8,Uptrend
+EA00380,Low,0.1301,none,66.794,0,13,Uptrend
+EA00381,Low,0.0654,none,68.475,0,2,Uptrend
+EA00382,Medium,0.323,"high absences, downward trend",66.594,0,23,Downtrend
+EA00383,High,0.5741,"below average grade, multiple failures",59.347,3,10,Uptrend
+EA00384,Low,0.0112,none,66.935,0,1,Uptrend
+EA00385,High,0.6507,"below average grade, multiple failures, downward trend",56.729,2,13,Downtrend
+EA00386,Low,0.2037,high absences,60.425,0,22,Uptrend
+EA00387,Low,0.2977,high absences,61.6,0,24,Uptrend
+EA00388,Low,0.1311,none,64.824,0,3,Uptrend
+EA00389,Low,0.2894,below average grade,57.838,1,4,Uptrend
+EA00390,Low,0.2489,high absences,65.339,1,17,Uptrend
+EA00391,High,0.7137,"below average grade, multiple failures, high absences, downward trend",57.065,3,18,Downtrend
+EA00392,Low,0.0318,none,66.753,0,7,Uptrend
+EA00393,High,0.638,"below average grade, multiple failures, high absences, downward trend",57.369,2,16,Downtrend
+EA00394,High,0.82,"low avg grade, multiple failures",54.1,5,14,Uptrend
+EA00395,Low,0.2633,downward trend,68.247,0,13,Downtrend
+EA00396,Medium,0.5321,"below average grade, multiple failures",55.967,3,8,Uptrend
+EA00397,Low,0.108,none,66.7,0,10,Uptrend
+EA00398,Medium,0.3123,high absences,60.141,1,20,Uptrend
+EA00399,High,0.6267,"below average grade, multiple failures, downward trend",56.044,3,9,Downtrend
+EA00400,Low,0.1749,none,60.212,0,14,Uptrend
+EA00401,Low,0.0491,none,67.706,0,3,Uptrend
+EA00402,Medium,0.3088,high absences,61.044,1,19,Uptrend
+EA00403,Medium,0.4015,"high absences, downward trend",62.119,0,16,Downtrend
+EA00404,Medium,0.3039,downward trend,60.975,1,0,Downtrend
+EA00405,Medium,0.3041,high absences,63.311,1,21,Uptrend
+EA00406,Medium,0.3663,"high absences, downward trend",65.539,0,16,Downtrend
+EA00407,Medium,0.3645,"below average grade, multiple failures",59.5,2,3,Uptrend
+EA00408,Low,0.1044,high absences,68.253,0,23,Uptrend
+EA00409,High,0.599,"below average grade, multiple failures",57.331,3,15,Uptrend
+EA00410,Low,0.2424,high absences,64.918,0,20,Uptrend
+EA00411,Low,0.189,downward trend,70.029,0,13,Downtrend
+EA00412,Medium,0.4733,"below average grade, multiple failures",57.112,3,4,Uptrend
+EA00413,Low,0.2179,high absences,67.506,1,24,Uptrend
+EA00414,Medium,0.5003,"below average grade, multiple failures",57.788,2,11,Uptrend
+EA00415,Low,0.1561,high absences,66.547,0,16,Uptrend
+EA00416,Medium,0.3726,"multiple failures, high absences",60.1,2,20,Uptrend
+EA00417,Low,0.2527,none,61.141,1,15,Uptrend
+EA00418,Medium,0.4584,"below average grade, multiple failures",57.322,2,10,Uptrend
+EA00419,Low,0.219,below average grade,59.931,0,5,Uptrend
+EA00420,Low,0.1329,none,64.788,1,2,Uptrend
+EA00421,High,0.7091,"below average grade, multiple failures, high absences, downward trend",57.594,3,19,Downtrend
+EA00422,Low,0.0,none,65.644,0,5,Uptrend
+EA00423,Low,0.1471,none,61.531,1,4,Uptrend
+EA00424,Low,0.1832,downward trend,60.3,0,0,Downtrend
+EA00425,Medium,0.3644,"high absences, downward trend",60.65,0,23,Downtrend
+EA00426,Low,0.1235,high absences,68.244,0,18,Uptrend
+EA00427,Low,0.1552,none,60.359,1,4,Uptrend
+EA00428,Medium,0.4603,"high absences, downward trend",60.594,1,17,Downtrend
+EA00429,High,0.6486,"below average grade, multiple failures, high absences",57.329,6,22,Uptrend
+EA00430,Medium,0.341,below average grade,56.612,0,13,Uptrend
+EA00431,Medium,0.4961,"below average grade, multiple failures, high absences",57.778,2,23,Uptrend
+EA00432,Low,0.1438,none,68.8,0,12,Uptrend
+EA00433,Low,0.2492,none,61.631,0,14,Uptrend
+EA00434,Low,0.1643,high absences,68.072,0,23,Uptrend
+EA00435,Low,0.0964,downward trend,68.371,0,3,Downtrend
+EA00436,Medium,0.4184,"multiple failures, downward trend",61.128,2,6,Downtrend
+EA00437,Low,0.0869,none,64.006,0,9,Uptrend
+EA00438,Medium,0.3232,below average grade,57.406,1,7,Uptrend
+EA00439,High,0.6812,"multiple failures, high absences, downward trend",60.572,3,23,Downtrend
+EA00440,Medium,0.4838,"below average grade, multiple failures, high absences",57.062,2,18,Uptrend
+EA00441,Medium,0.3256,high absences,60.85,1,24,Uptrend
+EA00442,Low,0.2653,high absences,63.194,1,17,Uptrend
+EA00443,Low,0.1673,high absences,67.256,0,20,Uptrend
+EA00444,Low,0.0894,none,60.312,0,7,Uptrend
+EA00445,Low,0.1421,high absences,66.676,0,21,Uptrend
+EA00446,Medium,0.4686,"below average grade, multiple failures",58.919,2,12,Uptrend
+EA00447,Low,0.2461,downward trend,64.788,0,0,Downtrend
+EA00448,Low,0.1082,none,67.261,0,14,Uptrend
+EA00449,Low,0.1462,none,63.1,0,1,Uptrend
+EA00450,High,0.7276,"below average grade, multiple failures, high absences, downward trend",59.994,3,20,Downtrend
+EA00451,Low,0.1605,downward trend,66.406,0,3,Downtrend
+EA00452,Low,0.0,none,68.619,0,5,Uptrend
+EA00453,Low,0.2531,none,61.856,1,5,Uptrend
+EA00454,Low,0.0689,none,66.056,0,10,Uptrend
+EA00455,Low,0.0447,none,65.088,0,10,Uptrend
+EA00456,Low,0.2056,downward trend,66.65,0,2,Downtrend
+EA00457,Medium,0.3811,"below average grade, high absences",58.022,0,18,Uptrend
+EA00458,Medium,0.3832,"high absences, downward trend",60.188,0,22,Downtrend
+EA00459,Low,0.125,none,62.747,0,11,Uptrend
+EA00460,Low,0.2626,none,60.869,0,15,Uptrend
+EA00461,High,0.5714,"below average grade, multiple failures, high absences",55.389,3,22,Uptrend
+EA00462,Medium,0.4749,"below average grade, downward trend",57.75,1,10,Downtrend
+EA00463,Low,0.0393,none,66.267,0,1,Uptrend
+EA00464,Low,0.176,none,60.541,1,10,Uptrend
+EA00465,High,0.667,"below average grade, multiple failures, high absences",56.125,5,22,Uptrend
+EA00466,Medium,0.4738,"high absences, downward trend",63.729,1,24,Downtrend
+EA00467,Low,0.2775,high absences,62.531,0,17,Uptrend
+EA00468,Low,0.0978,none,68.825,0,11,Uptrend
+EA00469,Medium,0.3142,"below average grade, multiple failures",58.647,2,2,Uptrend
+EA00470,Low,0.1029,none,62.522,0,6,Uptrend
+EA00471,Medium,0.4819,"below average grade, high absences",57.588,1,22,Uptrend
+EA00472,Low,0.1687,downward trend,66.744,0,1,Downtrend
+EA00473,Low,0.1354,none,65.806,0,7,Uptrend
+EA00474,Medium,0.3792,"below average grade, downward trend",56.672,1,1,Downtrend
+EA00475,Medium,0.5001,"below average grade, multiple failures, high absences",59.139,2,21,Uptrend
+EA00476,Low,0.2591,downward trend,67.188,0,10,Downtrend
+EA00477,High,0.5743,"below average grade, multiple failures",57.089,3,11,Uptrend
+EA00478,Medium,0.3358,high absences,61.712,0,19,Uptrend
+EA00479,Low,0.2623,high absences,65.541,1,18,Uptrend
+EA00480,Medium,0.4069,"high absences, downward trend",64.494,0,18,Downtrend
+EA00481,Medium,0.4994,"below average grade, multiple failures",59.506,2,11,Uptrend
+EA00482,Low,0.0599,none,66.182,0,3,Uptrend
+EA00483,High,0.6017,"below average grade, high absences, downward trend",59.624,1,21,Downtrend
+EA00484,Medium,0.4428,"below average grade, multiple failures",59.812,2,1,Uptrend
+EA00485,Low,0.2983,multiple failures,60.847,2,4,Uptrend
+EA00486,Low,0.1266,none,60.812,0,5,Uptrend
+EA00487,Medium,0.4195,"multiple failures, high absences",60.029,2,20,Uptrend
+EA00488,Medium,0.4536,"below average grade, multiple failures",56.265,2,13,Uptrend
+EA00489,Low,0.2852,downward trend,64.459,0,7,Downtrend
+EA00490,Low,0.0946,none,61.881,0,4,Uptrend
+EA00491,High,0.5942,"below average grade, multiple failures, downward trend",57.935,2,12,Downtrend
+EA00492,Low,0.095,none,61.322,0,1,Uptrend
+EA00493,Medium,0.4553,"below average grade, high absences, downward trend",58.988,0,17,Downtrend
+EA00494,Low,0.0741,none,68.083,0,9,Uptrend
+EA00495,Medium,0.3434,"high absences, downward trend",66.131,0,17,Downtrend
+EA00496,Low,0.2977,"high absences, downward trend",68.7,0,24,Downtrend
+EA00497,Low,0.0977,none,62.481,0,5,Uptrend
+EA00498,Medium,0.4256,below average grade,58.541,1,9,Uptrend
+EA00499,Low,0.2162,none,61.975,0,10,Uptrend
+EA00500,Low,0.166,none,60.456,1,3,Uptrend

+ 501 - 0
internal/src/models/datasets/nlp_pred_technical_drawing.csv

@@ -0,0 +1,501 @@
+student_id,predicted_subject,predicted_value,prediction_target,reasoning_summary,confidence,observations,error
+EA00001,Technical_Drawing,47.81,Technical_Drawing_final,"Based on STEM avg=49.9, Fine Arts avg=50.7, attendance=24",0.83,Grade tendency: Uptrend. Support level: High.,
+EA00002,Technical_Drawing,59.76,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=62.3, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00003,Technical_Drawing,63.59,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=72.0, attendance=7",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00004,Technical_Drawing,56.67,Technical_Drawing_final,"Based on STEM avg=56.5, Fine Arts avg=60.0, attendance=18",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00005,Technical_Drawing,68.34,Technical_Drawing_final,"Based on STEM avg=64.0, Fine Arts avg=78.6, attendance=22",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00006,Technical_Drawing,55.56,Technical_Drawing_final,"Based on STEM avg=49.8, Fine Arts avg=71.7, attendance=16",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00007,Technical_Drawing,65.96,Technical_Drawing_final,"Based on STEM avg=62.8, Fine Arts avg=70.1, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00008,Technical_Drawing,59.95,Technical_Drawing_final,"Based on STEM avg=57.4, Fine Arts avg=63.5, attendance=0",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00009,Technical_Drawing,56.71,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=58.3, attendance=14",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00010,Technical_Drawing,59.89,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=63.3, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00011,Technical_Drawing,69.18,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=76.5, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00012,Technical_Drawing,64.4,Technical_Drawing_final,"Based on STEM avg=66.7, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00013,Technical_Drawing,66.06,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=73.1, attendance=1",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00014,Technical_Drawing,58.44,Technical_Drawing_final,"Based on STEM avg=56.5, Fine Arts avg=60.0, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00015,Technical_Drawing,57.88,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00016,Technical_Drawing,69.75,Technical_Drawing_final,"Based on STEM avg=68.2, Fine Arts avg=77.5, attendance=1",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00017,Technical_Drawing,61.17,Technical_Drawing_final,"Based on STEM avg=57.5, Fine Arts avg=69.1, attendance=15",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00018,Technical_Drawing,66.94,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=68.1, attendance=2",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00019,Technical_Drawing,57.79,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00020,Technical_Drawing,57.56,Technical_Drawing_final,"Based on STEM avg=54.5, Fine Arts avg=63.5, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00021,Technical_Drawing,62.04,Technical_Drawing_final,"Based on STEM avg=61.4, Fine Arts avg=60.0, attendance=0",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00022,Technical_Drawing,56.47,Technical_Drawing_final,"Based on STEM avg=61.5, Fine Arts avg=60.0, attendance=17",0.91,Grade tendency: Uptrend. Support level: High.,
+EA00023,Technical_Drawing,56.75,Technical_Drawing_final,"Based on STEM avg=57.5, Fine Arts avg=60.0, attendance=15",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00024,Technical_Drawing,65.28,Technical_Drawing_final,"Based on STEM avg=61.8, Fine Arts avg=72.2, attendance=13",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00025,Technical_Drawing,61.0,Technical_Drawing_final,"Based on STEM avg=60.9, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00026,Technical_Drawing,55.15,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=60.0, attendance=22",0.9,Grade tendency: Downtrend. Support level: Low.,
+EA00027,Technical_Drawing,67.54,Technical_Drawing_final,"Based on STEM avg=59.5, Fine Arts avg=77.7, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00028,Technical_Drawing,60.42,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=61.4, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00029,Technical_Drawing,58.41,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=66.1, attendance=10",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00030,Technical_Drawing,58.72,Technical_Drawing_final,"Based on STEM avg=58.4, Fine Arts avg=65.8, attendance=15",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00031,Technical_Drawing,57.69,Technical_Drawing_final,"Based on STEM avg=54.1, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00032,Technical_Drawing,66.01,Technical_Drawing_final,"Based on STEM avg=66.6, Fine Arts avg=75.8, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00033,Technical_Drawing,49.72,Technical_Drawing_final,"Based on STEM avg=50.8, Fine Arts avg=62.1, attendance=8",0.85,Grade tendency: Downtrend. Support level: Medium.,
+EA00034,Technical_Drawing,51.67,Technical_Drawing_final,"Based on STEM avg=50.0, Fine Arts avg=60.0, attendance=17",0.87,Grade tendency: Uptrend. Support level: High.,
+EA00035,Technical_Drawing,61.51,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=68.5, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00036,Technical_Drawing,63.97,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=64.4, attendance=11",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00037,Technical_Drawing,64.43,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=68.5, attendance=1",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00038,Technical_Drawing,52.19,Technical_Drawing_final,"Based on STEM avg=51.7, Fine Arts avg=60.0, attendance=3",0.87,Grade tendency: Downtrend. Support level: High.,
+EA00039,Technical_Drawing,57.54,Technical_Drawing_final,"Based on STEM avg=50.1, Fine Arts avg=65.3, attendance=10",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00040,Technical_Drawing,49.95,Technical_Drawing_final,"Based on STEM avg=52.4, Fine Arts avg=50.5, attendance=5",0.85,Grade tendency: Uptrend. Support level: Low.,
+EA00041,Technical_Drawing,53.39,Technical_Drawing_final,"Based on STEM avg=48.0, Fine Arts avg=60.1, attendance=6",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00042,Technical_Drawing,57.47,Technical_Drawing_final,"Based on STEM avg=56.0, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00043,Technical_Drawing,58.23,Technical_Drawing_final,"Based on STEM avg=60.0, Fine Arts avg=60.0, attendance=21",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00044,Technical_Drawing,69.45,Technical_Drawing_final,"Based on STEM avg=68.2, Fine Arts avg=69.2, attendance=8",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00045,Technical_Drawing,59.16,Technical_Drawing_final,"Based on STEM avg=56.8, Fine Arts avg=60.0, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00046,Technical_Drawing,53.89,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=52.5, attendance=7",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00047,Technical_Drawing,56.4,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=57.2, attendance=19",0.91,Grade tendency: Downtrend. Support level: Low.,
+EA00048,Technical_Drawing,67.59,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=78.0, attendance=21",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00049,Technical_Drawing,60.95,Technical_Drawing_final,"Based on STEM avg=62.3, Fine Arts avg=60.0, attendance=0",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00050,Technical_Drawing,54.99,Technical_Drawing_final,"Based on STEM avg=58.3, Fine Arts avg=58.3, attendance=23",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00051,Technical_Drawing,58.36,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=63.0, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00052,Technical_Drawing,59.0,Technical_Drawing_final,"Based on STEM avg=60.3, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00053,Technical_Drawing,57.73,Technical_Drawing_final,"Based on STEM avg=55.2, Fine Arts avg=63.6, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00054,Technical_Drawing,62.58,Technical_Drawing_final,"Based on STEM avg=56.7, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00055,Technical_Drawing,56.79,Technical_Drawing_final,"Based on STEM avg=56.0, Fine Arts avg=63.3, attendance=11",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00056,Technical_Drawing,56.73,Technical_Drawing_final,"Based on STEM avg=63.1, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00057,Technical_Drawing,64.18,Technical_Drawing_final,"Based on STEM avg=64.0, Fine Arts avg=66.2, attendance=22",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00058,Technical_Drawing,72.92,Technical_Drawing_final,"Based on STEM avg=68.6, Fine Arts avg=73.4, attendance=6",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00059,Technical_Drawing,58.78,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=63.0, attendance=20",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00060,Technical_Drawing,64.21,Technical_Drawing_final,"Based on STEM avg=65.3, Fine Arts avg=60.6, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00061,Technical_Drawing,68.59,Technical_Drawing_final,"Based on STEM avg=61.6, Fine Arts avg=75.6, attendance=18",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00062,Technical_Drawing,56.78,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=64.0, attendance=10",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00063,Technical_Drawing,66.59,Technical_Drawing_final,"Based on STEM avg=67.7, Fine Arts avg=74.2, attendance=19",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00064,Technical_Drawing,57.52,Technical_Drawing_final,"Based on STEM avg=57.4, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00065,Technical_Drawing,59.1,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=60.0, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00066,Technical_Drawing,61.66,Technical_Drawing_final,"Based on STEM avg=61.6, Fine Arts avg=71.3, attendance=22",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00067,Technical_Drawing,66.91,Technical_Drawing_final,"Based on STEM avg=63.7, Fine Arts avg=74.1, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00068,Technical_Drawing,68.43,Technical_Drawing_final,"Based on STEM avg=61.9, Fine Arts avg=77.4, attendance=6",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00069,Technical_Drawing,60.33,Technical_Drawing_final,"Based on STEM avg=61.8, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00070,Technical_Drawing,64.99,Technical_Drawing_final,"Based on STEM avg=62.4, Fine Arts avg=65.2, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00071,Technical_Drawing,62.48,Technical_Drawing_final,"Based on STEM avg=64.2, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00072,Technical_Drawing,63.07,Technical_Drawing_final,"Based on STEM avg=57.5, Fine Arts avg=61.8, attendance=11",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00073,Technical_Drawing,54.36,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=58.0, attendance=17",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00074,Technical_Drawing,66.21,Technical_Drawing_final,"Based on STEM avg=65.9, Fine Arts avg=68.2, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00075,Technical_Drawing,58.39,Technical_Drawing_final,"Based on STEM avg=55.7, Fine Arts avg=68.0, attendance=11",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00076,Technical_Drawing,60.25,Technical_Drawing_final,"Based on STEM avg=67.2, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00077,Technical_Drawing,73.54,Technical_Drawing_final,"Based on STEM avg=64.1, Fine Arts avg=81.4, attendance=7",0.91,Grade tendency: Uptrend. Support level: Low.,
+EA00078,Technical_Drawing,56.97,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=59.1, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00079,Technical_Drawing,65.11,Technical_Drawing_final,"Based on STEM avg=63.2, Fine Arts avg=70.1, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00080,Technical_Drawing,66.79,Technical_Drawing_final,"Based on STEM avg=67.4, Fine Arts avg=66.4, attendance=15",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00081,Technical_Drawing,64.54,Technical_Drawing_final,"Based on STEM avg=56.7, Fine Arts avg=75.9, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00082,Technical_Drawing,58.49,Technical_Drawing_final,"Based on STEM avg=62.0, Fine Arts avg=58.1, attendance=17",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00083,Technical_Drawing,52.14,Technical_Drawing_final,"Based on STEM avg=53.7, Fine Arts avg=60.0, attendance=13",0.87,Grade tendency: Uptrend. Support level: Medium.,
+EA00084,Technical_Drawing,60.41,Technical_Drawing_final,"Based on STEM avg=66.4, Fine Arts avg=61.8, attendance=19",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00085,Technical_Drawing,68.66,Technical_Drawing_final,"Based on STEM avg=67.1, Fine Arts avg=73.9, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00086,Technical_Drawing,55.86,Technical_Drawing_final,"Based on STEM avg=54.0, Fine Arts avg=60.0, attendance=9",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00087,Technical_Drawing,64.3,Technical_Drawing_final,"Based on STEM avg=61.2, Fine Arts avg=69.8, attendance=20",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00088,Technical_Drawing,67.19,Technical_Drawing_final,"Based on STEM avg=66.6, Fine Arts avg=69.4, attendance=12",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00089,Technical_Drawing,57.62,Technical_Drawing_final,"Based on STEM avg=58.5, Fine Arts avg=60.0, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00090,Technical_Drawing,66.67,Technical_Drawing_final,"Based on STEM avg=53.7, Fine Arts avg=81.8, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00091,Technical_Drawing,65.17,Technical_Drawing_final,"Based on STEM avg=59.6, Fine Arts avg=72.0, attendance=15",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00092,Technical_Drawing,52.64,Technical_Drawing_final,"Based on STEM avg=52.3, Fine Arts avg=56.1, attendance=22",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00093,Technical_Drawing,61.64,Technical_Drawing_final,"Based on STEM avg=60.2, Fine Arts avg=66.7, attendance=21",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00094,Technical_Drawing,63.04,Technical_Drawing_final,"Based on STEM avg=60.8, Fine Arts avg=72.4, attendance=8",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00095,Technical_Drawing,68.3,Technical_Drawing_final,"Based on STEM avg=65.1, Fine Arts avg=69.4, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00096,Technical_Drawing,60.33,Technical_Drawing_final,"Based on STEM avg=53.5, Fine Arts avg=66.8, attendance=1",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00097,Technical_Drawing,71.87,Technical_Drawing_final,"Based on STEM avg=61.9, Fine Arts avg=81.2, attendance=10",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00098,Technical_Drawing,58.06,Technical_Drawing_final,"Based on STEM avg=57.6, Fine Arts avg=60.0, attendance=9",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00099,Technical_Drawing,58.46,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=60.0, attendance=24",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00100,Technical_Drawing,60.03,Technical_Drawing_final,"Based on STEM avg=66.3, Fine Arts avg=60.0, attendance=19",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00101,Technical_Drawing,66.98,Technical_Drawing_final,"Based on STEM avg=64.1, Fine Arts avg=68.6, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00102,Technical_Drawing,56.68,Technical_Drawing_final,"Based on STEM avg=54.4, Fine Arts avg=60.7, attendance=9",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00103,Technical_Drawing,61.65,Technical_Drawing_final,"Based on STEM avg=66.0, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00104,Technical_Drawing,52.66,Technical_Drawing_final,"Based on STEM avg=52.3, Fine Arts avg=60.0, attendance=13",0.88,Grade tendency: Uptrend. Support level: Medium.,
+EA00105,Technical_Drawing,63.47,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=67.2, attendance=2",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00106,Technical_Drawing,70.51,Technical_Drawing_final,"Based on STEM avg=61.8, Fine Arts avg=74.9, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00107,Technical_Drawing,64.29,Technical_Drawing_final,"Based on STEM avg=67.6, Fine Arts avg=60.0, attendance=18",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00108,Technical_Drawing,55.87,Technical_Drawing_final,"Based on STEM avg=53.1, Fine Arts avg=59.7, attendance=8",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00109,Technical_Drawing,51.29,Technical_Drawing_final,"Based on STEM avg=53.4, Fine Arts avg=60.0, attendance=9",0.86,Grade tendency: Uptrend. Support level: High.,
+EA00110,Technical_Drawing,62.07,Technical_Drawing_final,"Based on STEM avg=61.2, Fine Arts avg=63.4, attendance=10",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00111,Technical_Drawing,60.32,Technical_Drawing_final,"Based on STEM avg=62.9, Fine Arts avg=54.6, attendance=1",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00112,Technical_Drawing,59.7,Technical_Drawing_final,"Based on STEM avg=53.6, Fine Arts avg=69.2, attendance=6",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00113,Technical_Drawing,61.15,Technical_Drawing_final,"Based on STEM avg=60.9, Fine Arts avg=65.5, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00114,Technical_Drawing,58.12,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=60.0, attendance=19",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00115,Technical_Drawing,60.76,Technical_Drawing_final,"Based on STEM avg=61.5, Fine Arts avg=51.4, attendance=1",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00116,Technical_Drawing,55.62,Technical_Drawing_final,"Based on STEM avg=52.5, Fine Arts avg=63.4, attendance=3",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00117,Technical_Drawing,54.17,Technical_Drawing_final,"Based on STEM avg=54.4, Fine Arts avg=60.0, attendance=24",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00118,Technical_Drawing,57.12,Technical_Drawing_final,"Based on STEM avg=56.1, Fine Arts avg=60.0, attendance=21",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00119,Technical_Drawing,56.2,Technical_Drawing_final,"Based on STEM avg=57.8, Fine Arts avg=60.0, attendance=19",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00120,Technical_Drawing,56.94,Technical_Drawing_final,"Based on STEM avg=58.4, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00121,Technical_Drawing,58.27,Technical_Drawing_final,"Based on STEM avg=59.9, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00122,Technical_Drawing,53.5,Technical_Drawing_final,"Based on STEM avg=55.3, Fine Arts avg=56.2, attendance=4",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00123,Technical_Drawing,76.08,Technical_Drawing_final,"Based on STEM avg=69.8, Fine Arts avg=86.9, attendance=11",0.89,Grade tendency: Downtrend. Support level: Medium.,
+EA00124,Technical_Drawing,53.05,Technical_Drawing_final,"Based on STEM avg=50.3, Fine Arts avg=60.0, attendance=2",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00125,Technical_Drawing,60.72,Technical_Drawing_final,"Based on STEM avg=61.5, Fine Arts avg=60.0, attendance=22",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00126,Technical_Drawing,68.96,Technical_Drawing_final,"Based on STEM avg=64.6, Fine Arts avg=76.6, attendance=7",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00127,Technical_Drawing,60.84,Technical_Drawing_final,"Based on STEM avg=52.3, Fine Arts avg=63.2, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00128,Technical_Drawing,56.71,Technical_Drawing_final,"Based on STEM avg=55.1, Fine Arts avg=56.7, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00129,Technical_Drawing,70.95,Technical_Drawing_final,"Based on STEM avg=64.3, Fine Arts avg=75.4, attendance=9",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00130,Technical_Drawing,60.59,Technical_Drawing_final,"Based on STEM avg=59.8, Fine Arts avg=71.4, attendance=20",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00131,Technical_Drawing,62.07,Technical_Drawing_final,"Based on STEM avg=64.8, Fine Arts avg=60.0, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00132,Technical_Drawing,68.0,Technical_Drawing_final,"Based on STEM avg=62.8, Fine Arts avg=75.2, attendance=12",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00133,Technical_Drawing,63.82,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=68.5, attendance=23",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00134,Technical_Drawing,59.16,Technical_Drawing_final,"Based on STEM avg=60.9, Fine Arts avg=63.2, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00135,Technical_Drawing,61.8,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=64.8, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00136,Technical_Drawing,60.65,Technical_Drawing_final,"Based on STEM avg=59.2, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00137,Technical_Drawing,60.99,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=57.2, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00138,Technical_Drawing,65.35,Technical_Drawing_final,"Based on STEM avg=65.9, Fine Arts avg=66.6, attendance=12",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00139,Technical_Drawing,58.1,Technical_Drawing_final,"Based on STEM avg=55.2, Fine Arts avg=67.0, attendance=19",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00140,Technical_Drawing,56.22,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=59.0, attendance=21",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00141,Technical_Drawing,53.0,Technical_Drawing_final,"Based on STEM avg=52.9, Fine Arts avg=60.0, attendance=3",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00142,Technical_Drawing,55.13,Technical_Drawing_final,"Based on STEM avg=53.3, Fine Arts avg=60.0, attendance=7",0.9,Grade tendency: Downtrend. Support level: High.,
+EA00143,Technical_Drawing,57.31,Technical_Drawing_final,"Based on STEM avg=59.2, Fine Arts avg=59.5, attendance=21",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00144,Technical_Drawing,66.85,Technical_Drawing_final,"Based on STEM avg=55.3, Fine Arts avg=71.8, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00145,Technical_Drawing,59.42,Technical_Drawing_final,"Based on STEM avg=57.2, Fine Arts avg=66.1, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00146,Technical_Drawing,57.06,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=54.8, attendance=9",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00147,Technical_Drawing,57.68,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=60.0, attendance=12",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00148,Technical_Drawing,62.55,Technical_Drawing_final,"Based on STEM avg=64.7, Fine Arts avg=68.2, attendance=24",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00149,Technical_Drawing,58.64,Technical_Drawing_final,"Based on STEM avg=52.9, Fine Arts avg=68.8, attendance=14",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00150,Technical_Drawing,55.87,Technical_Drawing_final,"Based on STEM avg=55.6, Fine Arts avg=60.0, attendance=21",0.91,Grade tendency: Downtrend. Support level: High.,
+EA00151,Technical_Drawing,62.34,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=60.6, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00152,Technical_Drawing,67.19,Technical_Drawing_final,"Based on STEM avg=61.4, Fine Arts avg=74.1, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00153,Technical_Drawing,55.25,Technical_Drawing_final,"Based on STEM avg=58.5, Fine Arts avg=60.2, attendance=21",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00154,Technical_Drawing,70.55,Technical_Drawing_final,"Based on STEM avg=63.5, Fine Arts avg=76.2, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00155,Technical_Drawing,62.44,Technical_Drawing_final,"Based on STEM avg=64.2, Fine Arts avg=60.0, attendance=1",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00156,Technical_Drawing,53.33,Technical_Drawing_final,"Based on STEM avg=53.2, Fine Arts avg=55.8, attendance=20",0.88,Grade tendency: Uptrend. Support level: Medium.,
+EA00157,Technical_Drawing,67.39,Technical_Drawing_final,"Based on STEM avg=62.1, Fine Arts avg=76.3, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00158,Technical_Drawing,65.32,Technical_Drawing_final,"Based on STEM avg=60.9, Fine Arts avg=66.7, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00159,Technical_Drawing,61.73,Technical_Drawing_final,"Based on STEM avg=56.4, Fine Arts avg=70.3, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00160,Technical_Drawing,62.47,Technical_Drawing_final,"Based on STEM avg=64.5, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00161,Technical_Drawing,62.61,Technical_Drawing_final,"Based on STEM avg=55.5, Fine Arts avg=65.8, attendance=1",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00162,Technical_Drawing,55.11,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=53.4, attendance=7",0.9,Grade tendency: Downtrend. Support level: Medium.,
+EA00163,Technical_Drawing,66.03,Technical_Drawing_final,"Based on STEM avg=68.7, Fine Arts avg=62.8, attendance=7",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00164,Technical_Drawing,67.97,Technical_Drawing_final,"Based on STEM avg=64.5, Fine Arts avg=63.2, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00165,Technical_Drawing,60.18,Technical_Drawing_final,"Based on STEM avg=63.9, Fine Arts avg=59.4, attendance=16",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00166,Technical_Drawing,61.12,Technical_Drawing_final,"Based on STEM avg=54.7, Fine Arts avg=70.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00167,Technical_Drawing,60.63,Technical_Drawing_final,"Based on STEM avg=61.3, Fine Arts avg=60.0, attendance=9",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00168,Technical_Drawing,62.26,Technical_Drawing_final,"Based on STEM avg=58.3, Fine Arts avg=61.6, attendance=4",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00169,Technical_Drawing,61.92,Technical_Drawing_final,"Based on STEM avg=59.9, Fine Arts avg=63.4, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00170,Technical_Drawing,61.35,Technical_Drawing_final,"Based on STEM avg=63.3, Fine Arts avg=60.0, attendance=16",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00171,Technical_Drawing,59.74,Technical_Drawing_final,"Based on STEM avg=53.2, Fine Arts avg=63.2, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00172,Technical_Drawing,62.83,Technical_Drawing_final,"Based on STEM avg=67.1, Fine Arts avg=60.0, attendance=8",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00173,Technical_Drawing,73.22,Technical_Drawing_final,"Based on STEM avg=67.3, Fine Arts avg=89.9, attendance=16",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00174,Technical_Drawing,65.59,Technical_Drawing_final,"Based on STEM avg=66.4, Fine Arts avg=65.3, attendance=14",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00175,Technical_Drawing,53.34,Technical_Drawing_final,"Based on STEM avg=50.8, Fine Arts avg=60.0, attendance=23",0.88,Grade tendency: Uptrend. Support level: Medium.,
+EA00176,Technical_Drawing,64.93,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=75.7, attendance=5",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00177,Technical_Drawing,62.89,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=68.2, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00178,Technical_Drawing,57.36,Technical_Drawing_final,"Based on STEM avg=55.3, Fine Arts avg=60.9, attendance=9",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00179,Technical_Drawing,63.78,Technical_Drawing_final,"Based on STEM avg=66.3, Fine Arts avg=63.6, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00180,Technical_Drawing,67.06,Technical_Drawing_final,"Based on STEM avg=60.3, Fine Arts avg=73.7, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00181,Technical_Drawing,63.46,Technical_Drawing_final,"Based on STEM avg=67.1, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00182,Technical_Drawing,53.12,Technical_Drawing_final,"Based on STEM avg=51.4, Fine Arts avg=60.0, attendance=0",0.88,Grade tendency: Uptrend. Support level: Medium.,
+EA00183,Technical_Drawing,51.77,Technical_Drawing_final,"Based on STEM avg=52.2, Fine Arts avg=52.9, attendance=17",0.87,Grade tendency: Uptrend. Support level: Medium.,
+EA00184,Technical_Drawing,65.15,Technical_Drawing_final,"Based on STEM avg=62.4, Fine Arts avg=75.2, attendance=20",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00185,Technical_Drawing,52.48,Technical_Drawing_final,"Based on STEM avg=49.3, Fine Arts avg=59.8, attendance=4",0.87,Grade tendency: Uptrend. Support level: High.,
+EA00186,Technical_Drawing,70.66,Technical_Drawing_final,"Based on STEM avg=64.0, Fine Arts avg=75.6, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00187,Technical_Drawing,55.24,Technical_Drawing_final,"Based on STEM avg=49.8, Fine Arts avg=64.2, attendance=14",0.9,Grade tendency: Downtrend. Support level: Low.,
+EA00188,Technical_Drawing,58.8,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=60.0, attendance=16",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00189,Technical_Drawing,60.91,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=64.8, attendance=14",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00190,Technical_Drawing,55.68,Technical_Drawing_final,"Based on STEM avg=51.5, Fine Arts avg=60.2, attendance=7",0.91,Grade tendency: Downtrend. Support level: High.,
+EA00191,Technical_Drawing,63.5,Technical_Drawing_final,"Based on STEM avg=67.8, Fine Arts avg=60.0, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00192,Technical_Drawing,60.81,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=60.5, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00193,Technical_Drawing,58.66,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=61.8, attendance=11",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00194,Technical_Drawing,72.25,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=81.1, attendance=21",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00195,Technical_Drawing,60.83,Technical_Drawing_final,"Based on STEM avg=67.0, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00196,Technical_Drawing,61.78,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=60.0, attendance=8",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00197,Technical_Drawing,51.95,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=59.6, attendance=19",0.87,Grade tendency: Downtrend. Support level: High.,
+EA00198,Technical_Drawing,65.92,Technical_Drawing_final,"Based on STEM avg=64.3, Fine Arts avg=68.7, attendance=0",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00199,Technical_Drawing,57.19,Technical_Drawing_final,"Based on STEM avg=51.8, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00200,Technical_Drawing,55.24,Technical_Drawing_final,"Based on STEM avg=50.3, Fine Arts avg=60.5, attendance=1",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00201,Technical_Drawing,59.79,Technical_Drawing_final,"Based on STEM avg=51.7, Fine Arts avg=61.5, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00202,Technical_Drawing,61.37,Technical_Drawing_final,"Based on STEM avg=59.7, Fine Arts avg=65.1, attendance=6",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00203,Technical_Drawing,56.82,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00204,Technical_Drawing,61.82,Technical_Drawing_final,"Based on STEM avg=61.0, Fine Arts avg=65.1, attendance=8",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00205,Technical_Drawing,66.5,Technical_Drawing_final,"Based on STEM avg=61.9, Fine Arts avg=79.1, attendance=10",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00206,Technical_Drawing,60.35,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=69.5, attendance=22",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00207,Technical_Drawing,55.34,Technical_Drawing_final,"Based on STEM avg=53.8, Fine Arts avg=61.7, attendance=15",0.9,Grade tendency: Uptrend. Support level: High.,
+EA00208,Technical_Drawing,70.08,Technical_Drawing_final,"Based on STEM avg=59.2, Fine Arts avg=83.1, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00209,Technical_Drawing,54.58,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=60.0, attendance=23",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00210,Technical_Drawing,63.91,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=64.9, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00211,Technical_Drawing,55.77,Technical_Drawing_final,"Based on STEM avg=53.2, Fine Arts avg=59.0, attendance=2",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00212,Technical_Drawing,72.05,Technical_Drawing_final,"Based on STEM avg=65.1, Fine Arts avg=76.5, attendance=7",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00213,Technical_Drawing,51.97,Technical_Drawing_final,"Based on STEM avg=52.3, Fine Arts avg=60.0, attendance=21",0.87,Grade tendency: Uptrend. Support level: Medium.,
+EA00214,Technical_Drawing,64.11,Technical_Drawing_final,"Based on STEM avg=61.2, Fine Arts avg=70.9, attendance=15",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00215,Technical_Drawing,64.45,Technical_Drawing_final,"Based on STEM avg=60.0, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00216,Technical_Drawing,52.88,Technical_Drawing_final,"Based on STEM avg=53.6, Fine Arts avg=60.0, attendance=13",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00217,Technical_Drawing,56.62,Technical_Drawing_final,"Based on STEM avg=60.4, Fine Arts avg=60.0, attendance=12",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00218,Technical_Drawing,59.18,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=69.0, attendance=22",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00219,Technical_Drawing,65.08,Technical_Drawing_final,"Based on STEM avg=64.2, Fine Arts avg=65.2, attendance=10",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00220,Technical_Drawing,63.4,Technical_Drawing_final,"Based on STEM avg=57.2, Fine Arts avg=71.1, attendance=9",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00221,Technical_Drawing,63.79,Technical_Drawing_final,"Based on STEM avg=54.9, Fine Arts avg=68.4, attendance=0",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00222,Technical_Drawing,63.58,Technical_Drawing_final,"Based on STEM avg=60.3, Fine Arts avg=71.1, attendance=12",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00223,Technical_Drawing,55.09,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=60.0, attendance=22",0.9,Grade tendency: Uptrend. Support level: Low.,
+EA00224,Technical_Drawing,54.25,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=60.0, attendance=22",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00225,Technical_Drawing,60.45,Technical_Drawing_final,"Based on STEM avg=57.3, Fine Arts avg=60.9, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00226,Technical_Drawing,64.37,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=71.9, attendance=8",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00227,Technical_Drawing,58.81,Technical_Drawing_final,"Based on STEM avg=57.4, Fine Arts avg=68.8, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00228,Technical_Drawing,65.34,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=73.9, attendance=10",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00229,Technical_Drawing,64.77,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=66.2, attendance=6",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00230,Technical_Drawing,55.23,Technical_Drawing_final,"Based on STEM avg=51.5, Fine Arts avg=58.4, attendance=24",0.9,Grade tendency: Downtrend. Support level: High.,
+EA00231,Technical_Drawing,60.24,Technical_Drawing_final,"Based on STEM avg=56.7, Fine Arts avg=64.9, attendance=8",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00232,Technical_Drawing,66.37,Technical_Drawing_final,"Based on STEM avg=64.4, Fine Arts avg=71.8, attendance=12",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00233,Technical_Drawing,58.42,Technical_Drawing_final,"Based on STEM avg=63.2, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00234,Technical_Drawing,56.25,Technical_Drawing_final,"Based on STEM avg=59.7, Fine Arts avg=55.9, attendance=6",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00235,Technical_Drawing,57.05,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00236,Technical_Drawing,64.32,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=67.4, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00237,Technical_Drawing,56.65,Technical_Drawing_final,"Based on STEM avg=56.2, Fine Arts avg=61.5, attendance=24",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00238,Technical_Drawing,54.43,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=53.9, attendance=2",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00239,Technical_Drawing,53.66,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=52.0, attendance=3",0.89,Grade tendency: Uptrend. Support level: Low.,
+EA00240,Technical_Drawing,56.43,Technical_Drawing_final,"Based on STEM avg=54.9, Fine Arts avg=62.8, attendance=1",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00241,Technical_Drawing,59.52,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=61.9, attendance=1",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00242,Technical_Drawing,63.86,Technical_Drawing_final,"Based on STEM avg=59.2, Fine Arts avg=68.4, attendance=1",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00243,Technical_Drawing,53.78,Technical_Drawing_final,"Based on STEM avg=57.9, Fine Arts avg=60.3, attendance=13",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00244,Technical_Drawing,59.78,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=65.2, attendance=15",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00245,Technical_Drawing,58.18,Technical_Drawing_final,"Based on STEM avg=60.2, Fine Arts avg=60.0, attendance=17",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00246,Technical_Drawing,65.69,Technical_Drawing_final,"Based on STEM avg=64.9, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00247,Technical_Drawing,57.26,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00248,Technical_Drawing,59.63,Technical_Drawing_final,"Based on STEM avg=53.2, Fine Arts avg=64.5, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00249,Technical_Drawing,61.95,Technical_Drawing_final,"Based on STEM avg=63.4, Fine Arts avg=60.0, attendance=0",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00250,Technical_Drawing,54.44,Technical_Drawing_final,"Based on STEM avg=55.3, Fine Arts avg=60.0, attendance=16",0.89,Grade tendency: Uptrend. Support level: High.,
+EA00251,Technical_Drawing,57.18,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=60.0, attendance=8",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00252,Technical_Drawing,64.58,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=67.7, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00253,Technical_Drawing,52.01,Technical_Drawing_final,"Based on STEM avg=53.3, Fine Arts avg=52.6, attendance=17",0.87,Grade tendency: Downtrend. Support level: High.,
+EA00254,Technical_Drawing,64.69,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=74.8, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00255,Technical_Drawing,67.55,Technical_Drawing_final,"Based on STEM avg=64.9, Fine Arts avg=60.0, attendance=8",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00256,Technical_Drawing,58.22,Technical_Drawing_final,"Based on STEM avg=59.0, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00257,Technical_Drawing,64.89,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=64.6, attendance=9",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00258,Technical_Drawing,54.55,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=56.3, attendance=18",0.9,Grade tendency: Downtrend. Support level: Medium.,
+EA00259,Technical_Drawing,65.86,Technical_Drawing_final,"Based on STEM avg=63.8, Fine Arts avg=71.5, attendance=4",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00260,Technical_Drawing,61.87,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=63.6, attendance=18",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00261,Technical_Drawing,59.17,Technical_Drawing_final,"Based on STEM avg=53.1, Fine Arts avg=60.7, attendance=7",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00262,Technical_Drawing,55.85,Technical_Drawing_final,"Based on STEM avg=61.0, Fine Arts avg=60.0, attendance=21",0.91,Grade tendency: Uptrend. Support level: High.,
+EA00263,Technical_Drawing,64.69,Technical_Drawing_final,"Based on STEM avg=59.6, Fine Arts avg=71.8, attendance=4",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00264,Technical_Drawing,59.17,Technical_Drawing_final,"Based on STEM avg=52.4, Fine Arts avg=66.2, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00265,Technical_Drawing,58.12,Technical_Drawing_final,"Based on STEM avg=51.0, Fine Arts avg=67.4, attendance=18",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00266,Technical_Drawing,52.61,Technical_Drawing_final,"Based on STEM avg=49.1, Fine Arts avg=69.0, attendance=23",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00267,Technical_Drawing,52.23,Technical_Drawing_final,"Based on STEM avg=57.8, Fine Arts avg=58.6, attendance=14",0.87,Grade tendency: Downtrend. Support level: Low.,
+EA00268,Technical_Drawing,60.74,Technical_Drawing_final,"Based on STEM avg=60.4, Fine Arts avg=73.7, attendance=21",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00269,Technical_Drawing,54.93,Technical_Drawing_final,"Based on STEM avg=51.9, Fine Arts avg=61.5, attendance=14",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00270,Technical_Drawing,64.43,Technical_Drawing_final,"Based on STEM avg=61.5, Fine Arts avg=70.8, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00271,Technical_Drawing,59.56,Technical_Drawing_final,"Based on STEM avg=60.3, Fine Arts avg=63.2, attendance=18",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00272,Technical_Drawing,60.64,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=68.3, attendance=0",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00273,Technical_Drawing,53.02,Technical_Drawing_final,"Based on STEM avg=54.4, Fine Arts avg=60.0, attendance=21",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00274,Technical_Drawing,52.3,Technical_Drawing_final,"Based on STEM avg=57.7, Fine Arts avg=57.6, attendance=24",0.87,Grade tendency: Uptrend. Support level: Low.,
+EA00275,Technical_Drawing,56.92,Technical_Drawing_final,"Based on STEM avg=53.9, Fine Arts avg=60.0, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00276,Technical_Drawing,58.8,Technical_Drawing_final,"Based on STEM avg=57.5, Fine Arts avg=60.4, attendance=8",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00277,Technical_Drawing,63.34,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=66.3, attendance=15",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00278,Technical_Drawing,61.87,Technical_Drawing_final,"Based on STEM avg=61.0, Fine Arts avg=60.8, attendance=0",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00279,Technical_Drawing,57.07,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=60.0, attendance=22",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00280,Technical_Drawing,62.19,Technical_Drawing_final,"Based on STEM avg=55.0, Fine Arts avg=68.5, attendance=15",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00281,Technical_Drawing,59.89,Technical_Drawing_final,"Based on STEM avg=51.0, Fine Arts avg=70.4, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00282,Technical_Drawing,56.83,Technical_Drawing_final,"Based on STEM avg=55.2, Fine Arts avg=64.5, attendance=7",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00283,Technical_Drawing,62.62,Technical_Drawing_final,"Based on STEM avg=67.8, Fine Arts avg=65.0, attendance=23",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00284,Technical_Drawing,57.47,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00285,Technical_Drawing,51.77,Technical_Drawing_final,"Based on STEM avg=57.6, Fine Arts avg=56.2, attendance=9",0.87,Grade tendency: Downtrend. Support level: Low.,
+EA00286,Technical_Drawing,56.82,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00287,Technical_Drawing,56.91,Technical_Drawing_final,"Based on STEM avg=60.5, Fine Arts avg=52.6, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00288,Technical_Drawing,52.67,Technical_Drawing_final,"Based on STEM avg=56.1, Fine Arts avg=50.1, attendance=3",0.88,Grade tendency: Uptrend. Support level: High.,
+EA00289,Technical_Drawing,58.21,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=60.0, attendance=15",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00290,Technical_Drawing,59.51,Technical_Drawing_final,"Based on STEM avg=57.2, Fine Arts avg=60.8, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00291,Technical_Drawing,66.99,Technical_Drawing_final,"Based on STEM avg=66.5, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00292,Technical_Drawing,61.97,Technical_Drawing_final,"Based on STEM avg=66.7, Fine Arts avg=60.0, attendance=19",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00293,Technical_Drawing,56.7,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00294,Technical_Drawing,64.12,Technical_Drawing_final,"Based on STEM avg=65.4, Fine Arts avg=68.5, attendance=9",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00295,Technical_Drawing,67.44,Technical_Drawing_final,"Based on STEM avg=65.0, Fine Arts avg=71.6, attendance=10",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00296,Technical_Drawing,63.92,Technical_Drawing_final,"Based on STEM avg=63.1, Fine Arts avg=69.8, attendance=2",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00297,Technical_Drawing,67.57,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=75.5, attendance=4",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00298,Technical_Drawing,58.4,Technical_Drawing_final,"Based on STEM avg=56.8, Fine Arts avg=62.6, attendance=8",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00299,Technical_Drawing,69.51,Technical_Drawing_final,"Based on STEM avg=63.9, Fine Arts avg=68.6, attendance=1",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00300,Technical_Drawing,57.57,Technical_Drawing_final,"Based on STEM avg=53.1, Fine Arts avg=64.1, attendance=21",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00301,Technical_Drawing,58.9,Technical_Drawing_final,"Based on STEM avg=54.2, Fine Arts avg=63.2, attendance=17",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00302,Technical_Drawing,59.36,Technical_Drawing_final,"Based on STEM avg=57.3, Fine Arts avg=65.5, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00303,Technical_Drawing,60.07,Technical_Drawing_final,"Based on STEM avg=53.5, Fine Arts avg=65.1, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00304,Technical_Drawing,54.58,Technical_Drawing_final,"Based on STEM avg=54.3, Fine Arts avg=60.0, attendance=6",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00305,Technical_Drawing,63.46,Technical_Drawing_final,"Based on STEM avg=59.0, Fine Arts avg=70.7, attendance=18",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00306,Technical_Drawing,58.21,Technical_Drawing_final,"Based on STEM avg=58.5, Fine Arts avg=60.0, attendance=21",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00307,Technical_Drawing,60.25,Technical_Drawing_final,"Based on STEM avg=66.3, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00308,Technical_Drawing,62.12,Technical_Drawing_final,"Based on STEM avg=58.4, Fine Arts avg=60.0, attendance=9",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00309,Technical_Drawing,57.45,Technical_Drawing_final,"Based on STEM avg=55.7, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00310,Technical_Drawing,58.11,Technical_Drawing_final,"Based on STEM avg=66.8, Fine Arts avg=62.2, attendance=22",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00311,Technical_Drawing,55.97,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=60.0, attendance=0",0.91,Grade tendency: Downtrend. Support level: High.,
+EA00312,Technical_Drawing,65.97,Technical_Drawing_final,"Based on STEM avg=63.3, Fine Arts avg=75.7, attendance=24",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00313,Technical_Drawing,57.79,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00314,Technical_Drawing,57.0,Technical_Drawing_final,"Based on STEM avg=54.9, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00315,Technical_Drawing,61.75,Technical_Drawing_final,"Based on STEM avg=66.0, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00316,Technical_Drawing,50.21,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=52.2, attendance=20",0.85,Grade tendency: Uptrend. Support level: Low.,
+EA00317,Technical_Drawing,57.98,Technical_Drawing_final,"Based on STEM avg=59.6, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00318,Technical_Drawing,59.51,Technical_Drawing_final,"Based on STEM avg=57.7, Fine Arts avg=60.0, attendance=12",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00319,Technical_Drawing,57.49,Technical_Drawing_final,"Based on STEM avg=56.0, Fine Arts avg=60.0, attendance=18",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00320,Technical_Drawing,69.84,Technical_Drawing_final,"Based on STEM avg=63.5, Fine Arts avg=73.7, attendance=18",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00321,Technical_Drawing,62.59,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=67.9, attendance=21",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00322,Technical_Drawing,55.89,Technical_Drawing_final,"Based on STEM avg=56.1, Fine Arts avg=63.9, attendance=7",0.91,Grade tendency: Uptrend. Support level: Low.,
+EA00323,Technical_Drawing,70.21,Technical_Drawing_final,"Based on STEM avg=66.1, Fine Arts avg=72.8, attendance=5",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00324,Technical_Drawing,53.4,Technical_Drawing_final,"Based on STEM avg=59.1, Fine Arts avg=58.6, attendance=21",0.88,Grade tendency: Uptrend. Support level: Medium.,
+EA00325,Technical_Drawing,57.58,Technical_Drawing_final,"Based on STEM avg=53.9, Fine Arts avg=60.0, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00326,Technical_Drawing,68.94,Technical_Drawing_final,"Based on STEM avg=72.2, Fine Arts avg=65.7, attendance=9",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00327,Technical_Drawing,54.66,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=57.9, attendance=13",0.9,Grade tendency: Uptrend. Support level: High.,
+EA00328,Technical_Drawing,66.18,Technical_Drawing_final,"Based on STEM avg=54.5, Fine Arts avg=72.8, attendance=8",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00329,Technical_Drawing,56.39,Technical_Drawing_final,"Based on STEM avg=49.3, Fine Arts avg=65.3, attendance=13",0.91,Grade tendency: Uptrend. Support level: High.,
+EA00330,Technical_Drawing,52.86,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=60.0, attendance=21",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00331,Technical_Drawing,69.96,Technical_Drawing_final,"Based on STEM avg=68.0, Fine Arts avg=74.7, attendance=9",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00332,Technical_Drawing,58.53,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=60.0, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00333,Technical_Drawing,60.64,Technical_Drawing_final,"Based on STEM avg=60.0, Fine Arts avg=60.0, attendance=3",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00334,Technical_Drawing,65.91,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=76.7, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00335,Technical_Drawing,67.41,Technical_Drawing_final,"Based on STEM avg=59.3, Fine Arts avg=72.3, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00336,Technical_Drawing,58.99,Technical_Drawing_final,"Based on STEM avg=61.7, Fine Arts avg=64.3, attendance=15",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00337,Technical_Drawing,57.64,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00338,Technical_Drawing,60.6,Technical_Drawing_final,"Based on STEM avg=56.4, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00339,Technical_Drawing,51.16,Technical_Drawing_final,"Based on STEM avg=50.0, Fine Arts avg=59.1, attendance=16",0.86,Grade tendency: Uptrend. Support level: Medium.,
+EA00340,Technical_Drawing,54.08,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=54.8, attendance=20",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00341,Technical_Drawing,59.82,Technical_Drawing_final,"Based on STEM avg=61.8, Fine Arts avg=60.0, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00342,Technical_Drawing,68.32,Technical_Drawing_final,"Based on STEM avg=70.6, Fine Arts avg=71.9, attendance=20",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00343,Technical_Drawing,58.52,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00344,Technical_Drawing,57.77,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=61.8, attendance=4",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00345,Technical_Drawing,55.43,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=60.0, attendance=19",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00346,Technical_Drawing,56.82,Technical_Drawing_final,"Based on STEM avg=54.1, Fine Arts avg=60.0, attendance=20",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00347,Technical_Drawing,58.36,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=60.0, attendance=20",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00348,Technical_Drawing,61.3,Technical_Drawing_final,"Based on STEM avg=62.3, Fine Arts avg=65.6, attendance=14",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00349,Technical_Drawing,50.96,Technical_Drawing_final,"Based on STEM avg=49.3, Fine Arts avg=60.0, attendance=23",0.86,Grade tendency: Uptrend. Support level: Medium.,
+EA00350,Technical_Drawing,56.23,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=62.0, attendance=9",0.91,Grade tendency: Uptrend. Support level: Low.,
+EA00351,Technical_Drawing,68.73,Technical_Drawing_final,"Based on STEM avg=64.2, Fine Arts avg=71.7, attendance=7",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00352,Technical_Drawing,58.39,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=60.0, attendance=9",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00353,Technical_Drawing,62.41,Technical_Drawing_final,"Based on STEM avg=55.1, Fine Arts avg=71.4, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00354,Technical_Drawing,54.74,Technical_Drawing_final,"Based on STEM avg=54.2, Fine Arts avg=60.0, attendance=17",0.9,Grade tendency: Uptrend. Support level: High.,
+EA00355,Technical_Drawing,61.75,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=61.6, attendance=0",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00356,Technical_Drawing,61.57,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=60.0, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00357,Technical_Drawing,62.15,Technical_Drawing_final,"Based on STEM avg=63.7, Fine Arts avg=60.0, attendance=9",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00358,Technical_Drawing,56.47,Technical_Drawing_final,"Based on STEM avg=54.4, Fine Arts avg=61.9, attendance=4",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00359,Technical_Drawing,56.2,Technical_Drawing_final,"Based on STEM avg=55.2, Fine Arts avg=60.8, attendance=24",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00360,Technical_Drawing,59.28,Technical_Drawing_final,"Based on STEM avg=59.6, Fine Arts avg=60.0, attendance=21",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00361,Technical_Drawing,58.16,Technical_Drawing_final,"Based on STEM avg=59.8, Fine Arts avg=59.8, attendance=16",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00362,Technical_Drawing,70.44,Technical_Drawing_final,"Based on STEM avg=66.9, Fine Arts avg=78.9, attendance=1",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00363,Technical_Drawing,73.14,Technical_Drawing_final,"Based on STEM avg=66.8, Fine Arts avg=84.2, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00364,Technical_Drawing,56.06,Technical_Drawing_final,"Based on STEM avg=60.7, Fine Arts avg=60.0, attendance=24",0.91,Grade tendency: Downtrend. Support level: Medium.,
+EA00365,Technical_Drawing,55.81,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=62.0, attendance=13",0.91,Grade tendency: Downtrend. Support level: High.,
+EA00366,Technical_Drawing,60.8,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=61.1, attendance=12",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00367,Technical_Drawing,59.76,Technical_Drawing_final,"Based on STEM avg=63.7, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00368,Technical_Drawing,64.38,Technical_Drawing_final,"Based on STEM avg=65.3, Fine Arts avg=60.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00369,Technical_Drawing,63.36,Technical_Drawing_final,"Based on STEM avg=58.6, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00370,Technical_Drawing,62.25,Technical_Drawing_final,"Based on STEM avg=65.0, Fine Arts avg=63.4, attendance=20",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00371,Technical_Drawing,53.14,Technical_Drawing_final,"Based on STEM avg=55.1, Fine Arts avg=60.0, attendance=18",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00372,Technical_Drawing,61.48,Technical_Drawing_final,"Based on STEM avg=55.8, Fine Arts avg=72.8, attendance=5",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00373,Technical_Drawing,67.58,Technical_Drawing_final,"Based on STEM avg=61.4, Fine Arts avg=78.3, attendance=15",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00374,Technical_Drawing,57.15,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=66.7, attendance=20",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00375,Technical_Drawing,53.1,Technical_Drawing_final,"Based on STEM avg=54.7, Fine Arts avg=57.8, attendance=17",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00376,Technical_Drawing,53.64,Technical_Drawing_final,"Based on STEM avg=53.3, Fine Arts avg=56.7, attendance=22",0.89,Grade tendency: Uptrend. Support level: Low.,
+EA00377,Technical_Drawing,54.42,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=60.0, attendance=6",0.89,Grade tendency: Uptrend. Support level: High.,
+EA00378,Technical_Drawing,60.28,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=63.8, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00379,Technical_Drawing,60.32,Technical_Drawing_final,"Based on STEM avg=63.5, Fine Arts avg=60.5, attendance=8",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00380,Technical_Drawing,68.13,Technical_Drawing_final,"Based on STEM avg=65.5, Fine Arts avg=65.9, attendance=13",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00381,Technical_Drawing,67.71,Technical_Drawing_final,"Based on STEM avg=67.5, Fine Arts avg=68.5, attendance=2",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00382,Technical_Drawing,57.07,Technical_Drawing_final,"Based on STEM avg=62.8, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00383,Technical_Drawing,58.87,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=61.2, attendance=10",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00384,Technical_Drawing,66.82,Technical_Drawing_final,"Based on STEM avg=64.9, Fine Arts avg=64.5, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00385,Technical_Drawing,53.55,Technical_Drawing_final,"Based on STEM avg=54.2, Fine Arts avg=60.0, attendance=13",0.89,Grade tendency: Downtrend. Support level: Medium.,
+EA00386,Technical_Drawing,57.99,Technical_Drawing_final,"Based on STEM avg=58.4, Fine Arts avg=60.0, attendance=22",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00387,Technical_Drawing,64.84,Technical_Drawing_final,"Based on STEM avg=60.2, Fine Arts avg=72.5, attendance=24",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00388,Technical_Drawing,67.46,Technical_Drawing_final,"Based on STEM avg=65.0, Fine Arts avg=66.5, attendance=3",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00389,Technical_Drawing,63.86,Technical_Drawing_final,"Based on STEM avg=55.0, Fine Arts avg=69.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00390,Technical_Drawing,56.97,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=61.1, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00391,Technical_Drawing,57.98,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=60.0, attendance=18",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00392,Technical_Drawing,64.76,Technical_Drawing_final,"Based on STEM avg=62.7, Fine Arts avg=71.2, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00393,Technical_Drawing,57.95,Technical_Drawing_final,"Based on STEM avg=59.8, Fine Arts avg=62.7, attendance=16",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00394,Technical_Drawing,61.17,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=70.1, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00395,Technical_Drawing,61.69,Technical_Drawing_final,"Based on STEM avg=64.1, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00396,Technical_Drawing,50.96,Technical_Drawing_final,"Based on STEM avg=49.1, Fine Arts avg=53.6, attendance=8",0.86,Grade tendency: Uptrend. Support level: Low.,
+EA00397,Technical_Drawing,62.2,Technical_Drawing_final,"Based on STEM avg=69.5, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00398,Technical_Drawing,62.93,Technical_Drawing_final,"Based on STEM avg=57.6, Fine Arts avg=70.7, attendance=20",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00399,Technical_Drawing,49.44,Technical_Drawing_final,"Based on STEM avg=52.1, Fine Arts avg=52.2, attendance=9",0.84,Grade tendency: Downtrend. Support level: Medium.,
+EA00400,Technical_Drawing,60.32,Technical_Drawing_final,"Based on STEM avg=60.6, Fine Arts avg=66.3, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00401,Technical_Drawing,67.91,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=75.4, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00402,Technical_Drawing,58.45,Technical_Drawing_final,"Based on STEM avg=57.7, Fine Arts avg=61.7, attendance=19",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00403,Technical_Drawing,53.94,Technical_Drawing_final,"Based on STEM avg=59.8, Fine Arts avg=60.0, attendance=16",0.89,Grade tendency: Downtrend. Support level: Low.,
+EA00404,Technical_Drawing,61.79,Technical_Drawing_final,"Based on STEM avg=57.4, Fine Arts avg=66.1, attendance=0",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00405,Technical_Drawing,63.78,Technical_Drawing_final,"Based on STEM avg=57.7, Fine Arts avg=75.0, attendance=21",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00406,Technical_Drawing,64.39,Technical_Drawing_final,"Based on STEM avg=62.7, Fine Arts avg=74.6, attendance=16",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00407,Technical_Drawing,66.0,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=72.6, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00408,Technical_Drawing,61.68,Technical_Drawing_final,"Based on STEM avg=64.7, Fine Arts avg=60.0, attendance=23",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00409,Technical_Drawing,53.39,Technical_Drawing_final,"Based on STEM avg=51.8, Fine Arts avg=60.0, attendance=15",0.88,Grade tendency: Uptrend. Support level: Low.,
+EA00410,Technical_Drawing,58.61,Technical_Drawing_final,"Based on STEM avg=60.1, Fine Arts avg=63.0, attendance=20",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00411,Technical_Drawing,64.61,Technical_Drawing_final,"Based on STEM avg=69.8, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00412,Technical_Drawing,54.21,Technical_Drawing_final,"Based on STEM avg=54.4, Fine Arts avg=60.0, attendance=4",0.89,Grade tendency: Uptrend. Support level: Medium.,
+EA00413,Technical_Drawing,61.65,Technical_Drawing_final,"Based on STEM avg=57.9, Fine Arts avg=72.0, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00414,Technical_Drawing,54.89,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=56.1, attendance=11",0.9,Grade tendency: Uptrend. Support level: Low.,
+EA00415,Technical_Drawing,64.75,Technical_Drawing_final,"Based on STEM avg=65.1, Fine Arts avg=67.6, attendance=16",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00416,Technical_Drawing,56.45,Technical_Drawing_final,"Based on STEM avg=55.8, Fine Arts avg=60.0, attendance=20",0.91,Grade tendency: Uptrend. Support level: Low.,
+EA00417,Technical_Drawing,59.89,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=68.1, attendance=15",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00418,Technical_Drawing,59.35,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=59.7, attendance=10",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00419,Technical_Drawing,59.21,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=61.4, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00420,Technical_Drawing,62.05,Technical_Drawing_final,"Based on STEM avg=60.2, Fine Arts avg=65.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00421,Technical_Drawing,52.71,Technical_Drawing_final,"Based on STEM avg=55.8, Fine Arts avg=60.0, attendance=19",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00422,Technical_Drawing,66.32,Technical_Drawing_final,"Based on STEM avg=65.0, Fine Arts avg=65.3, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00423,Technical_Drawing,60.63,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00424,Technical_Drawing,62.5,Technical_Drawing_final,"Based on STEM avg=58.4, Fine Arts avg=62.1, attendance=0",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00425,Technical_Drawing,57.66,Technical_Drawing_final,"Based on STEM avg=57.9, Fine Arts avg=66.3, attendance=23",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00426,Technical_Drawing,60.92,Technical_Drawing_final,"Based on STEM avg=64.6, Fine Arts avg=60.0, attendance=18",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00427,Technical_Drawing,60.51,Technical_Drawing_final,"Based on STEM avg=56.8, Fine Arts avg=65.3, attendance=4",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00428,Technical_Drawing,60.0,Technical_Drawing_final,"Based on STEM avg=60.2, Fine Arts avg=65.1, attendance=17",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00429,Technical_Drawing,55.51,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=60.0, attendance=22",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00430,Technical_Drawing,51.87,Technical_Drawing_final,"Based on STEM avg=57.2, Fine Arts avg=55.0, attendance=13",0.87,Grade tendency: Uptrend. Support level: High.,
+EA00431,Technical_Drawing,56.29,Technical_Drawing_final,"Based on STEM avg=54.6, Fine Arts avg=59.5, attendance=23",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00432,Technical_Drawing,73.19,Technical_Drawing_final,"Based on STEM avg=64.5, Fine Arts avg=83.3, attendance=12",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00433,Technical_Drawing,64.06,Technical_Drawing_final,"Based on STEM avg=56.8, Fine Arts avg=71.5, attendance=14",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00434,Technical_Drawing,70.29,Technical_Drawing_final,"Based on STEM avg=66.2, Fine Arts avg=70.7, attendance=23",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00435,Technical_Drawing,67.41,Technical_Drawing_final,"Based on STEM avg=65.8, Fine Arts avg=72.6, attendance=3",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00436,Technical_Drawing,57.05,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=60.0, attendance=6",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00437,Technical_Drawing,63.47,Technical_Drawing_final,"Based on STEM avg=59.5, Fine Arts avg=69.6, attendance=9",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00438,Technical_Drawing,59.45,Technical_Drawing_final,"Based on STEM avg=56.9, Fine Arts avg=55.5, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00439,Technical_Drawing,53.68,Technical_Drawing_final,"Based on STEM avg=57.1, Fine Arts avg=63.5, attendance=23",0.89,Grade tendency: Downtrend. Support level: High.,
+EA00440,Technical_Drawing,55.82,Technical_Drawing_final,"Based on STEM avg=55.1, Fine Arts avg=58.4, attendance=18",0.91,Grade tendency: Uptrend. Support level: Medium.,
+EA00441,Technical_Drawing,57.65,Technical_Drawing_final,"Based on STEM avg=61.1, Fine Arts avg=63.8, attendance=24",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00442,Technical_Drawing,57.92,Technical_Drawing_final,"Based on STEM avg=60.3, Fine Arts avg=68.8, attendance=17",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00443,Technical_Drawing,60.37,Technical_Drawing_final,"Based on STEM avg=65.4, Fine Arts avg=65.8, attendance=20",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00444,Technical_Drawing,59.6,Technical_Drawing_final,"Based on STEM avg=59.7, Fine Arts avg=65.8, attendance=7",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00445,Technical_Drawing,69.59,Technical_Drawing_final,"Based on STEM avg=65.5, Fine Arts avg=74.1, attendance=21",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00446,Technical_Drawing,54.58,Technical_Drawing_final,"Based on STEM avg=54.7, Fine Arts avg=60.0, attendance=12",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00447,Technical_Drawing,62.05,Technical_Drawing_final,"Based on STEM avg=64.8, Fine Arts avg=60.0, attendance=0",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00448,Technical_Drawing,69.88,Technical_Drawing_final,"Based on STEM avg=65.1, Fine Arts avg=83.0, attendance=14",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00449,Technical_Drawing,61.5,Technical_Drawing_final,"Based on STEM avg=60.0, Fine Arts avg=60.0, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00450,Technical_Drawing,55.39,Technical_Drawing_final,"Based on STEM avg=55.5, Fine Arts avg=65.4, attendance=20",0.9,Grade tendency: Downtrend. Support level: Low.,
+EA00451,Technical_Drawing,67.61,Technical_Drawing_final,"Based on STEM avg=60.9, Fine Arts avg=72.5, attendance=3",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00452,Technical_Drawing,72.84,Technical_Drawing_final,"Based on STEM avg=69.0, Fine Arts avg=81.1, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00453,Technical_Drawing,57.56,Technical_Drawing_final,"Based on STEM avg=57.7, Fine Arts avg=60.0, attendance=5",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00454,Technical_Drawing,60.6,Technical_Drawing_final,"Based on STEM avg=62.6, Fine Arts avg=59.2, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00455,Technical_Drawing,60.06,Technical_Drawing_final,"Based on STEM avg=61.3, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00456,Technical_Drawing,63.93,Technical_Drawing_final,"Based on STEM avg=63.8, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00457,Technical_Drawing,56.85,Technical_Drawing_final,"Based on STEM avg=56.0, Fine Arts avg=61.1, attendance=18",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00458,Technical_Drawing,52.95,Technical_Drawing_final,"Based on STEM avg=59.7, Fine Arts avg=56.2, attendance=22",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00459,Technical_Drawing,58.44,Technical_Drawing_final,"Based on STEM avg=60.5, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00460,Technical_Drawing,60.75,Technical_Drawing_final,"Based on STEM avg=58.1, Fine Arts avg=69.6, attendance=15",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00461,Technical_Drawing,57.42,Technical_Drawing_final,"Based on STEM avg=57.0, Fine Arts avg=58.8, attendance=22",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00462,Technical_Drawing,60.73,Technical_Drawing_final,"Based on STEM avg=55.4, Fine Arts avg=69.5, attendance=10",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00463,Technical_Drawing,65.14,Technical_Drawing_final,"Based on STEM avg=63.9, Fine Arts avg=65.7, attendance=1",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00464,Technical_Drawing,58.86,Technical_Drawing_final,"Based on STEM avg=56.4, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00465,Technical_Drawing,55.15,Technical_Drawing_final,"Based on STEM avg=50.4, Fine Arts avg=67.5, attendance=22",0.9,Grade tendency: Uptrend. Support level: Medium.,
+EA00466,Technical_Drawing,62.28,Technical_Drawing_final,"Based on STEM avg=62.3, Fine Arts avg=69.4, attendance=24",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00467,Technical_Drawing,57.9,Technical_Drawing_final,"Based on STEM avg=57.8, Fine Arts avg=60.0, attendance=17",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00468,Technical_Drawing,65.81,Technical_Drawing_final,"Based on STEM avg=67.1, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00469,Technical_Drawing,58.1,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=60.0, attendance=2",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00470,Technical_Drawing,65.78,Technical_Drawing_final,"Based on STEM avg=63.9, Fine Arts avg=70.0, attendance=6",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00471,Technical_Drawing,53.05,Technical_Drawing_final,"Based on STEM avg=54.8, Fine Arts avg=60.0, attendance=22",0.88,Grade tendency: Uptrend. Support level: High.,
+EA00472,Technical_Drawing,63.22,Technical_Drawing_final,"Based on STEM avg=64.2, Fine Arts avg=60.0, attendance=1",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00473,Technical_Drawing,62.82,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=66.2, attendance=7",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00474,Technical_Drawing,55.23,Technical_Drawing_final,"Based on STEM avg=53.6, Fine Arts avg=57.2, attendance=1",0.9,Grade tendency: Downtrend. Support level: Medium.,
+EA00475,Technical_Drawing,54.53,Technical_Drawing_final,"Based on STEM avg=55.2, Fine Arts avg=65.0, attendance=21",0.9,Grade tendency: Uptrend. Support level: Low.,
+EA00476,Technical_Drawing,62.96,Technical_Drawing_final,"Based on STEM avg=65.0, Fine Arts avg=60.0, attendance=10",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00477,Technical_Drawing,55.64,Technical_Drawing_final,"Based on STEM avg=56.7, Fine Arts avg=60.8, attendance=11",0.91,Grade tendency: Uptrend. Support level: Low.,
+EA00478,Technical_Drawing,62.42,Technical_Drawing_final,"Based on STEM avg=62.0, Fine Arts avg=70.5, attendance=19",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00479,Technical_Drawing,67.55,Technical_Drawing_final,"Based on STEM avg=61.9, Fine Arts avg=74.4, attendance=18",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00480,Technical_Drawing,60.11,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=67.1, attendance=18",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00481,Technical_Drawing,57.09,Technical_Drawing_final,"Based on STEM avg=55.9, Fine Arts avg=60.0, attendance=11",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00482,Technical_Drawing,59.95,Technical_Drawing_final,"Based on STEM avg=59.4, Fine Arts avg=60.0, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00483,Technical_Drawing,53.2,Technical_Drawing_final,"Based on STEM avg=56.4, Fine Arts avg=60.0, attendance=21",0.88,Grade tendency: Downtrend. Support level: Medium.,
+EA00484,Technical_Drawing,56.2,Technical_Drawing_final,"Based on STEM avg=56.3, Fine Arts avg=50.3, attendance=1",0.91,Grade tendency: Uptrend. Support level: High.,
+EA00485,Technical_Drawing,65.4,Technical_Drawing_final,"Based on STEM avg=58.2, Fine Arts avg=68.7, attendance=4",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00486,Technical_Drawing,55.66,Technical_Drawing_final,"Based on STEM avg=60.6, Fine Arts avg=58.2, attendance=5",0.91,Grade tendency: Uptrend. Support level: High.,
+EA00487,Technical_Drawing,51.6,Technical_Drawing_final,"Based on STEM avg=56.0, Fine Arts avg=55.6, attendance=20",0.87,Grade tendency: Uptrend. Support level: Low.,
+EA00488,Technical_Drawing,56.83,Technical_Drawing_final,"Based on STEM avg=53.5, Fine Arts avg=60.0, attendance=13",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00489,Technical_Drawing,64.98,Technical_Drawing_final,"Based on STEM avg=61.4, Fine Arts avg=77.5, attendance=7",0.92,Grade tendency: Downtrend. Support level: Medium.,
+EA00490,Technical_Drawing,58.44,Technical_Drawing_final,"Based on STEM avg=57.9, Fine Arts avg=60.0, attendance=4",0.92,Grade tendency: Uptrend. Support level: Low.,
+EA00491,Technical_Drawing,55.0,Technical_Drawing_final,"Based on STEM avg=54.0, Fine Arts avg=60.0, attendance=12",0.9,Grade tendency: Downtrend. Support level: Medium.,
+EA00492,Technical_Drawing,63.22,Technical_Drawing_final,"Based on STEM avg=58.9, Fine Arts avg=64.8, attendance=1",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00493,Technical_Drawing,54.32,Technical_Drawing_final,"Based on STEM avg=58.0, Fine Arts avg=55.7, attendance=17",0.89,Grade tendency: Downtrend. Support level: Low.,
+EA00494,Technical_Drawing,65.06,Technical_Drawing_final,"Based on STEM avg=69.1, Fine Arts avg=61.0, attendance=9",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00495,Technical_Drawing,61.15,Technical_Drawing_final,"Based on STEM avg=63.0, Fine Arts avg=68.1, attendance=17",0.92,Grade tendency: Downtrend. Support level: High.,
+EA00496,Technical_Drawing,61.51,Technical_Drawing_final,"Based on STEM avg=63.4, Fine Arts avg=60.0, attendance=24",0.92,Grade tendency: Downtrend. Support level: Low.,
+EA00497,Technical_Drawing,63.58,Technical_Drawing_final,"Based on STEM avg=63.6, Fine Arts avg=65.8, attendance=5",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00498,Technical_Drawing,57.3,Technical_Drawing_final,"Based on STEM avg=57.5, Fine Arts avg=57.4, attendance=9",0.92,Grade tendency: Uptrend. Support level: High.,
+EA00499,Technical_Drawing,60.89,Technical_Drawing_final,"Based on STEM avg=57.2, Fine Arts avg=69.0, attendance=10",0.92,Grade tendency: Uptrend. Support level: Medium.,
+EA00500,Technical_Drawing,59.63,Technical_Drawing_final,"Based on STEM avg=58.7, Fine Arts avg=60.0, attendance=3",0.92,Grade tendency: Uptrend. Support level: Medium.,

+ 501 - 0
internal/src/models/datasets/support_level_predictions.csv

@@ -0,0 +1,501 @@
+student_id,real_support,prob_low,prob_medium,prob_high,predicted,term1_avg,failed_subjects
+EA00001,High,0.2476,0.5107,0.2417,Medium,54.16,3
+EA00002,Medium,0.2252,0.6079,0.1669,Medium,62.03,0
+EA00003,High,0.215,0.6228,0.1623,Medium,58.88,1
+EA00004,Medium,0.2933,0.5493,0.1574,Medium,60.72,1
+EA00005,Medium,0.1693,0.5124,0.3182,Medium,67.36,0
+EA00006,Medium,0.2067,0.4861,0.3072,Medium,54.86,4
+EA00007,Medium,0.1722,0.5628,0.2651,Medium,67.18,0
+EA00008,High,0.2288,0.5925,0.1786,Medium,59.34,1
+EA00009,High,0.2842,0.6178,0.0981,Medium,52.03,1
+EA00010,Medium,0.2446,0.5826,0.1728,Medium,57.26,1
+EA00011,Medium,0.2541,0.5716,0.1743,Medium,63.25,0
+EA00012,High,0.2255,0.523,0.2515,Medium,65.98,0
+EA00013,Low,0.1567,0.526,0.3173,Medium,60.15,3
+EA00014,Medium,0.2647,0.5776,0.1577,Medium,55.86,2
+EA00015,Medium,0.2194,0.6029,0.1777,Medium,62.13,0
+EA00016,High,0.2568,0.5572,0.186,Medium,67.87,0
+EA00017,Low,0.3188,0.5541,0.127,Medium,58.68,1
+EA00018,Low,0.1965,0.5769,0.2266,Medium,65.51,0
+EA00019,High,0.2427,0.5887,0.1687,Medium,54.04,2
+EA00020,Medium,0.2093,0.5734,0.2173,Medium,54.1,3
+EA00021,High,0.1959,0.5364,0.2676,Medium,65.77,1
+EA00022,High,0.2624,0.5623,0.1752,Medium,61.95,0
+EA00023,Medium,0.2086,0.5436,0.2478,Medium,61.05,1
+EA00024,Medium,0.3063,0.5614,0.1322,Medium,62.81,0
+EA00025,Medium,0.207,0.5728,0.2202,Medium,63.88,0
+EA00026,Low,0.2877,0.4948,0.2175,Medium,61.66,2
+EA00027,Medium,0.2182,0.6025,0.1793,Medium,62.24,0
+EA00028,Medium,0.2743,0.537,0.1887,Medium,56.69,2
+EA00029,Low,0.3283,0.5287,0.1431,Medium,58.62,2
+EA00030,Low,0.3474,0.5797,0.0728,Medium,53.34,0
+EA00031,Medium,0.1679,0.5247,0.3074,Medium,55.66,4
+EA00032,Medium,0.1284,0.4551,0.4165,Medium,71.45,0
+EA00033,Medium,0.265,0.5032,0.2317,Medium,57.61,4
+EA00034,High,0.2161,0.4909,0.293,Medium,54.04,4
+EA00035,Medium,0.2847,0.5813,0.134,Medium,60.19,0
+EA00036,High,0.2481,0.5592,0.1927,Medium,63.64,0
+EA00037,High,0.2034,0.609,0.1875,Medium,60.84,1
+EA00038,High,0.2256,0.4463,0.3281,Medium,58.41,5
+EA00039,High,0.2318,0.5399,0.2283,Medium,55.72,3
+EA00040,Low,0.2165,0.562,0.2214,Medium,56.71,3
+EA00041,Low,0.2819,0.5791,0.139,Medium,50.88,3
+EA00042,Low,0.2827,0.5804,0.137,Medium,57.33,1
+EA00043,Medium,0.2505,0.542,0.2075,Medium,63.15,0
+EA00044,High,0.1915,0.5,0.3084,Medium,69.37,0
+EA00045,Medium,0.2046,0.5591,0.2363,Medium,55.24,3
+EA00046,Medium,0.2316,0.5454,0.223,Medium,55.84,3
+EA00047,Low,0.2809,0.5275,0.1916,Medium,62.32,1
+EA00048,Medium,0.307,0.482,0.211,Medium,67.31,0
+EA00049,Low,0.2363,0.5995,0.1641,Medium,61.72,0
+EA00050,Medium,0.2999,0.5813,0.1188,Medium,57.54,0
+EA00051,Medium,0.2849,0.5868,0.1284,Medium,55.86,1
+EA00052,Medium,0.344,0.5308,0.1252,Medium,63.5,0
+EA00053,Medium,0.2716,0.5698,0.1586,Medium,58.19,1
+EA00054,Low,0.2608,0.5779,0.1614,Medium,59.25,1
+EA00055,High,0.2344,0.5082,0.2574,Medium,56.18,3
+EA00056,Medium,0.2313,0.4585,0.3101,Medium,66.84,1
+EA00057,High,0.1827,0.5143,0.303,Medium,66.28,0
+EA00058,High,0.1876,0.5006,0.3118,Medium,69.81,0
+EA00059,Medium,0.207,0.5596,0.2334,Medium,57.15,2
+EA00060,Medium,0.2448,0.5338,0.2214,Medium,64.83,0
+EA00061,High,0.1563,0.4669,0.3767,Medium,66.12,1
+EA00062,High,0.2417,0.5255,0.2328,Medium,62.63,2
+EA00063,Low,0.216,0.5073,0.2767,Medium,66.58,0
+EA00064,Medium,0.2575,0.6126,0.1299,Medium,58.91,0
+EA00065,Medium,0.2588,0.6178,0.1235,Medium,58.37,0
+EA00066,Low,0.1744,0.5289,0.2966,Medium,66.67,0
+EA00067,Medium,0.2535,0.5656,0.1809,Medium,60.15,0
+EA00068,Low,0.2547,0.528,0.2173,Medium,68.46,0
+EA00069,Medium,0.2632,0.6048,0.132,Medium,58.76,0
+EA00070,Medium,0.2744,0.5855,0.1401,Medium,61.13,0
+EA00071,Low,0.2299,0.5575,0.2125,Medium,59.18,1
+EA00072,Low,0.2433,0.578,0.1787,Medium,57.77,1
+EA00073,Medium,0.1894,0.5586,0.2519,Medium,55.55,3
+EA00074,Medium,0.2102,0.5871,0.2027,Medium,64.2,0
+EA00075,Medium,0.3308,0.5545,0.1147,Medium,61.18,0
+EA00076,High,0.1984,0.5365,0.2651,Medium,64.58,0
+EA00077,Low,0.1895,0.5509,0.2595,Medium,66.26,0
+EA00078,Medium,0.2619,0.5953,0.1428,Medium,58.82,0
+EA00079,Medium,0.2522,0.5906,0.1572,Medium,59.67,0
+EA00080,High,0.1924,0.5479,0.2597,Medium,65.45,0
+EA00081,Medium,0.1953,0.6063,0.1984,Medium,61.71,1
+EA00082,Low,0.2347,0.5837,0.1815,Medium,57.78,1
+EA00083,Medium,0.2548,0.5388,0.2064,Medium,57.08,2
+EA00084,High,0.2551,0.5276,0.2172,Medium,63.36,0
+EA00085,Medium,0.1728,0.5451,0.2821,Medium,67.64,0
+EA00086,Medium,0.2378,0.5259,0.2363,Medium,56.48,4
+EA00087,Medium,0.2698,0.4874,0.2428,Medium,66.42,1
+EA00088,Low,0.2265,0.5274,0.2461,Medium,65.98,0
+EA00089,Medium,0.2869,0.546,0.167,Medium,60.08,0
+EA00090,Medium,0.2171,0.5854,0.1974,Medium,56.95,2
+EA00091,Low,0.2803,0.5657,0.1541,Medium,64.61,0
+EA00092,Medium,0.2287,0.4751,0.2962,Medium,57.08,4
+EA00093,Medium,0.2282,0.5484,0.2234,Medium,62.23,0
+EA00094,Low,0.3503,0.5294,0.1202,Medium,63.43,0
+EA00095,Medium,0.2152,0.541,0.2438,Medium,66.9,0
+EA00096,High,0.1964,0.519,0.2846,Medium,56.26,4
+EA00097,High,0.1988,0.5627,0.2384,Medium,65.02,0
+EA00098,Medium,0.2219,0.5832,0.1948,Medium,59.47,1
+EA00099,Medium,0.3268,0.4984,0.1748,Medium,58.74,2
+EA00100,Medium,0.2071,0.4483,0.3445,Medium,74.55,0
+EA00101,Medium,0.2391,0.5165,0.2445,Medium,61.11,1
+EA00102,High,0.1985,0.5045,0.297,Medium,55.71,4
+EA00103,Medium,0.2177,0.521,0.2613,Medium,63.66,1
+EA00104,Medium,0.1175,0.4278,0.4547,High,54.16,6
+EA00105,High,0.2834,0.5466,0.17,Medium,68.51,0
+EA00106,Medium,0.1815,0.5544,0.2641,Medium,67.23,0
+EA00107,Low,0.2427,0.5544,0.2028,Medium,63.61,0
+EA00108,Medium,0.3176,0.5366,0.1458,Medium,56.9,2
+EA00109,High,0.2022,0.4966,0.3012,Medium,55.58,4
+EA00110,Low,0.2446,0.5264,0.229,Medium,68.89,0
+EA00111,High,0.2497,0.5728,0.1775,Medium,60.64,1
+EA00112,Low,0.2396,0.5849,0.1755,Medium,57.38,2
+EA00113,Medium,0.2153,0.583,0.2017,Medium,63.18,0
+EA00114,Low,0.2928,0.5764,0.1308,Medium,56.36,0
+EA00115,Low,0.239,0.5941,0.1669,Medium,58.36,1
+EA00116,Medium,0.2853,0.5876,0.1271,Medium,53.67,2
+EA00117,Medium,0.2762,0.5537,0.1701,Medium,54.13,2
+EA00118,High,0.2176,0.4749,0.3075,Medium,61.15,3
+EA00119,Medium,0.2202,0.548,0.2318,Medium,63.0,0
+EA00120,Low,0.2882,0.5047,0.2071,Medium,62.25,2
+EA00121,Medium,0.3498,0.5019,0.1483,Medium,64.5,0
+EA00122,Medium,0.2551,0.5831,0.1617,Medium,56.34,2
+EA00123,Medium,0.2449,0.4777,0.2774,Medium,72.22,0
+EA00124,Low,0.191,0.5346,0.2744,Medium,56.28,4
+EA00125,Low,0.2429,0.5372,0.2199,Medium,63.74,0
+EA00126,Low,0.2279,0.5134,0.2587,Medium,70.61,0
+EA00127,Medium,0.2878,0.5802,0.132,Medium,53.4,2
+EA00128,Medium,0.2686,0.614,0.1174,Medium,60.5,0
+EA00129,High,0.1969,0.5243,0.2788,Medium,68.45,0
+EA00130,High,0.282,0.5519,0.1661,Medium,60.56,0
+EA00131,Medium,0.2069,0.5842,0.2088,Medium,64.06,0
+EA00132,Medium,0.1911,0.553,0.2558,Medium,65.68,0
+EA00133,Medium,0.2255,0.5005,0.2741,Medium,62.47,1
+EA00134,Medium,0.2419,0.5588,0.1993,Medium,61.6,1
+EA00135,Medium,0.2082,0.5556,0.2363,Medium,57.4,2
+EA00136,Medium,0.2849,0.5693,0.1459,Medium,60.96,0
+EA00137,Medium,0.2198,0.5892,0.191,Medium,53.69,3
+EA00138,High,0.2979,0.5374,0.1647,Medium,64.55,0
+EA00139,Low,0.2344,0.5051,0.2606,Medium,56.37,4
+EA00140,Medium,0.2547,0.5868,0.1586,Medium,58.97,0
+EA00141,Medium,0.2538,0.5484,0.1977,Medium,58.46,3
+EA00142,High,0.2528,0.5361,0.2111,Medium,55.31,4
+EA00143,Low,0.2274,0.5211,0.2515,Medium,65.32,0
+EA00144,Medium,0.268,0.6037,0.1282,Medium,57.92,1
+EA00145,Medium,0.1985,0.5842,0.2173,Medium,58.01,2
+EA00146,Medium,0.395,0.5175,0.0875,Medium,59.89,0
+EA00147,Medium,0.2003,0.5048,0.2949,Medium,65.07,1
+EA00148,Medium,0.2668,0.4995,0.2337,Medium,66.88,0
+EA00149,Low,0.3416,0.5222,0.1362,Medium,57.52,2
+EA00150,High,0.274,0.4824,0.2435,Medium,62.93,2
+EA00151,Medium,0.219,0.5947,0.1863,Medium,59.92,1
+EA00152,Medium,0.2229,0.6273,0.1498,Medium,61.56,0
+EA00153,Medium,0.2045,0.5717,0.2239,Medium,60.21,1
+EA00154,Medium,0.1701,0.5591,0.2707,Medium,68.23,0
+EA00155,High,0.2385,0.5406,0.2209,Medium,69.7,0
+EA00156,Medium,0.2319,0.5154,0.2527,Medium,58.72,2
+EA00157,Medium,0.2725,0.5783,0.1492,Medium,61.68,0
+EA00158,Medium,0.2635,0.581,0.1555,Medium,62.07,0
+EA00159,Medium,0.2923,0.5724,0.1353,Medium,59.46,0
+EA00160,High,0.2289,0.5373,0.2338,Medium,65.49,0
+EA00161,High,0.2767,0.5691,0.1541,Medium,58.62,1
+EA00162,Medium,0.2542,0.4804,0.2654,Medium,58.9,4
+EA00163,High,0.1634,0.5375,0.2992,Medium,68.64,0
+EA00164,Low,0.2833,0.5761,0.1406,Medium,60.71,0
+EA00165,Low,0.3211,0.524,0.1548,Medium,62.98,0
+EA00166,High,0.2373,0.6103,0.1525,Medium,57.81,1
+EA00167,Low,0.2396,0.5449,0.2155,Medium,61.59,1
+EA00168,High,0.1773,0.5357,0.2869,Medium,61.28,2
+EA00169,Medium,0.2724,0.5887,0.1389,Medium,54.83,2
+EA00170,Medium,0.2308,0.5325,0.2367,Medium,65.22,0
+EA00171,Medium,0.2461,0.5696,0.1842,Medium,57.45,2
+EA00172,Low,0.2428,0.5529,0.2043,Medium,64.46,0
+EA00173,Low,0.2239,0.5182,0.2579,Medium,66.04,0
+EA00174,Low,0.1906,0.5595,0.2498,Medium,65.42,0
+EA00175,Medium,0.1596,0.4892,0.3512,Medium,55.42,4
+EA00176,Medium,0.3085,0.5491,0.1425,Medium,63.84,0
+EA00177,Medium,0.2671,0.5572,0.1757,Medium,55.84,2
+EA00178,Medium,0.3001,0.5678,0.1321,Medium,56.87,2
+EA00179,Medium,0.2613,0.5633,0.1754,Medium,63.38,0
+EA00180,Medium,0.1112,0.4247,0.4641,High,65.14,3
+EA00181,Medium,0.2051,0.5766,0.2183,Medium,64.58,0
+EA00182,Medium,0.2234,0.5131,0.2635,Medium,51.24,5
+EA00183,Medium,0.1668,0.4765,0.3567,Medium,49.01,6
+EA00184,Medium,0.2548,0.5322,0.213,Medium,67.21,0
+EA00185,High,0.2506,0.5442,0.2052,Medium,51.47,4
+EA00186,Medium,0.2511,0.5508,0.1981,Medium,64.56,0
+EA00187,Low,0.3006,0.4885,0.2109,Medium,58.38,3
+EA00188,High,0.1946,0.5258,0.2796,Medium,65.64,0
+EA00189,Low,0.2169,0.5917,0.1914,Medium,59.21,1
+EA00190,High,0.2748,0.5084,0.2168,Medium,56.89,4
+EA00191,Medium,0.2798,0.5836,0.1366,Medium,61.08,0
+EA00192,Medium,0.2161,0.5971,0.1868,Medium,60.24,1
+EA00193,Low,0.2789,0.5752,0.1459,Medium,54.13,2
+EA00194,Medium,0.2342,0.4795,0.2863,Medium,69.7,0
+EA00195,Low,0.2282,0.5701,0.2018,Medium,63.02,0
+EA00196,Medium,0.3275,0.5314,0.1412,Medium,65.22,0
+EA00197,High,0.3783,0.5136,0.1081,Medium,57.71,1
+EA00198,Low,0.3013,0.5256,0.1732,Medium,68.08,0
+EA00199,Medium,0.2606,0.5185,0.2209,Medium,58.23,3
+EA00200,Medium,0.2071,0.527,0.2659,Medium,55.3,4
+EA00201,Medium,0.1664,0.5416,0.292,Medium,55.33,4
+EA00202,Low,0.2449,0.6095,0.1456,Medium,60.1,0
+EA00203,High,0.2454,0.5705,0.1841,Medium,54.44,2
+EA00204,Low,0.2614,0.5589,0.1797,Medium,62.88,0
+EA00205,High,0.3063,0.5327,0.161,Medium,64.26,0
+EA00206,Medium,0.2555,0.5762,0.1683,Medium,59.22,0
+EA00207,High,0.241,0.6032,0.1558,Medium,53.47,2
+EA00208,Medium,0.1682,0.4709,0.361,Medium,65.24,2
+EA00209,Medium,0.1672,0.4914,0.3414,Medium,64.52,1
+EA00210,Medium,0.2466,0.596,0.1574,Medium,57.66,1
+EA00211,Medium,0.1965,0.5983,0.2052,Medium,58.43,2
+EA00212,Medium,0.2483,0.5235,0.2282,Medium,68.95,0
+EA00213,Medium,0.1728,0.4515,0.3758,Medium,54.16,5
+EA00214,Low,0.3117,0.5157,0.1726,Medium,63.2,1
+EA00215,High,0.2115,0.5704,0.2182,Medium,60.34,1
+EA00216,Medium,0.3551,0.5295,0.1154,Medium,56.12,2
+EA00217,Medium,0.2225,0.5828,0.1948,Medium,62.44,0
+EA00218,High,0.187,0.5076,0.3054,Medium,59.57,2
+EA00219,Low,0.2106,0.5215,0.2678,Medium,67.41,0
+EA00220,Low,0.2938,0.5635,0.1428,Medium,60.84,1
+EA00221,Medium,0.2068,0.5822,0.2111,Medium,61.45,1
+EA00222,High,0.2912,0.582,0.1269,Medium,59.19,0
+EA00223,Low,0.2792,0.5481,0.1727,Medium,60.75,0
+EA00224,Medium,0.3485,0.5631,0.0884,Medium,50.75,1
+EA00225,Medium,0.2231,0.5506,0.2263,Medium,59.34,2
+EA00226,Medium,0.2643,0.5374,0.1983,Medium,67.35,0
+EA00227,Medium,0.2897,0.5828,0.1275,Medium,59.76,0
+EA00228,High,0.1861,0.5697,0.2442,Medium,62.63,1
+EA00229,Low,0.2935,0.5281,0.1783,Medium,64.82,1
+EA00230,High,0.3349,0.4989,0.1662,Medium,58.15,2
+EA00231,Low,0.2861,0.5646,0.1493,Medium,61.5,1
+EA00232,Medium,0.225,0.48,0.295,Medium,70.18,1
+EA00233,Medium,0.2366,0.5115,0.2519,Medium,70.21,0
+EA00234,Medium,0.2415,0.5413,0.2172,Medium,61.79,1
+EA00235,Medium,0.2821,0.5692,0.1487,Medium,56.4,1
+EA00236,Medium,0.1924,0.4882,0.3194,Medium,65.52,1
+EA00237,Medium,0.3219,0.5159,0.1621,Medium,59.34,1
+EA00238,Medium,0.2731,0.5803,0.1466,Medium,55.13,2
+EA00239,Low,0.2214,0.5793,0.1993,Medium,56.87,2
+EA00240,Medium,0.258,0.5579,0.1841,Medium,61.26,2
+EA00241,Low,0.2902,0.5618,0.148,Medium,57.83,1
+EA00242,Medium,0.3735,0.5268,0.0997,Medium,62.05,0
+EA00243,Medium,0.3428,0.5718,0.0854,Medium,54.69,0
+EA00244,Low,0.2702,0.5899,0.1399,Medium,58.02,0
+EA00245,High,0.26,0.4939,0.2461,Medium,70.41,0
+EA00246,Medium,0.2096,0.5849,0.2055,Medium,64.23,0
+EA00247,High,0.2544,0.5288,0.2168,Medium,57.38,2
+EA00248,Medium,0.2142,0.5764,0.2093,Medium,57.12,2
+EA00249,Low,0.1617,0.5221,0.3162,Medium,66.36,1
+EA00250,High,0.149,0.4794,0.3716,Medium,57.0,4
+EA00251,High,0.3385,0.5229,0.1386,Medium,64.72,0
+EA00252,Medium,0.277,0.5862,0.1368,Medium,57.88,1
+EA00253,High,0.1523,0.3857,0.462,High,57.48,6
+EA00254,Medium,0.3033,0.5669,0.1298,Medium,55.46,1
+EA00255,Low,0.2707,0.4848,0.2445,Medium,70.64,0
+EA00256,Medium,0.2434,0.596,0.1606,Medium,60.12,0
+EA00257,Low,0.2597,0.5567,0.1836,Medium,62.99,0
+EA00258,Medium,0.2737,0.5217,0.2046,Medium,65.46,1
+EA00259,Medium,0.2639,0.5348,0.2013,Medium,67.78,0
+EA00260,Medium,0.2342,0.5362,0.2296,Medium,54.98,3
+EA00261,Low,0.2429,0.6079,0.1493,Medium,53.77,2
+EA00262,High,0.2032,0.5547,0.2421,Medium,63.97,0
+EA00263,Low,0.2455,0.6213,0.1332,Medium,59.68,0
+EA00264,Medium,0.2941,0.5867,0.1192,Medium,55.92,1
+EA00265,Medium,0.2779,0.5414,0.1807,Medium,51.66,3
+EA00266,Medium,0.3081,0.5383,0.1536,Medium,56.36,2
+EA00267,Low,0.3209,0.5265,0.1525,Medium,62.2,1
+EA00268,High,0.229,0.559,0.212,Medium,61.89,0
+EA00269,Medium,0.1574,0.4218,0.4208,Medium,52.99,6
+EA00270,Medium,0.2352,0.5301,0.2348,Medium,65.21,0
+EA00271,Medium,0.2746,0.56,0.1654,Medium,65.02,0
+EA00272,Low,0.2512,0.5659,0.183,Medium,54.34,3
+EA00273,Low,0.2255,0.5424,0.2321,Medium,52.84,3
+EA00274,Low,0.2927,0.5471,0.1602,Medium,53.11,2
+EA00275,Medium,0.2631,0.5614,0.1755,Medium,56.24,2
+EA00276,High,0.1626,0.5294,0.308,Medium,59.03,3
+EA00277,Low,0.1932,0.5324,0.2744,Medium,62.46,1
+EA00278,Medium,0.3707,0.5353,0.0941,Medium,61.86,0
+EA00279,High,0.2499,0.5203,0.2298,Medium,57.18,2
+EA00280,Medium,0.2783,0.5418,0.1799,Medium,55.11,2
+EA00281,Medium,0.1684,0.5616,0.2701,Medium,58.51,3
+EA00282,Medium,0.2673,0.5592,0.1735,Medium,60.02,2
+EA00283,Medium,0.2711,0.5224,0.2066,Medium,66.08,0
+EA00284,Medium,0.2959,0.5255,0.1786,Medium,68.07,0
+EA00285,Low,0.2944,0.5551,0.1506,Medium,61.14,1
+EA00286,High,0.2423,0.5704,0.1873,Medium,61.99,2
+EA00287,Medium,0.3099,0.5926,0.0975,Medium,57.13,0
+EA00288,High,0.2524,0.5705,0.1771,Medium,53.84,3
+EA00289,High,0.2658,0.5057,0.2285,Medium,67.53,0
+EA00290,Medium,0.2218,0.6253,0.1529,Medium,61.66,0
+EA00291,Low,0.2015,0.5379,0.2606,Medium,68.24,0
+EA00292,Medium,0.17,0.542,0.288,Medium,67.04,0
+EA00293,Medium,0.2362,0.4912,0.2726,Medium,58.67,2
+EA00294,Medium,0.2746,0.5137,0.2117,Medium,69.59,0
+EA00295,Low,0.2111,0.5601,0.2289,Medium,64.13,0
+EA00296,High,0.1753,0.5545,0.2702,Medium,64.49,1
+EA00297,High,0.317,0.5457,0.1373,Medium,65.76,0
+EA00298,Low,0.3446,0.5376,0.1178,Medium,63.5,0
+EA00299,High,0.2237,0.6031,0.1732,Medium,62.57,0
+EA00300,High,0.2568,0.5682,0.175,Medium,58.61,1
+EA00301,Low,0.2305,0.5258,0.2437,Medium,55.58,3
+EA00302,Medium,0.2342,0.5965,0.1693,Medium,54.94,2
+EA00303,Medium,0.1825,0.4919,0.3255,Medium,57.59,4
+EA00304,Medium,0.2705,0.5864,0.1431,Medium,58.01,1
+EA00305,Low,0.2438,0.53,0.2262,Medium,60.94,1
+EA00306,Low,0.2755,0.5671,0.1574,Medium,60.42,0
+EA00307,Medium,0.2361,0.5102,0.2537,Medium,69.78,0
+EA00308,Medium,0.2868,0.5532,0.1601,Medium,54.55,2
+EA00309,Medium,0.1984,0.5215,0.28,Medium,58.24,3
+EA00310,High,0.2054,0.4794,0.3153,Medium,71.59,0
+EA00311,High,0.3182,0.5317,0.1501,Medium,56.85,3
+EA00312,High,0.1945,0.5096,0.2959,Medium,65.3,0
+EA00313,High,0.2438,0.55,0.2062,Medium,55.22,3
+EA00314,Medium,0.2042,0.5609,0.2349,Medium,57.9,2
+EA00315,High,0.2995,0.4941,0.2064,Medium,68.12,0
+EA00316,Low,0.2417,0.5751,0.1833,Medium,59.65,1
+EA00317,Medium,0.1418,0.5052,0.353,Medium,55.18,5
+EA00318,Low,0.3139,0.5726,0.1135,Medium,57.62,0
+EA00319,Medium,0.2767,0.5285,0.1947,Medium,56.17,3
+EA00320,Medium,0.2687,0.5935,0.1378,Medium,57.74,0
+EA00321,High,0.2359,0.5309,0.2332,Medium,61.24,1
+EA00322,Low,0.1929,0.5373,0.2698,Medium,63.04,1
+EA00323,High,0.2414,0.5479,0.2107,Medium,64.95,0
+EA00324,Medium,0.2751,0.5494,0.1755,Medium,54.61,2
+EA00325,Medium,0.2202,0.5011,0.2787,Medium,53.01,4
+EA00326,High,0.1805,0.528,0.2915,Medium,67.26,0
+EA00327,High,0.2618,0.5646,0.1737,Medium,59.0,1
+EA00328,Low,0.3011,0.5872,0.1117,Medium,51.84,2
+EA00329,High,0.185,0.5357,0.2793,Medium,53.45,4
+EA00330,Low,0.2802,0.5593,0.1605,Medium,53.86,2
+EA00331,Medium,0.2866,0.4979,0.2156,Medium,69.21,0
+EA00332,Medium,0.1885,0.5743,0.2372,Medium,59.4,2
+EA00333,Low,0.2064,0.5812,0.2123,Medium,64.51,0
+EA00334,Medium,0.2877,0.5695,0.1429,Medium,60.72,0
+EA00335,Medium,0.2524,0.5694,0.1782,Medium,63.38,0
+EA00336,Medium,0.2421,0.5847,0.1733,Medium,60.56,0
+EA00337,High,0.1908,0.5788,0.2303,Medium,56.18,3
+EA00338,Low,0.2657,0.4757,0.2586,Medium,61.74,3
+EA00339,Medium,0.2009,0.5634,0.2357,Medium,57.86,2
+EA00340,Medium,0.2365,0.5886,0.1749,Medium,57.22,1
+EA00341,Medium,0.2692,0.5262,0.2046,Medium,62.58,0
+EA00342,Low,0.173,0.4979,0.3291,Medium,67.46,0
+EA00343,Medium,0.1886,0.5441,0.2673,Medium,56.25,3
+EA00344,Medium,0.257,0.497,0.246,Medium,65.11,2
+EA00345,Medium,0.2216,0.5541,0.2243,Medium,59.5,1
+EA00346,High,0.31,0.4894,0.2006,Medium,57.24,3
+EA00347,High,0.2292,0.6051,0.1657,Medium,57.25,1
+EA00348,Medium,0.3193,0.5047,0.1759,Medium,66.36,0
+EA00349,Medium,0.2116,0.5306,0.2578,Medium,47.48,5
+EA00350,Low,0.2452,0.5528,0.202,Medium,51.22,4
+EA00351,Low,0.2914,0.5535,0.155,Medium,64.85,0
+EA00352,Medium,0.2346,0.6113,0.1541,Medium,60.68,0
+EA00353,Medium,0.2303,0.6023,0.1674,Medium,57.98,1
+EA00354,High,0.1312,0.436,0.4328,Medium,55.71,5
+EA00355,Medium,0.2979,0.5386,0.1635,Medium,64.62,1
+EA00356,Medium,0.2075,0.5664,0.2262,Medium,61.57,1
+EA00357,Low,0.295,0.5186,0.1864,Medium,68.03,0
+EA00358,Medium,0.3792,0.5432,0.0776,Medium,56.77,1
+EA00359,Medium,0.2782,0.5134,0.2084,Medium,59.26,2
+EA00360,Low,0.249,0.5215,0.2295,Medium,63.79,0
+EA00361,Medium,0.2222,0.5853,0.1925,Medium,62.06,0
+EA00362,Low,0.2332,0.5381,0.2287,Medium,70.15,0
+EA00363,Low,0.2395,0.537,0.2235,Medium,65.38,0
+EA00364,Medium,0.307,0.4861,0.2069,Medium,66.95,0
+EA00365,High,0.1646,0.4494,0.3861,Medium,62.74,4
+EA00366,Low,0.2207,0.6017,0.1776,Medium,61.98,0
+EA00367,Medium,0.2963,0.5428,0.1609,Medium,64.31,0
+EA00368,High,0.2729,0.5604,0.1667,Medium,61.45,0
+EA00369,Medium,0.2446,0.5996,0.1558,Medium,57.44,1
+EA00370,High,0.2528,0.5348,0.2124,Medium,63.26,0
+EA00371,Low,0.2524,0.5387,0.209,Medium,56.85,2
+EA00372,High,0.3346,0.543,0.1224,Medium,64.33,0
+EA00373,Medium,0.1974,0.4748,0.3278,Medium,65.52,1
+EA00374,Low,0.2458,0.5272,0.227,Medium,60.71,1
+EA00375,Medium,0.3181,0.4867,0.1951,Medium,57.02,3
+EA00376,Low,0.2179,0.5675,0.2147,Medium,52.69,3
+EA00377,High,0.1532,0.4598,0.387,Medium,56.83,5
+EA00378,Medium,0.2608,0.5691,0.1701,Medium,63.06,0
+EA00379,Medium,0.2537,0.5902,0.1562,Medium,59.98,0
+EA00380,Low,0.225,0.5351,0.2399,Medium,65.83,0
+EA00381,High,0.1687,0.5422,0.2891,Medium,68.52,0
+EA00382,Medium,0.2896,0.4836,0.2268,Medium,68.23,0
+EA00383,Low,0.1998,0.5089,0.2913,Medium,58.7,3
+EA00384,Medium,0.1874,0.5504,0.2622,Medium,66.92,0
+EA00385,Medium,0.2857,0.5523,0.162,Medium,58.36,2
+EA00386,Medium,0.3434,0.5475,0.109,Medium,55.51,0
+EA00387,High,0.2187,0.5889,0.1924,Medium,61.59,0
+EA00388,Low,0.257,0.5757,0.1673,Medium,63.04,0
+EA00389,Medium,0.2907,0.5897,0.1195,Medium,56.23,1
+EA00390,Medium,0.2073,0.5388,0.2539,Medium,61.09,1
+EA00391,Low,0.2293,0.4882,0.2824,Medium,60.37,3
+EA00392,Medium,0.2594,0.541,0.1996,Medium,63.66,0
+EA00393,Medium,0.2487,0.5375,0.2137,Medium,61.33,2
+EA00394,Medium,0.1871,0.4796,0.3333,Medium,53.29,5
+EA00395,Medium,0.253,0.4779,0.2691,Medium,71.53,0
+EA00396,Low,0.2117,0.5701,0.2182,Medium,54.24,3
+EA00397,High,0.2197,0.5649,0.2154,Medium,63.34,0
+EA00398,Low,0.2496,0.5641,0.1862,Medium,57.0,1
+EA00399,Medium,0.2272,0.5149,0.2578,Medium,60.72,3
+EA00400,Medium,0.3376,0.5519,0.1105,Medium,56.45,0
+EA00401,Medium,0.2412,0.5614,0.1974,Medium,64.74,0
+EA00402,Low,0.2259,0.5399,0.2343,Medium,61.92,1
+EA00403,Low,0.3177,0.5406,0.1417,Medium,62.57,0
+EA00404,Low,0.3224,0.5395,0.1381,Medium,62.68,1
+EA00405,Medium,0.2255,0.5359,0.2386,Medium,61.87,1
+EA00406,High,0.3034,0.4991,0.1975,Medium,67.47,0
+EA00407,Medium,0.2296,0.5569,0.2135,Medium,59.25,2
+EA00408,Medium,0.1715,0.4615,0.3671,Medium,70.21,0
+EA00409,Low,0.2558,0.5503,0.1939,Medium,53.26,3
+EA00410,Low,0.2087,0.5602,0.231,Medium,63.49,0
+EA00411,Medium,0.2218,0.5086,0.2696,Medium,70.65,0
+EA00412,Medium,0.2171,0.5538,0.2291,Medium,56.95,3
+EA00413,Medium,0.1672,0.4388,0.3941,Medium,67.48,1
+EA00414,Low,0.2252,0.5862,0.1886,Medium,55.71,2
+EA00415,High,0.2623,0.5355,0.2021,Medium,62.88,0
+EA00416,Low,0.2882,0.5352,0.1766,Medium,54.19,2
+EA00417,Medium,0.2271,0.5482,0.2247,Medium,61.94,1
+EA00418,High,0.2175,0.5845,0.1979,Medium,56.46,2
+EA00419,Medium,0.2882,0.5921,0.1196,Medium,59.5,0
+EA00420,Medium,0.2393,0.5734,0.1873,Medium,58.97,1
+EA00421,Medium,0.2871,0.4857,0.2272,Medium,58.92,3
+EA00422,Medium,0.2161,0.5827,0.2013,Medium,63.53,0
+EA00423,Medium,0.2601,0.5515,0.1884,Medium,60.29,1
+EA00424,Low,0.3049,0.5874,0.1077,Medium,62.62,0
+EA00425,Low,0.3709,0.5133,0.1158,Medium,61.3,0
+EA00426,Medium,0.2212,0.5021,0.2767,Medium,66.39,0
+EA00427,Medium,0.2234,0.593,0.1836,Medium,59.45,1
+EA00428,Low,0.3558,0.5049,0.1393,Medium,60.18,1
+EA00429,Medium,0.139,0.3637,0.4973,High,54.29,6
+EA00430,High,0.3425,0.5779,0.0796,Medium,54.24,0
+EA00431,Medium,0.2689,0.5351,0.196,Medium,55.36,2
+EA00432,Medium,0.1929,0.5653,0.2418,Medium,65.26,0
+EA00433,High,0.3222,0.5615,0.1164,Medium,57.32,0
+EA00434,Medium,0.169,0.5213,0.3097,Medium,67.16,0
+EA00435,Medium,0.2321,0.5234,0.2445,Medium,70.42,0
+EA00436,Medium,0.2148,0.5395,0.2456,Medium,64.57,2
+EA00437,Medium,0.2949,0.5621,0.143,Medium,60.03,0
+EA00438,Medium,0.2852,0.5855,0.1293,Medium,56.7,1
+EA00439,High,0.2253,0.4655,0.3092,Medium,60.63,3
+EA00440,Medium,0.2675,0.5621,0.1704,Medium,54.99,2
+EA00441,Medium,0.2497,0.5414,0.2089,Medium,59.73,1
+EA00442,Medium,0.2287,0.514,0.2573,Medium,62.46,1
+EA00443,Low,0.1829,0.5298,0.2873,Medium,66.14,0
+EA00444,Medium,0.2616,0.6256,0.1129,Medium,57.68,0
+EA00445,Low,0.2595,0.5451,0.1954,Medium,62.39,0
+EA00446,Medium,0.2651,0.5626,0.1723,Medium,55.64,2
+EA00447,Low,0.2889,0.5728,0.1383,Medium,64.81,0
+EA00448,Medium,0.2162,0.5553,0.2285,Medium,63.53,0
+EA00449,Medium,0.2643,0.5804,0.1553,Medium,62.43,0
+EA00450,Low,0.2378,0.4685,0.2937,Medium,60.0,3
+EA00451,Medium,0.285,0.5593,0.1556,Medium,65.45,0
+EA00452,Low,0.1878,0.5754,0.2368,Medium,66.01,0
+EA00453,Low,0.207,0.5831,0.2099,Medium,61.0,1
+EA00454,Medium,0.2507,0.5714,0.1779,Medium,63.12,0
+EA00455,Medium,0.2155,0.5522,0.2324,Medium,63.99,0
+EA00456,Medium,0.3248,0.5071,0.1681,Medium,66.86,0
+EA00457,Medium,0.3025,0.5805,0.117,Medium,57.76,0
+EA00458,Medium,0.359,0.5089,0.1321,Medium,62.59,0
+EA00459,Medium,0.3072,0.5747,0.1181,Medium,58.21,0
+EA00460,High,0.2253,0.5884,0.1863,Medium,61.79,0
+EA00461,Low,0.3075,0.5471,0.1454,Medium,48.86,3
+EA00462,Medium,0.3146,0.5765,0.109,Medium,58.28,1
+EA00463,Low,0.2299,0.5604,0.2098,Medium,65.79,0
+EA00464,Medium,0.235,0.5961,0.1689,Medium,57.89,1
+EA00465,Medium,0.1625,0.4668,0.3708,Medium,52.31,5
+EA00466,Medium,0.2531,0.4728,0.2741,Medium,67.48,1
+EA00467,Low,0.2685,0.5727,0.1588,Medium,61.09,0
+EA00468,Medium,0.2068,0.5448,0.2485,Medium,64.74,0
+EA00469,Low,0.1925,0.5852,0.2223,Medium,59.12,2
+EA00470,Medium,0.221,0.6124,0.1667,Medium,62.07,0
+EA00471,High,0.3047,0.5467,0.1486,Medium,55.59,1
+EA00472,Low,0.3157,0.5253,0.1591,Medium,66.95,0
+EA00473,High,0.2392,0.5533,0.2075,Medium,64.8,0
+EA00474,Medium,0.3092,0.5671,0.1237,Medium,60.01,1
+EA00475,Low,0.266,0.5378,0.1962,Medium,55.64,2
+EA00476,Medium,0.3141,0.5018,0.1841,Medium,67.14,0
+EA00477,Low,0.1874,0.5472,0.2654,Medium,56.44,3
+EA00478,High,0.279,0.5924,0.1286,Medium,56.81,0
+EA00479,High,0.1617,0.4957,0.3425,Medium,65.3,1
+EA00480,Medium,0.2625,0.548,0.1896,Medium,66.36,0
+EA00481,Low,0.2325,0.5534,0.2141,Medium,58.48,2
+EA00482,Medium,0.223,0.5532,0.2238,Medium,66.35,0
+EA00483,Medium,0.345,0.5076,0.1474,Medium,60.57,1
+EA00484,High,0.2357,0.5633,0.2011,Medium,58.78,2
+EA00485,High,0.2204,0.5607,0.2189,Medium,59.77,2
+EA00486,High,0.3253,0.5861,0.0886,Medium,56.19,0
+EA00487,Low,0.2268,0.5126,0.2606,Medium,59.13,2
+EA00488,Medium,0.2755,0.5628,0.1617,Medium,54.74,2
+EA00489,Medium,0.3405,0.5163,0.1432,Medium,64.94,0
+EA00490,Low,0.2835,0.5692,0.1473,Medium,61.07,0
+EA00491,Medium,0.2927,0.5371,0.1702,Medium,58.46,2
+EA00492,Medium,0.3303,0.5949,0.0748,Medium,55.24,0
+EA00493,Low,0.3353,0.5531,0.1116,Medium,60.37,0
+EA00494,Medium,0.2337,0.5267,0.2396,Medium,65.72,0
+EA00495,High,0.3288,0.5093,0.162,Medium,65.28,0
+EA00496,Low,0.2391,0.4869,0.274,Medium,68.99,0
+EA00497,Medium,0.2769,0.5756,0.1475,Medium,61.27,0
+EA00498,High,0.2311,0.6151,0.1538,Medium,57.59,1
+EA00499,Medium,0.247,0.6121,0.1409,Medium,59.48,0
+EA00500,Medium,0.2505,0.5881,0.1614,Medium,59.86,1

BIN
internal/src/models/models-pkl/linear_avg_final.pkl


BIN
internal/src/models/models-pkl/linear_mathematics_final.pkl


BIN
internal/src/models/models-pkl/logistic_grade_tendency.pkl


BIN
internal/src/models/models-pkl/logistic_mathematics_pass.pkl


BIN
internal/src/models/models-pkl/logistic_support_level.pkl


BIN
internal/src/models/models-pkl/scaler_grade_tendency.pkl


BIN
internal/src/models/models-pkl/scaler_mathematics_pass.pkl


BIN
internal/src/models/models-pkl/scaler_support_level.pkl


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_avg_final.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_grade_tendency.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_math_final.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_math_pass.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_models.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/predictions/dashboard_ml_support_level.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 3200 - 0
internal/src/models/predictions/ml-preds.ipynb


+ 964 - 0
internal/src/models/predictions/nlp-preds.ipynb

@@ -0,0 +1,964 @@
+{
+ "cells": [
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a91ea08c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%pip install uv\n",
+    "%uv pip install pandas\n",
+    "%uv pip install pandas"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "da08ac5c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd\n",
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "434d7a6a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"../datasets/clean_dataset.csv\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f18eec6b",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "subjects = [\n",
+    "    \"Mathematics\",\n",
+    "    \"English\",\n",
+    "    \"Science\",\n",
+    "    \"ICT\",\n",
+    "    \"Geography\",\n",
+    "    \"History\",\n",
+    "    \"Civics\",\n",
+    "    \"Agriculture\",\n",
+    "    \"Business_Studies\",\n",
+    "    \"Physical_Education\",\n",
+    "    \"Swahili\",\n",
+    "    \"French\",\n",
+    "    \"Biology\",\n",
+    "    \"Chemistry\",\n",
+    "    \"Physics\",\n",
+    "    \"Entrepreneurship\",\n",
+    "    \"Fine_Arts\",\n",
+    "    \"Music\",\n",
+    "    \"Additional_Math\",\n",
+    "    \"Religious_Education\",\n",
+    "]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "f0d9d8eb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Definir contexto de usuario para predicción\n",
+    "\"\"\"\n",
+    "\n",
+    "final_cols = [c for c in df.columns if c.endswith(\"_final\")] # Columnas de notas finales\n",
+    "status_cols = [c for c in df.columns if c.endswith(\"_status\")]\n",
+    "term_1_columns = [c for c in df.columns if c.endswith(\"_term1\")]\n",
+    "term_2_columns = [c for c in df.columns if c.endswith(\"_term2\")]\n",
+    "term_3_columns = [c for c in df.columns if c.endswith(\"_term3\")]\n",
+    "\n",
+    "def check_null_val(data):\n",
+    "\n",
+    "    if pd.isna(data):\n",
+    "\n",
+    "        return \"\"\n",
+    "    \n",
+    "    return str(data)\n",
+    "\n",
+    "def define_user_context(r):\n",
+    "\n",
+    "    data = {\n",
+    "      \n",
+    "        \"student_id\": check_null_val(r.get(\"student_id\")),\n",
+    "        \"gender\": check_null_val(r.get(\"gender\")),\n",
+    "        \"age\": check_null_val(r.get(\"age\")),\n",
+    "        \"country\": check_null_val(r.get(\"country\")),\n",
+    "        \"city\": check_null_val(r.get(\"city\")),\n",
+    "        \"school_name\": check_null_val(r.get(\"school_name\")),\n",
+    "        \"course\": check_null_val(r.get(\"course\")),\n",
+    "        \"academic_year\": check_null_val(r.get(\"academic_year\")),\n",
+    "        \"class_group\": check_null_val(r.get(\"class_group\")),\n",
+    "        \"homeroom_teacher\": check_null_val(r.get(\"homeroom_teacher\")),\n",
+    "        \"attendance_absences_total\": check_null_val(r.get(\"attendance_absences_total\")),\n",
+    "        \"support_level\": check_null_val(r.get(\"support_level\")),\n",
+    "        \"support_level_num\": check_null_val(r.get(\"support_level_num\")),\n",
+    "        \"grade_tendency\": check_null_val(r.get(\"grade_tendency\")),\n",
+    "        \"sustained_decrease\": check_null_val(r.get(\"sustained_decrease\")),\n",
+    "        \"sustained_increase\": check_null_val(r.get(\"sustained_increase\")),\n",
+    "        \"notes\": check_null_val(r.get(\"notes\")),\n",
+    "    \n",
+    "    }\n",
+    "\n",
+    "    for final_col in final_cols:\n",
+    "    \n",
+    "        subject = final_col.replace(\"_final\", \"\")\n",
+    "\n",
+    "        data[f\"{subject}_teacher\"] = check_null_val(r.get(f\"{subject}_teacher\"))\n",
+    "        data[f\"{subject}_term1\"] = check_null_val(r.get(f\"{subject}_term1\"))\n",
+    "        data[f\"{subject}_term2\"] = check_null_val(r.get(f\"{subject}_term2\"))\n",
+    "        data[f\"{subject}_term3\"] = check_null_val(r.get(f\"{subject}_term3\"))\n",
+    "        data[f\"{subject}_final\"] = check_null_val(r.get(f\"{subject}_final\"))\n",
+    "        data[f\"{subject}_status\"] = check_null_val(r.get(f\"{subject}_status\"))\n",
+    "\n",
+    "    data[\"avg_final\"] = check_null_val(r.get(\"avg_final\"))\n",
+    "    data[\"max_grade\"] = check_null_val(r.get(\"max_grade\"))\n",
+    "    data[\"min_grade\"] = check_null_val(r.get(\"min_grade\"))\n",
+    "    data[\"passed_subjects\"] = check_null_val(r.get(\"passed_subjects\"))\n",
+    "    data[\"failed_subjects\"] = check_null_val(r.get(\"failed_subjects\"))\n",
+    "    data[\"passed_rate\"] = check_null_val(r.get(\"passed_rate\"))\n",
+    "    data[\"passed_rate_percent\"] = check_null_val(r.get(\"passed_rate_percent\"))\n",
+    "    data[\"final_grade_std\"] = check_null_val(r.get(\"final_grade_std\"))\n",
+    "    data[\"total_subjects\"] = check_null_val(r.get(\"total_subjects\"))\n",
+    "\n",
+    "    return data\n",
+    "\n",
+    "contexts = []\n",
+    "\n",
+    "for _, r in df.iterrows():\n",
+    "    \n",
+    "    context = define_user_context(r)\n",
+    "    contexts.append(context)\n",
+    "\n",
+    "df_context = pd.DataFrame(contexts)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "00a98be5",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_context.head()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "2ac2b163",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Ahora vamos con las predicciones de escenarios ficticios basados en los\n",
+    "contextos de los usuarios\n",
+    "\"\"\"\n",
+    "\n",
+    "# Comprobar que ollama está levantado\n",
+    "\n",
+    "%uv pip install requests\n",
+    "\n",
+    "import requests\n",
+    "\n",
+    "res = requests.get(\"http://localhost:11434/api/tags\")\n",
+    "\n",
+    "if res.status_code != 200:\n",
+    "\n",
+    "    raise ConnectionError(f\"Ollama no responde correctamente: {res.status_code}\")\n",
+    "\n",
+    "data = res.json()\n",
+    "\n",
+    "if data.get(\"models\") == []:\n",
+    "\n",
+    "    raise RuntimeError(\"Ollama está levantado, pero no hay modelos instalados\")\n",
+    "\n",
+    "print(\"Modelos detectados:\")\n",
+    "\n",
+    "for model in data[\"models\"]:\n",
+    "\n",
+    "    print(\"-\", model[\"name\"])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "385a0896",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def row_to_text(r):\n",
+    "    \n",
+    "    parts = []\n",
+    "\n",
+    "    for col, value in r.items():\n",
+    "    \n",
+    "        if value != \"\":\n",
+    "    \n",
+    "            parts.append(f\"{col}: {value}\")\n",
+    "\n",
+    "    return \". \".join(parts)\n",
+    "\n",
+    "df_context[\"base_text\"] = df_context.apply(row_to_text, axis=1)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "c7836f0d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "from pydantic import BaseModel, Field\n",
+    "from typing import Optional\n",
+    "\n",
+    "Definir el esquema del modelo con base model\n",
+    "\n",
+    "from langchain_ollama import ChatOllama\n",
+    "\n",
+    "Crear el modelo\n",
+    "\n",
+    "prediction_llm = llm.with_structured_output(StudentPrediction)\n",
+    "\n",
+    "Crear función de predicción\n",
+    "\n",
+    "Realizar un test\n",
+    "\n",
+    "Crear predicción para todos los alumnos\n",
+    "\n",
+    "\"\"\""
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "aaa6276c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%uv pip install pydantic\n",
+    "%uv pip install typing"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "94825731",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from pydantic import BaseModel, Field\n",
+    "from typing import Optional, List\n",
+    "\n",
+    "class StudentBatchPrediction(BaseModel):\n",
+    "  \n",
+    "    student_id: str = Field(description=\"Student ID\")\n",
+    "  \n",
+    "    predicted_value: float = Field(description=\"Predicted Technical Drawing grade from 0 to 100\")\n",
+    "  \n",
+    "    prediction_target: str = Field(description=\"Must be Technical_Drawing_final\")\n",
+    "  \n",
+    "    confidence: Optional[float] = Field(default=None, description=\"Confidence from 0 to 1\")\n",
+    "\n",
+    "\n",
+    "class StudentBatchPredictions(BaseModel):\n",
+    "  \n",
+    "    predictions: List[StudentBatchPrediction]"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "71149fa2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "%uv pip install langchain_ollama\n",
+    "%uv pip install asyncio"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5da755c6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from langchain_ollama import ChatOllama\n",
+    "\n",
+    "default_model = \"llama3.2:3b\"\n",
+    "temperature = 0\n",
+    "url = \"http://localhost:11434\"\n",
+    "\n",
+    "model = ChatOllama(\n",
+    "\n",
+    "    model = default_model,\n",
+    "    temperature = temperature,\n",
+    "    base_url = url,\n",
+    "    keep_alive=\"30m\",\n",
+    "    num_predict=80,\n",
+    "    num_ctx=2048,\n",
+    "\n",
+    ")\n",
+    "\n",
+    "batch_prediction_llm = model.with_structured_output(StudentBatchPredictions)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "36f87b6c",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# Test\n",
+    "\n",
+    "res = model.invoke(\"Responde solamente con OK\")\n",
+    "\n",
+    "print(res.content)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "5967552f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "def student_row_to_text(student):\n",
+    "\n",
+    "    parts = []\n",
+    "\n",
+    "    for col, value in student.items():\n",
+    "    \n",
+    "        if col == \"user_context\":\n",
+    "    \n",
+    "            continue\n",
+    "\n",
+    "        if value != \"\" and value is not None:\n",
+    "    \n",
+    "            parts.append(f\"{col}: {value}\")\n",
+    "\n",
+    "    return \". \".join(parts)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "676094d1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import time\n",
+    "import asyncio"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "eaad4b15",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import asyncio\n",
+    "import pandas as pd\n",
+    "import time\n",
+    "import json\n",
+    "import re\n",
+    "\n",
+    "RELEVANT_SUBJECTS = [\n",
+    "    \"Mathematics\",\n",
+    "    \"Science\",\n",
+    "    \"ICT\",\n",
+    "    \"Physics\",\n",
+    "    \"Chemistry\",\n",
+    "    \"Fine_Arts\",\n",
+    "    \"Additional_Math\",\n",
+    "    \"Business_Studies\",\n",
+    "    \"Physical_Education\",\n",
+    "]\n",
+    "\n",
+    "\n",
+    "def safe_get(row, col, default=\"\"):\n",
+    "    value = row.get(col, default)\n",
+    "\n",
+    "    if pd.isna(value):\n",
+    "        return default\n",
+    "\n",
+    "    return value\n",
+    "\n",
+    "\n",
+    "def build_compact_student_context(student):\n",
+    "    parts = []\n",
+    "\n",
+    "    # Contexto general\n",
+    "    parts.append(f\"student_id: {safe_get(student, 'student_id')}\")\n",
+    "    parts.append(f\"age: {safe_get(student, 'age')}\")\n",
+    "    parts.append(f\"gender: {safe_get(student, 'gender')}\")\n",
+    "    parts.append(f\"country: {safe_get(student, 'country')}\")\n",
+    "    parts.append(f\"city: {safe_get(student, 'city')}\")\n",
+    "    parts.append(f\"school_name: {safe_get(student, 'school_name')}\")\n",
+    "    parts.append(f\"course: {safe_get(student, 'course')}\")\n",
+    "    parts.append(f\"class_group: {safe_get(student, 'class_group')}\")\n",
+    "    parts.append(f\"attendance_absences_total: {safe_get(student, 'attendance_absences_total')}\")\n",
+    "    parts.append(f\"support_level: {safe_get(student, 'support_level')}\")\n",
+    "    parts.append(f\"grade_tendency: {safe_get(student, 'grade_tendency')}\")\n",
+    "    parts.append(f\"sustained_decrease: {safe_get(student, 'sustained_decrease')}\")\n",
+    "    parts.append(f\"sustained_increase: {safe_get(student, 'sustained_increase')}\")\n",
+    "\n",
+    "    # Resumen global útil\n",
+    "    parts.append(f\"avg_final: {safe_get(student, 'avg_final')}\")\n",
+    "    parts.append(f\"max_grade: {safe_get(student, 'max_grade')}\")\n",
+    "    parts.append(f\"min_grade: {safe_get(student, 'min_grade')}\")\n",
+    "    parts.append(f\"passed_subjects: {safe_get(student, 'passed_subjects')}\")\n",
+    "    parts.append(f\"failed_subjects: {safe_get(student, 'failed_subjects')}\")\n",
+    "    parts.append(f\"passed_rate_percent: {safe_get(student, 'passed_rate_percent')}\")\n",
+    "    parts.append(f\"final_grade_std: {safe_get(student, 'final_grade_std')}\")\n",
+    "\n",
+    "    # Asignaturas relevantes\n",
+    "    for subject in RELEVANT_SUBJECTS:\n",
+    "        final_col = f\"{subject}_final\"\n",
+    "        term1_col = f\"{subject}_term1\"\n",
+    "        term2_col = f\"{subject}_term2\"\n",
+    "        term3_col = f\"{subject}_term3\"\n",
+    "        status_col = f\"{subject}_status\"\n",
+    "\n",
+    "        if final_col in student.index:\n",
+    "            final_value = safe_get(student, final_col)\n",
+    "\n",
+    "            if final_value != \"\":\n",
+    "                parts.append(\n",
+    "                    f\"{subject}: \"\n",
+    "                    f\"term1={safe_get(student, term1_col)}, \"\n",
+    "                    f\"term2={safe_get(student, term2_col)}, \"\n",
+    "                    f\"term3={safe_get(student, term3_col)}, \"\n",
+    "                    f\"final={safe_get(student, final_col)}, \"\n",
+    "                    f\"status={safe_get(student, status_col)}\"\n",
+    "                )\n",
+    "\n",
+    "    notes = safe_get(student, \"notes\")\n",
+    "    if notes != \"\":\n",
+    "        parts.append(f\"notes: {notes}\")\n",
+    "\n",
+    "    return \"\\n\".join(str(p) for p in parts if str(p).strip())\n",
+    "\n",
+    "\n",
+    "def extract_json_from_response(text):\n",
+    "    \"\"\"\n",
+    "    Intenta extraer JSON aunque el modelo meta texto antes/después.\n",
+    "    \"\"\"\n",
+    "    text = text.strip()\n",
+    "\n",
+    "    # Caso ideal: respuesta ya es JSON\n",
+    "    try:\n",
+    "        return json.loads(text)\n",
+    "    except json.JSONDecodeError:\n",
+    "        pass\n",
+    "\n",
+    "    # Buscar primer bloque tipo {...}\n",
+    "    match = re.search(r\"\\{.*\\}\", text, re.DOTALL)\n",
+    "\n",
+    "    if not match:\n",
+    "        raise ValueError(f\"No se encontró JSON en la respuesta: {text}\")\n",
+    "\n",
+    "    json_text = match.group(0)\n",
+    "\n",
+    "    try:\n",
+    "        return json.loads(json_text)\n",
+    "    except json.JSONDecodeError as e:\n",
+    "        raise ValueError(f\"JSON inválido: {json_text}\") from e\n",
+    "\n",
+    "\n",
+    "system_prompt = \"\"\"\n",
+    "You are an academic prediction assistant.\n",
+    "\n",
+    "Task:\n",
+    "Predict the student's expected final grade in a new fictional subject: Technical Drawing.\n",
+    "\n",
+    "Rules:\n",
+    "- Return ONLY valid JSON.\n",
+    "- Do not use markdown.\n",
+    "- Do not wrap the JSON in ```json.\n",
+    "- Do not add explanations outside the JSON.\n",
+    "- predicted_value must be between 0 and 100.\n",
+    "- prediction_target must be \"Technical_Drawing_final\".\n",
+    "- Use the student's STEM subjects, Fine Arts, ICT, attendance, support level, trend, and notes.\n",
+    "- Keep reasoning_summary and observations very short.\n",
+    "\n",
+    "Required JSON format:\n",
+    "{\n",
+    "  \"predicted_value\": 0,\n",
+    "  \"prediction_target\": \"Technical_Drawing_final\",\n",
+    "  \"reasoning_summary\": \"short explanation\",\n",
+    "  \"confidence\": 0.0,\n",
+    "  \"observations\": \"short observation\"\n",
+    "}\n",
+    "\"\"\"\n",
+    "\n",
+    "user_prompt = \"Predict the final grade for the new subject Technical Drawing.\"\n",
+    "\n",
+    "\n",
+    "async def predict_one_student(student, semaphore):\n",
+    "    async with semaphore:\n",
+    "        start = time.time()\n",
+    "\n",
+    "        student_id = student[\"student_id\"]\n",
+    "        student_context = build_compact_student_context(student)\n",
+    "\n",
+    "        human_prompt = f\"\"\"\n",
+    "STUDENT CONTEXT:\n",
+    "{student_context}\n",
+    "\n",
+    "USER REQUEST:\n",
+    "{user_prompt}\n",
+    "\n",
+    "Return only valid JSON.\n",
+    "\"\"\"\n",
+    "\n",
+    "        try:\n",
+    "            response = await model.ainvoke([\n",
+    "                (\"system\", system_prompt),\n",
+    "                (\"human\", human_prompt)\n",
+    "            ])\n",
+    "\n",
+    "            raw_text = response.content\n",
+    "            prediction = extract_json_from_response(raw_text)\n",
+    "\n",
+    "            predicted_value = prediction.get(\"predicted_value\")\n",
+    "\n",
+    "            if predicted_value is not None:\n",
+    "                predicted_value = float(predicted_value)\n",
+    "                predicted_value = max(0, min(100, predicted_value))\n",
+    "\n",
+    "            result = {\n",
+    "                \"student_id\": student_id,\n",
+    "                \"predicted_subject\": \"Technical_Drawing\",\n",
+    "                \"predicted_value\": predicted_value,\n",
+    "                \"prediction_target\": prediction.get(\"prediction_target\", \"Technical_Drawing_final\"),\n",
+    "                \"reasoning_summary\": prediction.get(\"reasoning_summary\"),\n",
+    "                \"confidence\": prediction.get(\"confidence\"),\n",
+    "                \"observations\": prediction.get(\"observations\"),\n",
+    "                \"context_chars\": len(student_context),\n",
+    "                \"elapsed_seconds\": round(time.time() - start, 2),\n",
+    "                \"raw_response\": raw_text,\n",
+    "                \"error\": None,\n",
+    "            }\n",
+    "\n",
+    "            print(\n",
+    "                f\"OK - {student_id}: \"\n",
+    "                f\"{result['predicted_value']} \"\n",
+    "                f\"({result['elapsed_seconds']}s, {len(student_context)} chars)\"\n",
+    "            )\n",
+    "\n",
+    "            return result\n",
+    "\n",
+    "        except Exception as e:\n",
+    "            result = {\n",
+    "                \"student_id\": student_id,\n",
+    "                \"predicted_subject\": \"Technical_Drawing\",\n",
+    "                \"predicted_value\": None,\n",
+    "                \"prediction_target\": \"Technical_Drawing_final\",\n",
+    "                \"reasoning_summary\": None,\n",
+    "                \"confidence\": None,\n",
+    "                \"observations\": None,\n",
+    "                \"context_chars\": len(student_context),\n",
+    "                \"elapsed_seconds\": round(time.time() - start, 2),\n",
+    "                \"raw_response\": None,\n",
+    "                \"error\": str(e),\n",
+    "            }\n",
+    "\n",
+    "            print(f\"ERROR - {student_id}: {e}\")\n",
+    "            return result\n",
+    "\n",
+    "\n",
+    "async def predict_students_async(df_context, limit=5, concurrency_limit=2):\n",
+    "    semaphore = asyncio.Semaphore(concurrency_limit)\n",
+    "\n",
+    "    rows = [\n",
+    "        row\n",
+    "        for _, row in df_context.head(limit).iterrows()\n",
+    "    ]\n",
+    "\n",
+    "    tasks = [\n",
+    "        predict_one_student(row, semaphore)\n",
+    "        for row in rows\n",
+    "    ]\n",
+    "\n",
+    "    results = await asyncio.gather(*tasks)\n",
+    "\n",
+    "    return pd.DataFrame(results)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "08d54cae",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_predictions = await predict_students_async(\n",
+    "    df_context=df_context,\n",
+    "    limit=3,\n",
+    "    concurrency_limit=2\n",
+    ")\n",
+    "\n",
+    "df_predictions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "a093f1ce",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df_predictions.to_csv(\"../datasets/nlp_pred_technical_drawing.csv\", index = False)"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e540b36d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Predicciones Simuladas — Nuevas Asignaturas y Atributos\n",
+    "\n",
+    "Las siguientes predicciones siguen el mismo esquema Pydantic que las anteriores\n",
+    "pero los valores se generan mediante aproximaciones basadas en reglas.\n",
+    "Esto permite obtener CSVs completos para los 500 estudiantes sin\n",
+    "saturar el modelo LLM local con miles de llamadas.\n",
+    "\n",
+    "Los CSVs se guardan directamente en ../datasets/.\n",
+    "\"\"\"\n",
+    "\n",
+    "import random\n",
+    "\n",
+    "random.seed(42)\n",
+    "\n",
+    "\n",
+    "def safe_float(val, default = 60.0):\n",
+    "\n",
+    "    try:\n",
+    "\n",
+    "        v = float(val)\n",
+    "\n",
+    "        return v if v > 0 else default\n",
+    "\n",
+    "    except:\n",
+    "\n",
+    "        return default\n"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "14c839de",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Predicción 2 — Informática (Computer_Science_final)\n",
+    "\n",
+    "Asignatura técnica nueva. Se predice a partir del rendimiento en\n",
+    "ICT, Mathematics y Physics, ajustado por ausencias y nivel de soporte.\n",
+    "\"\"\"\n",
+    "\n",
+    "class ComputerSciencePrediction(BaseModel):\n",
+    "\n",
+    "    student_id:        str            = Field(description='Student ID')\n",
+    "\n",
+    "    predicted_value:   float          = Field(description='Predicted Computer Science grade 0-100')\n",
+    "\n",
+    "    prediction_target: str            = Field(description='Must be Computer_Science_final')\n",
+    "\n",
+    "    confidence:        Optional[float] = Field(default=None, description='Confidence 0-1')\n",
+    "\n",
+    "\n",
+    "def simulate_computer_science(student):\n",
+    "\n",
+    "    ict      = safe_float(student.get('ICT_final'))\n",
+    "    math     = safe_float(student.get('Mathematics_final'))\n",
+    "    physics  = safe_float(student.get('Physics_final'))\n",
+    "    absences = safe_float(student.get('attendance_absences_total'), 0)\n",
+    "    support  = str(student.get('support_level_num', '1'))\n",
+    "\n",
+    "    base = ict * 0.45 + math * 0.35 + physics * 0.20\n",
+    "\n",
+    "    absence_penalty = min(absences * 0.15, 3.0)\n",
+    "\n",
+    "    support_bonus = {'0': 0.5, '1': 0.0, '2': -0.3}.get(support, 0.0)\n",
+    "\n",
+    "    noise = random.gauss(0, 1.5)\n",
+    "\n",
+    "    predicted = base - absence_penalty + support_bonus + noise\n",
+    "\n",
+    "    return round(max(40.0, min(100.0, predicted)), 2)\n",
+    "\n",
+    "\n",
+    "results_cs = []\n",
+    "\n",
+    "for _, row in df_context.iterrows():\n",
+    "\n",
+    "    predicted_value = simulate_computer_science(row)\n",
+    "\n",
+    "    status     = 'Pass' if predicted_value >= 50 else 'Fail'\n",
+    "\n",
+    "    confidence = round(min(0.95, max(0.55, 1 - abs(predicted_value - 65) / 100)), 2)\n",
+    "\n",
+    "    results_cs.append({\n",
+    "        'student_id':        row['student_id'],\n",
+    "        'predicted_subject': 'Computer_Science',\n",
+    "        'predicted_value':   predicted_value,\n",
+    "        'prediction_target': 'Computer_Science_final',\n",
+    "        'status':            status,\n",
+    "        'confidence':        confidence,\n",
+    "        'reasoning_summary': f\"Based on ICT={safe_float(row.get('ICT_final')):.1f}, Math={safe_float(row.get('Mathematics_final')):.1f}, Physics={safe_float(row.get('Physics_final')):.1f}\",\n",
+    "    })\n",
+    "\n",
+    "df_cs = pd.DataFrame(results_cs)\n",
+    "\n",
+    "df_cs.to_csv('../datasets/nlp_pred_computer_science.csv', index = False)\n",
+    "\n",
+    "print(f'Computer Science: {len(df_cs)} estudiantes')\n",
+    "print(f'  Aprobados:  {(df_cs[\"status\"] == \"Pass\").sum()}')\n",
+    "print(f'  Suspendidos: {(df_cs[\"status\"] == \"Fail\").sum()}')\n",
+    "print(f'  Media predicha: {df_cs[\"predicted_value\"].mean():.2f}')\n",
+    "\n",
+    "print(df_cs.head(5).to_string(index = False))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "ef0d6053",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Predicción 3 — Alerta de Riesgo Académico (risk_alert)\n",
+    "\n",
+    "Clasifica a cada estudiante en Low / Medium / High según la probabilidad\n",
+    "de necesitar intervención urgente antes del siguiente trimestre.\n",
+    "\"\"\"\n",
+    "\n",
+    "class RiskAlertPrediction(BaseModel):\n",
+    "\n",
+    "    student_id:   str   = Field(description='Student ID')\n",
+    "\n",
+    "    risk_level:   str   = Field(description='Low, Medium or High risk')\n",
+    "\n",
+    "    risk_score:   float = Field(description='Risk score from 0 to 1')\n",
+    "\n",
+    "    main_factors: str   = Field(description='Main contributing factors')\n",
+    "\n",
+    "\n",
+    "def simulate_risk_alert(student):\n",
+    "\n",
+    "    avg_final       = safe_float(student.get('avg_final'), 60)\n",
+    "    failed_subjects = safe_float(student.get('failed_subjects'), 0)\n",
+    "    absences        = safe_float(student.get('attendance_absences_total'), 0)\n",
+    "    support         = str(student.get('support_level_num', '1'))\n",
+    "    tendency        = str(student.get('grade_tendency', 'Uptrend'))\n",
+    "\n",
+    "    risk_score = 0.0\n",
+    "    factors    = []\n",
+    "\n",
+    "    if avg_final < 55:\n",
+    "  \n",
+    "        risk_score += 0.35; factors.append('low avg grade')\n",
+    "  \n",
+    "    elif avg_final < 60:\n",
+    "  \n",
+    "        risk_score += 0.20; factors.append('below average grade')\n",
+    "  \n",
+    "    elif avg_final < 65:\n",
+    "\n",
+    "        risk_score += 0.08\n",
+    "\n",
+    "    risk_score += min(failed_subjects * 0.08, 0.30)\n",
+    "\n",
+    "    if failed_subjects >= 2: factors.append('multiple failures')\n",
+    "\n",
+    "    risk_score += min(absences * 0.008, 0.15)\n",
+    "\n",
+    "    if absences > 15: factors.append('high absences')\n",
+    "\n",
+    "    if tendency == 'Downtrend':\n",
+    "\n",
+    "        risk_score += 0.15; factors.append('downward trend')\n",
+    "\n",
+    "    if support == '2':\n",
+    "\n",
+    "        risk_score += 0.05\n",
+    "\n",
+    "    risk_score   = round(min(1.0, max(0.0, risk_score + random.gauss(0, 0.03))), 4)\n",
+    "\n",
+    "    risk_level   = 'High' if risk_score >= 0.55 else ('Medium' if risk_score >= 0.30 else 'Low')\n",
+    "\n",
+    "    main_factors = ', '.join(factors) if factors else 'none'\n",
+    "\n",
+    "    return risk_score, risk_level, main_factors\n",
+    "\n",
+    "\n",
+    "results_risk = []\n",
+    "\n",
+    "for _, row in df_context.iterrows():\n",
+    "\n",
+    "    risk_score, risk_level, main_factors = simulate_risk_alert(row)\n",
+    "\n",
+    "    results_risk.append({\n",
+    "  \n",
+    "        'student_id':                  row['student_id'],\n",
+    "        'risk_level':                  risk_level,\n",
+    "        'risk_score':                  risk_score,\n",
+    "        'main_factors':                main_factors,\n",
+    "        'avg_final':                   row['avg_final'],\n",
+    "        'failed_subjects':             row['failed_subjects'],\n",
+    "        'attendance_absences_total':   row['attendance_absences_total'],\n",
+    "        'grade_tendency':              row['grade_tendency'],\n",
+    "  \n",
+    "    })\n",
+    "\n",
+    "df_risk = pd.DataFrame(results_risk)\n",
+    "\n",
+    "df_risk.to_csv('../datasets/nlp_pred_risk_alert.csv', index = False)\n",
+    "\n",
+    "print(f'Risk Alert: {len(df_risk)} estudiantes')\n",
+    "print(df_risk['risk_level'].value_counts().to_string())\n",
+    "\n",
+    "print(f'\\nEstudiantes High Risk (top 10):')\n",
+    "\n",
+    "print(df_risk[df_risk['risk_level'] == 'High'][['student_id', 'risk_score', 'main_factors', 'avg_final']].head(10).to_string(index = False))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "dedf1331",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "\"\"\"\n",
+    "Predicción 4 — Recomendación de Carrera (career_recommendation)\n",
+    "\n",
+    "Clasifica al estudiante en un área vocacional basándose en sus\n",
+    "fortalezas académicas relativas: STEM, Humanities, Business o Arts.\n",
+    "\"\"\"\n",
+    "\n",
+    "class CareerRecommendation(BaseModel):\n",
+    "\n",
+    "    student_id:   str   = Field(description='Student ID')\n",
+    "\n",
+    "    career_area:  str   = Field(description='STEM, Humanities, Business or Arts')\n",
+    "\n",
+    "    confidence:   float = Field(description='Confidence from 0 to 1')\n",
+    "\n",
+    "    top_subjects: str   = Field(description='Top 3 subjects driving this recommendation')\n",
+    "\n",
+    "\n",
+    "STEM_SUBJ       = ['Mathematics', 'Science', 'ICT', 'Physics', 'Chemistry', 'Biology', 'Additional_Math']\n",
+    "HUMANITIES_SUBJ = ['English', 'History', 'Civics', 'Geography', 'Swahili', 'French', 'Religious_Education']\n",
+    "BUSINESS_SUBJ   = ['Business_Studies', 'Entrepreneurship', 'Agriculture']\n",
+    "ARTS_SUBJ       = ['Fine_Arts', 'Music', 'Physical_Education']\n",
+    "\n",
+    "\n",
+    "def area_avg(student, subjects):\n",
+    "\n",
+    "    vals = [safe_float(student.get(f'{s}_final'), 0) for s in subjects]\n",
+    "\n",
+    "    vals = [v for v in vals if v > 0]\n",
+    "\n",
+    "    return sum(vals) / len(vals) if vals else 0.0\n",
+    "\n",
+    "\n",
+    "def simulate_career_recommendation(student):\n",
+    "\n",
+    "    scores = {\n",
+    "        'STEM':       area_avg(student, STEM_SUBJ),\n",
+    "        'Humanities': area_avg(student, HUMANITIES_SUBJ),\n",
+    "        'Business':   area_avg(student, BUSINESS_SUBJ),\n",
+    "        'Arts':       area_avg(student, ARTS_SUBJ),\n",
+    "    }\n",
+    "\n",
+    "    best_area  = max(scores, key = scores.get)\n",
+    "    best_score = scores[best_area]\n",
+    "    total      = sum(scores.values())\n",
+    "\n",
+    "    confidence = round(best_score / total if total > 0 else 0.25, 4)\n",
+    "\n",
+    "    all_subj = STEM_SUBJ + HUMANITIES_SUBJ + BUSINESS_SUBJ + ARTS_SUBJ\n",
+    "\n",
+    "    subj_scores = {s: safe_float(student.get(f'{s}_final'), 0) for s in all_subj if safe_float(student.get(f'{s}_final'), 0) > 0}\n",
+    "\n",
+    "    top3 = ', '.join(sorted(subj_scores, key = subj_scores.get, reverse = True)[:3])\n",
+    "\n",
+    "    return best_area, confidence, top3, scores\n",
+    "\n",
+    "\n",
+    "results_career = []\n",
+    "\n",
+    "for _, row in df_context.iterrows():\n",
+    "\n",
+    "    career_area, confidence, top3, scores = simulate_career_recommendation(row)\n",
+    "\n",
+    "    results_career.append({\n",
+    "        'student_id':        row['student_id'],\n",
+    "        'career_area':       career_area,\n",
+    "        'confidence':        confidence,\n",
+    "        'top_subjects':      top3,\n",
+    "        'score_stem':        round(scores['STEM'], 2),\n",
+    "        'score_humanities':  round(scores['Humanities'], 2),\n",
+    "        'score_business':    round(scores['Business'], 2),\n",
+    "        'score_arts':        round(scores['Arts'], 2),\n",
+    "        'avg_final':         row['avg_final'],\n",
+    "    })\n",
+    "\n",
+    "df_career = pd.DataFrame(results_career)\n",
+    "\n",
+    "df_career.to_csv('../datasets/nlp_pred_career_recommendation.csv', index = False)\n",
+    "\n",
+    "print(f'Career Recommendation: {len(df_career)} estudiantes')\n",
+    "print(df_career['career_area'].value_counts().to_string())\n",
+    "\n",
+    "print(df_career[['student_id', 'career_area', 'confidence', 'top_subjects']].head(10).to_string(index = False))"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "internal_venv (3.13.11)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.13.11"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_attendance.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_demographics.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_school_group.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_student_performance.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_subject_performance.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_support.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
internal/src/models/statistics/dashboard_term_progression.json


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_10.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_12.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_14.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_16.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_163.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_171.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_18.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_20.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 18 - 0
internal/src/models/statistics/iframe_figures/figure_8.html


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 227 - 0
internal/src/models/statistics/stats.ipynb


+ 13 - 0
user_platform/Dockerfile

@@ -0,0 +1,13 @@
+FROM python:3.11-slim
+
+WORKDIR /app
+
+COPY src/backend/requirements.txt .
+RUN pip install --no-cache-dir -r requirements.txt
+
+# Copiar el backend y el frontend
+COPY src/backend ./src/backend
+COPY src/frontend ./src/frontend
+
+# El comando se ejecuta desde /app
+CMD ["uvicorn", "src.backend.app:app", "--host", "0.0.0.0", "--port", "8000"]

+ 211 - 0
user_platform/schemas/001_platform_table_setup.schema.sql

@@ -0,0 +1,211 @@
+
+CREATE TABLE roles (
+    id_rol SERIAL PRIMARY KEY,
+    nombre VARCHAR(50) UNIQUE NOT NULL
+);
+
+CREATE TABLE usuarios (
+    id_usuario SERIAL PRIMARY KEY,
+    nombre VARCHAR(100) NOT NULL,
+    apellidos VARCHAR(150),
+    email VARCHAR(150) UNIQUE NOT NULL,
+    fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    activo BOOLEAN DEFAULT TRUE,
+    id_rol INT NOT NULL,
+
+    CONSTRAINT fk_usuario_rol
+        FOREIGN KEY(id_rol)
+        REFERENCES roles(id_rol)
+);
+
+CREATE TABLE credenciales (
+    id_credencial SERIAL PRIMARY KEY,
+    id_usuario INT UNIQUE NOT NULL,
+    password_hash TEXT NOT NULL,
+    ultimo_login TIMESTAMP,
+
+    CONSTRAINT fk_credencial_usuario
+        FOREIGN KEY(id_usuario)
+        REFERENCES usuarios(id_usuario)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE profesores (
+    id_profesor SERIAL PRIMARY KEY,
+    id_usuario INT UNIQUE NOT NULL,
+    especialidad VARCHAR(100),
+
+    CONSTRAINT fk_profesor_usuario
+        FOREIGN KEY(id_usuario)
+        REFERENCES usuarios(id_usuario)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE alumnos (
+    id_alumno SERIAL PRIMARY KEY,
+    id_usuario INT UNIQUE NOT NULL,
+    nivel VARCHAR(50),
+
+    CONSTRAINT fk_alumno_usuario
+        FOREIGN KEY(id_usuario)
+        REFERENCES usuarios(id_usuario)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE cursos (
+    id_curso SERIAL PRIMARY KEY,
+    nombre VARCHAR(150) NOT NULL,
+    descripcion TEXT,
+    fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    id_profesor INT NOT NULL,
+
+    CONSTRAINT fk_curso_profesor
+        FOREIGN KEY(id_profesor)
+        REFERENCES profesores(id_profesor)
+);
+
+CREATE TABLE matriculas (
+    id_matricula SERIAL PRIMARY KEY,
+    id_alumno INT NOT NULL,
+    id_curso INT NOT NULL,
+    fecha_matricula TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+
+    CONSTRAINT fk_matricula_alumno
+        FOREIGN KEY(id_alumno)
+        REFERENCES alumnos(id_alumno)
+        ON DELETE CASCADE,
+
+    CONSTRAINT fk_matricula_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE,
+
+    CONSTRAINT unique_matricula
+        UNIQUE(id_alumno, id_curso)
+);
+
+CREATE TABLE actividades (
+    id_actividad SERIAL PRIMARY KEY,
+    titulo VARCHAR(200) NOT NULL,
+    descripcion TEXT,
+    fecha_publicacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    fecha_entrega TIMESTAMP,
+    puntuacion_maxima NUMERIC(5,2),
+    id_curso INT NOT NULL,
+
+    CONSTRAINT fk_actividad_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE entregas (
+    id_entrega SERIAL PRIMARY KEY,
+    id_actividad INT NOT NULL,
+    id_alumno INT NOT NULL,
+    fecha_entrega TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+    contenido TEXT,
+    estado VARCHAR(50) DEFAULT 'pendiente',
+
+    CONSTRAINT fk_entrega_actividad
+        FOREIGN KEY(id_actividad)
+        REFERENCES actividades(id_actividad)
+        ON DELETE CASCADE,
+
+    CONSTRAINT fk_entrega_alumno
+        FOREIGN KEY(id_alumno)
+        REFERENCES alumnos(id_alumno)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE calificaciones (
+    id_calificacion SERIAL PRIMARY KEY,
+    id_entrega INT UNIQUE NOT NULL,
+    nota NUMERIC(5,2),
+    observaciones TEXT,
+    fecha_calificacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+
+    CONSTRAINT fk_calificacion_entrega
+        FOREIGN KEY(id_entrega)
+        REFERENCES entregas(id_entrega)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE progreso (
+    id_progreso SERIAL PRIMARY KEY,
+    id_alumno INT NOT NULL,
+    id_curso INT NOT NULL,
+    porcentaje NUMERIC(5,2) DEFAULT 0,
+
+    CONSTRAINT fk_progreso_alumno
+        FOREIGN KEY(id_alumno)
+        REFERENCES alumnos(id_alumno)
+        ON DELETE CASCADE,
+
+    CONSTRAINT fk_progreso_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE,
+
+    CONSTRAINT unique_progreso
+        UNIQUE(id_alumno, id_curso)
+);
+
+CREATE TABLE horarios (
+    id_horario SERIAL PRIMARY KEY,
+    id_curso INT NOT NULL,
+    dia_semana INT NOT NULL CHECK (dia_semana BETWEEN 1 AND 5),
+    hora_inicio TIME NOT NULL,
+    hora_fin TIME NOT NULL,
+    aula VARCHAR(50),
+
+    CONSTRAINT fk_horario_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE proyectos (
+    id_proyecto SERIAL PRIMARY KEY,
+    id_curso INT NOT NULL,
+    titulo VARCHAR(200) NOT NULL,
+    descripcion TEXT,
+    fecha_entrega TIMESTAMP,
+    estado VARCHAR(50) DEFAULT 'en curso',
+    porcentaje_completado NUMERIC(5,2) DEFAULT 0,
+
+    CONSTRAINT fk_proyecto_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE proyectos_estudiantes (
+    id_proyecto INT NOT NULL,
+    id_alumno INT NOT NULL,
+
+    PRIMARY KEY (id_proyecto, id_alumno),
+    CONSTRAINT fk_pe_proyecto
+        FOREIGN KEY(id_proyecto)
+        REFERENCES proyectos(id_proyecto)
+        ON DELETE CASCADE,
+    CONSTRAINT fk_pe_alumno
+        FOREIGN KEY(id_alumno)
+        REFERENCES alumnos(id_alumno)
+        ON DELETE CASCADE
+);
+
+CREATE TABLE examenes (
+    id_examen SERIAL PRIMARY KEY,
+    id_curso INT NOT NULL,
+    titulo VARCHAR(200) NOT NULL,
+    temario TEXT,
+    fecha TIMESTAMP NOT NULL,
+    duracion_minutos INT NOT NULL,
+    modalidad VARCHAR(50),
+
+    CONSTRAINT fk_examen_curso
+        FOREIGN KEY(id_curso)
+        REFERENCES cursos(id_curso)
+        ON DELETE CASCADE
+);

BIN
user_platform/schemas/platform_table.pdf


+ 619 - 0
user_platform/seed.py

@@ -0,0 +1,619 @@
+#!/usr/bin/env python3
+"""
+Seed de la base de datos de la plataforma de usuarios.
+
+Crea: roles, usuarios, credenciales, profesores, alumnos,
+      cursos, matrículas, actividades, entregas, calificaciones y progreso.
+
+Uso (desde user_platform/):
+    python seed.py
+
+La contraseña por defecto de todos los usuarios es: Password123!
+"""
+
+import os
+import random
+from datetime import datetime, timedelta, timezone
+from decimal import Decimal
+
+# ── DB URL (lee el .env raíz del proyecto) ──────────────────────────────────────
+def _read_env(path: str) -> dict[str, str]:
+    result: dict[str, str] = {}
+    if os.path.exists(path):
+        with open(path) as f:
+            for line in f:
+                line = line.strip()
+                if line and not line.startswith("#") and "=" in line:
+                    key, _, val = line.partition("=")
+                    result[key.strip()] = val.strip()
+    return result
+
+# Busca el .env raíz subiendo desde user_platform/
+_here = os.path.dirname(os.path.abspath(__file__))
+env = _read_env(os.path.join(_here, "..", ".env"))
+
+pg_user = env.get("PLATFORM_DB_USER", "user-database-user")
+pg_pass = env.get("PLATFORM_DB_PASSWORD", "")
+pg_db   = env.get("PLATFORM_DB_NAME", "user-database")
+pg_port = env.get("PLATFORM_DB_PORT", "55557")
+DB_URL  = os.environ.get("PLATFORM_DB_URL", f"postgresql://{pg_user}:{pg_pass}@localhost:{pg_port}/{pg_db}")
+
+# ── ORM ─────────────────────────────────────────────────────────────────────────
+from sqlalchemy import create_engine, Column, Integer, String, Boolean, Text, DateTime, Numeric, ForeignKey, UniqueConstraint
+from sqlalchemy.orm import sessionmaker, DeclarativeBase, relationship
+from passlib.context import CryptContext
+
+pwd_ctx = CryptContext(schemes=["bcrypt"], deprecated="auto")
+
+engine = create_engine(DB_URL, pool_pre_ping=True)
+Session = sessionmaker(bind=engine)
+
+
+class Base(DeclarativeBase):
+    pass
+
+
+class Rol(Base):
+    __tablename__ = "roles"
+    id_rol   = Column(Integer, primary_key=True)
+    nombre   = Column(String(50), unique=True, nullable=False)
+    usuarios = relationship("Usuario", back_populates="rol")
+
+
+class Usuario(Base):
+    __tablename__ = "usuarios"
+    id_usuario    = Column(Integer, primary_key=True)
+    nombre        = Column(String(100), nullable=False)
+    apellidos     = Column(String(150))
+    email         = Column(String(150), unique=True, nullable=False)
+    fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    activo        = Column(Boolean, default=True)
+    id_rol        = Column(Integer, ForeignKey("roles.id_rol"), nullable=False)
+    rol           = relationship("Rol", back_populates="usuarios")
+    credencial    = relationship("Credencial", back_populates="usuario", uselist=False)
+    profesor      = relationship("Profesor", back_populates="usuario", uselist=False)
+    alumno        = relationship("Alumno", back_populates="usuario", uselist=False)
+
+
+class Credencial(Base):
+    __tablename__ = "credenciales"
+    id_credencial = Column(Integer, primary_key=True)
+    id_usuario    = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    password_hash = Column(Text, nullable=False)
+    ultimo_login  = Column(DateTime)
+    usuario       = relationship("Usuario", back_populates="credencial")
+
+
+class Profesor(Base):
+    __tablename__ = "profesores"
+    id_profesor  = Column(Integer, primary_key=True)
+    id_usuario   = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    especialidad = Column(String(100))
+    usuario      = relationship("Usuario", back_populates="profesor")
+    cursos       = relationship("Curso", back_populates="profesor")
+
+
+class Alumno(Base):
+    __tablename__ = "alumnos"
+    id_alumno  = Column(Integer, primary_key=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    nivel      = Column(String(50))
+    usuario    = relationship("Usuario", back_populates="alumno")
+    matriculas = relationship("Matricula", back_populates="alumno")
+    entregas   = relationship("Entrega", back_populates="alumno")
+    progreso   = relationship("Progreso", back_populates="alumno")
+
+
+class Curso(Base):
+    __tablename__ = "cursos"
+    id_curso      = Column(Integer, primary_key=True)
+    nombre        = Column(String(150), nullable=False)
+    descripcion   = Column(Text)
+    fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    id_profesor   = Column(Integer, ForeignKey("profesores.id_profesor"), nullable=False)
+    profesor      = relationship("Profesor", back_populates="cursos")
+    matriculas    = relationship("Matricula", back_populates="curso")
+    actividades   = relationship("Actividad", back_populates="curso")
+    progreso      = relationship("Progreso", back_populates="curso")
+
+
+class Matricula(Base):
+    __tablename__ = "matriculas"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+    id_matricula   = Column(Integer, primary_key=True)
+    id_alumno      = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    id_curso       = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    fecha_matricula = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    alumno         = relationship("Alumno", back_populates="matriculas")
+    curso          = relationship("Curso", back_populates="matriculas")
+
+
+class Actividad(Base):
+    __tablename__ = "actividades"
+    id_actividad     = Column(Integer, primary_key=True)
+    titulo           = Column(String(200), nullable=False)
+    descripcion      = Column(Text)
+    fecha_publicacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    fecha_entrega    = Column(DateTime)
+    puntuacion_maxima = Column(Numeric(5, 2))
+    id_curso         = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    curso            = relationship("Curso", back_populates="actividades")
+    entregas         = relationship("Entrega", back_populates="actividad")
+
+
+class Entrega(Base):
+    __tablename__ = "entregas"
+    id_entrega   = Column(Integer, primary_key=True)
+    id_actividad = Column(Integer, ForeignKey("actividades.id_actividad", ondelete="CASCADE"))
+    id_alumno    = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    fecha_entrega = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    contenido    = Column(Text)
+    estado       = Column(String(50), default="calificado")
+    actividad    = relationship("Actividad", back_populates="entregas")
+    alumno       = relationship("Alumno", back_populates="entregas")
+    calificacion = relationship("Calificacion", back_populates="entrega", uselist=False)
+
+
+class Calificacion(Base):
+    __tablename__ = "calificaciones"
+    id_calificacion  = Column(Integer, primary_key=True)
+    id_entrega       = Column(Integer, ForeignKey("entregas.id_entrega", ondelete="CASCADE"), unique=True)
+    nota             = Column(Numeric(5, 2))
+    observaciones    = Column(Text)
+    fecha_calificacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    entrega          = relationship("Entrega", back_populates="calificacion")
+
+
+class Progreso(Base):
+    __tablename__ = "progreso"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+    id_progreso = Column(Integer, primary_key=True)
+    id_alumno   = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    porcentaje  = Column(Numeric(5, 2), default=0)
+    alumno      = relationship("Alumno", back_populates="progreso")
+    curso       = relationship("Curso", back_populates="progreso")
+
+
+class Horario(Base):
+    __tablename__ = "horarios"
+    id_horario  = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    dia_semana  = Column(Integer, nullable=False)
+    hora_inicio = Column(String(5), nullable=False)
+    hora_fin    = Column(String(5), nullable=False)
+    aula        = Column(String(50))
+
+class Proyecto(Base):
+    __tablename__ = "proyectos"
+    id_proyecto = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    titulo      = Column(String(200), nullable=False)
+    descripcion = Column(Text)
+    fecha_entrega = Column(DateTime)
+    estado      = Column(String(50), default="en curso")
+    porcentaje_completado = Column(Numeric(5, 2), default=0)
+
+class ProyectoEstudiante(Base):
+    __tablename__ = "proyectos_estudiantes"
+    id_proyecto = Column(Integer, ForeignKey("proyectos.id_proyecto", ondelete="CASCADE"), primary_key=True)
+    id_alumno   = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), primary_key=True)
+
+class Examen(Base):
+    __tablename__ = "examenes"
+    id_examen   = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    titulo      = Column(String(200), nullable=False)
+    temario     = Column(Text)
+    fecha       = Column(DateTime, nullable=False)
+    duracion_minutos = Column(Integer, nullable=False)
+    modalidad   = Column(String(50))
+
+
+# ── Data ─────────────────────────────────────────────────────────────────────────
+
+DEFAULT_PASSWORD = pwd_ctx.hash("Password123!")
+
+ROLES = ["admin", "profesor", "estudiante"]
+
+ADMINS = [
+    {"nombre": "Emmanuel", "apellidos": "Bizimana", "email": "admin@opendata.edu"},
+]
+
+TEACHERS = [
+    {"nombre": "Sarah",    "apellidos": "Kamau",      "email": "s.kamau@opendata.edu",     "especialidad": "Mathematics"},
+    {"nombre": "Patrick",  "apellidos": "Habimana",   "email": "p.habimana@opendata.edu",  "especialidad": "Science"},
+    {"nombre": "Aline",    "apellidos": "Nkurunziza", "email": "a.nkurunziza@opendata.edu","especialidad": "English"},
+    {"nombre": "Moses",    "apellidos": "Kato",       "email": "m.kato@opendata.edu",      "especialidad": "ICT"},
+    {"nombre": "Faith",    "apellidos": "Okello",     "email": "f.okello@opendata.edu",    "especialidad": "History & Civics"},
+    {"nombre": "Neema",    "apellidos": "Mushi",      "email": "n.mushi@opendata.edu",     "especialidad": "Geography"},
+    {"nombre": "Samuel",   "apellidos": "Otieno",     "email": "s.otieno@opendata.edu",    "especialidad": "Biology & Chemistry"},
+]
+
+STUDENTS = [
+    {"nombre": "Grace",    "apellidos": "Uwimana",    "email": "g.uwimana@students.edu",   "nivel": "Secondary 5"},
+    {"nombre": "John",     "apellidos": "Mwangi",     "email": "j.mwangi@students.edu",    "nivel": "Secondary 3"},
+    {"nombre": "Claudine", "apellidos": "Bizimana",   "email": "c.bizimana@students.edu",  "nivel": "Secondary 2"},
+    {"nombre": "Peter",    "apellidos": "Habimana",   "email": "p.habimana2@students.edu", "nivel": "Secondary 4"},
+    {"nombre": "Amina",    "apellidos": "Kamau",      "email": "a.kamau@students.edu",     "nivel": "Secondary 1"},
+    {"nombre": "David",    "apellidos": "Okello",     "email": "d.okello@students.edu",    "nivel": "Secondary 6"},
+    {"nombre": "Fatuma",   "apellidos": "Otieno",     "email": "f.otieno@students.edu",    "nivel": "Secondary 3"},
+    {"nombre": "Joseph",   "apellidos": "Kato",       "email": "j.kato@students.edu",      "nivel": "Secondary 2"},
+    {"nombre": "Esperance","apellidos": "Nkurunziza", "email": "e.nkurunziza@students.edu","nivel": "Secondary 5"},
+    {"nombre": "Ali",      "apellidos": "Mushi",      "email": "a.mushi@students.edu",     "nivel": "Secondary 4"},
+    {"nombre": "Zawadi",   "apellidos": "Habimana",   "email": "z.habimana@students.edu",  "nivel": "Secondary 1"},
+    {"nombre": "Rehema",   "apellidos": "Uwimana",    "email": "r.uwimana@students.edu",   "nivel": "Secondary 3"},
+    {"nombre": "Baraka",   "apellidos": "Mwangi",     "email": "b.mwangi@students.edu",    "nivel": "Secondary 6"},
+    {"nombre": "Solange",  "apellidos": "Kato",       "email": "s.kato@students.edu",      "nivel": "Secondary 2"},
+    {"nombre": "Omar",     "apellidos": "Otieno",     "email": "o.otieno@students.edu",    "nivel": "Secondary 4"},
+    {"nombre": "Immaculée","apellidos": "Bizimana",   "email": "i.bizimana@students.edu",  "nivel": "Secondary 5"},
+    {"nombre": "Mussa",    "apellidos": "Kamau",      "email": "m.kamau@students.edu",     "nivel": "Secondary 1"},
+    {"nombre": "Vestine",  "apellidos": "Okello",     "email": "v.okello@students.edu",    "nivel": "Secondary 3"},
+    {"nombre": "Patrick",  "apellidos": "Mushi",      "email": "p.mushi@students.edu",     "nivel": "Secondary 6"},
+    {"nombre": "Dativa",   "apellidos": "Nkurunziza", "email": "d.nkurunziza@students.edu","nivel": "Secondary 2"},
+]
+
+COURSES = [
+    {
+        "nombre": "Advanced Mathematics — Secondary 5 & 6",
+        "descripcion": "Álgebra lineal, cálculo diferencial, estadística aplicada y resolución de problemas complejos.",
+        "teacher_email": "s.kamau@opendata.edu",
+    },
+    {
+        "nombre": "English Language & Literature",
+        "descripcion": "Comprensión lectora, escritura académica, análisis literario y comunicación oral.",
+        "teacher_email": "a.nkurunziza@opendata.edu",
+    },
+    {
+        "nombre": "Integrated Science — Secondary 1 & 2",
+        "descripcion": "Fundamentos de biología, química y física con enfoque experimental.",
+        "teacher_email": "p.habimana@opendata.edu",
+    },
+    {
+        "nombre": "ICT & Digital Literacy",
+        "descripcion": "Ofimática, programación básica, seguridad digital y gestión de datos.",
+        "teacher_email": "m.kato@opendata.edu",
+    },
+    {
+        "nombre": "History, Civics & Social Studies",
+        "descripcion": "Historia de África del Este, gobierno, derechos humanos y ciudadanía activa.",
+        "teacher_email": "f.okello@opendata.edu",
+    },
+    {
+        "nombre": "Geography & Environment",
+        "descripcion": "Geografía física y humana, cambio climático y gestión de recursos naturales.",
+        "teacher_email": "n.mushi@opendata.edu",
+    },
+    {
+        "nombre": "Biology & Chemistry — Secondary 4 & 5",
+        "descripcion": "Biología celular, genética, reacciones químicas orgánicas e inorgánicas.",
+        "teacher_email": "s.otieno@opendata.edu",
+    },
+]
+
+ACTIVITY_TEMPLATES = [
+    {
+        "titulo": "Tarea 1 — Evaluación inicial",
+        "descripcion": "Evaluación de conocimientos previos y diagnóstico del nivel del estudiante.",
+        "puntuacion_maxima": Decimal("20.00"),
+        "dias_publicacion": -60,
+        "dias_entrega": -55,
+    },
+    {
+        "titulo": "Cuestionario — Trimestre 1",
+        "descripcion": "Cuestionario de seguimiento sobre los contenidos del primer trimestre.",
+        "puntuacion_maxima": Decimal("30.00"),
+        "dias_publicacion": -40,
+        "dias_entrega": -35,
+    },
+    {
+        "titulo": "Proyecto colaborativo",
+        "descripcion": "Trabajo en grupo sobre un caso de estudio real relacionado con la asignatura.",
+        "puntuacion_maxima": Decimal("50.00"),
+        "dias_publicacion": -25,
+        "dias_entrega": -15,
+    },
+    {
+        "titulo": "Examen Parcial — Trimestre 2",
+        "descripcion": "Evaluación escrita de los contenidos del segundo trimestre.",
+        "puntuacion_maxima": Decimal("40.00"),
+        "dias_publicacion": -10,
+        "dias_entrega": -5,
+    },
+    {
+        "titulo": "Entrega final — Portfolio",
+        "descripcion": "Portfolio de trabajos realizados durante el curso con reflexión personal.",
+        "puntuacion_maxima": Decimal("60.00"),
+        "dias_publicacion": -3,
+        "dias_entrega": 10,
+    },
+]
+
+SUBMISSION_TEXTS = [
+    "He revisado el material y completado todos los ejercicios propuestos.",
+    "Adjunto mi trabajo con las correcciones indicadas en clase.",
+    "He realizado la tarea individualmente siguiendo las instrucciones del profesor.",
+    "Incluyo referencias adicionales que encontré durante la investigación.",
+    "Trabajo completado. Tuve dificultades con la parte práctica pero lo resolví.",
+]
+
+
+# ── Seed logic ───────────────────────────────────────────────────────────────────
+
+def seed():
+    print("\n=== Seed: Plataforma de Usuarios ===\n")
+
+    Base.metadata.create_all(bind=engine)
+    print("✓ Esquema verificado")
+
+    db = Session()
+    random.seed(42)
+
+    try:
+        # ── Roles
+        rol_map: dict[str, Rol] = {}
+        for nombre in ROLES:
+            r = db.query(Rol).filter(Rol.nombre == nombre).first()
+            if not r:
+                r = Rol(nombre=nombre)
+                db.add(r)
+                db.flush()
+            rol_map[nombre] = r
+        db.commit()
+        print(f"✓ Roles: {list(rol_map.keys())}")
+
+        # ── Admins
+        for data in ADMINS:
+            if db.query(Usuario).filter(Usuario.email == data["email"]).first():
+                continue
+            u = Usuario(**data, id_rol=rol_map["admin"].id_rol)
+            db.add(u)
+            db.flush()
+            db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
+        db.commit()
+        print(f"✓ Admins: {len(ADMINS)}")
+
+        # ── Teachers
+        profesor_map: dict[str, Profesor] = {}
+        for data in TEACHERS:
+            u = db.query(Usuario).filter(Usuario.email == data["email"]).first()
+            if not u:
+                u = Usuario(
+                    nombre=data["nombre"], apellidos=data["apellidos"],
+                    email=data["email"], id_rol=rol_map["profesor"].id_rol,
+                )
+                db.add(u)
+                db.flush()
+                db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
+            p = db.query(Profesor).filter(Profesor.id_usuario == u.id_usuario).first()
+            if not p:
+                p = Profesor(id_usuario=u.id_usuario, especialidad=data["especialidad"])
+                db.add(p)
+                db.flush()
+            profesor_map[data["email"]] = p
+        db.commit()
+        print(f"✓ Profesores: {len(TEACHERS)}")
+
+        # ── Students
+        alumno_list: list[Alumno] = []
+        for data in STUDENTS:
+            u = db.query(Usuario).filter(Usuario.email == data["email"]).first()
+            if not u:
+                u = Usuario(
+                    nombre=data["nombre"], apellidos=data["apellidos"],
+                    email=data["email"], id_rol=rol_map["estudiante"].id_rol,
+                )
+                db.add(u)
+                db.flush()
+                db.add(Credencial(id_usuario=u.id_usuario, password_hash=DEFAULT_PASSWORD))
+            a = db.query(Alumno).filter(Alumno.id_usuario == u.id_usuario).first()
+            if not a:
+                a = Alumno(id_usuario=u.id_usuario, nivel=data["nivel"])
+                db.add(a)
+                db.flush()
+            alumno_list.append(a)
+        db.commit()
+        print(f"✓ Alumnos: {len(STUDENTS)}")
+
+        # ── Courses
+        curso_list: list[Curso] = []
+        for data in COURSES:
+            c = db.query(Curso).filter(Curso.nombre == data["nombre"]).first()
+            if not c:
+                profesor = profesor_map[data["teacher_email"]]
+                c = Curso(
+                    nombre=data["nombre"],
+                    descripcion=data["descripcion"],
+                    id_profesor=profesor.id_profesor,
+                    fecha_creacion=datetime.now(timezone.utc) - timedelta(days=90),
+                )
+                db.add(c)
+                db.flush()
+            curso_list.append(c)
+        db.commit()
+        print(f"✓ Cursos: {len(COURSES)}")
+
+        # ── Horarios, Proyectos, Examenes
+        horario_count = 0
+        proyecto_count = 0
+        examen_count = 0
+        proyecto_list: list[Proyecto] = []
+        for curso in curso_list:
+            # Horarios
+            dias = random.sample([1, 2, 3, 4, 5], 2)
+            for dia in dias:
+                h_inicio = random.choice(["08:00", "10:00", "12:00", "14:00"])
+                h_fin = f"{int(h_inicio[:2])+2:02d}:00"
+                aula = random.choice(["Sala Digital A", "Sala Digital B", "Lab 1", "Lab 2", "Aula 101"])
+                h = Horario(id_curso=curso.id_curso, dia_semana=dia, hora_inicio=h_inicio, hora_fin=h_fin, aula=aula)
+                db.add(h)
+                horario_count += 1
+            
+            # Proyectos
+            now = datetime.now(timezone.utc)
+            p1 = Proyecto(id_curso=curso.id_curso, titulo=f"Proyecto Final: {curso.nombre}", descripcion="Aplicar los conceptos del curso en un caso práctico.", fecha_entrega=now + timedelta(days=30), estado="en curso", porcentaje_completado=Decimal(str(random.randint(10, 80))))
+            p2 = Proyecto(id_curso=curso.id_curso, titulo=f"Investigación: {curso.nombre}", descripcion="Investigación bibliográfica y presentación.", fecha_entrega=now - timedelta(days=10), estado="entregado", porcentaje_completado=Decimal("100.00"))
+            db.add_all([p1, p2])
+            db.flush()
+            proyecto_list.extend([p1, p2])
+            proyecto_count += 2
+
+            # Examenes
+            e1 = Examen(id_curso=curso.id_curso, titulo=f"Examen Parcial", temario="Unidades 1 a 3", fecha=now - timedelta(days=5), duracion_minutos=90, modalidad="Presencial")
+            e2 = Examen(id_curso=curso.id_curso, titulo=f"Examen Final", temario="Todo el temario", fecha=now + timedelta(days=25), duracion_minutos=120, modalidad="Online")
+            db.add_all([e1, e2])
+            examen_count += 2
+        
+        db.commit()
+        print(f"✓ Horarios: {horario_count}, Proyectos: {proyecto_count}, Exámenes: {examen_count}")
+
+        # ── Activities (5 per course)
+        actividad_map: dict[int, list[Actividad]] = {}
+        for curso in curso_list:
+            actividad_map[curso.id_curso] = []
+            for tmpl in ACTIVITY_TEMPLATES:
+                titulo_full = f"{curso.nombre.split('—')[0].strip()} — {tmpl['titulo']}"
+                a = db.query(Actividad).filter(Actividad.titulo == titulo_full).first()
+                if not a:
+                    now = datetime.now(timezone.utc)
+                    a = Actividad(
+                        titulo=titulo_full,
+                        descripcion=tmpl["descripcion"],
+                        puntuacion_maxima=tmpl["puntuacion_maxima"],
+                        id_curso=curso.id_curso,
+                        fecha_publicacion=now + timedelta(days=tmpl["dias_publicacion"]),
+                        fecha_entrega=now + timedelta(days=tmpl["dias_entrega"]),
+                    )
+                    db.add(a)
+                    db.flush()
+                actividad_map[curso.id_curso].append(a)
+        db.commit()
+        total_acts = sum(len(v) for v in actividad_map.values())
+        print(f"✓ Actividades: {total_acts}")
+
+        # ── Enrollments: cada alumno en 3-4 cursos aleatorios
+        matricula_count = 0
+        entrega_count = 0
+        for alumno in alumno_list:
+            n_cursos = random.randint(3, 5)
+            cursos_alumno = random.sample(curso_list, min(n_cursos, len(curso_list)))
+
+            for curso in cursos_alumno:
+                # Matrícula
+                m = db.query(Matricula).filter(
+                    Matricula.id_alumno == alumno.id_alumno,
+                    Matricula.id_curso == curso.id_curso,
+                ).first()
+                if not m:
+                    m = Matricula(
+                        id_alumno=alumno.id_alumno,
+                        id_curso=curso.id_curso,
+                        fecha_matricula=datetime.now(timezone.utc) - timedelta(days=85),
+                    )
+                    db.add(m)
+                    db.flush()
+                    matricula_count += 1
+
+                # Entregas y calificaciones (solo actividades ya cerradas)
+                actividades_cerradas = [
+                    a for a in actividad_map[curso.id_curso]
+                    if a.fecha_entrega and a.fecha_entrega.replace(tzinfo=timezone.utc) < datetime.now(timezone.utc)
+                ]
+                notas: list[float] = []
+                for actividad in actividades_cerradas:
+                    e = db.query(Entrega).filter(
+                        Entrega.id_alumno == alumno.id_alumno,
+                        Entrega.id_actividad == actividad.id_actividad,
+                    ).first()
+                    if not e:
+                        entrega_fecha = actividad.fecha_entrega - timedelta(hours=random.randint(1, 48))
+                        e = Entrega(
+                            id_actividad=actividad.id_actividad,
+                            id_alumno=alumno.id_alumno,
+                            fecha_entrega=entrega_fecha,
+                            contenido=random.choice(SUBMISSION_TEXTS),
+                            estado="calificado",
+                        )
+                        db.add(e)
+                        db.flush()
+                        entrega_count += 1
+
+                        # Nota: distribución realista centrada en 60-85
+                        max_nota = float(actividad.puntuacion_maxima)
+                        base_pct = random.gauss(0.72, 0.15)
+                        base_pct = max(0.3, min(1.0, base_pct))
+                        nota = round(base_pct * max_nota, 2)
+                        notas.append(nota / max_nota * 100)
+
+                        obs_list = [
+                            "Buen trabajo, sigue así.",
+                            "Necesita mejorar la argumentación.",
+                            "Excelente presentación y contenido.",
+                            "Trabajo correcto pero le falta profundidad.",
+                            "Muy buen desempeño.",
+                        ]
+                        db.add(Calificacion(
+                            id_entrega=e.id_entrega,
+                            nota=Decimal(str(nota)),
+                            observaciones=random.choice(obs_list),
+                            fecha_calificacion=entrega_fecha + timedelta(days=random.randint(1, 5)),
+                        ))
+
+                # Progreso del alumno en este curso
+                if actividades_cerradas:
+                    entregadas = len(actividades_cerradas)
+                    total_act = len(actividad_map[curso.id_curso])
+                    pct_entrega = (entregadas / total_act) * 100
+                    pct_nota = (sum(notas) / len(notas)) if notas else 0
+                    porcentaje = round((pct_entrega * 0.4 + pct_nota * 0.6), 2)
+
+                    prog = db.query(Progreso).filter(
+                        Progreso.id_alumno == alumno.id_alumno,
+                        Progreso.id_curso == curso.id_curso,
+                    ).first()
+                    if not prog:
+                        db.add(Progreso(
+                            id_alumno=alumno.id_alumno,
+                            id_curso=curso.id_curso,
+                            porcentaje=Decimal(str(porcentaje)),
+                        ))
+
+                # Asignar proyectos del curso al alumno
+                proyectos_curso = [p for p in proyecto_list if p.id_curso == curso.id_curso]
+                for p in proyectos_curso:
+                    pe = db.query(ProyectoEstudiante).filter(ProyectoEstudiante.id_proyecto == p.id_proyecto, ProyectoEstudiante.id_alumno == alumno.id_alumno).first()
+                    if not pe:
+                        db.add(ProyectoEstudiante(id_proyecto=p.id_proyecto, id_alumno=alumno.id_alumno))
+
+        db.commit()
+        print(f"✓ Matrículas:  {matricula_count}")
+        print(f"✓ Entregas:    {entrega_count}")
+        print(f"✓ Progreso calculado")
+
+        # ── Resumen final
+        print("\n─── Resumen ────────────────────────────────")
+        print(f"  Usuarios totales:  {db.query(Usuario).count()}")
+        print(f"  Profesores:        {db.query(Profesor).count()}")
+        print(f"  Alumnos:           {db.query(Alumno).count()}")
+        print(f"  Cursos:            {db.query(Curso).count()}")
+        print(f"  Actividades:       {db.query(Actividad).count()}")
+        print(f"  Entregas:          {db.query(Entrega).count()}")
+        print(f"  Calificaciones:    {db.query(Calificacion).count()}")
+        print(f"  Progreso registros:{db.query(Progreso).count()}")
+        print(f"  Horarios:          {db.query(Horario).count()}")
+        print(f"  Proyectos:         {db.query(Proyecto).count()}")
+        print(f"  Exámenes:          {db.query(Examen).count()}")
+        print("────────────────────────────────────────────")
+        print("\n  Contraseña de todos los usuarios: Password123!")
+        print("  Ya puedes hacer login desde la API.\n")
+
+    except Exception as e:
+        db.rollback()
+        print(f"\n✗ Error durante el seed: {e}")
+        raise
+    finally:
+        db.close()
+
+
+if __name__ == "__main__":
+    seed()

+ 47 - 0
user_platform/src/backend/app.py

@@ -0,0 +1,47 @@
+from fastapi import FastAPI
+from fastapi.staticfiles import StaticFiles
+from fastapi.responses import FileResponse
+from fastapi.middleware.cors import CORSMiddleware
+import os
+
+from .routers import student, auth
+
+app = FastAPI(title="BridgeLearn Student Platform")
+
+app.add_middleware(
+    CORSMiddleware,
+    allow_origins=["*"],
+    allow_credentials=True,
+    allow_methods=["*"],
+    allow_headers=["*"],
+)
+
+# Include API routers
+app.include_router(student.router)
+app.include_router(auth.router)
+
+# Mount frontend static files
+frontend_dir = os.path.join(os.path.dirname(__file__), "..", "frontend")
+if os.path.exists(frontend_dir):
+    app.mount("/static", StaticFiles(directory=frontend_dir), name="static")
+
+@app.get("/")
+def serve_index():
+    index_path = os.path.join(frontend_dir, "index.html")
+    if os.path.exists(index_path):
+        return FileResponse(index_path)
+    return {"message": "Frontend not found, but API is running."}
+
+@app.get("/login")
+def serve_login():
+    login_path = os.path.join(frontend_dir, "login.html")
+    if os.path.exists(login_path):
+        return FileResponse(login_path)
+    return {"message": "Login not found."}
+
+@app.get("/dashboard")
+def serve_dashboard():
+    dashboard_path = os.path.join(frontend_dir, "dashboard.html")
+    if os.path.exists(dashboard_path):
+        return FileResponse(dashboard_path)
+    return {"message": "Dashboard not found."}

+ 23 - 0
user_platform/src/backend/database.py

@@ -0,0 +1,23 @@
+import os
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+
+# In Docker, we pass PLATFORM_DB_URL or use env vars
+pg_user = os.getenv("PLATFORM_DB_USER", "user-database-user")
+pg_pass = os.getenv("PLATFORM_DB_PASSWORD", "password123")
+pg_db   = os.getenv("PLATFORM_DB_NAME", "user-database")
+pg_host = os.getenv("PLATFORM_DB_HOST", "platform_db") # Container name in compose
+pg_port = os.getenv("PLATFORM_DB_PORT", "5432")
+
+default_url = f"postgresql://{pg_user}:{pg_pass}@{pg_host}:{pg_port}/{pg_db}"
+DB_URL = os.getenv("PLATFORM_DB_URL", default_url)
+
+engine = create_engine(DB_URL, pool_pre_ping=True)
+SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
+
+def get_db():
+    db = SessionLocal()
+    try:
+        yield db
+    finally:
+        db.close()

+ 157 - 0
user_platform/src/backend/models.py

@@ -0,0 +1,157 @@
+from datetime import datetime, timezone
+from sqlalchemy import Column, Integer, String, Boolean, Text, DateTime, Numeric, ForeignKey, UniqueConstraint
+from sqlalchemy.orm import DeclarativeBase, relationship
+
+class Base(DeclarativeBase):
+    pass
+
+class Rol(Base):
+    __tablename__ = "roles"
+    id_rol   = Column(Integer, primary_key=True)
+    nombre   = Column(String(50), unique=True, nullable=False)
+    usuarios = relationship("Usuario", back_populates="rol")
+
+class Usuario(Base):
+    __tablename__ = "usuarios"
+    id_usuario    = Column(Integer, primary_key=True)
+    nombre        = Column(String(100), nullable=False)
+    apellidos     = Column(String(150))
+    email         = Column(String(150), unique=True, nullable=False)
+    fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    activo        = Column(Boolean, default=True)
+    id_rol        = Column(Integer, ForeignKey("roles.id_rol"), nullable=False)
+    rol           = relationship("Rol", back_populates="usuarios")
+    alumno        = relationship("Alumno", back_populates="usuario", uselist=False)
+    profesor      = relationship("Profesor", back_populates="usuario", uselist=False)
+    credencial    = relationship("Credencial", back_populates="usuario", uselist=False)
+
+class Credencial(Base):
+    __tablename__ = "credenciales"
+    id_credencial = Column(Integer, primary_key=True)
+    id_usuario    = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    password_hash = Column(Text, nullable=False)
+    ultimo_login  = Column(DateTime)
+    usuario       = relationship("Usuario", back_populates="credencial")
+
+class Alumno(Base):
+    __tablename__ = "alumnos"
+    id_alumno  = Column(Integer, primary_key=True)
+    id_usuario = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    nivel      = Column(String(50))
+    usuario    = relationship("Usuario", back_populates="alumno")
+    matriculas = relationship("Matricula", back_populates="alumno")
+    entregas   = relationship("Entrega", back_populates="alumno")
+    progreso   = relationship("Progreso", back_populates="alumno")
+
+class Profesor(Base):
+    __tablename__ = "profesores"
+    id_profesor  = Column(Integer, primary_key=True)
+    id_usuario   = Column(Integer, ForeignKey("usuarios.id_usuario", ondelete="CASCADE"), unique=True)
+    especialidad = Column(String(100))
+    cursos       = relationship("Curso", back_populates="profesor")
+    usuario      = relationship("Usuario", back_populates="profesor")
+
+class Curso(Base):
+    __tablename__ = "cursos"
+    id_curso      = Column(Integer, primary_key=True)
+    nombre        = Column(String(150), nullable=False)
+    descripcion   = Column(Text)
+    fecha_creacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    id_profesor   = Column(Integer, ForeignKey("profesores.id_profesor"), nullable=False)
+    profesor      = relationship("Profesor", back_populates="cursos")
+    matriculas    = relationship("Matricula", back_populates="curso")
+    actividades   = relationship("Actividad", back_populates="curso")
+    progreso      = relationship("Progreso", back_populates="curso")
+
+class Matricula(Base):
+    __tablename__ = "matriculas"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+    id_matricula   = Column(Integer, primary_key=True)
+    id_alumno      = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    id_curso       = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    fecha_matricula = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    alumno         = relationship("Alumno", back_populates="matriculas")
+    curso          = relationship("Curso", back_populates="matriculas")
+
+class Actividad(Base):
+    __tablename__ = "actividades"
+    id_actividad     = Column(Integer, primary_key=True)
+    titulo           = Column(String(200), nullable=False)
+    descripcion      = Column(Text)
+    fecha_publicacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    fecha_entrega    = Column(DateTime)
+    puntuacion_maxima = Column(Numeric(5, 2))
+    id_curso         = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    curso            = relationship("Curso", back_populates="actividades")
+    entregas         = relationship("Entrega", back_populates="actividad")
+
+class Entrega(Base):
+    __tablename__ = "entregas"
+    id_entrega   = Column(Integer, primary_key=True)
+    id_actividad = Column(Integer, ForeignKey("actividades.id_actividad", ondelete="CASCADE"))
+    id_alumno    = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    fecha_entrega = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    contenido    = Column(Text)
+    estado       = Column(String(50), default="calificado")
+    actividad    = relationship("Actividad", back_populates="entregas")
+    alumno       = relationship("Alumno", back_populates="entregas")
+    calificacion = relationship("Calificacion", back_populates="entrega", uselist=False)
+
+class Calificacion(Base):
+    __tablename__ = "calificaciones"
+    id_calificacion  = Column(Integer, primary_key=True)
+    id_entrega       = Column(Integer, ForeignKey("entregas.id_entrega", ondelete="CASCADE"), unique=True)
+    nota             = Column(Numeric(5, 2))
+    observaciones    = Column(Text)
+    fecha_calificacion = Column(DateTime, default=lambda: datetime.now(timezone.utc))
+    entrega          = relationship("Entrega", back_populates="calificacion")
+
+class Progreso(Base):
+    __tablename__ = "progreso"
+    __table_args__ = (UniqueConstraint("id_alumno", "id_curso"),)
+    id_progreso = Column(Integer, primary_key=True)
+    id_alumno   = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"))
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    porcentaje  = Column(Numeric(5, 2), default=0)
+    alumno      = relationship("Alumno", back_populates="progreso")
+    curso       = relationship("Curso", back_populates="progreso")
+
+class Horario(Base):
+    __tablename__ = "horarios"
+    id_horario  = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    dia_semana  = Column(Integer, nullable=False)
+    hora_inicio = Column(String(5), nullable=False) # Store as "HH:MM" for simplicity in JSON, mapped to TIME in DB
+    hora_fin    = Column(String(5), nullable=False)
+    aula        = Column(String(50))
+    curso       = relationship("Curso")
+
+class Proyecto(Base):
+    __tablename__ = "proyectos"
+    id_proyecto = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    titulo      = Column(String(200), nullable=False)
+    descripcion = Column(Text)
+    fecha_entrega = Column(DateTime)
+    estado      = Column(String(50), default="en curso")
+    porcentaje_completado = Column(Numeric(5, 2), default=0)
+    curso       = relationship("Curso")
+    estudiantes = relationship("ProyectoEstudiante", back_populates="proyecto")
+
+class ProyectoEstudiante(Base):
+    __tablename__ = "proyectos_estudiantes"
+    id_proyecto = Column(Integer, ForeignKey("proyectos.id_proyecto", ondelete="CASCADE"), primary_key=True)
+    id_alumno   = Column(Integer, ForeignKey("alumnos.id_alumno", ondelete="CASCADE"), primary_key=True)
+    proyecto    = relationship("Proyecto", back_populates="estudiantes")
+    alumno      = relationship("Alumno")
+
+class Examen(Base):
+    __tablename__ = "examenes"
+    id_examen   = Column(Integer, primary_key=True)
+    id_curso    = Column(Integer, ForeignKey("cursos.id_curso", ondelete="CASCADE"))
+    titulo      = Column(String(200), nullable=False)
+    temario     = Column(Text)
+    fecha       = Column(DateTime, nullable=False)
+    duracion_minutos = Column(Integer, nullable=False)
+    modalidad   = Column(String(50))
+    curso       = relationship("Curso")

+ 11 - 0
user_platform/src/backend/requirements.txt

@@ -0,0 +1,11 @@
+fastapi==0.109.2
+uvicorn==0.27.1
+sqlalchemy==2.0.25
+psycopg2-binary==2.9.9
+pydantic==2.6.1
+pydantic-settings==2.1.0
+python-dotenv==1.0.1
+PyJWT==2.8.0
+passlib==1.7.4
+bcrypt<4.1
+python-multipart==0.0.9

+ 1 - 0
user_platform/src/backend/routers/__init__.py

@@ -0,0 +1 @@
+from .student import router as student_router

+ 75 - 0
user_platform/src/backend/routers/auth.py

@@ -0,0 +1,75 @@
+from fastapi import APIRouter, Depends, HTTPException, status, Response, Request
+from fastapi.security import OAuth2PasswordRequestForm
+from sqlalchemy.orm import Session
+from datetime import datetime, timedelta, timezone
+from passlib.context import CryptContext
+import jwt
+import os
+
+from ..database import get_db
+from ..models import Usuario, Credencial, Alumno
+
+# Security setup
+pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
+SECRET_KEY = os.environ.get("JWT_SECRET_KEY", "changeme-use-a-strong-secret-in-production")
+ALGORITHM = "HS256"
+ACCESS_TOKEN_EXPIRE_MINUTES = int(os.environ.get("JWT_ACCESS_TOKEN_EXPIRE_MINUTES", "1440")) # 24 hours
+
+router = APIRouter(prefix="/api/auth", tags=["Auth"])
+
+def create_access_token(data: dict):
+    to_encode = data.copy()
+    expire = datetime.now(timezone.utc) + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
+    to_encode.update({"exp": expire})
+    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
+    return encoded_jwt
+
+@router.post("/login")
+def login(response: Response, form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)):
+    usuario = db.query(Usuario).filter(Usuario.email == form_data.username).first()
+    if not usuario or not usuario.activo:
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Credenciales inválidas")
+    
+    credencial = db.query(Credencial).filter(Credencial.id_usuario == usuario.id_usuario).first()
+    if not credencial or not pwd_context.verify(form_data.password, credencial.password_hash):
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Credenciales inválidas")
+        
+    alumno = db.query(Alumno).filter(Alumno.id_usuario == usuario.id_usuario).first()
+    if not alumno:
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="No eres un estudiante")
+
+    access_token = create_access_token(data={"sub": str(alumno.id_alumno)})
+    response.set_cookie(
+        key="access_token",
+        value=f"Bearer {access_token}",
+        httponly=True,
+        max_age=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
+        expires=ACCESS_TOKEN_EXPIRE_MINUTES * 60,
+        samesite="lax",
+    )
+    return {"message": "Sesión iniciada correctamente", "student_id": alumno.id_alumno}
+
+@router.post("/logout")
+def logout(response: Response):
+    response.delete_cookie(key="access_token", httponly=True, samesite="lax")
+    return {"message": "Sesión cerrada correctamente"}
+
+def get_current_student(request: Request, db: Session = Depends(get_db)):
+    token = request.cookies.get("access_token")
+    if not token or not token.startswith("Bearer "):
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="No autenticado")
+        
+    token = token.split(" ")[1]
+    try:
+        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
+        student_id: str = payload.get("sub")
+        if student_id is None:
+            raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token inválido")
+    except jwt.PyJWTError:
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Token inválido")
+        
+    alumno = db.query(Alumno).filter(Alumno.id_alumno == int(student_id)).first()
+    if not alumno:
+        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Usuario no encontrado")
+        
+    return alumno

+ 244 - 0
user_platform/src/backend/routers/student.py

@@ -0,0 +1,244 @@
+from fastapi import APIRouter, Depends, HTTPException, Body
+from pydantic import BaseModel
+from sqlalchemy.orm import Session, joinedload
+from sqlalchemy import func
+from datetime import datetime, timezone
+from ..database import get_db
+from ..models import Alumno, Curso, Matricula, Actividad, Entrega, Calificacion, Progreso, Profesor, Horario, Proyecto, ProyectoEstudiante, Examen
+from .auth import get_current_student
+
+router = APIRouter(prefix="/api/student", tags=["Student"])
+
+class TaskSubmit(BaseModel):
+    contenido: str
+
+class ProjectProgress(BaseModel):
+    progress: float
+
+@router.get("/dashboard")
+def get_dashboard(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    alumno = db.query(Alumno).options(joinedload(Alumno.usuario)).filter(Alumno.id_alumno == student_id).first()
+    
+    progresos = db.query(Progreso).filter(Progreso.id_alumno == student_id).all()
+    avg_progress = sum(float(p.porcentaje) for p in progresos) / len(progresos) if progresos else 0
+    
+    # Gamification mechanics
+    xp = int(avg_progress * 150)
+    level = max(1, xp // 1000)
+    streak = 12 # Hardcoded for demo purposes
+    
+    matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
+    curso_ids = [m.id_curso for m in matriculas]
+    active_courses = len(curso_ids)
+
+    pending_tasks = 0
+    if curso_ids:
+        actividades = db.query(Actividad).filter(Actividad.id_curso.in_(curso_ids)).all()
+        now = datetime.now(timezone.utc)
+        for a in actividades:
+            e = db.query(Entrega).filter(Entrega.id_actividad == a.id_actividad, Entrega.id_alumno == student_id).first()
+            if not e and (not a.fecha_entrega or a.fecha_entrega.replace(tzinfo=timezone.utc) >= now):
+                pending_tasks += 1
+
+    return {
+        "id": alumno.id_alumno,
+        "name": f"{alumno.usuario.nombre} {alumno.usuario.apellidos or ''}".strip(),
+        "email": alumno.usuario.email,
+        "level_name": alumno.nivel,
+        "xp": xp,
+        "level": level,
+        "streak": streak,
+        "avg_progress": round(avg_progress, 1),
+        "gpa": round(avg_progress * 0.1, 1), # Simulated GPA 0-10
+        "active_courses": active_courses,
+        "pending_tasks": pending_tasks
+    }
+
+@router.get("/courses")
+def get_courses(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    matriculas = db.query(Matricula).options(
+        joinedload(Matricula.curso).joinedload(Curso.profesor).joinedload(Profesor.usuario)
+    ).filter(Matricula.id_alumno == student_id).all()
+    
+    courses = []
+    for m in matriculas:
+        c = m.curso
+        prof_name = f"{c.profesor.usuario.nombre} {c.profesor.usuario.apellidos or ''}".strip()
+        
+        progreso = db.query(Progreso).filter(Progreso.id_alumno == student_id, Progreso.id_curso == c.id_curso).first()
+        prog_pct = float(progreso.porcentaje) if progreso else 0
+        
+        courses.append({
+            "id": c.id_curso,
+            "name": c.nombre,
+            "description": c.descripcion,
+            "teacher": prof_name,
+            "progress": prog_pct
+        })
+    return courses
+
+@router.get("/tasks")
+def get_tasks(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
+    curso_ids = [m.id_curso for m in matriculas]
+    
+    if not curso_ids:
+        return []
+        
+    actividades = db.query(Actividad).options(joinedload(Actividad.curso)).filter(Actividad.id_curso.in_(curso_ids)).all()
+    
+    tasks = []
+    now = datetime.now(timezone.utc)
+    for a in actividades:
+        entrega = db.query(Entrega).options(joinedload(Entrega.calificacion)).filter(
+            Entrega.id_actividad == a.id_actividad,
+            Entrega.id_alumno == student_id
+        ).first()
+        
+        status = "pending"
+        score = None
+        if entrega:
+            status = "submitted"
+            if entrega.calificacion:
+                status = "graded"
+                score = float(entrega.calificacion.nota)
+        elif a.fecha_entrega and a.fecha_entrega.replace(tzinfo=timezone.utc) < now:
+            status = "overdue"
+            
+        tasks.append({
+            "id": a.id_actividad,
+            "title": a.titulo,
+            "course": a.curso.nombre,
+            "due_date": a.fecha_entrega.isoformat() if a.fecha_entrega else None,
+            "max_score": float(a.puntuacion_maxima),
+            "status": status,
+            "score": score
+        })
+    return tasks
+
+@router.post("/tasks/{task_id}/submit")
+def submit_task(task_id: int, payload: TaskSubmit, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    actividad = db.query(Actividad).filter(Actividad.id_actividad == task_id).first()
+    if not actividad:
+        raise HTTPException(status_code=404, detail="Tarea no encontrada")
+
+    entrega = db.query(Entrega).filter(Entrega.id_actividad == task_id, Entrega.id_alumno == student_id).first()
+    if entrega:
+        entrega.contenido = payload.contenido
+        entrega.estado = "entregado"
+        entrega.fecha_entrega = datetime.now(timezone.utc)
+    else:
+        entrega = Entrega(
+            id_actividad=task_id,
+            id_alumno=student_id,
+            contenido=payload.contenido,
+            estado="entregado"
+        )
+        db.add(entrega)
+    db.commit()
+    return {"message": "Tarea entregada correctamente"}
+
+@router.get("/grades")
+def get_grades(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    entregas = db.query(Entrega).options(
+        joinedload(Entrega.actividad).joinedload(Actividad.curso),
+        joinedload(Entrega.calificacion)
+    ).filter(Entrega.id_alumno == student_id).all()
+    
+    grades = []
+    for e in entregas:
+        if not e.calificacion:
+            continue
+            
+        a = e.actividad
+        grades.append({
+            "task_id": a.id_actividad,
+            "task_title": a.titulo,
+            "course": a.curso.nombre,
+            "score": float(e.calificacion.nota),
+            "max_score": float(a.puntuacion_maxima),
+            "feedback": e.calificacion.observaciones,
+            "date": e.calificacion.fecha_calificacion.isoformat()
+        })
+    return grades
+
+@router.get("/schedule")
+def get_schedule(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
+    curso_ids = [m.id_curso for m in matriculas]
+    if not curso_ids: return []
+    horarios = db.query(Horario).options(joinedload(Horario.curso)).filter(Horario.id_curso.in_(curso_ids)).all()
+    res = []
+    for h in horarios:
+        res.append({
+            "day": h.dia_semana,
+            "start": h.hora_inicio,
+            "end": h.hora_fin,
+            "course": h.curso.nombre,
+            "room": h.aula
+        })
+    return res
+
+@router.get("/projects")
+def get_projects(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    pes = db.query(ProyectoEstudiante).options(joinedload(ProyectoEstudiante.proyecto).joinedload(Proyecto.curso)).filter(ProyectoEstudiante.id_alumno == student_id).all()
+    res = []
+    for pe in pes:
+        p = pe.proyecto
+        # Simulated teammates for UI purposes
+        teammates = [{"name": "A", "initial": "A"}, {"name": "B", "initial": "B"}, {"name": "C", "initial": "C"}]
+        res.append({
+            "id": p.id_proyecto,
+            "title": p.titulo,
+            "description": p.descripcion,
+            "course": p.curso.nombre,
+            "due_date": p.fecha_entrega.isoformat() if p.fecha_entrega else None,
+            "status": p.estado,
+            "progress": float(p.porcentaje_completado),
+            "team": teammates
+        })
+    return res
+
+@router.post("/projects/{project_id}/progress")
+def update_project_progress(project_id: int, payload: ProjectProgress, student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    pe = db.query(ProyectoEstudiante).filter(ProyectoEstudiante.id_proyecto == project_id, ProyectoEstudiante.id_alumno == student_id).first()
+    if not pe:
+        raise HTTPException(status_code=403, detail="No eres parte de este proyecto")
+        
+    p = db.query(Proyecto).filter(Proyecto.id_proyecto == project_id).first()
+    if p:
+        p.porcentaje_completado = max(0, min(100, payload.progress))
+        if p.porcentaje_completado == 100:
+            p.estado = "entregado"
+        else:
+            p.estado = "en curso"
+        db.commit()
+    return {"message": "Progreso actualizado"}
+
+@router.get("/exams")
+def get_exams(student: Alumno = Depends(get_current_student), db: Session = Depends(get_db)):
+    student_id = student.id_alumno
+    matriculas = db.query(Matricula).filter(Matricula.id_alumno == student_id).all()
+    curso_ids = [m.id_curso for m in matriculas]
+    if not curso_ids: return []
+    examenes = db.query(Examen).options(joinedload(Examen.curso)).filter(Examen.id_curso.in_(curso_ids)).all()
+    res = []
+    for e in examenes:
+        res.append({
+            "id": e.id_examen,
+            "title": e.titulo,
+            "syllabus": e.temario,
+            "course": e.curso.nombre,
+            "date": e.fecha.isoformat(),
+            "duration": e.duracion_minutos,
+            "mode": e.modalidad
+        })
+    return res

+ 240 - 0
user_platform/src/frontend/css/style.css

@@ -0,0 +1,240 @@
+@import url('https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700;800&display=swap');
+
+:root {
+  --bg: #f4f7fa;
+  --bg-soft: #ffffff;
+  --ink: #101828;
+  --muted: #475467;
+  --line: #eaecf0;
+  --teal: #00796B;
+  --teal-dark: #004D40;
+  --teal-soft: #E0F2F1;
+  --radius-xl: 24px;
+  --radius-lg: 16px;
+  --radius-md: 12px;
+  --shadow-sm: 0 4px 6px -1px rgba(0,0,0,0.05);
+  --shadow-md: 0 12px 24px -4px rgba(0,0,0,0.08);
+  --gradient: linear-gradient(135deg, var(--teal) 0%, var(--teal-dark) 100%);
+}
+
+* { box-sizing: border-box; margin: 0; padding: 0; }
+body { font-family: 'Manrope', sans-serif; background: var(--bg); color: var(--ink); -webkit-font-smoothing: antialiased; }
+h1,h2,h3 { font-family: 'Sora', sans-serif; }
+a { text-decoration: none; color: inherit; }
+
+/* ── Animations ───────────────────────── */
+@keyframes slideUpFade {
+  0% { opacity: 0; transform: translateY(20px); }
+  100% { opacity: 1; transform: translateY(0); }
+}
+@keyframes fadeIn {
+  from { opacity: 0; } to { opacity: 1; }
+}
+.animate-slide-up { animation: slideUpFade 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
+.animate-fade-in { animation: fadeIn 0.4s ease-out forwards; }
+.delay-100 { animation-delay: 100ms; opacity: 0; }
+.delay-200 { animation-delay: 200ms; opacity: 0; }
+
+/* ── Landing Page ─────────────────────── */
+.landing-body {
+  background: var(--bg);
+  min-height: 100vh; display: flex; flex-direction: column;
+  position: relative; overflow-x: hidden;
+}
+.landing-nav {
+  padding: 24px 48px; display: flex; justify-content: space-between; align-items: center;
+  position: relative; z-index: 10;
+}
+.brand { font-family: 'Sora', sans-serif; font-weight: 800; font-size: 1.4rem; color: var(--teal-dark); }
+.nav-links { display: flex; gap: 24px; font-weight: 600; font-size: 0.95rem; }
+
+.landing-main {
+  flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center;
+  padding: 40px 20px; text-align: center; position: relative; z-index: 10;
+}
+.hero-title { font-size: 4rem; font-weight: 800; line-height: 1.1; letter-spacing: -0.03em; max-width: 900px; margin-bottom: 24px; color: var(--ink); }
+.hero-subtitle { font-size: 1.25rem; color: var(--muted); max-width: 600px; margin: 0 auto 40px; line-height: 1.5; }
+.gradient-text { background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
+
+.btn {
+  display: inline-flex; align-items: center; justify-content: center;
+  padding: 12px 24px; border-radius: var(--radius-md);
+  font-weight: 700; font-size: 1rem; cursor: pointer; transition: all 0.2s; border: none; font-family: inherit;
+}
+.btn-primary { background: var(--gradient); color: #fff; box-shadow: 0 4px 14px rgba(0, 121, 107, 0.3); }
+.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(0, 121, 107, 0.4); }
+
+.stats-section { display: flex; gap: 40px; margin-top: 80px; }
+.stat-card {
+  background: rgba(255,255,255,0.8); backdrop-filter: blur(12px); border: 1px solid rgba(255,255,255,0.5);
+  padding: 24px 32px; border-radius: var(--radius-lg); box-shadow: var(--shadow-sm);
+}
+.stat-value { font-family: 'Sora', sans-serif; font-size: 2.5rem; font-weight: 800; color: var(--teal-dark); }
+.stat-label { font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.05em; font-size: 0.85rem; }
+
+/* Landing Background Blobs */
+.landing-bg { position: absolute; top: 0; left: 0; width: 100%; height: 100%; overflow: hidden; z-index: 0; pointer-events: none; }
+.blob { position: absolute; filter: blur(80px); opacity: 0.4; border-radius: 50%; }
+.blob-1 { width: 500px; height: 500px; background: #80CBC4; top: -100px; right: -100px; }
+.blob-2 { width: 600px; height: 600px; background: #B2DFDB; bottom: -200px; left: -200px; }
+
+/* ── Dashboard Layout ─────────────────── */
+.sidebar {
+  width: 260px; background: #fff; border-right: 1px solid var(--line);
+  position: fixed; top: 0; left: 0; bottom: 0; display: flex; flex-direction: column; z-index: 100;
+}
+.sidebar-brand { padding: 30px 24px; }
+.logo-text { font-family: 'Sora', sans-serif; font-size: 1.25rem; font-weight: 800; color: var(--teal-dark); }
+.logo-sub { font-size: 0.75rem; color: var(--muted); font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-top: 4px; }
+.sidebar-nav { padding: 0 16px; flex: 1; display: flex; flex-direction: column; gap: 4px; }
+.sidebar .nav-link {
+  display: flex; align-items: center; gap: 12px; padding: 12px 16px;
+  border-radius: var(--radius-md); font-weight: 600; color: var(--muted);
+  cursor: pointer; border: none; background: transparent; font-family: inherit; font-size: 0.95rem;
+  transition: all 0.2s; text-align: left; width: 100%;
+}
+.sidebar .nav-link:hover { background: var(--bg); color: var(--ink); }
+.sidebar .nav-link.active { background: var(--teal-soft); color: var(--teal-dark); font-weight: 700; }
+.sidebar .icon { font-size: 1.2rem; }
+
+.sidebar-footer { padding: 20px 24px; border-top: 1px solid var(--line); }
+.user-pill { display: flex; align-items: center; gap: 12px; }
+.user-avatar { width: 40px; height: 40px; border-radius: 50%; background: var(--gradient); color: #fff; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 1.1rem; }
+.user-name { font-weight: 700; font-size: 0.9rem; color: var(--ink); line-height: 1.2; }
+.user-role { font-size: 0.75rem; color: var(--muted); font-weight: 600; }
+.logout-btn { margin-left: auto; color: var(--muted); font-size: 1.2rem; transition: color 0.2s; }
+.logout-btn:hover { color: #d93025; }
+
+.workspace { margin-left: 260px; min-height: 100vh; display: flex; flex-direction: column; }
+.topbar {
+  padding: 24px 40px; display: flex; align-items: center; border-bottom: 1px solid var(--line); background: #fff;
+}
+.topbar-title { font-size: 1.25rem; font-weight: 700; }
+.topbar-spacer { flex: 1; }
+.gamification-header { display: flex; gap: 16px; font-weight: 700; font-size: 0.9rem; }
+.streak-badge, .level-badge { padding: 8px 16px; border-radius: 999px; background: var(--bg); border: 1px solid var(--line); color: var(--ink); display: flex; align-items: center; gap: 6px; }
+
+.content { padding: 40px; flex: 1; max-width: 1200px; margin: 0 auto; width: 100%; }
+
+/* ── UI Components ────────────────────── */
+.welcome-card {
+  background: var(--gradient); border-radius: var(--radius-xl); padding: 32px 40px; color: #fff;
+  display: flex; justify-content: space-between; align-items: center; box-shadow: var(--shadow-md);
+  margin-bottom: 32px;
+}
+.welcome-card .gradient-text { background: #fff; -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
+.welcome-xp { width: 300px; background: rgba(0,0,0,0.1); padding: 16px; border-radius: var(--radius-md); }
+
+.section-header { margin-bottom: 20px; }
+.section-title { font-size: 1.2rem; font-weight: 700; color: var(--ink); }
+
+.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
+.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 24px; }
+
+.card { background: #fff; border-radius: var(--radius-lg); padding: 24px; box-shadow: var(--shadow-sm); border: 1px solid var(--line); transition: all 0.2s; }
+.card:hover { box-shadow: var(--shadow-md); transform: translateY(-2px); border-color: rgba(0,121,107,0.2); }
+.card-title { font-weight: 700; font-size: 1.1rem; margin-bottom: 8px; color: var(--ink); }
+.card-sub { font-size: 0.85rem; color: var(--muted); margin-bottom: 16px; line-height: 1.5; }
+
+.progress { height: 8px; background: var(--line); border-radius: 999px; overflow: hidden; margin-top: auto; }
+.progress-fill { height: 100%; background: var(--teal); border-radius: 999px; transition: width 1s ease-out; }
+
+.table-wrap { background: #fff; border-radius: var(--radius-lg); border: 1px solid var(--line); overflow: hidden; }
+table { width: 100%; border-collapse: collapse; text-align: left; }
+th { padding: 16px 24px; background: var(--bg); font-size: 0.75rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); font-weight: 700; }
+td { padding: 16px 24px; border-bottom: 1px solid var(--line); font-size: 0.9rem; font-weight: 500; }
+tr:last-child td { border-bottom: none; }
+tr:hover { background: #fcfcfd; }
+
+.badge { display: inline-flex; padding: 4px 10px; border-radius: 999px; font-size: 0.75rem; font-weight: 700; }
+.badge-teal { background: var(--teal-soft); color: var(--teal-dark); }
+.badge-amber { background: #FEF0C7; color: #DC6803; }
+.badge-rose { background: #FEE4E2; color: #D92D20; }
+.badge-ok { background: #D1FADF; color: #039855; }
+
+.kpi-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
+.kpi-card { background: #fff; padding: 24px; border-radius: var(--radius-lg); border: 1px solid var(--line); box-shadow: var(--shadow-sm); border-top: 4px solid var(--teal); }
+.kpi-card.amber { border-top-color: #F79009; }
+.kpi-label { font-size: 0.8rem; font-weight: 700; color: var(--muted); text-transform: uppercase; margin-bottom: 8px; }
+.kpi-value { font-size: 2.5rem; font-weight: 800; font-family: 'Sora', sans-serif; color: var(--ink); }
+
+/* ── Upgraded UI Components ──────────────── */
+.topbar-actions { display: flex; align-items: center; gap: 16px; }
+.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--ink); }
+.btn-outline:hover { background: var(--bg); border-color: var(--muted); }
+.btn-outline.active { background: var(--teal-soft); border-color: var(--teal); color: var(--teal-dark); }
+.notification-bell { position: relative; font-size: 1.2rem; cursor: pointer; padding: 8px; }
+
+/* Profile Header */
+.profile-header {
+  background: #fff; padding: 32px 40px; border-bottom: 1px solid var(--line);
+  display: flex; justify-content: space-between; align-items: center;
+}
+.profile-info { display: flex; align-items: center; gap: 24px; }
+.profile-avatar { width: 80px; height: 80px; border-radius: 50%; background: var(--gradient); display: flex; align-items: center; justify-content: center; color: white; font-size: 2rem; font-weight: 800; font-family: 'Sora'; }
+.profile-details h1 { font-size: 1.8rem; margin-bottom: 4px; }
+.profile-details p { color: var(--muted); font-size: 0.9rem; font-weight: 500; }
+.profile-stats { display: flex; gap: 32px; }
+.stat-box { display: flex; flex-direction: column; min-width: 100px; }
+.stat-val { font-size: 1.25rem; font-weight: 800; font-family: 'Sora'; color: var(--ink); }
+.stat-lbl { font-size: 0.75rem; font-weight: 700; color: var(--muted); text-transform: uppercase; margin-top: 4px; }
+
+/* Schedule Grid */
+.schedule-grid { display: flex; flex-direction: column; gap: 16px; margin-top: 16px; }
+.schedule-col { flex: 1; background: #fff; border-radius: var(--radius-lg); padding: 16px; border: 1px solid var(--line); }
+
+/* Badges extended */
+.badge-outline { border: 1px solid var(--line); background: transparent; color: var(--muted); }
+.azure { border-top-color: #007BFF !important; }
+.badge-azure { background: #E6F2FF; color: #007BFF; }
+:root { --azure: #007BFF; --border: #eaecf0; }
+
+/* ── Modals ──────────────────────────────── */
+.modal-overlay {
+  position: fixed; top: 0; left: 0; width: 100%; height: 100%;
+  background: rgba(16, 24, 40, 0.5); backdrop-filter: blur(4px);
+  display: flex; align-items: center; justify-content: center;
+  z-index: 1000; opacity: 0; visibility: hidden; transition: all 0.3s ease;
+}
+.modal-overlay.active { opacity: 1; visibility: visible; }
+.modal-content {
+  background: #fff; width: 100%; max-width: 500px; padding: 32px;
+  border-radius: var(--radius-xl); box-shadow: var(--shadow-md);
+  transform: translateY(20px); transition: all 0.3s ease;
+  position: relative;
+}
+.modal-overlay.active .modal-content { transform: translateY(0); }
+.modal-close {
+  position: absolute; top: 20px; right: 20px; background: none; border: none;
+  font-size: 1.5rem; cursor: pointer; color: var(--muted);
+}
+.modal-close:hover { color: var(--rose); }
+.form-input { width: 100%; padding: 12px; border: 1px solid var(--line); border-radius: var(--radius-md); font-family: inherit; font-size: 1rem; margin-top: 8px; margin-bottom: 24px; }
+.form-input:focus { outline: none; border-color: var(--teal); }
+input[type="range"] { width: 100%; margin: 16px 0; }
+
+/* ── Dropdowns ───────────────────────────── */
+.dropdown-menu {
+  position: absolute; top: 120%; right: -10px; width: 320px;
+  background: #fff; border-radius: var(--radius-md);
+  box-shadow: var(--shadow-md); border: 1px solid var(--line);
+  opacity: 0; visibility: hidden; transform: translateY(-10px);
+  transition: all 0.2s ease; z-index: 2000; text-align: left;
+  cursor: default;
+}
+.dropdown-menu.active { opacity: 1; visibility: visible; transform: translateY(0); }
+.dropdown-header {
+  padding: 16px; border-bottom: 1px solid var(--line);
+  font-weight: 700; font-size: 1rem; color: var(--ink);
+}
+.notif-item {
+  padding: 16px; border-bottom: 1px solid var(--line);
+  display: flex; gap: 12px; align-items: flex-start;
+  transition: background 0.2s; cursor: pointer;
+}
+.notif-item:hover { background: var(--bg); }
+.notif-item:last-child { border-bottom: none; }
+.notif-icon { font-size: 1.5rem; }
+.notif-title { font-weight: 600; font-size: 0.9rem; color: var(--ink); margin-bottom: 4px; }
+.notif-desc { font-size: 0.8rem; color: var(--muted); line-height: 1.4; }
+.notif-time { font-size: 0.75rem; color: #a0aec0; margin-top: 4px; display: block; }

+ 273 - 0
user_platform/src/frontend/dashboard.html

@@ -0,0 +1,273 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Panel del Alumno | BridgeLearn</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="/static/css/style.css?v=3">
+</head>
+<body>
+
+  <nav class="sidebar">
+    <div class="sidebar-brand">
+      <div class="logo-text">BridgeLearn</div>
+      <div class="logo-sub">Plataforma Educativa</div>
+    </div>
+    <div class="sidebar-nav" id="sidebar-nav">
+      <button class="nav-link active" data-tab="inicio"><span class="icon">🏠</span> Inicio</button>
+      <button class="nav-link" data-tab="clases"><span class="icon">📚</span> Mis Clases</button>
+      <button class="nav-link" data-tab="tareas"><span class="icon">📝</span> Tareas</button>
+      <button class="nav-link" data-tab="calificaciones"><span class="icon">📈</span> Calificaciones</button>
+      <button class="nav-link" data-tab="proyectos"><span class="icon">🎯</span> Proyectos</button>
+      <button class="nav-link" data-tab="examenes"><span class="icon">✍️</span> Exámenes</button>
+    </div>
+  </nav>
+
+  <main class="workspace">
+    <header class="topbar">
+      <h2 class="topbar-title" id="topbar-title">Inicio</h2>
+      <div class="topbar-spacer"></div>
+      
+      <div class="topbar-actions">
+        <button class="btn btn-outline" style="margin-right: 16px;">Ver panel docente</button>
+        <div class="notification-bell" onclick="window.toggleNotifications(event)">
+          🔔 <span id="notif-badge" class="badge badge-rose" style="border-radius: 50%; padding: 2px 6px; font-size: 0.7rem; position: absolute; top: -5px; right: -5px;">2</span>
+          <div id="notifications-dropdown" class="dropdown-menu">
+            <div class="dropdown-header">Notificaciones</div>
+            <div id="notifications-list"></div>
+          </div>
+        </div>
+        <div class="user-pill topbar-user" style="background: transparent; border: 1px solid var(--border); padding: 8px 16px;">
+          <div class="user-avatar" id="user-avatar-initial" style="background: var(--teal);">A</div>
+          <div>
+            <div class="user-name" id="user-name-display" style="color: var(--ink);">Cargando...</div>
+            <div class="user-role" id="user-level-display" style="color: var(--muted);">Nivel</div>
+          </div>
+        </div>
+        <a href="/" id="logout-btn" class="btn btn-outline" title="Salir" style="color: var(--rose); border-color: var(--rose);">Salir ⎋</a>
+      </div>
+    </header>
+
+    <!-- Gamification Header Profile (Like Demo) -->
+    <div class="profile-header" id="profile-header">
+      <div class="profile-info">
+        <div class="profile-avatar"><span id="ph-avatar">A</span></div>
+        <div class="profile-details">
+          <h1 id="ph-name">Cargando...</h1>
+          <p>Kigali, Rwanda | Estudiante Activo</p>
+        </div>
+      </div>
+      <div class="profile-stats">
+        <div class="stat-box">
+          <span class="stat-val" id="ph-xp">0 XP</span>
+          <span class="stat-lbl">Experiencia</span>
+          <div class="progress" style="height: 6px; margin-top: 8px;"><div class="progress-fill" id="ph-xp-bar" style="width: 0%;"></div></div>
+        </div>
+        <div class="stat-box">
+          <span class="stat-val" id="ph-level">1</span>
+          <span class="stat-lbl">Nivel actual</span>
+        </div>
+        <div class="stat-box">
+          <span class="stat-val">🔥 <span id="ph-streak">0</span></span>
+          <span class="stat-lbl">Días de Racha</span>
+        </div>
+      </div>
+    </div>
+
+    <div class="content" id="main-content">
+      <!-- Loading State -->
+      <div id="loading" class="alert alert-info animate-fade-in">Cargando datos de la plataforma...</div>
+      
+      <!-- Pestañas -->
+      <div id="tab-inicio" class="tab-content" style="display: none;"></div>
+      <div id="tab-clases" class="tab-content" style="display: none;"></div>
+      <div id="tab-tareas" class="tab-content" style="display: none;"></div>
+      <div id="tab-calificaciones" class="tab-content" style="display: none;"></div>
+      <div id="tab-proyectos" class="tab-content" style="display: none;"></div>
+      <div id="tab-examenes" class="tab-content" style="display: none;"></div>
+    </div>
+  </main>
+
+  <!-- Template para Inicio -->
+  <template id="tmpl-inicio">
+    <div class="animate-slide-up">
+      <div class="kpi-grid" style="grid-template-columns: repeat(4, 1fr); margin-bottom: 32px;">
+        <div class="kpi-card teal">
+          <div class="kpi-label">GPA General</div>
+          <div class="kpi-value"><span id="kpi-gpa">--</span>/10</div>
+        </div>
+        <div class="kpi-card azure">
+          <div class="kpi-label">Cursos Activos</div>
+          <div class="kpi-value"><span id="kpi-cursos">--</span></div>
+        </div>
+        <div class="kpi-card amber">
+          <div class="kpi-label">Tareas Pendientes</div>
+          <div class="kpi-value"><span id="kpi-tareas">--</span></div>
+        </div>
+        <div class="kpi-card rose">
+          <div class="kpi-label">Días de Racha</div>
+          <div class="kpi-value"><span id="kpi-streak-card">--</span></div>
+        </div>
+      </div>
+
+      <div class="grid-2" style="grid-template-columns: 2fr 1fr; gap: 32px;">
+        <!-- Progreso Semanal Chart Placeholder -->
+        <div class="card">
+          <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 24px;">
+            <div class="card-title" style="margin:0;">Actividad Semanal</div>
+            <button class="btn btn-outline">Sincronizar</button>
+          </div>
+          <div class="chart-placeholder" style="height: 200px; display: flex; align-items: flex-end; gap: 8px; justify-content: space-between; border-bottom: 1px solid var(--border); padding-bottom: 10px;">
+            <div style="width: 12%; background: var(--teal); height: 40%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--teal); height: 70%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--teal); height: 50%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--amber); height: 90%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--teal); height: 30%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--border); height: 10%; border-radius: 4px;"></div>
+            <div style="width: 12%; background: var(--border); height: 10%; border-radius: 4px;"></div>
+          </div>
+          <div style="display: flex; justify-content: space-between; margin-top: 8px; color: var(--muted); font-size: 0.85rem;">
+            <span>Lun</span><span>Mar</span><span>Mié</span><span>Jue</span><span>Vie</span><span>Sáb</span><span>Dom</span>
+          </div>
+        </div>
+
+        <!-- Upcoming items -->
+        <div style="display: flex; flex-direction: column; gap: 24px;">
+          <div class="card">
+            <div class="card-title">Próximas Sesiones</div>
+            <div id="inicio-upcoming-sessions" style="margin-top: 16px;">
+              <!-- JS Injected -->
+            </div>
+          </div>
+          <div class="card">
+            <div class="card-title">Próximos Exámenes</div>
+            <div id="inicio-upcoming-exams" style="margin-top: 16px;">
+              <!-- JS Injected -->
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+  </template>
+
+  <!-- Template para Clases -->
+  <template id="tmpl-clases">
+    <div class="animate-slide-up">
+      <div class="banner-next-class card azure" style="margin-bottom: 32px; display:flex; justify-content:space-between; align-items:center; color: white;">
+        <div>
+          <h3 style="margin: 0 0 8px 0; font-size: 1.5rem;">Próxima clase: <span id="next-class-name">--</span></h3>
+          <p style="margin: 0; opacity: 0.9;">Empieza en <strong id="next-class-time">-- min</strong> | Aula: <span id="next-class-room">--</span></p>
+        </div>
+        <button class="btn" style="background: white; color: var(--azure); font-weight: bold;" onclick="window.joinClass(document.getElementById('next-class-name').textContent)">Unirse Online</button>
+      </div>
+
+      <div class="section-header">
+        <div class="section-title">Horario Semanal</div>
+      </div>
+      <div class="schedule-grid" id="schedule-grid">
+        <!-- JS Injected -->
+      </div>
+    </div>
+  </template>
+
+  <!-- Template para Tareas -->
+  <template id="tmpl-tareas">
+    <div class="animate-slide-up">
+      <div class="section-header" style="display: flex; justify-content: space-between;">
+        <div class="section-title">Tareas Pendientes y Entregadas</div>
+        <div>
+          <button class="btn btn-outline active">Todas</button>
+          <button class="btn btn-outline">Pendientes</button>
+          <button class="btn btn-outline">Completadas</button>
+        </div>
+      </div>
+      <div class="grid-3" id="tasks-container"></div>
+    </div>
+  </template>
+
+  <!-- Template para Calificaciones -->
+  <template id="tmpl-calificaciones">
+    <div class="animate-slide-up">
+      <div class="section-header">
+        <div class="section-title">Boletín de Calificaciones</div>
+      </div>
+      <div class="card" style="padding: 0;">
+        <table class="grades-table" style="width: 100%; border-collapse: collapse;">
+          <thead>
+            <tr style="background: var(--surface-alt); text-align: left; border-bottom: 2px solid var(--border);">
+              <th style="padding: 16px;">Asignatura</th>
+              <th style="padding: 16px;">Asistencia</th>
+              <th style="padding: 16px;">Tareas</th>
+              <th style="padding: 16px;">Examen</th>
+              <th style="padding: 16px;">Nota Final</th>
+            </tr>
+          </thead>
+          <tbody id="grades-table-body"></tbody>
+        </table>
+      </div>
+    </div>
+  </template>
+
+  <!-- Template para Proyectos -->
+  <template id="tmpl-proyectos">
+    <div class="animate-slide-up">
+      <div class="section-header">
+        <div class="section-title">Proyectos Colaborativos</div>
+      </div>
+      <div class="grid-2" id="projects-container"></div>
+    </div>
+  </template>
+
+  <!-- Template para Exámenes -->
+  <template id="tmpl-examenes">
+    <div class="animate-slide-up">
+      <div class="section-header">
+        <div class="section-title">Próximos Exámenes</div>
+      </div>
+      <div class="grid-3" id="exams-container"></div>
+    </div>
+  </template>
+
+  <!-- ── Modals ──────────────────────────────── -->
+  <!-- Task Submit Modal -->
+  <div id="task-modal" class="modal-overlay">
+    <div class="modal-content">
+      <button class="modal-close" onclick="closeModals()">×</button>
+      <h2 id="task-modal-title" style="margin-bottom: 8px;">Entregar Tarea</h2>
+      <p style="color: var(--muted); margin-bottom: 24px;">Introduce la URL de tu repositorio, documento compartido o el texto de la entrega.</p>
+      <textarea id="task-content" class="form-input" rows="4" placeholder="Ej. https://github.com/usuario/proyecto"></textarea>
+      <button class="btn btn-primary" style="width: 100%;" onclick="submitTask()">Enviar Tarea</button>
+    </div>
+  </div>
+
+  <!-- Project Details Modal -->
+  <div id="project-modal" class="modal-overlay">
+    <div class="modal-content">
+      <button class="modal-close" onclick="closeModals()">×</button>
+      <h2 id="project-modal-title" style="margin-bottom: 8px;">Detalles del Proyecto</h2>
+      <p id="project-modal-desc" style="color: var(--muted); margin-bottom: 24px;"></p>
+      
+      <div style="margin-bottom: 24px;">
+        <div style="font-weight: 600; font-size: 0.9rem;">Progreso: <span id="project-modal-progress-text">0</span>%</div>
+        <input type="range" id="project-modal-progress" min="0" max="100" value="0" oninput="document.getElementById('project-modal-progress-text').textContent=this.value">
+      </div>
+      <button class="btn btn-primary" style="width: 100%;" onclick="updateProjectProgress()">Guardar Cambios</button>
+    </div>
+  </div>
+
+  <!-- Class Join Modal -->
+  <div id="class-modal" class="modal-overlay">
+    <div class="modal-content" style="text-align: center;">
+      <h2 style="margin-bottom: 16px;">Conectando...</h2>
+      <div class="progress" style="width: 100%; margin: 24px 0;"><div class="progress-fill" style="width: 100%; animation: slideRight 2s infinite linear;"></div></div>
+      <p id="class-modal-text" style="color: var(--muted); margin-bottom: 24px;">Estableciendo conexión segura con el aula virtual.</p>
+      <button class="btn btn-outline" style="width: 100%;" onclick="closeModals()">Cancelar</button>
+    </div>
+  </div>
+
+  <script src="/static/js/app.js?v=3"></script>
+</body>
+</html>

+ 56 - 0
user_platform/src/frontend/index.html

@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>BridgeLearn Africa | Plataforma Educativa</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700;800&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="/static/css/style.css">
+</head>
+<body class="landing-body">
+
+  <nav class="landing-nav animate-slide-up">
+    <div class="brand">BridgeLearn Africa</div>
+    <div class="nav-links">
+      <a href="#about">Programa</a>
+      <a href="#stats">Impacto</a>
+    </div>
+  </nav>
+
+  <main class="landing-main">
+    <div class="hero-section animate-slide-up delay-100">
+      <div class="badge badge-teal" style="margin-bottom: 20px;">OpenData II Project</div>
+      <h1 class="hero-title">Educación sin fronteras para el <span class="gradient-text">futuro de África</span></h1>
+      <p class="hero-subtitle">Plataforma interactiva para reducir la brecha digital y potenciar el aprendizaje en comunidades de África del Este.</p>
+      
+      <div class="hero-actions">
+        <!-- Botón Demo Login que redirige al dashboard -->
+        <a href="/login" class="btn btn-primary btn-lg">Entrar a la Plataforma</a>
+      </div>
+    </div>
+
+    <div class="stats-section animate-slide-up delay-200" id="stats">
+      <div class="stat-card">
+        <div class="stat-value">14k+</div>
+        <div class="stat-label">Estudiantes</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">37</div>
+        <div class="stat-label">Escuelas</div>
+      </div>
+      <div class="stat-card">
+        <div class="stat-value">6</div>
+        <div class="stat-label">Países</div>
+      </div>
+    </div>
+  </main>
+
+  <div class="landing-bg">
+    <div class="blob blob-1"></div>
+    <div class="blob blob-2"></div>
+  </div>
+
+</body>
+</html>

+ 442 - 0
user_platform/src/frontend/js/app.js

@@ -0,0 +1,442 @@
+document.addEventListener('DOMContentLoaded', () => {
+  const state = {
+    dashboard: null,
+    courses: [],
+    tasks: [],
+    grades: [],
+    schedule: [],
+    projects: [],
+    exams: []
+  };
+
+  const UI = {
+    loading: document.getElementById('loading'),
+    tabs: document.querySelectorAll('.nav-link'),
+    tabContents: document.querySelectorAll('.tab-content'),
+    title: document.getElementById('topbar-title')
+  };
+
+  async function init() {
+    try {
+      const res = await fetch(`/api/student/dashboard`);
+      if (res.status === 401) {
+        window.location.href = '/login';
+        return;
+      }
+      if (!res.ok) throw new Error('API not reachable');
+      state.dashboard = await res.json();
+      
+      updateHeader();
+      await switchTab('inicio');
+      UI.loading.style.display = 'none';
+      
+      UI.tabs.forEach(btn => {
+        btn.addEventListener('click', () => {
+          switchTab(btn.getAttribute('data-tab'));
+        });
+      });
+    } catch (err) {
+      UI.loading.textContent = 'Error al cargar los datos. Verifica la API.';
+      UI.loading.className = 'alert alert-error';
+    }
+  }
+
+  function updateHeader() {
+    const d = state.dashboard;
+    // Topbar
+    document.getElementById('user-name-display').textContent = d.name;
+    document.getElementById('user-level-display').textContent = d.level_name;
+    document.getElementById('user-avatar-initial').textContent = d.name.charAt(0);
+    
+    // Profile Header (Gamification)
+    const phName = document.getElementById('ph-name');
+    if (phName) {
+      phName.textContent = d.name;
+      document.getElementById('ph-avatar').textContent = d.name.charAt(0);
+      document.getElementById('ph-xp').textContent = `${d.xp} XP`;
+      document.getElementById('ph-level').textContent = d.level;
+      document.getElementById('ph-streak').textContent = d.streak;
+      document.getElementById('ph-xp-bar').style.width = `${(d.xp % 1000) / 10}%`;
+    }
+  }
+
+  async function switchTab(tabId) {
+    UI.tabs.forEach(t => t.classList.remove('active'));
+    document.querySelector(`[data-tab="${tabId}"]`).classList.add('active');
+    
+    UI.tabContents.forEach(c => {
+      c.style.display = 'none';
+      c.innerHTML = ''; 
+    });
+    
+    const container = document.getElementById(`tab-${tabId}`);
+    const template = document.getElementById(`tmpl-${tabId}`);
+    
+    if (!template) return;
+    container.appendChild(template.content.cloneNode(true));
+    container.style.display = 'block';
+
+    if (tabId === 'inicio') {
+      UI.title.textContent = 'Inicio';
+      document.getElementById('kpi-gpa').textContent = state.dashboard.gpa || 7.5;
+      document.getElementById('kpi-cursos').textContent = state.dashboard.active_courses || 0;
+      document.getElementById('kpi-tareas').textContent = state.dashboard.pending_tasks || 0;
+      document.getElementById('kpi-streak-card').textContent = state.dashboard.streak;
+      
+      if (!state.schedule.length) {
+        const schRes = await fetch(`/api/student/schedule`);
+        if (schRes.ok) state.schedule = await schRes.json();
+      }
+      if (!state.exams.length) {
+        const exRes = await fetch(`/api/student/exams`);
+        if (exRes.ok) state.exams = await exRes.json();
+      }
+      renderInicioLists();
+    } 
+    else if (tabId === 'clases') {
+      UI.title.textContent = 'Mis Clases';
+      if (!state.schedule.length) {
+        const schRes = await fetch(`/api/student/schedule`);
+        if (schRes.ok) state.schedule = await schRes.json();
+      }
+      renderSchedule();
+    }
+    else if (tabId === 'tareas') {
+      UI.title.textContent = 'Tareas';
+      if (!state.tasks.length) {
+        const tsRes = await fetch(`/api/student/tasks`);
+        if (tsRes.ok) state.tasks = await tsRes.json();
+      }
+      renderTasks();
+    }
+    else if (tabId === 'calificaciones') {
+      UI.title.textContent = 'Calificaciones';
+      if (!state.grades.length) {
+        const grRes = await fetch(`/api/student/grades`);
+        if (grRes.ok) state.grades = await grRes.json();
+      }
+      renderGrades();
+    }
+    else if (tabId === 'proyectos') {
+      UI.title.textContent = 'Proyectos';
+      if (!state.projects.length) {
+        const prRes = await fetch(`/api/student/projects`);
+        if (prRes.ok) state.projects = await prRes.json();
+      }
+      renderProjects();
+    }
+    else if (tabId === 'examenes') {
+      UI.title.textContent = 'Exámenes';
+      if (!state.exams.length) {
+        const exRes = await fetch(`/api/student/exams`);
+        if (exRes.ok) state.exams = await exRes.json();
+      }
+      renderExams();
+    }
+  }
+
+  function renderInicioLists() {
+    const sContainer = document.getElementById('inicio-upcoming-sessions');
+    if (state.schedule.length) {
+      sContainer.innerHTML = state.schedule.slice(0,3).map(s => `
+        <div style="display:flex; justify-content:space-between; padding: 12px 0; border-bottom: 1px solid var(--border);">
+          <div>
+            <div style="font-weight: 600; color: var(--ink);">${s.course}</div>
+            <div style="font-size: 0.85rem; color: var(--muted);">${s.start} - ${s.end} | ${s.room}</div>
+          </div>
+          <button class="btn btn-outline" style="padding: 4px 12px; font-size: 0.8rem;" onclick="window.joinClass('${s.course}')">Entrar</button>
+        </div>
+      `).join('');
+    }
+
+    const eContainer = document.getElementById('inicio-upcoming-exams');
+    if (state.exams.length) {
+      eContainer.innerHTML = state.exams.slice(0,2).map(e => `
+        <div style="display:flex; justify-content:space-between; padding: 12px 0; border-bottom: 1px solid var(--border);">
+          <div>
+            <div style="font-weight: 600; color: var(--rose);">${e.title}</div>
+            <div style="font-size: 0.85rem; color: var(--muted);">${new Date(e.date).toLocaleDateString()} | ${e.course}</div>
+          </div>
+        </div>
+      `).join('');
+    }
+  }
+
+  function renderSchedule() {
+    if(state.schedule.length > 0) {
+      const next = state.schedule[0];
+      document.getElementById('next-class-name').textContent = next.course;
+      document.getElementById('next-class-time').textContent = next.start;
+      document.getElementById('next-class-room').textContent = next.room;
+    }
+
+    const grid = document.getElementById('schedule-grid');
+    const days = ['Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes'];
+    
+    let html = `<div style="display:grid; grid-template-columns: repeat(5, 1fr); gap: 16px;">`;
+    for(let d=1; d<=5; d++) {
+      const dayClasses = state.schedule.filter(s => s.day === d).sort((a,b) => a.start.localeCompare(b.start));
+      html += `<div class="schedule-col">
+        <h4 style="text-align:center; padding-bottom: 8px; border-bottom: 2px solid var(--border);">${days[d-1]}</h4>
+        <div style="margin-top: 16px; display:flex; flex-direction:column; gap:12px;">
+          ${dayClasses.map(c => `
+            <div class="card" style="padding: 12px; border-left: 4px solid var(--azure);">
+              <div style="font-size: 0.75rem; color: var(--muted); font-weight:700;">${c.start} - ${c.end}</div>
+              <div style="font-weight: 600; font-size: 0.9rem; margin: 4px 0;">${c.course}</div>
+              <div style="font-size: 0.8rem; color: var(--muted);">📍 ${c.room}</div>
+            </div>
+          `).join('') || '<div style="color:var(--muted); text-align:center; font-size:0.85rem;">Libre</div>'}
+        </div>
+      </div>`;
+    }
+    html += `</div>`;
+    grid.innerHTML = html;
+  }
+
+  function renderTasks() {
+    const c = document.getElementById('tasks-container');
+    c.innerHTML = state.tasks.map(t => {
+      let badge = '';
+      if (t.status === 'graded') badge = `<span class="badge badge-ok">Calificada (${t.score}/${t.max_score})</span>`;
+      else if (t.status === 'submitted') badge = '<span class="badge badge-teal">Entregada</span>';
+      else if (t.status === 'overdue') badge = '<span class="badge badge-rose">Atrasada</span>';
+      else badge = '<span class="badge badge-amber">Pendiente</span>';
+      
+      const dateStr = t.due_date ? new Date(t.due_date).toLocaleDateString() : 'Sin fecha límite';
+      const btn = (t.status === 'pending' || t.status === 'overdue') 
+        ? `<button class="btn btn-primary" style="width:100%;" onclick="window.openTaskModal(${t.id}, '${t.title}')">Entregar Tarea</button>`
+        : `<button class="btn btn-outline" style="width:100%;" disabled>Completada</button>`;
+
+      return `
+        <div class="card" style="display:flex; flex-direction:column; justify-content:space-between;">
+          <div>
+            <div style="display:flex; justify-content:space-between; margin-bottom: 12px;">
+              <span class="badge badge-outline">${t.course}</span>
+              ${badge}
+            </div>
+            <h3 style="font-size: 1.1rem; margin-bottom: 8px;">${t.title}</h3>
+            <p style="font-size: 0.85rem; color: var(--muted); margin-bottom: 16px;">Vence: ${dateStr}</p>
+          </div>
+          ${btn}
+        </div>
+      `;
+    }).join('');
+  }
+
+  function renderGrades() {
+    const tb = document.getElementById('grades-table-body');
+    // Group grades by course ideally, but here we just list them.
+    tb.innerHTML = state.grades.map(g => {
+      const pct = (g.score / g.max_score) * 100;
+      let color = pct >= 80 ? 'var(--teal)' : pct >= 50 ? 'var(--amber)' : 'var(--rose)';
+      
+      return `
+        <tr style="border-bottom: 1px solid var(--border);">
+          <td style="padding: 16px; font-weight: 600;">${g.course}<br><span style="font-size:0.8rem; color:var(--muted); font-weight:normal;">${g.task_title}</span></td>
+          <td style="padding: 16px;">--</td>
+          <td style="padding: 16px;">${g.score}/${g.max_score}</td>
+          <td style="padding: 16px;">--</td>
+          <td style="padding: 16px;">
+            <div style="display:flex; align-items:center; gap: 8px;">
+              <span style="font-weight: 800; font-family: 'Sora'; color: ${color};">${g.score}</span>
+              <div class="progress" style="flex: 1; height: 6px;"><div class="progress-fill" style="width: ${pct}%; background: ${color};"></div></div>
+            </div>
+          </td>
+        </tr>
+      `;
+    }).join('');
+  }
+
+  function renderProjects() {
+    const c = document.getElementById('projects-container');
+    c.innerHTML = state.projects.map(p => {
+      const teamHtml = p.team.map(m => `<div class="user-avatar" style="width: 28px; height: 28px; font-size: 0.7rem; border: 2px solid white; margin-left: -8px;">${m.initial}</div>`).join('');
+      const badge = p.status === 'entregado' ? '<span class="badge badge-teal">Entregado</span>' : '<span class="badge badge-amber">En Curso</span>';
+      
+      return `
+        <div class="card">
+          <div style="display:flex; justify-content:space-between; margin-bottom: 12px;">
+             <span style="font-size: 0.8rem; color: var(--muted);">${p.course}</span>
+             ${badge}
+          </div>
+          <h3 style="margin-bottom: 8px;">${p.title}</h3>
+          <p style="font-size: 0.9rem; color: var(--muted); margin-bottom: 16px;">${p.description}</p>
+          
+          <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 16px;">
+            <div style="display:flex; padding-left: 8px;">${teamHtml}</div>
+            <div style="font-size: 0.8rem; font-weight: 600;">Progreso: ${p.progress}%</div>
+          </div>
+          
+          <div class="progress" style="margin-bottom: 16px;"><div class="progress-fill" style="width: ${p.progress}%;"></div></div>
+          <button class="btn btn-outline" style="width:100%;" onclick="window.openProjectModal(${p.id})">Ver detalles</button>
+        </div>
+      `;
+    }).join('');
+  }
+
+  function renderExams() {
+    const c = document.getElementById('exams-container');
+    c.innerHTML = state.exams.map(e => `
+      <div class="card" style="border-top: 4px solid var(--rose);">
+        <div style="display:flex; justify-content:space-between; margin-bottom: 8px;">
+          <span class="badge badge-outline">${e.mode}</span>
+          <span style="font-size: 0.8rem; font-weight: bold;">${e.duration} min</span>
+        </div>
+        <h3 style="margin-bottom: 4px;">${e.title}</h3>
+        <p style="font-size: 0.8rem; color: var(--muted); margin-bottom: 16px;">${e.course}</p>
+        
+        <div style="background: var(--surface-alt); padding: 12px; border-radius: 8px; margin-bottom: 16px;">
+          <div style="font-size: 0.85rem; font-weight: 600; margin-bottom: 4px;">Temario:</div>
+          <div style="font-size: 0.8rem; color: var(--muted);">${e.syllabus}</div>
+        </div>
+        
+        <div style="font-weight: 600; color: var(--rose); text-align: center; font-size: 1.1rem;">
+          📅 ${new Date(e.date).toLocaleDateString()}
+        </div>
+      </div>
+    `).join('');
+  }
+
+  if (document.getElementById('sidebar-nav')) {
+    init();
+
+    // Setup Logout
+    const logoutBtn = document.getElementById('logout-btn');
+    if (logoutBtn) {
+      logoutBtn.addEventListener('click', async (e) => {
+        e.preventDefault();
+        await fetch('/api/auth/logout', { method: 'POST' });
+        window.location.href = '/login';
+      });
+    }
+  }
+
+  // --- Modal Logic ---
+  let activeTaskId = null;
+  let activeProjectId = null;
+
+  window.closeModals = function() {
+    document.querySelectorAll('.modal-overlay').forEach(m => m.classList.remove('active'));
+    activeTaskId = null;
+    activeProjectId = null;
+  };
+
+  window.openTaskModal = function(taskId, taskTitle) {
+    activeTaskId = taskId;
+    document.getElementById('task-modal-title').textContent = `Entregar Tarea: ${taskTitle}`;
+    document.getElementById('task-content').value = '';
+    document.getElementById('task-modal').classList.add('active');
+  };
+
+  window.submitTask = async function() {
+    if (!activeTaskId) return;
+    const content = document.getElementById('task-content').value.trim();
+    if (!content) return alert('Por favor introduce el contenido o enlace de la tarea.');
+
+    const btn = document.querySelector('#task-modal .btn-primary');
+    btn.disabled = true; btn.textContent = 'Enviando...';
+
+    try {
+      const res = await fetch(`/api/student/tasks/${activeTaskId}/submit`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ contenido: content })
+      });
+      if (res.ok) {
+        window.closeModals();
+        // Refresh tasks
+        state.tasks = await (await fetch(`/api/student/tasks`)).json();
+        renderTasks();
+      } else {
+        alert('Error al enviar la tarea.');
+      }
+    } catch(err) {
+      alert('Error de red.');
+    }
+    btn.disabled = false; btn.textContent = 'Enviar Tarea';
+  };
+
+  window.openProjectModal = function(projectId) {
+    const p = state.projects.find(x => x.id === projectId);
+    if (!p) return;
+    activeProjectId = projectId;
+    document.getElementById('project-modal-title').textContent = p.title;
+    document.getElementById('project-modal-desc').textContent = p.description;
+    const rng = document.getElementById('project-modal-progress');
+    rng.value = p.progress;
+    document.getElementById('project-modal-progress-text').textContent = p.progress;
+    document.getElementById('project-modal').classList.add('active');
+  };
+
+  window.updateProjectProgress = async function() {
+    if (!activeProjectId) return;
+    const progress = parseInt(document.getElementById('project-modal-progress').value, 10);
+    const btn = document.querySelector('#project-modal .btn-primary');
+    btn.disabled = true; btn.textContent = 'Guardando...';
+
+    try {
+      const res = await fetch(`/api/student/projects/${activeProjectId}/progress`, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json' },
+        body: JSON.stringify({ progress: progress })
+      });
+      if (res.ok) {
+        window.closeModals();
+        state.projects = await (await fetch(`/api/student/projects`)).json();
+        renderProjects();
+      } else {
+        alert('Error al actualizar el proyecto.');
+      }
+    } catch(err) {
+      alert('Error de red.');
+    }
+    btn.disabled = false; btn.textContent = 'Guardar Cambios';
+  };
+
+  window.joinClass = function(courseName) {
+    document.getElementById('class-modal-text').textContent = `Conectando con el aula virtual de ${courseName}...`;
+    document.getElementById('class-modal').classList.add('active');
+    setTimeout(() => {
+      document.getElementById('class-modal-text').textContent = 'El profesor aún no ha iniciado la sesión. Por favor, espera.';
+    }, 2000);
+  };
+
+  const fakeNotifs = [
+    { title: "Nueva Tarea", desc: "Se ha publicado la tarea de Matemáticas Avanzadas.", time: "Hace 10 min", icon: "📚" },
+    { title: "Calificación Publicada", desc: "Has recibido un 9.5 en Física.", time: "Hace 2 horas", icon: "⭐" }
+  ];
+
+  window.toggleNotifications = function(e) {
+    if (e) e.stopPropagation();
+    const drop = document.getElementById('notifications-dropdown');
+    const badge = document.getElementById('notif-badge');
+    if (!drop) return;
+    
+    if (!drop.classList.contains('active')) {
+      const list = document.getElementById('notifications-list');
+      list.innerHTML = fakeNotifs.map(n => `
+        <div class="notif-item">
+          <div class="notif-icon">${n.icon}</div>
+          <div>
+            <div class="notif-title">${n.title}</div>
+            <div class="notif-desc">${n.desc}</div>
+            <div class="notif-time">${n.time}</div>
+          </div>
+        </div>
+      `).join('');
+      drop.classList.add('active');
+      if (badge) badge.style.display = 'none'; // clear badge
+    } else {
+      drop.classList.remove('active');
+    }
+  };
+
+  // Close dropdown when clicking outside
+  document.addEventListener('click', (e) => {
+    const drop = document.getElementById('notifications-dropdown');
+    if (drop && drop.classList.contains('active') && !e.target.closest('.notification-bell')) {
+      drop.classList.remove('active');
+    }
+  });
+
+});

+ 45 - 0
user_platform/src/frontend/js/login.js

@@ -0,0 +1,45 @@
+document.addEventListener('DOMContentLoaded', () => {
+  const form = document.getElementById('login-form');
+  const errorBox = document.getElementById('login-error');
+
+  form.addEventListener('submit', async (e) => {
+    e.preventDefault();
+    const email = document.getElementById('email').value;
+    const password = document.getElementById('password').value;
+    const btn = form.querySelector('button[type="submit"]');
+
+    btn.disabled = true;
+    btn.textContent = 'Iniciando sesión...';
+    errorBox.style.display = 'none';
+
+    try {
+      const formData = new URLSearchParams();
+      formData.append('username', email);
+      formData.append('password', password);
+
+      const response = await fetch('/api/auth/login', {
+        method: 'POST',
+        headers: {
+          'Content-Type': 'application/x-www-form-urlencoded'
+        },
+        body: formData
+      });
+
+      if (response.ok) {
+        // Redirigir al dashboard
+        window.location.href = '/dashboard';
+      } else {
+        const errorData = await response.json();
+        errorBox.textContent = errorData.detail || 'Credenciales inválidas';
+        errorBox.style.display = 'block';
+        btn.disabled = false;
+        btn.textContent = 'Iniciar Sesión';
+      }
+    } catch (err) {
+      errorBox.textContent = 'Error de conexión con el servidor.';
+      errorBox.style.display = 'block';
+      btn.disabled = false;
+      btn.textContent = 'Iniciar Sesión';
+    }
+  });
+});

+ 62 - 0
user_platform/src/frontend/login.html

@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html lang="es">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Iniciar Sesión | BridgeLearn Alumnos</title>
+  <link rel="preconnect" href="https://fonts.googleapis.com">
+  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
+  <link href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600;700&family=Sora:wght@400;600;700;800&display=swap" rel="stylesheet">
+  <link rel="stylesheet" href="/static/css/style.css">
+  <style>
+    .login-container { display: flex; min-height: 100vh; }
+    .login-left { flex: 1; display: flex; flex-direction: column; justify-content: center; align-items: center; padding: 40px; background: #fff; }
+    .login-right { flex: 1; background: var(--gradient); display: flex; flex-direction: column; justify-content: center; align-items: center; color: white; padding: 40px; text-align: center; }
+    .login-box { width: 100%; max-width: 400px; }
+    .login-box h1 { font-size: 2rem; margin-bottom: 8px; color: var(--ink); }
+    .login-box p { color: var(--muted); margin-bottom: 32px; font-weight: 500; }
+    .form-group { margin-bottom: 24px; text-align: left; }
+    .form-label { display: block; font-size: 0.85rem; font-weight: 700; color: var(--ink); margin-bottom: 8px; }
+    .form-input { width: 100%; padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius-md); font-family: 'Manrope', sans-serif; font-size: 1rem; transition: all 0.2s; }
+    .form-input:focus { outline: none; border-color: var(--teal); box-shadow: 0 0 0 4px var(--teal-soft); }
+    .btn-block { width: 100%; padding: 14px; font-size: 1.1rem; }
+    .login-error { color: var(--rose); font-size: 0.85rem; font-weight: 600; margin-bottom: 16px; display: none; background: #FEE4E2; padding: 12px; border-radius: 8px; text-align: center; }
+  </style>
+</head>
+<body>
+  <div class="login-container">
+    <div class="login-left">
+      <div class="login-box animate-slide-up">
+        <div class="brand" style="margin-bottom: 40px; text-align: center;">BridgeLearn</div>
+        <h1>Bienvenido de nuevo</h1>
+        <p>Ingresa tus credenciales para acceder a tu panel de alumno.</p>
+        
+        <div id="login-error" class="login-error"></div>
+
+        <form id="login-form">
+          <div class="form-group">
+            <label class="form-label">Correo Electrónico</label>
+            <input type="email" id="email" class="form-input" placeholder="ejemplo@students.edu" required>
+          </div>
+          <div class="form-group">
+            <label class="form-label">Contraseña</label>
+            <input type="password" id="password" class="form-input" placeholder="••••••••" required>
+          </div>
+          <button type="submit" class="btn btn-primary btn-block">Iniciar Sesión</button>
+        </form>
+        <div style="text-align: center; margin-top: 24px; font-size: 0.85rem; color: var(--muted);">
+          ¿Problemas para acceder? Contacta a tu profesor.
+        </div>
+      </div>
+    </div>
+    <div class="login-right">
+      <div class="animate-slide-up delay-200">
+        <h2 style="font-size: 2.5rem; margin-bottom: 16px;">Aprende. Colabora. Avanza.</h2>
+        <p style="font-size: 1.1rem; opacity: 0.9; max-width: 400px; margin: 0 auto;">Únete a miles de estudiantes en África Oriental que ya están construyendo su futuro.</p>
+      </div>
+    </div>
+  </div>
+
+  <script src="/static/js/login.js"></script>
+</body>
+</html>

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov