// Auteur : Simon-Pierre Boucher — contact@spboucher.ai // Modèles Codable — miroir des types de l'API Vrai-Prix (src/lib/estimator.ts, engine.ts). import Foundation // MARK: - Recherche d'adresse (/api/search) struct SearchResponse: Decodable { let results: [SearchResult] } struct SearchResult: Decodable, Identifiable, Hashable { let id: String let adresse: String? let apt: String? let municipalite: String? let typeProp: String? } // MARK: - Estimation (/api/estimate) struct UnitEstimate: Decodable { let unit: PropertyUnit? let result: EstimateResult } struct PropertyUnit: Decodable { let id: String let adresse: String? let municipalite: String? let typeProp: String let cubfLibelle: String? let anneeConstruction: Int? let aireEtagesM2: Double? let superficieTerrainM2: Double? let nbLogements: Int? let valeurRole: Double? let lat: Double let lng: Double let history: [HistoryPoint] let specs: UnitSpecs } struct HistoryPoint: Decodable, Identifiable { let year: Int let value: Double? var id: Int { year } } struct UnitSpecs: Decodable { let cubf: Int? let cubfLibelle: String? let anneeEstimee: String? let frontTerrainM: Double? let nbEtages: Int? let nbLocauxNonResid: Int? let nbChambresLocatives: Int? let lienPhysique: String? let genreConstruction: String? let matricule: String? let uniteVoisinage: String? let nAdresses: Int? let datCondMarche: String? let valeurTerrain: Double? let valeurBatiment: Double? let valeurAnterieure: Double? let arrond: String? let apt: String? } struct EstimateResult: Decodable { let estimate: Double let low: Double let high: Double let confidencePct: Double let confidenceLevel: String // A | B | C | D let modelEstimate: Double? let compsEstimate: Double? let modelWeight: Double let comps: [AdjustedComp] let nCompsUsed: Int let compsDispersionPct: Double? } struct AdjustedComp: Decodable, Identifiable { let id: String let date: String // YYYY-MM-DD let amount: Double let lat: Double let lng: Double let propertyType: String? let yearBuilt: Int? let floorArea: Double? let street: String? let city: String? let distanceM: Double let monthsAgo: Double let adjTime: Double let adjArea: Double let adjAge: Double let adjustedPrice: Double let weight: Double } // MARK: - Recherche par carte (/api/nearby) struct NearbyResponse: Decodable { let results: [NearbyUnit] } struct NearbyUnit: Decodable, Identifiable, Hashable { let id: String let adresse: String? let apt: String? let municipalite: String? let typeProp: String let lat: Double let lng: Double let est2026: Double? /// Pour réutiliser EstimateLoaderView (même écran que la recherche texte). var asSearchResult: SearchResult { SearchResult(id: id, adresse: adresse, apt: apt, municipalite: municipalite, typeProp: typeProp) } } /// Erreur renvoyée par l'API ({"error": "..."}). struct APIErrorPayload: Decodable { let error: String } /// Libellés français des types de propriété (mêmes clés que le moteur). enum PropType: String, CaseIterable, Identifiable { case unifamilial case plex case condoOuMulti = "condo_ou_multi" case chalet case maisonMobile = "maison_mobile" case terrain var id: String { rawValue } var label: String { switch self { case .unifamilial: return "Unifamiliale" case .plex: return "Plex" case .condoOuMulti: return "Condo / multi" case .chalet: return "Chalet" case .maisonMobile: return "Maison mobile" case .terrain: return "Terrain" } } static func label(for raw: String?) -> String { guard let raw, let t = PropType(rawValue: raw) else { return raw ?? "—" } return t.label } }