Техническое досье · внутренний документ

Хорика: код, стенд, архив

Разбор трёх объектов проекта «Хорика» — инструмента для поставщика продуктов в HoReCa: репозиторий с исходным кодом, работающий дев-стенд и архив транскриптов рабочих сессий. Один продукт, три уровня: источник, развёрнутый снимок, история решений.

Продукт: catering-ai / «Хорика» Ветка: feature/wave1-foundations Схема БД: alembic 0025 Дата разбора: 2026-09-03
Объект 01

Репозиторий

git@github.com:farmgameowner/nashpartner

Исходный код: FastAPI-бэкенд, React-фронт, пайплайн «меню → КП», интеграция 1С. ~20.8k строк, 50 коммитов.

Объект 02

Дев-стенд

root@201.51.4.88 · horeca.uxbrain.ru

Развёрнутый стек из 7 контейнеров на общем сервере. Живые данные: 1742 организации, номенклатура и цены 1С.

Объект 03

Архив сессий

sessions.tar · 22.5 МБ · вне git

Транскрипты сессий Claude Code и Grok — история того, как принимались решения по проекту.

01

Репозиторий — исходный код «Хорики»

source of truth

Что это. Внутренний MVP для проверки гипотезы. Клиент — поставщик продуктов в HoReCa (не ресторан и не кейтеринг). Система находит заведения общепита в Яндекс.Картах и Поиске, собирает и раскладывает их меню до ингредиентов, матчит на номенклатуру клиента и генерирует коммерческое предложение (КП) двух типов: «что вы уже готовите и мы это поставляем» и «что вы могли бы добавить на нашей номенклатуре».

Репозиторий переехал и переименован: origin теперь farmgameowner/nashpartner, тогда как внутренние доки и сессии всё ещё ссылаются на прежний budka-dev/horeca. Имя пакета — catering-ai, продуктовое имя — «Хорика», инфраструктурный префикс — horeca_*.

Объём кодовой базы
12 152
строк Python (backend)
8 703
строк TS/TSX (frontend)
25
миграций Alembic
12
API-роутеров
19
ARQ-задач воркера
20
страниц фронта
121
тест-функций (17 файлов)
50
коммитов с 18.06
Стек
  • Backend: FastAPI, SQLAlchemy 2.0 (async), Alembic, Pydantic v2, ARQ + Redis.
  • Парсинг: Playwright (браузерный воркер), httpx + selectolax, пул прокси с health-check.
  • LLM: Claude через SDK или OpenRouter, prompt caching; классификация фото меню и извлечение позиций.
  • Хранилище: PostgreSQL 16, MinIO (S3) для картинок и PDF КП.
  • Frontend: React 19, Vite, shadcn/ui + Tailwind, TanStack Query/Table.
  • КП: Jinja2 → WeasyPrint (PDF) и python-docx (DOCX).
Данные и интеграция 1С
  • organizations — ключевая сущность, дедуп по fingerprint (телефон + домен/адрес).
  • menu_assets / menu_items — найденные артефакты меню и извлечённые позиции.
  • client_products — номенклатура клиента, на неё матчатся блюда.
  • onec_* — контрагенты, типы цен и цены из 1С (файловый импорт 4 CSV + OData-коннектор, пока выключен).
  • Продажи/проводки 1С не присланы — cross-sell «используют, но не покупают» отложен на Этап 2.
Пайплайн «URL → КП»
discovery→ resolve→ fetch_site→ classify_images→ extract_menu→ proposal_draft

Красным — шаги на LLM, которые сейчас не работают из-за блокера на стенде (см. Объект 02).

Статус волны 1 (по docs/PROGRESS.md, 27.08)
НаправлениеСостояниеКомментарий
Инфраструктура и деплойготовоbrowser-воркер добавлен в прод-compose, стенд развёрнут и проверен.
Калькуляторы цен (НДС, ед., упаковка)готовоЧистое ядро на Decimal, цены прайса 1С в редакторе КП.
Импорт выгрузок 1С (4 CSV)готовоИдемпотентный sync, связь с каталогом по штрихкоду.
Номенклатура и матчингготовоКаталог только из 1С; 1080 позиций, 367 ингредиент-термов.
Типы цен: смысл (−)/(+), дефолт-колонкачастично8 канонических расшифрованы; ждёт ответа заказчика.
Реальный шаблон КП + реквизиты, логистикаблокЖдёт данных заказчика (Юлия).
Продажи/проводки 1С, cross-sellЭтап 2Решением заказчика выведено из Этапа 1.
02

Дев-стенд — развёрнутый снимок

201.51.4.88 · msk-1-vm-2j9k

Что это. Общий сервер, на котором среди прочих проектов крутится стек «Хорики». Развёрнут из тарбола, не из git — на сервере нет рабочего дерева репозитория, только собранный код. Наружу торчит домен horeca.uxbrain.ru за TLS и единым парольным гейтом nginx (запрос отдаёт 302 на страницу логина). Доступ по SSH — по ключу владельца, загруженному в ssh-agent; названный пароль относится к веб-гейту, а не к root по SSH.

Хост
24.04
Ubuntu LTS, ядро 6.8
2
vCPU
3.8 ГБ
RAM (2.2 занято)
48 ГБ
диск, занято 81%
7 дн
аптайм
31
контейнеров всего

Сервер делят несколько проектов: erp, odeta, taroseer, skazka, prapor, demo-support и horeca. Стек «Хорики» — 7 контейнеров, поднят 3 дня назад, схема БД на миграции 0025, health отвечает {"status":"ok"}.

Контейнеры horeca
  • horeca_web — фронт за nginx (127.0.0.1:8081).
  • horeca_backend — FastAPI (:8000, health ok).
  • horeca_worker — light-очередь, кроны живы.
  • horeca_worker_browser — Playwright, очередь arq:browser.
  • horeca_postgres / redis / minio — все healthy.
Живые данные в БД
ТаблицаСтрок
organizations1 742
menu_assets17 713
menu_items4 532
client_products (1С)1 080
onec_contractors1 370
onec_product_prices24 742
onec_price_types (530 инд.)540
org_ingredients656
proposals · proxies · users · regions6 · 6 · 1 · 1

Живой блокер: ключ OpenRouter отдаёт 403

Проверено 2026-09-03: llm.transport = openrouter, но ключ на стенде получает HTTP 403 Forbidden. Поэтому шаги classify_images и extract_menu не работают, и сквозной сценарий «URL → КП» разорван. Воркер при этом жив: кроны авто-ретрая и health-check шести прокси идут штатно. Нужен рабочий LLM-ключ в /root/horeca/.env и рестарт воркеров.

Операционная заметка: диск и правовая рамка

Диск занят на 81%. Реально освобождаемого — около 8 ГБ build-кэша Docker плюс ~17.8 ГБ образов; при росте данных стоит подчистить. Данные проекта лежат в /root/horeca_data (9.1 МБ, chmod 700) и в БД. Выделенный сервер/домен под проект, защищённый канал передачи выгрузок 1С и NDA остаются открытыми вопросами к заказчику.

03

Архив сессий — история решений

sessions.tar · перенесён в проект

Что это. Тарбол транскриптов рабочих AI-сессий по «Хорике» — «исходники того, как принимались решения»: почему выбрана та или иная схема, что отвечал заказчик, какие грабли уже проходили. Скопирован с ноутбука владельца (/home/joda/sessions.tar) через обратный SSH-туннель, положен в корень проекта и добавлен в .gitignore. Проверено: git его не отслеживает.

Состав архива
СессияПериодСтрокО чём
claude/dc769d5a…18–28.061 875Старт: разбор репо, MVP-флоу, вопросы Юлии, волны скрапера 0–2.
claude/4dbc9588…29.06277Включение OData в 1С, ТЗ подрядчику, парольный гейт стенда.
claude/915d3d69…28.06–09.072 399Волна 3 (бюджет LLM), v1-вид фронта, переписка с 1С-подрядчиком.
claude/1ade26d6…27.08–01.092 091Импорт выгрузок 1С, контрагенты, цены прайса в КП, переезд репо.
grok/019f5a94…13.0769Расширение ТЗ 1С требованием продаж/проводок (plan.md + логи).
22.5 МБ
размер тарбола
32
записи в архиве
4 + 1
сессии Claude + Grok
gitignore
вне репозитория

Почему вне git

Транскрипты содержат всё, что звучало в чате: пароли сервисных учёток, содержимое .env.prod, доступы к стенду. Поэтому файл добавлен в .gitignore и не должен попадать в репозиторий или пересылаться целиком. Выжимки решений (без сырых логов) живут в docs/PROGRESS.md, docs/QUESTIONS_YULIA.md и docs/ONEC_CONTRACTOR_BRIEF.html.