Skip to content
107 changes: 90 additions & 17 deletions app/db/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -1248,6 +1248,89 @@ class IonChannelModelToEModel(Base):
)


class IonChannelModelToIonChannelModelSimulationCampaign(Base):
__tablename__ = "ion_channel_model__ion_channel_model_simulation_campaign"

ion_channel_model_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{EntityType.ion_channel_model}.id", ondelete="CASCADE"), primary_key=True
)
ion_channel_model_simulation_campaign_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey(f"{EntityType.ion_channel_model_simulation_campaign}.id", ondelete="CASCADE"),
primary_key=True,
)


class SimulationCampaignBase(
NameDescriptionVectorMixin,
Entity,
):
"""Represents a simulation campaign entity in the database.

A simulation campaign represents the specification of a set of simulations.

it has an asset which is the simulation campaign configuration file.
"""

__abstract__ = True

@declared_attr
@classmethod
def simulations(cls):
return relationship(
"Simulation",
uselist=True,
back_populates="simulation_campaign",
foreign_keys="Simulation.simulation_campaign_id",
)

@declared_attr
@classmethod
def scan_parameters(cls) -> Mapped[JSON_DICT]:
return mapped_column(
default={},
nullable=False,
server_default="{}",
)

__mapper_args__ = { # noqa: RUF012
"polymorphic_on": type,
}


class IonChannelModelSimulationCampaign(
SimulationCampaignBase,
):
"""Represents an ion channel model simulation campaign entity in the database.

An ion channel model simulation campaign represents the specification of a set of
ion channel model simulation tasks.

It has an asset which is the ion channel model simulation campaign configuration file.

Attributes:
id (uuid.UUID): Primary key for the ion channel model simulation campaign,
referencing the entity ID.
"""

__tablename__ = EntityType.ion_channel_model_simulation_campaign.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True)

ion_channel_models: Mapped[list["IonChannelModel"]] = relationship(
"IonChannelModel",
primaryjoin=(
"IonChannelModelSimulationCampaign.id == "
"IonChannelModelToIonChannelModelSimulationCampaign."
"ion_channel_model_simulation_campaign_id"
),
secondary="ion_channel_model__ion_channel_model_simulation_campaign",
)

__mapper_args__ = { # noqa: RUF012
"polymorphic_identity": __tablename__,
"inherit_condition": id == Entity.id,
}


class IonChannelRecordingToIonChannelModelingCampaign(Base):
__tablename__ = "ion_channel_recording__ion_channel_modeling_campaign"

Expand Down Expand Up @@ -1489,8 +1572,7 @@ class CellComposition(NameDescriptionVectorMixin, LocationMixin, SpeciesMixin, E


class SimulationCampaign(
NameDescriptionVectorMixin,
Entity,
SimulationCampaignBase,
):
"""Represents a simulation campaign entity in the database.

Expand All @@ -1507,17 +1589,6 @@ class SimulationCampaign(

entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), index=True)

simulations = relationship(
"Simulation",
uselist=True,
back_populates="simulation_campaign",
foreign_keys="Simulation.simulation_campaign_id",
)
scan_parameters: Mapped[JSON_DICT] = mapped_column(
default={},
nullable=False,
server_default="{}",
)
__mapper_args__ = { # noqa: RUF012
"polymorphic_identity": __tablename__,
"inherit_condition": id == Entity.id,
Expand All @@ -1544,14 +1615,16 @@ class Simulation(Entity, NameDescriptionVectorMixin):
__tablename__ = EntityType.simulation.value
id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), primary_key=True)
simulation_campaign_id: Mapped[uuid.UUID] = mapped_column(
ForeignKey("simulation_campaign.id"), index=True
ForeignKey("simulation_campaign_base.id"), index=True
)
simulation_campaign: Mapped[SimulationCampaign] = relationship(
"SimulationCampaign",
simulation_campaign: Mapped[SimulationCampaignBase] = relationship(
"SimulationCampaignBase",
uselist=False,
foreign_keys=[simulation_campaign_id],
)
entity_id: Mapped[uuid.UUID] = mapped_column(ForeignKey("entity.id"), index=True)
entity_id: Mapped[uuid.UUID | None] = mapped_column(
ForeignKey("entity.id"), index=True, nullable=True
)
entity: Mapped[Entity] = relationship(
"Entity",
uselist=False,
Expand Down
17 changes: 17 additions & 0 deletions app/db/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ class EntityType(StrEnum):
experimental_synapses_per_connection = auto()
external_url = auto()
ion_channel_model = auto()
ion_channel_model_simulation_campaign = auto()
ion_channel_modeling_campaign = auto()
ion_channel_modeling_config = auto()
ion_channel_recording = auto()
Expand Down Expand Up @@ -785,6 +786,22 @@ class LabelRequirements(BaseModel):
)
],
},
EntityType.ion_channel_model_simulation_campaign: {
AssetLabel.campaign_generation_config: [
LabelRequirements(
content_type=ContentType.json,
is_directory=False,
description="Campaign configuration.",
)
],
AssetLabel.campaign_summary: [
LabelRequirements(
content_type=ContentType.json,
is_directory=False,
description="Summary of generated campaign listing all created simulation configs.",
)
],
},
EntityType.simulation_campaign: {
AssetLabel.campaign_generation_config: [
LabelRequirements(
Expand Down
30 changes: 30 additions & 0 deletions app/filters/ion_channel_model_simulation_campaign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from typing import Annotated

from fastapi_filter import with_prefix

from app.db.model import IonChannelModelSimulationCampaign
from app.dependencies.filter import FilterDepends
from app.filters.base import CustomFilter
from app.filters.common import ILikeSearchFilterMixin, NameFilterMixin
from app.filters.entity import EntityFilterMixin
from app.filters.simulation import NestedSimulationFilter


class IonChannelModelSimulationCampaignFilter(
CustomFilter, EntityFilterMixin, NameFilterMixin, ILikeSearchFilterMixin
):
simulation: Annotated[
NestedSimulationFilter | None,
FilterDepends(with_prefix("simulation", NestedSimulationFilter)),
] = None

order_by: list[str] = ["-creation_date"] # noqa: RUF012

class Constants(CustomFilter.Constants):
model = IonChannelModelSimulationCampaign
ordering_model_fields = ["creation_date", "update_date", "name"] # noqa: RUF012


IonChannelModelSimulationCampaignFilterDep = Annotated[
IonChannelModelSimulationCampaignFilter, FilterDepends(IonChannelModelSimulationCampaignFilter)
]
2 changes: 2 additions & 0 deletions app/routers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
external_url,
ion_channel,
ion_channel_model,
ion_channel_model_simulation_campaign,
ion_channel_modeling_campaign,
ion_channel_modeling_config,
ion_channel_modeling_config_generation,
Expand Down Expand Up @@ -124,6 +125,7 @@
external_url.router,
ion_channel.router,
ion_channel_model.router,
ion_channel_model_simulation_campaign.router,
ion_channel_modeling_campaign.router,
ion_channel_modeling_config.router,
ion_channel_modeling_config_generation.router,
Expand Down
20 changes: 20 additions & 0 deletions app/routers/ion_channel_model_simulation_campaign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from fastapi import APIRouter

import app.service.ion_channel_model_simulation_campaign
from app.routers.admin import router as admin_router

ROUTE = "ion-channel-model-simulation-campaign"
router = APIRouter(prefix=f"/{ROUTE}", tags=[ROUTE])

read_many = router.get("")(app.service.ion_channel_model_simulation_campaign.read_many)
read_one = router.get("/{id_}")(app.service.ion_channel_model_simulation_campaign.read_one)
create_one = router.post("")(app.service.ion_channel_model_simulation_campaign.create_one)
update_one = router.patch("/{id_}")(app.service.ion_channel_model_simulation_campaign.update_one)
delete_one = router.delete("/{id_}")(app.service.ion_channel_model_simulation_campaign.delete_one)

admin_read_one = admin_router.get(f"/{ROUTE}/{{id_}}")(
app.service.ion_channel_model_simulation_campaign.admin_read_one
)
admin_update_one = admin_router.patch(f"/{ROUTE}/{{id_}}")(
app.service.ion_channel_model_simulation_campaign.admin_update_one
)
54 changes: 54 additions & 0 deletions app/schemas/ion_channel_model_simulation_campaign.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from pydantic import BaseModel, ConfigDict

from app.db.types import JSON_DICT
from app.schemas.agent import CreatedByUpdatedByMixin
from app.schemas.asset import AssetsMixin
from app.schemas.base import (
AuthorizationMixin,
AuthorizationOptionalPublicMixin,
CreationMixin,
EntityTypeMixin,
IdentifiableMixin,
NameDescriptionMixin,
)
from app.schemas.contribution import ContributionReadWithoutEntityMixin
from app.schemas.ion_channel import NestedIonChannelRead
from app.schemas.utils import make_update_schema


class IonChannelModelSimulationCampaignBase(BaseModel, NameDescriptionMixin):
model_config = ConfigDict(from_attributes=True)
scan_parameters: JSON_DICT


class IonChannelModelSimulationCampaignCreate(
IonChannelModelSimulationCampaignBase, AuthorizationOptionalPublicMixin
):
pass


IonChannelModelSimulationCampaignUserUpdate = make_update_schema(
IonChannelModelSimulationCampaignCreate, "IonChannelModelSimulationCampaignUserUpdate"
) # pyright: ignore [reportInvalidTypeForm]
IonChannelModelSimulationCampaignAdminUpdate = make_update_schema(
IonChannelModelSimulationCampaignCreate,
"IonChannelModelSimulationCampaignAdminUpdate",
excluded_fields=set(),
) # pyright : ignore [reportInvalidTypeForm]


class NestedIonChannelModelSimulationCampaignRead(
IonChannelModelSimulationCampaignBase, EntityTypeMixin, IdentifiableMixin
):
pass


class IonChannelModelSimulationCampaignRead(
NestedIonChannelModelSimulationCampaignRead,
AssetsMixin,
CreatedByUpdatedByMixin,
CreationMixin,
AuthorizationMixin,
ContributionReadWithoutEntityMixin,
):
ion_channel_models: list[NestedIonChannelRead]
Loading
Loading