/* ======================================== File: frontend/app/(routes)/model-explanation/page.tsx ======================================== */ "use client"; import React, { useState, useEffect } from "react"; import Link from "next/link"; import { ChevronRight, Info, ArrowRight, Home, Building, Ruler, Calendar, Coins, Droplets, Wind, Sun, Car, School, ShoppingBag, } from "lucide-react"; // Common UI components import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Progress } from "@/components/ui/progress"; import { Slider } from "@/components/ui/slider"; import { Skeleton } from "@/components/ui/skeleton"; // Removed SidebarProvider & ThemeProvider - should be in root layout // Removed MapSidebar - assuming it's not needed here or use a common one // Feature-specific components import { FeatureImportanceChart } from "@/features/model-explanation/components/feature-importance-chart"; import { PriceComparisonChart } from "@/features/model-explanation/components/price-comparison-chart"; // Feature-specific API and types import { fetchModelExplanation } from "@/features/model-explanation/api/explanationApi"; import type { ModelExplanationData, PropertyBaseDetails } from "@/features/model-explanation/types"; export default function ModelExplanationPage() { const [activeStep, setActiveStep] = useState(1); const [isLoading, setIsLoading] = useState(true); const [explanationData, setExplanationData] = useState(null); // State for interactive elements based on fetched data const [propertySize, setPropertySize] = useState(0); const [propertyAge, setPropertyAge] = useState(0); // Fetch data on mount useEffect(() => { async function loadExplanation() { setIsLoading(true); // TODO: Get actual property ID from route params or state const propertyId = "dummy-prop-123"; const response = await fetchModelExplanation(propertyId); if (response.success && response.data) { setExplanationData(response.data); // Initialize sliders with fetched data setPropertySize(response.data.propertyDetails.size); setPropertyAge(response.data.propertyDetails.age); } else { console.error("Failed to load model explanation:", response.error); // Handle error state in UI } setIsLoading(false); } loadExplanation(); }, []); // Stepper configuration const steps = [ { id: 1, title: "Property Details", icon: Home }, { id: 2, title: "Feature Analysis", icon: Ruler }, { id: 3, title: "Market Comparison", icon: Building }, { id: 4, title: "Environmental Factors", icon: Droplets }, { id: 5, title: "Final Prediction", icon: Coins }, ]; // Calculate adjusted price based on slider interaction const calculateAdjustedPrice = () => { if (!explanationData) return 0; // Simple formula for demonstration - refine with actual logic if possible const basePrice = explanationData.propertyDetails.predictedPrice; const baseSize = explanationData.propertyDetails.size; const baseAge = explanationData.propertyDetails.age; const sizeImpact = (propertySize - baseSize) * 50000; // 50,000 THB per sqm diff const ageImpact = (baseAge - propertyAge) * 200000; // 200,000 THB per year newer return basePrice + sizeImpact + ageImpact; }; const adjustedPrice = explanationData ? calculateAdjustedPrice() : 0; // Loading State UI if (isLoading) { return (
); } // Error State UI if (!explanationData) { return (

Failed to load model explanation.

); } // Main Content UI const { propertyDetails, features, similarProperties, environmentalFactors, confidence, priceRange } = explanationData; return ( // Assuming ThemeProvider is in the root layout // Assuming SidebarProvider and a common sidebar are in root layout or parent layout
{" "} {/* Adjusted for page content */} {/* Header */}
Map Price Prediction Model
{/* Add any specific header actions if needed */}
{/* Main content */}
{" "} {/* Make content area scrollable */}

Explainable Price Prediction Model

Understand how our AI model predicts property prices and what factors influence the valuation.

{/* Steps navigation */}
{steps.map((step) => ( ))}
{/* Step content */}
{/* --- Left Column: Property Details & Interaction --- */}
{" "} {/* Make left column sticky */} Property Details {propertyDetails.address} {/* Dynamically display details */} {propertyDetails.bedrooms && } {propertyDetails.bathrooms && } {propertyDetails.floor && } Adjust Parameters See how changes affect the prediction {/* Size Slider */}
{propertySize} sqm
setPropertySize(value[0])} />
{/* Age Slider */}
{propertyAge} years
setPropertyAge(value[0])} />
Adjusted Price {formatCurrency(adjustedPrice)}
{/* Show difference */} {propertyDetails.predictedPrice !== adjustedPrice && (
{adjustedPrice > propertyDetails.predictedPrice ? "↑" : "↓"} {Math.abs(adjustedPrice - propertyDetails.predictedPrice).toLocaleString()} THB from original
)}
{/* --- Right Column: Step Content --- */}
{activeStep === 1 && } {activeStep === 2 && } {activeStep === 3 && ( )} {activeStep === 4 && } {activeStep === 5 && ( )}
); } // --- Helper Components for Steps --- function DetailRow({ label, value }: { label: string; value: string | number }) { return (
{label} {value}
); } function formatCurrency(value: number): string { return new Intl.NumberFormat("th-TH", { style: "currency", currency: "THB", minimumFractionDigits: 0, maximumFractionDigits: 0, }).format(value); } // Step 1 Component function Step1Content({ propertyDetails, setActiveStep, }: { propertyDetails: PropertyBaseDetails; setActiveStep: (step: number) => void; }) { return ( Property Overview Basic information used in our prediction model

Our AI model begins by analyzing the core attributes of your property. These fundamental characteristics form the baseline for our prediction.

{propertyDetails.floor && ( )}
setActiveStep(2)} />
); } // Step 2 Component function Step2Content({ features, setActiveStep, }: { features: ModelExplanationData["features"]; setActiveStep: (step: number) => void; }) { return ( Feature Analysis How different features impact the predicted price

Our model analyzes various features and determines how each contributes to the price prediction. Below is a breakdown of the most important factors.

{features.map((feature) => (
{feature.name} {feature.impact === "positive" ? "↑ Positive" : feature.impact === "negative" ? "↓ Negative" : "→ Neutral"}
{feature.importance}%

{feature.value}

))}
setActiveStep(1)} onNext={() => setActiveStep(3)} />
); } // Step 3 Component function Step3Content({ property, comparisons, setActiveStep, }: { property: PropertyBaseDetails; comparisons: ModelExplanationData["similarProperties"]; setActiveStep: (step: number) => void; }) { // Prepare data for the chart, ensuring the main property is clearly labeled const chartProperty = { name: "Your Property", price: property.predictedPrice, size: property.size, age: property.age, }; const chartComparisons = comparisons.map((p, i) => ({ name: `Comp ${i + 1}`, address: p.address, price: p.price, size: p.size, age: p.age, })); return ( Market Comparison How your property compares to similar properties recently analyzed or sold in the area

We analyze recent data from similar properties to establish a baseline. This ensures our prediction aligns with current market conditions.

Similar Properties Details

{comparisons.map((p, index) => (
{p.address}
{p.size} sqm, {p.age} years old
{formatCurrency(p.price)}
))}
setActiveStep(2)} onNext={() => setActiveStep(4)} />
); } // Step 4 Component function Step4Content({ factors, setActiveStep, }: { factors: ModelExplanationData["environmentalFactors"]; setActiveStep: (step: number) => void; }) { const factorDetails = { floodRisk: { icon: Droplets, color: factors.floodRisk === "low" ? "green" : factors.floodRisk === "moderate" ? "yellow" : "red", text: "Historical data suggests this level of risk.", }, airQuality: { icon: Wind, color: factors.airQuality === "good" ? "green" : factors.airQuality === "moderate" ? "yellow" : "red", text: "Compared to city average.", }, noiseLevel: { icon: Sun, color: factors.noiseLevel === "low" ? "green" : factors.noiseLevel === "moderate" ? "yellow" : "red", text: "Based on proximity to major roads/sources.", }, }; return ( Environmental & Location Factors How surrounding conditions and amenities affect value

Environmental conditions and nearby amenities significantly impact desirability and value. Our model considers these external factors.

{/* Environmental Factors */}
{/* Proximity Example */}

Proximity to Amenities

setActiveStep(3)} onNext={() => setActiveStep(5)} />
); } // Step 5 Component function Step5Content({ predictedPrice, confidence, priceRange, setActiveStep, }: { predictedPrice: number; confidence: number; priceRange: { lower: number; upper: number }; setActiveStep: (step: number) => void; }) { return ( Final Prediction The AI-predicted price based on all analyzed factors
{/* Price Box */}

Predicted Price

{formatCurrency(predictedPrice)}
Confidence Level: {(confidence * 100).toFixed(0)}%
{/* Price Range Box */}

Price Range

Based on our model's confidence, the likely market range is:

Lower Bound
{formatCurrency(priceRange.lower)}
Prediction
{formatCurrency(predictedPrice)}
Upper Bound
{formatCurrency(priceRange.upper)}
{/* Summary */}

Summary of Factors

This prediction considers:

  • Property characteristics (size, age, layout)
  • Location and neighborhood profile
  • Recent market trends and comparable sales
  • Environmental factors and amenity access
); } // --- Sub-components for Steps --- function InfoCard({ icon: Icon, title, description }: { icon: React.ElementType; title: string; description: string }) { return (

{title}

{description}

); } function FactorCard({ title, factor, details, }: { title: string; factor: string; details: { icon: React.ElementType; color: string; text: string }; }) { const Icon = details.icon; const colorClass = `bg-${details.color}-500`; // Requires Tailwind JIT or safelisting const textColorClass = `text-${details.color}-500`; return (

{title}

{/* Explicit colors might be safer than dynamic Tailwind classes */}
{factor}

{details.text}

); } function ProximityItem({ icon: Icon, text }: { icon: React.ElementType; text: string }) { return (
{text}
); } function StepFooter({ onPrev, onNext }: { onPrev?: () => void; onNext?: () => void }) { return ( {onPrev ? ( ) : (
)} {onNext ? ( ) : (
)}
); }