ctao-calibpipe 0.1.0rc9__py3-none-any.whl → 0.2.0rc2__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.

Potentially problematic release.


This version of ctao-calibpipe might be problematic. Click here for more details.

Files changed (24) hide show
  1. calibpipe/_version.py +2 -2
  2. calibpipe/core/common_metadata_containers.py +3 -0
  3. calibpipe/database/adapter/adapter.py +1 -1
  4. calibpipe/database/adapter/database_containers/__init__.py +2 -0
  5. calibpipe/database/adapter/database_containers/common_metadata.py +2 -0
  6. calibpipe/database/adapter/database_containers/throughput.py +30 -0
  7. calibpipe/database/interfaces/table_handler.py +79 -97
  8. calibpipe/telescope/throughput/containers.py +59 -0
  9. calibpipe/tests/unittests/array/test_cross_calibration.py +417 -0
  10. calibpipe/tests/unittests/database/test_table_handler.py +95 -0
  11. calibpipe/tests/unittests/telescope/camera/test_calculate_camcalib_coefficients.py +347 -0
  12. calibpipe/tests/unittests/telescope/camera/test_produce_camcalib_test_data.py +42 -0
  13. calibpipe/tests/unittests/telescope/throughput/test_muon_throughput_calibrator.py +189 -0
  14. calibpipe/tools/camcalib_test_data.py +361 -0
  15. calibpipe/tools/camera_calibrator.py +558 -0
  16. calibpipe/tools/muon_throughput_calculator.py +239 -0
  17. calibpipe/tools/telescope_cross_calibration_calculator.py +721 -0
  18. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/METADATA +3 -2
  19. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/RECORD +24 -14
  20. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/WHEEL +1 -1
  21. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/entry_points.txt +4 -0
  22. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/licenses/AUTHORS.md +0 -0
  23. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/licenses/LICENSE +0 -0
  24. {ctao_calibpipe-0.1.0rc9.dist-info → ctao_calibpipe-0.2.0rc2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,417 @@
1
+ import logging
2
+ from enum import Enum
3
+ from pathlib import Path
4
+ from unittest.mock import MagicMock
5
+
6
+ import astropy.units as u
7
+ import pytest
8
+ from astropy.table import Table
9
+ from calibpipe.tools.telescope_cross_calibration_calculator import (
10
+ CalculateCrossCalibration,
11
+ RelativeThroughputFitter,
12
+ )
13
+ from ctapipe.instrument import SubarrayDescription
14
+ from ctapipe.io import read_table
15
+ from traitlets.config import Config
16
+
17
+ logger = logging.getLogger("calibpipe.application")
18
+
19
+
20
+ class TestTelescopeCrossCalibration:
21
+ @pytest.fixture(scope="class")
22
+ def test_calibrator(self):
23
+ data_path = (
24
+ Path(__file__).parent / "../../data/array/cross_calibration_test_dl2.h5"
25
+ )
26
+ test_config = {
27
+ "CalculateCrossCalibration": {
28
+ "input_url": str(data_path),
29
+ "event_filters": {
30
+ "min_gammaness": 0.5,
31
+ },
32
+ "reconstruction_algorithm": "RandomForest",
33
+ "RelativeThroughputFitter": {
34
+ "throughput_normalization": [
35
+ ["type", "LST*", 1.0],
36
+ ["type", "MST*", 1.0],
37
+ ["type", "SST*", 1.0],
38
+ ],
39
+ "reference_telescopes": [
40
+ ["type", "LST*", 1],
41
+ ["type", "MST*", 5],
42
+ ["type", "SST*", 37],
43
+ ],
44
+ },
45
+ "PairFinder": {
46
+ "max_impact_distance": [
47
+ ["type", "LST*", 125.0],
48
+ ["type", "MST*", 125.0],
49
+ ["type", "SST*", 225.0],
50
+ ],
51
+ },
52
+ },
53
+ }
54
+ tool = CalculateCrossCalibration(config=Config(test_config))
55
+
56
+ return tool
57
+
58
+ @pytest.fixture()
59
+ def test_fitter_config(self):
60
+ fitter_config = {
61
+ "RelativeThroughputFitter": {
62
+ "throughput_normalization": [
63
+ ["type", "LST*", 1.0],
64
+ ["type", "MST*", 1.0],
65
+ ["type", "SST*", 1.0],
66
+ ],
67
+ "reference_telescopes": [
68
+ ["type", "LST*", 1],
69
+ ["type", "MST*", 5],
70
+ ["type", "SST*", 37],
71
+ ],
72
+ },
73
+ }
74
+ return Config(fitter_config)
75
+
76
+ @pytest.fixture()
77
+ def mock_minuit(self):
78
+ m = MagicMock()
79
+ m.values = {"x0": 0.74, "x1": 0.75, "x2": 0.76}
80
+ m.errors = {"x0": 1e-6, "x1": 2e-6, "x2": 3e-6}
81
+ return m
82
+
83
+ @pytest.mark.verifies_usecase("UC-120-2.3")
84
+ def test_create_telescope_pairs(self, test_calibrator):
85
+ test_calibrator.setup()
86
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
87
+ assert "MST" in telescope_pairs
88
+ assert len(telescope_pairs["MST"]) > 0
89
+ assert "SST" in telescope_pairs
90
+ assert len(telescope_pairs["SST"]) > 0
91
+ telescope_pairs = test_calibrator.pair_finder.find_pairs(by_tel_type=False)
92
+ assert "ALL" in telescope_pairs
93
+ assert len(telescope_pairs["ALL"]) > 0
94
+
95
+ @pytest.mark.verifies_usecase("UC-120-2.3")
96
+ def test_distance(self, test_calibrator):
97
+ # if the requested maximum telescope distance is too small, no tel pair should be returned
98
+ updated_pair_finder_config = {
99
+ "CalculateCrossCalibration": {
100
+ "PairFinder": {
101
+ "max_impact_distance": [
102
+ ["type", "LST*", 10.0],
103
+ ["type", "MST*", 10.0],
104
+ ["type", "SST*", 10.0],
105
+ ],
106
+ },
107
+ },
108
+ }
109
+
110
+ test_calibrator.update_config(Config(updated_pair_finder_config))
111
+ test_calibrator.setup()
112
+
113
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
114
+ assert all(
115
+ isinstance(value, set) and len(value) == 0
116
+ for value in telescope_pairs.values()
117
+ )
118
+
119
+ @pytest.mark.verifies_usecase("UC-120-2.3")
120
+ def test_no_reverse_pairs(self, test_calibrator):
121
+ # Ensure the reverse pair (j, i) is not present
122
+ test_calibrator.setup()
123
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
124
+
125
+ for telescope_type, pairs in telescope_pairs.items():
126
+ for i, j in pairs:
127
+ assert (
128
+ (j, i) not in pairs
129
+ ), f"Reverse pair ({j}, {i}) exists in {telescope_type} pairs"
130
+
131
+ @pytest.mark.verifies_usecase("UC-120-2.3")
132
+ def test_allowed_telescope_names(self, test_calibrator):
133
+ # Ensure all keys in the dictionary belong to the allowed names
134
+ allowed_telescope_names = {"MST", "SST"}
135
+ test_calibrator.setup()
136
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
137
+ for telescope_name in telescope_pairs.keys():
138
+ assert (
139
+ telescope_name in allowed_telescope_names
140
+ ), f"Unexpected telescope type '{telescope_name}' found in the output"
141
+
142
+ @pytest.mark.verifies_usecase("UC-120-2.3")
143
+ def test_maximum_telescope_pairs(self, test_calibrator):
144
+ updated_pair_finder_config = {
145
+ "CalculateCrossCalibration": {
146
+ "PairFinder": {
147
+ "max_impact_distance": [
148
+ ["type", "LST*", 150000.0],
149
+ ["type", "MST*", 150000.0],
150
+ ["type", "SST*", 150000.0],
151
+ ],
152
+ },
153
+ },
154
+ }
155
+
156
+ test_calibrator.update_config(Config(updated_pair_finder_config))
157
+ test_calibrator.setup()
158
+
159
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
160
+
161
+ array = read_table(
162
+ test_calibrator.input_url,
163
+ "configuration/instrument/subarray/layout",
164
+ )
165
+ telescope_types = [str(t).strip() for t in array["type"] if t]
166
+ for tel_type in set(telescope_types):
167
+ tel_type_counts = (array["type"] == tel_type).sum()
168
+ if tel_type_counts > 0:
169
+ assert (
170
+ len(telescope_pairs[tel_type])
171
+ == (tel_type_counts * (tel_type_counts - 1)) // 2
172
+ )
173
+
174
+ @pytest.mark.verifies_usecase("UC-120-2.3")
175
+ def test_merge_tables_success(self, test_calibrator):
176
+ """Test successful merging of tables for telescope pairs."""
177
+ telescope_pairs = {"SST": [(37, 39)], "MST": [(6, 12)]}
178
+ result = test_calibrator.merge_tables(telescope_pairs)
179
+
180
+ assert (37, 39) in result["SST"]
181
+ assert (6, 12) in result["MST"]
182
+ assert len(result["SST"][(37, 39)]) > 0 # Ensure merged table is not empty
183
+
184
+ @pytest.mark.verifies_usecase("UC-120-2.3")
185
+ def test_merge_tables_empty_input(self, test_calibrator):
186
+ """Test merge_tables returns empty dict when given an empty input."""
187
+ telescope_pairs = {}
188
+ result = test_calibrator.merge_tables(telescope_pairs)
189
+ assert result == {}
190
+
191
+ @pytest.mark.verifies_usecase("UC-120-2.3")
192
+ def test_merge_tables_missing_data(self, test_calibrator):
193
+ """Test `merge_tables` handles missing telescope data correctly."""
194
+ telescope_pairs = {"SST": [(37, 39), (42, 143)], "MST": [(5, 6), (6, 120000)]}
195
+ result = test_calibrator.merge_tables(telescope_pairs)
196
+ assert set(result["MST"].keys()) == {(5, 6)}
197
+
198
+ @pytest.mark.verifies_usecase("UC-120-2.3")
199
+ def test_inter_calibration_result_format(self, test_fitter_config):
200
+ data_subsystem = {
201
+ (5, 6): {"mean_asymmetry": 0.1, "mean_uncertainty": 0.01},
202
+ (6, 8): {"mean_asymmetry": -0.05, "mean_uncertainty": 0.02},
203
+ }
204
+
205
+ measured_telescopes = set(
206
+ tel_id for (i, j), entry in data_subsystem.items() for tel_id in (i, j)
207
+ )
208
+
209
+ data_path = (
210
+ Path(__file__).parent / "../../data/array/cross_calibration_test_dl2.h5"
211
+ )
212
+ subarray = SubarrayDescription.read(data_path)
213
+
214
+ test_fitter = RelativeThroughputFitter(
215
+ subarray=subarray.select_subarray(tel_ids=measured_telescopes),
216
+ config=test_fitter_config,
217
+ )
218
+
219
+ result = test_fitter.fit("MST", data_subsystem)
220
+ assert "MST" in result
221
+ assert isinstance(result["MST"], dict)
222
+ assert set(result["MST"].keys()) == {"5", "6", "8"}
223
+ assert all(isinstance(v, tuple) and len(v) == 2 for v in result["MST"].values())
224
+
225
+ @pytest.mark.verifies_usecase("UC-120-2.3")
226
+ def test_event_selection_with_multiple_filters(self, test_calibrator, monkeypatch):
227
+ test_calibrator.setup()
228
+ test_calibrator.event_filters = {
229
+ "min_gammaness": 0.5,
230
+ "min_energy": u.Quantity(0.0, u.GeV),
231
+ }
232
+ gamma_set = {(1, 100), (2, 200), (3, 300)}
233
+ energy_set = {(1, 100), (3, 300)}
234
+
235
+ monkeypatch.setattr(
236
+ test_calibrator,
237
+ "_apply_min_gammaness",
238
+ lambda tel_id1, tel_id2, threshold: gamma_set,
239
+ )
240
+ monkeypatch.setattr(
241
+ test_calibrator,
242
+ "_apply_min_energy",
243
+ lambda tel_id1, tel_id2, threshold: energy_set,
244
+ )
245
+ merged_table = Table({"obs_id": [1, 2, 3, 4], "event_id": [100, 200, 300, 400]})
246
+
247
+ filtered = test_calibrator.event_selection(merged_table, tel1=1, tel2=3)
248
+ assert len(filtered) == 2
249
+
250
+ @pytest.mark.verifies_usecase("UC-120-2.3")
251
+ def test_event_selection_with_invalid_filter(self, test_calibrator):
252
+ test_calibrator.setup()
253
+ test_calibrator.event_filters = {
254
+ "non_existent_filter": 4,
255
+ }
256
+ merged_table = Table({"obs_id": [1, 2, 3, 4], "event_id": [100, 200, 300, 400]})
257
+
258
+ with pytest.raises(
259
+ ValueError,
260
+ match="Filter non_existent_filter is not implemented or not recognized.",
261
+ ):
262
+ _ = test_calibrator.event_selection(merged_table, tel1=1, tel2=2)
263
+
264
+ @pytest.mark.verifies_usecase("UC-120-2.3")
265
+ def test_system_cross_calibrator(self, test_calibrator, caplog):
266
+ # Small scale integration test
267
+ caplog.set_level(logging.ERROR, logger="calibpipe.application")
268
+ updated_cc_config = {
269
+ "CalculateCrossCalibration": {
270
+ "event_filters": {
271
+ "min_gammaness": 0.5,
272
+ },
273
+ "RelativeThroughputFitter": {
274
+ "throughput_normalization": [
275
+ ["type", "LST*", 1.0],
276
+ ["type", "MST*", 1.0],
277
+ ["type", "SST*", 1.0],
278
+ ],
279
+ "reference_telescopes": [
280
+ ["type", "LST*", 1],
281
+ ["type", "MST*", 5],
282
+ ["type", "SST*", 37],
283
+ ],
284
+ },
285
+ "PairFinder": {
286
+ "max_impact_distance": [
287
+ ["type", "LST*", 125.0],
288
+ ["type", "MST*", 125.0],
289
+ ["type", "SST*", 225.0],
290
+ ],
291
+ },
292
+ },
293
+ }
294
+ test_calibrator.update_config(Config(updated_cc_config))
295
+ test_calibrator.setup()
296
+ telescope_pairs = test_calibrator.pair_finder.find_pairs()
297
+ merged_tables = test_calibrator.merge_tables(telescope_pairs)
298
+ energy_asymmetry_results = test_calibrator.calculate_energy_asymmetry(
299
+ merged_tables
300
+ )
301
+ results = {}
302
+
303
+ for subarray_name, subarray_data in energy_asymmetry_results.items():
304
+ measured_telescopes = set(
305
+ tel_id for (i, j), entry in subarray_data.items() for tel_id in (i, j)
306
+ )
307
+ fitter = RelativeThroughputFitter(
308
+ subarray=test_calibrator.subarray.select_subarray(
309
+ tel_ids=measured_telescopes, name=subarray_name
310
+ ),
311
+ parent=test_calibrator,
312
+ )
313
+ results.update(fitter.fit(subarray_name, subarray_data))
314
+
315
+ cross_type_pairs = test_calibrator.pair_finder.find_pairs(
316
+ by_tel_type=False, cross_type_only=True
317
+ )
318
+ cross_calibration_results = test_calibrator.compute_cross_type_energy_ratios(
319
+ cross_type_pairs["XTEL"], results
320
+ )
321
+ assert cross_calibration_results[("MST", "SST")][0] == pytest.approx(
322
+ 1.0119, rel=1e-2
323
+ )
324
+ assert cross_calibration_results[("MST", "SST")][1] == pytest.approx(
325
+ 0.0042591, rel=1e-2
326
+ )
327
+ # Below testing energy asymmetry
328
+ assert energy_asymmetry_results["MST"][(6, 12)][
329
+ "mean_uncertainty"
330
+ ] == pytest.approx(1.3613071073770851e-06, rel=1e-4)
331
+ assert energy_asymmetry_results["MST"][(6, 12)][
332
+ "mean_asymmetry"
333
+ ] == pytest.approx(-0.0008690296768284202, rel=1e-2)
334
+ # Below testing the fixed telescopes
335
+ assert results["SST"]["37"][0] == pytest.approx(1.0, rel=1e-4)
336
+ assert results["MST"]["5"][0] == pytest.approx(1.0, rel=1e-4)
337
+
338
+ @pytest.mark.verifies_usecase("UC-120-2.3")
339
+ def test_save_monitoring_data(self, test_calibrator, tmp_path):
340
+ # Prepare input
341
+ class SizeType(Enum):
342
+ MST = "MST"
343
+ SST = "SST"
344
+ LST = "LST"
345
+
346
+ intercalibration_results = {
347
+ SizeType.MST: {
348
+ "6": (1.23, 0.01),
349
+ "8": (0.97, 0.02),
350
+ }
351
+ }
352
+ cross_calibration_results = {
353
+ (SizeType.MST, SizeType.SST): (1.05, 0.05),
354
+ (SizeType.LST, SizeType.SST): (1.15, 0.05),
355
+ (SizeType.LST, SizeType.MST): (0.95, 0.05),
356
+ }
357
+
358
+ # Run
359
+ updated_cc_config = {
360
+ "CalculateCrossCalibration": {
361
+ "event_filters": {
362
+ "get_gamma_like_events": 0.5,
363
+ },
364
+ "output_url": "x_calib_test_dl2.h5",
365
+ "overwrite": True,
366
+ "RelativeThroughputFitter": {
367
+ "throughput_normalization": [
368
+ ["type", "LST*", 1.0],
369
+ ["type", "MST*", 1.0],
370
+ ["type", "SST*", 1.0],
371
+ ],
372
+ "reference_telescopes": [
373
+ ["type", "LST*", 1],
374
+ ["type", "MST*", 5],
375
+ ["type", "SST*", 37],
376
+ ],
377
+ },
378
+ "PairFinder": {
379
+ "max_impact_distance": [
380
+ ["type", "LST*", 125.0],
381
+ ["type", "MST*", 125.0],
382
+ ["type", "SST*", 225.0],
383
+ ],
384
+ },
385
+ },
386
+ }
387
+ test_calibrator.update_config(Config(updated_cc_config))
388
+ test_calibrator.setup()
389
+ test_calibrator.save_monitoring_data(
390
+ intercalibration_results, cross_calibration_results
391
+ )
392
+
393
+ # Validate inter calibration table
394
+ inter_table = read_table(
395
+ test_calibrator.output_url, "/dl2/monitoring/inter_calibration"
396
+ )
397
+ assert len(inter_table) == 2
398
+ assert "tel_id" in inter_table.colnames
399
+ assert "value" in inter_table.colnames
400
+ assert "error" in inter_table.colnames
401
+
402
+ # Validate cross calibration table
403
+ cross_table = read_table(
404
+ test_calibrator.output_url, "/dl2/monitoring/cross_calibration"
405
+ )
406
+ assert len(cross_table) == 3
407
+ assert cross_table["ratio"][0] == pytest.approx(1.05)
408
+ assert cross_table["error"][0] == pytest.approx(0.05)
409
+ assert cross_table["ratio"][1] == pytest.approx(1.15)
410
+ assert cross_table["ratio"][2] == pytest.approx(0.95)
411
+
412
+ @pytest.mark.verifies_usecase("UC-120-2.3")
413
+ def test_get_equidistant_events(self, test_calibrator):
414
+ test_calibrator.setup()
415
+ set_of_events = test_calibrator._apply_max_distance_asymmetry(42, 143, 0.05)
416
+ assert (4991, 1181519) in set_of_events
417
+ assert (4991, 2196419) not in set_of_events
@@ -1,17 +1,35 @@
1
1
  # Import the necessary modules and classes for testing
2
+ import datetime
2
3
  from pathlib import Path
4
+ from unittest.mock import MagicMock, patch
3
5
 
4
6
  import astropy.units as u
5
7
  import pytest
6
8
  import yaml
9
+ from calibpipe.core.common_metadata_containers import (
10
+ ContactReferenceMetadataContainer,
11
+ ProductReferenceMetadataContainer,
12
+ ReferenceMetadataContainer,
13
+ )
14
+ from calibpipe.database.adapter import Adapter
7
15
  from calibpipe.database.connections import CalibPipeDatabase
8
16
  from calibpipe.database.interfaces import TableHandler
17
+ from calibpipe.telescope.throughput.containers import OpticalThoughtputContainer
9
18
  from calibpipe.utils.observatory import (
10
19
  Observatory,
11
20
  )
12
21
  from traitlets.config import Config
13
22
 
14
23
 
24
+ @pytest.fixture()
25
+ def mock_connection():
26
+ """
27
+ Fixture to create a mock database connection.
28
+ """
29
+ connection = MagicMock(spec=CalibPipeDatabase)
30
+ return connection
31
+
32
+
15
33
  # Fixture to provide a database connection
16
34
  @pytest.fixture()
17
35
  def test_config():
@@ -64,3 +82,80 @@ class TestTableHandler:
64
82
  assert qtable is not None
65
83
  assert "elevation" in qtable.colnames
66
84
  assert qtable["elevation"].unit == u.m
85
+
86
+
87
+ def test_upload_data(mock_connection):
88
+ """
89
+ Test the upload_data function to ensure it correctly uploads data and metadata.
90
+ """
91
+ # Use OpticalThoughtputContainer as the data container
92
+ data = OpticalThoughtputContainer(
93
+ optical_throughput_coefficient=0.95,
94
+ optical_throughput_coefficient_std=0.02,
95
+ method="muon analysis",
96
+ validity_start=datetime.datetime(2025, 1, 1, tzinfo=datetime.timezone.utc),
97
+ validity_end=datetime.datetime(2025, 12, 31, tzinfo=datetime.timezone.utc),
98
+ obs_id=12345,
99
+ )
100
+
101
+ # Mock metadata containers
102
+ reference_metadata = ReferenceMetadataContainer(
103
+ version_atmospheric_model="1.0",
104
+ version=1,
105
+ ID_optical_throughput=None, # Will be set during the upload
106
+ )
107
+ product_metadata = ProductReferenceMetadataContainer(
108
+ description="Test product",
109
+ creation_time="2025-04-08T12:00:00Z",
110
+ product_id="12345",
111
+ )
112
+ contact_metadata = ContactReferenceMetadataContainer(
113
+ organization="Test Organization",
114
+ name="Test User",
115
+ email="test@example.com",
116
+ )
117
+
118
+ # Combine metadata into a list
119
+ metadata = [reference_metadata, product_metadata, contact_metadata]
120
+
121
+ # Mock database behavior
122
+ mock_connection.execute.return_value.fetchone.return_value = MagicMock(
123
+ _asdict=lambda: {"ID": 1}
124
+ )
125
+
126
+ # Patch the insert_row_in_database method
127
+ with patch(
128
+ "calibpipe.database.interfaces.table_handler.TableHandler.insert_row_in_database",
129
+ ) as mock_insert:
130
+ # Call the upload_data function
131
+ TableHandler.upload_data(data, metadata, mock_connection)
132
+
133
+ # Assertions to verify correct behavior
134
+ # Verify that the main data was inserted
135
+ data_table, data_kwargs = Adapter.to_postgres(data)
136
+
137
+ calls = mock_insert.call_args_list
138
+
139
+ # Check that a call was made with the expected table and connection
140
+ assert any(
141
+ call_args[0][0] == data_table
142
+ and call_args[0][2] == mock_connection
143
+ and call_args[0][1]["optical_throughput_coefficient"] == 0.95
144
+ for call_args in calls
145
+ ), "Expected call to insert_row_in_database not found."
146
+
147
+ ref_table, ref_kwargs = Adapter.to_postgres(reference_metadata)
148
+
149
+ assert any(
150
+ call_args[0][0] == ref_table
151
+ and call_args[0][2] == mock_connection
152
+ and call_args[0][1]["version_atmospheric_model"] == "1.0"
153
+ for call_args in calls
154
+ ), "Expected call to insert_row_in_database not found."
155
+
156
+ for container in metadata[1:]:
157
+ meta_table, meta_kwargs = Adapter.to_postgres(container)
158
+ assert any(
159
+ call_args[0][0] == meta_table and call_args[0][2] == mock_connection
160
+ for call_args in calls
161
+ ), "Expected call to insert_row_in_database not found."