berlin-picnic-api/app/services/scoring_engine.py

844 lines
36 KiB
Python

# 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}")
# Calculate component scores
component_scores = await self._calculate_component_scores(
green_space, personality, user_location
)
# 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 _calculate_component_scores(
self,
green_space: GreenSpace,
personality: str,
user_location: Optional[Tuple[float, float]] = 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"] = await self._score_shade_quality_with_trees(green_space)
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"] = await self._score_nature_immersion_with_trees(green_space)
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"] = await self._score_meditation_spots_with_trees(green_space)
scores["air_quality"] = await self._score_air_quality_with_trees(green_space)
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"] = await self._score_wildlife_diversity_with_trees(green_space)
scores["natural_habitat"] = await self._score_natural_habitat_with_trees(green_space)
scores["water_features"] = 100 if green_space.environmental.water_features else 0
scores["tree_coverage"] = await self._score_tree_coverage_with_real_data(green_space)
scores["observation_spots"] = await self._score_observation_spots_with_trees(green_space)
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)
async 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."""
# 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
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 ===
async def _score_tree_coverage_with_real_data(self, green_space: GreenSpace) -> int:
"""Enhanced tree coverage scoring using real street tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=300
)
# Combine base environmental score with real tree data
base_score = green_space.environmental.tree_coverage_percent
tree_shade_coverage = tree_response.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_response.metrics.trees_per_hectare > 50:
enhanced_score = min(100, enhanced_score + 15)
elif tree_response.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
async def _score_wildlife_diversity_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced wildlife diversity scoring using real tree species data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=400
)
base_score = green_space.environmental.wildlife_diversity_score
tree_diversity = tree_response.metrics.species_diversity_score
mature_trees_bonus = min(20, tree_response.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
async def _score_shade_quality_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced shade quality scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=200
)
base_shade = green_space.environmental.shade_quality
tree_shade_quality = tree_response.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_response.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
async def _score_nature_immersion_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced nature immersion scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=350
)
# 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_response.metrics.trees_per_hectare)
canopy_density_bonus = int(tree_response.shade_analysis.canopy_density * 20) if tree_response.shade_analysis.canopy_density else 0
species_diversity_bonus = min(15, tree_response.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)
async def _score_natural_habitat_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced natural habitat scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=300
)
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_response.metrics.mature_trees_count // 2)
species_diversity_score = min(20, tree_response.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)
async def _score_observation_spots_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced wildlife observation scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=250
)
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_response.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_response.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)
async def _score_meditation_spots_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced meditation spots scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=200
)
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_response.shade_analysis.shade_quality_score // 4)
canopy_bonus = int(tree_response.shade_analysis.canopy_density * 15) if tree_response.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)
async def _score_air_quality_with_trees(self, green_space: GreenSpace) -> int:
"""Enhanced air quality scoring using real tree data."""
try:
tree_response = await self.street_tree_service.get_trees_near_location(
green_space.coordinates.lat,
green_space.coordinates.lng,
radius_m=400
)
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_response.metrics.trees_per_hectare // 2)
mature_trees_bonus = min(15, tree_response.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 await self._score_air_quality(green_space)