feat(adapter): scaffold balabit data adapter package
This commit is contained in:
1
ai_mouse/data_adapters/__init__.py
Normal file
1
ai_mouse/data_adapters/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""Data adapters: convert external datasets to the project's traces.jsonl format."""
|
||||||
79
ai_mouse/data_adapters/balabit.py
Normal file
79
ai_mouse/data_adapters/balabit.py
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
"""Adapter for the Balabit Mouse Dynamics Challenge dataset.
|
||||||
|
|
||||||
|
Source: https://github.com/balabit/Mouse-Dynamics-Challenge
|
||||||
|
|
||||||
|
Each session is a CSV file with columns:
|
||||||
|
record timestamp, client timestamp, button, state, x, y
|
||||||
|
|
||||||
|
Where:
|
||||||
|
state ∈ {Move, Pressed, Released, Drag, Scroll}
|
||||||
|
button ∈ {NoButton, Left, Right, Wheel}
|
||||||
|
|
||||||
|
We extract "click-anchored" trajectory segments: each Pressed event
|
||||||
|
defines a target, and the W ms of Move events preceding it form one
|
||||||
|
training trace.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class MouseEvent:
|
||||||
|
"""A single mouse event from a Balabit CSV row."""
|
||||||
|
|
||||||
|
t_ms: int # client timestamp in milliseconds (relative to session start)
|
||||||
|
button: str # "NoButton", "Left", "Right", "Wheel"
|
||||||
|
state: str # "Move", "Pressed", "Released", "Drag", "Scroll"
|
||||||
|
x: int
|
||||||
|
y: int
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Segment:
|
||||||
|
"""A click-anchored trajectory segment ready to be written to JSONL."""
|
||||||
|
|
||||||
|
events: list[MouseEvent] # only Move events, sorted by t_ms ascending
|
||||||
|
click_x: int # the Pressed event's x coordinate
|
||||||
|
click_y: int # the Pressed event's y coordinate
|
||||||
|
click_t_ms: int # the Pressed event's timestamp
|
||||||
|
session_id: str # e.g. "user7_session_42"
|
||||||
|
|
||||||
|
|
||||||
|
def parse_session_csv(path: Path) -> list[MouseEvent]:
|
||||||
|
"""Stub — implemented in Task 3."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def segment_by_clicks(
|
||||||
|
events: list[MouseEvent],
|
||||||
|
window_ms: int,
|
||||||
|
session_id: str,
|
||||||
|
) -> list[Segment]:
|
||||||
|
"""Stub — implemented in Task 4."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def filter_segments(
|
||||||
|
segments: list[Segment],
|
||||||
|
min_events: int,
|
||||||
|
min_dist: int,
|
||||||
|
max_span_ms: int,
|
||||||
|
max_gap_ms: int,
|
||||||
|
) -> list[Segment]:
|
||||||
|
"""Stub — implemented in Task 5."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
def process_session(
|
||||||
|
csv_path: Path,
|
||||||
|
output_jsonl: Path,
|
||||||
|
config,
|
||||||
|
) -> int:
|
||||||
|
"""Stub — implemented in Task 6."""
|
||||||
|
raise NotImplementedError
|
||||||
36
tests/test_balabit_adapter.py
Normal file
36
tests/test_balabit_adapter.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""Tests for Balabit Mouse Dynamics Challenge data adapter."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_exports():
|
||||||
|
"""The adapter module must export the public functions used by CLI."""
|
||||||
|
from ai_mouse.data_adapters import balabit
|
||||||
|
assert hasattr(balabit, "parse_session_csv")
|
||||||
|
assert hasattr(balabit, "segment_by_clicks")
|
||||||
|
assert hasattr(balabit, "filter_segments")
|
||||||
|
assert hasattr(balabit, "process_session")
|
||||||
|
assert hasattr(balabit, "MouseEvent")
|
||||||
|
assert hasattr(balabit, "Segment")
|
||||||
|
|
||||||
|
|
||||||
|
def test_mouse_event_dataclass():
|
||||||
|
"""MouseEvent has expected fields."""
|
||||||
|
from ai_mouse.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"
|
||||||
|
assert e.x == 300
|
||||||
|
|
||||||
|
|
||||||
|
def test_segment_dataclass():
|
||||||
|
"""Segment has expected fields."""
|
||||||
|
from ai_mouse.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
|
||||||
|
assert s.click_x == 100
|
||||||
|
assert s.session_id == "user1_s1"
|
||||||
Reference in New Issue
Block a user