Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/metrics global endpoints #370

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/bloom/domain/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions backend/bloom/routers/v1/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
147 changes: 131 additions & 16 deletions backend/bloom/services/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__(
Expand Down Expand Up @@ -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,
):
Expand All @@ -95,7 +207,7 @@ def getVesselsAtSea(self,
)
return session.execute(stmt).scalar()


def getZoneVisited(self,
datetime_range: DatetimeRangeRequest,
pagination: PageParams,
Expand Down Expand Up @@ -137,15 +249,16 @@ def getZoneVisited(self,
visiting_duration=item[1]
)\
for item in payload]



def getZoneVisitingTimeByVessel(self,
zone_id: int,
datetime_range: DatetimeRangeRequest,
order: OrderByRequest,
pagination: PageParams,):
payload=[]
with self.session_factory() as session:

stmt=select(
sql_model.Zone,
sql_model.Vessel,
Expand All @@ -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())
Expand All @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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]

26 changes: 16 additions & 10 deletions frontend/app/dashboard/page.tsx
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -14,7 +20,7 @@ export default function DashboardPage() {
}, [selectedDays])

const {
topVesselsInActivity,
topVesselsInMpas,
topAmpsVisited,
totalVesselsInActivity,
totalAmpsVisited,
Expand All @@ -31,15 +37,15 @@ export default function DashboardPage() {

<div className="size-full">
<DashboardOverview
topVesselsInActivity={topVesselsInActivity}
topVesselsInMpas={topVesselsInMpas}
topAmpsVisited={topAmpsVisited}
totalVesselsActive={totalVesselsInActivity}
totalAmpsVisited={totalAmpsVisited}
totalVesselsTracked={totalVesselsTracked}
onDateRangeChange={(value) => {
setSelectedDays(Number(value))
}}
topVesselsInActivityLoading={isLoading.topVesselsInActivity}
topVesselsInMpasLoading={isLoading.topVesselsInMpas}
topAmpsVisitedLoading={isLoading.topAmpsVisited}
totalVesselsActiveLoading={isLoading.totalVesselsInActivity}
totalAmpsVisitedLoading={isLoading.totalAmpsVisited}
Expand All @@ -49,4 +55,4 @@ export default function DashboardPage() {
</div>
</section>
)
}
}
31 changes: 18 additions & 13 deletions frontend/components/dashboard/dashboard-overview.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
"use client"
"use client";

import { TOTAL_AMPS, TOTAL_VESSELS } from "@/constants/totals.constants"
import { TOTAL_AMPS, TOTAL_VESSELS } from "@/constants/totals.constants";

import { Item } from "@/types/item"
import ListCard from "@/components/ui/list-card"
import KPICard from "@/components/dashboard/kpi-card"

import { DateRangeSelector } from "../ui/date-range-selector"

import { Item } from "@/types/item";
import ListCard from "@/components/ui/list-card";
import KPICard from "@/components/dashboard/kpi-card";



import { DateRangeSelector } from "../ui/date-range-selector";


type Props = {
topVesselsInActivity: Item[]
topVesselsInActivityLoading: boolean
topVesselsInMpas: Item[]
topVesselsInMpasLoading: boolean
topAmpsVisited: Item[]
topAmpsVisitedLoading: boolean
totalVesselsActive: number
Expand All @@ -23,8 +28,8 @@ type Props = {
}

export default function DashboardOverview({
topVesselsInActivity,
topVesselsInActivityLoading,
topVesselsInMpas,
topVesselsInMpasLoading,
topAmpsVisited,
topAmpsVisitedLoading,
totalVesselsActive,
Expand Down Expand Up @@ -74,14 +79,14 @@ export default function DashboardOverview({
<ListCard
key="top-vessels-in-activity"
title="Top Vessels visiting MPAs"
items={topVesselsInActivity ?? []}
items={topVesselsInMpas ?? []}
enableViewDetails
loading={topVesselsInActivityLoading}
loading={topVesselsInMpasLoading}
titleClassName="absolute -top-8 xl:-top-10"
/>
</div>
</div>
</div>
</section>
)
}
}
Loading
Loading