Skip to content

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.

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).

CapabilityTriggerEntry point
Show offer stats + personal score/rankpaste an offer URLhandlers/offer_text.py
Track / untrack offers (limit 5)inline buttonshandlers/offer_callbacks.py
My tracked offers/myoffershandlers/offers.py
My profile & score/myprofilehandlers/profile.py
My statistics/statisticshandlers/statistics.py
Open days I attend + pagination/myopendayshandlers/open_days.py
Open-day notification filters/opendayfiltershandlers/filters.py
Notification settings/notificationshandlers/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 reminderscron 07:00 Europe/Kyivinfra/scheduler.py + services/notification_service.py
Broadcast / open-day update fan-out/broadcast, /notifyOpenDaysUpdate (admin)handlers/admin.py
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>")
ConcernChoice
Language / runtimePython ≥ 3.12
Bot frameworkaiogram 3 (>=3.15,<4)
ORM / DB driverSQLAlchemy 2.0 (async) + asyncpg
DatabasePostgreSQL (shared schema abitly)
Cache / FSM / tokensRedis (redis-py async)
Configpydantic-settings (env / .env)
SchedulerAPScheduler (AsyncIOScheduler)
Rate limitingaiolimiter
Health endpointaiohttp
Toolinguv, 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).
  • 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).
  • 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).

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.