dkist-processing-common 6.2.1rc2__tar.gz → 6.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/CHANGELOG.rst +21 -0
  2. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/PKG-INFO +2 -1
  3. dkist-processing-common-6.2.2/dkist_processing_common/codecs/quality.py +88 -0
  4. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/tags.py +14 -0
  5. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/base.py +3 -1
  6. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/l1_output_data.py +79 -56
  7. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/metadata_store.py +5 -5
  8. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/quality/_base.py +3 -3
  9. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/quality_metrics.py +3 -1
  10. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/trial_catalog.py +54 -1
  11. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/trial_output_data.py +27 -0
  12. dkist-processing-common-6.2.2/dkist_processing_common/tests/test_assemble_quality.py +520 -0
  13. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_codecs.py +346 -43
  14. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_quality.py +1 -6
  15. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_quality_mixin.py +3 -3
  16. dkist-processing-common-6.2.2/dkist_processing_common/tests/test_submit_dataset_metadata.py +111 -0
  17. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_trial_catalog.py +62 -0
  18. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_trial_output_data.py +130 -25
  19. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common.egg-info/PKG-INFO +2 -1
  20. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common.egg-info/SOURCES.txt +3 -5
  21. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common.egg-info/requires.txt +4 -0
  22. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/setup.cfg +3 -0
  23. dkist-processing-common-6.2.1rc2/changelog/124.misc.rst +0 -1
  24. dkist-processing-common-6.2.1rc2/changelog/186.misc.rst +0 -1
  25. dkist-processing-common-6.2.1rc2/changelog/187.misc.rst +0 -1
  26. dkist-processing-common-6.2.1rc2/changelog/188.misc.rst +0 -1
  27. dkist-processing-common-6.2.1rc2/dkist_processing_common/models/quality_json_encoders.py +0 -31
  28. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/.gitignore +0 -0
  29. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/.pre-commit-config.yaml +0 -0
  30. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/.readthedocs.yml +0 -0
  31. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/README.rst +0 -0
  32. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/bitbucket-pipelines.yml +0 -0
  33. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/changelog/.gitempty +0 -0
  34. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/check_changelog_updated.sh +0 -0
  35. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/__init__.py +0 -0
  36. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/__init__.py +0 -0
  37. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/config.py +0 -0
  38. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/constants.py +0 -0
  39. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/dkist_location.py +0 -0
  40. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/graphql.py +0 -0
  41. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/scratch.py +0 -0
  42. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/_util/tags.py +0 -0
  43. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/__init__.py +0 -0
  44. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/asdf.py +0 -0
  45. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/bytes.py +0 -0
  46. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/fits.py +0 -0
  47. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/iobase.py +0 -0
  48. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/json.py +0 -0
  49. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/path.py +0 -0
  50. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/codecs/str.py +0 -0
  51. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
  52. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/fonts/__init__.py +0 -0
  53. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/manual.py +0 -0
  54. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/__init__.py +0 -0
  55. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/constants.py +0 -0
  56. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/fits_access.py +0 -0
  57. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/flower_pot.py +0 -0
  58. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/graphql.py +0 -0
  59. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/message.py +0 -0
  60. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/parameters.py +0 -0
  61. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/quality.py +0 -0
  62. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/task_name.py +0 -0
  63. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/models/wavelength.py +0 -0
  64. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/__init__.py +0 -0
  65. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/cs_step.py +0 -0
  66. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
  67. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
  68. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/id_bud.py +0 -0
  69. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
  70. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
  71. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
  72. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/quality.py +0 -0
  73. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
  74. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/task.py +0 -0
  75. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/time.py +0 -0
  76. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/unique_bud.py +0 -0
  77. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/parsers/wavelength.py +0 -0
  78. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/__init__.py +0 -0
  79. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/assemble_movie.py +0 -0
  80. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
  81. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/globus.py +0 -0
  82. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/input_dataset.py +0 -0
  83. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
  84. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
  85. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
  86. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/mixin/quality/_metrics.py +0 -0
  87. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/output_data_base.py +0 -0
  88. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
  89. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/teardown.py +0 -0
  90. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
  91. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tasks/write_l1.py +0 -0
  92. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/__init__.py +0 -0
  93. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/conftest.py +0 -0
  94. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
  95. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_base.py +0 -0
  96. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_constants.py +0 -0
  97. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_cs_step.py +0 -0
  98. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_dkist_location.py +0 -0
  99. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_fits_access.py +0 -0
  100. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_flower_pot.py +0 -0
  101. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_input_dataset.py +0 -0
  102. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_output_data_base.py +0 -0
  103. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_parameters.py +0 -0
  104. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
  105. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
  106. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_scratch.py +0 -0
  107. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_stems.py +0 -0
  108. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_tags.py +0 -0
  109. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_task_name.py +0 -0
  110. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_task_parsing.py +0 -0
  111. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_teardown.py +0 -0
  112. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
  113. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
  114. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
  115. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common/tests/test_write_l1.py +0 -0
  116. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
  117. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/dkist_processing_common.egg-info/top_level.txt +0 -0
  118. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/Makefile +0 -0
  119. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/changelog.rst +0 -0
  120. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/conf.py +0 -0
  121. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/index.rst +0 -0
  122. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/make.bat +0 -0
  123. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/docs/requirements.txt +0 -0
  124. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/licenses/LICENSE.rst +0 -0
  125. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/pyproject.toml +0 -0
  126. {dkist-processing-common-6.2.1rc2 → dkist-processing-common-6.2.2}/setup.py +0 -0
@@ -1,3 +1,24 @@
1
+ v6.2.2 (2024-05-07)
2
+ ===================
3
+
4
+ Features
5
+ --------
6
+
7
+ - Add the ability to create a quality report from a trial workflow. (`#185 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/185>`__)
8
+
9
+
10
+ v6.2.1 (2024-05-01)
11
+ ===================
12
+
13
+ Misc
14
+ ----
15
+
16
+ - Change filenames of browse movie and quality report to free up namespace for other future files. (`#124 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/124>`__)
17
+ - Trial framework asdf filenames match production run asdf filenames. (`#186 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/186>`__)
18
+ - Capture tracing data for rollback calls to enhance observability. (`#187 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/187>`__)
19
+ - Update legacy type hinting. (`#188 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/188>`__)
20
+
21
+
1
22
  v6.1.2 (2024-04-12)
2
23
  ===================
3
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dkist-processing-common
3
- Version: 6.2.1rc2
3
+ Version: 6.2.2
4
4
  Summary: Common task classes used by the DKIST Science Data Processing pipelines to process DKIST data.
5
5
  Home-page: https://bitbucket.org/dkistdc/dkist-processing-common/src/main/
6
6
  Author: NSO / AURA
@@ -15,6 +15,7 @@ Provides-Extra: test
15
15
  Provides-Extra: docs
16
16
  Provides-Extra: inventory
17
17
  Provides-Extra: asdf
18
+ Provides-Extra: quality
18
19
 
19
20
  dkist-processing-common
20
21
  =======================
@@ -0,0 +1,88 @@
1
+ """Encoder/decoders for writing and reading quality data."""
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+
10
+ from dkist_processing_common.codecs.json import json_decoder
11
+ from dkist_processing_common.codecs.json import json_encoder
12
+
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class QualityDataEncoder(json.JSONEncoder):
18
+ """A JSON encoder for the quality data."""
19
+
20
+ def __init__(self, **dumps_kwargs):
21
+ # Raise a ValueError for NaN and Infinity.
22
+ dumps_kwargs["allow_nan"] = False
23
+ super().__init__(**dumps_kwargs)
24
+
25
+ def default(self, obj) -> dict:
26
+ """Encode datetime as dict of iso formatted strings. JSONEncoder will later render the dict as a string."""
27
+ if isinstance(obj, datetime):
28
+ return {"iso_date": obj.isoformat("T")}
29
+ return super().default(obj)
30
+
31
+
32
+ def quality_data_encoder(
33
+ data: Any, encoding: str = "utf-8", errors="strict", **dumps_kwargs
34
+ ) -> bytes:
35
+ """Convert quality data to bytes by encoding as JSON."""
36
+ # This encoder is for the QualityDataEncoder class
37
+ if cls := dumps_kwargs.pop("cls", None):
38
+ logger.info(
39
+ f"Ignoring {cls=}. Default JSONEncoder for quality_data_encoder is QualityDataEncoder."
40
+ )
41
+ # allow_nan is per QualityDataEncoder class
42
+ if allow_nan := dumps_kwargs.pop("allow_nan", None):
43
+ logger.info(f"Ignoring {allow_nan=} for quality_data_encoder.")
44
+
45
+ return json_encoder(
46
+ data, encoding=encoding, errors=errors, cls=QualityDataEncoder, **dumps_kwargs
47
+ )
48
+
49
+
50
+ def quality_data_hook(obj: dict):
51
+ """
52
+ Decode iso date dict.
53
+
54
+ Convert object being json decoded into a datetime object if in the format `{"iso_date":"<iso formatted string>"}`
55
+ like those produced by QualityDataEncoder.
56
+ This is the same as datetime_json_object_hook in dkist-quality.
57
+ :param obj: dict of the object being json decoded
58
+ :return: datetime object
59
+ """
60
+ # extract date string if present in the object dict
61
+ iso_date = obj.get("iso_date")
62
+ if iso_date is not None:
63
+ return datetime.fromisoformat(iso_date)
64
+ # iso_date not found - not covered by this hook
65
+ return obj
66
+
67
+
68
+ def quality_data_decoder(
69
+ path: Path, encoding: str = "utf-8", errors="strict", **loads_kwargs
70
+ ) -> Any:
71
+ """Read a file as JSON and return the decoded objects."""
72
+ if object_hook := loads_kwargs.pop("object_hook", None):
73
+ logger.info(f"Ignoring {object_hook=} for quality_data_decoder.")
74
+
75
+ return json_decoder(
76
+ path, encoding=encoding, errors=errors, object_hook=quality_data_hook, **loads_kwargs
77
+ )
78
+
79
+
80
+ class QualityValueEncoder(json.JSONEncoder):
81
+ """A JSON encoder applied to the quality metrics."""
82
+
83
+ def default(self, obj: Any) -> Any:
84
+ """Implement an encoder that correctly handles numpy float32 data."""
85
+ # np.float32 is only for single values. Even an array of np.float32 objects is a np.ndarray
86
+ if isinstance(obj, np.float32):
87
+ return float(obj)
88
+ return super().default(obj)
@@ -30,8 +30,12 @@ class StemName(str, Enum):
30
30
  parameter = "PARAMETER"
31
31
  workflow_task = "WORKFLOW_TASK"
32
32
  debug = "DEBUG"
33
+ # The QUALITY_DATA is the json data that normally gets stored to the remote (metadata store) database.
34
+ quality_data = "QUALITY_DATA"
35
+ # For trial workflows.
33
36
  dataset_inventory = "DATASET_INVENTORY"
34
37
  asdf = "ASDF"
38
+ quality_report = "QUALITY_REPORT"
35
39
 
36
40
 
37
41
  class Tag:
@@ -173,6 +177,16 @@ class Tag:
173
177
  """Return a debug tag."""
174
178
  return cls.format_tag(StemName.debug)
175
179
 
180
+ @classmethod
181
+ def quality_data(cls) -> str:
182
+ """Tags the quality data that normally gets stored to the remote database."""
183
+ return cls.format_tag(StemName.quality_data.value)
184
+
185
+ @classmethod
186
+ def quality_report(cls) -> str:
187
+ """Tags the quality report .pdf that gets stored to the file system for trial workflows."""
188
+ return cls.format_tag(StemName.quality_report.value)
189
+
176
190
  # Task type tags
177
191
  @classmethod
178
192
  def task_observe(cls) -> str:
@@ -1,6 +1,7 @@
1
1
  """Wrappers for all workflow tasks."""
2
2
  import json
3
3
  import logging
4
+ import re
4
5
  from abc import ABC
5
6
  from pathlib import Path
6
7
  from typing import Any
@@ -279,7 +280,8 @@ class WorkflowTaskBase(TaskBase, MetadataStoreMixin, ABC):
279
280
  sorted_remaining_tags = sorted(copied_tags)
280
281
  filename_parts += sorted_remaining_tags
281
282
 
282
- dash_separated_parts = [t.replace("_", "-") for t in filename_parts]
283
+ # replace spaces and underscores with dashes - dynamic part (e.g. polcal `Beam 1` label) may include spaces
284
+ dash_separated_parts = [re.sub("[ _]", "-", t) for t in filename_parts]
283
285
 
284
286
  base_filename = "_".join(dash_separated_parts)
285
287
  base_filename_counter = str(self.filename_counter.increment(base_filename))
@@ -1,31 +1,36 @@
1
1
  """Task(s) for the transfer and publishing of L1 data from a production run of a processing pipeline."""
2
2
  import logging
3
+ from abc import ABC
4
+ from itertools import chain
3
5
  from pathlib import Path
4
6
  from typing import Iterable
5
7
 
8
+ from dkist_processing_common.codecs.quality import quality_data_decoder
9
+ from dkist_processing_common.codecs.quality import quality_data_encoder
6
10
  from dkist_processing_common.models.message import CatalogFrameMessage
7
11
  from dkist_processing_common.models.message import CatalogObjectMessage
8
12
  from dkist_processing_common.models.message import CreateQualityReportMessage
9
13
  from dkist_processing_common.models.tags import Tag
10
14
  from dkist_processing_common.tasks.mixin.globus import GlobusMixin
11
15
  from dkist_processing_common.tasks.mixin.interservice_bus import InterserviceBusMixin
16
+ from dkist_processing_common.tasks.mixin.quality import QualityMixin
17
+ from dkist_processing_common.tasks.output_data_base import OutputDataBase
18
+ from dkist_processing_common.tasks.output_data_base import TransferDataBase
12
19
 
13
20
 
14
21
  __all__ = [
15
- "AddDatasetReceiptAccount",
16
- "PublishCatalogAndQualityMessages",
17
- "TransferL1Data",
18
22
  "L1OutputDataBase",
19
- "SubmitQuality",
23
+ "TransferL1Data",
24
+ "AssembleQualityData",
25
+ "SubmitDatasetMetadata",
26
+ "PublishCatalogAndQualityMessages",
20
27
  ]
21
28
 
22
- from dkist_processing_common.tasks.mixin.quality import QualityMixin
23
- from dkist_processing_common.tasks.output_data_base import OutputDataBase, TransferDataBase
24
29
 
25
30
  logger = logging.getLogger(__name__)
26
31
 
27
32
 
28
- class L1OutputDataBase(OutputDataBase, QualityMixin):
33
+ class L1OutputDataBase(OutputDataBase, ABC):
29
34
  """Subclass of OutputDataBase which encapsulates common level 1 output data methods."""
30
35
 
31
36
  @property
@@ -94,6 +99,72 @@ class TransferL1Data(TransferDataBase, GlobusMixin):
94
99
  )
95
100
 
96
101
 
102
+ class AssembleQualityData(L1OutputDataBase, QualityMixin):
103
+ """Assemble quality data from the various quality metrics."""
104
+
105
+ @property
106
+ def polcal_label_list(self) -> list[str] | None:
107
+ """Return the list of labels to look for when building polcal metrics.
108
+
109
+ If no labels are specified then no polcal metrics will be built.
110
+ """
111
+ return None
112
+
113
+ def run(self):
114
+ """Run method for the task."""
115
+ with self.apm_processing_step("Assembling quality data"):
116
+ quality_data = self.quality_assemble_data(polcal_label_list=self.polcal_label_list)
117
+
118
+ with self.apm_writing_step(
119
+ f"Saving quality data with {len(quality_data)} metrics to the file system"
120
+ ):
121
+ self.write(
122
+ quality_data,
123
+ tags=Tag.quality_data(),
124
+ encoder=quality_data_encoder,
125
+ relative_path=f"{self.constants.dataset_id}_quality_data.json",
126
+ )
127
+
128
+
129
+ class SubmitDatasetMetadata(L1OutputDataBase):
130
+ """
131
+ Add quality data and receipt account to the metadata store.
132
+
133
+ Add the quality data to the Quality database.
134
+ Add a Dataset Receipt Account record to Processing Support for use by the Dataset Catalog Locker.
135
+ Adds the number of files created during the calibration processing to the Processing Support table
136
+ for use by the Dataset Catalog Locker.
137
+ """
138
+
139
+ def run(self) -> None:
140
+ """Run method for this task."""
141
+ with self.apm_writing_step(f"Storing quality data to metadata store"):
142
+ # each quality_data file is a list - this will combine the elements of multiple lists into a single list
143
+ quality_data = list(
144
+ chain.from_iterable(
145
+ self.read(tags=Tag.quality_data(), decoder=quality_data_decoder)
146
+ )
147
+ )
148
+ self.metadata_store_add_quality_data(
149
+ dataset_id=self.constants.dataset_id, quality_data=quality_data
150
+ )
151
+ with self.apm_processing_step("Count Expected Outputs"):
152
+ dataset_id = self.constants.dataset_id
153
+ expected_object_count = self.count(tags=Tag.output())
154
+ if self.dataset_has_quality_report:
155
+ expected_object_count += 1
156
+ logger.info(
157
+ f"Adding Dataset Receipt Account: "
158
+ f"{dataset_id=}, {expected_object_count=}, recipe_run_id={self.recipe_run_id}"
159
+ )
160
+ with self.apm_task_step(
161
+ f"Add Dataset Receipt Account: {dataset_id = }, {expected_object_count = }"
162
+ ):
163
+ self.metadata_store_add_dataset_receipt_account(
164
+ dataset_id=dataset_id, expected_object_count=expected_object_count
165
+ )
166
+
167
+
97
168
  class PublishCatalogAndQualityMessages(L1OutputDataBase, InterserviceBusMixin):
98
169
  """Task class for publishing Catalog and Quality Messages."""
99
170
 
@@ -162,7 +233,7 @@ class PublishCatalogAndQualityMessages(L1OutputDataBase, InterserviceBusMixin):
162
233
  )
163
234
 
164
235
  def run(self) -> None:
165
- """Run method for this trask."""
236
+ """Run method for this task."""
166
237
  with self.apm_task_step("Gather output data"):
167
238
  frames = self.read(tags=self.output_frame_tags)
168
239
  movies = self.read(tags=[Tag.output(), Tag.movie()])
@@ -179,51 +250,3 @@ class PublishCatalogAndQualityMessages(L1OutputDataBase, InterserviceBusMixin):
179
250
  f"Publish messages: {frame_message_count = }, {object_message_count = }, {dataset_has_quality_report = }"
180
251
  ):
181
252
  self.interservice_bus_publish(messages=messages)
182
-
183
-
184
- class AddDatasetReceiptAccount(L1OutputDataBase):
185
- """
186
- Add a Dataset Receipt Account record to Processing Support for use by the Dataset Catalog Locker.
187
-
188
- Adds the number of files created during the calibration processing to the Processing Support table
189
- for use by the Dataset Catalog Locker.
190
- """
191
-
192
- def run(self) -> None:
193
- """Run method for this task."""
194
- with self.apm_processing_step("Count Expected Outputs"):
195
- dataset_id = self.constants.dataset_id
196
- expected_object_count = self.count(tags=Tag.output())
197
- if self.dataset_has_quality_report:
198
- expected_object_count += 1
199
- logger.info(
200
- f"Adding Dataset Receipt Account: "
201
- f"{dataset_id=}, {expected_object_count=}, recipe_run_id={self.recipe_run_id}"
202
- )
203
- with self.apm_task_step(
204
- f"Add Dataset Receipt Account: {dataset_id = }, {expected_object_count = }"
205
- ):
206
- self.metadata_store_add_dataset_receipt_account(
207
- dataset_id=dataset_id, expected_object_count=expected_object_count
208
- )
209
-
210
-
211
- class SubmitQuality(L1OutputDataBase, QualityMixin):
212
- """Task class for submitting the quality report to the metadata store."""
213
-
214
- @property
215
- def polcal_label_list(self) -> list[str] | None:
216
- """Return the list of labels to look for when building polcal metrics.
217
-
218
- If no labels are specified then no polcal metrics will be built.
219
- """
220
- return None
221
-
222
- def run(self):
223
- """Run method for the task."""
224
- with self.apm_processing_step("Building quality report"):
225
- report = self.quality_build_report(polcal_label_list=self.polcal_label_list)
226
- with self.apm_task_step(f"Submitting quality report: report section count = {len(report)}"):
227
- self.metadata_store_add_quality_report(
228
- dataset_id=self.constants.dataset_id, quality_report=report
229
- )
@@ -5,6 +5,7 @@ from functools import cached_property
5
5
 
6
6
  from dkist_processing_common._util.config import service_configuration
7
7
  from dkist_processing_common._util.graphql import GraphQLClient
8
+ from dkist_processing_common.codecs.quality import QualityDataEncoder
8
9
  from dkist_processing_common.models.graphql import DatasetCatalogReceiptAccountMutation
9
10
  from dkist_processing_common.models.graphql import DatasetCatalogReceiptAccountResponse
10
11
  from dkist_processing_common.models.graphql import InputDatasetPartResponse
@@ -21,7 +22,6 @@ from dkist_processing_common.models.graphql import RecipeRunResponse
21
22
  from dkist_processing_common.models.graphql import RecipeRunStatusMutation
22
23
  from dkist_processing_common.models.graphql import RecipeRunStatusQuery
23
24
  from dkist_processing_common.models.graphql import RecipeRunStatusResponse
24
- from dkist_processing_common.models.quality_json_encoders import QualityReportEncoder
25
25
 
26
26
 
27
27
  logger = logging.getLogger(__name__)
@@ -78,10 +78,10 @@ class MetadataStoreMixin:
78
78
  mutation_response_cls=RecipeRunProvenanceResponse,
79
79
  )
80
80
 
81
- def metadata_store_add_quality_report(self, dataset_id: str, quality_report: list[dict]):
82
- """Add the quality report to the metadata-store."""
83
- quality_report_json = json.dumps(quality_report, cls=QualityReportEncoder, allow_nan=False)
84
- params = QualityReportMutation(datasetId=dataset_id, qualityReport=quality_report_json)
81
+ def metadata_store_add_quality_data(self, dataset_id: str, quality_data: list[dict]):
82
+ """Add the quality data to the metadata-store."""
83
+ quality_data_json = json.dumps(quality_data, cls=QualityDataEncoder)
84
+ params = QualityReportMutation(datasetId=dataset_id, qualityReport=quality_data_json)
85
85
  self.metadata_store_client.execute_gql_mutation(
86
86
  mutation_base="createQualityReport",
87
87
  mutation_parameters=params,
@@ -4,7 +4,7 @@ from typing import Iterable
4
4
  import numpy as np
5
5
 
6
6
  from dkist_processing_common.codecs.json import json_encoder
7
- from dkist_processing_common.models.quality_json_encoders import QualityValueEncoder
7
+ from dkist_processing_common.codecs.quality import QualityValueEncoder
8
8
  from dkist_processing_common.models.tags import Tag
9
9
  from dkist_processing_common.tasks.mixin.quality._metrics import _PolcalQualityMixin
10
10
  from dkist_processing_common.tasks.mixin.quality._metrics import _SimplePlotQualityMixin
@@ -17,8 +17,8 @@ class QualityMixin(
17
17
  ):
18
18
  """Mixin class supporting the generation of the quality reports."""
19
19
 
20
- def quality_build_report(self, polcal_label_list: list[str] | None = None) -> list[dict]:
21
- """Build the quality report by checking for the existence of data for each metric."""
20
+ def quality_assemble_data(self, polcal_label_list: list[str] | None = None) -> list[dict]:
21
+ """Assemble the quality data by checking for the existence of each metric."""
22
22
  report = []
23
23
  report += self.quality_task_independent_metrics()
24
24
  report += self.quality_task_dependent_metrics()
@@ -84,7 +84,9 @@ class QualityL0Metrics(WorkflowTaskBase, QualityMixin, ABC):
84
84
  # We grab the task name
85
85
  tags = self.tags(path)
86
86
  task_type = [
87
- t.replace(f"{StemName.task.value}_", "") for t in tags if "TASK" in t
87
+ t.replace(f"{StemName.task.value}_", "")
88
+ for t in tags
89
+ if t.startswith("TASK")
88
90
  ][0]
89
91
 
90
92
  if (
@@ -2,6 +2,7 @@
2
2
  import logging
3
3
  from datetime import datetime
4
4
  from importlib import resources
5
+ from itertools import chain
5
6
  from pathlib import Path
6
7
  from typing import Generator
7
8
  from uuid import uuid4
@@ -10,13 +11,14 @@ from dkist_processing_common.codecs.asdf import asdf_encoder
10
11
  from dkist_processing_common.codecs.fits import fits_access_decoder
11
12
  from dkist_processing_common.codecs.json import json_encoder
12
13
  from dkist_processing_common.codecs.path import path_decoder
14
+ from dkist_processing_common.codecs.quality import quality_data_decoder
13
15
  from dkist_processing_common.models.fits_access import FitsAccessBase
14
16
  from dkist_processing_common.models.tags import Tag
15
17
  from dkist_processing_common.tasks.output_data_base import OutputDataBase
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
- __all__ = ["CreateTrialDatasetInventory", "CreateTrialAsdf"]
21
+ __all__ = ["CreateTrialDatasetInventory", "CreateTrialAsdf", "CreateTrialQualityReport"]
20
22
 
21
23
 
22
24
  # Capture condition of dkist-processing-common[inventory] install
@@ -39,6 +41,16 @@ try:
39
41
  except ModuleNotFoundError:
40
42
  pass
41
43
 
44
+ # Verify dkist-quality is installed
45
+ QUALITY_EXTRA_INSTALLED = False
46
+ try:
47
+ from dkist_quality.report import format_report
48
+ from dkist_quality.report import ReportFormattingException
49
+
50
+ QUALITY_EXTRA_INSTALLED = True
51
+ except ModuleNotFoundError:
52
+ pass
53
+
42
54
 
43
55
  class CreateTrialDatasetInventory(OutputDataBase):
44
56
  """
@@ -143,3 +155,44 @@ class CreateTrialAsdf(OutputDataBase):
143
155
  ),
144
156
  custom_schema=schema_path.as_posix(),
145
157
  )
158
+
159
+
160
+ class CreateTrialQualityReport(OutputDataBase):
161
+ """
162
+ Task for use in Trial workflows to generate the quality report for the dataset.
163
+
164
+ Warning: This task requires the dkist-quality package.
165
+ """
166
+
167
+ def pre_run(self) -> None:
168
+ """Require the dkist-quality package be installed."""
169
+ if not QUALITY_EXTRA_INSTALLED:
170
+ raise ModuleNotFoundError(
171
+ f"{self.__class__.__name__} Task requires the dkist-quality package "
172
+ f"(e.g. via a 'quality' pip_extra on dkist_processing_core.Workflow().add_node())"
173
+ f" but the required dependencies were not found."
174
+ )
175
+
176
+ def run(self) -> None:
177
+ """Generate the quality report for the dataset."""
178
+ self.create_trial_quality_report()
179
+
180
+ def create_trial_quality_report(self) -> None:
181
+ """Generate a trial quality report in pdf format and save to the file system for future upload."""
182
+ with self.apm_processing_step(f"Building the trial quality report"):
183
+ # each quality_data file is a list - this will combine the elements of multiple lists into a single list
184
+ quality_data = list(
185
+ chain.from_iterable(
186
+ self.read(tags=Tag.quality_data(), decoder=quality_data_decoder)
187
+ )
188
+ )
189
+ quality_report = format_report(
190
+ report_data=quality_data, dataset_id=self.constants.dataset_id
191
+ )
192
+
193
+ with self.apm_writing_step(f"Saving the trial quality report to the file system"):
194
+ self.write(
195
+ quality_report,
196
+ tags=[Tag.output(), Tag.quality_report()],
197
+ relative_path=f"{self.constants.dataset_id}_quality_report.pdf",
198
+ )
@@ -93,6 +93,20 @@ class TransferTrialDataBase(TransferDataBase, GlobusMixin, ABC):
93
93
  "trial_transfer_output_asdf", True
94
94
  )
95
95
 
96
+ @property
97
+ def output_quality_data_switch(self) -> bool:
98
+ """Switch to turn on/off the transfer of the quality data to the trial location."""
99
+ return self.metadata_store_recipe_run_configuration().get(
100
+ "trial_transfer_output_quality_data", True
101
+ )
102
+
103
+ @property
104
+ def output_quality_report_switch(self) -> bool:
105
+ """Switch to turn on/off the transfer of the quality report to the trial location."""
106
+ return self.metadata_store_recipe_run_configuration().get(
107
+ "trial_transfer_output_quality_report", True
108
+ )
109
+
96
110
  @property
97
111
  def specific_frame_tag_lists(self) -> list:
98
112
  """Return list of tag lists that define specific files we want to transfer to the trial location."""
@@ -161,6 +175,19 @@ class TransferTrialDataBase(TransferDataBase, GlobusMixin, ABC):
161
175
  )
162
176
  return transfer_items
163
177
 
178
+ def build_output_quality_data_transfer_list(self) -> list[GlobusTransferItem]:
179
+ """Build a transfer list containing all files tagged with OUTPUT and QUALITY_DATA."""
180
+ # quality data is not tagged as OUTPUT
181
+ transfer_items = self.build_transfer_list_from_tag_lists(tag_lists=[Tag.quality_data()])
182
+ return transfer_items
183
+
184
+ def build_output_quality_report_transfer_list(self) -> list[GlobusTransferItem]:
185
+ """Build a transfer list containing all files tagged with OUTPUT and QUALITY_REPORT."""
186
+ transfer_items = self.build_transfer_list_from_tag_lists(
187
+ tag_lists=[Tag.output(), Tag.quality_report()]
188
+ )
189
+ return transfer_items
190
+
164
191
  @property
165
192
  def intermediate_task_names(self) -> list[str]:
166
193
  """List specifying which TASK types to build when selecting INTERMEDIATE frames."""