import { apiClient } from './api-client'; // ─── Types ────────────────────────────────────────────── export type FloodZoneRisk = 'NONE' | 'LOW' | 'MEDIUM' | 'HIGH'; export const FLOOD_RISK_OPTIONS: { value: FloodZoneRisk; label: string }[] = [ { value: 'NONE', label: 'Không có' }, { value: 'LOW', label: 'Thấp' }, { value: 'MEDIUM', label: 'Trung bình' }, { value: 'HIGH', label: 'Cao' }, ]; export interface ValuationRequest { propertyType: string; area: number; district: string; city: string; bedrooms?: number; bathrooms?: number; floors?: number; frontage?: number; roadWidth?: number; yearBuilt?: number; hasLegalPaper?: boolean; latitude?: number; longitude?: number; /** Optional project ID for project-based valuation */ projectId?: string; /** Image file for visual analysis */ imageUrl?: string; /** Description text for AI context */ description?: string; /** Request deep analysis (confidence explanation, more drivers) */ deepAnalysis?: boolean; /** Use AVM v2 ensemble model (extended features) */ useV2?: boolean; distanceToHospitalKm?: number; distanceToParkKm?: number; distanceToMallKm?: number; floodZoneRisk?: FloodZoneRisk; hasElevator?: boolean; hasParking?: boolean; hasPool?: boolean; } export interface ValuationComparable { id: string; title: string; address: string; district: string; priceVND: string; areaM2: number; pricePerM2: number; similarity: number; propertyType?: string; bedrooms?: number; bathrooms?: number; floors?: number; yearBuilt?: number; latitude?: number; longitude?: number; } export interface PriceDriver { feature: string; impact: number; direction: 'positive' | 'negative'; /** Human-readable explanation of this driver's impact */ explanation?: string; } export interface MarketContext { avgPricePerM2: number; medianPrice: number; priceGrowthYoY: number; demandIndex: number; supplyCount: number; avgDaysOnMarket: number; district: string; city: string; period: string; } export interface ValuationHistoryPoint { date: string; estimatedPriceVND: number; confidence: number; } export interface ConfidenceExplanation { level: 'high' | 'medium' | 'low'; score: number; factors: Array<{ factor: string; contribution: 'positive' | 'negative'; detail: string; }>; summary: string; } export interface ValuationResult { id: string; estimatedPriceVND: number; confidence: number; pricePerM2: number; priceRangeLow: number; priceRangeHigh: number; comparables: ValuationComparable[]; priceDrivers: PriceDriver[]; modelVersion: string; createdAt: string; /** Enhanced fields from deep analysis */ confidenceExplanation?: ConfidenceExplanation; marketContext?: MarketContext; valuationHistory?: ValuationHistoryPoint[]; } export interface ValuationHistoryItem { id: string; propertyType: string; district: string; city: string; area: number; estimatedPriceVND: number; confidence: number; createdAt: string; } export interface ValuationHistoryResponse { data: ValuationHistoryItem[]; total: number; page: number; limit: number; } export interface BatchValuationRequest { properties: ValuationRequest[]; } export interface BatchValuationResponse { results: ValuationResult[]; totalProcessed: number; errors: Array<{ index: number; message: string }>; } export interface ValuationCompareRequest { propertyIds: string[]; } export interface ValuationCompareResponse { properties: Array<{ id: string; valuation: ValuationResult; property: { title: string; district: string; city: string; area: number; propertyType: string; }; }>; } export interface ProjectSuggestion { id: string; name: string; district: string; city: string; type: string; } // ─── API ──────────────────────────────────────────────── export const valuationApi = { /** Request AVM estimate via POST /analytics/valuation */ predict: (data: ValuationRequest) => { const body: Record = { propertyType: data.propertyType, area: data.area, district: data.district, city: data.city, }; if (data.bedrooms != null) body['bedrooms'] = data.bedrooms; if (data.bathrooms != null) body['bathrooms'] = data.bathrooms; if (data.floors != null) body['floors'] = data.floors; if (data.frontage != null) body['frontage'] = data.frontage; if (data.roadWidth != null) body['roadWidth'] = data.roadWidth; if (data.yearBuilt != null) body['yearBuilt'] = data.yearBuilt; if (data.hasLegalPaper != null) body['hasLegalPaper'] = data.hasLegalPaper; if (data.latitude) body['latitude'] = data.latitude; if (data.longitude) body['longitude'] = data.longitude; if (data.projectId) body['projectId'] = data.projectId; if (data.imageUrl) body['imageUrl'] = data.imageUrl; if (data.description) body['description'] = data.description; if (data.deepAnalysis) body['deepAnalysis'] = data.deepAnalysis; // AVM v2 fields if (data.useV2) body['useV2'] = data.useV2; if (data.distanceToHospitalKm != null) body['distanceToHospitalKm'] = data.distanceToHospitalKm; if (data.distanceToParkKm != null) body['distanceToParkKm'] = data.distanceToParkKm; if (data.distanceToMallKm != null) body['distanceToMallKm'] = data.distanceToMallKm; if (data.floodZoneRisk) body['floodZoneRisk'] = data.floodZoneRisk; if (data.hasElevator != null) body['hasElevator'] = data.hasElevator; if (data.hasParking != null) body['hasParking'] = data.hasParking; if (data.hasPool != null) body['hasPool'] = data.hasPool; return apiClient.post('/analytics/valuation', body); }, /** Batch valuation: POST /analytics/valuation/batch (max 50) */ batchPredict: (data: BatchValuationRequest) => apiClient.post('/analytics/valuation/batch', data), /** Get valuation history for a property: GET /analytics/valuation/history/:propertyId */ getPropertyHistory: (propertyId: string) => apiClient.get<{ data: ValuationHistoryPoint[] }>( `/analytics/valuation/history/${propertyId}`, ), /** Compare valuations: POST /analytics/valuation/compare */ compare: (data: ValuationCompareRequest) => apiClient.post('/analytics/valuation/compare', data), /** User valuation history (paginated) */ getHistory: (page = 1, limit = 10) => apiClient.get( `/analytics/valuation/user-history?page=${page}&limit=${limit}`, ), /** Get single valuation by ID */ getById: (id: string) => apiClient.get(`/analytics/valuation/${id}`), /** Predict for existing listing */ predictForListing: (listingId: string) => apiClient.post('/analytics/valuation', { propertyId: listingId, }), /** Search projects for autocomplete */ searchProjects: (query: string) => apiClient.get<{ data: ProjectSuggestion[] }>( `/projects/search?q=${encodeURIComponent(query)}&limit=10`, ), };