Skip to main content

Architecture Overview

System Diagram

Single-Process Architecture

The bot runs as a single process orchestrated by main.py. The async main() function starts components in a specific order:

  1. Database initialization
  2. Survey engine loading
  3. Telegram bot (polling mode)
  4. APScheduler for timed delivery
  5. FastAPI HTTP server

Shutdown reverses this order to ensure clean teardown.

Key Components

Survey Engine (src/survey_engine.py)

Loads YAML survey definitions from src/surveys/, resolves composite surveys (which reference other surveys via includes), and supports per-section randomization of question order.

Database (src/database.py)

Uses aiosqlite with WAL mode for concurrent read access. Key tables:

  • participants -- enrolled study participants
  • surveys -- survey metadata
  • schedules -- cron-based delivery schedules
  • survey_sessions -- active/completed/expired session tracking
  • responses -- individual question responses
  • cognitive_results -- results from cognitive assessment tasks

Scheduler (src/scheduler.py)

APScheduler handles periodic survey delivery using cron-based schedules. Surveys are pushed to participants at configured times.

API (src/api.py)

FastAPI REST API with all endpoints under /api/v1/. Authentication uses Authorization: Bearer {API_KEY} header (except /health and /cognitive/complete which uses one-time tokens).

Telegram Bot (src/bot.py)

Built on python-telegram-bot with handler groups for priority-based message routing:

  • Group 0: ConversationHandler (signup flow), commands, callback query handlers
  • Group 1: Survey message handler (separate group to avoid interfering with ConversationHandler)
  • Group 2: Fallback handler

WebSocket API (src/api_webapp.py)

WebSocket transport for the PWA frontend. Handles participant registration, WebSocket connections, and survey session management using the same survey logic as the Telegram handler.

Survey Delivery Model

Surveys are pushed to participants by the scheduler or API -- they are not requested by users. Session state is tracked in the survey_sessions table using a state machine:

active → completed
active → expired

Dependency Injection

Multiple modules use a set_globals() pattern for dependency injection. These globals must be set before the module is used, which is why main.py enforces a strict startup order.

Callback Data Encoding

Telegram callback data uses the format s{8-char-session-id}_{payload} (e.g., s1a2b3c4d_3). The 8-character prefix is a truncated uuid4().hex[:8]. This keeps callback data within Telegram's 64-byte limit.