AI-Pricing-Agent/tests/test_line_picker.py

91 lines
3.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

"""Product-line discovery and ranking (pure, no network)."""
from __future__ import annotations
import pytest
from dashboard.line import LINE_SORTS, rank
from pricing_agent.cosmos.models import LineProduct
def _make(sku, inv=0.0, avg30=0.0, cover=None, minpo=0.0) -> LineProduct:
p = LineProduct.model_validate({
"sku": sku, "inventory": inv, "averageSale30Days": avg30,
"minPoQuantity": minpo})
p.cover_days = cover
return p
# ---------------------------------------------------------------- size parsing
@pytest.mark.parametrize("sku,expected", [
("UBMICROFIBERDUVETQUEENWHITE", "Queen"),
("UBMICROFIBERDUVETKINGWHITE2", "King"),
("UBMICROFIBERDUVETTWINGREY", "Twin"),
("UBMICROFIBERDUVETFULLNAVY", "Full"),
("UBMICROFIBERDUVETCALKINGWHITE", "Calking"),
("UBSOMETHINGELSE", "Other"),
])
def test_size_is_read_from_the_sku(sku, expected):
assert _make(sku).size == expected
def test_calking_is_not_mistaken_for_king():
"""CALKING contains KING, so the order tokens are matched in matters."""
assert _make("UBMICROFIBERDUVETCALKINGGREY").size == "Calking"
def test_line_product_maps_the_cosmos_aliases():
p = LineProduct.model_validate({
"sku": "UBMICROFIBERDUVETQUEENWHITE", "asin": "B00NWPUKMS",
"inventory": 6623, "averageSale30Days": 188, "averageSale6Months": 193,
"minPoQuantity": 11280, "parentAsin": "B0DMGS61XY"})
assert (p.inventory, p.avg_30d, p.avg_6m, p.min_po_quantity) == (
6623, 188, 193, 11280)
assert p.asin == "B00NWPUKMS" and p.parent_asin == "B0DMGS61XY"
# ---------------------------------------------------------------- ranking
ROWS = [
{"sku": "B_MID", "inventory": 500.0, "avg_30d": 20.0, "cover_days": 90,
"min_po_quantity": 0.0},
{"sku": "A_TOP", "inventory": 100.0, "avg_30d": 50.0, "cover_days": 5,
"min_po_quantity": 900.0},
{"sku": "C_DEAD", "inventory": 0.0, "avg_30d": 0.0, "cover_days": None,
"min_po_quantity": 0.0},
]
def _order(label):
return [r["sku"] for r in rank(ROWS, label)]
def test_rank_by_velocity_puts_the_biggest_seller_first():
assert _order("Sales velocity (30d)")[0] == "A_TOP"
def test_rank_by_inventory_puts_the_deepest_stock_first():
assert _order("Inventory on hand")[0] == "B_MID"
def test_rank_by_cover_puts_the_most_urgent_first():
assert _order("Cover days — lowest first")[0] == "A_TOP"
def test_missing_values_sort_last_not_first():
"""A SKU with no cover reading must not be presented as the most urgent."""
assert _order("Cover days — lowest first")[-1] == "C_DEAD"
def test_alphabetical_sorts_text_without_blowing_up():
"""A purely numeric sort key compares str against int and raises here."""
assert _order("AZ") == ["A_TOP", "B_MID", "C_DEAD"]
def test_every_offered_sort_is_usable():
"""Each option in the picker must order the pool without raising."""
for label in LINE_SORTS:
assert len(_order(label)) == len(ROWS)
def test_unknown_sort_label_falls_back_instead_of_raising():
assert len(rank(ROWS, "not a real sort")) == len(ROWS)