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