重构 Autocal 采集页:单一队列与控制器驱动的主按钮

选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
ykcheungab
2026-10-18 16:04:32 +08:00
parent 85d0b396e3
commit 06e06126ca
44 changed files with 7769 additions and 2218 deletions
+89
View File
@@ -3,7 +3,9 @@
from __future__ import annotations
import csv
import hashlib
import json
import logging
import pytest
@@ -14,11 +16,16 @@ from orisys_autocal.summary_export import (
POINTER_NAME,
SummaryExportError,
load_summary,
published_summary_directory,
publish_summary,
render_html,
)
def _sha256(path):
return hashlib.sha256(path.read_bytes()).hexdigest()
@pytest.fixture
def session(tmp_path):
root = multipoint_session(
@@ -173,3 +180,85 @@ def test_the_sensitivity_csv_has_one_row_per_point_trial_axis(session):
assert {row["review_status"] for row in rows} == {"candidate"}
fn = [row for row in rows if row["component"] == "FN"]
assert all(float(row["slope_raw_per_N"]) == pytest.approx(40.0) for row in fn)
def test_the_published_files_are_mirrored_under_the_session_root(session):
"""The folder the operator opens is the byte-identical copy the pointer names."""
summary = build_summary(session)
published = publish_summary(session, summary)
pointer = _pointer(session)
flat = session / "summary"
assert flat.is_dir()
assert sorted(path.name for path in flat.iterdir()) == sorted(pointer["files"])
for name, digest in pointer["files"].items():
assert _sha256(flat / name) == digest, name
assert (flat / name).read_bytes() == (published / name).read_bytes(), name
# The readable copy is not a second pointer: the authoritative path is derived/.
assert pointer["path"] == f"derived/{published.name}"
assert published_summary_directory(session) == flat
assert load_summary(session)[1]["summary_id"] == summary["summary_id"]
def test_a_failed_copy_reports_without_moving_the_pointer_back(session, monkeypatch, caplog):
"""Losing the readable copy must not lose the published evidence or the pointer."""
import orisys_autocal.summary_export as module
first = build_summary(session)
publish_summary(session, first)
flat = session / "summary"
before = {path.name: _sha256(path) for path in flat.iterdir()}
first_pointer = _pointer(session)
second = build_summary(session)
second["generated_at"] = "2999-01-01T00:00:00+00:00"
reports: list[str] = []
def explode(*args, **kwargs):
raise OSError("no space left on device")
monkeypatch.setattr(module, "_write_flat_summary", explode)
with caplog.at_level(logging.WARNING, logger="orisys_autocal.summary_export"):
published = publish_summary(session, second, on_warning=reports.append)
pointer = _pointer(session)
assert pointer["summary_id"] == second["summary_id"]
assert pointer["path"] == f"derived/{published.name}"
assert first_pointer["path"] != pointer["path"]
for name, digest in pointer["files"].items():
assert _sha256(published / name) == digest, name
# The previous readable copy still stands, unchanged.
assert {path.name: _sha256(path) for path in flat.iterdir()} == before
assert reports and "no space left on device" in reports[0]
assert any("no space left on device" in record.message for record in caplog.records)
def test_a_stale_readable_copy_is_not_offered_as_the_current_summary(session, monkeypatch):
"""A failed mirror keeps the older copy; opening it must not pass it off as current.
The pointer has moved on, so ``summary/`` describes a superseded version. The
operator must be sent to the directory the pointer names, not to numbers the
pointer no longer claims.
"""
import orisys_autocal.summary_export as module
first = build_summary(session)
publish_summary(session, first)
second = build_summary(session)
second["generated_at"] = "2999-01-01T00:00:00+00:00"
def explode(*args, **kwargs):
raise OSError("no space left on device")
monkeypatch.setattr(module, "_write_flat_summary", explode)
published = publish_summary(session, second)
pointer = _pointer(session)
assert pointer["summary_id"] == second["summary_id"]
opened = published_summary_directory(session)
assert opened is not None
assert opened == published, "the stale summary/ was offered as the current summary"
stored = json.loads((opened / "summary.json").read_text(encoding="utf-8"))
assert stored["summary_id"] == second["summary_id"]