Skip to content
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
3 changes: 3 additions & 0 deletions lightserve/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
from .lightcurves import lightcurves_router
from .settings import settings
from .sources import sources_router
from .analysis import analysis_router


app = FastAPI()

Expand All @@ -27,3 +29,4 @@
app.include_router(lightcurves_router)
app.include_router(sources_router)
app.include_router(cutouts_router)
app.include_router(analysis_router)
164 changes: 164 additions & 0 deletions lightserve/api/analysis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""
Endpoints for lightcurve statistics analysis.
"""

from datetime import datetime
from typing import Optional

from fastapi import APIRouter, HTTPException, Request, status, Query
from pydantic import BaseModel

from lightserve.database import AsyncSessionDependency
from lightcurvedb.models.analysis import BandStatistics, BandTimeSeries
from lightcurvedb.client.source import SourceNotFound, source_read
from lightcurvedb.client.band import BandNotFound, band_read
from lightcurvedb.analysis.statistics import get_band_statistics, get_band_timeseries


from .auth import requires

analysis_router = APIRouter(prefix="/analysis")


class BandStatisticsResponse(BaseModel):
"""
Response model for band statistics
"""
source_id: int
band_name: str
statistics: BandStatistics
start_time: datetime | None
end_time: datetime | None
time_resolution: str


class BandTimeSeriesResponse(BaseModel):
"""
Response model for band timeseries
"""
source_id: int
band_name: str
timeseries: BandTimeSeries
time_resolution: str



@analysis_router.get("/aggregate/{source_id}/{band_name}")
@requires("lcs:read")
async def get_source_band_statistics(
request: Request,
source_id: int,
band_name: str,
conn: AsyncSessionDependency,
start_time: Optional[datetime] = Query(
None,
description="Start time for statistics calculation (YYYY-MM-DD)"
),
end_time: Optional[datetime] = Query(
None,
description="End time for statistics calculation (YYYY-MM-DD)"
),
) -> BandStatisticsResponse:
"""
Calculate statistical measures for a specific source and band.

By default, uses continuous aggregate tables.
"""
try:
await source_read(id=source_id, conn=conn)
except SourceNotFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Source {source_id} not found"
)

try:
await band_read(band_name, conn=conn)
except BandNotFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Band '{band_name}' not found"
)

# Validate time range
if start_time and end_time and start_time >= end_time:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="start_time must be before end_time"
)

statistics, bucket_start, bucket_end, time_resolution = await get_band_statistics(
source_id=source_id,
band_name=band_name,
conn=conn,
start_time=start_time,
end_time=end_time
)

return BandStatisticsResponse(
source_id=source_id,
band_name=band_name,
statistics=statistics,
start_time=bucket_start,
end_time=bucket_end,
time_resolution=time_resolution
)


@analysis_router.get("/timeseries/{source_id}/{band_name}")
@requires("lcs:read")
async def get_source_band_timeseries(
request: Request,
source_id: int,
band_name: str,
conn: AsyncSessionDependency,
start_time: Optional[datetime] = Query(
None,
description="Start time for timeseries (YYYY-MM-DD)"
),
end_time: Optional[datetime] = Query(
None,
description="End time for timeseries (YYYY-MM-DD)"
),
) -> BandTimeSeriesResponse:
"""
Get timeseries of per bucket for a specific source and band
"""
try:
await source_read(id=source_id, conn=conn)
except SourceNotFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Source {source_id} not found"
)

try:
await band_read(band_name, conn=conn)
except BandNotFound:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Band '{band_name}' not found"
)

# Validate time range
if start_time and end_time and start_time >= end_time:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="start_time must be before end_time"
)

timeseries, time_resolution = await get_band_timeseries(
source_id=source_id,
band_name=band_name,
conn=conn,
start_time=start_time,
end_time=end_time
)

return BandTimeSeriesResponse(
source_id=source_id,
band_name=band_name,
timeseries=timeseries,
time_resolution=time_resolution
)