From 8b209e7f2652963cd849da1631cdfef1fb1f9c39 Mon Sep 17 00:00:00 2001 From: Talha Ahmed Date: Fri, 31 Jul 2026 20:42:09 +0500 Subject: [PATCH] fix Ui --- app.py | 132 ++++++++------------------------------------------------- 1 file changed, 17 insertions(+), 115 deletions(-) diff --git a/app.py b/app.py index cdc8fdf..df732a1 100644 --- a/app.py +++ b/app.py @@ -243,13 +243,12 @@ ACTION_GLYPH = {"Increase": "↑", "Decrease": "↓", "Maintain": "→", "Invest # 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", +VIEW_KEYS = ["price", "track", "scenarios", "why", "inventory", "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", } @@ -1568,9 +1567,13 @@ for _, r in rows.iterrows(): # "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", + # "Cover after the move", not "Cover". This is cover at the RECOMMENDED + # price, while the header tile and the Inventory grid both show cover at + # TODAY's price — 75 d against 78 d on one screen, under the same word, with + # nothing saying which was which. Both were right; the label was the bug. + m3.metric("Cover after the move", f"{d['rec_cover_days']} d", (f"out ~{so:%d %b}" if so else - f"{d['rec_cover_days'] - d['cover_days']:+d} d"), + f"{d['rec_cover_days'] - d['cover_days']:+d} d vs today"), delta_color="off") m4.metric("PPC/day", f"${d['ppc']['spend_day']:,.0f}", f"TACoS {d['ppc']['tacos']:.1f}%", delta_color="off") @@ -1816,6 +1819,15 @@ for _, r in rows.iterrows(): f"**{_last['units_new']:,} units** left at the end of the horizon " f"against {_last['units_now']:,} at today's price " f"(**{_last['delta_units']:+,}**).")) + # The one line worth interrupting for: the move empties the shelf sooner than + # COSMOS projects. Stated in words as well as drawn on the chart. + _out = next((r for r in _rows if r["stockout"]), None) + if _out: + st.error(esc( + f"At ${d['rec_price']:,.2f} the shelf empties by " + f"**{pd.Timestamp(_out['date']):%d %b}** — sooner than COSMOS " + f"projects at today's price. A cut sells the remaining stock faster; " + f"it does not create any.")) if all(r["delta_units"] == 0 for r in _rows): st.caption(esc( f"COSMOS projects no depletion for this SKU — the same " @@ -1837,117 +1849,7 @@ for _, r in rows.iterrows(): "the same shelf at the recommended price, scaling each week's SALES by " "the elasticity the Scenarios tab uses. Arrivals are unchanged — a PO " "already placed does not move because we re-priced — and stock is floored " - "at zero. Full week-by-week numbers are on **🔁 Stock at the new price**.") - - 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 - # COSMOS's own daily sale — the figure its Inventory Planning grid prints — so the - # cover days here reproduce COSMOS's coverDays rather than sitting a day or two off. - u_now = float((d.get("invp_stats") or {}).get("avg_sale") - or 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:,.0f}", f"{pct:+.1f}% vs {u_now:,.0f} 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." - ) + "at zero.") elif sel == "scenarios": render_scenarios(d, key_prefix=f"sc_{sku}")