from __future__ import annotations import json import sqlite3 from pathlib import Path from typing import Any ROOT = Path(__file__).resolve().parent.parent DATA_DIR = ROOT / "data" UPLOAD_DIR = DATA_DIR / "uploads" PREVIEW_DIR = DATA_DIR / "previews" DB_PATH = DATA_DIR / "lernotafiscal.sqlite3" def ensure_storage() -> None: DATA_DIR.mkdir(exist_ok=True) UPLOAD_DIR.mkdir(exist_ok=True) PREVIEW_DIR.mkdir(exist_ok=True) def connect(db_path: Path = DB_PATH) -> sqlite3.Connection: ensure_storage() conn = sqlite3.connect(db_path) conn.row_factory = sqlite3.Row conn.execute("PRAGMA foreign_keys = ON") return conn def init_db(conn: sqlite3.Connection) -> None: conn.executescript( """ CREATE TABLE IF NOT EXISTS uploaded_files ( id INTEGER PRIMARY KEY AUTOINCREMENT, original_name TEXT NOT NULL, stored_path TEXT NOT NULL, content_type TEXT NOT NULL, size_bytes INTEGER NOT NULL, status TEXT NOT NULL DEFAULT 'pending', detected_count INTEGER NOT NULL DEFAULT 0, message TEXT, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS detected_documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, upload_id INTEGER NOT NULL REFERENCES uploaded_files(id) ON DELETE CASCADE, source_file_name TEXT NOT NULL, source_page INTEGER, source_location TEXT NOT NULL, raw_text TEXT NOT NULL, purchase_date TEXT, supplier_name TEXT, total_paid REAL, confidence TEXT NOT NULL, field_confidence_json TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'pending', created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS fiscal_documents ( id INTEGER PRIMARY KEY AUTOINCREMENT, detected_document_id INTEGER REFERENCES detected_documents(id) ON DELETE SET NULL, source_file_name TEXT NOT NULL, source_location TEXT NOT NULL, purchase_date TEXT NOT NULL, supplier_name TEXT NOT NULL, total_paid REAL NOT NULL, confidence TEXT NOT NULL, created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP ); """ ) conn.commit() def insert_upload( conn: sqlite3.Connection, original_name: str, stored_path: Path, content_type: str, size_bytes: int, ) -> int: cur = conn.execute( """ INSERT INTO uploaded_files (original_name, stored_path, content_type, size_bytes) VALUES (?, ?, ?, ?) """, (original_name, str(stored_path), content_type, size_bytes), ) conn.commit() return int(cur.lastrowid) def update_upload_status( conn: sqlite3.Connection, upload_id: int, status: str, detected_count: int, message: str | None = None, ) -> None: conn.execute( """ UPDATE uploaded_files SET status = ?, detected_count = ?, message = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ? """, (status, detected_count, message, upload_id), ) conn.commit() def insert_detected_document( conn: sqlite3.Connection, upload_id: int, candidate: Any, ) -> int: cur = conn.execute( """ INSERT INTO detected_documents ( upload_id, source_file_name, source_page, source_location, raw_text, purchase_date, supplier_name, total_paid, confidence, field_confidence_json ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) """, ( upload_id, candidate.source_file_name, candidate.source_page, candidate.source_location, candidate.raw_text, candidate.purchase_date, candidate.supplier_name, candidate.total_paid, candidate.confidence, json.dumps(candidate.field_confidence, ensure_ascii=True), ), ) conn.commit() return int(cur.lastrowid) def pending_documents(conn: sqlite3.Connection) -> list[sqlite3.Row]: return list( conn.execute( """ SELECT d.*, u.original_name FROM detected_documents d JOIN uploaded_files u ON u.id = d.upload_id WHERE d.status = 'pending' ORDER BY d.created_at DESC, d.id DESC """ ) ) def uploads_summary(conn: sqlite3.Connection) -> list[sqlite3.Row]: return list( conn.execute( """ SELECT * FROM uploaded_files ORDER BY created_at DESC, id DESC LIMIT 20 """ ) ) def attention_uploads(conn: sqlite3.Connection) -> list[sqlite3.Row]: return list( conn.execute( """ SELECT * FROM uploaded_files WHERE status IN ('needs_attention', 'failed') ORDER BY updated_at DESC, id DESC LIMIT 20 """ ) ) def review_counts(conn: sqlite3.Connection) -> dict[str, int]: rows = conn.execute( """ SELECT status, COUNT(*) AS total FROM detected_documents GROUP BY status """ ).fetchall() counts = {"pending": 0, "confirmed": 0, "ignored": 0} for row in rows: counts[row["status"]] = int(row["total"]) return counts def get_detected(conn: sqlite3.Connection, detected_id: int) -> sqlite3.Row | None: return conn.execute( "SELECT * FROM detected_documents WHERE id = ?", (detected_id,), ).fetchone() def confirm_document( conn: sqlite3.Connection, detected_id: int, purchase_date: str, supplier_name: str, total_paid: float, ) -> None: row = get_detected(conn, detected_id) if row is None: raise ValueError("Documento detectado nao encontrado.") conn.execute( """ UPDATE detected_documents SET purchase_date = ?, supplier_name = ?, total_paid = ?, status = 'confirmed', updated_at = CURRENT_TIMESTAMP WHERE id = ? """, (purchase_date, supplier_name, total_paid, detected_id), ) conn.execute( """ INSERT INTO fiscal_documents ( detected_document_id, source_file_name, source_location, purchase_date, supplier_name, total_paid, confidence ) VALUES (?, ?, ?, ?, ?, ?, ?) """, ( detected_id, row["source_file_name"], row["source_location"], purchase_date, supplier_name, total_paid, row["confidence"], ), ) conn.commit() def ignore_document(conn: sqlite3.Connection, detected_id: int) -> None: conn.execute( """ UPDATE detected_documents SET status = 'ignored', updated_at = CURRENT_TIMESTAMP WHERE id = ? """, (detected_id,), ) conn.commit() def dashboard_documents( conn: sqlite3.Connection, start: str | None = None, end: str | None = None, supplier: str | None = None, ) -> list[sqlite3.Row]: clauses = [] params: list[Any] = [] if start: clauses.append("purchase_date >= ?") params.append(start) if end: clauses.append("purchase_date <= ?") params.append(end) if supplier: clauses.append("supplier_name LIKE ?") params.append(f"%{supplier}%") where = f"WHERE {' AND '.join(clauses)}" if clauses else "" return list( conn.execute( f""" SELECT * FROM fiscal_documents {where} ORDER BY purchase_date DESC, id DESC """, params, ) ) def monthly_totals(conn: sqlite3.Connection) -> list[sqlite3.Row]: return list( conn.execute( """ SELECT substr(purchase_date, 1, 7) AS month, SUM(total_paid) AS total, COUNT(*) AS count FROM fiscal_documents GROUP BY substr(purchase_date, 1, 7) ORDER BY month DESC """ ) ) def supplier_totals(conn: sqlite3.Connection) -> list[sqlite3.Row]: return list( conn.execute( """ SELECT supplier_name, SUM(total_paid) AS total, COUNT(*) AS count FROM fiscal_documents GROUP BY supplier_name ORDER BY total DESC LIMIT 10 """ ) )