BuyingAgent/backend/tests/unit/test_engine_cases.py

166 lines
7.1 KiB
Python
Raw Permalink Normal View History

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