temdb 3.0.0__py3-none-any.whl

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 (51) hide show
  1. temdb/alembic.ini +29 -0
  2. temdb/migrations/env.py +66 -0
  3. temdb/migrations/script.py.mako +28 -0
  4. temdb/migrations/versions/.gitkeep +0 -0
  5. temdb/migrations/versions/20260612_0001_initial_schema.py +542 -0
  6. temdb/migrations/versions/20260717_ec4c96d0bf77_add_description_to_block_table.py +32 -0
  7. temdb/migrations/versions/20260721_44f75ed44d66_updated_at_nullable_no_server_default.py +165 -0
  8. temdb/migrations/versions/20260721_bec991e0d0eb_dataset_status_server_default.py +38 -0
  9. temdb/migrations/versions/20260801_a1d2f3e4b5c6_add_sections_run_parameters.py +67 -0
  10. temdb/server/__init__.py +3 -0
  11. temdb/server/api/__init__.py +0 -0
  12. temdb/server/api/v2/__init__.py +0 -0
  13. temdb/server/api/v2/acquisition.py +1094 -0
  14. temdb/server/api/v2/block.py +228 -0
  15. temdb/server/api/v2/cutting_session.py +229 -0
  16. temdb/server/api/v2/dataset.py +139 -0
  17. temdb/server/api/v2/lens_correction.py +121 -0
  18. temdb/server/api/v2/microscope.py +93 -0
  19. temdb/server/api/v2/quality_control.py +185 -0
  20. temdb/server/api/v2/roi.py +372 -0
  21. temdb/server/api/v2/section.py +457 -0
  22. temdb/server/api/v2/specimen.py +246 -0
  23. temdb/server/api/v2/substrate.py +180 -0
  24. temdb/server/api/v2/tasks.py +508 -0
  25. temdb/server/cli.py +48 -0
  26. temdb/server/config.py +46 -0
  27. temdb/server/database.py +75 -0
  28. temdb/server/dependencies.py +24 -0
  29. temdb/server/exception_handlers.py +109 -0
  30. temdb/server/ids.py +12 -0
  31. temdb/server/main.py +172 -0
  32. temdb/server/sqlmodels/__init__.py +29 -0
  33. temdb/server/sqlmodels/acquisition.py +101 -0
  34. temdb/server/sqlmodels/base.py +41 -0
  35. temdb/server/sqlmodels/block.py +18 -0
  36. temdb/server/sqlmodels/cutting_session.py +27 -0
  37. temdb/server/sqlmodels/dataset.py +34 -0
  38. temdb/server/sqlmodels/lens_correction.py +36 -0
  39. temdb/server/sqlmodels/microscope.py +22 -0
  40. temdb/server/sqlmodels/roi.py +55 -0
  41. temdb/server/sqlmodels/section.py +55 -0
  42. temdb/server/sqlmodels/specimen.py +17 -0
  43. temdb/server/sqlmodels/substrate.py +23 -0
  44. temdb/server/sqlmodels/task.py +60 -0
  45. temdb/server/sqlmodels/tile.py +53 -0
  46. temdb/server/sqlmodels/tile_partition.py +147 -0
  47. temdb-3.0.0.dist-info/METADATA +16 -0
  48. temdb-3.0.0.dist-info/RECORD +51 -0
  49. temdb-3.0.0.dist-info/WHEEL +4 -0
  50. temdb-3.0.0.dist-info/entry_points.txt +3 -0
  51. temdb-3.0.0.dist-info/licenses/LICENSE +11 -0
@@ -0,0 +1,542 @@
1
+ """initial schema
2
+
3
+ Revision ID: 0001
4
+ Revises:
5
+ Create Date: 2026-06-12 04:14:29.040319
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+ from sqlalchemy.dialects import postgresql
14
+
15
+ # revision identifiers, used by Alembic.
16
+ revision: str = "0001"
17
+ down_revision: str | Sequence[str] | None = None
18
+ branch_labels: str | Sequence[str] | None = None
19
+ depends_on: str | Sequence[str] | None = None
20
+
21
+
22
+ def upgrade() -> None:
23
+ """Upgrade schema."""
24
+ op.execute("CREATE EXTENSION IF NOT EXISTS pg_stat_statements")
25
+ # ### commands auto generated by Alembic - please adjust! ###
26
+ op.create_table(
27
+ "microscopes",
28
+ sa.Column("microscope_id", sa.UUID(), server_default=sa.text("uuidv7()"), nullable=False),
29
+ sa.Column("label", sa.String(), nullable=False),
30
+ sa.Column("microscope_type", sa.String(), server_default=sa.text("'TEM'"), nullable=False),
31
+ sa.Column("model", sa.String(), nullable=True),
32
+ sa.Column("location", sa.String(), nullable=True),
33
+ sa.Column("notes", sa.String(), nullable=True),
34
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
35
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
36
+ sa.PrimaryKeyConstraint("microscope_id", name=op.f("pk_microscopes")),
37
+ sa.UniqueConstraint("label", name=op.f("uq_microscopes_label")),
38
+ )
39
+ op.create_table(
40
+ "specimens",
41
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
42
+ sa.Column("specimen_id", sa.String(), nullable=False),
43
+ sa.Column("description", sa.String(), nullable=True),
44
+ sa.Column("specimen_images", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
45
+ sa.Column("functional_imaging_metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
46
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
47
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
48
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_specimens")),
49
+ )
50
+ op.create_index(op.f("ix_specimens_specimen_id"), "specimens", ["specimen_id"], unique=True)
51
+ op.create_table(
52
+ "substrates",
53
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
54
+ sa.Column("media_id", sa.String(), nullable=False),
55
+ sa.Column("media_type", sa.String(), nullable=False),
56
+ sa.Column("uid", sa.String(), nullable=True),
57
+ sa.Column("status", sa.String(), nullable=True),
58
+ sa.Column("refpoint", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
59
+ sa.Column("refpoint_world", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
60
+ sa.Column("source_path", sa.String(), nullable=True),
61
+ sa.Column("metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
62
+ sa.Column("apertures", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
63
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
64
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
65
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_substrates")),
66
+ )
67
+ op.create_index(op.f("ix_substrates_media_id"), "substrates", ["media_id"], unique=True)
68
+ op.create_index("ix_substrates_media_type_status", "substrates", ["media_type", "status"], unique=False)
69
+ op.create_table(
70
+ "blocks",
71
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
72
+ sa.Column("block_id", sa.String(), nullable=False),
73
+ sa.Column("specimen_id", sa.String(), nullable=False),
74
+ sa.Column("microCT_info", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
75
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
76
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
77
+ sa.ForeignKeyConstraint(
78
+ ["specimen_id"], ["specimens.specimen_id"], name=op.f("fk_blocks_specimen_id_specimens")
79
+ ),
80
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_blocks")),
81
+ sa.UniqueConstraint("specimen_id", "block_id", name="uq_blocks_specimen_block"),
82
+ )
83
+ op.create_table(
84
+ "datasets",
85
+ sa.Column("dataset_id", sa.Uuid(), server_default=sa.text("uuidv7()"), nullable=False),
86
+ sa.Column("name", sa.String(), nullable=False),
87
+ sa.Column("description", sa.String(), nullable=True),
88
+ sa.Column("specimen_id", sa.String(), nullable=True),
89
+ sa.Column("parent_dataset_id", sa.UUID(), nullable=True),
90
+ sa.Column("status", sa.String(), nullable=False),
91
+ sa.Column("collected_at", sa.DateTime(timezone=True), nullable=True),
92
+ sa.Column("archived_at", sa.DateTime(timezone=True), nullable=True),
93
+ sa.Column("size_class", sa.String(), nullable=False),
94
+ sa.Column("tile_hash_modulus", sa.Integer(), nullable=True),
95
+ sa.Column("estimated_tile_count", sa.BigInteger(), nullable=True),
96
+ sa.Column("metadata_json", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
97
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
98
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
99
+ sa.CheckConstraint("status IN ('collecting', 'collected', 'archived')", name=op.f("ck_datasets_status_vocab")),
100
+ sa.ForeignKeyConstraint(
101
+ ["parent_dataset_id"], ["datasets.dataset_id"], name=op.f("fk_datasets_parent_dataset_id_datasets")
102
+ ),
103
+ sa.ForeignKeyConstraint(
104
+ ["specimen_id"], ["specimens.specimen_id"], name=op.f("fk_datasets_specimen_id_specimens")
105
+ ),
106
+ sa.PrimaryKeyConstraint("dataset_id", name=op.f("pk_datasets")),
107
+ )
108
+ op.create_index(op.f("ix_datasets_name"), "datasets", ["name"], unique=True)
109
+ op.create_table(
110
+ "lens_corrections",
111
+ sa.Column("lc_id", sa.UUID(), server_default=sa.text("uuidv7()"), nullable=False),
112
+ sa.Column("microscope_id", sa.UUID(), nullable=False),
113
+ sa.Column("magnification", sa.Integer(), nullable=False),
114
+ sa.Column("started_at", sa.DateTime(timezone=True), nullable=False),
115
+ sa.Column("source_run_id", sa.UUID(), nullable=True),
116
+ sa.Column("source_dataset_id", sa.UUID(), nullable=True),
117
+ sa.Column("shared_transform", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
118
+ sa.Column("correction_x_uri", sa.String(), nullable=True),
119
+ sa.Column("correction_y_uri", sa.String(), nullable=True),
120
+ sa.Column("solver_params", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
121
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
122
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
123
+ sa.ForeignKeyConstraint(
124
+ ["microscope_id"], ["microscopes.microscope_id"], name=op.f("fk_lens_corrections_microscope_id_microscopes")
125
+ ),
126
+ sa.PrimaryKeyConstraint("lc_id", name=op.f("pk_lens_corrections")),
127
+ )
128
+ op.create_index(
129
+ "ix_lens_corrections_microscope_mag",
130
+ "lens_corrections",
131
+ ["microscope_id", "magnification", sa.literal_column("started_at DESC")],
132
+ unique=False,
133
+ )
134
+ op.create_table(
135
+ "cutting_sessions",
136
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
137
+ sa.Column("cutting_session_id", sa.String(), nullable=False),
138
+ sa.Column("specimen_id", sa.String(), nullable=False),
139
+ sa.Column("block_id", sa.String(), nullable=False),
140
+ sa.Column("start_time", sa.DateTime(timezone=True), nullable=False),
141
+ sa.Column("end_time", sa.DateTime(timezone=True), nullable=True),
142
+ sa.Column("operator", sa.String(), nullable=True),
143
+ sa.Column("sectioning_device", sa.String(), nullable=False),
144
+ sa.Column("media_type", sa.String(), nullable=False),
145
+ sa.Column("knife_id", sa.String(), nullable=True),
146
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
147
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
148
+ sa.ForeignKeyConstraint(
149
+ ["specimen_id", "block_id"],
150
+ ["blocks.specimen_id", "blocks.block_id"],
151
+ name=op.f("fk_cutting_sessions_specimen_id_blocks"),
152
+ ),
153
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_cutting_sessions")),
154
+ )
155
+ op.create_index(
156
+ op.f("ix_cutting_sessions_cutting_session_id"), "cutting_sessions", ["cutting_session_id"], unique=True
157
+ )
158
+ op.create_index(op.f("ix_cutting_sessions_operator"), "cutting_sessions", ["operator"], unique=False)
159
+ op.create_index("ix_cutting_sessions_specimen_block", "cutting_sessions", ["specimen_id", "block_id"], unique=False)
160
+ op.create_table(
161
+ "tiles",
162
+ sa.Column("dataset_id", sa.Uuid(), nullable=False),
163
+ sa.Column("run_id", sa.UUID(), nullable=False),
164
+ sa.Column("raster_index", sa.Integer(), nullable=False),
165
+ sa.Column("tile_id", sa.Uuid(), server_default=sa.text("uuidv7()"), nullable=False),
166
+ sa.Column("stage_x_nm", sa.Double(), nullable=False),
167
+ sa.Column("stage_y_nm", sa.Double(), nullable=False),
168
+ sa.Column("montage_row", sa.Integer(), nullable=False),
169
+ sa.Column("montage_col", sa.Integer(), nullable=False),
170
+ sa.Column("focus_score", sa.REAL(), nullable=True),
171
+ sa.Column("min_value", sa.REAL(), nullable=True),
172
+ sa.Column("max_value", sa.REAL(), nullable=True),
173
+ sa.Column("mean_value", sa.REAL(), nullable=True),
174
+ sa.Column("std_value", sa.REAL(), nullable=True),
175
+ sa.Column("image_path", sa.String(), nullable=False),
176
+ sa.Column("matcher", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
177
+ sa.Column("supertile_id", sa.String(), nullable=True),
178
+ sa.Column("supertile_raster_position", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
179
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
180
+ sa.ForeignKeyConstraint(["dataset_id"], ["datasets.dataset_id"], name=op.f("fk_tiles_dataset_id_datasets")),
181
+ sa.PrimaryKeyConstraint("dataset_id", "run_id", "raster_index", name=op.f("pk_tiles")),
182
+ postgresql_partition_by="LIST (dataset_id)",
183
+ )
184
+ op.create_index(
185
+ "ix_tiles_focus_score_nn",
186
+ "tiles",
187
+ ["dataset_id", "focus_score"],
188
+ unique=False,
189
+ postgresql_where=sa.text("focus_score IS NOT NULL"),
190
+ )
191
+ op.create_index(
192
+ "ix_tiles_supertile_id_nn",
193
+ "tiles",
194
+ ["supertile_id"],
195
+ unique=False,
196
+ postgresql_where=sa.text("supertile_id IS NOT NULL"),
197
+ )
198
+ op.create_index(op.f("ix_tiles_tile_id"), "tiles", ["tile_id"], unique=False)
199
+ op.create_table(
200
+ "sections",
201
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
202
+ sa.Column("section_id", sa.String(), nullable=False),
203
+ sa.Column("section_number", sa.Integer(), nullable=False),
204
+ sa.Column("timestamp", sa.DateTime(timezone=True), nullable=False),
205
+ sa.Column("cutting_session_id", sa.String(), nullable=False),
206
+ sa.Column("block_id", sa.String(), nullable=False),
207
+ sa.Column("specimen_id", sa.String(), nullable=False),
208
+ sa.Column("media_id", sa.String(), nullable=False),
209
+ sa.Column("optical_image", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
210
+ sa.Column("aperture_uid", sa.String(), nullable=True),
211
+ sa.Column("aperture_index", sa.Integer(), nullable=True),
212
+ sa.Column("barcode", sa.String(), nullable=True),
213
+ sa.Column("condition", sa.String(), server_default=sa.text("'ok'"), nullable=False),
214
+ sa.Column("condition_reason", sa.String(), nullable=True),
215
+ sa.Column("section_metrics", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
216
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
217
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
218
+ sa.CheckConstraint(
219
+ "condition IN ('ok', 'damaged', 'destroyed', 'contaminated', 'lost')",
220
+ name=op.f("ck_sections_condition_vocab"),
221
+ ),
222
+ sa.ForeignKeyConstraint(
223
+ ["cutting_session_id"],
224
+ ["cutting_sessions.cutting_session_id"],
225
+ name=op.f("fk_sections_cutting_session_id_cutting_sessions"),
226
+ ),
227
+ sa.ForeignKeyConstraint(["media_id"], ["substrates.media_id"], name=op.f("fk_sections_media_id_substrates")),
228
+ sa.ForeignKeyConstraint(
229
+ ["specimen_id", "block_id"],
230
+ ["blocks.specimen_id", "blocks.block_id"],
231
+ name=op.f("fk_sections_specimen_id_blocks"),
232
+ ),
233
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_sections")),
234
+ sa.UniqueConstraint("cutting_session_id", "section_number", name="uq_sections_session_number"),
235
+ )
236
+ op.create_index(
237
+ "ix_sections_barcode_nn", "sections", ["barcode"], unique=False, postgresql_where=sa.text("barcode IS NOT NULL")
238
+ )
239
+ op.create_index(op.f("ix_sections_media_id"), "sections", ["media_id"], unique=False)
240
+ op.create_index(op.f("ix_sections_section_id"), "sections", ["section_id"], unique=True)
241
+ op.create_index("ix_sections_specimen_block", "sections", ["specimen_id", "block_id"], unique=False)
242
+ op.create_table(
243
+ "rois",
244
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
245
+ sa.Column("roi_id", sa.String(), nullable=False),
246
+ sa.Column("roi_number", sa.Integer(), nullable=False),
247
+ sa.Column("section_id", sa.String(), nullable=False),
248
+ sa.Column("block_id", sa.String(), nullable=False),
249
+ sa.Column("specimen_id", sa.String(), nullable=False),
250
+ sa.Column("substrate_media_id", sa.String(), nullable=False),
251
+ sa.Column("hierarchy_level", sa.Integer(), nullable=False),
252
+ sa.Column("parent_roi_id", sa.String(), nullable=True),
253
+ sa.Column("dataset_id", sa.Uuid(), nullable=True),
254
+ sa.Column("section_number", sa.Integer(), nullable=True),
255
+ sa.Column("roi_payload", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
256
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
257
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
258
+ sa.ForeignKeyConstraint(["dataset_id"], ["datasets.dataset_id"], name=op.f("fk_rois_dataset_id_datasets")),
259
+ sa.ForeignKeyConstraint(["parent_roi_id"], ["rois.roi_id"], name=op.f("fk_rois_parent_roi_id_rois")),
260
+ sa.ForeignKeyConstraint(["section_id"], ["sections.section_id"], name=op.f("fk_rois_section_id_sections")),
261
+ sa.ForeignKeyConstraint(
262
+ ["specimen_id", "block_id"],
263
+ ["blocks.specimen_id", "blocks.block_id"],
264
+ name=op.f("fk_rois_specimen_id_blocks"),
265
+ ),
266
+ sa.ForeignKeyConstraint(
267
+ ["substrate_media_id"], ["substrates.media_id"], name=op.f("fk_rois_substrate_media_id_substrates")
268
+ ),
269
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_rois")),
270
+ sa.UniqueConstraint("roi_id", name=op.f("uq_rois_roi_id")),
271
+ )
272
+ op.create_index(
273
+ "ix_rois_dataset_id_nn",
274
+ "rois",
275
+ ["dataset_id"],
276
+ unique=False,
277
+ postgresql_where=sa.text("dataset_id IS NOT NULL"),
278
+ )
279
+ op.create_index(
280
+ "ix_rois_parent_roi_id_nn",
281
+ "rois",
282
+ ["parent_roi_id"],
283
+ unique=False,
284
+ postgresql_where=sa.text("parent_roi_id IS NOT NULL"),
285
+ )
286
+ op.create_index(op.f("ix_rois_section_id"), "rois", ["section_id"], unique=False)
287
+ op.create_index("ix_rois_specimen_block", "rois", ["specimen_id", "block_id"], unique=False)
288
+ op.create_index(op.f("ix_rois_substrate_media_id"), "rois", ["substrate_media_id"], unique=False)
289
+ op.create_table(
290
+ "acquisition_tasks",
291
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
292
+ sa.Column("task_id", sa.String(), nullable=False),
293
+ sa.Column("specimen_id", sa.String(), nullable=True),
294
+ sa.Column("block_id", sa.String(), nullable=True),
295
+ sa.Column("roi_id", sa.String(), nullable=True),
296
+ sa.Column("dataset_id", sa.Uuid(), nullable=True),
297
+ sa.Column("kind", sa.String(), server_default=sa.text("'montage'"), nullable=False),
298
+ sa.Column("superseded_by", sa.String(), nullable=True),
299
+ sa.Column("task_group_id", sa.UUID(), nullable=True),
300
+ sa.Column("tilt_angle_deg", sa.Double(), nullable=True),
301
+ sa.Column("sub_region", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
302
+ sa.Column("tags", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
303
+ sa.Column("metadata", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
304
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
305
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
306
+ sa.CheckConstraint(
307
+ "kind = 'lens_correction' OR (roi_id IS NOT NULL AND specimen_id IS NOT NULL AND block_id IS NOT NULL)",
308
+ name=op.f("ck_acquisition_tasks_lineage_required_for_montage"),
309
+ ),
310
+ sa.CheckConstraint("kind IN ('montage', 'lens_correction')", name=op.f("ck_acquisition_tasks_kind_vocab")),
311
+ sa.ForeignKeyConstraint(
312
+ ["dataset_id"], ["datasets.dataset_id"], name=op.f("fk_acquisition_tasks_dataset_id_datasets")
313
+ ),
314
+ sa.ForeignKeyConstraint(["roi_id"], ["rois.roi_id"], name=op.f("fk_acquisition_tasks_roi_id_rois")),
315
+ sa.ForeignKeyConstraint(
316
+ ["specimen_id", "block_id"],
317
+ ["blocks.specimen_id", "blocks.block_id"],
318
+ name=op.f("fk_acquisition_tasks_specimen_id_blocks"),
319
+ ),
320
+ sa.ForeignKeyConstraint(
321
+ ["superseded_by"],
322
+ ["acquisition_tasks.task_id"],
323
+ name=op.f("fk_acquisition_tasks_superseded_by_acquisition_tasks"),
324
+ ),
325
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_acquisition_tasks")),
326
+ sa.UniqueConstraint("task_id", name=op.f("uq_acquisition_tasks_task_id")),
327
+ )
328
+ op.create_index(
329
+ "ix_acquisition_tasks_dataset_id_nn",
330
+ "acquisition_tasks",
331
+ ["dataset_id"],
332
+ unique=False,
333
+ postgresql_where=sa.text("dataset_id IS NOT NULL"),
334
+ )
335
+ op.create_index(op.f("ix_acquisition_tasks_roi_id"), "acquisition_tasks", ["roi_id"], unique=False)
336
+ op.create_index(
337
+ "ix_acquisition_tasks_specimen_block", "acquisition_tasks", ["specimen_id", "block_id"], unique=False
338
+ )
339
+ op.create_index(
340
+ "ix_acquisition_tasks_superseded_nn",
341
+ "acquisition_tasks",
342
+ ["superseded_by"],
343
+ unique=False,
344
+ postgresql_where=sa.text("superseded_by IS NOT NULL"),
345
+ )
346
+ op.create_index(
347
+ "ix_acquisition_tasks_task_group_nn",
348
+ "acquisition_tasks",
349
+ ["task_group_id"],
350
+ unique=False,
351
+ postgresql_where=sa.text("task_group_id IS NOT NULL"),
352
+ )
353
+ op.create_table(
354
+ "acquisitions",
355
+ sa.Column("id", sa.Integer(), sa.Identity(always=True), nullable=False),
356
+ sa.Column("acquisition_id", sa.String(), nullable=False),
357
+ sa.Column("run_id", sa.UUID(), server_default=sa.text("uuidv7()"), nullable=False),
358
+ sa.Column("montage_id", sa.String(), nullable=False),
359
+ sa.Column("specimen_id", sa.String(), nullable=True),
360
+ sa.Column("roi_id", sa.String(), nullable=True),
361
+ sa.Column("acquisition_task_id", sa.String(), nullable=False),
362
+ sa.Column("microscope_id", sa.UUID(), nullable=False),
363
+ sa.Column("dataset_id", sa.Uuid(), nullable=True),
364
+ sa.Column("kind", sa.String(), server_default=sa.text("'montage'"), nullable=False),
365
+ sa.Column("lc_id", sa.UUID(), nullable=True),
366
+ sa.Column("hardware_settings", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
367
+ sa.Column("acquisition_settings", postgresql.JSONB(astext_type=sa.Text()), nullable=False),
368
+ sa.Column("calibration_info", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
369
+ sa.Column("status", sa.String(), nullable=True),
370
+ sa.Column("error_message", sa.String(), nullable=True),
371
+ sa.Column("qc_state", sa.String(), server_default=sa.text("'pending'"), nullable=False),
372
+ sa.Column("qc_state_updated_at", sa.DateTime(timezone=True), nullable=True),
373
+ sa.Column("qc_state_updated_by", sa.String(), nullable=True),
374
+ sa.Column("transfer_state", sa.String(), server_default=sa.text("'not_started'"), nullable=False),
375
+ sa.Column("transfer_state_updated_at", sa.DateTime(timezone=True), nullable=True),
376
+ sa.Column("transfer_state_updated_by", sa.String(), nullable=True),
377
+ sa.Column("tile_count", sa.Integer(), nullable=True),
378
+ sa.Column("avg_focus_score", sa.REAL(), nullable=True),
379
+ sa.Column("failed_tile_count", sa.Integer(), nullable=True),
380
+ sa.Column("median_match_quality", sa.REAL(), nullable=True),
381
+ sa.Column("tilt_angle_deg", sa.Double(), nullable=True),
382
+ sa.Column("start_time", sa.DateTime(timezone=True), nullable=False),
383
+ sa.Column("end_time", sa.DateTime(timezone=True), nullable=True),
384
+ sa.Column("storage_locations", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
385
+ sa.Column("montage_set_name", sa.String(), nullable=True),
386
+ sa.Column("sub_region", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
387
+ sa.Column("replaces_acquisition_id", sa.String(), nullable=True),
388
+ sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
389
+ sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.text("now()"), nullable=False),
390
+ sa.CheckConstraint(
391
+ "kind = 'lens_correction' OR (roi_id IS NOT NULL AND specimen_id IS NOT NULL)",
392
+ name=op.f("ck_acquisitions_lineage_required_for_montage"),
393
+ ),
394
+ sa.CheckConstraint("kind IN ('montage', 'lens_correction')", name=op.f("ck_acquisitions_kind_vocab")),
395
+ sa.CheckConstraint(
396
+ "qc_state IN ('pending', 'qc_pass', 'qc_fail', 'needs_review')", name=op.f("ck_acquisitions_qc_state_vocab")
397
+ ),
398
+ sa.CheckConstraint(
399
+ "status IS NULL OR status IN ('complete', 'aborted', 'failed')", name=op.f("ck_acquisitions_status_vocab")
400
+ ),
401
+ sa.CheckConstraint(
402
+ "transfer_state IN ('not_started', 'in_progress', 'complete', 'error')",
403
+ name=op.f("ck_acquisitions_transfer_state_vocab"),
404
+ ),
405
+ sa.CheckConstraint(
406
+ "(status IS NULL) = (end_time IS NULL)", name=op.f("ck_acquisitions_status_terminal_consistency")
407
+ ),
408
+ sa.ForeignKeyConstraint(
409
+ ["acquisition_task_id"],
410
+ ["acquisition_tasks.task_id"],
411
+ name=op.f("fk_acquisitions_acquisition_task_id_acquisition_tasks"),
412
+ ),
413
+ sa.ForeignKeyConstraint(
414
+ ["dataset_id"], ["datasets.dataset_id"], name=op.f("fk_acquisitions_dataset_id_datasets")
415
+ ),
416
+ sa.ForeignKeyConstraint(
417
+ ["lc_id"], ["lens_corrections.lc_id"], name=op.f("fk_acquisitions_lc_id_lens_corrections")
418
+ ),
419
+ sa.ForeignKeyConstraint(
420
+ ["microscope_id"], ["microscopes.microscope_id"], name=op.f("fk_acquisitions_microscope_id_microscopes")
421
+ ),
422
+ sa.ForeignKeyConstraint(
423
+ ["replaces_acquisition_id"],
424
+ ["acquisitions.acquisition_id"],
425
+ name=op.f("fk_acquisitions_replaces_acquisition_id_acquisitions"),
426
+ ),
427
+ sa.ForeignKeyConstraint(["roi_id"], ["rois.roi_id"], name=op.f("fk_acquisitions_roi_id_rois")),
428
+ sa.ForeignKeyConstraint(
429
+ ["specimen_id"], ["specimens.specimen_id"], name=op.f("fk_acquisitions_specimen_id_specimens")
430
+ ),
431
+ sa.PrimaryKeyConstraint("id", name=op.f("pk_acquisitions")),
432
+ sa.UniqueConstraint("acquisition_id", name=op.f("uq_acquisitions_acquisition_id")),
433
+ sa.UniqueConstraint("run_id", name=op.f("uq_acquisitions_run_id")),
434
+ )
435
+ op.create_index(op.f("ix_acquisitions_acquisition_task_id"), "acquisitions", ["acquisition_task_id"], unique=False)
436
+ op.create_index(
437
+ "ix_acquisitions_dataset_id_nn",
438
+ "acquisitions",
439
+ ["dataset_id"],
440
+ unique=False,
441
+ postgresql_where=sa.text("dataset_id IS NOT NULL"),
442
+ )
443
+ op.create_index("ix_acquisitions_dataset_kind_qc", "acquisitions", ["dataset_id", "kind", "qc_state"], unique=False)
444
+ op.create_index(
445
+ "ix_acquisitions_lc_id_nn",
446
+ "acquisitions",
447
+ ["lc_id"],
448
+ unique=False,
449
+ postgresql_where=sa.text("lc_id IS NOT NULL"),
450
+ )
451
+ op.create_index(
452
+ "ix_acquisitions_montage_set_name_nn",
453
+ "acquisitions",
454
+ ["montage_set_name"],
455
+ unique=False,
456
+ postgresql_where=sa.text("montage_set_name IS NOT NULL"),
457
+ )
458
+ op.create_index(
459
+ "ix_acquisitions_replaces_nn",
460
+ "acquisitions",
461
+ ["replaces_acquisition_id"],
462
+ unique=False,
463
+ postgresql_where=sa.text("replaces_acquisition_id IS NOT NULL"),
464
+ )
465
+ op.create_index(op.f("ix_acquisitions_roi_id"), "acquisitions", ["roi_id"], unique=False)
466
+ op.create_index(op.f("ix_acquisitions_specimen_id"), "acquisitions", ["specimen_id"], unique=False)
467
+ op.create_index(op.f("ix_acquisitions_start_time"), "acquisitions", ["start_time"], unique=False)
468
+ # ### end Alembic commands ###
469
+
470
+
471
+ def downgrade() -> None:
472
+ """Downgrade schema."""
473
+ # ### commands auto generated by Alembic - please adjust! ###
474
+ op.drop_index(op.f("ix_acquisitions_start_time"), table_name="acquisitions")
475
+ op.drop_index(op.f("ix_acquisitions_specimen_id"), table_name="acquisitions")
476
+ op.drop_index(op.f("ix_acquisitions_roi_id"), table_name="acquisitions")
477
+ op.drop_index(
478
+ "ix_acquisitions_replaces_nn",
479
+ table_name="acquisitions",
480
+ postgresql_where=sa.text("replaces_acquisition_id IS NOT NULL"),
481
+ )
482
+ op.drop_index(
483
+ "ix_acquisitions_montage_set_name_nn",
484
+ table_name="acquisitions",
485
+ postgresql_where=sa.text("montage_set_name IS NOT NULL"),
486
+ )
487
+ op.drop_index("ix_acquisitions_lc_id_nn", table_name="acquisitions", postgresql_where=sa.text("lc_id IS NOT NULL"))
488
+ op.drop_index("ix_acquisitions_dataset_kind_qc", table_name="acquisitions")
489
+ op.drop_index(
490
+ "ix_acquisitions_dataset_id_nn", table_name="acquisitions", postgresql_where=sa.text("dataset_id IS NOT NULL")
491
+ )
492
+ op.drop_index(op.f("ix_acquisitions_acquisition_task_id"), table_name="acquisitions")
493
+ op.drop_table("acquisitions")
494
+ op.drop_index(
495
+ "ix_acquisition_tasks_task_group_nn",
496
+ table_name="acquisition_tasks",
497
+ postgresql_where=sa.text("task_group_id IS NOT NULL"),
498
+ )
499
+ op.drop_index(
500
+ "ix_acquisition_tasks_superseded_nn",
501
+ table_name="acquisition_tasks",
502
+ postgresql_where=sa.text("superseded_by IS NOT NULL"),
503
+ )
504
+ op.drop_index("ix_acquisition_tasks_specimen_block", table_name="acquisition_tasks")
505
+ op.drop_index(op.f("ix_acquisition_tasks_roi_id"), table_name="acquisition_tasks")
506
+ op.drop_index(
507
+ "ix_acquisition_tasks_dataset_id_nn",
508
+ table_name="acquisition_tasks",
509
+ postgresql_where=sa.text("dataset_id IS NOT NULL"),
510
+ )
511
+ op.drop_table("acquisition_tasks")
512
+ op.drop_index(op.f("ix_rois_substrate_media_id"), table_name="rois")
513
+ op.drop_index("ix_rois_specimen_block", table_name="rois")
514
+ op.drop_index(op.f("ix_rois_section_id"), table_name="rois")
515
+ op.drop_index("ix_rois_parent_roi_id_nn", table_name="rois", postgresql_where=sa.text("parent_roi_id IS NOT NULL"))
516
+ op.drop_index("ix_rois_dataset_id_nn", table_name="rois", postgresql_where=sa.text("dataset_id IS NOT NULL"))
517
+ op.drop_table("rois")
518
+ op.drop_index("ix_sections_specimen_block", table_name="sections")
519
+ op.drop_index(op.f("ix_sections_section_id"), table_name="sections")
520
+ op.drop_index(op.f("ix_sections_media_id"), table_name="sections")
521
+ op.drop_index("ix_sections_barcode_nn", table_name="sections", postgresql_where=sa.text("barcode IS NOT NULL"))
522
+ op.drop_table("sections")
523
+ op.drop_index(op.f("ix_tiles_tile_id"), table_name="tiles")
524
+ op.drop_index("ix_tiles_supertile_id_nn", table_name="tiles", postgresql_where=sa.text("supertile_id IS NOT NULL"))
525
+ op.drop_index("ix_tiles_focus_score_nn", table_name="tiles", postgresql_where=sa.text("focus_score IS NOT NULL"))
526
+ op.drop_table("tiles")
527
+ op.drop_index("ix_cutting_sessions_specimen_block", table_name="cutting_sessions")
528
+ op.drop_index(op.f("ix_cutting_sessions_operator"), table_name="cutting_sessions")
529
+ op.drop_index(op.f("ix_cutting_sessions_cutting_session_id"), table_name="cutting_sessions")
530
+ op.drop_table("cutting_sessions")
531
+ op.drop_index("ix_lens_corrections_microscope_mag", table_name="lens_corrections")
532
+ op.drop_table("lens_corrections")
533
+ op.drop_index(op.f("ix_datasets_name"), table_name="datasets")
534
+ op.drop_table("datasets")
535
+ op.drop_table("blocks")
536
+ op.drop_index("ix_substrates_media_type_status", table_name="substrates")
537
+ op.drop_index(op.f("ix_substrates_media_id"), table_name="substrates")
538
+ op.drop_table("substrates")
539
+ op.drop_index(op.f("ix_specimens_specimen_id"), table_name="specimens")
540
+ op.drop_table("specimens")
541
+ op.drop_table("microscopes")
542
+ # ### end Alembic commands ###
@@ -0,0 +1,32 @@
1
+ """Add description to block table
2
+
3
+ Revision ID: ec4c96d0bf77
4
+ Revises: 0001
5
+ Create Date: 2026-07-17 13:49:07.328683
6
+
7
+ """
8
+
9
+ from collections.abc import Sequence
10
+
11
+ import sqlalchemy as sa
12
+ from alembic import op
13
+
14
+ # revision identifiers, used by Alembic.
15
+ revision: str = "ec4c96d0bf77"
16
+ down_revision: str | Sequence[str] | None = "0001"
17
+ branch_labels: str | Sequence[str] | None = None
18
+ depends_on: str | Sequence[str] | None = None
19
+
20
+
21
+ def upgrade() -> None:
22
+ """Upgrade schema."""
23
+ # ### commands auto generated by Alembic - please adjust! ###
24
+ op.add_column("blocks", sa.Column("description", sa.String(), nullable=True))
25
+ # ### end Alembic commands ###
26
+
27
+
28
+ def downgrade() -> None:
29
+ """Downgrade schema."""
30
+ # ### commands auto generated by Alembic - please adjust! ###
31
+ op.drop_column("blocks", "description")
32
+ # ### end Alembic commands ###