from unittest.mock import MagicMock import pytest from evaluations.evaluators.map import MAPEvaluator from evaluations.evaluators.number_match import NumberMatchEvaluator class TestMAPEvaluator: def setup_method(self) -> None: self.evaluator = MAPEvaluator() def _make_ctx( self, relevant_uris: list[str], retrieved_uris: list[str] ) -> MagicMock: ctx = MagicMock() ctx.metadata = {"relevant_uris": relevant_uris} ctx.output = retrieved_uris return ctx def test_perfect_single_doc(self) -> None: ctx = self._make_ctx(["doc1"], ["doc1", "doc2", "doc3"]) assert self.evaluator.evaluate(ctx) == 1.0 def test_perfect_two_docs(self) -> None: # Both relevant at positions 1 and 2: P@1=1/1, P@2=2/2 → AP = (1+1)/2 = 1.0 ctx = self._make_ctx(["doc1", "doc2"], ["doc1", "doc2", "doc3"]) assert self.evaluator.evaluate(ctx) == 1.0 def test_one_relevant_at_second_position(self) -> None: # 1 relevant doc at position 2: P@2=1/2 → AP = 0.5/1 = 0.5 ctx = self._make_ctx(["doc2"], ["doc1", "doc2", "doc3"]) assert self.evaluator.evaluate(ctx) == 0.5 def test_two_relevant_with_gap(self) -> None: # Relevant at positions 1 and 3: P@1=1/1, P@3=2/3 → AP = (1 + 2/3)/2 ctx = self._make_ctx(["doc1", "doc3"], ["doc1", "doc2", "doc3"]) expected = (1.0 + 2 / 3) / 2 assert self.evaluator.evaluate(ctx) == pytest.approx(expected) def test_no_relevant_found(self) -> None: ctx = self._make_ctx(["doc_x"], ["doc1", "doc2", "doc3"]) assert self.evaluator.evaluate(ctx) == 0.0 def test_empty_retrieved(self) -> None: ctx = self._make_ctx(["doc1"], []) assert self.evaluator.evaluate(ctx) == 0.0 def test_none_metadata(self) -> None: ctx = MagicMock() ctx.metadata = None ctx.output = ["doc1"] assert self.evaluator.evaluate(ctx) == 0.0 def test_empty_relevant_uris(self) -> None: ctx = self._make_ctx([], ["doc1", "doc2"]) assert self.evaluator.evaluate(ctx) == 0.0 class TestNumberMatchEvaluator: def setup_method(self) -> None: self.evaluator = NumberMatchEvaluator() def _make_ctx(self, expected: str, output: str) -> MagicMock: ctx = MagicMock() ctx.expected_output = expected ctx.output = output return ctx def test_exact(self) -> None: assert self.evaluator.evaluate(self._make_ctx("127.4", "127.4")) == 1.0 def test_within_tolerance(self) -> None: ctx = self._make_ctx("127.4", "about $127.40 per transaction") assert self.evaluator.evaluate(ctx) == 1.0 def test_outside_tolerance(self) -> None: ctx = self._make_ctx("127.4", "the answer is 150") assert self.evaluator.evaluate(ctx) == 0.0 def test_picks_matching_candidate_among_many(self) -> None: ctx = self._make_ctx("50.3", "In 2008 it grew from 27.0 to 50.3 percent") assert self.evaluator.evaluate(ctx) == 1.0 def test_percent_answer_matches_decimal_gold(self) -> None: ctx = self._make_ctx("0.935", "the cumulative total return was 93.5%") assert self.evaluator.evaluate(ctx) == 1.0 def test_percent_answer_matches_percent_gold(self) -> None: ctx = self._make_ctx("24.691358024691358", "approximately 24.69% of production") assert self.evaluator.evaluate(ctx) == 1.0 def test_non_numeric_prediction(self) -> None: ctx = self._make_ctx("127.4", "I cannot determine the value") assert self.evaluator.evaluate(ctx) == 0.0 def test_non_numeric_gold(self) -> None: ctx = self._make_ctx("not a number", "127.4") assert self.evaluator.evaluate(ctx) == 0.0 def test_negative_match(self) -> None: ctx = self._make_ctx("-12.3", "the change was (12.3)") assert self.evaluator.evaluate(ctx) == 1.0