refactor(tools): resolve_project uses shared score_project_match helper

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-12 16:43:05 -04:00
parent 460959f0d4
commit 9a96fdb3c0
2 changed files with 40 additions and 14 deletions
+8 -14
View File
@@ -54,30 +54,24 @@ def score_project_match(query: str, project) -> float:
async def resolve_project(user_id: int, project_name: str):
"""Exact-then-fuzzy project lookup. Returns the Project or None.
"""Exact-then-scored project lookup. Returns the Project or None.
Resolution order:
1. Exact title match (case-insensitive via DB)
2. project_name is a substring of an existing title
3. Existing title is a substring of project_name
4. SequenceMatcher ratio >= 0.55
1. Exact title match (case-insensitive via DB query).
2. Highest `score_project_match` across all projects, threshold 0.55.
"""
from fabledassistant.services.projects import get_project_by_title, list_projects
proj = await get_project_by_title(user_id, project_name)
if proj is not None:
return proj
needle = project_name.lower().strip()
all_p = await list_projects(user_id)
best, best_score = None, 0.0
for p in all_p:
haystack = p.title.lower().strip()
if needle in haystack or haystack in needle:
return p
best, best_r = None, 0.0
for p in all_p:
r = SequenceMatcher(None, needle, p.title.lower().strip()).ratio()
if r >= 0.55 and r > best_r:
best, best_r = p, r
score = score_project_match(project_name, p)
if score >= 0.55 and score > best_score:
best, best_score = p, score
return best
+32
View File
@@ -109,3 +109,35 @@ async def test_search_projects_tool_ranks_substring_match_above_others():
top = result["data"]["projects"][0]
assert top["id"] == 5, f"expected Famous-Supply to rank first, got {top}"
assert top["score"] >= 0.85
@pytest.mark.asyncio
async def test_resolve_project_finds_colloquial_match(monkeypatch):
"""resolve_project must surface 'Famous-Supply Work topics' when the
user passes 'famous supply project' — substring match via the shared
score helper, score 0.85 ≥ 0.55 threshold."""
from fabledassistant.services.tools import _helpers
fake_projects = [
_project(12, "Minstrel", auto_summary="self-hosted music server"),
_project(5, "Famous-Supply Work topics", auto_summary="AT&T fiber circuit"),
]
async def fake_get_project_by_title(uid, name):
return None # force the scored path
async def fake_list_projects(uid):
return fake_projects
monkeypatch.setattr(
"fabledassistant.services.projects.get_project_by_title",
fake_get_project_by_title,
)
monkeypatch.setattr(
"fabledassistant.services.projects.list_projects",
fake_list_projects,
)
result = await _helpers.resolve_project(user_id=1, project_name="famous supply project")
assert result is not None
assert result.id == 5