diff --git a/bedhost/cli.py b/bedhost/cli.py index ce5e7c06..044ecc50 100644 --- a/bedhost/cli.py +++ b/bedhost/cli.py @@ -3,6 +3,8 @@ from . import PKG_NAME from ._version import __version__ +CFG_ENV_VARS = ["BEDBASE_CONFIG"] + def build_parser(): """ diff --git a/bedhost/const.py b/bedhost/const.py index adf64362..88d223e1 100644 --- a/bedhost/const.py +++ b/bedhost/const.py @@ -15,6 +15,8 @@ USAGE_RECORD_DAYS = 30 +MAX_BATCH_IDS = 1000 + MAX_FILE_SIZE = 1024 * 1024 * 2 MAX_REGION_NUMBER = 5000000 MIN_REGION_WIDTH = 10 diff --git a/bedhost/data_models.py b/bedhost/data_models.py index e5b45cca..c5849702 100644 --- a/bedhost/data_models.py +++ b/bedhost/data_models.py @@ -84,5 +84,13 @@ class CreateBEDsetRequest(BaseModel): registry_path: str +class BatchBedRequest(BaseModel): + ids: list[str] + + +class CollectionStatsRequest(BaseModel): + ids: list[str] + + class ChromLengthUploadModel(BaseModel): bed_file: Dict[str, int] diff --git a/bedhost/routers/base_api.py b/bedhost/routers/base_api.py index bcbb48ba..fcb9acf7 100644 --- a/bedhost/routers/base_api.py +++ b/bedhost/routers/base_api.py @@ -6,7 +6,12 @@ from platform import python_version -from bbconf import __version__ as bbconf_version +try: + from bbconf import __version__ as bbconf_version +except ImportError: + from importlib.metadata import version + + bbconf_version = version("bbconf") from bbconf.models.base_models import StatsReturn, FileStats, UsageStats from fastapi import APIRouter, Request from geniml import __version__ as geniml_version diff --git a/bedhost/routers/bed_api.py b/bedhost/routers/bed_api.py index b53c9c05..87eb1520 100644 --- a/bedhost/routers/bed_api.py +++ b/bedhost/routers/bed_api.py @@ -36,12 +36,20 @@ from gtars.models import RegionSet from .. import _LOGGER -from ..const import EXAMPLE_BED, MAX_FILE_SIZE, MAX_REGION_NUMBER, MIN_REGION_WIDTH +from ..const import ( + EXAMPLE_BED, + MAX_BATCH_IDS, + MAX_FILE_SIZE, + MAX_REGION_NUMBER, + MIN_REGION_WIDTH, +) from ..data_models import ( CROM_NUMBERS, BaseListResponse, + BatchBedRequest, BedDigest, ChromLengthUploadModel, + CollectionStatsRequest, ) from ..main import bbagent, usage_data, ref_validator from ..helpers import count_requests, test_query_parameter @@ -159,15 +167,51 @@ async def get_bed_files( ) async def get_bed_stats( bed_id: str = BedDigest, + distributions: bool = Query( + True, + description="Include distribution arrays in the response. Set to false to exclude the large distributions blob.", + ), ): try: - return bbagent.bed.get_stats(bed_id) + return bbagent.bed.get_stats(bed_id, distributions=distributions) except BEDFileNotFoundError as _: raise HTTPException( status_code=404, ) +@router.post( + "/batch", + summary="Get metadata for multiple BED records", +) +async def get_bed_batch( + request: BatchBedRequest, +): + """Retrieve metadata for multiple BED files in a single request.""" + if len(request.ids) > MAX_BATCH_IDS: + raise HTTPException( + status_code=400, + detail=f"Too many IDs. Maximum is {MAX_BATCH_IDS}.", + ) + return bbagent.bed.get_batch(request.ids, full=True, distributions=False) + + +@router.post( + "/collection/stats", + summary="Compute ad-hoc collection statistics from a list of BED IDs", +) +async def get_collection_stats( + request: CollectionStatsRequest, +): + """Aggregate distribution statistics across a set of BED files on the fly.""" + if len(request.ids) > MAX_BATCH_IDS: + raise HTTPException( + status_code=400, + detail=f"Too many IDs. Maximum is {MAX_BATCH_IDS}.", + ) + return bbagent.bed.aggregate_collection(request.ids) + + @router.get( "/{bed_id}/metadata/classification", summary="Get classification of single BED file", diff --git a/bedhost/routers/bedset_api.py b/bedhost/routers/bedset_api.py index 0d7a4910..0dff181f 100644 --- a/bedhost/routers/bedset_api.py +++ b/bedhost/routers/bedset_api.py @@ -3,6 +3,7 @@ from bbconf.exceptions import BedSetNotFoundError, BedSetTrackHubLimitError from bbconf.models.bedset_models import ( BedSetBedFiles, + BedSetDistributions, BedSetListResult, BedSetMetadata, BedSetPlots, @@ -109,10 +110,10 @@ async def get_bedset_metadata( @router.get( "/{bedset_id}/metadata/stats", response_model=BedSetStats, - summary="Get stats for a single BEDSET record", - description=f"Example\n bed_id: {EXAMPLE_BEDSET}", + summary="Get scalar stats (mean/sd) for a single BEDSET record", + description=f"Example\n bedset_id: {EXAMPLE_BEDSET}", ) -async def get_bedset_metadata( +async def get_bedset_stats( bedset_id: str, ): try: @@ -121,6 +122,27 @@ async def get_bedset_metadata( raise HTTPException(status_code=404, detail="No records found") +@router.get( + "/{bedset_id}/metadata/distributions", + response_model=BedSetDistributions, + summary="Get distribution statistics for a single BEDSET record", + description=f"Example\n bedset_id: {EXAMPLE_BEDSET}", +) +async def get_bedset_distributions( + bedset_id: str, +): + """Get aggregated distribution statistics from the JSONB column. + + Returns full distribution data (histograms, KDEs, partitions, etc.) + computed across all member BED files. Falls back to wrapping old + scalar stats if distribution data is not yet available. + """ + try: + return bbagent.bedset.get_distributions(bedset_id) + except BedSetNotFoundError as _: + raise HTTPException(status_code=404, detail="No records found") + + @router.get( "/{bedset_id}/bedfiles", response_model=BedSetBedFiles, diff --git a/ui/package.json b/ui/package.json index 72218c77..9d4b0d45 100644 --- a/ui/package.json +++ b/ui/package.json @@ -9,7 +9,9 @@ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "preview": "vite preview", "generate-types": "npx openapi-typescript https://api-dev.bedbase.org/openapi.json -o bedbase-types.d.ts", - "generate-types-local": "npx openapi-typescript http://localhost:8000/openapi.json -o bedbase-types.d.ts" + "generate-types-local": "npx openapi-typescript http://localhost:8000/openapi.json -o bedbase-types.d.ts", + "deploy": "npm run build && wrangler deploy", + "deploy:preview": "npm run build && wrangler dev" }, "dependencies": { "@databio/gtars": "^0.5.3", diff --git a/ui/wrangler.jsonc b/ui/wrangler.jsonc new file mode 100644 index 00000000..bb40743e --- /dev/null +++ b/ui/wrangler.jsonc @@ -0,0 +1,8 @@ +{ + "name": "bedhost-ui", + "compatibility_date": "2026-02-20", + "assets": { + "directory": "./dist", + "not_found_handling": "single-page-application" + } +}