A conversational Telegram-based health tracker for chronic illness management. Users log symptoms, medications, and general wellbeing through natural conversation via Telegram. Supports multiple users, each with an isolated database.
Luigi is a personal health assistant that:
- Responds to inbound Telegram messages with contextual, empathetic replies
- Sends scheduled morning and evening check-in prompts (per user)
- Stores all conversation history locally in per-user SQLite databases for privacy
- Uses GPT-4o-mini via OpenRouter for natural language understanding
- Runs in polling mode — no HTTP server or public webhook required
This project follows a local-first architecture with these key decisions:
- Telegram over SMS: Rich bot API, no per-message cost, polling removes need for a public webhook
- Polling over webhook: Simplifies deployment — no ingress, no ngrok, no port forwarding
- GPT-4o-mini: Best balance of cost, latency, and conversational quality
- SQLite (per-user): Each user gets an isolated
data/{chat_id}.db; portable, no server required - APScheduler: In-process scheduling simplifies deployment
- Hetzner VPS: Low-cost always-on Linux host; managed via systemd
- Local-first: Keeps health data private, no cloud storage
luigi_app/
├── src/
│ ├── __init__.py
│ ├── main.py # Polling entrypoint — builds Application, starts scheduler
│ ├── agent.py # LLM conversation logic + name extraction
│ ├── database.py # SQLite connection + per-user queries
│ ├── scheduler.py # APScheduler setup + per-user check-in jobs
│ ├── telegram_handler.py # Telegram send/receive helpers + message dispatch
│ └── config.py # Environment variable loading
├── tests/
│ ├── __init__.py
│ ├── test_agent.py
│ ├── test_database.py
│ └── test_telegram.py
├── data/ # Per-user SQLite databases (gitignored)
│ └── {chat_id}.db # One file per Telegram user
├── deploy/
│ └── luigi.service # systemd unit file for VPS deployment
├── .env # Local environment variables (gitignored)
├── .env.example # Template for required env vars
├── requirements.txt # Python dependencies
└── README.md # This file
Name: Luigi Tone: Calm, polite, empathetic, concise, straightforward Function: Records symptoms, medications, and wellbeing — does not give health advice Communication: Asks for the user's name on first contact; uses it occasionally thereafter
Example first message: "Hi, I'm Luigi — your health tracking assistant. What's your name?"
Example follow-up: "Got it — migraine starting around 3pm, ibuprofen taken. Let me know how you're feeling later."
-
Configuration (
src/config.py)- Loads environment variables using python-dotenv
- Validates all required settings on startup; raises
ValueErrorif any are missing - Provides global settings access via
get_settings()
-
Database (
src/database.py)- Per-user SQLite databases at
data/{chat_id}.db - Three tables per database:
messages,schedules,user_profile - Auto-seeds default check-ins (10:00 AM and 8:00 PM) on first user contact
get_user_db_path()andget_all_user_databases()for multi-user routing
- Per-user SQLite databases at
-
Agent (
src/agent.py)- LLM integration with OpenAI GPT-4o-mini via OpenRouter
- Conversation context limited to the lesser of: last 24 hours or last 5 messages
extract_name_from_message()detects user name from natural language- Error handling with graceful fallback messages
-
Telegram Handler (
src/telegram_handler.py)send_message(): sends outbound messages via the Bot APIhandle_message(): core dispatch — per-user DB init, inbound logging, stop-command, name extraction, LLM response, outbound logging_on_message(): python-telegram-bot handler that delegates tohandle_message()start_command(): handles/start, logs new chat_id
-
Scheduler (
src/scheduler.py)- APScheduler
AsyncIOSchedulerfor timed check-ins - Timezone-aware scheduling (configured via
TIMEZONEenv var) - Iterates all user databases to register per-user cron jobs
- Scheduled messages use the LLM for contextual check-ins; falls back to static template on failure
- APScheduler
-
Application (
src/main.py)- Builds the
python-telegram-botApplication with polling - Starts/stops the scheduler via
post_init/post_shutdownhooks - Registers
/startcommand handler and free-text message handler
- Builds the
Each user gets their own data/{chat_id}.db file with the following tables:
-- Conversation history
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),
body TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
message_id INTEGER
);
-- Scheduled check-ins
CREATE TABLE schedules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
hour INTEGER NOT NULL CHECK (hour >= 0 AND hour <= 23),
minute INTEGER NOT NULL CHECK (minute >= 0 AND minute <= 59),
message_template TEXT NOT NULL,
active BOOLEAN DEFAULT TRUE
);
-- User profile (name stored here after extraction)
CREATE TABLE user_profile (
id INTEGER PRIMARY KEY CHECK (id = 1),
name TEXT
);Per-user defaults seeded on first contact:
- 10:00 AM: "Good morning! How are you feeling today?"
- 8:00 PM: "Evening check-in: How was your day? Any symptoms or notes to share?"
Users can opt out by sending stop.
-
Copy
.env.exampleto.env:cp .env.example .env
-
Fill in your credentials:
# Telegram TELEGRAM_BOT_TOKEN=your_bot_token_from_botfather # OpenRouter OPENROUTER_API_KEY=your_openrouter_api_key OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 LLM_MODEL=openai/gpt-4o-mini # App Config TIMEZONE=America/New_York DATABASE_DIR=data/ LOG_LEVEL=INFO
-
Install dependencies:
pip install -r requirements.txt
python -m src.mainNo ngrok or port forwarding needed — Luigi uses Telegram's polling API.
-
Clone the repository and configure the environment:
git clone https://github.com/your-username/luigi_app.git /opt/luigi_app cd /opt/luigi_app cp .env.example .env # Edit .env with your credentials
-
Create a virtual environment and install dependencies:
python -m venv .venv .venv/bin/pip install -r requirements.txt
-
Install and enable the systemd service:
cp deploy/luigi.service /etc/systemd/system/luigi.service systemctl daemon-reload systemctl enable luigi systemctl start luigi -
Check logs:
journalctl -u luigi -f
Run the full test suite:
pytest tests/ -vTest coverage:
- ✅ Database layer: table creation, per-user DB routing, message insertion, scheduling, name get/set
- ✅ Agent layer: LLM conversation building, name extraction, fallback handling
- ✅ Telegram layer: message handling, stop command, new user init, handler wiring
All 57 tests currently passing.
- LLM failures: Returns "The LLM call is failing, I'll try again soon."
- Scheduled message failures: Falls back to static template; logs error if fallback also fails
- Telegram API errors: Logs exception details and re-raises
- Database errors: Logged with full exception information
- Missing environment variables: Raises
ValueErroron startup
All modules use Python's logging library with configurable levels:
INFO: Message received/sent, new user registration, scheduler jobs, startup/shutdownDEBUG: LLM prompts/responses, database queries, schedule routingERROR: API failures, exceptions (withexc_info=True)
✅ Phase 1: Project scaffolding ✅ Phase 2: Configuration & database layer ✅ Phase 3: Agent (LLM) layer ✅ Phase 4: Telegram handler (send/receive, multi-user dispatch) ✅ Phase 5: Scheduler layer (per-user cron jobs) ✅ Phase 6: Polling entrypoint ✅ Phase 7: Integration verification + VPS deployment
Current Status: Complete v1.1 implementation — Telegram polling, multi-user, all 57 tests passing
- Structured extraction: Add
health_eventstable for parsed symptoms/medications - Natural language scheduling: LLM extracts intent → inserts into
schedules - Web dashboard: Read-only view of conversation history and trends per user
- Admin commands: Bot owner commands to inspect user count, DB sizes, or force check-ins
- Webhook mode: Optional switch to webhook deployment if VPS gets a stable domain + TLS
This project is for personal health tracking use. Ensure compliance with healthcare data regulations in your jurisdiction.
- Built with python-telegram-bot, SQLite, APScheduler, and OpenAI GPT-4o-mini
- Telegram transport via python-telegram-bot (polling mode)
- LLM access via OpenRouter
- Designed for local-first privacy and simplicity