data problem fix
This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from python_app.gui.runtime.history import remove_last_aligned_histories
|
||||||
from python_app.hardware_full.librevna_service import LibreVnaService
|
from python_app.hardware_full.librevna_service import LibreVnaService
|
||||||
from python_app.models.run_config_model import ComboModel, RunConfigModel
|
from python_app.models.run_config_model import ComboModel, RunConfigModel
|
||||||
from python_app.orchestration.config_writer import parse_combos_from_text
|
from python_app.orchestration.config_writer import parse_combos_from_text
|
||||||
@@ -154,10 +155,20 @@ class AppWindowConfigMixin:
|
|||||||
dropped_results = self._result_reader.drop_all()
|
dropped_results = self._result_reader.drop_all()
|
||||||
|
|
||||||
if remove_last_only:
|
if remove_last_only:
|
||||||
if self._result_history:
|
retained_raw, retained_pre, retained_result = remove_last_aligned_histories(
|
||||||
self._result_history.pop()
|
list(self._raw_history),
|
||||||
|
list(self._pre_history),
|
||||||
|
list(self._result_history),
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self._result_history.clear()
|
retained_raw = []
|
||||||
|
retained_pre = []
|
||||||
|
retained_result = []
|
||||||
|
self._replace_runtime_history(
|
||||||
|
retained_raw=retained_raw,
|
||||||
|
retained_pre=retained_pre,
|
||||||
|
retained_result=retained_result,
|
||||||
|
)
|
||||||
self._clear_bscan_plot_history()
|
self._clear_bscan_plot_history()
|
||||||
|
|
||||||
self._write_live_processing_config(history_command=history_command, bump_history_seq=True)
|
self._write_live_processing_config(history_command=history_command, bump_history_seq=True)
|
||||||
|
|||||||
@@ -86,7 +86,9 @@ class AppWindowPipelineMixin:
|
|||||||
self._single_capture_seen_raw = False
|
self._single_capture_seen_raw = False
|
||||||
self._single_capture_target_collection_id = None
|
self._single_capture_target_collection_id = None
|
||||||
|
|
||||||
self._drop_pending_ring_payloads(include_results=not single_capture)
|
# Always drop unread payloads for all stages so single-capture starts
|
||||||
|
# from a clean boundary and does not retain stale results-only tail.
|
||||||
|
self._drop_pending_ring_payloads(include_results=True)
|
||||||
if single_capture:
|
if single_capture:
|
||||||
self._single_capture_start_ns = time.monotonic_ns()
|
self._single_capture_start_ns = time.monotonic_ns()
|
||||||
|
|
||||||
@@ -208,7 +210,7 @@ class AppWindowPipelineMixin:
|
|||||||
self._update_history_indicator()
|
self._update_history_indicator()
|
||||||
|
|
||||||
if self._single_capture_active:
|
if self._single_capture_active:
|
||||||
if self._finish_single_capture_if_ready(result_latest):
|
if self._finish_single_capture_if_ready():
|
||||||
return
|
return
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -216,31 +218,41 @@ class AppWindowPipelineMixin:
|
|||||||
except Exception as exc: # noqa: BLE001
|
except Exception as exc: # noqa: BLE001
|
||||||
self._log(f"Reader error: {exc}")
|
self._log(f"Reader error: {exc}")
|
||||||
|
|
||||||
def _finish_single_capture_if_ready(self, result_latest: ResultCollection | None) -> bool:
|
def _finish_single_capture_if_ready(self) -> bool:
|
||||||
"""Finalize single capture when new result matching start criteria is available."""
|
"""Finalize single capture when the exact target result becomes available."""
|
||||||
if not self._single_capture_active:
|
if not self._single_capture_active:
|
||||||
return False
|
return False
|
||||||
if result_latest is None:
|
|
||||||
return False
|
|
||||||
if self._single_capture_start_ns is None:
|
if self._single_capture_start_ns is None:
|
||||||
return False
|
return False
|
||||||
if not self._single_capture_seen_raw:
|
if not self._single_capture_seen_raw:
|
||||||
return False
|
return False
|
||||||
if (
|
target_result = self._find_single_capture_target_result()
|
||||||
self._single_capture_target_collection_id is not None
|
if target_result is None:
|
||||||
and result_latest.collection_id < self._single_capture_target_collection_id
|
|
||||||
):
|
|
||||||
return False
|
|
||||||
if result_latest.monotonic_ns < self._single_capture_start_ns:
|
|
||||||
return False
|
|
||||||
if not self._result_collection_has_trace(result_latest):
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._draw_results(result_latest)
|
self._draw_results(target_result)
|
||||||
self._log("Single capture completed")
|
self._log("Single capture completed")
|
||||||
self._stop_run()
|
self._stop_run()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def _find_single_capture_target_result(self) -> ResultCollection | None:
|
||||||
|
"""Return the exact result collection corresponding to the captured target raw sweep."""
|
||||||
|
if self._single_capture_target_collection_id is None:
|
||||||
|
return None
|
||||||
|
if self._single_capture_start_ns is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
for collection in reversed(self._result_history):
|
||||||
|
if collection.collection_id != self._single_capture_target_collection_id:
|
||||||
|
continue
|
||||||
|
if collection.monotonic_ns < self._single_capture_start_ns:
|
||||||
|
continue
|
||||||
|
if not self._result_collection_has_trace(collection):
|
||||||
|
continue
|
||||||
|
return collection
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
def _read_all_raw(self) -> SweepCollection | None:
|
def _read_all_raw(self) -> SweepCollection | None:
|
||||||
"""Read available raw collections from raw ring."""
|
"""Read available raw collections from raw ring."""
|
||||||
assert self._raw_reader is not None
|
assert self._raw_reader is not None
|
||||||
@@ -277,14 +289,10 @@ class AppWindowPipelineMixin:
|
|||||||
collection = self._result_reader.pop_result_collection()
|
collection = self._result_reader.pop_result_collection()
|
||||||
if collection is None:
|
if collection is None:
|
||||||
break
|
break
|
||||||
if self._record_result_history(collection):
|
if record_result_history(self._result_history, collection):
|
||||||
latest = collection
|
latest = collection
|
||||||
return latest
|
return latest
|
||||||
|
|
||||||
def _record_result_history(self, collection: ResultCollection) -> bool:
|
|
||||||
"""Merge collection into result history preserving de-dup semantics."""
|
|
||||||
return record_result_history(self._result_history, collection)
|
|
||||||
|
|
||||||
def _drain_rings_once_for_history(self) -> None:
|
def _drain_rings_once_for_history(self) -> None:
|
||||||
"""Perform one non-blocking read pass to extend histories."""
|
"""Perform one non-blocking read pass to extend histories."""
|
||||||
if self._raw_reader is not None:
|
if self._raw_reader is not None:
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ from pathlib import Path
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QFileDialog
|
from PyQt6.QtWidgets import QFileDialog
|
||||||
|
from python_app.gui.runtime.history import record_result_history
|
||||||
|
|
||||||
|
|
||||||
class AppWindowSnapshotMixin:
|
class AppWindowSnapshotMixin:
|
||||||
@@ -179,7 +180,7 @@ class AppWindowSnapshotMixin:
|
|||||||
collection = self._result_reader.pop_result_collection()
|
collection = self._result_reader.pop_result_collection()
|
||||||
if collection is None:
|
if collection is None:
|
||||||
break
|
break
|
||||||
self._record_result_history(collection)
|
record_result_history(self._result_history, collection)
|
||||||
progress = True
|
progress = True
|
||||||
|
|
||||||
if progress:
|
if progress:
|
||||||
|
|||||||
@@ -3,10 +3,13 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
from python_app.models.dataset_model import ResultCollection
|
from python_app.models.dataset_model import ResultCollection, SweepCollection
|
||||||
from python_app.models.run_config_model import RunConfigModel
|
from python_app.models.run_config_model import RunConfigModel
|
||||||
|
|
||||||
|
THistoryCollection = TypeVar("THistoryCollection", SweepCollection, ResultCollection)
|
||||||
|
|
||||||
|
|
||||||
def record_result_history(
|
def record_result_history(
|
||||||
result_history: deque[ResultCollection],
|
result_history: deque[ResultCollection],
|
||||||
@@ -26,6 +29,35 @@ def record_result_history(
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def remove_last_aligned_histories(
|
||||||
|
raw_history: list[SweepCollection],
|
||||||
|
preprocessed_history: list[SweepCollection],
|
||||||
|
result_history: list[ResultCollection],
|
||||||
|
) -> tuple[list[SweepCollection], list[SweepCollection], list[ResultCollection]]:
|
||||||
|
"""Remove the newest aligned history entry using results as preferred anchor."""
|
||||||
|
retained_raw = list(raw_history)
|
||||||
|
retained_preprocessed = list(preprocessed_history)
|
||||||
|
retained_results = list(result_history)
|
||||||
|
|
||||||
|
if retained_results:
|
||||||
|
target_key = _tail_occurrence_key(retained_results, len(retained_results) - 1)
|
||||||
|
retained_results.pop()
|
||||||
|
_remove_by_tail_occurrence_key(retained_raw, target_key)
|
||||||
|
_remove_by_tail_occurrence_key(retained_preprocessed, target_key)
|
||||||
|
return retained_raw, retained_preprocessed, retained_results
|
||||||
|
|
||||||
|
if retained_preprocessed:
|
||||||
|
target_key = _tail_occurrence_key(retained_preprocessed, len(retained_preprocessed) - 1)
|
||||||
|
retained_preprocessed.pop()
|
||||||
|
_remove_by_tail_occurrence_key(retained_raw, target_key)
|
||||||
|
return retained_raw, retained_preprocessed, retained_results
|
||||||
|
|
||||||
|
if retained_raw:
|
||||||
|
retained_raw.pop()
|
||||||
|
|
||||||
|
return retained_raw, retained_preprocessed, retained_results
|
||||||
|
|
||||||
|
|
||||||
def build_run_history_signature(
|
def build_run_history_signature(
|
||||||
config: RunConfigModel,
|
config: RunConfigModel,
|
||||||
) -> tuple[object, ...]:
|
) -> tuple[object, ...]:
|
||||||
@@ -51,3 +83,27 @@ def build_run_history_signature(
|
|||||||
str(config.preprocess.reference_set),
|
str(config.preprocess.reference_set),
|
||||||
combos_signature,
|
combos_signature,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tail_occurrence_key(history: list[THistoryCollection], index: int) -> tuple[int, int]:
|
||||||
|
"""Return `(collection_id, occurrence_from_tail)` for the item at `index`."""
|
||||||
|
collection_id = int(history[index].collection_id)
|
||||||
|
occurrence_from_tail = 0
|
||||||
|
for cursor in range(len(history) - 1, index, -1):
|
||||||
|
if int(history[cursor].collection_id) == collection_id:
|
||||||
|
occurrence_from_tail += 1
|
||||||
|
return collection_id, occurrence_from_tail
|
||||||
|
|
||||||
|
|
||||||
|
def _remove_by_tail_occurrence_key(history: list[THistoryCollection], key: tuple[int, int]) -> bool:
|
||||||
|
"""Remove newest matching entry identified by `(collection_id, occurrence_from_tail)`."""
|
||||||
|
collection_id, target_occurrence = key
|
||||||
|
occurrence_from_tail = 0
|
||||||
|
for index in range(len(history) - 1, -1, -1):
|
||||||
|
if int(history[index].collection_id) != collection_id:
|
||||||
|
continue
|
||||||
|
if occurrence_from_tail == target_occurrence:
|
||||||
|
history.pop(index)
|
||||||
|
return True
|
||||||
|
occurrence_from_tail += 1
|
||||||
|
return False
|
||||||
|
|||||||
Reference in New Issue
Block a user