104 lines
3.8 KiB
Python
104 lines
3.8 KiB
Python
|
|
from datetime import datetime, timezone
|
||
|
|
from decimal import Decimal
|
||
|
|
from uuid import uuid4
|
||
|
|
|
||
|
|
from app.domain.models import Budget
|
||
|
|
from app.scoring.price import daily_series, median, score_price, select_current_observation
|
||
|
|
from tests.conftest import observation, utc
|
||
|
|
|
||
|
|
|
||
|
|
def test_budget_and_history_points(scoring_model):
|
||
|
|
budget = Budget(Decimal("1500.00"), Decimal("2000.00"))
|
||
|
|
score, budget_score, history_score, basis, warning = score_price(
|
||
|
|
current_price=Decimal("1500.00"),
|
||
|
|
reference_history=(Decimal("1500"), Decimal("1500"), Decimal("1500")),
|
||
|
|
budget=budget,
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
assert budget_score == Decimal("100")
|
||
|
|
assert history_score == Decimal("50")
|
||
|
|
assert basis.value == "BUDGET_AND_HISTORY"
|
||
|
|
assert warning is None
|
||
|
|
|
||
|
|
over, over_budget, _, _, _ = score_price(
|
||
|
|
current_price=Decimal("2000.00"),
|
||
|
|
reference_history=(Decimal("1000"), Decimal("1000"), Decimal("1000")),
|
||
|
|
budget=budget,
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
assert over_budget == Decimal("0")
|
||
|
|
|
||
|
|
cheap, _, hist, _, _ = score_price(
|
||
|
|
current_price=Decimal("800.00"),
|
||
|
|
reference_history=(Decimal("1000"), Decimal("1000"), Decimal("1000")),
|
||
|
|
budget=budget,
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
assert hist == Decimal("100")
|
||
|
|
expensive, _, hist2, _, _ = score_price(
|
||
|
|
current_price=Decimal("1200.00"),
|
||
|
|
reference_history=(Decimal("1000"), Decimal("1000"), Decimal("1000")),
|
||
|
|
budget=budget,
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
assert hist2 == Decimal("0")
|
||
|
|
|
||
|
|
|
||
|
|
def test_budget_only_with_short_history(scoring_model):
|
||
|
|
budget = Budget(Decimal("1500.00"), Decimal("2000.00"))
|
||
|
|
for days in ((), (Decimal("10"),), (Decimal("10"), Decimal("11"))):
|
||
|
|
score, budget_score, history_score, basis, warning = score_price(
|
||
|
|
current_price=Decimal("1298.00"),
|
||
|
|
reference_history=days,
|
||
|
|
budget=budget,
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
assert score == budget_score == Decimal("100")
|
||
|
|
assert history_score is None
|
||
|
|
assert basis.value == "BUDGET_ONLY"
|
||
|
|
assert warning == "INSUFFICIENT_HISTORY"
|
||
|
|
|
||
|
|
|
||
|
|
def test_median_even_and_odd():
|
||
|
|
assert median((Decimal("1"), Decimal("3"), Decimal("5"))) == Decimal("3")
|
||
|
|
assert median((Decimal("1"), Decimal("2"), Decimal("3"), Decimal("4"))) == Decimal("2.5")
|
||
|
|
|
||
|
|
|
||
|
|
def test_daily_series_latest_then_quality_filter(scoring_model):
|
||
|
|
offer = uuid4()
|
||
|
|
morning = observation((10, 1), "10.00", offer_id=offer, hour=8)
|
||
|
|
later_unknown = observation((10, 1), "9.00", offer_id=offer, hour=12, availability="unknown")
|
||
|
|
values, _ = daily_series(
|
||
|
|
(morning, later_unknown),
|
||
|
|
window_start=datetime(2026, 9, 1, tzinfo=timezone.utc).date(),
|
||
|
|
window_end_exclusive=datetime(2026, 10, 3, tzinfo=timezone.utc).date(),
|
||
|
|
minimum_confidence=scoring_model.minimum_confidence,
|
||
|
|
)
|
||
|
|
assert values == ()
|
||
|
|
|
||
|
|
|
||
|
|
def test_current_observation_does_not_fall_back():
|
||
|
|
cheap_old = observation((10, 2), "10.00")
|
||
|
|
unknown_new = observation((10, 3), "99.00", availability="unknown")
|
||
|
|
current = select_current_observation(
|
||
|
|
(cheap_old, unknown_new),
|
||
|
|
as_of=utc(2026, 10, 3, 12),
|
||
|
|
snapshot_cutoff=utc(2026, 10, 3, 12),
|
||
|
|
)
|
||
|
|
assert current is not None
|
||
|
|
assert current.availability.value == "unknown"
|
||
|
|
assert current.item_price == Decimal("99.00")
|
||
|
|
|
||
|
|
|
||
|
|
def test_no_offer_object_required_for_price_function(scoring_model):
|
||
|
|
score, _, _, basis, _ = score_price(
|
||
|
|
current_price=Decimal("1298.00"),
|
||
|
|
reference_history=(Decimal("1599"), Decimal("1599"), Decimal("1599")),
|
||
|
|
budget=Budget(Decimal("1500"), Decimal("2000")),
|
||
|
|
model=scoring_model,
|
||
|
|
)
|
||
|
|
from app.scoring.mathutil import round_score
|
||
|
|
|
||
|
|
assert basis.value == "BUDGET_AND_HISTORY"
|
||
|
|
assert round_score(score) == Decimal("98.24")
|