Repository: gammasim/simtools
Pull request #1498: Refactor event scaling application
Author: login: tobiaskleiner
Created: 2025-04-08T14:32:08Z | Merged: 2025-04-10T14:29:37Z

This PR moves the methods of the `production_scale_events` into a new module called `scale_events_manager` and adds unit tests for the moved modules.


Diff:
diff --git a/docs/changes/1498.maintenance.md b/docs/changes/1498.maintenance.md
new file mode 100644
index 0000000000..2dd5c2d6de
--- /dev/null
+++ b/docs/changes/1498.maintenance.md
@@ -0,0 +1 @@
+Refactoring of event scaling application and moving most of the functionality into a new module.
diff --git a/docs/source/api-reference/production_configuration.md b/docs/source/api-reference/production_configuration.md
index 458b120e73..e71df9f8ae 100644
--- a/docs/source/api-reference/production_configuration.md
+++ b/docs/source/api-reference/production_configuration.md
@@ -59,3 +59,12 @@ the calculation of the number of events to be simulated given a pre-determined m
 .. automodule:: production_configuration.generate_production_grid
    :members:
 ```
+
+(scale-events-manager)=
+
+## scale_events_manager
+
+```{eval-rst}
+.. automodule:: production_configuration.scale_events_manager
+   :members:
+```
diff --git a/src/simtools/applications/production_generate_simulation_config.py b/src/simtools/applications/production_generate_simulation_config.py
index e2016cd9d6..5eb29e83f3 100644
--- a/src/simtools/applications/production_generate_simulation_config.py
+++ b/src/simtools/applications/production_generate_simulation_config.py
@@ -31,8 +31,6 @@
 file_path (str, required)
         Path to file with MC events at CTAO DL2 data level.
         Used for statistical uncertainty evaluation.
-file_type (str, required)
-        Type of the DL2 MC event file ('point-like' or 'cone').
 metrics (str, required)
         Path to a YAML file containing metrics for evaluation.
 site (str, required)
@@ -46,7 +44,6 @@
 
         simtools-production-generate-simulation-config --azimuth 60.0 --elevation 45.0 \
                 --nsb 0.3 --file_path tests/resources/production_dl2_fits/dl2_mc_events_file.fits \
-                --file_type "point-like"    \
                 --metrics_file tests/resources/production_simulation_config_metrics.yml --site North
 
 The output will show the derived simulation parameters.
@@ -85,12 +82,6 @@ def _parse(label):
     config.parser.add_argument(
         "--file_path", type=str, required=True, help="Path to MC event file in DL2 format."
     )
-    config.parser.add_argument(
-        "--file_type",
-        type=str,
-        required=True,
-        help="Type of the DL2 MC event file ('point-like' or 'cone').",
-    )
     config.parser.add_argument(
         "--metrics_file",
         required=True,
@@ -138,7 +129,6 @@ def main():
     simulation_config = SimulationConfig(
         grid_point=grid_point_config,
         file_path=args_dict["file_path"],
-        file_type=args_dict["file_type"],
         metrics=metrics,
     )
 
diff --git a/src/simtools/applications/production_scale_events.py b/src/simtools/applications/production_scale_events.py
index 13b287ad8e..a838a3870c 100644
--- a/src/simtools/applications/production_scale_events.py
+++ b/src/simtools/applications/production_scale_events.py
@@ -4,7 +4,7 @@
 Application to run the StatisticalErrorEvaluator and interpolate results.
 
 This application evaluates statistical uncertainties from DL2 MC event files
-based on input parameters like zenith angles and offsets, and performs interpolation
+based on input parameters like zenith angles and camera offsets, and performs interpolation
 for a specified query point.
 
 Command line arguments
@@ -13,7 +13,7 @@
     Path to the directory containing the DL2 MC event file for interpolation.
 zeniths (list of int, required)
     List of zenith angles to consider.
-offsets (list of int, required)
+camera_offsets (list of int, required)
     List of offsets in degrees.
 query_point (list of float, required)
     Query point for interpolation. The query point must contain exactly 5 values:
@@ -37,30 +37,19 @@
 .. code-block:: console
 
     simtools-production-scale-events --base_path tests/resources/production_dl2_fits/ \\
-        --zeniths 20 40 52 60 --offsets 0 --query_point 1 180 30 0 0 \\
+        --zeniths 20 40 52 60 --camera_offsets 0 --query_point 1 180 30 0 0 \\
         --metrics_file "path/to/metrics.yaml" \\
-        --output_file "output.json"
+        --output_path simtools-output/derived_events \\
+        --output_file derived_events.json
 
 The output will display the scaled events for the specified query point and save
  the results to the specified output file.
 """
 
-import itertools
-import json
-import logging
 from pathlib import Path
 
-import astropy.units as u
-import numpy as np
-
-import simtools.utils.general as gen
 from simtools.configuration import configurator
-from simtools.configuration.commandline_parser import CommandLineParser
-from simtools.io_operations import io_handler
-from simtools.production_configuration.calculate_statistical_errors_grid_point import (
-    StatisticalErrorEvaluator,
-)
-from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.production_configuration.scale_events_manager import ScaleEventsManager
 
 
 def _parse(label, description):
@@ -84,13 +73,16 @@ def _parse(label, description):
         "--zeniths",
         required=True,
         nargs="+",
-        type=CommandLineParser.zenith_angle,
+        type=float,
         help="List of zenith angles.",
     )
     config.parser.add_argument(
-        "--offsets", required=True, nargs="+", type=float, help="List of offsets in degrees."
+        "--camera_offsets",
+        required=True,
+        nargs="+",
+        type=float,
+        help="List of camera offsets in degrees.",
     )
-
     config.parser.add_argument(
         "--query_point",
         required=True,
@@ -98,13 +90,6 @@ def _parse(label, description):
         type=float,
         help="Grid point for interpolation (energy, azimuth, zenith, NSB, offset).",
     )
-    config.parser.add_argument(
-        "--output_file",
-        required=False,
-        type=str,
-        default="interpolated_scaled_events.json",
-        help="Output file to store the results. (default: 'interpolated_scaled_events.json').",
-    )
     config.parser.add_argument(
         "--metrics_file",
         required=False,
@@ -117,68 +102,21 @@ def _parse(label, description):
         required=False,
         type=str,
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
-        help=(
-            "Template for the file name. (default: "
-            "'prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits')"
-        ),
+        help=("Template for the DL2 MC event file name."),
     )
-    return config.initialize(db_config=False)
+    return config.initialize(db_config=False, output=True)
 
 
 def main():
-    """Run the evaluator and interpolate."""
+    """Run the ScaleEventsManager."""
     label = Path(__file__).stem
     args_dict, _ = _parse(
         label,
         "Evaluate statistical uncertainties from DL2 MC event files and interpolate results.",
     )
-    logger = logging.getLogger()
-    logger.setLevel(logging.INFO)
-    logger.info(f"args dict: {args_dict}")
-
-    output_path = io_handler.IOHandler().get_output_directory(label)
-    output_filepath = Path(output_path).joinpath(f"{args_dict['output_file']}")
-
-    evaluator_instances = []
-
-    metrics = gen.collect_data_from_file(args_dict["metrics_file"])
-
-    if args_dict["base_path"] and args_dict["zeniths"] and args_dict["offsets"]:
-        for zenith, offset in itertools.product(args_dict["zeniths"], args_dict["offsets"]):
-            file_name = args_dict["file_name_template"].format(zenith=int(zenith.value))
-            file_path = Path(args_dict["base_path"]).joinpath(file_name)
-
-            if not file_path.exists():
-                logger.warning(f"File not found: {file_path}. Skipping.")
-                continue
-
-            evaluator = StatisticalErrorEvaluator(
-                file_path,
-                file_type="Gamma-cone",
-                metrics=metrics,
-                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
-            )
-            evaluator.calculate_metrics()
-            evaluator_instances.append(evaluator)
-
-    else:
-        logger.warning("No files read")
-        logger.warning(f"Base Path: {args_dict['base_path']}")
-        logger.warning(f"Zeniths: {args_dict['zeniths']}")
-        logger.warning(f"Offsets: {args_dict['offsets']}")
-
-    interpolation_handler = InterpolationHandler(evaluator_instances, metrics=metrics)
-    query_points = np.array([args_dict["query_point"]])
-    scaled_events = interpolation_handler.interpolate(query_points)
-
-    output_data = {
-        "query_point": args_dict["query_point"],
-        "scaled_events": scaled_events.tolist(),
-    }
-    with open(output_filepath, "w", encoding="utf-8") as f:
-        json.dump(output_data, f, indent=4)
-    logger.info(f"Output saved to {output_filepath}")
-    logger.info(f"Scaled events for grid point {args_dict['query_point']}: {scaled_events}")
+
+    manager = ScaleEventsManager(args_dict)
+    manager.run()
 
 
 if __name__ == "__main__":
diff --git a/src/simtools/production_configuration/calculate_statistical_errors_grid_point.py b/src/simtools/production_configuration/calculate_statistical_errors_grid_point.py
index 5dd9a4f3e1..372e05720e 100644
--- a/src/simtools/production_configuration/calculate_statistical_errors_grid_point.py
+++ b/src/simtools/production_configuration/calculate_statistical_errors_grid_point.py
@@ -17,8 +17,6 @@ class StatisticalErrorEvaluator:
     ----------
     file_path : str
         Path to the DL2 MC event file.
-    file_type : str
-        Type of the file, either 'point-like' or 'cone'.
     metrics : dict, optional
         Dictionary of metrics to evaluate. Default is None.
     grid_point : tuple, optional
@@ -28,13 +26,11 @@ class StatisticalErrorEvaluator:
     def __init__(
         self,
         file_path: str,
-        file_type: str,
         metrics: dict[str, float],
         grid_point: tuple[float, float, float, float, float] | None = None,
     ):
         """Init the evaluator with a DL2 MC event file, its type, and metrics to calculate."""
         self._logger = logging.getLogger(__name__)
-        self.file_type = file_type
         self.metrics = metrics
         self.grid_point = grid_point
 
diff --git a/src/simtools/production_configuration/generate_simulation_config.py b/src/simtools/production_configuration/generate_simulation_config.py
index e2016da47d..2df27718fb 100644
--- a/src/simtools/production_configuration/generate_simulation_config.py
+++ b/src/simtools/production_configuration/generate_simulation_config.py
@@ -18,8 +18,6 @@ class SimulationConfig:
         Dictionary representing a grid point with azimuth, elevation, and night sky background.
     file_path : str
         Path to the DL2 MC event file for statistical uncertainty evaluation.
-    file_type : str
-        Type of the DL2 MC event file ('point-like' or 'cone').
     metrics : dict, optional
         Dictionary of metrics to evaluate.
     """
@@ -28,15 +26,13 @@ def __init__(
         self,
         grid_point: dict[str, float],
         file_path: str,
-        file_type: str,
         metrics: dict[str, float] | None = None,
     ):
         """Initialize the simulation configuration for a grid point."""
         self.grid_point = grid_point
         self.file_path = file_path
-        self.file_type = file_type
         self.metrics = metrics or {}
-        self.evaluator = StatisticalErrorEvaluator(file_path, file_type, metrics)
+        self.evaluator = StatisticalErrorEvaluator(file_path, metrics)
         self.event_scaler = EventScaler(self.evaluator, self.metrics)
         self.simulation_params = {}
 
diff --git a/src/simtools/production_configuration/scale_events_manager.py b/src/simtools/production_configuration/scale_events_manager.py
new file mode 100644
index 0000000000..ba893e876d
--- /dev/null
+++ b/src/simtools/production_configuration/scale_events_manager.py
@@ -0,0 +1,119 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.output_path = Path(self.args.get("output_path", "."))
+        self.output_filepath = self.output_path.joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["camera_offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Camera offsets: {self.args['camera_offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["camera_offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                metrics=self.metrics,
+                grid_point=(None, None, zenith, None, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the derived event statistics to a file."""
+        output_data = {
+            "query_point": self.args["query_point"],
+            "scaled_events": scaled_events.tolist(),
+        }
+        self.output_filepath.parent.mkdir(parents=True, exist_ok=True)
+        with open(self.output_filepath, "w", encoding="utf-8") as f:
+            json.dump(output_data, f, indent=4)
+        self.logger.info(f"Output saved to {self.output_filepath}")
+        self.logger.info(
+            f"Scaled events for query point {self.args['query_point']}: {scaled_events}"
+        )
+
+    def run(self):
+        """Run the scaling and interpolation workflow."""
+        self.logger.info(f"Zeniths: {self.args['zeniths']}")
+        self.logger.info(f"Camera offsets: {self.args['camera_offsets']}")
+        self.logger.info(f"Query Point: {self.args['query_point']}")
+        self.logger.info(f"Metrics File: {self.args['metrics_file']}")
+
+        self.initialize_evaluators()
+        scaled_events = self.perform_interpolation()
+        self.write_output(scaled_events)
diff --git a/tests/integration_tests/config/production_generate_simulation_config.yml b/tests/integration_tests/config/production_generate_simulation_config.yml
index 18aafdd423..78f59f7771 100644
--- a/tests/integration_tests/config/production_generate_simulation_config.yml
+++ b/tests/integration_tests/config/production_generate_simulation_config.yml
@@ -9,7 +9,6 @@ CTA_SIMPIPE:
       ELEVATION: 45.0
       NSB: 0.3
       FILE_PATH: tests/resources/production_dl2_fits/prod6_LaPalma-20deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits
-      FILE_TYPE: point-like
       METRICS_FILE: tests/resources/production_simulation_config_metrics.yml
       OUTPUT_PATH: simtools-output
       OUTPUT_FILE: configured_simulation_params.json
diff --git a/tests/integration_tests/config/production_scale_events.yml b/tests/integration_tests/config/production_scale_events.yml
index 5211f46a78..083ecb38a1 100644
--- a/tests/integration_tests/config/production_scale_events.yml
+++ b/tests/integration_tests/config/production_scale_events.yml
@@ -10,7 +10,7 @@ CTA_SIMPIPE:
       - 40
       - 52
       - 60
-      OFFSETS:
+      CAMERA_OFFSETS:
       - 0
       QUERY_POINT:
       - 1
diff --git a/tests/unit_tests/production_configuration/test_calculate_statistical_errors_grid.py b/tests/unit_tests/production_configuration/test_calculate_statistical_errors_grid.py
index f08520beff..8afe285caf 100644
--- a/tests/unit_tests/production_configuration/test_calculate_statistical_errors_grid.py
+++ b/tests/unit_tests/production_configuration/test_calculate_statistical_errors_grid.py
@@ -36,19 +36,14 @@ def metric():
 
 def test_initialization(test_fits_file, metric):
     """Test the initialization of the StatisticalErrorEvaluator."""
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
-    assert evaluator.file_type == "point-like"
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
     assert isinstance(evaluator.data, dict)
     assert "event_energies_reco" in evaluator.data
 
 
 def test_calculate_uncertainty_effective_area(test_fits_file, metric):
     """Test the calculation of effective area error."""
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
     evaluator.calculate_metrics()
     errors = evaluator.calculate_uncertainty_effective_area()
     assert "relative_errors" in errors
@@ -57,9 +52,7 @@ def test_calculate_uncertainty_effective_area(test_fits_file, metric):
 
 def test_calculate_energy_estimate(test_fits_file, metric):
     """Test the calculation of energy estimate error."""
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
     evaluator.calculate_metrics()
     error, sigma, delta = evaluator.calculate_energy_estimate()
     assert isinstance(sigma, list)
@@ -69,24 +62,23 @@ def test_calculate_energy_estimate(test_fits_file, metric):
 def test_missing_file():
     """Test initialization with a missing file."""
     file_path = "nonexistent_file.fits"
-    file_type = "point-like"
     metrics = {
         "uncertainty_effective_area": {"target_error": {"value": 0.1, "unit": "dimensionless"}}
     }
 
     with pytest.raises(FileNotFoundError, match=f"Error loading file {file_path}:"):
-        StatisticalErrorEvaluator(file_path, file_type, metrics)
+        StatisticalErrorEvaluator(file_path, metrics)
 
 
 def test_interpolation_handler(test_fits_file, test_fits_file_2, metric):
     """Test interpolation with the InterpolationHandler."""
     grid_point1 = (1, 180, 45, 0, 0.5)
     evaluator1 = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric, grid_point=grid_point1
+        file_path=test_fits_file, metrics=metric, grid_point=grid_point1
     )
     grid_point2 = (1, 180, 60, 0, 0.5)
     evaluator2 = StatisticalErrorEvaluator(
-        file_path=test_fits_file_2, file_type="point-like", metrics=metric, grid_point=grid_point2
+        file_path=test_fits_file_2, metrics=metric, grid_point=grid_point2
     )
     handler = InterpolationHandler([evaluator1, evaluator2], metrics=metric)
     query_point = np.array([[1, 180, 50, 0, 0.5]])
@@ -101,9 +93,7 @@ def test_interpolation_handler(test_fits_file, test_fits_file_2, metric):
 def test_calculate_scaled_events(test_fits_file, metric):
     """Test the calculation of scaled events for a specific grid point using EventScaler."""
 
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
     evaluator.grid_point = (1.5, 180, 45, 0, 0.5)
 
     event_scaler = EventScaler(evaluator, metrics=metric)
@@ -117,9 +107,7 @@ def test_calculate_scaled_events(test_fits_file, metric):
 
 def test_calculate_metrics(test_fits_file, metric):
     """Test the calculation of metrics."""
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
 
     evaluator.calculate_energy_estimate = lambda: (
         0.33,
@@ -147,12 +135,9 @@ def test_calculate_metrics(test_fits_file, metric):
 @pytest.fixture
 def setup_evaluator(metric):
     file_path = "path_to_fits_file"
-    file_type = "point-like"
     grid_point = (1.0, 45.0, 30.0, 0.1, 0.05)
 
-    evaluator = StatisticalErrorEvaluator(
-        file_path, file_type, metrics=metric, grid_point=grid_point
-    )
+    evaluator = StatisticalErrorEvaluator(file_path, metrics=metric, grid_point=grid_point)
 
     evaluator.metric_results = {
         "uncertainty_effective_area": {"relative_errors": np.array([0.04, 0.05, 0.06])},
@@ -168,7 +153,6 @@ def setup_evaluator(metric):
 def test_calculate_overall_metric_average(test_fits_file):
     evaluator = StatisticalErrorEvaluator(
         file_path=test_fits_file,
-        file_type="point-like",
         metrics={
             "uncertainty_effective_area": {"target_error": {"value": 0.1, "unit": "dimensionless"}}
         },
@@ -188,7 +172,6 @@ def test_calculate_overall_metric_average(test_fits_file):
 def test_calculate_overall_metric_maximum(test_fits_file):
     evaluator = StatisticalErrorEvaluator(
         file_path=test_fits_file,
-        file_type="point-like",
         metrics={
             "uncertainty_effective_area": {"target_error": {"value": 0.1, "unit": "dimensionless"}}
         },
@@ -209,9 +192,7 @@ def test_calculate_overall_metric_maximum(test_fits_file):
 
 def test_create_bin_edges(test_fits_file, metric):
     """Test the creation of unique energy bin edges."""
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
 
     evaluator.data = {
         "bin_edges_low": np.array([1.0, 2.0, 3.0]),
@@ -228,9 +209,7 @@ def test_create_bin_edges(test_fits_file, metric):
 
 
 def test_compute_efficiency_and_errors(test_fits_file, metric):
-    evaluator = StatisticalErrorEvaluator(
-        file_path=test_fits_file, file_type="point-like", metrics=metric
-    )
+    evaluator = StatisticalErrorEvaluator(file_path=test_fits_file, metrics=metric)
 
     reconstructed_event_counts = np.array([10, 20, 5, 0]) * u.ct
     simulated_event_counts = np.array([100, 200, 50, 0]) * u.ct
@@ -258,7 +237,6 @@ def test_compute_efficiency_and_errors(test_fits_file, metric):
 def test_calculate_overall_metric_invalid_metric(test_fits_file):
     evaluator = StatisticalErrorEvaluator(
         file_path=test_fits_file,
-        file_type="point-like",
         metrics={"invalid_metric": {"target_error": {"value": 0.1, "unit": "dimensionless"}}},
     )
 
@@ -268,7 +246,7 @@ def test_calculate_overall_metric_invalid_metric(test_fits_file):
 
 def test_set_grid_point_single_azimuth_zenith(caplog):
     with patch.object(StatisticalErrorEvaluator, "load_data_from_file", return_value={}):
-        evaluator = StatisticalErrorEvaluator(file_path="", file_type="point-like", metrics={})
+        evaluator = StatisticalErrorEvaluator(file_path="", metrics={})
         events_data = {"PNT_AZ": np.array([45.0]), "PNT_ALT": np.array([45.0])}
         evaluator._set_grid_point(events_data)
         assert evaluator.grid_point == (1 * u.TeV, 45 * u.deg, 45 * u.deg, 0, 0 * u.deg)
diff --git a/tests/unit_tests/production_configuration/test_generate_simulation_config.py b/tests/unit_tests/production_configuration/test_generate_simulation_config.py
index f9c4f08c5f..8770e8f67d 100644
--- a/tests/unit_tests/production_configuration/test_generate_simulation_config.py
+++ b/tests/unit_tests/production_configuration/test_generate_simulation_config.py
@@ -60,7 +60,6 @@ def test_initialization(mock_statistical_error_evaluator):
 
     assert config.grid_point == grid_point
     assert config.file_path == PATH_FITS
-    assert config.file_type == "point-like"
 
 
 def test_configure_simulation(mock_statistical_error_evaluator):
@@ -75,7 +74,7 @@ def test_configure_simulation(mock_statistical_error_evaluator):
             "energy_range": {"value": [0.04, 200], "unit": "TeV"},
         },
     }
-    config = SimulationConfig(grid_point, PATH_FITS, "Off-source", metrics)
+    config = SimulationConfig(grid_point, PATH_FITS, metrics)
     config.evaluator = mock_statistical_error_evaluator
 
     params = config.configure_simulation()
@@ -85,7 +84,7 @@ def test_configure_simulation(mock_statistical_error_evaluator):
 
 def test_calculate_core_scatter_area(mock_statistical_error_evaluator):
     grid_point = {"azimuth": 45.0, "elevation": 60.0}
-    config = SimulationConfig(grid_point, PATH_FITS, "point-like")
+    config = SimulationConfig(grid_point, PATH_FITS)
     config.evaluator = mock_statistical_error_evaluator
 
     # Mocking the method calculate_core_scatter_area
@@ -98,7 +97,7 @@ def test_calculate_core_scatter_area(mock_statistical_error_evaluator):
 
 def test_calculate_viewcone(mock_statistical_error_evaluator):
     grid_point = {"azimuth": 15.0, "elevation": 25.0}
-    config = SimulationConfig(grid_point, PATH_FITS, "Off-source")
+    config = SimulationConfig(grid_point, PATH_FITS)
     config.evaluator = mock_statistical_error_evaluator
 
     # Mocking the method calculate_viewcone
@@ -115,7 +114,6 @@ def test_edge_cases(mock_statistical_error_evaluator, metric):
     config = SimulationConfig(
         grid_point=grid_point,
         file_path=PATH_FITS,
-        file_type="point-like",
         metrics=metric,
     )
     config.evaluator = mock_statistical_error_evaluator
diff --git a/tests/unit_tests/production_configuration/test_scale_events_manager.py b/tests/unit_tests/production_configuration/test_scale_events_manager.py
new file mode 100644
index 0000000000..6edb3c749e
--- /dev/null
+++ b/tests/unit_tests/production_configuration/test_scale_events_manager.py
@@ -0,0 +1,167 @@
+import json
+from pathlib import Path
+from unittest.mock import MagicMock, mock_open, patch
+
+import numpy as np
+import pytest
+
+from simtools.production_configuration.scale_events_manager import ScaleEventsManager
+
+
+@pytest.fixture
+def args_dict(tmp_path, metrics_file):
+    """Fixture to provide a mock args_dict for testing."""
+    return {
+        "base_path": "tests/resources/production_dl2_fits/",
+        "zeniths": [20, 40],
+        "camera_offsets": [0.5, 1.0],
+        "query_point": [1.0, 180.0, 20.0, 4.0, 0.5],
+        "output_file": "scaled_events.json",
+        "output_path": str(tmp_path),  # Ensure output_path is set
+        "metrics_file": str(metrics_file),
+        "file_name_template": "prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits",
+    }
+
+
+@pytest.fixture
+def metrics_file():
+    """Fixture to return a metrics file."""
+    return Path("tests/resources/production_simulation_config_metrics.yml")
+
+
+def test_no_evaluators_initialized(args_dict, tmp_path):
+    """Test behavior when no evaluators are initialized."""
+    args_dict["zeniths"] = []  # No zeniths provided
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators()
+
+    # Check that no evaluators are initialized
+    assert len(manager.evaluator_instances) == 0
+
+
+def test_invalid_query_point(args_dict):
+    """Test behavior with an invalid query point."""
+    args_dict["query_point"] = [1, 2]  # Invalid query point
+    manager = ScaleEventsManager(args_dict)
+    manager.initialize_evaluators()
+
+    with pytest.raises(ValueError, match="Invalid query point format. Expected 5 values, got 2."):
+        manager.perform_interpolation()
+
+
+def test_missing_event_files(args_dict, tmp_path):
+    """Test behavior when event files are missing."""
+    args_dict["file_name_template"] = "non_existent_file.fits"
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators()
+
+    # Check that no evaluators are initialized due to missing files
+    assert len(manager.evaluator_instances) == 0
+
+
+def test_no_base_path(args_dict, tmp_path):
+    """Test behavior when base_path is not provided."""
+    args_dict["base_path"] = None
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators()
+
+    # Check that no evaluators are initialized
+    assert len(manager.evaluator_instances) == 0
+
+
+def test_empty_offsets(args_dict, tmp_path):
+    """Test behavior when offsets are empty."""
+    args_dict["camera_offsets"] = []  # Empty offsets
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators()
+
+    # Check that no evaluators are initialized
+    assert len(manager.evaluator_instances) == 0
+
+
+def test_no_query_point(args_dict, tmp_path):
+    """Test behavior when query_point is missing."""
+    args_dict.pop("query_point", None)  # Remove query_point
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators()
+
+    with pytest.raises(
+        ValueError, match="Invalid query point format. Expected 5 values, got None."
+    ):
+        manager.perform_interpolation()
+
+
+@patch("builtins.open", new_callable=mock_open)
+def test_write_output(mock_open, args_dict, tmp_path):
+    """Test the write_output method."""
+    manager = ScaleEventsManager(args_dict)
+
+    scaled_events = np.array([10000])
+
+    manager.write_output(scaled_events=scaled_events)
+
+    mock_open.assert_any_call(Path(tmp_path) / args_dict["output_file"], "w", encoding="utf-8")
+
+    handle = mock_open()
+    written_data = "".join(call.args[0] for call in handle.write.call_args_list)
+
+    expected_data = {
+        "query_point": [1.0, 180.0, 20.0, 4.0, 0.5],
+        "scaled_events": [10000],
+    }
+    assert json.loads(written_data) == expected_data
+
+
+def test_run(args_dict, tmp_path):
+    """Test the run method."""
+    manager = ScaleEventsManager(args_dict)
+
+    manager.initialize_evaluators = MagicMock()
+    manager.perform_interpolation = MagicMock()
+    manager.write_output = MagicMock()
+
+    manager.run()
+
+    manager.initialize_evaluators.assert_called_once()
+    manager.perform_interpolation.assert_called_once()
+    manager.write_output.assert_called_once()
+
+
+def test_perform_interpolation_not_initialized(args_dict, tmp_path):
+    """Test perform_interpolation when no evaluators are initialized."""
+    manager = ScaleEventsManager(args_dict)
+
+    manager.logger = MagicMock()
+
+    manager.evaluator_instances = []
+
+    result = manager.perform_interpolation()
+
+    manager.logger.error.assert_called_once_with(
+        "No evaluators initialized. Cannot perform interpolation."
+    )
+    assert result is None
+
+
+@patch("simtools.production_configuration.interpolation_handler.InterpolationHandler.interpolate")
+def test_perform_interpolation_with_valid_query_point(mock_interpolate, args_dict, tmp_path):
+    manager = ScaleEventsManager(args_dict)
+
+    manager.evaluator_instances = [MagicMock()]
+
+    mock_interpolate.return_value = np.array([12345])
+
+    result = manager.perform_interpolation()
+
+    expected_query_points = np.array([args_dict["query_point"]])
+
+    actual_query_points = mock_interpolate.call_args[0][0]
+
+    np.testing.assert_array_equal(actual_query_points, expected_query_points)
+
+    assert np.array_equal(result, np.array([12345]))


Reviews:
user: login: GernotMaier
body: Thanks for this! 

A couple of minor comments - some of them are related to understanding. My question indicate where code /  docstrings or not entirely clear.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T08:31:33Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:39:09Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:40:05Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:41:27Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:50:02Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:51:56Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:52:04Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:52:10Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:52:15Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:52:19Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:53:59Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T09:54:09Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T10:00:08Z
user: login: GernotMaier
body: Thanks a lot. Some tiny changes and then we are good.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T12:49:09Z
user: login: tobiaskleiner
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-10T13:48:26Z
user: login: GernotMaier
body: All good now, thanks!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T14:21:18Z
user: login: orelgueta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T12:47:15Z
user: login: orelgueta
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T12:47:49Z

Review comments:
diff_hunk: @@ -119,66 +106,22 @@ def _parse(label, description):
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
         help=(
             "Template for the file name. (default: "
-            "'prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits')"
+            "'prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits'"
path: src/simtools/applications/production_scale_events.py
user: login: GernotMaier
body: This is a very very specific default value. Is this necessary?
created_at: 2025-04-10T07:42:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: suggest to changes this in a description describing what the module does: `Derives requires number of events for a simulation production using a pre-defined metric` or something similar.
created_at: 2025-04-10T07:50:51Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: What are 'statistical uncertainties from DL2 files? I guess you mean statistical uncertainties on e.g. effective areas or other distributions?
created_at: 2025-04-10T07:51:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Same comment as above - no statistical uncertainies on files.

The class and module docstring seems to be almost the same. Suggest to reduce the module docstring to a single line and keep all the details in the class docstring.
created_at: 2025-04-10T07:54:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Suggest to remove this - we usually set this in the calling application (there should be line `logger.setLevel(gen.get_log_level_from_user(args_dict["log_level"]))` in the application). Otherwise the user cannot change the log level.
created_at: 2025-04-10T07:55:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Not sure I understand - the output_path is the python file name (scale_event_manager)?
created_at: 2025-04-10T07:57:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
+        output_data = {
+            "query_point": self.args["query_point"],
+            "scaled_events": scaled_events.tolist(),
+        }
+        with open(self.output_filepath, "w", encoding="utf-8") as f:
+            json.dump(output_data, f, indent=4)
+        self.logger.info(f"Output saved to {self.output_filepath}")
+        self.logger.info(
+            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
+        )
+
+    def run(self):
+        """Run the scaling and interpolation workflow."""
+        self.logger.info(f"args dict: {self.args}")
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: This is printing a lot of unrelated args_dict to the screen which might be confusing for the user. Do we want to concentrate on those relevant to this application?
created_at: 2025-04-10T08:05:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -84,13 +72,12 @@ def _parse(label, description):
         "--zeniths",
         required=True,
         nargs="+",
-        type=CommandLineParser.zenith_angle,
+        type=float,
         help="List of zenith angles.",
     )
     config.parser.add_argument(
         "--offsets", required=True, nargs="+", type=float, help="List of offsets in degrees."
path: src/simtools/applications/production_scale_events.py
user: login: GernotMaier
body: 'camera offsets' would be clearer
created_at: 2025-04-10T08:07:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,66 +106,22 @@ def _parse(label, description):
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
path: src/simtools/applications/production_scale_events.py
user: login: GernotMaier
body: For all parameters: do not add the default value to the help message. argpars is printing it out (now it is duplicated).
created_at: 2025-04-10T08:08:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -119,66 +106,22 @@ def _parse(label, description):
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
         help=(
             "Template for the file name. (default: "
path: src/simtools/applications/production_scale_events.py
user: login: GernotMaier
body: This help message is incomplete - what is "the file name"?
created_at: 2025-04-10T08:10:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Is this fine tuning on file names clear to the user? 
created_at: 2025-04-10T08:13:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Fixed file type? What if I would look at protons?
created_at: 2025-04-10T08:14:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Default values? Or is it calculating the values at this grid point only?
created_at: 2025-04-10T08:14:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Suggest to merge this line with the almost identical line 5 code lines earlier.
created_at: 2025-04-10T08:17:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
+        output_data = {
+            "query_point": self.args["query_point"],
+            "scaled_events": scaled_events.tolist(),
+        }
+        with open(self.output_filepath, "w", encoding="utf-8") as f:
+            json.dump(output_data, f, indent=4)
+        self.logger.info(f"Output saved to {self.output_filepath}")
+        self.logger.info(
+            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: grid point or query point?
created_at: 2025-04-10T08:18:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Be more concrete, what are 'results'? These are the derived / interpolated event statistics?
created_at: 2025-04-10T08:19:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: I am bit confused on the dimensions: the grid_point has 3 dimensions, but the query point has 5? 
created_at: 2025-04-10T08:27:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: done
created_at: 2025-04-10T09:39:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: Yes right, these are the default values and they get overwritten by the parameters from the DL2 MC event files.
created_at: 2025-04-10T09:40:05Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
+        output_data = {
+            "query_point": self.args["query_point"],
+            "scaled_events": scaled_events.tolist(),
+        }
+        with open(self.output_filepath, "w", encoding="utf-8") as f:
+            json.dump(output_data, f, indent=4)
+        self.logger.info(f"Output saved to {self.output_filepath}")
+        self.logger.info(
+            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: fixed
created_at: 2025-04-10T09:41:27Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: The grid is in principle 5 dimensional and gets reduced to lower dimensions if there are flat axis (no change in the axis values). The query point is also 5 dimensional and is reduced in the same way as the grid to match the dimensions for interpolation, but we require it to be 5 dimensional at the beginning.
created_at: 2025-04-10T09:50:02Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: This creates the folder scale_event_manager (now renamed in the other PR) in the simtools-output directory, no?
created_at: 2025-04-10T09:51:56Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
+        output_data = {
+            "query_point": self.args["query_point"],
+            "scaled_events": scaled_events.tolist(),
+        }
+        with open(self.output_filepath, "w", encoding="utf-8") as f:
+            json.dump(output_data, f, indent=4)
+        self.logger.info(f"Output saved to {self.output_filepath}")
+        self.logger.info(
+            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
+        )
+
+    def run(self):
+        """Run the scaling and interpolation workflow."""
+        self.logger.info(f"args dict: {self.args}")
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: fixed
created_at: 2025-04-10T09:52:04Z
author_association: COLLABORATOR
diff_hunk: @@ -84,13 +72,12 @@ def _parse(label, description):
         "--zeniths",
         required=True,
         nargs="+",
-        type=CommandLineParser.zenith_angle,
+        type=float,
         help="List of zenith angles.",
     )
     config.parser.add_argument(
         "--offsets", required=True, nargs="+", type=float, help="List of offsets in degrees."
path: src/simtools/applications/production_scale_events.py
user: login: tobiaskleiner
body: fixed
created_at: 2025-04-10T09:52:10Z
author_association: COLLABORATOR
diff_hunk: @@ -119,66 +106,22 @@ def _parse(label, description):
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
path: src/simtools/applications/production_scale_events.py
user: login: tobiaskleiner
body: fixed
created_at: 2025-04-10T09:52:14Z
author_association: COLLABORATOR
diff_hunk: @@ -119,66 +106,22 @@ def _parse(label, description):
         default=("prod6_LaPalma-{zenith}deg_gamma_cone.N.Am-4LSTs09MSTs_ID0_reduced.fits"),
         help=(
             "Template for the file name. (default: "
path: src/simtools/applications/production_scale_events.py
user: login: tobiaskleiner
body: added
created_at: 2025-04-10T09:52:19Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: I removed the file_type now, as this was not used anywhere in the code and I am not sure this is needed if we just want to derive the events for a certain production.
created_at: 2025-04-10T09:53:59Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
+            )
+            evaluator.calculate_metrics()
+            self.evaluator_instances.append(evaluator)
+
+    def perform_interpolation(self):
+        """Perform interpolation for the query point."""
+        if not self.evaluator_instances:
+            self.logger.error("No evaluators initialized. Cannot perform interpolation.")
+            return None
+
+        interpolation_handler = InterpolationHandler(self.evaluator_instances, metrics=self.metrics)
+        query_point = self.args.get("query_point")
+        if not query_point or len(query_point) != 5:
+            raise ValueError(
+                "Invalid query point format. "
+                f"Expected 5 values, got {len(query_point) if query_point else 'None'}."
+            )
+        query_points = np.array([self.args["query_point"]])
+        return interpolation_handler.interpolate(query_points)
+
+    def write_output(self, scaled_events):
+        """Write the interpolation results to a file."""
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: fixed
created_at: 2025-04-10T09:54:09Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: I guess to get the files this should be no problem, and we get also an error if the filenames don't match. Only if we think we would use float values in the filenames this should be changed.
created_at: 2025-04-10T10:00:08Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: Not needed. Please use the `output=True` for the configuration of the command line parameters in the application (e.g. return config.initialize(db_config=True, output=True))`. This will also the user to set a output path.
created_at: 2025-04-10T10:15:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: The question is: is it clear to the user that this file naming is expected?
created_at: 2025-04-10T10:15:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,117 @@
+r"""
+Module to run the StatisticalErrorEvaluator and interpolate results.
+
+This module provides the `ScaleEventsManager` class, which manages the workflow for scaling
+events and performing interpolation. It is designed to handle the evaluation of statistical
+uncertainties from DL2 Monte Carlo (MC) event files and perform interpolation for a specified
+query point.
+
+The module includes functionality to:
+- Initialize evaluators for statistical error calculations based on input parameters.
+- Perform interpolation using the initialized evaluators to estimate scaled events at a query point.
+- Write the results of the interpolation to an output file.
+
+"""
+
+import itertools
+import json
+import logging
+from pathlib import Path
+
+import astropy.units as u
+import numpy as np
+
+from simtools.io_operations import io_handler
+from simtools.production_configuration.calculate_statistical_errors_grid_point import (
+    StatisticalErrorEvaluator,
+)
+from simtools.production_configuration.interpolation_handler import InterpolationHandler
+from simtools.utils.general import collect_data_from_file
+
+
+class ScaleEventsManager:
+    """
+    Manages the workflow for scaling events and performing interpolation.
+
+    This class handles the evaluation of statistical uncertainties from DL2 MC event files
+    and performs interpolation for a specified query point.
+    """
+
+    def __init__(self, args_dict):
+        """
+        Initialize the manager with the provided arguments.
+
+        Parameters
+        ----------
+        args_dict : dict
+            Dictionary of command-line arguments.
+        """
+        self.args = args_dict
+        self.logger = logging.getLogger(__name__)
+        self.logger.setLevel(logging.INFO)
+        self.output_path = io_handler.IOHandler().get_output_directory(Path(__file__).stem)
+        self.output_filepath = Path(self.output_path).joinpath(f"{self.args['output_file']}")
+        self.metrics = collect_data_from_file(self.args["metrics_file"])
+        self.evaluator_instances = []
+
+    def initialize_evaluators(self):
+        """Initialize StatisticalErrorEvaluator instances for the given zeniths and offsets."""
+        if not (self.args["base_path"] and self.args["zeniths"] and self.args["offsets"]):
+            self.logger.warning("No files read")
+            self.logger.warning(f"Base Path: {self.args['base_path']}")
+            self.logger.warning(f"Zeniths: {self.args['zeniths']}")
+            self.logger.warning(f"Offsets: {self.args['offsets']}")
+            return
+
+        for zenith, offset in itertools.product(self.args["zeniths"], self.args["offsets"]):
+            file_name = self.args["file_name_template"].format(zenith=int(zenith))
+            file_path = Path(self.args["base_path"]).joinpath(file_name)
+
+            if not file_path.exists():
+                self.logger.warning(f"File not found: {file_path}. Skipping.")
+                continue
+
+            evaluator = StatisticalErrorEvaluator(
+                file_path,
+                file_type="Gamma-cone",
+                metrics=self.metrics,
+                grid_point=(1 * u.TeV, 180 * u.deg, zenith, 0, offset * u.deg),
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: It is a bit confusing when reading the code. Why don't we set a default value in the `__init__` function of StatisticalErrorEvaluator (e.g. `None` it is anyway set later?) and don't mention it here at all?
created_at: 2025-04-10T10:16:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,12 +104,16 @@ def write_output(self, scaled_events):
             json.dump(output_data, f, indent=4)
         self.logger.info(f"Output saved to {self.output_filepath}")
         self.logger.info(
-            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
+            f"Scaled events for query point {self.args['query_point']}: {scaled_events}"
         )
 
     def run(self):
         """Run the scaling and interpolation workflow."""
-        self.logger.info(f"args dict: {self.args}")
+        self.logger.info(f"Zeniths: {self.args['zeniths']}")
+        self.logger.info(f"camera_offsets: {self.args['camera_offsets']}")
path: src/simtools/production_configuration/scale_events_manager.py
user: login: GernotMaier
body: 'C' (to be consistent with the other lines)
created_at: 2025-04-10T10:17:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -106,12 +104,16 @@ def write_output(self, scaled_events):
             json.dump(output_data, f, indent=4)
         self.logger.info(f"Output saved to {self.output_filepath}")
         self.logger.info(
-            f"Scaled events for grid point {self.args['query_point']}: {scaled_events}"
+            f"Scaled events for query point {self.args['query_point']}: {scaled_events}"
         )
 
     def run(self):
         """Run the scaling and interpolation workflow."""
-        self.logger.info(f"args dict: {self.args}")
+        self.logger.info(f"Zeniths: {self.args['zeniths']}")
+        self.logger.info(f"camera_offsets: {self.args['camera_offsets']}")
path: src/simtools/production_configuration/scale_events_manager.py
user: login: tobiaskleiner
body: Thanks for the review. The remaining points have been adressed. Let me know if everything is good now.
created_at: 2025-04-10T13:48:26Z
author_association: COLLABORATOR
diff_hunk: @@ -84,27 +73,23 @@ def _parse(label, description):
         "--zeniths",
         required=True,
         nargs="+",
-        type=CommandLineParser.zenith_angle,
+        type=float,
path: src/simtools/applications/production_scale_events.py
user: login: orelgueta
body: Why do you use type float here instead of the zenith angle type? It should be fine to apply for each angle given by the user.
created_at: 2025-04-11T12:47:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -84,27 +73,23 @@ def _parse(label, description):
         "--zeniths",
path: src/simtools/applications/production_scale_events.py
user: login: orelgueta
body: It would be better to rename this `zenith_angles`
created_at: 2025-04-11T12:47:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-08T14:38:50Z
body: ![Failed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/failed.svg?sanitize=true)

- 0.00% Coverage on New Code (is less than 80.00%)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/0.svg?sanitize=true) 0.00% Coverage (95.70% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-08T14:38:55Z
body: ![Failed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/failed.svg?sanitize=true)

- 0.00% Coverage on New Code (is less than 80.00%)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/0.svg?sanitize=true) 0.00% Coverage (95.70% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-08T14:48:52Z
body: ![Failed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/failed.svg?sanitize=true)

- 0.00% Coverage on New Code (is less than 80.00%)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/0.svg?sanitize=true) 0.00% Coverage (95.70% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T09:50:58Z
body: ![Failed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/failed.svg?sanitize=true)

- 72.90% Coverage on New Code (is less than 80.00%)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/60.svg?sanitize=true) 72.90% Coverage (96.10% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T09:59:29Z
body: ![Failed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/failed.svg?sanitize=true)

- 79.70% Coverage on New Code (is less than 80.00%)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/60.svg?sanitize=true) 79.70% Coverage (96.20% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T10:37:22Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/90.svg?sanitize=true) 92.90% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T12:29:58Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T12:34:29Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-09T14:21:25Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T07:23:00Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T09:14:03Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: tobiaskleiner
created_at: 2025-04-10T10:00:34Z
body: @GernotMaier thanks for the review. I have adressed most of your comments. Some I let to the next PR, since they are/will be fixed there with the renaming of the modules and change of docstrings. Let me know if this is fine.
author_association: COLLABORATOR
user: login: GernotMaier
created_at: 2025-04-10T10:11:24Z
body: Unit tests failing?
author_association: CONTRIBUTOR
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T11:28:39Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T11:36:53Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T11:38:14Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: tobiaskleiner
created_at: 2025-04-10T12:02:30Z
body: @GernotMaier now they ran through.
author_association: COLLABORATOR
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T13:51:45Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE
user: login: ctao-dpps-sonarqube[bot]
created_at: 2025-04-10T14:07:11Z
body: ![Passed](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/QualityGateBadge/passed.svg?sanitize=true)

# Analysis Details
## 0 Issues
- ![Bug](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/bug.svg?sanitize=true) 0 Bugs
- ![Vulnerability](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/vulnerability.svg?sanitize=true) 0 Vulnerabilities
- ![Code Smell](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/common/code_smell.svg?sanitize=true) 0 Code Smells

## Coverage and Duplications
- ![Coverage](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/CoverageChart/100.svg?sanitize=true) 100.00% Coverage (96.30% Estimated after merge)
- ![Duplications](https://sonar-cta-dpps.zeuthen.desy.de/static/communityBranchPlugin/checks/Duplications/3.svg?sanitize=true) 0.00% Duplicated Code (0.00% Estimated after merge)

**Project ID:** gammasim_simtools_AY_ssha9WiFxsX-2oy_w

[View in SonarQube](https://sonar-cta-dpps.zeuthen.desy.de/dashboard?id=gammasim_simtools_AY_ssha9WiFxsX-2oy_w&pullRequest=1498)


author_association: NONE