style: apply ruff-format to entire codebase

First-time run of ruff-format via pre-commit hook normalises quote
style, trailing commas, and whitespace across 188 Python files.
No logic changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
James Smith
2026-07-05 14:48:11 +01:00
parent 82e64104fe
commit 96172ca593
189 changed files with 19883 additions and 19552 deletions
+61 -80
View File
@@ -14,16 +14,18 @@ import math
from dataclasses import dataclass, field
from datetime import datetime, timezone
logger = logging.getLogger('intercept.trilateration')
logger = logging.getLogger("intercept.trilateration")
# =============================================================================
# Data Classes
# =============================================================================
@dataclass
class AgentObservation:
"""A single observation of a device by an agent."""
agent_name: str
agent_lat: float
agent_lon: float
@@ -35,6 +37,7 @@ class AgentObservation:
@dataclass
class LocationEstimate:
"""Estimated location of a device with confidence metrics."""
latitude: float
longitude: float
accuracy_meters: float # Estimated accuracy radius
@@ -47,14 +50,14 @@ class LocationEstimate:
def to_dict(self) -> dict:
"""Convert to JSON-serializable dictionary."""
return {
'latitude': self.latitude,
'longitude': self.longitude,
'accuracy_meters': self.accuracy_meters,
'confidence': self.confidence,
'num_observations': self.num_observations,
'method': self.method,
'timestamp': self.timestamp.isoformat(),
'agents': [obs.agent_name for obs in self.observations]
"latitude": self.latitude,
"longitude": self.longitude,
"accuracy_meters": self.accuracy_meters,
"confidence": self.confidence,
"num_observations": self.num_observations,
"method": self.method,
"timestamp": self.timestamp.isoformat(),
"agents": [obs.agent_name for obs in self.observations],
}
@@ -62,6 +65,7 @@ class LocationEstimate:
# Path Loss Models
# =============================================================================
class PathLossModel:
"""
Convert RSSI to estimated distance using path loss models.
@@ -79,25 +83,22 @@ class PathLossModel:
# Default parameters for different environments
ENVIRONMENTS = {
'free_space': {'n': 2.0, 'rssi_ref': -40},
'outdoor': {'n': 2.5, 'rssi_ref': -45},
'indoor': {'n': 3.0, 'rssi_ref': -50},
'indoor_obstructed': {'n': 4.0, 'rssi_ref': -55},
"free_space": {"n": 2.0, "rssi_ref": -40},
"outdoor": {"n": 2.5, "rssi_ref": -45},
"indoor": {"n": 3.0, "rssi_ref": -50},
"indoor_obstructed": {"n": 4.0, "rssi_ref": -55},
}
# Frequency-specific reference RSSI adjustments (WiFi vs Bluetooth)
FREQUENCY_ADJUSTMENTS = {
2400: 0, # 2.4 GHz WiFi/Bluetooth - baseline
5000: -3, # 5 GHz WiFi - weaker propagation
900: +5, # 900 MHz ISM - better propagation
433: +8, # 433 MHz sensors - even better
2400: 0, # 2.4 GHz WiFi/Bluetooth - baseline
5000: -3, # 5 GHz WiFi - weaker propagation
900: +5, # 900 MHz ISM - better propagation
433: +8, # 433 MHz sensors - even better
}
def __init__(
self,
environment: str = 'outdoor',
path_loss_exponent: float | None = None,
reference_rssi: float | None = None
self, environment: str = "outdoor", path_loss_exponent: float | None = None, reference_rssi: float | None = None
):
"""
Initialize path loss model.
@@ -107,15 +108,11 @@ class PathLossModel:
path_loss_exponent: Override the environment's default n value
reference_rssi: Override the environment's default RSSI at 1m
"""
env_params = self.ENVIRONMENTS.get(environment, self.ENVIRONMENTS['outdoor'])
self.n = path_loss_exponent if path_loss_exponent is not None else env_params['n']
self.rssi_ref = reference_rssi if reference_rssi is not None else env_params['rssi_ref']
env_params = self.ENVIRONMENTS.get(environment, self.ENVIRONMENTS["outdoor"])
self.n = path_loss_exponent if path_loss_exponent is not None else env_params["n"]
self.rssi_ref = reference_rssi if reference_rssi is not None else env_params["rssi_ref"]
def rssi_to_distance(
self,
rssi: float,
frequency_mhz: float | None = None
) -> float:
def rssi_to_distance(self, rssi: float, frequency_mhz: float | None = None) -> float:
"""
Convert RSSI to estimated distance in meters.
@@ -146,11 +143,7 @@ class PathLossModel:
except (ValueError, OverflowError):
return 100.0 # Default fallback
def distance_to_rssi(
self,
distance: float,
frequency_mhz: float | None = None
) -> float:
def distance_to_rssi(self, distance: float, frequency_mhz: float | None = None) -> float:
"""
Estimate RSSI at a given distance (inverse of rssi_to_distance).
Useful for testing and validation.
@@ -174,6 +167,7 @@ class PathLossModel:
# Geographic Utilities
# =============================================================================
def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""
Calculate the great-circle distance between two points in meters.
@@ -187,8 +181,7 @@ def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl
delta_phi = math.radians(lat2 - lat1)
delta_lambda = math.radians(lon2 - lon1)
a = math.sin(delta_phi / 2) ** 2 + \
math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
a = math.sin(delta_phi / 2) ** 2 + math.cos(phi1) * math.cos(phi2) * math.sin(delta_lambda / 2) ** 2
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
return R * c
@@ -225,6 +218,7 @@ def offset_position(lat: float, lon: float, north_m: float, east_m: float) -> tu
# Trilateration Algorithm
# =============================================================================
class Trilateration:
"""
Estimate device location using multilateration from multiple RSSI observations.
@@ -240,7 +234,7 @@ class Trilateration:
path_loss_model: PathLossModel | None = None,
min_observations: int = 2,
max_iterations: int = 100,
convergence_threshold: float = 0.1 # meters
convergence_threshold: float = 0.1, # meters
):
"""
Initialize trilateration engine.
@@ -256,10 +250,7 @@ class Trilateration:
self.max_iterations = max_iterations
self.convergence_threshold = convergence_threshold
def estimate_location(
self,
observations: list[AgentObservation]
) -> LocationEstimate | None:
def estimate_location(self, observations: list[AgentObservation]) -> LocationEstimate | None:
"""
Estimate device location from multiple agent observations.
@@ -275,9 +266,12 @@ class Trilateration:
# Filter out observations with invalid coordinates
valid_obs = [
obs for obs in observations
if obs.agent_lat is not None and obs.agent_lon is not None
and -90 <= obs.agent_lat <= 90 and -180 <= obs.agent_lon <= 180
obs
for obs in observations
if obs.agent_lat is not None
and obs.agent_lon is not None
and -90 <= obs.agent_lat <= 90
and -180 <= obs.agent_lon <= 180
]
if len(valid_obs) < self.min_observations:
@@ -307,13 +301,10 @@ class Trilateration:
total_error = 0.0
for obs, expected_dist in zip(valid_obs, distances):
actual_dist = haversine_distance(
current_lat, current_lon,
obs.agent_lat, obs.agent_lon
)
actual_dist = haversine_distance(current_lat, current_lon, obs.agent_lat, obs.agent_lon)
error = actual_dist - expected_dist
total_error += error ** 2
total_error += error**2
if actual_dist > 0.1: # Avoid division by zero
# Gradient components
@@ -350,10 +341,7 @@ class Trilateration:
# Calculate accuracy estimate (average distance error)
total_error = 0.0
for obs, expected_dist in zip(valid_obs, distances):
actual_dist = haversine_distance(
current_lat, current_lon,
obs.agent_lat, obs.agent_lon
)
actual_dist = haversine_distance(current_lat, current_lon, obs.agent_lat, obs.agent_lon)
total_error += abs(actual_dist - expected_dist)
avg_error = total_error / len(valid_obs)
@@ -367,7 +355,7 @@ class Trilateration:
error_factor = max(0.0, 1.0 - avg_error / 500.0) # Decreases as error increases
rssi_factor = min(1.0, max(0.0, (max(obs.rssi for obs in valid_obs) + 90) / 50.0))
confidence = (obs_factor * 0.3 + error_factor * 0.5 + rssi_factor * 0.2)
confidence = obs_factor * 0.3 + error_factor * 0.5 + rssi_factor * 0.2
return LocationEstimate(
latitude=current_lat,
@@ -376,7 +364,7 @@ class Trilateration:
confidence=confidence,
num_observations=len(valid_obs),
observations=valid_obs,
method="multilateration"
method="multilateration",
)
@@ -384,6 +372,7 @@ class Trilateration:
# Device Location Tracker
# =============================================================================
class DeviceLocationTracker:
"""
Track device locations over time using observations from multiple agents.
@@ -396,7 +385,7 @@ class DeviceLocationTracker:
self,
trilateration: Trilateration | None = None,
observation_window_seconds: float = 60.0,
min_observations: int = 2
min_observations: int = 2,
):
"""
Initialize device tracker.
@@ -424,7 +413,7 @@ class DeviceLocationTracker:
agent_lon: float,
rssi: float,
frequency_mhz: float | None = None,
timestamp: datetime | None = None
timestamp: datetime | None = None,
) -> LocationEstimate | None:
"""
Add an observation and potentially update location estimate.
@@ -447,7 +436,7 @@ class DeviceLocationTracker:
agent_lon=agent_lon,
rssi=rssi,
frequency_mhz=frequency_mhz,
timestamp=timestamp or datetime.now(timezone.utc)
timestamp=timestamp or datetime.now(timezone.utc),
)
if device_id not in self.observations:
@@ -467,8 +456,7 @@ class DeviceLocationTracker:
cutoff = now.timestamp() - self.observation_window
self.observations[device_id] = [
obs for obs in self.observations[device_id]
if obs.timestamp.timestamp() > cutoff
obs for obs in self.observations[device_id] if obs.timestamp.timestamp() > cutoff
]
def _update_location(self, device_id: str) -> LocationEstimate | None:
@@ -501,12 +489,7 @@ class DeviceLocationTracker:
"""Get all current location estimates."""
return dict(self.locations)
def get_devices_near(
self,
lat: float,
lon: float,
radius_meters: float
) -> list[tuple[str, LocationEstimate]]:
def get_devices_near(self, lat: float, lon: float, radius_meters: float) -> list[tuple[str, LocationEstimate]]:
"""Find all tracked devices within radius of a point."""
results = []
for device_id, estimate in self.locations.items():
@@ -525,10 +508,8 @@ class DeviceLocationTracker:
# Convenience Functions
# =============================================================================
def estimate_location_from_observations(
observations: list[dict],
environment: str = 'outdoor'
) -> dict | None:
def estimate_location_from_observations(observations: list[dict], environment: str = "outdoor") -> dict | None:
"""
Convenience function to estimate location from a list of observation dicts.
@@ -555,17 +536,17 @@ def estimate_location_from_observations(
"""
obs_list = []
for obs in observations:
obs_list.append(AgentObservation(
agent_name=obs.get('agent_name', 'unknown'),
agent_lat=obs['agent_lat'],
agent_lon=obs['agent_lon'],
rssi=obs['rssi'],
frequency_mhz=obs.get('frequency_mhz')
))
obs_list.append(
AgentObservation(
agent_name=obs.get("agent_name", "unknown"),
agent_lat=obs["agent_lat"],
agent_lon=obs["agent_lon"],
rssi=obs["rssi"],
frequency_mhz=obs.get("frequency_mhz"),
)
)
trilat = Trilateration(
path_loss_model=PathLossModel(environment=environment)
)
trilat = Trilateration(path_loss_model=PathLossModel(environment=environment))
estimate = trilat.estimate_location(obs_list)
return estimate.to_dict() if estimate else None