diff --git a/src/entitysdk/models/__init__.py b/src/entitysdk/models/__init__.py index 225f1ed1..760fa052 100644 --- a/src/entitysdk/models/__init__.py +++ b/src/entitysdk/models/__init__.py @@ -10,9 +10,11 @@ from entitysdk.models.ion_channel_model import IonChannelModel, NeuronBlock, UseIon from entitysdk.models.license import License from entitysdk.models.memodel import MEModel +from entitysdk.models.memodelcalibrationresult import MEModelCalibrationResult from entitysdk.models.morphology import ReconstructionMorphology from entitysdk.models.mtype import MTypeClass from entitysdk.models.taxonomy import Species, Strain, Taxonomy +from entitysdk.models.validation_result import ValidationResult __all__ = [ "Asset", @@ -24,6 +26,7 @@ "IonChannelModel", "License", "MEModel", + "MEModelCalibrationResult", "MTypeClass", "NeuronBlock", "Organization", @@ -34,4 +37,5 @@ "Strain", "Taxonomy", "UseIon", + "ValidationResult", ] diff --git a/src/entitysdk/models/memodel.py b/src/entitysdk/models/memodel.py index 1b53ce9a..c59df458 100644 --- a/src/entitysdk/models/memodel.py +++ b/src/entitysdk/models/memodel.py @@ -8,6 +8,7 @@ from entitysdk.models.emodel import EModel from entitysdk.models.entity import Entity from entitysdk.models.etype import ETypeClass +from entitysdk.models.memodelcalibrationresult import MEModelCalibrationResult from entitysdk.models.morphology import ( BrainRegion, License, @@ -93,4 +94,10 @@ class MEModel(Entity): description="The mtype classes of the memodel.", ), ] = None + calibration_result: Annotated[ + MEModelCalibrationResult | None, + Field( + description="The calibration result of the memodel.", + ), + ] = None legacy_id: list[str] | None = None diff --git a/src/entitysdk/models/memodelcalibrationresult.py b/src/entitysdk/models/memodelcalibrationresult.py new file mode 100644 index 00000000..ee633c0d --- /dev/null +++ b/src/entitysdk/models/memodelcalibrationresult.py @@ -0,0 +1,42 @@ +"""ME-Model Calibration Result.""" + +from typing import Annotated + +from pydantic import Field + +from entitysdk.models.entity import Entity +from entitysdk.types import ID + + +class MEModelCalibrationResult(Entity): + """ME-Model calibration result.""" + + holding_current: Annotated[ + float, + Field( + description="The holding current to apply to the simulatable neuron, in nA.", + example=-0.016, + ), + ] + threshold_current: Annotated[ + float, + Field( + description="The minimal amount of current needed to make " + "the simulatable neuron spike, in nA.", + example=0.1, + ), + ] + rin: Annotated[ + float | None, + Field( + description="The input resistance of the simulatable neuron, in MOhm.", + example=0.1, + ), + ] = None + calibrated_entity_id: Annotated[ + ID, + Field( + description="ID of the calibrated entity.", + example="85663316-a7ff-4107-9eb9-236de8868c5c", + ), + ] diff --git a/src/entitysdk/models/validation_result.py b/src/entitysdk/models/validation_result.py new file mode 100644 index 00000000..168f50dd --- /dev/null +++ b/src/entitysdk/models/validation_result.py @@ -0,0 +1,34 @@ +"""Validation result.""" + +from typing import Annotated + +from pydantic import Field + +from entitysdk.models.entity import Entity +from entitysdk.types import ID + + +class ValidationResult(Entity): + """Validation result.""" + + passed: Annotated[ + bool, + Field( + description="True if the validation passed, False otherwise.", + example=True, + ), + ] + name: Annotated[ + str, + Field( + description="Name of the validation.", + example="Neuron spiking validation", + ), + ] + validated_entity_id: Annotated[ + ID, + Field( + description="ID of the validated entity.", + example="85663316-a7ff-4107-9eb9-236de8868c5c", + ), + ] diff --git a/src/entitysdk/route.py b/src/entitysdk/route.py index 76900908..daf32966 100644 --- a/src/entitysdk/route.py +++ b/src/entitysdk/route.py @@ -11,8 +11,13 @@ "BrainRegion": "brain-region", "Contribution": "contribution", "ElectricalCellRecording": "electrical-cell-recording", + "EModel": "emodel", "Entity": "entity", + "Ion": "ion", + "IonChannelModel": "ion-channel-model", "License": "license", + "MEModel": "memodel", + "MEModelCalibrationResult": "memodel-calibration-result", "MTypeClass": "mtype", "Organization": "organization", "Person": "person", @@ -21,10 +26,7 @@ "Species": "species", "Strain": "strain", "Taxonomy": "taxonomy", - "IonChannelModel": "ion-channel-model", - "Ion": "ion", - "EModel": "emodel", - "MEModel": "memodel", + "ValidationResult": "validation-result", } diff --git a/tests/integration/test_searching.py b/tests/integration/test_searching.py index ce175501..cf846f7b 100644 --- a/tests/integration/test_searching.py +++ b/tests/integration/test_searching.py @@ -7,6 +7,7 @@ IonChannelModel, License, MEModel, + MEModelCalibrationResult, MTypeClass, Organization, Person, @@ -14,6 +15,7 @@ Role, Species, Strain, + ValidationResult, ) @@ -33,6 +35,8 @@ EModel, MEModel, ElectricalCellRecording, + ValidationResult, + MEModelCalibrationResult, ], ) def test_is_searchable(entity_type, client): diff --git a/tests/unit/models/data/memodel_calibration_result.json b/tests/unit/models/data/memodel_calibration_result.json new file mode 100644 index 00000000..bb86402e --- /dev/null +++ b/tests/unit/models/data/memodel_calibration_result.json @@ -0,0 +1,8 @@ +{ + "authorized_public": false, + "id" : "cfecab84-b37c-4fa0-adaa-86457d660061", + "holding_current" : 0.0, + "threshold_current" : 0.06554153953301577, + "rin" : 100.03205800593804, + "calibrated_entity_id" : "54004e96-cef0-4f1c-8c89-d9cdf7b94e43" +} \ No newline at end of file diff --git a/tests/unit/models/data/validation_result.json b/tests/unit/models/data/validation_result.json new file mode 100644 index 00000000..9c3e72d9 --- /dev/null +++ b/tests/unit/models/data/validation_result.json @@ -0,0 +1,7 @@ +{ + "authorized_public": false, + "id" : "6fa52c8b-e9df-4f10-bdee-07c9af59a5ba", + "passed" : true, + "name" : "Simulatable Neuron Spiking Validation", + "validated_entity_id" : "54004e96-cef0-4f1c-8c89-d9cdf7b94e43" +} \ No newline at end of file diff --git a/tests/unit/models/test_memodel_calibration_result.py b/tests/unit/models/test_memodel_calibration_result.py new file mode 100644 index 00000000..b167ab2a --- /dev/null +++ b/tests/unit/models/test_memodel_calibration_result.py @@ -0,0 +1,42 @@ +import json +from pathlib import Path + +import pytest + +from entitysdk.models import MEModelCalibrationResult + +from ..util import MOCK_UUID + +DATA_DIR = Path(__file__).parent / "data" + +Model = MEModelCalibrationResult + + +@pytest.fixture +def json_data(): + return json.loads(Path(DATA_DIR / "memodel_calibration_result.json").read_bytes()) + + +@pytest.fixture +def model(json_data): + return Model.model_validate(json_data) + + +def test_read(client, httpx_mock, auth_token, json_data): + httpx_mock.add_response(method="GET", json=json_data) + entity = client.get_entity( + entity_id=MOCK_UUID, + entity_type=Model, + token=auth_token, + with_assets=False, + ) + assert entity.model_dump(mode="json", exclude_none=True) == json_data + + +def test_register(client, httpx_mock, auth_token, model, json_data): + httpx_mock.add_response( + method="POST", json=model.model_dump(mode="json") | {"id": str(MOCK_UUID)} + ) + registered = client.register_entity(entity=model, token=auth_token) + expected_json = json_data | {"id": str(MOCK_UUID)} + assert registered.model_dump(mode="json", exclude_none=True) == expected_json diff --git a/tests/unit/models/test_validation_result.py b/tests/unit/models/test_validation_result.py new file mode 100644 index 00000000..dd92d1d7 --- /dev/null +++ b/tests/unit/models/test_validation_result.py @@ -0,0 +1,42 @@ +import json +from pathlib import Path + +import pytest + +from entitysdk.models import ValidationResult + +from ..util import MOCK_UUID + +DATA_DIR = Path(__file__).parent / "data" + +Model = ValidationResult + + +@pytest.fixture +def json_data(): + return json.loads(Path(DATA_DIR / "validation_result.json").read_bytes()) + + +@pytest.fixture +def model(json_data): + return Model.model_validate(json_data) + + +def test_read(client, httpx_mock, auth_token, json_data): + httpx_mock.add_response(method="GET", json=json_data) + entity = client.get_entity( + entity_id=MOCK_UUID, + entity_type=Model, + token=auth_token, + with_assets=False, + ) + assert entity.model_dump(mode="json", exclude_none=True) == json_data + + +def test_register(client, httpx_mock, auth_token, model, json_data): + httpx_mock.add_response( + method="POST", json=model.model_dump(mode="json") | {"id": str(MOCK_UUID)} + ) + registered = client.register_entity(entity=model, token=auth_token) + expected_json = json_data | {"id": str(MOCK_UUID)} + assert registered.model_dump(mode="json", exclude_none=True) == expected_json