# 🎨 Custom Criteria Guide - Add Any Evaluation You Want!

## Overview

The new **custom_criteria.py** system lets you easily add ANY criterion you want to evaluate properties.

**Included criteria:**
- 🌧️ Rainfall (annual precipitation)
- 🌡️ Temperature & Growing Season
- 🌍 Climate Change Risk
- ✈️ Airport Distance
- 🏘️ Population Density (Rural Character)
- 🌱 Soil Quality
- 💧 Water Availability
- 🏠 Airbnb Rentability (short-term rental potential)

**Easy to add:**
- Distance to specific cities
- Elevation
- Internet speed/fiber availability
- Property tax rates
- Tourism potential
- Wildfire risk
- Flood risk
- Solar potential
- Wind exposure
- Anything else!

---

## 🚀 Quick Start

### Run Evaluation with All Custom Criteria:

```bash
cd scraper
/usr/bin/python3 custom_criteria.py
```

**What it does:**
- Evaluates all 186 properties
- Adds columns like `custom_rainfall`, `custom_climate_change_risk`, etc.
- Uses FREE APIs (Open-Meteo for weather data)
- Calculates `custom_overall_score`

**Cost:** FREE (uses open data sources)

---

## 📊 Available Criteria

### 1. Rainfall (🌧️)
**What:** Annual precipitation in mm
**Source:** Open-Meteo API (FREE)
**Scoring:**
- 800-1200mm → Score 5 (ideal for farming)
- 600-1500mm → Score 4 (good)
- 400-2000mm → Score 3 (moderate)
- <400mm → Score 2-1 (irrigation needed)

**Data stored:**
- `custom_rainfall` - Score (1-5)
- `custom_rainfall_data` - JSON with annual_rainfall_mm

### 2. Temperature (🌡️)
**What:** Average temperature & growing season
**Source:** Open-Meteo API (FREE)
**Scoring:**
- 250+ growing days → Score 5
- 200+ days → Score 4
- 150+ days → Score 3
- <150 days → Score 2

**Data stored:**
- `custom_temperature` - Score (1-5)
- `custom_temperature_data` - JSON with avg_temperature, growing_days

### 3. Climate Change Risk (🌍)
**What:** Climate vulnerability assessment
**Based on:**
- Latitude (tropical = heat risk, northern = rapid warming)
- Region (Mediterranean = drought, coastal = sea level rise)
- Country-specific risks

**Scoring:**
- Score 5 = Low risk (temperate, inland, northern Europe)
- Score 3 = Moderate risk
- Score 1 = High risk (Mediterranean, coastal, <10m elevation)

### 4. Airport Distance (✈️)
**What:** Distance to nearest major airport
**Airports included:** Amsterdam, Paris, Frankfurt, Madrid, Barcelona, Brussels, etc.

**Scoring:**
- <50km → Score 5
- <100km → Score 4
- <200km → Score 3
- <400km → Score 2
- >400km → Score 1

### 5. Population Density (🏘️)
**What:** How rural is the area
**Scoring:**
- <50 people/km² → Score 5 (very rural)
- <100 → Score 4
- <200 → Score 3
- <400 → Score 2
- >400 → Score 1 (dense)

### 6. Soil Quality (🌱)
**What:** Agricultural soil potential
**Based on:** Regional knowledge (could add SoilGrids API)
**Scoring:**
- Netherlands/Belgium → Score 5 (excellent)
- Northern France → Score 4
- Mediterranean → Score 2-3 (variable)

### 7. Water Availability (💧)
**What:** Water stress assessment
**Combines:** Rainfall + regional water stress
**Scoring:**
- >800mm + no stress → Score 5
- >600mm → Score 4
- Water-stressed region → Score 2

### 8. Airbnb Rentability (🏠)
**What:** Short-term rental income potential
**Based on:**
- Coastal proximity (Mediterranean, Atlantic, North Sea)
- Tourist region keywords (Provence, Tuscany, Algarve, etc.)
- Climate (warm = year-round appeal)
- Airport accessibility
- Building size (larger = multi-unit potential)
- Rural character (nature tourism)

**Scoring:**
- Coastal + warm climate + near airport → Score 5
- Tourist region + good access → Score 4
- Moderate tourist appeal → Score 3
- Remote or low-demand area → Score 2

**Data stored:**
- `custom_airbnb_rentability` - Score (1-5)
- `custom_airbnb_rentability_data` - JSON with score and tourist market status

---

## 🛠️ How to Add Your Own Criterion

### Step 1: Create a New Criterion Class

Edit **custom_criteria.py** and add your class:

```python
class YourCustomCriterion(Criterion):
    """Description of your criterion"""

    def __init__(self):
        super().__init__()
        self.name = "your_criterion"  # Internal name (no spaces)
        self.display_name = "🎯 Your Criterion"  # Display name
        self.weight = 2.0  # How important (1.0 to 5.0)
        self.description = "What this evaluates"

    def evaluate(self, property_data):
        """
        Evaluate this criterion for a property

        property_data contains:
            - latitude, longitude
            - country, land_size_m2, building_size_m2
            - any other data from the CSV
        """

        # Your evaluation logic here
        score = 3  # Default score (1-5)
        reasoning = []

        # Example: Score based on land size
        land_size = property_data.get('land_size_m2', 0)
        if land_size > 50000:
            score = 5
            reasoning.append(f"Large property: {land_size/10000:.1f} ha")
        elif land_size > 20000:
            score = 4
            reasoning.append(f"Medium property: {land_size/10000:.1f} ha")
        else:
            score = 3
            reasoning.append(f"Small property: {land_size/10000:.1f} ha")

        return {
            'score': score,  # 1-5
            'reasoning': reasoning,  # List of strings
            'raw_data': {  # Store raw values for later use
                'land_size_m2': land_size
            }
        }
```

### Step 2: Add to Manager

Find the `load_default_criteria()` method (around line 388) and add your criterion:

```python
def load_default_criteria(self):
    """Load all available criteria"""
    self.criteria = [
        RainfallCriterion(),
        TemperatureCriterion(),
        ClimateChangeCriterion(),
        AirportDistanceCriterion(),
        PopulationDensityCriterion(),
        SoilQualityCriterion(),
        WaterAvailabilityCriterion(),
        AirbnbRentabilityCriterion(),
        YourCustomCriterion()  # ← Add here
    ]
```

### Step 3: Run Evaluation

```bash
/usr/bin/python3 custom_criteria.py
```

Done! Your criterion will be evaluated for all properties.

---

## 📝 Real Examples

### Example 1: Distance to Specific City

```python
class DistanceToCityCriterion(Criterion):
    """Distance to a specific city (e.g., Amsterdam)"""

    def __init__(self, target_city="Amsterdam", target_lat=52.3676, target_lon=4.9041):
        super().__init__()
        self.name = f"distance_to_{target_city.lower()}"
        self.display_name = f"📍 Distance to {target_city}"
        self.weight = 1.5
        self.target_lat = target_lat
        self.target_lon = target_lon

    def evaluate(self, property_data):
        import math

        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {'score': 3, 'reasoning': ["No coordinates"], 'raw_data': {}}

        # Haversine distance
        R = 6371  # Earth radius in km
        dlat = math.radians(lat - self.target_lat)
        dlon = math.radians(lon - self.target_lon)
        a = math.sin(dlat/2)**2 + math.cos(math.radians(self.target_lat)) * \
            math.cos(math.radians(lat)) * math.sin(dlon/2)**2
        distance = R * 2 * math.asin(math.sqrt(a))

        # Score based on distance
        if distance < 100:
            score = 5
        elif distance < 300:
            score = 4
        elif distance < 500:
            score = 3
        elif distance < 1000:
            score = 2
        else:
            score = 1

        return {
            'score': score,
            'reasoning': [f"{distance:.0f}km from Amsterdam"],
            'raw_data': {'distance_km': distance}
        }
```

### Example 2: Elevation (Flood/View Potential)

```python
class ElevationCriterion(Criterion):
    """Evaluates property elevation"""

    def __init__(self):
        super().__init__()
        self.name = "elevation"
        self.display_name = "⛰️ Elevation"
        self.weight = 1.0

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {'score': 3, 'reasoning': ["No coordinates"], 'raw_data': {}}

        try:
            # Use Open-Elevation API (FREE)
            url = f"https://api.open-elevation.com/api/v1/lookup?locations={lat},{lon}"
            response = requests.get(url, timeout=10)
            data = response.json()

            elevation = data['results'][0]['elevation']

            # Score based on elevation
            if 200 < elevation < 600:
                score = 5
                reasoning = [f"Excellent elevation: {elevation}m (views + flood safety)"]
            elif 100 < elevation < 800:
                score = 4
                reasoning = [f"Good elevation: {elevation}m"]
            elif elevation < 10:
                score = 2
                reasoning = [f"Low elevation: {elevation}m (flood risk)"]
            else:
                score = 3
                reasoning = [f"Moderate elevation: {elevation}m"]

            return {
                'score': score,
                'reasoning': reasoning,
                'raw_data': {'elevation_m': elevation}
            }
        except:
            return {'score': 3, 'reasoning': ["Elevation data unavailable"], 'raw_data': {}}
```

### Example 3: Solar Potential

```python
class SolarPotentialCriterion(Criterion):
    """Solar energy potential"""

    def __init__(self):
        super().__init__()
        self.name = "solar_potential"
        self.display_name = "☀️ Solar Potential"
        self.weight = 1.5

    def evaluate(self, property_data):
        lat = property_data.get('latitude')
        lon = property_data.get('longitude')

        if not lat or not lon:
            return {'score': 3, 'reasoning': ["No coordinates"], 'raw_data': {}}

        try:
            # Use Open-Meteo solar radiation API
            url = "https://archive-api.open-meteo.com/v1/archive"
            params = {
                'latitude': lat,
                'longitude': lon,
                'start_date': '2023-01-01',
                'end_date': '2023-12-31',
                'daily': 'shortwave_radiation_sum'
            }

            response = requests.get(url, params=params, timeout=10)
            data = response.json()

            # Calculate annual solar radiation
            radiation_values = data['daily']['shortwave_radiation_sum']
            annual_radiation = sum(r for r in radiation_values if r is not None)

            # Score based on solar potential (kWh/m²/year roughly)
            if annual_radiation > 5000:
                score = 5
                reasoning = [f"Excellent solar: {annual_radiation:.0f} kWh/m²/year"]
            elif annual_radiation > 4000:
                score = 4
                reasoning = [f"Good solar: {annual_radiation:.0f} kWh/m²/year"]
            else:
                score = 3
                reasoning = [f"Moderate solar: {annual_radiation:.0f} kWh/m²/year"]

            return {
                'score': score,
                'reasoning': reasoning,
                'raw_data': {'annual_solar_kwh_m2': annual_radiation}
            }
        except Exception as e:
            return {'score': 3, 'reasoning': [f"Solar data unavailable"], 'raw_data': {}}
```

### Example 4: Property Tax Rate

```python
class PropertyTaxCriterion(Criterion):
    """Estimates property tax burden"""

    def __init__(self):
        super().__init__()
        self.name = "property_tax"
        self.display_name = "💶 Property Tax"
        self.weight = 1.0

    def evaluate(self, property_data):
        country = property_data.get('country', '')

        # Average property tax rates (% of value per year)
        tax_rates = {
            'France': 0.3,  # Low
            'Spain': 0.5,
            'Portugal': 0.4,
            'Italy': 0.8,
            'Netherlands': 0.3,
            'Belgium': 0.2,
            'Germany': 0.4
        }

        rate = tax_rates.get(country, 0.5)

        # Score (lower tax = better score)
        if rate < 0.3:
            score = 5
        elif rate < 0.5:
            score = 4
        elif rate < 0.7:
            score = 3
        else:
            score = 2

        return {
            'score': score,
            'reasoning': [f"Property tax: ~{rate}% per year in {country}"],
            'raw_data': {'tax_rate_percent': rate, 'country': country}
        }
```

---

## 🔧 Advanced Features

### Criteria That Depend on Other Criteria

```python
def evaluate(self, property_data):
    # Get data from another criterion
    rainfall = property_data.get('annual_rainfall_mm', 0)
    temperature = property_data.get('avg_temperature', 0)

    # Combine them
    if rainfall > 600 and temperature > 15:
        score = 5
        reasoning = ["Perfect climate for farming"]
    # ...
```

### Using External APIs

```python
def evaluate(self, property_data):
    try:
        # Call external API
        response = requests.get(api_url, params=params, timeout=10)
        data = response.json()

        # Process data
        value = data['result']

        # Score
        score = calculate_score(value)

    except Exception as e:
        # Fallback if API fails
        return {'score': 3, 'reasoning': ["API unavailable"], 'raw_data': {}}
```

### Adding Weight Dynamically

```python
def __init__(self, weight=2.0):
    super().__init__()
    self.name = "flexible_criterion"
    self.display_name = "Flexible"
    self.weight = weight  # Allow customization
```

---

## 📊 Using Custom Criteria Results

### View in Database

```bash
# Check what was added
cd scraper
/usr/bin/python3 -c "
import pandas as pd
df = pd.read_csv('analysis_output.csv')
print(df[['URL', 'custom_rainfall', 'custom_climate_change_risk', 'custom_overall_score']].head())
"
```

### Filter by Custom Criteria

```python
# In smart_unfavorite.py or config.json, add:
{
  "custom_thresholds": {
    "rainfall": 4,  # Min score
    "climate_change_risk": 3,
    "airport_distance": 3
  }
}
```

### Add to Web Interface

Edit **map_viewer_advanced.html** to add sliders for custom criteria (same as existing criteria).

---

## 🎯 Recommended Combinations

### For Farming Focus:
- ✅ Rainfall (weight: 3.0)
- ✅ Temperature/Growing Season (weight: 2.5)
- ✅ Soil Quality (weight: 3.0)
- ✅ Water Availability (weight: 2.5)
- ✅ Climate Change Risk (weight: 2.0)

### For Tourism/Guest Focus:
- ✅ Airport Distance (weight: 3.0)
- ✅ Climate (weight: 2.0)
- ✅ Population Density (rural character) (weight: 1.5)
- ✅ Elevation (views) (weight: 1.5)

### For Self-Sufficiency:
- ✅ Rainfall (weight: 3.0)
- ✅ Solar Potential (weight: 2.5)
- ✅ Water Availability (weight: 3.0)
- ✅ Soil Quality (weight: 2.5)
- ✅ Population Density (weight: 2.0)

---

## 💰 Cost & Performance

### FREE Data Sources Used:
- **Open-Meteo** - Weather, climate, solar data
- **Nominatim** - Geocoding
- **Open-Elevation** - Elevation data
- **Built-in calculations** - Distances, regional knowledge

### API Limits:
- Open-Meteo: ~10,000 requests/day (FREE)
- Nominatim: 1 request/second
- Open-Elevation: Unlimited (FREE)

### Processing Time:
- ~2-3 seconds per property per criterion
- 7 criteria × 186 properties ÷ 60 = ~40 minutes for full evaluation
- Can run in background or overnight

---

## 🚀 Full Workflow

```bash
cd scraper

# 1. Extract basic facts (deterministic, FREE, fast)
/usr/bin/python3 deterministic_analyzer.py

# 2. Add custom criteria (climate, location, etc.)
/usr/bin/python3 custom_criteria.py

# 3. Parse all criteria
/usr/bin/python3 parse_criteria.py

# 4. View results
./view_map.sh
# Open: http://localhost:8000/map_viewer_advanced.html
```

---

**Your property analysis is now completely customizable!** 🎉

Add any criterion you can imagine:
- Environmental data
- Economic factors
- Social metrics
- Infrastructure
- Regulatory considerations
- Personal preferences

The sky's the limit! 🚀
