Spaces:
Build error
Build error
| 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) |