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")