Files
ai-worker-platform/backend/app/services/alert_service.py
T

102 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Alert service: budget monitoring and system alerts."""
from sqlalchemy.orm import Session
from typing import List, Optional
from app.models.alert import Alert
from app.models.project import Project
from app.models.worker import AIWorker
from app.services.cost_service import CostService
class AlertService:
@staticmethod
def check_and_alert(db: Session, tenant_id: int, project_id: int) -> Optional[Alert]:
"""Check project budget and create alert if needed."""
project = db.query(Project).filter(
Project.id == project_id, Project.tenant_id == tenant_id
).first()
if not project or not project.budget_limit_cents:
return None
spent = CostService.get_project_cost(db, tenant_id, project_id)
limit = project.budget_limit_cents
pct = (spent / limit) if limit > 0 else 0
if pct >= 1.0:
# Pause project
project.status = "paused"
db.commit()
return AlertService._create(
db, tenant_id, "budget_exceeded", "critical",
f"项目「{project.name}」预算已超限!已花费 ¥{spent/100:.2f} / ¥{limit/100:.2f},项目已自动暂停。",
project_id=project_id,
)
elif pct >= 0.8:
return AlertService._create(
db, tenant_id, "budget_warning", "warning",
f"项目「{project.name}」预算使用达 {pct*100:.0f}%(¥{spent/100:.2f} / ¥{limit/100:.2f}",
project_id=project_id,
)
return None
@staticmethod
def check_worker_budget(db: Session, tenant_id: int, worker_id: int) -> Optional[Alert]:
worker = db.query(AIWorker).filter(
AIWorker.id == worker_id, AIWorker.tenant_id == tenant_id
).first()
if not worker or not worker.max_cost_per_month_cents:
return None
spent = CostService.get_worker_monthly_cost(db, tenant_id, worker_id)
limit = worker.max_cost_per_month_cents
pct = (spent / limit) if limit > 0 else 0
if pct >= 1.0:
worker.is_active = False
db.commit()
return AlertService._create(
db, tenant_id, "budget_exceeded", "critical",
f"Worker「{worker.name}」月度预算已超限!已花费 ¥{spent/100:.2f} / ¥{limit/100:.2f}Worker已停用。",
worker_id=worker_id,
)
elif pct >= 0.8:
return AlertService._create(
db, tenant_id, "budget_warning", "warning",
f"Worker「{worker.name}」月度预算使用达 {pct*100:.0f}%",
worker_id=worker_id,
)
return None
@staticmethod
def _create(db, tenant_id, alert_type, severity, message, project_id=None, worker_id=None):
alert = Alert(
tenant_id=tenant_id, alert_type=alert_type, severity=severity,
message=message, project_id=project_id, worker_id=worker_id,
)
db.add(alert)
db.commit()
db.refresh(alert)
return alert
@staticmethod
def list_alerts(db, tenant_id, is_read=None, skip=0, limit=50):
q = db.query(Alert).filter(Alert.tenant_id == tenant_id)
if is_read is not None:
q = q.filter(Alert.is_read == is_read)
return q.order_by(Alert.created_at.desc()).offset(skip).limit(limit).all()
@staticmethod
def unread_count(db, tenant_id):
return db.query(Alert).filter(
Alert.tenant_id == tenant_id, Alert.is_read == False
).count()
@staticmethod
def mark_read(db, tenant_id, alert_id):
alert = db.query(Alert).filter(
Alert.id == alert_id, Alert.tenant_id == tenant_id
).first()
if alert:
alert.is_read = True
db.commit()
return alert