Spaces:
Sleeping
Sleeping
| from datetime import datetime, timedelta | |
| import logging | |
| from typing import Any, Dict, List | |
| from accounting.models import Invoice, InvoiceStatus | |
| from sales.models import Deal, DealStage | |
| from sqlalchemy import func | |
| from sqlalchemy.orm import Session | |
| logger = logging.getLogger(__name__) | |
| class EarlyWarningSystem: | |
| def __init__(self, db: Session): | |
| self.db = db | |
| def monitor_financial_health(self, workspace_id: str = "default") -> List[Dict[str, Any]]: | |
| """ | |
| Monitors valid leading indicators of trouble. | |
| 1. AR Aging (invoices taking longer to pay). | |
| 2. Booking Velocity Drops (sudden stop in new deals). | |
| """ | |
| alerts = [] | |
| # 1. Accounts Receivable (AR) Aging Check | |
| # Find overdue invoices | |
| overdue_invoices = self.db.query(Invoice).filter( | |
| Invoice.workspace_id == workspace_id, | |
| Invoice.status == InvoiceStatus.OVERDUE | |
| ).all() | |
| if overdue_invoices: | |
| # Calculate average days overdue | |
| total_days = 0 | |
| for inv in overdue_invoices: | |
| due = inv.due_date.replace(tzinfo=None) if inv.due_date else datetime.utcnow() | |
| total_days += (datetime.utcnow() - due).days | |
| avg_overdue = total_days / len(overdue_invoices) | |
| if avg_overdue > 15: # Alert if avg overdue is > 2 weeks | |
| alerts.append({ | |
| "type": "ar_delay", | |
| "severity": "medium", | |
| "metric": "Average Days Overdue", | |
| "current_value": round(avg_overdue, 1), | |
| "threshold": 15, | |
| "action": "Trigger automated dunning sequence." | |
| }) | |
| # 2. Booking Velocity Drop | |
| # Compare deals created in last 7 days vs previous 7 days | |
| now = datetime.utcnow() | |
| last_7_days = self.db.query(func.count(Deal.id)).filter( | |
| Deal.workspace_id == workspace_id, | |
| Deal.created_at >= now - timedelta(days=7) | |
| ).scalar() or 0 | |
| prev_7_days = self.db.query(func.count(Deal.id)).filter( | |
| Deal.workspace_id == workspace_id, | |
| Deal.created_at >= now - timedelta(days=14), | |
| Deal.created_at < now - timedelta(days=7) | |
| ).scalar() or 0 | |
| if prev_7_days > 5 and last_7_days == 0: # Hard stop check | |
| alerts.append({ | |
| "type": "booking_drop", | |
| "severity": "high", | |
| "details": "Zero new deals created in last 7 days (vs active previous week).", | |
| "action": "Check lead sources or sales team activity." | |
| }) | |
| return alerts | |