diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml new file mode 100644 index 0000000..22b82eb --- /dev/null +++ b/.github/workflows/validate.yml @@ -0,0 +1,94 @@ +name: Validate + +on: + push: + branches: [master, main] + pull_request: + branches: [master, main] + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: LICENSE exists + run: test -s LICENSE || (echo "::error::LICENSE missing or empty" && exit 1) + + - name: CHANGELOG.md exists + run: test -s CHANGELOG.md || (echo "::error::CHANGELOG.md missing or empty" && exit 1) + + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-asyncio + + - name: Python compile check + run: | + set -e + fail=0 + for f in $(git ls-files '*.py'); do + if ! python -m py_compile "$f"; then + echo "::error file=$f::python compile error" + fail=1 + fi + done + exit $fail + + - name: Run pytest suite + env: + TELEGRAM_TOKEN: dummy-test-token + ADMIN_IDS: "0" + GEMINI_API_KEY: dummy + OPENROUTER_API_KEY: dummy + GROQ_API_KEY: dummy + run: python -m pytest tests/ -v --tb=short + + - name: SVG files are well-formed XML + run: | + set -e + fail=0 + for f in $(git ls-files 'docs/*.svg' '*.svg'); do + if ! python -c "import xml.etree.ElementTree as ET; ET.parse('$f')" 2>/dev/null; then + echo "::error file=$f::malformed SVG XML" + fail=1 + fi + done + exit $fail + + - name: All docs/* assets referenced from README exist + run: | + set -e + fail=0 + for ref in $(grep -hoE 'docs/[a-zA-Z0-9_/-]+\.(svg|png|jpg|jpeg|gif)' README.md README.ru.md | sort -u); do + if [ ! -f "$ref" ]; then + echo "::error file=README.md::missing referenced asset $ref" + fail=1 + fi + done + exit $fail + + - name: Internal Markdown links resolve + run: | + set -e + fail=0 + for src in README.md README.ru.md CHANGELOG.md CONTRIBUTING.md CLAUDE.md; do + [ -f "$src" ] || continue + base="$(dirname "$src")" + for tgt in $(grep -hoE '\]\([^)]+\)' "$src" | sed 's/](\(.*\))/\1/' | sed 's/#.*$//'); do + case "$tgt" in + http*|mailto:*|"") continue ;; + esac + [ "$base" = "." ] && resolved="$tgt" || resolved="$base/$tgt" + if [ ! -e "$resolved" ] && [ ! -e "$tgt" ]; then + echo "::error file=$src::broken internal link → $tgt" + fail=1 + fi + done + done + exit $fail diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8ee8a20 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,65 @@ +# Changelog + +All notable changes to this project will be documented in this file. +Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) · [SemVer](https://semver.org/spec/v2.0.0.html). + +## [0.3.0] — 2026-05-03 + +### Changed + +- **License changed from MIT → PolyForm Noncommercial 1.0.0.** This project is non-commercial: it was built for friends and family by a sibling-of-T1D developer and is shared with the community for personal / family use only. Commercial use — including hosting it as a paid service or bundling it into a paid product — is not permitted. + +### Added + +- `LICENSE` is now the official PolyForm Noncommercial 1.0.0 text (replacing the previous MIT license) +- `README.ru.md` — full Russian version mirroring the new English structure (the previous bilingual inline format has been split into two locale files for consistency with sister repos and easier translation maintenance) +- Hero `Status — non-commercial use only` callout linking to the license and the contributor priorities +- `Roadmap & known limitations` section grouping safety disclaimer, active improvements (LLM prompt-tightening, dietetic accuracy, user-facing answer verification, external KBJU databases, regional adaptation), and current limits (LLM misidentification edge cases, no CGM integration, EN-locale verification ongoing, grounding only on Gemini chain, metric units only) +- Hero screenshot `docs/screenshots/01-kbju-result.png` — final KBJU table + daily progress +- Four supporting screenshots — `02-photo-recognition`, `03-daily-report`, `04-onboarding-setup`, `05-onboard-confirm` — arranged in a 2-column gallery with captions +- `docs/architecture.svg` — pipeline diagram (Telegram → handlers → litellm Router with two chains → user confirmation gate → nutrition + database → daily progress, with Google Search grounding sidecar) +- `CHANGELOG.md` (this file) — earlier history reconstructed below from `git log` / commit messages +- `CONTRIBUTING.md` with explicit non-commercial clause and a concrete priority list +- `.github/workflows/validate.yml` — runs the 72-test pytest suite on push and PR, plus `python -m py_compile` on every `.py`, SVG well-formed XML, internal Markdown links resolve, presence of `LICENSE` and `CHANGELOG.md` +- "Stars" and "Validate CI" badges; static `Tests: 72` badge replaced with the dynamic Validate badge +- "Related" section cross-linking to all sister Claude Code repos by the same author (anti-regression-setup, ai-context-hierarchy, claude-statusline, lingua-companion) +- Author signature expanded with full name and Habr / dev.to profile links +- Local `tests/manual screenshots/` paths and any deployment URLs are deliberately omitted from the public README per author instruction + +### Notes + +- Topics on GitHub applied separately via `gh api` after merge. +- Default branch remains `master` for now; rename to `main` deferred to a separate change because it would invalidate any external bookmarks / CI badges pointing at `master`. + +## [0.2.0] — 2026-04-03 + +### Added — initial portfolio-quality README & live fixes +- Bilingual inline RU/EN `README.md` rewritten as a portfolio piece (commit `f0745f0`) +- `CLAUDE.md` aligned with the README (same commit) +- `PicklePersistence` for `python-telegram-bot` so the bot survives systemd restarts without losing in-flight conversations (commit `eceebf4`) +- All command handlers added as `entry_points` so the bot works even if a user types `/today` before `/start` (commit `41acab9`) +- Service injection in `post_init` after persistence restore — fixes a race where the database service was unavailable to the conversation handler on cold start (commit `ecb735d`) +- Correction-prompt always returns `is_food=true` and falls back to the original items when the LLM produces an empty correction — prevents silent food deletion on edit (commit `59c1a30`) +- `allow_reentry=False` on `entry_points` to prevent the user from accidentally re-entering onboarding while in the middle of a correction (commit `2619043`) +- LLM response handler tolerates unexpected fields like `volume_ml` that some providers add (commit `fb9811b`) +- Access check moved to before onboarding; database reference fix in settings (commit `0f638e1`) + +### Architecture (as of this version) + +- Two LLM chains via `litellm` Router with auto-failover: vision (Gemini 2.5 Flash → OpenRouter Gemini) for photos, text (Gemini → OpenRouter → Groq Llama 4) for descriptions +- Google Search grounding via `google-genai` for low-confidence branded products +- 72 pytest cases covering every handler and service +- `diabot.service` systemd unit for production +- Privacy-first: photos never written to disk (only Telegram `file_id`), `/export`, `/delete_my_data`, GDPR-style consent on first launch +- Multi-user with admin approval workflow (admins from `.env`, additional users via `/adduser` / approval queue) + +## [0.1.0] — earlier + +### Added +- Initial implementation: photo → recognition → confirm → KBJU + XE → diary +- Onboarding state machine: consent → gender → height → weight → age → targets +- Daily KBJU targets via the Mifflin-St Jeor formula with manual override +- Reply keyboard for navigation, inline keyboard for confirmation +- SQLite via `aiosqlite` for users / meals / glucose / targets +- Bilingual i18n (RU default, EN fully supported) with prompts living next to the locale strings in `locales/` +- 1 XE = 12 g carbs default, configurable per user diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..69bb482 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,45 @@ +# Contributing + +Thanks for considering a contribution. **Important: this project is licensed under [PolyForm Noncommercial 1.0.0](LICENSE).** Contributions are welcome under the same terms — code you submit becomes part of a non-commercial project, and you agree that you (and downstream users) are not allowed to use it commercially. If you need a commercial licence, contact the author first. + +DiaBot is in active development. The core flow works end to end — but several layers need genuine improvement. **Contributors with diabetes / dietetics / endocrinology backgrounds are especially welcome** because the gaps below are domain-knowledge gaps, not just engineering gaps. + +## Priorities (highest impact first) + +1. **Tighter LLM prompting & schema validation.** The vision chain currently estimates portions in a relatively loose way. PRs welcome to: lower temperature, add explicit JSON-schema constraints, validate units more strictly, return confidence per item, retry on schema violations with a tighter follow-up prompt. Files to look at: `services/llm.py`, `locales/ru.py` and `locales/en.py` (prompts live in the locale files because they are language-tied). +2. **More precise dietetic calculations.** Verified portion-size heuristics, glycaemic load (not just GI label), fibre subtraction in digestible carbs, lactose handling, protein-impact-on-glucose for high-protein meals. Files: `services/nutrition.py`, `models/schemas.py`. +3. **User-facing answer verification.** Add an explicit confidence score per recognised item, render it in the confirmation card, surface a "double-check this" flag at low confidence, prompt the user to weigh portions when the LLM is uncertain. Files: `services/llm.py` (return structured confidence), `handlers/confirm.py` (render). +4. **External KBJU database integration.** Today the bot relies entirely on the LLM for KBJU values. Adding [USDA FoodData Central](https://fdc.nal.usda.gov/), [OpenFoodFacts](https://world.openfoodfacts.org/), or regional Russian / Eastern-European sources as a verification layer would make values much more reliable. Sketch: after recognition, look up each item in the external DB, fall back to LLM only on miss. +5. **Regional adaptation.** Food norms and cuisines differ between EU / US / RU / Asia. Branded products differ. Locally common dishes are recognised unevenly. Roadmap: locale-specific prompt addenda, locale-specific external DB priority, optional locale flag in the user profile. +6. **Native localisations beyond RU / EN.** New locale files in `locales/.py` mirroring the structure of `ru.py` / `en.py`. Translation must include both UI strings and LLM prompts (they are tied to language). + +## What we will not merge + +- Anything that bypasses the two-step confirmation flow. The `recognise → confirm` step is safety-critical; do not "optimise" it away even if it adds a tap. +- Changes that expose more LLM raw output to the user without confirmation. The bot's response always passes through human review. +- Anything that stores food photos to disk. Only Telegram `file_id` is stored; bytes are streamed and discarded. +- Hard dependencies on a single LLM provider. The whole point of the `litellm` Router is auto-failover; do not collapse the chain. +- Commercial-use forks or features that gate functionality behind a paywall. The licence forbids commercial use. + +## Pull request checklist + +- [ ] `python -m pytest tests/ -v` — all 72 tests pass (and any new tests you added pass) +- [ ] `python -m py_compile $(git ls-files '*.py')` clean +- [ ] If you touched a handler: thin I/O only — business logic moved into `services/` +- [ ] If you touched LLM prompts: both `locales/ru.py` and `locales/en.py` updated +- [ ] If user-visible behaviour changed: both `README.md` and `README.ru.md` mirrored +- [ ] `CHANGELOG.md` entry added under a new minor / patch version +- [ ] No new file written to disk for user data without an explicit `/export` / `/delete_my_data` path +- [ ] Adheres to the existing code style (English code / comments / docstrings, type hints everywhere, HTML parse_mode for Telegram, no hardcoded user-facing strings) + +## Style + +- All code, comments, docstrings in **English**. Bot-facing strings only in `locales/`. +- Logging via `logging` (INFO for actions, DEBUG for LLM requests). No `print()`. +- Telegram messages use HTML parse_mode (not Markdown). Existing helpers handle escaping. +- LLM responses use JSON mode (`response_format: json_object`) with retry on invalid JSON. +- One feature per PR. Stack PRs if you have multiple. + +## Author / maintainer + +[@CreatmanCEO](https://github.com/CreatmanCEO) — Nick Podolyak. Open an issue first for anything larger than a single fix or a single locale. diff --git a/LICENSE b/LICENSE index b69fdc6..1a71cb6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,131 @@ -MIT License - -Copyright (c) 2026 Creatman - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +# PolyForm Noncommercial License 1.0.0 + + + +## Acceptance + +In order to get any license under these terms, you must agree +to them as both strict obligations and conditions to all +your licenses. + +## Copyright License + +The licensor grants you a copyright license for the +software to do everything you might do with the software +that would otherwise infringe the licensor's copyright +in it for any permitted purpose. However, you may +only distribute the software according to [Distribution +License](#distribution-license) and make changes or new works +based on the software according to [Changes and New Works +License](#changes-and-new-works-license). + +## Distribution License + +The licensor grants you an additional copyright license +to distribute copies of the software. Your license +to distribute covers distributing the software with +changes and new works permitted by [Changes and New Works +License](#changes-and-new-works-license). + +## Notices + +You must ensure that anyone who gets a copy of any part of +the software from you also gets a copy of these terms or the +URL for them above, as well as copies of any plain-text lines +beginning with `Required Notice:` that the licensor provided +with the software. For example: + +> Required Notice: Copyright Yoyodyne, Inc. (http://example.com) + +## Changes and New Works License + +The licensor grants you an additional copyright license to +make changes and new works based on the software for any +permitted purpose. + +## Patent License + +The licensor grants you a patent license for the software that +covers patent claims the licensor can license, or becomes able +to license, that you would infringe by using the software. + +## Noncommercial Purposes + +Any noncommercial purpose is a permitted purpose. + +## Personal Uses + +Personal use for research, experiment, and testing for +the benefit of public knowledge, personal study, private +entertainment, hobby projects, amateur pursuits, or religious +observance, without any anticipated commercial application, +is use for a permitted purpose. + +## Noncommercial Organizations + +Use by any charitable organization, educational institution, +public research organization, public safety or health +organization, environmental protection organization, +or government institution is use for a permitted purpose +regardless of the source of funding or obligations resulting +from the funding. + +## Fair Use + +You may have "fair use" rights for the software under the +law. These terms do not limit them. + +## No Other Rights + +These terms do not allow you to sublicense or transfer any of +your licenses to anyone else, or prevent the licensor from +granting licenses to anyone else. These terms do not imply +any other licenses. + +## Patent Defense + +If you make any written claim that the software infringes or +contributes to infringement of any patent, your patent license +for the software granted under these terms ends immediately. If +your company makes such a claim, your patent license ends +immediately for work on behalf of your company. + +## Violations + +The first time you are notified in writing that you have +violated any of these terms, or done anything with the software +not covered by your licenses, your licenses can nonetheless +continue if you come into full compliance with these terms, +and take practical steps to correct past violations, within +32 days of receiving notice. Otherwise, all your licenses +end immediately. + +## No Liability + +***As far as the law allows, the software comes as is, without +any warranty or condition, and the licensor will not be liable +to you for any damages arising out of these terms or the use +or nature of the software, under any kind of legal claim.*** + +## Definitions + +The **licensor** is the individual or entity offering these +terms, and the **software** is the software the licensor makes +available under these terms. + +**You** refers to the individual or entity agreeing to these +terms. + +**Your company** is any legal entity, sole proprietorship, +or other kind of organization that you work for, plus all +organizations that have control over, are under the control of, +or are under common control with that organization. **Control** +means ownership of substantially all the assets of an entity, +or the power to direct its management and policies by vote, +contract, or otherwise. Control can be direct or indirect. + +**Your licenses** are all the licenses granted to you for the +software under these terms. + +**Use** means anything you do with the software requiring one +of your licenses. diff --git a/README.md b/README.md index 252d772..e68f13f 100644 --- a/README.md +++ b/README.md @@ -1,232 +1,171 @@ -# 🍽 DiaBot — AI Nutrition Assistant for Type 1 Diabetes +# 🍽 DiaBot — non-commercial AI nutrition assistant for type 1 diabetes +[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-9d6cff)](LICENSE) +[![Stars](https://img.shields.io/github/stars/CreatmanCEO/diabot?style=flat&color=yellow)](https://github.com/CreatmanCEO/diabot/stargazers) +[![Validate](https://github.com/CreatmanCEO/diabot/actions/workflows/validate.yml/badge.svg)](https://github.com/CreatmanCEO/diabot/actions/workflows/validate.yml) +[![Status](https://img.shields.io/badge/status-active%20development-22c55e)](#status--non-commercial-use-only) [![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) -[![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Telegram Bot API](https://img.shields.io/badge/Telegram-Bot%20API-26A5E4.svg)](https://core.telegram.org/bots/api) -[![Tests: 72](https://img.shields.io/badge/tests-72-brightgreen.svg)]() -[Русский](#русский) | [English](#english) +🇬🇧 English · [🇷🇺 Русский](README.ru.md) ---- - -## Русский - -### Проблема - -Каждый приём пищи для человека с диабетом 1 типа — это математическая задача. Нужно оценить количество углеводов в тарелке, перевести их в хлебные единицы и рассчитать дозу инсулина. Ошибка в 1-2 ХЕ может привести к гипогликемии или гипергликемии — а это госпитализация, потеря сознания, кома. Люди делают это 4-6 раз в день, каждый день, всю жизнь. - -### Решение - -DiaBot превращает подсчёт углеводов в одно действие: **сфотографируй еду — получи результат**. Бот распознаёт продукты через AI, считает КБЖУ и хлебные единицы, ведёт дневник и показывает прогресс к дневным целям. Углеводы и ХЕ всегда на первом месте — потому что именно от них зависит доза инсулина. - -### 📸 Как это работает - -1. **Отправь фото** еды в чат (или опиши текстом) -2. **Бот распознаёт** продукты и порции через Gemini AI -3. **Подтверди или поправь** — бот показывает что распознал, ты корректируешь при необходимости -4. **Получи КБЖУ + ХЕ** — точный расчёт с акцентом на углеводы -5. **Следи за прогрессом** — дневник, статистика, прогресс-бары к дневным целям - -### 🎯 Ключевые возможности - -**Распознавание еды** -- 📷 Фото → AI распознавание продуктов и порций -- ✏️ Текстовое описание (без фото) -- 📷+✏️ Фото с подписью для уточнения -- 🏷 Распознавание брендовых продуктов (с Google Search для редких марок) - -**Точный подсчёт** -- 🔢 Калории, белки, жиры, углеводы (КБЖУ) -- 🍞 Хлебные единицы (ХЕ) — с настраиваемым коэффициентом -- 🎯 Углеводы всегда выделены первыми (приоритет дозирования инсулина) -- ✅ Двухшаговый процесс: распознал → подтверди → сохрани - -**Дневные цели и прогресс** -- 👤 Профиль: пол, рост, вес, возраст -- 📊 Автоматический расчёт норм по формуле Mifflin-St Jeor -- ✍️ Ручная настройка целей (рекомендации диетолога) -- 📈 Компактный прогресс после каждого приёма пищи -- ▓▓▓░░░ Визуальные прогресс-бары в дневнике - -**Дневник питания** -- 📅 Дневник за сегодня с разбивкой по приёмам (`/today`) -- 📊 Недельная статистика (`/week`) -- 📜 История записей (`/history N`) -- ↩️ Отмена последней записи (`/undo`) -- 🩸 Учёт показаний глюкозы (`/sugar`) - -**Конфиденциальность** -- 🔒 Согласие при первом запуске с описанием обработки данных -- 🚫 Фото НИКОГДА не сохраняются на диск (только Telegram file_id) -- 📦 Полный экспорт данных (`/export` — JSON) -- 🗑 Полное удаление данных (`/delete_my_data`) -- ✅ GDPR-совместимость - -**Мультипользовательский режим** -- 👥 Self-hosted с системой одобрения доступа -- 📩 Пользователь запрашивает → админ получает уведомление с кнопками → пользователь получает ответ -- ⚡ Rate limiting для каждого пользователя - -### Технологии +**Open-source non-commercial Telegram bot for people with type 1 diabetes. Built for friends and family by a sibling-of-T1D developer, shared with the community. Active development — contributors welcome under non-commercial terms.** -| Компонент | Технология | -|-----------|-----------| -| Язык | Python 3.11+, полностью асинхронный | -| Telegram | python-telegram-bot 21+ | -| AI/LLM | litellm Router — Gemini 2.5 Flash, OpenRouter, Groq (Llama 4) | -| Поиск | Google Search grounding (google-genai) | -| База данных | SQLite через aiosqlite, без ORM | -| Деплой | systemd | -| Тесты | 72 автотеста (pytest) | +--- -### Быстрый старт +## Why this exists -```bash -git clone https://github.com/CreatmanCEO/diabot.git -cd diabot -python -m venv venv -source venv/bin/activate # Linux/Mac -pip install -r requirements.txt -cp .env.example .env -# Заполни .env своими API-ключами -python main.py -``` - -**Переменные окружения:** +Every meal for a person with type 1 diabetes is a math problem. You estimate the carbohydrates on your plate, convert them to bread units (XE), and calculate the insulin dose. An error of 1–2 XE can cause hypoglycaemia or hyperglycaemia — hospitalisation, loss of consciousness, coma. People do this 4–6 times a day, every day, for the rest of their lives. -| Переменная | Описание | Обязательно | -|-----------|----------|:-----------:| -| `TELEGRAM_TOKEN` | Токен бота от @BotFather | да | -| `ADMIN_IDS` | Telegram ID админов через запятую | да | -| `GEMINI_API_KEY` | API ключ Google AI Studio | * | -| `OPENROUTER_API_KEY` | API ключ OpenRouter | * | -| `GROQ_API_KEY` | API ключ Groq | * | -| `DEFAULT_TIMEZONE` | Часовой пояс (Europe/Moscow) | нет | -| `DEFAULT_HE_GRAMS` | Граммов углеводов в 1 ХЕ (12) | нет | -| `DEFAULT_LANGUAGE` | Язык по умолчанию (ru) | нет | -| `RATE_LIMIT_REQUESTS` | Лимит запросов в час (30) | нет | -| `DB_PATH` | Путь к SQLite базе | нет | +DiaBot turns carb counting into a single action: **send a photo — get the result.** AI recognises the food, the user confirms or corrects, the bot calculates KBJU and bread units, writes a diary entry, and shows progress against daily targets. Carbs and XE are always rendered first — because that is what insulin dose depends on. -\* Нужен хотя бы один LLM API ключ. Для распознавания фото нужен GEMINI или OPENROUTER. +## Status — non-commercial use only -### Деплой (systemd) +This project is in **active development** and shared under the [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0) licence. You may run it for yourself, your family, or a friends-and-family circle. **Commercial use — including hosting it as a paid service or bundling it into a paid product — is not permitted.** Contributors are welcome under the same terms. -```bash -sudo useradd -r -s /bin/false diabot -sudo mkdir -p /opt/diabot -sudo git clone https://github.com/CreatmanCEO/diabot.git /opt/diabot -cd /opt/diabot -sudo python3 -m venv venv -sudo venv/bin/pip install -r requirements.txt -sudo cp .env.example .env -sudo nano .env # заполнить токены -sudo cp diabot.service /etc/systemd/system/ -sudo systemctl daemon-reload -sudo systemctl enable diabot -sudo systemctl start diabot -``` +The bot needs help with: tighter LLM prompting, more precise dietetic calculations, user-facing answer verification, integration with external KBJU databases (USDA, OpenFoodFacts, regional sources), and regional adaptation. See [Roadmap & known limitations](#roadmap--known-limitations) and [`CONTRIBUTING.md`](CONTRIBUTING.md) for the priority list. ---- +## What it looks like -## English +> UI shown in Russian (default locale). English locale also fully supported — language follows the user's Telegram preference. -### The Problem + + + + + + + + + + + + + + + + + + + + + + + +
Result after corrections — bot returns updated item list (multigrain bread 60 g, cheese 30 g, brisket 70 g), monospace KBJU table per item with Calories / Protein / Fat / Carbs columns, totals 419 kcal · carbs 24 g (fibre 3 g) · digestible carbs 21 g · 1.8 XE · low GI, plus daily progress 886 kcal / 7.6 XE / 96 of 351 g carbs (27%)
Final result — KBJU table + daily progress
The whole loop in one screen: per-item nutrition, totals, carbs / digestible carbs / XE / GI separately highlighted, and daily progress bar against the user's targets.
Photo recognition step — user sent a photo of brisket on a wooden board with cheese and bread next to it, bot replied 'I can see: 1. Rye bread ~60 g, 2. Cheese ~30 g, 3. Meat (probably smoked turkey or beef brisket) ~70 g (thinly sliced)' followed by Confirm and Cancel inline buttonsDaily report — date header, list of three meals with timestamps and per-meal calories / carbs / XE, totals row, daily progress bars for calories / carbs / fat / protein with percentages
Photo recognition
Send a photo. The vision LLM names items and estimates portions in grams. Two-step confirmation is the safety mechanism — the user has the final word before insulin is dosed against these numbers.
Daily diary
/today shows every meal with timestamps and per-meal totals, plus daily progress bars. /week aggregates seven days. /history N walks back further.
Onboarding setup — bot asks for gender, height, weight, age, automatically computes daily KBJU targets via the Mifflin-St Jeor formula, user can override manually if a dietician has prescribed different valuesOnboarding confirm step — bot summarises the entered profile and target values for review and waits for the user to confirm before going to IDLE state
Onboarding
Five-step setup: consent → gender → height → weight → age → targets. KBJU targets auto-computed via Mifflin-St Jeor; manual override is one tap away if a dietician has prescribed different values.
Profile confirmation
Final review before the IDLE state. All inputs are stored locally in SQLite and visible to the user via /export; deletable via /delete_my_data.
-Every meal for a person with type 1 diabetes is a math problem. You need to estimate the carbohydrates on your plate, convert them to bread units, and calculate the insulin dose. An error of 1-2 XE can lead to hypoglycemia or hyperglycemia — hospitalization, loss of consciousness, coma. People do this 4-6 times a day, every day, for the rest of their lives. +## Architecture -### The Solution +![Recognition pipeline: Telegram → handlers → litellm Router with two chains (vision + text) → user confirmation → nutrition + database → daily progress; Google Search grounding for low-confidence branded products](docs/architecture.svg) -DiaBot turns carb counting into a single action: **snap a photo — get the result**. The bot recognizes food via AI, calculates nutrition and bread units, keeps a food diary, and shows progress toward daily targets. Carbs and XE are always highlighted first — because insulin dosing depends on them. +| Layer | Technology | Notes | +|---|---|---| +| Language | Python 3.11+, fully async | one event loop, no thread pools | +| Telegram | `python-telegram-bot` 21+ | polling mode, `ConversationHandler` for state | +| LLM router | `litellm` Router with two chains | vision (Gemini 2.5 Flash → OpenRouter Gemini) + text (Gemini → OpenRouter → Groq Llama 4) | +| Search grounding | `google-genai` | Google Search tool — fallback for low-confidence branded products | +| Database | SQLite via `aiosqlite` | no ORM, plain SQL, schema fits in one file | +| Config | `python-dotenv` | frozen `Settings` dataclass validated at start | +| Deployment | systemd unit included | runs as a non-privileged user under `/opt/diabot/` | +| Tests | 72 pytest cases | covers every handler and service | -### 📸 How It Works +## How it works 1. **Send a photo** of your meal (or describe it in text) -2. **Bot recognizes** products and portions via Gemini AI -3. **Confirm or correct** — the bot shows what it found, you adjust if needed -4. **Get nutrition + XE** — precise calculation with carbs prioritized -5. **Track your progress** — diary, statistics, progress bars toward daily goals +2. **Bot recognises** items and portions via the vision LLM +3. **Confirm or correct** — the bot prints what it saw, you adjust if anything is off +4. **Get KBJU + XE** — calculation with carbs prioritised +5. **Track progress** — diary, weekly stats, progress bars towards daily targets -### 🎯 Key Features +## Key features -**Food Recognition** +**Food recognition** - 📷 Photo → AI recognition of products and portions - ✏️ Text-based food description (no photo needed) - 📷+✏️ Photo with caption for extra context -- 🏷 Branded product recognition (with Google Search fallback for rare brands) +- 🏷 Branded-product recognition with Google Search grounding for rare brands -**Precise Calculation** -- 🔢 Calories, protein, fat, carbs (KBJU) -- 🍞 Bread units (XE/HE) — with configurable ratio -- 🎯 Carbs always highlighted first (insulin dosing priority) -- ✅ Two-step flow: recognize → confirm → save +**Precise calculation** +- 🔢 Calories, protein, fat, carbohydrates (KBJU) +- 🍞 Bread units (XE / HE) — configurable ratio (default 1 XE = 12 g carbs) +- 🎯 Carbs always rendered first — insulin dosing priority +- ✅ Two-step flow: recognise → confirm → save -**Daily Targets & Progress** +**Daily targets and progress** - 👤 Profile: gender, height, weight, age -- 📊 Automatic target calculation via Mifflin-St Jeor formula -- ✍️ Manual target override (for dietician recommendations) +- 📊 Automatic target calculation via Mifflin-St Jeor +- ✍️ Manual target override (for dietician-prescribed values) - 📈 Compact progress after every meal -- ▓▓▓░░░ Visual progress bars in diary +- ▓▓▓░░░ Visual progress bars in the diary -**Food Diary** +**Food diary** - 📅 Today's diary with per-meal breakdown (`/today`) - 📊 Weekly statistics (`/week`) - 📜 Meal history (`/history N`) - ↩️ Undo last entry (`/undo`) - 🩸 Glucose readings tracking (`/sugar`) -**Privacy & Data** -- 🔒 Consent on first launch with data processing details -- 🚫 Photos are NEVER saved to disk (only Telegram file_id) +**Privacy and data** +- 🔒 Consent on first launch with data-processing details +- 🚫 Photos are NEVER saved to disk — only Telegram `file_id` is stored - 📦 Full data export (`/export` as JSON) - 🗑 Complete data deletion (`/delete_my_data`) - ✅ GDPR-style compliance -**Multi-User** +**Multi-user** - 👥 Self-hosted with admin approval workflow -- 📩 User requests access → admin gets notification with approve/reject buttons → user notified +- 📩 User requests access → admin gets notification → user is notified back - ⚡ Per-user rate limiting -### Tech Stack +## Roadmap & known limitations + +> **Safety disclaimer.** This is an assistance tool, **not a medical device**. Always cross-check critical carb counts before insulin dosing. The two-step recognise → confirm flow is safety-critical, not cosmetic — do not skip it. Not a substitute for an endocrinologist or a dietician. + +### Active improvements (contributors welcome) -| Component | Technology | -|-----------|-----------| -| Language | Python 3.11+, fully async | -| Telegram | python-telegram-bot 21+ | -| AI/LLM | litellm Router — Gemini 2.5 Flash, OpenRouter, Groq (Llama 4) | -| Search | Google Search grounding (google-genai) | -| Database | SQLite via aiosqlite, no ORM | -| Deployment | systemd | -| Tests | 72 automated tests (pytest) | +- **Tighter LLM prompting** — stricter JSON-schema validation, lower temperature, deterministic portion estimation +- **More precise dietetic calculations** — verified portion-size heuristics, glycaemic load, fibre subtraction in digestible carbs +- **User-facing answer verification** — confidence scores per item, explicit "double-check this" flag at low confidence +- **External KBJU databases** — [USDA FoodData Central](https://fdc.nal.usda.gov/), [OpenFoodFacts](https://world.openfoodfacts.org/), regional Russian / Eastern-European sources +- **Regional adaptation** — EU / US / RU / Asia food norms and cuisines, branded products, locally common dishes -### Quick Start +### Current limits + +- LLM may misidentify food (poor lighting, small portions, regional dishes, unusual angles). The two-step confirmation exists exactly because of this. +- No CGM integration yet +- Bilingual prompts mostly tested in RU; EN locale verification is ongoing +- Google Search grounding is available only for the Gemini chain — OpenRouter / Groq fallbacks do not support it yet +- Onboarding asks for body metrics in metric units only — imperial conversion is on the backlog + +## Quick start ```bash git clone https://github.com/CreatmanCEO/diabot.git cd diabot python -m venv venv -source venv/bin/activate # Linux/Mac +source venv/bin/activate # Linux / macOS — on Windows: venv\Scripts\activate pip install -r requirements.txt cp .env.example .env # Fill in your API keys in .env python main.py ``` -**Environment Variables:** +### Environment variables | Variable | Description | Required | -|----------|-------------|:--------:| +|---|---|:---:| | `TELEGRAM_TOKEN` | Bot token from @BotFather | yes | | `ADMIN_IDS` | Comma-separated admin Telegram IDs | yes | | `GEMINI_API_KEY` | Google AI Studio API key | * | | `OPENROUTER_API_KEY` | OpenRouter API key | * | | `GROQ_API_KEY` | Groq API key | * | -| `DEFAULT_TIMEZONE` | Default timezone (Europe/Moscow) | no | -| `DEFAULT_HE_GRAMS` | Grams of carbs per 1 XE (12) | no | -| `DEFAULT_LANGUAGE` | Default language (ru) | no | -| `RATE_LIMIT_REQUESTS` | Requests per hour limit (30) | no | +| `DEFAULT_TIMEZONE` | Default timezone (`Europe/Moscow`) | no | +| `DEFAULT_HE_GRAMS` | Grams of carbs per 1 XE (`12`) | no | +| `DEFAULT_LANGUAGE` | Default language (`ru`) | no | +| `RATE_LIMIT_REQUESTS` | Per-user requests per hour (`30`) | no | | `DB_PATH` | Path to SQLite database | no | -\* At least one LLM API key required. Photo recognition needs GEMINI or OPENROUTER. +\* At least one LLM API key required. Photo recognition needs `GEMINI_API_KEY` or `OPENROUTER_API_KEY`. ### Deploy (systemd) @@ -238,84 +177,100 @@ cd /opt/diabot sudo python3 -m venv venv sudo venv/bin/pip install -r requirements.txt sudo cp .env.example .env -sudo nano .env # fill in tokens +sudo nano .env # fill in tokens sudo cp diabot.service /etc/systemd/system/ sudo systemctl daemon-reload sudo systemctl enable diabot sudo systemctl start diabot ``` ---- +## Project structure -## Architecture +``` +diabot/ +├── main.py # Entry point, ConversationHandler wiring +├── config.py # Settings dataclass from .env (frozen, validated) +├── handlers/ # Telegram handlers (thin I/O, no business logic) +│ ├── start.py # /start, 5-step onboarding, /help +│ ├── photo.py # Food photo recognition pipeline +│ ├── text.py # Text food input + reply keyboard routing +│ ├── confirm.py # Confirmation, correction, cancellation +│ ├── diary.py # /today, /week, /history, /undo +│ ├── glucose.py # /sugar — glucose readings +│ ├── settings.py # Profile / target editing, admin panel +│ ├── admin.py # /adduser, /removeuser, /listusers, approval workflow +│ ├── privacy.py # /privacy, /export, /delete_my_data, consent +│ └── keyboards.py # Keyboard factory (reply + inline) +├── services/ # Business logic (no Telegram imports) +│ ├── llm.py # litellm Router, two chains, auto-failover +│ ├── nutrition.py # KBJU formatting, progress bars, XE calculation +│ ├── database.py # aiosqlite CRUD +│ └── auth.py # Access control, rate limiting, approval queue +├── models/schemas.py # Dataclasses: MealItem, User, NutritionTarget, … +├── locales/ # i18n strings + LLM prompts (tied to language) +│ ├── ru.py # Russian (default) +│ └── en.py # English +├── docs/ +│ ├── architecture.svg # this README's pipeline diagram +│ ├── plans/ # design docs and implementation plans +│ └── screenshots/ # README assets +├── tests/ # 72 automated tests (pytest) +├── diabot.service # systemd unit +├── CHANGELOG.md +├── CONTRIBUTING.md +├── CLAUDE.md # Claude Code project constitution +└── LICENSE # PolyForm Noncommercial 1.0.0 +``` + +## State machine ``` -main.py Entry point, ConversationHandler wiring -config.py Settings dataclass from .env (frozen, validated) - -handlers/ Telegram handlers (thin I/O layer) - start.py /start, 5-step onboarding, /help - photo.py Food photo recognition pipeline - text.py Text food input + reply keyboard routing - confirm.py Confirmation, correction, cancellation - diary.py /today, /week, /history, /undo - glucose.py /sugar — glucose readings - settings.py Profile/target editing, admin panel - admin.py /adduser, /removeuser, /listusers, approval workflow - privacy.py /privacy, /export, /delete_my_data, consent - keyboards.py Keyboard factory (reply + inline) - -services/ Business logic (no Telegram dependencies) - llm.py litellm Router: 2 chains (vision + text), failover - nutrition.py KBJU formatting, progress bars, XE calculation - database.py aiosqlite CRUD (users, meals, glucose, targets) - auth.py Access control, rate limiting, approval queue - -models/ - schemas.py Dataclasses: MealItem, User, NutritionTarget, etc. - -locales/ i18n strings + LLM prompts (tied to language) - ru.py Russian (default) - en.py English +ONBOARDING: consent → gender → height → weight → age → targets → IDLE +IDLE ↔ AWAITING_CONFIRM (food recognition flow) +IDLE ↔ AWAITING_GLUCOSE (glucose recording) ``` -### Design Decisions +## Design decisions -- **Handlers are thin** — all business logic lives in `services/`. Handlers only do I/O (Telegram API calls, user input parsing). -- **Two LLM chains** — Vision chain (Gemini/OpenRouter) for photos. Text chain adds Groq/Llama as a cheaper fallback. litellm Router handles automatic failover between providers. -- **Google Search grounding** — When the primary model returns low-confidence results for branded products, a separate `google-genai` call with Search grounding retrieves accurate nutrition data. -- **State machine** — `ConversationHandler` manages onboarding flow and food recognition flow. Services are injected via `context.bot_data`. -- **Carb accuracy over calorie accuracy** — Architectural decision reflected in prompts, formatting, and UI ordering. Insulin dosing depends on carb precision. -- **No ORM** — Plain SQL with aiosqlite. The schema is simple enough that an ORM would add complexity without benefit. -- **Photos never touch disk** — Only Telegram `file_id` is stored. The image is fetched on-demand from Telegram servers and passed directly to the LLM API as bytes. +- **Handlers are thin** — every business decision lives in `services/`. Handlers only do Telegram I/O. +- **Two LLM chains** — vision (Gemini → OpenRouter) for photos; text (Gemini → OpenRouter → Groq Llama 4) for descriptions. `litellm` Router handles automatic failover. +- **Google Search grounding** — when the primary model returns low confidence on a branded product, a separate `google-genai` call with the Search tool retrieves accurate KBJU. +- **Carb accuracy over calorie accuracy** — reflected in prompts, formatting, and UI ordering. Insulin dosing depends on carb precision; calories are secondary. +- **No ORM** — plain SQL with `aiosqlite`. The schema fits in one file; an ORM would add complexity without benefit. +- **Photos never touch disk** — only Telegram `file_id` is stored. Image bytes are fetched on demand and passed directly to the LLM API as bytes. +- **Two-step confirmation is a safety mechanism** — recognition → confirm. Without explicit confirmation no meal is saved and no insulin guidance is implied. -### State Machine +## Working on it +The project is configured for [Claude Code](https://code.claude.com) as the primary development driver — see [`CLAUDE.md`](CLAUDE.md) for the project constitution. The same author maintains a [Claude Code Anti-Regression Setup](https://github.com/CreatmanCEO/claude-code-antiregression-setup) — that pattern is what keeps the 72-test suite green during refactors. + +Run tests: + +```bash +python -m pytest tests/ -v ``` -ONBOARDING: consent → gender → height → weight → age → targets → IDLE -IDLE ↔ AWAITING_CONFIRM (food recognition flow) -IDLE ↔ AWAITING_GLUCOSE (glucose recording) -``` + +Per-iteration design docs live in `docs/plans/`. New work starts with a design doc, then code follows. ## Contributing -1. Fork the repository -2. Create a feature branch (`git checkout -b feature/your-feature`) -3. Write tests for new functionality -4. Ensure all tests pass: `python -m pytest tests/ -v` -5. Submit a pull request +PRs are welcome under the same non-commercial terms as the project. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for priorities (LLM prompt-tightening, dietetic accuracy, answer verification, external KBJU databases, regional adaptation, native localisations beyond RU/EN). -Code style: -- All code, comments, and docstrings in English -- Type hints on all function signatures -- Docstrings for public functions -- HTML parse_mode for Telegram messages (not Markdown) -- Bot-facing strings in `locales/` modules (never hardcoded) +## Related -## License +- [Claude Code Anti-Regression Setup](https://github.com/CreatmanCEO/claude-code-antiregression-setup) — sister repo by the same author. The `.claude/` config + subagents + hooks pattern that keeps the 72-test suite green during refactors. +- [ai-context-hierarchy](https://github.com/CreatmanCEO/ai-context-hierarchy) — sister repo. Three-level context system used by Claude Code on this project. +- [claude-statusline](https://github.com/CreatmanCEO/claude-statusline) — sister repo. Statusline for Claude Code with VPS monitoring. +- [lingua-companion](https://github.com/CreatmanCEO/lingua-companion) — sister product from the same author — a voice-first English tutor for Russian-speaking IT professionals. Different domain, same engineering discipline. -MIT License. Copyright (c) 2026 Creatman. +## Author ---- +**Nick Podolyak** — Python developer and digital architect at [CREATMAN](https://creatman.site) + +- GitHub: [@CreatmanCEO](https://github.com/CreatmanCEO) +- Habr: [creatman](https://habr.com/ru/users/creatman/) +- dev.to: [@creatman](https://dev.to/creatman) + +## License -Built with care. Powered by AI. +[PolyForm Noncommercial 1.0.0](LICENSE) · Nick Podolyak. Non-commercial use only — see the [Status](#status--non-commercial-use-only) section. diff --git a/README.ru.md b/README.ru.md new file mode 100644 index 0000000..53111f8 --- /dev/null +++ b/README.ru.md @@ -0,0 +1,276 @@ +# 🍽 DiaBot — некоммерческий AI-ассистент по питанию для диабета 1 типа + +[![License: PolyForm Noncommercial 1.0.0](https://img.shields.io/badge/license-PolyForm%20Noncommercial%201.0.0-9d6cff)](LICENSE) +[![Stars](https://img.shields.io/github/stars/CreatmanCEO/diabot?style=flat&color=yellow)](https://github.com/CreatmanCEO/diabot/stargazers) +[![Validate](https://github.com/CreatmanCEO/diabot/actions/workflows/validate.yml/badge.svg)](https://github.com/CreatmanCEO/diabot/actions/workflows/validate.yml) +[![Status](https://img.shields.io/badge/%D1%81%D1%82%D0%B0%D1%82%D1%83%D1%81-%D0%B0%D0%BA%D1%82%D0%B8%D0%B2%D0%BD%D0%B0%D1%8F%20%D1%80%D0%B0%D0%B7%D1%80%D0%B0%D0%B1%D0%BE%D1%82%D0%BA%D0%B0-22c55e)](#статус--только-некоммерческое-использование) +[![Python 3.11+](https://img.shields.io/badge/python-3.11+-blue.svg)](https://www.python.org/downloads/) +[![Telegram Bot API](https://img.shields.io/badge/Telegram-Bot%20API-26A5E4.svg)](https://core.telegram.org/bots/api) + +🇷🇺 Русский · [🇬🇧 English](README.md) + +**Open-source некоммерческий Telegram-бот для людей с диабетом 1 типа. Сделан для близких и друзей разработчиком, у которого диабет в семье — открыт для сообщества. Активная разработка — приветствуются контрибьюторы на условиях некоммерческого использования.** + +--- + +## Зачем это существует + +Каждый приём пищи для человека с диабетом 1 типа — математическая задача. Оценить углеводы в тарелке, перевести в хлебные единицы, рассчитать дозу инсулина. Ошибка в 1–2 ХЕ — гипогликемия или гипергликемия: госпитализация, потеря сознания, кома. Это делается 4–6 раз в день, каждый день, всю жизнь. + +DiaBot превращает подсчёт углеводов в одно действие: **сфотографируй еду — получи результат.** AI распознаёт продукты, пользователь подтверждает или поправляет, бот считает КБЖУ и хлебные единицы, ведёт дневник, показывает прогресс к дневным целям. Углеводы и ХЕ всегда впереди — потому что от них зависит доза инсулина. + +## Статус — только некоммерческое использование + +Проект в **активной разработке** и распространяется по [PolyForm Noncommercial 1.0.0](https://polyformproject.org/licenses/noncommercial/1.0.0). Можно поднимать для себя, семьи, круга друзей. **Коммерческое использование — включая хостинг как платный сервис или встраивание в коммерческий продукт — не разрешено.** Контрибьюторы приветствуются на тех же условиях. + +Бот нуждается в помощи: более жёсткое промптирование LLM, точнее диетологические расчёты, верификация ответов перед пользователем, интеграция с внешними базами КБЖУ (USDA, OpenFoodFacts, региональные), региональная адаптация. Подробности в [Дорожная карта и ограничения](#дорожная-карта-и-ограничения) и [`CONTRIBUTING.md`](CONTRIBUTING.md). + +## Как это выглядит + +> Интерфейс показан на русском (язык по умолчанию). Английская локаль также полностью поддерживается — выбирается автоматически по настройкам Telegram. + + + + + + + + + + + + + + + + + + + + + + + + +
Результат после правок — бот возвращает обновлённый список (хлеб 60 г, сыр 30 г, буженина 70 г), таблицу КБЖУ по позициям, итог 419 ккал, углеводы 24 г, усваиваемые 21 г, 1.8 ХЕ, ГИ низкий, плюс прогресс за день 886 ккал и 7.6 ХЕ
Финальный результат — таблица КБЖУ + прогресс дня
Весь цикл на одном экране: КБЖУ по позициям, итоги, отдельно углеводы / усваиваемые / ХЕ / ГИ, и прогресс-бары к дневным целям.
Шаг распознавания фото — пользователь прислал фото буженины на доске рядом с сыром и хлебом, бот ответил списком 1. Хлеб ржаной 60 г, 2. Сыр 30 г, 3. Мясо (предположительно буженина или индейка) 70 г, и инлайн-кнопками Верно и ОтменаДневной отчёт — заголовок с датой, список приёмов с временем и КБЖУ, итоговая строка, прогресс-бары для калорий, углеводов, белков, жиров с процентами
Распознавание фото
Отправил фото — vision-LLM назвал продукты и оценил порции в граммах. Двухшаговое подтверждение — это safety-механизм: пользователь имеет последнее слово до того, как доза инсулина считается на эти числа.
Дневник
/today — каждый приём с временем и КБЖУ, плюс прогресс-бары. /week — недельная агрегация. /history N — глубже.
Onboarding — бот спрашивает пол, рост, вес, возраст, автоматически считает дневные нормы КБЖУ по формуле Миффлина-Сан Жеора, пользователь может вручную перебить если диетолог прописал другие значенияПодтверждение onboarding — бот резюмирует введённый профиль и целевые значения, ждёт подтверждения перед переходом в IDLE
Onboarding
Пять шагов: согласие → пол → рост → вес → возраст → цели. Нормы КБЖУ автосчитаются по Mifflin-St Jeor; ручной override — один тап, если диетолог прописал другое.
Подтверждение профиля
Финальный обзор перед IDLE. Все данные хранятся локально в SQLite; видны через /export; удаляются через /delete_my_data.
+ +## Архитектура + +![Recognition pipeline: Telegram → handlers → litellm Router с двумя цепочками (vision + text) → подтверждение пользователя → nutrition + database → дневной прогресс; Google Search grounding для брендов с низкой уверенностью](docs/architecture.svg) + +| Слой | Технология | Заметки | +|---|---|---| +| Язык | Python 3.11+, полностью async | один event loop, без thread pools | +| Telegram | `python-telegram-bot` 21+ | polling, `ConversationHandler` для состояний | +| LLM router | `litellm` Router с двумя цепочками | vision (Gemini 2.5 Flash → OpenRouter Gemini) + text (Gemini → OpenRouter → Groq Llama 4) | +| Search grounding | `google-genai` | Google Search tool — fallback для брендов с низкой уверенностью | +| База | SQLite через `aiosqlite` | без ORM, plain SQL, схема в одном файле | +| Конфиг | `python-dotenv` | frozen `Settings` dataclass валидируется при старте | +| Деплой | systemd unit включён | работает под отдельным пользователем в `/opt/diabot/` | +| Тесты | 72 pytest-кейса | покрывают каждый handler и сервис | + +## Как это работает + +1. **Отправь фото** еды (или опиши текстом) +2. **Бот распознаёт** продукты и порции через vision-LLM +3. **Подтверди или поправь** — бот покажет что увидел, ты корректируешь при необходимости +4. **Получи КБЖУ + ХЕ** — расчёт с углеводами на первом месте +5. **Следи за прогрессом** — дневник, недельная статистика, прогресс-бары к дневным целям + +## Ключевые возможности + +**Распознавание еды** +- 📷 Фото → AI распознаёт продукты и порции +- ✏️ Текстовое описание (без фото) +- 📷+✏️ Фото с подписью для уточнения +- 🏷 Распознавание брендов с Google Search grounding для редких марок + +**Точный подсчёт** +- 🔢 Калории, белки, жиры, углеводы (КБЖУ) +- 🍞 Хлебные единицы (ХЕ) — настраиваемый коэффициент (по умолчанию 1 ХЕ = 12 г углеводов) +- 🎯 Углеводы всегда первыми — приоритет дозирования инсулина +- ✅ Двухшаговый flow: распознал → подтвердил → сохранил + +**Дневные цели и прогресс** +- 👤 Профиль: пол, рост, вес, возраст +- 📊 Авто-расчёт норм по Mifflin-St Jeor +- ✍️ Ручной override (для рекомендаций диетолога) +- 📈 Компактный прогресс после каждого приёма +- ▓▓▓░░░ Визуальные прогресс-бары в дневнике + +**Дневник питания** +- 📅 Сегодня по приёмам (`/today`) +- 📊 Недельная статистика (`/week`) +- 📜 История (`/history N`) +- ↩️ Отмена последней записи (`/undo`) +- 🩸 Глюкоза (`/sugar`) + +**Конфиденциальность** +- 🔒 Согласие на старте с описанием обработки +- 🚫 Фото НИКОГДА не сохраняются на диск (только Telegram `file_id`) +- 📦 Полный экспорт (`/export` как JSON) +- 🗑 Полное удаление (`/delete_my_data`) +- ✅ GDPR-совместимость + +**Мультипользовательский режим** +- 👥 Self-hosted с системой одобрения +- 📩 Пользователь запрашивает → админ получает уведомление → пользователь получает ответ +- ⚡ Per-user rate limiting + +## Дорожная карта и ограничения + +> **Disclaimer по безопасности.** Это инструмент-помощник, **не медицинское устройство**. Всегда перепроверяй критичные значения углеводов перед дозированием инсулина. Двухшаговый flow «распознал → подтвердил» — это safety-механизм, не косметика, не пропускай его. Не замена эндокринолога / диетолога. + +### Активные направления (нужны контрибьюторы) + +- **Жёсткое промптирование LLM** — строже JSON-schema валидация, ниже температура, детерминированная оценка порций +- **Точные диетологические расчёты** — верифицированная эвристика порций, гликемическая нагрузка, вычитание клетчатки в усваиваемых углеводах +- **Верификация ответов перед пользователем** — confidence scores на позицию, явный «double-check» флаг при низкой уверенности +- **Внешние базы КБЖУ** — [USDA FoodData Central](https://fdc.nal.usda.gov/), [OpenFoodFacts](https://world.openfoodfacts.org/), региональные русские / восточно-европейские источники +- **Региональная адаптивность** — EU / US / RU / Asia нормы и кухни, бренды, локальные блюда + +### Текущие ограничения + +- LLM может ошибаться (плохое освещение, мелкие порции, региональные блюда, необычные ракурсы). Двухшаговое подтверждение существует именно поэтому. +- Интеграции с CGM пока нет +- Билингв-промпты протестированы в основном на RU; верификация EN-локали в процессе +- Google Search grounding доступен только в Gemini-цепочке — OpenRouter / Groq fallbacks пока не поддерживают +- Onboarding принимает метрики только в метрической системе — imperial-перевод в backlog'е + +## Быстрый старт + +```bash +git clone https://github.com/CreatmanCEO/diabot.git +cd diabot +python -m venv venv +source venv/bin/activate # Linux / macOS — на Windows: venv\Scripts\activate +pip install -r requirements.txt +cp .env.example .env +# Заполни API-ключи в .env +python main.py +``` + +### Переменные окружения + +| Переменная | Описание | Обязательно | +|---|---|:---:| +| `TELEGRAM_TOKEN` | Токен от @BotFather | да | +| `ADMIN_IDS` | Через запятую Telegram ID админов | да | +| `GEMINI_API_KEY` | API ключ Google AI Studio | * | +| `OPENROUTER_API_KEY` | API ключ OpenRouter | * | +| `GROQ_API_KEY` | API ключ Groq | * | +| `DEFAULT_TIMEZONE` | Часовой пояс (`Europe/Moscow`) | нет | +| `DEFAULT_HE_GRAMS` | Граммов углеводов в 1 ХЕ (`12`) | нет | +| `DEFAULT_LANGUAGE` | Язык по умолчанию (`ru`) | нет | +| `RATE_LIMIT_REQUESTS` | Per-user лимит в час (`30`) | нет | +| `DB_PATH` | Путь к SQLite | нет | + +\* Нужен хотя бы один LLM-ключ. Распознавание фото требует `GEMINI_API_KEY` или `OPENROUTER_API_KEY`. + +### Деплой (systemd) + +```bash +sudo useradd -r -s /bin/false diabot +sudo mkdir -p /opt/diabot +sudo git clone https://github.com/CreatmanCEO/diabot.git /opt/diabot +cd /opt/diabot +sudo python3 -m venv venv +sudo venv/bin/pip install -r requirements.txt +sudo cp .env.example .env +sudo nano .env # заполни токены +sudo cp diabot.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable diabot +sudo systemctl start diabot +``` + +## Структура + +``` +diabot/ +├── main.py # Entry point, ConversationHandler +├── config.py # Settings из .env (frozen, validated) +├── handlers/ # Telegram-обёртки (тонкий I/O) +│ ├── start.py # /start, onboarding, /help +│ ├── photo.py # Pipeline распознавания фото +│ ├── text.py # Текстовый ввод + reply keyboard +│ ├── confirm.py # Подтверждение / коррекция / отмена +│ ├── diary.py # /today, /week, /history, /undo +│ ├── glucose.py # /sugar +│ ├── settings.py # Профиль / цели / админ-панель +│ ├── admin.py # /adduser, /removeuser, /listusers +│ ├── privacy.py # /privacy, /export, /delete_my_data +│ └── keyboards.py # Фабрика клавиатур +├── services/ # Бизнес-логика (без Telegram) +│ ├── llm.py # litellm Router, две цепочки +│ ├── nutrition.py # КБЖУ, прогресс-бары, ХЕ +│ ├── database.py # aiosqlite CRUD +│ └── auth.py # Доступ, rate limiting, очередь одобрения +├── models/schemas.py # Dataclasses +├── locales/ # i18n + LLM-промпты +│ ├── ru.py # Русский (по умолчанию) +│ └── en.py # Английский +├── docs/ +│ ├── architecture.svg # Pipeline-диаграмма этого README +│ ├── plans/ # Design-документы и планы +│ └── screenshots/ # Ассеты README +├── tests/ # 72 теста pytest +├── diabot.service # systemd unit +├── CHANGELOG.md +├── CONTRIBUTING.md +├── CLAUDE.md # Конституция Claude Code +└── LICENSE # PolyForm Noncommercial 1.0.0 +``` + +## State machine + +``` +ONBOARDING: согласие → пол → рост → вес → возраст → цели → IDLE +IDLE ↔ AWAITING_CONFIRM (распознавание еды) +IDLE ↔ AWAITING_GLUCOSE (запись глюкозы) +``` + +## Архитектурные решения + +- **Handlers тонкие** — вся логика в `services/`, handlers только Telegram-I/O +- **Две LLM-цепочки** — vision (Gemini → OpenRouter) для фото; text (Gemini → OpenRouter → Groq Llama 4) для описаний. `litellm` Router делает auto-failover. +- **Google Search grounding** — при низкой уверенности на брендах отдельный `google-genai` запрос с Search-инструментом достаёт точные КБЖУ. +- **Точность углеводов > точности калорий** — отражено в промптах, форматировании, порядке UI. Дозирование инсулина зависит от углеводов. +- **Без ORM** — plain SQL через `aiosqlite`. Схема в одном файле; ORM добавил бы сложность без пользы. +- **Фото не пишутся на диск** — только Telegram `file_id`. Байты подгружаются по запросу и передаются прямо в LLM. +- **Двухшаговое подтверждение — safety-механизм** — распознал → подтвердил. Без явного подтверждения приём не сохраняется и расчёт инсулина не подразумевается. + +## Работа над проектом + +Сконфигурирован под [Claude Code](https://code.claude.com) как primary драйвер — см. [`CLAUDE.md`](CLAUDE.md). Тот же автор поддерживает [Claude Code Anti-Regression Setup](https://github.com/CreatmanCEO/claude-code-antiregression-setup) — этот паттерн держит 72-test-suite зелёным во время рефакторингов. + +Тесты: + +```bash +python -m pytest tests/ -v +``` + +Per-iteration design-документы в `docs/plans/`. Новая работа стартует с design-доком, потом код. + +## Контрибьют + +PR приветствуются на тех же некоммерческих условиях. См. [`CONTRIBUTING.md`](CONTRIBUTING.md) — приоритеты: ужесточение LLM-промптов, диетологическая точность, верификация ответов, внешние базы КБЖУ, региональная адаптивность, нативные локализации помимо RU/EN. + +## Связанные проекты + +- [Claude Code Anti-Regression Setup](https://github.com/CreatmanCEO/claude-code-antiregression-setup) — sister-репо того же автора. `.claude/` config + субагенты + хуки, держат 72-test-suite зелёным. +- [ai-context-hierarchy](https://github.com/CreatmanCEO/ai-context-hierarchy) — sister-репо. Трёхуровневая система контекста, которой пользуется Claude Code на этом проекте. +- [claude-statusline](https://github.com/CreatmanCEO/claude-statusline) — sister-репо. Statusline для Claude Code с VPS-мониторингом. +- [lingua-companion](https://github.com/CreatmanCEO/lingua-companion) — другой продукт того же автора — voice-first English tutor для русскоговорящих IT-специалистов. Другой домен, та же инженерная дисциплина. + +## Автор + +**Николай Подоляк (Nick Podolyak)** — Python-разработчик и цифровой архитектор в [CREATMAN](https://creatman.site) + +- GitHub: [@CreatmanCEO](https://github.com/CreatmanCEO) +- Habr: [creatman](https://habr.com/ru/users/creatman/) +- dev.to: [@creatman](https://dev.to/creatman) + +## Лицензия + +[PolyForm Noncommercial 1.0.0](LICENSE) · Николай Подоляк. Только некоммерческое использование — см. [Статус](#статус--только-некоммерческое-использование). diff --git a/docs/architecture.svg b/docs/architecture.svg new file mode 100644 index 0000000..e6b6dfb --- /dev/null +++ b/docs/architecture.svg @@ -0,0 +1,81 @@ + + + DiaBot — recognition pipeline + Pipeline: a Telegram photo or text message arrives, the bot routes it to a vision or text LLM chain, the user confirms or corrects the recognised items, the bot computes carbohydrates / bread units / KBJU, writes the meal to SQLite, and shows daily progress towards targets. Photos never touch disk — only Telegram file_id is stored. + + + + DiaBot — recognition pipeline + Photo never touches disk · two-step confirm · carbs prioritised over calories · multi-provider LLM with auto-failover + + + + Telegram + photo · text · /commands + + + + + + + + handlers/ (thin I/O) + photo · text · confirm · diary · glucose + + + + + + + + services/llm.py + litellm Router · 2 chains · failover + + + + Two LLM chains (auto-failover via litellm Router) + + + Vision chain (food photo) + Gemini 2.5 Flash → OpenRouter Gemini + + + Text chain (text-only) + Gemini → OpenRouter → Groq Llama 4 + + + + + + + + User confirmation (✅ Верно / ✏ Исправить / ❌ Отмена) + safety-critical — insulin dose depends on the carb count below + + + + + + + + services/nutrition.py + KBJU · bread units (XE) · GI · daily targets · progress bars + + + services/database.py · aiosqlite + users · meals · glucose · targets · timezone-aware + + + + grounding (low + confidence) + google-genai + + Search tool + branded + products + resolution + → KBJU lookup + + + Photos never saved to disk · only Telegram file_id stored · bytes streamed to LLM and discarded · GDPR-style /export and /delete_my_data + diff --git a/docs/screenshots/01-kbju-result.png b/docs/screenshots/01-kbju-result.png new file mode 100644 index 0000000..81ed715 Binary files /dev/null and b/docs/screenshots/01-kbju-result.png differ diff --git a/docs/screenshots/02-photo-recognition.png b/docs/screenshots/02-photo-recognition.png new file mode 100644 index 0000000..fbbd57d Binary files /dev/null and b/docs/screenshots/02-photo-recognition.png differ diff --git a/docs/screenshots/03-daily-report.png b/docs/screenshots/03-daily-report.png new file mode 100644 index 0000000..e69b116 Binary files /dev/null and b/docs/screenshots/03-daily-report.png differ diff --git a/docs/screenshots/04-onboarding-setup.png b/docs/screenshots/04-onboarding-setup.png new file mode 100644 index 0000000..aebbadc Binary files /dev/null and b/docs/screenshots/04-onboarding-setup.png differ diff --git a/docs/screenshots/05-onboard-confirm.png b/docs/screenshots/05-onboard-confirm.png new file mode 100644 index 0000000..fdb1442 Binary files /dev/null and b/docs/screenshots/05-onboard-confirm.png differ