From 76eac2d6a423f7a8e817cf60904e55da7168a723 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Mon, 2 Feb 2026 17:07:26 -0300 Subject: [PATCH] fix: use summary data for workload/pod/pvc overrides --- atlasbot/engine/answerer.py | 114 ++++++++++++++++++++---------------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/atlasbot/engine/answerer.py b/atlasbot/engine/answerer.py index 8062aa2..41b2b55 100644 --- a/atlasbot/engine/answerer.py +++ b/atlasbot/engine/answerer.py @@ -690,80 +690,96 @@ class AnswerEngine: if pods_line: reply = f"From the latest snapshot: {pods_line}." if "load_index" in lowered_q or "load index" in lowered_q: - top = None if isinstance(snapshot_used, dict): summary = snapshot_used.get("summary") if isinstance(snapshot_used.get("summary"), dict) else {} - if summary: - top_list = summary.get("top", {}).get("node_load") if isinstance(summary.get("top"), dict) else None - if isinstance(top_list, list) and top_list: - top = top_list[0] - if isinstance(top, dict): - node = top.get("node") - load = top.get("load_index") - if node and load is not None: - reply = f"From the latest snapshot: hottest_load_index_node: {node} load_index={load}." + node_load = summary.get("node_load") if isinstance(summary.get("node_load"), list) else [] + best = None + for item in node_load: + if not isinstance(item, dict): + continue + node = item.get("node") + load = item.get("load_index") + try: + numeric = float(load) + except (TypeError, ValueError): + continue + if best is None or numeric > best["load_index"]: + best = {"node": node, "load_index": numeric} + if best and best.get("node") is not None: + reply = ( + "From the latest snapshot: hottest_load_index_node: " + f"{best['node']} load_index={best['load_index']}." + ) if "workload" in lowered_q and any(tok in lowered_q for tok in ("not ready", "not-ready", "unready")): - top = None if isinstance(snapshot_used, dict): summary = snapshot_used.get("summary") if isinstance(snapshot_used.get("summary"), dict) else {} - if summary: - top = summary.get("top", {}).get("workload_not_ready") if isinstance(summary.get("top"), dict) else None - if isinstance(top, list): - if top: - entries = [] - for item in top[:3]: - if isinstance(item, dict): - entries.append( - f"{item.get('namespace','?')}/{item.get('name','?')} {item.get('kind','?')} ready={item.get('ready')} desired={item.get('desired')}" - ) + health = summary.get("workloads_health") if isinstance(summary.get("workloads_health"), dict) else {} + items: list[dict[str, Any]] = [] + for key in ("deployments", "statefulsets", "daemonsets"): + entry = health.get(key) if isinstance(health.get(key), dict) else {} + for item in entry.get("items") or []: + if not isinstance(item, dict): + continue + items.append( + { + "kind": key[:-1], + "namespace": item.get("namespace") or "", + "name": item.get("name") or "", + "desired": item.get("desired"), + "ready": item.get("ready"), + } + ) + if items: + items = sorted(items, key=lambda item: (item.get("namespace") or "", item.get("name") or "")) + entries = [ + f"{item.get('namespace','?')}/{item.get('name','?')} {item.get('kind','?')} ready={item.get('ready')} desired={item.get('desired')}" + for item in items[:3] + ] if entries: reply = f"From the latest snapshot: workloads_not_ready: {', '.join(entries)}." else: reply = "From the latest snapshot: workloads_not_ready: none." if "pod" in lowered_q and ("waiting" in lowered_q or "wait" in lowered_q) and "reason" in lowered_q: - waiting = None if isinstance(snapshot_used, dict): summary = snapshot_used.get("summary") if isinstance(snapshot_used.get("summary"), dict) else {} - if summary: - waiting = summary.get("pod_reason_totals", {}).get("waiting") if isinstance(summary.get("pod_reason_totals"), dict) else None - if isinstance(waiting, dict): - items = [] - for reason, window in waiting.items(): - if not isinstance(window, dict): - continue - # prefer 1h max if available, else 6h, else 24h - val = None - for key in ("1h", "6h", "24h"): - entry = window.get(key) - if isinstance(entry, dict): - val = entry.get("max") - if val is None: - val = entry.get("avg") - if val: - items.append(f"{reason}={val}") - break - if items: + pod_issues = summary.get("pod_issues") if isinstance(summary.get("pod_issues"), dict) else {} + waiting = pod_issues.get("waiting_reasons") if isinstance(pod_issues.get("waiting_reasons"), dict) else {} + if waiting: + top = sorted(waiting.items(), key=lambda item: (-item[1], item[0]))[:3] + items = [f"{reason}={count}" for reason, count in top] reply = f"From the latest snapshot: pod_waiting_reasons: {'; '.join(items)}." else: reply = "From the latest snapshot: pod_waiting_reasons: none." - if "pvc" in lowered_q and any(tok in lowered_q for tok in ("usage", "used", "percent", "80")): - top = None + if "pvc" in lowered_q and any(tok in lowered_q for tok in ("usage", "used", "percent", "80", "full")): if isinstance(snapshot_used, dict): summary = snapshot_used.get("summary") if isinstance(snapshot_used.get("summary"), dict) else {} - if summary: - top = summary.get("top", {}).get("pvc_usage") if isinstance(summary.get("top"), dict) else None - if isinstance(top, list): + pvc_usage = summary.get("pvc_usage_top") if isinstance(summary.get("pvc_usage_top"), list) else [] over = [] - for item in top: + for item in pvc_usage: if not isinstance(item, dict): continue used = item.get("used_percent") - if used is not None and used >= 80: - over.append(f"{item.get('namespace','?')}/{item.get('pvc','?')}={used:.2f}%") + if used is None: + continue + try: + used_val = float(used) + except (TypeError, ValueError): + continue + if used_val >= 80: + name = item.get("name") or item.get("pvc") + if name: + over.append(f"{name}={used_val:.2f}%") if over: reply = f"From the latest snapshot: pvc_usage>=80%: {', '.join(over)}." else: reply = "From the latest snapshot: pvc_usage>=80%: none." + if "pressure" in lowered_q: + if isinstance(snapshot_used, dict): + summary = snapshot_used.get("summary") if isinstance(snapshot_used.get("summary"), dict) else {} + pressure_nodes = summary.get("pressure_nodes") if isinstance(summary.get("pressure_nodes"), dict) else {} + entries = [f"{key}={value}" for key, value in pressure_nodes.items() if value] + if entries: + reply = f"From the latest snapshot: pressure_nodes: {', '.join(entries)}." if _has_token(lowered_q, "io") and ("node" in lowered_q or "nodes" in lowered_q): io_facts = _extract_hottest_facts(summary_lines, lowered_q) io_line = next((fact for fact in io_facts if fact.startswith("hottest_io_node")), None)