from datetime import timedelta from decimal import Decimal from uuid import uuid4 from app.domain.enums import Eligibility, PriceReferenceScope from app.scoring.deal import classify_deal_score from app.scoring.engine import evaluate_deal from app.scoring.mathutil import round_score from tests.conftest import ( AS_OF, CUTOFF, case_a_attributes, case_a_prices, offer_a, observation, product_a, statement, variant_a, ) def _run(profile, scoring_model, definitions, attributes=None, observations=None, product=None, offer=None): return evaluate_deal( profile=profile, scoring_model=scoring_model, product=product or product_a(), variant=variant_a(), offer=offer or offer_a(), attributes=attributes or case_a_attributes(), observations=observations or case_a_prices(), definitions=definitions, as_of=AS_OF, snapshot_cutoff=CUTOFF, engine_build="test", ) def test_reference_case_a(profile, scoring_model, definitions): result = _run(profile, scoring_model, definitions) assert result.product_eligibility == Eligibility.ELIGIBLE assert result.offer_eligibility == Eligibility.ELIGIBLE assert result.overall_eligibility == Eligibility.ELIGIBLE assert result.eligible is True assert result.product_score == Decimal("90.21") assert result.price_statistics.median_30d == Decimal("1599.00") assert result.price_statistics.median_30d_days == 2 assert result.price_statistics.median_90d == Decimal("1599.00") assert result.price_statistics.median_90d_days == 3 assert result.price_statistics.lowest_365d == Decimal("1599.00") assert result.price_statistics.lowest_365d_days == 3 assert result.price_score == Decimal("98.24") assert result.deal_score == Decimal("93.42") assert result.classification == "EXCEPTIONAL" assert result.product_coverage == Decimal("1") assert result.price_reference_scope == PriceReferenceScope.OFFER assert result.price_basis.value == "BUDGET_AND_HISTORY" assert any(reason.code == "PRICE_BELOW_MEDIAN" for reason in result.reasons) def test_case_b_ineligible_product(profile, scoring_model, definitions): attrs = ( statement("ram_gb", 8), statement("storage_gb", 1024), statement("weight_kg", Decimal("1.2")), statement("battery_score", 90), statement("display_score", 90), statement("cpu_score", 90), ) result = _run(profile, scoring_model, definitions, attributes=attrs) assert result.product_eligibility == Eligibility.INELIGIBLE assert result.offer_eligibility == Eligibility.ELIGIBLE assert result.overall_eligibility == Eligibility.INELIGIBLE assert result.eligible is False assert result.product_score is not None assert result.deal_score is None assert result.classification == "INELIGIBLE" assert any(item.constraint_id == "min_ram" and item.status == "FAIL" for item in result.constraint_results) def test_case_c_unknown_weight(profile, scoring_model, definitions): attrs = ( statement("ram_gb", 32), statement("storage_gb", 1024), statement("weight_kg", None, status="unknown"), statement("battery_score", 90), statement("display_score", 90), statement("cpu_score", 90), ) result = _run(profile, scoring_model, definitions, attributes=attrs) assert result.product_eligibility == Eligibility.UNKNOWN assert result.offer_eligibility == Eligibility.ELIGIBLE assert result.overall_eligibility == Eligibility.UNKNOWN assert result.eligible is None assert result.product_score is not None assert result.deal_score is None assert result.classification == "INSUFFICIENT_DATA" def test_case_d_over_budget_keeps_product_fit(profile, scoring_model, definitions): prices = ( observation((9, 1), "1599.00"), observation((9, 15), "1599.00"), observation((10, 1), "1599.00"), observation((10, 3), "2049.00"), ) result = _run(profile, scoring_model, definitions, observations=prices) assert result.product_eligibility == Eligibility.ELIGIBLE assert result.offer_eligibility == Eligibility.INELIGIBLE assert result.overall_eligibility == Eligibility.INELIGIBLE assert result.product_score == Decimal("90.21") assert result.price_score is not None assert result.deal_score is None def test_freshness_boundary(profile, scoring_model, definitions): fresh = observation((10, 3), "1298.00") fresh = fresh.__class__(**{**fresh.__dict__, "observed_at": AS_OF - timedelta(hours=48)}) result = _run(profile, scoring_model, definitions, observations=(fresh,)) assert result.offer_eligibility in {Eligibility.ELIGIBLE, Eligibility.UNKNOWN} stale = fresh.__class__(**{**fresh.__dict__, "observed_at": AS_OF - timedelta(hours=48, seconds=1)}) result_stale = _run(profile, scoring_model, definitions, observations=(stale,)) assert result_stale.offer_eligibility == Eligibility.UNKNOWN assert result_stale.price_score is None def test_unknown_shipping(profile, scoring_model, definitions): price = observation((10, 3), "1298.00") price = price.__class__(**{**price.__dict__, "shipping_price": None}) result = _run(profile, scoring_model, definitions, observations=(price,)) assert result.offer_eligibility == Eligibility.UNKNOWN assert result.price_score is None def test_classification_boundaries(scoring_model): assert classify_deal_score(Decimal("59.999"), scoring_model) == "IGNORE" assert classify_deal_score(Decimal("60"), scoring_model) == "WATCH" assert classify_deal_score(Decimal("74.999"), scoring_model) == "WATCH" assert classify_deal_score(Decimal("75"), scoring_model) == "GOOD" assert classify_deal_score(Decimal("85"), scoring_model) == "VERY_GOOD" assert classify_deal_score(Decimal("92"), scoring_model) == "VERY_GOOD" assert classify_deal_score(Decimal("92.01"), scoring_model) == "EXCEPTIONAL" def test_rounding_does_not_shift_class(scoring_model): raw = Decimal("59.996") assert classify_deal_score(raw, scoring_model) == "IGNORE" assert round_score(raw) == Decimal("60.00") def test_other_category_without_engine_change(profile, scoring_model, definitions): camera = product_a().__class__(**{**product_a().__dict__, "category": "camera"}) result = _run(profile, scoring_model, definitions, product=camera) assert result.product_eligibility == Eligibility.INELIGIBLE assert any(gate.reason_code == "CATEGORY_MISMATCH" for gate in result.product_gates) def test_does_not_mix_other_offer_history(profile, scoring_model, definitions): other = observation((9, 1), "100.00", offer_id=uuid4()) mixed = case_a_prices() + (other,) result = _run(profile, scoring_model, definitions, observations=mixed) # Engine is called with the provided list; the assembler in v0.1 should only # receive one offer. Guard the function by using only same-offer rows. same_offer = tuple(item for item in mixed if item.offer_id == offer_a().id) result = _run(profile, scoring_model, definitions, observations=same_offer) assert result.price_statistics.median_90d == Decimal("1599.00")