- Introduced a new function `load_graph_schema_full` to parse and cache both typical and prohibited edge types from the graph schema. - Updated `load_graph_schema` to utilize the full schema for improved edge type extraction. - Added `get_topology_info` to retrieve typical and prohibited edges for source/target pairs. - Implemented `validate_intra_note_edge` and `validate_edge_against_schema` for schema validation of intra-note edges. - Enhanced logging for schema validation outcomes and edge handling. - Updated documentation to reflect new validation features and testing procedures.
388 lines
9.0 KiB
Markdown
388 lines
9.0 KiB
Markdown
# WP-26 Manuelle Testszenarien
|
|
|
|
**Version:** 1.3
|
|
**Datum:** 25. Januar 2026
|
|
**Status:** Alle Phasen (Phase 1-3) implementiert
|
|
|
|
---
|
|
|
|
## 1. Überblick
|
|
|
|
Dieses Dokument beschreibt die manuellen Testszenarien für WP-26 Phase 1: Section-Types und Intra-Note-Edges.
|
|
|
|
---
|
|
|
|
## 2. Voraussetzungen
|
|
|
|
1. **Python-Umgebung** mit allen Dependencies aus `requirements.txt`
|
|
2. **Qdrant-Instanz** erreichbar (lokal oder Docker)
|
|
3. **Vault mit Test-Note** (siehe Abschnitt 3)
|
|
|
|
---
|
|
|
|
## 3. Test-Note erstellen
|
|
|
|
Erstelle eine neue Markdown-Datei im Vault mit folgendem Inhalt:
|
|
|
|
```markdown
|
|
---
|
|
id: wp26-test-experience
|
|
title: WP-26 Test Experience
|
|
type: experience
|
|
tags: [test, wp26]
|
|
---
|
|
|
|
# WP-26 Test Experience
|
|
|
|
## Situation ^sit
|
|
> [!section] experience
|
|
|
|
Am 25. Januar 2026 testete ich das neue Section-Type Feature.
|
|
Dies ist der Experience-Teil der Note.
|
|
|
|
## Meine Reaktion ^react
|
|
> [!section] experience
|
|
|
|
> [!edge] followed_by
|
|
> [[#^sit]]
|
|
|
|
Ich war zunächst skeptisch, aber die Implementierung sah solide aus.
|
|
|
|
## Reflexion ^ref
|
|
> [!section] insight
|
|
|
|
Diese Erfahrung zeigt mir, dass typ-spezifische Sektionen
|
|
die semantische Präzision des Retrievals verbessern können.
|
|
|
|
> [!abstract] Semantic Edges
|
|
>> [!edge] derives
|
|
>> [[#^sit]]
|
|
>> [[#^react]]
|
|
|
|
## Nächste Schritte ^next
|
|
> [!section] decision
|
|
|
|
Ich werde:
|
|
1. Die Tests ausführen
|
|
2. Die Ergebnisse dokumentieren
|
|
|
|
> [!edge] caused_by
|
|
> [[#^ref]]
|
|
```
|
|
|
|
---
|
|
|
|
## 4. Testszenarien
|
|
|
|
### 4.1 TS-01: Section-Type-Erkennung
|
|
|
|
**Ziel:** Prüfen, ob `[!section]`-Callouts korrekt erkannt werden.
|
|
|
|
**Schritte:**
|
|
|
|
1. Importiere die Test-Note via `scripts/import_markdown.py`
|
|
2. Prüfe die Chunks in Qdrant via API oder Debug-Skript
|
|
|
|
**Prüfkriterien:**
|
|
|
|
| Chunk | Erwarteter `type` | Erwarteter `note_type` | Erwarteter `section` |
|
|
|-------|-------------------|------------------------|----------------------|
|
|
| #c00 | experience | experience | Situation |
|
|
| #c01 | experience | experience | Meine Reaktion |
|
|
| #c02 | insight | experience | Reflexion |
|
|
| #c03 | decision | experience | Nächste Schritte |
|
|
|
|
**Prüf-Script:**
|
|
|
|
```python
|
|
# scripts/check_wp26_chunks.py
|
|
from qdrant_client import QdrantClient
|
|
|
|
client = QdrantClient("http://localhost:6333")
|
|
note_id = "wp26-test-experience"
|
|
|
|
# Hole alle Chunks der Note
|
|
result = client.scroll(
|
|
collection_name="mindnet_chunks",
|
|
scroll_filter={"must": [{"key": "note_id", "match": {"value": note_id}}]},
|
|
with_payload=True,
|
|
limit=100
|
|
)
|
|
|
|
for point in result[0]:
|
|
p = point.payload
|
|
print(f"Chunk: {p.get('chunk_id')}")
|
|
print(f" type: {p.get('type')}")
|
|
print(f" note_type: {p.get('note_type')}")
|
|
print(f" section: {p.get('section')}")
|
|
print(f" section_type: {p.get('section_type')}")
|
|
print(f" block_id: {p.get('block_id')}")
|
|
print()
|
|
```
|
|
|
|
---
|
|
|
|
### 4.2 TS-02: Block-ID-Erkennung
|
|
|
|
**Ziel:** Prüfen, ob Block-IDs (`^id`) aus Überschriften korrekt extrahiert werden.
|
|
|
|
**Prüfkriterien:**
|
|
|
|
| Chunk | Erwartete `block_id` |
|
|
|-------|---------------------|
|
|
| #c00 | sit |
|
|
| #c01 | react |
|
|
| #c02 | ref |
|
|
| #c03 | next |
|
|
|
|
---
|
|
|
|
### 4.3 TS-03: is_internal Flag für Edges
|
|
|
|
**Ziel:** Prüfen, ob Intra-Note-Edges das `is_internal: true` Flag erhalten.
|
|
|
|
**Schritte:**
|
|
|
|
1. Importiere die Test-Note
|
|
2. Prüfe die Edges in Qdrant
|
|
|
|
**Prüfkriterien:**
|
|
|
|
| Edge | `is_internal` |
|
|
|------|---------------|
|
|
| #c01 → #c00 (followed_by) | `true` |
|
|
| #c02 → #c00 (derives) | `true` |
|
|
| #c02 → #c01 (derives) | `true` |
|
|
| #c03 → #c02 (caused_by) | `true` |
|
|
| Alle structure edges (next/prev) | `true` |
|
|
|
|
**Prüf-Script:**
|
|
|
|
```python
|
|
# scripts/check_wp26_edges.py
|
|
from qdrant_client import QdrantClient
|
|
|
|
client = QdrantClient("http://localhost:6333")
|
|
note_id = "wp26-test-experience"
|
|
|
|
# Hole alle Edges der Note
|
|
result = client.scroll(
|
|
collection_name="mindnet_edges",
|
|
scroll_filter={"must": [{"key": "note_id", "match": {"value": note_id}}]},
|
|
with_payload=True,
|
|
limit=100
|
|
)
|
|
|
|
for point in result[0]:
|
|
p = point.payload
|
|
kind = p.get('kind', 'unknown')
|
|
source = p.get('source_id', '?')
|
|
target = p.get('target_id', '?')
|
|
is_internal = p.get('is_internal', 'MISSING')
|
|
provenance = p.get('provenance', '?')
|
|
source_hint = p.get('source_hint', '-')
|
|
|
|
print(f"{source} --[{kind}]--> {target}")
|
|
print(f" is_internal: {is_internal}")
|
|
print(f" provenance: {provenance}")
|
|
print(f" source_hint: {source_hint}")
|
|
print()
|
|
```
|
|
|
|
---
|
|
|
|
### 4.4 TS-04: Provenance-Normalisierung
|
|
|
|
**Ziel:** Prüfen, ob Provenance-Werte korrekt normalisiert werden.
|
|
|
|
**Prüfkriterien:**
|
|
|
|
| Altes Provenance | Neues `provenance` | `source_hint` |
|
|
|------------------|-------------------|---------------|
|
|
| explicit:callout | explicit | callout |
|
|
| explicit:wikilink | explicit | wikilink |
|
|
| structure:belongs_to | structure | belongs_to |
|
|
| structure:order | structure | order |
|
|
| edge_defaults | rule | edge_defaults |
|
|
|
|
---
|
|
|
|
### 4.5 TS-05: Automatische Section-Erkennung
|
|
|
|
**Ziel:** Prüfen, ob neue Überschriften ohne `[!section]` automatisch neue Chunks erstellen.
|
|
|
|
**Test-Note:**
|
|
|
|
```markdown
|
|
---
|
|
id: wp26-test-auto-section
|
|
type: experience
|
|
---
|
|
|
|
# Test Auto Section
|
|
|
|
## Section A ^a
|
|
> [!section] insight
|
|
|
|
Content A (insight).
|
|
|
|
## Section B ^b
|
|
|
|
Content B (sollte experience sein - Fallback).
|
|
|
|
## Section C ^c
|
|
> [!section] decision
|
|
|
|
Content C (decision).
|
|
```
|
|
|
|
**Prüfkriterien:**
|
|
|
|
| Chunk | `type` | Grund |
|
|
|-------|--------|-------|
|
|
| Section A | insight | Explizites `[!section]` |
|
|
| Section B | experience | Fallback auf `note_type` |
|
|
| Section C | decision | Explizites `[!section]` |
|
|
|
|
---
|
|
|
|
## 5. Unit-Tests ausführen
|
|
|
|
```bash
|
|
# Im Projekt-Root
|
|
cd c:\Dev\cursor\mindnet
|
|
|
|
# Aktiviere virtuelle Umgebung (falls vorhanden)
|
|
# .venv\Scripts\activate
|
|
|
|
# Führe WP-26 Tests aus
|
|
python -m pytest tests/test_wp26_section_types.py -v
|
|
```
|
|
|
|
**Erwartetes Ergebnis:** Alle Tests grün.
|
|
|
|
---
|
|
|
|
## 6. Bekannte Einschränkungen
|
|
|
|
1. **Block-ID-Stability:** Obsidian aktualisiert Block-IDs nicht automatisch bei Umbenennung von Überschriften.
|
|
2. **Heading-Links:** Links wie `[[#Section Name]]` werden unterstützt, aber Block-References (`[[#^id]]`) werden bevorzugt.
|
|
3. **Nested Callouts:** Verschachtelte Callouts (`>> [!edge]`) werden korrekt verarbeitet.
|
|
|
|
---
|
|
|
|
## 7. Phase 2: Retriever-Anpassungen
|
|
|
|
### 7.1 is_internal-Boost
|
|
|
|
**Konfiguration:** `config/retriever.yaml`
|
|
|
|
```yaml
|
|
edge_scoring:
|
|
internal_edge_boost: 1.2 # +20% Boost für Intra-Note-Edges
|
|
external_edge_boost: 1.0 # Standard für Inter-Note-Edges
|
|
```
|
|
|
|
**Manuelle Prüfung:**
|
|
|
|
1. Führe eine Suche durch, die eine Note mit internen Edges trifft
|
|
2. Prüfe im Debug-Log, dass `is_internal: True` Edges höheres Gewicht erhalten
|
|
|
|
### 7.2 Aggregation-Level
|
|
|
|
**Konfiguration:** `config/retriever.yaml`
|
|
|
|
```yaml
|
|
aggregation:
|
|
level: note # "note" (default) oder "chunk"
|
|
max_chunks_per_note: 3 # Limit bei "note"-Level
|
|
```
|
|
|
|
**Test mit Chunk-Level:**
|
|
|
|
1. Setze `level: chunk` in `retriever.yaml`
|
|
2. Führe Suche durch
|
|
3. Prüfe, dass mehrere Chunks derselben Note zurückgegeben werden (keine Deduplizierung)
|
|
|
|
### 7.3 Unit-Tests Phase 2
|
|
|
|
```bash
|
|
python -m pytest tests/test_wp26_phase2_retriever.py -v
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Phase 3: Schema-Validierung (FA-12)
|
|
|
|
### 8.1 get_topology_info()
|
|
|
|
Die neue Funktion ermittelt typische und verbotene Edge-Types für ein Source/Target-Typ-Paar.
|
|
|
|
**Beispiel:**
|
|
|
|
```python
|
|
from app.core.graph.graph_utils import get_topology_info
|
|
|
|
topology = get_topology_info("experience", "insight")
|
|
# Gibt: {"typical": ["resulted_in", ...], "prohibited": [...]}
|
|
```
|
|
|
|
### 8.2 validate_intra_note_edge()
|
|
|
|
Validiert Intra-Note-Edges gegen das `graph_schema.md`.
|
|
|
|
**Verhalten:**
|
|
|
|
| Edge-Typ | Ergebnis | Confidence |
|
|
|----------|----------|------------|
|
|
| In `typical` | ✅ Erlaubt | 1.0 |
|
|
| Nicht in `typical`, nicht in `prohibited` | ✅ Erlaubt (atypisch) | 0.7 |
|
|
| In `prohibited` | ❌ Abgelehnt | 0.0 |
|
|
|
|
### 8.3 Manuelle Prüfung
|
|
|
|
1. Erstelle eine Note mit einer verbotenen Edge-Kombination
|
|
2. Führe Ingestion durch
|
|
3. Prüfe, dass die Edge abgelehnt wurde (Log: `🚫 [SCHEMA-VALIDATION]`)
|
|
|
|
### 8.4 Unit-Tests Phase 3
|
|
|
|
```bash
|
|
python -m pytest tests/test_wp26_phase3_validation.py -v
|
|
```
|
|
|
|
---
|
|
|
|
## 9. Alle WP-26 Tests ausführen
|
|
|
|
```bash
|
|
# Alle WP-26 Unit-Tests
|
|
python -m pytest tests/test_wp26_section_types.py tests/test_wp26_phase2_retriever.py tests/test_wp26_phase3_validation.py -v
|
|
|
|
# Nur fehlgeschlagene Tests erneut ausführen
|
|
python -m pytest --lf -v
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Bekannte Einschränkungen
|
|
|
|
1. **Block-ID-Stability:** Obsidian aktualisiert Block-IDs nicht automatisch bei Umbenennung von Überschriften.
|
|
2. **Heading-Links:** Links wie `[[#Section Name]]` werden unterstützt, aber Block-References (`[[#^id]]`) werden bevorzugt.
|
|
3. **Nested Callouts:** Verschachtelte Callouts (`>> [!edge]`) werden korrekt verarbeitet.
|
|
4. **Strict-Mode:** `strict_mode=True` in der Validierung lehnt atypische Edges ab (Standard: `False`).
|
|
|
|
---
|
|
|
|
## 11. Zusammenfassung
|
|
|
|
| Phase | Status | Beschreibung |
|
|
|-------|--------|--------------|
|
|
| Phase 1 | ✅ | Section-Types, Block-IDs, Intra-Note-Edges |
|
|
| Phase 2 | ✅ | is_internal-Boost, Aggregation-Level |
|
|
| Phase 3 | ✅ | Schema-Validierung (FA-12) |
|
|
|
|
---
|
|
|
|
**Ende der Testdokumentation (WP-26 v1.3)**
|