188 lines
7.0 KiB
Python
188 lines
7.0 KiB
Python
# app/routers/green_spaces.py
|
|
from fastapi import APIRouter, Query, HTTPException
|
|
from typing import Optional, List
|
|
from enum import Enum
|
|
|
|
from app.models.green_space import ScoringRequest
|
|
from app.models.response import GreenSpaceResponse, NearbyAmenitiesResponse
|
|
from app.services.green_space_service import GreenSpaceService
|
|
from app.services.berlin_data_service import BerlinDataService
|
|
|
|
router = APIRouter(prefix="/green-spaces", tags=["green-spaces"])
|
|
|
|
# Services
|
|
green_space_service = GreenSpaceService()
|
|
berlin_data = BerlinDataService()
|
|
|
|
class PersonalityType(str, Enum):
|
|
LITTLE_ADVENTURERS = "little_adventurers"
|
|
DATE_NIGHT = "date_night"
|
|
SQUAD_GOALS = "squad_goals"
|
|
ZEN_MASTERS = "zen_masters"
|
|
ACTIVE_LIFESTYLE = "active_lifestyle"
|
|
WILDLIFE_LOVER = "wildlife_lover"
|
|
ART_NERD = "art_nerd"
|
|
HISTORY_GEEK = "history_geek"
|
|
|
|
@router.get("/search", response_model=GreenSpaceResponse)
|
|
async def search_green_spaces(
|
|
# Location filters
|
|
lat: Optional[float] = Query(None, description="Latitude for location-based search"),
|
|
lng: Optional[float] = Query(None, description="Longitude for location-based search"),
|
|
radius: int = Query(2000, ge=100, le=10000, description="Search radius in meters"),
|
|
|
|
# Area filters
|
|
neighborhood: Optional[str] = Query(None, description="Berlin neighborhood"),
|
|
|
|
# Personality scoring
|
|
personality: PersonalityType = Query(..., description="Personality type for scoring"),
|
|
min_score: int = Query(60, ge=0, le=100, description="Minimum personality score"),
|
|
|
|
# Feature filters
|
|
min_size: Optional[int] = Query(None, ge=100, description="Minimum area in square meters"),
|
|
has_water: Optional[bool] = Query(None, description="Must have water features"),
|
|
has_playground: Optional[bool] = Query(None, description="Must have playground nearby"),
|
|
max_noise_level: Optional[int] = Query(None, ge=1, le=5, description="Maximum noise level"),
|
|
|
|
# Response options
|
|
limit: int = Query(20, ge=1, le=100, description="Maximum results"),
|
|
include_amenities: bool = Query(False, description="Include detailed amenity data"),
|
|
):
|
|
"""
|
|
Search for green spaces in Berlin and score them for a personality type.
|
|
|
|
This endpoint dynamically analyzes Berlin's green spaces and scores them
|
|
based on the selected personality preferences.
|
|
"""
|
|
try:
|
|
# Build search request
|
|
search_request = ScoringRequest(
|
|
personality=personality.value,
|
|
location=(lat, lng) if lat and lng else None,
|
|
radius=radius,
|
|
neighborhood=neighborhood,
|
|
min_score=min_score,
|
|
filters={
|
|
"min_size": min_size,
|
|
"has_water": has_water,
|
|
"has_playground": has_playground,
|
|
"max_noise_level": max_noise_level,
|
|
},
|
|
limit=limit,
|
|
include_amenities=include_amenities
|
|
)
|
|
|
|
# Get and score green spaces
|
|
results = await green_space_service.search_and_score(search_request)
|
|
|
|
return GreenSpaceResponse(
|
|
green_spaces=results,
|
|
total_found=len(results),
|
|
search_params=search_request.dict(),
|
|
personality=personality.value
|
|
)
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Search failed: {str(e)}")
|
|
|
|
@router.get("/score-location")
|
|
async def score_specific_location(
|
|
lat: float = Query(..., description="Latitude of location to score"),
|
|
lng: float = Query(..., description="Longitude of location to score"),
|
|
personality: PersonalityType = Query(..., description="Personality type"),
|
|
radius: int = Query(300, ge=50, le=1000, description="Analysis radius in meters"),
|
|
):
|
|
"""
|
|
Score a specific location for a personality type.
|
|
|
|
Analyzes the immediate area around the given coordinates and provides
|
|
a personality-based score with detailed explanations.
|
|
"""
|
|
try:
|
|
# Check if location is in a green space
|
|
green_space = await 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.value
|
|
}
|
|
|
|
# Score the location
|
|
from app.services.scoring_engine import ScoringEngine
|
|
scoring_engine = ScoringEngine()
|
|
score_result = await scoring_engine.score_location(
|
|
lat, lng, personality.value, radius
|
|
)
|
|
|
|
return score_result
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Scoring failed: {str(e)}")
|
|
|
|
@router.get("/discover")
|
|
async def discover_green_spaces(
|
|
lat: float = Query(..., description="Your current latitude"),
|
|
lng: float = Query(..., description="Your current longitude"),
|
|
max_distance: int = Query(5000, ge=500, le=20000, description="Maximum distance in meters"),
|
|
personality: PersonalityType = Query(..., description="Your personality type"),
|
|
):
|
|
"""
|
|
Discover highly-rated green spaces near your location.
|
|
|
|
Returns the best green spaces within walking/cycling distance,
|
|
scored for your personality type.
|
|
"""
|
|
try:
|
|
discoveries = await green_space_service.discover_nearby(
|
|
lat, lng, max_distance, personality.value
|
|
)
|
|
|
|
return {
|
|
"discoveries": discoveries,
|
|
"your_location": {"lat": lat, "lng": lng},
|
|
"search_radius": max_distance,
|
|
"personality": personality.value
|
|
}
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Discovery failed: {str(e)}")
|
|
|
|
@router.get("/{space_id}/analysis")
|
|
async def analyze_green_space(
|
|
space_id: str,
|
|
personality: Optional[PersonalityType] = Query(None, description="Personality for focused analysis"),
|
|
):
|
|
"""
|
|
Get detailed analysis of a specific green space.
|
|
|
|
Provides comprehensive scoring across all personalities or
|
|
focused analysis for a specific personality type.
|
|
"""
|
|
try:
|
|
analysis = await green_space_service.get_detailed_analysis(
|
|
space_id, personality.value if personality else None
|
|
)
|
|
|
|
if not analysis:
|
|
raise HTTPException(status_code=404, detail="Green space not found")
|
|
|
|
return analysis
|
|
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Analysis failed: {str(e)}")
|
|
|
|
@router.get("/conditions")
|
|
async def get_current_conditions(
|
|
lat: float = Query(..., description="Latitude"),
|
|
lng: float = Query(..., description="Longitude"),
|
|
):
|
|
"""Get current conditions at a green space (weather, crowds, etc.)."""
|
|
try:
|
|
conditions = await berlin_data.get_current_conditions(lat, lng)
|
|
return conditions
|
|
except Exception as e:
|
|
raise HTTPException(status_code=500, detail=f"Failed to get conditions: {str(e)}")
|