diff --git a/atlasbot/snapshot/builder.py b/atlasbot/snapshot/builder.py index 721f4a9..92a5999 100644 --- a/atlasbot/snapshot/builder.py +++ b/atlasbot/snapshot/builder.py @@ -103,9 +103,25 @@ def build_summary(snapshot: dict[str, Any] | None) -> dict[str, Any]: summary.update(_build_cluster_watchlist(summary)) summary.update(_build_workloads(snapshot)) summary.update(_build_flux(snapshot)) + _merge_cluster_summary(snapshot, summary) return summary +def _merge_cluster_summary(snapshot: dict[str, Any], summary: dict[str, Any]) -> None: + cluster_summary = snapshot.get("summary") if isinstance(snapshot.get("summary"), dict) else {} + if not cluster_summary: + return + signals = cluster_summary.get("signals") + profiles = cluster_summary.get("profiles") + inventory = cluster_summary.get("inventory") + if isinstance(signals, list): + summary["signals"] = signals + if isinstance(profiles, dict): + summary["profiles"] = profiles + if isinstance(inventory, dict): + summary["inventory"] = inventory + + def _nodes_detail(snapshot: dict[str, Any]) -> list[dict[str, Any]]: items = snapshot.get("nodes_detail") return items if isinstance(items, list) else [] @@ -1335,6 +1351,64 @@ def _append_flux(lines: list[str], summary: dict[str, Any]) -> None: lines.append("flux_not_ready_items: " + "; ".join(parts)) +def _append_signals(lines: list[str], summary: dict[str, Any]) -> None: + signals = summary.get("signals") if isinstance(summary.get("signals"), list) else [] + if not signals: + return + lines.append("signals:") + for entry in signals[:8]: + if not isinstance(entry, dict): + continue + scope = entry.get("scope") or "" + target = entry.get("target") or "" + metric = entry.get("metric") or "" + current = entry.get("current") + delta = entry.get("delta_pct") + severity = entry.get("severity") or "" + detail = f"{scope}:{target} {metric}={current}" + if delta is not None: + detail += f" delta={delta}%" + if severity: + detail += f" severity={severity}" + lines.append(f"- {detail}") + + +def _append_profiles(lines: list[str], summary: dict[str, Any]) -> None: + profiles = summary.get("profiles") if isinstance(summary.get("profiles"), dict) else {} + if not profiles: + return + nodes = profiles.get("nodes") if isinstance(profiles.get("nodes"), list) else [] + namespaces = profiles.get("namespaces") if isinstance(profiles.get("namespaces"), list) else [] + workloads = profiles.get("workloads") if isinstance(profiles.get("workloads"), list) else [] + if nodes: + lines.append("node_profiles:") + for entry in nodes[:3]: + if not isinstance(entry, dict): + continue + lines.append( + f"- {entry.get('node')}: load={entry.get('load_index')} cpu={entry.get('cpu')} ram={entry.get('ram')} " + f"pods={entry.get('pods_total')} hw={entry.get('hardware')}" + ) + if namespaces: + lines.append("namespace_profiles:") + for entry in namespaces[:3]: + if not isinstance(entry, dict): + continue + lines.append( + f"- {entry.get('namespace')}: pods={entry.get('pods_total')} cpu={entry.get('cpu_usage')} " + f"mem={entry.get('mem_usage')} primary={entry.get('primary_node')}" + ) + if workloads: + lines.append("workload_profiles:") + for entry in workloads[:3]: + if not isinstance(entry, dict): + continue + lines.append( + f"- {entry.get('namespace')}/{entry.get('workload')}: pods={entry.get('pods_total')} " + f"running={entry.get('pods_running')} node={entry.get('primary_node')}" + ) + + def _append_units_windows(lines: list[str], summary: dict[str, Any]) -> None: metrics = summary.get("metrics") if isinstance(summary.get("metrics"), dict) else {} units = metrics.get("units") if isinstance(metrics.get("units"), dict) else {} @@ -1587,5 +1661,7 @@ def summary_text(snapshot: dict[str, Any] | None) -> str: _append_cluster_watchlist(lines, summary) _append_hardware_usage(lines, summary) _append_flux(lines, summary) + _append_signals(lines, summary) + _append_profiles(lines, summary) _append_units_windows(lines, summary) return "\n".join(lines)