01 · Обзор
Назначение и границы
KitchUp — домашний склад продуктов, список покупок и журнал расходников для техники. Один склад определяется введённым именем и доступен всем устройствам, использующим это имя.
Пароля и полноценной учётной записи пока нет. Любой знающий имя склада может открыть и изменить его данные. Нельзя считать текущую схему подходящей для чувствительных или коммерческих данных пользователей.
02 · Система
Архитектура
- Клиент — статические 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 · Сканирование
Штрихкоды и центральный каталог
- Сначала проверяется уже сохранённая связь штрихкода с товаром текущего склада.
- Затем проверяется общий каталог KitchUp.
- При промахе последовательно опрашиваются настроенные внешние провайдеры.
- Успешный внешний результат сохраняется в общий каталог и повторно API не расходует.
- Если товар не найден, пользователь может вручную привязать код или создать позицию.
В режиме «На склад +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. Такая схема позволяет позднее строить сводки частых неизвестных кодов и массовых сбоев по провайдерам.
Расширенный план покрытия российских товаров
- Накопить репрезентативный набор минимум из 50–100 российских GTIN, не ограничиваясь продуктами одного магазина.
- По журналу отделить настоящее
not_foundот лимитов, таймаутов и ошибок ответа. - Прогнать один и тот же набор через Open Food Facts, UPCitemdb, Barcode Lookup, Go-UPC и доступные сервисы GS1 Russia; сравнить покрытие, корректность названий, задержку и цену найденного товара.
- Подключить лучший дополнительный источник третьим провайдером, сохранив каскад и центральный кэш KitchUp.
- Для российских GTIN 460–469 отдельно оценить официальный доступ GS1 Russia. Данные GS1 полезны для проверки идентичности, но не гарантируют удобное потребительское название каждого товара.
- Для массового использования разместить локальную реплику ежедневной выгрузки Open Food Facts, а платные API вызывать только при промахе.
- Неизвестный после всех источников товар подтверждается пользователем и пополняет собственный каталог. Следующий такой код не требует внешнего запроса.
- Использовать уже добавленные агрегаты дашборда: долю найденных по источникам, частые неизвестные 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.86 | auto — объединить автоматически |
0.67–0.859 | suggest — вероятное совпадение |
< 0.67 | different — разные товары |
Режим E5
Модель intfloat/multilingual-e5-small работает в отдельном приватном Hugging Face TEI endpoint. Каждое нормализованное название превращается в embedding из 384 чисел, после чего KitchUp считает косинусную близость запроса со складскими товарами.
E5 лучше понимает разные формулировки одного смысла, но использует более строгие пороги:
| Оценка | Решение E5 |
|---|---|
≥ 0.91 | auto |
0.80–0.909 | suggest |
< 0.80 | different |
На текущем небольшом CPU endpoint production использует batch 8 и таймаут 45 секунд. Клиент создаёт отдельный HTTP opener для каждого inference-вызова и не следует redirect, чтобы токен не ушёл на другой сервер.
Защита от ложного объединения
- Различия в числах и важных вариантах — жирность, цвет, «молотый/растворимый», «обычный/зелёный» — не допускают
auto. - Если два лучших кандидата одновременно проходят
autoи отличаются меньше чем на 0.04, результат понижается доsuggest. - В ответ возвращаются пять лучших кандидатов.
Только auto привязывает новый штрихкод к существующему товару. При suggest и different сейчас создаётся отдельная позиция. Если E5 недоступен, реальное сканирование безопасно использует обычный режим; лаборатория сравнения показывает ошибку E5 без маскирующего fallback.
Собственный каталог уменьшает внешние запросы, но не дообучает модель. Подтверждения suggest и накопление обучающих пар — отдельный будущий этап.
07 · Техника
Расходники и напоминания
Устройство содержит название, модель и фото шильдика. Расходник содержит название, артикул, фото, дату последней замены и пользовательский интервал в днях. Кнопка «Заменил» записывает текущую дату; следующая дата считается арифметически от интервала.
Сервис не определяет безопасный срок эксплуатации и не заменяет инструкцию производителя.
08 · Метрики
Аналитика и бонусные баллы
- Согласие на журнал и улучшение каталога обязательно для входа, отмечено по умолчанию и передаётся как
analyticsConsent: trueвPOST /api/session. - В псевдонимные события популярности не записываются имя склада, IP, фотографии и голосовые данные. Диагностический журнал хранит связь со складом и точный штрихкод, чтобы пользователь видел свои ошибки.
- Сырой журнал событий и диагностические ошибки по умолчанию хранятся 90 дней; агрегаты сохраняются отдельно.
- Публичный дашборд возвращает только общие показатели и не раскрывает отдельные склады.
- Популярность показывается только при участии минимум трёх складов.
- Один балл начисляется складу, который первым добавил новый подтверждённый внешний GTIN/EAN в глобальный каталог.
Метрики эффективности сопоставления
При фактическом сопоставлении нового штрихкода и при запуске лаборатории сервер увеличивает только суточные агрегаты в product_match_daily_stats. В таблице нет идентификатора склада, названия товара, штрихкода или исходного текста.
Дашборд показывает за 30 календарных дней:
- число попыток сопоставления при сканировании и число проверок в лаборатории;
- сколько товаров автоматически привязано к существующим и сколько создано отдельно;
- распределение решений
auto,suggest,different/none; - сколько раз E5 был запрошен, фактически обработал запрос или перешёл в fallback;
- доступность E5 в лаборатории и среднюю оценку лучшего кандидата для фактически использованного режима.
Пока пользователь не подтверждает и не отклоняет результат, система не знает, было ли объединение правильным. Для измерения точности нужен отдельный 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 эта страница обновляется в том же коммите.