from pydantic import BaseModel, Field from typing import Optional, List, Dict, Any from datetime import datetime from enum import Enum from .green_space import Coordinates class TreeCategory(str, Enum): STREET_TREE = "street_tree" PARK_TREE = "park_tree" ANLAGEBAUM = "anlagebaum" ALLEE_TREE = "allee_tree" class TreeHealthStatus(str, Enum): EXCELLENT = "excellent" GOOD = "good" FAIR = "fair" POOR = "poor" CRITICAL = "critical" UNKNOWN = "unknown" class TreeGenus(str, Enum): AHORN = "ahorn" LINDE = "linde" KASTANIE = "kastanie" EICHE = "eiche" PLATANE = "platane" BIRKE = "birke" WEISSDORN = "weissdorn" ROSSKASTANIE = "rosskastanie" PAPPEL = "pappel" ESCHE = "esche" OTHER = "other" class StreetTree(BaseModel): """Individual street tree model based on Berlin Baumkataster data.""" id: str object_id: Optional[int] = None tree_id: Optional[str] = None location_number: Optional[str] = None identifier: Optional[str] = None object_name: Optional[str] = None species_german: Optional[str] = None species_botanical: Optional[str] = None genus_german: Optional[str] = None genus_botanical: Optional[str] = None genus_category: Optional[TreeGenus] = None coordinates: Coordinates district: Optional[str] = None owner: Optional[str] = None category: Optional[str] = None street: Optional[str] = None house_number: Optional[str] = None address_addition: Optional[str] = None full_address: Optional[str] = None planting_year: Optional[int] = None age: Optional[int] = None crown_diameter_m: Optional[float] = None trunk_circumference_cm: Optional[int] = None height_m: Optional[float] = None health_status: TreeHealthStatus = TreeHealthStatus.UNKNOWN confidence_score: int = Field(80, ge=0, le=100) last_updated: datetime = Field(default_factory=datetime.now) class TreeDensityMetrics(BaseModel): """Tree density and coverage metrics for an area.""" total_trees: int = 0 trees_per_hectare: float = 0.0 average_tree_age: Optional[float] = None average_height: Optional[float] = None average_crown_diameter: Optional[float] = None shade_coverage_percent: float = Field(0.0, ge=0, le=100) mature_trees_count: int = 0 # Trees older than 20 years young_trees_count: int = 0 # Trees younger than 10 years dominant_species: List[str] = [] species_diversity_score: int = Field(0, ge=0, le=100) class TreeShadeAnalysis(BaseModel): """Shade analysis for picnic spot evaluation.""" has_nearby_trees: bool = False trees_within_50m: int = 0 trees_within_100m: int = 0 estimated_shade_coverage: int = Field(0, ge=0, le=100) shade_quality_score: int = Field(0, ge=0, le=100) best_shade_times: List[str] = [] # Time periods with best shade seasonal_shade_variation: Optional[str] = None nearby_large_trees: List[StreetTree] = [] canopy_density: Optional[float] = None class TreesSearchFilters(BaseModel): """Filters for searching trees.""" species: Optional[List[str]] = None genus: Optional[List[TreeGenus]] = None min_age: Optional[int] = None max_age: Optional[int] = None min_height: Optional[float] = None max_height: Optional[float] = None min_crown_diameter: Optional[float] = None district: Optional[str] = None category: Optional[str] = None within_radius_m: Optional[int] = None center_lat: Optional[float] = None center_lng: Optional[float] = None class TreesNearLocationResponse(BaseModel): """Response for trees near a location query.""" location: Coordinates radius_m: int trees: List[StreetTree] metrics: TreeDensityMetrics shade_analysis: TreeShadeAnalysis total_found: int query_time_ms: Optional[int] = None data_source: str = "baumkataster" last_updated: datetime = Field(default_factory=datetime.now)