119 lines
2.7 KiB
Python
119 lines
2.7 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, Literal
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
|
|
|
|
class GeoAntenna(BaseModel):
|
|
lat: float
|
|
lng: float
|
|
antenna_height_m: float = Field(default=2.0, ge=0)
|
|
|
|
@field_validator("lat")
|
|
@classmethod
|
|
def validate_lat(cls, value: float) -> float:
|
|
if not -90 <= value <= 90:
|
|
raise ValueError("lat must be between -90 and 90")
|
|
return value
|
|
|
|
@field_validator("lng")
|
|
@classmethod
|
|
def validate_lng(cls, value: float) -> float:
|
|
if not -180 <= value <= 180:
|
|
raise ValueError("lng must be between -180 and 180")
|
|
return value
|
|
|
|
|
|
class LinkRequest(BaseModel):
|
|
model_config = ConfigDict(populate_by_name=True)
|
|
|
|
from_: GeoAntenna = Field(alias="from")
|
|
to: GeoAntenna
|
|
frequency_mhz: float = Field(gt=0)
|
|
fresnel_clearance: float = Field(default=0.6, ge=0, le=1)
|
|
tx_power_dbm: float = 20.0
|
|
rx_sensitivity_dbm: float = -137.0
|
|
sample_step_m: float = Field(default=30.0, gt=0)
|
|
|
|
|
|
class LinkBudget(BaseModel):
|
|
tx_power_dbm: float
|
|
rx_sensitivity_dbm: float
|
|
margin_db: float
|
|
status: Literal["good", "marginal", "poor"]
|
|
|
|
|
|
class Obstacle(BaseModel):
|
|
lat: float
|
|
lng: float
|
|
distance_from_start_m: float
|
|
terrain_height_m: float
|
|
canopy_height_m: float
|
|
building_height_m: float
|
|
total_height_m: float
|
|
fresnel_radius_m: float
|
|
clearance_m: float
|
|
|
|
|
|
class PathProfilePoint(BaseModel):
|
|
distance_m: float
|
|
lat: float
|
|
lng: float
|
|
terrain_m: float
|
|
canopy_m: float
|
|
building_m: float
|
|
total_m: float
|
|
los_height_m: float
|
|
fresnel_radius_m: float
|
|
clearance_m: float
|
|
|
|
|
|
class LinkResponse(BaseModel):
|
|
distance_m: float
|
|
los: bool
|
|
fresnel_clear: bool
|
|
free_space_loss_db: float
|
|
link_budget: LinkBudget
|
|
obstacles: list[Obstacle]
|
|
path_profile: list[PathProfilePoint]
|
|
|
|
|
|
class CoverageRequest(BaseModel):
|
|
origin: GeoAntenna
|
|
frequency_mhz: float = Field(gt=0)
|
|
radius_m: float = Field(gt=0)
|
|
resolution_m: float = Field(gt=0)
|
|
fresnel_clearance: float = Field(default=0.6, ge=0, le=1)
|
|
tx_power_dbm: float = 20.0
|
|
rx_sensitivity_dbm: float = -137.0
|
|
sample_step_m: float = Field(default=30.0, gt=0)
|
|
|
|
|
|
class CoverageResponse(BaseModel):
|
|
origin: GeoAntenna
|
|
coverage_geojson: dict[str, Any]
|
|
|
|
|
|
class ElevationProfilePoint(BaseModel):
|
|
distance_m: float
|
|
lat: float
|
|
lng: float
|
|
terrain_m: float
|
|
|
|
|
|
class ElevationProfileResponse(BaseModel):
|
|
distance_m: float
|
|
path_profile: list[ElevationProfilePoint]
|
|
|
|
|
|
class DataLayersHealth(BaseModel):
|
|
srtm: bool
|
|
canopy: bool
|
|
buildings: bool
|
|
|
|
|
|
class HealthResponse(BaseModel):
|
|
status: Literal["ok", "degraded"]
|
|
data_layers: DataLayersHealth
|