Skip to content

Commit a31a970

Browse files
precommit
1 parent 37bfd4e commit a31a970

File tree

11 files changed

+190
-345
lines changed

11 files changed

+190
-345
lines changed

mpcontribs-lux/mpcontribs/lux/_types.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@
1212
"""Type annotation for complex numbers which are required fields."""
1313

1414
NullableComplexType = Annotated[tuple[float, float] | None, _complex_type_validator]
15-
"""Type annotation for complex numbers which are nullable fields."""
15+
"""Type annotation for complex numbers which are nullable fields."""

mpcontribs-lux/mpcontribs/lux/projects/alab/schemas/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,3 @@
3333
"XRDRefinement",
3434
"XRDPhase",
3535
]
36-

mpcontribs-lux/mpcontribs/lux/projects/alab/schemas/base.py

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,13 @@
88
from pydantic import Field
99

1010

11-
def ExcludeFromUpload(
12-
default: Any = None,
13-
description: str = "",
14-
**kwargs
15-
) -> Any:
11+
def ExcludeFromUpload(default: Any = None, description: str = "", **kwargs) -> Any:
1612
"""
1713
Field that should NOT be uploaded to MPContribs.
18-
14+
1915
Use this for sensitive data that must remain private until publication.
2016
Examples: weight_collected, mass measurements that are embargoed.
21-
17+
2218
Usage:
2319
weight_collected: float | None = ExcludeFromUpload(
2420
description="Weight of powder collected (embargoed)"
@@ -35,10 +31,10 @@ def ExcludeFromUpload(
3531
def get_uploadable_fields(model_class) -> list[str]:
3632
"""
3733
Get list of fields that should be uploaded to MPContribs.
38-
34+
3935
Args:
4036
model_class: Pydantic model class
41-
37+
4238
Returns:
4339
List of field names that are NOT marked with exclude_from_upload
4440
"""
@@ -53,10 +49,10 @@ def get_uploadable_fields(model_class) -> list[str]:
5349
def get_excluded_fields(model_class) -> list[str]:
5450
"""
5551
Get list of fields that should NOT be uploaded to MPContribs.
56-
52+
5753
Args:
5854
model_class: Pydantic model class
59-
55+
6056
Returns:
6157
List of field names that ARE marked with exclude_from_upload
6258
"""
@@ -66,4 +62,3 @@ def get_excluded_fields(model_class) -> list[str]:
6662
if extra.get("exclude_from_upload", False):
6763
excluded.append(field_name)
6864
return excluded
69-

mpcontribs-lux/mpcontribs/lux/projects/alab/schemas/experiment_elements.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,14 @@
1111
class ExperimentElement(BaseModel, extra="forbid"):
1212
"""
1313
Element present in an experiment.
14-
14+
1515
Each experiment can have multiple elements (1:N relationship).
1616
"""
17-
18-
experiment_id: str = Field(
19-
description="Reference to parent experiment"
20-
)
21-
22-
element_symbol: str = Field(
23-
description="Element symbol (e.g., Na, Mg, O)"
24-
)
25-
17+
18+
experiment_id: str = Field(description="Reference to parent experiment")
19+
20+
element_symbol: str = Field(description="Element symbol (e.g., Na, Mg, O)")
21+
2622
target_atomic_percent: float | None = Field(
27-
default=None,
28-
description="Target atomic percentage of this element"
23+
default=None, description="Target atomic percentage of this element"
2924
)
30-

mpcontribs-lux/mpcontribs/lux/projects/alab/schemas/experiments.py

Lines changed: 69 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -18,210 +18,165 @@
1818
# When imported dynamically, use absolute import
1919
import sys
2020
from pathlib import Path
21+
2122
sys.path.insert(0, str(Path(__file__).parent))
2223
from base import ExcludeFromUpload
2324

2425

2526
class Experiment(BaseModel, extra="forbid"):
2627
"""
2728
Main experiment schema with all 1:1 data consolidated.
28-
29+
2930
This is the primary table - one row per experiment.
3031
All related 1:1 data (heating, recovery, xrd, finalization, dosing) is merged here.
3132
"""
32-
33+
3334
# === Core experiment fields ===
34-
experiment_id: str = Field(
35-
description="Unique experiment identifier (MongoDB _id)"
36-
)
37-
38-
name: str = Field(
39-
description="Experiment name (e.g., NSC_249, MINES_12)"
40-
)
41-
35+
experiment_id: str = Field(description="Unique experiment identifier (MongoDB _id)")
36+
37+
name: str = Field(description="Experiment name (e.g., NSC_249, MINES_12)")
38+
4239
experiment_type: str = Field(
4340
description="Root experiment type (NSC, Na, PG, MINES, TRI)"
4441
)
45-
42+
4643
experiment_subgroup: str | None = Field(
47-
default=None,
48-
description="Experiment subgroup (e.g., NSC_249, Na_123)"
44+
default=None, description="Experiment subgroup (e.g., NSC_249, Na_123)"
4945
)
50-
51-
target_formula: str = Field(
52-
description="Target chemical formula"
53-
)
54-
55-
last_updated: datetime = Field(
56-
description="Last modification timestamp"
57-
)
58-
46+
47+
target_formula: str = Field(description="Target chemical formula")
48+
49+
last_updated: datetime = Field(description="Last modification timestamp")
50+
5951
status: Literal["completed", "error", "active", "unknown"] = Field(
6052
description="Workflow status"
6153
)
62-
54+
6355
notes: str | None = Field(
64-
default=None,
65-
description="Optional notes about the experiment"
56+
default=None, description="Optional notes about the experiment"
6657
)
6758

6859
# === Heating fields (prefix: heating_) ===
6960
heating_method: Literal["standard", "atmosphere", "manual", "none"] | None = Field(
70-
default=None,
71-
description="Heating method used"
61+
default=None, description="Heating method used"
7262
)
73-
63+
7464
heating_temperature: float | None = Field(
75-
default=None,
76-
description="Target heating temperature in °C"
65+
default=None, description="Target heating temperature in °C"
7766
)
78-
67+
7968
heating_time: float | None = Field(
80-
default=None,
81-
description="Heating duration in minutes"
69+
default=None, description="Heating duration in minutes"
8270
)
83-
71+
8472
heating_cooling_rate: float | None = Field(
85-
default=None,
86-
description="Cooling rate in °C/min"
73+
default=None, description="Cooling rate in °C/min"
8774
)
88-
75+
8976
heating_atmosphere: str | None = Field(
90-
default=None,
91-
description="Atmosphere used during heating (e.g., N2, Ar, Air)"
77+
default=None, description="Atmosphere used during heating (e.g., N2, Ar, Air)"
9278
)
93-
79+
9480
heating_flow_rate_ml_min: float | None = Field(
95-
default=None,
96-
description="Gas flow rate during heating in mL/min"
81+
default=None, description="Gas flow rate during heating in mL/min"
9782
)
98-
83+
9984
heating_low_temp_calcination: bool | None = Field(
100-
default=None,
101-
description="Whether low temperature calcination was used"
85+
default=None, description="Whether low temperature calcination was used"
10286
)
10387

10488
# === Recovery fields (prefix: recovery_) ===
10589
recovery_total_dosed_mass_mg: float | None = Field(
106-
default=None,
107-
description="Total mass of all powders dosed in mg"
90+
default=None, description="Total mass of all powders dosed in mg"
10891
)
109-
92+
11093
# EXCLUDED FROM UPLOAD per team request
11194
recovery_weight_collected_mg: float | None = ExcludeFromUpload(
11295
description="Weight of powder collected after heating in mg (EMBARGOED)"
11396
)
114-
97+
11598
recovery_yield_percent: float | None = Field(
116-
default=None,
117-
description="Recovery yield (collected / dosed * 100)"
99+
default=None, description="Recovery yield (collected / dosed * 100)"
118100
)
119-
101+
120102
recovery_initial_crucible_weight_mg: float | None = Field(
121-
default=None,
122-
description="Initial crucible weight before experiment in mg"
103+
default=None, description="Initial crucible weight before experiment in mg"
123104
)
124-
105+
125106
recovery_failure_classification: str | None = Field(
126-
default=None,
127-
description="Classification of any failure during recovery"
107+
default=None, description="Classification of any failure during recovery"
128108
)
129109

130110
# === XRD measurement fields (prefix: xrd_) ===
131111
xrd_sampleid_in_aeris: str | None = Field(
132-
default=None,
133-
description="Sample ID in Aeris XRD system"
112+
default=None, description="Sample ID in Aeris XRD system"
134113
)
135-
114+
136115
xrd_holder_index: int | None = Field(
137-
default=None,
138-
description="XRD sample holder position index"
116+
default=None, description="XRD sample holder position index"
139117
)
140-
118+
141119
# EXCLUDED FROM UPLOAD per team request
142120
xrd_total_mass_dispensed_mg: float | None = ExcludeFromUpload(
143121
description="Mass dispensed for XRD measurement in mg (EMBARGOED)"
144122
)
145-
123+
146124
xrd_met_target_mass: bool | None = Field(
147-
default=None,
148-
description="Whether target mass was achieved for XRD"
125+
default=None, description="Whether target mass was achieved for XRD"
149126
)
150127

151128
# === Finalization fields (prefix: finalization_) ===
152129
finalization_decoded_sample_id: str | None = Field(
153-
default=None,
154-
description="Decoded sample ID from barcode"
130+
default=None, description="Decoded sample ID from barcode"
155131
)
156-
132+
157133
finalization_successful_labeling: bool | None = Field(
158-
default=None,
159-
description="Whether sample was successfully labeled"
134+
default=None, description="Whether sample was successfully labeled"
160135
)
161-
136+
162137
finalization_storage_location: str | None = Field(
163-
default=None,
164-
description="Final storage location of sample"
138+
default=None, description="Final storage location of sample"
165139
)
166140

167141
# === Dosing fields (prefix: dosing_) ===
168142
dosing_crucible_position: int | None = Field(
169-
default=None,
170-
description="Crucible position in rack",
171-
ge=1,
172-
le=4
173-
)
174-
175-
dosing_crucible_sub_rack: Literal["SubRackA", "SubRackB", "SubRackC", "SubRackD"] | None = Field(
176-
default=None,
177-
description="Sub-rack identifier"
143+
default=None, description="Crucible position in rack", ge=1, le=4
178144
)
179-
145+
146+
dosing_crucible_sub_rack: (
147+
Literal["SubRackA", "SubRackB", "SubRackC", "SubRackD"] | None
148+
) = Field(default=None, description="Sub-rack identifier")
149+
180150
dosing_mixing_pot_position: int | None = Field(
181-
default=None,
182-
description="Mixing pot position",
183-
ge=1,
184-
le=16
151+
default=None, description="Mixing pot position", ge=1, le=16
185152
)
186-
153+
187154
dosing_ethanol_dispense_volume: int | None = Field(
188-
default=None,
189-
description="Volume of ethanol dispensed in µL",
190-
ge=0
155+
default=None, description="Volume of ethanol dispensed in µL", ge=0
191156
)
192-
157+
193158
dosing_target_transfer_volume: int | None = Field(
194-
default=None,
195-
description="Target transfer volume in µL",
196-
ge=0
159+
default=None, description="Target transfer volume in µL", ge=0
197160
)
198-
161+
199162
dosing_actual_transfer_mass: float | None = Field(
200-
default=None,
201-
description="Actual mass transferred in g",
202-
ge=0
163+
default=None, description="Actual mass transferred in g", ge=0
203164
)
204-
165+
205166
dosing_dac_duration: int | None = Field(
206-
default=None,
207-
description="DAC duration in seconds",
208-
ge=0
167+
default=None, description="DAC duration in seconds", ge=0
209168
)
210-
169+
211170
dosing_dac_speed: int | None = Field(
212-
default=None,
213-
description="DAC rotation speed in rpm",
214-
ge=0
171+
default=None, description="DAC rotation speed in rpm", ge=0
215172
)
216-
173+
217174
dosing_actual_heat_duration: int | None = Field(
218175
default=None,
219176
description="Actual heating duration during dosing in seconds",
220-
ge=0
177+
ge=0,
221178
)
222-
179+
223180
dosing_end_reason: str | None = Field(
224-
default=None,
225-
description="Reason for ending dosing session"
181+
default=None, description="Reason for ending dosing session"
226182
)
227-

0 commit comments

Comments
 (0)