diff --git a/backend/bloom/domain/metrics.py b/backend/bloom/domain/metrics.py index d59af644..c7294177 100644 --- a/backend/bloom/domain/metrics.py +++ b/backend/bloom/domain/metrics.py @@ -39,6 +39,11 @@ class ResponseMetricsVesselInActivitySchema(BaseModel): vessel: VesselListView total_time_at_sea: Optional[timedelta] +class ResponseMetricsVesselInZonesSchema(BaseModel): + model_config = ConfigDict(from_attributes=True) + vessel: VesselListView + total_time_in_zones: Optional[timedelta] + class ResponseMetricsZoneVisitedSchema(BaseModel): zone: ZoneListView visiting_duration: timedelta diff --git a/backend/bloom/routers/v1/metrics.py b/backend/bloom/routers/v1/metrics.py index 0f3e6cdf..4cd3afbf 100644 --- a/backend/bloom/routers/v1/metrics.py +++ b/backend/bloom/routers/v1/metrics.py @@ -123,3 +123,44 @@ async def read_metrics_all_vessels_visiting_time_by_zone(request: Request, category=category, sub_category=sub_category) return jsonable_encoder(payload) + + +@router.get("/metrics/vessels-activity") +# @cache +async def read_metrics_all_vessels_visiting_time_in_zones( + request: Request, + datetime_range: DatetimeRangeRequest = Depends(), + category: Optional[str] = None, + pagination: PageParams = Depends(), + order: OrderByRequest = Depends(), + key: str = Depends(X_API_KEY_HEADER), +): + check_apikey(key) + use_cases = UseCases() + MetricsService = use_cases.metrics_service() + payload = MetricsService.get_vessels_activity_in_zones( + datetime_range=datetime_range, + pagination=pagination, + order=order, + category=category, + ) + return jsonable_encoder(payload) + + +@router.get("/metrics/zones-visited") +# @cache +async def read_metrics_all_zones_visited( + request: Request, + datetime_range: DatetimeRangeRequest = Depends(), + category: Optional[str] = None, + pagination: PageParams = Depends(), + order: OrderByRequest = Depends(), + key: str = Depends(X_API_KEY_HEADER), +): + check_apikey(key) + use_cases = UseCases() + MetricsService = use_cases.metrics_service() + payload = MetricsService.get_zones_visited( + datetime_range=datetime_range, pagination=pagination, order=order, category=category + ) + return jsonable_encoder(payload) diff --git a/backend/bloom/services/metrics.py b/backend/bloom/services/metrics.py index c9a08ef1..1059e214 100644 --- a/backend/bloom/services/metrics.py +++ b/backend/bloom/services/metrics.py @@ -16,11 +16,22 @@ from bloom.infra.repositories.repository_zone import ZoneRepository from bloom.domain.metrics import TotalTimeActivityTypeRequest -from bloom.domain.metrics import (ResponseMetricsVesselInActivitySchema, - ResponseMetricsZoneVisitedSchema, - ResponseMetricsZoneVisitingTimeByVesselSchema, - ResponseMetricsVesselTotalTimeActivityByActivityTypeSchema, - ResponseMetricsVesselVisitingTimeByZoneSchema) +from bloom.domain.metrics import ( + ResponseMetricsVesselInActivitySchema, + ResponseMetricsZoneVisitedSchema, + ResponseMetricsVesselInZonesSchema, + ResponseMetricsZoneVisitingTimeByVesselSchema, + ResponseMetricsVesselTotalTimeActivityByActivityTypeSchema, + ResponseMetricsVesselVisitingTimeByZoneSchema, +) +from bloom.domain.metrics import ( + ResponseMetricsVesselInActivitySchema, + ResponseMetricsZoneVisitedSchema, + ResponseMetricsVesselInZonesSchema, + ResponseMetricsZoneVisitingTimeByVesselSchema, + ResponseMetricsVesselTotalTimeActivityByActivityTypeSchema, + ResponseMetricsVesselVisitingTimeByZoneSchema, +) class MetricsService(): def __init__( @@ -72,7 +83,108 @@ def getVesselsInActivity(self, total_time_at_sea=item[1] )\ for item in payload] - + + def get_vessels_activity_in_zones( + self, + datetime_range: DatetimeRangeRequest, + pagination: PageParams, + order: OrderByRequest, + category: Optional[str] = None, + ): + payload=[] + with self.session_factory() as session: + stmt = ( + select( + sql_model.Vessel, + func.sum(sql_model.Metrics.duration_total).label( + "total_time_in_zones" + ), + ) + .select_from(sql_model.Metrics) + .join( + sql_model.Vessel, + sql_model.Metrics.vessel_id == sql_model.Vessel.id + ) + .join( + sql_model.Zone, + sql_model.Metrics.zone_id == sql_model.Zone.id + ) + .where( + sql_model.Metrics.timestamp.between( + datetime_range.start_at, datetime_range.end_at + ) + ) + .group_by( + sql_model.Vessel + ) + ) + stmt = stmt.offset(pagination.offset) if pagination.offset != None else stmt + if category: + stmt = stmt.where(sql_model.Zone.category == category) + stmt = ( + stmt.order_by(asc("total_time_in_zones")) + if order.order == OrderByEnum.ascending + else stmt.order_by(desc("total_time_in_zones")) + ) + stmt = stmt.limit(pagination.limit) if pagination.limit != None else stmt + payload=session.execute(stmt).all() + + return [ + ResponseMetricsVesselInZonesSchema( + vessel=VesselRepository.map_to_domain(item[0]).model_dump(), + total_time_in_zones=item[1], + ) + for item in payload + ] + + def get_zones_visited( + self, + datetime_range: DatetimeRangeRequest, + pagination: PageParams, + order: OrderByRequest, + category: Optional[str] = None, + ): + payload = [] + with self.session_factory() as session: + stmt = ( + select( + sql_model.Zone, + func.sum(sql_model.Metrics.duration_total).label( + "visiting_duration" + ), + ) + .select_from(sql_model.Metrics) + .join( + sql_model.Zone, + sql_model.Zone.id == sql_model.Metrics.zone_id, + ) + .where( + sql_model.Metrics.timestamp.between( + datetime_range.start_at, datetime_range.end_at + ) + ) + .where(sql_model.Metrics.zone_category == category) + .group_by(sql_model.Zone) + ) + stmt = stmt.offset(pagination.offset) if pagination.offset != None else stmt + if category: + stmt = stmt.where(sql_model.Zone.category == category) + stmt = ( + stmt.order_by(asc("visiting_duration")) + if order.order == OrderByEnum.ascending + else stmt.order_by(desc("visiting_duration")) + ) + stmt = stmt.limit(pagination.limit) if pagination.limit != None else stmt + payload = session.execute(stmt).all() + + return [ + ResponseMetricsZoneVisitedSchema( + zone=ZoneRepository.map_to_domain(item[0]).model_dump(), + visiting_duration=item[1], + ) + for item in payload + ] + def getVesselsAtSea(self, datetime_range: DatetimeRangeRequest, ): @@ -95,7 +207,7 @@ def getVesselsAtSea(self, ) return session.execute(stmt).scalar() - + def getZoneVisited(self, datetime_range: DatetimeRangeRequest, pagination: PageParams, @@ -137,7 +249,8 @@ def getZoneVisited(self, visiting_duration=item[1] )\ for item in payload] - + + def getZoneVisitingTimeByVessel(self, zone_id: int, datetime_range: DatetimeRangeRequest, @@ -145,7 +258,7 @@ def getZoneVisitingTimeByVessel(self, pagination: PageParams,): payload=[] with self.session_factory() as session: - + stmt=select( sql_model.Zone, sql_model.Vessel, @@ -166,7 +279,8 @@ def getZoneVisitingTimeByVessel(self, ) )\ .group_by(sql_model.Zone.id,sql_model.Vessel.id) - + + stmt = stmt.order_by(func.sum(sql_model.Segment.segment_duration).asc())\ if order.order == OrderByEnum.ascending \ else stmt.order_by(func.sum(sql_model.Segment.segment_duration).desc()) @@ -185,7 +299,8 @@ def getZoneVisitingTimeByVessel(self, zone_visiting_time_by_vessel=item[2] )\ for item in payload] - + + def getVesselVisitingTimeByZone(self, order: OrderByRequest, datetime_range: DatetimeRangeRequest, @@ -226,14 +341,13 @@ def getVesselVisitingTimeByZone(self, stmt = stmt.limit(pagination.limit) if pagination.limit != None else stmt if vessel_id is not None: stmt=stmt.where(sql_model.Vessel.id==vessel_id) - - + + return [ResponseMetricsVesselVisitingTimeByZoneSchema( vessel=VesselListView(**VesselRepository.map_to_domain(model[0]).model_dump()), zone=ZoneListView(**ZoneRepository.map_to_domain(model[1]).model_dump()), vessel_visiting_time_by_zone=model[2]) for model in session.execute(stmt).all()] - def getVesselVisitsByActivityType(self, vessel_id: int, activity_type: TotalTimeActivityTypeRequest, @@ -261,10 +375,11 @@ def getVesselVisitsByActivityType(self, literal_column('0 seconds'), )) payload=session.execute(stmt.limit(1)).scalar_one_or_none() - + + return [ ResponseMetricsVesselTotalTimeActivityByActivityTypeSchema( vessel_id=item.id, activity=item.activity, total_activity_time=item.total_activity_time, ) for item in payload] - \ No newline at end of file + diff --git a/frontend/app/dashboard/page.tsx b/frontend/app/dashboard/page.tsx index 95bd06e8..46457ba5 100644 --- a/frontend/app/dashboard/page.tsx +++ b/frontend/app/dashboard/page.tsx @@ -1,11 +1,17 @@ -"use client" +"use client"; + +import { useMemo, useState } from "react"; +import { useDashboardData } from "@/services/dashboard.service"; + + + +import { getDateRange } from "@/libs/dateUtils"; +import DashboardHeader from "@/components/dashboard/dashboard-header"; +import DashboardOverview from "@/components/dashboard/dashboard-overview"; + + -import { useMemo, useState } from "react" -import { useDashboardData } from "@/services/dashboard.service" -import { getDateRange } from "@/libs/dateUtils" -import DashboardHeader from "@/components/dashboard/dashboard-header" -import DashboardOverview from "@/components/dashboard/dashboard-overview" export default function DashboardPage() { const [selectedDays, setSelectedDays] = useState(7) @@ -14,7 +20,7 @@ export default function DashboardPage() { }, [selectedDays]) const { - topVesselsInActivity, + topVesselsInMpas, topAmpsVisited, totalVesselsInActivity, totalAmpsVisited, @@ -31,7 +37,7 @@ export default function DashboardPage() {