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 @@
{"specId": "218474d5-eca5-49e7-80ca-f7373f4397fb", "workflowType": "requirements-first", "specType": "bugfix"}
@@ -0,0 +1,31 @@
# Bugfix Requirements Document
## Einleitung
Die Kompetenz-Inferenz im Tool `validate_task_requirements` (sowie in `extract_requirements` und `find_matching_tasks`) funktioniert nicht mehr. Der frühere Embedding-basierte Ansatz (`infer_competences`, `ensure_task_embedding`) wurde im Rahmen der Umstellung auf BM25+LLM entfernt, ohne dass ein Ersatz implementiert wurde. Die Variable `inferred_comps` ist daher immer eine leere Liste `[]`.
Es soll ein LLM-basierter Ansatz implementiert werden, der aus allen verfügbaren Kompetenzen (via `get_all_competence_names()`) bis zu 10 passende Kompetenzen für einen gegebenen Task-Text auswählt analog zum bereits funktionierenden LLM-basierten `infer_primary_role`.
## Bug-Analyse
### Aktuelles Verhalten (Defekt)
1.1 WHEN `validate_task_requirements(task_id)` aufgerufen wird THEN liefert das System immer eine leere Kompetenz-Tabelle, da `inferred_comps` stets `[]` ist
1.2 WHEN `extract_requirements(task_description)` aufgerufen wird THEN enthält das Ergebnis keine inferierten Kompetenzen (`inferred_competences` ist immer `[]`)
1.3 WHEN `find_matching_tasks(capacity_id)` intern Kompetenzen inferieren soll THEN werden stattdessen nur die DB-Skills verwendet, da die Kompetenz-Inferenz deaktiviert ist
### Erwartetes Verhalten (Korrekt)
2.1 WHEN `validate_task_requirements(task_id)` aufgerufen wird THEN SHALL das System per LLM-Aufruf bis zu 10 passende Kompetenzen aus der vollständigen Kompetenzliste (`get_all_competence_names()`) inferieren und mit Konfidenzwerten in der Tabelle anzeigen
2.2 WHEN `extract_requirements(task_description)` aufgerufen wird THEN SHALL das System per LLM-Aufruf bis zu 10 passende Kompetenzen inferieren und diese in den `requirements.competences` aufnehmen
2.3 WHEN `find_matching_tasks(capacity_id)` intern Kompetenzen inferiert THEN SHALL das System per LLM-Aufruf bis zu 10 passende Kompetenzen inferieren und diese für das Scoring verwenden
2.4 WHEN der Task-Text leer ist oder keine Kompetenzen in der DB vorhanden sind THEN SHALL das System eine leere Kompetenzliste zurückgeben, ohne einen Fehler zu werfen
2.5 WHEN der LLM-Aufruf fehlschlägt (Timeout, API-Fehler) THEN SHALL das System eine leere Kompetenzliste zurückgeben und den Fehler loggen, ohne den gesamten Tool-Aufruf abzubrechen
### Unverändertes Verhalten (Regressionsprävention)
3.1 WHEN `validate_task_requirements(task_id)` aufgerufen wird THEN SHALL das System WEITERHIN die Rollen-Inferenz per LLM korrekt durchführen
3.2 WHEN `validate_task_requirements(task_id)` aufgerufen wird THEN SHALL das System WEITERHIN die DB-Skills des Tasks korrekt anzeigen
3.3 WHEN `find_matching_capacities(task_id)` aufgerufen wird THEN SHALL das System WEITERHIN das bestehende LLM-Fulltext-Matching unverändert verwenden
3.4 WHEN der LLM-Aufruf für Rollen-Inferenz fehlschlägt THEN SHALL das System WEITERHIN `None` zurückgeben ohne Absturz
3.5 WHEN `infer_primary_role` aufgerufen wird THEN SHALL das System WEITERHIN genau eine Rolle aus der Rollenliste auswählen
@@ -0,0 +1,220 @@
# LLM-Kompetenz-Inferenz Bugfix Design
## Übersicht
Die Kompetenz-Inferenz in `validate_task_requirements`, `extract_requirements` und `find_matching_tasks` (Score-Modus) ist defekt, weil der alte Embedding-Ansatz entfernt wurde, ohne einen Ersatz zu implementieren. Die Variable `inferred_comps` ist stets eine leere Liste `[]`.
Der Fix implementiert eine neue Methode `infer_competences` in der bestehenden `VocabularyCache`-Klasse, analog zum bereits funktionierenden `infer_primary_role`. Diese Methode nutzt den `AzureOpenAIClient.chat_completion`-Aufruf, um aus der vollständigen Kompetenzliste (`get_all_competence_names()`) bis zu 10 passende Kompetenzen mit Konfidenzwerten auszuwählen.
## Glossar
- **Bug_Condition (C)**: Der Zustand, in dem ein Task-Text vorhanden ist UND Kompetenzen in der DB existieren, aber `inferred_comps` trotzdem `[]` zurückgibt
- **Property (P)**: Das gewünschte Verhalten eine nicht-leere Liste von bis zu 10 `(competence_name, confidence)`-Tupeln, wobei jeder Name in `get_all_competence_names()` enthalten ist
- **Preservation**: Die bestehende Rollen-Inferenz (`infer_primary_role`), DB-Skills-Anzeige und LLM-Fulltext-Matching bleiben unverändert
- **VocabularyCache**: Klasse in `src/teamlandkarte_mcp/matching/vocabulary.py`, die LLM-basierte Inferenz kapselt
- **AzureOpenAIClient**: Client in `src/teamlandkarte_mcp/azure/openai_client.py` mit `chat_completion(system, user) -> str`
- **inferred_comps**: Die lokale Variable in den betroffenen Tools, die aktuell immer `[]` ist
## Bug-Details
### Fault Condition
Der Bug manifestiert sich, wenn ein Tool (`validate_task_requirements`, `extract_requirements`, `find_matching_tasks` im Score-Modus) einen nicht-leeren Task-Text verarbeitet und Kompetenzen in der DB vorhanden sind. Die Kompetenz-Inferenz liefert stets eine leere Liste, weil kein LLM-Aufruf stattfindet.
**Formale Spezifikation:**
```
FUNCTION isBugCondition(input)
INPUT: input of type {task_text: str, competence_names: list[str]}
OUTPUT: boolean
RETURN input.task_text.strip() != ""
AND len(input.competence_names) > 0
AND infer_competences(input.task_text) == []
END FUNCTION
```
### Beispiele
- `validate_task_requirements("task-123")` mit Task-Titel "Python Backend Entwicklung" und 50 Kompetenzen in der DB → Erwartung: bis zu 10 Kompetenzen mit Konfidenz; Aktuell: leere Tabelle
- `extract_requirements("Wir brauchen einen React-Entwickler mit TypeScript-Erfahrung")` mit Kompetenzen ["React", "TypeScript", "Angular", ...] in der DB → Erwartung: ["React", "TypeScript", ...] mit Konfidenz; Aktuell: `inferred_competences = []`
- `find_matching_tasks(capacity_id=1)` im Score-Modus → Erwartung: `inferred_comp_names` enthält LLM-inferierte Kompetenzen für das Scoring; Aktuell: nur DB-Skills werden verwendet
- Leerer Task-Text → Erwartung: leere Liste (kein Fehler) dieses Verhalten ist korrekt und bleibt erhalten
## Erwartetes Verhalten
### Preservation Requirements
**Unverändertes Verhalten:**
- `infer_primary_role` muss weiterhin genau eine Rolle mit Konfidenz zurückgeben
- DB-Skills eines Tasks müssen weiterhin korrekt in der Ausgabe angezeigt werden
- LLM-Fulltext-Matching (`find_matching_capacities` / `find_matching_tasks` im `llm_fulltext`-Modus) bleibt unverändert
- Fehlerbehandlung bei LLM-Aufruf-Fehlern für Rollen-Inferenz bleibt unverändert (`None` zurückgeben)
- Die `AzureOpenAIClient`-Schnittstelle wird nicht verändert
**Scope:**
Alle Eingaben, die KEINEN nicht-leeren Task-Text mit vorhandenen Kompetenzen in der DB kombinieren, sind vom Fix nicht betroffen:
- Leerer Task-Text → weiterhin leere Liste
- Keine Kompetenzen in der DB → weiterhin leere Liste
- Mausklick-/UI-Interaktionen → nicht betroffen (MCP-Server)
- LLM-Fulltext-Modus → verwendet eigene Logik, nicht betroffen
## Hypothesierte Ursache
Basierend auf der Bug-Analyse sind die Ursachen klar identifiziert:
1. **Fehlende Implementierung**: Die alte `infer_competences`-Methode (Embedding-basiert) wurde entfernt. An den drei Stellen im Code steht nur noch `inferred_comps: list[tuple[str, float]] = []` bzw. `inferred_competences: list[tuple[str, float]] = []` ohne jeglichen LLM-Aufruf.
2. **Kein System-Prompt für Kompetenz-Inferenz**: Im Gegensatz zu `_ROLE_INFERENCE_SYSTEM_PROMPT` existiert kein entsprechender Prompt für Kompetenz-Inferenz.
3. **Keine Methode in VocabularyCache**: Die Klasse hat nur `infer_primary_role`, aber keine `infer_competences`-Methode.
4. **Keine Integration in die Tools**: Selbst wenn eine Methode existieren würde, fehlt der `await`-Aufruf an den drei betroffenen Stellen.
## Correctness Properties
Property 1: Fault Condition - Kompetenz-Inferenz liefert Ergebnisse
_For any_ input where der Task-Text nicht leer ist UND mindestens eine Kompetenz in der DB existiert (isBugCondition returns true), SHALL die fixierte `infer_competences`-Methode eine nicht-leere Liste von bis zu 10 Tupeln `(competence_name, confidence)` zurückgeben, wobei jeder `competence_name` in `get_all_competence_names()` enthalten ist und `confidence` im Bereich [0.0, 1.0] liegt.
**Validates: Requirements 2.1, 2.2, 2.3**
Property 2: Preservation - Rollen-Inferenz und DB-Skills unverändert
_For any_ input (unabhängig davon ob die Bug-Condition gilt oder nicht), SHALL die fixierte Codebasis das gleiche Ergebnis für `infer_primary_role` und die DB-Skills-Anzeige produzieren wie der originale Code, und das LLM-Fulltext-Matching bleibt unverändert.
**Validates: Requirements 3.1, 3.2, 3.3, 3.4, 3.5**
## Fix-Implementierung
### Erforderliche Änderungen
**Datei**: `src/teamlandkarte_mcp/matching/vocabulary.py`
**Änderung 1: System-Prompt hinzufügen**
- Neuer Modul-Level-Konstante `_COMPETENCE_INFERENCE_SYSTEM_PROMPT` analog zu `_ROLE_INFERENCE_SYSTEM_PROMPT`
- Prompt instruiert das LLM, aus einer gegebenen Kompetenzliste bis zu 10 passende Kompetenzen für einen Task-Text auszuwählen
- Antwortformat: `{"competences": [{"name": "<name>", "confidence": <float>}]}`
**Änderung 2: Neue Methode `infer_competences` in `VocabularyCache`**
- Signatur: `async def infer_competences(self, *, task_text: str, max_competences: int = 10) -> list[tuple[str, float]]`
- Holt Kompetenzliste via `self._db.get_all_competence_names()`
- Bei leerer Liste oder leerem Text: `[]` zurückgeben
- LLM-Aufruf via `self._client.chat_completion(system, user)`
- JSON-Parsing der Antwort, Validierung gegen DB-Kompetenzliste
- Bei Fehler: leere Liste zurückgeben + Warning loggen
---
**Datei**: `src/teamlandkarte_mcp/mcp_server.py`
**Änderung 3: `validate_task_requirements` LLM-Aufruf integrieren**
- Ersetze `inferred_comps: list[tuple[str, float]] = []` durch:
```python
inferred_comps = await vocab_cache.infer_competences(task_text=task_text)
```
**Änderung 4: `extract_requirements` LLM-Aufruf integrieren**
- Ersetze `inferred_competences: list[tuple[str, float]] = []` durch:
```python
inferred_competences = await vocab_cache.infer_competences(task_text=desc)
```
**Änderung 5: `find_matching_tasks` (Score-Modus) LLM-Aufruf integrieren**
- Ersetze den Block `inferred_comp_names = [str(x) for x in (getattr(t, "skills", None) or []) if x]` durch:
```python
inferred_comp_tuples = await vocab_cache.infer_competences(task_text=full_text)
inferred_comp_names = [name for name, _conf in inferred_comp_tuples]
if not inferred_comp_names:
inferred_comp_names = [str(x) for x in (getattr(t, "skills", None) or []) if x]
```
## Testing-Strategie
### Validierungsansatz
Die Testing-Strategie folgt einem zweiphasigen Ansatz: Zuerst Counterexamples auf dem unfixierten Code aufdecken, dann den Fix verifizieren und Preservation sicherstellen.
### Exploratory Fault Condition Checking
**Ziel**: Counterexamples aufdecken, die den Bug VOR der Implementierung des Fixes demonstrieren. Root-Cause-Analyse bestätigen oder widerlegen.
**Testplan**: Tests schreiben, die `infer_competences` (bzw. die betroffenen Tools) mit nicht-leerem Task-Text und vorhandenen Kompetenzen aufrufen. Auf dem unfixierten Code beobachten, dass stets `[]` zurückkommt.
**Testfälle**:
1. **validate_task_requirements mit gültigem Task**: Aufruf mit Task-ID, der einen beschriebenen Task hat (wird auf unfixiertem Code leere Kompetenz-Tabelle liefern)
2. **extract_requirements mit Freitext**: Aufruf mit beschreibendem Text (wird auf unfixiertem Code `inferred_competences = []` liefern)
3. **find_matching_tasks im Score-Modus**: Aufruf mit Capacity-ID (wird auf unfixiertem Code nur DB-Skills verwenden, keine LLM-Inferenz)
4. **Leerer Task-Text**: Aufruf mit leerem Text (soll auch nach Fix `[]` liefern Baseline)
**Erwartete Counterexamples**:
- `inferred_comps` ist immer `[]`, unabhängig vom Task-Text
- Ursache: Kein LLM-Aufruf, keine `infer_competences`-Methode vorhanden
### Fix Checking
**Ziel**: Verifizieren, dass für alle Eingaben, bei denen die Bug-Condition gilt, die fixierte Funktion das erwartete Verhalten produziert.
**Pseudocode:**
```
FOR ALL input WHERE isBugCondition(input) DO
result := infer_competences_fixed(input.task_text)
ASSERT len(result) > 0
ASSERT len(result) <= 10
FOR EACH (name, confidence) IN result DO
ASSERT name IN get_all_competence_names()
ASSERT 0.0 <= confidence <= 1.0
END FOR
END FOR
```
### Preservation Checking
**Ziel**: Verifizieren, dass für alle Eingaben, bei denen die Bug-Condition NICHT gilt, die fixierte Funktion das gleiche Ergebnis wie die originale Funktion produziert.
**Pseudocode:**
```
FOR ALL input WHERE NOT isBugCondition(input) DO
ASSERT infer_competences_fixed(input.task_text) == []
END FOR
FOR ALL input DO
ASSERT infer_primary_role_fixed(input) == infer_primary_role_original(input)
END FOR
```
**Testing-Ansatz**: Property-Based Testing wird für Preservation Checking empfohlen, weil:
- Es automatisch viele Testfälle über den Eingabebereich generiert
- Es Randfälle findet, die manuelle Unit-Tests übersehen könnten
- Es starke Garantien bietet, dass Verhalten für alle nicht-buggy Eingaben unverändert bleibt
**Testplan**: Verhalten auf unfixiertem Code zuerst beobachten (leere Ergebnisse, funktionierende Rollen-Inferenz), dann Property-Based Tests schreiben, die dieses Verhalten nach dem Fix verifizieren.
**Testfälle**:
1. **Rollen-Inferenz Preservation**: Verifizieren, dass `infer_primary_role` nach dem Fix identische Ergebnisse liefert
2. **DB-Skills Preservation**: Verifizieren, dass DB-Skills weiterhin korrekt angezeigt werden
3. **Leerer Text Preservation**: Verifizieren, dass leerer Task-Text weiterhin `[]` liefert
4. **LLM-Fulltext Preservation**: Verifizieren, dass der LLM-Fulltext-Modus nicht beeinflusst wird
### Unit Tests
- Test `infer_competences` mit gemocktem LLM-Client: gültige JSON-Antwort → korrekte Tupel-Liste
- Test `infer_competences` mit leerem Task-Text → `[]`
- Test `infer_competences` mit leerer Kompetenzliste in DB → `[]`
- Test `infer_competences` bei LLM-Fehler (Exception) → `[]` + Warning geloggt
- Test `infer_competences` bei ungültiger JSON-Antwort → `[]`
- Test `infer_competences` bei Kompetenz-Namen die nicht in DB sind → werden herausgefiltert
- Test `validate_task_requirements` liefert nicht-leere Kompetenz-Tabelle
- Test `extract_requirements` liefert nicht-leere `inferred_competences`
### Property-Based Tests
- Generiere zufällige Task-Texte und Kompetenzlisten; verifiziere, dass Ergebnisse stets Subset der DB-Kompetenzen sind
- Generiere zufällige Eingaben; verifiziere, dass Konfidenzwerte immer in [0.0, 1.0] liegen
- Generiere zufällige Eingaben; verifiziere, dass maximal 10 Kompetenzen zurückgegeben werden
- Generiere zufällige Eingaben; verifiziere, dass `infer_primary_role` unverändert funktioniert (Preservation)
### Integration Tests
- End-to-End Test: `validate_task_requirements` mit echtem (gemocktem) LLM-Client zeigt Kompetenzen
- End-to-End Test: `extract_requirements` → `find_matching_capacities` Pipeline mit inferierten Kompetenzen
- End-to-End Test: `find_matching_tasks` im Score-Modus nutzt inferierte Kompetenzen für besseres Scoring
@@ -0,0 +1,120 @@
# Implementation Plan: LLM-Kompetenz-Inferenz Bugfix
## Übersicht
Explorativer Bugfix-Workflow: Zuerst den Bug durch Tests bestätigen, dann Preservation sicherstellen, anschließend den Fix implementieren und validieren. Die `infer_competences`-Methode wird in `VocabularyCache` ergänzt und an drei Stellen im `mcp_server.py` integriert.
## Tasks
- [x] 1. Bug-Condition Explorationstest schreiben
- **Property 1: Fault Condition** - Kompetenz-Inferenz liefert stets leere Liste
- **CRITICAL**: Dieser Test MUSS auf dem unfixierten Code FEHLSCHLAGEN das Fehlschlagen bestätigt den Bug
- **DO NOT** versuchen den Test oder den Code zu fixen wenn er fehlschlägt
- **NOTE**: Dieser Test kodiert das erwartete Verhalten er validiert den Fix wenn er nach der Implementierung besteht
- **GOAL**: Counterexamples aufdecken, die demonstrieren dass der Bug existiert
- **Scoped PBT Approach**: Property auf konkrete Fälle scopen: nicht-leerer Task-Text mit vorhandenen Kompetenzen in der DB
- Test-Datei: `tests/test_competence_inference_fault_pbt.py`
- Hypothesis-Strategie: `st.text(min_size=1)` für Task-Text, `st.lists(st.text(min_size=1), min_size=1, max_size=50)` für Kompetenzliste
- Mock `AzureOpenAIClient.chat_completion` so dass er gültiges JSON mit Kompetenzen zurückgibt
- Assertion: `infer_competences(task_text)` liefert eine nicht-leere Liste von bis zu 10 Tupeln `(name, confidence)` wobei jeder Name in der Kompetenzliste enthalten ist und `confidence` in [0.0, 1.0] liegt
- Test auf UNFIXIERTEM Code ausführen **ERWARTETES ERGEBNIS**: Test SCHLÄGT FEHL (bestätigt Bug)
- Counterexamples dokumentieren (z.B. "infer_competences('Python Backend') gibt [] zurück statt Kompetenzen")
- Task als abgeschlossen markieren wenn Test geschrieben, ausgeführt und Fehlschlag dokumentiert ist
- _Requirements: 1.1, 1.2, 1.3, 2.1, 2.2, 2.3_
- [x] 2. Preservation Property-Tests schreiben (VOR der Fix-Implementierung)
- **Property 2: Preservation** - Rollen-Inferenz und Leer-Eingaben unverändert
- **IMPORTANT**: Observation-First-Methodik befolgen
- Test-Datei: `tests/test_competence_inference_preservation_pbt.py`
- Beobachten: `infer_primary_role` liefert auf unfixiertem Code weiterhin eine Rolle mit Konfidenz
- Beobachten: Leerer Task-Text liefert auf unfixiertem Code `[]` (korrektes Verhalten)
- Beobachten: Leere Kompetenzliste in DB liefert auf unfixiertem Code `[]` (korrektes Verhalten)
- Property-Based Test 1: Für alle nicht-leeren Task-Texte liefert `infer_primary_role` weiterhin ein Tupel `(role_name, confidence)` oder `None` bei Fehler (aus Preservation Requirements)
- Property-Based Test 2: Für alle leeren Task-Texte (`st.just("")` oder `st.from_regex(r'^\s*$')`) liefert `infer_competences` stets `[]`
- Property-Based Test 3: Für leere Kompetenzliste in DB liefert `infer_competences` stets `[]`
- Tests auf UNFIXIERTEM Code ausführen **ERWARTETES ERGEBNIS**: Tests BESTEHEN (bestätigt Baseline-Verhalten)
- Task als abgeschlossen markieren wenn Tests geschrieben, ausgeführt und bestanden auf unfixiertem Code
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [x] 3. Fix für LLM-Kompetenz-Inferenz implementieren
- [x] 3.1 System-Prompt `_COMPETENCE_INFERENCE_SYSTEM_PROMPT` in `vocabulary.py` hinzufügen
- Modul-Level-Konstante analog zu `_ROLE_INFERENCE_SYSTEM_PROMPT`
- Prompt instruiert das LLM, aus einer Kompetenzliste bis zu 10 passende Kompetenzen für einen Task-Text auszuwählen
- Antwortformat: `{"competences": [{"name": "<name>", "confidence": <float>}]}`
- _Bug_Condition: isBugCondition(input) where task_text.strip() != "" AND len(competence_names) > 0 AND infer_competences(task_text) == []_
- _Expected_Behavior: Nicht-leere Liste von bis zu 10 (name, confidence)-Tupeln_
- _Requirements: 2.1, 2.2, 2.3_
- [x] 3.2 Methode `infer_competences` in `VocabularyCache` implementieren
- Signatur: `async def infer_competences(self, *, task_text: str, max_competences: int = 10) -> list[tuple[str, float]]`
- Kompetenzliste via `self._db.get_all_competence_names()` holen
- Bei leerer Liste oder leerem Text: `[]` zurückgeben
- LLM-Aufruf via `self._client.chat_completion(system, user)`
- JSON-Parsing, Validierung gegen DB-Kompetenzliste (nur bekannte Namen übernehmen)
- Bei Fehler (Exception, ungültiges JSON): leere Liste zurückgeben + Warning loggen
- _Bug_Condition: isBugCondition(input) where task_text.strip() != "" AND len(competence_names) > 0_
- _Expected_Behavior: expectedBehavior(result) = len(result) > 0 AND len(result) <= 10 AND all(name in competence_names for name, _ in result) AND all(0.0 <= conf <= 1.0 for _, conf in result)_
- _Preservation: Leerer Text → [], Leere DB → [], Fehler → [] + Warning_
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [x] 3.3 LLM-Aufruf in `validate_task_requirements` integrieren
- Ersetze `inferred_comps: list[tuple[str, float]] = []` durch `inferred_comps = await vocab_cache.infer_competences(task_text=task_text)`
- _Bug_Condition: validate_task_requirements aufgerufen mit Task der beschriebenen Text hat_
- _Expected_Behavior: inferred_comps enthält bis zu 10 Kompetenzen mit Konfidenz_
- _Preservation: Rollen-Inferenz und DB-Skills bleiben unverändert_
- _Requirements: 2.1, 3.1, 3.2_
- [x] 3.4 LLM-Aufruf in `extract_requirements` integrieren
- Ersetze `inferred_competences: list[tuple[str, float]] = []` durch `inferred_competences = await vocab_cache.infer_competences(task_text=desc)`
- _Bug_Condition: extract_requirements aufgerufen mit nicht-leerem Text_
- _Expected_Behavior: inferred_competences enthält bis zu 10 Kompetenzen_
- _Requirements: 2.2_
- [x] 3.5 LLM-Aufruf in `find_matching_tasks` (Score-Modus) integrieren
- Ersetze den bestehenden `inferred_comp_names`-Block durch LLM-Inferenz mit Fallback auf DB-Skills
- `inferred_comp_tuples = await vocab_cache.infer_competences(task_text=full_text)`
- `inferred_comp_names = [name for name, _conf in inferred_comp_tuples]`
- Fallback: `if not inferred_comp_names: inferred_comp_names = [str(x) for x in (getattr(t, "skills", None) or []) if x]`
- _Bug_Condition: find_matching_tasks im Score-Modus mit Task der beschriebenen Text hat_
- _Expected_Behavior: inferred_comp_names enthält LLM-inferierte Kompetenzen für Scoring_
- _Preservation: LLM-Fulltext-Modus bleibt unverändert_
- _Requirements: 2.3, 3.3_
- [x] 3.6 Unit-Tests für `infer_competences` mit gemocktem LLM-Client
- Test gültige JSON-Antwort → korrekte Tupel-Liste
- Test leerer Task-Text → `[]`
- Test leere Kompetenzliste in DB → `[]`
- Test LLM-Fehler (Exception) → `[]` + Warning geloggt
- Test ungültige JSON-Antwort → `[]`
- Test Kompetenz-Namen die nicht in DB sind → werden herausgefiltert
- Test max_competences Begrenzung auf 10
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.5_
- [x] 3.7 Bug-Condition Explorationstest erneut ausführen Verifizieren dass er jetzt besteht
- **Property 1: Expected Behavior** - Kompetenz-Inferenz liefert Ergebnisse
- **IMPORTANT**: Den GLEICHEN Test aus Task 1 erneut ausführen KEINEN neuen Test schreiben
- Der Test aus Task 1 kodiert das erwartete Verhalten
- Wenn dieser Test besteht, bestätigt das dass das erwartete Verhalten erfüllt ist
- Bug-Condition Explorationstest aus Schritt 1 ausführen
- **ERWARTETES ERGEBNIS**: Test BESTEHT (bestätigt Bug ist behoben)
- _Requirements: 2.1, 2.2, 2.3_
- [x] 3.8 Preservation-Tests erneut ausführen Verifizieren dass sie weiterhin bestehen
- **Property 2: Preservation** - Rollen-Inferenz und Leer-Eingaben unverändert
- **IMPORTANT**: Die GLEICHEN Tests aus Task 2 erneut ausführen KEINE neuen Tests schreiben
- Preservation Property-Tests aus Schritt 2 ausführen
- **ERWARTETES ERGEBNIS**: Tests BESTEHEN (bestätigt keine Regressionen)
- Bestätigen dass alle Tests nach dem Fix weiterhin bestehen
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.5_
- [x] 4. Checkpoint - Sicherstellen dass alle Tests bestehen
- Alle Tests ausführen und sicherstellen dass sie bestehen, bei Fragen den User konsultieren.
## Hinweise
- Property-Based Tests verwenden Hypothesis mit `@settings(max_examples=100)`.
- Unit- und Integrationstests verwenden `pytest` (Run-once, kein Watch-Modus); der `AzureOpenAIClient` wird stets gemockt.
- Die Implementierungssprache ist Python (bestehende Codebase).
- Tasks referenzieren explizit Anforderungen aus `bugfix.md` zur lückenlosen Nachverfolgbarkeit.
- Der Fix ist additiv: bestehende Funktionalität (Rollen-Inferenz, DB-Skills, LLM-Fulltext-Matching) bleibt unverändert.