The risk classification engine inDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Pierrot-01/Hackathon_epis_2026/llms.txt
Use this file to discover all available pages before exploring further.
backend/classifier.py is the authoritative decision-maker of Vanguardia EPIS. It takes a single student record as input, evaluates each of three academic variables independently against frozen numeric thresholds, and then aggregates the results using a worst-case rule to produce an overall risk level. The entire process is deterministic: given the same input, the same output is guaranteed every time — no randomness, no AI inference, no network call.
Risk Levels
The engine produces exactly one of four risk levels. The emoji is the canonical representation used throughout the codebase and the frontend.| Constant | Value | Meaning |
|---|---|---|
NIVEL_BAJO | 🟢 | Student is on track — all indicators within expected ranges |
NIVEL_MEDIO | 🟡 | One warning signal detected — teacher should monitor |
NIVEL_ALTO | 🔴 | High risk — immediate teacher intervention recommended |
NIVEL_INSUFICIENTE | ⚪ | No data available — manual review required |
Frozen Thresholds (§4.1)
These constants are marked congelados (frozen) in the source code. They must not be modified without a specification change.Variable-Level Evaluation Rules
Each variable is evaluated in isolation before aggregation. Attendance (asistencia_pct) — numeric, range 0–100:
| Value | Level |
|---|---|
| ≥ 90% | 🟢 |
| 75% – 89% | 🟡 |
| < 75% | 🔴 |
notas_promedio) — vigesimal scale 0–20:
| Value | Level |
|---|---|
| ≥ 13 | 🟢 |
| 11 – 12 | 🟡 |
| < 11 | 🔴 |
participacion) — categorical:
| Value | Level |
|---|---|
"alta" or "media" | 🟢 |
"baja" | 🟡 |
By design (§4.1), participation never reaches 🔴 in isolation. Low participation is a supporting signal that can contribute to an overall 🔴 via the worst-case aggregation rule, but a student cannot be classified as high-risk on participation alone.
Worst-Case Aggregation Rule (Art. III §3.2.1)
After each variable is evaluated independently, the engine applies a single aggregation pass over the three results.clasificar_estudiante():
Missing Data Handling (Art. IX §9.3.2)
The engine handles missing or invalid data gracefully without raising exceptions.normalizar_campo() validates all three fields
Before any evaluation occurs,
normalizar_campo() converts invalid values to None:asistencia_pctoutside 0–100 →Nonenotas_promediooutside 0–20 →Noneparticipacionnot in{"alta", "media", "baja"}→None
Missing variables are excluded from aggregation
Variables with a
None value are added to variables_faltantes[] and are not included in the worst-case aggregation. Only the variables that have valid data contribute to the overall risk level.normalizar_campo() Function
None. It never mutates the original dict.
construir_motivo() — Audit Trail
Every classification result includes a human-readable audit trail. construir_motivo() generates one message per evaluated variable, and one message per missing variable:
clasificar_estudiante() — Full Signature
id, asistencia_pct, notas_promedio, and participacion. Any field may be None or absent.
Output:
Calling the Classifier
Built-In Test Suite
The classifier ships with a self-contained test runner in theif __name__ == "__main__" block. Run it directly to verify the logic at any time:
Test Cases
| Case | Attendance | Grade | Participation | Expected | Rule triggered |
|---|---|---|---|---|---|
| Caso 1 — Todo bien | 95% | 15 | alta | 🟢 | All 🟢 |
| Caso 2 — 1 señal 🟡 | 80% | 15 | alta | 🟡 | 1 × 🟡 (attendance) |
| Caso 3 — 2 señales 🟡 → 🔴 | 80% | 11.5 | alta | 🔴 | 2 × 🟡 accumulation |
| Caso 4 — 1 var 🔴 directa | 60% | 15 | alta | 🔴 | attendance = 🔴 directly |
| Caso 5 — Desempate peor caso | 95% | 9 | alta | 🔴 | grades = 🔴 directly |
| Caso 6 — Falta participacion | 95% | 15 | None | 🟢 | 2 of 3 vars, both 🟢 |
| Caso 7 — Faltan 2 vars | None | 15 | None | 🟢 | 1 of 3 vars, 🟢 |
| Caso 8 — Faltan 3 vars | None | None | None | ⚪ | Art. IX §9.3.2 short-circuit |
| Caso 9 — Límite exacto asist | 90% | 15 | alta | 🟢 | Boundary: exactly 90 = 🟢 |
| Caso 10 — Límite exacto notas | 95% | 13 | alta | 🟢 | Boundary: exactly 13 = 🟢 |