fin-hub/src/fin_hub/services/alerts.py
tegwick 1abbbe85e4 Implement fin-hub T24-T26: ingest, runway, coupling, RaaS packaging
Add CSV importers, runway calculator, budget alerts, cross-hub coupling
emitters, finhub CLI, and raas-mvp-packaging docs with full test coverage.
2026-07-08 00:59:39 +02:00

60 lines
No EOL
1.7 KiB
Python

"""Budget and runway alert evaluation."""
from __future__ import annotations
from dataclasses import dataclass
from fin_hub.services.runway import RunwayResult
@dataclass(frozen=True)
class BudgetAlert:
code: str
severity: str
summary: str
detail: dict
def as_dict(self) -> dict:
return {
"code": self.code,
"severity": self.severity,
"summary": self.summary,
"detail": self.detail,
}
def evaluate_budget_alerts(
*,
runway: RunwayResult,
allocated: float | None = None,
spent: float | None = None,
) -> list[BudgetAlert]:
alerts: list[BudgetAlert] = []
if runway.below_threshold and runway.monthly_burn > 0:
alerts.append(
BudgetAlert(
code="runway_below_threshold",
severity="high",
summary=(
f"Projected runway {runway.months_remaining:.1f} months "
f"is below threshold {runway.alert_threshold_months:.1f}"
),
detail=runway.as_dict(),
)
)
if allocated is not None and spent is not None and allocated > 0:
utilisation = spent / allocated
if utilisation >= 0.8:
alerts.append(
BudgetAlert(
code="budget_pressure",
severity="medium" if utilisation < 1.0 else "high",
summary=f"Budget utilisation at {utilisation * 100:.0f}%",
detail={
"allocated": allocated,
"spent": spent,
"utilisation": utilisation,
},
)
)
return alerts