модуль tryon Flask · per-tenant JSON · IIFE-виджет по лекалу seo/widgets/reviews

Примерочная: детальный план модуля

Клонируем механику Looksy (реверс §1¾: конфиг, prewarm, fitting, polling, история, комплекты, лимиты), но на своих рельсах: серверный рендер + dashboard/tryon.py + blueprints/tryon.py + static/widgets/tryon.js. GPU — только через серверный прокси, ключ в env, дневные капы. Фото покупателей — за TTL, не в git, не в бэкапы вечно.

0. Цель, текущее состояние, желаемое

Цель

Магазин женской одежды вставляет одну строку в карточку товара → кнопка «Примерить онлайн» → фото покупателя → результат ~25–40 с + размер + «собери образ» + «Купить». Владелец видит в дашборде: примерки, конверсию в корзину, расход GPU-кредитов, топ-товары, логи.

Сейчас

Платформа: Flask/waitress на VPS, per-tenant JSON (data/<slug>/), модули через MODULE_ROUTE_PREFIXES, виджеты launcher.js/reviews.js + public JSON, тарифы free/pro/business/enterprise (без счётчиков использования). Модуля примерки нет.

Желаемое (MVP)

Модуль tryon: настройки кнопки, сниппет, журнал заданий, статистика, лимиты (магазин/день + юзер/день=10), stub-провайдер для E2E без GPU-ключа, FASHN-клиент за флагом. Пилот на 1 магазине.

1. Паритет с Looksy: функция → как у нас (+улучшения ★)

#Функция LooksyПримерочная (план)Улучшение ★
F1Одна строка вставки + shop-tokentryon.js data-config=/<slug>/widget/tryon.json, секрета в браузере нет★ конфиг без токена вообще (public JSON), origin-check
F2Кнопка сама находит галереюселекторы галереи в конфиге + fallback + ручной якорь data-tryon-anchor★ Tilda-T123 пресет из коробки
F3Согласие + гайд с good/bad фототексты из конфига (версионирование consent_version), примеры — URL из админки★ русский текст + 152-ФЗ формулировки by default
F4Загрузка фото (25 МБ, JPG/PNG/HEIC)лимит 8 МБ MVP, ресайз на клиенте (canvas, max 1600px) до отправки★ клиентский ресайз режет трафик и GPU-счёт в разы
F5Prewarm при открытии карточкинет в MVP (дорого); прогрев только после клика «Примерить»★ экономия холостых вызовов — осознанное отличие
F6Генерация ~24 с, pollingjob: queued→processing→succeeded/failed; polling 3 с, таймаут 300 с; Idempotency-Key★ очередь с приоритетом + дедупликация одинаковых (вещь×фото-хэш)
F7Сайзинг (person-size-v5)MVP: показываем размер/уверенность как вернул провайдер; свой сайзинг — фаза 2—
F8Ретрай (parent_generation_id), «ещё раз»цепочка попыток в логе; 1 бесплатный ретрай при техошибке, осознанный — за квоту★ правило в конфиге, видно в статистике
F9Луки сохраняются (history)«Мои луки» в localStorage + опционально серверная история 30 дней★ экспорт лука в корзину комплектом
F10Complete look / outfits v2фаза 2: подбор 4 товаров из catalog.json магазина★ комплекты из своего каталога (sync_catalog уже есть)
F11Результат: Купить / поделиться / скачать / оценкато же; share-ссылка с TTL; feedback 1–5 → лог★ «Купить» = deep-link с utm tryon
F12Лимиты: daily 10/юзер, пакеты магазинадва уровня: посетитель/день (конфиг) + магазин/день (тариф)★ мягкий стоп с текстом «завтра», а не 500-я
F13Удаление фото (72 ч, DELETE)TTL 72 ч + кнопка «удалить мои фото» + purge-cron★ авто-purge по cron, отчёт в админке
F14Аналитика: события воронки + Метрикасобытия в tryon_stats + опциональный reachGoal в Метрику магазина★ воронка из коробки: показ→клик→загрузка→генерация→корзина
F15Админка: брендинг, тексты, фиче-флаги, бюджет/<slug>/tryon: настройки / задания / статистика / затраты★ себестоимость в ₽ рядом с каждой цифрой
F16AI-продавец (ai-seller)вне скоупа (отдельный модуль позже)—
F17Видео-анимация (Pro)фаза 3, за флагом—

2. Архитектура

Магазин → tryon.js → Flask → провайдер → S3 → админка Сайт магазинаtryon.js (IIFE)кнопка·модалка·ресайз Flask /tryonconfig·jobs·statsdata/<slug>/tryon*.json Провайдер GPUstub → FASHN → …ключ только в env Хранилищеfoto: 72ч TTLлокально+cron purge Очередьпоток-воркеркапы·ретраи·дедуп Админка /tryonлог·стата·затраты+ Метрика reachGoal Правила, скопированные из реверса Idempotency-Key · parent_generation_id · presigned-URL 1ч · события воронки · мягкие лимиты Файлы: dashboard/tryon.py · blueprints/tryon.py · templates/tryon.html · static/widgets/tryon.js Регистрация: client_provision (labels/groups/prefixes) · blueprints/__init__ · base.html (меню «Ещё») · limits()

3. Данные (per-tenant JSON, flock+atomic как widgets.py)

tryon_config.json

  • button: text/color/size/radius/icon, позиция
  • gallery_selectors[], ручной якорь
  • fits: категории, consent_version, тексты гайда/ошибок
  • limits: visitor_per_day=10, free_retry_on_tech_error=1
  • features: sizing, share, history, complete_look(off MVP)
  • metrika_counter (опц.), ym goals on/off

tryon_jobs.jsonl + tryon_stats.json

  • job: id, created_at, visitor_hash, product(external_id,title,price,url,img), consent_version, mode, parent_id, status, provider, cost_usd, timings, error_code, rating
  • stats/day: shows/clicks/uploads/started/succeeded/failed/retries/cart_clicks, spend_usd, spend_rub
  • visitor_hash = sha256(ip+ua+day) — без PII в логах

Фото и их жизнь

  • data/<slug>/tryon_uploads/<job>/: src.webp, result.webp — TTL 72 ч, purge-cron ежедневно
  • В git/bэкапы — не попадают (gitignore + exclude)
  • Публичная отдача результата — подписанным URL с TTL 1 ч (как у Looksy)

4. API (blueprint tryon, guard как у widgets)

Метод и путьДоступНазначение
GET /tryonadmin/client_adminадминка: настройки + статистика + журнал + сниппет
POST /tryon/saveadmin + CSRF, ≤64 KiBсохранить конфиг (валидация как validate_config)
GET /widget/tryon.jsonpublic, CORS *, ETag, 60sконфиг для лоадера (без секретов)
POST /widget/tryon/jobspublic, origin-check, ≤8 МБсоздать задание: фото + товар + consent; Idempotency-Key; капы
GET /widget/tryon/jobs/<id>public, signed tokenстатус для polling (3 с / 300 с)
POST /widget/tryon/jobs/<id>/feedbackpublicоценка 1–5 + комментарий
POST /widget/tryon/eventspublic, 202события воронки (батч, как у них)
GET /widget/tryon/historypublic, cookie-visitor«мои луки» (30 дней)
POST /tryon/purgecron/adminчистка фото старше TTL + отчёт

Провайдер: TRYON_PROVIDER=stub|fashn, ключ FASHN_API_KEY в env; stub возвращает демо-картинку через ~5 с (E2E без денег). Лимит магазина/день из limits(): free 10 / pro 100 / business 500 / enterprise 9999 (ключ tryon_shop_daily).

5. Лоадер static/widgets/tryon.js (IIFE, как launcher.js)

  • Сниппет: <script defer src="https://rayprism.ru/static/widgets/tryon.js" data-config="https://rayprism.ru/<slug>/widget/tryon.json"> — credentials omit, abort 8 с, MIME-check.
  • Поиск галереи: селекторы из конфига → fallback (крупнейшее img в main) → data-tryon-anchor; MutationObserver на смену варианта товара (как их 140 мс).
  • Модалка (Shadow DOM): согласие (версия!) → гайд → upload (canvas-ресайз ≤1600px, ≤8 МБ) → прогресс с фактами → результат (Купить/share/скачать/оценка/«ещё раз») → «мои луки».
  • События воронки батчем + опционально ym(counter,'reachGoal',...).
  • Вес цели: ≤40 КБ gzip, без зависимостей.

6. Админка /<slug>/tryon (templates/tryon.html)

Вкладка «Настройки»

  • кнопка (текст/цвет/размер), селекторы галереи, Tilda-пресет
  • тексты согласия/гайда/ошибок, примеры фото (URL)
  • лимиты: юзер/день, бесплатный ретрай; фиче-флаги; счётчик Метрики
  • сниппет вставки + кнопка «проверить public JSON»

Вкладка «Статистика и затраты»

  • воронка 7 шагов с конверсиями; период 7/30 дней
  • расход: генерации × себестоимость → ₽ и $, остаток дневного лимита
  • топ-товары по примеркам/кликам «Купить»; средние оценки
  • журнал заданий: статусы, ошибки (коды как у них), ретраи, purge-отчёт

7. Биллинг и защита маржи

  • Тарифный ключ tryon_shop_daily в limits(); превышение = мягкий стоп («завтра») + событие в лог.
  • Себестоимость считается при каждом succeeded: cost_usd от провайдера × курс (конфиг платформы) → spend_rub в статистике.
  • Дедуп: хэш (visitor+photo+product) 24 ч — повторный одинаковый запрос отдаёт кэш, не GPU.
  • Анти-абьюз: visitor/day из конфига, origin-check, размер/тип файла, rate-limit на создание заданий, модерация-флаг от провайдера → отдельная ошибка.
  • Пилотный биллинг: 30 бесплатно → пакеты 100/400/1000 (цены — решение ниже).

8. Фазы и приёмка

Фаза 0 — фундамент (эта сессия)

  • tryon.py + blueprints/tryon.py + tryon.html + tryon.js + регистрация (provision/blueprints/menu/limits)
  • stub-провайдер, E2E: кнопка→модалка→загрузка→статус→демо-результат
  • Приёмка: py_compile, route_inventory diff = +ожидаемые, ручной прогон на тестовом тенанте

Фаза 1 — живой GPU

  • FASHN-клиент (run→poll→webhook), воркер, дедуп, purge-cron
  • Приёмка: 5 реальных генераций, wall-time, confront с HAR-замерами (24 с)

Фаза 2 — деньги

Риски платформы (из аудита): файлы от root → chown; restart после деплоя (Jinja-кэш); POST только без /mail/; thread-local active() — воркер вне request-контекста работает только с файлами.

9. Твои решения (выбери → «Отправить»)

GPU-ПРОВАЙДЕР ФАЗЫ 1
ХРАНЕНИЕ ФОТО
ПИЛОТ
ПИЛОТНЫЙ БИЛЛИНГ