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,101 @@
|
||||
# Feature: llm-batch-matching, Property 5: Korrekte Zuordnung (Response-Mapping)
|
||||
"""Property-based test for correct response-to-candidate mapping in batch matching.
|
||||
|
||||
For any list of capacities with item_id-specific category assignments, each
|
||||
item must land in exactly the category that the mock LLM returned for it.
|
||||
This validates that concurrent execution does not mix up responses between
|
||||
candidates.
|
||||
|
||||
**Validates: Requirements 3.4**
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from hypothesis import given, settings
|
||||
from hypothesis import strategies as st
|
||||
|
||||
from teamlandkarte_mcp.matching.llm_fulltext_matcher import LlmFulltextMatcher
|
||||
from teamlandkarte_mcp.matching.profiles import TaskProfile
|
||||
from teamlandkarte_mcp.models import Capacity
|
||||
|
||||
_ID_RE = re.compile(r"ID:\s*(\S+)")
|
||||
|
||||
_CATEGORIES: tuple[str, ...] = ("Top", "Good", "Partial", "Low", "Irrelevant")
|
||||
|
||||
|
||||
class _MapLlm:
|
||||
def __init__(self, id_to_category: dict[str, str]) -> None:
|
||||
self._map = id_to_category
|
||||
|
||||
async def chat_completion(self, system: str, user: str) -> str:
|
||||
match = _ID_RE.search(user)
|
||||
cap_id = match.group(1) if match else "unknown"
|
||||
category = self._map.get(cap_id, "Irrelevant")
|
||||
await asyncio.sleep(0.005) # simulate I/O
|
||||
return json.dumps({"category": category, "rationale": f"r{cap_id}"})
|
||||
|
||||
|
||||
class _StubDb:
|
||||
def batch_get_capacity_descriptions(self, capacity_ids: list[Any]) -> dict[str, str | None]:
|
||||
return {str(i): None for i in capacity_ids}
|
||||
|
||||
def batch_get_capacity_certificates(self, capacity_ids: list[Any]) -> dict[str, list[str]]:
|
||||
return {str(i): [] for i in capacity_ids}
|
||||
|
||||
def batch_get_capacity_references(self, capacity_ids: list[Any]) -> dict[str, list[dict]]:
|
||||
return {str(i): [] for i in capacity_ids}
|
||||
|
||||
|
||||
def _make_capacity(cap_id: int) -> Capacity:
|
||||
return Capacity(
|
||||
id=cap_id,
|
||||
owner_name=f"o{cap_id}",
|
||||
role_name="R",
|
||||
role_level=None,
|
||||
begin_date=None,
|
||||
end_date=None,
|
||||
competences=[],
|
||||
)
|
||||
|
||||
|
||||
@settings(max_examples=100, deadline=None)
|
||||
@given(
|
||||
ids=st.lists(st.integers(min_value=1, max_value=99), min_size=2, max_size=15, unique=True),
|
||||
max_concurrency=st.integers(min_value=1, max_value=5),
|
||||
data=st.data(),
|
||||
)
|
||||
def test_batch_correct_mapping(
|
||||
ids: list[int], max_concurrency: int, data: st.DataObject
|
||||
) -> None:
|
||||
"""Property 5: each item lands in exactly the category the mock assigned."""
|
||||
id_to_category = {str(i): data.draw(st.sampled_from(_CATEGORIES)) for i in ids}
|
||||
capacities = [_make_capacity(i) for i in ids]
|
||||
task_profile = TaskProfile(id="t", title="T", description="D", skills=[])
|
||||
|
||||
matcher = LlmFulltextMatcher(
|
||||
db=_StubDb(), client=_MapLlm(id_to_category), max_concurrency=max_concurrency,
|
||||
)
|
||||
result = asyncio.run(
|
||||
matcher.match_capacities(task_profile=task_profile, capacities=capacities)
|
||||
)
|
||||
|
||||
# Build a mapping from item_id to actual category in the result
|
||||
actual_mapping: dict[str, str] = {}
|
||||
for cat, items in result.by_category.items():
|
||||
for it in items:
|
||||
actual_mapping[it.item_id] = cat
|
||||
|
||||
# Assert: every capacity ended up in the correct category
|
||||
for cap_id_str, expected_cat in id_to_category.items():
|
||||
assert cap_id_str in actual_mapping, (
|
||||
f"Capacity {cap_id_str} not found in any category"
|
||||
)
|
||||
assert actual_mapping[cap_id_str] == expected_cat, (
|
||||
f"Capacity {cap_id_str}: expected {expected_cat}, "
|
||||
f"got {actual_mapping[cap_id_str]}"
|
||||
)
|
||||
Reference in New Issue
Block a user