Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 15 additions & 15 deletions ardupilot_methodic_configurator/frontend_tkinter_log_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class Severity(Enum):
}


def _collect_links(quality_dict: dict[str, Any] | None, analysis_dict: dict[str, Any] | None) -> list[dict[str, Any]]:
def _collect_links(availability_dict: dict[str, Any] | None, analysis_dict: dict[str, Any] | None) -> list[dict[str, Any]]:
seen: set[tuple[str | None, str | None]] = set()
links: list[dict[str, Any]] = []

Expand All @@ -70,8 +70,8 @@ def _add(step_info: dict[str, Any] | None) -> None:
seen.add(key)
links.append(step_info)

if quality_dict:
for issue in quality_dict.get("issues", []):
if availability_dict:
for issue in availability_dict.get("issues", []):
_add(issue.get("step_info"))
if analysis_dict:
for outcome in analysis_dict.get("outcomes", []):
Expand Down Expand Up @@ -135,14 +135,14 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
self.upload_callback = upload_callback
self._ai_panel_visible = False

self.pairs = summary.paired_quality_and_analysis_results()
self.pairs = summary.paired_availability_and_analysis_results()
self.subsystem_names = [q.name for q, _a in self.pairs]

self._report_quality_by_name: dict[str, dict[str, Any]] = {}
self._report_availability_by_name: dict[str, dict[str, Any]] = {}
self._report_analysis_by_name: dict[str, dict[str, Any]] = {}
if report is not None:
for entry in report.get("data_quality", []):
self._report_quality_by_name[entry.get("name", "")] = entry
for entry in report.get("data_availability", []):
self._report_availability_by_name[entry.get("name", "")] = entry
for entry in report.get("analysis", []):
name = entry.get("name", "")
self._report_analysis_by_name[name.removesuffix(" Analysis")] = entry
Expand Down Expand Up @@ -238,13 +238,13 @@ def _render_subsystem(self, name: str) -> None: # pylint: disable=too-many-loca
matching = [(q, a) for q, a in self.pairs if q.name == name]
if not matching:
return
quality_result, analysis_result = matching[0]
availability_result, analysis_result = matching[0]

quality_dict = self._report_quality_by_name.get(name)
availability_dict = self._report_availability_by_name.get(name)
analysis_dict = self._report_analysis_by_name.get(name)

self._section_heading(_("Links"))
links = _collect_links(quality_dict, analysis_dict)
links = _collect_links(availability_dict, analysis_dict)
if not links:
self._section_body(_("No linked documentation for this subsystem."))
else:
Expand All @@ -255,7 +255,7 @@ def _render_subsystem(self, name: str) -> None: # pylint: disable=too-many-loca
self._section_link(_("Guide"), link.get("blog_text") or link["blog_url"], link["blog_url"])

vehicle_components = (self.report or {}).get("vehicle_components") or {}
component_keys = self.summary.component_keys_for_subsystem(quality_result.subsystem_key)
component_keys = self.summary.component_keys_for_subsystem(availability_result.subsystem_key)
hardware_lines: list[tuple[str, list[str]]] = []
for key in component_keys:
component = vehicle_components.get(key)
Expand All @@ -271,14 +271,14 @@ def _render_subsystem(self, name: str) -> None: # pylint: disable=too-many-loca
for line in lines:
self._bullet_line(line)

self._section_heading(_("Quality"))
self._section_body(quality_result.reason)
for issue in quality_result.issues:
self._section_heading(_("Availability"))
self._section_body(availability_result.reason)
for issue in availability_result.issues:
self._bullet_line(issue.message)

self._section_heading(_("Analysis"))
if analysis_result is None:
self._section_body(_("Not yet analyzed - {reason}").format(reason=quality_result.reason))
self._section_body(_("Not yet analyzed - {reason}").format(reason=availability_result.reason))
elif not analysis_result.outcomes:
self._section_body(_("No findings."))
else:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Log quality report window for the ArduPilot Methodic Configurator.
Log availability report window for the ArduPilot Methodic Configurator.

Displays a parsed ArduPilot .bin log analysis.

Expand All @@ -24,14 +24,14 @@
from ardupilot_methodic_configurator.formatting import format_filesize
from ardupilot_methodic_configurator.frontend_tkinter_base_window import BaseWindow
from ardupilot_methodic_configurator.frontend_tkinter_log_analysis import LogAnalysisReportWindow
from ardupilot_methodic_configurator.frontend_tkinter_log_hardware_quality import build_hardware_tab
from ardupilot_methodic_configurator.frontend_tkinter_log_hardware_availability import build_hardware_tab
from ardupilot_methodic_configurator.frontend_tkinter_scroll_frame import ScrollFrame
from ardupilot_methodic_configurator.frontend_tkinter_show import show_tooltip
from ardupilot_methodic_configurator.log_analysis.data_model_log_analysis import LogSummary
from ardupilot_methodic_configurator.log_analysis.data_model_log_quality import (
LogQualityResult,
LogQualityState,
QualityIssue,
from ardupilot_methodic_configurator.log_analysis.data_model_log_availability import (
AvailabilityIssue,
LogAvailabilityResult,
LogAvailabilityState,
StepValidationResult,
)
from ardupilot_methodic_configurator.log_analysis.data_model_log_report import (
Expand All @@ -47,7 +47,7 @@ def _format_parameter_value(value: float) -> str:
return str(int(value)) if value.is_integer() else str(value)


class LogQualityReportWindow(BaseWindow): # pylint: disable=too-many-instance-attributes
class LogAvailabilityReportWindow(BaseWindow): # pylint: disable=too-many-instance-attributes
"""Displays log analysis results as a beginner-friendly, detailed dashboard."""

# pylint: disable=duplicate-code
Expand All @@ -68,7 +68,7 @@ def __init__( # pylint: disable=too-many-arguments, too-many-positional-argumen
self.upload_callback = upload_callback
self.navigate_callback = navigate_callback
self._parent_root = root_tk
self.root.title(_("Log Quality Report"))
self.root.title(_("Log Availability Report"))
self.root.geometry(self.calculate_scaled_geometry(1000, 750))
self.center_window(self.root, root_tk)
self.root.resizable(width=True, height=True)
Expand Down Expand Up @@ -147,8 +147,8 @@ def _build_footer(self) -> None:

def _on_continue_to_analysis(self) -> None:
pending_names = [
quality_result.name
for quality_result, analysis_result in self.summary.paired_quality_and_analysis_results()
availability_result.name
for availability_result, analysis_result in self.summary.paired_availability_and_analysis_results()
if analysis_result is None
]
if pending_names:
Expand Down Expand Up @@ -223,7 +223,7 @@ def _apply_param_fixes(self, fixes: list[tuple[str, float, float, list[str]]], d
dialog.destroy()

@staticmethod
def _first_config_step(issues: list[QualityIssue]) -> str:
def _first_config_step(issues: list[AvailabilityIssue]) -> str:
for issue in issues:
if issue.config_step:
return issue.config_step
Expand All @@ -236,15 +236,15 @@ def _navigate_to_step(self, step: str) -> None:
self._parent_root.lift()
self._parent_root.focus_force()

def _fixes_for_issues(self, issues: list[QualityIssue]) -> list[tuple[str, float, float, list[str]]]:
def _fixes_for_issues(self, issues: list[AvailabilityIssue]) -> list[tuple[str, float, float, list[str]]]:
"""
Compute proposed parameter changes for a specific set of issues.

Returns (param_name, current_value, proposed_value, reasons) tuples.
LOG_BITMASK entries within the given issues are OR-merged; every other
parameter takes its first suggested value.
"""
by_param: dict[str, list[QualityIssue]] = {}
by_param: dict[str, list[AvailabilityIssue]] = {}
for issue in issues:
if issue.param_name is not None and issue.suggested_value is not None:
by_param.setdefault(issue.param_name, []).append(issue)
Expand Down Expand Up @@ -326,22 +326,22 @@ def _build_tabs(self) -> None:
notebook = ttk.Notebook(self.main_frame)
notebook.pack(side=tk.TOP, fill=tk.BOTH, expand=True, padx=12, pady=(12, 12))

quality_frame = ttk.Frame(notebook)
notebook.add(quality_frame, text=_(" Quality Report "))
self._build_quality_tab(quality_frame)
availability_frame = ttk.Frame(notebook)
notebook.add(availability_frame, text=_(" Data Availability Report "))
self._build_availability_tab(availability_frame)

hardware_frame = ttk.Frame(notebook)
notebook.add(hardware_frame, text=_(" Hardware Overview "))
build_hardware_tab(hardware_frame, self.summary.hardware_report)

def _build_quality_tab(self, parent: ttk.Frame) -> None: # pylint: disable=too-many-branches
def _build_availability_tab(self, parent: ttk.Frame) -> None: # pylint: disable=too-many-branches
scroll_container = ScrollFrame(parent)
scroll_container.pack(fill=tk.BOTH, expand=True)
inner = scroll_container.view_port

absorbed_by_step: dict[str, list[StepValidationResult]] = {}
for step_result in self.summary.step_results:
for q in self.summary.quality_results:
for q in self.summary.availability_results:
if q.related_step and q.related_step == step_result.step:
absorbed_by_step.setdefault(q.related_step, []).append(step_result)
break
Expand All @@ -351,8 +351,8 @@ def _build_quality_tab(self, parent: ttk.Frame) -> None: # pylint: disable=too-
needs_attention: list[tuple[str, object]] = []
passed_checks: list[tuple[str, object]] = []

for q in self.summary.quality_results:
(passed_checks if q.state == LogQualityState.INFO else needs_attention).append(("quality", q))
for q in self.summary.availability_results:
(passed_checks if q.state == LogAvailabilityState.INFO else needs_attention).append(("availability", q))
for s in self.summary.step_results:
if s.step in absorbed_steps:
continue
Expand All @@ -363,10 +363,10 @@ def _build_quality_tab(self, parent: ttk.Frame) -> None: # pylint: disable=too-
anchor=tk.W, padx=14, pady=(18, 6)
)
for kind, item in needs_attention:
if kind == "quality":
quality_item = cast("LogQualityResult", item)
quality_absorbed_steps = absorbed_by_step.get(quality_item.related_step, [])
self._quality_result_card(inner, quality_item, quality_absorbed_steps)
if kind == "availability":
availability_item = cast("LogAvailabilityResult", item)
availability_absorbed_steps = absorbed_by_step.get(availability_item.related_step, [])
self._availability_result_card(inner, availability_item, availability_absorbed_steps)
else:
self._step_result_card(inner, item) # type: ignore[arg-type]
ttk.Separator(inner, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=14, pady=(14, 14))
Expand All @@ -376,15 +376,15 @@ def _build_quality_tab(self, parent: ttk.Frame) -> None: # pylint: disable=too-
anchor=tk.W, padx=14, pady=(10, 6)
)
for kind, item in passed_checks:
if kind == "quality":
quality_item = cast("LogQualityResult", item)
quality_absorbed_steps = absorbed_by_step.get(quality_item.related_step, [])
self._quality_result_card(inner, quality_item, quality_absorbed_steps)
if kind == "availability":
availability_item = cast("LogAvailabilityResult", item)
availability_absorbed_steps = absorbed_by_step.get(availability_item.related_step, [])
self._availability_result_card(inner, availability_item, availability_absorbed_steps)
else:
self._step_result_card(inner, item) # type: ignore[arg-type]

def _quality_result_card(
self, parent: ttk.Frame, result: LogQualityResult, absorbed_steps: list[StepValidationResult]
def _availability_result_card(
self, parent: ttk.Frame, result: LogAvailabilityResult, absorbed_steps: list[StepValidationResult]
) -> None:
card = ttk.Frame(parent)
card.pack(fill=tk.X, padx=14, pady=6)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
from ardupilot_methodic_configurator.frontend_tkinter_directory_selection import VehicleDirectorySelectionWidgets
from ardupilot_methodic_configurator.frontend_tkinter_fc_banner_window import FlightControllerBannerWindow
from ardupilot_methodic_configurator.frontend_tkinter_font import get_safe_font_config
from ardupilot_methodic_configurator.frontend_tkinter_log_quality import LogQualityReportWindow
from ardupilot_methodic_configurator.frontend_tkinter_log_availability import LogAvailabilityReportWindow
from ardupilot_methodic_configurator.frontend_tkinter_parameter_compare_and_upload import ParameterFileUploadWindow
from ardupilot_methodic_configurator.frontend_tkinter_parameter_editor_documentation_frame import DocumentationFrame
from ardupilot_methodic_configurator.frontend_tkinter_parameter_editor_table import ParameterEditorTable
Expand Down Expand Up @@ -284,7 +284,7 @@ def __init__(
self._tempcal_imu_progress_window: ProgressWindow | None = None
self.file_upload_progress_window: ProgressWindow | None = None
self._param_download_progress_window: ProgressWindow | None = None
self._log_quality_report_window: LogQualityReportWindow | None = None
self._log_availability_report_window: LogAvailabilityReportWindow | None = None
self._log_report_return_pending: bool = False
self.inline_component_editor: ComponentEditorWindow | None = None
self._inline_component_name: str | None = None
Expand Down Expand Up @@ -587,7 +587,7 @@ def _create_parameter_area_widgets(self) -> None:
command=self.on_analyse_log_click,
)
analyse_log_button.pack(side=tk.LEFT, padx=(8, 8))
show_tooltip(analyse_log_button, _("Open a .bin flight log and analyse its quality"))
show_tooltip(analyse_log_button, _("Open a .bin flight log and analyse its availability"))

# Create Zip file for forum button
zip_vehicle_for_forum_button = ttk.Button(
Expand Down Expand Up @@ -798,7 +798,7 @@ def check_done() -> None:
self.ui.show_error(_("Log Analysis Error"), str(e))
return

report_window = LogQualityReportWindow(
report_window = LogAvailabilityReportWindow(
self.root,
summary,
self.parameter_editor.get_vehicle_directory(),
Expand All @@ -807,20 +807,20 @@ def check_done() -> None:
navigate_callback=self._navigate_to_config_step,
report=report,
)
self._log_quality_report_window = report_window
self._log_availability_report_window = report_window

if isinstance(self.root, tk.Tk) and UsagePopupWindow.should_display("log_quality_report"):
display_log_quality_report_usage_popup(report_window.root)
if isinstance(self.root, tk.Tk) and UsagePopupWindow.should_display("log_availability_report"):
display_log_availability_report_usage_popup(report_window.root)

thread = threading.Thread(target=run_extraction, daemon=True)
thread.start()
self.root.after(100, check_done)

def display_log_quality_report_usage_popup(parent: tk.Tk | tk.Toplevel) -> None:
def display_log_availability_report_usage_popup(parent: tk.Tk | tk.Toplevel) -> None:
usage_popup_window = BaseWindow(parent)
usage_popup_window.root.withdraw()
instructions_text = RichText(usage_popup_window.main_frame, height=12, width=80)
instructions_text.insert(tk.END, _("Log Quality Report\n\n"), "title")
instructions_text.insert(tk.END, _("Log availability Report\n\n"), "title")
instructions_text.insert(
tk.END,
_(
Expand All @@ -836,8 +836,8 @@ def display_log_quality_report_usage_popup(parent: tk.Tk | tk.Toplevel) -> None:
UsagePopupWindow.display(
cast("tk.Tk", parent),
usage_popup_window,
_("Log Quality Report"),
"log_quality_report",
_("Log availability Report"),
"log_availability_report",
"520x320",
instructions_text,
)
Expand Down Expand Up @@ -1511,13 +1511,13 @@ def _continue_to_analyse(self) -> None:
if not self._log_report_return_pending:
return
self._log_report_return_pending = False
if self._log_quality_report_window is not None and self.ui.ask_yesno(
_("Continue Log Quality Review"),
_("Parameters uploaded. Return to the log quality report to continue?"),
if self._log_availability_report_window is not None and self.ui.ask_yesno(
_("Continue Log availability Review"),
_("Parameters uploaded. Return to the log availability report to continue?"),
):
self._log_quality_report_window.root.deiconify()
self._log_quality_report_window.root.lift()
self._log_quality_report_window.root.focus_force()
self._log_availability_report_window.root.deiconify()
self._log_availability_report_window.root.lift()
self._log_availability_report_window.root.focus_force()

# This function can recurse multiple times if there is an upload error

Expand Down
Loading
Loading