Files
Orchestrator/bahn/teamlandkarte-mcp/tests/test_batch_completeness_pbt.py
T
ankn a5f8fb49ab 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.
2026-06-30 20:39:52 +02:00

122 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature: llm-batch-matching, Property 1: Vollständigkeit der Ergebnisse
"""Property-based test for result completeness (partition) in batch matching.
For any list of capacities and any mix of LLM successes/failures, the total
number of items across all ``by_category`` buckets plus the number of entries
in ``errors`` must equal the number of input candidates. No candidate may be
lost or duplicated.
The fake LLM is driven by a Hypothesis-generated boolean mask: position ``i``
decides whether the call for capacity ``i+1`` raises ``AzureAPIError``
(``True``) or returns a valid JSON payload (``False``).
**Validates: Requirements 1.1, 1.4, 3.2, 4.2, 4.3**
"""
from __future__ import annotations
import asyncio
import json
from typing import Any
from hypothesis import given, settings
from hypothesis import strategies as st
from teamlandkarte_mcp.azure.openai_client import AzureAPIError
from teamlandkarte_mcp.matching.llm_fulltext_matcher import LlmFulltextMatcher
from teamlandkarte_mcp.matching.profiles import TaskProfile
from teamlandkarte_mcp.models import Capacity
class _MaskLlm:
"""Returns valid JSON for mask[i] is False; raises AzureAPIError for True."""
def __init__(self, mask: list[bool]) -> None:
self._mask = mask
self._call_index = 0
async def chat_completion(self, system: str, user: str) -> str:
idx = self._call_index
self._call_index += 1
if self._mask[idx]:
raise AzureAPIError("simulated")
return json.dumps({"category": "Top", "rationale": "ok"})
class _StubDb:
"""Minimal DBClient double returning empty extras for every id."""
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_capacities(n: int) -> list[Capacity]:
return [
Capacity(
id=i + 1,
owner_name=f"o{i + 1}",
role_name="R",
role_level=None,
begin_date=None,
end_date=None,
competences=[],
)
for i in range(n)
]
@settings(max_examples=100, deadline=None)
@given(mask=st.lists(st.booleans(), min_size=1, max_size=20))
def test_batch_completeness_partition(mask: list[bool]) -> None:
"""Property 1: all input candidates appear exactly once in output.
The sum of items across all by_category buckets plus the number of
errors must equal the number of input capacities. Additionally, all
item_ids across by_category and errors must be unique (no duplicates).
"""
matcher = LlmFulltextMatcher(db=_StubDb(), client=_MaskLlm(mask))
capacities = _make_capacities(len(mask))
task_profile = TaskProfile(id="t", title="T", description="D", skills=[])
result = asyncio.run(
matcher.match_capacities(
task_profile=task_profile,
capacities=capacities,
)
)
# Collect all item_ids from by_category
category_ids: list[str] = []
for items in result.by_category.values():
for it in items:
category_ids.append(it.item_id)
# Collect all item_ids from errors
error_ids: list[str] = [e.item_id for e in result.errors]
# Property: completeness no candidate lost
total = len(category_ids) + len(error_ids)
assert total == len(capacities), (
f"Expected {len(capacities)} total items, got {total} "
f"({len(category_ids)} categorized + {len(error_ids)} errors)"
)
# Property: no duplicates all item_ids are unique
all_ids = category_ids + error_ids
assert len(all_ids) == len(set(all_ids)), (
f"Duplicate item_ids detected: {len(all_ids)} total vs "
f"{len(set(all_ids))} unique"
)