2026-06-13 02:03:58 +00:00
|
|
|
# core/bulk_importer.py
|
2026-06-16 05:06:08 +00:00
|
|
|
import os
|
2026-06-13 02:03:58 +00:00
|
|
|
import re
|
2026-06-13 02:41:59 +00:00
|
|
|
from docling.document_converter import DocumentConverter
|
2026-06-16 05:06:08 +00:00
|
|
|
from database.connection import get_connection
|
2026-06-13 02:03:58 +00:00
|
|
|
|
|
|
|
|
class BulkImporter:
|
2026-06-16 05:06:08 +00:00
|
|
|
def import_pdf_glossary(self, pdf_path: str, textbook_name: str):
|
|
|
|
|
"""Uses Docling layout extraction engine to parse tables out of multi-column glossary PDFs."""
|
|
|
|
|
print(f"🔄 Analyzing structural layout for '{pdf_path}'...")
|
2026-06-13 07:02:06 +00:00
|
|
|
|
2026-06-16 05:06:08 +00:00
|
|
|
if not os.path.exists(pdf_path):
|
|
|
|
|
print(f"❌ Target document path could not be found: {pdf_path}")
|
|
|
|
|
return
|
2026-06-13 02:03:58 +00:00
|
|
|
|
|
|
|
|
conn = get_connection()
|
|
|
|
|
cursor = conn.cursor()
|
|
|
|
|
|
2026-06-16 05:06:08 +00:00
|
|
|
# Reset staging tables to guarantee fresh data state
|
|
|
|
|
cursor.execute("DELETE FROM translations")
|
|
|
|
|
cursor.execute("DELETE FROM phrases")
|
2026-06-13 07:02:06 +00:00
|
|
|
conn.commit()
|
2026-06-16 05:06:08 +00:00
|
|
|
|
|
|
|
|
print("📥 Initializing Docling layout analysis engine...")
|
|
|
|
|
try:
|
|
|
|
|
# 1. Initialize Docling converter
|
|
|
|
|
converter = DocumentConverter()
|
|
|
|
|
result = converter.convert(pdf_path)
|
|
|
|
|
|
|
|
|
|
print("📥 Parsing extracted text tables and tabular structures...")
|
|
|
|
|
raw_inserts_count = 0
|
|
|
|
|
|
|
|
|
|
# 2. Iterate through extracted tables found inside the layout structure
|
|
|
|
|
for table_idx, table_element in enumerate(result.document.tables):
|
|
|
|
|
# Convert Docling table data framework back to standard pandas-like dictionary arrays
|
|
|
|
|
table_data = table_element.export_to_dataframe()
|
|
|
|
|
|
|
|
|
|
# Iterate rows while ensuring it's not looking at headers or broken table pieces
|
|
|
|
|
for row_idx, row in table_data.iterrows():
|
|
|
|
|
row_list = list(row)
|
|
|
|
|
|
|
|
|
|
# Ensure we have enough columns to look at your glossary structure (expecting 4-6 columns)
|
|
|
|
|
if len(row_list) < 3:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Extract positions out of the 6-column structure:
|
|
|
|
|
# Usually: Col 0 = Spanish, Col 1 = English, Col 2 or 3 = Context/Unit tag
|
|
|
|
|
es_raw = str(row_list[0]).strip() if row_list[0] else ""
|
|
|
|
|
en_raw = str(row_list[1]).strip() if row_list[1] else ""
|
|
|
|
|
context_raw = str(row_list[2]).strip() if row_list[2] else ""
|
|
|
|
|
|
|
|
|
|
# Ignore empty lines or column labels (like "Spanish", "Español", "English")
|
|
|
|
|
if not es_raw or not en_raw or "español" in es_raw.lower() or "english" in en_raw.lower():
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Parse out unit identifiers from context strings (e.g. "U2_3A" gives unit = 2)
|
|
|
|
|
unit_num = 1
|
|
|
|
|
unit_match = re.search(r'U(\d+)', context_raw, re.IGNORECASE)
|
|
|
|
|
if unit_match:
|
|
|
|
|
unit_num = int(unit_match.group(1))
|
|
|
|
|
|
|
|
|
|
# 3. Stage Spanish item row
|
|
|
|
|
cursor.execute("""
|
|
|
|
|
INSERT INTO phrases (text, language, textbook, unit, source_context, word_type)
|
|
|
|
|
VALUES (?, 'es', ?, ?, ?, 'phrase')
|
|
|
|
|
""", (es_raw, textbook_name, unit_num, context_raw))
|
|
|
|
|
|
|
|
|
|
# 4. Stage English item row (immediately adjacent)
|
|
|
|
|
cursor.execute("""
|
|
|
|
|
INSERT INTO phrases (text, language, textbook, unit, source_context, word_type)
|
|
|
|
|
VALUES (?, 'en', ?, ?, ?, 'phrase')
|
|
|
|
|
""", (en_raw, textbook_name, unit_num, context_raw))
|
|
|
|
|
|
|
|
|
|
raw_inserts_count += 2
|
|
|
|
|
|
|
|
|
|
conn.commit()
|
|
|
|
|
print(f"🎉 Bulk ingestion staging complete! Staged {raw_inserts_count} entries.")
|
|
|
|
|
|
|
|
|
|
except Exception as docling_error:
|
|
|
|
|
print(f"❌ Docling encountered a processing failure: {docling_error}")
|
|
|
|
|
import traceback
|
|
|
|
|
traceback.print_exc()
|
|
|
|
|
finally:
|
|
|
|
|
conn.close()
|