# Polleo Demand — Knowledge Base / AI Briefing

*Single reference for company context, metrics, forecasting, S&OP, assortment, supply, and the data model. Compiled from project artefacts and prior working sessions, then **verified against the live repo + Postgres (read-only)**. This is the human/dev source of truth; a distilled, business-facing subset of it is embedded in `backend/services/polleo_ai_system_prompt.py` as the AI chat's knowledge.*

> **Verification status:** A pass with repo + live DB access (role `polleo_ai_ro`, SELECT-only) checked the claims below. Anchor reality: `erp_transactions` ≈ 306k rows; `v_sales_weekly_full` ≈ 116k rows; `dim_products` = 47,832 (raw/dirty); real used universe ≈ 7,600 (stock and/or recent sales — 6,976 in stock, 5,790 sold in 2026, 9,905 ever sold); planned = 489 (`sku_planning`). Items confirmed against running code/data are tagged `✅✅ VERIFIED-LIVE`.

**Confidence legend:**
`✅✅ VERIFIED-LIVE` — checked against running code + live DB · `✅ CONFIRMED` — verified in code/files · `🟡 PARTIAL` — direction known, detail unverified · `⚠ BUSINESS INPUT NEEDED` — not in code; only management can answer.

---

## 1. Company & users

**Company.** Polleo Sport — fitness / sports-nutrition company, started as a retailer, now scaling. ~€30–40M revenue, 35 own stores across HR / AT / SLO. Scaling into export and wholesale (FMCG sector in Croatia and Slovenia). Three channels: retail (own stores), wholesale (B2B), webshop. Real used SKU universe ≈ 7,600 (stock + sales + active); ~489 actively planned; `dim_products` holds ~47.8k raw ERP rows (dirty — not a product count). ~€5M company inventory budget at cost. Largest supplier = ABC Nutritional (~70% of goods).

**Who uses the AI tool, and what they need.** Intended for anyone in the company; more departments onboard as it matures. Initially:
- **Demand Planning (DP).** Controls the forecast, reports on the S&OP cycle, edits the forecast. DP is the connective tissue between Sales, Procurement, Logistics and Finance and the one that *regulates* each department's wishes: Sales = optimistic, Finance = pessimistic, **DP = realistic** (on both forecast and cash).
- **KAM (Key Account Managers, wholesale).** The people selling product into the FMCG sector / export. Through the app they give **inputs**: quantities their buyers will take, up to 13 weeks ahead — often only a projection, not a done deal, which DP must **challenge in the S&OP cycle**.
- **CM (Category Managers, procurement).** Direct line to suppliers; define purchase price (nabavna cijena), order goods, create promotions (decide which SKUs / parent groups go on promo) and forecast promos. Should use the supply book to order, the promo tool to create promotions, and maintain supplier data (log data, MOQ, lead time).
- **Uprava (CEO / COO / CFO).** Want everything report-wise — all relevant company data on demand — and should be the heaviest AI-chat users. The **CFO** sets financial guidelines / GOALS (cash flow, stock value, margins).

**Top questions the tool should nail (eval anchors).**
1. Will our stock level be over benchmark in the next 3 months?
2. Which articles should we drop from the assortment, given we're developing 50 new SKUs?
3. Where are we losing the most money — what can we optimize in pricing / promotions / assortment?
4. How should we build our forecasting to be more accurate? *(strategic/advisory — not a pure lookup)*

---

## 2. Metrics & definitions

> Core caveat: **there is no single "source of truth" page per metric.** The same SKU can legitimately show different numbers on different pages because the questions and inputs differ. §2.7 lists the *four* axes of divergence.

### 2.1 Revenue ✅✅ VERIFIED-LIVE — **inconsistent across modules**
- **No `revenue_eur` column** on `v_sales_weekly` / `v_sales_weekly_full` (quantities only; `v_sales_weekly` also has `ruc_total`). Revenue is **computed in code from `erp_transactions`**, and the VAT convention is **not the same everywhere**:
  - **GROSS (incl-VAT, `total_value`)** — Demand and Executive pages (and the AI system prompt's revenue examples).
  - **NET (excl-VAT, `tax_base`; fallback `total_value × 0.80` when `tax_base = 0`)** — Finance, monthly S&OP, monthly-plan snapshots.
- "Revenue is net" is **not** company-wide. When quoting revenue, say *which module* produced it (4th axis, §2.7).
- The `× 0.80` fallback hard-codes **25% VAT**; it fires on `tax_base = 0` rows (~9.7% live).
- All values EUR (HR converted at ingest).

### 2.2 RUC and RUC% ✅✅ VERIFIED-LIVE
- **RUC = "razlika u cijeni" = gross margin** (sale − cost), absolute EUR, before opex ("bruto marža").
- Trustworthy source = per-line `erp_transactions.ruc_eur` / `ruc_pct`. `v_sales_weekly.ruc_total = GREATEST(SUM(ruc_eur), 0)`.
- ⚠ Trap: any SKU-level cost/ruc CSV `ruc` column is mostly 0.0 (being recomputed). Use line-level `ruc_eur`.

### 2.3 Coverage / weeks of cover ✅✅ VERIFIED-LIVE — five coexisting implementations
| # | Definition | Formula | Where |
|---|---|---|---|
| 1 | Simple bare | `stock / weekly_demand` (mean next 13w) | headline; supply dashboard |
| 2 | Effective w/ incoming | `(stock + incoming_in_LT) / weekly_demand` | coverage classifier (Exec/Finance) |
| 3 | 8-week-avg walk | `stock / mean(forecast[wk:wk+8])` | superseded — do not reuse |
| 4 | Walk-forward (real) | subtract real weekly forecast; partial week `remaining/d`; zero-demand → ~∞ | ABC scenarios + Scenario Planner |
| 5 | Walk-forward stock projection | full week-by-week closing trajectory with PO inflows | multi-PO checks, €5M scheduling |

**Decision rule:** for order/postpone/cancel use **walk-forward real cover (#4) on WH-only stock**. Averaged cover hides spike weeks. Headline display: #1 is fine.

### 2.4 Which stock counts ✅ CONFIRMED
- **WH-only** = ordering basis (stores can't replenish each other quickly).
- **Combined (WH + stores HR/AT/SLO)** = "company stock view", context only.

### 2.5 Inventory value ✅ CONFIRMED
- **Cost basis:** `stock_units × cost_price` (landed). Drives the €5M target, locked-cash, slow-mover math. Retail-value used only for opportunity framing.
- Walk-forward € floors each SKU at 0; a stocked-out SKU stops contributing value and absorbing demand.

### 2.6 Finance metrics ✅ CONFIRMED
- **Lost sales** (`/finance/lost-sales`): walk WH stock + POs − weekly forecast from CW20; stockout week = first 0; lost units = forecast to horizon; × cost (cost basis) or × selling price (revenue basis).
- **Locked cash** (`/finance/locked-cash`): `excess = max(0, stock − target)`, `target_weeks = LT + review(1w) + safety(Gold 2 / Silver 1.5 / Bronze 1)`, `locked = excess × cost`; real LT from `supply_master`.
- **Slow mover / dead stock** (`/finance/slow-movers`): LT-relative — `UNDERSTOCK` <LT · `HEALTHY` ≤2×LT · `OVERSTOCK` >2×LT · `SLOW_MOVER` >3×LT (demand>0) · `DEAD_STOCK` = 0 demand 13w (or 90d). Gadgets / non-food flagged.
- **Contest risk** (`/finance/contest-risk`): July **CW27–CW31**; baseline + uplift; fall back to 1.5×/2× sensitivity on affected Gold SKUs.

### 2.7 The "two numbers" root cause ✅✅ VERIFIED-LIVE — **four** axes
1. **Classifier scheme** — three live (absolute weeks / LT-relative / ABC scenario; §6.3).
2. **Stock view** — WH-only vs WH+stores.
3. **Demand** — averaged (mean 13w) vs real per-week walk-forward.
4. **Revenue VAT basis** — GROSS (Demand+Executive) vs NET (Finance+S&OP+monthly-plan).
Always state *which page/definition* a figure came from until one canonical choice is migrated.

### 2.8 Gotchas
- **Channel attribution** ✅: `tip_dok → channel` via `lookup_channel_map`. **RAC = wholesale** (B2B-ish web orders priced/serviced as wholesale; moved from webshop 2026-06), alongside TRC/VPT/VPB/RIZ/RPE. Retail = RCM; webshop = WSA–WSD only.
- **Per-line partner/store** ✅ (2026-06): the sales upload now persists `partner_id`, `store_id`, `sales_rep`, `document` and all financials per `erp_transactions` row — so buyer-level (join `dim_partners`) and store-level analysis is queryable. Retail RCM rows have NULL partner (anonymous B2C); business rows carry it. Older app-uploaded weeks may lack partner until re-uploaded (the upload is append-only — re-load deletes the ISO-week range first).
- **Returns** ✅✅: negative-qty rows (~3,894 live); net via SUM within a week; quantity views wrap weekly sums (and `ruc_total`) in `GREATEST(…,0)` → weekly net clamped ≥0. Revenue sums net out but are **not** floored.
- **Approved rebate** ✅✅: `erp_transactions.approved_discount` is per-line but **no code subtracts it** — assumed already baked into invoiced `total_value`/`ruc_eur`.
- **Sell-through** ⚠ BUSINESS INPUT NEEDED: no definition in code.
- **Pre-2026-04 data** ✅ trap: monthly P&L bridge filters out anything before `FORECAST_START = 2026-04` (pre-cutoff "plan" is a reconstructed proxy).

---

## 3. Forecasting (current / Postgres world)

### 3.1 How it works ✅✅ VERIFIED-LIVE
- **Channel-split**: retail + wholesale fitted separately then summed.
- **Model pool**: AutoARIMA, AutoCES, AutoTheta, CrostonOptimized, ADIDA, IMAPA, TSB, seasonal ARIMA/Theta variants, fallback Naive; engine-native SES, custom Croston, Hybrid, SeasonIdx also compete. **Best per SKU by backtest. Holt explicitly excluded.**
- **GBR** = `HistGradientBoostingRegressor` (`loss='absolute_error'`); features = is_promo / discount / retail_pct / ws_spike.
- ⚠ Two engines exist: root `/forecast_engine.py` (**live**) and `/Demo/forecast_engine.py` (demo). Cite root.

### 3.2 The flow + bridge math ✅✅ VERIFIED-LIVE — naive formula over-counts
- **Two-pass cycle**: Mon = baseline (no on-top); Fri (after 12:00 deadline) = final with on-top.
- VP on-top is additive, but **MP on-top REPLACES the retail baseline** on its weeks (MP = the promo-week retail sell-through), so retail baseline is removed first:
  - split mode: for any forecast week with MP on-top > 0, retail forecast is zeroed before summing.
  - total mode: baseline scaled by `(1 − retail_phys_share)` on MP weeks.
  - **Correct:** `demand = (baseline, physical-retail zeroed on MP weeks) × planner_factor + VP_on_top (additive) + MP_on_top (replaces retail baseline)`. Display "Total Demand" = Adjusted FC + On-top Total (nets to the same); literal additive `baseline+VP+MP` over-counts retail on promo weeks.
- Result lands in `forecast_for_supply.csv` (`sku, year, week, demand`); every downstream supply/finance/ABC decision reads it.

### 3.3 Consensus ✅✅ VERIFIED-LIVE
- **What:** the locked weekly plan (per 3.2). **Who/when:** Demand Planner locks it **Friday**. Saving snapshots full plan state → `consensus_snapshots` (9 rows live; e.g. "CW16 S&OP lock", `n_skus≈496`) and writes `forecast_for_supply.csv`.

### 3.4 Guardrails ✅✅ VERIFIED-LIVE — **per channel**
- Cap 2.0×, floor 0.5× over 8-week median, wholesale cap 1.5×, promo-discount threshold 10%. Promo-clean **skip** if `promo_pct > 0.40` or `consecutive_end ≥ 4`.
- Applied **per channel**: retail = last-13w mean × 2.0; wholesale = last-26w MAX × 1.5 — not one total×2 cap. The engine hardcodes thresholds (treat `constants.py` as docs, engine as truth).

### 3.5 Promo uplift & "what if we promo X" ✅✅ VERIFIED-LIVE
- ERP promo calendar = ground truth (~96.5%); statistical detection fills gaps.
- **Known gap:** the engine applies a **default uplift**: per-SKU (`sku_uplift.csv`, gated) → category (`cat_uplift.csv`) → **1.35 hard default**; it does NOT read `promo_performance.csv`. For "what if we promo X": prefer the SKU's historical `actual_uplift` → category avg → 1.35, and net out cannibalization.

### 3.6 Forecast accuracy ✅✅ VERIFIED-LIVE
- `fa = max(0, 1 − |F−A|/A)`, `bias = (F−A)/A`, `hit = (|F−A|/max(A,1)) ≤ 0.30`; rows with `A ≤ 0` dropped.
- Views: Global / KAM·CM / Model-only / Live.
- **Why Bronze drags:** XYZ is CV-based (X<0.5, Y 0.5–1.0, Z>1.0). Bronze skews Z; intermittent demand has a lower achievable FA ceiling. Expected — slice FA by XYZ to show it.

---

## 4. S&OP workflow

### 4.1 Weekly rhythm ✅ CONFIRMED — S&OE meeting **Thursday**, exceptions-only
| Day | Who | What |
|---|---|---|
| **Mon AM** | Demand | Supply plan (coverage, alerts, inventory health) → Nabava. **Baseline forecast** (pass 1). |
| **Mon EOD** | Nabava → Demand | POs for 100% missing goods same day; update `incoming_supply`; regenerate plan. |
| **Tue/Wed** | Nabava + Demand | Supplier confirmations + **ABC arrival prioritization by Wed**; update supply book; prep S&OE. |
| **Wed EOD** | VP / MP | Sales changes for upcoming weeks. |
| **Thu** | All | **S&OE meeting** — KPI check; in-plan→continue, out→action who/what/why/by-when; report to Finance. |
| **Fri 12:00** | VP / MP / Nabava | **On-top deadline**. Integrate inputs, **final forecast** (pass 2), lock consensus, distribute templates. |

### 4.2 Monthly rhythm ✅ CONFIRMED
- **Pre-S&OP** (mgmt forum): consolidate weekly S&OE; review KPIs/financials/stock projection; prepare proposals.
- **S&OP** (executive + CEO): present agreed plan; **CEO approves** significant working-capital/business decisions even when all departments agree.

### 4.3 KAM / CM (VP / MP) input ✅✅ VERIFIED-LIVE — divergence to watch
- Pre-filled Excel templates per user; VP carries a **buyer** column, MP doesn't.
- **Intended combine: MAX per `(sku, type)` across buyers/sheets, then SUM** — implemented in legacy Streamlit.
- ⚠ **Divergence:** the new Postgres path does plain SUM, no MAX-per-type dedupe → double-count risk if a buyer's commitment lands on two sheets. Confirm the React on-top write path dedupes.
- Stored in `on_top_inputs` (`product_id, year_week, qty, channel, buyer (NULL retail), submitted_by, submitted_at`).

---

## 5. Products & assortment

### 5.1 Tiers Gold / Silver / Bronze ✅✅ (origin + implications) · ⚠ (criteria)
- **Origin:** `sku_planning.tier` / `sku_plan_list.csv` `oznaka` (`01 GOLD / 02 SILVER / 03 BRONZE`). **An input** (business/ERP assigned), not computed.
- ⚠ BUSINESS INPUT NEEDED: the assignment rule is not in code.
- **Service-level Z-scores** ✅✅: Gold 2.054 (98%) / Silver 1.645 (95%) / Bronze 1.405 (92%) / Unplanned 1.476 (93%). Drive safety stock + inventory health. Gold also gets ABC-planning protection.

### 5.2 Planned vs unplanned — "plan ~489, real universe ~7,600, raw ERP ~48k" ✅ CONFIRMED
- **Planned (~489, in `sku_planning`):** forecasting, FA, coverage, reorder, order entry, watchlist, KAM/CM input, promo planning, S&OP.
- **Real used universe (~7,600):** SKUs with stock and/or recent sales. **Raw `dim_products` (~48k, `active=true`) is dirty ERP — never use as a count.**
- **Unplanned:** reporting only; demand signal = **run rate** (avg non-promo weekly sales last 13w), labelled "Run rate" not "Forecast".

### 5.3 Food vs non-food / gadget cash trap 🟡
- Non-food, esp. **gadgets (~€430k, −30% YoY)**, a recurring cash-trap concern; flagged in slow-mover/locked-cash.
- ⚠ BUSINESS INPUT NEEDED: a formal **max-500 / 50-SKU-delist** policy.

### 5.4 NPL/NPD — adding/delisting SKUs ✅✅ VERIFIED-LIVE
- `npl_products`: sku, name, group_enum, channels, launch_date, barcode, parent_sku_id, relationship_type, supplier_id, cost_price, lead_time_weeks, safety_buffer_weeks, fmb_date, status, npl_flag, approved_by/at.
- **FMB (First Minute Buy)** = `launch_date − lead_time_weeks − safety_buffer_weeks`.
- **Substitution:** soft = same barcode (coexist); hard = new barcode (parent auto-phases-out). Status: `draft → approved → fmb_triggered → ordered → in_stock → active → phase_out`.
- Roles: Admin / Nabava / Veleprodaja full access; only Admin configures enums.

---

## 6. Supply & inventory

### 6.1 Reorder / order proposal ✅✅ VERIFIED-LIVE
- `target = 2 × LT × avg_weekly_demand` · `gap = target − stock − incoming_in_LT` · `suggested_qty = ceil(gap/MOQ) × MOQ` (gap>0 else 0).
- MOQ falls back to 1.0 when NULL/≤0; `incoming_in_LT` summed over first ⌈LT⌉ weeks.

### 6.2 Lead time & MOQ ✅✅ VERIFIED-LIVE — one fallback exception
- From `supply_master`. 2026-05 policy: tier-based LT fallbacks removed — missing LT surfaces as `MISSING_LT`, not a guess.
- ✅ **Fixed (this cycle):** Scenario Planner no longer guesses 8w — missing LT now surfaces as `MISSING_LT` (None), consistent with the rest of the app.

### 6.3 Coverage status thresholds ✅✅ VERIFIED-LIVE — three schemes
- **Dashboard / Stock Projection:** `<2 Order Now · <4 Order Soon · 4–13 OK · >13 Pull in`.
- **Reorder Alerts:** `eff_cover<LT Order Now · eff_cover<2×LT Order Soon · (cover>13 & incoming>0) Pull in`.
- **Exec + Finance + Inventory Health:** `≤LT CRITICAL · ≤1.5×LT ORDER_SOON · ≤2×LT HEALTHY · >2×LT OVERSTOCK` + `MISSING_LT` + `NO_DEMAND`.

### 6.4 Inventory Health ✅✅ VERIFIED-LIVE
- `sigma_weekly = avg_weekly × CV`; `forecast_err_sigma = avg_weekly × (1 − FA)`; `combined_sigma = sqrt(sigma_weekly² + forecast_err_sigma²)`; `safety_stock = z × combined_sigma × sqrt(LT)`; `max_cover_needed = 1.5×LT + safety_weeks`. Statuses: overstock / stockout_risk / balanced. `stockout_risk` does NOT subtract incoming POs.

### 6.5 The €5M inventory target ✅ CONFIRMED (nature) · 🟡 (enforcement)
- Company-wide inventory at **cost**; "stay under €5M". **Guideline enforced through planning, not a hard cap** (ABC scheduler uses it as a sequencing ceiling; big moves → CEO approval). Baseline peaked €5.27M at CW24; scenarios pulled it under €5M.
- ⚠ Confirm "hard cap vs guideline" with management.

---

## 7. Data dictionary (selected)

> `db/schema.sql` is **stale**: several tables were added later in `db/migrations/*.sql`, and `v_sales_weekly_full` is a **materialized view** absent from it.

### 7.1 Confirmed & reliable ✅✅ VERIFIED-LIVE
- **`monthly_plan_snapshots`** — immutable monthly SKU-plan snapshots; canonical plan source for the margin bridge / revenue forecast. `month_key (YYYYMM), total_revenue_eur, total_ruc_eur, per_sku_data (JSONB)`.
- **`management_plan_lines`** — leadership top-down **RUC (margin) plan per channel × region per month** (not revenue). `month_key, channel, region, ruc_plan, ruc_achieved, …`.
- **`wholesale_listings`** (~963) — per-buyer listed assortment (which SKUs each wholesale buyer carries); feeds Wholesale Availability. `kam, buyer, product_id, sku, rank`.
- **`promo_policies`** + **`promo_policy_items`** (~500, the value table) — discount/rebate policy headers + per-(policy × SKU) items. `rabat_pct, promo_price, vp/mp_margin, valid_from/to`. Same migration adds `erp_transactions.has_loyalty`.
- **`contest_monthly_plan`** — which `promo_policy_items` belong to each month's **Contest** sections (promo-section→SKU map; not a demand-volume plan). The July CW27–31 contest math lives in `finance_service`.
- **`demand_review_notes`** (wired, unused) — S&OP Demand-Review log of RISK/OPPORTUNITY/DECISION; unresolved carry across cycles.
- **`consensus_snapshots`** — see §3.3.
- **`forecast_runs` / `forecasts`** — run header + per-SKU-per-week (`baseline, on_top_*, promo_uplift, planner_factor, total, model, channel`).
- **`v_sales_weekly_full`** — materialized view; `product_id, year, week, qty_retail/webshop/wholesale, qty_total` (no revenue/RUC), `GREATEST(…,0)` clamp.

### 7.2 Dirty / don't-trust ✅✅ VERIFIED-LIVE
- `dim_products.active` (~48k) — raw ERP, dirty; never a product count.
- SKU-level cost/ruc CSV `ruc` — mostly 0.0; use line-level `ruc_eur`.
- Revenue convention is split (§2.1).
- Pre-2026-04 bridge data — reconstructed proxy.
- `audit_log` (0 rows) — only writer is legacy Streamlit-era engine; unused by the React/FastAPI app.
- Hardcoded constants (`CURRENT_WEEK=20`, `CURRENT_STOCK_EUR≈4.62M`, `CONTEST_CW=27/31`, `FORECAST_START=2026-04`) go stale.
- `tax_base = 0` rows (~9.7%) → NET-revenue paths apply `×0.80` (25% VAT) fallback.

---

## 8. Open items — management only (do not infer)

**Canonical decisions made (lock these in code/views):**
- **Revenue = NET of VAT** (`erp_transactions.tax_base`), company-wide. `total_value` (gross/incl-VAT) is "turnover incl. VAT" only when explicitly asked. ✅ *Done — Demand + Executive migrated to net (see Changelog §9).*
- **€5M total inventory (at cost) = HARD CAP.**
- **Coverage = ONE metric: walk-forward on warehouse stock with incoming POs**, demand = live forecast; fallback for SKUs with no forecast = run-rate (avg weekly sales over last 8 non-promo weeks) applied flat over next 13 weeks. (Retire the other 4 coverage variants.)
- **Stock = keep both** WH-only (ordering) and WH+stores (whole-company view) — always labelled distinctly. Not a bug.

**Still open (management/business):**
| Ref | Open item | Owner |
|---|---|---|
| §2.8 | Sell-through definition | Business |
| §5.1 | Gold/Silver/Bronze assignment rule | Management |
| §5.3 | max-500 / 50-SKU-delist policy | Management |

---

## 9. Changelog — implemented (cycle: May–Jun 2026)

**Revenue (canonical = NET):**
- Demand (`demand_repo.get_revenue_summary`) + Executive (`executive_service` revenue pulse / weekly / plan-compare / revenue-at-risk) now compute revenue as **NET** = `COALESCE(NULLIF(tax_base,0), total_value*0.80)` — matches Finance. (Executive headline revenue dropped ~20-29% on screen = VAT correctly removed.)
- **Revenue Forecast page:** PAST actuals now **summed from the ledger** (net `tax_base` + actual `ruc_eur`) — removed the `qty × avg_sell_price` reconstruction that valued wholesale at retail price (~2× overstated). FUTURE forecast now values each channel at its **realized net price** (`retail_price_rate` / `wholesale_price_rate` from last 4 weeks) instead of one retail price for all.
- **Monthly rollup fix:** a selected month now loads its **full** history (was truncated to last 8 weeks → early weeks of older months dropped, e.g. April understated ~€255k). April RUC now ties to management (€1.17M ≈ €1.18M). Calendar/ISO-week boundary weeks split by day-fraction; exact calendar-day split still optional.

**Canonical data layer:**
- `v_sku_universe` view — labels every product `is_planned` / `is_real` (stock or sold ≤52w) / raw. AI uses it; "our SKUs" = planned (~489), "carry" = real (~7,600), never `dim_products.active` (~48k dirty). *(db/migrations/canonical_sku_universe.sql)*
- **Category dropdowns** (`/demand/categories`) return only the **9 canonical** categories (those with ≥1 planned SKU). Hides the dirty ERP soup (services dups STORITVE/USLUGE/DIENSTLEISTUNGEN, GADGETS dup, PARIS, OTHER). Display filter only — data untouched. Feeds all 9 category dropdowns.

**Coverage:**
- Unified to ONE canonical walk-forward engine `backend/services/coverage.py` (`walk_forward` + `weeks_of_cover`). `scenario_service.real_weeks_cover` and `supply_service._roll_forward` now delegate to it. Behaviour-preserving (49/49 tests green). Run-rate fallback uses 8w-non-promo. *(Repointing the few simple-ratio displays + exact 8w-non-promo fallback everywhere still optional — needs page validation.)*

**Bugs (triaged):**
- ✅ Scenario Planner 8w LT default → fixed (MISSING_LT).
- ❎ On-top "double-count" — investigated, NOT real (write path DELETE-then-INSERT upserts; SUM across buyers is correct).
- ❎ Stale `CURRENT_WEEK=20` / `CURRENT_STOCK` constants — NOT in the live app (legacy CSV/analysis only); live derives week from data.

**Promo:**
- **Price-disruptor** re-anchored from **p25 → p10** and capped **7× → 4×** (less aggressive — boost only for bottom-10% prices). Added `p10` to `get_category_price_stats`.
- **New-SKU fallback:** a new SKU with no own history borrows **baseline + price + cost from its parent-group siblings** (`get_group_baseline`, `get_group_price_cost`; analog forecast returns `baseline_source`/`baseline_group_n`/`group_price`/`group_cost`; planner passes `group_skus`, re-forecasts on grouping). Flows through the detail card, the per-SKU breakdown, and the basket aggregate. (NC30 stays "no data" — the SKU's own price-floor, not borrowed.)
- `effective_discount` (1+1→50%, 2+1→33%) exists; frontend applies it to the forecast discount. Backend wiring + auto-fill deferred until a precise mechanic vocabulary is set ("BOGO" is ambiguous: free vs %-off).

**Polleo AI:** agentic `run_sql`/`respond` loop + SSE streaming; per-channel × region forecast detail (`forecasts_detail` + allocation auto-run after each forecast); runs as read-only `polleo_ai_ro` role; markdown rendering + hr-HR number formatting; business knowledge embedded in the system prompt.

**Still open (optional):** exact calendar-day month split (kills ~€80k boundary rounding); repoint simple-ratio coverage displays; backend `effective_discount` wiring. Management/business: sell-through def, tier-assignment rule, max-500/delist policy.
