236 lines
8.5 KiB
Markdown
236 lines
8.5 KiB
Markdown
# Training Assistant — Design Spec
|
||
|
||
**Date:** 2026-05-20
|
||
**Status:** Approved
|
||
|
||
---
|
||
|
||
## Overview
|
||
|
||
A personal Telegram bot that tracks anal training sessions, sends reminders, and uses an AI coach (via OpenRouter) to adaptively manage the training schedule and provide progress feedback.
|
||
|
||
Built with Node.js/TypeScript, Telegraf, SQLite, and node-cron. Designed so a web dashboard (Approach C) can be added later as a read-only layer on the same SQLite file.
|
||
|
||
---
|
||
|
||
## Architecture
|
||
|
||
A single TypeScript process with four internal components:
|
||
|
||
```
|
||
┌─────────────────────────────────────────┐
|
||
│ Telegram Bot (Telegraf) │
|
||
│ - command routing │
|
||
│ - message handling │
|
||
└──────────────┬──────────────────────────┘
|
||
│
|
||
┌───────┼───────┐
|
||
▼ ▼ ▼
|
||
┌──────────┐ ┌──────┐ ┌─────────────┐
|
||
│ Session │ │ Cron │ │ AI Coach │
|
||
│ Logger │ │ Jobs │ │ (OpenRouter)│
|
||
└────┬─────┘ └──┬───┘ └──────┬──────┘
|
||
│ │ │
|
||
└──────────┴─────────────┘
|
||
│
|
||
┌──────▼──────┐
|
||
│ SQLite │
|
||
└─────────────┘
|
||
```
|
||
|
||
**Components:**
|
||
|
||
- **Bot handler** — Telegraf v4, routes commands and free-text messages
|
||
- **Session logger** — writes training sessions to SQLite, validates input
|
||
- **Cron jobs** — `node-cron`, sends reminders at configured times
|
||
- **AI coach** — OpenRouter via OpenAI-compatible client; reads history from DB, generates coaching and schedule proposals
|
||
- **SQLite** — `better-sqlite3`, single file, easy to back up
|
||
- **Config** — `.env` for Telegram token, OpenRouter API key, user chat ID, reminder times, preferred model
|
||
|
||
---
|
||
|
||
## Data Model
|
||
|
||
### `sessions`
|
||
| Column | Type | Notes |
|
||
|---|---|---|
|
||
| `id` | INTEGER PRIMARY KEY | |
|
||
| `date` | TEXT | YYYY-MM-DD |
|
||
| `duration_min` | INTEGER | |
|
||
| `notes` | TEXT | Optional free text |
|
||
| `created_at` | TEXT | ISO timestamp |
|
||
|
||
### `schedule`
|
||
| Column | Type | Notes |
|
||
|---|---|---|
|
||
| `id` | INTEGER PRIMARY KEY | |
|
||
| `date` | TEXT | YYYY-MM-DD |
|
||
| `planned_min` | INTEGER | |
|
||
| `status` | TEXT | `pending` \| `completed` \| `skipped` |
|
||
| `skip_reason` | TEXT | Optional |
|
||
| `ai_rationale` | TEXT | Why the AI scheduled this |
|
||
| `created_at` | TEXT | ISO timestamp |
|
||
|
||
### `settings`
|
||
| Column | Type | Notes |
|
||
|---|---|---|
|
||
| `key` | TEXT PRIMARY KEY | |
|
||
| `value` | TEXT | |
|
||
|
||
Used for: reminder times, preferred OpenRouter model, training goal, reminder enabled flag.
|
||
|
||
**Session logging** writes to `sessions` and updates the matching `schedule` row to `completed`. The AI reads both tables to understand history and the current plan.
|
||
|
||
The existing `.ics` calendar is not imported. The AI generates a fresh adaptive schedule on first run, seeded by onboarding answers.
|
||
|
||
---
|
||
|
||
## Commands
|
||
|
||
### Logging (no AI, always fast)
|
||
|
||
| Command | Example | Action |
|
||
|---|---|---|
|
||
| `/log <duration>` | `/log 2h`, `/log 90m` | Record a completed session for today |
|
||
| `/skip [reason]` | `/skip work trip` | Mark today as skipped |
|
||
| `/snooze` | `/snooze` | Postpone today's reminder by 2 hours |
|
||
|
||
### Status & history
|
||
|
||
| Command | Action |
|
||
|---|---|
|
||
| `/status` | Today's plan, completion state, next reminder time |
|
||
| `/history` | Last 7 sessions (date, duration, notes) |
|
||
| `/streak` | Current streak and longest streak |
|
||
|
||
### AI-powered
|
||
|
||
| Command | Action |
|
||
|---|---|
|
||
| `/progress` | AI analyzes recent weeks, gives assessment and flags patterns |
|
||
| `/next` | AI proposes the next training step (duration or frequency change) |
|
||
| `/reschedule [note]` | AI regenerates upcoming schedule; optional context ("busy next week") |
|
||
| `/chat <message>` | Free-form coaching conversation |
|
||
|
||
### Setup
|
||
|
||
| Command | Action |
|
||
|---|---|
|
||
| `/setup` | First-run onboarding: current level, typical availability, goal — generates initial schedule |
|
||
| `/settings model <id>` | Switch OpenRouter model at runtime without redeploying |
|
||
|
||
**Fallback:** Unrecognized messages prompt the user to use `/chat` for free-form questions.
|
||
|
||
**Error handling:** AI commands show a clear error message if OpenRouter is unavailable. Logging commands (`/log`, `/skip`) always work regardless of AI availability.
|
||
|
||
---
|
||
|
||
## AI Integration
|
||
|
||
**Client:** `openai` npm package pointed at `https://openrouter.ai/api/v1`. Model is read from `settings` at runtime.
|
||
|
||
**Context sent on every AI call:**
|
||
- Training goal (from setup)
|
||
- Last 30 sessions (date, duration, notes)
|
||
- Upcoming schedule with statuses
|
||
- Recent skips and their reasons
|
||
- Current streak
|
||
|
||
**Default model:** `google/gemini-2.5-flash-preview`
|
||
Chosen for strong reasoning, low cost, fast responses, and large context window. Swappable at runtime via `/settings model <id>`.
|
||
|
||
**Alternative models (all via OpenRouter):**
|
||
- `deepseek/deepseek-chat` — very cheap, good for analytical tasks
|
||
- `meta-llama/llama-3.3-70b-instruct` — free on some providers
|
||
- `openai/gpt-4o-mini` — reliable, cheap, structured-prompt-friendly
|
||
- `qwen/qwen-2.5-72b-instruct` — strong instruction following, low cost
|
||
|
||
**Prompt strategy:**
|
||
- Coaching prompts live in a `prompts/` directory — tweakable without touching logic
|
||
- The AI is instructed to be encouraging but honest, explain its reasoning, and flag overtraining or undertraining
|
||
- Schedule proposals are requested as JSON for reliable parsing:
|
||
|
||
```json
|
||
[
|
||
{ "date": "2026-05-22", "planned_min": 60, "rationale": "Continuing current level" },
|
||
{ "date": "2026-05-24", "planned_min": 60, "rationale": "Rest day between" },
|
||
{ "date": "2026-05-26", "planned_min": 75, "rationale": "Step up if 22nd went well" }
|
||
]
|
||
```
|
||
|
||
---
|
||
|
||
## Reminder & Scheduling Logic
|
||
|
||
### Cron jobs
|
||
|
||
| Job | Default time | Condition |
|
||
|---|---|---|
|
||
| Morning reminder | **05:30** | Session scheduled today and status is `pending` |
|
||
| Evening nudge | **16:30** | Session scheduled today and status still `pending` |
|
||
| Snooze | 2 hours after `/snooze` | One-off, clears itself after firing |
|
||
|
||
No reminders fire on days with no scheduled session, or on days already `completed` or `skipped`.
|
||
|
||
### Adaptive schedule generation
|
||
|
||
`/reschedule` sends full history to the AI, which returns a JSON array of upcoming sessions (7–14 days). The bot parses this into `schedule` rows. Skip reasons are included in the next `/reschedule` call so the AI accounts for gaps.
|
||
|
||
---
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
src/
|
||
bot/ # Telegraf setup, command handlers
|
||
db/ # SQLite schema, queries
|
||
scheduler/ # node-cron jobs
|
||
ai/ # OpenRouter client, context builder
|
||
prompts/ # Prompt templates
|
||
config.ts # Env loading and validation
|
||
index.ts # Entry point
|
||
.env.example
|
||
Dockerfile
|
||
docker-compose.yml
|
||
.dockerignore
|
||
package.json
|
||
tsconfig.json
|
||
```
|
||
|
||
---
|
||
|
||
## Deployment
|
||
|
||
### Local development
|
||
|
||
Run directly with `npm run dev` (ts-node or tsx). Uses `.env` file for config.
|
||
|
||
### Production — Docker
|
||
|
||
The app runs as a Docker container on the home server. SQLite data is persisted via a named volume mounted at `/app/data/`.
|
||
|
||
**Dockerfile** — multi-stage build:
|
||
1. `builder` stage: installs all dependencies, compiles TypeScript to `dist/`
|
||
2. `runner` stage: copies only `dist/` and production `node_modules`, runs as a non-root user
|
||
|
||
**docker-compose.yml** — single service definition:
|
||
- Mounts a named volume for the SQLite file (`./data:/app/data`)
|
||
- Loads env vars from `.env` (not baked into the image)
|
||
- `restart: unless-stopped` so it survives reboots
|
||
|
||
**SQLite file location:** `/app/data/assistant.db` inside the container, mapped to `./data/assistant.db` on the host — easy to back up by copying that file.
|
||
|
||
**`.dockerignore`** excludes: `node_modules/`, `src/`, `.env`, `data/`, `*.md`.
|
||
|
||
**Timezone:** Container timezone set via `TZ` env var (e.g. `TZ=Europe/Amsterdam`) so cron jobs fire at the correct local times.
|
||
|
||
**Updating:** `docker compose pull && docker compose up -d --build` rebuilds from source and restarts with zero downtime for a single-container setup.
|
||
|
||
---
|
||
|
||
## Future Extension Points
|
||
|
||
- **Web dashboard (Approach C):** Read-only layer on the same SQLite file — no schema changes needed
|
||
- **Multi-user:** `settings` and `schedule` tables can gain a `user_id` column
|
||
- **Export:** Sessions table trivially exportable to CSV or JSON
|