Files
ai-balance-monitor/app/alert.py
T

82 lines
3.2 KiB
Python

"""阈值提醒状态机:低于阈值提醒一次,恢复后重新武装。
状态(accounts.alert_armed):
- 1 = 已武装,低于阈值时可提醒
- 0 = 已提醒,等待余额恢复到阈值以上
发送成功才切换状态;Telegram 失败则保持原状态,下次检查再试。
"""
from __future__ import annotations
import logging
from datetime import datetime
from app import db
from app.config import Config
from app.telegram import send_message
logger = logging.getLogger("monitor.alert")
def _fmt(v: float | None) -> str:
return "—" if v is None else f"{v:g}"
def build_alert_message(kind: str, account: dict, platform: dict, balance: float | None) -> str:
pname = platform["name"]
aname = account["name"]
cur = platform["currency"]
th = account["threshold"]
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if kind == "below":
return (f"⚠️ 余额不足提醒\n"
f"平台:{pname}\n账号:{aname}\n"
f"当前余额:{_fmt(balance)} {cur}\n阈值:{_fmt(th)} {cur}\n"
f"时间:{now}")
if kind == "recovered":
return (f"✅ 余额已恢复\n"
f"平台:{pname}\n账号:{aname}\n"
f"当前余额:{_fmt(balance)} {cur}\n阈值:{_fmt(th)} {cur}\n"
f"时间:{now}")
if kind == "disabled":
return (f"🚫 账号已自动禁用\n"
f"平台:{pname}\n账号:{aname}\n"
f"原因:{account.get('last_error') or '认证失败'}")
return ""
def evaluate(account: dict, platform: dict, balance: float, cfg: Config) -> None:
"""拉取成功后调用:按状态机决定是否发送提醒,发送成功才更新武装状态。"""
threshold = account["threshold"]
armed = bool(account["alert_armed"])
below = balance < threshold
if below and armed:
kind = "below"
elif not below and not armed:
kind = "recovered"
else:
return
message = build_alert_message(kind, account, platform, balance)
if send_message(cfg.telegram_bot_token, cfg.telegram_chat_id, message):
new_armed = 0 if kind == "below" else 1
with db.get_conn() as conn:
conn.execute("UPDATE accounts SET alert_armed = ? WHERE id = ?", (new_armed, account["id"]))
conn.execute(
"INSERT INTO alert_log (account_id, type, message) VALUES (?, ?, ?)",
(account["id"], kind, message),
)
logger.info("已发送%s提醒: account=%s", kind, account["id"])
else:
logger.warning("提醒发送失败,保持原状态稍后重试: account=%s kind=%s", account["id"], kind)
def notify_disabled(account: dict, platform: dict, cfg: Config) -> None:
"""认证失败禁用账号后通知用户(不参与状态机)。"""
message = build_alert_message("disabled", account, platform, None)
if send_message(cfg.telegram_bot_token, cfg.telegram_chat_id, message):
with db.get_conn() as conn:
conn.execute(
"INSERT INTO alert_log (account_id, type, message) VALUES (?, ?, ?)",
(account["id"], "disabled", message),
)