| import logging |
| from typing import Any, Dict, List |
| from intelligence.models import CapacityPlan, ResourceRole |
| from sales.models import Deal, DealStage |
| from sqlalchemy import func |
| from sqlalchemy.orm import Session |
|
|
| logger = logging.getLogger(__name__) |
|
|
| class StaffingForecaster: |
| def __init__(self, db: Session): |
| self.db = db |
|
|
| def predict_resource_demand(self, workspace_id: str) -> Dict[str, float]: |
| """ |
| Calculates demand based on open pipeline probability. |
| Heuristic: $100k Deal Value = 500 Engineering Hours (Rate $200/hr) |
| """ |
| |
| pipeline = self.db.query(Deal).filter( |
| Deal.workspace_id == workspace_id, |
| Deal.stage.notin_([DealStage.CLOSED_WON, DealStage.CLOSED_LOST]) |
| ).all() |
| |
| weighted_pipeline_value = 0.0 |
| for deal in pipeline: |
| |
| prob = 0.1 |
| if deal.stage == DealStage.NEGOTIATION: prob = 0.8 |
| elif deal.stage == DealStage.PROPOSAL: prob = 0.5 |
| |
| weighted_pipeline_value += (deal.value * prob) |
| |
| |
| |
| labor_budget = weighted_pipeline_value * 0.5 |
| demand_hours = labor_budget / 100.0 |
| |
| return { |
| "weighted_pipeline_value": weighted_pipeline_value, |
| "estimated_engineering_hours": demand_hours |
| } |
|
|
| def check_capacity_gap(self, workspace_id: str, demand_hours: float) -> Dict[str, Any]: |
| """ |
| Compare Demand vs Supply (Capacity Plans) |
| """ |
| |
| plans = self.db.query(CapacityPlan).filter( |
| CapacityPlan.workspace_id == workspace_id |
| ).all() |
| |
| supply_hours = sum(p.available_hours for p in plans) |
| |
| if demand_hours > supply_hours: |
| gap = demand_hours - supply_hours |
| return { |
| "status": "SHORTAGE", |
| "gap_hours": gap, |
| "message": f"Capacity Shortage: Need {int(gap)} more hours to support pipeline." |
| } |
| |
| return {"status": "OK", "surplus_hours": supply_hours - demand_hours} |
|
|