Files
radar_system/python_app/workflows/radar_config_variants.py
T
2026-10-06 12:32:26 +03:00

153 lines
5.0 KiB
Python

"""Helpers for loading radar sweep variants from a directory of JSON files."""
from __future__ import annotations
from dataclasses import dataclass
import json
import logging
from pathlib import Path
from python_app.models.run_config_schema import RunConfigModel
logger = logging.getLogger(__name__)
_RADAR_SWEEP_KEYS = (
"start_hz",
"stop_hz",
"points",
"if_bandwidth_hz",
"stimulus_power_dbm",
)
@dataclass(frozen=True, slots=True)
class RadarConfigVariant:
"""One capture-ready radar sweep variant loaded from JSON."""
display_name: str
config: RunConfigModel
radar_key: str
@dataclass(frozen=True, slots=True)
class RadarConfigScanSummary:
"""Scan results for one radar-config directory refresh."""
directory_path: str
json_file_count: int
valid_variant_count: int
skipped_file_count: int
duplicate_variant_count: int
issues: tuple[str, ...]
def scan_radar_config_variants(
directory_path: str,
*,
base_config: RunConfigModel,
) -> tuple[list[RadarConfigVariant], RadarConfigScanSummary]:
"""Load valid radar sweep variants from `directory_path` using `base_config` as the baseline."""
normalized_path = str(directory_path).strip()
if not normalized_path:
return [], RadarConfigScanSummary(
directory_path="",
json_file_count=0,
valid_variant_count=0,
skipped_file_count=0,
duplicate_variant_count=0,
issues=(),
)
directory = Path(normalized_path).expanduser()
if not directory.exists():
logger.warning("Radar config variant directory does not exist: %s", directory)
return [], RadarConfigScanSummary(
directory_path=str(directory),
json_file_count=0,
valid_variant_count=0,
skipped_file_count=0,
duplicate_variant_count=0,
issues=(f"Directory does not exist: {directory}",),
)
if not directory.is_dir():
logger.warning("Radar config variant path is not a directory: %s", directory)
return [], RadarConfigScanSummary(
directory_path=str(directory),
json_file_count=0,
valid_variant_count=0,
skipped_file_count=0,
duplicate_variant_count=0,
issues=(f"Path is not a directory: {directory}",),
)
variants: list[RadarConfigVariant] = []
issues: list[str] = []
duplicate_variant_count = 0
seen_radar_keys: set[str] = set()
json_paths = sorted(path for path in directory.glob("*.json") if path.is_file())
for path in json_paths:
try:
variant = _load_radar_config_variant(path, base_config=base_config)
except Exception as exc: # noqa: BLE001
issues.append(f"{path.name}: {type(exc).__name__}: {exc}")
logger.warning("Skipping radar config variant %s: %s: %s", path.name, type(exc).__name__, exc)
continue
if variant.radar_key in seen_radar_keys:
duplicate_variant_count += 1
issues.append(
f"{path.name}: duplicate radar variant key {variant.radar_key}; keeping the first matching file only"
)
logger.warning(
"Skipping duplicate radar config variant %s (radar_key=%s already seen)",
path.name,
variant.radar_key,
)
continue
seen_radar_keys.add(variant.radar_key)
variants.append(variant)
logger.info(
"Scanned radar config variants in %s: json=%d valid=%d duplicates=%d",
directory,
len(json_paths),
len(variants),
duplicate_variant_count,
)
return variants, RadarConfigScanSummary(
directory_path=str(directory),
json_file_count=len(json_paths),
valid_variant_count=len(variants),
skipped_file_count=max(0, len(json_paths) - len(variants) - duplicate_variant_count),
duplicate_variant_count=duplicate_variant_count,
issues=tuple(issues),
)
def _load_radar_config_variant(path: Path, *, base_config: RunConfigModel) -> RadarConfigVariant:
"""Load one radar sweep variant by overlaying JSON `radar.sweep` onto `base_config`."""
payload = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(payload, dict):
raise ValueError("JSON root must be an object")
radar_payload = payload.get("radar")
if not isinstance(radar_payload, dict):
raise ValueError("Missing radar section")
sweep_payload = radar_payload.get("sweep")
if not isinstance(sweep_payload, dict):
raise ValueError("Missing radar.sweep section")
if not any(key in sweep_payload for key in _RADAR_SWEEP_KEYS):
raise ValueError("radar.sweep does not contain any supported sweep keys")
config_payload = base_config.to_dict()
config_payload["radar"]["sweep"].update(sweep_payload)
config = RunConfigModel.from_dict(config_payload)
return RadarConfigVariant(
display_name=path.stem,
config=config,
radar_key=config.radar_key(),
)