scripts/make_test_vault.py aktualisiert
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 1s
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 1s
This commit is contained in:
parent
4f54e750b2
commit
a202c53594
|
|
@ -1,245 +1,223 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
make_test_vault.py — Erzeugt einen kleinen, reproduzierbaren Test-Vault für den Markdown-Importer.
|
make_test_vault.py — erzeugt einen minimalen, nachvollziehbaren Test-Vault
|
||||||
|
|
||||||
Version: 1.0.0 (2025-09-05)
|
Version: 1.1 (2025-09-05)
|
||||||
Änderungen:
|
Änderungen ggü. 1.0:
|
||||||
- Initiale Version mit 4 Szenarien (base, add-targets, change-chunking, cleanup)
|
- created/updated werden als QUOTED ISO-Strings geschrieben, damit der YAML-Parser
|
||||||
- Frontmatter entspricht Parser-Anforderungen: title, id, type, status, created (+ optionale Felder) (vgl. parser.validate_required_frontmatter)
|
sie nicht in datetime-Objekte konvertiert (Schema verlangt strings).
|
||||||
- Inhalte decken Wikilinks, zunächst fehlende Ziele (unresolved), spätere Zielanlage und Re-Chunking ab.
|
- Kommentare/Output gestrafft.
|
||||||
|
|
||||||
Beschreibung:
|
Zweck
|
||||||
Erstellt unter ./test_vault/ eine kleine Ordnerstruktur mit Markdown-Dateien:
|
- Kleiner Obsidian-ähnlicher Vault zum Durchspielen des Importers (Chunks/Edges).
|
||||||
- base: Legt 4 Notizen an. Zwei haben Wikilinks, wovon einige zunächst ins Leere zeigen (Ziel-Notizen existieren noch nicht).
|
- Szenarien: leere Links, spätere Anlage fehlender Noten, Chunk-Neuaufteilung.
|
||||||
- add-targets: Legt die vorher fehlenden Ziel-Notizen an, so dass der Importer Kanten (references/backlink und references_at) auflösen kann.
|
|
||||||
- change-chunking: Ändert eine bestehende Notiz so, dass sich die Chunk-Grenzen deutlich verschieben (Überschriften + Textblöcke). Dient zum Test, ob der Importer Notes/Chunks/Edges sauber nachzieht.
|
|
||||||
- cleanup: Löscht den gesamten test_vault wieder.
|
|
||||||
|
|
||||||
Aufruf:
|
Struktur (Default: ./test_vault)
|
||||||
python3 -m scripts.make_test_vault --scenario base
|
- 40_concepts/concept-alpha.md
|
||||||
python3 -m scripts.make_test_vault --scenario add-targets
|
- 20_experiences/exp-one.md (verlinkt auf [[concept-alpha]] und [[missing-note]])
|
||||||
python3 -m scripts.make_test_vault --scenario change-chunking
|
- 20_experiences/exp-two.md (verlinkt auf [[concept-alpha]])
|
||||||
python3 -m scripts.make_test_vault --scenario cleanup
|
- 30_projects/project-demo.md (verlinkt auf [[concept-alpha]] und [[exp-one]])
|
||||||
|
|
||||||
Hinweise:
|
Voraussetzungen
|
||||||
- Dieses Skript erzeugt KEINE Qdrant-Daten; es schreibt nur Markdown-Dateien.
|
- Keine. Rein Dateigenerierung.
|
||||||
- Zum Testen:
|
|
||||||
1) base erzeugen → Importer (dry-run) → Audit vergleichen.
|
|
||||||
2) add-targets erzeugen → Importer erneut laufen lassen → prüfen, ob vormals „unresolved“ jetzt aufgelöst wurden (edges & edges_at).
|
|
||||||
3) change-chunking ausführen → Importer mit --apply → prüfen, ob Chunks/Edges umgezogen/aktualisiert wurden.
|
|
||||||
- Verwende beim Importer denselben Vault-Pfad: --vault ./test_vault
|
|
||||||
- Virtuelle Umgebung: Für dieses Skript selbst nicht nötig; für den Importer natürlich weiterhin in deinem venv laufen.
|
|
||||||
|
|
||||||
|
Aufruf
|
||||||
|
python3 -m scripts.make_test_vault [--out ./test_vault] [--force]
|
||||||
|
|
||||||
|
Parameter
|
||||||
|
--out Zielverzeichnis (Standard: ./test_vault)
|
||||||
|
--force Bestehenden Ordner löschen und neu anlegen.
|
||||||
|
|
||||||
|
Hinweise für Tests
|
||||||
|
1) Erster Import (dry-run oder --apply): Es gibt einen „leeren“ Link [[missing-note]].
|
||||||
|
2) Lege danach eine Datei für „missing-note“ an (mit gleicher ID im YAML) und importiere erneut:
|
||||||
|
-> Erwartung: Edges für ehemals leeren Link werden korrekt nachgezogen (references + backlink).
|
||||||
|
3) Ändere den Body von exp-one.md so, dass andere Chunk-Grenzen entstehen und importiere erneut:
|
||||||
|
-> Erwartung: Chunks/Edges/Note werden für betroffene Noten aktualisiert.
|
||||||
|
|
||||||
|
Kompatibel mit:
|
||||||
|
- note.schema.json: 'created'/'updated' müssen strings sein. (Wichtig!)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from datetime import datetime, timezone
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parents[1] # Projektwurzel (.. /mindnet)
|
def iso_now() -> str:
|
||||||
VAULT = ROOT / "test_vault"
|
# ISO 8601 mit Offset; als String zurückgeben
|
||||||
|
return datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds")
|
||||||
|
|
||||||
# ---------- Hilfsfunktionen ----------
|
def q(s: str) -> str:
|
||||||
|
# Sicheres Quoten für YAML-Frontmatter
|
||||||
|
# (einfaches doppelt-Quoten reicht hier, da wir keine eingebetteten """ verwenden)
|
||||||
|
return f"\"{s}\""
|
||||||
|
|
||||||
def write_file(path: Path, content: str):
|
TEMPLATE_CONCEPT = """---
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
title: "Concept Alpha"
|
||||||
|
id: "concept-alpha"
|
||||||
|
type: "concept"
|
||||||
|
status: "active"
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: ["area/mindnet","type/concept","topic/test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Beschreibung
|
||||||
|
Dies ist ein einfaches Konzept für Testzwecke.
|
||||||
|
|
||||||
|
## Mögliche Verbindungen
|
||||||
|
- [[exp-one]]
|
||||||
|
- [[project-demo]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEMPLATE_EXP_ONE = """---
|
||||||
|
title: "Experience One"
|
||||||
|
id: "exp-one"
|
||||||
|
type: "experience"
|
||||||
|
status: "active"
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: ["area/mindnet","type/experience","topic/test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Diese Notiz verlinkt auf ein existierendes Konzept und auf eine noch **fehlende** Note.
|
||||||
|
|
||||||
|
## Beobachtung
|
||||||
|
- Bezug zu [[concept-alpha]]
|
||||||
|
- Offener Verweis auf [[missing-note]]
|
||||||
|
|
||||||
|
## Interpretation
|
||||||
|
Wenn später eine Datei für 'missing-note' angelegt wird, sollte der Importer die Kanten nachziehen.
|
||||||
|
|
||||||
|
## Mögliche Verbindungen
|
||||||
|
- [[concept-alpha]]
|
||||||
|
- [[missing-note]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEMPLATE_EXP_TWO = """---
|
||||||
|
title: "Experience Two"
|
||||||
|
id: "exp-two"
|
||||||
|
type: "experience"
|
||||||
|
status: "active"
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: ["area/mindnet","type/experience","topic/test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Zweite Experience, die auf dasselbe Konzept verweist.
|
||||||
|
|
||||||
|
## Beobachtung
|
||||||
|
Verweis auf [[concept-alpha]].
|
||||||
|
|
||||||
|
## Mögliche Verbindungen
|
||||||
|
- [[concept-alpha]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEMPLATE_PROJECT = """---
|
||||||
|
title: "Project Demo"
|
||||||
|
id: "project-demo"
|
||||||
|
type: "project"
|
||||||
|
status: "active"
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: ["area/mindnet","type/project","topic/test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
Ein kleines Demo-Projekt, das andere Notizen referenziert.
|
||||||
|
|
||||||
|
## Arbeitspakete
|
||||||
|
- AP-1: Zusammenhang erklären (siehe [[concept-alpha]])
|
||||||
|
- AP-2: Ereignis berücksichtigen (siehe [[exp-one]])
|
||||||
|
|
||||||
|
## Mögliche Verbindungen
|
||||||
|
- [[concept-alpha]]
|
||||||
|
- [[exp-one]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
TEMPLATE_MISSING = """---
|
||||||
|
title: "Missing Note (jetzt angelegt)"
|
||||||
|
id: "missing-note"
|
||||||
|
type: "concept"
|
||||||
|
status: "active"
|
||||||
|
created: {created}
|
||||||
|
updated: {updated}
|
||||||
|
tags: ["area/mindnet","type/concept","topic/test"]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Kontext
|
||||||
|
Diese Notiz wurde **nachträglich** erstellt, um einen zuvor offenen Link zu schließen.
|
||||||
|
|
||||||
|
## Mögliche Verbindungen
|
||||||
|
- [[exp-one]]
|
||||||
|
"""
|
||||||
|
|
||||||
|
def write_file(path: str, content: str) -> None:
|
||||||
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||||
with open(path, "w", encoding="utf-8") as f:
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
def iso_now():
|
def build_vault(base: str, include_missing_note: bool) -> None:
|
||||||
return datetime.now().isoformat(timespec="seconds")
|
created = q(iso_now())
|
||||||
|
updated = q(iso_now())
|
||||||
|
|
||||||
# ---------- Inhalte (Szenario-Varianten) ----------
|
# 40_concepts
|
||||||
|
write_file(
|
||||||
|
os.path.join(base, "40_concepts", "concept-alpha.md"),
|
||||||
|
TEMPLATE_CONCEPT.format(created=created, updated=updated),
|
||||||
|
)
|
||||||
|
|
||||||
def file_index_base() -> str:
|
# 20_experiences
|
||||||
# Enthält Links auf bestehende und noch NICHT bestehende Ziele
|
write_file(
|
||||||
return f"""---
|
os.path.join(base, "20_experiences", "exp-one.md"),
|
||||||
title: "Index – Mini-Testvault"
|
TEMPLATE_EXP_ONE.format(created=created, updated=updated),
|
||||||
id: "test-index"
|
)
|
||||||
type: "concept"
|
write_file(
|
||||||
status: "draft"
|
os.path.join(base, "20_experiences", "exp-two.md"),
|
||||||
created: {iso_now()}
|
TEMPLATE_EXP_TWO.format(created=created, updated=updated),
|
||||||
tags: ["area/test","type/concept"]
|
)
|
||||||
---
|
|
||||||
|
|
||||||
## Überblick
|
# 30_projects
|
||||||
Dies ist der kleine Test-Vault.
|
write_file(
|
||||||
|
os.path.join(base, "30_projects", "project-demo.md"),
|
||||||
|
TEMPLATE_PROJECT.format(created=created, updated=updated),
|
||||||
|
)
|
||||||
|
|
||||||
## Verweise
|
# Optional: zunächst NICHT anlegen (Szenario „leerer Link“),
|
||||||
- [[test-alpha]] (existiert)
|
# später für den 2. Testlauf anlegen.
|
||||||
- [[test-beta]] (existiert)
|
if include_missing_note:
|
||||||
- [[test-gamma]] (existiert)
|
write_file(
|
||||||
- [[test-delta]] (FEHLT zunächst — wird erst in add-targets angelegt)
|
os.path.join(base, "40_concepts", "missing-note.md"),
|
||||||
"""
|
TEMPLATE_MISSING.format(created=created, updated=updated),
|
||||||
|
)
|
||||||
def file_alpha_base() -> str:
|
|
||||||
# Hat Wikilinks auf beta (existiert) und epsilon (fehlt zunächst)
|
|
||||||
return f"""---
|
|
||||||
title: "Alpha"
|
|
||||||
id: "test-alpha"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
## Abschnitt A
|
|
||||||
Alpha verweist auf [[test-beta]] und auf [[test-epsilon]] (FEHLT zunächst).
|
|
||||||
|
|
||||||
## Abschnitt B
|
|
||||||
Noch etwas Text, damit beim Re-Chunking später Grenzen sichtbar werden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def file_beta_base() -> str:
|
|
||||||
# Kurze Notiz ohne Links
|
|
||||||
return f"""---
|
|
||||||
title: "Beta"
|
|
||||||
id: "test-beta"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
Beta hat zunächst keine Links.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def file_gamma_base() -> str:
|
|
||||||
# Viele Abschnitte → erzeugt mehrere Chunks
|
|
||||||
return f"""---
|
|
||||||
title: "Gamma"
|
|
||||||
id: "test-gamma"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
## Einleitung
|
|
||||||
Dieser Abschnitt enthält einen Link zu [[test-index]].
|
|
||||||
|
|
||||||
## Details
|
|
||||||
Gamma liefert Details. Hier noch ein Link zu [[test-beta]].
|
|
||||||
|
|
||||||
## Weitere Hinweise
|
|
||||||
Hier steht Text, damit die Chunker-Logik genug Material hat.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def file_delta_added() -> str:
|
|
||||||
# Wird erst im Szenario add-targets erstellt (auflösen von vormals unresolved [[test-delta]])
|
|
||||||
return f"""---
|
|
||||||
title: "Delta (neu angelegt)"
|
|
||||||
id: "test-delta"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
Delta wurde nachträglich angelegt, um ehemals leere Links aufzulösen.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def file_epsilon_added() -> str:
|
|
||||||
# Wird erst im Szenario add-targets erstellt (auflösen von [[test-epsilon]] aus Alpha)
|
|
||||||
return f"""---
|
|
||||||
title: "Epsilon (neu angelegt)"
|
|
||||||
id: "test-epsilon"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
Epsilon existiert nun und kann referenziert werden.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def file_gamma_changed_chunking() -> str:
|
|
||||||
# Ersetzt die ursprüngliche Gamma-Datei mit deutlich mehr (und anders geschnittenen) Abschnitten,
|
|
||||||
# so dass sich die Chunk-Grenzen ändern.
|
|
||||||
return f"""---
|
|
||||||
title: "Gamma (rechunked)"
|
|
||||||
id: "test-gamma"
|
|
||||||
type: "concept"
|
|
||||||
status: "draft"
|
|
||||||
created: {iso_now()}
|
|
||||||
tags: ["area/test","type/concept"]
|
|
||||||
---
|
|
||||||
|
|
||||||
# Einleitung (H1 statt H2)
|
|
||||||
Kurzer Teaser. Verweis auf [[test-index]] bleibt bestehen.
|
|
||||||
|
|
||||||
## Teil 1 – Kontext
|
|
||||||
Mehr Text. Dieser Abschnitt ist länger und soll den ursprünglichen „Details“-Block ersetzen.
|
|
||||||
Außerdem Verweis auf [[test-beta]].
|
|
||||||
|
|
||||||
## Teil 2 – Beispiele
|
|
||||||
- Beispiel 1: Ein etwas längerer Listenpunkt mit Beschreibung.
|
|
||||||
- Beispiel 2: Noch ein Punkt.
|
|
||||||
- Beispiel 3: Und noch einer.
|
|
||||||
|
|
||||||
## Teil 3 – Fazit
|
|
||||||
Zusammenfassung und ggf. ein weiterer Verweis auf [[test-alpha]].
|
|
||||||
"""
|
|
||||||
|
|
||||||
# ---------- Szenarien ausführen ----------
|
|
||||||
|
|
||||||
def scenario_base():
|
|
||||||
# Frisch anlegen / überschreiben
|
|
||||||
(VAULT / "notes").mkdir(parents=True, exist_ok=True)
|
|
||||||
write_file(VAULT / "notes" / "index.md", file_index_base())
|
|
||||||
write_file(VAULT / "notes" / "alpha.md", file_alpha_base())
|
|
||||||
write_file(VAULT / "notes" / "beta.md", file_beta_base())
|
|
||||||
write_file(VAULT / "notes" / "gamma.md", file_gamma_base())
|
|
||||||
print(f"[base] Angelegt: {VAULT}")
|
|
||||||
|
|
||||||
def scenario_add_targets():
|
|
||||||
# Nur neue Ziele hinzufügen (delta, epsilon)
|
|
||||||
if not VAULT.exists():
|
|
||||||
print("test_vault fehlt – bitte erst --scenario base ausführen.")
|
|
||||||
return
|
|
||||||
write_file(VAULT / "notes" / "delta.md", file_delta_added())
|
|
||||||
write_file(VAULT / "notes" / "epsilon.md", file_epsilon_added())
|
|
||||||
print("[add-targets] Neue Zielnotizen delta/epsilon angelegt.")
|
|
||||||
|
|
||||||
def scenario_change_chunking():
|
|
||||||
# Gamma ersetzen, um Chunk-Grenzen zu verschieben
|
|
||||||
if not VAULT.exists():
|
|
||||||
print("test_vault fehlt – bitte erst --scenario base ausführen.")
|
|
||||||
return
|
|
||||||
write_file(VAULT / "notes" / "gamma.md", file_gamma_changed_chunking())
|
|
||||||
print("[change-chunking] gamma.md geändert (deutlich andere Abschnittsstruktur).")
|
|
||||||
|
|
||||||
def scenario_cleanup():
|
|
||||||
if VAULT.exists():
|
|
||||||
shutil.rmtree(VAULT)
|
|
||||||
print("[cleanup] test_vault gelöscht.")
|
|
||||||
else:
|
|
||||||
print("[cleanup] nichts zu tun – test_vault existiert nicht.")
|
|
||||||
|
|
||||||
# ---------- CLI ----------
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ap = argparse.ArgumentParser()
|
ap = argparse.ArgumentParser()
|
||||||
ap.add_argument(
|
ap.add_argument("--out", default="./test_vault", help="Zielverzeichnis für den Test-Vault")
|
||||||
"--scenario",
|
ap.add_argument("--force", action="store_true", help="Falls vorhanden: Verzeichnis löschen und neu anlegen")
|
||||||
required=True,
|
ap.add_argument("--with-missing", action="store_true",
|
||||||
choices=["base", "add-targets", "change-chunking", "cleanup"],
|
help="Auch die 'missing-note' direkt anlegen (Standard: erst im 2. Lauf)")
|
||||||
help="Welches Setup erzeugt werden soll."
|
|
||||||
)
|
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
|
||||||
if args.scenario == "base":
|
base = os.path.abspath(args.out)
|
||||||
scenario_base()
|
if os.path.exists(base):
|
||||||
elif args.scenario == "add-targets":
|
if args.force:
|
||||||
scenario_add_targets()
|
shutil.rmtree(base)
|
||||||
elif args.scenario == "change-chunking":
|
else:
|
||||||
scenario_change_chunking()
|
print(f"Ziel existiert bereits: {base}\nNutze --force zum Überschreiben.")
|
||||||
elif args.scenario == "cleanup":
|
return
|
||||||
scenario_cleanup()
|
|
||||||
|
build_vault(base, include_missing_note=args.with_missing)
|
||||||
|
print(f"Test-Vault erstellt unter: {base}")
|
||||||
|
print("Hinweis:")
|
||||||
|
print(" 1) Zuerst ohne missing-note anlegen (Standard), importieren, Edges prüfen.")
|
||||||
|
print(" 2) Dann erneut ausführen mit --force --with-missing (missing-note wird erzeugt),")
|
||||||
|
print(" und Import wiederholen → Edges/Backlinks sollten nachgezogen werden.")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user