berlin-picnic-api/app/models/street_tree.py

133 lines
4.0 KiB
Python

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)