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__search→mcp__mcp-gwptd__rag_search→ этот файл. Если ты агент сотрудника и здесь впервые — for-ai-agents/START-HERE.md: куда идти за ответом, куда класть правку, чего не делать без владельца.
🧠 Память и поиск информации
При любом вопросе — сначала смотреть в память
ОБЯЗАТЕЛЬНО перед ответом на вопрос о проекте, задаче или технической теме:
- Искать в MCP-памяти (
mcp__vestige__search) — там могут быть контекст, решения, заметки из прошлых сессий - Искать в кодовой базе (
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 --import | Google 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 × курс |
| BitmapService | Bitmap-экспозиция: 31 бит/месяц, OR-идемпотентно |
| RatingService | Рейтинг = Profit / (Exposure × CostPrice × Seasonality) |
| SupplierApiService | gwptd.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/exposure | Bitmap-экспозиция |
| 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/sync | Google 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 Service | marketplace_id |
|---|---|---|
| Wildberries | WildberriesApiService | 1 (FBO), 2 (FBS) |
| Ozon | OzonApiService | 3 (FBS), 4 (rFBS), 5 (FBO) |
| Yandex Market | YandexMarketApiService | 6 (FBS), 7 (Express), 8 (FBY) |
| Lamoda | LamodaApiService | 9 |
Данные → 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
- Stocks:
- 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)
- Warehouses:
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 byoperation_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_id | tinyint unsigned FK | Маркетплейс |
| warehouse_name | varchar(255) | Название склада из API |
| limit_qty | int unsigned NULL | Лимит штук (NULL = без лимита) |
| limit_rub | decimal(12,2) NULL | Лимит рублей (NULL = без лимита) |
| notes | text NULL | Доп. ограничения текстом |
| is_active | boolean | Вкл/выкл склад |
- Модель:
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 | Имя | Роль | Статус | |
|---|---|---|---|---|
| 1 | Anton | anton@gwptd.com | super_admin | active |
| 2 | Misha | misha@gwptd.com | super_admin | active |
| 3 | Lizaveta | lizaveta@gwptd.com | panel_user | active |
| 4 | Anna | annan@gwptd.com | panel_user | active |
| 7 | Robertas Pavlov | robertas.pavlov@gmail.com | panel_user | active |
| 8 | Alya Raskina | raskina@gwptd.com | panel_user | active |
⚠️ Таблица выше — не источник истины и устаревает. Замер 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)
| Коммит | Проблема | Серьёзность |
|---|---|---|
d0faf07 | MarketplaceDict → Marketplace в 5 моделях — класс не существовал, Fatal Error | 🔴 КРИТ |
65e5b84 | Регрессия коллектора — BitmapService::updateFromApiStocks() не вызывался после upsert стоков. Рейтинг WB/Ozon/YM был молча нулевым | 🔴 КРИТ |
f662739 | NATS пароль убран из ChatController.php → .env | 🔴 SEC |
53dd5bf | Path Traversal в ChatController::poll() — session теперь валидируется как UUID | 🔴 SEC |
2e2c297 | HSTS добавлен в nginx (max-age=31536000) | 🟠 SEC |
436ace3 | Лог пропущенных моделей в RatingService (zero exposure / no cost price) | 🟠 |
0a89f96 | Force flag в collector page шёл не в тот аргумент | 🟠 |
8920c34 | 58 пар дублей проверены — трейт HasMarketplaceGroups, хелпер MarketplaceName, getMpStocks через DI | 🟡 |
8ee715e | N+1 в ImportService — ModelDictCache предзагружает model_dict до цикла (было 10K запросов) | 🟡 PERF |
ed5043a | N+1 в RatingService — batch load через getAllModelMeta() JOIN | 🟡 PERF |
014ac9f | while(true) → bounded loop (maxPages=500) в LamodaApiService | 🟡 |
22c67c2 | Авто-очистка mp_collection_log (90 дней), PHP_MEMORY_LIMIT через env | 🟡 |
503ff01 | Retry в 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 при Excel —
PhpSpreadsheetна файлах >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; этот файл не заменяет их.
Правила
- Не коммить .env, credentials — они в .gitignore
- Все новые фичи — через сервисы (app/Services/), не в контроллерах
- Модели нормализуются: UPPER + trim + collapse spaces → VARBINARY
- Дедупликация через import_log: файл импортируется один раз (--force для переимпорта)
- После изменений обновлять этот CLAUDE.md
- Перед 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 владельца. - 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