Короткое резюме
FourCast должен быть не просто OCR-ботом, а контролируемой очередью чеков: каждый чек либо строго обработан, либо немедленно отмечен как требующий локальной или ручной проверки.
Что строим первым: рабочую цепочку Telegram photo/document -> очередь -> Drive original -> Sheets row -> Drive OCR -> parser -> validation -> результат в Telegram.
Что откладываем: автопробуждение ПК, LLM-категоризацию, красивые графики после каждого чека, универсальный парсер всех магазинов мира.
Цель и жесткие правила данных
Цель MVP - ежедневно отправлять чеки в Telegram и получать точную семейную статистику без ручного ввода, но с честной эскалацией всех сомнительных случаев.
Целевой пользовательский сценарий
Вы или жена отправляете чек
Лучше отправлять как файл/document, если чек длинный, смазанный или важный. Обычное photo тоже поддерживается.
Webhook принимает событие
Система проверяет секрет, канал или группу, message_id и кладет чек в очередь без тяжелого OCR.
Processor сохраняет оригинал
Фото сохраняется в Drive, в Sheets создается или обновляется idempotent receipt row.
OCR, parser и validation
Drive OCR извлекает текст, merchant-specific parser извлекает товары, validation проверяет сумму и полноту.
Telegram сообщает результат
Если чек надежный, он включается в статистику. Если нет, вы сразу видите причину и следующий шаг.
Непереговорные правила
- 1Статистика считает только статусы processed и human_verified.
- 2Любая сумма хранится в центах как integer, не float.
- 3Если позиции не найдены, чек уходит в needs_local_processing.
- 4Если сумма позиций не сходится с total, чек уходит в needs_review.
- 5Повторный webhook не создает дубль: receipt_id = chat_id + message_id.
- 6LLM может помогать с категорией, но финальные суммы проверяются кодом.
Интерактивная блок-схема
Нажимайте на узлы схемы: справа появится назначение шага, что хранится, какие ошибки ловим и почему это важно.
Варианты архитектуры
Ниже сравнение трех практических вариантов. Для вашей цели я рекомендую вариант B: он остается бесплатным, но закрывает безопасность webhook и устойчивость к пикам.
Apps Script only
Самый простой путь: Telegram webhook указывает прямо на Web App URL Apps Script. Secret передается в URL, проверяется chat_id и message_id.
Компромисс Apps Script плохо подходит для проверки Telegram secret header, а doPost должен завершиться быстро.
Когда выбирать: если нужен самый короткий путь к первому прототипу и вы готовы принять более слабую защиту публичного webhook.
Cloudflare Worker + Queue + Apps Script
Worker проверяет Telegram header secret, ограничивает вход, кладет update в очередь и отвечает Telegram быстро. Apps Script обрабатывает задачу отдельно.
Рекомендовано Бесплатно для вашего объема, безопаснее и устойчивее, чем прямой Apps Script webhook.
Почему это лучший MVP: вы не заводите VPS, не платите за Cloud Run, но получаете нормальный gateway и очередь.
Local worker как fallback
Домашний ПК не обрабатывает каждый чек. Он подключается только для статусов needs_local_processing, когда Drive OCR или parser не смогли доказать полноту.
Позже Запуск через Windows Task Scheduler при старте ПК или по расписанию.
Роль ПК: не основная инфраструктура 24/7, а усилитель качества для сложных чеков и способ не терять товары.
| Вариант | Стоимость | Плюсы | Минусы | Рекомендация |
|---|---|---|---|---|
| Apps Script only | 0 EUR | Самый быстрый старт, меньше компонентов. | Слабее webhook security, нет нормальной внешней очереди. | Можно для прототипа |
| Cloudflare Worker + Queue | 0 EUR при вашем объеме | Быстрый ответ Telegram, secret header, retries, буфер. | Нужно настроить еще один сервис. | Лучший MVP |
| Local worker | 0 EUR, когда ПК включен | Сильнее OCR, можно использовать PaddleOCR/Tesseract/Qwen-VL. | Не 24/7, нужен мониторинг last_seen и безопасный доступ. | Fallback |
| VPS до 5 EUR | 3-5 EUR/мес | Полный контроль, worker 24/7, проще Python pipeline. | Операционное сопровождение, обновления, секреты, мониторинг. | Не нужен для MVP |
Ресурсы, доступы и секреты
Этот список нужен, чтобы команда не начала писать код без понимания, какие аккаунты, ключи и лимиты реально участвуют в системе.
| Ресурс | Что хранит или делает | Где настраивается | Критичность | Замечание |
|---|---|---|---|---|
| Telegram Bot | Получает update, отправляет статусы и отчеты. | BotFather, channel/group permissions. | Critical | Token не хранить в коде и не писать в Sheets. |
| Telegram private group | Семейный интерфейс для вас и жены. | Telegram app. | Important | Группа удобнее канала для команд и sender attribution. |
| Cloudflare Worker | Webhook gateway, проверка secret header, быстрый ответ. | Cloudflare dashboard или Wrangler. | Critical | Рекомендованный бесплатный gateway. |
| Cloudflare Queue | Буфер webhook events, retries, защита Apps Script. | Cloudflare dashboard. | Important | Можно заменить Sheets Inbox, но очередь лучше. |
| Apps Script | Drive, OCR, Sheets, parser orchestration. | script.google.com, clasp из repo. | Critical | Использовать PropertiesService для секретов. |
| Google Drive | Оригиналы чеков, OCR docs, reports, failed assets. | Google Drive folders. | Critical | Нужна стабильная folder structure. |
| Google Sheets | Receipts, Items, Errors, Settings, Summary. | Spreadsheet FourCast Ledger. | Critical | Source of truth: Receipts + Items. |
| Windows PC worker | Сильный OCR и parser fallback. | Task Scheduler, Python venv. | Optional | Подключать после cloud MVP. |
TELEGRAM_BOT_TOKEN=*** TELEGRAM_ALLOWED_CHAT_ID=-1001234567890 WEBHOOK_SHARED_SECRET=*** SPREADSHEET_ID=*** DRIVE_ROOT_FOLDER_ID=*** CLOUDFLARE_WORKER_SHARED_SECRET=***
https://api.telegram.org/bot<BOT_TOKEN>/setWebhook ?url=https://fourcast-webhook.example.workers.dev/telegram &secret_token=<LONG_RANDOM_SECRET>
Google Sheets schema
Разделение важно: Receipts и Items - источник истины, Summary - производная таблица или формулы. Это защищает от расхождений при retry и reprocess.
Receipts
| Поле | Тип | Назначение |
|---|---|---|
| receipt_id | string | chat_id + message_id. Главный ключ идемпотентности. |
| telegram_chat_id, telegram_message_id | string/int | Источник Telegram события. |
| telegram_file_id, file_unique_id | string | Скачивание и дедупликация файла. |
| received_at_utc, updated_at, processed_at | datetime | Аудит и мониторинг задержек. |
| status, status_reason | enum/string | Текущее состояние и короткая причина. |
| include_in_stats | boolean | Истинно только для processed/human_verified. |
| merchant, receipt_date, currency | string/date | Нормализованные данные чека. |
| total_cents | integer | Итоговая сумма без float-ошибок. |
| photo_drive_file_id, ocr_doc_id | string | Связь с Drive. |
| parser_version, confidence | string/number | Повторная обработка после улучшения parser. |
| validation_errors_json | json text | Машиночитаемые причины review/local fallback. |
Items
| Поле | Тип | Назначение |
|---|---|---|
| item_id | string | receipt_id + item_index. |
| receipt_id | string | Связь с Receipts. |
| item_index | integer | Порядок в чеке. |
| raw_item_name | string | Как OCR увидел позицию. |
| normalized_item_name | string | Нормализованное имя для аналитики. |
| quantity, unit | number/string | Количество и единица: tk, kg, l. |
| unit_price_cents | integer | Цена за единицу, если найдена. |
| line_total_cents | integer | Сумма строки. |
| discount_cents | integer | Скидка по позиции, отрицательное число. |
| category | string | Категория, сначала rules/dictionary, позже AI. |
| confidence, flags_json | number/json | Причины сомнений parser. |
Errors, Settings, Summary
Errors: receipt_id, stage, error_code, message, payload_excerpt, created_at.
Settings: key, value, description. Здесь хранить не секреты, а настройки parser, merchant aliases, thresholds.
Daily/Monthly Summary: считать из Receipts + Items. Не обновлять вручную как источник истины.
Структура репозитория и Apps Script
Главный риск Apps Script-проектов - весь код в одном файле без тестов. Parser должен быть отделен от Google APIs, чтобы его можно было гонять на fixtures локально.
apps-script/
appsscript.json
src/
00_config.gs
01_secrets.gs
10_webhook_adapter.gs
20_telegram_client.gs
30_sheet_store.gs
40_drive_store.gs
50_ocr_drive.gs
60_parser_core.gs
61_parser_maxima.gs
62_parser_rimi.gs
70_validation.gs
80_summary.gs
90_triggers.gs
cloudflare-worker/
src/index.ts
wrangler.toml
local-worker/
fourcast_worker/
pyproject.toml
tests/
fixtures/ocr/
parser/
docs/
architecture.md
deployment.md
runbook.md
Принципы кодовой архитектуры
- 1Google APIs в adapter слоях, бизнес-логика отдельно.
- 2Parser pure function: OCR text -> parsed receipt + flags.
- 3Validation pure function: parsed receipt -> decision.
- 4Sheet writes идемпотентные: reprocess заменяет items для receipt_id.
- 5Все внешние вызовы с retry, timeout и error logging.
- 6Fixtures обязательны до подключения второго магазина.
Parser strategy для чеков Эстонии
Не делайте один универсальный regex. Начните с одного магазина, соберите fixtures, потом расширяйте правила по merchant.
Phase 1: Normalize
Чистим OCR: пробелы, decimal comma, похожие символы, мусорные строки, переносы имени товара.
OCR text hygienePhase 2: Merchant parser
Maxima, Rimi, Selver, Prisma, Lidl, Coop получают отдельные правила item-section и total-section.
Доказуемая точностьPhase 3: Reconcile
Складываем товары, скидки, pant/deposit, округления. Если total не сходится, чек не processed.
Strict validation| Элемент | Что ищем | Риск | Решение |
|---|---|---|---|
| Merchant | Maxima/Rimi/Selver aliases, registrikood, address. | OCR портит название. | Alias dictionary + fuzzy matching. |
| Date | dd.mm.yyyy, yyyy-mm-dd, time рядом. | Дата печати или loyalty block. | Prefer purchase timestamp near receipt header/footer. |
| Total | KOKKU, SUMMA, TASUDA, TOTAL. | Есть subtotal, card payment, VAT total. | Merchant-specific total labels. |
| Items | name, qty, unit, unit price, line total. | Multi-line names and discounts. | Right-to-left numeric parsing + line stitching. |
| Discounts | soodustus, allahindlus, campaign rows. | Скидка отдельной строкой. | Attach to previous item or global adjustment. |
| Pant/deposit | pant, tagatis, bottle deposit. | Может быть отдельной позицией. | Keep as item or adjustment by merchant rule. |
Validation strategy
Validation - это gatekeeper статистики. Она не должна быть мягкой: лучше сразу сказать, что чек требует действия, чем тихо исказить месяц.
Условия processed
- 1Найден merchant.
- 2Найдена дата покупки.
- 3Найден total в EUR или корректно обработана foreign currency.
- 4Найдена хотя бы одна item line.
- 5Каждая item line имеет name и line_total_cents.
- 6sum(items + discounts + adjustments) == total с tolerance 1-2 cents.
Decision matrix
| Ситуация | Статус | Действие |
|---|---|---|
| OCR short/noisy | needs_local_processing | Локальный worker или ручная проверка. |
| Total missing | needs_review | Не включать в статистику. |
| Items missing | needs_local_processing | Запустить сильный OCR. |
| Sum mismatch | needs_review | Показать diff и фото. |
| All checks passed | processed | Записать Items и обновить summary. |
Порядок реализации
План построен так, чтобы уже на раннем этапе получить end-to-end цепочку и не тратить месяц на универсальный parser до появления реальных OCR fixtures.
Подготовить repo и окружение
Склонировать GitHub в C:\FourCast\ai-workspace, добавить apps-script, cloudflare-worker, tests, docs.
Создать Google Drive и Sheets
Family Ledger/receipts/YYYY-MM, ocr_docs, reports, failed. Создать Receipts, Items, Errors, Settings.
Webhook gateway
Cloudflare Worker проверяет Telegram secret header и allowed route, кладет update в Queue.
Apps Script queue processor
Сохраняет original photo/document, создает idempotent receipt row, отправляет "чек принят".
Drive OCR
Создает Google Doc из изображения, извлекает raw text, сохраняет ссылку и текстовый artifact.
Maxima parser
Покрыть 10-20 реальных чеков fixture-тестами до перехода к другим магазинам.
Validation gate
Суммы в центах, tolerance, flags, статусы processed/needs_local_processing/needs_review.
Telegram UX
/status, /review, /summary_today, /summary_month, понятные сообщения об ошибках.
Local worker
Python worker берет needs_local_processing и запускает PaddleOCR/Tesseract при включенном ПК.
Графики по запросу
Apps Script Charts Service или Sheets charts, только по командам, не после каждого чека.
Домашний ПК и локальная обработка
ПК не должен быть обязательной частью happy path. Его роль - спасать сложные чеки, когда облако не смогло доказать полноту.
Когда подключать ПК
- 1Drive OCR вернул слишком короткий или мусорный текст.
- 2Не найдены позиции товаров.
- 3Не найден total.
- 4Сумма позиций не сходится с total.
- 5Parser выставил low confidence.
Рекомендованный старт
Windows Task Scheduler запускает worker при входе в систему и каждые 10-15 минут, пока ПК включен. Worker пишет local_worker_last_seen_at в Settings.
Wake-on-LAN через интернет, port forwarding и smart plug лучше отложить до момента, когда cloud MVP стабилен.
Telegram команды и ответы
Команды должны быть короткими, предсказуемыми и показывать не только суммы, но и наличие незакрытых чеков.
| Команда | Ответ |
|---|---|
| /status | Состояние system health, pending queue, local worker last seen, needs_review count. |
| /review | Список чеков, которые не включены в статистику, с причинами. |
| /summary_today | Итог за сегодня только по processed/human_verified. |
| /summary_month | Итог за месяц, количество чеков, warnings. |
| /chart_today | PNG chart по категориям или магазинам за день. |
| /chart_month | PNG chart по категориям, магазинам, динамике дней. |
Чек обработан Магазин: Maxima Дата: 2026-07-21 Итого: 23.41 EUR Позиций: 8 Сегодня: 74.20 EUR За месяц: 612.80 EUR
Риски и конкретные меры
Это список мест, где проект обычно ломается. Его стоит использовать как архитектурный review checklist перед реализацией.
| Риск | Серьезность | Почему опасно | Мера |
|---|---|---|---|
| Тяжелая работа в webhook | High | Telegram retry, дубли, потеря UX. | Webhook только queue/inbox, OCR отдельно. |
| Нет idempotency | High | Дубли чеков и завышенная статистика. | receipt_id + LockService + upsert. |
| Float для денег | High | Ошибки округления и mismatch. | Все суммы в integer cents. |
| Один regex parser | High | Сломается на первом другом магазине. | Merchant-specific parsers + fixtures. |
| Summary как источник истины | Medium | Retry/reprocess создают расхождения. | Summary пересчитывать из Receipts + Items. |
| Сжатые Telegram фото | Medium | OCR теряет мелкий текст. | Поддержать document/image и подсказки пользователю. |
| Секреты в коде или Sheets | High | Компрометация бота и данных. | PropertiesService, Worker secrets, no logs. |
| Нет local fallback | Medium | Сложные чеки будут висеть без решения. | Python worker для needs_local_processing. |
Чеклист запуска
Отмечайте пункты по мере выполнения. Состояние сохраняется в браузере локально.
Полезные ссылки
Официальные документы, которые стоит держать рядом при реализации.