Skip to content

Commit 71788f8

Browse files
committed
fix: restore patched data sources after each evaluate call
_apply_claims_to_data_sources mutated DataFrames in svc.source_ dataframes in place. That let cross-law lookups see the claim, but never rolled back, so a subsequent evaluation for a different BSN or a different approved mode observed stale claim values (e.g. eval 1 with approved=False patches geboortedatum; eval 2 with approved=True still sees the patched value and misattributes the result). Keep a per-evaluation _patched_source_backups dict of the original DataFrames, and wrap the evaluate body in a _restore_on_exit context manager that rolls them back and re-syncs the engine on exit.
1 parent 29c1e4d commit 71788f8

1 file changed

Lines changed: 39 additions & 1 deletion

File tree

machine/regelrecht_services.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,24 @@ def _resolve_param_ref(ref: str, params: dict[str, Any]) -> Any | None:
9494
LAWS_DIR = Path("laws")
9595

9696

97+
from contextlib import contextmanager
98+
99+
100+
@contextmanager
101+
def _restore_on_exit(services: "RegelrechtServices"):
102+
"""Yield then roll back any data-source mutations made during the body.
103+
104+
Used around ``evaluate`` to guarantee that claim-driven patches to
105+
registered DataFrames don't leak across evaluations.
106+
"""
107+
try:
108+
yield
109+
finally:
110+
if services._patched_source_backups:
111+
services._restore_patched_data_sources()
112+
services._sync_engine_data_sources()
113+
114+
97115
class RegelrechtServices:
98116
"""Drop-in replacement for Services using the regelrecht Rust engine."""
99117

@@ -102,6 +120,7 @@ def __init__(self, reference_date: str) -> None:
102120
self.resolver = self._services.resolver
103121
self.root_reference_date = reference_date
104122
self._engine = RegelrechtEngine()
123+
self._patched_source_backups: dict[tuple[str, str], pd.DataFrame] = {}
105124
self._load_all_laws()
106125

107126
def _load_all_laws(self) -> None:
@@ -186,6 +205,19 @@ def set_source_dataframe(self, service: str, table: str, df: pd.DataFrame) -> No
186205
self._services.set_source_dataframe(service, table, df)
187206
self._register_dataframe(table, df)
188207

208+
def _restore_patched_data_sources(self) -> None:
209+
"""Undo dataframe mutations made by the previous claim patching.
210+
211+
Called from ``evaluate`` at teardown so claim-driven row edits
212+
never leak into the next evaluation (different BSN, different
213+
approval mode, next request on a long-running web server).
214+
"""
215+
for (service, table), df in list(self._patched_source_backups.items()):
216+
svc = self._services.services.get(service)
217+
if svc is not None and table in svc.source_dataframes:
218+
svc.source_dataframes[table] = df
219+
self._patched_source_backups.clear()
220+
189221
def _apply_claims_to_data_sources(self, bsn: str, reference_date: str, approved: bool) -> bool:
190222
"""Mutate registered DataFrames so cross-law lookups see claim values.
191223
@@ -236,6 +268,12 @@ def _apply_claims_to_data_sources(self, bsn: str, reference_date: str, approved:
236268
mask = df["bsn"].astype(str) == str(bsn)
237269
if not mask.any():
238270
continue
271+
# Back up the original row once so evaluate()'s teardown
272+
# can roll back. Without this the mutation leaks into
273+
# subsequent evaluations.
274+
backup_key = (claim.service, table)
275+
if backup_key not in self._patched_source_backups:
276+
self._patched_source_backups[backup_key] = df
239277
new_df = df.copy()
240278
new_df.loc[mask, field] = _to_native(claim.new_value)
241279
svc.source_dataframes[table] = new_df
@@ -269,7 +307,7 @@ def evaluate(
269307
if not rule:
270308
raise ValueError(f"No rule found for {law}")
271309

272-
with logger.indent_block(f"{service}: {law} ({reference_date})", double_line=True):
310+
with _restore_on_exit(self), logger.indent_block(f"{service}: {law} ({reference_date})", double_line=True):
273311
# Sync engine data sources
274312
self._sync_engine_data_sources()
275313

0 commit comments

Comments
 (0)