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:
2026-06-30 20:39:52 +02:00
parent 2f2b295531
commit a5f8fb49ab
1717 changed files with 447332 additions and 0 deletions
@@ -0,0 +1,188 @@
# Feature: llm-fulltext-matching, Property 2b:
# Referenzen mit leerem Partner-Name behalten projects, ohne Partner-Token
"""Property-based tests for reference rendering with empty partner names.
These tests cover the deterministic serializer
``serialize_capacity_profile`` defined in
``teamlandkarte_mcp.matching.profiles``. They focus specifically on the
``Referenzen:`` block and verify three invariants for any list of
:class:`CapacityReferenceEntry` values that mixes empty and non-empty
``partner_name`` values:
(a) The number of rendered reference lines equals the number of input
entries with non-whitespace ``projects``.
(b) Each line that corresponds to an entry with empty ``partner_name``
starts with ``Projekte:`` and does NOT contain a ``Partner:`` token.
(c) Each line that corresponds to an entry with non-empty
``partner_name`` contains both ``Partner: <name>`` and
``Projekte: <projects>``.
Because the serializer classifies entries via ``if entry.partner_name:``
(truthiness), this test mirrors that exact logic and uses
``bool(entry.partner_name)`` to decide whether the ``Partner:`` token is
expected.
The strategies deliberately exclude line-break-like characters
(everything ``str.splitlines`` treats as a line boundary) so the
line-based extraction of the rendered ``Referenzen:`` block remains
unambiguous. The property under test is the *content* of the rendered
references, not how the serializer would behave on text containing
literal newlines.
**Validates: Requirements 2.8, 3.4, 3.6**
"""
from __future__ import annotations
from hypothesis import given, settings
from hypothesis import strategies as st
from teamlandkarte_mcp.matching.profiles import (
CapacityProfile,
CapacityReferenceEntry,
serialize_capacity_profile,
)
# ---------------------------------------------------------------------------
# Strategies
# ---------------------------------------------------------------------------
# Alphabet for partner_name/projects: any unicode codepoint that
# ``str.splitlines`` does NOT treat as a line boundary, so the rendered
# output remains a stable, single-line-per-entry block. We exclude:
#
# * ``Cs`` (surrogates) — invalid in well-formed strings.
# * ``Cc`` (control characters) — includes ``\n``, ``\r``, ``\v``,
# ``\f``, ``\x1c``-``\x1f``, ``\x85`` etc.
# * ``Zl`` (line separator, ``\u2028``).
# * ``Zp`` (paragraph separator, ``\u2029``).
_safe_chars = st.characters(
blacklist_categories=("Cs", "Cc", "Zl", "Zp"),
)
# ``partner_name``: either empty (NULL ``partner_id`` or join mismatch,
# see requirement 2.8) or a short non-empty string. The serializer uses
# truthy-checking, so any non-empty string (including whitespace-only)
# counts as "has partner" — we keep this strategy aligned with that
# logic and classify entries by ``bool(entry.partner_name)`` in the
# assertions below.
_partner_name = st.one_of(
st.just(""),
st.text(alphabet=_safe_chars, min_size=1, max_size=20),
)
# ``projects``: non-whitespace text so the serializer never drops the
# entry defensively. This lets us assert the stronger invariant
# "input entry count == rendered line count".
_projects = st.text(
alphabet=_safe_chars, min_size=1, max_size=30
).filter(lambda s: s.strip() != "")
_reference = st.builds(
CapacityReferenceEntry,
partner_name=_partner_name,
projects=_projects,
)
_reference_list = st.lists(_reference, min_size=1, max_size=8)
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _extract_reference_lines(serialized: str) -> list[str]:
"""Extract the rendered reference lines from a serialized profile.
The serializer renders the ``Referenzen:`` block as a heading line
followed by either a bullet list (one ``- <line>`` per entry) or
`` (keine)`` on the same line as the heading. The next field
heading is always ``Zertifikate:`` at the start of its own line,
which we use as the terminator. The leading ``- `` prefix is
stripped from each line so callers can assert directly on the
rendered content.
"""
lines: list[str] = []
in_references = False
for raw_line in serialized.splitlines():
if raw_line.startswith("Zertifikate:"):
break
if raw_line.startswith("Referenzen:"):
in_references = True
continue
if in_references and raw_line.startswith("- "):
lines.append(raw_line[2:])
return lines
# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------
@settings(max_examples=100, deadline=None)
@given(refs=_reference_list)
def test_references_with_empty_partner_name_keep_projects_without_partner_token(
refs: list[CapacityReferenceEntry],
) -> None:
"""Property 2b: empty ``partner_name`` keeps ``projects`` without token.
For any list of :class:`CapacityReferenceEntry` values with mixed
``partner_name``:
(a) The number of rendered reference lines equals the number of
input entries (the strategy guarantees non-whitespace
``projects``, so no entry is dropped).
(b) Every line for an entry with empty ``partner_name`` starts with
``Projekte:`` and contains no ``Partner:`` token.
(c) Every line for an entry with non-empty ``partner_name`` contains
both ``Partner: <name>`` and ``Projekte: <projects>``.
"""
profile = CapacityProfile(
id="x",
owner_name="o",
role_name="r",
competences=[],
description="",
references=list(refs),
certificates=[],
)
serialized = serialize_capacity_profile(profile)
lines = _extract_reference_lines(serialized)
# (a) One rendered line per input entry, in input order.
assert len(lines) == len(refs), (
f"Expected {len(refs)} reference lines, got {len(lines)}: "
f"{lines!r}"
)
# (b) and (c): per-entry assertions, mirroring the serializer's
# ``if entry.partner_name:`` truthiness check.
for entry, line in zip(refs, lines):
projects_clean = entry.projects.strip()
if entry.partner_name:
# (c) Non-empty partner name → both tokens present.
assert f"Partner: {entry.partner_name}" in line, (
f"Expected 'Partner: {entry.partner_name}' in line: "
f"{line!r}"
)
assert f"Projekte: {projects_clean}" in line, (
f"Expected 'Projekte: {projects_clean}' in line: "
f"{line!r}"
)
else:
# (b) Empty partner name → starts with ``Projekte:`` and no
# ``Partner:`` token at all.
assert line.startswith("Projekte:"), (
f"Expected line to start with 'Projekte:', got: {line!r}"
)
assert "Partner:" not in line, (
f"Did not expect 'Partner:' token in line: {line!r}"
)
assert line == f"Projekte: {projects_clean}", (
f"Expected exact 'Projekte: {projects_clean}', got: "
f"{line!r}"
)