from fastapi import APIRouter, HTTPException, Query, Depends from typing import List, Optional from datetime import datetime, timedelta, date from integrations.jira_service import create_jira_service from services.intelligence_service import intelligence_service from services.auth_service import get_current_user from models.auth_models import UserCredentials from models.intelligence_models import ( DeliveryHealthMetrics, ProductivityMetrics, CostEfficiencyMetrics, TeamCapacityMetrics, RiskAlert, InsightRecommendation, KanbanFlowMetrics, KanbanColumnAnalysis, WIPLimitRecommendation ) import logging logger = logging.getLogger(__name__) router = APIRouter(prefix="/intelligence", tags=["Intelligence"]) @router.get("/delivery-health/{project_key}", response_model=DeliveryHealthMetrics) async def get_delivery_health( project_key: str, current_user: UserCredentials = Depends(get_current_user), board_id: Optional[int] = None, sprint_id: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get delivery health metrics for a project or sprint""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else None end_dt = date.fromisoformat(end_date) if end_date else None # Get sprint if specified sprint = None if sprint_id: issues = jira_service.get_sprint_issues(sprint_id) if board_id: sprints = jira_service.get_sprints(board_id) sprint = next((s for s in sprints if s.sprint_id == sprint_id), None) else: # Get all issues for the project issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()) if start_dt else None, end_date=datetime.combine(end_dt, datetime.max.time()) if end_dt else None ) return intelligence_service.calculate_delivery_health( issues=issues, sprint=sprint, period_start=start_dt, period_end=end_dt ) except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error calculating delivery health: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/productivity/{project_key}", response_model=List[ProductivityMetrics]) async def get_productivity_metrics( project_key: str, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get productivity metrics for all team members in a project""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=14)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get data issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) worklogs = jira_service.get_worklogs( project_key=project_key, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) team_members = jira_service.get_team_members(project_key) # Calculate metrics for each team member metrics = [] for member in team_members: metric = intelligence_service.calculate_productivity_metrics( issues=issues, worklogs=worklogs, team_member_id=member.account_id, team_member_name=member.display_name, period_start=start_dt, period_end=end_dt ) metrics.append(metric) return metrics except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error calculating productivity metrics: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/cost-efficiency/{project_key}", response_model=CostEfficiencyMetrics) async def get_cost_efficiency( project_key: str, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None, avg_hourly_rate: float = Query(75.0, gt=0) ): """Get cost efficiency metrics for a project""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=14)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get data issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) worklogs = jira_service.get_worklogs( project_key=project_key, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) return intelligence_service.calculate_cost_efficiency( issues=issues, worklogs=worklogs, period_start=start_dt, period_end=end_dt, avg_hourly_rate=avg_hourly_rate ) except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error calculating cost efficiency: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/risk-alerts/{project_key}", response_model=List[RiskAlert]) async def get_risk_alerts( project_key: str, current_user: UserCredentials = Depends(get_current_user), board_id: Optional[int] = None, sprint_id: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get risk alerts for a project""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=14)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get all metrics sprint = None if sprint_id and board_id: issues = jira_service.get_sprint_issues(sprint_id) sprints = jira_service.get_sprints(board_id) sprint = next((s for s in sprints if s.sprint_id == sprint_id), None) else: issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) worklogs = jira_service.get_worklogs( project_key=project_key, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) team_members = jira_service.get_team_members(project_key) # Calculate metrics delivery_health = intelligence_service.calculate_delivery_health( issues=issues, sprint=sprint, period_start=start_dt, period_end=end_dt ) productivity_metrics = [ intelligence_service.calculate_productivity_metrics( issues=issues, worklogs=worklogs, team_member_id=member.account_id, team_member_name=member.display_name, period_start=start_dt, period_end=end_dt ) for member in team_members ] cost_metrics = intelligence_service.calculate_cost_efficiency( issues=issues, worklogs=worklogs, period_start=start_dt, period_end=end_dt ) # Generate alerts return intelligence_service.generate_risk_alerts( delivery_health=delivery_health, productivity_metrics=productivity_metrics, cost_metrics=cost_metrics ) except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating risk alerts: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/insights/{project_key}", response_model=List[InsightRecommendation]) async def get_insights( project_key: str, current_user: UserCredentials = Depends(get_current_user), board_id: Optional[int] = None, sprint_id: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get AI-generated insights and recommendations""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=14)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get all metrics (same as risk alerts) sprint = None if sprint_id and board_id: issues = jira_service.get_sprint_issues(sprint_id) sprints = jira_service.get_sprints(board_id) sprint = next((s for s in sprints if s.sprint_id == sprint_id), None) else: issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) worklogs = jira_service.get_worklogs( project_key=project_key, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) team_members = jira_service.get_team_members(project_key) # Calculate metrics delivery_health = intelligence_service.calculate_delivery_health( issues=issues, sprint=sprint, period_start=start_dt, period_end=end_dt ) productivity_metrics = [ intelligence_service.calculate_productivity_metrics( issues=issues, worklogs=worklogs, team_member_id=member.account_id, team_member_name=member.display_name, period_start=start_dt, period_end=end_dt ) for member in team_members ] cost_metrics = intelligence_service.calculate_cost_efficiency( issues=issues, worklogs=worklogs, period_start=start_dt, period_end=end_dt ) # Generate insights return intelligence_service.generate_insights( delivery_health=delivery_health, productivity_metrics=productivity_metrics, cost_metrics=cost_metrics ) except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating insights: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/dashboard/{project_key}") async def get_dashboard_data( project_key: str, current_user: UserCredentials = Depends(get_current_user), board_id: Optional[int] = None, sprint_id: Optional[int] = None, start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get comprehensive dashboard data including all metrics""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=14)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get all data sprint = None if sprint_id and board_id: issues = jira_service.get_sprint_issues(sprint_id) sprints = jira_service.get_sprints(board_id) sprint = next((s for s in sprints if s.sprint_id == sprint_id), None) else: issues = jira_service.get_issues_by_project( project_key=project_key, max_results=1000, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) worklogs = jira_service.get_worklogs( project_key=project_key, start_date=datetime.combine(start_dt, datetime.min.time()), end_date=datetime.combine(end_dt, datetime.max.time()) ) team_members = jira_service.get_team_members(project_key) # Calculate all metrics delivery_health = intelligence_service.calculate_delivery_health( issues=issues, sprint=sprint, period_start=start_dt, period_end=end_dt ) productivity_metrics = [ intelligence_service.calculate_productivity_metrics( issues=issues, worklogs=worklogs, team_member_id=member.account_id, team_member_name=member.display_name, period_start=start_dt, period_end=end_dt ) for member in team_members ] cost_metrics = intelligence_service.calculate_cost_efficiency( issues=issues, worklogs=worklogs, period_start=start_dt, period_end=end_dt ) risk_alerts = intelligence_service.generate_risk_alerts( delivery_health=delivery_health, productivity_metrics=productivity_metrics, cost_metrics=cost_metrics ) insights = intelligence_service.generate_insights( delivery_health=delivery_health, productivity_metrics=productivity_metrics, cost_metrics=cost_metrics ) return { "delivery_health": delivery_health, "productivity_metrics": productivity_metrics, "cost_efficiency": cost_metrics, "risk_alerts": risk_alerts, "insights": insights, "period": { "start": start_dt, "end": end_dt } } except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating dashboard data: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) # ===== KANBAN INTELLIGENCE ENDPOINTS ===== @router.get("/kanban/flow-metrics/{board_id}", response_model=KanbanFlowMetrics) async def get_kanban_flow_metrics( board_id: int, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get Kanban flow efficiency metrics for a board""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=30)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get board info board = jira_service.get_kanban_board_by_id(board_id) if not board: raise HTTPException(status_code=404, detail=f"Kanban board {board_id} not found") # Get issues and columns columns = jira_service.get_kanban_issues_by_column(board_id) # Get all issues for the board all_issues = [] for col in columns: all_issues.extend(col.issues) return intelligence_service.calculate_kanban_flow_metrics( board_id=board_id, board_name=board.board_name, issues=all_issues, columns=columns, period_start=start_dt, period_end=end_dt ) except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error calculating Kanban flow metrics: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/kanban/column-analysis/{board_id}", response_model=List[KanbanColumnAnalysis]) async def get_kanban_column_analysis( board_id: int, current_user: UserCredentials = Depends(get_current_user) ): """Get detailed analysis of each Kanban column""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Get board info board = jira_service.get_kanban_board_by_id(board_id) if not board: raise HTTPException(status_code=404, detail=f"Kanban board {board_id} not found") # Get issues by column columns = jira_service.get_kanban_issues_by_column(board_id) # Get all issues all_issues = [] for col in columns: all_issues.extend(col.issues) return intelligence_service.analyze_kanban_columns( columns=columns, issues=all_issues ) except HTTPException: raise except Exception as e: logger.error(f"Error analyzing Kanban columns: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/kanban/wip-recommendations/{board_id}", response_model=List[WIPLimitRecommendation]) async def get_wip_recommendations( board_id: int, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get WIP limit recommendations for Kanban board columns""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=30)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get board info board = jira_service.get_kanban_board_by_id(board_id) if not board: raise HTTPException(status_code=404, detail=f"Kanban board {board_id} not found") # Get columns and issues columns = jira_service.get_kanban_issues_by_column(board_id) all_issues = [] for col in columns: all_issues.extend(col.issues) # Analyze columns column_analyses = intelligence_service.analyze_kanban_columns( columns=columns, issues=all_issues ) # Calculate flow metrics flow_metrics = intelligence_service.calculate_kanban_flow_metrics( board_id=board_id, board_name=board.board_name, issues=all_issues, columns=columns, period_start=start_dt, period_end=end_dt ) # Generate recommendations return intelligence_service.generate_wip_recommendations( column_analyses=column_analyses, flow_metrics=flow_metrics ) except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating WIP recommendations: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/kanban/insights/{board_id}", response_model=List[InsightRecommendation]) async def get_kanban_insights( board_id: int, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get AI-generated insights for Kanban board""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=30)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get board info board = jira_service.get_kanban_board_by_id(board_id) if not board: raise HTTPException(status_code=404, detail=f"Kanban board {board_id} not found") # Get columns and issues columns = jira_service.get_kanban_issues_by_column(board_id) all_issues = [] for col in columns: all_issues.extend(col.issues) # Calculate all metrics flow_metrics = intelligence_service.calculate_kanban_flow_metrics( board_id=board_id, board_name=board.board_name, issues=all_issues, columns=columns, period_start=start_dt, period_end=end_dt ) column_analyses = intelligence_service.analyze_kanban_columns( columns=columns, issues=all_issues ) wip_recommendations = intelligence_service.generate_wip_recommendations( column_analyses=column_analyses, flow_metrics=flow_metrics ) # Generate insights return intelligence_service.generate_kanban_insights( flow_metrics=flow_metrics, column_analyses=column_analyses, wip_recommendations=wip_recommendations ) except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating Kanban insights: {str(e)}") raise HTTPException(status_code=500, detail=str(e)) @router.get("/kanban/dashboard/{board_id}") async def get_kanban_dashboard( board_id: int, current_user: UserCredentials = Depends(get_current_user), start_date: Optional[str] = None, end_date: Optional[str] = None ): """Get comprehensive Kanban dashboard data""" try: # Create user-specific Jira service jira_service = create_jira_service( current_user.jira_email, current_user.jira_api_token, current_user.jira_server_url ) # Parse dates start_dt = date.fromisoformat(start_date) if start_date else (date.today() - timedelta(days=30)) end_dt = date.fromisoformat(end_date) if end_date else date.today() # Get board info board = jira_service.get_kanban_board_by_id(board_id) if not board: raise HTTPException(status_code=404, detail=f"Kanban board {board_id} not found") # Get columns and issues columns = jira_service.get_kanban_issues_by_column(board_id) all_issues = [] for col in columns: all_issues.extend(col.issues) # Calculate all metrics flow_metrics = intelligence_service.calculate_kanban_flow_metrics( board_id=board_id, board_name=board.board_name, issues=all_issues, columns=columns, period_start=start_dt, period_end=end_dt ) column_analyses = intelligence_service.analyze_kanban_columns( columns=columns, issues=all_issues ) wip_recommendations = intelligence_service.generate_wip_recommendations( column_analyses=column_analyses, flow_metrics=flow_metrics ) insights = intelligence_service.generate_kanban_insights( flow_metrics=flow_metrics, column_analyses=column_analyses, wip_recommendations=wip_recommendations ) return { "board": { "id": board.board_id, "name": board.board_name, "type": board.board_type, "project_key": board.project_key }, "flow_metrics": flow_metrics, "column_analyses": column_analyses, "wip_recommendations": wip_recommendations, "insights": insights, "columns_with_issues": columns, "period": { "start": start_dt, "end": end_dt } } except HTTPException: raise except ValueError as e: raise HTTPException(status_code=400, detail=f"Invalid date format: {str(e)}") except Exception as e: logger.error(f"Error generating Kanban dashboard: {str(e)}") raise HTTPException(status_code=500, detail=str(e))