diff --git a/src/benchmark_radar/corpus.py b/src/benchmark_radar/corpus.py index f0021d3b..d4f5518e 100644 --- a/src/benchmark_radar/corpus.py +++ b/src/benchmark_radar/corpus.py @@ -75,7 +75,7 @@ def _exact_candidates(item: dict[str, Any]) -> list[tuple[int, str]]: if host == "openreview.net": forum = (parse_qs(parsed.query).get("id") or [None])[0] if forum: - candidates.add((3, f"artifact:openreview:{str(forum).casefold()}")) + candidates.add((3, f"artifact:openreview:{str(forum)}")) if host == "github.com" and len(segments) >= 2: candidates.add( (4, f"artifact:github:{segments[0].casefold()}/{segments[1].casefold()}") @@ -103,17 +103,23 @@ def _exact_candidates(item: dict[str, Any]) -> list[tuple[int, str]]: # No recognizable URL identifier, so fall back to the source's own id. source = str(item.get("source") or "").casefold() - source_id = str(item.get("source_id") or "").strip().casefold() + source_id = str(item.get("source_id") or "").strip() if source == "arxiv": - base_id = re.sub(r"v\d+$", "", source_id) + base_id = re.sub(r"v\d+$", "", source_id.casefold()) return [(2, f"artifact:arxiv:{base_id}")] if source == "openreview": return [(3, f"artifact:openreview:{source_id}")] if source in {"github", "github release"}: - return [(4, f"artifact:github:{source_id.split('@', 1)[0]}")] + return [(4, f"artifact:github:{source_id.casefold().split('@', 1)[0]}")] if source == "hugging face": - return [(5, f"artifact:huggingface:datasets:{source_id}")] - return [(9, _stable_id("artifact:url", str(item.get("url") or source_id).casefold()))] + return [(5, f"artifact:huggingface:datasets:{source_id.casefold()}")] + # Schemes and hostnames are case-insensitive; paths and queries are not. + url = str(item.get("url") or source_id) + parsed = urlsplit(url) + normalized = parsed._replace( + scheme=parsed.scheme.lower(), netloc=parsed.netloc.lower() + ).geturl() + return [(9, _stable_id("artifact:url", normalized))] def exact_artifact_keys(item: dict[str, Any]) -> list[str]: diff --git a/tests/test_corpus.py b/tests/test_corpus.py index 4b294fd9..7e0ed8e1 100644 --- a/tests/test_corpus.py +++ b/tests/test_corpus.py @@ -135,3 +135,44 @@ def test_validation_rejects_edges_to_unknown_entities(): with pytest.raises(CorpusError, match="unknown entity"): validate_corpus(corpus) + + +@pytest.mark.parametrize( + "first,second", + [ + ( + item( + source="OpenReview", + source_id="AbC123", + url="https://openreview.net/forum?id=AbC123", + ), + item( + source="OpenReview", + source_id="abc123", + url="https://openreview.net/forum?id=abc123", + ), + ), + ( + item(source="OpenReview", source_id="AbC123", url="https://openreview.net/pdf/AbC123"), + item(source="OpenReview", source_id="abc123", url="https://openreview.net/pdf/abc123"), + ), + ( + item(url="https://example.com/Benchmark?version=A"), + item(source_id="paper-2", url="https://example.com/benchmark?version=a"), + ), + ], +) +def test_corpus_keeps_case_sensitive_identifiers_distinct(first, second): + corpus = build_corpus([snapshot(first, second)]) + artifacts = [entity for entity in corpus["entities"] if entity["type"] == "artifact"] + assert len(artifacts) == 2 + assert all(entity["observation_count"] == 1 for entity in artifacts) + + +def test_generic_url_hostname_is_case_insensitive(): + first = item(url="https://EXAMPLE.com/Benchmark?version=A") + second = item(source_id="paper-2", url="https://example.COM/Benchmark?version=A") + corpus = build_corpus([snapshot(first, second)]) + artifacts = [entity for entity in corpus["entities"] if entity["type"] == "artifact"] + assert len(artifacts) == 1 + assert artifacts[0]["observation_count"] == 2