""" Statistics dashboard screen for the AUTOPARTES console application. Displays database table counts and coverage metrics retrieved via :meth:`Database.get_stats`. """ from console.core.screens import Screen from console.core.keybindings import Key from console.config import APP_NAME, VERSION from console.utils.formatting import format_number # Human-readable labels for each database table counter. _TABLE_LABELS = [ ("brands", "Marcas"), ("models", "Modelos"), ("engines", "Motores"), ("years", "Anos"), ("part_categories", "Categorias"), ("part_groups", "Grupos de Partes"), ("parts", "Partes OEM"), ("aftermarket_parts", "Partes Aftermarket"), ("manufacturers", "Fabricantes"), ("part_cross_references","Cross-References"), ] # Footer key labels _FOOTER = [ ("F5", "Refrescar"), ("F10", "Menu"), ("ESC", "Atras"), ] class EstadisticasScreen(Screen): """Read-only statistics dashboard showing database counters.""" def __init__(self): super().__init__(name="estadisticas", title="Estadisticas del Sistema") self._stats = None # ------------------------------------------------------------------ # Helpers # ------------------------------------------------------------------ def _load_stats(self, db): """Fetch fresh statistics from the database.""" try: self._stats = db.get_stats() except Exception: self._stats = None def _build_fields(self): """Build the detail fields list from the cached stats dict.""" if self._stats is None: return [("Error", "No se pudieron cargar las estadisticas")] fields = [] # -- Section: BASE DE DATOS -- for key, label in _TABLE_LABELS: value = self._stats.get(key, 0) fields.append((label, format_number(value))) return fields def _build_coverage_fields(self): """Build coverage / summary fields.""" if self._stats is None: return [] fields = [] # Vehicle-part fitments fitments = self._stats.get("vehicle_parts", 0) fields.append(("Fitments", format_number(fitments))) # Top brands by fitment count top_brands = self._stats.get("top_brands", []) if top_brands: parts = [] for b in top_brands[:5]: parts.append(f"{b['name']}({format_number(b['count'])})") fields.append(("Top marcas", " ".join(parts))) return fields # ------------------------------------------------------------------ # Screen interface # ------------------------------------------------------------------ def render(self, context, db, renderer): # Load stats on first render (or after refresh) if self._stats is None: self._load_stats(db) # Header renderer.draw_header( f" {APP_NAME} v{VERSION}", " Estadisticas ", ) h, w = renderer.get_size() # -- Section title: BASE DE DATOS -- section_title = " BASE DE DATOS " border_char = "\u2500" # ─ pad_len = max(w - 4 - len(section_title), 0) section_line = border_char * 2 + section_title + border_char * pad_len renderer.draw_text(3, 2, section_line[:w - 4], "title") # Database counters db_fields = self._build_fields() max_label = max((len(lbl) for lbl, _ in db_fields), default=10) dot_total = max_label + 4 row = 5 for label, value in db_fields: if row >= h - 6: break dots = "." * (dot_total - len(label)) label_part = f" {label}{dots}: " renderer.draw_text(row, 0, label_part, "field_label") renderer.draw_text(row, len(label_part), str(value), "field_value") row += 1 # -- Section title: COBERTURA -- row += 1 if row < h - 5: section_title2 = " COBERTURA " pad_len2 = max(w - 4 - len(section_title2), 0) section_line2 = border_char * 2 + section_title2 + border_char * pad_len2 renderer.draw_text(row, 2, section_line2[:w - 4], "title") row += 2 coverage_fields = self._build_coverage_fields() cov_max_label = max( (len(lbl) for lbl, _ in coverage_fields), default=10 ) cov_dot_total = cov_max_label + 4 for label, value in coverage_fields: if row >= h - 3: break dots = "." * (cov_dot_total - len(label)) label_part = f" {label}{dots}: " renderer.draw_text(row, 0, label_part, "field_label") renderer.draw_text( row, len(label_part), str(value), "field_value" ) row += 1 # Footer renderer.draw_footer(_FOOTER) def on_key(self, key, context, db, renderer, nav): # F5: refresh stats if key == Key.F5: self._stats = None # will reload on next render return None # ESC or Backspace: go back if key in (Key.ESCAPE, Key.BACKSPACE): return "back" return None