diff --git a/atlasbot/engine/answerer.py b/atlasbot/engine/answerer.py index a37069a..7a42b24 100644 --- a/atlasbot/engine/answerer.py +++ b/atlasbot/engine/answerer.py @@ -274,6 +274,22 @@ class AnswerEngine: "answer_style": "direct", "follow_up": False, } + heuristic_reply = _quick_fact_sheet_heuristic_answer(question, fact_lines) + if heuristic_reply: + reply = heuristic_reply + scores = _default_scores() + meta = _build_meta( + mode, + call_count, + call_cap, + limit_hit, + time_budget_hit, + time_budget_sec, + classify, + tool_hint, + started, + ) + return AnswerResult(reply, scores, meta) quick_context = _quick_fact_sheet_text(fact_lines) quick_prompt = ( "Question: " @@ -3456,6 +3472,33 @@ def _quick_fact_sheet_text(lines: list[str]) -> str: return "Fact Sheet:\n" + body +def _quick_fact_sheet_heuristic_answer(question: str, fact_lines: list[str]) -> str: + lowered = question.lower() + if ( + any(token in lowered for token in ("placement", "schedule", "last resort", "last-resort")) + and any(token in lowered for token in ("node", "workload", "worker", "titan")) + ): + return ( + "General workload placement is: prefer rpi5 workers first, then rpi4 workers. " + "titan-22 is the last-resort general compute node, and titan-24 is the absolute last resort " + "reserved for heavy one-offs." + ) + + for line in fact_lines: + compact = line.replace(" ", "") + match = re.search(r"nodes_total[:=](\d+),ready[:=](\d+),not_ready[:=](\d+)", compact) + if not match: + continue + total = match.group(1) + ready = match.group(2) + not_ready = match.group(3) + if "how many" in lowered and "ready" in lowered and "node" in lowered: + return f"The latest snapshot shows {ready} ready nodes out of {total} total ({not_ready} not ready)." + if ("not ready" in lowered or "unready" in lowered) and "node" in lowered: + return f"The latest snapshot shows {not_ready} not-ready nodes ({ready} ready out of {total} total)." + return "" + + def _json_excerpt(summary: dict[str, Any], max_chars: int = 12000) -> str: raw = json.dumps(summary, ensure_ascii=False) return raw[:max_chars]