From 0a3b702982876c54d9b30e2d587b349f2032d79d Mon Sep 17 00:00:00 2001 From: "angre.garcia-gomez@ait.ac.at" Date: Wed, 22 Jul 2026 17:44:22 +0200 Subject: [PATCH 1/4] bug correction --- .../common/_core_op/_fit_logic.py | 4 +++ tests/test_common/test_core.py | 2 +- tests/test_common/test_fit_logic.py | 30 +++++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/detectmatelibrary/common/_core_op/_fit_logic.py b/src/detectmatelibrary/common/_core_op/_fit_logic.py index 62dd264b..18d79148 100644 --- a/src/detectmatelibrary/common/_core_op/_fit_logic.py +++ b/src/detectmatelibrary/common/_core_op/_fit_logic.py @@ -111,6 +111,10 @@ def update_state(self, state: StatesL) -> None: self.train_state, self.configure_state = update_state( state=state, train_state=self.train_state, config_state=self.configure_state ) + if self.configure_state == ConfigState.STOP_CONFIGURE: + self.config_finished, self._configuration_done = False, True + if self.train_state == TrainState.STOP_TRAINING: + self.training_finished, self._training_done = False, True def finish_config(self) -> bool: if self._configuration_done and not self.config_finished: diff --git a/tests/test_common/test_core.py b/tests/test_common/test_core.py index 92bc88dc..4e75884c 100644 --- a/tests/test_common/test_core.py +++ b/tests/test_common/test_core.py @@ -340,7 +340,7 @@ def test_configuration_force_stop(self) -> None: component.process(_make_log(i)) assert len(component.configure_data) == 0 - assert component.set_configuration_called == 0 + assert component.set_configuration_called == 1 def test_configuration_keep_configure(self) -> None: component = MockComponentWithConfigure(name="DummyCfg4") diff --git a/tests/test_common/test_fit_logic.py b/tests/test_common/test_fit_logic.py index 11e96c49..4babb5c9 100644 --- a/tests/test_common/test_fit_logic.py +++ b/tests/test_common/test_fit_logic.py @@ -60,3 +60,33 @@ def test_finish_training_after_configure_and_training(self) -> None: logic.run() finish_calls.append(logic.finish_training()) assert finish_calls.count(True) == 1 + + def test_force_stop_config(self) -> None: + logic = FitLogic(data_use_configure=20, data_use_training=None) + + # Stop in the middle + assert not logic.finish_config() + logic.update_state("stop_configuring") + assert logic.finish_config() + assert not logic.finish_config() + + # Stop after a force start + logic.update_state("keep_configuring") + assert not logic.finish_config() + logic.update_state("stop_configuring") + assert logic.finish_config() + + def test_force_stop_train(self) -> None: + logic = FitLogic(data_use_configure=None, data_use_training=20) + + # Stop in the middle + assert not logic.finish_training() + logic.update_state("stop_training") + assert logic.finish_training() + assert not logic.finish_training() + + # Stop after a force start + logic.update_state("keep_training") + assert not logic.finish_training() + logic.update_state("stop_training") + assert logic.finish_training() \ No newline at end of file From a1321cbaf5ee2f47df54c716b0952961b38761a4 Mon Sep 17 00:00:00 2001 From: "angre.garcia-gomez@ait.ac.at" Date: Thu, 23 Jul 2026 10:00:29 +0200 Subject: [PATCH 2/4] refactor config state --- AGENTS.md | 2 +- .../common/_core_op/_fit_logic.py | 141 ++++++++++-------- tests/test_common/test_core.py | 4 +- .../test_bigram_frequency_detector.py | 6 +- tests/test_detectors/test_charset_detector.py | 6 +- .../test_detectors/test_new_event_detector.py | 6 +- .../test_detectors/test_new_value_detector.py | 6 +- .../test_value_range_detector.py | 6 +- .../test_configuration_engine.py | 4 +- 9 files changed, 97 insertions(+), 84 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index dd994543..4b089d4b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,7 +59,7 @@ Each `process()` call passes through a state machine controlled by config fields Phases in order: **CONFIGURE → TRAIN → DETECT** (phases are skipped when the corresponding field is `None`). -State control enums allow overriding automatic transitions: `TrainState.KEEP_TRAINING` / `STOP_TRAINING` and `ConfigState.KEEP_CONFIGURE` / `STOP_CONFIGURE` on the component's `fit_logic`. +State control enums allow overriding automatic transitions: `TrainState.KEEP_TRAINING` / `STOP_TRAINING` and `EnumState.KEEP` / `STOP_CONFIGURE` on the component's `fit_logic`. #### CoreDetectorConfig: EventsConfig diff --git a/src/detectmatelibrary/common/_core_op/_fit_logic.py b/src/detectmatelibrary/common/_core_op/_fit_logic.py index 18d79148..0963e54d 100644 --- a/src/detectmatelibrary/common/_core_op/_fit_logic.py +++ b/src/detectmatelibrary/common/_core_op/_fit_logic.py @@ -19,39 +19,6 @@ def describe(self) -> str: return descriptions[self.value] -class ConfigState(Enum): - DEFAULT = 0 - STOP_CONFIGURE = 1 - KEEP_CONFIGURE = 2 - - def describe(self) -> str: - descriptions = [ - "Follow default configuration behavior.", - "Force stop configuration.", - "Keep configuring regardless of default behavior." - ] - return descriptions[self.value] - - -StatesL = Literal["keep_training", "stop_training", "keep_configuring", "stop_configuring"] - -def update_state( - state: StatesL, train_state: TrainState, config_state: ConfigState -) -> tuple[TrainState, ConfigState]: - if state == "keep_training": - train_state = TrainState.KEEP_TRAINING - elif state == "stop_training": - train_state = TrainState.STOP_TRAINING - elif state == "keep_configuring": - config_state = ConfigState.KEEP_CONFIGURE - elif state == "stop_configuring": - config_state = ConfigState.STOP_CONFIGURE - else: - warnings.warn(f"State {state} unknown, use: {get_args(StatesL)}") - - return train_state, config_state - - def do_training( data_use_training: int | None, index: int, train_state: TrainState ) -> bool: @@ -63,17 +30,6 @@ def do_training( return data_use_training is not None and data_use_training > index -def do_configure( - data_use_configure: int | None, index: int, configure_state: ConfigState -) -> bool: - if configure_state == ConfigState.STOP_CONFIGURE: - return False - elif configure_state == ConfigState.KEEP_CONFIGURE: - return True - - return data_use_configure is not None and data_use_configure > index - - class FitLogicState(Enum): DO_CONFIG = 0 DO_TRAIN = 1 @@ -88,39 +44,101 @@ def describe(self) -> str: return descriptions[self.value] +class EnumState(Enum): + DEFAULT = 0 + STOP = 1 + KEEP = 2 + + def describe(self) -> str: + descriptions = [ + "Follow default behavior.", + "Force stop", + "Keep doing it regardless of default behavior." + ] + return descriptions[self.value] + + +class State: + def __init__(self, total_need_data: int | None) -> None: + self.total_need_data = total_need_data + + self.ready_to_finish = False + self.finished = False + self.data_used = 0 + self.current = EnumState.DEFAULT + + def keep_doing(self) -> bool: + if self.current == EnumState.STOP: + return False + if self.current == EnumState.KEEP: + return True + + return self.total_need_data is not None and self.total_need_data > self.data_used + + def force_finish(self) -> None: + self.finished, self.ready_to_finish = False, True + + def check_if_ready_finish(self) -> None: + if self.data_used > 0 and not self.ready_to_finish: + self.ready_to_finish = True + + def is_finish(self) -> bool: + if self.ready_to_finish and not self.finished: + self.finished = True + return True + return False + + +StatesL = Literal["keep_training", "stop_training", "keep_configuring", "stop_configuring"] + + +def update_state( + state: StatesL, train_state: TrainState, config_state: EnumState +) -> tuple[TrainState, EnumState]: + if state == "keep_training": + train_state = TrainState.KEEP_TRAINING + elif state == "stop_training": + train_state = TrainState.STOP_TRAINING + elif state == "keep_configuring": + config_state = EnumState.KEEP + elif state == "stop_configuring": + config_state = EnumState.STOP + else: + warnings.warn(f"State {state} unknown, use: {get_args(StatesL)}") + + return train_state, config_state + + class FitLogic: def __init__( self, data_use_configure: int | None, data_use_training: int | None ) -> None: self.train_state = TrainState.DEFAULT - self.configure_state = ConfigState.DEFAULT self.last_state = FitLogicState.NOTHING - self.data_used_train, self.data_used_configure = 0, 0 - self._configuration_done, self.config_finished = False, False + self.config_state = State(data_use_configure) + + self.data_used_train = 0 self._training_done, self.training_finished = False, False - self.data_use_configure = data_use_configure self.data_use_training = data_use_training def get_last_state(self) -> str: return self.last_state.describe() def update_state(self, state: StatesL) -> None: - self.train_state, self.configure_state = update_state( - state=state, train_state=self.train_state, config_state=self.configure_state + self.train_state, self.config_state.current = update_state( + state=state, train_state=self.train_state, config_state=self.config_state.current ) - if self.configure_state == ConfigState.STOP_CONFIGURE: - self.config_finished, self._configuration_done = False, True + if self.config_state.current == EnumState.STOP: + self.config_state.force_finish() + if self.train_state == TrainState.STOP_TRAINING: self.training_finished, self._training_done = False, True def finish_config(self) -> bool: - if self._configuration_done and not self.config_finished: - self.config_finished = True - return True - return False + return self.config_state.is_finish() def finish_training(self) -> bool: if self._training_done and not self.training_finished: @@ -129,16 +147,11 @@ def finish_training(self) -> bool: return False def __check_state(self) -> FitLogicState: - if do_configure( - data_use_configure=self.data_use_configure, - index=self.data_used_configure, - configure_state=self.configure_state - ): - self.data_used_configure += 1 + if self.config_state.keep_doing(): + self.config_state.data_used += 1 return FitLogicState.DO_CONFIG else: - if self.data_used_configure > 0 and not self._configuration_done: - self._configuration_done = True + self.config_state.check_if_ready_finish() if do_training( data_use_training=self.data_use_training, diff --git a/tests/test_common/test_core.py b/tests/test_common/test_core.py index 4e75884c..efc3e7de 100644 --- a/tests/test_common/test_core.py +++ b/tests/test_common/test_core.py @@ -266,7 +266,7 @@ def test_training_use_config_data(self) -> None: "hostname": "test_hostname" }) ) - total = component.fitlogic.data_use_training + component.fitlogic.data_use_configure + total = component.fitlogic.data_use_training + component.fitlogic.config_state.total_need_data assert len(component.train_data) == total for i, log in enumerate(component.train_data): expected = schemas.LogSchema({ @@ -315,7 +315,7 @@ def test_configuration(self) -> None: results = [component.process(_make_log(i)) for i in range(10)] - assert component.fitlogic.data_used_configure == 3 + assert component.fitlogic.config_state.data_used == 3 assert len(component.configure_data) == 3 assert all(r is None for r in results[:3]) assert component.set_configuration_called == 1 diff --git a/tests/test_detectors/test_bigram_frequency_detector.py b/tests/test_detectors/test_bigram_frequency_detector.py index 59bf7e1b..db8e47bf 100644 --- a/tests/test_detectors/test_bigram_frequency_detector.py +++ b/tests/test_detectors/test_bigram_frequency_detector.py @@ -12,7 +12,7 @@ from detectmatelibrary.detectors.bigram_frequency_detector import ( BigramFrequencyDetector, BigramFrequencyDetectorConfig, BufferMode ) -from detectmatelibrary.common._core_op._fit_logic import ConfigState +from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From @@ -273,12 +273,12 @@ def test_audit_log_anomalies_via_process(self): logs = list(From.log(parser, in_path=AUDIT_LOG, do_process=True)) # Phase 1: configure — keep configuring for logs[:TRAIN_UNTIL] - detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Transition: stop configure so next process() call triggers set_configuration() - detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] detector.fitlogic.train_state = TrainState.KEEP_TRAINING diff --git a/tests/test_detectors/test_charset_detector.py b/tests/test_detectors/test_charset_detector.py index 3d474b40..726e5ec5 100644 --- a/tests/test_detectors/test_charset_detector.py +++ b/tests/test_detectors/test_charset_detector.py @@ -10,7 +10,7 @@ from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.charset_detector import CharsetDetector, CharsetDetectorConfig, BufferMode -from detectmatelibrary.common._core_op._fit_logic import ConfigState +from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From @@ -322,12 +322,12 @@ def test_audit_log_anomalies_via_process(self): logs = list(From.log(parser, in_path=AUDIT_LOG, do_process=True)) # Phase 1: configure — keep configuring for logs[:TRAIN_UNTIL] - detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Transition: stop configure so next process() call triggers set_configuration() - detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] detector.fitlogic.train_state = TrainState.KEEP_TRAINING diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index 977ab614..d36eeed7 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -14,7 +14,7 @@ from detectmatelibrary.helper.from_to import From import detectmatelibrary.schemas as schemas from detectmatelibrary.utils.aux import time_test_mode -from detectmatelibrary.common._core_op._fit_logic import ConfigState, TrainState +from detectmatelibrary.common._core_op._fit_logic import EnumState, TrainState from detectmatelibrary.constants import GLOBAL_EVENT_ID from tests.test_data import AUDIT_LOG, AUDIT_TEMPLATES, TRAIN_UNTIL @@ -187,12 +187,12 @@ def test_audit_log_anomalies_via_process(self): logs = list(From.log(pars, in_path=AUDIT_LOG, do_process=True)) # Phase 1: configure — keep configuring for logs[:TRAIN_UNTIL] - detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Transition: stop configure so next process() call triggers set_configuration() - detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] detector.fitlogic.train_state = TrainState.KEEP_TRAINING diff --git a/tests/test_detectors/test_new_value_detector.py b/tests/test_detectors/test_new_value_detector.py index 1c285f2d..9bd73ac1 100644 --- a/tests/test_detectors/test_new_value_detector.py +++ b/tests/test_detectors/test_new_value_detector.py @@ -11,7 +11,7 @@ from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.new_value_detector import NewValueDetector, NewValueDetectorConfig, \ BufferMode -from detectmatelibrary.common._core_op._fit_logic import ConfigState +from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From @@ -249,12 +249,12 @@ def test_audit_log_anomalies_via_process(self): logs = list(From.log(parser, in_path=AUDIT_LOG, do_process=True)) # Phase 1: configure — keep configuring for logs[:TRAIN_UNTIL] - detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Transition: stop configure so next process() call triggers set_configuration() - detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] detector.fitlogic.train_state = TrainState.KEEP_TRAINING diff --git a/tests/test_detectors/test_value_range_detector.py b/tests/test_detectors/test_value_range_detector.py index ea03b58e..ae65ce71 100644 --- a/tests/test_detectors/test_value_range_detector.py +++ b/tests/test_detectors/test_value_range_detector.py @@ -13,7 +13,7 @@ from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.value_range_detector import (ValueRangeDetector, ValueRangeDetectorConfig, BufferMode) -from detectmatelibrary.common._core_op._fit_logic import ConfigState +from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID from detectmatelibrary.parsers.template_matcher import MatcherParser from detectmatelibrary.helper.from_to import From @@ -361,12 +361,12 @@ def test_audit_log_anomalies_via_process(self): logs = list(From.log(parser, in_path=AUDIT_LOG, do_process=True)) # Phase 1: configure — keep configuring for logs[:TRAIN_UNTIL] - detector.fitlogic.configure_state = ConfigState.KEEP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Transition: stop configure so next process() call triggers set_configuration() - detector.fitlogic.configure_state = ConfigState.STOP_CONFIGURE + detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] detector.fitlogic.train_state = TrainState.KEEP_TRAINING diff --git a/tests/test_pipelines/test_configuration_engine.py b/tests/test_pipelines/test_configuration_engine.py index 0624fa1f..28058570 100644 --- a/tests/test_pipelines/test_configuration_engine.py +++ b/tests/test_pipelines/test_configuration_engine.py @@ -64,8 +64,8 @@ def test_process_configure_train_detect(self) -> None: for log in logs: detector.process(log) - assert detector.fitlogic.data_used_configure == TRAIN_UNTIL - assert detector.fitlogic._configuration_done is True + assert detector.fitlogic.config_state.data_used == TRAIN_UNTIL + assert detector.fitlogic.config_state.ready_to_finish is True # Train on same logs used for configuration (mirrors detect.py) for log in logs[:TRAIN_UNTIL]: From ecb3de44386129c7a67a7da6f55ac94f065f356d Mon Sep 17 00:00:00 2001 From: "angre.garcia-gomez@ait.ac.at" Date: Thu, 23 Jul 2026 10:42:02 +0200 Subject: [PATCH 3/4] refactor train state --- .../common/_core_op/_fit_logic.py | 65 ++++--------------- tests/test_common/test_core.py | 5 +- tests/test_common/test_fit_logic.py | 4 +- .../test_bigram_frequency_detector.py | 5 +- tests/test_detectors/test_charset_detector.py | 5 +- .../test_detectors/test_new_event_detector.py | 6 +- .../test_detectors/test_new_value_detector.py | 5 +- .../test_value_range_detector.py | 5 +- 8 files changed, 29 insertions(+), 71 deletions(-) diff --git a/src/detectmatelibrary/common/_core_op/_fit_logic.py b/src/detectmatelibrary/common/_core_op/_fit_logic.py index 0963e54d..42ad7214 100644 --- a/src/detectmatelibrary/common/_core_op/_fit_logic.py +++ b/src/detectmatelibrary/common/_core_op/_fit_logic.py @@ -4,32 +4,6 @@ import warnings -class TrainState(Enum): - DEFAULT = 0 - STOP_TRAINING = 1 - KEEP_TRAINING = 2 - - def describe(self) -> str: - descriptions = [ - "Follow default training behavior.", - "Force stop training.", - "Keep training regardless of default behavior." - ] - - return descriptions[self.value] - - -def do_training( - data_use_training: int | None, index: int, train_state: TrainState -) -> bool: - if train_state == TrainState.STOP_TRAINING: - return False - elif train_state == TrainState.KEEP_TRAINING: - return True - - return data_use_training is not None and data_use_training > index - - class FitLogicState(Enum): DO_CONFIG = 0 DO_TRAIN = 1 @@ -93,12 +67,12 @@ def is_finish(self) -> bool: def update_state( - state: StatesL, train_state: TrainState, config_state: EnumState -) -> tuple[TrainState, EnumState]: + state: StatesL, train_state: EnumState, config_state: EnumState +) -> tuple[EnumState, EnumState]: if state == "keep_training": - train_state = TrainState.KEEP_TRAINING + train_state = EnumState.KEEP elif state == "stop_training": - train_state = TrainState.STOP_TRAINING + train_state = EnumState.STOP elif state == "keep_configuring": config_state = EnumState.KEEP elif state == "stop_configuring": @@ -114,37 +88,29 @@ def __init__( self, data_use_configure: int | None, data_use_training: int | None ) -> None: - self.train_state = TrainState.DEFAULT self.last_state = FitLogicState.NOTHING self.config_state = State(data_use_configure) - - self.data_used_train = 0 - self._training_done, self.training_finished = False, False - - self.data_use_training = data_use_training + self.train_state = State(data_use_training) def get_last_state(self) -> str: return self.last_state.describe() def update_state(self, state: StatesL) -> None: - self.train_state, self.config_state.current = update_state( - state=state, train_state=self.train_state, config_state=self.config_state.current + self.train_state.current, self.config_state.current = update_state( + state=state, train_state=self.train_state.current, config_state=self.config_state.current ) if self.config_state.current == EnumState.STOP: self.config_state.force_finish() - if self.train_state == TrainState.STOP_TRAINING: - self.training_finished, self._training_done = False, True + if self.train_state.current == EnumState.STOP: + self.train_state.force_finish() def finish_config(self) -> bool: return self.config_state.is_finish() def finish_training(self) -> bool: - if self._training_done and not self.training_finished: - self.training_finished = True - return True - return False + return self.train_state.is_finish() def __check_state(self) -> FitLogicState: if self.config_state.keep_doing(): @@ -153,15 +119,10 @@ def __check_state(self) -> FitLogicState: else: self.config_state.check_if_ready_finish() - if do_training( - data_use_training=self.data_use_training, - index=self.data_used_train, - train_state=self.train_state - ): - self.data_used_train += 1 + if self.train_state.keep_doing(): + self.train_state.data_used += 1 return FitLogicState.DO_TRAIN - elif self.data_used_train > 0 and not self._training_done: - self._training_done = True + self.train_state.check_if_ready_finish() return FitLogicState.NOTHING diff --git a/tests/test_common/test_core.py b/tests/test_common/test_core.py index efc3e7de..e5822c34 100644 --- a/tests/test_common/test_core.py +++ b/tests/test_common/test_core.py @@ -241,7 +241,7 @@ def test_training(self) -> None: }) ) - assert len(component.train_data) == component.fitlogic.data_used_train + assert len(component.train_data) == component.fitlogic.train_state.data_used for i, log in enumerate(component.train_data): expected = schemas.LogSchema({ "__version__": "1.0.0", @@ -266,7 +266,8 @@ def test_training_use_config_data(self) -> None: "hostname": "test_hostname" }) ) - total = component.fitlogic.data_use_training + component.fitlogic.config_state.total_need_data + total = component.fitlogic.train_state.total_need_data + total += component.fitlogic.config_state.total_need_data assert len(component.train_data) == total for i, log in enumerate(component.train_data): expected = schemas.LogSchema({ diff --git a/tests/test_common/test_fit_logic.py b/tests/test_common/test_fit_logic.py index 4babb5c9..060841f6 100644 --- a/tests/test_common/test_fit_logic.py +++ b/tests/test_common/test_fit_logic.py @@ -1,7 +1,7 @@ """Tests for FitLogic training lifecycle hooks.""" from detectmatelibrary.common._core_op._fit_logic import ( - FitLogic, FitLogicState, TrainState + FitLogic, FitLogicState, EnumState ) @@ -46,7 +46,7 @@ def test_finish_training_not_called_during_training(self) -> None: def test_finish_training_not_called_with_keep_training(self) -> None: logic = FitLogic(data_use_configure=None, data_use_training=None) - logic.train_state = TrainState.KEEP_TRAINING + logic.train_state.current = EnumState.KEEP for _ in range(10): state = logic.run() assert state == FitLogicState.DO_TRAIN diff --git a/tests/test_detectors/test_bigram_frequency_detector.py b/tests/test_detectors/test_bigram_frequency_detector.py index db8e47bf..01f590b1 100644 --- a/tests/test_detectors/test_bigram_frequency_detector.py +++ b/tests/test_detectors/test_bigram_frequency_detector.py @@ -8,7 +8,6 @@ - Input/output schema validation """ -from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.bigram_frequency_detector import ( BigramFrequencyDetector, BigramFrequencyDetectorConfig, BufferMode ) @@ -281,12 +280,12 @@ def test_audit_log_anomalies_via_process(self): detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] - detector.fitlogic.train_state = TrainState.KEEP_TRAINING + detector.fitlogic.train_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Phase 3: detect — stop training so process() only calls detect() - detector.fitlogic.train_state = TrainState.STOP_TRAINING + detector.fitlogic.train_state.current = EnumState.STOP detected_ids: set[str] = set() for log in logs[TRAIN_UNTIL:]: if detector.process(log) is not None: diff --git a/tests/test_detectors/test_charset_detector.py b/tests/test_detectors/test_charset_detector.py index 726e5ec5..a53fa2dd 100644 --- a/tests/test_detectors/test_charset_detector.py +++ b/tests/test_detectors/test_charset_detector.py @@ -8,7 +8,6 @@ - Input/output schema validation """ -from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.charset_detector import CharsetDetector, CharsetDetectorConfig, BufferMode from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID @@ -330,12 +329,12 @@ def test_audit_log_anomalies_via_process(self): detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] - detector.fitlogic.train_state = TrainState.KEEP_TRAINING + detector.fitlogic.train_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Phase 3: detect — stop training so process() only calls detect() - detector.fitlogic.train_state = TrainState.STOP_TRAINING + detector.fitlogic.train_state.current = EnumState.STOP detected_ids: set[str] = set() for log in logs[TRAIN_UNTIL:]: if detector.process(log) is not None: diff --git a/tests/test_detectors/test_new_event_detector.py b/tests/test_detectors/test_new_event_detector.py index d36eeed7..62ff9f36 100644 --- a/tests/test_detectors/test_new_event_detector.py +++ b/tests/test_detectors/test_new_event_detector.py @@ -14,7 +14,7 @@ from detectmatelibrary.helper.from_to import From import detectmatelibrary.schemas as schemas from detectmatelibrary.utils.aux import time_test_mode -from detectmatelibrary.common._core_op._fit_logic import EnumState, TrainState +from detectmatelibrary.common._core_op._fit_logic import EnumState from detectmatelibrary.constants import GLOBAL_EVENT_ID from tests.test_data import AUDIT_LOG, AUDIT_TEMPLATES, TRAIN_UNTIL @@ -195,12 +195,12 @@ def test_audit_log_anomalies_via_process(self): detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] - detector.fitlogic.train_state = TrainState.KEEP_TRAINING + detector.fitlogic.train_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Phase 3: detect — stop training so process() only calls detect() - detector.fitlogic.train_state = TrainState.STOP_TRAINING + detector.fitlogic.train_state.current = EnumState.STOP detected_ids: set[str] = set() for log in logs[TRAIN_UNTIL:]: if detector.process(log) is not None: diff --git a/tests/test_detectors/test_new_value_detector.py b/tests/test_detectors/test_new_value_detector.py index 9bd73ac1..b66a4c11 100644 --- a/tests/test_detectors/test_new_value_detector.py +++ b/tests/test_detectors/test_new_value_detector.py @@ -8,7 +8,6 @@ - Input/output schema validation """ -from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.new_value_detector import NewValueDetector, NewValueDetectorConfig, \ BufferMode from detectmatelibrary.common._core_op._fit_logic import EnumState @@ -257,12 +256,12 @@ def test_audit_log_anomalies_via_process(self): detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] - detector.fitlogic.train_state = TrainState.KEEP_TRAINING + detector.fitlogic.train_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: detector.process(log) # Phase 3: detect — stop training so process() only calls detect() - detector.fitlogic.train_state = TrainState.STOP_TRAINING + detector.fitlogic.train_state.current = EnumState.STOP detected_ids: set[str] = set() for log in logs[TRAIN_UNTIL:]: if detector.process(log) is not None: diff --git a/tests/test_detectors/test_value_range_detector.py b/tests/test_detectors/test_value_range_detector.py index ae65ce71..ace3c604 100644 --- a/tests/test_detectors/test_value_range_detector.py +++ b/tests/test_detectors/test_value_range_detector.py @@ -10,7 +10,6 @@ import logging import random import pytest -from detectmatelibrary.common._core_op._fit_logic import TrainState from detectmatelibrary.detectors.value_range_detector import (ValueRangeDetector, ValueRangeDetectorConfig, BufferMode) from detectmatelibrary.common._core_op._fit_logic import EnumState @@ -369,14 +368,14 @@ def test_audit_log_anomalies_via_process(self): detector.fitlogic.config_state.current = EnumState.STOP # Phase 2: train — keep training for logs[:TRAIN_UNTIL] - detector.fitlogic.train_state = TrainState.KEEP_TRAINING + detector.fitlogic.train_state.current = EnumState.KEEP for log in logs[:TRAIN_UNTIL]: logger.setLevel(logging.CRITICAL) detector.process(log) logger.setLevel(logging.DEBUG) # Phase 3: detect — stop training so process() only calls detect() - detector.fitlogic.train_state = TrainState.STOP_TRAINING + detector.fitlogic.train_state.current = EnumState.STOP detected_ids: set[str] = set() for log in logs[TRAIN_UNTIL:]: if detector.process(log) is not None: From 0365dd9f26fedcf0d29c446b291e6e89599ac476 Mon Sep 17 00:00:00 2001 From: "angre.garcia-gomez@ait.ac.at" Date: Thu, 23 Jul 2026 10:52:15 +0200 Subject: [PATCH 4/4] refactor complete of fitlogic --- .../common/_core_op/_fit_logic.py | 62 ++++++++----------- 1 file changed, 27 insertions(+), 35 deletions(-) diff --git a/src/detectmatelibrary/common/_core_op/_fit_logic.py b/src/detectmatelibrary/common/_core_op/_fit_logic.py index 42ad7214..d0699191 100644 --- a/src/detectmatelibrary/common/_core_op/_fit_logic.py +++ b/src/detectmatelibrary/common/_core_op/_fit_logic.py @@ -3,20 +3,7 @@ from enum import Enum import warnings - -class FitLogicState(Enum): - DO_CONFIG = 0 - DO_TRAIN = 1 - NOTHING = 2 - - def describe(self) -> str: - descriptions = [ - "Configuring", - "Training.", - "Default" - ] - return descriptions[self.value] - +# Individual state of config or train ################################################ class EnumState(Enum): DEFAULT = 0 @@ -41,6 +28,9 @@ def __init__(self, total_need_data: int | None) -> None: self.data_used = 0 self.current = EnumState.DEFAULT + def force_keep_doing(self) -> None: + self.current = EnumState.KEEP + def keep_doing(self) -> bool: if self.current == EnumState.STOP: return False @@ -50,6 +40,7 @@ def keep_doing(self) -> bool: return self.total_need_data is not None and self.total_need_data > self.data_used def force_finish(self) -> None: + self.current = EnumState.STOP self.finished, self.ready_to_finish = False, True def check_if_ready_finish(self) -> None: @@ -63,24 +54,23 @@ def is_finish(self) -> bool: return False +# Overall state of the core component ################################################## + StatesL = Literal["keep_training", "stop_training", "keep_configuring", "stop_configuring"] -def update_state( - state: StatesL, train_state: EnumState, config_state: EnumState -) -> tuple[EnumState, EnumState]: - if state == "keep_training": - train_state = EnumState.KEEP - elif state == "stop_training": - train_state = EnumState.STOP - elif state == "keep_configuring": - config_state = EnumState.KEEP - elif state == "stop_configuring": - config_state = EnumState.STOP - else: - warnings.warn(f"State {state} unknown, use: {get_args(StatesL)}") +class FitLogicState(Enum): + DO_CONFIG = 0 + DO_TRAIN = 1 + NOTHING = 2 - return train_state, config_state + def describe(self) -> str: + descriptions = [ + "Configuring", + "Training.", + "Default" + ] + return descriptions[self.value] class FitLogic: @@ -97,14 +87,16 @@ def get_last_state(self) -> str: return self.last_state.describe() def update_state(self, state: StatesL) -> None: - self.train_state.current, self.config_state.current = update_state( - state=state, train_state=self.train_state.current, config_state=self.config_state.current - ) - if self.config_state.current == EnumState.STOP: - self.config_state.force_finish() - - if self.train_state.current == EnumState.STOP: + if state == "keep_training": + self.train_state.force_keep_doing() + elif state == "stop_training": self.train_state.force_finish() + elif state == "keep_configuring": + self.config_state.force_keep_doing() + elif state == "stop_configuring": + self.config_state.force_finish() + else: + warnings.warn(f"State {state} unknown, use: {get_args(StatesL)}") def finish_config(self) -> bool: return self.config_state.is_finish()