Personal academic ops system for Moodle-based university classes.
It collects Moodle data, normalizes it into structured items, detects changes and deadlines, and exposes both human-facing notifications and agent-facing briefs.
If you are an automated agent, read AGENTS.md first.
- Syncs enrolled courses, course contents, assignments, quizzes, forums, grade metadata, and calendar exports from Moodle.
- Stores raw source artifacts and normalized items separately.
- Detects deadline changes, schedule changes, and new announcements.
- Sends Telegram digests and urgent alerts.
- Exposes compact item/course briefs for downstream agents.
- Keeps provenance available through item facts, notifications, and LLM job history.
- Create a
.envfile with the required settings. - Start the stack:
docker compose up -d --build- Visit the API:
http://localhost:8000/health
Required:
MOODLE_BASE_URLMOODLE_USERNAMEMOODLE_PASSWORD
Common optional settings:
APP_ENVLOG_LEVELDATABASE_URLRAW_STORAGE_PATHDAILY_DIGEST_HOURTELEGRAM_BOT_TOKENTELEGRAM_CHAT_IDENABLE_LLMNVIDIA_API_KEYNVIDIA_API_URLNVIDIA_MODEL
Time handling:
- User-facing times are normalized to
America/Argentina/Buenos_Aires. - Telegram renders compact human-readable dates.
Docker Compose starts:
db: PostgreSQLapi: FastAPI serverworker: collector, digest, Telegram polling, and enrichment loop
The worker runs the scheduled Moodle syncs and notification dispatches. The API serves read-only views and manual sync endpoints.
Useful endpoints:
GET /healthGET /health/detailsGET /coursesGET /itemsGET /items/{item_id}GET /items/{item_id}/briefGET /items/{item_id}/provenanceGET /courses/{course_id}/snapshotGET /courses/{course_id}/briefGET /changes/recentGET /changes/since?since=2026-03-27T03:30:00ZGET /deadlines/upcomingGET /risksGET /sync/collectorsPOST /sync/run/{collector_name}POST /items/{item_id}/acknowledgePOST /notifications/dispatchGET /notifications/digest
If Telegram is configured, the bot can answer:
/digestor/digest 48/risks/deadlines/changes/help
For efficient external monitoring, prefer:
GET /changes/since?since=<ISO-8601 timestamp>
Behavior:
- Returns items with
updated_at >= since - Orders results by
updated_atascending, thenidascending - Intended for cursor-based polling by external assistants or cron jobs
- Includes semantic delta metadata on each item:
meaningful_key: stable hash of student-relevant fieldsmeaningful_change: whether the item meaningfully changed vs the latest pre-cursor versionchange_kind: one ofnew,deadline_changed,schedule_changed,review_changed,content_changed,refresh_only
Recommended pattern:
- Store the latest seen
updated_at - Poll
/changes/since - Alert only when
meaningful_change == trueandchange_kind != refresh_only - Advance the cursor to the newest returned
updated_at
Use /risks and /deadlines/upcoming for periodic snapshots, not as your primary change feed.
The LLM layer is optional.
When enabled, it compresses ambiguous or high-value Moodle content into agent-friendly briefs. It does not replace the deterministic collectors or become the source of truth.
Relevant data layers:
llm_jobs: audit trail for model requests and responsesitem_facts: granular extracted evidenceitem_briefs: compact agent-facing projection
If a generated brief is weak, the system can backfill it with deterministic fallback logic instead of keeping a useless model echo.
To refresh weak briefs manually:
docker compose exec worker python /app/scripts/backfill_briefs.py --weak-onlyTo target one item:
docker compose exec worker python /app/scripts/backfill_briefs.py --item-id 380Run tests:
.venv/bin/pytest -qCompile check:
python -m compileall -q src scripts- Moodle is the primary source of truth.
- Raw source artifacts are retained under
data/uni-tracker/artifacts/runtime/for provenance and replay. - To store artifacts in Cloudflare R2 instead of the VPS filesystem, set
ARTIFACT_STORAGE_BACKEND=s3plus theS3_*variables from.env.example, runalembic upgrade head, and migrate existing local artifacts withscripts/migrate_artifacts_to_r2.pyafter a dry run and verification. - Postgres persists in the named Docker volume
uni_tracker_postgres_data. - Human-facing alerts stay conservative and source-linked.
- Agent-facing consumers should prefer briefs and provenance over raw Moodle pages whenever possible.