dkist-processing-common 14.0.1__tar.gz → 14.0.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 (141) hide show
  1. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/CHANGELOG.rst +10 -0
  2. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/PKG-INFO +1 -1
  3. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/quality.py +21 -0
  4. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/l1_output_data.py +53 -0
  5. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/quality/_base.py +0 -5
  6. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/quality/_metrics.py +0 -84
  7. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_assemble_quality.py +260 -10
  8. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_quality_mixin.py +0 -50
  9. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common.egg-info/PKG-INFO +1 -1
  10. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/.gitignore +0 -0
  11. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/.pre-commit-config.yaml +0 -0
  12. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/.readthedocs.yml +0 -0
  13. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/.snyk +0 -0
  14. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/README.rst +0 -0
  15. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/bitbucket-pipelines.yml +0 -0
  16. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/changelog/.gitempty +0 -0
  17. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/__init__.py +0 -0
  18. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/_util/__init__.py +0 -0
  19. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/_util/constants.py +0 -0
  20. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/_util/graphql.py +0 -0
  21. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/_util/scratch.py +0 -0
  22. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/_util/tags.py +0 -0
  23. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/__init__.py +0 -0
  24. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/array.py +0 -0
  25. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/asdf.py +0 -0
  26. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/basemodel.py +0 -0
  27. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/bytes.py +0 -0
  28. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/fits.py +0 -0
  29. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/iobase.py +0 -0
  30. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/json.py +0 -0
  31. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/path.py +0 -0
  32. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/quality.py +0 -0
  33. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/codecs/str.py +0 -0
  34. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/config.py +0 -0
  35. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/fonts/Lato-Regular.ttf +0 -0
  36. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/fonts/__init__.py +0 -0
  37. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/manual.py +0 -0
  38. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/__init__.py +0 -0
  39. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/constants.py +0 -0
  40. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/dkist_location.py +0 -0
  41. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/extras.py +0 -0
  42. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/fits_access.py +0 -0
  43. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/flower_pot.py +0 -0
  44. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/fried_parameter.py +0 -0
  45. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/graphql.py +0 -0
  46. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/input_dataset.py +0 -0
  47. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/message.py +0 -0
  48. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/message_queue_binding.py +0 -0
  49. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/metric_code.py +0 -0
  50. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/parameters.py +0 -0
  51. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/tags.py +0 -0
  52. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/task_name.py +0 -0
  53. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/telemetry.py +0 -0
  54. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/models/wavelength.py +0 -0
  55. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/__init__.py +0 -0
  56. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/average_bud.py +0 -0
  57. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/cs_step.py +0 -0
  58. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/dsps_repeat.py +0 -0
  59. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/experiment_id_bud.py +0 -0
  60. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/id_bud.py +0 -0
  61. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/l0_fits_access.py +0 -0
  62. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/l1_fits_access.py +0 -0
  63. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/lookup_bud.py +0 -0
  64. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/near_bud.py +0 -0
  65. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/observing_program_id_bud.py +0 -0
  66. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/proposal_id_bud.py +0 -0
  67. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/quality.py +0 -0
  68. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/retarder.py +0 -0
  69. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/single_value_single_key_flower.py +0 -0
  70. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/task.py +0 -0
  71. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/time.py +0 -0
  72. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/unique_bud.py +0 -0
  73. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/parsers/wavelength.py +0 -0
  74. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/__init__.py +0 -0
  75. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/assemble_movie.py +0 -0
  76. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/base.py +0 -0
  77. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/__init__.py +0 -0
  78. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/globus.py +0 -0
  79. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/interservice_bus.py +0 -0
  80. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/metadata_store.py +0 -0
  81. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/object_store.py +0 -0
  82. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/mixin/quality/__init__.py +0 -0
  83. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/output_data_base.py +0 -0
  84. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/parse_l0_input_data.py +0 -0
  85. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/quality_metrics.py +0 -0
  86. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/teardown.py +0 -0
  87. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/transfer_input_data.py +0 -0
  88. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/trial_catalog.py +0 -0
  89. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/trial_output_data.py +0 -0
  90. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/write_extra.py +0 -0
  91. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/write_l1.py +0 -0
  92. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tasks/write_l1_base.py +0 -0
  93. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/__init__.py +0 -0
  94. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/conftest.py +0 -0
  95. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/mock_metadata_store.py +0 -0
  96. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_assemble_movie.py +0 -0
  97. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_base.py +0 -0
  98. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_codecs.py +0 -0
  99. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_constants.py +0 -0
  100. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_construct_dataset_extras.py +0 -0
  101. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_cs_step.py +0 -0
  102. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_dkist_location.py +0 -0
  103. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_fits_access.py +0 -0
  104. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_flower_pot.py +0 -0
  105. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_fried_parameter.py +0 -0
  106. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_input_dataset.py +0 -0
  107. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_interservice_bus.py +0 -0
  108. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_interservice_bus_mixin.py +0 -0
  109. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_manual_processing.py +0 -0
  110. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_output_data_base.py +0 -0
  111. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_parameters.py +0 -0
  112. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_parse_l0_input_data.py +0 -0
  113. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_publish_catalog_messages.py +0 -0
  114. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_quality.py +0 -0
  115. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_scratch.py +0 -0
  116. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_stems.py +0 -0
  117. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_submit_dataset_metadata.py +0 -0
  118. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_tags.py +0 -0
  119. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_task_name.py +0 -0
  120. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_task_parsing.py +0 -0
  121. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_teardown.py +0 -0
  122. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_transfer_input_data.py +0 -0
  123. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_transfer_l1_output_data.py +0 -0
  124. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_trial_catalog.py +0 -0
  125. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_trial_output_data.py +0 -0
  126. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_workflow_task_base.py +0 -0
  127. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common/tests/test_write_l1.py +0 -0
  128. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common.egg-info/SOURCES.txt +0 -0
  129. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common.egg-info/dependency_links.txt +0 -0
  130. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common.egg-info/requires.txt +0 -0
  131. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/dkist_processing_common.egg-info/top_level.txt +0 -0
  132. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/Makefile +0 -0
  133. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/changelog.rst +0 -0
  134. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/conf.py +0 -0
  135. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/index.rst +0 -0
  136. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/landing_page.rst +0 -0
  137. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/make.bat +0 -0
  138. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/docs/requirements.txt +0 -0
  139. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/licenses/LICENSE.rst +0 -0
  140. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/pyproject.toml +0 -0
  141. {dkist_processing_common-14.0.1 → dkist_processing_common-14.0.2}/setup.cfg +0 -0
@@ -1,3 +1,13 @@
1
+ v14.0.2 (2026-05-04)
2
+ ====================
3
+
4
+ Features
5
+ --------
6
+
7
+ - Convert TASK_TYPES quality metric to the new methodology. (`#332 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/332>`__)
8
+ - Add `TaskCount` quality metric model. (`#332 <https://bitbucket.org/dkistdc/dkist-processing-common/pull-requests/332>`__)
9
+
10
+
1
11
  v14.0.1 (2026-04-22)
2
12
  ====================
3
13
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 14.0.1
3
+ Version: 14.0.2
4
4
  Summary: Common task classes used by the DKIST science data processing pipelines
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause
@@ -10,6 +10,7 @@ from pydantic import ConfigDict
10
10
  from pydantic import Field
11
11
  from pydantic import FiniteFloat
12
12
  from pydantic import PlainSerializer
13
+ from pydantic import StringConstraints
13
14
  from pydantic import field_validator
14
15
  from pydantic import model_serializer
15
16
  from pydantic import model_validator
@@ -348,3 +349,23 @@ class QualityMetric(QualityModel):
348
349
  raincloud_data: list[RaincloudData] | None = None
349
350
  efficiency_data: list[EfficiencyData] | None = None
350
351
  warnings: list[str] | None = None
352
+
353
+
354
+ ###########################################################
355
+ # Internal Quality Models #
356
+ # #
357
+ # Shared models used for generating quality metrics, #
358
+ # but not part of the dkist-quality interface #
359
+ ###########################################################
360
+
361
+
362
+ class TaskCount(BaseModel):
363
+ """Frame counts for a single IP task type."""
364
+
365
+ task_type: Annotated[str, StringConstraints(to_upper=True)]
366
+ total_frames: int
367
+ frames_not_used: int = 0
368
+
369
+ def as_tuple(self) -> tuple[str, str, str]:
370
+ """Render as a tuple."""
371
+ return self.task_type, str(self.total_frames), str(self.frames_not_used)
@@ -6,12 +6,16 @@ from pathlib import Path
6
6
  from typing import Iterable
7
7
 
8
8
  from dkist_processing_common.codecs.basemodel import basemodel_decoder
9
+ from dkist_processing_common.codecs.basemodel import basemodel_encoder
9
10
  from dkist_processing_common.codecs.quality import quality_data_encoder
10
11
  from dkist_processing_common.models.message import CatalogFrameMessage
11
12
  from dkist_processing_common.models.message import CatalogFrameMessageBody
12
13
  from dkist_processing_common.models.message import CatalogObjectMessage
13
14
  from dkist_processing_common.models.message import CatalogObjectMessageBody
15
+ from dkist_processing_common.models.metric_code import MetricCode
14
16
  from dkist_processing_common.models.quality import QualityMetric
17
+ from dkist_processing_common.models.quality import TableData
18
+ from dkist_processing_common.models.quality import TaskCount
15
19
  from dkist_processing_common.models.tags import Tag
16
20
  from dkist_processing_common.tasks.mixin.globus import GlobusMixin
17
21
  from dkist_processing_common.tasks.mixin.interservice_bus import InterserviceBusMixin
@@ -150,6 +154,7 @@ class AssembleQualityData(L1OutputDataBase, QualityMixin):
150
154
  """Run method for the task."""
151
155
  # this is the new way, which will eventually supplant the old `quality_assemble_data`
152
156
  with self.telemetry_span("Assembling generic quality data"):
157
+ self.compute_and_write_type_counts()
153
158
  quality_data = self.assemble_quality_data()
154
159
 
155
160
  # this is the old way, which is being replaced by the new `assemble_quality_data`
@@ -176,6 +181,54 @@ class AssembleQualityData(L1OutputDataBase, QualityMixin):
176
181
  metrics.append(metric.model_dump(mode="json"))
177
182
  return metrics
178
183
 
184
+ def compute_and_write_type_counts(self) -> None:
185
+ """Compute and write TASK_TYPES quality metric."""
186
+ # retrieve the intermediate TASK_TYPES data for each IP task
187
+ task_counts: list[TaskCount] = self.quality_read_intermediate_task_type_counts()
188
+
189
+ # don't write anything if there is no data
190
+ if not task_counts:
191
+ return
192
+
193
+ header = ("Task Type", "Total Frames", "Unused Frames")
194
+ rows = [header]
195
+
196
+ warning_count_threshold = 0.05
197
+ warnings = []
198
+ for task_count in task_counts:
199
+ # add the row
200
+ rows.append(task_count.as_tuple())
201
+ # determine if there are any warnings for this task type
202
+ if task_count.total_frames == 0:
203
+ warnings.append(f"NO {task_count.task_type} frames were used!")
204
+ elif (
205
+ frame_ratio := task_count.frames_not_used / task_count.total_frames
206
+ ) > warning_count_threshold:
207
+ warnings.append(
208
+ f"{round(100 * frame_ratio, 1)}% of frames were not used in the "
209
+ f"processing of task type {task_count.task_type}"
210
+ )
211
+ warnings = warnings or None
212
+
213
+ table_data = TableData(rows=tuple(rows))
214
+ metric = QualityMetric(
215
+ name="Frame Counts",
216
+ description="This metric is a count of the number of frames used to produce a "
217
+ "calibrated L1 dataset",
218
+ metric_code=MetricCode.task_types,
219
+ table_data=[table_data],
220
+ warnings=warnings,
221
+ )
222
+ self.write(metric, tags=Tag.quality("GENERIC"), encoder=basemodel_encoder)
223
+
224
+ def quality_read_intermediate_task_type_counts(self) -> list[TaskCount]:
225
+ """Read intermediate task type data."""
226
+ tags = Tag.quality(MetricCode.task_types)
227
+ task_counts: list[TaskCount] = list(
228
+ self.read(tags=tags, decoder=basemodel_decoder, model=TaskCount)
229
+ )
230
+ return task_counts
231
+
179
232
 
180
233
  class SubmitDatasetMetadata(L1OutputDataBase):
181
234
  """
@@ -2,21 +2,17 @@
2
2
 
3
3
  from typing import Iterable
4
4
 
5
- import numpy as np
6
-
7
5
  from dkist_processing_common.codecs.json import json_encoder
8
6
  from dkist_processing_common.codecs.quality import QualityValueEncoder
9
7
  from dkist_processing_common.models.tags import Tag
10
8
  from dkist_processing_common.models.task_name import TaskName
11
9
  from dkist_processing_common.tasks.mixin.quality._metrics import _PolcalQualityMixin
12
10
  from dkist_processing_common.tasks.mixin.quality._metrics import _SimplePlotQualityMixin
13
- from dkist_processing_common.tasks.mixin.quality._metrics import _TableQualityMixin
14
11
  from dkist_processing_common.tasks.mixin.quality._metrics import _WavecalQualityMixin
15
12
 
16
13
 
17
14
  class QualityMixin(
18
15
  _SimplePlotQualityMixin,
19
- _TableQualityMixin,
20
16
  _PolcalQualityMixin,
21
17
  _WavecalQualityMixin,
22
18
  ):
@@ -64,7 +60,6 @@ class QualityMixin(
64
60
  def quality_metrics_no_task_dependence(self) -> dict:
65
61
  """Return a dict of the quality metrics with no task dependence."""
66
62
  return {
67
- "TASK_TYPES": self.quality_build_task_type_counts,
68
63
  "WAVECAL_FIT": self.quality_build_wavecal_results,
69
64
  }
70
65
 
@@ -6,12 +6,7 @@ These classes should not be directly mixed in to anything. They are pre-mixed in
6
6
  import copy
7
7
  import json
8
8
  import logging
9
- from collections import defaultdict
10
9
  from datetime import datetime
11
- from functools import partial
12
- from typing import Any
13
- from typing import Iterable
14
- from typing import Literal
15
10
  from typing import Sequence
16
11
 
17
12
  import astropy.units as u
@@ -67,85 +62,6 @@ class _SimplePlotQualityMixin:
67
62
  return warnings
68
63
 
69
64
 
70
- class _TableQualityMixin:
71
- """Mixing for metrics that present as tables."""
72
-
73
- @staticmethod
74
- def _create_table_metric(
75
- name: str,
76
- description: str,
77
- metric_code: str,
78
- rows: list[list[Any]],
79
- statement: str | None = None,
80
- warnings: list[str] | None = None,
81
- facet: str | None = None,
82
- ) -> dict:
83
- metric = ReportMetric(
84
- name=name,
85
- description=description,
86
- metric_code=metric_code,
87
- facet=facet,
88
- statement=statement,
89
- table_data=SimpleTable(rows=rows),
90
- warnings=warnings,
91
- )
92
- return metric.model_dump()
93
-
94
- def quality_store_task_type_counts(
95
- self, task_type: str, total_frames: int, frames_not_used: int = 0
96
- ):
97
- """
98
- Collect and store task type data.
99
-
100
- Parameters
101
- ----------
102
- task_type: task type as listed in the headers
103
- total_frames: total number of frames supplied of the given task type
104
- frames_not_used: if some frames aren't used, how many
105
- """
106
- data = {
107
- "task_type": task_type.upper(),
108
- "total_frames": total_frames,
109
- "frames_not_used": frames_not_used,
110
- }
111
- self._record_values(values=data, tags=Tag.quality(MetricCode.task_types))
112
-
113
- def quality_build_task_type_counts(self) -> dict:
114
- """Build task type count schema from stored data."""
115
- # Raise warning if more than 5% of frames of a given type are not used
116
- warning_count_threshold = 0.05
117
- default_int_dict = partial(defaultdict, int)
118
- task_type_counts = defaultdict(default_int_dict)
119
- # Loop over files that contain data for this metric
120
- for path in self.read(tags=Tag.quality(MetricCode.task_types)):
121
- with path.open() as f:
122
- data = json.load(f)
123
- task_type_counts[data["task_type"]]["total_frames"] += data["total_frames"]
124
- task_type_counts[data["task_type"]]["frames_not_used"] += data["frames_not_used"]
125
-
126
- # Now, build metric from the counts dict
127
- table_data = [[i[0]] + list(i[1].values()) for i in task_type_counts.items()]
128
- warnings = []
129
- for row in table_data:
130
- if row[1] == 0:
131
- warnings.append(f"NO {row[0]} frames were used!")
132
- elif row[2] / row[1] > warning_count_threshold:
133
- warnings.append(
134
- f"{round(100 * row[2] / row[1], 1)}% of frames were not used in the "
135
- f"processing of task type {row[0]}"
136
- )
137
- # Add header row
138
- table_data.insert(0, ["Task Type", "Total Frames", "Unused Frames"])
139
- return self._create_table_metric(
140
- name="Frame Counts",
141
- description="This metric is a count of the number of frames used to produce a "
142
- "calibrated L1 dataset",
143
- metric_code=MetricCode.task_types,
144
- rows=table_data,
145
- warnings=self._format_warnings(warnings),
146
- )
147
-
148
-
149
65
  class _PolcalQualityMixin:
150
66
  """Mixin Class supporting the building of polcal-specific metrics."""
151
67
 
@@ -14,11 +14,13 @@ from dkist_quality.report import ReportMetric
14
14
  from pandas import DataFrame
15
15
 
16
16
  from dkist_processing_common._util.scratch import WorkflowFileSystem
17
+ from dkist_processing_common.codecs.basemodel import basemodel_decoder
17
18
  from dkist_processing_common.codecs.basemodel import basemodel_encoder
18
19
  from dkist_processing_common.codecs.json import json_decoder
19
20
  from dkist_processing_common.models.metric_code import MetricCode
20
21
  from dkist_processing_common.models.quality import QualityMetric
21
22
  from dkist_processing_common.models.quality import TableData
23
+ from dkist_processing_common.models.quality import TaskCount
22
24
  from dkist_processing_common.models.quality import TimeSeriesData
23
25
  from dkist_processing_common.models.quality import XYData
24
26
  from dkist_processing_common.models.tags import Tag
@@ -76,12 +78,6 @@ def quality_metrics_old(dataframe_json) -> list[Metric]:
76
78
  Quality metric data - this OLD FORMAT is being phased out
77
79
  """
78
80
  metrics = [
79
- Metric(
80
- {"task_type": "dark", "total_frames": 100, "frames_not_used": 7}, ["QUALITY_TASK_TYPES"]
81
- ),
82
- Metric(
83
- {"task_type": "gain", "total_frames": 100, "frames_not_used": 0}, ["QUALITY_TASK_TYPES"]
84
- ),
85
81
  Metric(
86
82
  {
87
83
  "param_names": ["foo"],
@@ -312,6 +308,23 @@ def quality_metrics() -> list[QualityMetric]:
312
308
  statement=[f"{MetricCode.light_level} statement."],
313
309
  description=f"{MetricCode.light_level} description.",
314
310
  ),
311
+ # TASK_TYPES
312
+ QualityMetric(
313
+ metric_code=MetricCode.task_types,
314
+ name=f"Frame Counts",
315
+ table_data=[
316
+ TableData(
317
+ rows=(
318
+ ("Task Type", "Total Frames", "Unused Frames"),
319
+ ("dark", "1000", "1"),
320
+ ("gain", "1000", "0"),
321
+ ("observe", "2000", "0"),
322
+ )
323
+ )
324
+ ],
325
+ warnings=["Past performance does not guarantee future results."],
326
+ description=f"{MetricCode.task_types} description.",
327
+ ),
315
328
  ]
316
329
  return metrics
317
330
 
@@ -473,6 +486,28 @@ def warnings_expected() -> Callable[[str], bool]:
473
486
  return expected
474
487
 
475
488
 
489
+ @pytest.fixture()
490
+ def assemble_quality_data_task_no_data(
491
+ recipe_run_id: int,
492
+ tmp_path: Path,
493
+ fake_constants_db,
494
+ ) -> AssembleQualityData:
495
+ """An instance of AssembleQualityData with no quality data."""
496
+ task = AssembleQualityData(
497
+ recipe_run_id=recipe_run_id,
498
+ workflow_name="assemble_quality",
499
+ workflow_version="assemble_quality_version",
500
+ )
501
+ scratch = WorkflowFileSystem(
502
+ recipe_run_id=recipe_run_id,
503
+ scratch_base_path=tmp_path,
504
+ )
505
+ task.scratch = scratch
506
+ task.constants._update(fake_constants_db)
507
+ yield task
508
+ task._purge()
509
+
510
+
476
511
  @pytest.fixture()
477
512
  def scratch_with_quality_metrics(
478
513
  recipe_run_id: int,
@@ -498,11 +533,10 @@ def scratch_with_quality_metrics(
498
533
 
499
534
  @pytest.fixture()
500
535
  def assemble_quality_data_task(
501
- recipe_run_id,
502
- tmp_path,
536
+ recipe_run_id: int,
537
+ tmp_path: Path,
503
538
  scratch_with_quality_metrics,
504
539
  fake_constants_db,
505
- quality_metrics: list[QualityMetric],
506
540
  ) -> AssembleQualityData:
507
541
  """An instance of AssembleQualityData with tagged quality metrics."""
508
542
  task = AssembleQualityData(
@@ -518,7 +552,7 @@ def assemble_quality_data_task(
518
552
 
519
553
  @pytest.fixture()
520
554
  def assemble_quality_data_for_polcal_task(
521
- recipe_run_id, tmp_path, scratch_with_quality_metrics, fake_constants_db
555
+ recipe_run_id: int, tmp_path: Path, scratch_with_quality_metrics, fake_constants_db
522
556
  ) -> AssembleQualityData:
523
557
  """An instance of AssembleQualityData with tagged quality metrics and configured to process PolCal."""
524
558
 
@@ -639,3 +673,219 @@ def test_assemble_quality_data_for_polcal(
639
673
  assert rm.statement
640
674
  if warnings_expected(rm.name):
641
675
  assert rm.warnings
676
+
677
+
678
+ @pytest.mark.parametrize(
679
+ "input_task_counts, expected_metric_count, expected_task_count, expected_warning_count",
680
+ [
681
+ pytest.param([], 0, 0, 0, id="no_tasks"),
682
+ pytest.param(
683
+ [
684
+ TaskCount(
685
+ task_type="one",
686
+ total_frames=100,
687
+ ),
688
+ ],
689
+ 1,
690
+ 1,
691
+ 0,
692
+ id="one_task",
693
+ ),
694
+ pytest.param(
695
+ [
696
+ TaskCount(
697
+ task_type="one",
698
+ total_frames=100,
699
+ frames_not_used=6,
700
+ ),
701
+ TaskCount(
702
+ task_type="two",
703
+ total_frames=200,
704
+ frames_not_used=10,
705
+ ),
706
+ TaskCount(
707
+ task_type="three",
708
+ total_frames=300,
709
+ frames_not_used=16,
710
+ ),
711
+ ],
712
+ 1,
713
+ 3,
714
+ 2,
715
+ id="three_tasks_two_outer_warnings",
716
+ ),
717
+ pytest.param(
718
+ [
719
+ TaskCount(
720
+ task_type="one",
721
+ total_frames=100,
722
+ frames_not_used=5,
723
+ ),
724
+ TaskCount(
725
+ task_type="two",
726
+ total_frames=200,
727
+ frames_not_used=11,
728
+ ),
729
+ TaskCount(
730
+ task_type="three",
731
+ total_frames=300,
732
+ frames_not_used=16,
733
+ ),
734
+ TaskCount(
735
+ task_type="four",
736
+ total_frames=400,
737
+ frames_not_used=20,
738
+ ),
739
+ ],
740
+ 1,
741
+ 4,
742
+ 2,
743
+ id="four_tasks_two_inner_warnings",
744
+ ),
745
+ ],
746
+ )
747
+ def test_compute_and_write_type_counts(
748
+ input_task_counts,
749
+ expected_metric_count: int,
750
+ expected_task_count: int,
751
+ expected_warning_count: int,
752
+ assemble_quality_data_task_no_data: AssembleQualityData,
753
+ ):
754
+ """
755
+ Given: valid TaskCount files in scratch
756
+ When: executing AssembleQualityData.compute_and_write_type_counts()
757
+ Then: writes one FRAME_AVERAGE QualityMetric for each task type
758
+ Then: writes one TASK_TYPES QualityMetric
759
+ """
760
+ # Given
761
+ task = assemble_quality_data_task_no_data
762
+ for task_count in input_task_counts:
763
+ task.write(task_count, tags="QUALITY_TASK_TYPES", encoder=basemodel_encoder)
764
+
765
+ # When
766
+ task.compute_and_write_type_counts()
767
+
768
+ # Then
769
+ tags = [Tag.quality("GENERIC")]
770
+ metrics: list[QualityMetric] = list(
771
+ task.read(tags=tags, decoder=basemodel_decoder, model=QualityMetric)
772
+ )
773
+ assert len(metrics) == expected_metric_count
774
+
775
+ if expected_metric_count == 0:
776
+ # nothing to inspect
777
+ return
778
+
779
+ metric = metrics[0]
780
+ # basics
781
+ assert metric.metric_code == "TASK_TYPES"
782
+ assert metric.facet is None
783
+ assert metric.name == "Frame Counts"
784
+ assert metric.description.startswith(
785
+ "This metric is a count of the number of frames used to produce"
786
+ )
787
+ assert metric.statement is None
788
+ all_warnings = metric.warnings
789
+ if expected_warning_count:
790
+ assert isinstance(all_warnings, list)
791
+ assert len(all_warnings) == expected_warning_count
792
+ for warning in all_warnings:
793
+ assert "% of frames were not used in the processing of task type" in warning
794
+ for task_count in input_task_counts:
795
+ # check that each out of tolerance task generated a warning
796
+ if task_count.frames_not_used / task_count.total_frames > 0.05:
797
+ assert any(task_count.task_type in w for w in all_warnings)
798
+ else:
799
+ assert all_warnings is None
800
+ # data
801
+ all_table_data = metric.table_data
802
+ assert all_table_data
803
+ assert isinstance(all_table_data, list)
804
+ assert len(all_table_data) == 1
805
+ table_data = all_table_data[0]
806
+ assert isinstance(table_data, TableData)
807
+ rows = table_data.rows
808
+ assert isinstance(rows, tuple)
809
+ assert len(rows) == expected_task_count + 1
810
+ header = rows[0]
811
+ assert header[0] == "Task Type"
812
+ assert header[1] == "Total Frames"
813
+ assert header[2] == "Unused Frames"
814
+ # make sure each task got included in the table
815
+ for task_count in input_task_counts:
816
+ assert any(row[0] == task_count.task_type for row in rows[1:])
817
+ for row in rows[1:]:
818
+ # get the specific task associated with this specific row
819
+ input_task_count = next((it for it in input_task_counts if it.task_type == row[0]))
820
+ # now make sure the other attributes match
821
+ assert row[1] == str(input_task_count.total_frames)
822
+ assert row[2] == str(input_task_count.frames_not_used)
823
+
824
+
825
+ @pytest.mark.parametrize(
826
+ "input_task_counts",
827
+ [
828
+ pytest.param([], id="no_tasks"),
829
+ pytest.param(
830
+ [
831
+ TaskCount(
832
+ task_type="one",
833
+ total_frames=100,
834
+ ),
835
+ ],
836
+ id="one_task",
837
+ ),
838
+ pytest.param(
839
+ [
840
+ TaskCount(
841
+ task_type="one",
842
+ total_frames=100,
843
+ frames_not_used=6,
844
+ ),
845
+ TaskCount(
846
+ task_type="two",
847
+ total_frames=200,
848
+ frames_not_used=10,
849
+ ),
850
+ TaskCount(
851
+ task_type="three",
852
+ total_frames=300,
853
+ frames_not_used=16,
854
+ ),
855
+ ],
856
+ id="three_tasks",
857
+ ),
858
+ ],
859
+ )
860
+ def test_read_intermediate_task_type_counts(
861
+ input_task_counts: list[TaskCount],
862
+ assemble_quality_data_task_no_data: AssembleQualityData,
863
+ ):
864
+ """
865
+ Given: valid TaskCount files in scratch
866
+ When: executing AssembleQualityData.quality_read_intermediate_task_type_counts()
867
+ Then: returns one TASK_TYPES file for each TaskCount
868
+ """
869
+ # Given
870
+ task = assemble_quality_data_task_no_data
871
+ for task_count in input_task_counts:
872
+ task.write(task_count, tags="QUALITY_TASK_TYPES", encoder=basemodel_encoder)
873
+ expected_task_count = len(input_task_counts)
874
+
875
+ # When
876
+ returned_task_counts: list[TaskCount] = task.quality_read_intermediate_task_type_counts()
877
+
878
+ # Then
879
+ assert isinstance(returned_task_counts, list)
880
+ assert len(returned_task_counts) == expected_task_count
881
+ # make sure each task got returned
882
+ for task_count in input_task_counts:
883
+ assert any(rt.task_type == task_count.task_type for rt in returned_task_counts)
884
+ for task_count in returned_task_counts:
885
+ # get the specific input task associated with this one
886
+ input_task_count = next(
887
+ (it for it in input_task_counts if it.task_type == task_count.task_type)
888
+ )
889
+ # now make sure the other attributes match
890
+ assert task_count.total_frames == input_task_count.total_frames
891
+ assert task_count.frames_not_used == input_task_count.frames_not_used
@@ -97,56 +97,6 @@ def test_find_iqr_outliers(quality_mixin_task):
97
97
  ]
98
98
 
99
99
 
100
- def test_create_table_metric(quality_mixin_task):
101
- """
102
- Given: a task with the QualityMixin
103
- When: submitting data to create a table metric
104
- Then: the metric is encoded with the expected schema
105
- """
106
- task = quality_mixin_task
107
- name = "table metric"
108
- description = "table metric description"
109
- metric_code = "TEST_METRIC"
110
- rows = [["a", 1], ["b", 2], ["c", 3]]
111
- statement = "table statement"
112
- warnings = ["table warning"]
113
- table_metric = task._create_table_metric(
114
- name=name,
115
- description=description,
116
- rows=rows,
117
- metric_code=metric_code,
118
- statement=statement,
119
- warnings=warnings,
120
- )
121
- assert table_metric["name"] == name
122
- assert table_metric["description"] == description
123
- assert table_metric["metric_code"] == metric_code
124
- assert table_metric["facet"] is None
125
- assert table_metric["statement"] == statement
126
- assert table_metric["table_data"] == {"header_column": False, "header_row": True, "rows": rows}
127
- assert table_metric["warnings"] == warnings
128
-
129
-
130
- def test_build_task_type_counts(quality_mixin_task):
131
- """
132
- Given: a task with the QualityMixin
133
- When: writing multiple task type count data elements
134
- Then: a schema is built containing all elements
135
- """
136
- task = quality_mixin_task
137
- task.quality_store_task_type_counts(task_type="dark", total_frames=109, frames_not_used=0)
138
- task.quality_store_task_type_counts(task_type="gain", total_frames=276, frames_not_used=58)
139
- task.quality_store_task_type_counts(task_type="foo", total_frames=0, frames_not_used=0)
140
- path = list(task.read(tags=Tag.quality("TASK_TYPES")))
141
- assert len(path) == 3
142
- metric = task.quality_build_task_type_counts()
143
- assert metric["name"] == "Frame Counts"
144
- assert metric["metric_code"] == "TASK_TYPES"
145
- assert metric["facet"] is None
146
- assert "21.0% of frames were not used in the processing of task type GAIN" in metric["warnings"]
147
- assert "NO FOO frames were used!" in metric["warnings"]
148
-
149
-
150
100
  @pytest.mark.parametrize(
151
101
  "thin, samples_larger_than_total",
152
102
  [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dkist-processing-common
3
- Version: 14.0.1
3
+ Version: 14.0.2
4
4
  Summary: Common task classes used by the DKIST science data processing pipelines
5
5
  Author-email: NSO / AURA <dkistdc@nso.edu>
6
6
  License: BSD-3-Clause