Files
Orchestrator/bahn/teamlandkarte-mcp/.kiro/specs/llm-fulltext-matching/tasks.md
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

385 lines
25 KiB
Markdown
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.
# Implementation Plan: LLM-Volltext-Matching als zweites Verfahren
## Overview
Inkrementelle Einführung des neuen `llm_fulltext`-Matching-Verfahrens neben dem bestehenden `score`-Verfahren. Die Reihenfolge stellt sicher, dass keine Zwischenstände kaputt sind: zuerst Datenbankschicht (DBClient/TrinoClient) erweitern, dann Datenmodelle (`CapacityProfile`/`TaskProfile`) und der `LlmFulltextMatcher`, anschließend Konfiguration und MCP-Tool-Integration, am Ende Cache-/Tabellenausgabe sowie Anpassungen an `get_results_by_category`/`filter_search_results` und Dokumentation.
## Tasks
- [x] 1. Datenbankschicht für Capacity-Volltext-Felder erweitern
- [x] 1.1 Neue Methoden im `DBClient`-Protokoll deklarieren (`database/types.py`)
- `CapacityReferenceRow` als `TypedDict` mit Feldern `partner_name: str` und `projects: str` im Protokoll-Modul (bzw. unter `database/types.py`) deklarieren; `partner_name` darf leer sein (NULL `partner_id` oder Join-Mismatch, Anforderung 2.8)
- `get_capacity_description(capacity_id) -> str | None`
- `get_capacity_certificates(capacity_id) -> list[str]`
- `get_capacity_references(capacity_id) -> list[CapacityReferenceRow]`
- `batch_get_capacity_descriptions(capacity_ids) -> dict[str, str | None]`
- `batch_get_capacity_certificates(capacity_ids) -> dict[str, list[str]]`
- `batch_get_capacity_references(capacity_ids) -> dict[str, list[CapacityReferenceRow]]`
- Docstrings gemäß Design (Quelle, Join-Verhalten inkl. Partner-Join `partner_id = id`, Rückgabetyp bei leerer Eingabe, leerer `partner_name` bei NULL/Mismatch)
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.8_
- [x] 1.2 Einzel-Methoden im `TrinoClient` implementieren (`database/trino_client.py`)
- SELECT-Only-Statements mit `_ensure_select_only`, Connection-Pool und `_retry`
- `get_capacity_description`: NULL/leerer String → `None`
- `get_capacity_certificates`: Join über `capacity_id`, leere Strings filtern, Rückgabe `[]` wenn keine Treffer
- `get_capacity_references`: zusätzlich `LEFT JOIN teamlandkarte_v_partners_latest p ON r.partner_id = p.id` und `COALESCE(p.name, '') AS partner_name` in derselben Abfrage; Rückgabe als `CapacityReferenceRow`-Liste mit `partner_name` und `projects`
- NULL `partner_id` bzw. Join-Mismatch → leerer `partner_name`, Referenz bleibt mit `projects` erhalten (Anforderung 2.8)
- Parameter-Bindung gegen SQL-Injection
- _Requirements: 2.1, 2.2, 2.3, 2.5, 2.6, 2.7, 2.8, 2.9, 2.10_
- [x] 1.3 Batch-Methoden im `TrinoClient` implementieren
- Genau **eine** SQL-Abfrage pro Quelle (Anforderung 2.4); der Partner-LEFT-JOIN ist Bestandteil derselben Referenz-Abfrage und erzeugt keinen zusätzlichen Roundtrip
- `batch_get_capacity_references`: `SELECT r.capacity_id, r.projects, COALESCE(p.name, '') AS partner_name ... LEFT JOIN teamlandkarte_v_partners_latest p ON r.partner_id = p.id`
- Schlüssel der Rückgaben sind `str(capacity_id)` (deterministisch)
- Fehlende IDs als `None` (Beschreibungen) bzw. `[]` (Listen) vorbelegen
- Gruppierung n:1 via `defaultdict(list)`, leere `projects`-Strings filtern; leerer `partner_name` (NULL/Mismatch) führt nicht zum Verwerfen der Referenz
- Frühe Rückkehr bei leerer Eingabeliste (`{}`)
- _Requirements: 2.4, 2.5, 2.6, 2.7, 2.8, 2.9, 2.10_
- [x] 1.4 Unit-Tests für die neuen DB-Methoden mit Mock-Cursor*
- Genau eine SQL-Abfrage je Methode wird abgesetzt; LEFT JOIN auf `teamlandkarte_v_partners_latest` ist Bestandteil derselben Referenz-Abfrage (kein zusätzlicher Roundtrip)
- SELECT-Only-Guard wird angewendet
- Korrekte Gruppierung n:1 in den Batch-Varianten
- Mock-Cursor liefert für `references` drei Spalten (`capacity_id`, `projects`, `partner_name`); Test inkl. Fall NULL `partner_id` → leerer `partner_name`, Referenz bleibt erhalten
- Defaults für fehlende IDs (None / [])
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8_
- [x] 2. Datenmodelle `CapacityProfile` und `TaskProfile`
- [x] 2.1 Neue frozen Dataclasses anlegen (`matching/profiles.py`)
- `CapacityReferenceEntry` als `@dataclass(frozen=True)` mit Feldern `partner_name: str` und `projects: str` (`partner_name` darf leer sein, vgl. Anforderung 2.8 / 3.2)
- `CapacityProfile` mit Feldern `id`, `owner_name`, `role_name`, `competences`, `description`, `references: list[CapacityReferenceEntry]`, `certificates`
- `TaskProfile` mit Feldern `id`, `title`, `description`, `skills`
- Beide `@dataclass(frozen=True)` und nur primitive Felder bzw. `list[str]` / `list[CapacityReferenceEntry]`
- _Requirements: 3.1, 3.2, 4.1_
- [x] 2.2 Profil-Builder implementieren (`matching/profiles.py`)
- `build_capacity_profile(capacity, *, description, certificates, references) -> CapacityProfile`
- Konvertiere `CapacityReferenceRow`-Einträge aus dem DBClient in `CapacityReferenceEntry`-Instanzen; ein leerer `partner_name` führt nicht zum Verwerfen der Referenz (Anforderung 3.4)
- `build_task_profile(task) -> TaskProfile` (alternativ `build_task_profile_from_requirements(...)` für Suchrichtung Aufgabe→Kapazität)
- Leere/`None`-Felder → leerer String bzw. leere Liste, Profil wird nie verworfen
- Reihenfolge der Felder ist über alle Profile konstant
- _Requirements: 3.1, 3.2, 3.4, 4.1, 4.2_
- [x] 2.3 Deterministische Profil-Serialisierung implementieren
- `serialize_capacity_profile(profile) -> str` mit fixer Reihenfolge `Rolle:`, `Kompetenzen:`, `Beschreibung:`, `Referenzen:`, `Zertifikate:`
- `_format_reference(entry: CapacityReferenceEntry) -> str`: bei nicht-leerem `partner_name``Partner: <partner_name> Projekte: <projects>`; bei leerem `partner_name``Projekte: <projects>` (kein Platzhalter, kein `Partner:`-Token)
- `serialize_task_profile(profile) -> str` mit fixer Reihenfolge `Titel:`, `Beschreibung:`, `Gesuchte Kompetenzen:`
- Leere Listen werden als `(keine)` ausgegeben, alle Feldüberschriften erscheinen immer; Reihenfolge der Felder und Referenzen bleibt deterministisch
- _Requirements: 3.3, 3.4, 3.6, 3.7, 4.3, 4.4_
- [x] 2.4 Property-Test für deterministische Serialisierung schreiben
- **Property 1: Profil-Serialisierung ist deterministisch und feldvollständig**
- Hypothesis mit `@settings(max_examples=100)`, `st.builds(...)` für Profile inkl. `CapacityReferenceEntry` mit/ohne `partner_name`
- Zwei Aufrufe liefern identischen String; alle Feldüberschriften enthalten; Partner-Name erscheint deterministisch in der serialisierten Ausgabe, sofern nicht leer
- **Validates: Requirements 3.3, 3.4, 3.6, 3.7, 4.3, 4.4**
- [x] 2.5 Property-Test für Profil-Builder mit leeren Feldern schreiben
- **Property 2: Leere/None-Felder verwerfen das Profil nicht**
- Capacity-/Task-Strategien mit `st.one_of(st.none(), st.text())`; Referenzen-Strategie mit gemischtem `partner_name` (`st.one_of(st.just(""), st.text(min_size=1))`)
- Profil enthält leere Strings/Listen; Serialisierung enthält weiterhin alle Überschriften; auch Referenzen mit leerem `partner_name` werden nicht verworfen
- **Validates: Requirements 3.2, 3.4, 4.2**
- [x] 2.6 Property-Test für Referenzen mit leerem Partner-Name schreiben
- **Property 2b: Referenzen mit leerem Partner_Name behalten projects, ohne Partner-Token**
- Hypothesis-Generator mischt leere und nicht-leere `partner_name`-Werte: `st.lists(st.builds(CapacityReferenceEntry, partner_name=st.one_of(st.just(""), st.text(min_size=1)), projects=st.text(min_size=1)))`
- Prüft (a) die Anzahl der Referenz-Zeilen entspricht der Anzahl der Einträge mit nicht-leerem `projects`, (b) jede Zeile zu einem Eintrag mit leerem `partner_name` beginnt mit `Projekte:` und enthält keinen `Partner:`-Token, (c) jede Zeile zu einem Eintrag mit nicht-leerem `partner_name` enthält sowohl `Partner: <name>` als auch `Projekte: <projects>`
- **Validates: Requirements 2.8, 3.4, 3.6**
- [x] 3. `LlmFulltextMatcher`-Komponente bauen
- [x] 3.1 Modul-Skelett `matching/llm_fulltext_matcher.py` anlegen
- Konstanten `_ALLOWED_CATEGORIES = ("Top", "Good", "Partial", "Low", "Irrelevant")` und `_ALIAS`
- Dataclasses `LlmFulltextItem`, `LlmFulltextError`, `LlmFulltextResult`
- Klasse `LlmFulltextMatcher` mit Konstruktor `(*, db, client, rationale_max_chars=280)`
- _Requirements: 5.1, 5.3, 6.1, 6.3, 7.4_
- [x] 3.2 Kategorie-Normalisierung implementieren
- `normalize_category(value) -> tuple[str, bool]`
- Trim + lowercase, Mapping über `_ALIAS`, ungültige Werte → `("Irrelevant", False)`
- Nicht-String-Eingaben → `("Irrelevant", False)`
- _Requirements: 5.3, 5.6, 6.3, 6.6_
- [x] 3.3 Property-Test für Kategorie-Normalisierung schreiben
- **Property 3: Kategorienormalisierung bildet auf erlaubte Menge ab**
- Hypothesis mit `st.text()` und Aliase via `st.sampled_from`
- Output immer in erlaubter Menge; `is_valid` korrekt
- **Validates: Requirements 5.3, 5.6, 6.3, 6.6**
- [x] 3.4 LLM-Aufruf und Antwort-Parsing implementieren
- System-Prompt gemäß Design (deutschsprachig, deterministisch, JSON-only)
- User-Prompt baut auf `serialize_task_profile`/`serialize_capacity_profile` auf
- `chat_completion(system, user, response_format=json_object)` aufrufen
- JSON parsen, Felder `category` und `rationale` extrahieren
- Bei `JSONDecodeError``LlmFulltextError` mit Meldung `"invalid JSON: <excerpt>"`
- Bei ungültiger Kategorie → Item nach `Irrelevant` mit Hinweis `[Hinweis: ungültige LLM-Kategorie: <wert>]` an Rationale anhängen
- Bei `AzureAPIError`/Timeout/sonstiger Exception → `LlmFulltextError` mit Klassenname + erstem Satz
- _Requirements: 5.4, 5.5, 5.6, 5.7, 6.4, 6.5, 6.6, 6.7_
- [x] 3.5 `match_capacities` implementieren
- Eingabe: `task_profile`, `capacities` (bereits vorgefiltert)
- Capacity-IDs sammeln, `batch_get_capacity_descriptions/certificates/references` aufrufen
- Pro Kapazität `CapacityProfile` bauen und LLM-Aufruf durchführen
- Erfolgreiche Items in `by_category` einsortieren, fehlerhafte in `errors`
- Innerhalb jeder Kategorie deterministisch nach `item_id` aufsteigend (lexikographisch) sortieren
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.7, 5.8_
- [x] 3.6 `match_tasks` implementieren
- Eingabe: `capacity_profile`, `tasks`
- Pro Aufgabe `TaskProfile` bauen und LLM-Aufruf durchführen
- Sortierung primär nach Kategorie, sekundär nach `task_id` aufsteigend
- _Requirements: 6.1, 6.2, 6.3, 6.4, 6.5, 6.7, 6.8_
- [x] 3.7 Property-Test für ungültige LLM-Kategorie schreiben
- **Property 4: Ungültige LLM-Kategorie wird auf Irrelevant gemappt**
- Mock-LLM gibt zufällige ungültige Kategorie zurück
- Item landet in `Irrelevant`, Rationale enthält Originaltext + Hinweis
- **Validates: Requirements 5.6, 6.6**
- [x] 3.8 Property-Test für LLM-Fehler-Trennung schreiben
- **Property 5: LLM-Fehler erscheinen in der Fehlerliste, nicht als Ergebnis**
- Hypothesis-Strategy `st.lists(st.booleans())` als Fehlermaske
- Items aus Fehlermaske erscheinen ausschließlich in `result.errors`
- **Validates: Requirements 5.7, 6.7**
- [x] 3.9 Property-Test für deterministische Sortierung schreiben
- **Property 6: Ergebnisse sind innerhalb jeder Kategorie deterministisch sortiert**
- Permutationen der Eingabeliste erzeugen identische `by_category`-Reihenfolge
- **Validates: Requirements 5.8, 6.8**
- [x] 3.10 Unit-Tests für `LlmFulltextMatcher` mit gemocktem `AzureOpenAIClient`*
- Erfolgsfall (gültige Kategorie + Rationale)
- Ungültiges JSON
- Gültiges JSON mit unbekannter Kategorie
- `chat_completion` wirft `AzureAPIError`
- Vermischung mehrerer Items: Reihenfolge und Sortierung korrekt
- _Requirements: 5.4, 5.5, 5.6, 5.7, 5.8, 6.4, 6.5, 6.6, 6.7, 6.8_
- [x] 4. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 5. Konfiguration `matching.default_method` einführen
- [x] 5.1 `MatchingConfig` um Feld `default_method: str = "score"` erweitern (`config.py`)
- Validierung in `load_config`: Wert muss `"score"` oder `"llm_fulltext"` sein
- Bei ungültigem Wert `ConfigError` mit beschreibender Meldung werfen
- Fehlender Schlüssel → Default `"score"`
- _Requirements: 12.2, 12.3, 12.4_
- [x] 5.2 `config.toml` und `config.toml.example` aktualisieren
- Neuer Abschnitt `[matching].default_method = "score"` mit Kommentar zu erlaubten Werten
- _Requirements: 12.2, 12.3_
- [x] 5.3 Unit-Tests für Konfigurations-Validierung*
- `default_method` fehlt → Default `"score"`
- `default_method = "llm_fulltext"` → übernommen
- `default_method = "irgendwas"``ConfigError`
- _Requirements: 12.3, 12.4_
- [x] 6. MCP-Tool-Integration für `matching_method`
- [x] 6.1 Hilfsfunktion `_resolve_matching_method` im `mcp_server.py` implementieren
- `None``cfg.matching.default_method`
- Trim + lowercase, gegen `("score", "llm_fulltext")` validieren
- Bei ungültigem Wert `ValueError` mit beiden erlaubten Werten in der Meldung werfen
- _Requirements: 1.1, 1.2, 1.5, 12.3_
- [x] 6.2 `find_matching_capacities` um `matching_method` erweitern
- Neuer Parameter `matching_method: Optional[str] = None`
- Validierung **vor** DB-/LLM-Aufrufen, bei Fehler frühe Markdown-Fehlermeldung mit erlaubten Werten zurückgeben
- Routing: `"score"` → bestehender `Matcher`-Pfad; `"llm_fulltext"``LlmFulltextMatcher.match_capacities`
- Im LLM-Modus Vorfilter (z. B. Verfügbarkeit) identisch zum Score-Modus anwenden
- Task_Profile aus aktuell bestätigtem Anforderungs-Set bauen
- _Requirements: 1.1, 1.3, 1.4, 1.5, 5.1, 5.2_
- [x] 6.3 `find_matching_tasks` um `matching_method` erweitern
- Neuer Parameter `matching_method: Optional[str] = None`
- Routing analog 6.2
- Im LLM-Modus für die `capacity_id` Beschreibung, Zertifikate und Referenzen aus DB laden und ins `CapacityProfile` einbeziehen
- _Requirements: 1.1, 1.3, 1.4, 1.5, 6.1, 6.2_
- [x] 6.4 Docstrings für betroffene MCP-Tools aktualisieren
- `find_matching_capacities`, `find_matching_tasks`: Parameter `matching_method` mit erlaubten Werten dokumentieren
- Hinweis auf Unterschiede in Ausgabe-Schema (keine Score-Spalten im LLM-Modus, `Begründung`-Spalte)
- _Requirements: 12.1_
- [x] 6.5 Property-Test für `matching_method`-Validierung schreiben
- **Property 9: matching_method-Validierung lehnt unbekannte Werte ab**
- Hypothesis-Strategy `st.text()`
- Bei ungültiger Eingabe enthält die Fehlermeldung beide erlaubten Werte; DB- und LLM-Mocks werden nicht aufgerufen
- **Validates: Requirements 1.5**
- [x] 6.6 Unit-Tests für Routing in beiden MCP-Tools
- `matching_method=None` → Default greift, Schema entspricht Score-Modus
- `matching_method="llm_fulltext"``LlmFulltextMatcher` wird aufgerufen, Score-Pfad nicht
- `matching_method="bogus"` → Fehlermeldung, weder DB noch LLM werden aufgerufen
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5_
- [x] 7. SearchCache-Payload und META-JSON
- [x] 7.1 Persistenz im SearchCache an LLM-Modus anpassen
- `matching_method` als Schlüssel im Payload aufnehmen (auch im Score-Modus)
- Im LLM-Modus pro Item Felder `category` und `rationale` (ungekürzt) speichern, **keine** `role_score`/`competence_score`/`overall_score`
- Optionale `errors`-Liste mit `{"item_id", "error"}` im LLM-Modus
- Im Score-Modus bisheriges Schema unverändert (keine `rationale`/`errors`)
- _Requirements: 7.1, 7.4, 8.6, 9.5_
- [x] 7.2 META-JSON in Tool-Antworten um `matching_method` erweitern
- In beiden Modi setzen, sodass Folgewerkzeuge das Schema korrekt interpretieren
- _Requirements: 1.6, 9.5_
- [x] 7.3 Property-Test für persistierte ungekürzte Rationale schreiben
- **Property 8: Ungekürzte Rationale wird persistiert**
- Hypothesis `st.text(min_size=300)`, Cache-Eintrag muss exakt der LLM-Antwort entsprechen
- **Validates: Requirements 8.6**
- [x] 7.4 Property-Test für META-`matching_method` schreiben
- **Property 11: META enthält das verwendete Verfahren**
- Aus Tool-Output META-JSON parsen und Wert prüfen
- **Validates: Requirements 1.6, 9.5**
- [x] 7.5 Property-Test für Score-Modus-Abwärtskompatibilität schreiben
- **Property 10: Score-Modus ist abwärtskompatibel**
- Tool-Aufruf ohne `matching_method` bzw. mit `"score"`: Persistiertes Payload-Schema entspricht weiter dem bisherigen (Felder `role_score`, `competence_score`, `overall_score`; kein `rationale`)
- **Validates: Requirements 1.2, 1.3, 7.3**
- [x] 8. Tabellenausgabe mit `Begründung`-Spalte und Rationale-Formatierung
- [x] 8.1 Hilfsfunktion `_format_rationale_for_table` implementieren
- Pipes (`|`) → `/`, Carriage Return / Linefeed → Leerzeichen
- Whitespace zusammenfalten
- Bei > 280 Zeichen kürzen und mit `…` abschließen
- _Requirements: 8.4, 8.5_
- [x] 8.2 Ausgabe-Tabellen für `find_matching_capacities` (LLM-Modus) anpassen
- Spalten: `ID | Owner | Role | Competences | Availability | Category | Begründung`
- **Keine** Spalten `Role Score`, `Competence Score`, `Overall Score`
- Pro Zeile gekürzte Rationale via `_format_rationale_for_table`
- Leere Ergebnistabelle weiterhin korrekt rendern (Header-Konsistenz)
- _Requirements: 7.1, 7.2, 8.1, 8.2, 8.3, 8.4, 8.5_
- [x] 8.3 Ausgabe-Tabellen für `find_matching_tasks` (LLM-Modus) anpassen
- Spalten: `task_id | Title | Required Competences | Availability | Category | Begründung`
- Verhalten analog 8.2
- _Requirements: 7.1, 7.2, 8.1, 8.2, 8.3, 8.4, 8.5_
- [x] 8.4 Summary-Tabelle und Errors-Block einbauen
- Summary bleibt in beiden Modi identisch (Counter je Kategorie)
- Wenn `errors` nicht leer: zusätzlicher Markdown-Block `## Errors` nach der Ergebnistabelle
- _Requirements: 5.7, 6.7, 7.5_
- [x] 8.5 Score-Modus-Tabelle unverändert lassen (Regression)
- Bestehende Spalten `Role Score`, `Competence Score`, `Overall Score`, `Category` bleiben
- Keine `Begründung`-Spalte
- _Requirements: 7.3_
- [x] 8.6 Property-Test für `_format_rationale_for_table` schreiben
- **Property 7: Tabellen-Rationale ist gültiges Markdown und längenbegrenzt**
- Hypothesis erzeugt Strings inkl. `|`, `\n`, sehr lang
- Output ≤ 280 Zeichen, weder `|` noch Zeilenumbrüche, `…`-Suffix genau dann, wenn normalisierte Eingabe > 280 Zeichen war
- **Validates: Requirements 8.4, 8.5**
- [x] 9. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 10. Anpassungen für `get_results_by_category`
- [x] 10.1 Hilfsfunktion `_format_results_table(items, *, search_type, matching_method, ref_start, ref_end)` einführen
- Routing nach `matching_method`: Score-Tabelle (bisher) oder LLM-Tabelle mit `Begründung`-Spalte
- Wiederverwendbar in `find_matching_capacities`, `find_matching_tasks` und `get_results_by_category`
- _Requirements: 7.1, 7.2, 8.1, 8.2, 9.2_
- [x] 10.2 `get_results_by_category` an LLM-Modus anpassen
- `matching_method` aus persistiertem SearchEntry lesen
- Im LLM-Modus Tabelle ohne Score-Spalten und mit `Begründung`-Spalte rendern
- Im Score-Modus unverändertes Verhalten
- META-JSON enthält `matching_method`
- _Requirements: 9.1, 9.2, 9.5_
- [x] 10.3 Docstring für `get_results_by_category` aktualisieren
- Hinweis auf modusabhängige Spalten (`Begründung` im LLM-Modus)
- _Requirements: 12.1_
- [x] 11. Anpassungen für `filter_search_results`
- [x] 11.1 Modus-Erkennung über persistiertes `matching_method`
- Im LLM-Modus bestehende Filter (Rollen-, Kompetenz-, Verfügbarkeits-, Aufgaben-Text-/Kompetenzfilter) weiterhin anwenden
- Score-bezogenen Filter `min_similarity` ignorieren und in `Applied Filters` einen Hinweis aufnehmen (`min_similarity (ignored: not applicable in llm_fulltext mode)`)
- _Requirements: 9.3, 9.4_
- [x] 11.2 Sortierung in `filter_search_results` modusabhängig
- Score-Modus: bestehende Sortierung nach `overall_score`
- LLM-Modus: stabile Sortierung nach `(category_rank, item_id)`
- _Requirements: 5.8, 6.8, 9.3_
- [x] 11.3 Tabellen-Rendering in `filter_search_results` an `_format_results_table` anbinden
- Verwendung der neuen Hilfsfunktion (siehe 10.1) für konsistente Spalten
- META-JSON enthält `matching_method`
- _Requirements: 9.2, 9.5_
- [x] 11.4 Docstring für `filter_search_results` aktualisieren
- Hinweis, dass `min_similarity` im LLM-Modus ignoriert wird
- _Requirements: 12.1_
- [x] 11.5 Unit-Tests für `get_results_by_category` und `filter_search_results` in beiden Modi
- LLM-Modus: `Begründung`-Spalte vorhanden, `min_similarity` ignoriert mit Hinweis
- Score-Modus: bestehendes Verhalten unverändert (Regression)
- _Requirements: 9.1, 9.2, 9.3, 9.4, 9.5_
- [x] 12. Server-Wiring und Integrationstests
- [x] 12.1 `LlmFulltextMatcher` in `build_server` instanziieren
- Konstruktor mit `db`, `client` (`AzureOpenAIClient`) verdrahten
- Komponente an MCP-Tool-Handler weiterreichen
- _Requirements: 1.4, 5.1, 6.1_
- [x] 12.2 End-to-End-Integrationstests mit gemocktem LLM-Client und gemocktem `DBClient`
- `find_matching_capacities` und `find_matching_tasks` mit `matching_method="llm_fulltext"`
- Verfügbarkeitsfilter im LLM-Modus identisch zum Score-Modus
- SearchCache enthält `matching_method`, ungekürzte `rationale`, `errors`-Liste
- _Requirements: 5.1, 5.2, 6.1, 6.2, 7.4, 8.6, 9.5_
- [x] 12.3 Tabellen-Snapshot-Test für Header in beiden Modi
- Score-Modus-Header unverändert
- LLM-Modus-Header endet auf `... | Category | Begründung`
- _Requirements: 7.1, 8.1, 8.2_
- [x] 13. Checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
- [x] 14. Dokumentation aktualisieren
- [x] 14.1 `docs/architecture.md` erweitern
- `LLM_Fulltext_Matcher` als Komponente in der Business-Logic-Layer beschreiben (Eingaben, Ausgaben, externe Abhängigkeit Azure OpenAI Chat Completion)
- Zusätzliche Datenquellen (`teamlandkarte_v_capacities_latest.description`, `teamlandkarte_v_capacity_certificates_latest`, `teamlandkarte_v_capacity_references_latest`, `teamlandkarte_v_partners_latest.{id, name}`) im Schema-Verifikationsabschnitt aufführen
- Join `teamlandkarte_v_capacity_references_latest.partner_id = teamlandkarte_v_partners_latest.id` und Übernahme der Spalte `name` als Partner_Name dokumentieren
- Tool-Surface-Tabelle um Parameter `matching_method` (Wertebereich `score`/`llm_fulltext`) ergänzen
- Runtime-View für beide Suchrichtungen um den LLM-Volltext-Pfad erweitern
- _Requirements: 11.1, 11.2, 11.3, 11.4, 11.5_
- [x] 14.2 `README.md` erweitern
- Quick-Start- und Usage-Abschnitt: Auswahl zwischen `score` und `llm_fulltext` per Tool-Parameter
- Hinweis: Im LLM-Volltext-Modus keine numerischen Scores; stattdessen Spalte `Begründung`
- Zusätzliche Datenbank-Views auflisten, die der Server im LLM-Volltext-Modus liest, einschließlich `teamlandkarte_v_partners_latest` mit Hinweis auf den LEFT JOIN über `partner_id` in der Referenz-Abfrage
- Default-Konfiguration `[matching].default_method`
- _Requirements: 11.6, 11.7, 11.8_
- [x] 14.3 `.github/agents/teamlandkarte_agent.md` aktualisieren
- Beschreibung beider Verfahren `score` und `llm_fulltext` (Existenz und Zweck)
- Pflicht-Frage nach `matching_method` vor `find_matching_capacities`/`find_matching_tasks`, sofern nicht aus dem Verlauf bekannt
- Skills/Workflows: Aufruf der Tools mit zusätzlichem Parameter `matching_method`
- Rolle der Spalte `Begründung` und Hinweis, dass im LLM-Modus keine numerischen Scores erscheinen
- Bestehender Bestätigungs-Workflow (`show_pending_requirements`, `confirm_requirements`) bleibt für beide Verfahren gleich
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5_
- [x] 14.4 `.kiro/agents/teamlandkarte.md` analog zu 14.3 aktualisieren
- Inhaltliche Gleichheit zum GitHub-Pendant sicherstellen
- _Requirements: 10.1, 10.2, 10.3, 10.4, 10.5_
- [x] 14.5 Beispiele und Mini-Walkthroughs in README/Architektur ergänzen
- Beispielausgabe einer LLM-Volltext-Tabelle inkl. Spalte `Begründung`
- Beispielhafte META-JSON-Ausgabe mit `matching_method`
- _Requirements: 11.5, 11.6_
- [x] 15. Final checkpoint - Ensure all tests pass
- Ensure all tests pass, ask the user if questions arise.
## Notes
- Tasks markiert mit `*` sind optional und können für einen schnelleren MVP übersprungen werden.
- Property-Based Tests verwenden Hypothesis mit `@settings(max_examples=100)` (mindestens 100 Iterationen pro Property) und werden als `# Feature: llm-fulltext-matching, Property {N}: {title}` getaggt.
- Unit- und Integrationstests verwenden `pytest` (Run-once, kein Watch-Modus); der `AzureOpenAIClient` wird stets gemockt.
- Tasks referenzieren explizit Anforderungen aus `requirements.md` zur lückenlosen Nachverfolgbarkeit.
- Die Implementierungssprache ist Python (bestehende Codebase).
- Score-Modus bleibt vollständig abwärtskompatibel; nur additive Erweiterungen am Payload und am META-JSON.