Spaces:
Sleeping
Sleeping
| 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"]) | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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 ===== | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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)) | |
| 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)) | |