refactor: move eval/ and data_adapters/ to tools/

This commit is contained in:
2026-05-12 00:40:33 +08:00
parent d1ecb13175
commit 3507fdc202
9 changed files with 40 additions and 40 deletions

View File

@@ -9,7 +9,7 @@ import pytest
def test_module_exports():
"""The adapter module must export the public functions used by CLI."""
from ai_mouse.data_adapters import balabit
from tools.data_adapters import balabit
assert hasattr(balabit, "parse_session_csv")
assert hasattr(balabit, "segment_by_clicks")
assert hasattr(balabit, "filter_segments")
@@ -20,7 +20,7 @@ def test_module_exports():
def test_mouse_event_dataclass():
"""MouseEvent has expected fields."""
from ai_mouse.data_adapters.balabit import MouseEvent
from tools.data_adapters.balabit import MouseEvent
e = MouseEvent(t_ms=100, button="NoButton", state="Move", x=300, y=400)
assert e.t_ms == 100
assert e.state == "Move"
@@ -29,7 +29,7 @@ def test_mouse_event_dataclass():
def test_segment_dataclass():
"""Segment has expected fields."""
from ai_mouse.data_adapters.balabit import MouseEvent, Segment
from tools.data_adapters.balabit import MouseEvent, Segment
events = [MouseEvent(t_ms=0, button="NoButton", state="Move", x=10, y=20)]
s = Segment(events=events, click_x=100, click_y=200, click_t_ms=500, session_id="user1_s1")
assert s.events == events
@@ -45,7 +45,7 @@ def _write_csv(path: Path, rows: list[str]) -> None:
class TestParseSessionCsv:
def test_parses_basic_rows(self, tmp_path):
from ai_mouse.data_adapters.balabit import parse_session_csv
from tools.data_adapters.balabit import parse_session_csv
csv = tmp_path / "session_1"
_write_csv(csv, [
"1500000000.000,0.000,NoButton,Move,100,200",
@@ -63,7 +63,7 @@ class TestParseSessionCsv:
def test_handles_float_timestamps(self, tmp_path):
"""Client timestamps are floats in seconds; we convert to int ms."""
from ai_mouse.data_adapters.balabit import parse_session_csv
from tools.data_adapters.balabit import parse_session_csv
csv = tmp_path / "session_2"
_write_csv(csv, [
"0,1.234,NoButton,Move,50,60",
@@ -75,7 +75,7 @@ class TestParseSessionCsv:
def test_skips_malformed_rows(self, tmp_path):
"""Rows with bad data are logged and skipped, not raised."""
from ai_mouse.data_adapters.balabit import parse_session_csv
from tools.data_adapters.balabit import parse_session_csv
csv = tmp_path / "session_3"
_write_csv(csv, [
"0,0.000,NoButton,Move,100,200",
@@ -89,7 +89,7 @@ class TestParseSessionCsv:
assert events[1].x == 150
def test_returns_empty_list_for_empty_file(self, tmp_path):
from ai_mouse.data_adapters.balabit import parse_session_csv
from tools.data_adapters.balabit import parse_session_csv
csv = tmp_path / "session_4"
csv.write_text("record timestamp,client timestamp,button,state,x,y\n", encoding="utf-8")
events = parse_session_csv(csv)
@@ -98,11 +98,11 @@ class TestParseSessionCsv:
class TestSegmentByClicks:
def _make_event(self, t_ms: int, state: str, x: int, y: int, button: str = "NoButton"):
from ai_mouse.data_adapters.balabit import MouseEvent
from tools.data_adapters.balabit import MouseEvent
return MouseEvent(t_ms=t_ms, button=button, state=state, x=x, y=y)
def test_one_click_one_segment(self):
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20),
self._make_event(100, "Move", 50, 60),
@@ -120,7 +120,7 @@ class TestSegmentByClicks:
def test_window_excludes_old_events(self):
"""Move events earlier than (click_t - window_ms) are dropped."""
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20), # too old
self._make_event(100, "Move", 20, 30), # too old
@@ -133,7 +133,7 @@ class TestSegmentByClicks:
assert segments[0].events[0].t_ms == 900
def test_multiple_clicks_multiple_segments(self):
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20),
self._make_event(100, "Pressed", 50, 50, button="Left"),
@@ -152,7 +152,7 @@ class TestSegmentByClicks:
def test_skips_pressed_with_non_left_button(self):
"""Right-clicks and wheel-clicks don't anchor segments (only Left)."""
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20),
self._make_event(100, "Pressed", 50, 50, button="Right"), # ignored
@@ -164,7 +164,7 @@ class TestSegmentByClicks:
assert segments[0].click_x == 70
def test_no_clicks_returns_empty(self):
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20),
self._make_event(100, "Move", 20, 30),
@@ -174,7 +174,7 @@ class TestSegmentByClicks:
def test_excludes_drag_events(self):
"""Drag events are not Move; segment should only include Move."""
from ai_mouse.data_adapters.balabit import segment_by_clicks
from tools.data_adapters.balabit import segment_by_clicks
events = [
self._make_event(0, "Move", 10, 20),
self._make_event(100, "Drag", 30, 40), # not Move
@@ -191,7 +191,7 @@ class TestSegmentByClicks:
class TestFilterSegments:
def _seg(self, events_data: list[tuple[int, int, int]], click=(500, 500), session="s"):
"""events_data: list of (t_ms, x, y) tuples."""
from ai_mouse.data_adapters.balabit import MouseEvent, Segment
from tools.data_adapters.balabit import MouseEvent, Segment
events = [MouseEvent(t_ms=t, button="NoButton", state="Move", x=x, y=y)
for (t, x, y) in events_data]
click_t = events[-1].t_ms + 50 if events else 100
@@ -199,14 +199,14 @@ class TestFilterSegments:
click_t_ms=click_t, session_id=session)
def test_drops_segment_with_too_few_events(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
seg = self._seg([(0, 100, 100), (50, 105, 105)]) # 2 events, min=5
result = filter_segments([seg], min_events=5, min_dist=10,
max_span_ms=5000, max_gap_ms=200)
assert result == []
def test_drops_segment_with_short_distance(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# Start (100,100), end click=(105,100) → dist=5, min_dist=50
events = [(i*10, 100+i, 100) for i in range(10)]
seg = self._seg(events, click=(105, 100))
@@ -215,7 +215,7 @@ class TestFilterSegments:
assert result == []
def test_drops_segment_with_too_long_span(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# Span 6000ms, max_span=5000
events = [(0, 100, 100), (2000, 200, 200), (4000, 300, 300), (6000, 400, 400),
(6010, 410, 400), (6020, 420, 400)]
@@ -225,7 +225,7 @@ class TestFilterSegments:
assert result == []
def test_drops_segment_with_gap(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# Gap of 500ms between events 2 and 3, max_gap=200
events = [(0, 100, 100), (50, 110, 110), (100, 120, 120),
(600, 200, 200), (650, 210, 210), (700, 220, 220)]
@@ -235,7 +235,7 @@ class TestFilterSegments:
assert result == []
def test_drops_segment_with_out_of_range_coords(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# x=10000 out of range
events = [(i*10, 10000, 100) for i in range(6)]
seg = self._seg(events, click=(10100, 100))
@@ -245,7 +245,7 @@ class TestFilterSegments:
def test_drops_segment_with_short_arc_length(self):
"""Total arc < 50 even though endpoints are far → high-frequency jitter only."""
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# Tiny back-and-forth with click 100px away — unrealistic, drop it
events = [(i*10, 100 + (i % 2), 100) for i in range(10)]
seg = self._seg(events, click=(200, 100))
@@ -255,7 +255,7 @@ class TestFilterSegments:
assert result == []
def test_keeps_valid_segment(self):
from ai_mouse.data_adapters.balabit import filter_segments
from tools.data_adapters.balabit import filter_segments
# Smooth 100→500 px straight line, 10 events, span 500ms
events = [(i*50, 100 + i*40, 100) for i in range(10)]
seg = self._seg(events, click=(500, 100))
@@ -267,7 +267,7 @@ class TestFilterSegments:
class TestProcessSession:
def test_writes_jsonl_in_expected_format(self, tmp_path):
from tools.config import BalabitAdapterConfig
from ai_mouse.data_adapters.balabit import process_session
from tools.data_adapters.balabit import process_session
# Construct a Balabit-format CSV with one valid segment
csv_path = tmp_path / "user_session_42"
@@ -304,7 +304,7 @@ class TestProcessSession:
def test_returns_zero_for_session_with_no_valid_segments(self, tmp_path):
from tools.config import BalabitAdapterConfig
from ai_mouse.data_adapters.balabit import process_session
from tools.data_adapters.balabit import process_session
csv_path = tmp_path / "empty_session"
_write_csv(csv_path, ["0,0.000,NoButton,Move,100,100"]) # no clicks
@@ -319,7 +319,7 @@ class TestProcessSession:
def test_appends_to_existing_jsonl(self, tmp_path):
from tools.config import BalabitAdapterConfig
from ai_mouse.data_adapters.balabit import process_session
from tools.data_adapters.balabit import process_session
out = tmp_path / "out.jsonl"
out.write_text('{"meta":{"start":[0,0],"end":[1,1]},"events":[]}\n', encoding="utf-8")