Faraz618's picture
Create utils/helpers.py
b346f34 verified
Raw
History Blame Contribute Delete
4 kB
import re
import os
import time
import logging
from pathlib import Path
from typing import Tuple
logger = logging.getLogger(__name__)
def reshape_arabic_text(text: str) -> str:
"""Reshape Arabic text for proper display."""
try:
import arabic_reshaper
from bidi.algorithm import get_display
reshaped = arabic_reshaper.reshape(text)
bidi_text = get_display(reshaped)
return bidi_text
except Exception:
return text
def validate_file(file_path: str, max_size_mb: int = 20) -> Tuple[bool, str]:
"""Validate uploaded file size and extension."""
from config import SUPPORTED_FORMATS
path = Path(file_path)
if not path.exists():
return False, "File does not exist"
ext = path.suffix.lower()
if ext not in SUPPORTED_FORMATS:
return False, f"Unsupported format: {ext}. Supported: {', '.join(SUPPORTED_FORMATS)}"
size_mb = path.stat().st_size / (1024 * 1024)
if size_mb > max_size_mb:
return False, f"File too large: {size_mb:.1f}MB. Maximum: {max_size_mb}MB"
return True, "OK"
def format_file_size(size_bytes: int) -> str:
"""Human-readable file size."""
if size_bytes < 1024:
return f"{size_bytes} B"
elif size_bytes < 1024 * 1024:
return f"{size_bytes / 1024:.1f} KB"
else:
return f"{size_bytes / (1024 * 1024):.1f} MB"
def truncate_text(text: str, max_chars: int = 500) -> str:
"""Truncate long text for display."""
if len(text) <= max_chars:
return text
return text[:max_chars] + "..."
def calculate_arabic_ratio(text: str) -> float:
"""Calculate ratio of Arabic characters in text."""
if not text:
return 0.0
arabic_chars = len(re.findall(r'[\u0600-\u06FF]', text))
return round(arabic_chars / len(text), 4)
def timer_ms(func):
"""Decorator to measure function execution time in ms."""
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = (time.time() - start) * 1000
logger.info(f"{func.__name__} took {elapsed:.2f}ms")
return result, elapsed
return wrapper
def clean_arabic_text(text: str) -> str:
"""Basic Arabic text cleaning."""
# Remove non-printable characters
text = re.sub(r'[\x00-\x08\x0b\x0c\x0e-\x1f\x7f-\x9f]', '', text)
# Normalize whitespace
text = re.sub(r'\s+', ' ', text).strip()
# Remove standalone punctuation clusters
text = re.sub(r'([^\w\s])\1{3,}', r'\1', text)
return text
def build_summary_display(structured_doc: dict) -> str:
"""Build a human-readable summary of structured document."""
if not structured_doc:
return "No document processed yet."
meta = structured_doc.get("metadata", {})
analysis = structured_doc.get("document_analysis", {})
entities = structured_doc.get("extracted_entities", {})
dates = structured_doc.get("extracted_dates", [])
financials = structured_doc.get("financial_data", {})
lines = [
f"πŸ“„ **Document ID:** {structured_doc.get('document_id', 'N/A')}",
f"πŸ“ **Filename:** {meta.get('filename', 'N/A')}",
f"πŸ” **Type:** {analysis.get('document_type', 'N/A')}",
f"🌐 **Language:** {analysis.get('language', 'N/A')}",
f"πŸ“Š **Pages:** {analysis.get('total_pages', 0)} | **Words:** {analysis.get('total_words', 0)}",
f"βš™οΈ **Extraction:** {meta.get('extraction_source', 'N/A')} | **Confidence:** {meta.get('extraction_confidence', 0):.2%}",
"",
f"πŸ“… **Dates Found:** {', '.join(dates) if dates else 'None'}",
f"πŸ’° **Amounts:** {', '.join(financials.get('amounts', [])) if financials.get('amounts') else 'None'}",
f"πŸ“§ **Emails:** {', '.join(entities.get('emails', [])) if entities.get('emails') else 'None'}",
f"πŸ“ž **Phones:** {', '.join(entities.get('phone_numbers', [])) if entities.get('phone_numbers') else 'None'}",
]
return "\n".join(lines)