hackathon / src /ingestion.py
thieuluan's picture
init first spec
191645f
Raw
History Blame Contribute Delete
3.02 kB
import json
import os
import re
from dataclasses import dataclass, asdict
from typing import List, Optional, Tuple
from pypdf import PdfReader
import pdfplumber
@dataclass
class Chunk:
content: str
source: str
page: Optional[int] = None
chapter: Optional[str] = None
def normalize_text(text: str) -> str:
# Remove control characters, normalize whitespace
text = text.encode("utf-8", errors="ignore").decode("utf-8", errors="ignore")
text = re.sub(r"[\x00-\x08\x0B\x0C\x0E-\x1F]", " ", text)
text = re.sub(r"\s+", " ", text).strip()
return text
def extract_text_pypdf(file_path: str) -> List[Tuple[int, str]]:
pages: List[Tuple[int, str]] = []
reader = PdfReader(file_path)
for i, page in enumerate(reader.pages, start=1):
text = page.extract_text() or ""
pages.append((i, text))
return pages
def extract_text_pdfplumber(file_path: str) -> List[Tuple[int, str]]:
pages: List[Tuple[int, str]] = []
with pdfplumber.open(file_path) as pdf:
for i, page in enumerate(pdf.pages, start=1):
text = page.extract_text() or ""
pages.append((i, text))
return pages
def chunk_text(text: str, chunk_size: int = 4000, overlap: int = 400) -> List[str]:
# Simple recursive-like character splitter
if chunk_size <= 0:
return [text]
chunks: List[str] = []
start = 0
n = len(text)
while start < n:
end = min(start + chunk_size, n)
chunk = text[start:end]
chunks.append(chunk)
if end == n:
break
start = max(end - overlap, start + 1)
return chunks
def make_chunks(pages: List[Tuple[int, str]], source: str, chunk_size: int, overlap: int) -> List[Chunk]:
all_text = []
for page_num, raw in pages:
norm = normalize_text(raw)
if norm:
all_text.append((page_num, norm))
merged = "\n".join(t for _, t in all_text)
pieces = chunk_text(merged, chunk_size=chunk_size, overlap=overlap)
chunks = [Chunk(content=p, source=source) for p in pieces if p.strip()]
return chunks
def persist_chunks(session_id: str, chunks: List[Chunk]) -> str:
base = os.path.join(".tmp", "ingestion")
os.makedirs(base, exist_ok=True)
path = os.path.join(base, f"{session_id}.json")
with open(path, "w", encoding="utf-8") as f:
json.dump([asdict(c) for c in chunks], f, ensure_ascii=False, indent=2)
return path
def ingest_pdf(
file_path: str,
session_id: str,
parser: str = "pypdf",
chunk_size: int = 4000,
overlap: int = 400,
) -> Tuple[List[Chunk], str]:
if parser == "pypdf":
pages = extract_text_pypdf(file_path)
elif parser == "pdfplumber":
pages = extract_text_pdfplumber(file_path)
else:
raise ValueError("Unknown parser: " + parser)
chunks = make_chunks(pages, source=os.path.basename(file_path), chunk_size=chunk_size, overlap=overlap)
out = persist_chunks(session_id, chunks)
return chunks, out