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.
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
"""
|
|
Knowledge Graph DB - Basis-Modul
|
|
SQLite-basierte Graph-Datenbank fuer pathOS Audit
|
|
"""
|
|
import sqlite3
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
DB_PATH = Path(__file__).parent / "pathos.db"
|
|
SCHEMA_PATH = Path(__file__).parent / "schema.sql"
|
|
|
|
|
|
def get_db():
|
|
"""Verbindung zur DB herstellen, Schema anlegen falls noetig."""
|
|
db = sqlite3.connect(str(DB_PATH))
|
|
db.row_factory = sqlite3.Row
|
|
db.execute("PRAGMA journal_mode=WAL")
|
|
db.execute("PRAGMA foreign_keys=ON")
|
|
# Schema anlegen
|
|
with open(SCHEMA_PATH, "r", encoding="utf-8") as f:
|
|
db.executescript(f.read())
|
|
return db
|
|
|
|
|
|
def upsert_node(db, node_id, node_type, name, summary=None, properties=None, source=None):
|
|
"""Node einfuegen oder aktualisieren."""
|
|
props_json = json.dumps(properties, ensure_ascii=False) if properties else None
|
|
db.execute("""
|
|
INSERT INTO nodes (id, type, name, summary, properties, source, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, datetime('now'))
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
type=excluded.type, name=excluded.name, summary=excluded.summary,
|
|
properties=excluded.properties, source=excluded.source, updated_at=datetime('now')
|
|
""", (node_id, node_type, name, summary, props_json, source))
|
|
|
|
|
|
def add_edge(db, source_id, target_id, relation, properties=None, source=None):
|
|
"""Kante hinzufuegen (Duplikate werden vermieden)."""
|
|
props_json = json.dumps(properties, ensure_ascii=False) if properties else None
|
|
# Pruefen ob schon existiert
|
|
existing = db.execute(
|
|
"SELECT id FROM edges WHERE source_id=? AND target_id=? AND relation=?",
|
|
(source_id, target_id, relation)
|
|
).fetchone()
|
|
if existing:
|
|
db.execute(
|
|
"UPDATE edges SET properties=?, source=?, updated_at=datetime('now') WHERE id=?",
|
|
(props_json, source, existing["id"])
|
|
)
|
|
else:
|
|
db.execute(
|
|
"INSERT INTO edges (source_id, target_id, relation, properties, source, updated_at) VALUES (?, ?, ?, ?, ?, datetime('now'))",
|
|
(source_id, target_id, relation, props_json, source)
|
|
)
|
|
|
|
|
|
def upsert_summary(db, summary_id, topic, content, detail_path=None, tags=None):
|
|
"""Zusammenfassung einfuegen oder aktualisieren."""
|
|
db.execute("""
|
|
INSERT INTO summaries (id, topic, content, detail_path, tags, updated_at)
|
|
VALUES (?, ?, ?, ?, ?, datetime('now'))
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
topic=excluded.topic, content=excluded.content,
|
|
detail_path=excluded.detail_path, tags=excluded.tags, updated_at=datetime('now')
|
|
""", (summary_id, topic, content, detail_path, tags))
|
|
# FTS aktualisieren
|
|
db.execute("INSERT INTO summaries_fts(summaries_fts) VALUES('rebuild')")
|
|
|
|
|
|
def add_fact(db, subject, predicate, value, confidence="confirmed", source=None, date=None):
|
|
"""Fakt hinzufuegen."""
|
|
db.execute(
|
|
"INSERT INTO facts (subject, predicate, value, confidence, source, date, updated_at) VALUES (?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
(subject, predicate, value, confidence, source, date)
|
|
)
|
|
db.execute("INSERT INTO facts_fts(facts_fts) VALUES('rebuild')")
|
|
|
|
|
|
def search_summaries(db, query, limit=5):
|
|
"""Volltextsuche in Zusammenfassungen."""
|
|
# FTS5 Query escapen: Woerter mit Anfuehrungszeichen schuetzen
|
|
safe_query = " ".join(f'"{w}"' for w in query.split())
|
|
results = db.execute("""
|
|
SELECT s.id, s.topic, s.content, s.detail_path, s.tags
|
|
FROM summaries_fts fts
|
|
JOIN summaries s ON s.rowid = fts.rowid
|
|
WHERE summaries_fts MATCH ?
|
|
LIMIT ?
|
|
""", (safe_query, limit)).fetchall()
|
|
return [dict(r) for r in results]
|
|
|
|
|
|
def search_facts(db, query, limit=10):
|
|
"""Volltextsuche in Fakten."""
|
|
safe_query = " ".join(f'"{w}"' for w in query.split())
|
|
results = db.execute("""
|
|
SELECT f.subject, f.predicate, f.value, f.confidence, f.source, f.date
|
|
FROM facts_fts fts
|
|
JOIN facts f ON f.rowid = fts.rowid
|
|
WHERE facts_fts MATCH ?
|
|
LIMIT ?
|
|
""", (safe_query, limit)).fetchall()
|
|
return [dict(r) for r in results]
|
|
|
|
|
|
def get_node_context(db, node_id):
|
|
"""Alle Infos zu einem Node + seine Beziehungen."""
|
|
node = db.execute("SELECT * FROM nodes WHERE id=?", (node_id,)).fetchone()
|
|
if not node:
|
|
return None
|
|
edges_out = db.execute(
|
|
"SELECT e.relation, n.name, n.type, e.properties FROM edges e JOIN nodes n ON n.id=e.target_id WHERE e.source_id=?",
|
|
(node_id,)
|
|
).fetchall()
|
|
edges_in = db.execute(
|
|
"SELECT e.relation, n.name, n.type, e.properties FROM edges e JOIN nodes n ON n.id=e.source_id WHERE e.target_id=?",
|
|
(node_id,)
|
|
).fetchall()
|
|
return {
|
|
"node": dict(node),
|
|
"outgoing": [dict(e) for e in edges_out],
|
|
"incoming": [dict(e) for e in edges_in]
|
|
}
|