91 lines
4.9 KiB
YAML
91 lines
4.9 KiB
YAML
# 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
|