# Pricing rules — the encoded policy of the Pricing & Profitability Analyst playbook. # All money math in tools/margin_engine.py reads these values. Change policy here, # not in code. # Contribution-margin floor. A SKU must clear this at the WORST-CASE referral fee # or it does not get Pricing Approved. Playbook standard: 25% floor, 30%+ preferred. margin_floor: 0.25 margin_target: 0.30 # Returns reserve, taken as a % of selling price (playbook: ~2%). returns_reserve_pct: 0.02 # Allocated monthly storage per unit, flat $ (playbook: ~$0.25 for the sheet set). storage_alloc: 0.25 # Worst-case referral fee % used for the margin GATE. Amazon Home & Kitchen = 15%. # We always test the floor against this, never the optimistic modeled referral. worst_case_referral_pct: 0.15 # Rounding for customer-facing prices (charm pricing to .99). Set to null to disable. charm_ending: 0.99 # Price-competitiveness tolerance vs the competitive median (playbook KPI: within 5%). price_competitiveness_tolerance: 0.05 # ── inventory cover: where a price move is triggered ──────────────────────────── # These are PRICING triggers: at or below `low` the engine raises 5% to slow the burn, at or # above `high` it cuts 5% to clear stock. They were module constants in dashboard/live_data.py; # they live here because they are policy, and because they need to be reconcilable with the # COSMOS Inventory Planning bands that colour the same numbers on screen. # # THEY DELIBERATELY DO NOT EQUAL THE COSMOS BANDS. COSMOS's Alpha scheme calls 40-70 days # "healthy" (green) and 70-100 "high" (pink), but pink there is a REPLENISHMENT warning — it # tells a buyer not to reorder yet. It is not an instruction to discount, and the engine here # spends real margin when it fires. # # Measured on 47 SKUs of the covered product line, on live COSMOS data: # # thresholds raise +5% cut -5% no action # 35 / 90 (these) 6 20 21 # 40 / 70 (band edges) 7 26 14 # 20 / 100 (red bands) 2 16 29 # # Adopting the band edges would put six more SKUs on a 5% discount — every one of them in the # 70-90 day window, none of them actually at risk of ageing out. So the bands stay COSMOS's # for display parity, the triggers stay where they are, and the UI states which is which. # To converge them, set these to 40 / 70; nothing else needs to change. low_cover_days: 35 high_cover_days: 90 # ── competitor-driven pricing rules ───────────────────────────────────────────── # Master switch for the two competitor rules ONLY: BUYBOX_SUPPRESSED and # COMPETITOR_UNDERCUT. Set false and the cascade behaves exactly as it did before competitor # data existed — it routes through the SAME fail-safe path as missing/stale data, so there is # only ever one "pretend competitor data isn't there" code path to reason about. # # This is NOT the global kill switch. That one lives in the sidebar (app.py) and pauses # APPROVALS across the board without touching the cascade. The two are independent in both # directions: you can disable competitor rules while approvals continue normally, and you can # pause approvals while competitor rules keep informing the verdicts a human is reading. # # Every other cascade rule — cost data, break-even, ad losses, inventory, profit-optimal — is # untouched by this flag. competitor_rules_enabled: true # These gate the COMPETITOR branches of the live decision cascade. They live here, not in # code, because every one of them is a policy judgement someone may want to change without # a deploy. # How far below us a rival has to sit before "they undercut us" is a fact rather than noise. # 3% is above the ~2% band our own realized price already swings through as coupons toggle # (the mattress protector moves $11.71-$12.98 on its own), so anything under it cannot be # distinguished from our own price wobble. competitor_undercut_material_pct: 0.03 # How far ABOVE the rival field we have to sit before it is worth remarking on while we # still hold the Buy Box. Only ever a narrative note -- it never moves a price on its own. competitor_premium_material_pct: 0.10 # How old competitor state may be and still inform a verdict. Matched to the Playwright # scraper's own 6h cache TTL so the two cannot disagree about what "stale" means. Past this # the verdict is computed competitor-BLIND rather than on a price that has since moved. competitor_state_max_age_hours: 6.0 # The comparison WORKBOOK gets its own, much longer limit. It is a 25-35 minute batch run over # a whole product line, not a single-ASIN scrape, so nobody re-runs it hourly and the 6h rule # above would make every sheet unusable by the next morning. Raise or lower this as the # refresh cadence settles; set it to null to accept a sheet of any age. competitor_sheet_max_age_hours: 168.0