@mytec: 1.4iter ready for testing

This commit is contained in:
2026-01-31 00:59:30 +02:00
parent 1ffac9f510
commit 61e113965c
8 changed files with 1398 additions and 36 deletions

View File

@@ -1,3 +1,5 @@
import time
from fastapi import APIRouter, HTTPException, BackgroundTasks
from typing import List, Optional
from pydantic import BaseModel
@@ -5,7 +7,9 @@ from app.services.coverage_service import (
coverage_service,
CoverageSettings,
SiteParams,
CoveragePoint
CoveragePoint,
apply_preset,
PRESETS,
)
router = APIRouter()
@@ -23,6 +27,8 @@ class CoverageResponse(BaseModel):
count: int
settings: CoverageSettings
stats: dict
computation_time: float # seconds
models_used: List[str] # which models were active
@router.post("/calculate")
@@ -30,7 +36,8 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
"""
Calculate RF coverage for one or more sites
Returns grid of RSRP values with terrain and building effects
Returns grid of RSRP values with terrain and building effects.
Supports propagation model presets: fast, standard, detailed, full.
"""
if not request.sites:
raise HTTPException(400, "At least one site required")
@@ -45,6 +52,13 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
if request.settings.resolution < 50:
raise HTTPException(400, "Minimum resolution 50m")
# Apply preset and determine active models
effective_settings = apply_preset(request.settings.model_copy())
models_used = _get_active_models(effective_settings)
# Time the calculation
start_time = time.time()
# Calculate
if len(request.sites) == 1:
points = await coverage_service.calculate_coverage(
@@ -57,6 +71,8 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
request.settings
)
computation_time = time.time() - start_time
# Calculate stats
rsrp_values = [p.rsrp for p in points]
los_count = sum(1 for p in points if p.has_los)
@@ -68,16 +84,48 @@ async def calculate_coverage(request: CoverageRequest) -> CoverageResponse:
"los_percentage": (los_count / len(points) * 100) if points else 0,
"points_with_buildings": sum(1 for p in points if p.building_loss > 0),
"points_with_terrain_loss": sum(1 for p in points if p.terrain_loss > 0),
"points_with_reflection_gain": sum(1 for p in points if p.reflection_gain > 0),
}
return CoverageResponse(
points=points,
count=len(points),
settings=request.settings,
stats=stats
settings=effective_settings,
stats=stats,
computation_time=round(computation_time, 2),
models_used=models_used
)
@router.get("/presets")
async def get_presets():
"""Get available propagation model presets"""
return {
"presets": {
"fast": {
"description": "Quick calculation - terrain only",
**PRESETS["fast"],
"estimated_speed": "~5 seconds for 5km radius"
},
"standard": {
"description": "Balanced - terrain + buildings with materials",
**PRESETS["standard"],
"estimated_speed": "~30 seconds for 5km radius"
},
"detailed": {
"description": "Accurate - adds dominant path analysis",
**PRESETS["detailed"],
"estimated_speed": "~2 minutes for 5km radius"
},
"full": {
"description": "Maximum realism - all models enabled",
**PRESETS["full"],
"estimated_speed": "~5 minutes for 5km radius"
}
}
}
@router.get("/buildings")
async def get_buildings(
min_lat: float,
@@ -102,3 +150,23 @@ async def get_buildings(
"count": len(buildings),
"buildings": [b.model_dump() for b in buildings]
}
def _get_active_models(settings: CoverageSettings) -> List[str]:
"""Determine which propagation models are active"""
models = ["okumura_hata"] # Always active as base model
if settings.use_terrain:
models.append("terrain_los")
if settings.use_buildings:
models.append("buildings")
if settings.use_materials:
models.append("materials")
if settings.use_dominant_path:
models.append("dominant_path")
if settings.use_street_canyon:
models.append("street_canyon")
if settings.use_reflections:
models.append("reflections")
return models