mindnet/app/core/chunker.py

86 lines
3.4 KiB
Python
Raw Normal View History

2025-12-15 15:40:39 +01:00
"""
FILE: app/core/chunker.py
DESCRIPTION: Facade für das Chunking-Package. Stellt 100% Abwärtskompatibilität sicher.
WP-14: Modularisierung abgeschlossen.
WP-15b: Edge-Inheritance und Candidate-Pool Logik integriert.
Verwendet neue 'chunking_' Präfixe für Untermodule.
VERSION: 3.3.0
2025-12-26 21:52:08 +01:00
STATUS: Active
2025-12-15 15:40:39 +01:00
"""
import asyncio
2025-09-03 07:15:00 +02:00
import re
2025-12-12 10:25:01 +01:00
import logging
from typing import List, Dict, Optional
# Interne Package-Imports mit neuer Präfix-Konvention
from .chunking.chunking_models import Chunk, RawBlock
from .chunking.chunking_utils import get_chunk_config, extract_frontmatter_from_text
from .chunking.chunking_parser import parse_blocks, parse_edges_robust
from .chunking.chunking_strategies import strategy_sliding_window, strategy_by_heading
from .chunking.chunking_propagation import propagate_section_edges
2025-12-11 22:39:35 +01:00
logger = logging.getLogger(__name__)
# Legacy Support für SemanticAnalyzer (Optional für andere Skripte)
2025-12-26 21:52:08 +01:00
try:
from app.services.semantic_analyzer import get_semantic_analyzer
except ImportError:
def get_semantic_analyzer(): return None
2025-12-12 11:40:38 +01:00
async def assemble_chunks(note_id: str, md_text: str, note_type: str, config: Optional[Dict] = None) -> List[Chunk]:
"""
Hauptfunktion zur Chunk-Generierung. Orchestriert die modularisierten Komponenten.
Sichert die Kompatibilität zum bestehenden Ingestion-Prozess.
"""
2025-12-12 11:40:38 +01:00
if config is None:
config = get_chunk_config(note_type)
fm, body_text = extract_frontmatter_from_text(md_text)
primary_strategy = config.get("strategy", "sliding_window")
2025-12-12 10:25:01 +01:00
# 1. Parsing
2025-12-12 11:40:38 +01:00
blocks, doc_title = parse_blocks(md_text)
# 2. Splitting via Thread-Offloading
2025-12-12 10:25:01 +01:00
if primary_strategy == "by_heading":
chunks = await asyncio.to_thread(strategy_by_heading, blocks, config, note_id, doc_title)
2025-12-12 11:40:38 +01:00
else:
chunks = await asyncio.to_thread(strategy_sliding_window, blocks, config, note_id)
2025-12-26 21:52:08 +01:00
if not chunks: return []
# 3. WP-15b: Candidate Pool Vorbereitung
2025-12-26 21:52:08 +01:00
# A. Edge Inheritance (Sektions-Propagation)
chunks = propagate_section_edges(chunks, blocks)
2025-12-26 21:52:08 +01:00
# B. Explicit Edges (Direkt im Chunk-Text)
2025-12-26 21:52:08 +01:00
for ch in chunks:
explicit = parse_edges_robust(ch.text)
2025-12-26 21:52:08 +01:00
for e_str in explicit:
kind, target = e_str.split(':', 1)
ch.candidate_pool.append({"kind": kind, "to": target, "provenance": "explicit"})
# C. Global Pool Detection (Sektion 'Unzugeordnete Kanten')
2025-12-26 21:52:08 +01:00
pool_match = re.search(r'###?\s*(?:Unzugeordnete Kanten|Edge Pool|Candidates)\s*\n(.*?)(?:\n#|$)', body_text, re.DOTALL | re.IGNORECASE)
if pool_match:
unassigned = parse_edges_robust(pool_match.group(1))
2025-12-26 21:52:08 +01:00
for ch in chunks:
for e_str in unassigned:
2025-12-26 21:52:08 +01:00
kind, target = e_str.split(':', 1)
ch.candidate_pool.append({"kind": kind, "to": target, "provenance": "global_pool"})
2025-12-11 23:21:24 +01:00
# D. Eindeutigkeit sicherstellen
2025-12-26 21:52:08 +01:00
for ch in chunks:
seen = set(); unique_pool = []
2025-12-26 21:52:08 +01:00
for cand in ch.candidate_pool:
key = (cand["kind"], cand["to"])
if key not in seen:
seen.add(key); unique_pool.append(cand)
2025-12-26 21:52:08 +01:00
ch.candidate_pool = unique_pool
# 4. Graph-Struktur (Nachbarschaft)
2025-12-12 11:40:38 +01:00
for i, ch in enumerate(chunks):
ch.neighbors_prev = chunks[i-1].id if i > 0 else None
ch.neighbors_next = chunks[i+1].id if i < len(chunks)-1 else None
2025-12-12 10:25:01 +01:00
return chunks