Architecture
┌──────────────────────────┐
│ Streamlit UI (ui.py) │
│ upload · dashboard · chat │
└────────────┬─────────────┘
│
┌────────────────────┴────────────────────┐
│ │
PDF upload │ chat message │
▼ ▼
╔══════════════════════════════╗ ╔══════════════════════════════╗
║ INGESTION GRAPH ║ ║ QUERY GRAPH ║
║ (runs once/PDF) ║ ║ (runs once/message) ║
╟──────────────────────────────╢ ╟──────────────────────────────╢
║ ║ ║ ║
║ START ║ ║ START ║
║ │ ║ ║ │ ║
║ ▼ ║ ║ ▼ ║
║ split_document ║ ║ route_query ║
║ (locate 3 statements) ║ ║ (pick relevant statements) ║
║ ╱ ╲ ║ ║ │ ║
║ Send×3╱ ╲Send×3 ║ ║ ▼ ║
║ ▼ ▼ ║ ║ generate_answer ◄──────┐ ║
║ summarize_ parse_ ║ ║ (answer + chart_spec) │ ║
║ statement statement ║ ║ │ │ ║
║ (×3 ‖) (×3 ‖) ║ ║ ▼ "retry" │ ║
║ ╲ ╱ ║ ║ validate_answer ───────┘ ║
║ ╲ ╱ ║ ║ (audit vs source data) ║
║ ▼ ▼ ║ ║ │ "done" ║
║ build_kpis ║ ║ ▼ (max 2 retries)║
║ (8 executive KPIs) ║ ║ END ║
║ │ ║ ║ ║
║ END ║ ╚══════════════════════════════╝
╚══════════════════════════════╝
│ │
▼ ▼
raw_pages → chunks → summaries answer_text + chart_spec
+ parsed_statements + kpis (rendered in chat)
┌─────────────────────────┐
│ OpenAI GPT-4o │
│ (structured JSON output) │
└─────────────────────────┘
every node calls the LLM via main.py client
Tech Stack
| Layer | Technology | Purpose |
|---|---|---|
| Orchestration | LangGraph >=0.2.0 | State-graph workflows, parallel fan-out (Send), conditional retry edges |
| LLM | OpenAI GPT-4o (openai>=1.0.0) | Document navigation, parsing, summarisation, routing, answering, validation |
| UI | Streamlit >=1.44.0 | Web app — dashboard, tabs, chat interface |
| PDF parsing | PyMuPDF (fitz) >=1.25.0 | Per-page text extraction from annual reports |
| Charts | Plotly >=5.24.0 | Interactive bar / line / pie / waterfall charts |
| Data | pandas >=2.0.0 | Statement tables in the UI |
| Validation | Pydantic >=2.7.4 | Type models (via LangGraph) |
| Config | python-dotenv >=1.0.0 | Loads OPENAI_API_KEY from .env |
| Language | Python 3.12 | — |