temdb-models 2.0.0__tar.gz → 3.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. temdb_models-3.0.0/LICENSE +11 -0
  2. temdb_models-3.0.0/PKG-INFO +12 -0
  3. {temdb_models-2.0.0 → temdb_models-3.0.0}/pyproject.toml +9 -3
  4. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/__init__.py +53 -6
  5. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/acquisition.py +86 -36
  6. temdb_models-3.0.0/src/temdb/models/base.py +9 -0
  7. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/block.py +8 -3
  8. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/cutting_session.py +8 -4
  9. temdb_models-3.0.0/src/temdb/models/dataset.py +46 -0
  10. temdb_models-3.0.0/src/temdb/models/enums.py +27 -0
  11. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/error.py +4 -2
  12. temdb_models-3.0.0/src/temdb/models/lens_correction.py +39 -0
  13. temdb_models-3.0.0/src/temdb/models/microscope.py +35 -0
  14. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/quality_control.py +7 -5
  15. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/roi.py +27 -11
  16. temdb_models-3.0.0/src/temdb/models/section.py +154 -0
  17. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/specimen.py +11 -4
  18. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/substrate.py +20 -14
  19. temdb_models-3.0.0/src/temdb/models/task.py +78 -0
  20. {temdb_models-2.0.0 → temdb_models-3.0.0}/src/temdb/models/tile.py +15 -10
  21. temdb_models-3.0.0/src/temdb/models/utils/uri.py +222 -0
  22. temdb_models-2.0.0/PKG-INFO +0 -6
  23. temdb_models-2.0.0/src/temdb/models/enums.py +0 -27
  24. temdb_models-2.0.0/src/temdb/models/section.py +0 -98
  25. temdb_models-2.0.0/src/temdb/models/task.py +0 -55
  26. {temdb_models-2.0.0 → temdb_models-3.0.0}/.gitignore +0 -0
@@ -0,0 +1,11 @@
1
+ Copyright 2026 Allen Institute
2
+
3
+ Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
4
+
5
+ 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
6
+
7
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
8
+
9
+ 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
10
+
11
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.5
2
+ Name: temdb-models
3
+ Version: 3.0.0
4
+ Summary: Shared Pydantic models for TEMdb
5
+ License-Expression: BSD-3-Clause
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: pathlib-abc>=0.5
9
+ Requires-Dist: platformdirs>=4.10
10
+ Requires-Dist: pydantic>=2.13
11
+ Requires-Dist: pyyaml>=6.0
12
+ Requires-Dist: smart-open[s3]>=7.6
@@ -1,14 +1,20 @@
1
1
  [project]
2
2
  name = "temdb-models"
3
- version = "2.0.0"
3
+ version = "3.0.0"
4
4
  description = "Shared Pydantic models for TEMdb"
5
+ license = "BSD-3-Clause"
6
+ license-files = ["LICENSE"]
5
7
  requires-python = ">=3.10"
6
8
  dependencies = [
7
- "pydantic>=2.0",
9
+ "pathlib-abc>=0.5",
10
+ "platformdirs>=4.10",
11
+ "pydantic>=2.13",
12
+ "pyyaml>=6.0",
13
+ "smart-open[s3]>=7.6",
8
14
  ]
9
15
 
10
16
  [build-system]
11
- requires = ["hatchling"]
17
+ requires = ["hatchling>=1.27"]
12
18
  build-backend = "hatchling.build"
13
19
 
14
20
  [tool.hatch.build.targets.wheel]
@@ -23,12 +23,34 @@ from temdb.models.cutting_session import (
23
23
  CuttingSessionResponse,
24
24
  CuttingSessionUpdate,
25
25
  )
26
+ from temdb.models.dataset import (
27
+ DatasetBase,
28
+ DatasetCreate,
29
+ DatasetResponse,
30
+ DatasetUpdate,
31
+ )
26
32
  from temdb.models.enums import (
27
- AcquisitionStatus,
28
- AcquisitionTaskStatus,
33
+ QC_STATES,
34
+ RUN_STATUSES,
35
+ SECTION_CONDITIONS,
36
+ TASK_KINDS,
37
+ TRANSFER_STATES,
38
+ AcquisitionStatusFilter,
29
39
  SectionQuality,
30
40
  )
31
41
  from temdb.models.error import APIErrorResponse
42
+ from temdb.models.lens_correction import (
43
+ LensCorrectionBase,
44
+ LensCorrectionCreate,
45
+ LensCorrectionResponse,
46
+ LensCorrectionUpdate,
47
+ )
48
+ from temdb.models.microscope import (
49
+ MicroscopeBase,
50
+ MicroscopeCreate,
51
+ MicroscopeResponse,
52
+ MicroscopeUpdate,
53
+ )
32
54
  from temdb.models.quality_control import (
33
55
  AcquisitionFocusScoresResponse,
34
56
  BadFocusTileInfo,
@@ -36,9 +58,9 @@ from temdb.models.quality_control import (
36
58
  TileFocusScore,
37
59
  )
38
60
  from temdb.models.roi import (
39
- ROIBase,
40
61
  ROIChildrenResponse,
41
62
  ROICreate,
63
+ ROIPayload,
42
64
  ROIResponse,
43
65
  ROIUpdate,
44
66
  )
@@ -46,6 +68,7 @@ from temdb.models.section import (
46
68
  SectionBase,
47
69
  SectionCreate,
48
70
  SectioningRunParameters,
71
+ SectionMetric,
49
72
  SectionMetrics,
50
73
  SectionResponse,
51
74
  SectionUpdate,
@@ -78,12 +101,18 @@ from temdb.models.tile import (
78
101
  TileResponse,
79
102
  TileUpdate,
80
103
  )
104
+ from temdb.models.utils.uri import URI
81
105
 
82
106
  __all__ = [
83
107
  # Enums
84
- "AcquisitionStatus",
85
- "AcquisitionTaskStatus",
108
+ "AcquisitionStatusFilter",
86
109
  "SectionQuality",
110
+ # Vocab constants
111
+ "RUN_STATUSES",
112
+ "QC_STATES",
113
+ "TRANSFER_STATES",
114
+ "SECTION_CONDITIONS",
115
+ "TASK_KINDS",
87
116
  # Tile
88
117
  "Matcher",
89
118
  "TileBase",
@@ -113,7 +142,7 @@ __all__ = [
113
142
  "BlockUpdate",
114
143
  "BlockResponse",
115
144
  # ROI
116
- "ROIBase",
145
+ "ROIPayload",
117
146
  "ROICreate",
118
147
  "ROIUpdate",
119
148
  "ROIResponse",
@@ -123,6 +152,7 @@ __all__ = [
123
152
  "SectionCreate",
124
153
  "SectionUpdate",
125
154
  "SectionResponse",
155
+ "SectionMetric",
126
156
  "SectionMetrics",
127
157
  "SectioningRunParameters",
128
158
  # Substrate
@@ -143,6 +173,21 @@ __all__ = [
143
173
  "CuttingSessionCreate",
144
174
  "CuttingSessionUpdate",
145
175
  "CuttingSessionResponse",
176
+ # Dataset
177
+ "DatasetBase",
178
+ "DatasetCreate",
179
+ "DatasetUpdate",
180
+ "DatasetResponse",
181
+ # Microscope
182
+ "MicroscopeBase",
183
+ "MicroscopeCreate",
184
+ "MicroscopeUpdate",
185
+ "MicroscopeResponse",
186
+ # Lens Correction
187
+ "LensCorrectionBase",
188
+ "LensCorrectionCreate",
189
+ "LensCorrectionUpdate",
190
+ "LensCorrectionResponse",
146
191
  # Error
147
192
  "APIErrorResponse",
148
193
  # Quality Control
@@ -150,4 +195,6 @@ __all__ = [
150
195
  "AcquisitionFocusScoresResponse",
151
196
  "BadFocusTileInfo",
152
197
  "BadFocusTilesResponse",
198
+ # Utils
199
+ "URI",
153
200
  ]
@@ -1,13 +1,15 @@
1
+ import uuid
1
2
  from datetime import datetime
2
3
  from typing import Any
3
4
 
4
- from pydantic import BaseModel, ConfigDict, Field
5
- from temdb.models.enums import AcquisitionStatus
5
+ from pydantic import ConfigDict, Field, field_validator
6
+ from pydantic_core import PydanticCustomError
6
7
 
8
+ from temdb.models.base import TEMDBModel
9
+ from temdb.models.enums import QC_STATES, RUN_STATUSES, TASK_KINDS, TRANSFER_STATES
7
10
 
8
- class LensCorrectionModel(BaseModel):
9
- model_config = ConfigDict(extra="allow")
10
11
 
12
+ class LensCorrectionModel(TEMDBModel):
11
13
  id: int = Field(..., description="ID of lens correction model")
12
14
  type: str = Field(
13
15
  ...,
@@ -23,18 +25,14 @@ class LensCorrectionModel(BaseModel):
23
25
  )
24
26
 
25
27
 
26
- class Calibration(BaseModel):
27
- model_config = ConfigDict(extra="allow")
28
-
28
+ class Calibration(TEMDBModel):
29
29
  pixel_size: float = Field(..., description="Pixel size in nm")
30
30
  rotation_angle: float = Field(..., description="Rotation angle in degrees")
31
31
  lens_model: LensCorrectionModel | None = Field(None, description="Lens correction model")
32
32
  aperture_centroid: list[float] | None = Field(None, description="Aperture centroid in stage coordinates in nm")
33
33
 
34
34
 
35
- class HardwareParams(BaseModel):
36
- model_config = ConfigDict(extra="allow")
37
-
35
+ class HardwareParams(TEMDBModel):
38
36
  scope_id: str = Field(..., description="ID of microscope")
39
37
  camera_model: str = Field(..., description="Model of camera")
40
38
  camera_serial: str = Field(..., description="Serial number of camera")
@@ -42,9 +40,7 @@ class HardwareParams(BaseModel):
42
40
  media_type: str = Field(..., description="Type of substrate in microscope")
43
41
 
44
42
 
45
- class AcquisitionParams(BaseModel):
46
- model_config = ConfigDict(extra="allow")
47
-
43
+ class AcquisitionParams(TEMDBModel):
48
44
  magnification: int = Field(..., description="Magnification of acquisition")
49
45
  spot_size: int = Field(..., description="Spot size of acquisition")
50
46
  exposure_time: int = Field(..., description="Exposure time of camera in ms")
@@ -53,9 +49,7 @@ class AcquisitionParams(BaseModel):
53
49
  saved_bit_depth: int = Field(..., description="Bit depth of saved image")
54
50
 
55
51
 
56
- class StorageLocation(BaseModel):
57
- model_config = ConfigDict(extra="allow")
58
-
52
+ class StorageLocation(TEMDBModel):
59
53
  location_type: str = Field(..., description="Type of storage location, e.g. local, s3, etc.")
60
54
  base_path: str = Field(..., description="Base path of storage location")
61
55
  is_current: bool = Field(..., description="Whether this is the current storage location")
@@ -63,24 +57,17 @@ class StorageLocation(BaseModel):
63
57
  metadata: dict[str, Any] = Field(..., description="Metadata of storage location")
64
58
 
65
59
 
66
- class StorageLocationCreate(BaseModel):
67
- model_config = ConfigDict(extra="allow")
68
-
60
+ class StorageLocationCreate(TEMDBModel):
69
61
  location_type: str = Field(..., description="Type of storage location, e.g. local, s3, etc.")
70
62
  base_path: str = Field(..., description="Base path of storage location")
71
63
  metadata: dict[str, Any] = Field(default_factory=dict, description="Metadata of storage location")
72
64
 
73
65
 
74
- class AcquisitionBase(BaseModel):
75
- model_config = ConfigDict(extra="allow")
76
-
66
+ class AcquisitionBase(TEMDBModel):
77
67
  hardware_settings: HardwareParams | None = None
78
68
  acquisition_settings: AcquisitionParams | None = None
79
69
  calibration_info: Calibration | None = None
80
- status: AcquisitionStatus | None = None
81
- tilt_angle: float | None = Field(None, description="Tilt angle of acquisition in degrees")
82
- lens_correction: bool | None = Field(None, description="Whether this acquisition is a lens correction calibration")
83
- end_time: datetime | None = Field(None, description="End time of acquisition")
70
+ tilt_angle_deg: float | None = Field(None, description="Tilt angle of acquisition in degrees")
84
71
  storage_locations: list[StorageLocation] | None = Field(None, description="Storage locations of acquisition")
85
72
  montage_set_name: str | None = Field(None, description="Name of montage set")
86
73
  sub_region: dict[str, int] | None = Field(None, description="Sub region of acquisition")
@@ -90,42 +77,105 @@ class AcquisitionBase(BaseModel):
90
77
  class AcquisitionCreate(AcquisitionBase):
91
78
  acquisition_id: str = Field(..., description="Unique acquisition identifier")
92
79
  montage_id: str = Field(..., description="Montage identifier")
93
- roi_id: str = Field(..., description="ROI identifier")
80
+ roi_id: str | None = Field(None, description="ROI identifier (required for montage runs)")
94
81
  acquisition_task_id: str = Field(..., description="Parent task identifier")
82
+ microscope_id: uuid.UUID = Field(..., description="Microscope that executes this run")
83
+ dataset_id: str | None = Field(None, description="Dataset this acquisition belongs to (UUIDv7)")
84
+ kind: str = Field("montage", description="Run kind: montage or lens_correction")
85
+ lc_id: uuid.UUID | None = Field(None, description="Lens correction applied to this run")
95
86
  hardware_settings: HardwareParams = Field(..., description="Hardware settings of acquisition")
96
87
  acquisition_settings: AcquisitionParams = Field(..., description="Acquisition settings of acquisition")
97
- tilt_angle: float = Field(..., description="Tilt angle of acquisition in degrees")
98
- lens_correction: bool = Field(..., description="Whether this acquisition is a lens correction calibration")
99
- status: AcquisitionStatus = Field(default=AcquisitionStatus.IMAGING, description="Status of acquisition")
100
88
  start_time: datetime | None = Field(None, description="Start time of acquisition (defaults to now if not provided)")
89
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
90
+
91
+ @field_validator("kind")
92
+ @classmethod
93
+ def _kind_vocab(cls, v: str) -> str:
94
+ if v not in TASK_KINDS:
95
+ raise PydanticCustomError("value_error", f"kind must be one of {TASK_KINDS}")
96
+ return v
101
97
 
102
98
 
103
99
  class AcquisitionUpdate(AcquisitionBase):
104
100
  calibration_info: dict[str, Any] | None = Field(None, description="Calibration information of acquisition")
101
+ status: str | None = Field(None, description="Terminal run status (write-once): complete, aborted, or failed")
102
+ end_time: datetime | None = Field(None, description="End time of acquisition (set with terminal status)")
103
+ error_message: str | None = Field(None, description="Failure detail for terminal status")
104
+ qc_state: str | None = Field(None, description="QC axis state")
105
+ transfer_state: str | None = Field(None, description="Transfer axis state")
106
+ updated_by: str | None = Field(None, description="Actor stamped on qc_state/transfer_state changes")
107
+ tile_count: int | None = Field(None, description="Rollup: total tiles")
108
+ avg_focus_score: float | None = Field(None, description="Rollup: mean tile focus score")
109
+ failed_tile_count: int | None = Field(None, description="Rollup: failed tiles")
110
+ median_match_quality: float | None = Field(None, description="Rollup: median tile match quality")
111
+
112
+ @field_validator("status")
113
+ @classmethod
114
+ def _status_terminal(cls, v: str | None) -> str:
115
+ if v not in RUN_STATUSES:
116
+ raise PydanticCustomError("value_error", f"status must be one of {RUN_STATUSES}")
117
+ return v
118
+
119
+ @field_validator("qc_state")
120
+ @classmethod
121
+ def _qc_state_vocab(cls, v: str | None) -> str:
122
+ if v not in QC_STATES:
123
+ raise PydanticCustomError("value_error", f"qc_state must be one of {QC_STATES}")
124
+ return v
125
+
126
+ @field_validator("transfer_state")
127
+ @classmethod
128
+ def _transfer_state_vocab(cls, v: str | None) -> str:
129
+ if v not in TRANSFER_STATES:
130
+ raise PydanticCustomError("value_error", f"transfer_state must be one of {TRANSFER_STATES}")
131
+ return v
105
132
 
106
133
 
107
134
  class AcquisitionResponse(AcquisitionBase):
135
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
136
+
137
+ id: int = Field(..., description="Internal database ID")
108
138
  acquisition_id: str = Field(..., description="Unique acquisition identifier")
139
+ run_id: uuid.UUID = Field(..., description="DB-minted UUIDv7 execution key")
109
140
  montage_id: str = Field(..., description="Montage identifier")
110
- specimen_id: str = Field(..., description="Specimen identifier")
111
- roi_id: str = Field(..., description="ROI identifier")
141
+ specimen_id: str | None = Field(None, description="Specimen identifier")
142
+ roi_id: str | None = Field(None, description="ROI identifier")
112
143
  acquisition_task_id: str = Field(..., description="Parent task identifier")
144
+ microscope_id: uuid.UUID = Field(..., description="Microscope that executed this run")
145
+ dataset_id: uuid.UUID | None = Field(None, description="Dataset this acquisition belongs to (UUIDv7)")
146
+ kind: str = Field(..., description="Run kind: montage or lens_correction")
147
+ lc_id: uuid.UUID | None = Field(None, description="Lens correction applied to this run")
113
148
  hardware_settings: HardwareParams = Field(..., description="Hardware settings of acquisition")
114
149
  acquisition_settings: AcquisitionParams = Field(..., description="Acquisition settings of acquisition")
115
- status: AcquisitionStatus = Field(..., description="Status of acquisition")
150
+ replaces_acquisition_id: str | None = Field(
151
+ None, description="Business ID (acquisition_id) of acquisition this acquisition replaces"
152
+ )
153
+ status: str | None = Field(None, description="Terminal run status; null while in flight")
154
+ error_message: str | None = Field(None, description="Failure detail for terminal status")
155
+ qc_state: str = Field(..., description="QC axis state")
156
+ qc_state_updated_at: datetime | None = None
157
+ qc_state_updated_by: str | None = None
158
+ transfer_state: str = Field(..., description="Transfer axis state")
159
+ transfer_state_updated_at: datetime | None = None
160
+ transfer_state_updated_by: str | None = None
161
+ tile_count: int | None = None
162
+ avg_focus_score: float | None = None
163
+ failed_tile_count: int | None = None
164
+ median_match_quality: float | None = None
116
165
  start_time: datetime = Field(..., description="Start time of acquisition")
166
+ end_time: datetime | None = Field(None, description="End time of acquisition")
117
167
 
118
168
  created_at: datetime | None = None
119
169
  updated_at: datetime | None = None
120
170
 
121
171
 
122
- class AcquisitionFullMetadata(BaseModel):
172
+ class AcquisitionFullMetadata(TEMDBModel):
123
173
  """Acquisition with complete hierarchy metadata."""
124
174
 
125
- model_config = ConfigDict(extra="allow")
175
+ model_config = ConfigDict(from_attributes=True, extra="ignore", populate_by_name=True)
126
176
 
127
177
  acquisition: AcquisitionResponse
128
- task: dict[str, Any] | None = None
178
+ task: dict[str, Any] | None = Field(None, alias="acquisition_task")
129
179
  roi: dict[str, Any] | None = None
130
180
  section: dict[str, Any] | None = None
131
181
  cutting_session: dict[str, Any] | None = None
@@ -0,0 +1,9 @@
1
+ from pydantic import BaseModel, ConfigDict
2
+
3
+
4
+ class TEMDBModel(BaseModel):
5
+ model_config = ConfigDict(extra="forbid")
6
+
7
+
8
+ class TEMDBResponseModel(BaseModel):
9
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
@@ -1,18 +1,20 @@
1
1
  from datetime import datetime
2
2
  from typing import Any
3
3
 
4
- from pydantic import BaseModel, ConfigDict, Field
4
+ from pydantic import ConfigDict, Field
5
5
 
6
+ from .base import TEMDBModel
6
7
 
7
- class BlockBase(BaseModel):
8
- model_config = ConfigDict(extra="allow")
9
8
 
9
+ class BlockBase(TEMDBModel):
10
10
  microCT_info: dict[str, Any] | None = Field(None, description="MicroCT information of block")
11
+ description: str | None = Field(None, description="Description of block")
11
12
 
12
13
 
13
14
  class BlockCreate(BlockBase):
14
15
  block_id: str = Field(..., description="Unique block identifier")
15
16
  specimen_id: str = Field(..., description="Parent specimen ID")
17
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
16
18
 
17
19
 
18
20
  class BlockUpdate(BlockBase):
@@ -20,6 +22,9 @@ class BlockUpdate(BlockBase):
20
22
 
21
23
 
22
24
  class BlockResponse(BlockBase):
25
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
26
+
27
+ id: int
23
28
  block_id: str = Field(..., description="Unique block identifier")
24
29
  specimen_id: str = Field(..., description="Parent specimen ID")
25
30
 
@@ -1,12 +1,12 @@
1
1
  from datetime import datetime
2
2
 
3
- from pydantic import BaseModel, ConfigDict, Field
3
+ from pydantic import ConfigDict, Field
4
4
 
5
+ from .base import TEMDBModel
5
6
 
6
- class CuttingSessionBase(BaseModel):
7
- """Base cutting session fields."""
8
7
 
9
- model_config = ConfigDict(extra="allow")
8
+ class CuttingSessionBase(TEMDBModel):
9
+ """Base cutting session fields."""
10
10
 
11
11
  start_time: datetime | None = Field(None, description="Time when cutting session started")
12
12
  end_time: datetime | None = Field(None, description="Time when cutting session ended")
@@ -24,6 +24,7 @@ class CuttingSessionCreate(CuttingSessionBase):
24
24
  start_time: datetime = Field(..., description="Time when cutting session started")
25
25
  sectioning_device: str = Field(..., description="Device used for sectioning")
26
26
  media_type: str = Field(..., description="Type of substrate the sections are placed upon")
27
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
27
28
 
28
29
 
29
30
  class CuttingSessionUpdate(CuttingSessionBase):
@@ -36,6 +37,9 @@ class CuttingSessionUpdate(CuttingSessionBase):
36
37
  class CuttingSessionResponse(CuttingSessionBase):
37
38
  """Schema for cutting session API responses."""
38
39
 
40
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
41
+
42
+ id: int
39
43
  cutting_session_id: str = Field(..., description="Unique cutting session identifier")
40
44
  specimen_id: str = Field(..., description="ID of specimen")
41
45
  block_id: str = Field(..., description="ID of block")
@@ -0,0 +1,46 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from typing import Any, Literal
4
+
5
+ from pydantic import ConfigDict, Field
6
+
7
+ from .base import TEMDBModel
8
+
9
+ SizeClass = Literal["small", "medium", "large", "xlarge"]
10
+ DatasetStatus = Literal["collecting", "collected", "archived"]
11
+
12
+
13
+ class DatasetBase(TEMDBModel):
14
+ description: str | None = Field(None, description="Human description of the dataset")
15
+ specimen_id: str | None = Field(None, description="Specimen this dataset belongs to")
16
+ size_class: SizeClass | None = Field(None, description="Planner size class; drives tile partitioning")
17
+ estimated_tile_count: int | None = Field(None, description="Estimated total tiles; resolves size_class when set")
18
+ metadata_json: dict[str, Any] | None = Field(None, description="Free-form metadata")
19
+
20
+
21
+ class DatasetCreate(DatasetBase):
22
+ name: str = Field(..., description="Unique human-readable dataset name")
23
+ parent_dataset_id: uuid.UUID | None = Field(None, description="Optional parent dataset (one level only)")
24
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
25
+
26
+
27
+ class DatasetUpdate(TEMDBModel):
28
+ description: str | None = None
29
+ status: DatasetStatus | None = None
30
+ size_class: SizeClass | None = None
31
+ metadata_json: dict[str, Any] | None = None
32
+
33
+
34
+ class DatasetResponse(DatasetBase):
35
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
36
+
37
+ dataset_id: uuid.UUID = Field(..., description="UUIDv7 primary key")
38
+ name: str = Field(..., description="Unique human-readable dataset name")
39
+ parent_dataset_id: uuid.UUID | None = Field(None, description="Parent dataset if this is a child (one level only)")
40
+ status: DatasetStatus = Field(..., description="Lifecycle status")
41
+ tile_hash_modulus: int | None = Field(None, description="System-managed frozen partition modulus")
42
+ estimated_tile_count: int | None = Field(None, description="Estimated total tiles recorded at creation")
43
+ collected_at: datetime | None = None
44
+ archived_at: datetime | None = None
45
+ created_at: datetime | None = None
46
+ updated_at: datetime | None = None
@@ -0,0 +1,27 @@
1
+ from enum import Enum
2
+ from typing import Literal
3
+
4
+
5
+ class SectionQuality(str, Enum):
6
+ GOOD = "good"
7
+ BROKEN = "broken"
8
+ THIN = "thin"
9
+ THICK = "thick"
10
+ EMPTY = "empty"
11
+
12
+
13
+ class MatchPosition(str, Enum):
14
+ INVALID = "invalid"
15
+ CENTER = "center"
16
+ LEFT = "left"
17
+ TOP = "top"
18
+ RIGHT = "right"
19
+ BOTTOM = "bottom"
20
+
21
+
22
+ RUN_STATUSES = ("complete", "aborted", "failed")
23
+ AcquisitionStatusFilter = Literal["complete", "aborted", "failed", "in_flight"]
24
+ QC_STATES = ("pending", "qc_pass", "qc_fail", "needs_review")
25
+ TRANSFER_STATES = ("not_started", "in_progress", "complete", "error")
26
+ SECTION_CONDITIONS = ("ok", "damaged", "destroyed", "contaminated", "lost")
27
+ TASK_KINDS = ("montage", "lens_correction")
@@ -2,10 +2,12 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
- from pydantic import BaseModel, Field
5
+ from pydantic import Field
6
6
 
7
+ from .base import TEMDBModel
7
8
 
8
- class APIErrorResponse(BaseModel):
9
+
10
+ class APIErrorResponse(TEMDBModel):
9
11
  """Standard structure for API error responses."""
10
12
 
11
13
  detail: str = Field(..., description="Human-readable description of the error.")
@@ -0,0 +1,39 @@
1
+ import uuid
2
+ from datetime import datetime
3
+ from typing import Any
4
+
5
+ from pydantic import ConfigDict, Field
6
+
7
+ from .base import TEMDBModel
8
+
9
+
10
+ class LensCorrectionBase(TEMDBModel):
11
+ microscope_id: uuid.UUID
12
+ magnification: int
13
+ started_at: datetime
14
+ source_run_id: uuid.UUID | None = None
15
+ source_dataset_id: uuid.UUID | None = None
16
+ shared_transform: dict[str, Any] | None = None
17
+ correction_x_uri: str | None = None
18
+ correction_y_uri: str | None = None
19
+ solver_params: dict[str, Any] | None = None
20
+
21
+
22
+ class LensCorrectionCreate(LensCorrectionBase):
23
+ lc_id: uuid.UUID | None = Field(None, description="Optional client-supplied UUIDv7")
24
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
25
+
26
+
27
+ class LensCorrectionUpdate(TEMDBModel):
28
+ shared_transform: dict[str, Any] | None = None
29
+ correction_x_uri: str | None = None
30
+ correction_y_uri: str | None = None
31
+ solver_params: dict[str, Any] | None = None
32
+
33
+
34
+ class LensCorrectionResponse(LensCorrectionBase):
35
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
36
+
37
+ lc_id: uuid.UUID
38
+ created_at: datetime
39
+ updated_at: datetime | None = None
@@ -0,0 +1,35 @@
1
+ import uuid
2
+ from datetime import datetime
3
+
4
+ from pydantic import ConfigDict, Field
5
+
6
+ from .base import TEMDBModel
7
+
8
+
9
+ class MicroscopeBase(TEMDBModel):
10
+ microscope_type: str = Field("TEM", description="Instrument type")
11
+ model: str | None = Field(None, description="Manufacturer model")
12
+ location: str | None = Field(None, description="Physical location")
13
+ notes: str | None = Field(None, description="Free-form notes")
14
+
15
+
16
+ class MicroscopeCreate(MicroscopeBase):
17
+ label: str = Field(..., description="Unique human handle, e.g. 'TEM-01'")
18
+ microscope_id: uuid.UUID | None = Field(None, description="Optional client-supplied UUIDv7")
19
+ created_at: datetime | None = Field(None, description="Creation timestamp; server-generated if omitted")
20
+
21
+
22
+ class MicroscopeUpdate(TEMDBModel):
23
+ microscope_type: str | None = None
24
+ model: str | None = None
25
+ location: str | None = None
26
+ notes: str | None = None
27
+
28
+
29
+ class MicroscopeResponse(MicroscopeBase):
30
+ model_config = ConfigDict(from_attributes=True, extra="ignore")
31
+
32
+ microscope_id: uuid.UUID
33
+ label: str = Field(..., description="Unique human handle, e.g. 'TEM-01'")
34
+ created_at: datetime
35
+ updated_at: datetime | None = None
@@ -1,7 +1,9 @@
1
- from pydantic import BaseModel, Field
1
+ from pydantic import Field
2
2
 
3
+ from .base import TEMDBModel
3
4
 
4
- class TileFocusScore(BaseModel):
5
+
6
+ class TileFocusScore(TEMDBModel):
5
7
  """Represents focus score data for a single tile."""
6
8
 
7
9
  tile_id: str = Field(..., description="Unique ID of the tile.")
@@ -9,7 +11,7 @@ class TileFocusScore(BaseModel):
9
11
  focus_score: float = Field(..., description="Calculated focus score for the tile.")
10
12
 
11
13
 
12
- class AcquisitionFocusScoresResponse(BaseModel):
14
+ class AcquisitionFocusScoresResponse(TEMDBModel):
13
15
  """Response model containing focus scores for an acquisition."""
14
16
 
15
17
  acquisition_id: str = Field(..., description="ID of the acquisition analyzed.")
@@ -22,7 +24,7 @@ class AcquisitionFocusScoresResponse(BaseModel):
22
24
  max_focus: float | None = Field(None, description="Maximum focus score found.")
23
25
 
24
26
 
25
- class BadFocusTileInfo(BaseModel):
27
+ class BadFocusTileInfo(TEMDBModel):
26
28
  """Information about a tile with bad focus."""
27
29
 
28
30
  tile_id: str
@@ -35,7 +37,7 @@ class BadFocusTileInfo(BaseModel):
35
37
  specimen_id: str | None = None
36
38
 
37
39
 
38
- class BadFocusTilesResponse(BaseModel):
40
+ class BadFocusTilesResponse(TEMDBModel):
39
41
  """Response model for bad focus tiles query."""
40
42
 
41
43
  threshold: float