From fe23d7dff4bd43af604dac90c4a705bbcae16a5f Mon Sep 17 00:00:00 2001 From: hanmo1 Date: Wed, 17 Jun 2026 14:16:26 +0800 Subject: [PATCH] fix node label lookup normalization --- graphify/affected.py | 15 ++++++++++----- graphify/serve.py | 12 +++++++++--- tests/test_affected_cli.py | 21 +++++++++++++++++++++ tests/test_serve.py | 7 +++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/graphify/affected.py b/graphify/affected.py index 80bdc204a..5d586da63 100644 --- a/graphify/affected.py +++ b/graphify/affected.py @@ -4,6 +4,7 @@ from dataclasses import dataclass from pathlib import Path from typing import Iterable +import unicodedata import networkx as nx @@ -45,18 +46,22 @@ def _format_location(data: dict) -> str: def _bare_name(label: str) -> str: """Lowercased label with the callable decoration (trailing "()") removed.""" - label = label.lower() + label = _normalize_label(label) return label[:-2] if label.endswith("()") else label +def _normalize_label(label: str) -> str: + return unicodedata.normalize("NFC", label).casefold() + + def resolve_seed(graph: nx.Graph, query: str) -> str | None: if query in graph: return query - query_lower = query.lower() + query_lower = _normalize_label(query) exact_label_matches = [ str(node_id) for node_id, data in graph.nodes(data=True) - if str(data.get("label", "")).lower() == query_lower + if _normalize_label(str(data.get("label", ""))) == query_lower ] if len(exact_label_matches) == 1: return exact_label_matches[0] @@ -74,14 +79,14 @@ def resolve_seed(graph: nx.Graph, query: str) -> str | None: exact_source_matches = [ str(node_id) for node_id, data in graph.nodes(data=True) - if str(data.get("source_file", "")).lower() == query_lower + if _normalize_label(str(data.get("source_file", ""))) == query_lower ] if len(exact_source_matches) == 1: return exact_source_matches[0] contains_matches = [ str(node_id) for node_id, data in graph.nodes(data=True) - if query_lower in str(data.get("label", "")).lower() + if query_lower in _normalize_label(str(data.get("label", ""))) ] if len(contains_matches) == 1: return contains_matches[0] diff --git a/graphify/serve.py b/graphify/serve.py index 6e05b6a98..110f398ad 100644 --- a/graphify/serve.py +++ b/graphify/serve.py @@ -464,12 +464,18 @@ def _find_node(G: nx.Graph, label: str) -> list[str]: for nid, d in G.nodes(data=True): norm_label = d.get("norm_label") or _strip_diacritics(d.get("label") or "").lower() bare_label = norm_label.rstrip("()") + label_tokens = " ".join(_search_tokens(d.get("label") or "")) nid_lower = nid.lower() - if term == norm_label or term == bare_label or term == nid_lower: + if term == norm_label or term == bare_label or term == label_tokens or term == nid_lower: exact.append(nid) - elif norm_label.startswith(term) or bare_label.startswith(term) or nid_lower.startswith(term): + elif ( + norm_label.startswith(term) + or bare_label.startswith(term) + or label_tokens.startswith(term) + or nid_lower.startswith(term) + ): prefix.append(nid) - elif term in norm_label: + elif term in norm_label or term in label_tokens: substring.append(nid) return exact + prefix + substring diff --git a/tests/test_affected_cli.py b/tests/test_affected_cli.py index 40e0d68eb..b37cf552a 100644 --- a/tests/test_affected_cli.py +++ b/tests/test_affected_cli.py @@ -115,6 +115,27 @@ def test_resolve_seed_decorated_query_matches_bare_label(): assert resolve_seed(graph, "Foo()") == "a" +def test_resolve_seed_matches_unicode_normalized_label(): + import unicodedata + + from graphify.affected import resolve_seed + + graph = nx.DiGraph() + graph.add_node("a", label="Auditoría", source_file="pkg/auditoria.py") + + assert resolve_seed(graph, unicodedata.normalize("NFD", "Auditoría")) == "a" + + +def test_resolve_seed_preserves_distinct_accents(): + from graphify.affected import resolve_seed + + graph = nx.DiGraph() + graph.add_node("a", label="resume", source_file="pkg/resume.py") + graph.add_node("b", label="résumé", source_file="pkg/resume_accented.py") + + assert resolve_seed(graph, "resume") == "a" + + def test_resolve_seed_bare_name_tie_still_returns_none(): from graphify.affected import resolve_seed diff --git a/tests/test_serve.py b/tests/test_serve.py index c98dece2b..5b5926b96 100644 --- a/tests/test_serve.py +++ b/tests/test_serve.py @@ -123,6 +123,13 @@ def test_find_node_ignores_trailing_punctuation(): assert _find_node(G, "extract?") == ["n1"] +def test_find_node_matches_full_punctuated_unicode_label(): + G = nx.Graph() + G.add_node("n1", label="Skill /auditar — Auditoría inquisitiva de enlaces") + + assert _find_node(G, "Skill /auditar — Auditoría inquisitiva de enlaces") == ["n1"] + + def test_query_terms_strips_search_punctuation(): assert _query_terms("what calls extract?") == ["what", "calls", "extract"]