Skip to content

Commit ee54b2b

Browse files
authored
Replace watchlist match reasons with MatchReason enum (#60)
1 parent 5a6f574 commit ee54b2b

6 files changed

Lines changed: 34 additions & 25 deletions

File tree

src/paperscout/models.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,13 @@ class Tier(str, Enum):
159159
COLD = "cold"
160160

161161

162+
class MatchReason(str, Enum):
163+
"""Why a watchlist entry matched a paper or probe hit."""
164+
165+
AUTHOR = "author"
166+
PAPER = "paper"
167+
168+
162169
@dataclass(slots=True)
163170
class ProbeHit:
164171
"""Successful HEAD to an unpublished draft URL plus optional excerpt text."""
@@ -211,7 +218,7 @@ def __post_init__(self) -> None:
211218

212219
@dataclass
213220
class PerUserMatches:
214-
"""One user's watchlist hits: ``(paper|hit, 'author'|'paper')`` tuples."""
221+
"""One user's watchlist hits: ``(paper|hit, MatchReason)`` tuples."""
215222

216-
papers: list[tuple[Paper, str]] = field(default_factory=list)
217-
probe_hits: list[tuple[ProbeHit, str]] = field(default_factory=list)
223+
papers: list[tuple[Paper, MatchReason]] = field(default_factory=list)
224+
probe_hits: list[tuple[ProbeHit, MatchReason]] = field(default_factory=list)

src/paperscout/scout.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -521,15 +521,15 @@ def notify_users(app: App, result: PollResult, mq: MessageQueue) -> None:
521521
lines.append("*:rotating_light: Papers matching your watchlist:*")
522522
for paper, reason in matches.papers:
523523
p_link = _paper_link(paper)
524-
tag = f"[{reason} match]"
524+
tag = f"[{reason.value} match]"
525525
lines.append(f"• {p_link}{paper.title} (by *{paper.author}*) {tag}")
526526

527527
if matches.probe_hits:
528528
lines.append("*:rotating_light: New drafts matching your watchlist:*")
529529
for hit, reason in matches.probe_hits:
530530
h_link = _hit_label(hit.url, hit.prefix, hit.number, hit.revision, hit.extension)
531531
lm = _fmt_lm(hit.last_modified)
532-
tag = f"[{reason} match]"
532+
tag = f"[{reason.value} match]"
533533
lines.append(f"• {h_link}{lm} {tag}")
534534

535535
if not lines:

src/paperscout/storage.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from contextlib import contextmanager
1111
from typing import TYPE_CHECKING, Any, cast
1212

13-
from .models import PerUserMatches
13+
from .models import MatchReason, PerUserMatches
1414

1515
if TYPE_CHECKING:
1616
from psycopg2.pool import ThreadedConnectionPool
@@ -371,29 +371,29 @@ def matches_for_users(
371371
authors = user_authors.get(uid, [])
372372
paper_nums = user_papers.get(uid, set())
373373

374-
matched_papers: list[tuple[Paper, str]] = []
374+
matched_papers: list[tuple[Paper, MatchReason]] = []
375375
for paper in new_papers:
376376
# Author match
377377
if authors and paper.author:
378378
author_lower = paper.author.lower()
379379
if any(a in author_lower for a in authors):
380-
matched_papers.append((paper, "author"))
380+
matched_papers.append((paper, MatchReason.AUTHOR))
381381
continue
382382
# Paper-number match
383383
if paper_nums and paper.number is not None and paper.number in paper_nums:
384-
matched_papers.append((paper, "paper"))
384+
matched_papers.append((paper, MatchReason.PAPER))
385385

386-
matched_hits: list[tuple[ProbeHit, str]] = []
386+
matched_hits: list[tuple[ProbeHit, MatchReason]] = []
387387
for hit in probe_hits:
388388
# Author match via front_text
389389
if authors and hit.front_text:
390390
text_lower = hit.front_text.lower()
391391
if any(a in text_lower for a in authors):
392-
matched_hits.append((hit, "author"))
392+
matched_hits.append((hit, MatchReason.AUTHOR))
393393
continue
394394
# Paper-number match via probe hit number
395395
if paper_nums and hit.number in paper_nums:
396-
matched_hits.append((hit, "paper"))
396+
matched_hits.append((hit, MatchReason.PAPER))
397397

398398
if matched_papers or matched_hits:
399399
result[uid] = PerUserMatches(

tests/test_monitor.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import pytest
1313

1414
from paperscout.errors import ConfigurationError
15-
from paperscout.models import CycleResult, CycleStatus, Paper, PerUserMatches, ProbeHit
15+
from paperscout.models import CycleResult, CycleStatus, MatchReason, Paper, PerUserMatches, ProbeHit
1616
from paperscout.monitor import (
1717
DiffResult,
1818
PollResult,
@@ -189,7 +189,7 @@ def test_explicit_dp_transitions(self):
189189
def test_explicit_per_user_matches(self):
190190
diff = DiffResult(new_papers=[], updated_papers=[])
191191
paper = Paper(id="P2300R11")
192-
pum = PerUserMatches(papers=[(paper, "author")], probe_hits=[])
192+
pum = PerUserMatches(papers=[(paper, MatchReason.AUTHOR)], probe_hits=[])
193193
result = PollResult(diff=diff, probe_hits=[], per_user_matches={"U1": pum})
194194
assert "U1" in result.per_user_matches
195195

@@ -358,7 +358,7 @@ async def test_poll_once_populates_per_user_matches(self, fake_pool):
358358
prober.run_cycle = AsyncMock(return_value=_empty_cycle())
359359

360360
user_watchlist.matches_for_users.return_value = {
361-
"U123": PerUserMatches(papers=[(new_paper, "author")], probe_hits=[])
361+
"U123": PerUserMatches(papers=[(new_paper, MatchReason.AUTHOR)], probe_hits=[])
362362
}
363363
result = await scheduler.poll_once()
364364
assert "U123" in result.per_user_matches
@@ -373,7 +373,7 @@ async def test_poll_once_per_user_probe_hit(self, fake_pool):
373373
index.papers = {}
374374

375375
user_watchlist.matches_for_users.return_value = {
376-
"U123": PerUserMatches(papers=[], probe_hits=[(hit, "author")])
376+
"U123": PerUserMatches(papers=[], probe_hits=[(hit, MatchReason.AUTHOR)])
377377
}
378378
result = await scheduler.poll_once()
379379
assert "U123" in result.per_user_matches
@@ -403,7 +403,7 @@ async def test_restart_with_prior_poll_notifies_seed_hits(self, fake_pool):
403403
hit = _recent_hit()
404404
prober.run_cycle = AsyncMock(return_value=_success_cycle([hit]))
405405
user_watchlist.matches_for_users.return_value = {
406-
"U123": PerUserMatches(papers=[], probe_hits=[(hit, "author")])
406+
"U123": PerUserMatches(papers=[], probe_hits=[(hit, MatchReason.AUTHOR)])
407407
}
408408
result = await scheduler.poll_once()
409409
assert len(notified) == 1
@@ -418,7 +418,7 @@ async def test_restart_with_discovered_urls_notifies(self, fake_pool):
418418
hit = _recent_hit()
419419
prober.run_cycle = AsyncMock(return_value=_success_cycle([hit]))
420420
user_watchlist.matches_for_users.return_value = {
421-
"U123": PerUserMatches(papers=[], probe_hits=[(hit, "author")])
421+
"U123": PerUserMatches(papers=[], probe_hits=[(hit, MatchReason.AUTHOR)])
422422
}
423423
result = await scheduler.poll_once()
424424
assert len(notified) == 1

tests/test_scout.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from datetime import datetime, timedelta, timezone
66
from unittest.mock import MagicMock, patch
77

8-
from paperscout.models import Paper, PerUserMatches, ProbeHit
8+
from paperscout.models import MatchReason, Paper, PerUserMatches, ProbeHit
99
from paperscout.monitor import DiffResult, DPTransition, PollResult
1010
from paperscout.scout import (
1111
_batch_lines,
@@ -278,7 +278,7 @@ def test_author_match_sends_dm(self):
278278
paper = Paper(
279279
id="P2300R11", title="Senders", author="Eric Niebler", url="https://wg21.link/P2300R11"
280280
)
281-
pum = PerUserMatches(papers=[(paper, "author")], probe_hits=[])
281+
pum = PerUserMatches(papers=[(paper, MatchReason.AUTHOR)], probe_hits=[])
282282
result = _make_result(per_user_matches={"U123": pum})
283283
notify_users(app, result, mq)
284284
mq.enqueue.assert_called_once()
@@ -291,7 +291,7 @@ def test_paper_match_sends_dm(self):
291291
app = MagicMock()
292292
mq = MagicMock()
293293
paper = Paper(id="P2300R11", title="X", author="Someone", url="https://wg21.link/P2300R11")
294-
pum = PerUserMatches(papers=[(paper, "paper")], probe_hits=[])
294+
pum = PerUserMatches(papers=[(paper, MatchReason.PAPER)], probe_hits=[])
295295
result = _make_result(per_user_matches={"U456": pum})
296296
notify_users(app, result, mq)
297297
channel, text = mq.enqueue.call_args[0]
@@ -302,7 +302,7 @@ def test_probe_hit_match_sends_dm(self):
302302
app = MagicMock()
303303
mq = MagicMock()
304304
hit = _recent_hit()
305-
pum = PerUserMatches(papers=[], probe_hits=[(hit, "author")])
305+
pum = PerUserMatches(papers=[], probe_hits=[(hit, MatchReason.AUTHOR)])
306306
result = _make_result(per_user_matches={"U789": pum})
307307
notify_users(app, result, mq)
308308
mq.enqueue.assert_called_once()
@@ -313,7 +313,7 @@ def test_multiple_users_get_separate_dms(self):
313313
app = MagicMock()
314314
mq = MagicMock()
315315
paper = Paper(id="P2300R11", title="X", author="Niebler")
316-
pum = PerUserMatches(papers=[(paper, "author")], probe_hits=[])
316+
pum = PerUserMatches(papers=[(paper, MatchReason.AUTHOR)], probe_hits=[])
317317
result = _make_result(per_user_matches={"U1": pum, "U2": pum})
318318
notify_users(app, result, mq)
319319
assert mq.enqueue.call_count == 2

tests/test_storage.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import pytest
1010

11-
from paperscout.models import Paper
11+
from paperscout.models import MatchReason, Paper
1212
from paperscout.storage import (
1313
PaperCache,
1414
ProbeState,
@@ -309,6 +309,8 @@ def test_matches_for_users_author_match(self, fake_pool):
309309
assert "U1" in result
310310
matched_papers = [p for p, _ in result["U1"].papers]
311311
assert paper in matched_papers
312+
_, reason = result["U1"].papers[0]
313+
assert reason is MatchReason.AUTHOR
312314

313315
def test_matches_for_users_paper_match(self, fake_pool):
314316
wl = UserWatchlist(fake_pool)
@@ -406,7 +408,7 @@ def test_matches_skips_bad_paper_row_author_match_still_works(self, fake_pool):
406408
result = wl.matches_for_users([paper], [])
407409
assert "U1" in result
408410
reasons = [r for _, r in result["U1"].papers]
409-
assert "author" in reasons
411+
assert MatchReason.AUTHOR in reasons
410412

411413
def test_matches_paper_with_none_number_never_paper_matched(self, fake_pool):
412414
wl = UserWatchlist(fake_pool)

0 commit comments

Comments
 (0)