"""Tests for the evidence-based price-performance functions (pure, no network).""" from __future__ import annotations from pricing_agent.cosmos.models import SalesDay from pricing_agent.performance import ( best_observed, fix_suggestion, loss_reason, monthly_performance, price_band_performance, recent_profit_per_unit, reconcile, root_cause_split, unprofitable_months, ) def _day(date, price, units, profit, ad=0.0): return SalesDay(date=date, salePrice=price, unitOrders=units, profit=profit, marketingCost=ad) # Two months: June profitable at $20, July loss-making at $18. HISTORY = ( [_day(f"06/{d:02d}/2026", 20.0, 100, 500.0, 100.0) for d in range(1, 11)] + [_day(f"07/{d:02d}/2026", 18.0, 120, -240.0, 150.0) for d in range(1, 11)] ) def test_monthly_performance_uses_actual_profit(): m = monthly_performance(HISTORY) assert [x["month"] for x in m] == ["2026-06", "2026-07"] jun, jul = m assert jun["avg_price"] == 20.0 and jun["units_per_day"] == 100 assert jun["actual_profit_per_day"] == 500.0 assert jun["profit_per_unit"] == 5.0 # 500 / 100 assert jul["actual_profit_per_day"] == -240.0 assert jul["profit_per_unit"] == -2.0 def test_unprofitable_months_counts_losses(): assert unprofitable_months(monthly_performance(HISTORY)) == 1 def test_price_bands_pool_by_price_not_month(): bands = price_band_performance(HISTORY, band=0.50, min_days=5) prices = [b["price_band"] for b in bands] assert prices == [18.0, 20.0] hi = next(b for b in bands if b["price_band"] == 20.0) assert hi["days"] == 10 and hi["enough_data"] is True assert hi["actual_profit_per_day"] == 500.0 def test_best_observed_picks_max_actual_profit(): bands = price_band_performance(HISTORY, band=0.50, min_days=5) best = best_observed(bands) assert best["price_band"] == 20.0 # not the cheaper, higher-volume $18 assert best["actual_profit_per_day"] == 500.0 def test_best_observed_respects_min_days(): # Only 3 days at $20 -> not enough sample; $18 has 10 days. hist = ([_day(f"06/{d:02d}/2026", 20.0, 100, 900.0) for d in range(1, 4)] + [_day(f"07/{d:02d}/2026", 18.0, 120, 100.0) for d in range(1, 11)]) bands = price_band_performance(hist, band=0.50, min_days=7) best = best_observed(bands) assert best["price_band"] == 18.0 # the richer band is ineligible def test_best_observed_none_when_no_sample(): hist = [_day("06/01/2026", 20.0, 10, 5.0)] assert best_observed(price_band_performance(hist, min_days=7)) is None def test_best_observed_can_still_be_loss_making(): """A SKU unprofitable at every observed price still returns its least-bad band.""" hist = ([_day(f"06/{d:02d}/2026", 20.0, 100, -100.0) for d in range(1, 11)] + [_day(f"07/{d:02d}/2026", 18.0, 120, -400.0) for d in range(1, 11)]) best = best_observed(price_band_performance(hist, min_days=5)) assert best["price_band"] == 20.0 assert best["actual_profit_per_day"] < 0 def test_reconcile_reports_model_overstatement(): # Model says $2.00/unit, reality is $0.50 -> model overstates by $1.50. assert reconcile(0.50, 2.00) == 1.50 assert reconcile(None, 2.00) is None def test_recent_profit_per_unit_is_units_weighted(): m = monthly_performance(HISTORY) # last 1 month = July: -240/day over 10 days / (120*10 units) = -2.00 assert recent_profit_per_unit(m, months=1) == -2.0 # ── Diagnosis: WHY it loses, and the fix ───────────────────────────────── def test_reason_blames_ads_when_profitable_before_ads(): # -2.50/u after 3.01/u ads => +0.51/u before ads -> ads are the cause. r = loss_reason(-2.50, 3.01, price=15.28) assert r.startswith("ADS:") and "$0.51" in r and "20% of price" in r def test_reason_blames_cost_when_underwater_before_ads(): # -4.56/u after 2.19/u ads => -2.37/u before ads -> below cost. r = loss_reason(-4.56, 2.19, price=14.19) assert r.startswith("BELOW COST:") and "$2.37" in r def test_fix_for_ads_cause_is_an_ad_cut_no_price_change(): f = fix_suggestion(-2.50, 3.01, price=15.28) assert "Cut ad/unit" in f and "$0.51" in f and "no price change" in f def test_fix_for_below_cost_is_a_price_raise_sized_to_the_gap(): # gap = 4.56/u; uplift = 4.56/0.83 = 5.49 -> target ~ 14.19 + 5.49 = 19.68 f = fix_suggestion(-4.56, 2.19, price=14.19) assert "+$4.56/u" in f and "$19.6" in f def test_healthy_sku_needs_no_fix(): assert loss_reason(1.20, 0.30) == "healthy" assert fix_suggestion(1.20, 0.30) == "—" def test_root_cause_split_uses_profit_before_ads(): ads, cost = root_cause_split([ {"profit_per_unit": -2.50, "ad_per_unit": 3.01}, # +0.51 before ads -> ads {"profit_per_unit": -4.56, "ad_per_unit": 2.19}, # -2.37 before ads -> cost ]) assert len(ads) == 1 and len(cost) == 1