"""Dataclass schema for full GUI config profiles.""" from __future__ import annotations from copy import deepcopy from dataclasses import dataclass, field import json from pathlib import Path from typing import Any from python_app.models.run_config_model import RunConfigModel @dataclass(slots=True) class GuiSwitchStateModel: """UI-only state for switch selection controls.""" combo_mode: str = "text" combos_text: str = "" single_input: str = "0" single_output: str = "0" @dataclass(slots=True) class GuiPassThroughStateModel: """UI-only defaults for pass-through rendering.""" show_magnitude: bool = True show_phase: bool = True fixed_y_enabled: bool = False y_min_db: float = -100.0 y_max_db: float = 0.0 @dataclass(slots=True) class GuiBscanStateModel: """UI-only defaults for B-scan live settings.""" axis: str = "abs" cut_m: float = 0.824 max_depth_m: float = 1.0 gain: float = 1.0 start_freq_mhz: float = 100.0 stop_freq_mhz: float = 8800.0 @dataclass(slots=True) class GuiGprStateModel: """UI-only defaults for GPR live settings.""" input_positions: str = "" output_positions: str = "" min_depth_m: float = 2.0 max_depth_m: float = 14.0 comp_power: float = 0.2 start_freq_mhz: float = 3000.0 stop_freq_mhz: float = 6000.0 background_subtract_enabled: bool = True background_mean_count: int = 10 @dataclass(slots=True) class GuiProcessingStateModel: """UI-only processing-section defaults.""" selected_mode: str = "pass_through" pass_through: GuiPassThroughStateModel = field(default_factory=GuiPassThroughStateModel) bscan: GuiBscanStateModel = field(default_factory=GuiBscanStateModel) gpr: GuiGprStateModel = field(default_factory=GuiGprStateModel) @dataclass(slots=True) class GuiDataActionsStateModel: """UI-only defaults for snapshot/export controls.""" save_count: int = 10 save_path: str = "" save_name: str = "snapshot_manual" @dataclass(slots=True) class GuiPreprocessDialogStateModel: """UI-only defaults for preprocessing dialog controls.""" set_name: str = "set_001" @dataclass(slots=True) class GuiStateModel: """GUI-only profile payload stored under top-level `gui`.""" version: int = 1 switches: GuiSwitchStateModel = field(default_factory=GuiSwitchStateModel) processing: GuiProcessingStateModel = field(default_factory=GuiProcessingStateModel) data_actions: GuiDataActionsStateModel = field(default_factory=GuiDataActionsStateModel) preprocess_dialog: GuiPreprocessDialogStateModel = field(default_factory=GuiPreprocessDialogStateModel) @dataclass(slots=True) class GuiProfileModel: """Full GUI profile with pipeline-compatible root config and optional UI state.""" run_config: RunConfigModel = field(default_factory=RunConfigModel) gui: GuiStateModel | None = None @staticmethod def from_dict(payload: dict[str, Any]) -> GuiProfileModel: """Build profile from JSON-like payload using codec layer.""" from python_app.models.gui_profile_codec import gui_profile_from_dict return gui_profile_from_dict(payload) @classmethod def load_from_path(cls, path: Path) -> GuiProfileModel: """Load JSON file from disk and decode into profile model.""" payload = json.loads(path.read_text(encoding="utf-8")) if not isinstance(payload, dict): raise ValueError(f"Config profile root must be JSON object: {path}") return cls.from_dict(payload) def clone(self) -> GuiProfileModel: """Create deep copy while preserving whether `gui` is absent.""" return deepcopy(self) def to_dict(self) -> dict[str, Any]: """Encode profile into JSON-serializable dictionary.""" from python_app.models.gui_profile_codec import gui_profile_to_dict return gui_profile_to_dict(self)