added fresnel zone

This commit is contained in:
2026-06-23 16:08:47 +03:00
parent b9a1ca4125
commit 89ca1ee945
10 changed files with 72 additions and 4 deletions
Binary file not shown.
+1 -1
View File
@@ -65,5 +65,5 @@ def manual_link_budget(
rx_power_dbm=rx_power,
fade_margin_db=margin,
fresnel_clear=fresnel_clear,
link_viable=margin > 0 and fresnel_clear,
link_viable=margin > 0,
)
Binary file not shown.
+1
View File
@@ -27,6 +27,7 @@ class LinkBudgetResponse(BaseModel):
rx_power_dbm: float
fade_margin_db: float
fresnel_clear: bool
fresnel_quality: Literal["clear", "partial", "blocked"]
link_viable: bool
los_clear: bool
geometric_los: bool
Binary file not shown.
+9
View File
@@ -68,6 +68,7 @@ def link_budget(request: LinkBudgetRequest, db: Session | None = None) -> LinkBu
]
return LinkBudgetResponse(
**budget.__dict__,
fresnel_quality=_fresnel_quality(los_result.los_clear, los_result.geometric_los),
los_clear=los_result.los_clear,
geometric_los=los_result.geometric_los,
first_fresnel_clearance_pct=los_result.first_fresnel_clearance_pct,
@@ -85,6 +86,14 @@ def link_budget(request: LinkBudgetRequest, db: Session | None = None) -> LinkBu
)
def _fresnel_quality(los_clear: bool, geometric_los: bool) -> str:
if los_clear:
return "clear"
if geometric_los:
return "partial"
return "blocked"
def _convert_obstruction(sample: LosSample | None) -> Obstruction | None:
if sample is None:
return None
+1
View File
@@ -123,4 +123,5 @@ def test_manual_link_budget_endpoint() -> None:
data = response.json()
assert data["fspl_db"] > 0
assert "link_viable" in data
assert data["fresnel_quality"] in {"clear", "partial", "blocked"}
assert "data_sources" in data
+47
View File
@@ -97,7 +97,54 @@ def test_link_budget_includes_diffraction_loss(monkeypatch) -> None:
assert isclose(result.total_loss_db, result.fspl_db + result.diffraction_db)
assert result.fresnel_clear is False
assert result.los_clear is False
assert result.fresnel_quality == "blocked"
assert result.geometric_los is False
assert result.fresnel_violations_count > 0
assert result.geometric_obstructions_count > 0
assert result.worst_obstruction is not None
def test_link_viable_depends_on_margin_not_fresnel_quality(monkeypatch) -> None:
monkeypatch.setattr(link_service, "vegetation_loss_along", lambda *args, **kwargs: 0.0)
def fake_profile(points, *args, **kwargs):
midpoint = len(points) // 2
return SurfaceProfile(
distance_m=points[-1].distance_m,
samples=[
SurfaceSample(
i=index,
lat=point.lat,
lon=point.lon,
distance_m=point.distance_m,
ground_m=20.0 if index == midpoint else 0.0,
building_m=0,
canopy_m=0,
surface_m=20.0 if index == midpoint else 0.0,
)
for index, point in enumerate(points)
],
)
monkeypatch.setattr(link_service, "surface_profile_from_points", fake_profile)
request = LinkBudgetRequest(
tx=LinkTx(lat=60.17, lon=24.94, height_agl=0, power_dbm=70, gain_dbi=8),
rx=LinkRx(
lat=60.25,
lon=25.10,
height_agl=0,
gain_dbi=2,
sensitivity_dbm=-120,
),
frequency_mhz=433,
model="manual",
include_buildings=False,
include_vegetation=False,
)
result = link_service.link_budget(request)
assert result.fade_margin_db > 0
assert result.fresnel_quality == "blocked"
assert result.fresnel_clear is False
assert result.link_viable is True