telegram: attach AI panel to alert-quality sends (advisory block)
Auto-scan and level-breach alert paths attach the panel AFTER the quality gate and dedup decision (no LLM spend on suppressed alerts), via run_in_executor so the event loop stays free. Alert message gains a two-line 🤖 AI Panel block (verdict + confidence + rationale, persona votes); /status shows the daily panel budget when the feature is on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SisdfkxnXNL9iZsXxN3T15
This commit is contained in:
@@ -71,6 +71,7 @@ from app_config import (
|
||||
telegram_channel_id,
|
||||
)
|
||||
from providers import BaseDataProvider, make_data_provider
|
||||
from agent_panel import attach_panel, panel_health
|
||||
from llm_client import get_llm_client
|
||||
from news_engine import FundamentalContext, NewsEngine
|
||||
from pair_signals import PAIR_PRODUCERS
|
||||
@@ -407,6 +408,17 @@ def format_signal_message(report: SignalReport) -> str:
|
||||
extra.append(f"⚠️ {_escape_md(w)}")
|
||||
if report.news_summary:
|
||||
extra.append(f"📰 {_escape_md(report.news_summary)}")
|
||||
if report.panel_verdict:
|
||||
mark = {"AGREE": "✅", "OPPOSE": "⛔", "NEUTRAL": "➖"}.get(
|
||||
report.panel_verdict, "")
|
||||
extra.append(
|
||||
f"🤖 AI Panel {mark} *{_escape_md(report.panel_verdict)}* "
|
||||
f"({report.panel_confidence:.0f}%) — "
|
||||
f"{_escape_md(report.panel_rationale)}")
|
||||
if report.panel_takes:
|
||||
votes = " · ".join(
|
||||
f"{t['persona']}:{t['stance'][:3]}" for t in report.panel_takes)
|
||||
extra.append(f" _{_escape_md(votes)}_")
|
||||
if extra:
|
||||
lines += [""] + extra
|
||||
|
||||
@@ -720,6 +732,13 @@ async def _scan_timeframe(
|
||||
async with _alert_lock(key):
|
||||
if not _should_alert(key, report):
|
||||
continue
|
||||
# Panel AFTER the quality gate + dedup decision so no LLM
|
||||
# spend on suppressed alerts. Runs on the scan executor to
|
||||
# keep the event loop free; this key's alert lock is held
|
||||
# for the panel's wall time (≤~25 s), which only delays a
|
||||
# same-key duplicate that _should_alert would drop anyway.
|
||||
await asyncio.get_running_loop().run_in_executor(
|
||||
_executor, attach_panel, report)
|
||||
detail = format_signal_message(report)
|
||||
delivered = False
|
||||
for chat_id in _signal_targets():
|
||||
@@ -855,6 +874,10 @@ async def _handle_breach(bot, pair_symbol: str, price: float, level_name: str) -
|
||||
if not _should_alert(key, report):
|
||||
return
|
||||
|
||||
# Same post-gate panel attach as the auto-scan path (see
|
||||
# _scan_timeframe for the spend/lock rationale).
|
||||
await asyncio.get_running_loop().run_in_executor(
|
||||
_executor, attach_panel, report)
|
||||
header = (
|
||||
f"⚡ *LEVEL BREAK* — {_escape_md(display)} crossed "
|
||||
f"*{_escape_md(label)}* @ `{_fmt_price(price)}`\n\n"
|
||||
@@ -1193,6 +1216,10 @@ def _news_health_lines() -> List[str]:
|
||||
else:
|
||||
lines.append(f" ✅ DeepSeek: {used}/{cap} calls today,"
|
||||
f" {scored} headlines scored")
|
||||
if get_app_config().agent_panel_settings.get("enabled", False):
|
||||
ph = panel_health()
|
||||
lines.append(f" 🤖 AI panel: {ph['used_today']}/{ph['cap']}"
|
||||
" panels today")
|
||||
return lines
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user