# Marketplace Ads — Compute Core (cross-MP)

Кросс-маркетплейсный compute-движок для биддера. Pure pandas — без сети, без знания о конкретном маркетплейсе. Один вход — DataFrame, один выход — DataFrame или `list[dict]` с детерминированным результатом.

Скилл предназначен для горячего пути биддера (тик каждые 15 минут): нужен **детерминизм**, а не LLM-арифметика. Все скрипты импортируются в sandbox и не делают сетевых вызовов.

## Когда что использовать

| Скрипт | Назначение | Вход | Выход |
| --- | --- | --- | --- |
| `compute_sku_pnl.py :: compute_sku_pnl(df)` | Истинный ДРР с маржой по каждому SKU | DataFrame: `sku, revenue, cogs, commission, logistics, returns, ad_spend` (всё в копейках) | DataFrame + `pnl_kopeks, true_drr, margin_pct` |
| `find_loss_makers.py :: find_loss_makers(pnl_df, target_drr)` | Поиск SKU, которые жгут бюджет или работают в минус | `pnl_df` (выход `compute_sku_pnl`), `target_drr` (%) | `list[{sku, reason, severity}]` |
| `apply_pause_rules.py :: apply_pause_rules(df)` | Детерминированные правила «pause / keep / exploration» | DataFrame: `sku, margin_pct, oos_days, returns_7d_pct, exploration_until` | `list[{sku, action, reason}]` |
| `propose_lineup.py :: propose_lineup(scored_df, total_budget, target_drr)` | Распределение бюджета по ABC + target-ставки | `scored_df`: `sku, abc_class, price, cogs, commission_pct, logistics` (+ опц.) | `list[{sku, allocated_budget, target_bid, strategy}]` |
| `compute_target_bid.py :: compute_target_bid(sku_metrics, target_drr, current_bid)` | Pricing-loop: следующая ставка для одного SKU | `dict`: `price, cr` (+ опц. `exploration_until`) | `int` — новая ставка в копейках |

## Контракты входа

- Все денежные значения — **в копейках** (int или float). Если на руках рубли — умножь на 100.
- ABC-класс — строка `'A' | 'B' | 'C'` (case-insensitive). Если SKU не классифицирован, передай `'C'`.
- `exploration_until` — `pd.Timestamp` (UTC-aware) или `pd.NaT`. SKU считается cold-start, пока значение в будущем.
- `target_drr` — процент (`12.0`, не `0.12`).
- `cr` (conversion rate) — доля `0..1` (например, `0.04` = 4%).

## Контракты выхода

- `compute_sku_pnl`: копия входа + три числовые колонки (`pnl_kopeks` в копейках, `true_drr` и `margin_pct` округлены до 2 знаков).
- `find_loss_makers`: один SKU может встречаться несколько раз — по одному элементу на каждое сработавшее правило (`negative_margin`, `drr_overshoot`, `ad_spend_above_margin`); `severity` ∈ `{high, medium}`.
- `apply_pause_rules`: ровно один результат на SKU; первое сработавшее правило побеждает (`exploration` → `negative_margin` → `oos_lt_7d` → `returns_above_15pct` → `ok`).
- `propose_lineup`: ровно один результат на SKU; `strategy` ∈ `{push, hold, pause, exploration}`. Для `pause` бюджет и ставка = 0.
- `compute_target_bid`: возвращает `int`. Cold-start → `EXPLORATION_BID_KOPEKS = 5000` (50 ₽). Без CR-сигнала → не трогаем `current_bid` (с минимальным флором). Иначе клампим в ±50% от `current_bid` (страховка от качелей при коротких тиках).

## Связки с другими скиллами

- Методология ABC-классификации, расчёт чистой маржи, маппинг типов списаний → `read_skill("unit_economics_ru")`.
- Когда применять авто vs ручную стратегию ставок, как связывать рекламу с жизненным циклом товара, чек-лист готовности карточки до запуска кампании → `read_skill("marketplace_ads_strategy_ru")`.
- Применение решений на конкретный маркетплейс (write-сторона) → `read_skill("ozon_guide_ru")` / `read_skill("wildberries_guide_ru")` / `read_skill("yandex_market_guide_ru")`.

## Что этот скилл НЕ делает

- Не ходит в сеть. Не вызывает Ozon/WB/ЯМ API. Если нужно прочитать данные — это работа гайд-навыка маркетплейса.
- Не пишет в БД. Только compute.
- Не решает «авто или ручная кампания». Это методологический выбор → `marketplace_ads_strategy_ru`.
- Не считает COGS / комиссии / логистику с нуля — ожидает их уже в DataFrame. Маппинг типов списаний — задача `unit_economics_ru`.
