"""Utopia Pricing Agent โ one-page dashboard (CRAI design system).
Utopia Brands look (crai.utopiabrands.com): off-white cards on warm cream paper,
teal primary, coral accent, navy chrome. Sidebar with filters, stat tiles, action
pills, and a recommendation queue where clicking a SKU row drops down the advanced
analysis (price & demand, inventory, scenarios, competitors, PPC, costs, AI
reasoning). All numbers come from the live COSMOS pipeline โ fees, 6-month
history, elasticity, actual profit.
Run: streamlit run app.py
"""
from __future__ import annotations
import hmac
import os
import sys
from pathlib import Path
# Make src/ + project root importable when launched via `streamlit run`.
ROOT = Path(__file__).resolve().parent
for p in (str(ROOT / "src"), str(ROOT)):
if p not in sys.path:
sys.path.insert(0, p)
from datetime import date, datetime, timedelta
import pandas as pd
import streamlit as st
from plotly.subplots import make_subplots
import plotly.graph_objects as go
from dashboard import theme
# Ranking rules live in the data layer so they can be tested without booting
# Streamlit โ importing app.py would execute the whole page.
from dashboard.line import LINE_SORTS, line_sort_key as _line_sort_key
from dashboard.live_data import (
FALLBACK_ELASTICITY, REASON_LABELS, REFERRAL_PCT, VARIABLE, modelled,
reproject_inventory,
scenarios_for_window,
)
TODAY = date.today()
st.set_page_config(page_title="Utopia Pricing Agent", page_icon="๐งญ",
layout="wide", initial_sidebar_state="auto")
theme.register_template()
def _require_password() -> None:
"""Shared-password gate, active only when APP_PASSWORD is set.
The app authenticates to COSMOS with the credentials in .env and shows live
cost, margin and sales data, so on a shared network the URL alone is enough
for anyone to read it. Set APP_PASSWORD before binding to 0.0.0.0. Left unset
(the default, and the local-only case) nothing changes.
This is a doorlock, not an identity system: one shared secret, no accounts,
no audit of who looked. For anything beyond an internal LAN, put it behind a
real reverse proxy with SSO.
"""
expected = os.environ.get("APP_PASSWORD")
if not expected or st.session_state.get("_authed"):
return
st.markdown(
'
๐ Utopia Pricing Agent
'
'
This dashboard shows live cost and margin data. '
'Enter the shared password to continue.
',
unsafe_allow_html=True)
_, mid, _ = st.columns([1, 2, 1])
with mid:
pw = st.text_input("Password", type="password", label_visibility="collapsed",
placeholder="Shared password")
if pw:
# compare_digest so a wrong guess takes the same time as a right one
if hmac.compare_digest(pw, expected):
st.session_state["_authed"] = True
st.rerun()
else:
st.error("Incorrect password.")
st.stop()
st.markdown("""
""", unsafe_allow_html=True)
# Gate runs after the stylesheet so the lock screen is styled like the rest.
_require_password()
ACTION_GLYPH = {"Increase": "โ", "Decrease": "โ", "Maintain": "โ", "Investigate": "๐"}
# The per-SKU section switcher (replaces st.tabs so "View more" can jump to a section).
# Ordered by the questions a reviewer actually asks, in order: what's the shape of
# this product, can I trust the model, what are my options, why this, then reference.
# "Track record" was previously buried at the bottom of the last tab โ it is the one
# thing that says how wrong the engine has been, so it sits second.
VIEW_KEYS = ["price", "track", "scenarios", "why", "inventory", "repricing",
"competitors", "ppc", "costs"]
VIEW_LABELS = {
"price": "๐ Price & demand", "track": "๐ฏ Track record",
"scenarios": "๐งฎ Scenarios", "why": "๐ Why this",
"inventory": "๐ฆ Inventory outlook",
"repricing": "๐ Stock at the new price",
"competitors": "๐ฅ Competitors",
"ppc": "๐ฃ PPC", "costs": "๐ฐ Costs",
}
VIEW_SCENARIOS = "scenarios"
# Internal objective codes โ language a human uses.
OBJECTIVE_LABELS = {
"max_profit": "maximise profit", "margin_protection": "protect margin",
"low_stock_protection": "protect low stock", "overstock_reduction": "clear overstock",
"fix_data_first": "fix the data first", "observed_best": "match the best price we've run",
"fix_ad_efficiency": "fix ad efficiency",
}
# Inventory band codes โ what they mean.
INV_CLASS_LABELS = {"alpha": "Alpha (fast-moving)", "beta": "Beta (slower-moving)"}
MARKETS = { # marketplace code โ (flag, short label)
"AMAZON_USA": ("๐บ๐ธ", "US"), "AMAZON_CA": ("๐จ๐ฆ", "CA"), "AMAZON_UK": ("๐ฌ๐ง", "UK"),
"AMAZON_DE": ("๐ฉ๐ช", "DE"), "AMAZON_FR": ("๐ซ๐ท", "FR"), "AMAZON_IT": ("๐ฎ๐น", "IT"),
"AMAZON_ES": ("๐ช๐ธ", "ES"), "AMAZON_NL": ("๐ณ๐ฑ", "NL"), "AMAZON_SE": ("๐ธ๐ช", "SE"),
"AMAZON_PL": ("๐ต๐ฑ", "PL"), "AMAZON_BE": ("๐ง๐ช", "BE"), "AMAZON_TR": ("๐น๐ท", "TR"),
"AMAZON_IE": ("๐ฎ๐ช", "IE"), "AMAZON_AU": ("๐ฆ๐บ", "AU"),
}
def mkt_label(m: str) -> str:
flag, short = MARKETS.get(m, ("๐", m))
return f"{flag} {short}"
# ---------------------------------------------------------------- data loading
def _load_live(skus: tuple, with_comp: bool, progress=None) -> dict:
"""Session-cached loader. Not @st.cache_data โ the progress callback writes to a
live Streamlit element, which the cache's element-replay can't handle. We cache
the pure result in session_state instead, so progress stays live on cold loads."""
cache = st.session_state.setdefault("_live_cache", {})
key = (skus, with_comp)
if key in cache:
return cache[key]
from dashboard.live_data import get_live_data
data = get_live_data(skus, with_competitive=with_comp, progress_cb=progress)
cache[key] = data
return data
def _clear_live_cache():
st.session_state["_live_cache"] = {}
def _amazon_url(asin: str | None, marketplace: str = "AMAZON_USA") -> str | None:
"""Amazon product-detail URL for an ASIN, per marketplace domain."""
if not asin or asin == "โ":
return None
domains = {
"AMAZON_USA": "com", "AMAZON_CA": "ca", "AMAZON_UK": "co.uk",
"AMAZON_DE": "de", "AMAZON_FR": "fr", "AMAZON_IT": "it", "AMAZON_ES": "es",
"AMAZON_NL": "nl", "AMAZON_SE": "se", "AMAZON_PL": "pl", "AMAZON_BE": "com.be",
"AMAZON_TR": "com.tr", "AMAZON_IE": "ie", "AMAZON_AU": "com.au",
}
return f"https://www.amazon.{domains.get(marketplace, 'com')}/dp/{asin}"
@st.cache_data(ttl=3600, show_spinner=False)
def _find_skus(prefix: str, limit: int) -> tuple:
from config.settings import get_settings
from pricing_agent.analyze import _build_service
svc = _build_service(get_settings())
return tuple(svc.list_skus(limit=limit, sku_prefix=prefix or None))
@st.cache_data(ttl=3600, show_spinner=False)
def _find_line(prefix: str) -> list[dict]:
"""Every product in a line, with stock and velocity โ one COSMOS call."""
from config.settings import get_settings
from pricing_agent.analyze import _build_service
svc = _build_service(get_settings())
return [{"sku": p.sku, "asin": p.asin, "size": p.size,
"inventory": p.inventory, "avg_7d": p.avg_7d, "avg_30d": p.avg_30d,
"avg_6m": p.avg_6m, "cover_days": p.cover_days,
"min_po_quantity": p.min_po_quantity,
"potential_daily_sale": p.potential_daily_sale}
for p in svc.find_line(prefix)]
def _parse_skus(raw: str) -> tuple:
parts = [p.strip() for chunk in raw.replace(",", "\n").splitlines()
for p in [chunk] if p.strip()]
seen, out = set(), []
for p in parts:
if p.upper() not in seen:
seen.add(p.upper())
out.append(p)
return tuple(out)
# ---------------------------------------------------------------- helpers
def esc(s: str) -> str:
"""Escape $ so Streamlit markdown never enters LaTeX math mode."""
return s.replace("$", "\\$")
def dash(v, fmt: str = "{}") -> str:
"""Format a value, or an em-dash when it is missing."""
if v is None or (isinstance(v, float) and pd.isna(v)):
return "โ"
return fmt.format(v)
def compact_money(v: float) -> str:
"""$33k rather than $32,974 โ false precision from a model with a known error
band reads as certainty the number does not have."""
a = abs(v)
sign = "-" if v < 0 else ""
if a >= 1_000_000:
return f"{sign}${a / 1_000_000:.1f}M"
if a >= 1_000:
return f"{sign}${a / 1_000:,.0f}k"
return f"{sign}${a:,.0f}"
def model_error_pct(d: dict) -> float | None:
"""This SKU's backtested error, as a fraction โ the width of any honest range."""
bt = d.get("backtest") or {}
sel = bt.get("selected")
mape = (bt.get("scores", {}).get(sel, {}) or {}).get("mape") if sel else None
return (mape / 100.0) if mape else None
def money_range(v: float, err: float | None) -> str:
"""A point estimate widened by the model's own measured error."""
if not err:
return compact_money(v)
lo, hi = v * (1 - err), v * (1 + err)
return f"{compact_money(min(lo, hi))}โ{compact_money(max(lo, hi))}"
def unit_take_home(price: float, d: dict) -> float:
"""Per-unit take-home under this SKU's COSMOS fee model."""
rp = d.get("referral_pct", REFERRAL_PCT)
ret = d.get("returns_pct", 0.0)
var = d.get("variable", VARIABLE)
return price * (1 - rp - ret) - d["cfg"]["fba"] - d["cfg"]["cost"] - var
def _price_move_html(delta_pct: float) -> str:
"""Colored arrow + percentage chip for a price move vs current."""
if abs(delta_pct) < 0.05:
return 'โ 0.0%'
if delta_pct > 0:
return (f'โฒ +{delta_pct:.1f}%')
return (f'โผ {delta_pct:.1f}%')
WINDOW_OPTS = {"7 days": 7, "14 days": 14, "30 days": 30, "90 days": 90,
"6 months": 180}
DEFAULT_WINDOW = "90 days" # one month is mostly noise; 90d spans real price variation
def render_scenarios(d: dict, key_prefix: str, compact: bool = False):
"""Scenario view on ONE price basis: revenue + ad-inclusive net profit,
coloured price moves, and per-row marketing advice.
A window filter drives the averaging: units/day, revenue, ad spend and profit
all come from the SAME window, so revenue รท units/day == the actual average
selling price."""
base_econ = d.get("scen_econ") or []
if not base_econ:
st.info("No scenario economics available for this product.")
return
# โโ The recommendation itself. This is the engine's decision, not a separate
# re-derivation from a different window โ showing one number here and another
# in the table below is how the two used to disagree by 3.5x. โโ
tgt = d.get("target_price", d["rec_price"])
if d["action"] == "Maintain":
body = (f"Hold at ${d['current_price']:.2f} โ nothing in the evidence "
f"beats the current price.")
else:
arrow = "Raise" if d["rec_price"] > d["current_price"] else "Lower"
body = (f"{arrow} from ${d['current_price']:.2f} to "
f"${d['rec_price']:.2f} ({d['delta_pct']:+.1f}%)")
if d.get("stepped") and abs(tgt - d["rec_price"]) > 0.01:
body += (f", stepping toward ${tgt:.2f} โ one move at a time so the "
f"demand response can be read before the next one")
body += "."
if d.get("impact_30d"):
body += f" Projected {d['impact_30d']:+,.0f}/30d net profit."
ob = d.get("observed_price_max")
if ob:
body += (f" Highest price with a real sample behind it: ${ob:.2f}.")
st.markdown(
f''
f'๐ก Recommendation: '
f'{body}
',
unsafe_allow_html=True,
)
# Averaging-window filter โ everything below derives from this one window.
wl = st.segmented_control(
"Averaging window", list(WINDOW_OPTS), key=f"win_{key_prefix}",
default=DEFAULT_WINDOW, help="Units/day, revenue, ad spend and profit are all "
"averaged over this window, so they always reconcile.")
days = WINDOW_OPTS.get(wl or DEFAULT_WINDOW, 90)
if days == d.get("scen_window", 90):
econ = base_econ
summary = d.get("scen_summary")
best_key = d.get("scen_best_key")
facts = None
else:
econ, meta, facts = scenarios_for_window(d, days)
summary = meta.get("summary")
best_key = meta.get("best_key")
cur = next((x for x in econ if x["key"] == "current"), econ[0])
price_check = (cur["revenue_30d"] / cur["units_30d"]) if cur["units_30d"] else 0.0
best = next((x for x in econ if x["key"] == best_key), None)
# ONE caveat block, not four scattered ones. Every projection on this tab
# carries the same two qualifications, so they are stated once, together.
el = d.get("elasticity_detail") or {}
caveats = []
if not d.get("elasticity_actionable"):
caveats.append("**Elasticity is not usable for pricing on this SKU** โ "
+ (el.get("why") or "the fit does not establish a price/volume link")
+ ". The recommendation comes from prices this product has "
"actually run, not from the curve below.")
if d.get("profit_optimal_blocked_reason"):
line = f"**No profit-optimal price published** โ {d['profit_optimal_blocked_reason']}."
if d.get("profit_optimal_unconstrained"):
line += (f" Taken literally the fit implies pricing at "
f"${d['profit_optimal_unconstrained']:.2f}, which is itself evidence "
f"the fit is unusable.")
caveats.append(line)
if caveats:
st.warning(esc("โ ๏ธ " + "\n\n".join(caveats)))
a1, a2, a3 = st.columns(3)
a1.metric(f"Units/day (last {days}d)", f"{cur['units_day']:,}",
f"avg sold ${price_check:,.2f}/unit", delta_color="off")
a2.metric("Net profit /30d", f"${cur['net_30d']:,.0f}",
f"on ${cur['revenue_30d']:,.0f} revenue", delta_color="off")
if best:
lbl = "Best price = hold" if best["key"] == "current" else "Best price (projected)"
a3.metric(lbl, f"${best['price']:.2f}",
f"{best['net_vs_current']:+,.0f}/30d vs now", delta_color="off")
rows_html = []
for x in econ:
is_best = x["key"] == best_key
is_cur = x["key"] == "current"
emoji = "๐ฐ" if is_cur else ("๐" if x["price"] > cur["price"] else "๐")
row_bg = "#eef6f3" if is_best else ("#fbf6ee" if is_cur else "transparent")
star = " โญ" if is_best else ""
net_color = "#0a6f65" if x["net_30d"] >= 0 else "#c9442b"
# Current row: show the AVG PRICE SOLD (revenue รท units) so price ร units ร 30 =
# revenue reconciles. It sits below list when there are promos, and varies by
# window because the avg selling price differed period to period.
if is_cur and x["units_30d"]:
eff = x["revenue_30d"] / x["units_30d"]
price_cell = (f'${eff:,.2f}'
f'avg sold ยท '
f'{days}d
'
f'list '
f'${x.get("list_price", x["price"]):.2f}
')
move_cell = 'actual'
else:
# Provenance: a price this SKU has really run is evidence; one it has
# never run is extrapolation, and the table must not look the same.
obs = x.get("observed_days") or 0
tag = (f''
f'โ ran {obs}d
' if obs else
'never tested
')
price_cell = f'${x["price"]:.2f}{tag}'
move_cell = _price_move_html(x["delta_pct"])
rows_html.append(
f''
f'| {emoji} {esc(x["label"])}{star} | '
f'{price_cell} | '
f'{move_cell} | '
f'{x["units_day"]:,} | '
f'${x["revenue_30d"]:,.0f} | '
f'${x["ad_30d"]:,.0f} | '
f'${x["net_30d"]:,.0f} | '
f'{x["net_margin_pct"]:.1f}% | '
# No esc() inside the raw HTML table โ Streamlit does not run LaTeX on
# these cells, so escaping would render a literal backslash.
+ ("" if compact else
f'{x["advice"]} | ')
+ '
'
)
# Column headers with hover tooltips explaining how each number is computed.
elas = d["cfg"].get("elasticity", -1.3)
eld = d.get("elasticity_detail") or {}
el_note = (f"elasticity {elas:.2f}"
+ (f" (95% CI {eld['ci_low']} to {eld['ci_high']}, "
f"{'usable' if d.get('elasticity_actionable') else 'NOT statistically usable'})"
if eld.get("ci_low") is not None else ""))
anchor_p = cur.get("price", d["current_price"])
tacos_now = cur["ad_30d"] / cur["revenue_30d"] * 100 if cur["revenue_30d"] else 0.0
def th(label, tip, left=False):
align = "left" if left else "center"
return (f''
f'{label}'
f' โ | ')
headers = (
th("Scenario", f"Current = your actual last-{days}-day facts. Every other row is "
f"a what-if. 'โ ran Nd' means this price really has been live for "
f"N days โ that row is evidence, not a projection.", left=True)
+ th("Price", "Current = your AVERAGE price sold over the window (revenue รท "
"units); your list price is shown beneath it. Other rows = the "
"price you would set.")
+ th("Move", "Percentage change vs your current list price.")
+ th("Units/day", f"Projected demand = current units ร (new price รท the price "
f"customers actually paid, ${anchor_p:,.2f}) ^ elasticity. "
f"Anchoring on the paid price โ not list โ is what keeps every "
f"row on one basis. {el_note}.")
+ th("Revenue/30d", "Units/day ร 30 ร price.")
+ th("Ad spend/30d", f"Ad cost PER UNIT ร units, with per-unit cost fitted "
f"against price from your own history (a higher price "
f"converts worse, so each sale costs more). Current TACoS "
f"{tacos_now:.1f}%.")
+ th("Net profit/30d", "Take-home โ storage โ ad spend. No realization factor: "
"a single multiplier fitted at one price also distorts the "
"difference between prices, which is the whole question.")
+ th("Net margin", "Net profit รท revenue.")
+ ("" if compact else th("Marketing advice",
"Plain-language read of the price / volume / ad-spend "
"trade-off for this row.", left=True))
)
st.markdown(
''
'' + headers + '
'
+ "".join(rows_html) + '
',
unsafe_allow_html=True,
)
st.caption("โ = this price has really been run ยท โญ = highest projected net profit")
# Methodology lives behind a disclosure. It answers "how is this computed?",
# which is a question asked once โ not on every read of the table. The same
# detail is on each column header, but hover is dead on touch and keyboard,
# so this is the accessible copy of it.
with st.expander("How these numbers are calculated"):
anchor = cur.get("price", d["current_price"])
adm = d.get("ad_cost_model") or {}
ad_note = "held flat per unit"
if adm and adm.get("r2", 0) >= 0.25:
ad_note = (f"rising ${adm['slope']:+.2f}/unit per $1 of price โ fitted on "
f"{adm['days']} days of your own data (rยฒ={adm['r2']:.2f}), "
f"because a higher price converts worse and costs more per sale")
st.markdown(esc(
f"- **Window** โ every figure is averaged over the last **{days} days**, so "
f"units, revenue, ad spend and profit always reconcile.\n"
f"- **Current row** โ COSMOS fact: actual booked revenue, ad spend and "
f"profit. Its price is your **average sold** (${price_check:,.2f}), not your "
f"list price (${d['current_price']:.2f}); the two differ whenever there are "
f"promotions or coupons.\n"
f"- **Projected rows** โ anchored at the price customers actually paid "
f"(${anchor:,.2f}), not list, so every row sits on one basis.\n"
f"- **Units/day** โ current units ร (new price รท ${anchor:,.2f}) ^ "
f"elasticity, where {el_note}.\n"
f"- **Ad spend** โ ad cost per unit ร units, with per-unit cost {ad_note}.\n"
f"- **Net profit** โ take-home โ storage (${d.get('storage_30d', 0):,.0f}/30d) "
f"โ ad spend"
+ (f", less a ${d['gap_per_unit']:.2f}/unit unmodeled cost measured against "
f"actual booked profit" if d.get("gap_per_unit") else "")
+ ".\n"
f"- Reconciliation check: ${price_check:,.2f} ร {cur['units_day']:,} units "
f"ร 30 โ ${cur['revenue_30d']:,.0f} revenue."))
def set_status(sku: str, status: str):
st.session_state.status[sku] = status
if status == "Pending":
st.session_state.modified.pop(sku, None)
icon = {"Approved": "โ
", "Rejected": "๐ซ", "Pending": "๐"}[status]
st.toast(f"{sku} โ {status}", icon=icon)
def apply_modify(sku: str, details: dict):
new_price = float(st.session_state[f"mod_{sku}"])
d = details[sku]
st.session_state.modified[sku] = new_price
st.session_state.status[sku] = "Approved"
over_cap = abs(new_price - d["current_price"]) / d["current_price"] > 0.05
note = " ยท exceeds 5% step cap โ elevated approval tier" if over_cap else ""
st.toast(f"{sku} approved at modified ${new_price:.2f}{note}", icon="โ๏ธ")
def bulk_approve(skus: list):
for s in skus:
st.session_state.status[s] = "Approved"
st.toast(f"Bulk-approved {len(skus)} high-confidence recommendations", icon="โ
")
def reset_workspace():
st.session_state.status = {}
st.session_state.modified = {}
st.session_state.action_sel = "All"
# Also drop the analysis cache. Without this, 'Reset workspace' cleared the
# approve/reject marks but kept every previously computed SKU in
# st.session_state['_live_cache'], so re-analysing returned the SAME numbers
# from before โ including after a code change โ and the button looked broken.
st.session_state.pop("_live_cache", None)
st.toast("Workspace reset", icon="๐")
def set_action(a: str):
st.session_state.action_sel = a
def goto_view(sku: str, view: str):
"""Jump a SKU's section switcher to `view` (used by the View more button)."""
st.session_state[f"view_{sku}"] = view
# ---------------------------------------------------------------- charts
def price_demand_chart(d) -> go.Figure:
"""Two stacked panels, one shared x-axis โ never a dual-axis chart."""
hist, fc = d["hist"], d["forecast"]
fig = make_subplots(rows=2, cols=1, shared_xaxes=True, row_heights=[0.5, 0.5],
vertical_spacing=0.12, subplot_titles=("Price ($)", "Units / day"))
fig.add_trace(go.Scatter(x=hist["date"], y=hist["price"], name="Our price",
line=dict(color=theme.SERIES["us"], width=2, shape="hv")), row=1, col=1)
has_comp_hist = hist["comp_median"].notna().any()
if has_comp_hist:
fig.add_trace(go.Scatter(x=hist["date"], y=hist["comp_median"], name="Competitor median",
line=dict(color=theme.SERIES["competitor"], width=2)), row=1, col=1)
if d["change_dates"]:
ys = [float(hist.loc[hist["date"] == dt, "price"].iloc[0]) for dt in d["change_dates"]]
fig.add_trace(go.Scatter(x=d["change_dates"], y=ys, mode="markers", name="Price change",
marker=dict(symbol="triangle-down", size=10,
color=theme.SERIES["us"])), row=1, col=1)
lo = float(min(hist["price"].min(), hist["comp_median"].min())
if has_comp_hist else hist["price"].min())
hi = float(max(hist["price"].max(), hist["comp_median"].max())
if has_comp_hist else hist["price"].max())
span = hi - lo if hi > lo else max(hi * 0.1, 0.1)
guides = [(d["min_price"], f"min ${d['min_price']:.2f}", theme.STATUS["critical"]),
(d["max_price"], f"max ${d['max_price']:.2f}", theme.STATUS["serious"]),
(d["break_even"], f"break-even ${d['break_even']:.2f}", theme.MUTED)]
if d.get("comp_median_now"):
guides.append((d["comp_median_now"],
f"competitor median ${d['comp_median_now']:.2f}",
theme.SERIES["competitor"]))
for y, txt, color in guides:
if y and lo - span * 0.6 <= y <= hi + span * 0.6:
fig.add_hline(y=y, line_dash="dot", line_color=color, line_width=1,
annotation_text=txt, annotation_font_size=10,
annotation_font_color=theme.MUTED, row=1, col=1)
fig.add_trace(go.Scatter(x=hist["date"], y=hist["units"], name="Units",
line=dict(color=theme.SERIES["us"], width=1.5)), row=2, col=1)
fig.add_trace(go.Scatter(x=fc["date"], y=fc["p90"], line=dict(width=0),
showlegend=False, hoverinfo="skip"), row=2, col=1)
fig.add_trace(go.Scatter(x=fc["date"], y=fc["p10"], line=dict(width=0), fill="tonexty",
fillcolor=theme.US_BAND, name="Forecast p10โp90",
hoverinfo="skip"), row=2, col=1)
fig.add_trace(go.Scatter(x=fc["date"], y=fc["p50"], name="Forecast p50",
line=dict(color=theme.SERIES["us"], width=2, dash="dash")), row=2, col=1)
today = pd.Timestamp(TODAY)
fig.add_shape(type="line", x0=today, x1=today, y0=0, y1=1, xref="x", yref="paper",
line=dict(color=theme.AXIS, width=1, dash="dot"))
fig.add_annotation(x=today, y=1.02, xref="x", yref="paper", text="today", showarrow=False,
font=dict(size=10, color=theme.MUTED), xanchor="left")
fig.update_yaxes(rangemode="tozero", row=2, col=1)
fig.update_layout(height=440)
return fig
def inventory_chart(d) -> go.Figure:
"""Projected inventory over COSMOS's own INVP weekly snapshots, with arrivals."""
proj = d.get("inv_projection") or []
fig = go.Figure()
if not proj:
return fig
dates = [pd.Timestamp(p["date"]) for p in proj]
units = [float(p["inventory"] or 0) for p in proj]
fig.add_trace(go.Scatter(x=dates, y=units, name="Projected inventory",
mode="lines+markers",
line=dict(color=theme.SERIES["us"], width=2)))
fig.add_hline(y=0, line_color=theme.STATUS["critical"], line_dash="dot", line_width=1)
for p, dt, u in zip(proj, dates, units):
arr = float(p["warehouse_arrival"] or 0)
if arr > 0:
fig.add_annotation(x=dt, y=u, text=f"๐ +{arr:,.0f}", showarrow=True,
arrowhead=2, font=dict(size=10, color=theme.INK2))
fig.update_yaxes(rangemode="tozero", title_text="units")
fig.update_layout(height=280, showlegend=False)
return fig
# Cover-day color bands (Alpha/Beta scheme, matched to the planning sheet)
def _bucket_bg(cover: float, inv_class: str) -> str:
"""Cell shading by projected cover days, per the SKU's Alpha/Beta band scheme.
Thresholds and colours both live in `theme.COVER_BANDS` โ one definition, so the
inventory grid, the cover tile and anything added later cannot drift into shading the
same number three different ways.
"""
return theme.cover_band(cover, inv_class)[0]
def ppc_chart(d) -> go.Figure:
"""Daily PPC spend (thin) with 7-day moving average (bold), last 90 days."""
h = d["hist"].tail(90)
ma = h["ad_spend"].rolling(7, min_periods=1).mean()
fig = go.Figure()
fig.add_trace(go.Scatter(x=h["date"], y=h["ad_spend"], name="Daily spend",
line=dict(color=theme.MUTED, width=1)))
fig.add_trace(go.Scatter(x=h["date"], y=ma, name="7-day average",
line=dict(color=theme.SERIES["us"], width=2)))
fig.update_yaxes(rangemode="tozero", title_text="spend ($/day)")
fig.update_layout(height=280)
return fig
def inventory_table_html(d) -> str:
"""COSMOS INVP weekly projection table โ REAL data from invp-insight dateMap:
projected units, inventory value, cover days (blue), and incoming arrivals."""
proj = d.get("inv_projection") or []
cfg, hist = d["cfg"], d["hist"]
asin = d.get("asin") or f"B0{abs(hash(cfg['sku'])) % 10**8:08d}"
asin_url = _amazon_url(asin, cfg.get("marketplace", "AMAZON_USA"))
asin_html = (f'{asin}' if asin_url else asin)
# COSMOS's OWN daily-sale figures, not ours re-derived from sales-insight history. The
# weekly cells below come straight from the INVP dateMap and always matched; the header did
# not, so one SKU showed 56 / 62 / 65 here against 64 / 130 / 67 in Inventory Planning.
#
# Falls back to the history means only where COSMOS returns nothing, so a gap still shows a
# number rather than a dash โ but the two tools agree wherever COSMOS has an answer.
_st = d.get("invp_stats") or {}
d7 = _st.get("avg_sale") or _st.get("avg_7d") or hist["units"].tail(7).mean()
# The middle figure in COSMOS's header is POTENTIAL daily sale โ its own demand estimate,
# not a trailing average. It is a different quantity from the 30-day mean that used to sit
# here, so it is labelled for what it is.
d_pot = _st.get("potential_daily_sale")
d6m = _st.get("avg_6m") or hist["units"].tail(90).mean()
reviews = f" ({cfg['reviews']:,})" if cfg.get("reviews") else ""
if not proj:
return (''
'No INVP projection available for this product from COSMOS.
')
# Match the COSMOS Inventory Planning header exactly.
#
# `proj[0].inventory` is the FIRST PROJECTED WEEK, which already has that
# week's arrival folded in (5,029 = 3,999 on hand + 1,030 landing), so showing
# it as stock overstated the shelf by the inbound load. COSMOS shows them apart.
#
# Inbound is likewise the IMMEDIATE arrival, not every arrival on the horizon:
# summing the lot gave 1,770 against COSMOS's 1,030 by sweeping in +90 and +650
# that land weeks later. Those are real, so they are reported separately.
on_hand = float((d.get("invp_stats") or {}).get("inventory")
or proj[0]["inventory"] or 0)
total_inbound = float(proj[0]["warehouse_arrival"] or 0)
later_inbound = sum(float(p["warehouse_arrival"] or 0) for p in proj[1:])
heads = "".join(
f'{pd.Timestamp(p["date"]):%m/%d/%Y} | ' for p in proj
)
cells = []
for p in proj:
units = float(p["inventory"] or 0)
value = float(p["inventory_value"] or (units * cfg["cost"]))
cover = float(p["cover_days"]) if p["cover_days"] is not None else 0.0
arr = float(p["warehouse_arrival"] or 0)
cells.append(
f''
f' {units:,.0f} '
f'${value:,.0f} '
f'{cover:,.0f}'
f'{arr:,.0f} | '
)
return (
''
f'| Product Description | Daily Sale | {heads}
'
""
f'{cfg["title"]} '
f'{cfg["sku"]} ยท {asin_html}{reviews} '
f'๐ฆ {on_hand:,.0f} on hand ยท ๐ {total_inbound:,.0f} inbound'
f'{f" (+{later_inbound:,.0f} later)" if later_inbound else ""} ยท '
f'class {cfg["inv_class"].capitalize()} | '
f'{d7:,.0f} '
+ (f'{d_pot:,.0f} ยท potential ' if d_pot else '')
+ f'{d6m:,.0f} ยท 6mo avg | '
f'{"".join(cells)}'
"
"
)
# ---------------------------------------------------------------- sidebar
if "action_sel" not in st.session_state:
st.session_state.action_sel = "All"
if "status" not in st.session_state:
st.session_state.status = {}
if "modified" not in st.session_state:
st.session_state.modified = {}
if "live_skus" not in st.session_state:
st.session_state.live_skus = ()
with st.sidebar:
st.markdown(
'U
'
'
Utopia Pricing
'
'
AI Pricing Agent
',
unsafe_allow_html=True,
)
st.markdown("")
st.caption("ANALYZE")
with st.expander("Load products", expanded=not st.session_state.live_skus):
mode = st.radio("Mode", ["Single product", "Product line"], horizontal=True,
label_visibility="collapsed")
with_comp = st.checkbox("Include competitor prices", value=False,
help="Adds Buy Box + rival offers per product. Slower; "
"needs the competitor-data token configured in .env.")
if mode == "Single product":
one = st.text_input("Product SKU", placeholder="e.g. UB-PILLOW-QUEEN",
help="Enter one Amazon SKU to analyze.")
if st.button("๐ Analyze product", use_container_width=True, type="primary"):
skus = _parse_skus(one)
if not skus:
st.warning("Enter a SKU first.")
else:
_clear_live_cache()
st.session_state.live_skus = skus[:1]
st.session_state.live_comp = with_comp
else:
# Two steps on purpose: find the WHOLE line first, then decide what
# to analyze. Asking for a count up front means choosing blind โ and
# silently truncating a 99-product line to the first 15.
prefix = st.text_input("Product line / SKU prefix",
placeholder="e.g. UBMICROFIBERDUVET",
help="Finds every product whose SKU contains this text.")
if st.button("๐ Find products", use_container_width=True):
if not prefix.strip():
st.warning("Enter a product-line prefix first.")
else:
with st.spinner("Finding every product in this lineโฆ"):
st.session_state.line = _find_line(prefix.strip())
st.session_state.line_prefix = prefix.strip()
paste = st.text_area("โฆor paste an explicit SKU list",
placeholder="SKU-001, SKU-002, SKU-003", height=68)
if paste.strip() and st.button("๐ Analyze pasted list",
use_container_width=True, type="primary"):
skus = _parse_skus(paste)
if not skus:
st.warning("Could not read any SKUs from that list.")
else:
_clear_live_cache()
st.session_state.live_skus = skus
st.session_state.live_comp = with_comp
st.divider()
st.caption("FILTERS")
q = st.text_input("Search", placeholder="Search SKU or productโฆ")
# ------------------------------------------------- product-line picker (main area)
line = st.session_state.get("line") or []
if line and not st.session_state.live_skus:
st.markdown(f"## {len(line)} products in `{st.session_state.get('line_prefix', '')}`")
in_stock = [p for p in line if p["inventory"] > 0]
selling = [p for p in line if p["avg_30d"] > 0]
st.caption(f"{len(in_stock)} hold stock ยท {len(selling)} sold in the last 30 days ยท "
f"{sum(p['inventory'] for p in line):,.0f} units on hand")
f1, f2, f3 = st.columns([1.4, 1.4, 1.2])
with f1:
order = st.selectbox(
"Rank by", list(LINE_SORTS),
help="Which products matter most to look at first.")
with f2:
sizes = sorted({p["size"] for p in line})
pick_sizes = st.multiselect("Size", sizes, default=[],
help="Bedding size, read from the SKU. "
"Empty = all sizes.")
with f3:
n_no_stock = len(line) - len(in_stock)
only_stock = st.toggle(
f"Only products with stock ({len(in_stock)})", value=True,
help=f"{n_no_stock} of {len(line)} products hold no inventory. A price "
f"change does nothing for a SKU with nothing to sell, so they are "
f"hidden by default โ switch this off to include them.")
pool = [p for p in line
if (not only_stock or p["inventory"] > 0)
and (not pick_sizes or p["size"] in pick_sizes)]
key, reverse = LINE_SORTS[order]
pool = sorted(pool, key=lambda p: _line_sort_key(p, key), reverse=reverse)
if not pool:
st.warning("No products match those filters โ loosen them to continue.")
st.stop()
# Say why the pool is smaller than the line. Without this the headline reads
# "99 products" while the slider stops at 68, and nothing on screen explains it.
hidden = len(line) - len(pool)
if hidden:
why = []
if only_stock and n_no_stock:
why.append(f"{n_no_stock} have no stock")
if pick_sizes:
why.append(f"size filter: {', '.join(pick_sizes)}")
st.info(esc(
f"Showing **{len(pool)} of {len(line)}** โ {hidden} hidden "
f"({'; '.join(why)}). Clear the filters above to reach all {len(line)}."))
if len(pool) == 1: # a slider needs a range to be a slider
how_many = 1
st.caption("1 product matches โ it will be analyzed on its own.")
else:
how_many = st.slider(
f"How many to analyze โ up to {len(pool)}", 1, len(pool),
min(10, len(pool)),
help="Each product takes roughly 30โ60 seconds against COSMOS, so start "
"small and widen once you trust the results.")
chosen = pool[:how_many]
mins = max(1, round(how_many * 45 / 60))
est = (f"about **{mins} minute{'s' if mins != 1 else ''}**" if mins < 60 else
f"about **{mins / 60:.1f} hours** โ consider a smaller batch")
st.caption(f"Analyzing the top **{how_many}** of {len(pool)} by *{order.lower()}* "
f"ยท {est}")
st.dataframe(
pd.DataFrame([{
"": "โถ" if i < how_many else "",
"SKU": p["sku"], "Size": p["size"], "Inventory": p["inventory"],
"Units/day (30d)": p["avg_30d"], "Cover (days)": p["cover_days"],
"Min PO": p["min_po_quantity"],
} for i, p in enumerate(pool)]),
hide_index=True, use_container_width=True, height=320,
column_config={
"": st.column_config.TextColumn(width="small", help="Included in this run"),
"Inventory": st.column_config.NumberColumn(format="%d"),
"Units/day (30d)": st.column_config.NumberColumn(format="%d"),
"Min PO": st.column_config.NumberColumn(format="%d"),
})
b1, b2 = st.columns([1, 3])
with b1:
if st.button(f"โถ Analyze {how_many}", type="primary", use_container_width=True):
_clear_live_cache()
st.session_state.live_skus = tuple(p["sku"] for p in chosen)
st.session_state.live_comp = st.session_state.get("live_comp", False)
st.rerun()
with b2:
if st.button("โฉ Clear this line", use_container_width=True):
st.session_state.line = []
st.rerun()
st.stop()
# ---------------------------------------------------------------- load data
if not st.session_state.live_skus:
st.markdown("## Utopia Pricing Agent")
st.info("Open the sidebar and choose what to analyze:\n\n"
"- **Single product** โ enter one SKU for a full price analysis.\n"
"- **Product line** โ enter a SKU prefix and press **Find products**. "
"You'll see the whole line with stock and sales, then choose how many "
"to analyze and in what order.")
st.stop()
n = len(st.session_state.live_skus)
_loader = st.empty()
def _on_progress(frac: float, message: str):
frac = min(max(frac, 0.0), 1.0)
pct = int(round(frac * 100))
# Segmented bar (filled vs empty) for a crisp, on-brand look.
fill = int(round(frac * 34))
bar = ("" + "โ" * fill + ""
+ "" + "โ" * (34 - fill) + "")
try:
_loader.markdown(
f"""
โ๏ธ Analyzing against COSMOS
{n} product{'s' if n > 1 else ''} ยท live fees, 6-month
history, elasticity & bulk economics
{bar}
{pct}%
{esc(message)}
""",
unsafe_allow_html=True,
)
except Exception:
pass
_on_progress(0.0, "Connecting to COSMOSโฆ")
data = _load_live(st.session_state.live_skus,
st.session_state.get("live_comp", False), progress=_on_progress)
_loader.empty()
if data["errors"]:
st.warning("Some SKUs could not be analyzed: " + esc(
" ยท ".join(f"**{s}** ({m})" for s, m in data["errors"].items())))
summary, details = data["summary"], data["details"]
for sku in details: # keep statuses across reruns; init the new ones
st.session_state.status.setdefault(sku, "Pending")
if summary.empty:
st.info("No SKUs produced an analysis. Check the warnings above (typical causes: "
"wrong SKU code, or the SKU has no sales/price data in COSMOS).")
st.stop()
# ---------------------------------------------------------------- sidebar filters (cont.)
with st.sidebar:
f_mkt = st.selectbox("Marketplace", ["All"] + sorted(summary["marketplace"].unique()),
format_func=lambda m: "๐ All" if m == "All" else mkt_label(m))
f_conf = st.selectbox(
"Model trust", ["All", "High", "Medium", "Low", "None"],
help="How far this SKU's model may be trusted: backtest accuracy, breadth "
"of price evidence, elasticity usability and data completeness. Only "
"High may be bulk-approved.")
f_stat = st.selectbox("Status", ["All", "Pending", "Approved", "Rejected"])
st.divider()
st.caption("CONTROLS")
kill = st.toggle("๐ Global kill switch",
help="Emergency stop: pauses all approvals.")
st.button("โบ Reset workspace", on_click=reset_workspace, use_container_width=True)
st.divider()
st.caption("DATA STATUS")
st.caption(f"Prices as of {TODAY:%b %d, %Y}\n\n"
f"{len(details)} SKUs ยท 6-month COSMOS history\n\n"
+ ("Competitor prices: included\n\n" if st.session_state.get("live_comp")
else "Competitor prices: not included\n\n")
+ "Engine v1 ยท live COSMOS data")
# ---------------------------------------------------------------- header
hour = datetime.now().hour
greet = "Good morning" if hour < 12 else ("Good afternoon" if hour < 18 else "Good evening")
pending = [s for s, v in st.session_state.status.items()
if v == "Pending" and s in details]
# Bulk approval requires a model that has been SCORED on that SKU's own history,
# not merely a confident-looking label. `trust.auto_approve` is High only when the
# backtest, the evidence base and the data completeness all pass.
bulk_eligible = [s for s in pending
if (details[s].get("trust") or {}).get("auto_approve")
and details[s]["action"] in ("Increase", "Decrease")
and abs(details[s]["delta_pct"]) <= 5]
h1, h2 = st.columns([3, 1.3])
with h1:
# The headline slot goes to the thing the reviewer is here to do, not to a
# greeting. The greeting keeps the warmth, at caption size where it belongs.
n_pend = len(pending)
if len(details) == 1:
# Lead with the product and the verdict. "1 recommendation to review" is a queue
# heading, and on a single product there is no queue โ the reader already knows what
# they opened and wants to know what to do about it.
_hs = next(iter(details))
_hd = details[_hs]
_title = (_hd.get("cfg") or {}).get("title") or _hs
_act = _hd["action"]
_verb = {"Increase": "Raise", "Decrease": "Lower",
"Maintain": "Hold", "Investigate": "Investigate"}.get(_act, _act)
if _act in ("Increase", "Decrease"):
_hl = (f"{_verb} to ${_hd['rec_price']:,.2f} "
f"({_hd['delta_pct']:+.1f}% from ${_hd['current_price']:,.2f})")
elif _act == "Maintain":
_hl = f"Hold at ${_hd['current_price']:,.2f}"
else:
_hl = f"Investigate โ hold at ${_hd['current_price']:,.2f}"
st.markdown(f"## {esc(_hl)}")
_why = ", ".join(REASON_LABELS.get(c, c) for c in _hd["reasons"]) or "no signals"
st.caption(esc(f"{_title} ยท {_hs} ยท {_why}"))
else:
st.markdown(f"## {n_pend} recommendation{'s' if n_pend != 1 else ''} to review")
st.caption(f"{greet}, Utopia ๐ ยท sorted by expected profit impact")
with h2:
st.markdown("")
# Explain a control that cannot fire, rather than leaving a dead button.
if not bulk_eligible and not kill:
low_trust = [s for s in pending
if not (details[s].get("trust") or {}).get("auto_approve")]
why_blocked = (f"{len(low_trust)} of {n_pend} below High trust"
if low_trust else "nothing eligible")
else:
why_blocked = ""
st.button(f"โ
Bulk approve ยท {len(bulk_eligible)}",
disabled=kill or not bulk_eligible, use_container_width=True,
on_click=bulk_approve, args=(bulk_eligible,),
help=("Bulk approval needs a model scored on that SKU's own history. "
+ (f"Blocked: {why_blocked}." if why_blocked else
"Approves pending High-trust recommendations within the 5% step cap.")))
if why_blocked:
st.caption(esc(f"Needs High trust ยท {why_blocked}"))
if kill:
st.error("๐ **Kill switch active** โ approvals and price pushes are paused across all "
"marketplaces. Toggle it off in the sidebar to resume.")
# ---------------------------------------------------------------- stat tiles
needing_action = [s for s in pending if details[s]["action"] != "Maintain"]
opportunity = sum(max(details[s]["impact_30d"], 0) for s in pending)
# Same thresholds the decision logic uses, imported rather than restated so the
# tiles and the recommendations can never drift apart.
from dashboard.live_data import HIGH_COVER_DAYS, LOW_COVER_DAYS
stockout_risk = sum(1 for s in details if 0 < details[s]["cover_days"] <= LOW_COVER_DAYS)
overstock_risk = sum(1 for s in details if details[s]["cover_days"] >= HIGH_COVER_DAYS)
# The opportunity figure inherits the model's measured error, so it is shown as a
# band. A precise-looking $32,974 from a model backtested at ยฑ58% reads as a promise.
_errs = [e for s in pending if (e := model_error_pct(details[s])) is not None]
_err = max(_errs) if _errs else None
# ONE SKU is a different question from a portfolio, so it gets different tiles.
#
# The counts above answer "where in my catalogue should I look?" โ they are triage. On a
# single product that question is already answered, and the same tiles degenerate into
# tautologies: "1 SKU needing action" out of one, "0 stockout-risk SKUs", "1 pending
# approval". Nothing there tells a seller whether to take the price.
#
# So on a single product the header answers the seller's questions instead: what does this
# move earn, what margin does it leave, how much room is there before it loses money, will
# stock last, is it selling, and who else is on the listing.
if len(details) == 1:
_sku = next(iter(details))
_d = details[_sku]
_e1 = model_error_pct(_d)
_econ = {x["key"]: x for x in (_d.get("scen_econ") or [])}
_cur_e = _econ.get("current") or {}
# The row at the price we are RECOMMENDING TODAY, not the rung the cascade named. When a
# move is step-capped those differ: `rec_key` here is the $20.00 destination while the
# headline recommends $17.94, and quoting the destination's margin beside a $17.94 headline
# would promise 13.4% for a move that delivers 6.6%.
_rec_e = next((x for x in (_d.get("scen_econ") or [])
if abs(x["price"] - _d["rec_price"]) < 0.011),
_econ.get(_d.get("rec_key")) or {})
_fi = _d.get("floor_info") or {}
_floor = _fi.get("floor")
_cover = _d.get("cover_days")
_out = _d.get("outlook") or {}
_cm = _d.get("comp_meta") or {}
_ppc = _d.get("ppc") or {}
# 1. What the move is worth. Held to the same error band as the portfolio figure.
_impact = _d.get("impact_30d") or 0.0
_t_impact = ("๐ฐ", money_range(_impact, _e1) if _impact else "โ",
"Impact ยท 30d if approved",
(f"net of ads + storage ยท ยฑ{_e1 * 100:.0f}% model error" if _e1
else "net of advertising and storage"))
# 2. Margin now vs after. The number a seller judges a price by โ so both halves have to
# be the same model at two prices. The current row's headline margin is computed from
# BOOKED profit at the average price customers paid; comparing that to a projection makes
# the move look better or worse than it is. On this SKU booked reads 6.7% while the model
# at today's price reads 3.3%, so "6.7% โ 6.6%" said the move achieved nothing when it
# actually roughly doubles margin.
_m_rec = modelled(_rec_e).get("net_margin_pct")
_m_now = modelled(_cur_e).get("net_margin_pct")
_t_margin = ("๐",
f"{_m_rec:.1f}%" if _m_rec is not None else "โ",
"Net margin after the move",
(f"now {_m_now:.1f}% ยท same model, both prices" if _m_now is not None
else "after ads and storage"))
# 3. Headroom to the floor โ how much room before the price stops paying.
if _floor and _d.get("current_price"):
_head = (_d["current_price"] - _floor) / _d["current_price"] * 100
_t_floor = ("๐", f"{_head:+.1f}%", "Headroom above the floor",
f"floor ${_floor:,.2f} ({_fi.get('binding', 'break-even')}) ยท "
f"today ${_d['current_price']:,.2f}")
else:
_t_floor = ("๐", "โ", "Headroom above the floor", "no floor โ cost data missing")
# 4. Will the stock last? A date beats a threshold count on a single SKU.
#
# Both readings are shown, because COSMOS's Inventory Planning grid prints the LONGER
# one and a single unexplained number here reads as a mismatch with their tool. They are
# different questions: ours is what is on the shelf now, COSMOS's counts stock still in
# transit. Neither is wrong; the pricing rules use the shorter one deliberately, because
# a SKU cannot sell a unit that has not landed.
_inv_cls = ((_d.get("cfg") or {}).get("inv_class") or "alpha").lower()
_onhand = _d.get("cover_days_onhand")
_band_hex, _band_word = theme.cover_band(_cover, _inv_cls)
_so, _arr = _out.get("stockout_date"), _out.get("next_arrival_date")
if _so and _arr:
_cover_note = (f"{_band_word} ยท out ~{_so:%d %b} ยท restock {_arr:%d %b}"
+ (" โ gap" if _so < _arr else " โ covered"))
elif _so:
_cover_note = f"{_band_word} ยท out ~{_so:%d %b} ยท no restock scheduled"
else:
_cover_note = f"{_band_word} ยท {_inv_cls.capitalize()}-class band"
# Matches the COSMOS Inventory Planning grid. On-hand is shown beside it because the two
# differ by stock still in transit, and a stockout judged on undelivered units is worth
# being able to see.
if _onhand is not None and _cover is not None and _onhand != _cover:
_cover_note += f" ยท {_onhand:,} d on hand, rest inbound"
# A COSMOS band and a pricing trigger are different things, and the colour is the loudest
# thing on the tile โ so where they disagree, say so. Without this a pink 82-day SKU reads
# as "the engine is about to discount this", when the cut rule does not fire until 90.
if _cover is not None and _band_word in ("high", "low") and not (
0 < _cover <= LOW_COVER_DAYS or _cover >= HIGH_COVER_DAYS):
_edge = HIGH_COVER_DAYS if _band_word == "high" else LOW_COVER_DAYS
_cover_note += (f" ยท COSMOS band only โ no price move until {_edge} d")
_t_cover = ("๐ฆ", f"{_cover:,} d" if _cover is not None else "โ",
"Inventory cover", _cover_note, _band_hex)
# 5. Is it actually selling?
_u = _d.get("units_day")
_t_vel = ("โก", f"{_u:,.0f}/day" if _u else "โ", "Sales velocity",
(f"TACoS {_ppc['tacos']:.1f}%" if _ppc.get("tacos") is not None
else "30-day average"))
# 6. Who else is on this listing โ the one tile that can invalidate all the others.
if _cm.get("state_usable"):
_bb = str(_cm.get("state") or "โ").replace("_", " ").title()
_riv = _cm.get("rivals") or 0
_t_comp = ("๐", _bb, "Buy Box",
(f"{_riv} rival(s) ยท median ${_cm['median']:,.2f}"
if _cm.get("median") else f"{_riv} rival(s)"))
else:
_t_comp = ("๐", "N/A", "Buy Box",
"not covered by the competitor sheet"
if not _cm.get("sheet_covers_sku") else "no usable competitor data")
tiles = [_t_impact, _t_margin, _t_floor, _t_cover, _t_vel, _t_comp]
else:
tiles = [
("โก", str(len(needing_action)), "SKUs needing action", "queue below, ranked by impact"),
("๐ฐ", money_range(opportunity, _err), "Profit opportunity ยท 30d",
(f"open positive impacts ยท ยฑ{_err * 100:.0f}% model error" if _err
else "sum of open positive impacts")),
("โณ", str(len(pending)), "Pending approvals", f"{len(bulk_eligible)} bulk-eligible"),
("๐", str(stockout_risk), "Stockout-risk SKUs", f"cover โค {LOW_COVER_DAYS} days"),
("๐ฆ", str(overstock_risk), "Overstock-risk SKUs", f"cover โฅ {HIGH_COVER_DAYS} days"),
]
st.markdown(''
# Tiles are (icon, value, label, note) with an OPTIONAL 5th accent colour, so a
# banded reading can tint its icon without every other tile growing a field.
+ "".join(theme.tile(*t) for t in tiles)
+ '
', unsafe_allow_html=True)
st.markdown("")
# ---------------------------------------------------------------- action pills
counts = summary["action"].value_counts().to_dict()
pills = [("All", "All", len(summary)),
("Increase", "โ Raise", counts.get("Increase", 0)),
("Decrease", "โ Lower", counts.get("Decrease", 0)),
("Maintain", "โ Hold", counts.get("Maintain", 0)),
("Investigate", "๐ Check", counts.get("Investigate", 0))]
with st.container(key="pillrow"): # keyed so the CSS can keep these pills tight
pcols = st.columns([0.8, 1, 1, 1, 1, 2])
for col, (a, lbl, n) in zip(pcols, pills):
col.button(f"{lbl} ยท {n}", key=f"pill_{a}",
type="primary" if st.session_state.action_sel == a else "secondary",
use_container_width=True, on_click=set_action, args=(a,),
help=f"Show {a.lower()} recommendations" if a != "All" else "Show all")
# ---------------------------------------------------------------- queue
rows = summary.copy()
if f_mkt != "All":
rows = rows[rows["marketplace"] == f_mkt]
if f_conf != "All":
rows = rows[rows["trust_tier"] == f_conf]
if f_stat != "All":
rows = rows[[st.session_state.status[s] == f_stat for s in rows["sku"]]]
if st.session_state.action_sel != "All":
rows = rows[rows["action"] == st.session_state.action_sel]
if q:
mask = (rows["sku"].str.contains(q, case=False, regex=False)
| rows["title"].str.contains(q, case=False, regex=False))
rows = rows[mask]
if rows.empty:
st.info("No recommendations match the current filters.")
for _, r in rows.iterrows():
sku = r["sku"]
d = details[sku]
status = st.session_state.status[sku]
modified_price = st.session_state.modified.get(sku)
tier, score = d["confidence"]
stat_icon = theme.REC_STATUS[status][1]
glyph = ACTION_GLYPH[d["action"]]
# Header: SKU ยท action+price ยท reason. ASIN, marketplace and inventory band moved
# into the card โ six competing facts in one line had no hierarchy and wrapped.
asin = d.get("asin") or d["competitors"].iloc[0]["asin"]
inv_cls = d["cfg"]["inv_class"].lower()
trust = d.get("trust") or {}
target = modified_price if modified_price is not None else d["rec_price"]
mod_tag = " (modified)" if modified_price is not None else ""
if d["action"] == "Increase":
act = (f":green[**โ RAISE ${d['current_price']:.2f} โ ${target:.2f} "
f"(+{abs(d['delta_pct']):.1f}%){mod_tag}**]")
elif d["action"] == "Decrease":
act = (f":red[**โ LOWER ${d['current_price']:.2f} โ ${target:.2f} "
f"(โ{abs(d['delta_pct']):.1f}%){mod_tag}**]")
elif d["action"] == "Investigate":
act = f":orange[**๐ INVESTIGATE ยท hold ${d['current_price']:.2f}**]"
else:
act = f":gray[**โ HOLD ${d['current_price']:.2f}**]"
# Trust rides in the row header, so it is visible before the row is opened.
trust_mark = {"High": "", "Medium": " ยท โ ๏ธ medium trust",
"Low": " ยท โ ๏ธ low trust", "None": " ยท โ data incomplete"}.get(
trust.get("tier", ""), "")
label = esc(f"{stat_icon} **{sku}** ยท {act} ยท {r['primary_reason']}{trust_mark}")
with st.expander(label):
# ---------- decision card ----------
# Keyed container so the responsive CSS can stack these three columns on a
# narrow window instead of squeezing the metrics.
with st.container(key=f"deccard_{sku}"):
c1, c2, c3 = st.columns([2.4, 2.2, 1.4])
with c1:
asin_url = _amazon_url(asin, r["marketplace"])
asin_md = f"[{asin}]({asin_url})" if asin_url else esc(str(asin))
st.caption(f"{r['title']} ยท ASIN {asin_md} ยท {mkt_label(r['marketplace'])}"
f" ยท {INV_CLASS_LABELS.get(inv_cls, inv_cls)}")
st.markdown(esc(f"#### {glyph} {d['action']}" + (
f" to **${d['rec_price']:.2f}**" if d["action"] in ("Increase", "Decrease") else "")))
st.markdown(esc(f"**Current price ${d['current_price']:.2f}**"))
chips = "".join(theme.chip(REASON_LABELS.get(c, c), theme.BRAND)
for c in d["reasons"])
err = model_error_pct(d)
trust_chip = ""
if trust:
tcol = {"High": theme.STATUS["good"], "Medium": theme.STATUS["warning"],
"Low": theme.STATUS["serious"],
"None": theme.STATUS["critical"]}[trust["tier"]]
tlabel = f"๐ฏ {trust['tier']} trust"
if err:
tlabel += f" ยท ยฑ{err * 100:.0f}% backtested"
trust_chip = theme.chip(tlabel, tcol)
# Only ONE reliability badge. The old confidence tier was derived
# from days-with-sales and happily read "Medium (62)" for a SKU
# whose elasticity could not be told apart from zero โ shown next
# to "Low trust" it just contradicted it. The tier is still used
# internally to width the forecast band.
st.markdown(trust_chip + theme.status_badge(status) + chips,
unsafe_allow_html=True)
if trust.get("tier") in ("Low", "None"):
st.caption(esc(f"โ {trust['permits']} ยท see **๐ฏ Track record**"))
if modified_price is not None:
st.caption(esc(f"โ๏ธ Approved at modified price ${modified_price:.2f} "
f"(engine recommended ${d['rec_price']:.2f})"))
for c in (d.get("clamps") or []):
st.caption(esc(f"๐ฏ {c}"))
# Guardrail detail is reference, not a decision input โ one line of
# it belongs on the card, the rest behind a disclosure.
fl = d.get("floor_info") or {}
with st.popover("๐ก๏ธ Guardrails & break-even", use_container_width=True):
st.markdown(esc(
f"**Allowed range ${d['min_price']:.2f}โ${d['max_price']:.2f}** ยท "
f"objective: {OBJECTIVE_LABELS.get(r['objective'], r['objective'])}"))
be_rows = [("Fees + COGS only", d["break_even"], "ignores advertising")]
if d.get("break_even_with_ads"):
be_rows.append(("Including advertising", d["break_even_with_ads"],
"at current ad cost per unit"))
if d.get("break_even_empirical"):
be_rows.append(("From actual booked profit", d["break_even_empirical"],
"carries every cost COSMOS books"))
st.dataframe(
pd.DataFrame(be_rows, columns=["Break-even", "Price", "Basis"]),
hide_index=True, use_container_width=True,
column_config={"Price": st.column_config.NumberColumn(format="$%.2f")})
if fl.get("binding") and fl["binding"] != "accounting":
st.caption(esc(
f"Floor is set by the **{fl['binding']}** figure "
f"(${fl['floors'][fl['binding']]:.2f}). The fees-only number "
f"(${fl['floors']['accounting']:.2f}) ignores advertising, so "
f"pricing to it loses money on every unit."))
with c2:
# Calibrated net profit for the headline metric (matches the tables).
_econ = {x["key"]: x for x in (d.get("scen_econ") or [])}
_rec_e = _econ.get(d["rec_key"])
_cur_e = _econ.get("current")
rec_units = _rec_e["units_day"] if _rec_e else round(d["rec_units_day"])
cur_units = _cur_e["units_day"] if _cur_e else round(d["units_day"])
rec_net = _rec_e["net_30d"] if _rec_e else d["rec_profit_30d"]
cur_net = _cur_e["net_30d"] if _cur_e else d["profit_30d"]
m1, m2, m3, m4 = st.columns(4)
m1.metric("Units/day", f"{rec_units:,}",
f"{rec_units - cur_units:+,} vs now", delta_color="off")
# Point estimate stays the headline so it can be scanned; the range
# sits underneath. A range in the value slot has no break
# opportunity and shatters character-by-character in a narrow column.
# Point estimate as the headline, uncertainty as a short suffix. The
# delta cell is ~110px wide, so the full range belongs on the
# Scenarios tab, not here.
m2.metric("Net profit/30d", compact_money(rec_net),
(f"ยฑ{err * 100:.0f}% model error" if err else
f"{'+' if rec_net >= cur_net else 'โ'}"
f"{compact_money(abs(rec_net - cur_net))} vs now"),
delta_color="off")
# Cover in days, with the DATE it runs out โ "26 days" is abstract,
# "out on 24 Aug" is a diary entry someone can act on.
ol = d.get("outlook") or {}
so = ol.get("stockout_date")
m3.metric("Cover", f"{d['rec_cover_days']} d",
(f"out ~{so:%d %b}" if so else
f"{d['rec_cover_days'] - d['cover_days']:+d} d"),
delta_color="off")
m4.metric("PPC/day", f"${d['ppc']['spend_day']:,.0f}",
f"TACoS {d['ppc']['tacos']:.1f}%", delta_color="off")
with c3:
# The primary action states what the evidence actually supports. At
# Low trust a bold "Approve" invites a commitment the model cannot
# back โ the same click, honestly labelled, is a test.
tier = trust.get("tier", "Medium")
if tier == "None":
st.button("โ Blocked โ fix data", key=f"ap_{sku}", disabled=True,
use_container_width=True,
help="; ".join(trust.get("reasons", [])) or "data incomplete")
elif tier == "Low":
st.button(f"๐งช Start test at ${d['rec_price']:.2f}", key=f"ap_{sku}",
type="primary", disabled=kill or status == "Approved",
on_click=set_status, args=(sku, "Approved"),
use_container_width=True,
help="Backtest error is high on this SKU, so treat the move "
"as an experiment: run it, then re-read the response.")
else:
st.button("โ
Approve", key=f"ap_{sku}", type="primary",
disabled=kill or status == "Approved",
on_click=set_status, args=(sku, "Approved"),
use_container_width=True)
with st.popover("โ๏ธ Modify", use_container_width=True, disabled=kill):
st.number_input("New price ($)", key=f"mod_{sku}",
min_value=float(d["min_price"]), max_value=float(d["max_price"]),
value=float(min(max(d["rec_price"], d["min_price"]),
d["max_price"])),
step=0.05, format="%.2f")
st.caption(esc(f"Allowed ${d['min_price']:.2f}โ${d['max_price']:.2f} ยท "
">5% from current needs elevated approval"))
st.button("Apply & approve", key=f"modb_{sku}",
on_click=apply_modify, args=(sku, details), use_container_width=True)
st.button("โ๏ธ Reject", key=f"rj_{sku}", disabled=kill or status == "Rejected",
on_click=set_status, args=(sku, "Rejected"), use_container_width=True)
if status != "Pending":
st.button("โฉ๏ธ Undo", key=f"un_{sku}",
on_click=set_status, args=(sku, "Pending"), use_container_width=True)
# ---------- strategy options ----------
scen = d["scenarios"].set_index("scenario")
if d["action"] == "Investigate":
st.info("No price options proposed โ investigate first. A change without a "
"confirmed cause is blocked by the root-cause gate.")
else:
# Calibrated economics keyed by scenario, so this table matches the
# Scenarios tab (net profit anchored to actual booked profit).
econ_by_key = {x["key"]: x for x in (d.get("scen_econ") or [])}
cover_by_key = {k: scen.loc[k]["cover_days"] for k in scen.index}
def opt_row(label, key):
e = econ_by_key.get(key)
if not e:
return None
return {
"Option": label, "Price": e["price"], "Units/day": e["units_day"],
"Profit/30d": e["net_30d"], "Margin %": e["net_margin_pct"],
"Cover (days)": int(cover_by_key.get(key, 0)),
}
# One row per distinct price. Rungs can legitimately share a price (e.g.
# nothing in the grid is bolder than the recommendation), so merge their
# labels instead of rendering the same row twice.
#
# Two things this table used to say that were not true:
#
# 1. It called the first row "Current" at a price that is NOT the current price.
# That row is real booked history, so its price is the AVERAGE SOLD price over
# the window (revenue / units) โ deliberately, so revenue reconciles. But the
# rungs below it are built from today's price, so on one SKU the table read
# Current $18.18 / Conservative $17.43 and the "conservative" option looked like
# a cut on an Increase recommendation. Same column, two different bases, one
# label. Naming the basis is what makes the column readable.
# 2. It starred "Recommended" against the TARGET price while the header recommended
# the step-capped price for today. A row labelled Recommended has to be the
# thing being recommended.
win = d.get("scen_window") or 90
rec_now, tgt = float(d["rec_price"]), float(d["target_price"])
merged: dict[str, list[str]] = {}
for name, key in [(f"Current ยท avg sold ({win}d)", "current")] \
+ list(d["strategy_keys"].items()):
merged.setdefault(key, []).append(name)
# Which grid key are we actually moving to today?
rec_key_now = next(
(k for k, e in econ_by_key.items() if abs(e["price"] - rec_now) < 0.011), None)
opt_rows = []
for key, names in merged.items():
e = econ_by_key.get(key)
label = " ยท ".join(names)
# This rung is the destination, not this week's move โ said once, over the
# whole merged label, not once per name it merged.
if d.get("stepped") and e is not None and abs(e["price"] - tgt) < 0.011:
label = f"Target (~14d) ยท {label}"
star = (rec_key_now is None and "Recommended" in names) or key == rec_key_now
opt_rows.append(opt_row(("โ
" if star else "") + label, key))
# The step-capped price may not be one of the named rungs. If it is a grid row that
# nothing else labelled, surface it โ otherwise the price in the header appears
# nowhere in the table underneath it.
if rec_key_now is not None and rec_key_now not in merged:
row = opt_row("โ
Recommended now (5% step cap)", rec_key_now)
if row:
opt_rows.append(row)
opt_rows = [x for x in opt_rows if x]
opt_rows.sort(key=lambda x: x["Price"])
with st.container(key=f"optrow_{sku}"):
oc1, oc2 = st.columns([4, 1])
with oc1:
st.dataframe(
pd.DataFrame(opt_rows), hide_index=True, use_container_width=True,
column_config={
"Price": st.column_config.NumberColumn(format="$%.2f"),
"Units/day": st.column_config.NumberColumn(format="%d"),
"Profit/30d": st.column_config.NumberColumn(format="$%d"),
"Margin %": st.column_config.NumberColumn(format="%.1f%%"),
},
)
cur_econ = econ_by_key.get("current") or {}
cur_sold = cur_econ.get("price")
cap = ("Profit/30d is net of advertising and storage โ same "
"basis as the Scenarios tab.")
# Reconcile the two prices ON SCREEN. They are both correct and they are
# different things; without this the reader is left to conclude one is a bug.
if cur_sold and abs(cur_sold - d["current_price"]) >= 0.01:
cap += (f" The Current row is **${cur_sold:,.2f}** โ the average price "
f"actually *sold* over the last {win} days (revenue รท units), "
f"which is what its profit is computed from. Today's price is "
f"**${d['current_price']:,.2f}**, and every option below is "
f"calculated from that, so an option can sit below the Current "
f"row without being a price cut.")
st.caption(cap)
with oc2:
st.button("๐ View more", key=f"vm_{sku}", use_container_width=True,
on_click=goto_view, args=(sku, VIEW_SCENARIOS),
help="Jump to the full Scenarios breakdown")
# ---------- section switcher (jumpable, unlike native tabs) ----------
view_key = f"view_{sku}"
if view_key not in st.session_state:
st.session_state[view_key] = VIEW_KEYS[0]
sel = st.segmented_control("Section", VIEW_KEYS, key=view_key,
format_func=lambda k: VIEW_LABELS[k],
label_visibility="collapsed")
if sel is None: # ignore an accidental deselect
sel = st.session_state[view_key] or VIEW_KEYS[0]
if sel == "price":
st.plotly_chart(price_demand_chart(d), use_container_width=True,
config={"displayModeBar": False}, key=f"pd_{sku}")
with st.popover("View as table"):
st.dataframe(d["hist"].tail(30), hide_index=True, use_container_width=True)
elif sel == "inventory":
# Dated outlook up top: when it empties, when relief lands, and how
# many days of the last month were spent effectively out of stock.
ol = d.get("outlook") or {}
oos, known = d.get("stockout_days_30d") or (0, 0)
i1, i2, i3 = st.columns(3)
so, arr = ol.get("stockout_date"), ol.get("next_arrival_date")
if so:
src = ("COSMOS projection" if ol.get("stockout_source") == "projection"
else "extrapolated from velocity")
i1.metric("Runs out", f"{so:%d %b}",
f"{ol.get('days_to_stockout')} days ยท {src}", delta_color="off")
else:
i1.metric("Runs out", "not in horizon",
f"within {ol.get('horizon_days') or 0} days", delta_color="off")
if arr:
gap = (arr - so).days if so else None
i2.metric("Next arrival", f"{arr:%d %b}",
(f"{gap:+d} days vs stockout" if gap is not None
else f"{ol.get('next_arrival_units', 0):,.0f} units"),
delta_color="off")
else:
i2.metric("Next arrival", "none scheduled",
f"within {ol.get('horizon_days') or 0} days", delta_color="off")
i3.metric("Days out of stock", f"{oos}",
f"of {known} days with a reading" if known
else "no inventory readings", delta_color="off")
if so and arr and (arr - so).days > 0:
st.warning(esc(
f"โ ๏ธ Stock runs out around **{so:%d %b}** but the next arrival is "
f"**{arr:%d %b}** โ **{(arr - so).days} days uncovered.** Raising "
f"price slows the burn; it does not create stock."))
st.markdown(inventory_table_html(d), unsafe_allow_html=True)
st.caption(
"Cell: big = projected units ยท purple = inventory value ยท blue = days of "
"cover ยท right = incoming units. Shading per this SKU's "
f"{d['cfg']['inv_class'].capitalize()}-class cover bands."
)
mp = d.get("min_po") or {}
if mp.get("qty"):
po_note = (f"COSMOS suggests a min PO of **{mp['qty']:,.0f} units**"
+ (f" within **{mp['days']:,.0f} days**" if mp.get("days") else "")
+ " to avoid stockout.")
else:
po_note = "No minimum-PO recommendation from COSMOS."
st.caption(f"**Real COSMOS INVP projection** (invp-insight dateMap โ projected "
f"units, value, cover days & warehouse arrivals). {po_note}")
st.plotly_chart(inventory_chart(d), use_container_width=True,
config={"displayModeBar": False}, key=f"inv_{sku}")
elif sel == "repricing":
# What the recommended price does to the SHELF. The Inventory tab above is
# COSMOS's projection at TODAY's price, so a raise or a cut is otherwise shown
# with no stock consequence at all.
econ = {x["key"]: x for x in (d.get("scen_econ") or [])}
cur_e = econ.get("current") or {}
# The row at the price we actually recommend today, not the rung the cascade
# named โ those differ whenever the move is step-capped.
rec_e = next((x for x in (d.get("scen_econ") or [])
if abs(x["price"] - d["rec_price"]) < 0.011), None)
# Demand response from the PRICE RATIO, not from the scenario table's unit
# counts. Those are rounded to whole units, and at low volume the rounding eats
# the entire signal: one real SKU sells 10 a month, and a 5% cut should lift that
# to 10.7 โ both round to 10, so the re-projection reported a price cut with no
# effect on stock whatsoever.
#
# (p_new / p_now) ** elasticity is exact, and it is the same relationship the
# Scenarios tab applies: their common anchor cancels when you take the ratio of
# two rows, so this cannot drift from that table.
_ed = d.get("elasticity_detail") or {}
el = _ed.get("elasticity") or FALLBACK_ELASTICITY
u_now = float(d.get("units_day") or 0.0)
ratio = ((d["rec_price"] / d["current_price"]) ** el
if d.get("current_price") else 1.0)
u_new = u_now * ratio
rows = reproject_inventory(d.get("inv_projection") or [], u_now or 0, u_new or 0)
if d["action"] == "Maintain" or abs(d["rec_price"] - d["current_price"]) < 0.005:
st.info("**No price change is recommended**, so the stock outlook is the "
"one on the Inventory tab. Nothing to re-project.")
elif not rows:
st.warning(esc(
"Cannot re-project the shelf for this SKU โ that needs COSMOS's weekly "
"projection and a demand response at both prices, and one of them is "
"missing. The Inventory tab still shows the outlook at today's price."))
else:
pct = (u_new / u_now - 1) * 100 if u_now else 0.0
verb = "slows" if u_new < u_now else "speeds up"
r1, r2, r3 = st.columns(3)
r1.metric("Price", f"${d['rec_price']:,.2f}",
f"{d['delta_pct']:+.1f}% vs ${d['current_price']:,.2f}",
delta_color="off")
r2.metric("Units/day", f"{u_new:,.1f}", f"{pct:+.1f}% vs {u_now:,.1f} today",
delta_color="off")
last = rows[-1]
r3.metric("Stock at horizon", f"{last['units_new']:,}",
f"{last['delta_units']:+,} vs today's price", delta_color="off")
# COSMOS sometimes projects a completely flat shelf โ same units every week,
# no draw at all. That happens on very low-velocity SKUs (one real example
# holds 167 units and COSMOS shows 167 for all eleven weeks). There is then no
# depletion to re-scale, so the units column cannot move however the price
# changes, and only cover responds. Say so: a table of "+0" with no
# explanation reads as a broken feature rather than an upstream gap.
if all(r["delta_units"] == 0 for r in rows):
st.info(esc(
"COSMOS projects **no depletion** for this SKU โ the same "
f"{rows[0]['units_now']:,} units in every one of its "
f"{len(rows)} weekly snapshots. There is no draw to re-scale, so the "
"unit columns cannot respond to a price change; only cover days move, "
"because those are computed from our own velocity."))
first_out = next((r for r in rows if r["stockout"]), None)
if first_out:
st.error(esc(
f"At ${d['rec_price']:,.2f} the shelf empties by "
f"**{pd.Timestamp(first_out['date']):%d %b}** โ earlier than COSMOS "
f"projects at today's price. A cut sells the remaining stock faster; "
f"it does not create any."))
else:
st.success(esc(
f"Demand {verb} by {abs(pct):.1f}%, so the shelf lasts "
f"{'longer' if u_new < u_now else 'less time'}. No stockout inside "
f"COSMOS's {len(rows)}-week horizon at this price."))
st.dataframe(
pd.DataFrame([{
"Week": pd.Timestamp(r["date"]).strftime("%m/%d/%Y"),
"Units (today's price)": r["units_now"],
"Units (new price)": r["units_new"],
"Change": r["delta_units"],
"Cover now": r["cover_now"],
"Cover new": r["cover_new"],
"Arriving": int(r["arrival"]),
} for r in rows]),
hide_index=True, use_container_width=True,
column_config={
"Units (today's price)": st.column_config.NumberColumn(format="%d"),
"Units (new price)": st.column_config.NumberColumn(format="%d"),
"Change": st.column_config.NumberColumn(format="%+d"),
"Cover now": st.column_config.NumberColumn(format="%d d"),
"Cover new": st.column_config.NumberColumn(format="%d d"),
"Arriving": st.column_config.NumberColumn(format="%d"),
},
)
st.caption(
"**This table is OUR projection, not COSMOS's.** The Inventory tab is "
"COSMOS's own forecast at today's price; this re-runs it at the "
"recommended price by scaling each week's SALES with the same elasticity "
"the Scenarios tab uses. Arrivals are unchanged โ a PO already placed "
"does not move because we re-priced โ and unit value stays on COSMOS's "
"cost basis. Stock is floored at zero; a negative shelf is not a forecast. "
"**Both cover columns divide by OUR velocity** so the difference between "
"them is the price effect alone โ which is why they can sit a day or two "
"off the Inventory tab, where COSMOS's own figure is shown verbatim."
)
elif sel == "scenarios":
render_scenarios(d, key_prefix=f"sc_{sku}")
elif sel == "competitors":
comp = d["competitors"]
meta = d.get("comp_meta") or {}
rivals = comp[~comp["is_us"]] # third-party sellers only
if len(rivals) == 0:
# Nothing to compare against โ show a message, NOT your own listing.
if meta.get("sheet_covers_sku"):
# The workbook covers this SKU but produced no usable rival: every match
# was fuzzy, or the only rivals had their own Buy Box suppressed. Say which,
# because "no data" and "data we refused to price against" are different.
st.warning(
"๐ **The comparison sheet covers this SKU but has no priceable "
"rival.** Every matched rival was either a NEAR colour match "
"(unverified) or had its own Buy Box suppressed, so none may move a "
"price. Open the workbook to see the raw pairings.")
elif not meta.get("scraped"):
st.info("๐ **Competitor prices not scraped.** Tick **Include "
"competitor prices** in the sidebar and reload to pull Buy "
"Box + rival offers for this ASIN."
+ (f" This SKU is also outside the comparison sheet, which "
f"covers {meta['sheet_covered_skus']} SKU(s) of "
f"{meta['sheet_line']}."
if meta.get("sheet_line") else ""))
else:
bb = meta.get("buy_box_status")
bb_price = meta.get("buy_box_price")
own = meta.get("own_offers", 0)
detail = ""
if bb == "WON" and bb_price:
detail = (f" You hold the **Buy Box at ${bb_price:.2f}**"
+ (f" across {own} of your own offers" if own > 1 else "")
+ ".")
st.success("โ
**No competitors on this ASIN.** Utopia Brands is the "
f"only seller โ no third-party offers were found.{detail} "
"Pricing is driven by your own economics, not rivals.")
else:
# Real competitors exist โ show ONLY the rival sellers.
#
# The two sources describe different things and must not share a sentence: an
# Apify row is another seller on OUR listing, a sheet row is a rival brand's own
# listing. Calling the latter "an offer on this ASIN" would be simply untrue.
if meta.get("source") == "comparison-sheet":
as_of = meta.get("sheet_as_of")
st.caption(
f"**{len(rivals)} like-for-like rival(s)** from the comparison sheet โ "
f"each is a *different brand's own listing* matched on size + colour, "
f"not an offer on your ASIN"
+ (f" ยท {meta['exact_rivals']} exact match(es) priceable"
if meta.get("exact_rivals") is not None else "")
+ (f" ยท median ${meta['median']:,.2f}" if meta.get("median") else "")
+ f" ยท your price ${d['current_price']:.2f}"
+ (f" ยท sheet dated {as_of:%d %b %Y}" if as_of else "") + ".")
else:
st.caption(f"**{len(rivals)} competitor offer(s)** on this ASIN"
+ (f" ยท competitor median ${meta['median']:,.2f}"
if meta.get("median") else "")
+ f" ยท your price ${d['current_price']:.2f}.")
disp = pd.DataFrame({
"Seller": rivals["seller"],
"ASIN": rivals["asin"].map(lambda a: _amazon_url(a, r["marketplace"])),
"ASIN code": rivals["asin"],
"Effective price": rivals["effective_price"].map(lambda v: f"${v:,.2f}"),
"Coupon": rivals["coupon"].map(lambda v: f"${v:,.2f}" if v else "โ"),
"Badge": rivals["badge"],
"In stock": rivals["in_stock"].map({True: "Yes", False: "No"}),
"Scraped": rivals["freshness"],
})
st.dataframe(
disp, hide_index=True, use_container_width=True,
column_config={
"ASIN": st.column_config.LinkColumn(
"ASIN", display_text=r"/dp/([A-Z0-9]+)", help="Open on Amazon"),
"ASIN code": None,
},
)
st.caption("Third-party sellers on the same ASIN. Your own offers are "
"excluded. Prices are the effective (after-coupon) offer price.")
elif sel == "ppc":
p = d["ppc"]
st.markdown("**Whole business โ from sales-insight (last 30 days)**")
b1, b2, b3, b4 = st.columns(4)
b1.metric("Total revenue/30d", dash(p.get("revenue_30d"), "${:,.0f}"),
f"{p.get('units_30d', 0):,} units")
b2.metric("Ad budget spent/30d", dash(p["spend_30d"], "${:,.0f}"),
"marketing + promotion")
b3.metric("Ad spend/day", dash(p["spend_day"], "${:,.0f}"))
b4.metric("TACoS", dash(p["tacos"], "{:.1f}%"), "ad spend รท TOTAL revenue")
if p.get("acos") is not None:
st.markdown("**Advertising only โ from the Amazon Ads data COSMOS "
"proxies (`marketing/dashboard`)**")
a1, a2, a3, a4 = st.columns(4)
a1.metric("ACoS", dash(p["acos"], "{:.1f}%"),
"ad spend รท ad-attributed sales")
a2.metric("Ad-attributed sales", dash(p["ad_sales_30d"], "${:,.0f}"),
f"{p['ad_units_30d']:,} units" if p.get("ad_units_30d")
else "")
a3.metric("ROAS", dash(p["roas"], "{:.2f}ร"), "sales per $1 of ad spend")
a4.metric("Assigned budget", dash(p["daily_budget"], "${:,.0f}"),
dash(p["budget_utilization"], "{:.0f}% used"))
e1, e2, e3, e4 = st.columns(4)
e1.metric("CPC", dash(p["cpc"], "${:.2f}"))
e2.metric("Clicks", dash(p["clicks"], "{:,}"))
e3.metric("Impressions", dash(p["impressions"], "{:,}"))
e4.metric("Conversion", dash(p["conversion"], "{:.2f}%"),
"orders รท clicks")
if p.get("ad_share_pct") is not None:
st.info(esc(
f"๐ฃ Ads can claim **{p['ad_share_pct']:.0f}%** of units "
f"({p['ad_units_30d']:,} of {p['units_30d']:,}); the rest is "
f"organic. ACoS **{p['acos']:.1f}%** is the cost of the "
f"attributed portion โ TACoS **{p['tacos']:.1f}%** spreads the "
f"same spend over all revenue. Judge campaigns on ACoS; judge "
f"the SKU's price on TACoS."))
else:
st.info("No advertising campaigns found for this SKU in the last 30 "
"days, so ACoS / ad-attributed sales are not applicable.")
st.plotly_chart(ppc_chart(d), use_container_width=True,
config={"displayModeBar": False}, key=f"ppc_{sku}")
st.caption(
"Two different denominators, both real: **TACoS** = total ad spend รท "
"**total** revenue (from `sales-insight`, includes promotions); "
"**ACoS** = ad spend รท **ad-attributed** sales (from the Ads data). "
"The pricing model uses total ad cost per unit, because a price change "
"moves all units โ not only the ad-attributed ones.")
elif sel == "costs":
cur_p, rec_p = d["current_price"], d["rec_price"]
rp = d.get("referral_pct", REFERRAL_PCT)
ret = d.get("returns_pct", 0.0)
var = d.get("variable", VARIABLE)
cost_rows = [
("Selling price", cur_p, rec_p),
(f"Referral fee ({rp * 100:.1f}%)", -cur_p * rp, -rec_p * rp),
("FBA fee", -d["cfg"]["fba"], -d["cfg"]["fba"]),
("Landed cost", -d["cfg"]["cost"], -d["cfg"]["cost"]),
]
if ret:
cost_rows.append((f"Returns reserve ({ret * 100:.1f}%)",
-cur_p * ret, -rec_p * ret))
cost_rows += [
("Other fees / variable", -var, -var),
("Take-home / unit", unit_take_home(cur_p, d), unit_take_home(rec_p, d)),
]
cdf = pd.DataFrame(cost_rows, columns=["Line", "At current price", "At recommended"])
st.dataframe(cdf, hide_index=True, use_container_width=True,
column_config={
"At current price": st.column_config.NumberColumn(format="$%.2f"),
"At recommended": st.column_config.NumberColumn(format="$%.2f"),
})
st.caption(esc(f"Break-even ${d['break_even']:.2f} ยท guardrail floor "
f"${d['min_price']:.2f} ยท ceiling ${d['max_price']:.2f}"))
if d["cfg"]["cost"] <= 0 and d["cfg"]["fba"] <= 0:
st.warning("โ ๏ธ COSMOS returned **no cost data** for this SKU โ every line "
"above that depends on COGS/FBA is wrong until the product's "
"costs are filled in COSMOS/Inventory Planner.")
elif sel == "track":
# How wrong has this model been on THIS SKU? Previously buried at the
# bottom of the last tab, under the narration it is meant to qualify.
tr, bt = d.get("trust") or {}, d.get("backtest")
if tr:
icon = {"High": "๐ข", "Medium": "๐ก", "Low": "๐ ", "None": "โ"}[tr["tier"]]
st.markdown(f"### {icon} {tr['tier']} trust")
st.markdown(esc(f"**What this permits:** {tr['permits']}"))
if tr["reasons"]:
st.markdown("**Why it isn't higher**")
for why in tr["reasons"]:
st.markdown(esc(f"- {why}"))
st.divider()
if bt:
sc = bt["scores"]
st.markdown(esc(
f"**Backtest** โ fitted on older history, then asked to predict the "
f"profit actually booked over {bt['n_folds']} held-out "
f"{bt['holdout_days']}-day window(s), at the price really charged:"))
names = {"anchored": "Anchored (no calibration)",
"anchored_gap": "Anchored + per-unit gap",
"persistence": "Assume last month repeats (baseline)",
"legacy": "Previous engine (TACoS + factor)"}
st.dataframe(
pd.DataFrame([
{"Method": ("โ " if m == bt["selected"] else "") + names[m],
"Avg error $/30d": sc[m]["mae"],
"Error % of actual": sc[m]["mape"],
"Bias $/30d": sc[m]["bias"]}
for m in ("anchored", "anchored_gap", "persistence", "legacy")]),
hide_index=True, use_container_width=True,
column_config={
"Avg error $/30d": st.column_config.NumberColumn(format="$%d"),
"Error % of actual": st.column_config.NumberColumn(format="%.1f%%"),
"Bias $/30d": st.column_config.NumberColumn(format="$%d"),
})
gp = d.get("gap_per_unit") or 0.0
st.caption(esc(
f"โ marks the method actually used for this SKU's projections, "
f"chosen by this table"
+ (f" (applying a **${gp:.2f}/unit** unmodeled cost)" if gp else "")
+ ". Lower is better. **Bias** shows the direction of the miss: "
"positive means the model flatters this SKU. Beating *'assume last "
"month repeats'* is the minimum bar โ a model that can't has added "
"nothing, and drops this SKU to Low trust."))
with st.popover("Per-fold detail"):
st.dataframe(pd.DataFrame([
{"Train days": f["train_days"], "Held-out days": f["test_days"],
"Price charged": f["test_price"],
"Actual $/30d": f["actual_net_30d"],
"Selected": f["pred"][bt["selected"]],
"Persistence": f["pred"]["persistence"],
"Previous": f["pred"]["legacy"]} for f in bt["folds"]]),
hide_index=True, use_container_width=True)
else:
st.info("Not enough history to hold any out โ this SKU's model is "
"unscored, so recommendations stay human-approved.")
elif sel == "why":
st.markdown(esc(d["explanation"]))
ev = d.get("evidence") or {}
if any(v is not None for v in ev.values()):
st.markdown("**Evidence from COSMOS actuals (6 months)**")
lines = []
if ev.get("actual_profit_per_unit") is not None:
lines.append(f"- Actual profit/unit (ads incl.): "
f"**${ev['actual_profit_per_unit']:.2f}**")
if ev.get("unprofitable_months"):
lines.append(f"- Months that lost money: **{ev['unprofitable_months']}**")
if ev.get("best_observed_price") is not None:
lines.append(f"- Best observed price: **${ev['best_observed_price']:.2f}** "
f"(${ev.get('best_observed_profit_day') or 0:,.0f}/day actual)")
if ev.get("unmodeled_cost_gap") is not None:
lines.append(f"- Model vs actual gap: **${ev['unmodeled_cost_gap']:.2f}/unit**")
if ev.get("suggested_price") is not None:
lines.append(f"- Margin-floor suggested price: "
f"**${ev['suggested_price']:.2f}**")
if ev.get("trend"):
lines.append(f"- Sales trend: **{ev['trend']}**")
st.markdown(esc("\n".join(lines)))
st.markdown("**Root-cause checks**")
for check, result in d["root_cause"]:
ok = result.startswith(("confirmed", "positive", "detected"))
icon = "โ
" if ok else ("โ ๏ธ" if ("missing" in result or "not feasible" in result)
else "โซ๏ธ")
st.markdown(esc(f"{icon} {check} โ *{result}*"))
st.markdown("**Risks**")
for risk, mit in d["risks"]:
st.markdown(f"- {risk} โ *{mit}*")
st.caption("Every number here comes from the deterministic engine over "
"COSMOS data โ see **๐ฏ Track record** for how accurate it has "
"been on this SKU.")
st.divider()
st.caption("Utopia Pricing Agent ยท live COSMOS data ยท read-only (no prices are "
"written back) ยท legacy analyst UI: `streamlit run legacy_app.py`")