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:
@@ -183,3 +183,64 @@ class AVMv2ModelInfo(BaseModel):
|
||||
metrics: dict
|
||||
is_active: bool = Field(True)
|
||||
ab_test_traffic_pct: float = Field(0.0, ge=0, le=1)
|
||||
|
||||
|
||||
class AVMv1Summary(BaseModel):
|
||||
"""Compact summary of a v1 prediction for comparison."""
|
||||
|
||||
estimated_price_vnd: float
|
||||
confidence: float
|
||||
price_per_m2: float
|
||||
price_range_low: float
|
||||
price_range_high: float
|
||||
|
||||
|
||||
class AVMv2Summary(BaseModel):
|
||||
"""Compact summary of a v2 prediction for comparison."""
|
||||
|
||||
estimated_price_vnd: float
|
||||
confidence: float
|
||||
price_per_m2_vnd: float
|
||||
price_range_low_vnd: float
|
||||
price_range_high_vnd: float
|
||||
model_version: str
|
||||
ensemble_method: str
|
||||
|
||||
|
||||
class ABComparisonRequest(BaseModel):
|
||||
"""Request for A/B comparison between v1 and v2."""
|
||||
|
||||
district: str = Field(..., min_length=1)
|
||||
city: str = Field(..., min_length=1)
|
||||
property_type: str = Field(...)
|
||||
area_m2: float = Field(..., gt=0)
|
||||
rooms: int = Field(0, ge=0)
|
||||
bedrooms: int = Field(0, ge=0, description="Alias for rooms, used by v1")
|
||||
floors: int = Field(0, ge=0)
|
||||
frontage: float = Field(0.0, ge=0)
|
||||
has_legal_paper: bool = Field(True)
|
||||
# v2-specific features (optional, defaults applied)
|
||||
distance_to_cbd_km: float = Field(0.0, ge=0)
|
||||
distance_to_metro_km: float = Field(0.0, ge=0)
|
||||
flood_zone_risk: float = Field(0.0, ge=0, le=1)
|
||||
building_age_years: int = Field(0, ge=0)
|
||||
has_elevator: bool = Field(False)
|
||||
has_parking: bool = Field(False)
|
||||
has_pool: bool = Field(False)
|
||||
renovation_score: float = Field(0.5, ge=0, le=1)
|
||||
view_quality: float = Field(0.5, ge=0, le=1)
|
||||
interior_quality: float = Field(0.5, ge=0, le=1)
|
||||
month: int = Field(1, ge=1, le=12)
|
||||
quarter: int = Field(1, ge=1, le=4)
|
||||
is_year_end: bool = Field(False)
|
||||
|
||||
|
||||
class ABComparisonResponse(BaseModel):
|
||||
"""Side-by-side A/B comparison of v1 vs v2 predictions."""
|
||||
|
||||
v1: AVMv1Summary
|
||||
v2: AVMv2Summary
|
||||
price_diff_vnd: float = Field(..., description="v2 - v1 price difference")
|
||||
price_diff_pct: float = Field(..., description="Percentage difference ((v2-v1)/v1 * 100)")
|
||||
confidence_diff: float = Field(..., description="v2 - v1 confidence difference")
|
||||
recommendation: str = Field(..., description="Which model to prefer and why")
|
||||
|
||||
Reference in New Issue
Block a user