Migrate all repos into monorepo context folders
Bahn: aisupport, Analyse-O2C-C2S, awesome-bahn-mcp-servers, beam-mcp,
Confluence_Bot, db-planet-mcp-server, O2C-Harness, project-audit,
Projekt-KIQ-HP, teamlandkarte-mcp
Dhive: Jury-Voting
Privat: CV, NoteGraph (NOTE: NoteGraph needs complete redo after consolidation)
Shared: AI-Orchestrator, OrgMyLife, power_skills_and_more
Shared/references: symphony (read-only)
Bahn repos remain available as independent remotes - this monorepo
pulls them in via subtree, the originals are untouched.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from teamlandkarte_mcp.cache.search_cache import SearchCache
|
||||
|
||||
|
||||
def test_filter_id_increments_under_same_search_id() -> None:
|
||||
cache = SearchCache(ttl_minutes=60, max_size=10)
|
||||
search_id = cache.store_search(
|
||||
task_id=None, requirements={}, results={"by_category": {}}
|
||||
)
|
||||
|
||||
filter_id_1 = cache.add_filter(
|
||||
search_id, filtered_results={"by_category": {}}, filter_meta={}
|
||||
)
|
||||
filter_id_2 = cache.add_filter(
|
||||
search_id, filtered_results={"by_category": {}}, filter_meta={}
|
||||
)
|
||||
|
||||
assert filter_id_1 == "filter-1"
|
||||
assert filter_id_2 == "filter-2"
|
||||
|
||||
entry = cache.get(search_id)
|
||||
assert entry is not None
|
||||
assert filter_id_1 in entry.filters
|
||||
assert filter_id_2 in entry.filters
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Team-search coverage (Profile_Type="team")
|
||||
# ---------------------------------------------------------------------------
|
||||
#
|
||||
# The SearchCache itself is search-type-agnostic - it stores whatever
|
||||
# ``results`` payload it receives. The tests below exercise the same
|
||||
# cache primitives (``store_search``, ``add_filter``, ``get``) for
|
||||
# ``team_search`` payloads in both ``matching_method`` flavours
|
||||
# (``score`` and ``llm_fulltext``) so the entire cache surface is
|
||||
# covered for both ``Profile_Type`` values.
|
||||
#
|
||||
# _Requirements: 8.1, 8.2, 8.3, 12.7_
|
||||
|
||||
|
||||
def _team_payload(matching_method: str) -> dict:
|
||||
"""Return a minimal team_search payload mimicking the shapes that
|
||||
``find_matching_teams`` persists in either ``matching_method`` mode.
|
||||
"""
|
||||
base_team = {
|
||||
"team_id": "t1",
|
||||
"ouid": "ou-1",
|
||||
"team_name": "Team Alpha",
|
||||
"focus_name": "Backend Developer",
|
||||
"about_us": "",
|
||||
"offerings": "",
|
||||
"interests": "",
|
||||
"competences": [{"name": "python", "top_competency": True}],
|
||||
"references": [],
|
||||
"category": "Top",
|
||||
}
|
||||
if matching_method == "score":
|
||||
scored_team = {
|
||||
**base_team,
|
||||
"competence_score": 1.0,
|
||||
"role_score": 1.0,
|
||||
"overall_score": 1.0,
|
||||
}
|
||||
return {
|
||||
"search_type": "team_search",
|
||||
"matching_method": "score",
|
||||
"reference": {
|
||||
"availability_date_start": None,
|
||||
"availability_date_end": None,
|
||||
},
|
||||
"summary": {"Top": 1, "Good": 0, "Partial": 0, "Low": 0,
|
||||
"Irrelevant": 0},
|
||||
"by_category": {
|
||||
"Top": [scored_team],
|
||||
"Good": [],
|
||||
"Partial": [],
|
||||
"Low": [],
|
||||
"Irrelevant": [],
|
||||
},
|
||||
}
|
||||
# llm_fulltext
|
||||
llm_team = {**base_team, "rationale": "Backend match."}
|
||||
return {
|
||||
"search_type": "team_search",
|
||||
"matching_method": "llm_fulltext",
|
||||
"reference": {
|
||||
"availability_date_start": None,
|
||||
"availability_date_end": None,
|
||||
},
|
||||
"summary": {"Top": 1, "Good": 0, "Partial": 0, "Low": 0,
|
||||
"Irrelevant": 0},
|
||||
"by_category": {
|
||||
"Top": [llm_team],
|
||||
"Good": [],
|
||||
"Partial": [],
|
||||
"Low": [],
|
||||
"Irrelevant": [],
|
||||
},
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
|
||||
def test_team_search_cache_roundtrip_score_mode() -> None:
|
||||
"""``team_search`` payloads round-trip through the cache and keep
|
||||
their ``search_type``/``matching_method`` markers (Anforderungen
|
||||
8.1, 8.2, 8.3)."""
|
||||
cache = SearchCache(ttl_minutes=60, max_size=10)
|
||||
payload = _team_payload("score")
|
||||
|
||||
search_id = cache.store_search(
|
||||
task_id=None,
|
||||
requirements={"role_name": "Backend Developer",
|
||||
"competences": ["python"]},
|
||||
results=payload,
|
||||
)
|
||||
|
||||
entry = cache.get(search_id)
|
||||
assert entry is not None
|
||||
assert entry.results["search_type"] == "team_search"
|
||||
assert entry.results["matching_method"] == "score"
|
||||
# The cached items keep the team-specific fields used by the
|
||||
# team-search column layout (Anforderung 8.4).
|
||||
top = entry.results["by_category"]["Top"]
|
||||
assert top and top[0]["team_id"] == "t1"
|
||||
assert "competence_score" in top[0]
|
||||
assert "role_score" in top[0]
|
||||
assert "overall_score" in top[0]
|
||||
|
||||
|
||||
def test_team_search_cache_roundtrip_llm_fulltext_mode() -> None:
|
||||
"""``team_search`` LLM-mode payloads survive a cache round trip
|
||||
including the ``rationale`` field used by ``Begründung``."""
|
||||
cache = SearchCache(ttl_minutes=60, max_size=10)
|
||||
payload = _team_payload("llm_fulltext")
|
||||
|
||||
search_id = cache.store_search(
|
||||
task_id=None,
|
||||
requirements={"role_name": "Backend Developer",
|
||||
"competences": ["python"]},
|
||||
results=payload,
|
||||
)
|
||||
|
||||
entry = cache.get(search_id)
|
||||
assert entry is not None
|
||||
assert entry.results["search_type"] == "team_search"
|
||||
assert entry.results["matching_method"] == "llm_fulltext"
|
||||
# ``errors`` list is preserved for the LLM-fulltext path
|
||||
# (Anforderung 7.6).
|
||||
assert entry.results.get("errors") == []
|
||||
top = entry.results["by_category"]["Top"]
|
||||
assert top and top[0]["team_id"] == "t1"
|
||||
assert top[0]["category"] == "Top"
|
||||
assert top[0]["rationale"] == "Backend match."
|
||||
|
||||
|
||||
def test_team_search_filter_ids_increment_independently() -> None:
|
||||
"""``add_filter`` produces incrementing ids for ``team_search``
|
||||
entries just like for capacity searches.
|
||||
"""
|
||||
cache = SearchCache(ttl_minutes=60, max_size=10)
|
||||
search_id = cache.store_search(
|
||||
task_id=None,
|
||||
requirements={"role_name": "Backend Developer",
|
||||
"competences": ["python"]},
|
||||
results=_team_payload("score"),
|
||||
)
|
||||
|
||||
fid1 = cache.add_filter(
|
||||
search_id,
|
||||
filtered_results={"by_category": {}, "search_type": "team_search",
|
||||
"matching_method": "score"},
|
||||
filter_meta={"role_filter": "Backend"},
|
||||
)
|
||||
fid2 = cache.add_filter(
|
||||
search_id,
|
||||
filtered_results={"by_category": {}, "search_type": "team_search",
|
||||
"matching_method": "score"},
|
||||
filter_meta={"role_filter": "Frontend"},
|
||||
)
|
||||
|
||||
assert fid1 == "filter-1"
|
||||
assert fid2 == "filter-2"
|
||||
|
||||
entry = cache.get(search_id)
|
||||
assert entry is not None
|
||||
# The filter payloads keep the team_search marker independent of
|
||||
# the parent entry, so downstream consumers can distinguish them.
|
||||
for fid in (fid1, fid2):
|
||||
fdata = entry.filters[fid]
|
||||
assert fdata["results"]["search_type"] == "team_search"
|
||||
assert fdata["results"]["matching_method"] == "score"
|
||||
|
||||
Reference in New Issue
Block a user