CoolFace
Apppublic

Scooooooootttt/Equitylens

sourceHugging Faceupdated 3mo agoView on Hugging Face
0likes
App README

EquityLens

Agentic financial analysis pipeline for European public companies. Ingests SEC 20-F filings, IFRS-tagged XBRL financials, and ECB/Eurostat macro data; orchestrates specialist agents via a Supervisor pattern; outputs a structured research memo with sentence-level citation traceability.


Architecture

Streamlit UI (HuggingFace Spaces)
  Sidebar: controls + agent trace + quality report
  Centre:  four-section memo (clickable sentences)
  Right:   citation detail panel
       │
       ▼
LangGraph 4-node graph
  ┌─────────────────────────────────────────────────┐
  │  Supervisor  (ReAct loop, max 8 tool calls)     │
  │  Dynamically routes specialist agents based on  │
  │  intermediate findings. Different sectors and   │
  │  anomaly patterns produce different call paths. │
  └──────┬──────────────┬──────────────┬────────────┘
         │              │              │
   Financial         Document        Macro
   Agent (ReAct)     Agent (ReAct)   Agent (ReAct)
   XBRL metrics      LanceDB RAG     ECB / Eurostat
   ratio analysis    hybrid search   rate + GDP data
         │              │              │
         └──────────────┴──────────────┘
                        │
                  AnalysisState
                        │
              ┌─────────▼──────────┐
              │  Synthesis Agent   │
              │  XBRL vs narrative │
              │  cross-validation  │
              │  confidence scoring│
              └─────────┬──────────┘
                        │
              ┌─────────▼──────────┐
              │  Writer Agent      │
              │  4-section memo    │
              │  citation_ids per  │
              │  sentence          │
              └─────────┬──────────┘
                        │
              ┌─────────▼──────────┐
              │  QA Agent          │◄──── retry (max 2)
              │  citation grounding│
              │  numeric tracing   │
              └────────────────────┘

Graph topology: supervisor → synthesis → writer → qa, with a conditional edge qa → writer on failure (max 2 iterations). Supervisor routing complexity lives inside the ReAct loop, not in graph edges — this is the core architectural claim.


Tech Stack

LayerTechnology
LLMDeepSeek-V3 (deepseek-chat) via OpenAI-compatible SDK
Agent frameworkLangGraph — Supervisor pattern with create_react_agent
ObservabilityLangSmith — agent trace visualisation, sector routing comparison
EmbeddingsBAAI/bge-base-en-v1.5 (sentence-transformers, 768-dim, CPU)
Vector + FTSLanceDB 0.10.0 (pinned) + Tantivy BM25
RAG fusionManual RRF implementation (k=60 smoothing constant)
Document parsingunstructured[html] — NarrativeText / Table / FootnoteText
Data: filingsSEC EDGAR REST API (20-F HTM)
Data: financialsSEC XBRL companyfacts API (IFRS ifrs-full namespace)
Data: macroECB SDW API + Eurostat REST API
File cacheCloudflare R2 (optional, S3-compatible)
EvaluationLLM-as-Judge (5-dimension memo scoring) + Sector Routing Verification
FrontendStreamlit
DeploymentHuggingFace Spaces (free CPU tier)

Coverage Universe

Five European companies cross-listed on US exchanges. The app also supports any 20-F filer via dynamic SEC EDGAR CIK lookup — first-run analysis fetches and indexes the filing automatically.

CompanyTickerCIKCountrySectorPre-indexed
ASML HoldingASML0000937966NLSemiconductorsFY2023
SAP SESAP0001000184DEEnterprise SoftwareFY2023
Novo NordiskNVO0000353278DKPharmaceuticalsFY2023
Shell plcSHEL0001306965NLEnergyFY2023
ABB LtdABB0001091587CHIndustrial AutomationFY2023

Sector diversity is intentional: it produces observable differences in Supervisor call sequences (e.g. Enterprise Software skips macro_agent; Energy always calls it).


Quickstart

Prerequisites: Python 3.11

bash
git clone <repo-url>
cd equitylens
pip install -r requirements.txt
cp .env.example .env   # fill in required values (see below)
streamlit run app/app.py

Pre-built assets (XBRL metrics, macro data, LanceDB index for all 5 companies FY2022–2023) are committed to the repository. The UI loads instantly for the pre-indexed companies. For any other company/year, the preflight step auto-fetches and indexes on first request.


Environment Variables

Copy .env.example to .env and set:

VariableRequiredDescription
EDGAR_USER_AGENTYesAny string with name + email: EquityLens you@example.com
DEEPSEEK_API_KEYYesDeepSeek platform API key
LANGCHAIN_API_KEYNoLangSmith API key (enables agent trace logging)
LANGCHAIN_TRACING_V2NoSet true to activate LangSmith tracing
LANGCHAIN_PROJECTNoLangSmith project name (default: equitylens)
USE_CLOUD_CACHENoSet true to back up fetched data to Cloudflare R2
CF_R2_ACCOUNT_IDNoRequired if USE_CLOUD_CACHE=true
CF_R2_KEYNoRequired if USE_CLOUD_CACHE=true
CF_R2_SECRETNoRequired if USE_CLOUD_CACHE=true

HuggingFace Spaces: set EDGAR_USER_AGENT and DEEPSEEK_API_KEY as Secrets in the Space settings. LangSmith credentials are optional but recommended for trace inspection.


Data Ingestion (manual)

Pre-indexed data covers all 5 companies for FY2023. To add a company or year:

bash
# XBRL metrics + 20-F download + macro data
python data/ingest.py --company ASML --year 2022

# Build LanceDB index from downloaded filing
python rag/indexer.py --company ASML --year 2022

# Or let the UI handle it automatically on first run

Evaluation

Two evaluation scripts use DeepSeek as the judge LLM. No ground-truth QA pairs required.

LLM-as-Judge — evaluates the generated memo on 5 dimensions (1–5 scale, target ≥ 4):

bash
python evaluation/llm_judge_eval.py --company ASML --year 2023
python evaluation/llm_judge_eval.py --all --year 2023
python evaluation/llm_judge_eval.py --all --year 2023 --runs 2   # average over 2 judge runs
DimensionWhat it measures
factual_accuracyFinancial figures match XBRL reference data
analytical_depthInsights beyond data summarisation
risk_identificationCorrect risks, correctly prioritised
internal_consistencyInvestment Thesis coheres with Key Risks and Outlook
coverage_completenessAll 4 required sections are substantive

Sector Routing Verification — validates that the Supervisor's agent call sequence matches sector rules:

bash
python evaluation/routing_eval.py --company SHEL --year 2023
python evaluation/routing_eval.py --all --year 2023
SectorRule
Energy / Banksmacro_agent MUST be called
Enterprise Software / Softwaremacro_agent MUST NOT be called
Semiconductorsconditional (export-control signal required)

The --all run also checks the diversity claim: ≥ 3 unique call sequences across 5 companies, demonstrating that sector-aware routing is real, not uniform.

Results are saved to evaluation/results/.


Repository Structure

equitylens/
├── config.py                   # all env vars in one place (imported by all modules)
│
├── data/
│   ├── coverage_universe.py    # hardcoded registry + dynamic SEC EDGAR CIK lookup
│   ├── edgar.py                # 20-F HTM download from SEC EDGAR
│   ├── xbrl.py                 # IFRS metrics from SEC XBRL companyfacts API
│   ├── macro.py                # ECB SDW + Eurostat fetch + ECB publication index
│   ├── storage.py              # local / Cloudflare R2 dual-mode read/write
│   ├── ingest.py               # CLI orchestrator
│   ├── preflight.py            # auto-ingestion on first UI request
│   └── local/                  # pre-fetched data committed to repo
│       ├── metrics/            # XBRL JSONs (5 companies × FY2022–2023)
│       └── macro/              # ECB + Eurostat cache (2022, 2023)
│
├── rag/
│   ├── parser.py               # unstructured.io wrapper
│   ├── chunker.py              # three-path chunking (narrative / table / footnote)
│   ├── indexer.py              # LanceDB table management + FTS index build
│   ├── embed.py                # BGE embedding wrapper
│   └── tools.py                # search_narrative, extract_table, fetch_section (RRF)
│
├── tools/
│   ├── financial_tools.py      # get_xbrl_metrics, compute_derived_ratios, compare_yoy, detect_anomalies
│   └── macro_tools.py          # get_ecb_rates, get_eurostat_indicator, search_ecb_publications
│
├── agents/
│   ├── state.py                # AnalysisState TypedDict + all sub-TypedDicts
│   ├── supervisor.py           # Supervisor ReAct agent + sector-keyed macro routing rules
│   ├── financial_agent.py      # Financial ReAct subgraph
│   ├── document_agent.py       # Document ReAct subgraph
│   ├── macro_agent.py          # Macro ReAct subgraph
│   ├── synthesis_agent.py      # Synthesis node (cross-validation, confidence scoring)
│   ├── writer_agent.py         # Writer node + WriterLLMClient abstraction
│   ├── qa_agent.py             # QA node + route_after_qa conditional
│   └── graph.py                # LangGraph graph assembly + compile()
│
├── evaluation/
│   ├── llm_judge_eval.py       # LLM-as-Judge: 5-dimension memo scoring (CLI)
│   ├── routing_eval.py         # Sector routing verification (CLI)
│   ├── results/                # JSON outputs per company/year
│   └── test_dataset/           # 12 QA pairs × 5 companies (reference only)
│
├── app/
│   └── app.py                  # Streamlit UI
│
├── notebooks/
│   └── agent_traces.ipynb      # LangSmith trace comparison across sectors
│
├── data/lancedb_index/         # pre-built LanceDB index committed to repo
└── requirements.txt

Cost

DeepSeek-V3: ~$0.03 per analysis run (65k input + 15k output tokens across all agents).