# app/services/scoring_engine.py from typing import Any, Dict, List, Optional, Tuple import math from geopy.distance import geodesic from app.models.green_space import ( GreenSpace, PersonalityScore, LocationScore, Coordinates, Amenity, AmenityType ) from app.services.berlin_data_service import BerlinDataService from app.services.street_tree_service import StreetTreeService class ScoringEngine: """Dynamic scoring engine for green spaces based on personality preferences.""" def __init__(self): self.berlin_data = BerlinDataService() self.street_tree_service = StreetTreeService() self.personality_weights = self._initialize_personality_weights() def _initialize_personality_weights(self) -> Dict[str, Dict[str, float]]: """Define scoring weights for each personality type.""" return { "little_adventurers": { "playground_quality": 25, "safety_score": 20, "toilet_proximity": 15, "shade_quality": 10, "noise_level": 10, # Lower noise = higher score "family_amenities": 15, "accessibility": 5 }, "date_night": { "romantic_atmosphere": 25, "noise_level": 20, # Quiet = romantic "scenic_beauty": 20, "restaurant_proximity": 15, "privacy_score": 10, "lighting_quality": 10 }, "squad_goals": { "space_size": 25, "group_amenities": 20, "accessibility": 15, "food_options": 15, "parking_availability": 10, "activity_options": 15 }, "zen_masters": { "quietness": 30, "nature_immersion": 25, "crowd_density": 15, # Lower crowds = higher score "water_features": 10, "meditation_spots": 10, "air_quality": 10 }, "active_lifestyle": { "fitness_facilities": 25, "running_cycling_paths": 25, "sports_areas": 20, "accessibility": 15, "terrain_variety": 10, "safety_score": 5 }, "wildlife_lover": { "wildlife_diversity": 30, "natural_habitat": 25, "water_features": 15, "tree_coverage": 15, "noise_level": 10, # Quiet for animals "observation_spots": 5 }, "art_nerd": { "cultural_proximity": 30, "artistic_features": 20, "museum_accessibility": 20, "creative_atmosphere": 15, "cafe_culture": 10, "inspiring_views": 5 }, "history_geek": { "historical_significance": 35, "monument_proximity": 25, "educational_value": 20, "architectural_features": 10, "cultural_context": 10 } } async def score_green_space( self, green_space: GreenSpace, personality: str, user_location: Optional[Tuple[float, float]] = None ) -> PersonalityScore: """Score a green space for a specific personality type.""" weights = self.personality_weights.get(personality, {}) if not weights: raise ValueError(f"Unknown personality type: {personality}") # Pre-fetch tree data once for all calculations tree_data = await self._fetch_tree_data_once(green_space) # Calculate component scores with cached tree data component_scores = await self._calculate_component_scores( green_space, personality, user_location, tree_data ) # Calculate weighted final score final_score = 0 explanation_parts = [] key_factors = [] for component, weight in weights.items(): if component in component_scores: component_score = component_scores[component] weighted_score = (component_score * weight) / 100 final_score += weighted_score if component_score > 70: # Highlight strong factors key_factors.append(component) explanation_parts.append( f"{component.replace('_', ' ')}: {component_score}/100" ) # Generate recommendations recommendations = self._generate_recommendations( green_space, personality, component_scores ) return PersonalityScore( personality=personality, score=min(100, max(0, int(final_score))), explanation=self._create_explanation(personality, explanation_parts), key_factors=key_factors, recommendations=recommendations ) async def _fetch_tree_data_once(self, green_space: GreenSpace) -> Optional[Any]: """Fetch tree data once and reuse for all calculations.""" try: # Use the largest radius needed across all methods (400m) tree_response = await self.street_tree_service.get_trees_near_location( green_space.coordinates.lat, green_space.coordinates.lng, radius_m=400 ) return tree_response except Exception as e: print(f"Error fetching tree data: {e}") return None async def _calculate_component_scores( self, green_space: GreenSpace, personality: str, user_location: Optional[Tuple[float, float]] = None, tree_data: Optional[Any] = None ) -> Dict[str, int]: """Calculate individual component scores.""" scores = {} # Universal components scores["accessibility"] = self._score_accessibility(green_space, user_location) scores["safety_score"] = self._score_safety(green_space) scores["noise_level"] = self._score_noise_level(green_space) # Personality-specific components if personality == "little_adventurers": scores["playground_quality"] = green_space.recreation.playground_quality scores["shade_quality"] = self._score_shade_quality_with_trees(green_space, tree_data) scores["toilet_proximity"] = await self._score_toilet_proximity(green_space) scores["family_amenities"] = await self._score_family_amenities(green_space) elif personality == "date_night": scores["romantic_atmosphere"] = await self._score_romantic_atmosphere(green_space) scores["scenic_beauty"] = self._score_scenic_beauty(green_space) scores["restaurant_proximity"] = await self._score_restaurant_proximity(green_space) scores["privacy_score"] = self._score_privacy(green_space) scores["lighting_quality"] = green_space.accessibility.lighting_quality * 20 elif personality == "squad_goals": scores["space_size"] = self._score_space_size(green_space) scores["group_amenities"] = await self._score_group_amenities(green_space) scores["food_options"] = await self._score_food_options(green_space) scores["parking_availability"] = green_space.accessibility.parking_availability * 20 scores["activity_options"] = self._score_activity_options(green_space) elif personality == "zen_masters": scores["quietness"] = self._score_quietness(green_space) scores["nature_immersion"] = self._score_nature_immersion_with_trees(green_space, tree_data) scores["crowd_density"] = await self._score_crowd_density(green_space) scores["water_features"] = 100 if green_space.environmental.water_features else 0 scores["meditation_spots"] = self._score_meditation_spots_with_trees(green_space, tree_data) scores["air_quality"] = self._score_air_quality_with_trees(green_space, tree_data) elif personality == "active_lifestyle": scores["fitness_facilities"] = 100 if green_space.recreation.sports_facilities else 0 scores["running_cycling_paths"] = self._score_running_cycling_paths(green_space) scores["sports_areas"] = self._score_sports_areas(green_space) scores["terrain_variety"] = self._score_terrain_variety(green_space) elif personality == "wildlife_lover": scores["wildlife_diversity"] = self._score_wildlife_diversity_with_trees(green_space, tree_data) scores["natural_habitat"] = self._score_natural_habitat_with_trees(green_space, tree_data) scores["water_features"] = 100 if green_space.environmental.water_features else 0 scores["tree_coverage"] = self._score_tree_coverage_with_real_data(green_space, tree_data) scores["observation_spots"] = self._score_observation_spots_with_trees(green_space, tree_data) elif personality == "art_nerd": scores["cultural_proximity"] = await self._score_cultural_proximity(green_space) scores["artistic_features"] = await self._score_artistic_features(green_space) scores["museum_accessibility"] = await self._score_museum_accessibility(green_space) scores["creative_atmosphere"] = self._score_creative_atmosphere(green_space) scores["cafe_culture"] = await self._score_cafe_culture(green_space) scores["inspiring_views"] = self._score_inspiring_views(green_space) elif personality == "history_geek": scores["historical_significance"] = await self._score_historical_significance(green_space) scores["monument_proximity"] = await self._score_monument_proximity(green_space) scores["educational_value"] = self._score_educational_value(green_space) scores["architectural_features"] = self._score_architectural_features(green_space) scores["cultural_context"] = await self._score_cultural_context(green_space) return scores # === COMPONENT SCORING METHODS === def _score_accessibility(self, green_space: GreenSpace, user_location: Optional[Tuple[float, float]]) -> int: """Score overall accessibility.""" score = green_space.accessibility.public_transport_score * 20 if green_space.accessibility.wheelchair_accessible: score += 20 if user_location: distance = geodesic( user_location, (green_space.coordinates.lat, green_space.coordinates.lng) ).meters # Score based on distance (closer = better) if distance <= 500: score += 20 elif distance <= 1000: score += 15 elif distance <= 2000: score += 10 else: score += 5 return min(100, score) def _score_safety(self, green_space: GreenSpace) -> int: """Score safety based on lighting and accessibility.""" score = green_space.accessibility.lighting_quality * 15 if green_space.accessibility.wheelchair_accessible: score += 20 score += green_space.accessibility.public_transport_score * 10 return min(100, score) def _score_noise_level(self, green_space: GreenSpace) -> int: """Score based on noise level (lower noise = higher score).""" return (6 - green_space.environmental.noise_level.value) * 20 async def _score_toilet_proximity(self, green_space: GreenSpace) -> int: """Score based on toilet proximity.""" toilets = [a for a in green_space.nearby_amenities if a.type == AmenityType.TOILET] if not toilets: return 20 # Assume some basic facilities closest_distance = min(toilet.distance_meters for toilet in toilets) if closest_distance <= 100: return 100 elif closest_distance <= 300: return 80 elif closest_distance <= 500: return 60 else: return 40 async def _score_family_amenities(self, green_space: GreenSpace) -> int: """Score family-friendly amenities.""" score = green_space.recreation.playground_quality if green_space.environmental.shade_quality > 70: score += 20 if green_space.accessibility.wheelchair_accessible: score += 10 return min(100, score) async def _score_romantic_atmosphere(self, green_space: GreenSpace) -> int: """Score romantic atmosphere.""" score = 0 if green_space.environmental.water_features: score += 30 if green_space.environmental.noise_level.value <= 2: score += 25 score += green_space.environmental.tree_coverage_percent // 2 score += green_space.accessibility.lighting_quality * 5 return min(100, score) def _score_scenic_beauty(self, green_space: GreenSpace) -> int: """Score scenic beauty.""" score = green_space.environmental.tree_coverage_percent if green_space.environmental.water_features: score += 20 score += green_space.environmental.shade_quality // 2 return min(100, score) async def _score_restaurant_proximity(self, green_space: GreenSpace) -> int: """Score restaurant proximity.""" restaurants = [a for a in green_space.nearby_amenities if a.type in [AmenityType.RESTAURANT, AmenityType.CAFE]] if not restaurants: return 30 closest_distance = min(r.distance_meters for r in restaurants) if closest_distance <= 200: return 100 elif closest_distance <= 500: return 80 else: return 60 def _score_privacy(self, green_space: GreenSpace) -> int: """Score privacy based on size and tree coverage.""" score = 0 if green_space.area_sqm and green_space.area_sqm > 50000: score += 40 elif green_space.area_sqm and green_space.area_sqm > 10000: score += 20 score += green_space.environmental.tree_coverage_percent // 2 return min(100, score) def _score_space_size(self, green_space: GreenSpace) -> int: """Score based on space size for groups.""" if not green_space.area_sqm: return 50 if green_space.area_sqm > 100000: return 100 elif green_space.area_sqm > 50000: return 80 elif green_space.area_sqm > 20000: return 60 else: return 40 async def _score_group_amenities(self, green_space: GreenSpace) -> int: """Score amenities suitable for groups.""" score = 0 if green_space.recreation.bbq_allowed: score += 30 if green_space.recreation.sports_facilities: score += 25 toilets = [a for a in green_space.nearby_amenities if a.type == AmenityType.TOILET] if toilets: score += 20 score += green_space.accessibility.parking_availability * 5 return min(100, score) async def _score_food_options(self, green_space: GreenSpace) -> int: """Score food options nearby.""" food_amenities = [a for a in green_space.nearby_amenities if a.type in [AmenityType.RESTAURANT, AmenityType.CAFE, AmenityType.ICE_CREAM, AmenityType.SPATI]] return min(100, len(food_amenities) * 25) def _score_activity_options(self, green_space: GreenSpace) -> int: """Score activity options.""" score = 0 if green_space.recreation.sports_facilities: score += 30 if green_space.recreation.running_paths: score += 20 if green_space.recreation.cycling_paths: score += 20 if green_space.recreation.playground_quality > 0: score += 15 if green_space.recreation.bbq_allowed: score += 15 return min(100, score) def _score_quietness(self, green_space: GreenSpace) -> int: """Score quietness for zen seekers.""" return (6 - green_space.environmental.noise_level.value) * 25 def _score_nature_immersion(self, green_space: GreenSpace) -> int: """Score nature immersion.""" score = green_space.environmental.tree_coverage_percent score += green_space.environmental.natural_surface_percent // 2 if green_space.environmental.water_features: score += 15 return min(100, score) async def _score_crowd_density(self, green_space: GreenSpace) -> int: """Score based on crowd density (lower crowds = higher score).""" # Mock implementation - in reality would use real-time data base_score = 70 if green_space.area_sqm and green_space.area_sqm > 100000: base_score += 20 if green_space.accessibility.public_transport_score <= 3: base_score += 10 return min(100, base_score) def _score_meditation_spots(self, green_space: GreenSpace) -> int: """Score meditation spot quality.""" score = green_space.environmental.tree_coverage_percent // 2 if green_space.environmental.water_features: score += 25 if green_space.environmental.noise_level.value <= 2: score += 25 return min(100, score) def _score_air_quality(self, green_space: GreenSpace) -> int: """Score air quality.""" score = green_space.environmental.tree_coverage_percent if green_space.environmental.natural_surface_percent > 80: score += 20 return min(100, score) def _score_running_cycling_paths(self, green_space: GreenSpace) -> int: """Score running and cycling infrastructure.""" score = 0 if green_space.recreation.running_paths: score += 50 if green_space.recreation.cycling_paths: score += 50 return score def _score_sports_areas(self, green_space: GreenSpace) -> int: """Score sports areas.""" return 100 if green_space.recreation.sports_facilities else 30 def _score_terrain_variety(self, green_space: GreenSpace) -> int: """Score terrain variety for active users.""" score = 50 # Base score if green_space.area_sqm and green_space.area_sqm > 50000: score += 30 if green_space.environmental.natural_surface_percent > 70: score += 20 return min(100, score) def _score_natural_habitat(self, green_space: GreenSpace) -> int: """Score natural habitat quality.""" score = green_space.environmental.tree_coverage_percent score += green_space.environmental.natural_surface_percent // 2 if green_space.environmental.water_features: score += 15 return min(100, score) def _score_observation_spots(self, green_space: GreenSpace) -> int: """Score wildlife observation opportunities.""" score = green_space.environmental.tree_coverage_percent // 2 if green_space.environmental.water_features: score += 30 if green_space.environmental.noise_level.value <= 2: score += 20 return min(100, score) async def _score_cultural_proximity(self, green_space: GreenSpace) -> int: """Score proximity to cultural venues.""" # Mock implementation - would check for nearby museums, galleries, etc. if green_space.neighborhood.lower() in ["mitte", "kreuzberg", "prenzlauer berg"]: return 80 return 50 async def _score_artistic_features(self, green_space: GreenSpace) -> int: """Score artistic features in the space.""" # Mock implementation - would check for sculptures, installations, etc. return 60 # Base score async def _score_museum_accessibility(self, green_space: GreenSpace) -> int: """Score museum accessibility.""" # Mock implementation if green_space.neighborhood.lower() == "mitte": return 90 elif green_space.neighborhood.lower() in ["kreuzberg", "friedrichshain"]: return 70 return 40 def _score_creative_atmosphere(self, green_space: GreenSpace) -> int: """Score creative atmosphere.""" score = 50 # Base score if green_space.environmental.tree_coverage_percent > 60: score += 20 if green_space.environmental.water_features: score += 15 if green_space.accessibility.lighting_quality >= 4: score += 15 return min(100, score) async def _score_cafe_culture(self, green_space: GreenSpace) -> int: """Score cafe culture nearby.""" cafes = [a for a in green_space.nearby_amenities if a.type == AmenityType.CAFE] return min(100, len(cafes) * 30 + 40) def _score_inspiring_views(self, green_space: GreenSpace) -> int: """Score inspiring views.""" score = green_space.environmental.tree_coverage_percent // 2 if green_space.environmental.water_features: score += 25 if green_space.area_sqm and green_space.area_sqm > 50000: score += 25 return min(100, score) async def _score_historical_significance(self, green_space: GreenSpace) -> int: """Score historical significance.""" # Mock implementation - would check historical databases historical_neighborhoods = ["mitte", "kreuzberg", "charlottenburg"] if green_space.neighborhood.lower() in historical_neighborhoods: return 80 return 40 async def _score_monument_proximity(self, green_space: GreenSpace) -> int: """Score proximity to monuments.""" # Mock implementation if green_space.neighborhood.lower() == "mitte": return 90 return 50 def _score_educational_value(self, green_space: GreenSpace) -> int: """Score educational value.""" score = 50 # Base score if green_space.environmental.wildlife_diversity_score > 70: score += 25 if green_space.environmental.water_features: score += 15 if green_space.area_sqm and green_space.area_sqm > 100000: score += 10 return min(100, score) def _score_architectural_features(self, green_space: GreenSpace) -> int: """Score architectural features.""" # Mock implementation return 60 async def _score_cultural_context(self, green_space: GreenSpace) -> int: """Score cultural context.""" # Mock implementation cultural_neighborhoods = ["mitte", "kreuzberg", "prenzlauer berg", "friedrichshain"] if green_space.neighborhood.lower() in cultural_neighborhoods: return 75 return 45 def _create_explanation(self, personality: str, explanation_parts: List[str]) -> str: """Create a human-readable explanation.""" if not explanation_parts: return f"This space has moderate appeal for {personality.replace('_', ' ')} personality type." return f"Great for {personality.replace('_', ' ')}: " + ", ".join(explanation_parts[:3]) def _generate_recommendations( self, green_space: GreenSpace, personality: str, component_scores: Dict[str, int] ) -> List[str]: """Generate personalized recommendations.""" recommendations = [] if personality == "little_adventurers": if green_space.recreation.playground_quality > 70: recommendations.append("Perfect playground for kids to explore") if green_space.environmental.shade_quality > 70: recommendations.append("Plenty of shade for family comfort") elif personality == "date_night": if green_space.environmental.water_features: recommendations.append("Romantic walks by the water") if component_scores.get("lighting_quality", 0) > 70: recommendations.append("Beautiful evening atmosphere") elif personality == "zen_masters": if component_scores.get("quietness", 0) > 80: recommendations.append("Perfect for meditation and reflection") if green_space.environmental.water_features: recommendations.append("Peaceful water sounds enhance tranquility") # Add generic recommendations if none specific if not recommendations: recommendations.append("Enjoy the natural beauty of this space") recommendations.append("Great for relaxation and outdoor activities") return recommendations async def score_location( self, lat: float, lng: float, personality: str, radius: int ) -> Dict[str, Any]: """Score a specific location with optimized performance.""" # Check if location is in a green space green_space = await self.berlin_data.get_green_space_at_location(lat, lng) if not green_space: return { "score": 0, "explanation": "Location is not in a recognized green space", "location": {"lat": lat, "lng": lng}, "personality": personality } # Score the green space (this now uses cached tree data internally) personality_score = await self.score_green_space(green_space, personality, (lat, lng)) return { "score": personality_score.score, "explanation": personality_score.explanation, "location": {"lat": lat, "lng": lng}, "personality": personality, "green_space": green_space.name, "recommendations": personality_score.recommendations } async def find_best_locations_within( self, green_space: GreenSpace, personality: str ) -> List[LocationScore]: """Find best locations within a green space.""" # Mock implementation - in reality would analyze specific spots locations = [] # Generate a few sample locations within the space base_lat, base_lng = green_space.coordinates.lat, green_space.coordinates.lng for i, (offset_lat, offset_lng, description) in enumerate([ (0.001, 0.001, "Northeast corner with mature trees"), (-0.001, 0.001, "Southeast area near water feature"), (0.0, -0.001, "Western meadow area") ]): location = LocationScore( coordinates=Coordinates( lat=base_lat + offset_lat, lng=base_lng + offset_lng ), score=85 - i * 5, # Decreasing scores explanation=f"Excellent spot: {description}", nearby_features=["trees", "seating", "paths"], best_for=[personality], recommendations=[f"Perfect for {personality.replace('_', ' ')} activities"] ) locations.append(location) return locations # === ENHANCED TREE-BASED SCORING METHODS === def _score_tree_coverage_with_real_data(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced tree coverage scoring using real street tree data.""" if not tree_data: return green_space.environmental.tree_coverage_percent try: # Combine base environmental score with real tree data base_score = green_space.environmental.tree_coverage_percent tree_shade_coverage = tree_data.shade_analysis.estimated_shade_coverage # Use the higher of the two scores, with bonus for high tree density enhanced_score = max(base_score, tree_shade_coverage) # Bonus for high tree density if tree_data.metrics.trees_per_hectare > 50: enhanced_score = min(100, enhanced_score + 15) elif tree_data.metrics.trees_per_hectare > 20: enhanced_score = min(100, enhanced_score + 10) return int(enhanced_score) except Exception as e: print(f"Error enhancing tree coverage score: {e}") return green_space.environmental.tree_coverage_percent def _score_wildlife_diversity_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced wildlife diversity scoring using real tree species data.""" if not tree_data: return green_space.environmental.wildlife_diversity_score try: base_score = green_space.environmental.wildlife_diversity_score tree_diversity = tree_data.metrics.species_diversity_score mature_trees_bonus = min(20, tree_data.metrics.mature_trees_count) # Combine scores with weighting enhanced_score = int((base_score * 0.6) + (tree_diversity * 0.4) + mature_trees_bonus) return min(100, enhanced_score) except Exception as e: print(f"Error enhancing wildlife diversity score: {e}") return green_space.environmental.wildlife_diversity_score def _score_shade_quality_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced shade quality scoring using real tree data.""" if not tree_data: return green_space.environmental.shade_quality try: base_shade = green_space.environmental.shade_quality tree_shade_quality = tree_data.shade_analysis.shade_quality_score # Use the better of the two scores enhanced_score = max(base_shade, tree_shade_quality) # Bonus for large nearby trees large_trees_count = len(tree_data.shade_analysis.nearby_large_trees) if large_trees_count > 5: enhanced_score = min(100, enhanced_score + 15) elif large_trees_count > 2: enhanced_score = min(100, enhanced_score + 10) return int(enhanced_score) except Exception as e: print(f"Error enhancing shade quality score: {e}") return green_space.environmental.shade_quality def _score_nature_immersion_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced nature immersion scoring using real tree data.""" if not tree_data: return self._score_nature_immersion(green_space) try: # Base score from existing method base_score = green_space.environmental.tree_coverage_percent base_score += green_space.environmental.natural_surface_percent // 2 if green_space.environmental.water_features: base_score += 15 # Enhancement from tree data tree_density_score = min(30, tree_data.metrics.trees_per_hectare) canopy_density_bonus = int(tree_data.shade_analysis.canopy_density * 20) if tree_data.shade_analysis.canopy_density else 0 species_diversity_bonus = min(15, tree_data.metrics.species_diversity_score // 5) enhanced_score = base_score + tree_density_score + canopy_density_bonus + species_diversity_bonus return min(100, int(enhanced_score)) except Exception as e: print(f"Error enhancing nature immersion score: {e}") return self._score_nature_immersion(green_space) def _score_natural_habitat_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced natural habitat scoring using real tree data.""" if not tree_data: return self._score_natural_habitat(green_space) try: base_score = green_space.environmental.tree_coverage_percent base_score += green_space.environmental.natural_surface_percent // 2 if green_space.environmental.water_features: base_score += 15 # Tree habitat quality factors mature_trees_score = min(25, tree_data.metrics.mature_trees_count // 2) species_diversity_score = min(20, tree_data.metrics.species_diversity_score // 3) enhanced_score = base_score + mature_trees_score + species_diversity_score return min(100, int(enhanced_score)) except Exception as e: print(f"Error enhancing natural habitat score: {e}") return self._score_natural_habitat(green_space) def _score_observation_spots_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced wildlife observation scoring using real tree data.""" if not tree_data: return self._score_observation_spots(green_space) try: base_score = green_space.environmental.tree_coverage_percent // 2 if green_space.environmental.water_features: base_score += 30 if green_space.environmental.noise_level.value <= 2: base_score += 20 # Large trees provide better observation opportunities large_trees_count = len(tree_data.shade_analysis.nearby_large_trees) observation_bonus = min(25, large_trees_count * 3) # Species diversity attracts more wildlife to observe diversity_bonus = min(15, tree_data.metrics.species_diversity_score // 4) enhanced_score = base_score + observation_bonus + diversity_bonus return min(100, int(enhanced_score)) except Exception as e: print(f"Error enhancing observation spots score: {e}") return self._score_observation_spots(green_space) def _score_meditation_spots_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced meditation spots scoring using real tree data.""" if not tree_data: return self._score_meditation_spots(green_space) try: base_score = green_space.environmental.tree_coverage_percent // 2 if green_space.environmental.water_features: base_score += 25 if green_space.environmental.noise_level.value <= 2: base_score += 25 # Trees enhance meditation through natural sounds and shade shade_quality_bonus = min(20, tree_data.shade_analysis.shade_quality_score // 4) canopy_bonus = int(tree_data.shade_analysis.canopy_density * 15) if tree_data.shade_analysis.canopy_density else 0 enhanced_score = base_score + shade_quality_bonus + canopy_bonus return min(100, int(enhanced_score)) except Exception as e: print(f"Error enhancing meditation spots score: {e}") return self._score_meditation_spots(green_space) def _score_air_quality_with_trees(self, green_space: GreenSpace, tree_data: Optional[Any] = None) -> int: """Enhanced air quality scoring using real tree data.""" if not tree_data: return self._score_air_quality(green_space) try: base_score = green_space.environmental.tree_coverage_percent if green_space.environmental.natural_surface_percent > 80: base_score += 20 # More trees = better air quality tree_density_bonus = min(25, tree_data.metrics.trees_per_hectare // 2) mature_trees_bonus = min(15, tree_data.metrics.mature_trees_count // 3) enhanced_score = base_score + tree_density_bonus + mature_trees_bonus return min(100, int(enhanced_score)) except Exception as e: print(f"Error enhancing air quality score: {e}") return self._score_air_quality(green_space)