dkist-processing-trend 0.1.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 (66) hide show
  1. changelog/.gitempty +0 -0
  2. dkist_processing_trend/__init__.py +10 -0
  3. dkist_processing_trend/config.py +11 -0
  4. dkist_processing_trend/models/__init__.py +1 -0
  5. dkist_processing_trend/models/constants.py +143 -0
  6. dkist_processing_trend/models/fit_options.py +15 -0
  7. dkist_processing_trend/models/fits_access.py +65 -0
  8. dkist_processing_trend/models/instrument.py +35 -0
  9. dkist_processing_trend/models/instrument_options.py +35 -0
  10. dkist_processing_trend/models/parameters.py +134 -0
  11. dkist_processing_trend/models/tags.py +117 -0
  12. dkist_processing_trend/models/task_name.py +20 -0
  13. dkist_processing_trend/parsers/__init__.py +1 -0
  14. dkist_processing_trend/parsers/arm_id.py +103 -0
  15. dkist_processing_trend/parsers/instrument_unique_bud.py +40 -0
  16. dkist_processing_trend/parsers/time.py +27 -0
  17. dkist_processing_trend/parsers/trend_l0_fits_access.py +123 -0
  18. dkist_processing_trend/tasks/__init__.py +27 -0
  19. dkist_processing_trend/tasks/arm_task_factory.py +57 -0
  20. dkist_processing_trend/tasks/dark.py +56 -0
  21. dkist_processing_trend/tasks/gain.py +74 -0
  22. dkist_processing_trend/tasks/initialize_arm_tasks.py +50 -0
  23. dkist_processing_trend/tasks/parse.py +169 -0
  24. dkist_processing_trend/tasks/prepare_fit_data_base.py +257 -0
  25. dkist_processing_trend/tasks/run_pac_fitter.py +392 -0
  26. dkist_processing_trend/tasks/trend_base.py +97 -0
  27. dkist_processing_trend/tasks/trend_output_data.py +167 -0
  28. dkist_processing_trend/tasks/visp/__init__.py +6 -0
  29. dkist_processing_trend/tasks/visp/visp_dmpd.py +410 -0
  30. dkist_processing_trend/tasks/visp/visp_extract_beam.py +14 -0
  31. dkist_processing_trend/tasks/visp/visp_geometric.py +260 -0
  32. dkist_processing_trend/tasks/visp/visp_prep_fit_data.py +162 -0
  33. dkist_processing_trend/tasks/visp/visp_process_demod.py +236 -0
  34. dkist_processing_trend/tasks/write_trend.py +663 -0
  35. dkist_processing_trend/tests/__init__.py +1 -0
  36. dkist_processing_trend/tests/conftest.py +718 -0
  37. dkist_processing_trend/tests/local_trial_workflows/__init__.py +0 -0
  38. dkist_processing_trend/tests/local_trial_workflows/l0_to_trend_visp_polcal.py +294 -0
  39. dkist_processing_trend/tests/local_trial_workflows/local_trial_helpers.py +488 -0
  40. dkist_processing_trend/tests/test_arm_task_factory.py +82 -0
  41. dkist_processing_trend/tests/test_base_tasks.py +86 -0
  42. dkist_processing_trend/tests/test_constants.py +120 -0
  43. dkist_processing_trend/tests/test_dark.py +97 -0
  44. dkist_processing_trend/tests/test_gain.py +135 -0
  45. dkist_processing_trend/tests/test_parameters.py +149 -0
  46. dkist_processing_trend/tests/test_parse.py +276 -0
  47. dkist_processing_trend/tests/test_prep_fit_data_base.py +233 -0
  48. dkist_processing_trend/tests/test_publish_catalog_messages.py +45 -0
  49. dkist_processing_trend/tests/test_run_pac_fitter.py +371 -0
  50. dkist_processing_trend/tests/test_stems.py +75 -0
  51. dkist_processing_trend/tests/test_transfer_output_data.py +76 -0
  52. dkist_processing_trend/tests/test_trend_fits_access.py +173 -0
  53. dkist_processing_trend/tests/test_visp.py +874 -0
  54. dkist_processing_trend/tests/test_workflows.py +10 -0
  55. dkist_processing_trend/tests/test_write_trend.py +460 -0
  56. dkist_processing_trend/workflows/__init__.py +3 -0
  57. dkist_processing_trend/workflows/visp.py +58 -0
  58. dkist_processing_trend-0.1.0.dist-info/METADATA +549 -0
  59. dkist_processing_trend-0.1.0.dist-info/RECORD +66 -0
  60. dkist_processing_trend-0.1.0.dist-info/WHEEL +5 -0
  61. dkist_processing_trend-0.1.0.dist-info/top_level.txt +3 -0
  62. docs/conf.py +57 -0
  63. docs/index.rst +10 -0
  64. docs/l0_to_trend_visp_polcal.rst +4 -0
  65. docs/landing_page.rst +11 -0
  66. docs/requirements_table.rst +8 -0
@@ -0,0 +1,120 @@
1
+ import json
2
+ from dataclasses import asdict
3
+ from dataclasses import dataclass
4
+ from dataclasses import field
5
+
6
+ import pytest
7
+ from dkist_processing_common.tasks.base import WorkflowTaskBase
8
+
9
+ from dkist_processing_trend.models.constants import TrendBudName
10
+ from dkist_processing_trend.models.constants import TrendConstants
11
+
12
+
13
+ @dataclass
14
+ class TrendOnlyConstantDB:
15
+ earliest_ip_start_time: str = "1900-01-01"
16
+ arm_id_list: tuple[str | int] = (1, 2, 3)
17
+ wavelength_for_arm: dict[str, float] = field(default_factory=lambda: {"1": 123.4, '"CI"': 567})
18
+ observing_program_id_list: tuple[str] = ("TOP1", "TOP2")
19
+ experiment_id_list: tuple[str] = "EX1"
20
+ proposal_id_list: tuple[str] = "PROP5"
21
+ hls_version_list: tuple[str] = ("Tau 6.2.8", "Pi 3.14")
22
+ visp_grating_angle_for_arm: dict[str, float] = field(
23
+ default_factory=lambda: {"1": 9.87, "2": 9.872}
24
+ )
25
+ visp_arm_position_for_arm: dict[str, float] = field(
26
+ default_factory=lambda: {"1": 8.76, "2": 8.762}
27
+ )
28
+ visp_grating_constant_for_arm: dict[str, float] = field(
29
+ default_factory=lambda: {"1": 9870, "2": 9872}
30
+ )
31
+ cryonirsp_od_filter_name_for_arm: dict[str, str] = field(
32
+ default_factory=lambda: {'"CI"': "CRYOFILT"}
33
+ )
34
+ cryonirsp_grating_angle_for_arm: dict[str, float] = field(
35
+ default_factory=lambda: {'"CI"': 123.4, '"SP"': 123.5}
36
+ )
37
+ cryonirsp_grating_littrow_angle_for_arm: dict[str, float] = field(
38
+ default_factory=lambda: {'"CI"': 12.34, '"SP"': 12.35}
39
+ )
40
+ cryonirsp_grating_constant_for_arm: dict[str, float] = field(
41
+ default_factory=lambda: {'"SP"': 1234}
42
+ )
43
+ cryonirsp_slit_width_for_arm: dict[str, float] = field(default_factory=lambda: {'"CI"': 0.1234})
44
+ dlnirsp_grating_angle_for_arm: dict[str, float] = field(
45
+ default_factory=lambda: {'"VIS"': 2.22, '"JBand"': 2.222, '"HBand"': 2.2222}
46
+ )
47
+ dlnirsp_arm_position_for_arm: dict[str, float] = field(
48
+ default_factory=lambda: {'"JBand"': 3.33}
49
+ )
50
+ dlnirsp_grating_constant_for_arm: dict[str, float] = field(
51
+ default_factory=lambda: {'"HBand"': 4444}
52
+ )
53
+ # And one "common" constant just to confirm subclassing
54
+ instrument: str = "an instrument"
55
+
56
+
57
+ @pytest.fixture(scope="session")
58
+ def arm_id_lookup_constant_names():
59
+ return {
60
+ "wavelength_for_arm",
61
+ "visp_grating_angle_for_arm",
62
+ "visp_arm_position_for_arm",
63
+ "visp_grating_constant_for_arm",
64
+ "cryonirsp_od_filter_name_for_arm",
65
+ "cryonirsp_grating_angle_for_arm",
66
+ "cryonirsp_grating_littrow_angle_for_arm",
67
+ "cryonirsp_grating_constant_for_arm",
68
+ "cryonirsp_slit_width_for_arm",
69
+ "dlnirsp_grating_angle_for_arm",
70
+ "dlnirsp_arm_position_for_arm",
71
+ "dlnirsp_grating_constant_for_arm",
72
+ }
73
+
74
+
75
+ @pytest.fixture
76
+ def trend_constants_db():
77
+ values = TrendOnlyConstantDB()
78
+ return {k.upper(): v for k, v in asdict(values).items()}
79
+
80
+
81
+ @pytest.fixture
82
+ def task_with_trend_constants(recipe_run_id, link_constants_db, trend_constants_db):
83
+ class Task(WorkflowTaskBase):
84
+ @property
85
+ def constants_model_class(self):
86
+ return TrendConstants
87
+
88
+ def run(self): ...
89
+
90
+ link_constants_db(recipe_run_id, trend_constants_db)
91
+ with Task(
92
+ recipe_run_id=recipe_run_id,
93
+ workflow_name="workflow_name",
94
+ workflow_version="workflow_version",
95
+ ) as task:
96
+ yield task
97
+ task._purge()
98
+
99
+
100
+ def test_trend_constants(
101
+ task_with_trend_constants, trend_constants_db, arm_id_lookup_constant_names
102
+ ):
103
+ """
104
+ Given: A task whose `constants_model_class` property is `TrendConstants`
105
+ When: Accessing the constants implied by `TrendBudName`
106
+ Then: The property on `.constants` matches the correct value in the constants db
107
+ """
108
+ task = task_with_trend_constants
109
+ task_constants = task.constants
110
+ assert task_constants.instrument == trend_constants_db["INSTRUMENT"]
111
+ for constant in TrendBudName:
112
+ expected_value = trend_constants_db[constant.value]
113
+ if isinstance(expected_value, tuple):
114
+ # B/c JSON serializes/deserializes lists as tuples
115
+ expected_value = list(expected_value)
116
+ if constant.name in arm_id_lookup_constant_names:
117
+ expected_value = {json.loads(k): v for k, v in expected_value.items()}
118
+ assert (
119
+ getattr(task_constants, constant.name) == expected_value
120
+ ), f"{constant.name} does not match"
@@ -0,0 +1,97 @@
1
+ import numpy as np
2
+ import pytest
3
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
4
+ from dkist_processing_common.codecs.fits import fits_array_decoder
5
+
6
+ from dkist_processing_trend.models.instrument import InstrumentName
7
+ from dkist_processing_trend.models.tags import TrendTag
8
+ from dkist_processing_trend.tasks.dark import DarkCalibration
9
+ from dkist_processing_trend.tests.conftest import ModulatedCSStepHeaders
10
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
11
+ from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
12
+
13
+
14
+ @pytest.fixture
15
+ def arm_id() -> str:
16
+ return "arm2"
17
+
18
+
19
+ @pytest.fixture
20
+ def dark_task(tmp_path, recipe_run_id, link_constants_db, arm_id):
21
+
22
+ link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(ARM_ID_LIST=(1, "arm3", "arm2")))
23
+ with DarkCalibration(
24
+ arm_id=arm_id, recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
25
+ ) as task:
26
+ task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
27
+ yield task
28
+ task._purge
29
+
30
+
31
+ def correct_arm_dark_signal_from_modstate(frame: ModulatedCSStepHeaders) -> np.ndarray:
32
+ shape = frame.array_shape
33
+ signal = frame.current_modstate * 100
34
+ return np.full(shape, signal)
35
+
36
+
37
+ def wrong_arm_dark_signal_from_modstate(frame: ModulatedCSStepHeaders) -> np.ndarray:
38
+ return correct_arm_dark_signal_from_modstate(frame) * 1000
39
+
40
+
41
+ def test_dark_task(dark_task, arm_id, mocker, fake_gql_client):
42
+ """
43
+ Given: A `DarkCalibration` task and some frames tagged with POLCAL_DARK in *two different arms*
44
+ When: Running the task instantiated with a single arm
45
+ Then: The correct dark frame is made and did not include data from arms other than the task was instantiated with
46
+ """
47
+ mocker.patch(
48
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
49
+ )
50
+ task = dark_task
51
+ shape = (3, 3)
52
+
53
+ # Write POLCAL_DARK data for the arm we want
54
+ write_calibration_sequence_frames_to_task(
55
+ task=task,
56
+ instrument=InstrumentName.visp,
57
+ arm_id=arm_id,
58
+ wavelength=636.0,
59
+ pol_status=["clear"],
60
+ pol_theta=[99.0],
61
+ ret_status=["clear"],
62
+ ret_theta=[99.0],
63
+ dark_status=["DarkShutter"],
64
+ num_modstates=4,
65
+ array_shape=shape,
66
+ data_func=correct_arm_dark_signal_from_modstate,
67
+ tags=[TrendTag.task_polcal_dark(), TrendTag.arm_id(arm_id)],
68
+ )
69
+
70
+ # Now the same, but for a different arm with totally different signal
71
+ write_calibration_sequence_frames_to_task(
72
+ task=task,
73
+ instrument=InstrumentName.visp,
74
+ arm_id="wrong_arm",
75
+ wavelength=636.0,
76
+ pol_status=["clear"],
77
+ pol_theta=[99.0],
78
+ ret_status=["clear"],
79
+ ret_theta=[99.0],
80
+ dark_status=["DarkShutter"],
81
+ num_modstates=4,
82
+ array_shape=(2, 2), # Different!
83
+ data_func=wrong_arm_dark_signal_from_modstate,
84
+ tags=[TrendTag.task_polcal_dark(), TrendTag.arm_id("wrong_arm")],
85
+ )
86
+
87
+ task()
88
+
89
+ tags = [
90
+ TrendTag.arm_id(arm_id),
91
+ TrendTag.intermediate(),
92
+ TrendTag.frame(),
93
+ TrendTag.task_dark(),
94
+ ]
95
+ assert task.count(tags=tags) == 1
96
+ dark_array = next(task.read(tags=tags, decoder=fits_array_decoder))
97
+ np.testing.assert_array_equal(dark_array, 250.0) # avg of [1, 2, 3, 4] * 100
@@ -0,0 +1,135 @@
1
+ from functools import partial
2
+
3
+ import numpy as np
4
+ import pytest
5
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
6
+ from dkist_processing_common.codecs.fits import fits_array_decoder
7
+ from dkist_processing_common.codecs.fits import fits_array_encoder
8
+
9
+ from dkist_processing_trend.models.instrument import InstrumentName
10
+ from dkist_processing_trend.models.tags import TrendTag
11
+ from dkist_processing_trend.tasks.gain import GainCalibration
12
+ from dkist_processing_trend.tests.conftest import ModulatedCSStepHeaders
13
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
14
+ from dkist_processing_trend.tests.conftest import write_calibration_sequence_frames_to_task
15
+
16
+
17
+ @pytest.fixture
18
+ def arm_id() -> str:
19
+ return "arm2"
20
+
21
+
22
+ @pytest.fixture
23
+ def gain_task(tmp_path, recipe_run_id, link_constants_db, arm_id):
24
+
25
+ link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(ARM_ID_LIST=(1, "arm3", "arm2")))
26
+ with GainCalibration(
27
+ arm_id=arm_id, recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
28
+ ) as task:
29
+ task.scratch = WorkflowFileSystem(recipe_run_id=recipe_run_id, scratch_base_path=tmp_path)
30
+ yield task
31
+ task._purge
32
+
33
+
34
+ @pytest.fixture
35
+ def dark_signal() -> float:
36
+ return 314.0
37
+
38
+
39
+ def correct_arm_gain_signal_from_modstate(
40
+ frame: ModulatedCSStepHeaders, dark_signal: float
41
+ ) -> np.ndarray:
42
+ shape = frame.array_shape
43
+ signal = frame.current_modstate * 1000
44
+ return np.full(shape, signal) + dark_signal
45
+
46
+
47
+ def wrong_arm_gain_signal_from_modstate(
48
+ frame: ModulatedCSStepHeaders, dark_signal: float
49
+ ) -> np.ndarray:
50
+ return correct_arm_gain_signal_from_modstate(frame, dark_signal=0) * 1000 + dark_signal
51
+
52
+
53
+ def test_gain_task(gain_task, arm_id, dark_signal, mocker, fake_gql_client):
54
+ """
55
+ Given: A `GainCalibration` task and some frames tagged with POLCAL_GAIN in *two different arms*
56
+ When: Running the task instantiated with a single arm
57
+ Then: The correct gain frame is made and did not include data from arms other than the task was instantiated with
58
+ """
59
+ mocker.patch(
60
+ "dkist_processing_common.tasks.mixin.metadata_store.GraphQLClient", new=fake_gql_client
61
+ )
62
+ task = gain_task
63
+ shape = (3, 3)
64
+
65
+ # Write POLCAL_GAIN data for the arm we want
66
+ write_calibration_sequence_frames_to_task(
67
+ task=task,
68
+ instrument=InstrumentName.visp,
69
+ arm_id=arm_id,
70
+ wavelength=636.0,
71
+ pol_status=["clear"],
72
+ pol_theta=[99.0],
73
+ ret_status=["clear"],
74
+ ret_theta=[99.0],
75
+ dark_status=["clear"],
76
+ num_modstates=4,
77
+ array_shape=shape,
78
+ data_func=partial(correct_arm_gain_signal_from_modstate, dark_signal=dark_signal),
79
+ tags=[TrendTag.task_polcal_gain(), TrendTag.arm_id(arm_id)],
80
+ )
81
+
82
+ # Now the same, but for a different arm with totally different signal
83
+ write_calibration_sequence_frames_to_task(
84
+ task=task,
85
+ instrument=InstrumentName.visp,
86
+ arm_id="wrong_arm",
87
+ wavelength=636.0,
88
+ pol_status=["clear"],
89
+ pol_theta=[99.0],
90
+ ret_status=["clear"],
91
+ ret_theta=[99.0],
92
+ dark_status=["DarkShutter"],
93
+ num_modstates=4,
94
+ array_shape=(2, 2), # Different!
95
+ data_func=partial(wrong_arm_gain_signal_from_modstate, dark_signal=dark_signal),
96
+ tags=[TrendTag.task_polcal_gain(), TrendTag.arm_id("wrong_arm")],
97
+ )
98
+
99
+ # Write two INTERMEDIATE DARK frames: one for the correct arm and one for a different arm
100
+ dark_array = np.full(shape, dark_signal)
101
+ wrong_arm_dark_array = dark_array * 100000
102
+ task.write(
103
+ data=dark_array,
104
+ tags=[
105
+ TrendTag.intermediate(),
106
+ TrendTag.frame(),
107
+ TrendTag.task_dark(),
108
+ TrendTag.arm_id(arm_id),
109
+ ],
110
+ encoder=fits_array_encoder,
111
+ )
112
+ task.write(
113
+ data=wrong_arm_dark_array,
114
+ tags=[
115
+ TrendTag.intermediate(),
116
+ TrendTag.frame(),
117
+ TrendTag.task_dark(),
118
+ TrendTag.arm_id("wrong_arm"),
119
+ ],
120
+ encoder=fits_array_encoder,
121
+ )
122
+
123
+ task()
124
+
125
+ tags = [
126
+ TrendTag.arm_id(arm_id),
127
+ TrendTag.intermediate(),
128
+ TrendTag.frame(),
129
+ TrendTag.task_gain(),
130
+ ]
131
+
132
+ expected_gain_value = 2500.0 # Mean of [1,2,3,4] * 1000 (dark signal is removed)
133
+ assert task.count(tags=tags) == 1
134
+ dark_array = next(task.read(tags=tags, decoder=fits_array_decoder))
135
+ np.testing.assert_array_equal(dark_array, expected_gain_value)
@@ -0,0 +1,149 @@
1
+ from dataclasses import asdict
2
+
3
+ import pytest
4
+ from dkist_processing_common._util.scratch import WorkflowFileSystem
5
+ from dkist_processing_common.tasks.base import WorkflowTaskBase
6
+
7
+ from dkist_processing_trend.models.fit_options import FitOptions
8
+ from dkist_processing_trend.models.instrument import InstrumentName
9
+ from dkist_processing_trend.models.instrument_options import CryonirspInstrumentOptions
10
+ from dkist_processing_trend.models.instrument_options import DlnirspInstrumentOptions
11
+ from dkist_processing_trend.models.instrument_options import VispInstrumentOptions
12
+ from dkist_processing_trend.models.parameters import TrendParameters
13
+ from dkist_processing_trend.models.parameters import TrendParsingParameters
14
+ from dkist_processing_trend.tasks.trend_base import TrendTaskBase
15
+ from dkist_processing_trend.tests.conftest import TrendInputDatasetParameterValues
16
+ from dkist_processing_trend.tests.conftest import TrendTestingConstantsDatabase
17
+
18
+
19
+ @pytest.fixture(scope="session")
20
+ def parse_parameter_names() -> set[str]:
21
+ # The property names of all parameter properties on `TrendParsingParameters`
22
+ return set(k for k, v in vars(TrendParsingParameters).items() if isinstance(v, property))
23
+
24
+
25
+ @pytest.fixture(scope="session")
26
+ def parameter_names() -> set[str]:
27
+ return set(k for k, v in vars(TrendParameters).items() if isinstance(v, property))
28
+
29
+
30
+ @pytest.fixture(scope="session")
31
+ def instrument_parameter_raw_names() -> set[str]:
32
+ return {"trend_instrument_processing_options_visp"}
33
+
34
+
35
+ @pytest.fixture(scope="session")
36
+ def instrument_parameter_property_names(instrument_parameter_raw_names) -> set[str]:
37
+ return {
38
+ p.removeprefix("trend_")
39
+ .replace("_visp", "")
40
+ .replace("_cryonirsp", "")
41
+ .replace("_dlnirsp", "")
42
+ for p in instrument_parameter_raw_names
43
+ }
44
+
45
+
46
+ class Task(WorkflowTaskBase):
47
+ def run(self):
48
+ pass
49
+
50
+
51
+ class ScienceTask(TrendTaskBase):
52
+ def run(self):
53
+ pass
54
+
55
+
56
+ @pytest.fixture
57
+ def task_with_parsing_parameters(tmp_path, recipe_run_id, assign_input_dataset_doc_to_task):
58
+ with Task(
59
+ recipe_run_id=recipe_run_id,
60
+ workflow_name="workflow_name",
61
+ workflow_version="workflow_version",
62
+ ) as task:
63
+ task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
64
+ param_values = TrendInputDatasetParameterValues()
65
+ assign_input_dataset_doc_to_task(
66
+ task=task,
67
+ parameter_values=param_values,
68
+ )
69
+ task.parameters = TrendParsingParameters(scratch=task.scratch)
70
+
71
+ yield task, param_values
72
+ task._purge()
73
+
74
+
75
+ @pytest.fixture
76
+ def task_with_parameters(
77
+ tmp_path, recipe_run_id, assign_input_dataset_doc_to_task, link_constants_db, instrument
78
+ ):
79
+ link_constants_db(recipe_run_id, TrendTestingConstantsDatabase(INSTRUMENT=instrument))
80
+ with ScienceTask(
81
+ recipe_run_id=recipe_run_id, workflow_name="name", workflow_version="version"
82
+ ) as task:
83
+ task.scratch = WorkflowFileSystem(scratch_base_path=tmp_path, recipe_run_id=recipe_run_id)
84
+ param_values = TrendInputDatasetParameterValues()
85
+ assign_input_dataset_doc_to_task(task, parameter_values=param_values)
86
+ task.parameters = TrendParameters(
87
+ scratch=task.scratch,
88
+ obs_ip_start_time=task.constants.earliest_ip_start_time,
89
+ instrument=task.constants.instrument,
90
+ )
91
+
92
+ yield task, param_values
93
+ task._purge()
94
+
95
+
96
+ def test_parse_parameters(task_with_parsing_parameters, parse_parameter_names):
97
+ """
98
+ Given: A Task whose `parameter` property is an instance of `TrendParsingParameters`
99
+ When: Accessing properties for Parse parameters
100
+ Then: The correct value is returned
101
+ """
102
+ task, expected_param_values = task_with_parsing_parameters
103
+ tested_parse_parameters = set()
104
+ task_param_attr = task.parameters
105
+ for pn, pv in asdict(expected_param_values).items():
106
+ property_name = pn.removeprefix("trend_")
107
+ if property_name in parse_parameter_names and type(pv) is not dict:
108
+ assert getattr(task_param_attr, property_name) == pv
109
+ tested_parse_parameters.add(property_name)
110
+
111
+ assert (
112
+ tested_parse_parameters == parse_parameter_names
113
+ ), f"Did not find test values for parameters {parse_parameter_names - tested_parse_parameters}"
114
+
115
+
116
+ @pytest.mark.parametrize(
117
+ "instrument", [InstrumentName.visp, InstrumentName.cryonirsp, InstrumentName.dlnirsp]
118
+ )
119
+ def test_parameters(
120
+ task_with_parameters, instrument, parameter_names, instrument_parameter_property_names
121
+ ):
122
+ """
123
+ Given: A task with `TrendParameters` as its parameters object
124
+ When: Using the `.parameters` property to access parameters
125
+ Then: The correct values are returned
126
+ """
127
+ task, expected_param_values = task_with_parameters
128
+ task_param_attr = task.parameters
129
+ expected_dict = asdict(expected_param_values)
130
+
131
+ for parameter_name in parameter_names:
132
+ db_name = f"trend_{parameter_name}"
133
+ if parameter_name in instrument_parameter_property_names:
134
+ db_name = f"{db_name}_{task_param_attr._instrument}"
135
+ expected = expected_dict[db_name]
136
+ if parameter_name == "fit_options_list":
137
+ expected = [FitOptions.model_validate(i) for i in expected]
138
+ if parameter_name == "instrument_processing_options":
139
+ match instrument:
140
+ case InstrumentName.visp:
141
+ expected = [VispInstrumentOptions.model_validate(i) for i in expected]
142
+ case InstrumentName.cryonirsp:
143
+ expected = [CryonirspInstrumentOptions.model_validate(i) for i in expected]
144
+ case InstrumentName.dlnirsp:
145
+ expected = [DlnirspInstrumentOptions.model_validate(i) for i in expected]
146
+
147
+ if isinstance(expected, tuple):
148
+ expected = list(expected)
149
+ assert getattr(task_param_attr, parameter_name) == expected