← Вернуться в KitchUp

Живой технический справочник

Как устроен KitchUp

Эта страница фиксирует текущее поведение, архитектуру и эксплуатационные решения. Её нужно обновлять вместе с кодом, когда меняется важная механика или инфраструктура.

01 · Обзор

Назначение и границы

KitchUp — домашний склад продуктов, список покупок и журнал расходников для техники. Один склад определяется введённым именем и доступен всем устройствам, использующим это имя.

Имя склада — не авторизация.

Пароля и полноценной учётной записи пока нет. Любой знающий имя склада может открыть и изменить его данные. Нельзя считать текущую схему подходящей для чувствительных или коммерческих данных пользователей.

02 · Система

Архитектура

Web / Androidnginx + HTTPSPython APISQLite
  • Клиент — статические HTML, CSS и JavaScript без frontend-фреймворка.
  • Android — Capacitor-оболочка с нативным сканером штрихкодов.
  • Backend — стандартная библиотека Python 3.11 и ThreadingHTTPServer.
  • Постоянное состояние — одна SQLite-база вне каталога релиза.
  • nginx завершает HTTPS и проксирует API на 127.0.0.1:3000.
  • Внешние интеграции: Open Food Facts, UPCitemdb и приватный Hugging Face TEI endpoint.

Основные точки входа: public/index.html, public/app.js, server.py и product_matcher.py.

03 · Состояние

Данные и хранение

ДанныеГде хранятсяОсобенности
Склады и товарыSQLiteРазделены по вычисляемому ключу имени склада
Покупки и историяSQLiteИстория фиксирует изменения состава и количества
Каталог штрихкодовSQLiteОбщий для всех складов, уменьшает внешние lookup
Ошибки распознаванияSQLiteПо складу: код, итоговая причина и структурированные попытки провайдеров
Метрики сопоставленияСуточные агрегаты SQLiteБез названий складов, товаров и штрихкодов
Устройства и расходникиSQLiteФото хранятся как проверенные data URL PNG/JPEG/WebP
Настройки интерфейсаlocalStorage браузераРежим списка и режим сопоставления привязаны к устройству

На production база находится в /var/lib/kitchen-storage/kitchen-storage.sqlite. Каталог приложения заменяется при деплое, каталог данных сохраняется. Резервировать нужно всю директорию /var/lib/kitchen-storage.

Только склад сторож получает стартовые продукты и устройства; остальные новые склады пустые.

04 · Основная механика

Запасы и покупки

  • Остаток можно менять вручную либо кнопками −/+.
  • Максимум товара растёт автоматически и используется как личная точка отсчёта.
  • Статус «заканчивается» появляется, когда текущий числовой остаток ниже 30% исторического максимума.
  • Ручная отметка «нужно купить» важнее автоматического бейджа и подсвечивает строку жёлтым.
  • Добавление в покупки не создаёт активные дубли одного складского товара.
  • Список отображается по категориям или единым алфавитным списком.
  • «Классический» режим показывает полный склад, «Быстрый» фокусируется на действиях.

05 · Сканирование

Штрихкоды и центральный каталог

  1. Сначала проверяется уже сохранённая связь штрихкода с товаром текущего склада.
  2. Затем проверяется общий каталог KitchUp.
  3. При промахе последовательно опрашиваются настроенные внешние провайдеры.
  4. Успешный внешний результат сохраняется в общий каталог и повторно API не расходует.
  5. Если товар не найден, пользователь может вручную привязать код или создать позицию.

В режиме «На склад +1» количество увеличивается. В режиме «Я в магазине» соответствующий пункт списка покупок отмечается купленным.

Сейчас узкое место — не чтение цифр камерой, а покрытие российских товаров внешними каталогами. Нативный сканер стабильно передаёт код, после чего поиск часто заканчивается статусом unknown.

Идемпотентность сканирования.

Клиент передаёт requestId; сервер резервирует действие, чтобы повторная доставка одного запроса не увеличила количество дважды.

Диагностика промахов

Каждое фактическое обращение к провайдеру сохраняется в catalog_lookup_attempts. Если итоговый товар не найден, дополнительно создаётся запись в barcode_scan_failures. Фиксируются провайдер, результат, нормализованный тип ошибки, HTTP-статус, задержка и безопасное сообщение без URL, ключей и токенов. В интерфейсе ошибки доступны в «История → Ошибки сканирования» только для текущего склада.

При запуске сервер восстанавливает отсутствующие записи barcode_scan_failures из прежних операций со статусом unknown. Такие записи имеют причину legacy_unknown и не содержат детализации провайдеров, которой ещё не было в старом журнале.

Нормализованные причины включают not_found, rate_limited, timeout, network_error, service_error, invalid_response и lookup_deferred. Такая схема позволяет позднее строить сводки частых неизвестных кодов и массовых сбоев по провайдерам.

Расширенный план покрытия российских товаров

  1. Накопить репрезентативный набор минимум из 50–100 российских GTIN, не ограничиваясь продуктами одного магазина.
  2. По журналу отделить настоящее not_found от лимитов, таймаутов и ошибок ответа.
  3. Прогнать один и тот же набор через Open Food Facts, UPCitemdb, Barcode Lookup, Go-UPC и доступные сервисы GS1 Russia; сравнить покрытие, корректность названий, задержку и цену найденного товара.
  4. Подключить лучший дополнительный источник третьим провайдером, сохранив каскад и центральный кэш KitchUp.
  5. Для российских GTIN 460–469 отдельно оценить официальный доступ GS1 Russia. Данные GS1 полезны для проверки идентичности, но не гарантируют удобное потребительское название каждого товара.
  6. Для массового использования разместить локальную реплику ежедневной выгрузки Open Food Facts, а платные API вызывать только при промахе.
  7. Неизвестный после всех источников товар подтверждается пользователем и пополняет собственный каталог. Следующий такой код не требует внешнего запроса.
  8. Использовать уже добавленные агрегаты дашборда: долю найденных по источникам, частые неизвестные GTIN, ошибки, p50/p95 latency и прирост собственного каталога.

Улучшения чтения повреждённых или бликующих кодов, A/B ML Kit/ZXing и коммерческие scanning SDK остаются в плане, но отложены: на текущем этапе камера читает цифры, поэтому они не устраняют основную причину промахов.

06 · Product matching

Обычное сопоставление и E5

Сопоставление запускается только для нового, ещё не привязанного штрихкода, если каталог уже вернул описание товара. Сохранённая связь штрихкода всегда имеет приоритет.

Общая нормализация

  • нижний регистр, Unicode NFKC и замена ё → е;
  • удаление пунктуации и фасовки вроде 800 г или 500 мл;
  • удаление отдельно полученных бренда и количества;
  • удаление слабых слов: «продукт», «крупа», «напиток»;
  • простые алиасы: «гречневая → гречка», «макаронный → макароны», «томатный → томаты».

Например, Гречневая крупа Увелка, 800 г при отдельно известном бренде превращается в гречка. Категория товара в оценке не участвует.

Обычный режим

Работает внутри KitchUp без внешней модели. Точное нормализованное совпадение получает 1.0. Для остальных кандидатов оценка складывается из:

  • 48% — покрытие слов существующего товара;
  • 22% — точность совпадения слов запроса;
  • 30% — косинусная близость трёхсимвольных фрагментов;
  • бонус 0.10, если все слова складского товара входят в запрос.
ОценкаРешение обычного режима
≥ 0.86auto — объединить автоматически
0.67–0.859suggest — вероятное совпадение
< 0.67different — разные товары

Режим E5

Модель intfloat/multilingual-e5-small работает в отдельном приватном Hugging Face TEI endpoint. Каждое нормализованное название превращается в embedding из 384 чисел, после чего KitchUp считает косинусную близость запроса со складскими товарами.

E5 лучше понимает разные формулировки одного смысла, но использует более строгие пороги:

ОценкаРешение E5
≥ 0.91auto
0.80–0.909suggest
< 0.80different

На текущем небольшом CPU endpoint production использует batch 8 и таймаут 45 секунд. Клиент создаёт отдельный HTTP opener для каждого inference-вызова и не следует redirect, чтобы токен не ушёл на другой сервер.

Защита от ложного объединения

  • Различия в числах и важных вариантах — жирность, цвет, «молотый/растворимый», «обычный/зелёный» — не допускают auto.
  • Если два лучших кандидата одновременно проходят auto и отличаются меньше чем на 0.04, результат понижается до suggest.
  • В ответ возвращаются пять лучших кандидатов.

Только auto привязывает новый штрихкод к существующему товару. При suggest и different сейчас создаётся отдельная позиция. Если E5 недоступен, реальное сканирование безопасно использует обычный режим; лаборатория сравнения показывает ошибку E5 без маскирующего fallback.

E5 пока не обучается на действиях пользователей.

Собственный каталог уменьшает внешние запросы, но не дообучает модель. Подтверждения suggest и накопление обучающих пар — отдельный будущий этап.

07 · Техника

Расходники и напоминания

Устройство содержит название, модель и фото шильдика. Расходник содержит название, артикул, фото, дату последней замены и пользовательский интервал в днях. Кнопка «Заменил» записывает текущую дату; следующая дата считается арифметически от интервала.

Сервис не определяет безопасный срок эксплуатации и не заменяет инструкцию производителя.

08 · Метрики

Аналитика и бонусные баллы

  • Согласие на журнал и улучшение каталога обязательно для входа, отмечено по умолчанию и передаётся как analyticsConsent: true в POST /api/session.
  • В псевдонимные события популярности не записываются имя склада, IP, фотографии и голосовые данные. Диагностический журнал хранит связь со складом и точный штрихкод, чтобы пользователь видел свои ошибки.
  • Сырой журнал событий и диагностические ошибки по умолчанию хранятся 90 дней; агрегаты сохраняются отдельно.
  • Публичный дашборд возвращает только общие показатели и не раскрывает отдельные склады.
  • Популярность показывается только при участии минимум трёх складов.
  • Один балл начисляется складу, который первым добавил новый подтверждённый внешний GTIN/EAN в глобальный каталог.

Метрики эффективности сопоставления

При фактическом сопоставлении нового штрихкода и при запуске лаборатории сервер увеличивает только суточные агрегаты в product_match_daily_stats. В таблице нет идентификатора склада, названия товара, штрихкода или исходного текста.

Дашборд показывает за 30 календарных дней:

  • число попыток сопоставления при сканировании и число проверок в лаборатории;
  • сколько товаров автоматически привязано к существующим и сколько создано отдельно;
  • распределение решений auto, suggest, different/none;
  • сколько раз E5 был запрошен, фактически обработал запрос или перешёл в fallback;
  • доступность E5 в лаборатории и среднюю оценку лучшего кандидата для фактически использованного режима.
Автообъединение — proxy эффективности, а не accuracy.

Пока пользователь не подтверждает и не отклоняет результат, система не знает, было ли объединение правильным. Для измерения точности нужен отдельный feedback-flow и метрики последующих исправлений.

Метрики внешних каталогов

За 30 дней дашборд показывает число обращений, found/not_found/error, долю найденных товаров и p50/p95 задержки в целом и отдельно по каждому провайдеру. Ошибки группируются по нормализованному типу. Неизвестный GTIN выводится в общей статистике только после неудачных сканирований минимум в трёх разных складах.

Обязательная галочка MVP является условием использования сервиса, но не заменяет юридически корректные политику, версионирование условий и механизм управления данными. До коммерческого использования нужны полноценные аккаунты, административная авторизация и антифрод.

09 · Environment

Ключевая конфигурация

HOST=127.0.0.1
PORT=3000
DATA_DIR=/var/lib/kitchen-storage

PRODUCT_CATALOG_PROVIDERS=openfoodfacts,upcitemdb
UPCITEMDB_API_KEY=
PRODUCT_CATALOG_DAILY_LOOKUP_LIMIT=100
PRODUCT_CATALOG_MINUTE_LOOKUP_LIMIT=6
PRODUCT_CATALOG_MISS_RETRY_SECONDS=3600

PRODUCT_MATCH_E5_ENDPOINT=
PRODUCT_MATCH_E5_TOKEN=
PRODUCT_MATCH_E5_TIMEOUT_SECONDS=45
PRODUCT_MATCH_E5_BATCH_SIZE=8
PRODUCT_MATCH_PREVIEW_MINUTE_LIMIT=10
PRODUCT_MATCH_PREVIEW_GLOBAL_MINUTE_LIMIT=30

ANALYTICS_EVENT_RETENTION_DAYS=90

Production-секреты хранятся только в /etc/kitchen-storage.env с правами 600. Нельзя помещать токены, API-ключи, пароли и приватные SSH-ключи в репозиторий, эту страницу или логи.

10 · Production

Развёртывание

  • Push в main или master запускает Bitbucket Pipeline.
  • Pipeline выполняет npm test, собирает архив без .git и каталога данных, затем передаёт его по SSH.
  • deploy/remote-deploy.sh заменяет только каталог приложения, обновляет unit/nginx и перезапускает kitchen-storage.
  • Главная страница, app.js и styles.css отдаются с обязательной перепроверкой кэша. Версия в URL ресурсов разрывает кэш при несовместимом обновлении HTML и JavaScript.
  • HTTPS обслуживает nginx с сертификатом Let's Encrypt; продление выполняет certbot timer.
  • WireGuard и KitchUp — независимые systemd-сервисы; текущий выделенный сервер KitchUp WireGuard не использует.

Unit: kitchen-storage.service. Рабочий каталог: /opt/kitchen-storage/app. Данные: /var/lib/kitchen-storage. Серверные логи: journalctl -u kitchen-storage.

11 · Mobile

Android

Capacitor использует ту же клиентскую кодовую базу и production API. Нативный плагин открывает сканер штрихкодов; web fallback использует доступные браузерные механизмы камеры.

Текущая Android-версия: 1.1, versionCode 2. Она передаёт обязательное согласие analyticsConsent при входе и совместима с актуальным API.

pnpm android:sync
pnpm android:open

Для debug APK нужны Node.js 22+, JDK 21 и Android SDK. Аккаунт Google Play для локальной установки не нужен. После изменения статических файлов Android-проект нужно синхронизировать и пересобрать отдельно от web-деплоя.

12 · Риски

Известные ограничения

  • Нет полноценной аутентификации и разграничения ролей.
  • Покрытие российских товаров Open Food Facts и UPCitemdb не подтверждено репрезентативным тестовым набором.
  • Результат suggest ещё нельзя подтвердить в отдельном UX — создаётся новая позиция.
  • E5 повторно рассчитывает embeddings кандидатов и зависит от внешнего endpoint.
  • Scale-to-zero экономит деньги, но первый запрос после простоя может уйти в fallback.
  • Фото хранятся внутри SQLite и при большом использовании заметно увеличат базу.
  • Голосовой ввод скрыт и не считается поддерживаемой production-функцией.
  • iOS-приложение и публикация в магазинах пока не подготовлены.

13 · Поддержка

Короткий runbook

systemctl status kitchen-storage
journalctl -u kitchen-storage --since "10 minutes ago"
nginx -t
systemctl status nginx
systemctl status certbot-renew.timer
  • Интерфейс не открывается: проверить nginx, затем kitchen-storage.
  • API возвращает ошибку: смотреть journal сервиса, не выводя секретные env.
  • E5 недоступен: проверить статус endpoint, timeout/batch и наличие переменных в процессе.
  • После деплоя исчезли данные: не создавать новую БД в каталоге приложения; проверить DATA_DIR.
  • Перед рискованным обновлением: сделать копию каталога данных и проверить восстановление.
Правило поддержки документации.

При изменении API, схемы данных, бизнес-правил, интеграций, безопасности, аналитики, mobile-сборки или deployment эта страница обновляется в том же коммите.