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