The backend is a Python 3.12 / FastAPI service that applies both Clean Architecture and Hexagonal Architecture. The golden rule is that dependencies always point toward the domain — the domain knows nothing about Playwright, Tesseract, or FastAPI. Infrastructure adapters implement abstract ports defined in the application layer, and use cases wire together domain services without ever importing an infrastructure class directly.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Andr21Da16/UNITRU-ACADEMIC/llms.txt
Use this file to discover all available pages before exploring further.
Layer Diagram
Domain Layer
The domain layer contains pure Python — no async I/O, no external libraries beyond the standard library anddecimal. It is the stable core of the application.
Entities
| Entity | Description |
|---|---|
Course | A single course row from the grades table, with unit grades (U1–U6), sustentación, NP, and inhibition flag |
GradeReport | Collection of Course objects for the current period, plus payment metadata |
AcademicRecord | Full historical course list grouped by period, with weighted average and credits |
Attendance | Per-course attendance summary: sessions attended, absent, justified, percentage, and at-risk flag |
StudentProfile | Personal data: full name, enrollment number, faculty, school, emails, photo as data URL |
Enrollment | Current period enrollment sheet (ficha de matrícula): enrolled courses with teacher and group |
OptimizedSchedule | A candidate conflict-free schedule from the optimizer, with a numeric score |
ScheduleCatalog | Loaded catalog of official course sections from the JSON file |
BrowserSession | Value object wrapping a Playwright Browser, BrowserContext, and Page |
Domain Services
Domain services are pure functions — they accept entities and return derived values with no side effects.| Service | Responsibility |
|---|---|
grade_predictor | Given the known unit grades, generates every combination of pending unit scores (0–20) whose weighted average is ≥ 14 (using ROUND_HALF_UP) |
academic_analytics | Groups the full AcademicRecord by period and computes pass rate, weighted average, and retried courses per period |
grade_analytics | Computes the partial average for a single course from its published unit grades |
schedule_builder | Deduplicates attendance session records by (day, time, course) to produce the weekly schedule grid |
schedule_optimizer | Combines sections from the catalog, searching for the conflict-free set with the highest score (fewer gaps, balanced days, fewer extreme-hour sessions) |
All grade values in the domain use
Decimal, never float. Unpublished grades are None — never 0 or an empty string — so that callers can distinguish “not yet graded” from “zero”.Application Layer
The application layer defines what the system does, independent of how it is done.Ports (Hexagonal Interfaces)
Ports are abstract base classes (ABC) that express the contracts the application layer needs from the outside world.
| Port | Implemented by |
|---|---|
SuvAuthenticationPort | SuvAuthenticator |
SuvGradeExtractionPort | SuvGradeExtractor |
SuvProfileExtractionPort | SuvProfileExtractor |
SuvRecordExtractionPort | SuvRecordExtractor |
SuvAttendanceExtractionPort | SuvAttendanceExtractor |
SuvEnrollmentExtractionPort | SuvEnrollmentExtractor |
SuvNavigationPort | SuvNavigator |
ScheduleCatalogPort | JsonCatalogAdapter |
CaptchaSolverPort | TesseractAdapter |
BrowserAutomationPort | PlaywrightAdapter |
Use Cases
AuthenticateStudentUseCase
Orchestrates the full login flow, including CAPTCHA retries.
ExtractFullDashboardUseCase
Navigates all SUV modules sequentially in one browser session. Non-critical extractors (profile, record, enrollment, attendance) are wrapped in try/except — if they fail, the field is set to None and extraction continues. Grades are mandatory; a failure there propagates immediately.
Infrastructure Layer
Infrastructure adapters implement the application ports and contain all I/O.Playwright Adapters
PlaywrightAdapter — BrowserAutomationPort
Creates and destroys Chromium browser sessions with anti-detection hardening:
async_playwright instance created per-session is stopped last, preventing process-level resource leaks.
SuvAuthenticator — SuvAuthenticationPort
Navigates to https://suv2.unitru.edu.pe/, fills the login form, clicks the JS-driven login button (#myButton), and detects success by checking for "SELECCIONAR PERFIL" and "Alumno" in the page content.
SessionManager
Maintains a dictionary of BrowserSession objects keyed by session_id. An asyncio.Lock serializes concurrent WebSocket connections modifying the session registry.
sidebar_navigation.py — shared navigation helpers
The SUV sidebar starts closed (body.ls-closed). All extractors call the centralized helpers in sidebar_navigation.py rather than re-implementing the click sequence.
OCR Adapter — TesseractAdapter
The SUV CAPTCHA is an arithmetic expression image (e.g. 3 + 5 = ?). TesseractAdapter implements CaptchaSolverPort with a robust voting pipeline:
subprocess.run with the image piped through stdin — not via pytesseract — because Leptonica on the deployment environment cannot open the temporary file that pytesseract creates:
- Convert to grayscale
- Threshold at
< 60to remove the gray noise lines and keep only the black text - Scale by the given factor using
LANCZOSresampling - Re-threshold at
< 128to sharpen after scaling - Add a white border of 20 px
Catalog Adapter — JsonCatalogAdapter
Loads data/horarios_catalogo.json (generated from the official Google Sheets schedule) and provides the ScheduleCatalogPort interface consumed by the schedule optimizer use case.
Presentation Layer
The presentation layer is a single file:websocket_handler.py, which registers the /ws endpoint.
WebSocketHandler
EventEmitter
A thin wrapper that sends {event, data} JSON over the WebSocket:
Domain Exception → User Message Mapping
The handler maps each domain exception to a user-facing Spanish message:domain/exceptions/suv_errors.py:
Dependency Injection
Dependencies are wired manually inmain.py — no DI framework is used. The wiring order follows the dependency graph:
The FastAPI lifespan hook calls
session_manager.destroy_all() on shutdown, ensuring any orphaned Chromium processes are cleaned up if the server is stopped mid-session.Environment Variables
| Variable | Default | Description |
|---|---|---|
ALLOWED_ORIGINS | http://localhost:3000 | Comma-separated CORS origins (Railway injects the production frontend URL) |
PORT | 8000 | Server listen port (injected automatically by Railway) |