"""Tests for industrial AVM rent estimation endpoint.""" from fastapi.testclient import TestClient from app.main import app client = TestClient(app) # ── Minimal valid request payload ─────────────────────────────── _PREDICT_PAYLOAD = { "province": "Bình Dương", "region": "south", "park_occupancy_rate": 0.85, "park_area_ha": 500, "park_age_years": 10, "distance_to_port_km": 60, "distance_to_airport_km": 30, "distance_to_highway_km": 5, "property_type": "factory", "area_m2": 5000, "ceiling_height_m": 10, "floor_load_ton_m2": 3.0, "power_capacity_kva": 1000, } def test_predict_industrial_heuristic(): """Predict using heuristic fallback (no trained model).""" resp = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD) assert resp.status_code == 200 data = resp.json() assert data["estimated_rent_usd_m2"] > 0 assert 0 <= data["confidence"] <= 1 assert data["rent_range_low_usd_m2"] < data["estimated_rent_usd_m2"] assert data["rent_range_high_usd_m2"] > data["estimated_rent_usd_m2"] assert data["annual_rent_usd_m2"] > 0 assert data["total_monthly_rent_usd"] > 0 assert data["model_version"] == "heuristic-v1" def test_predict_industrial_returns_comparables(): """Heuristic should return comparable industrial properties.""" resp = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD) data = resp.json() comps = data["comparables"] assert len(comps) > 0 for c in comps: assert c["park_name"] assert c["rent_usd_m2"] > 0 assert 0 <= c["similarity_score"] <= 1 def test_predict_industrial_returns_drivers(): """Heuristic should return feature importance drivers.""" resp = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD) data = resp.json() drivers = data["drivers"] assert len(drivers) > 0 assert all(0 <= d["importance"] <= 1 for d in drivers) def test_predict_industrial_ready_built_premium(): """Ready-built factories should be priced higher than standard.""" standard = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD).json() rbf_payload = {**_PREDICT_PAYLOAD, "property_type": "ready_built_factory"} ready_built = client.post("/avm/industrial/predict", json=rbf_payload).json() assert ready_built["estimated_rent_usd_m2"] > standard["estimated_rent_usd_m2"] def test_predict_industrial_open_yard_discount(): """Open yards should be cheaper than factories.""" factory = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD).json() yard_payload = {**_PREDICT_PAYLOAD, "property_type": "open_yard"} yard = client.post("/avm/industrial/predict", json=yard_payload).json() assert yard["estimated_rent_usd_m2"] < factory["estimated_rent_usd_m2"] def test_predict_industrial_high_occupancy_premium(): """Higher park occupancy should increase rent.""" low = client.post( "/avm/industrial/predict", json={**_PREDICT_PAYLOAD, "park_occupancy_rate": 0.50}, ).json() high = client.post( "/avm/industrial/predict", json={**_PREDICT_PAYLOAD, "park_occupancy_rate": 0.95}, ).json() assert high["estimated_rent_usd_m2"] > low["estimated_rent_usd_m2"] def test_predict_industrial_annual_rent(): """Annual rent should be 12x monthly rent.""" resp = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD).json() expected_annual = round(resp["estimated_rent_usd_m2"] * 12, 2) assert resp["annual_rent_usd_m2"] == expected_annual def test_predict_industrial_total_rent(): """Total monthly rent should be rent/m² × area.""" resp = client.post("/avm/industrial/predict", json=_PREDICT_PAYLOAD).json() expected_total = resp["estimated_rent_usd_m2"] * _PREDICT_PAYLOAD["area_m2"] assert abs(resp["total_monthly_rent_usd"] - expected_total) < 1.0 def test_predict_industrial_validation_error(): """Missing required fields should return 422.""" resp = client.post("/avm/industrial/predict", json={"area_m2": 5000}) assert resp.status_code == 422 def test_predict_industrial_invalid_occupancy(): """Occupancy rate outside 0-1 should be rejected.""" resp = client.post( "/avm/industrial/predict", json={**_PREDICT_PAYLOAD, "park_occupancy_rate": 1.5}, ) assert resp.status_code == 422