Open to projects
All insights

Building a RAG Pipeline for Persian Financial Documents

In early 2025, I faced a problem that sounded straightforward on paper: extract structured ESG and business performance data from the annual reports of over 150 Iranian public companies. In practice, it was anything but.

The reports were in Persian (Farsi), they were PDFs of variable quality — some scanned, some natively digital — and they followed no consistent schema. Previous attempts to analyse this data had relied on manual extraction, which capped the sample at 20–30 companies at best.

The Core Challenge

Persian NLP has come a long way, but financial document analysis introduces compounding difficulties:

  • Right-to-left layout confuses most PDF parsers, which assume LTR text flow
  • Mixed-script tables — many reports embed English numbers and Latin abbreviations inside Persian prose
  • Inconsistent structure — disclosure formats changed year-over-year, and smaller companies used bespoke templates
  • Scanned pages — approximately 30% of documents required OCR before any NLP could run

Architecture: OCR → Chunking → LLM Extraction → Validation

The pipeline I designed has four stages:

Stage 1 — OCR and normalisation. For scanned pages, I used a combination of pytesseract with a Persian-trained model and pdfplumber for native-text pages. A preprocessing step detects whether a page is image-based or text-based before routing it.

Stage 2 — Semantic chunking. Rather than splitting on fixed token counts, I chunked by semantic section — using header detection (bold, larger font-size) to identify report sections like “Financial Performance”, “Board Composition”, and “Environmental Policy”. This dramatically improved extraction accuracy because the LLM received coherent context.

Stage 3 — LLM extraction with structured output. I used a large language model prompted to extract specific fields into a strict JSON schema defined with Pydantic. The schema enforced types, ranges, and required fields — so hallucinated or malformed outputs were rejected at the validation layer, not silently accepted.

Stage 4 — Asynchronous orchestration. Processing 150+ companies synchronously would have taken days. I built an async backend using asyncio and httpx that processed multiple documents concurrently, with rate limiting to stay within API quotas.

What Surprised Me

The biggest lesson was about failure modes at scale. When you test on 10 documents, edge cases are invisible. At 150+, they become the dominant problem. I spent roughly 40% of the project handling documents that broke assumptions I hadn’t known I was making:

  • Reports where the “Environmental” section was titled differently in every year
  • Tables that spanned page breaks, causing OCR to split a single data row across two chunks
  • Companies that filed their reports as protected PDFs — requiring a separate extraction path

Building robust error handling, logging every failure reason, and designing the pipeline to be resumable (not restart-from-scratch on failure) turned out to be as important as the model prompting itself.

Results

The final dataset covered 153 public companies across three fiscal years, with a verified extraction accuracy of approximately 87% on structured numeric fields. This enabled ESG analysis that had previously been infeasible — work that is now being used as part of a Master’s research project on corporate social responsibility compliance at the Tehran Stock Exchange.

The pipeline code is not yet public, but I’m happy to discuss the architecture in detail. Get in touch →