diff --git a/src/pyrecest/filters/measurement_scoring.py b/src/pyrecest/filters/measurement_scoring.py index 6d0ea0321..a4e1cf676 100644 --- a/src/pyrecest/filters/measurement_scoring.py +++ b/src/pyrecest/filters/measurement_scoring.py @@ -2,6 +2,7 @@ from __future__ import annotations +from collections.abc import Sequence from dataclasses import dataclass from typing import Any @@ -25,11 +26,19 @@ class MeasurementScore: predicted_measurements: Any | None innovation_covariance: Any | None residual: Any | None - active_measurement_indices: list[int] + active_measurement_indices: Sequence[int] measurement_weights: Any | None = None quadratic_form: float | None = None skipped_reason: str | None = None + def __post_init__(self) -> None: + # Own caller-provided sequences so this frozen result cannot be mutated indirectly. + object.__setattr__( + self, + "active_measurement_indices", + tuple(self.active_measurement_indices), + ) + @property def is_active(self) -> bool: """Return whether the score contains at least one active measurement.""" diff --git a/tests/filters/test_measurement_scoring.py b/tests/filters/test_measurement_scoring.py index bfd63d276..7c750c45c 100644 --- a/tests/filters/test_measurement_scoring.py +++ b/tests/filters/test_measurement_scoring.py @@ -15,3 +15,13 @@ def _score(active_measurement_indices): def test_measurement_score_is_active_handles_numpy_index_arrays(): assert _score(np.array([0, 2])).is_active assert not _score(np.array([], dtype=int)).is_active + + +def test_measurement_score_owns_active_measurement_indices(): + active_measurement_indices = [] + score = _score(active_measurement_indices) + + active_measurement_indices.append(0) + + assert score.active_measurement_indices == () + assert not score.is_active