Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion skills/deeppapernote/scripts/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,10 @@ def env_config_value(*names: str, default: str = "") -> str:


def title_similarity(a: str, b: str) -> float:
a_identity = normalize_identity_title(a)
b_identity = normalize_identity_title(b)
if a_identity and a_identity == b_identity:
return 1.0
a_norm = normalize_title(a)
b_norm = normalize_title(b)
if not a_norm or not b_norm:
Expand Down Expand Up @@ -587,6 +591,10 @@ def _full_leading_author_matches(
work_authors = _dedupe_string_list(work_record.get("authors", []))
if not source_authors or not work_authors:
return False
source_normalized = normalize_identity_title(source_authors[0])
work_normalized = normalize_identity_title(work_authors[0])
if source_normalized == work_normalized:
return True
source_parts = _author_identity_parts(source_authors[0])
work_parts = _author_identity_parts(work_authors[0])
if len(source_parts) < 2 or len(work_parts) < 2:
Expand All @@ -609,9 +617,16 @@ def _leading_author_status(
work_authors = _dedupe_string_list(work_record.get("authors", []))
if not source_authors or not work_authors:
return None
source_normalized = normalize_identity_title(source_authors[0])
work_normalized = normalize_identity_title(work_authors[0])
source_key = _author_key(source_authors[0])
work_key = _author_key(work_authors[0])
status = "match" if source_key and source_key == work_key else "conflict"
status = (
"match"
if source_normalized == work_normalized
or (source_key and source_key == work_key)
else "conflict"
)
return {
"kind": "leading_author",
"status": status,
Expand Down
74 changes: 74 additions & 0 deletions tests/test_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,80 @@ def test_local_pdf_good_title_does_not_use_unrelated_external_title() -> None:
assert corrected == ""


def test_manifestation_equivalence_accepts_identical_cjk_identity() -> None:
title = "双元联盟网络如何影响突破式创新——技术能力与网络惯例的调节作用"
decision = common.manifestation_equivalence_decision(
{"title": title, "authors": ["朱云鹃"]},
{"title": title, "authors": ["朱云鹃"]},
)

evidence = {item["kind"]: item for item in decision["evidence"]}
assert decision["status"] == "equivalent"
assert evidence["title_similarity"]["score"] == 1.0
assert evidence["leading_author"]["status"] == "match"


def test_manifestation_equivalence_rejects_distinct_spaced_cjk_authors() -> None:
decision = common.manifestation_equivalence_decision(
{"title": "完全不同论文", "authors": ["李 云鹃"]},
{"title": "深度学习研究一", "authors": ["朱 云鹃"]},
)

evidence = {item["kind"]: item for item in decision["evidence"]}
assert decision["status"] == "ambiguous"
assert evidence["leading_author"]["status"] == "conflict"


def test_manifestation_equivalence_preserves_unicode_combining_marks() -> None:
decision = common.manifestation_equivalence_decision(
{"title": "कतब", "authors": ["करण"]},
{"title": "किताब", "authors": ["किरण"]},
)

evidence = {item["kind"]: item for item in decision["evidence"]}
assert decision["status"] == "ambiguous"
assert evidence["title_similarity"]["score"] == 0.0
assert evidence["leading_author"]["status"] == "conflict"


def test_ascii_identity_matching_remains_unchanged() -> None:
assert common.normalize_title("Attention: Is All You Need!") == (
"attention is all you need"
)
equivalent = common.manifestation_equivalence_decision(
{"title": "attention is all you need", "authors": ["Alice Example"]},
{"title": "Attention Is All You Need", "authors": ["Alice Example"]},
)
ambiguous = common.manifestation_equivalence_decision(
{"title": "Completely Different Paper", "authors": ["Bob Other"]},
{"title": "Attention Is All You Need", "authors": ["Alice Example"]},
)

assert equivalent["status"] == "equivalent"
assert ambiguous["status"] == "ambiguous"


def test_identity_adjudication_accepts_identical_cjk_author() -> None:
title = "双元联盟网络如何影响突破式创新——技术能力与网络惯例的调节作用"
decision = common.adjudicate_identity_observations(
{"title": title, "authors": ["朱云鹃"], "year": "2026"},
[
{
"provider": "crossref",
"record": {
"title": title,
"authors": ["朱云鹃"],
"year": "2026",
},
}
],
)

assert decision["rejected_observations"] == []
assert len(decision["accepted_observations"]) == 1
assert decision["accepted_observations"][0]["reason"] == "title_author_year"


def test_resolve_note_output_mode_falls_back_to_workspace(tmp_path: Path, monkeypatch) -> None:
monkeypatch.chdir(tmp_path)
config = {
Expand Down