← всі звіти · read-2026-05-05T090700Z-expense-bot-source.md

from: Desktop Claude topic: expense-bot-source date: 2026-05-05T00:00:00Z priority: high

Expense Bot — артефакти для міграції на VPS

Код

expense-bot-source.zip у цьому ж inbox. Містить:

Не включено: .env, credentials.json, Expense_Tracker.xlsx, deploy.bat, __pycache__

requirements.txt (повний)

python-telegram-bot==21.8
google-genai>=1.0.0
gspread>=6.0.0
google-auth>=2.0.0
python-dotenv>=1.0.0
cryptography>=43.0.0

Поточна схема секретів (GCP deployment)

config.py читає зашифровані значення з .env і розшифровує через Fernet з MASTER_PASSWORD:

.env keys:
  TELEGRAM_BOT_TOKEN_ENC=<fernet-encrypted>
  GEMINI_API_KEY_ENC=<fernet-encrypted>
  GOOGLE_SHEETS_ID_ENC=<fernet-encrypted>
  GOOGLE_CREDENTIALS_PATH=credentials.json
  ALLOWED_USER_IDS=<telegram user id>

Runtime (не в .env, передається окремо):
  MASTER_PASSWORD=<пароль>

⚠️ Рекомендація для VPS: спростити config.py

На VPS секрети захищені systemd EnvironmentFile (chmod 600), шар crypto непотрібний. Замінити config.py на простий варіант:

import os

TELEGRAM_BOT_TOKEN = os.environ["TELEGRAM_BOT_TOKEN"]
GEMINI_API_KEY     = os.environ["GEMINI_API_KEY"]
GOOGLE_SHEETS_ID   = os.environ["GOOGLE_SHEETS_ID"]
GOOGLE_CREDENTIALS_PATH = os.environ.get("GOOGLE_CREDENTIALS_PATH", "/srv/services/expense-bot/credentials.json")
ALLOWED_USER_IDS = [int(x) for x in os.environ.get("ALLOWED_USER_IDS", "").split(",") if x.strip()]

CATEGORIES = [
    "Food", "Transport", "Housing", "Utilities",
    "Health", "Entertainment", "Clothing",
    "Education", "Subscriptions", "Other",
]

EXPENSES_WORKSHEET = "Expenses"

Тоді secrets.env у passepartout — стандартний KEY=value. crypto_utils.py можна залишити (не шкодить), просто не використовувати.

Команда запуску

python bot.py

(або /srv/services/expense-bot/.venv/bin/python bot.py у systemd unit)

credentials.json — Google Service Account

Файл: expense-bot@expense-bot-492610.iam.gserviceaccount.com

IP restriction: GCP Service Account keys за замовчуванням НЕ прив'язані до IP. IP-обмеження можливі лише через VPC Service Controls або Org Policy — у звичайному проекті їх немає. Той самий credentials.json працюватиме з VPS IP 31.131.26.203 без змін.

Де взяти файл на VPS: credentials.json не потрапив у zip (secret). Варіанти:

  1. Сергій копіює вручну: scp credentials.json root@31.131.26.203:/srv/passepartout/expense-bot/
  2. Або Сергій завантажує новий ключ у GCP Console → IAM & Admin → Service Accounts.

Після копіювання у systemd unit додати:

EnvironmentFile=/srv/passepartout/expense-bot/secrets.env
Environment=GOOGLE_CREDENTIALS_PATH=/srv/passepartout/expense-bot/credentials.json

Gemini-промпт (поточний)

Файл expense_parser.py, модель gemini-2.5-flash, thinking=0.

You extract expense data from user messages in Ukrainian or Russian.
Return ONLY valid JSON, no other text.

Available categories: {CATEGORIES}

JSON schema:
{"date": "YYYY-MM-DD", "amount": float, "currency": "UAH",
 "category": "...", "comment": "short description in user's language"}

Rules:
- If no date: use today
- If no currency: default to UAH
- amount must be positive
- Pick best matching category
- comment: brief description of what was purchased

Застосовується до text / voice / photo однаково (системний prompt + контентний блок). Нова 2-рівнева таксономія — просто замінити список CATEGORIES і оновити системний промпт.

Sheets структура

Worksheet: Expenses Колонки: Date | Amount | Currency | Category | Comment | Timestamp

/report команда → get_monthly_totals(YYYY-MM) → агрегація по Category.

Що ще потрібно від Сергія