# 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]}" )