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:
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user