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.
115 lines
3.0 KiB
Python
115 lines
3.0 KiB
Python
import datetime as dt
|
|
|
|
import pytest
|
|
|
|
from teamlandkarte_mcp.matching.matcher import Matcher
|
|
from teamlandkarte_mcp.matching.scorer import categorize
|
|
from teamlandkarte_mcp.mcp_server import _calculate_overlap_percentage, _task_role_text
|
|
from teamlandkarte_mcp.models import Capacity, Requirements
|
|
|
|
|
|
def test_task_role_text_title_first_fallback_to_description() -> None:
|
|
assert _task_role_text("DevOps Engineer", "foo") == "DevOps Engineer"
|
|
assert _task_role_text(" ", "Backend Developer") == "Backend Developer"
|
|
assert _task_role_text(None, " Backend Developer ") == "Backend Developer"
|
|
|
|
|
|
def test_overlap_percentage_basic() -> None:
|
|
ref_start = dt.date(2025, 3, 1)
|
|
ref_end = dt.date(2025, 6, 30)
|
|
|
|
other_start = dt.date(2025, 3, 15)
|
|
other_end = dt.date(2025, 6, 30)
|
|
|
|
# 2025-03-01..2025-06-30 inclusive is 122 days; overlap is 108 days.
|
|
assert (
|
|
_calculate_overlap_percentage(
|
|
ref_start=ref_start,
|
|
ref_end=ref_end,
|
|
other_start=other_start,
|
|
other_end=other_end,
|
|
)
|
|
== "89%"
|
|
)
|
|
|
|
|
|
def test_overlap_percentage_open_ended_other_end() -> None:
|
|
ref_start = dt.date(2025, 3, 1)
|
|
ref_end = dt.date(2025, 3, 31)
|
|
|
|
other_start = dt.date(2025, 3, 15)
|
|
other_end = None
|
|
|
|
assert (
|
|
_calculate_overlap_percentage(
|
|
ref_start=ref_start,
|
|
ref_end=ref_end,
|
|
other_start=other_start,
|
|
other_end=other_end,
|
|
)
|
|
== "55%"
|
|
)
|
|
|
|
|
|
def test_categorize_includes_irrelevant() -> None:
|
|
class _T:
|
|
top = 0.8
|
|
good = 0.6
|
|
partial = 0.4
|
|
low = 0.2
|
|
|
|
class _Cfg:
|
|
thresholds = _T()
|
|
|
|
assert categorize(0.19, _Cfg()) == "Irrelevant"
|
|
assert categorize(0.20, _Cfg()) == "Low"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_matcher_includes_irrelevant_bucket(monkeypatch) -> None:
|
|
# Arrange: Build a matcher with a fake similarity engine that returns 0 scores.
|
|
class _FakeSim:
|
|
use_bm25_search = False # tells Matcher not to build a global index
|
|
|
|
async def compute_competence_similarity(self, required, present, global_index=None):
|
|
return {r: {"score": 0.0} for r in required}
|
|
|
|
async def compute_role_similarity(self, required_role, cap_role):
|
|
return 0.0
|
|
|
|
class _Thresh:
|
|
top = 0.8
|
|
good = 0.6
|
|
partial = 0.4
|
|
low = 0.2
|
|
|
|
class _Cfg:
|
|
competence_weight = 0.8
|
|
role_weight = 0.2
|
|
thresholds = _Thresh()
|
|
|
|
matcher = Matcher(_FakeSim(), _Cfg())
|
|
|
|
cap = Capacity(
|
|
id=1,
|
|
owner_name="A",
|
|
role_name="X",
|
|
role_level=None,
|
|
begin_date=dt.date(2025, 1, 1),
|
|
end_date=dt.date(2025, 12, 31),
|
|
competences=["a"],
|
|
)
|
|
req = Requirements(
|
|
role_name="Y",
|
|
competences=["b"],
|
|
date_start=dt.date(2025, 1, 1),
|
|
date_end=dt.date(2025, 1, 2),
|
|
)
|
|
|
|
# Act
|
|
res = await matcher.match([cap], req)
|
|
|
|
# Assert
|
|
assert "Irrelevant" in res.by_category
|
|
assert len(res.by_category["Irrelevant"]) == 1
|