"""Scheduled runway evaluation from ledger data.""" from __future__ import annotations import json from pathlib import Path from fin_hub.coupling.canon import emit_viability_alert from fin_hub.coupling.dev_hub import emit_resource_pressure from fin_hub.services.alerts import evaluate_budget_alerts from fin_hub.services.ledger import ( get_opening_balance, list_current_commitments, monthly_burn_series, ) from fin_hub.services.runway import compute_runway def evaluate_runway( *, ledger_path: Path, opening_balance: float | None = None, alert_threshold_months: float = 3.0, currency: str = "EUR", allocated: float | None = None, spent: float | None = None, emit: bool = False, api_base: str | None = None, ) -> dict: stored_balance, stored_currency = get_opening_balance(ledger_path) balance = opening_balance if opening_balance is not None else stored_balance if balance is None: raise ValueError("opening balance required — use ledger set-balance or --balance") effective_currency = currency or stored_currency burns = monthly_burn_series(ledger_path=ledger_path, currency=effective_currency) commitments = list_current_commitments( ledger_path=ledger_path, currency=effective_currency ) if not burns: committed = sum((float(item.amount) for item in commitments), 0.0) if committed > 0: burns = [committed] runway = compute_runway( current_balance=balance, monthly_burns=burns, alert_threshold_months=alert_threshold_months, currency=effective_currency, ) alerts = evaluate_budget_alerts(runway=runway, allocated=allocated, spent=spent) report: dict = { "runway": runway.as_dict(), "alerts": [alert.as_dict() for alert in alerts], "monthly_burns": burns, "commitments": [item.as_dict() for item in commitments], "ledger_path": str(ledger_path.resolve()), } if emit: report["dev_hub"] = emit_resource_pressure(alerts, api_base=api_base) report["canon"] = emit_viability_alert(runway, api_base=api_base) return report def evaluate_runway_json(**kwargs) -> str: return json.dumps(evaluate_runway(**kwargs), indent=2, default=str)