dkist-processing-common 11.7.0rc3__py3-none-any.whl → 11.9.1__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.
- dkist_processing_common/config.py +28 -6
- dkist_processing_common/models/constants.py +12 -36
- dkist_processing_common/models/graphql.py +0 -33
- dkist_processing_common/parsers/observing_program_id_bud.py +24 -0
- dkist_processing_common/tasks/l1_output_data.py +38 -32
- dkist_processing_common/tasks/mixin/globus.py +23 -26
- dkist_processing_common/tasks/mixin/metadata_store.py +0 -49
- dkist_processing_common/tasks/mixin/object_store.py +21 -0
- dkist_processing_common/tasks/mixin/quality/_metrics.py +4 -6
- dkist_processing_common/tasks/parse_l0_input_data.py +9 -30
- dkist_processing_common/tasks/trial_catalog.py +49 -1
- dkist_processing_common/tasks/trial_output_data.py +1 -1
- dkist_processing_common/tests/mock_metadata_store.py +39 -4
- dkist_processing_common/tests/test_input_dataset.py +1 -37
- dkist_processing_common/tests/test_parse_l0_input_data.py +9 -3
- dkist_processing_common/tests/test_publish_catalog_messages.py +0 -21
- dkist_processing_common/tests/test_quality_mixin.py +11 -3
- dkist_processing_common/tests/test_stems.py +35 -0
- dkist_processing_common/tests/test_submit_dataset_metadata.py +1 -5
- dkist_processing_common/tests/test_trial_catalog.py +72 -2
- dkist_processing_common/tests/test_trial_output_data.py +1 -2
- {dkist_processing_common-11.7.0rc3.dist-info → dkist_processing_common-11.9.1.dist-info}/METADATA +17 -13
- {dkist_processing_common-11.7.0rc3.dist-info → dkist_processing_common-11.9.1.dist-info}/RECORD +25 -30
- changelog/267.feature.1.rst +0 -1
- changelog/267.feature.2.rst +0 -1
- changelog/267.feature.rst +0 -1
- changelog/267.misc.rst +0 -1
- changelog/267.removal.1.rst +0 -2
- changelog/267.removal.rst +0 -1
- {dkist_processing_common-11.7.0rc3.dist-info → dkist_processing_common-11.9.1.dist-info}/WHEEL +0 -0
- {dkist_processing_common-11.7.0rc3.dist-info → dkist_processing_common-11.9.1.dist-info}/top_level.txt +0 -0
|
@@ -37,7 +37,9 @@ from dkist_processing_common.models.task_name import TaskName
|
|
|
37
37
|
from dkist_processing_common.parsers.average_bud import TaskAverageBud
|
|
38
38
|
from dkist_processing_common.parsers.experiment_id_bud import ContributingExperimentIdsBud
|
|
39
39
|
from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
|
|
40
|
-
from dkist_processing_common.parsers.
|
|
40
|
+
from dkist_processing_common.parsers.observing_program_id_bud import (
|
|
41
|
+
TaskContributingObservingProgramExecutionIdsBud,
|
|
42
|
+
)
|
|
41
43
|
from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
|
|
42
44
|
from dkist_processing_common.parsers.proposal_id_bud import ProposalIdBud
|
|
43
45
|
from dkist_processing_common.parsers.task import parse_header_ip_task_with_gains
|
|
@@ -88,27 +90,19 @@ def dataset_extra_bud_factory() -> list[S]:
|
|
|
88
90
|
constant_name=BudName.hls_version,
|
|
89
91
|
metadata_key=MetadataKey.hls_version,
|
|
90
92
|
),
|
|
91
|
-
|
|
92
|
-
constant_name=BudName.
|
|
93
|
-
metadata_key=MetadataKey.observing_program_execution_id,
|
|
93
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
94
|
+
constant_name=BudName.dark_observing_program_execution_ids,
|
|
94
95
|
ip_task_types=TaskName.dark,
|
|
95
96
|
),
|
|
96
|
-
|
|
97
|
-
constant_name=BudName.
|
|
98
|
-
metadata_key=MetadataKey.observing_program_execution_id,
|
|
97
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
98
|
+
constant_name=BudName.solar_gain_observing_program_execution_ids,
|
|
99
99
|
ip_task_types=TaskName.solar_gain,
|
|
100
100
|
task_type_parsing_function=parse_header_ip_task_with_gains,
|
|
101
101
|
),
|
|
102
|
-
|
|
103
|
-
constant_name=BudName.
|
|
104
|
-
metadata_key=MetadataKey.observing_program_execution_id,
|
|
102
|
+
TaskContributingObservingProgramExecutionIdsBud(
|
|
103
|
+
constant_name=BudName.polcal_observing_program_execution_ids,
|
|
105
104
|
ip_task_types=TaskName.polcal,
|
|
106
105
|
),
|
|
107
|
-
TaskUniqueBud(
|
|
108
|
-
constant_name=BudName.dark_num_raw_frames_per_fpa,
|
|
109
|
-
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
110
|
-
ip_task_types=TaskName.dark,
|
|
111
|
-
),
|
|
112
106
|
TaskUniqueBud(
|
|
113
107
|
constant_name=BudName.solar_gain_num_raw_frames_per_fpa,
|
|
114
108
|
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
@@ -120,11 +114,6 @@ def dataset_extra_bud_factory() -> list[S]:
|
|
|
120
114
|
metadata_key=MetadataKey.num_raw_frames_per_fpa,
|
|
121
115
|
ip_task_types=TaskName.polcal,
|
|
122
116
|
),
|
|
123
|
-
TaskUniqueBud(
|
|
124
|
-
constant_name=BudName.dark_telescope_tracking_mode,
|
|
125
|
-
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
126
|
-
ip_task_types=TaskName.dark,
|
|
127
|
-
),
|
|
128
117
|
TaskUniqueBud(
|
|
129
118
|
constant_name=BudName.solar_gain_telescope_tracking_mode,
|
|
130
119
|
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
@@ -136,11 +125,6 @@ def dataset_extra_bud_factory() -> list[S]:
|
|
|
136
125
|
metadata_key=MetadataKey.telescope_tracking_mode,
|
|
137
126
|
ip_task_types=TaskName.polcal,
|
|
138
127
|
),
|
|
139
|
-
TaskUniqueBud(
|
|
140
|
-
constant_name=BudName.dark_coude_table_tracking_mode,
|
|
141
|
-
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
142
|
-
ip_task_types=TaskName.dark,
|
|
143
|
-
),
|
|
144
128
|
TaskUniqueBud(
|
|
145
129
|
constant_name=BudName.solar_gain_coude_table_tracking_mode,
|
|
146
130
|
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
@@ -152,11 +136,6 @@ def dataset_extra_bud_factory() -> list[S]:
|
|
|
152
136
|
metadata_key=MetadataKey.coude_table_tracking_mode,
|
|
153
137
|
ip_task_types=TaskName.polcal,
|
|
154
138
|
),
|
|
155
|
-
TaskUniqueBud(
|
|
156
|
-
constant_name=BudName.dark_telescope_scanning_mode,
|
|
157
|
-
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
158
|
-
ip_task_types=TaskName.dark,
|
|
159
|
-
),
|
|
160
139
|
TaskUniqueBud(
|
|
161
140
|
constant_name=BudName.solar_gain_telescope_scanning_mode,
|
|
162
141
|
metadata_key=MetadataKey.telescope_scanning_mode,
|
|
@@ -5,15 +5,18 @@ import logging
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from itertools import chain
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
8
9
|
from typing import Generator
|
|
9
10
|
from uuid import uuid4
|
|
10
11
|
|
|
11
12
|
from dkist_processing_common.codecs.asdf import asdf_fileobj_encoder
|
|
13
|
+
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
12
14
|
from dkist_processing_common.codecs.fits import fits_access_decoder
|
|
13
15
|
from dkist_processing_common.codecs.json import json_encoder
|
|
14
16
|
from dkist_processing_common.codecs.path import path_decoder
|
|
15
17
|
from dkist_processing_common.codecs.quality import quality_data_decoder
|
|
16
18
|
from dkist_processing_common.models.fits_access import FitsAccessBase
|
|
19
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
17
20
|
from dkist_processing_common.models.tags import Tag
|
|
18
21
|
from dkist_processing_common.tasks.output_data_base import OutputDataBase
|
|
19
22
|
|
|
@@ -27,6 +30,7 @@ INVENTORY_EXTRA_INSTALLED = False
|
|
|
27
30
|
try:
|
|
28
31
|
from dkist_inventory.inventory import generate_asdf_filename
|
|
29
32
|
from dkist_inventory.inventory import generate_inventory_from_frame_inventory
|
|
33
|
+
from dkist_inventory.inventory import generate_quality_report_filename
|
|
30
34
|
|
|
31
35
|
INVENTORY_EXTRA_INSTALLED = True
|
|
32
36
|
except ModuleNotFoundError:
|
|
@@ -138,11 +142,15 @@ class CreateTrialAsdf(OutputDataBase):
|
|
|
138
142
|
|
|
139
143
|
def run(self) -> None:
|
|
140
144
|
"""Generate an ASDF file simulating the ASDF file that would be produced when cataloging the dataset."""
|
|
145
|
+
with self.telemetry_span("Collate input dataset parameters"):
|
|
146
|
+
parameters = self.parse_input_dataset_parameters()
|
|
147
|
+
|
|
141
148
|
with self.telemetry_span("Generate ASDF tree"):
|
|
142
149
|
tree = asdf_tree_from_filenames(
|
|
143
150
|
filenames=self.absolute_output_frame_paths,
|
|
144
151
|
hdu=1, # compressed
|
|
145
152
|
relative_to=self.scratch.workflow_base_path,
|
|
153
|
+
parameters=parameters,
|
|
146
154
|
)
|
|
147
155
|
|
|
148
156
|
trial_history = [
|
|
@@ -169,6 +177,37 @@ class CreateTrialAsdf(OutputDataBase):
|
|
|
169
177
|
),
|
|
170
178
|
)
|
|
171
179
|
|
|
180
|
+
def parse_input_dataset_parameters(self) -> list[dict[str, Any]]:
|
|
181
|
+
"""
|
|
182
|
+
Return the parameters associated with the dataset.
|
|
183
|
+
|
|
184
|
+
Returns
|
|
185
|
+
-------
|
|
186
|
+
list[dict[str, Any]]
|
|
187
|
+
A list of dictionaries, each containing a parameter name and its values.
|
|
188
|
+
|
|
189
|
+
Raises
|
|
190
|
+
------
|
|
191
|
+
ValueError
|
|
192
|
+
If there is not exactly one ``InputDatasetPartDocumentList`` found.
|
|
193
|
+
"""
|
|
194
|
+
part_docs_iter = self.read(
|
|
195
|
+
tags=Tag.input_dataset_parameters(),
|
|
196
|
+
decoder=basemodel_decoder,
|
|
197
|
+
model=InputDatasetPartDocumentList,
|
|
198
|
+
)
|
|
199
|
+
docs = list(part_docs_iter)
|
|
200
|
+
|
|
201
|
+
if not docs:
|
|
202
|
+
logger.warning("No parameter list decoded from files")
|
|
203
|
+
return []
|
|
204
|
+
|
|
205
|
+
if len(docs) > 1:
|
|
206
|
+
raise ValueError(f"Expected 1 parameter list, found {len(docs)}")
|
|
207
|
+
|
|
208
|
+
parameters = docs[0].model_dump(by_alias=True).get("doc_list", [])
|
|
209
|
+
return parameters
|
|
210
|
+
|
|
172
211
|
|
|
173
212
|
class CreateTrialQualityReport(OutputDataBase):
|
|
174
213
|
"""
|
|
@@ -186,6 +225,13 @@ class CreateTrialQualityReport(OutputDataBase):
|
|
|
186
225
|
f" but the required dependencies were not found."
|
|
187
226
|
)
|
|
188
227
|
|
|
228
|
+
if not INVENTORY_EXTRA_INSTALLED:
|
|
229
|
+
raise ModuleNotFoundError(
|
|
230
|
+
f"{self.__class__.__name__} Task requires the dkist-inventory package "
|
|
231
|
+
f"(e.g. via an 'inventory' pip_extra on dkist_processing_core.Workflow().add_node())"
|
|
232
|
+
f" but the required dependencies were not found."
|
|
233
|
+
)
|
|
234
|
+
|
|
189
235
|
def run(self) -> None:
|
|
190
236
|
"""Generate the quality report for the dataset."""
|
|
191
237
|
self.create_trial_quality_report()
|
|
@@ -207,5 +253,7 @@ class CreateTrialQualityReport(OutputDataBase):
|
|
|
207
253
|
self.write(
|
|
208
254
|
quality_report,
|
|
209
255
|
tags=[Tag.output(), Tag.quality_report()],
|
|
210
|
-
relative_path=
|
|
256
|
+
relative_path=generate_quality_report_filename(
|
|
257
|
+
dataset_id=self.constants.dataset_id
|
|
258
|
+
),
|
|
211
259
|
)
|
|
@@ -100,7 +100,7 @@ class TransferTrialData(TransferDataBase, GlobusMixin):
|
|
|
100
100
|
tag_list = []
|
|
101
101
|
tag_list += [[Tag.output(), Tag.dataset_inventory()]]
|
|
102
102
|
tag_list += [[Tag.output(), Tag.asdf()]]
|
|
103
|
-
tag_list += [[Tag.quality_data()]]
|
|
103
|
+
tag_list += [[Tag.output(), Tag.quality_data()]]
|
|
104
104
|
tag_list += [[Tag.output(), Tag.quality_report()]]
|
|
105
105
|
tag_list += [[Tag.output(), Tag.movie()]]
|
|
106
106
|
return tag_list
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
from abc import ABC
|
|
7
7
|
from abc import abstractmethod
|
|
8
8
|
from datetime import datetime
|
|
9
|
+
from datetime import timedelta
|
|
9
10
|
from pathlib import Path
|
|
10
11
|
from uuid import uuid4
|
|
11
12
|
|
|
@@ -134,10 +135,6 @@ class InputDatasetRecipeRunResponseMapping(ResponseMapping):
|
|
|
134
135
|
return Unset
|
|
135
136
|
|
|
136
137
|
|
|
137
|
-
class QualityResponseMapping(ResponseMapping):
|
|
138
|
-
pass # TODO
|
|
139
|
-
|
|
140
|
-
|
|
141
138
|
def make_default_recipe_run_status_response() -> RecipeRunStatusResponse:
|
|
142
139
|
return RecipeRunStatusResponse(recipeRunStatusId=1)
|
|
143
140
|
|
|
@@ -234,3 +231,41 @@ def fake_gql_client():
|
|
|
234
231
|
Convenience fixture for default mock GQL client. To customize, use fake_gql_client_factory.
|
|
235
232
|
"""
|
|
236
233
|
return fake_gql_client_factory()
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def input_dataset_parameters_part_factory(
|
|
237
|
+
parameter_count: int = 1,
|
|
238
|
+
parameter_value_count: int = 1,
|
|
239
|
+
has_date: bool = False,
|
|
240
|
+
has_file: bool = False,
|
|
241
|
+
) -> list[dict]:
|
|
242
|
+
"""Create a mock InputDatasetPartDocumentList with parameters."""
|
|
243
|
+
result = [
|
|
244
|
+
{
|
|
245
|
+
"parameterName": uuid4().hex[:6],
|
|
246
|
+
"parameterValues": [
|
|
247
|
+
{"parameterValueId": i, "parameterValue": json.dumps(uuid4().hex)}
|
|
248
|
+
for i in range(parameter_value_count)
|
|
249
|
+
],
|
|
250
|
+
}
|
|
251
|
+
for _ in range(parameter_count)
|
|
252
|
+
]
|
|
253
|
+
if has_date:
|
|
254
|
+
base = datetime(2018, 9, 14, 0, 0, 0) # This date is before any possible start dates
|
|
255
|
+
for parameter_index, data in enumerate(result):
|
|
256
|
+
for item in data["parameterValues"]:
|
|
257
|
+
dt = base + timedelta(days=parameter_index)
|
|
258
|
+
item["parameterValueStartDate"] = dt.isoformat()
|
|
259
|
+
if has_file:
|
|
260
|
+
for data in result:
|
|
261
|
+
param_list = data["parameterValues"]
|
|
262
|
+
for item in param_list:
|
|
263
|
+
item["parameterValue"] = json.dumps(
|
|
264
|
+
{
|
|
265
|
+
"__file__": {
|
|
266
|
+
"bucket": "data",
|
|
267
|
+
"objectKey": f"parameters/{data['parameterName']}/{uuid4().hex}.dat",
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
return result
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
from datetime import datetime
|
|
3
2
|
from typing import Any
|
|
4
3
|
from uuid import uuid4
|
|
5
4
|
|
|
@@ -8,6 +7,7 @@ import pytest
|
|
|
8
7
|
from dkist_processing_common.codecs.basemodel import basemodel_decoder
|
|
9
8
|
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
10
9
|
from dkist_processing_common.models.tags import Tag
|
|
10
|
+
from dkist_processing_common.tests.mock_metadata_store import input_dataset_parameters_part_factory
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
def input_dataset_frames_part_factory(bucket_count: int = 1) -> list[dict]:
|
|
@@ -25,42 +25,6 @@ def flatten_frame_parts(frame_parts: list[dict]) -> list[tuple[str, str]]:
|
|
|
25
25
|
return result
|
|
26
26
|
|
|
27
27
|
|
|
28
|
-
def input_dataset_parameters_part_factory(
|
|
29
|
-
parameter_count: int = 1,
|
|
30
|
-
parameter_value_count: int = 1,
|
|
31
|
-
has_date: bool = False,
|
|
32
|
-
has_file: bool = False,
|
|
33
|
-
) -> list[dict]:
|
|
34
|
-
result = [
|
|
35
|
-
{
|
|
36
|
-
"parameterName": uuid4().hex[:6],
|
|
37
|
-
"parameterValues": [
|
|
38
|
-
{"parameterValueId": i, "parameterValue": json.dumps(uuid4().hex)}
|
|
39
|
-
for i in range(parameter_value_count)
|
|
40
|
-
],
|
|
41
|
-
}
|
|
42
|
-
for _ in range(parameter_count)
|
|
43
|
-
]
|
|
44
|
-
if has_date:
|
|
45
|
-
for data in result:
|
|
46
|
-
param_list = data["parameterValues"]
|
|
47
|
-
for item in param_list:
|
|
48
|
-
item["parameterValueStartDate"] = datetime(2022, 9, 14).isoformat()
|
|
49
|
-
if has_file:
|
|
50
|
-
for data in result:
|
|
51
|
-
param_list = data["parameterValues"]
|
|
52
|
-
for item in param_list:
|
|
53
|
-
item["parameterValue"] = json.dumps(
|
|
54
|
-
{
|
|
55
|
-
"__file__": {
|
|
56
|
-
"bucket": "data",
|
|
57
|
-
"objectKey": f"parameters/{data['parameterName']}/{uuid4().hex}.dat",
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
)
|
|
61
|
-
return result
|
|
62
|
-
|
|
63
|
-
|
|
64
28
|
@pytest.mark.parametrize(
|
|
65
29
|
"input_dataset_parts",
|
|
66
30
|
[
|
|
@@ -260,7 +260,7 @@ def test_subclass_flowers(visp_parse_inputs_task, max_cs_step_time_sec):
|
|
|
260
260
|
tag_pot, constant_pot = visp_parse_inputs_task.make_flower_pots()
|
|
261
261
|
|
|
262
262
|
assert len(tag_pot.stems) == 1
|
|
263
|
-
assert len(constant_pot.stems) ==
|
|
263
|
+
assert len(constant_pot.stems) == 60
|
|
264
264
|
all_flower_names = [StemName.modstate]
|
|
265
265
|
assert sorted([f.stem_name for f in tag_pot.stems]) == sorted(all_flower_names)
|
|
266
266
|
all_bud_names = [b.stem_name for b in default_constant_bud_factory()] + [BudName.num_modstates]
|
|
@@ -277,7 +277,7 @@ def test_dataset_extra_bud_factory(visp_parse_inputs_task, max_cs_step_time_sec)
|
|
|
277
277
|
stem_names = [f.stem_name.value for f in constant_pot.stems]
|
|
278
278
|
bud_name_base = [
|
|
279
279
|
"DATE_BEGIN",
|
|
280
|
-
"
|
|
280
|
+
"OBSERVING_PROGRAM_EXECUTION_IDS",
|
|
281
281
|
"NUM_RAW_FRAMES_PER_FPA",
|
|
282
282
|
"TELESCOPE_TRACKING_MODE",
|
|
283
283
|
"COUDE_TABLE_TRACKING_MODE",
|
|
@@ -295,8 +295,14 @@ def test_dataset_extra_bud_factory(visp_parse_inputs_task, max_cs_step_time_sec)
|
|
|
295
295
|
"GOS_LEVEL0_STATUS",
|
|
296
296
|
]
|
|
297
297
|
for base in bud_name_base:
|
|
298
|
-
assert "DARK_" + base in stem_names
|
|
299
298
|
assert "SOLAR_GAIN_" + base in stem_names
|
|
299
|
+
if base not in [
|
|
300
|
+
"NUM_RAW_FRAMES_PER_FPA",
|
|
301
|
+
"TELESCOPE_TRACKING_MODE",
|
|
302
|
+
"COUDE_TABLE_TRACKING_MODE",
|
|
303
|
+
"TELESCOPE_SCANNING_MODE",
|
|
304
|
+
]:
|
|
305
|
+
assert "DARK_" + base in stem_names
|
|
300
306
|
if "GOS" not in base:
|
|
301
307
|
assert "POLCAL_" + base in stem_names
|
|
302
308
|
|
|
@@ -57,24 +57,3 @@ def test_object_messages(publish_catalog_and_quality_messages_task):
|
|
|
57
57
|
assert message.body.conversationId == str(task.recipe_run_id)
|
|
58
58
|
assert message.body.objectType == object_type
|
|
59
59
|
assert message.body.groupId == task.constants.dataset_id
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
def test_quality_report_message(publish_catalog_and_quality_messages_task):
|
|
63
|
-
"""
|
|
64
|
-
:Given: a PublishCatalogAndQualityMessages task
|
|
65
|
-
:When: creating quality report message
|
|
66
|
-
:Then: the attributes are correctly populated
|
|
67
|
-
"""
|
|
68
|
-
# Given
|
|
69
|
-
task, proposal_id = publish_catalog_and_quality_messages_task
|
|
70
|
-
# When
|
|
71
|
-
message = task.quality_report_message
|
|
72
|
-
# Then
|
|
73
|
-
assert isinstance(message, CreateQualityReportMessage)
|
|
74
|
-
assert message.body.bucket == task.destination_bucket
|
|
75
|
-
# objectName exists and can be evaluated as a valid path
|
|
76
|
-
assert message.body.objectName
|
|
77
|
-
_ = Path(message.body.objectName)
|
|
78
|
-
assert message.body.datasetId == task.constants.dataset_id
|
|
79
|
-
assert message.body.conversationId == str(task.recipe_run_id)
|
|
80
|
-
assert message.body.incrementDatasetCatalogReceiptCount is True
|
|
@@ -1214,12 +1214,20 @@ def wavecal_weights(wavecal_input_wavelength) -> np.ndarray:
|
|
|
1214
1214
|
|
|
1215
1215
|
|
|
1216
1216
|
@pytest.fixture(scope="session")
|
|
1217
|
-
def wavecal_fit_result(wavecal_input_wavelength) -> FitResult:
|
|
1217
|
+
def wavecal_fit_result(wavecal_input_wavelength, wavecal_input_spectrum) -> FitResult:
|
|
1218
1218
|
wavelength_params = WavelengthParameters(
|
|
1219
1219
|
crpix=1, crval=10.0, dispersion=1, grating_constant=1, order=1, incident_light_angle=0
|
|
1220
1220
|
)
|
|
1221
|
-
|
|
1222
|
-
|
|
1221
|
+
|
|
1222
|
+
residuals = np.random.random(wavecal_input_wavelength.size)
|
|
1223
|
+
residuals[-1] = np.nan
|
|
1224
|
+
minimizer_result = MinimizerResult(residual=residuals)
|
|
1225
|
+
return FitResult(
|
|
1226
|
+
wavelength_parameters=wavelength_params,
|
|
1227
|
+
minimizer_result=minimizer_result,
|
|
1228
|
+
input_wavelength_vector=wavecal_input_wavelength,
|
|
1229
|
+
input_spectrum=wavecal_input_spectrum,
|
|
1230
|
+
)
|
|
1223
1231
|
|
|
1224
1232
|
|
|
1225
1233
|
@pytest.mark.parametrize(
|
|
@@ -19,6 +19,9 @@ from dkist_processing_common.parsers.experiment_id_bud import ExperimentIdBud
|
|
|
19
19
|
from dkist_processing_common.parsers.id_bud import TaskContributingIdsBud
|
|
20
20
|
from dkist_processing_common.parsers.near_bud import NearFloatBud
|
|
21
21
|
from dkist_processing_common.parsers.near_bud import TaskNearFloatBud
|
|
22
|
+
from dkist_processing_common.parsers.observing_program_id_bud import (
|
|
23
|
+
TaskContributingObservingProgramExecutionIdsBud,
|
|
24
|
+
)
|
|
22
25
|
from dkist_processing_common.parsers.proposal_id_bud import ContributingProposalIdsBud
|
|
23
26
|
from dkist_processing_common.parsers.proposal_id_bud import ProposalIdBud
|
|
24
27
|
from dkist_processing_common.parsers.retarder import RetarderNameBud
|
|
@@ -51,6 +54,7 @@ class FitsReaderMetadataKey(StrEnum):
|
|
|
51
54
|
near_thing = "near"
|
|
52
55
|
proposal_id = "ID___013"
|
|
53
56
|
experiment_id = "ID___012"
|
|
57
|
+
observing_program_execution_id = "ID___008"
|
|
54
58
|
ip_task_type = "DKIST004"
|
|
55
59
|
ip_start_time = "DKIST011"
|
|
56
60
|
fpa_exposure_time_ms = "XPOSURE"
|
|
@@ -77,6 +81,9 @@ class FitsReader(FitsAccessBase):
|
|
|
77
81
|
self.name = name
|
|
78
82
|
self.proposal_id: str = self.header.get(FitsReaderMetadataKey.proposal_id)
|
|
79
83
|
self.experiment_id: str = self.header.get(FitsReaderMetadataKey.experiment_id)
|
|
84
|
+
self.observing_program_execution_id: str = self.header.get(
|
|
85
|
+
FitsReaderMetadataKey.observing_program_execution_id
|
|
86
|
+
)
|
|
80
87
|
self.ip_task_type: str = self.header.get(FitsReaderMetadataKey.ip_task_type)
|
|
81
88
|
self.ip_start_time: str = self.header.get(FitsReaderMetadataKey.ip_start_time)
|
|
82
89
|
self.fpa_exposure_time_ms: float = self.header.get(
|
|
@@ -113,6 +120,7 @@ def basic_header_objs():
|
|
|
113
120
|
"DKIST004": "observe",
|
|
114
121
|
"ID___012": "experiment_id_1",
|
|
115
122
|
"ID___013": "proposal_id_1",
|
|
123
|
+
"ID___008": "observing_program_execution_id_1",
|
|
116
124
|
"XPOSURE": 0.0013000123,
|
|
117
125
|
"TEXPOSUR": 10.0,
|
|
118
126
|
"NSUMEXP": 3,
|
|
@@ -131,6 +139,7 @@ def basic_header_objs():
|
|
|
131
139
|
"DKIST004": "observe",
|
|
132
140
|
"ID___012": "experiment_id_1",
|
|
133
141
|
"ID___013": "proposal_id_1",
|
|
142
|
+
"ID___008": "observing_program_execution_id_2",
|
|
134
143
|
"XPOSURE": 0.0013000987,
|
|
135
144
|
"TEXPOSUR": 10.0,
|
|
136
145
|
"NSUMEXP": 3,
|
|
@@ -150,6 +159,7 @@ def basic_header_objs():
|
|
|
150
159
|
"DKIST004": "dark",
|
|
151
160
|
"ID___012": "experiment_id_2",
|
|
152
161
|
"ID___013": "proposal_id_2",
|
|
162
|
+
"ID___008": "observing_program_execution_id_2",
|
|
153
163
|
"XPOSURE": 12.345,
|
|
154
164
|
"TEXPOSUR": 1.123456789,
|
|
155
165
|
"NSUMEXP": 1,
|
|
@@ -170,6 +180,7 @@ def basic_header_objs():
|
|
|
170
180
|
"DKIST004": "observe",
|
|
171
181
|
"ID___012": "experiment_id_1",
|
|
172
182
|
"ID___013": "proposal_id_1",
|
|
183
|
+
"ID___008": "observing_program_execution_id_1",
|
|
173
184
|
"XPOSURE": 100.0,
|
|
174
185
|
"TEXPOSUR": 11.0,
|
|
175
186
|
"NSUMEXP": 4,
|
|
@@ -185,6 +196,7 @@ def basic_header_objs():
|
|
|
185
196
|
{
|
|
186
197
|
"DKIST004": "gain",
|
|
187
198
|
"ID___013": "proposal_id_1",
|
|
199
|
+
"ID___008": "observing_program_execution_id_1",
|
|
188
200
|
"id_key": 0,
|
|
189
201
|
"constant": 6.28,
|
|
190
202
|
"near": 1.23,
|
|
@@ -603,6 +615,29 @@ def test_task_contributing_ids_bud(basic_header_objs):
|
|
|
603
615
|
assert sorted(list(petal[0].value)) == ["experiment_id_2"]
|
|
604
616
|
|
|
605
617
|
|
|
618
|
+
def test_task_contributing_observing_program_execution_ids_bud(basic_header_objs):
|
|
619
|
+
"""
|
|
620
|
+
Given: A set of headers with observing program execution ID values for different tasks
|
|
621
|
+
When: Ingesting the headers with a TaskContributingObservingProgramExecutionIdsBud for a task type
|
|
622
|
+
Then: The Bud's petal is the observing program execution IDs for the that task type
|
|
623
|
+
"""
|
|
624
|
+
bud = TaskContributingObservingProgramExecutionIdsBud(
|
|
625
|
+
constant_name="NOT_A_REAL_BUD",
|
|
626
|
+
ip_task_types=TaskName.observe,
|
|
627
|
+
)
|
|
628
|
+
assert bud.stem_name == "NOT_A_REAL_BUD"
|
|
629
|
+
for fo in basic_header_objs:
|
|
630
|
+
key = fo.name
|
|
631
|
+
bud.update(key, fo)
|
|
632
|
+
|
|
633
|
+
petal = list(bud.petals)
|
|
634
|
+
assert len(petal) == 1
|
|
635
|
+
assert sorted(list(petal[0].value)) == [
|
|
636
|
+
"observing_program_execution_id_1",
|
|
637
|
+
"observing_program_execution_id_2",
|
|
638
|
+
]
|
|
639
|
+
|
|
640
|
+
|
|
606
641
|
def test_exp_time_flower(basic_header_objs):
|
|
607
642
|
"""
|
|
608
643
|
Given: A set of filepaths and associated headers with XPOSURE keywords
|
|
@@ -95,13 +95,10 @@ def test_submit_dataset_metadata(
|
|
|
95
95
|
mocker.patch(
|
|
96
96
|
"dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
|
|
97
97
|
)
|
|
98
|
-
# intercept
|
|
98
|
+
# intercept this GraphQLClient call so it can be confirmed
|
|
99
99
|
mocked_metadata_store_add_dataset_receipt_account = mocker.patch.object(
|
|
100
100
|
metadata_store.MetadataStoreMixin, "metadata_store_add_dataset_receipt_account"
|
|
101
101
|
)
|
|
102
|
-
mocked_metadata_store_add_quality_data = mocker.patch.object(
|
|
103
|
-
metadata_store.MetadataStoreMixin, "metadata_store_add_quality_data"
|
|
104
|
-
)
|
|
105
102
|
task = submit_dataset_metadata_task
|
|
106
103
|
|
|
107
104
|
# When
|
|
@@ -109,4 +106,3 @@ def test_submit_dataset_metadata(
|
|
|
109
106
|
|
|
110
107
|
# Then
|
|
111
108
|
mocked_metadata_store_add_dataset_receipt_account.assert_called_once()
|
|
112
|
-
mocked_metadata_store_add_quality_data.assert_called_once()
|
|
@@ -12,14 +12,30 @@ from sqids import Sqids
|
|
|
12
12
|
|
|
13
13
|
from dkist_processing_common._util.scratch import WorkflowFileSystem
|
|
14
14
|
from dkist_processing_common.codecs.asdf import asdf_decoder
|
|
15
|
+
from dkist_processing_common.codecs.basemodel import basemodel_encoder
|
|
15
16
|
from dkist_processing_common.codecs.bytes import bytes_decoder
|
|
16
17
|
from dkist_processing_common.codecs.fits import fits_hdulist_encoder
|
|
17
18
|
from dkist_processing_common.codecs.json import json_decoder
|
|
18
19
|
from dkist_processing_common.codecs.quality import quality_data_encoder
|
|
20
|
+
from dkist_processing_common.models.input_dataset import InputDatasetParameter
|
|
21
|
+
from dkist_processing_common.models.input_dataset import InputDatasetPartDocumentList
|
|
19
22
|
from dkist_processing_common.models.tags import Tag
|
|
20
23
|
from dkist_processing_common.tasks import CreateTrialAsdf
|
|
21
24
|
from dkist_processing_common.tasks import CreateTrialDatasetInventory
|
|
22
25
|
from dkist_processing_common.tasks import CreateTrialQualityReport
|
|
26
|
+
from dkist_processing_common.tests.mock_metadata_store import input_dataset_parameters_part_factory
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@pytest.fixture()
|
|
30
|
+
def mock_input_dataset_parts() -> InputDatasetPartDocumentList:
|
|
31
|
+
"""An InputDatasetPartDocumentList with two parameters, each with one value and a date."""
|
|
32
|
+
raw = input_dataset_parameters_part_factory(
|
|
33
|
+
parameter_count=2,
|
|
34
|
+
parameter_value_count=1,
|
|
35
|
+
has_date=True,
|
|
36
|
+
has_file=False,
|
|
37
|
+
)
|
|
38
|
+
return InputDatasetPartDocumentList.model_validate({"doc_list": raw})
|
|
23
39
|
|
|
24
40
|
|
|
25
41
|
@pytest.fixture()
|
|
@@ -41,6 +57,24 @@ def scratch_with_l1_frames(recipe_run_id, tmp_path) -> WorkflowFileSystem:
|
|
|
41
57
|
scratch.write(
|
|
42
58
|
file_obj, tags=[Tag.output(), Tag.frame()], relative_path=f"{uuid4().hex}.dat"
|
|
43
59
|
)
|
|
60
|
+
|
|
61
|
+
return scratch
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.fixture()
|
|
65
|
+
def scratch_with_l1_frames_and_parameters(
|
|
66
|
+
scratch_with_l1_frames, mock_input_dataset_parts
|
|
67
|
+
) -> WorkflowFileSystem:
|
|
68
|
+
"""Scratch instance for a recipe run id with tagged L1 frames and input parameters."""
|
|
69
|
+
scratch = scratch_with_l1_frames
|
|
70
|
+
|
|
71
|
+
# Write validated Pydantic model bytes expected by InputDatasetPartDocumentList
|
|
72
|
+
file_obj = basemodel_encoder(mock_input_dataset_parts)
|
|
73
|
+
scratch.write(
|
|
74
|
+
file_obj,
|
|
75
|
+
tags=Tag.input_dataset_parameters(),
|
|
76
|
+
relative_path=f"{uuid4().hex}.json",
|
|
77
|
+
)
|
|
44
78
|
return scratch
|
|
45
79
|
|
|
46
80
|
|
|
@@ -85,6 +119,22 @@ def create_trial_asdf_task(
|
|
|
85
119
|
task._purge()
|
|
86
120
|
|
|
87
121
|
|
|
122
|
+
@pytest.fixture(scope="function")
|
|
123
|
+
def create_trial_asdf_task_with_params(
|
|
124
|
+
recipe_run_id, tmp_path, scratch_with_l1_frames_and_parameters, fake_constants_db
|
|
125
|
+
) -> CreateTrialAsdf:
|
|
126
|
+
"""An instance of CreateTrialAsdf with L1 frames and input parameters tagged in scratch."""
|
|
127
|
+
task = CreateTrialAsdf(
|
|
128
|
+
recipe_run_id=recipe_run_id,
|
|
129
|
+
workflow_name="trial_asdf",
|
|
130
|
+
workflow_version="trial_asdf_version",
|
|
131
|
+
)
|
|
132
|
+
task.scratch = scratch_with_l1_frames_and_parameters
|
|
133
|
+
task.constants._update(fake_constants_db)
|
|
134
|
+
yield task
|
|
135
|
+
task._purge()
|
|
136
|
+
|
|
137
|
+
|
|
88
138
|
@pytest.fixture()
|
|
89
139
|
def create_trial_quality_report_task(
|
|
90
140
|
recipe_run_id, tmp_path, fake_constants_db
|
|
@@ -143,25 +193,32 @@ def test_create_trial_dataset_inventory(create_trial_dataset_inventory_task):
|
|
|
143
193
|
assert len(inventory) > 20 # a bunch
|
|
144
194
|
|
|
145
195
|
|
|
146
|
-
|
|
196
|
+
@pytest.mark.parametrize("with_params", [False, True], ids=["no_params", "with_params"])
|
|
197
|
+
def test_create_trial_asdf(with_params, request, recipe_run_id, mock_input_dataset_parts):
|
|
147
198
|
"""
|
|
148
199
|
:Given: An instance of CreateTrialAsdf with L1 frames tagged in scratch
|
|
149
200
|
:When: CreateTrialAsdf is run
|
|
150
201
|
:Then: An asdf file for the dataset is tagged in scratch
|
|
151
202
|
"""
|
|
152
|
-
task =
|
|
203
|
+
task = request.getfixturevalue(
|
|
204
|
+
"create_trial_asdf_task_with_params" if with_params else "create_trial_asdf_task"
|
|
205
|
+
)
|
|
153
206
|
# When
|
|
154
207
|
task()
|
|
208
|
+
|
|
155
209
|
# Then
|
|
156
210
|
asdf_tags = [Tag.output(), Tag.asdf()]
|
|
157
211
|
filepaths = list(task.scratch.find_all(tags=asdf_tags))
|
|
158
212
|
assert len(filepaths) == 1
|
|
159
213
|
dataset_id = Sqids(min_length=6, alphabet=ascii_uppercase).encode([recipe_run_id])
|
|
160
214
|
assert filepaths[0].name == f"INSTRUMENT_L1_20240416T160000_{dataset_id}_metadata.asdf"
|
|
215
|
+
|
|
161
216
|
results = list(task.read(tags=asdf_tags, decoder=asdf_decoder))
|
|
162
217
|
assert len(results) == 1
|
|
218
|
+
|
|
163
219
|
tree = results[0]
|
|
164
220
|
assert isinstance(tree, dict)
|
|
221
|
+
|
|
165
222
|
for file_name in tree["dataset"].files.filenames:
|
|
166
223
|
# This is a slightly better than check that `not Path(file_name).is_absolute()` because it confirms
|
|
167
224
|
# we've correctly stripped the path of *all* parents (not just those that start at root).
|
|
@@ -169,6 +226,19 @@ def test_create_trial_asdf(create_trial_asdf_task, recipe_run_id):
|
|
|
169
226
|
# `scratch.workflow_base_path`
|
|
170
227
|
assert Path(file_name).name == file_name
|
|
171
228
|
|
|
229
|
+
# Only check parameters when present
|
|
230
|
+
ds = tree["dataset"]
|
|
231
|
+
assert "parameters" in ds.meta
|
|
232
|
+
parameters = ds.meta["parameters"]
|
|
233
|
+
assert isinstance(parameters, list)
|
|
234
|
+
if with_params:
|
|
235
|
+
assert parameters, f"ASDF tree must include input parameters: {parameters}"
|
|
236
|
+
assert len(parameters) == len(mock_input_dataset_parts.doc_list)
|
|
237
|
+
for param in parameters:
|
|
238
|
+
assert InputDatasetParameter.model_validate(param) in mock_input_dataset_parts.doc_list
|
|
239
|
+
else:
|
|
240
|
+
assert ds.meta["parameters"] == []
|
|
241
|
+
|
|
172
242
|
|
|
173
243
|
def test_create_trial_quality_report(create_trial_quality_report_task):
|
|
174
244
|
"""
|
|
@@ -158,13 +158,12 @@ def complete_trial_output_task(
|
|
|
158
158
|
task.write(asdf_file_obj, relative_path=asdf_file_name, tags=[Tag.output(), Tag.asdf()])
|
|
159
159
|
|
|
160
160
|
# Write quality data
|
|
161
|
-
# quality data is not tagged as OUTPUT
|
|
162
161
|
quality_data_obj = uuid4().hex.encode("utf8")
|
|
163
162
|
quality_data_name = "quality_data.json"
|
|
164
163
|
task.write(
|
|
165
164
|
quality_data_obj,
|
|
166
165
|
relative_path=quality_data_name,
|
|
167
|
-
tags=Tag.quality_data(),
|
|
166
|
+
tags=[Tag.output(), Tag.quality_data()],
|
|
168
167
|
)
|
|
169
168
|
|
|
170
169
|
# Write a quality report file
|