UI-Focused Routers#

Routers primarily serving the web frontend (ops-db-ui).

Transfer Router#

Path: /api/transfer

Purpose: Data transfer pipeline monitoring and recovery

Key endpoints:

  • GET /overview - Transfer statistics (uniform funnel + per-kind counts)

  • GET /raw-packages - List packages with pagination

  • WS /ws/overview - Real-time WebSocket updates

  • GET /failure-history/{operation_type}/{operation_id} - Durable failure trail

  • POST /operation/{kind}/{id}/reset - Reset one operation of a kind (admin)

  • POST /operation/{kind}/reset-all-failed - Reset all FAILED operations of a kind (admin)

  • POST /raw-data-package/{id}/reconcile - Recompute one package’s state (admin)

  • POST /raw-data-package/reconcile-inconsistent - Reconcile the inconsistent set (admin)

  • POST /data-transfer/{id}/reset - Legacy reset shim (#95-safe, admin)

Authentication: GitHub OAuth (JWT); reset/reconcile actions are admin-gated (ADR-0002)

Example — overview reads the Operation chain, not a paired status column:

The overview does not query a per-stage status column off each artifact. It loads the packages, bulk-attaches their operation chain by FK (_attach_operations), and derives the funnel + failure attribution from the operation rows. Failures are charged latest-step-first and keyed by OperationKind.value + the Operation row id:

@router.get("/api/transfer/overview")
async def get_transfer_overview(db: AsyncSession = Depends(get_async_db)):
    # crud.get_transfer_overview:
    #   - COUNT operations per OperationKind -> operation_counts_by_kind
    #   - load non-archived RawDataPackages
    #   - _attach_operations(db, packages): bulk-load the five operation lists
    #     BY FK (no artifact->operation ORM relationship, no N+1)
    #   - classify_transferring_step(pkg): place each in the seven-step funnel
    #   - attribute_failed_package(pkg): charge the failing op,
    #     operation_type = op.operation_kind.value, operation_id = op.id
    return await crud.get_transfer_overview(db)

The WS /ws/overview handler runs outside the per-request Depends chain, so it pulls its own session from websocket.app.state.async_session_factory instead.

Recovery — uniform reset and reconcile:

# Reset one operation of any kind; {kind} is an OperationKind value
@router.post("/api/transfer/operation/{kind}/{operation_id:int}/reset")
async def reset_operation(kind: OperationKind, operation_id: int, ...):
    # status -> PENDING, retry_count++, breadcrumb cache nulled,
    # OperationFailureEvent history preserved (ADR-0001)
    op = await crud.reset_operation(db, kind, operation_id)

# Reconcile a stale state=FAILED package from its op chain (ADR-0004)
@router.post("/api/transfer/raw-data-package/{package_id:int}/reconcile")
async def reconcile_raw_data_package(package_id: int, ...):
    pkg = await crud.reconcile_raw_data_package(db, package_id)

Observing Program Router#

Path: /observing_program

Purpose: Manage observation programs

Key endpoints:

  • GET / - List programs

  • POST / - Create program

  • GET /{id} - Get program details

  • PUT /{id} - Update program

Sources Router#

Path: /sources

Purpose: Astronomical source catalog queries

Key endpoints:

  • GET / - List/search sources

  • GET /{id}/chip - Source card view

  • GET /{id}/details - Detailed info

  • POST /resolve - Name resolution

Features:

  • Cone search by coordinates

  • Name resolution (SIMBAD/NED)

  • Multiple detail levels

Visibility Router#

Path: /visibility

Purpose: Source visibility calculations

Key endpoints:

  • GET /{source_id} - Visibility for source

  • GET /heatmap - Heatmap data

  • POST /precalculate - Trigger precalculation (admin)

Characteristics:

  • Computationally expensive

  • Heavily cached

  • Time-based queries

Next Steps#