import json from enum import StrEnum from typing import Optional from fastapi import APIRouter, Body, Query, Request from monday_com_orca_backend.typings import LabelId from monday_com_orca_backend.utils.snowflake import get_snowflake_client from ..decorators import cache_response from ..enums import BaseRequestParam from . import helpers, models, sql_queries from .enums import RequestParam router = APIRouter() class Tags(StrEnum): """API tags used in the endpoints.""" DASHBOARD = "Dashboard" @router.get( "/countries", tags=[Tags.DASHBOARD], response_model=models.responses.CountriesResponse, responses={ 200: { "model": models.responses.CountriesResponse, "description": "List of countries with their metadata " "(country codes, names, etc.).", } }, summary="Get country and region metadata", description="Fetches metadata for countries and regions", ) @cache_response async def get_countries(request: Request): """ Get country and region metadata (country codes, names, etc.). Regions are e.g., GSA. """ data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_queries.get_countries(), model_class=models.responses.Country, ) response = models.responses.CountriesResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response async def get_label_meta_internal( label_ids: list[LabelId], ) -> list[models.responses.Label]: """Internal function to get label metadata.""" sql_query = sql_queries.get_label_meta(label_ids=label_ids) def hook_after_fetch(data_: list[dict]): """ Hook to process the fetched data after retrieval. This function modifies the 'top_clients' and 'yearly_growth' fields to convert them from JSON strings to Python objects. """ for row in data_: for key in [ "contract", "master_contact", ]: row[key] = json.loads(row[key]) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.Label, hook_after_fetch=hook_after_fetch, ) return data @router.get( "/label_meta", tags=[Tags.DASHBOARD], response_model=models.responses.LabelMetaResponse, responses={ 200: { "model": models.responses.LabelMetaResponse, "description": "List of labels with their metadata.", } }, summary="Get label metadata", description="Fetches metadata for labels based on provided label IDs.", ) @cache_response async def get_label_meta( request: Request, label_ids: str = Query(..., alias=RequestParam.LABEL_IDS), ): """ Get label metadata for a list of label IDs. Parameters: - label_ids (str): Comma-separated list of label IDs to fetch metadata for. E.g.: "33960,12345". """ label_ids_list = helpers.args_extract_label_ids(label_ids) or [] data = await get_label_meta_internal(label_ids=label_ids_list) response = models.responses.LabelMetaResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response @router.get( "/relationship_manager_stats", tags=[Tags.DASHBOARD], response_model=models.responses.RelationshipManagerStatsResponse, responses={ 200: { "model": models.responses.RelationshipManagerStatsResponse, "description": "List of relationship manager objects with statistics.", } }, summary="Get relationship manager stats", description="Fetches statistics for relationship managers based on filters.", ) @cache_response async def get_relationship_manager_stats( request: Request, label_ids: Optional[str] = Query(None, alias=RequestParam.LABEL_IDS), date_yearmonth: Optional[str] = Query( None, alias=RequestParam.DATE_YEARMONTH, description="Year and month in YYYY-MM format to get a client snapshot for.", regex=r"^\d{4}-(0[1-9]|1[0-2])$", ), ): """ Get relationship manager stats for given label IDs. """ label_ids_list = helpers.args_extract_label_ids(label_ids) sql_query = sql_queries.get_relationship_manager_stats( label_ids=label_ids_list, date_yearmonth=date_yearmonth, ) def hook_after_fetch(data_: list[dict]): """ Hook to process the fetched data after retrieval. This function modifies the 'top_clients' and 'yearly_growth' fields to convert them from JSON strings to Python objects. """ for row in data_: for key in [ "top_clients_ltm", "top_clients_fiscal_year", "yearly_growth", "yearly_growth_fiscal", ]: row[key] = json.loads(row[key]) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.RelationshipManagerStats, hook_after_fetch=hook_after_fetch, ) response = models.responses.RelationshipManagerStatsResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response @router.post( "/relationship_manager_clients", tags=[Tags.DASHBOARD], response_model=models.responses.RelationshipManagerClientResponse, responses={ 200: { "model": models.responses.RelationshipManagerClientResponse, "description": "List of clients matching the filters.", } }, summary="Get clients by relationship manager(s), if any", description="Fetches clients based on relationship manager emails. If no emails " "are provided, the response will also include clients with no " "relationship manager assigned.", ) @cache_response async def get_relationship_manager_clients( request: Request, request_data: dict = Body(...), ): """ Get clients by relationship manager emails. If no emails are provided, the response will also include clients with no relationship manager assigned. """ relationship_manager_emails = request_data.get( RequestParam.RELATIONSHIP_MANAGER_EMAILS ) limit = request_data.get(BaseRequestParam.LIMIT) sql_query = sql_queries.get_relationship_manager_clients( relationship_manager_emails=relationship_manager_emails, limit=int(limit) if limit is not None else limit, ) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.RelationshipManagerClient, ) response = models.responses.RelationshipManagerClientResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response @router.post( "/scorecard_filter_meta", tags=[Tags.DASHBOARD], response_model=models.responses.ScorecardFilterMetaResponse, responses={ 200: { "model": models.responses.ScorecardFilterMetaResponse, "description": "List of clients matching the filters.", } }, summary="Get the filtering metadata for the ORCA dashboard", description=( "Get the filtering metadata for the ORCA dashboard, based " "on relationship manager emails and sourcing it only from the scorecard table." ), ) @cache_response async def get_scorecard_filter_meta( request: Request, request_data: dict = Body(...), ): """ Get clients by relationship manager emails. If no emails are provided, the response will also include clients with no relationship manager assigned. """ relationship_manager_emails = request_data.get( RequestParam.RELATIONSHIP_MANAGER_EMAILS, None ) sql_query = sql_queries.get_scorecard_filter_meta( relationship_manager_emails=relationship_manager_emails ) def hook_after_fetch(data_: list[dict]): for row in data_: row["clients"] = json.loads(row["clients"]) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.ScorecardFilterMeta, hook_after_fetch=hook_after_fetch, ) response = models.responses.ScorecardFilterMetaResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response @router.get( "/scorecard_relationship_managers", tags=[Tags.DASHBOARD], response_model=models.responses.RelationshipManagerResponse, responses={ 200: { "model": models.responses.RelationshipManagerResponse, "description": "List of scorecard relationship managers with " "their metadata.", } }, summary="Get all scorecard relationship managers", description="Fetches all relationship managers which appear in the scorecard.", ) @cache_response async def get_scorecard_relationship_managers( request: Request, ): """ Get all relationship managers that appear in the scorecard. """ sql_query = sql_queries.get_scorecard_relationship_managers() data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.RelationshipManager, ) response = models.responses.RelationshipManagerResponse( data=data, more=False, # No pagination for this endpoint ).model_dump() return response @router.get( "/scorecard_clients", tags=[Tags.DASHBOARD], response_model=models.responses.ScorecardClientsResponse, responses={ 200: { "model": models.responses.ScorecardClientsResponse, "description": "List of scorecard clients with their metadata.", } }, summary="Get scorecard client metadata.", description="Fetches metadata for scorecard clients based on relationship manager " "emails and/or label IDs. This won't get all clients for one or more relationship " "managers, but only those that appear in the scorecard.", ) @cache_response async def get_scorecard_clients( request: Request, relationship_manager_emails: Optional[str] = Query( None, alias=RequestParam.RELATIONSHIP_MANAGER_EMAILS ), limit: Optional[int] = Query(None, alias=BaseRequestParam.LIMIT), label_ids: Optional[str] = Query(None, alias=RequestParam.LABEL_IDS), date_yearmonth: Optional[str] = Query( None, alias=RequestParam.DATE_YEARMONTH, description="Year and month in YYYY-MM format to get a client snapshot for.", regex=r"^\d{4}-(0[1-9]|1[0-2])$", ), ): """ Get scorecard clients for given relationship manager emails and/or label IDs. """ label_ids_list = helpers.args_extract_label_ids(label_ids) sql_query = sql_queries.get_scorecard_clients( relationship_manager_emails=( relationship_manager_emails.split(",") if relationship_manager_emails else None ), label_ids=label_ids_list, date_yearmonth=date_yearmonth, limit=(limit + 1) if limit is not None else limit, ) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.ScorecardClient, ) response = models.responses.ScorecardClientsResponse( data=data[:limit] if limit else data, more=len(data) > limit if limit else False ).model_dump() return response @router.get( "/releases", tags=[Tags.DASHBOARD], response_model=models.responses.ReleasesResponse, responses={ 200: { "model": models.responses.ReleasesResponse, "description": "List of releases with their metadata.", } }, summary="Get releases", description="Fetches releases filtered by relationship manager emails, " "priorities, date range, order, and label IDs.", ) @cache_response async def get_releases( request: Request, relationship_manager_emails: Optional[str] = Query( None, alias=RequestParam.RELATIONSHIP_MANAGER_EMAILS ), priorities: Optional[str] = Query(None, alias=RequestParam.PRIORITIES), date_from: Optional[str] = Query(None, alias=BaseRequestParam.DATE_FROM), date_to: Optional[str] = Query(None, alias=BaseRequestParam.DATE_TO), order_by: Optional[str] = Query(None, alias=BaseRequestParam.ORDER_BY), order: Optional[str] = Query(None, alias=BaseRequestParam.ORDER), limit: Optional[int] = Query(None, alias=BaseRequestParam.LIMIT), label_ids: Optional[str] = Query(None, alias=RequestParam.LABEL_IDS), ): """ Get releases filtered by various parameters. """ label_ids_list = helpers.args_extract_label_ids(label_ids) sql_query = sql_queries.get_releases( relationship_manager_emails=( relationship_manager_emails.split(",") if relationship_manager_emails else None ), priorities=priorities.split(",") if priorities else None, date_from=date_from, date_to=date_to, order_by=order_by, order=order, limit=limit, label_ids=label_ids_list, ) def hook_after_fetch(data_: list[dict]): for row in data_: row["priorities"] = json.loads(row["priorities"]) data = await helpers.get_formatted_data( get_snowflake_client(), sql_query=sql_query, model_class=models.responses.Release, hook_after_fetch=hook_after_fetch, ) response = models.responses.ReleasesResponse( data=data[:limit] if limit else data, more=len(data) > limit if limit else False ).model_dump() return response