"""The two inventory reason codes, and a guard that behaviour did not change. `STOCKOUT_BEFORE_ARRIVAL` and `INBOUND_PO` had labels in REASON_LABELS and no condition anywhere — dead codes. They are ADDITIVE by design: the low-stock branch already recommends +5%, which is the step cap, so there is nothing to escalate to. These tests pin that intent, so a later reader does not "fix" the missing escalation and silently start moving prices further. """ from __future__ import annotations from datetime import date import pandas as pd import pytest from dashboard.live_data import ( HIGH_COVER_DAYS, LOW_COVER_DAYS, REASON_LABELS, _decide_raw, ) TODAY = date(2026, 7, 29) SCEN = pd.DataFrame([ {"scenario": "current", "price": 26.07}, {"scenario": "up_2", "price": 26.59}, {"scenario": "up_5", "price": 27.37}, {"scenario": "down_2", "price": 25.55}, {"scenario": "down_5", "price": 24.77}, ]) FP = {"referral_pct": 0.15, "returns_pct": 0.02, "cost": 9.28, "fba": 8.97, "variable": 0.11} class _R: """Minimal AnalysisResult stand-in.""" def __init__(self, cover, avg_30d=100.0, avg_6m=100.0): self.cover_days = cover self.break_even = 22.19 self.is_losing_money = False self.avg_30d, self.avg_6m = avg_30d, avg_6m self.ad_cost_per_unit = 1.58 self.elasticity = None self.profit_optimal_price = None self.ad_curve_unrecoverable = False self.contribution_per_dollar = 0.64 self.break_even_ad_curve = None self.observed_price_max = 28.87 self.price_bands = [] self.best_observed_price = None self.best_observed_profit_day = None self.elasticity_actionable = False self.profit_optimal_is_corner = False def _hist(days=40, inventory=9_000.0, units=100.0): return pd.DataFrame({ "price": [26.07] * days, "units": [units] * days, "ad_spend": [150.0] * days, "revenue": [2_600.0] * days, "profit": [50.0] * days, "inventory": [inventory] * days, }) def _decide(r, outlook=None, hist=None): action, rec, cons, aggr, reasons, root, obj = _decide_raw( r, 26.07, hist if hist is not None else _hist(), FP, SCEN, outlook) return {"action": action, "rec": rec, "reasons": reasons, "objective": obj, "root": dict(root)} def _outlook(stockout=None, arrival=None, horizon_days=63): return {"stockout_date": stockout, "next_arrival_date": arrival, "stockout_source": "projection" if stockout else None, "days_to_stockout": (stockout - TODAY).days if stockout else None, "next_arrival_units": 5_000.0 if arrival else 0.0, "horizon_days": horizon_days, "horizon_end": None} # ---------------------------------------------------------------- labels exist def test_both_codes_have_labels_so_the_ui_can_render_them(): assert REASON_LABELS["STOCKOUT_BEFORE_ARRIVAL"] assert REASON_LABELS["INBOUND_PO"] # ---------------------------------------------------------------- the codes fire def test_stockout_before_arrival_fires_when_the_dates_cross(): got = _decide(_R(cover=26), _outlook(stockout=date(2026, 8, 12), arrival=date(2026, 8, 26))) assert "STOCKOUT_BEFORE_ARRIVAL" in got["reasons"] assert "14 days uncovered" in got["root"]["Stockout outlook"] def test_it_does_not_fire_when_the_arrival_beats_the_stockout(): got = _decide(_R(cover=26), _outlook(stockout=date(2026, 8, 26), arrival=date(2026, 8, 12))) assert "STOCKOUT_BEFORE_ARRIVAL" not in got["reasons"] assert "arrives in time" in got["root"]["Stockout outlook"] def test_it_fires_when_no_arrival_is_scheduled_at_all(): got = _decide(_R(cover=26), _outlook(stockout=date(2026, 8, 12), arrival=None)) assert "STOCKOUT_BEFORE_ARRIVAL" in got["reasons"] assert "no arrival scheduled" in got["root"]["Stockout outlook"] def test_inbound_po_fires_on_a_scheduled_arrival(): got = _decide(_R(cover=26), _outlook(stockout=date(2026, 8, 26), arrival=date(2026, 8, 12))) assert "INBOUND_PO" in got["reasons"] def test_inbound_po_also_reported_on_overstock(): got = _decide(_R(cover=120), _outlook(arrival=date(2026, 8, 12))) assert got["action"] == "Decrease" assert {"EXCESS_STOCK", "INBOUND_PO"} <= set(got["reasons"]) # ------------------------------------------------- additive, NOT escalating def test_the_codes_do_not_change_the_action_or_the_rung(): """+5% is already MAX_STEP_PCT — there is nothing to escalate to. If this test fails, someone added escalation; that needs an explicit policy decision, not a quiet code change.""" plain = _decide(_R(cover=26), _outlook()) urgent = _decide(_R(cover=26), _outlook(stockout=date(2026, 8, 1), arrival=date(2026, 9, 30))) assert plain["action"] == urgent["action"] == "Increase" assert plain["rec"] == urgent["rec"] == "up_5" assert plain["objective"] == urgent["objective"] == "low_stock_protection" # ---------------------------------------------- stockout vs demand drop def test_a_stockout_explained_drop_is_no_longer_an_unexplained_mystery(): """Cutting price cannot refill a shelf. This used to land on Investigate.""" r = _R(cover=60, avg_30d=40.0, avg_6m=100.0) # a real velocity drop starved = _hist(days=30, inventory=5.0, units=100.0) # ...while out of stock got = _decide(r, _outlook(), hist=starved) assert got["action"] == "Maintain" assert "UNEXPLAINED_DROP" not in got["reasons"] assert "STOCKOUT_BEFORE_ARRIVAL" in got["reasons"] assert "enough to explain" in got["root"]["Days effectively out of stock (30d)"] def test_a_drop_with_healthy_stock_is_still_investigated(): r = _R(cover=60, avg_30d=40.0, avg_6m=100.0) got = _decide(r, _outlook(), hist=_hist(days=30, inventory=9_000.0)) assert got["action"] == "Investigate" assert "UNEXPLAINED_DROP" in got["reasons"] def test_a_drop_with_no_inventory_readings_stays_investigated(): """Absence of data is not evidence of a stockout.""" r = _R(cover=60, avg_30d=40.0, avg_6m=100.0) blind = _hist(days=30) blind["inventory"] = None got = _decide(r, _outlook(), hist=blind) assert got["action"] == "Investigate" # ---------------------------------------------- unchanged-behaviour guard @pytest.mark.parametrize("cover", [LOW_COVER_DAYS + 1, 60, HIGH_COVER_DAYS - 1]) def test_healthy_cover_is_untouched_by_any_of_this(cover): """A SKU between the thresholds with no stockout must behave exactly as before.""" got = _decide(_R(cover=cover), _outlook()) assert got["action"] == "Maintain" assert got["reasons"] == ["NO_SIGNALS"] def test_thresholds_are_unchanged(): """These were explicitly out of scope; pin them so a refactor cannot drift.""" assert (LOW_COVER_DAYS, HIGH_COVER_DAYS) == (35, 90) def test_outlook_is_optional_so_older_callers_keep_working(): got = _decide(_R(cover=26), outlook=None) assert got["action"] == "Increase" and "LOW_STOCK" in got["reasons"]