From d1c5190bbb28bf21360d22e50dfbacec1ec9fcfa Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Thu, 4 Jun 2026 01:35:15 -0700 Subject: [PATCH] feat: selection Coordinate and weighted distance --- hef/selection.py | 36 +++++++++++++++++++++++++++++ tests/test_selection.py | 50 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 tests/test_selection.py diff --git a/hef/selection.py b/hef/selection.py index 6ddcefa..4a0c6ff 100644 --- a/hef/selection.py +++ b/hef/selection.py @@ -1 +1,37 @@ """Nearest-match selection of a catalog record for a knob coordinate.""" + +from __future__ import annotations + +import math +from dataclasses import dataclass + +from hef.catalog import Record + +SELECTOR_MODES = frozenset({"none", "audio", "video", "av"}) +CONTENT_MODES = frozenset({"audio", "video", "av"}) + + +@dataclass(frozen=True) +class Coordinate: + left: int + right: int + dark: int + light: int + + +@dataclass(frozen=True) +class Weights: + brain: float = 1.0 + mood: float = 1.0 + + +def distance(a: Coordinate, b: Coordinate, weights: Weights = Weights()) -> float: + """Weighted Euclidean distance; brain plane and mood plane weighted separately.""" + brain_sq = (a.left - b.left) ** 2 + (a.right - b.right) ** 2 + mood_sq = (a.dark - b.dark) ** 2 + (a.light - b.light) ** 2 + return math.sqrt(weights.brain * brain_sq + weights.mood * mood_sq) + + +def record_coordinate(record: Record) -> Coordinate: + """The (left, right, dark, light) coordinate of a catalog record.""" + return Coordinate(record.left, record.right, record.dark, record.light) diff --git a/tests/test_selection.py b/tests/test_selection.py new file mode 100644 index 0000000..ac6d1d4 --- /dev/null +++ b/tests/test_selection.py @@ -0,0 +1,50 @@ +import math + +import pytest + +from hef.catalog import Record +from hef.selection import Coordinate, Weights, distance, record_coordinate + + +def make_record(**overrides): + base = dict( + id="r", + title="t", + source_url="u", + source_archive="internet_archive", + license="public_domain", + mode="video", + left=0, + right=0, + dark=0, + light=0, + duration_s=600, + file_path="/media/r.mp4", + ) + base.update(overrides) + return Record(**base) + + +def test_distance_zero_for_same_point(): + c = Coordinate(2, 2, 2, 2) + assert distance(c, c) == 0.0 + + +def test_distance_is_euclidean_by_default(): + a = Coordinate(0, 0, 0, 0) + b = Coordinate(1, 1, 1, 1) + assert distance(a, b) == pytest.approx(2.0) # sqrt(1+1+1+1) + + +def test_weights_scale_planes_independently(): + a = Coordinate(0, 0, 0, 0) + brain_only = Coordinate(1, 0, 0, 0) + mood_only = Coordinate(0, 0, 1, 0) + w = Weights(brain=4.0, mood=1.0) + assert distance(a, brain_only, w) == pytest.approx(2.0) # sqrt(4*1) + assert distance(a, mood_only, w) == pytest.approx(1.0) # sqrt(1*1) + + +def test_record_coordinate_extracts_axes(): + record = make_record(left=1, right=2, dark=3, light=4) + assert record_coordinate(record) == Coordinate(1, 2, 3, 4)