bulk importer did not work using docling
This commit is contained in:
parent
31de93dad3
commit
1def894c48
4 changed files with 1979 additions and 51 deletions
|
|
@ -1,78 +1,89 @@
|
||||||
# core/bulk_importer.py
|
# core/bulk_importer.py
|
||||||
import re
|
import re
|
||||||
from database.connection import get_connection
|
from database.connection import get_connection
|
||||||
|
from docling.document_converter import DocumentConverter
|
||||||
|
|
||||||
class BulkImporter:
|
class BulkImporter:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
# Matches typical patterns: "Spanish word", "English translation", "Unit metadata"
|
# Converter engine for handling multi-column documents
|
||||||
self.row_regex = re.compile(r'"([^"]+)"\s*,\s*"([^"]+)"\s*,\s*"([^"]+)"')
|
self.converter = DocumentConverter()
|
||||||
|
# Matches textbook unit codes (e.g., U5_3D, U7 4A, UG_11B)
|
||||||
|
self.unit_regex = re.compile(r'U([0-9]+)')
|
||||||
|
|
||||||
def clean_text(self, text: str) -> str:
|
def clean_text(self, text: str) -> str:
|
||||||
"""Strips newlines and extra spaces from extracted data fields."""
|
"""Cleans syntax breaks, structural commas, and quotes from strings."""
|
||||||
return text.replace('\n', ' ').strip()
|
if not text:
|
||||||
|
return ""
|
||||||
|
return text.replace('\n', ' ').replace('"', '').replace("'", "").strip()
|
||||||
|
|
||||||
def parse_unit(self, context_str: str) -> int:
|
def parse_unit(self, context_str: str) -> int:
|
||||||
"""
|
"""Extracts the exact Unit integer from codes like U5_3D or U2 1A."""
|
||||||
Extracts the unit integer from codes like 'U5_3D', 'U7 4A', or 'UG_11B'.
|
match = self.unit_regex.search(context_str)
|
||||||
Returns None if it's a general marker like 'UT LEX'.
|
|
||||||
"""
|
|
||||||
match = re.search(r'U([0-9])', context_str)
|
|
||||||
if match:
|
if match:
|
||||||
return int(match.group(1))
|
return int(match.group(1))
|
||||||
return None
|
return None # General reference markers like 'UT LEX' or 'UG'
|
||||||
|
|
||||||
|
def import_pdf_glossary(self, file_path: str, textbook_name: str):
|
||||||
|
"""Converts multi-column PDF via Docling and maps items to SQLite."""
|
||||||
|
print(f"🔄 Docling is analyzing structural layout for '{file_path}'...")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Render layout-aware structural conversion
|
||||||
|
result = self.converter.convert(file_path)
|
||||||
|
document_text = result.document.export_to_markdown()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Docling processing failed: {e}")
|
||||||
|
return 0
|
||||||
|
|
||||||
def import_glossary_file(self, file_path: str, textbook_name: str):
|
|
||||||
"""Reads the structural text lines and inserts them into the translation database."""
|
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
print(f"📖 Starting bulk ingestion for '{file_path}'...")
|
print("📥 Parsing text structures and writing to database...")
|
||||||
count = 0
|
count = 0
|
||||||
|
|
||||||
|
# Regex targeted to catch both standard blocks and tabular lines safely
|
||||||
|
# Matches patterns like: "Spanish Word", "English Translation", "Unit Code"
|
||||||
|
pattern = re.compile(r'([^,\n"\[]+?)\s*,\s*([^,\n"\[]+?)\s*,\s*(U[G|T|0-9][^\n,]+)')
|
||||||
|
matches = pattern.findall(document_text)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
|
||||||
content = f.read()
|
|
||||||
|
|
||||||
# Find all matching row structures inside the text
|
|
||||||
matches = self.row_regex.findall(content)
|
|
||||||
|
|
||||||
for es_raw, en_raw, ctx_raw in matches:
|
for es_raw, en_raw, ctx_raw in matches:
|
||||||
spanish_text = self.clean_text(es_raw)
|
spanish_text = self.clean_text(es_raw)
|
||||||
english_text = self.clean_text(en_raw)
|
english_text = self.clean_text(en_raw)
|
||||||
context_tag = self.clean_text(ctx_raw)
|
context_tag = self.clean_text(ctx_raw)
|
||||||
unit_number = self.parse_unit(context_tag)
|
unit_number = self.parse_unit(context_tag)
|
||||||
|
|
||||||
# Skip header rows or structural markers
|
# Avoid table headers or non-lexical entries
|
||||||
if spanish_text.lower() in ["alphabetical glossary", "spanish", "word"]:
|
if spanish_text.lower() in ["spanish", "word", "alphabetical glossary"] or len(spanish_text) <= 1:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 1. Insert Spanish Term
|
# 1. Insert Spanish Term Entry
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO phrases (text, language, textbook, unit, source_context)
|
INSERT INTO phrases (text, language, textbook, unit, source_context)
|
||||||
VALUES (?, 'es', ?, ?, ?)
|
VALUES (?, 'es', ?, ?, ?)
|
||||||
""", (spanish_text, textbook_name, unit_number, context_tag))
|
""", (spanish_text, textbook_name, unit_number, context_tag))
|
||||||
es_id = cursor.lastrowid
|
es_id = cursor.lastrowid
|
||||||
|
|
||||||
# 2. Insert English Term
|
# 2. Insert English Translation Entry
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
INSERT INTO phrases (text, language, textbook, unit, source_context)
|
INSERT INTO phrases (text, language, textbook, unit, source_context)
|
||||||
VALUES (?, 'en', ?, ?, ?)
|
VALUES (?, 'en', ?, ?, ?)
|
||||||
""", (english_text, textbook_name, unit_number, context_tag))
|
""", (english_text, textbook_name, unit_number, context_tag))
|
||||||
en_id = cursor.lastrowid
|
en_id = cursor.lastrowid
|
||||||
|
|
||||||
# 3. Create Bidirectional Bridges
|
# 3. Create Bidirectional Cross-Reference Records
|
||||||
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id))
|
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (es_id, en_id))
|
||||||
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id))
|
cursor.execute("INSERT INTO translations (source_phrase_id, target_phrase_id) VALUES (?, ?)", (en_id, es_id))
|
||||||
|
|
||||||
count += 1
|
count += 1
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
print(f"🎉 Successfully imported {count} linked glossary pairs into SQLite!")
|
print(f"🎉 Bulk ingestion complete! Registered {count} matched glossary items.")
|
||||||
return count
|
return count
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
conn.rollback()
|
conn.rollback()
|
||||||
print(f"❌ Error during bulk data ingestion: {e}")
|
print(f"❌ Error during database transaction: {e}")
|
||||||
return 0
|
return 0
|
||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
33
main.py
33
main.py
|
|
@ -1,33 +1,20 @@
|
||||||
# main.py
|
# main.py
|
||||||
import sys
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from database.connection import init_db
|
from database.connection import init_db
|
||||||
from core.phrase_manager import PhraseManager
|
from core.bulk_importer import BulkImporter
|
||||||
|
|
||||||
async def test_pipeline():
|
async def main():
|
||||||
print("🚀 Booting Castilian Voice Trainer Ingestion Engine...")
|
# Keep database schemas active and verified
|
||||||
|
|
||||||
# Ensure database is present
|
|
||||||
init_db()
|
init_db()
|
||||||
|
|
||||||
# Initialize the phrase controller manager
|
# Initialize our ingestion utility
|
||||||
manager = PhraseManager()
|
importer = BulkImporter()
|
||||||
|
|
||||||
# Test Entry: Let's log an authentic textbook phrase from Aula Internacional
|
# Process the file sitting right in your root directory
|
||||||
print("\n📥 Processing sample entry...")
|
importer.import_pdf_glossary(
|
||||||
success = await manager.add_translation_pair(
|
file_path="aula_int_plus_1_glos_en_alfa.pdf",
|
||||||
spanish_text="¿Cómo se pronuncia esta palabra?",
|
textbook_name="Aula Internacional 1 Plus"
|
||||||
english_text="How do you pronounce this word?",
|
|
||||||
textbook="Aula Internacional 1 Plus",
|
|
||||||
unit=2,
|
|
||||||
context="Glossary / Lesson Terms",
|
|
||||||
voice_gender="female" # Let's verify Elvira's voice on this one!
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if success:
|
|
||||||
print("\n🎉 Verification Phase 1 Pipeline Test complete!")
|
|
||||||
else:
|
|
||||||
print("\n⚠️ Pipeline processing failed.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
asyncio.run(test_pipeline())
|
asyncio.run(main())
|
||||||
|
|
@ -5,6 +5,7 @@ description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"docling>=2.102.1",
|
||||||
"edge-tts>=7.2.8",
|
"edge-tts>=7.2.8",
|
||||||
"fastdtw>=0.3.4",
|
"fastdtw>=0.3.4",
|
||||||
"genanki>=0.13.1",
|
"genanki>=0.13.1",
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue