feat(ai-services): add AVM v2 A/B comparison endpoint and tests
Add POST /avm/v2/compare-v1 endpoint that runs both v1 (single-model) and v2 (ensemble) AVM predictions on the same property and returns a side-by-side comparison with price diff, confidence delta, and a recommendation on which model to prefer. - ABComparisonRequest/Response schemas in avm_v2 models - compare_v1() method in AVMv2EnsembleService - 4 new integration tests for the comparison endpoint - All 47 Python tests pass Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -172,3 +172,75 @@ def test_model_info_v2():
|
||||
data = resp.json()
|
||||
assert "model_version" in data
|
||||
assert data["is_active"] is True
|
||||
|
||||
|
||||
# ── A/B comparison tests ─────────────────────────────────────
|
||||
|
||||
_COMPARE_PAYLOAD = {
|
||||
"district": "Cầu Giấy",
|
||||
"city": "Hà Nội",
|
||||
"property_type": "apartment",
|
||||
"area_m2": 80.0,
|
||||
"rooms": 2,
|
||||
"month": 3,
|
||||
"quarter": 1,
|
||||
}
|
||||
|
||||
|
||||
def test_compare_v1_returns_both_models():
|
||||
"""Compare endpoint returns v1 and v2 predictions."""
|
||||
resp = client.post("/avm/v2/compare-v1", json=_COMPARE_PAYLOAD)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
|
||||
assert "v1" in data
|
||||
assert "v2" in data
|
||||
assert data["v1"]["estimated_price_vnd"] > 0
|
||||
assert data["v2"]["estimated_price_vnd"] > 0
|
||||
assert 0 <= data["v1"]["confidence"] <= 1
|
||||
assert 0 <= data["v2"]["confidence"] <= 1
|
||||
|
||||
|
||||
def test_compare_v1_returns_diffs():
|
||||
"""Compare endpoint computes price and confidence differences."""
|
||||
resp = client.post("/avm/v2/compare-v1", json=_COMPARE_PAYLOAD)
|
||||
data = resp.json()
|
||||
|
||||
expected_diff = data["v2"]["estimated_price_vnd"] - data["v1"]["estimated_price_vnd"]
|
||||
assert abs(data["price_diff_vnd"] - expected_diff) < 10_000 # rounding tolerance
|
||||
|
||||
assert "price_diff_pct" in data
|
||||
assert isinstance(data["price_diff_pct"], float)
|
||||
assert "confidence_diff" in data
|
||||
|
||||
|
||||
def test_compare_v1_returns_recommendation():
|
||||
"""Compare endpoint provides a recommendation string."""
|
||||
resp = client.post("/avm/v2/compare-v1", json=_COMPARE_PAYLOAD)
|
||||
data = resp.json()
|
||||
|
||||
assert "recommendation" in data
|
||||
assert len(data["recommendation"]) > 0
|
||||
|
||||
|
||||
def test_compare_v1_with_v2_features():
|
||||
"""Compare endpoint passes v2-specific features correctly."""
|
||||
payload = {
|
||||
**_COMPARE_PAYLOAD,
|
||||
"distance_to_cbd_km": 5.0,
|
||||
"distance_to_metro_km": 0.8,
|
||||
"flood_zone_risk": 0.1,
|
||||
"building_age_years": 3,
|
||||
"has_elevator": True,
|
||||
"has_parking": True,
|
||||
"renovation_score": 0.9,
|
||||
"view_quality": 0.8,
|
||||
"interior_quality": 0.85,
|
||||
}
|
||||
resp = client.post("/avm/v2/compare-v1", json=payload)
|
||||
assert resp.status_code == 200
|
||||
data = resp.json()
|
||||
|
||||
# v2 should capture these extra features
|
||||
assert data["v2"]["estimated_price_vnd"] > 0
|
||||
assert data["v2"]["model_version"] is not None
|
||||
|
||||
Reference in New Issue
Block a user