berlin-picnic-api/app/routers/green_spaces.py

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)}")