added experiment api
This commit is contained in:
569
dockerfile_build/src/api_endpoint/experiment_api.py
Normal file
569
dockerfile_build/src/api_endpoint/experiment_api.py
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from connections.db import get_db
|
||||||
|
from connections.keycloak import (
|
||||||
|
get_current_token_payload,
|
||||||
|
)
|
||||||
|
from crud.experiment_crud import (
|
||||||
|
create_experiment,
|
||||||
|
create_experiment_type,
|
||||||
|
create_instance,
|
||||||
|
delete_experiment,
|
||||||
|
delete_instance,
|
||||||
|
get_all_experiment_types,
|
||||||
|
get_experiment_instances,
|
||||||
|
get_single_experiment,
|
||||||
|
get_single_instance,
|
||||||
|
get_user_experiments,
|
||||||
|
set_simulation_result,
|
||||||
|
update_experiment,
|
||||||
|
update_instance,
|
||||||
|
)
|
||||||
|
from crud.team_crud import check_team_permission
|
||||||
|
from fastapi import Depends, Query
|
||||||
|
from fastapi.routing import APIRouter
|
||||||
|
from rest_models.experiment_models import (
|
||||||
|
CreateExperimentRequest,
|
||||||
|
CreateExperimentResponse,
|
||||||
|
CreateExperimentTypeRequest,
|
||||||
|
CreateExperimentTypeResponse,
|
||||||
|
CreateInstanceRequest,
|
||||||
|
CreateInstanceResponse,
|
||||||
|
ExperimentData,
|
||||||
|
ExperimentListResponse,
|
||||||
|
ExperimentTypeData,
|
||||||
|
InstanceData,
|
||||||
|
InstanceListResponse,
|
||||||
|
StartExperimentRequest,
|
||||||
|
StartExperimentResponse,
|
||||||
|
UpdateExperimentRequest,
|
||||||
|
UpdateExperimentResponse,
|
||||||
|
UpdateInstanceRequest,
|
||||||
|
UpdateInstanceResponse,
|
||||||
|
)
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
router = APIRouter(tags=["Experiments"])
|
||||||
|
|
||||||
|
|
||||||
|
# ============= EXPERIMENT TYPE ENDPOINTS =============
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/types", response_model=List[ExperimentTypeData])
|
||||||
|
async def get_experiment_types_request(
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> List[ExperimentTypeData]:
|
||||||
|
"""Get all existing experiment types"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment_types = await get_all_experiment_types(db)
|
||||||
|
|
||||||
|
return [
|
||||||
|
ExperimentTypeData(
|
||||||
|
id=exp_type.id,
|
||||||
|
name=exp_type.name,
|
||||||
|
description=exp_type.description,
|
||||||
|
file_frontend=exp_type.file_frontend,
|
||||||
|
server_path=exp_type.server_path,
|
||||||
|
file_comp_system=exp_type.file_comp_system,
|
||||||
|
)
|
||||||
|
for exp_type in experiment_types
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/types", response_model=CreateExperimentTypeResponse)
|
||||||
|
async def create_experiment_type_request(
|
||||||
|
create_data: CreateExperimentTypeRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> CreateExperimentTypeResponse:
|
||||||
|
"""Create a new experiment type (admin only)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# TODO: Add admin permission check here if needed
|
||||||
|
|
||||||
|
experiment_type = await create_experiment_type(
|
||||||
|
db=db,
|
||||||
|
name=create_data.name,
|
||||||
|
file_frontend=create_data.file_frontend,
|
||||||
|
server_path=create_data.server_path,
|
||||||
|
file_comp_system=create_data.file_comp_system,
|
||||||
|
description=create_data.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CreateExperimentTypeResponse(
|
||||||
|
id=experiment_type.id,
|
||||||
|
name=experiment_type.name,
|
||||||
|
description=experiment_type.description,
|
||||||
|
file_frontend=experiment_type.file_frontend,
|
||||||
|
server_path=experiment_type.server_path,
|
||||||
|
file_comp_system=experiment_type.file_comp_system,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# ============= EXPERIMENT ENDPOINTS =============
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("", response_model=CreateExperimentResponse)
|
||||||
|
async def create_experiment_request(
|
||||||
|
create_data: CreateExperimentRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> CreateExperimentResponse:
|
||||||
|
"""Create a new experiment"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# Check if user has permission to create experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], create_data.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
experiment = await create_experiment(
|
||||||
|
db=db,
|
||||||
|
team_id=create_data.team_id,
|
||||||
|
experiment_type_id=create_data.experiment_type_id,
|
||||||
|
name=create_data.name,
|
||||||
|
description=create_data.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CreateExperimentResponse(
|
||||||
|
id=experiment.id,
|
||||||
|
team_id=experiment.team_id,
|
||||||
|
experiment_type_id=experiment.experiment_type_id,
|
||||||
|
name=experiment.name,
|
||||||
|
description=experiment.description,
|
||||||
|
created_at=experiment.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("", response_model=ExperimentData)
|
||||||
|
async def get_experiment_request(
|
||||||
|
experiment_id: int = Query(),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> ExperimentData:
|
||||||
|
"""Get a single experiment's data (without instances)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has access to this experiment's team
|
||||||
|
await check_team_permission([], experiment.team_id, db, keycloak_id)
|
||||||
|
|
||||||
|
return ExperimentData(
|
||||||
|
id=experiment.id,
|
||||||
|
team_id=experiment.team_id,
|
||||||
|
team_name=experiment.team.name if experiment.team else None,
|
||||||
|
experiment_type_id=experiment.experiment_type_id,
|
||||||
|
experiment_type_name=experiment.experiment_type.name
|
||||||
|
if experiment.experiment_type
|
||||||
|
else None,
|
||||||
|
name=experiment.name,
|
||||||
|
description=experiment.description,
|
||||||
|
created_at=experiment.created_at,
|
||||||
|
instances_count=len(experiment.instances) if experiment.instances else 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("", response_model=UpdateExperimentResponse)
|
||||||
|
async def update_experiment_request(
|
||||||
|
update_data: UpdateExperimentRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> UpdateExperimentResponse:
|
||||||
|
"""Update experiment data (cannot update type)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, update_data.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to update experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_experiment = await update_experiment(
|
||||||
|
db=db,
|
||||||
|
experiment_id=update_data.experiment_id,
|
||||||
|
name=update_data.name,
|
||||||
|
description=update_data.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
return UpdateExperimentResponse(
|
||||||
|
id=updated_experiment.id,
|
||||||
|
team_id=updated_experiment.team_id,
|
||||||
|
experiment_type_id=updated_experiment.experiment_type_id,
|
||||||
|
name=updated_experiment.name,
|
||||||
|
description=updated_experiment.description,
|
||||||
|
created_at=updated_experiment.created_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/user", response_model=ExperimentListResponse)
|
||||||
|
async def get_user_experiments_request(
|
||||||
|
page_num: int = Query(1, ge=1),
|
||||||
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> ExperimentListResponse:
|
||||||
|
"""Get all experiments accessible to the current user (via teams)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
offset = (page_num - 1) * page_size
|
||||||
|
|
||||||
|
experiments, total_count = await get_user_experiments(
|
||||||
|
db=db, user_id=keycloak_id, offset=offset, limit=page_size
|
||||||
|
)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for experiment in experiments:
|
||||||
|
result.append(
|
||||||
|
ExperimentData(
|
||||||
|
id=experiment.id,
|
||||||
|
team_id=experiment.team_id,
|
||||||
|
team_name=experiment.team.name if experiment.team else None,
|
||||||
|
experiment_type_id=experiment.experiment_type_id,
|
||||||
|
experiment_type_name=experiment.experiment_type.name
|
||||||
|
if experiment.experiment_type
|
||||||
|
else None,
|
||||||
|
name=experiment.name,
|
||||||
|
description=experiment.description,
|
||||||
|
created_at=experiment.created_at,
|
||||||
|
instances_count=len(experiment.instances)
|
||||||
|
if experiment.instances
|
||||||
|
else 0,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ExperimentListResponse(
|
||||||
|
experiments=result,
|
||||||
|
cur_page=page_num,
|
||||||
|
total_experiments=total_count,
|
||||||
|
page_size=page_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("")
|
||||||
|
async def delete_experiment_request(
|
||||||
|
experiment_id: int = Query(...),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
):
|
||||||
|
"""Delete an experiment (cascade will delete instances)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to delete experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
deleted = await delete_experiment(db, experiment_id)
|
||||||
|
|
||||||
|
if not deleted:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
return {"message": "Experiment deleted successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
# ============= INSTANCE ENDPOINTS =============
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/instance", response_model=CreateInstanceResponse)
|
||||||
|
async def create_instance_request(
|
||||||
|
create_data: CreateInstanceRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> CreateInstanceResponse:
|
||||||
|
"""Add an instance to an experiment"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, create_data.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to manage experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
instance = await create_instance(
|
||||||
|
db=db,
|
||||||
|
experiment_id=create_data.experiment_id,
|
||||||
|
instance_data_id=create_data.instance_data_id,
|
||||||
|
name=create_data.name,
|
||||||
|
description=create_data.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
return CreateInstanceResponse(
|
||||||
|
id=instance.id,
|
||||||
|
experiment_id=instance.experiment_id,
|
||||||
|
instance_data_id=instance.instance_data_id,
|
||||||
|
name=instance.name,
|
||||||
|
description=instance.description,
|
||||||
|
simulation_result_id=instance.simulation_result_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.put("/instance", response_model=UpdateInstanceResponse)
|
||||||
|
async def update_instance_request(
|
||||||
|
update_data: UpdateInstanceRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> UpdateInstanceResponse:
|
||||||
|
"""Update instance data (cannot update instance_data_id)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# Get instance to find its experiment
|
||||||
|
instance = await get_single_instance(db, update_data.instance_id)
|
||||||
|
if not instance:
|
||||||
|
raise HTTPException(404, "Instance not found")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, instance.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to manage experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
updated_instance = await update_instance(
|
||||||
|
db=db,
|
||||||
|
instance_id=update_data.instance_id,
|
||||||
|
name=update_data.name,
|
||||||
|
description=update_data.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
return UpdateInstanceResponse(
|
||||||
|
id=updated_instance.id,
|
||||||
|
experiment_id=updated_instance.experiment_id,
|
||||||
|
instance_data_id=updated_instance.instance_data_id,
|
||||||
|
name=updated_instance.name,
|
||||||
|
description=updated_instance.description,
|
||||||
|
simulation_result_id=updated_instance.simulation_result_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/instance", response_model=InstanceListResponse)
|
||||||
|
async def get_experiment_instances_request(
|
||||||
|
experiment_id: int = Query(),
|
||||||
|
page_num: int = Query(1, ge=1),
|
||||||
|
page_size: int = Query(10, ge=1, le=100),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> InstanceListResponse:
|
||||||
|
"""Get all instances of an experiment"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has access to this experiment's team
|
||||||
|
await check_team_permission([], experiment.team_id, db, keycloak_id)
|
||||||
|
|
||||||
|
offset = (page_num - 1) * page_size
|
||||||
|
|
||||||
|
instances, total_count = await get_experiment_instances(
|
||||||
|
db=db, experiment_id=experiment_id, offset=offset, limit=page_size
|
||||||
|
)
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for instance in instances:
|
||||||
|
result.append(
|
||||||
|
InstanceData(
|
||||||
|
id=instance.id,
|
||||||
|
experiment_id=instance.experiment_id,
|
||||||
|
instance_data_id=instance.instance_data_id,
|
||||||
|
name=instance.name,
|
||||||
|
description=instance.description,
|
||||||
|
simulation_result_id=instance.simulation_result_id,
|
||||||
|
simulation_status=instance.simulation_result.status.name
|
||||||
|
if instance.simulation_result and instance.simulation_result.status
|
||||||
|
else None,
|
||||||
|
simulation_started_at=instance.simulation_result.started_at
|
||||||
|
if instance.simulation_result
|
||||||
|
else None,
|
||||||
|
simulation_ended_at=instance.simulation_result.ended_at
|
||||||
|
if instance.simulation_result
|
||||||
|
else None,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return InstanceListResponse(
|
||||||
|
instances=result,
|
||||||
|
cur_page=page_num,
|
||||||
|
total_instances=total_count,
|
||||||
|
page_size=page_size,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.delete("/instance")
|
||||||
|
async def delete_instance_request(
|
||||||
|
instance_id: int = Query(...),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
):
|
||||||
|
"""Remove an instance from an experiment"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# Get instance to find its experiment
|
||||||
|
instance = await get_single_instance(db, instance_id)
|
||||||
|
if not instance:
|
||||||
|
raise HTTPException(404, "Instance not found")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, instance.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to manage experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["manage_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
deleted = await delete_instance(db, instance_id)
|
||||||
|
|
||||||
|
if not deleted:
|
||||||
|
raise HTTPException(404, "Instance not found")
|
||||||
|
|
||||||
|
return {"message": "Instance deleted successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
# ============= SIMULATION ENDPOINTS =============
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/start", response_model=StartExperimentResponse)
|
||||||
|
async def start_experiment_request(
|
||||||
|
start_data: StartExperimentRequest,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> StartExperimentResponse:
|
||||||
|
"""Start an experiment (create simulation result for an instance)"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# Get instance to find its experiment
|
||||||
|
instance = await get_single_instance(db, start_data.instance_id)
|
||||||
|
if not instance:
|
||||||
|
raise HTTPException(404, "Instance not found")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, instance.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has permission to run experiments in this team
|
||||||
|
await check_team_permission(
|
||||||
|
["run_experiments"], experiment.team_id, db, keycloak_id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create or update simulation result
|
||||||
|
simulation_result = await set_simulation_result(
|
||||||
|
db=db,
|
||||||
|
comp_system_id=start_data.comp_system_id,
|
||||||
|
simulation_result_id=start_data.simulation_result_id,
|
||||||
|
status_name="PENDING", # Start with PENDING status
|
||||||
|
started_at=datetime.now(),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Link simulation result to instance if not already linked
|
||||||
|
if not instance.simulation_result_id:
|
||||||
|
await update_instance(
|
||||||
|
db=db,
|
||||||
|
instance_id=instance.id,
|
||||||
|
simulation_result_id=simulation_result.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
return StartExperimentResponse(
|
||||||
|
instance_id=instance.id,
|
||||||
|
simulation_result_id=simulation_result.id,
|
||||||
|
status="PENDING",
|
||||||
|
started_at=simulation_result.started_at,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/instance", response_model=InstanceData)
|
||||||
|
async def get_instance_request(
|
||||||
|
instance_id: int = Query(),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
payload: dict = Depends(get_current_token_payload),
|
||||||
|
) -> InstanceData:
|
||||||
|
"""Get a single instance's data by ID"""
|
||||||
|
keycloak_id = payload.get("sub")
|
||||||
|
|
||||||
|
if not keycloak_id:
|
||||||
|
raise HTTPException(403, "Permission denied")
|
||||||
|
|
||||||
|
# Get instance with all relationships
|
||||||
|
instance = await get_single_instance(db, instance_id)
|
||||||
|
if not instance:
|
||||||
|
raise HTTPException(404, "Instance not found")
|
||||||
|
|
||||||
|
experiment = await get_single_experiment(db, instance.experiment_id)
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(404, "Experiment not found")
|
||||||
|
|
||||||
|
# Check if user has access to this experiment's team
|
||||||
|
await check_team_permission([], experiment.team_id, db, keycloak_id)
|
||||||
|
|
||||||
|
return InstanceData(
|
||||||
|
id=instance.id,
|
||||||
|
experiment_id=instance.experiment_id,
|
||||||
|
instance_data_id=instance.instance_data_id,
|
||||||
|
name=instance.name,
|
||||||
|
description=instance.description,
|
||||||
|
simulation_result_id=instance.simulation_result_id,
|
||||||
|
simulation_status=instance.simulation_result.status.name
|
||||||
|
if instance.simulation_result and instance.simulation_result.status
|
||||||
|
else None,
|
||||||
|
simulation_started_at=instance.simulation_result.started_at
|
||||||
|
if instance.simulation_result
|
||||||
|
else None,
|
||||||
|
simulation_ended_at=instance.simulation_result.ended_at
|
||||||
|
if instance.simulation_result
|
||||||
|
else None,
|
||||||
|
)
|
||||||
@@ -232,6 +232,9 @@ async def get_team_systems_request(
|
|||||||
created_at = ts.created_at
|
created_at = ts.created_at
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if created_at == 0:
|
||||||
|
raise HTTPException(404, "Team data not found")
|
||||||
|
|
||||||
result.append(
|
result.append(
|
||||||
SystemWithTeamResponse(
|
SystemWithTeamResponse(
|
||||||
system=ComputationalSystemData(
|
system=ComputationalSystemData(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from collections.abc import AsyncIterator
|
from collections.abc import AsyncIterator
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
|
|
||||||
|
from api_endpoint.experiment_api import router as experiment_router
|
||||||
from api_endpoint.health_api import router as health_router
|
from api_endpoint.health_api import router as health_router
|
||||||
from api_endpoint.machine_api import router as machine_router
|
from api_endpoint.machine_api import router as machine_router
|
||||||
from api_endpoint.teams_api import router as team_router
|
from api_endpoint.teams_api import router as team_router
|
||||||
@@ -52,3 +53,4 @@ app.include_router(health_router, prefix="", tags=["Health"])
|
|||||||
app.include_router(user_router, prefix="/user", tags=["User"])
|
app.include_router(user_router, prefix="/user", tags=["User"])
|
||||||
app.include_router(team_router, prefix="/team", tags=["Team"])
|
app.include_router(team_router, prefix="/team", tags=["Team"])
|
||||||
app.include_router(machine_router, prefix="/machine", tags=["Machine"])
|
app.include_router(machine_router, prefix="/machine", tags=["Machine"])
|
||||||
|
app.include_router(experiment_router, prefix="/experiment", tags=["Experiment"])
|
||||||
|
|||||||
453
dockerfile_build/src/crud/experiment_crud.py
Normal file
453
dockerfile_build/src/crud/experiment_crud.py
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from crud.team_crud import get_user_teams
|
||||||
|
from sql_models.models import (
|
||||||
|
Experiment,
|
||||||
|
ExperimentType,
|
||||||
|
Instance,
|
||||||
|
SimulationResult,
|
||||||
|
SimulationStatus,
|
||||||
|
Team,
|
||||||
|
)
|
||||||
|
from sqlalchemy import select
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm import selectinload
|
||||||
|
from sqlalchemy.sql.expression import func
|
||||||
|
from starlette.exceptions import HTTPException
|
||||||
|
|
||||||
|
# ============= HELPER FUNCTIONS =============
|
||||||
|
|
||||||
|
|
||||||
|
async def get_simulation_status_by_name(
|
||||||
|
db: AsyncSession, status_name: str
|
||||||
|
) -> SimulationStatus:
|
||||||
|
"""Get simulation status by name, raise exception if not found"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(SimulationStatus).where(SimulationStatus.name == status_name)
|
||||||
|
)
|
||||||
|
status = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=f"Invalid simulation status name: {status_name}. Valid statuses: PENDING, RUNNING, COMPLETED, FAILED",
|
||||||
|
)
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
|
# ============= EXPERIMENT TYPE CRUD =============
|
||||||
|
|
||||||
|
|
||||||
|
async def get_all_experiment_types(
|
||||||
|
db: AsyncSession,
|
||||||
|
) -> List[ExperimentType]:
|
||||||
|
"""Get all existing experiment types"""
|
||||||
|
result = await db.execute(select(ExperimentType).order_by(ExperimentType.name))
|
||||||
|
return list(result.scalars().all())
|
||||||
|
|
||||||
|
|
||||||
|
async def create_experiment_type(
|
||||||
|
db: AsyncSession,
|
||||||
|
name: str,
|
||||||
|
file_frontend: str,
|
||||||
|
server_path: str,
|
||||||
|
file_comp_system: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
) -> ExperimentType:
|
||||||
|
"""Create a new experiment type"""
|
||||||
|
# Check if experiment type with this name already exists
|
||||||
|
existing_result = await db.execute(
|
||||||
|
select(ExperimentType).where(ExperimentType.name == name)
|
||||||
|
)
|
||||||
|
existing = existing_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if existing:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=409, detail=f"Experiment type with name '{name}' already exists"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create new experiment type
|
||||||
|
experiment_type = ExperimentType(
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
file_frontend=file_frontend,
|
||||||
|
server_path=server_path,
|
||||||
|
file_comp_system=file_comp_system,
|
||||||
|
)
|
||||||
|
db.add(experiment_type)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(experiment_type)
|
||||||
|
|
||||||
|
return experiment_type
|
||||||
|
|
||||||
|
|
||||||
|
# ============= EXPERIMENT CRUD =============
|
||||||
|
|
||||||
|
|
||||||
|
async def create_experiment(
|
||||||
|
db: AsyncSession,
|
||||||
|
team_id: int,
|
||||||
|
experiment_type_id: int,
|
||||||
|
name: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
) -> Experiment:
|
||||||
|
"""Create an experiment of a specific type"""
|
||||||
|
|
||||||
|
team_result = await db.execute(select(Team).where(Team.id == team_id))
|
||||||
|
team = team_result.scalar_one_or_none()
|
||||||
|
if not team:
|
||||||
|
raise HTTPException(status_code=404, detail="Team not found")
|
||||||
|
|
||||||
|
# Verify experiment type exists
|
||||||
|
exp_type_result = await db.execute(
|
||||||
|
select(ExperimentType).where(ExperimentType.id == experiment_type_id)
|
||||||
|
)
|
||||||
|
experiment_type = exp_type_result.scalar_one_or_none()
|
||||||
|
if not experiment_type:
|
||||||
|
raise HTTPException(status_code=404, detail="Experiment type not found")
|
||||||
|
|
||||||
|
# Create experiment
|
||||||
|
experiment = Experiment(
|
||||||
|
team_id=team_id,
|
||||||
|
experiment_type_id=experiment_type_id,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
created_at=datetime.now(),
|
||||||
|
)
|
||||||
|
db.add(experiment)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(experiment, attribute_names=["team", "experiment_type"])
|
||||||
|
|
||||||
|
return experiment
|
||||||
|
|
||||||
|
|
||||||
|
async def update_experiment(
|
||||||
|
db: AsyncSession,
|
||||||
|
experiment_id: int,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
) -> Experiment:
|
||||||
|
"""Update experiment data (cannot update type)"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(Experiment)
|
||||||
|
.where(Experiment.id == experiment_id)
|
||||||
|
.options(
|
||||||
|
selectinload(Experiment.team), selectinload(Experiment.experiment_type)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
experiment = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(status_code=404, detail="Experiment not found")
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
experiment.name = name
|
||||||
|
|
||||||
|
if description is not None:
|
||||||
|
experiment.description = description
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(experiment, attribute_names=["team", "experiment_type"])
|
||||||
|
|
||||||
|
return experiment
|
||||||
|
|
||||||
|
|
||||||
|
async def get_user_experiments(
|
||||||
|
db: AsyncSession,
|
||||||
|
user_id: str,
|
||||||
|
offset: int = 0,
|
||||||
|
limit: int = 256,
|
||||||
|
) -> Tuple[List[Experiment], int]:
|
||||||
|
"""Get paginated experiments of user (via user's teams)"""
|
||||||
|
# Get user's team IDs first
|
||||||
|
user_teams_result, _ = await get_user_teams(db, user_id, 0, 255)
|
||||||
|
team_ids = [row.id for row in user_teams_result]
|
||||||
|
|
||||||
|
if not team_ids:
|
||||||
|
return [], 0
|
||||||
|
|
||||||
|
# Query experiments for those teams
|
||||||
|
query = select(Experiment).where(Experiment.team_id.in_(team_ids))
|
||||||
|
|
||||||
|
# Get paginated experiments
|
||||||
|
result = await db.execute(
|
||||||
|
query.options(
|
||||||
|
selectinload(Experiment.team),
|
||||||
|
selectinload(Experiment.experiment_type),
|
||||||
|
selectinload(Experiment.instances),
|
||||||
|
)
|
||||||
|
.order_by(Experiment.created_at.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
experiments = list(result.scalars().all())
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
count_query = (
|
||||||
|
select(func.count())
|
||||||
|
.select_from(Experiment)
|
||||||
|
.where(Experiment.team_id.in_(team_ids))
|
||||||
|
)
|
||||||
|
count_result = await db.execute(count_query)
|
||||||
|
total_count = count_result.scalar()
|
||||||
|
|
||||||
|
return experiments, total_count if total_count else 0
|
||||||
|
|
||||||
|
|
||||||
|
# Helper function to get single experiment (add to experiment_crud.py)
|
||||||
|
async def get_single_experiment(
|
||||||
|
db: AsyncSession,
|
||||||
|
experiment_id: int,
|
||||||
|
) -> Optional[Experiment]:
|
||||||
|
"""Get a single experiment with all relationships loaded"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(Experiment)
|
||||||
|
.where(Experiment.id == experiment_id)
|
||||||
|
.options(
|
||||||
|
selectinload(Experiment.team),
|
||||||
|
selectinload(Experiment.experiment_type),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_experiment(
|
||||||
|
db: AsyncSession,
|
||||||
|
experiment_id: int,
|
||||||
|
) -> bool:
|
||||||
|
"""Delete an experiment (cascade will delete instances)"""
|
||||||
|
result = await db.execute(select(Experiment).where(Experiment.id == experiment_id))
|
||||||
|
experiment = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not experiment:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await db.delete(experiment)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def create_instance(
|
||||||
|
db: AsyncSession,
|
||||||
|
experiment_id: int,
|
||||||
|
instance_data_id: int,
|
||||||
|
name: str,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
) -> Instance:
|
||||||
|
"""Create an instance for an experiment"""
|
||||||
|
# Verify experiment exists
|
||||||
|
experiment_result = await db.execute(
|
||||||
|
select(Experiment).where(Experiment.id == experiment_id)
|
||||||
|
)
|
||||||
|
experiment = experiment_result.scalar_one_or_none()
|
||||||
|
if not experiment:
|
||||||
|
raise HTTPException(status_code=404, detail="Experiment not found")
|
||||||
|
|
||||||
|
# Create instance
|
||||||
|
instance = Instance(
|
||||||
|
experiment_id=experiment_id,
|
||||||
|
instance_data_id=instance_data_id,
|
||||||
|
name=name,
|
||||||
|
description=description,
|
||||||
|
)
|
||||||
|
db.add(instance)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(instance, attribute_names=["experiment", "simulation_result"])
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
async def update_instance(
|
||||||
|
db: AsyncSession,
|
||||||
|
instance_id: int,
|
||||||
|
name: Optional[str] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
simulation_result_id: Optional[int] = None,
|
||||||
|
) -> Instance:
|
||||||
|
"""Update instance data (cannot update instance_data_id)"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(Instance)
|
||||||
|
.where(Instance.id == instance_id)
|
||||||
|
.options(
|
||||||
|
selectinload(Instance.experiment), selectinload(Instance.simulation_result)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
instance = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not instance:
|
||||||
|
raise HTTPException(status_code=404, detail="Instance not found")
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
instance.name = name
|
||||||
|
|
||||||
|
if description is not None:
|
||||||
|
instance.description = description
|
||||||
|
|
||||||
|
if simulation_result_id is not None:
|
||||||
|
# Verify simulation result exists
|
||||||
|
sim_result_result = await db.execute(
|
||||||
|
select(SimulationResult).where(SimulationResult.id == simulation_result_id)
|
||||||
|
)
|
||||||
|
if not sim_result_result.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=404, detail="Simulation result not found")
|
||||||
|
instance.simulation_result_id = simulation_result_id
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(instance, attribute_names=["experiment", "simulation_result"])
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
|
async def get_experiment_instances(
|
||||||
|
db: AsyncSession,
|
||||||
|
experiment_id: int,
|
||||||
|
offset: int = 0,
|
||||||
|
limit: int = 256,
|
||||||
|
) -> Tuple[List[Instance], int]:
|
||||||
|
"""Get paginated instances of an experiment"""
|
||||||
|
# Verify experiment exists
|
||||||
|
experiment_result = await db.execute(
|
||||||
|
select(Experiment).where(Experiment.id == experiment_id)
|
||||||
|
)
|
||||||
|
if not experiment_result.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=404, detail="Experiment not found")
|
||||||
|
|
||||||
|
# Query instances
|
||||||
|
query = select(Instance).where(Instance.experiment_id == experiment_id)
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
query.options(
|
||||||
|
selectinload(Instance.experiment),
|
||||||
|
selectinload(Instance.simulation_result).selectinload(
|
||||||
|
SimulationResult.status
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.order_by(Instance.id.desc())
|
||||||
|
.offset(offset)
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
instances = list(result.scalars().all())
|
||||||
|
|
||||||
|
# Get total count
|
||||||
|
count_query = (
|
||||||
|
select(func.count())
|
||||||
|
.select_from(Instance)
|
||||||
|
.where(Instance.experiment_id == experiment_id)
|
||||||
|
)
|
||||||
|
count_result = await db.execute(count_query)
|
||||||
|
total_count = count_result.scalar()
|
||||||
|
|
||||||
|
return instances, total_count if total_count else 0
|
||||||
|
|
||||||
|
|
||||||
|
async def get_single_instance(
|
||||||
|
db: AsyncSession,
|
||||||
|
instance_id: int,
|
||||||
|
) -> Optional[Instance]:
|
||||||
|
"""Get a single instance's data with all relationships loaded"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(Instance)
|
||||||
|
.where(Instance.id == instance_id)
|
||||||
|
.options(
|
||||||
|
selectinload(Instance.experiment).selectinload(Experiment.experiment_type),
|
||||||
|
selectinload(Instance.simulation_result).selectinload(
|
||||||
|
SimulationResult.status
|
||||||
|
),
|
||||||
|
selectinload(Instance.simulation_result).selectinload(
|
||||||
|
SimulationResult.computational_system
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
|
|
||||||
|
|
||||||
|
async def delete_instance(
|
||||||
|
db: AsyncSession,
|
||||||
|
instance_id: int,
|
||||||
|
) -> bool:
|
||||||
|
"""Delete an instance from an experiment"""
|
||||||
|
result = await db.execute(select(Instance).where(Instance.id == instance_id))
|
||||||
|
instance = result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if not instance:
|
||||||
|
return False
|
||||||
|
|
||||||
|
await db.delete(instance)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
# ============= SIMULATION RESULT CRUD =============
|
||||||
|
|
||||||
|
|
||||||
|
async def set_simulation_result(
|
||||||
|
db: AsyncSession,
|
||||||
|
comp_system_id: int,
|
||||||
|
simulation_result_id: int,
|
||||||
|
status_name: str,
|
||||||
|
started_at: Optional[datetime] = None,
|
||||||
|
ended_at: Optional[datetime] = None,
|
||||||
|
) -> SimulationResult:
|
||||||
|
"""Create or update simulation result"""
|
||||||
|
# Verify computational system exists
|
||||||
|
from sql_models.models import ComputationalSystem
|
||||||
|
|
||||||
|
system_result = await db.execute(
|
||||||
|
select(ComputationalSystem).where(ComputationalSystem.id == comp_system_id)
|
||||||
|
)
|
||||||
|
if not system_result.scalar_one_or_none():
|
||||||
|
raise HTTPException(status_code=404, detail="Computational system not found")
|
||||||
|
|
||||||
|
# Get status
|
||||||
|
status = await get_simulation_status_by_name(db, status_name)
|
||||||
|
|
||||||
|
# Check if simulation result already exists
|
||||||
|
existing_result = await db.execute(
|
||||||
|
select(SimulationResult).where(
|
||||||
|
SimulationResult.simulation_result_id == simulation_result_id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
simulation_result = existing_result.scalar_one_or_none()
|
||||||
|
|
||||||
|
if simulation_result:
|
||||||
|
# Update existing
|
||||||
|
simulation_result.comp_system_id = comp_system_id
|
||||||
|
simulation_result.status_id = status.id
|
||||||
|
simulation_result.started_at = started_at or simulation_result.started_at
|
||||||
|
simulation_result.ended_at = ended_at
|
||||||
|
else:
|
||||||
|
# Create new
|
||||||
|
simulation_result = SimulationResult(
|
||||||
|
comp_system_id=comp_system_id,
|
||||||
|
simulation_result_id=simulation_result_id,
|
||||||
|
status_id=status.id,
|
||||||
|
started_at=started_at or datetime.now(),
|
||||||
|
ended_at=ended_at,
|
||||||
|
)
|
||||||
|
db.add(simulation_result)
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(
|
||||||
|
simulation_result, attribute_names=["computational_system", "status"]
|
||||||
|
)
|
||||||
|
|
||||||
|
return simulation_result
|
||||||
|
|
||||||
|
|
||||||
|
async def get_simulation_result_by_id(
|
||||||
|
db: AsyncSession,
|
||||||
|
simulation_result_id: int,
|
||||||
|
) -> Optional[SimulationResult]:
|
||||||
|
"""Get a simulation result by its external ID"""
|
||||||
|
result = await db.execute(
|
||||||
|
select(SimulationResult)
|
||||||
|
.where(SimulationResult.simulation_result_id == simulation_result_id)
|
||||||
|
.options(
|
||||||
|
selectinload(SimulationResult.computational_system),
|
||||||
|
selectinload(SimulationResult.status),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return result.scalar_one_or_none()
|
||||||
171
dockerfile_build/src/rest_models/experiment_models.py
Normal file
171
dockerfile_build/src/rest_models/experiment_models.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
# ============= EXPERIMENT TYPE MODELS =============
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentTypeData(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
file_frontend: str
|
||||||
|
server_path: str
|
||||||
|
file_comp_system: str
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExperimentTypeRequest(BaseModel):
|
||||||
|
name: str
|
||||||
|
file_frontend: str
|
||||||
|
server_path: str
|
||||||
|
file_comp_system: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExperimentTypeResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
file_frontend: str
|
||||||
|
server_path: str
|
||||||
|
file_comp_system: str
|
||||||
|
|
||||||
|
|
||||||
|
# ============= EXPERIMENT MODELS =============
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExperimentRequest(BaseModel):
|
||||||
|
team_id: int
|
||||||
|
experiment_type_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CreateExperimentResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
team_id: int
|
||||||
|
experiment_type_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateExperimentRequest(BaseModel):
|
||||||
|
experiment_id: int
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateExperimentResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
team_id: int
|
||||||
|
experiment_type_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
created_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentData(BaseModel):
|
||||||
|
id: int
|
||||||
|
team_id: int
|
||||||
|
team_name: Optional[str] = None
|
||||||
|
experiment_type_id: int
|
||||||
|
experiment_type_name: Optional[str] = None
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
created_at: datetime
|
||||||
|
instances_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
|
class ExperimentListResponse(BaseModel):
|
||||||
|
experiments: List[ExperimentData]
|
||||||
|
cur_page: int
|
||||||
|
total_experiments: int
|
||||||
|
page_size: int
|
||||||
|
|
||||||
|
|
||||||
|
# ============= INSTANCE MODELS =============
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInstanceRequest(BaseModel):
|
||||||
|
experiment_id: int
|
||||||
|
instance_data_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class CreateInstanceResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
experiment_id: int
|
||||||
|
instance_data_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
simulation_result_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateInstanceRequest(BaseModel):
|
||||||
|
instance_id: int
|
||||||
|
name: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateInstanceResponse(BaseModel):
|
||||||
|
id: int
|
||||||
|
experiment_id: int
|
||||||
|
instance_data_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
simulation_result_id: Optional[int] = None
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceData(BaseModel):
|
||||||
|
id: int
|
||||||
|
experiment_id: int
|
||||||
|
instance_data_id: int
|
||||||
|
name: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
simulation_result_id: Optional[int] = None
|
||||||
|
simulation_status: Optional[str] = None
|
||||||
|
simulation_started_at: Optional[datetime] = None
|
||||||
|
simulation_ended_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceListResponse(BaseModel):
|
||||||
|
instances: List[InstanceData]
|
||||||
|
cur_page: int
|
||||||
|
total_instances: int
|
||||||
|
page_size: int
|
||||||
|
|
||||||
|
|
||||||
|
# ============= SIMULATION MODELS =============
|
||||||
|
|
||||||
|
|
||||||
|
class StartExperimentRequest(BaseModel):
|
||||||
|
instance_id: int
|
||||||
|
comp_system_id: int
|
||||||
|
simulation_result_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class StartExperimentResponse(BaseModel):
|
||||||
|
instance_id: int
|
||||||
|
simulation_result_id: int
|
||||||
|
status: str
|
||||||
|
started_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
class SetSimulationResultRequest(BaseModel):
|
||||||
|
comp_system_id: int
|
||||||
|
simulation_result_id: int
|
||||||
|
status_name: str
|
||||||
|
started_at: Optional[datetime] = None
|
||||||
|
ended_at: Optional[datetime] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SimulationResultData(BaseModel):
|
||||||
|
id: int
|
||||||
|
simulation_result_id: int
|
||||||
|
comp_system_id: int
|
||||||
|
status: str
|
||||||
|
started_at: datetime
|
||||||
|
ended_at: Optional[datetime] = None
|
||||||
@@ -202,9 +202,6 @@ class User(Base):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Add these new classes before your existing User class
|
|
||||||
|
|
||||||
|
|
||||||
class ExperimentType(Base):
|
class ExperimentType(Base):
|
||||||
__tablename__ = "experiment_types"
|
__tablename__ = "experiment_types"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user