Перейти к основному содержимому

CLAUDE.md — GWPTD Analytics (Laravel)

СНАЧАЛА ПРОЧИТАЙ AGENTS.md — жёсткие правила разработки (MCP-first, пайплайн local→s3→s2-по-OK, канон «всё через 1С», режим Codex). Иерархия системы описана в docs/README.md, раздел «Система сверху вниз»: стратегический → функциональный → описательный. Этот CLAUDE.md — то, что нужно именно при работе в этой сессии: legacy-приложение mp.hyp.ru, таблицы, инфраструктура; общий контекст и правила проекта — в AGENTS.md. Старт сессии: AGENTS.md → mcp__vestige__searchmcp__mcp-gwptd__rag_search → этот файл. Если ты агент сотрудника и здесь впервые — for-ai-agents/START-HERE.md: куда идти за ответом, куда класть правку, чего не делать без владельца.

🧠 Память и поиск информации

При любом вопросе — сначала смотреть в память

ОБЯЗАТЕЛЬНО перед ответом на вопрос о проекте, задаче или технической теме:

  1. Искать в MCP-памяти (mcp__vestige__search) — там могут быть контекст, решения, заметки из прошлых сессий
  2. Искать в кодовой базе (mcp__mcp-gwptd__rag_search) — для вопросов о коде
Порядок: vestige search → rag_search → ответ

Интернет — только с одобрения пользователя

Если информации в памяти и кодовой базе недостаточно, спросить пользователя:

«Не нашёл в памяти. Хочешь, поищу в интернете через Firecrawl?»

Только после явного «да» — использовать mcp__firecrawl-mcp__firecrawl_search или mcp__firecrawl-mcp__firecrawl_scrape.

Сохранять в память автоматически

Сохранять в mcp__vestige__smart_ingest без запроса когда:

  • Найдено важное решение или баг-фикс
  • Принято архитектурное решение
  • Пользователь явно просит запомнить
  • Завершена крупная задача

🎯 Статус: new-first рефакторинг (2026-07)

Целевая архитектура — /new (Filament) + Python Data API + kernel ETL (marketplace-collector-v3/kernel/etl/gwptd_kernel.*). Старый PHP-слой, описанный ниже в этом файле (RatingService, TurnoverCalculationService, mp:collect, legacy *Resource/*Page), заморожен — не развивается, помечен @deprecated + гейтом OldInterfaceOnly (недостижим в /new), идёт на удаление после контрольного окна сверки. Ключевые фиксы бизнес-логики (дубль Ozon, turnover realized, вычет возвратов net-of-returns) сделаны на ветке refactor/remediation-kernel-2026-07в kernel ETL, не в PHP ниже. Подробности: docs/remediation/README.md (стратегия) и журнал волн выведен в архив docs/_archive/docs-archive-2026-08-history.zip. См. также README.md — актуальная навигация по проекту.

P1B-5 reader coherence реализован в Data API как dark/default-off контракт: синхронные /api/v1/*-роуты при DATA_API_READ_COHERENCE_MODE=observe|enforce читают все свои SQL через один read-only REPEATABLE READ ... WITH CONSISTENT SNAPSHOT; open/use/rollback/close выполняются в одном AnyIO worker thread. Активация enforce остаётся отдельным reviewed rollout gate.

Параллельно 05.08.2026 подготовлен не развёрнутый контур независимого сбора и переразбора: immutable gwptd_raw → immutable generation в gwptd_stage → отдельная activation. Raw collector, parser и promoter имеют разные exact DB grants; S20/current 1C/future 1C описаны SourceProfile. Пока не применены migration/credentials/cron и не обкатан на реальных S3 generations первый candidate-only stage→core publisher current 1C (publish_onec_product_listing.py), штатный next_collector.py остаётся совмещённым. Publisher требует synchronized set из трёх active sealed generations product master/description/barcode, пишет generation-scoped state одной candidate-транзакцией, защищает durable article↔UUID/barcode bindings и даже с --execute не активирует readers. Канон и rollout gates: docs/architecture/INDEPENDENT-SOURCE-PIPELINE.md. Raw runtime читает только сгенерированные parser-free contracts в specs/acquisition/; правка parser outputs/target tables не должна менять их. Его Python acquisition class находится в spec_runtime/runner.py, а старый combined parser вынесен в лениво импортируемый spec_runtime/parsed_runner.py. Candidate publisher и отдельный promoter прошли локальный end-to-end smoke на одноразовом MySQL 8 под собственными exact-grant accounts; это не является production rollout или разрешением на reader switch. Единый словарь новых полей и обязательные database comments: docs/architecture/CANONICAL-FIELD-NAMING.md.

Что это

Аналитический дашборд Global Watch Parts Trading (GWPTD) — продажи на маркетплейсах. Переписан с Python (FastAPI + Streamlit) на Laravel 12 + Filament 3. Этот файл описывает legacy /admin-слой (заморожен, см. блок статуса выше) и общую Laravel-инфраструктуру (деплой, БД, авторизация), актуальную для обоих интерфейсов.

Graphiti group_id: lamoda Оригинальный проект: /Users/antonnozdrin/Projects/MISHA/mp_shipment/

Стек

  • Laravel 12 + Filament 3 (admin dashboard)
  • PHP 8.4/8.5, PhpSpreadsheet (xlsx), Guzzle (HTTP)
  • MySQL 8.0 — Hetzner 10.8.0.1:3306 (VPN), база lamoda_reports
  • Google Drive API — синхронизация xlsx-отчётов
  • SSH-туннельssh -f -N -L 3307:127.0.0.1:3306 root@10.8.0.1

Запуск

# SSH-туннель
ssh -f -N -L 3307:127.0.0.1:3306 root@10.8.0.1

# Миграции
php artisan migrate

# Dev-сервер
php artisan serve --port=8501

# Дашборд: http://localhost:8501/admin (legacy) или /new — по config('gwptd.interface_variant'): new → /new, иначе /admin

# Scheduler (dev)
php artisan schedule:work

Artisan-команды

⚠️ Заморожено: mp:collect и все команды ниже — старый PHP-коллектор (/admin-слой). Новый сбор идёт через marketplace-collector-v3/ (Python) → gwptd_intake → kernel ETL.

КомандаОписание
php artisan rate:fetchКурс USD/RUB от ЦБ
php artisan sync:supplierКаталог gwptd.com → supplier_catalog
php artisan sync:gdrive --importGoogle Drive → xlsx → import
php artisan import:reports [path] [--force]Импорт xlsx (--force для переимпорта)
php artisan mp:collectСбор stocks + orders (default)
php artisan mp:collect --storageТолько хранение и приёмка
php artisan mp:collect --promotionsТолько промоакции и реклама
php artisan mp:collect --allВсё: stocks, orders, finances, returns, prices, analytics, storage, promotions
php artisan mp:collect --finances --returnsТолько финансы + возвраты
php artisan mp:collect --pricesТолько цены каталога
php artisan mp:collect --analyticsТолько аналитика воронки (views→cart→orders→buyouts)
php artisan mp:collect --mp=ozon --forceОдин МП, принудительно
php artisan turnover:calculate [date]Расчёт оборачиваемости из API (--mp=N, --backfill=30)
php artisan rating:calculate [YYYY-MM]Расчёт рейтинга (--all, --all-mp, --mp=N)
php artisan shipment:calculate --mp=NAMEРасчёт отгрузки (--save, --safety=N, --month=YYYY-MM)

Структура

app/
├── Console/Commands/ # 32 Artisan-команды (ETL, sync/backfill по МП, shipment, turnover, rating)
├── Filament/
│ ├── Pages/ # 66 страниц: 54 *ApiPage (новый контур /new) + 12 legacy
│ └── Widgets/ # 2 виджета (stats + chart)
├── Http/Controllers/Api/ # 9 REST контроллеров (42 endpoints в routes/api.php, включая /health)
├── Models/ # 32 Eloquent-модели
├── Services/ # 13 сервисов (+ подпакеты Marketplace/Shipment/DataApi/Access/SpecPlatform)
└── Providers/Filament/ # AdminPanelProvider

config/gwptd.php # Бизнес-конфиг (сезонность, формулы, API)
routes/api.php # REST API маршруты
routes/console.php # Scheduler (cron)

Сервисы (app/Services/)

СервисНазначение
ExchangeRateServiceКурс USD/RUB от ЦБ (кэш 1 час, fallback 96.0)
CostPriceServiceСебестоимость: (price_usd + $3) × 1.47 × курс
BitmapServiceBitmap-экспозиция: 31 бит/месяц, OR-идемпотентно
RatingServiceРейтинг = Profit / (Exposure × CostPrice × Seasonality)
SupplierApiServicegwptd.com JSON API → supplier_catalog
ReportParserServiceПарсинг xlsx: 4 типа (Sales/Stock/Turnover/Weekly), auto header detection
GoogleDriveServiceСинхронизация отчётов с Google Drive
ImportServiceОркестратор импорта xlsx с дедупликацией (--force для переимпорта)
TurnoverCalculationServiceРасчёт оборачиваемости из API (28-day rolling window)
Shipment/ShipmentDataServiceАгрегация данных для расчёта отгрузки (stocks, sales, ratings)
Shipment/ShipmentCalculatorЧистый алгоритм: forecast→need→ship с лимитами
Shipment/ShipmentServiceОркестратор: calculate→save→confirm→export

API Endpoints (/api/*)

⚠️ LEGACY Laravel REST (заморожен). Целевой контур — Python Data API /api/v1/* (см. docs/data-api-endpoint-matrix.md).

МетодURLОписание
GET/api/overviewСводка: модели, выручка
GET/api/filtersБренды, категории, месяцы
GET/api/usd-rateКурс USD/RUB
GET/api/salesПродажи с фильтрацией
GET/api/sales/dailyПродажи по дням
GET/api/sales/model/{key}Продажи модели
GET/api/stockОстатки на маркетплейсе
GET/api/stock/datesДаты stock-отчётов
GET/api/exposureBitmap-экспозиция
GET/api/turnoverОборачиваемость
GET/api/ratingРейтинг за месяц (?marketplace_id=N, ?month=YYYY-MM)
POST/api/rating/calculateЗапуск расчёта рейтинга (body: month, marketplace_id)
GET/api/rating/seasonalityКоэффициенты сезонности
GET/api/rating/topТоп моделей (?marketplace_id=N, ?month=YYYY-MM)
GET/api/rating/history/{key}История рейтинга модели (?marketplace_id=N)
GET/api/supplier-stockПрайс поставщика
GET/api/supplier-catalogКаталог 1С
POST/api/supplier-catalog/syncСинхронизация каталога
GET/api/marketplacesСписок маркетплейсов
POST/api/syncGoogle Drive + импорт
GET/api/import-logЛог импорта
GET/api/warehousesАктивные склады (?marketplace_id=N)
POST/api/warehouse-limits/importИмпорт лимитов из Excel (multipart)
GET/api/warehouse-limits/templateСкачать шаблон Excel
POST/api/shipments/calculateРасчёт отгрузки
GET/api/shipmentsСписок batch-ей (?marketplace_id=N)
GET/api/shipments/{batchId}Детали batch
PATCH/api/shipments/{batchId}/confirmПодтверждение
GET/api/shipments/{batchId}/exportСкачать Excel

MySQL-таблицы

⚠️ Legacy lamoda_reports. Ниже перечислены таблицы СТАРОЙ БД замороженного контура. Актуальная модель данных — gwptd_kernel (см. docs/domain/data-model/): там dim_marketplace = 9 строк, и mp7 в kernel ОТСУТСТВУЕТ.

  • model_dict — словарь моделей (VARBINARY key)
  • marketplace_dict — 13 маркетплейсов (Lamoda = 9) — legacy-справочник lamoda_reports; в актуальном kernel — gwptd_kernel.dim_marketplace (9 строк, без mp7, см. docs/domain/data-model/)
  • supplier_stock — прайс поставщика (USD)
  • supplier_catalog — каталог из 1С (gwptd.com API)
  • sales — дедуплицированные продажи (+ price_original/discounted, coupon, loyalty, is_promo, brand, category)
  • sales_artikuly — артикулы из xlsx (55 cols: gender, EAN, collection, sales 7d/14d/28d/180d, turnover, stock breakdown, refusal %)
  • stock_db — stock-отчёты (42 cols: ABC, TO grade, OOS, coverage, NMV, RRR, ASP, speed, 7d/14d metrics)
  • lamoda_sku_map — маппинг Product SKU ↔ Seller SKU (1243 записей)
  • lamoda_weekly_kpi — еженедельные KPI по брендам (revenue, commission, sold/shipped, stock, site metrics)
  • model_exposure_month — bitmap-экспозиция
  • turnover — оборачиваемость
  • rating_history — снимки рейтингов
  • import_log — дедупликация файлов
  • mp_stocks_daily — остатки маркетплейсов по дням
  • mp_orders_daily — заказы маркетплейсов по дням
  • mp_finances — финансовые транзакции (WB reportDetail, Ozon transactions; unique: marketplace_id + finance_date + transaction_id)
  • mp_returns — возвраты (WB sales R-prefix, Ozon /v1/returns, YM returns; unique: marketplace_id + return_id + offer_id)
  • mp_prices_daily — ежедневные снимки цен каталога (WB, Ozon, YM; unique: marketplace_id + collected_date + offer_id)
  • mp_analytics_daily — воронка трафика (views, cart_adds, orders, buyouts, conversions; unique: marketplace_id + metric_date + offer_id)
  • mp_storage_costs — расходы на хранение, приёмку, штрафы (WB paid_storage + acceptance; unique: marketplace_id + cost_date + offer_id + warehouse_name + cost_type)
  • mp_promotions — рекламные кампании и промоакции (WB adverts, Ozon actions, YM promos; unique: marketplace_id + promo_id + collected_date)
  • mp_turnover_daily — расчётная оборачиваемость (28-day rolling window, unique: marketplace_id + calc_date + offer_id; turnover_28d = avg_stock*28/sales)
  • mp_collection_log — лог сбора (task_type ENUM: stocks, orders, finances, returns, prices, analytics, storage, promotions)
  • shipments — расчёт отгрузок (batch_id, forecast, need, ship_qty; status: draft→confirmed→shipped)
  • warehouse_limits — лимиты складов (qty, rub, is_active)

Бизнес-логика

  • Рейтинг = Profit / (Exposure × CostPrice × Seasonality)
  • Себестоимость = (price_usd + $3) × 1.47 × курс ЦБ
  • Bitmap = 7 бит назад от даты stock-отчёта, OR-идемпотентно
  • Сезонность = 12 фиксированных коэфф. (см. config/gwptd.php)
  • Отгрузка = forecast(ceil(sales/season_last×season_curr)) − mp_stock + safety, ≤ supplier_stock, по рейтингу DESC
  • LAMODA_MARKETPLACE_ID = 9

Marketplace Collector (MP API) — legacy, заморожен

⚠️ Описывает старый PHP/Python-коллектор (/admin-слой, lamoda_reports.*). Новый контур — marketplace-collector-v3/ (см. docs/README.md, уровень блоков) → gwptd_intake → kernel ETL.

4 маркетплейса собираются через единый MarketplaceCollectorService:

МаркетплейсAPI Servicemarketplace_id
WildberriesWildberriesApiService1 (FBO), 2 (FBS)
OzonOzonApiService3 (FBS), 4 (rFBS), 5 (FBO)
Yandex MarketYandexMarketApiService6 (FBS), 7 (Express), 8 (FBY)
LamodaLamodaApiService9

Данные → mp_stocks_daily, mp_orders_daily, mp_finances, mp_returns, mp_prices_daily, mp_analytics_daily, mp_storage_costs, mp_promotions, mp_collection_log.

8 типов данных: stocks, orders, finances, returns, prices, analytics, storage, promotions. Все через MarketplaceCollectorService. upsertStocks/upsertOrders/upsertFinances/upsertReturns/upsertPrices/upsertAnalytics/upsertStorage/upsertPromotions используют $row['marketplace_id'] если есть, иначе дефолтный $mpId.

Wildberries FBO/FBS разделение:

  • FBO (MP#1) — Statistics API (statistics-api.wildberries.ru):
    • Stocks: GET /api/v1/supplier/stocks — остатки на складах WB
    • Sales: GET /api/v1/supplier/sales + Orders: GET /api/v1/supplier/orders
  • FBS (MP#2) — Marketplace API v3 (marketplace-api.wildberries.ru):
    • Warehouses: GET /api/v3/warehouses → seller FBS warehouses (deliveryType=1)
    • SKUs: POST content-api /content/v2/get/cards/list → barcodes из каталога
    • Stocks: POST /api/v3/stocks/{warehouseId} с SKU-баркодами
    • Orders: GET /api/v3/orders?limit=1000&next=0&dateFrom=UNIX_TS
    • Цены в копейках (делим на 100), артикул в поле article (не supplierArticle)

Ozon FBS/FBO разделение:

  • Stocks: /v4/product/info/stocks → type=fbs → MP#3, type=fbo → MP#5
  • Orders FBS: /v3/posting/fbs/list → MP#3
  • Orders FBO: /v3/posting/fbo/list → MP#5
  • Analytics: /v1/analytics/data → агрегат, идёт как MP#3
  • Warehouse stocks: /v2/analytics/stock_on_warehouses → MP#5

Yandex Market FBS/FBY разделение:

  • 2 кампании в конфиге: fbs=22027612, fby=61824486
  • Stocks/Orders per-campaign → fbs → MP#6, fby → MP#8
  • FBY orders may be 0 if no FBY fulfillment orders in period

Finance endpoints (finances):

  • WB: GET statistics-api/api/v5/supplier/reportDetailByPeriod — rrdid cursor, limit=100000
  • Ozon: POST /v3/finance/transaction/list — page-based, 7-day window
  • YM: нет API (только async united-netting reports)

Return endpoints (returns):

  • WB: GET statistics-api/api/v1/supplier/sales → filter saleID prefix 'R'
  • Ozon: POST /v1/returns/list — last_id + has_next, limit=500
  • YM: GET /v2/campaigns/{id}/returns — per campaign, page_token pagination

Price endpoints (prices):

  • WB: GET discounts-prices-api/api/v2/list/goods/filter — limit+offset, kopecks÷100
  • Ozon: POST /v5/product/info/prices — cursor broken! 2-step: first 1000 + remaining by offer_id
  • YM: POST /v2/businesses/{id}/offer-prices — business-level, page_token pagination
  • Lamoda: no API (probe confirmed)

Analytics endpoints (analytics):

  • WB: POST seller-analytics-api/api/analytics/v3/sales-funnel/products — 3 req/min rate limit! 20s sleep between batches, nmIds=[] for all products
  • Ozon: POST /v1/analytics/data — expanded 7 metrics (hits_view, hits_tocart, session_view, revenue, ordered_units, returns, cancellations), manual retry for 429
  • YM: POST /v2/reports/shows-sales/generate → async poll → download xlsx (7000+ rows), openpyxl/PhpSpreadsheet parser
  • Lamoda: no API

Storage endpoints (storage):

  • WB: GET seller-analytics-api/api/v1/paid_storage — async 3-step (create task → poll → download), 15K+ rows/week. Rate limit: 3 req/min
  • WB: GET seller-analytics-api/api/v1/acceptance_report — async 3-step, acceptance fees (0 rows if no acceptance)
  • Ozon: POST /v3/finance/transaction/list — filtered by operation_type=['OperationMarketplaceServiceStorage']. Daily aggregate only (no per-SKU breakdown, items=[]). ~30 rows/month, ~-14 RUB/day
  • YM: POST /v2/reports/united-marketplace-services/generate — async report (generate → poll → download xlsx). Sheet "Хранение невыкупов и возвратов" (return/non-pickup storage). Rate limit: 100 req/hour. ~49 rows/month. XLSX has dual-row headers (Row 0=group, Row 1=columns)
  • Lamoda: no API

Promotions endpoints (promotions):

  • WB: GET advert-api.wildberries.ru/adv/v1/promotion/count → all campaigns by type/status. POST /adv/v2/fullstats → per-campaign stats (views, clicks, orders, spend). Rate limit: 3 req/min, batch 100 IDs, 20s sleep. GET /adv/v1/balance → ad account balance
  • Ozon: GET /v1/actions → list of promotions with participation status, product counts
  • YM: POST /v2/businesses/{id}/promos → promos with mechanicsInfo, assortmentInfo, pagination
  • Lamoda: no API

Два коллектора (Python + PHP) — оба ЗАМОРОЖЕНЫ (legacy):

  • Python-коллектор: /opt/marketplace-collector/ на Hetzner (standalone, прямой MySQL) — заморожен
  • PHP-коллектор: php artisan mp:collect (Laravel, через Docker) — заморожен
  • ⚠️ Единственный развиваемый контур — marketplace-collector-v3. Изменения в API-логике вносить ТОЛЬКО туда.

Оба коллектора поддерживают 8 типов данных:

  • --stocks / --orders — по умолчанию (backward compatible)
  • --finances — финансовые транзакции (WB reportDetail, Ozon v3/transaction/list)
  • --returns — возвраты (WB sales R-prefix, Ozon /v1/returns/list, YM /v2/campaigns/returns)
  • --prices — ежедневные цены каталога (WB, Ozon, YM)
  • --analytics — воронка трафика (views, cart_adds, orders, buyouts, conversions; WB, Ozon, YM)
  • --storage — расходы на хранение и приёмку (WB paid_storage + acceptance async 3-step, Ozon finance filter, YM async xlsx report)
  • --promotions — промоакции и рекламные кампании (WB advert-api fullstats + balance, Ozon /v1/actions, YM /v2/businesses/promos)
  • --all — все 8 типов вместе

Расписание сбора (cron). Описанный выше PHP/Laravel schedule — исторический legacy-контур и не запускается для S3 Next. Единственный действующий источник истины — marketplace-collector-v3/scripts/crontab.s3-next.txt; deployment проверяет его побайтное совпадение с root crontab. На текущем S3 Next среди ключевых slots: reference onec в 04:30 UTC, onec-stock в 04:50 UTC, группы collector'ов с 05:00 UTC и kernel build_all в 09:15 UTC. Для точного набора endpoint'ов читать generated schedule, не менять cron вручную и не запускать legacy mp:collect ради Next-проверок.

Warehouse Limits (Лимиты складов)

Таблица warehouse_limits — лимиты отгрузок по складам:

КолонкаТипОписание
marketplace_idtinyint unsigned FKМаркетплейс
warehouse_namevarchar(255)Название склада из API
limit_qtyint unsigned NULLЛимит штук (NULL = без лимита)
limit_rubdecimal(12,2) NULLЛимит рублей (NULL = без лимита)
notestext NULLДоп. ограничения текстом
is_activebooleanВкл/выкл склад
  • Модель: app/Models/WarehouseLimit.php
  • Filament CRUD: app/Filament/Resources/WarehouseLimitResource.php
  • Миграция: 2026_02_11_000000_create_warehouse_limits_table.php
  • 118 складов найдено (WB=70, Ozon=43+3, Yandex=5)
  • Excel шаблон для менеджеров: ~/Desktop/Лимиты_складов.xlsx
  • Импорт Excel: WarehouseLimitImportService — парсинг xlsx, upsert по (marketplace_id, warehouse_name)
  • Инструкция: docs/WAREHOUSE-LIMITS-IMPORT.md — формат файла, примеры, FAQ

Деплой на Hetzner

Рабочий контур — S3 Next. Последний функциональный release — 10c720f на next/spec-platform: он включает исправление poll-side quota для YM competitors (partial scope + 65-секундный cooldown перед следующим /generate). Опубликованный 2026-07-20 checkout — ca8c38a; это потомок релиза через documentation-only commits 4bd5b95 и ca8c38a. Точный live SHA перед любым действием проверять через mcp__gwptd__repo_status, а не по этой исторической строке. S2 является legacy/production oracle и в обычной разработке строго read-only.

# S3: только защищённый root-owned deploy wrapper и точный SHA, уже опубликованный в origin.
ssh s3-int /usr/local/sbin/gwptd-next-deploy --expected-sha <40-hex-commit>

# После успешного deploy: read-only attestation.
ssh s3-int 'cd /opt/gwptd-analytics/repo && git status --short --branch && git rev-parse HEAD'

Не использовать для S3 deploy.sh, ручной git pull, docker compose up, artisan migrate или ручную замену cron: они обходят блокировки, rollback и MCP reindex. Команды старого production deploy из прежних версий этого файла не являются текущей инструкцией и не запускаются на S2 без отдельного OK владельца.

S3 Next runtime: Docker-контейнеры gwptd-app-new и gwptd-data-api; их health и public /new* проверяются после guarded deploy. S2 legacy runtime: исторический PHP/Nginx контур; не считать его источником состояния нового интерфейса.

Авторизация и пользователи

Basic Auth (Nginx): учитывается из .env.production (переменные окружения, не коммитятся в репо).

Auto-login (URL): GET /auto-login?email=X&password=Y — логинит через Auth::attempt(), создаёт сессию, редиректит по config('gwptd.interface_variant'): new/new, иначе (old/default) → /admin. Rate limit: 10 req/min.

  • Контроллер: app/Http/Controllers/AutoLoginController.php
  • Роут: routes/web.php
  • Нужен потому что Filament Livewire ломается при Basic Auth credentials в URL (SecurityError: replaceState)

Redirect: //new или /admin (через Route::redirect, по config('gwptd.interface_variant'): new/new, иначе (old/default) → /admin)

Публичная топология: внешний https://mp.hyp.ru/ остаётся legacy-root на S2 и ведёт на /admin; новый интерфейс S3 опубликован через https://mp.hyp.ru/new*. Этот redirect описывает маршрут внутри S3 application container, поэтому результаты проверки публичного / и прямого S3 / различаются намеренно.

Пользователи (Filament + Spatie Shield):

IDИмяEmailРольСтатус
1Antonanton@gwptd.comsuper_adminactive
2Mishamisha@gwptd.comsuper_adminactive
3Lizavetalizaveta@gwptd.companel_useractive
4Annaannan@gwptd.companel_useractive
7Robertas Pavlovrobertas.pavlov@gmail.companel_useractive
8Alya Raskinaraskina@gwptd.companel_useractive

⚠️ Таблица выше — не источник истины и устаревает. Замер 12.08.2026: у Анны адрес annan@gwptd.com, а не anna@gwptd.com; пользователя с id 5 («Alya») в базе нет вовсе — Аля Раскина это id 8, raskina@gwptd.com. Кроме перечисленных, в базе есть ещё десять пользователей. Перед рассылкой или выдачей прав брать адреса ИЗ БАЗЫ, а не отсюда.

Пароли пользователей — управляются через Filament UI (не коммитятся в репо). Для сброса через tinker: $u->password = Hash::make('plaintext'); $u->save().

Важно: User model имеет 'password' => 'hashed' cast. При сбросе через tinker: $u->password = 'plaintext'; $u->save(). Новым пользователям нужно assignRole('panel_user') для доступа к Filament (HasPanelShield).

Ключевая документация

Вход — docs/README.md: документация разделена по роду, и от рода зависит, можно ли файл править руками.

ФайлКогда читать
AGENTS.mdвсегда первым: правила, границы, что заморожено
docs/README.mdвход в документацию и в иерархию: раздел «Система сверху вниз» (стратегический → функциональный → описательный); три рода — порождаемое, канон, архив
docs/domain/до правки любых бизнес-формул: формулы, запреты, семантика статусов
docs/remediation/HANDOFF-2026-08-04.mdпервым в новой сессии: состояние работ
docs/remediation/agent-exchange/results/F71-ozon-city.mdрезультат F-71: город Ozon хранится отдельно; регион по нему не выводится
docs/remediation/S3-NEXT-PRODUCTION-READINESS-PLAN.mdпрограмма доведения s3 до production
docs/backend/generated/ENDPOINTS-CATALOG.mdчто и откуда собирается: 81 файл описаний (свыше 125 адресов API — 27 описаний это обёртки), окна, лимиты
QUICKSTART.mdбыстрый старт

⚠️ Формула рейтинга и алгоритм отгрузки — только в docs/domain/machine-rules/. Прежние описания (RATING_FORMULA_v2.md, SHIPMENT-ALGORITHM.md) выведены в архив: они эпохи Lamoda, без вычета возвратов и с другим составом множителей. Брать их как действующие нельзя.

Code Review 2026-03-11 — Результаты и статус

Полный review: 4 ревьюера (Claude Opus, Gemini, Codex/OpenAI, Борис/S1).
Детали: /root/gwptd-rag/reviews/MASTER-REVIEW.md, план: IMPLEMENTATION-PLAN.md

✅ Исправлено (13 коммитов, 11.03.2026)

КоммитПроблемаСерьёзность
d0faf07MarketplaceDictMarketplace в 5 моделях — класс не существовал, Fatal Error🔴 КРИТ
65e5b84Регрессия коллектора — BitmapService::updateFromApiStocks() не вызывался после upsert стоков. Рейтинг WB/Ozon/YM был молча нулевым🔴 КРИТ
f662739NATS пароль убран из ChatController.php.env🔴 SEC
53dd5bfPath Traversal в ChatController::poll() — session теперь валидируется как UUID🔴 SEC
2e2c297HSTS добавлен в nginx (max-age=31536000)🟠 SEC
436ace3Лог пропущенных моделей в RatingService (zero exposure / no cost price)🟠
0a89f96Force flag в collector page шёл не в тот аргумент🟠
8920c3458 пар дублей проверены — трейт HasMarketplaceGroups, хелпер MarketplaceName, getMpStocks через DI🟡
8ee715eN+1 в ImportServiceModelDictCache предзагружает model_dict до цикла (было 10K запросов)🟡 PERF
ed5043aN+1 в RatingService — batch load через getAllModelMeta() JOIN🟡 PERF
014ac9fwhile(true) → bounded loop (maxPages=500) в LamodaApiService🟡
22c67c2Авто-очистка mp_collection_log (90 дней), PHP_MEMORY_LIMIT через env🟡
503ff01Retry в SupplierApiService, ShipmentPage дефолтный МП из конфига🟢

БД:

  • ALTER TABLE mp_orders_daily ADD INDEX idx_mp_date
  • ALTER TABLE mp_stocks_daily ADD INDEX idx_mp_date
  • ALTER TABLE shipments ADD INDEX idx_batch_mp
  • ALTER TABLE mp_finances ADD FOREIGN KEY → marketplace_dict ✅ (12.03.2026)

⏳ Открытые задачи (не реализовано)

ПриоритетЗадача
🟡Unit-тесты: ShipmentCalculatorTest, RatingServiceTest
🟡Feature-тест: collector → BitmapService (регрессия)
🟡Merge overrides/ в основной код (47 файлов техдолга)
🟢Партиционирование mp_orders_daily / mp_stocks_daily (при >5M строк)
🟢Laravel Queue + Horizon (ETL async)
🟢Кэш дашборда с тегами (Cache::tags)
🟢Сезонность в БД + Filament UI вместо config/gwptd.php
🟢Sentry + Laravel Pulse (мониторинг)
🟢CI/CD GitHub Actions

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

  • GWPTD API 404 с Hetzner IP с ~04.03 — нужно обратиться в поддержку GWPTD для whitelist
  • Два коллектора (Python + PHP) — оба заморожены (legacy); изменения в API-логике вносить ТОЛЬКО в marketplace-collector-v3
  • overrides/ паттерн — 47 файлов перекрывают app/ через Docker volume mount; при docker compose build берутся overrides, не оригинал
  • OOM при ExcelPhpSpreadsheet на файлах >10MB падает; лимит: PHP_MEMORY_LIMIT=512M в .env

Ротация API-токенов WB

S2 = read-only production. Не менять вручную S2 .env, collector_config.py или контейнеры и не выполнять docker compose up без отдельного явного решения владельца. Не передавать токен в командной строке, документации или чате.

Ротация требует отдельного owner-approved change: обновление root-owned secret store, контролируемая S3 публикация через /usr/local/sbin/gwptd-next-deploy --expected-sha <40-hex-commit> и согласованная no-write проверка доступа. Актуальная процедура и границы публикации заданы AGENTS.md и docs/remediation/S3-NEXT-AUTONOMOUS-RUNBOOK.md; этот файл не заменяет их.

Правила

  1. Не коммить .env, credentials — они в .gitignore
  2. Все новые фичи — через сервисы (app/Services/), не в контроллерах
  3. Модели нормализуются: UPPER + trim + collapse spaces → VARBINARY
  4. Дедупликация через import_log: файл импортируется один раз (--force для переимпорта)
  5. После изменений обновлять этот CLAUDE.md
  6. Перед S3 deploy — запушить точный SHA и вызвать только root-owned /usr/local/sbin/gwptd-next-deploy --expected-sha <40-hex-commit>; не использовать bash deploy.sh, ручной git pull, docker compose up или замену cron. S2 deploy возможен только по отдельному явному OK владельца.
  7. MySQL: marketplace_dict.marketplace_id — tinyint unsigned (FK тоже tinyint!)

@for-ai-agents/START-HERE.md @for-ai-agents/laravel-boost.md @for-ai-agents/andrej-karpathy.md