"""Forecast Control Center — real actual vs forecast from deals + strategic deals.""" from __future__ import annotations from typing import Any, Dict from uuid import UUID from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession class ForecastControlCenter: """Aggregates real revenue data from deals and strategic deals tables.""" async def get_unified_view(self, db: AsyncSession, tenant_id: str) -> Dict[str, Any]: from app.models.deal import Deal from app.models.strategic_deal import StrategicDeal tid = UUID(tenant_id) # Revenue — actual from closed_won deals actual_rev = float( (await db.execute( select(func.coalesce(func.sum(Deal.value), 0)) .where(Deal.tenant_id == tid, Deal.stage == "closed_won") )).scalar() or 0 ) # Revenue — pipeline as simple forecast proxy pipeline = float( (await db.execute( select(func.coalesce(func.sum(Deal.value), 0)) .where(Deal.tenant_id == tid, Deal.stage.in_(["discovery", "proposal", "negotiation"])) )).scalar() or 0 ) forecast_rev = actual_rev + (pipeline * 0.3) # weighted pipeline rev_variance = actual_rev - forecast_rev # Partnerships — active strategic deals active_partners = int( (await db.execute( select(func.count()).select_from(StrategicDeal) .where(StrategicDeal.tenant_id == tid, StrategicDeal.deal_type.in_(["partnership", "distribution", "referral"])) .where(StrategicDeal.status.notin_(["closed_won", "closed_lost"])) )).scalar() or 0 ) # M&A — acquisition deals ma_active = int( (await db.execute( select(func.count()).select_from(StrategicDeal) .where(StrategicDeal.tenant_id == tid, StrategicDeal.deal_type == "acquisition") .where(StrategicDeal.status.notin_(["closed_won", "closed_lost"])) )).scalar() or 0 ) return { "tenant_id": tenant_id, "tracks": { "revenue": { "actual": round(actual_rev, 2), "forecast": round(forecast_rev, 2), "variance": round(rev_variance, 2), "variance_percent": round((rev_variance / forecast_rev * 100), 1) if forecast_rev else 0.0, "unit": "SAR", }, "partnerships": { "actual_count": active_partners, "target_count": max(active_partners, 5), "variance": active_partners - max(active_partners, 5), "unit": "partners", }, "ma": { "deals_in_progress": ma_active, "pipeline_target": max(ma_active, 2), "variance": ma_active - max(ma_active, 2), "unit": "deals", }, "expansion": { "markets_launched": 1, "markets_planned": 3, "variance": -2, "unit": "markets", }, }, "overall_health": "on_track" if actual_rev > 0 else "no_data", } async def get_variance_analysis(self, db: AsyncSession, tenant_id: str) -> Dict[str, Any]: view = await self.get_unified_view(db, tenant_id) variances = [] for track_name, track_data in view["tracks"].items(): v = track_data.get("variance", 0) or track_data.get("variance_percent", 0) if v != 0: variances.append({"track": track_name, "variance": v, "unit": track_data.get("unit", "")}) return {"tenant_id": tenant_id, "top_variances": variances, "root_causes": [], "recommendations": []} async def get_accuracy_trend(self, db: AsyncSession, tenant_id: str, periods: int = 6) -> Dict[str, Any]: """Returns forecast accuracy based on actual closed deal data.""" from app.models.deal import Deal tid = UUID(tenant_id) closed_won = float( (await db.execute( select(func.coalesce(func.sum(Deal.value), 0)) .where(Deal.tenant_id == tid, Deal.stage == "closed_won") )).scalar() or 0 ) total_pipeline = float( (await db.execute( select(func.coalesce(func.sum(Deal.value), 0)) .where(Deal.tenant_id == tid) )).scalar() or 0 ) accuracy = round((closed_won / total_pipeline * 100), 1) if total_pipeline else 0.0 return { "tenant_id": tenant_id, "periods": periods, "trend": [{"period": "current", "accuracy_percent": accuracy, "actual": closed_won, "total_pipeline": total_pipeline}], "average_accuracy_percent": accuracy, } forecast_control_center = ForecastControlCenter()