System Overview
What the bot is, who uses it, what it talks to, and the boundaries it operates within. This is the C4 Level 1 (System Context) view. For the internals, see Architecture.
What it does
Section titled “What it does”abitly-bot is a Telegram bot that helps Ukrainian university applicants (“abiturients”) monitor their admission chances. Users paste an offer (program) link and the bot replies with that program’s statistics plus the user’s own competitive score and rank in the applicant pool. Users can track offers, get reminders for university open days, and link their Telegram chat to their web account on the Abitly platform.
It is a Python / aiogram 3 rewrite of a retiring TypeScript/Telegraf bot; behavioural parity with that bot is a hard requirement during cutover (see ADR 0004).
Primary capabilities
Section titled “Primary capabilities”| Capability | Trigger | Entry point |
|---|---|---|
| Show offer stats + personal score/rank | paste an offer URL | handlers/offer_text.py |
| Track / untrack offers (limit 5) | inline buttons | handlers/offer_callbacks.py |
| My tracked offers | /myoffers | handlers/offers.py |
| My profile & score | /myprofile | handlers/profile.py |
| My statistics | /statistics | handlers/statistics.py |
| Open days I attend + pagination | /myopendays | handlers/open_days.py |
| Open-day notification filters | /opendayfilters | handlers/filters.py |
| Notification settings | /notifications | handlers/notifications.py |
| Welcome, university deep links, subscribe | /start [payload] | handlers/start.py |
| Link web account | /start link_<token> | services/linking_service.py |
| Daily open-day reminders | cron 07:00 Europe/Kyiv | infra/scheduler.py + services/notification_service.py |
| Broadcast / open-day update fan-out | /broadcast, /notifyOpenDaysUpdate (admin) | handlers/admin.py |
Actors and external systems
Section titled “Actors and external systems”C4Context
title System Context — abitly-bot
Person(applicant, "Applicant", "Ukrainian university applicant using Telegram")
Person(admin, "Admin", "Operator; runs /broadcast and /notifyOpenDaysUpdate")
System(bot, "abitly-bot", "Python / aiogram 3 long-polling bot")
System_Ext(tg, "Telegram Bot API", "Delivers updates (getUpdates), sends messages")
SystemDb_Ext(pg, "PostgreSQL (schema 'abitly')", "Shared DB owned by abitly-api-v2")
SystemDb_Ext(redis, "Redis", "Shared: FSM state + one-time link tokens")
System_Ext(api, "abitly-api-v2 (backend)", "Owns schema; mints link tokens")
Rel(applicant, bot, "Sends commands / offer links")
Rel(admin, bot, "Admin commands")
Rel(bot, tg, "Long-polls updates; sends replies & fan-outs")
Rel(bot, pg, "Reads programs/applicants; writes tracking & link", "asyncpg + TLS")
Rel(bot, redis, "FSM storage; redeems link tokens")
Rel(api, pg, "Owns & migrates the schema")
Rel(api, redis, "Mints abitly:link:<token>")
Tech stack
Section titled “Tech stack”| Concern | Choice |
|---|---|
| Language / runtime | Python ≥ 3.12 |
| Bot framework | aiogram 3 (>=3.15,<4) |
| ORM / DB driver | SQLAlchemy 2.0 (async) + asyncpg |
| Database | PostgreSQL (shared schema abitly) |
| Cache / FSM / tokens | Redis (redis-py async) |
| Config | pydantic-settings (env / .env) |
| Scheduler | APScheduler (AsyncIOScheduler) |
| Rate limiting | aiolimiter |
| Health endpoint | aiohttp |
| Tooling | uv, ruff, mypy --strict, pytest |
Dependencies: pyproject.toml.
- Behavioural parity with the retiring TS bot during cutover (ADR 0004).
- Business logic (score, rank, rendering) verifiable offline (ADR 0002).
- Safe operation against an incomplete shared schema (ADR 0003, ADR 0006).
- Respect Telegram flood limits on fan-outs (ADR 0005).
Non-goals
Section titled “Non-goals”- Owning the database schema. The backend owns all DDL (ADR 0006).
- Horizontal scaling / multi-instance. Single polling worker; FSM and sender state are in-process (ADR 0001, ADR 0005).
- Minting link tokens. The backend mints; the bot only redeems.
- Webhook ingress. Long-polling only (ADR 0001).
Constraints
Section titled “Constraints”- One shared schema with two writers; the bot writes only its owned subset.
- Two notification-filter join tables are a backend prerequisite and do not exist yet — filter features are gated until then (Data Model).
- Live DB access needs a network allowlist + provider CA (ADR 0007).
Current status
Section titled “Current status”Code-complete (phases Ф0–Ф7); offline-verified by pytest + ruff + mypy --strict.
Remaining work is not in this repo (backend prerequisites + a live smoke run).
The living status / handoff doc is docs/MIGRATION_STATUS.md —
that is transient operational status, intentionally kept outside this system-design set.