# ============================================================ # Project: EDGAR Research Database # Description: SEC EDGAR Research Infrastructure # # Authors: # - Manel Kammoun — manel.kammoun@uqo.ca # - Djerrycharli Tandjambianda — djerrycharli.tandjambianda@uqo.ca # - Simon-Pierre Boucher — simon-pierre.boucher@uqo.ca # # Institution: # Université du Québec en Outaouais (UQO) # # License: # Research / Academic Use # ============================================================ from __future__ import annotations from pathlib import Path from typing import Any import pandas as pd from src.utils.constants import DUCKDB_PATH from src.utils.logging import get_logger logger = get_logger("api.queries") class ResearchAPI: def __init__(self, db_path: Path | str | None = None) -> None: self._db_path = Path(db_path) if db_path else DUCKDB_PATH self._db: Any = None self._emb: Any = None self._search: Any = None self._panel: Any = None @property def db(self): if self._db is None: from src.storage.database import DatabaseManager self._db = DatabaseManager(self._db_path) self._db.initialize() return self._db @property def embedding_manager(self): if self._emb is None: from src.embeddings.manager import EmbeddingManager from src.storage.parquet_manager import ParquetManager parquet = ParquetManager() self._emb = EmbeddingManager(self.db, parquet) return self._emb @property def semantic_search(self): if self._search is None: from src.search.semantic import SemanticSearch self._search = SemanticSearch(self.db, self.embedding_manager) return self._search @property def panel_builder(self): if self._panel is None: from src.analytics.panel import PanelBuilder self._panel = PanelBuilder(self.db) return self._panel def get_filings(self, **kwargs) -> pd.DataFrame: return self.db.get_filings(**kwargs) def get_sections(self, accession_number: str) -> pd.DataFrame: return self.db.get_sections(accession_number) def get_chunks( self, accession_number: str | None = None, section_id: str | None = None, ) -> pd.DataFrame: return self.db.get_chunks(accession_number=accession_number, section_id=section_id) def search(self, query: str, top_k: int = 20, **filters) -> pd.DataFrame: return self.semantic_search.search(query, top_k=top_k, **filters) def build_panel(self, **kwargs) -> pd.DataFrame: return self.panel_builder.build_panel(**kwargs) def get_company_history(self, ticker: str) -> pd.DataFrame: sql = """ SELECT f.accession_number, f.form_type, f.filing_date, f.fiscal_year, f.fiscal_quarter, f.download_status, f.parse_status, COUNT(DISTINCT s.section_id) AS section_count, COUNT(DISTINCT ch.chunk_id) AS chunk_count, COALESCE(SUM(s.word_count), 0) AS total_word_count FROM filings f LEFT JOIN filing_sections s ON f.accession_number = s.accession_number LEFT JOIN filing_chunks ch ON f.accession_number = ch.accession_number WHERE f.ticker = ? GROUP BY f.accession_number, f.form_type, f.filing_date, f.fiscal_year, f.fiscal_quarter, f.download_status, f.parse_status ORDER BY f.filing_date DESC """ return self.db.fetch_df(sql, [ticker]) def get_stats(self) -> dict: return self.db.get_stats() def sql(self, query: str) -> pd.DataFrame: return self.db.fetch_df(query) def close(self) -> None: if self._db is not None: self._db.close() self._db = None self._emb = None self._search = None self._panel = None def __enter__(self) -> ResearchAPI: return self def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None: self.close()