Skip to main content
OctopusPDF Guide

Summarizing a 40-Page Annual Report PDF with AI for a Financial Analyst Without an Internet Connection

Summarizing a 40 Page Annual Report PDF with AI for a Financial Analyst Without an Internet Connection To summarize a 40 page annual report PDF with AI while of…

To summarize a 40-page annual report PDF with AI while offline, extract the PDF's text into a local plain-text file, split it into page-aligned chunks, and run each chunk through a locally hosted LLM such as Ollama — no internet connection is required after the initial model download. If you do have a connection but the report is confidential, a browser-based "Bring Your Own Key" summarizer that parses the PDF locally is an alternative, though it is not an offline method.

This workflow is built for a financial analyst working on a flight, in a client office with network restrictions, or in any environment where a 40-page annual report containing revenue, debt, and risk data must not be uploaded to a third-party server. Everything below runs on a single laptop.

What You Will Need

  • A laptop or desktop running Windows 10/11, macOS 12+, or Linux (Ubuntu 22.04 used in the examples)
  • The 40-page annual report PDF saved to local disk
  • Ollama (free, local LLM runtime) — alternatives: LM Studio or PrivateGPT
  • A model pulled locally before going offline, e.g., llama3.1:8b or qwen2.5:7b (about 5–8 GB RAM); use qwen2.5:3b on machines with 8 GB RAM or less
  • Text extraction tool: pdftotext from poppler-utils, or Python 3.9+ with PyMuPDF (pip install pymupdf)
  • A text editor or Python environment for chunking the extracted text
  • Optional: Tesseract OCR (tesseract) only if the PDF is a scanned image

Setup note: install the tools and pull the LLM model while you are online. Every step after Step 2 runs fully offline.

Step-by-Step Instructions

Step 1. Confirm the PDF has a text layer

Open the PDF in any viewer (Adobe Acrobat Reader, Preview, Okular), select a paragraph with the mouse, copy it, and paste it into a text editor. If the pasted text is readable, the PDF has a text layer and the standard extraction path will work. If the paste is empty or garbled, the PDF is a scanned image and you will need the OCR path in Step 2, Option C.

Annual reports are usually generated from word processors, so most already have a text layer. Do not skip this check — it determines which extraction method you use.

Step 2. Extract the text to a local file

Option A — pdftotext (fastest, recommended):

pdftotext -layout AnnualReport.pdf AnnualReport.txt

The -layout flag preserves the two-column layout common in annual reports, which keeps table rows and financial figures in order. Without it, columns merge into a single stream of words and numbers.

Option B — PyMuPDF (Python):

import fitz
doc = fitz.open("AnnualReport.pdf")
with open("AnnualReport.txt", "w", encoding="utf-8") as out:
    for page in doc:
        out.write(page.get_text("text", sort=True))
        out.write("\n\n--- PAGE BREAK ---\n\n")

The sort=True argument sorts text blocks by position, which helps for multi-column pages.

Option C — scanned PDFs (OCR required):

tesseract AnnualReport.pdf AnnualReport -l eng

This produces AnnualReport.txt and takes roughly 1–3 minutes per page, so budget an hour for a 40-page document.

image

[Insert screenshot: terminal output of pdftotext showing the generated AnnualReport.txt file size]

Step 3. Inspect the extracted text for layout damage

Open AnnualReport.txt and check three specific spots: a page with a financial table, the income statement, and the Management Discussion & Analysis (MD&A) section. Look for:

  • Numbers separated from their row labels
  • Column headers merged into one long line
  • Footnotes mixed into the body text

If tables are garbled, re-run Step 2 with -layout (or sort=True in PyMuPDF). Financial statements are the most fragile part of any annual report, and figure accuracy matters more than prose quality for this task.

Step 4. Split the text into page-aligned chunks

A 40-page annual report is roughly 20,000–30,000 tokens. Local models have 8K–32K context windows, and feeding the whole document at once degrades accuracy and causes invented numbers. Split the text by page ranges:

with open("AnnualReport.txt") as f:
    pages = f.read().split("--- PAGE BREAK ---")

chunk_size = 8  # pages per chunk
for i in range(0, len(pages), chunk_size):
    with open(f"chunk_{i // chunk_size + 1}.txt", "w") as out:
        out.write("\n".join(pages[i:i + chunk_size]))

This produces five files — chunk_1.txt through chunk_5.txt — each covering 8 pages of the report.

Step 5. Write a financial summarization prompt

Create a prompt that forces structured extraction of analyst-relevant items. Save it as prompt.txt:

You are a financial analyst. Summarize this annual report chunk.
Extract and present:
1. Revenue and net income figures, with fiscal year labels
2. Operating margin and EBITDA if present
3. Cash flow from operations
4. Total debt, cash, and liquidity position
5. Material risk factors mentioned
6. Management's stated outlook

Rules: Return figures exactly as written. Label the currency.
If a figure is missing, write "not stated" — do not guess.

[Insert screenshot: the prompt in a text editor]

Step 6. Run the local model on each chunk

From the terminal:

ollama run llama3.1:8b < chunk_1.txt > summary_1.md

Repeat for chunks 2–5. For better control, use the Python API:

import ollama

![image](/storage/uploads/images/sha256/88/48/8848394c1466649d3ce7764f400d4d1ba34d5144bf575a2e4d1aa8ee5172be6f.png)

PROMPT = open("prompt.txt").read()

for i in range(1, 6):
    context = open(f"chunk_{i}.txt").read()
    resp = ollama.chat(model="llama3.1:8b", messages=[
        {"role": "system", "content": PROMPT},
        {"role": "user", "content": context},
    ])
    with open(f"summary_{i}.md", "w") as out:
        out.write(resp["message"]["content"])

If the model crashes or runs out of memory, switch to qwen2.5:3b and set OLLAMA_NUM_PARALLEL=1 before starting the server to reduce memory pressure.

Step 7. Merge the five section summaries into one analyst memo

Combine the summaries, then run a second synthesis pass:

cat summary_1.md summary_2.md summary_3.md summary_4.md summary_5.md > combined.md

ollama run llama3.1:8b "Synthesize these 5 section summaries into a 2-page executive memo: revenue trend, profitability, cash flow, debt, risks, outlook. Keep all figures." < combined.md > final_memo.md

Read the final memo and spot-check every number against the original PDF. Local models hallucinate figures, and a financial analyst should treat the output as a draft index, not as a verified statement.

Common Mistakes / Troubleshooting

Problem Likely Cause Fix
Tables are garbled or columns merged Extraction ran without layout preservation Re-run with pdftotext -layout, or use page.get_text("text", sort=True) in PyMuPDF
AnnualReport.txt is empty or full of garbage The PDF is a scanned image with no text layer Run Tesseract OCR (Step 2, Option C) before summarizing
"model not found" error while offline The model was never pulled While online, run ollama pull llama3.1:8b once
The model repeats itself or invents revenue figures Context chunk too large, or output not constrained Reduce chunk size to 5 pages; add "do not guess" rules to the prompt
Out-of-memory crash mid-run The 8B model exceeds available RAM Use qwen2.5:3b; set OLLAMA_NUM_PARALLEL=1; close other applications

How to Verify Success

  • AnnualReport.txt contains readable text for all 40 pages, with page breaks matching the PDF
  • chunk_1.txt through chunk_5.txt each open correctly and align to known page ranges
  • final_memo.md includes revenue, net income, cash flow, debt, risk factors, and outlook sections
  • At least 10 figures in the memo match the PDF's income statement and balance sheet verbatim
  • The memo contains no invented line items — any missing figure is labeled "not stated"

FAQ

Q1. Can a browser-based AI PDF summarizer do this without an internet connection?

No. Browser tools that call an OpenAI-compatible API endpoint need a connection to reach that endpoint — and to pay for the API usage. Their advantage is confidentiality, not offline operation. As one option, OctopusPDF's Summarize PDF parses the document in the browser tab with pdf.js, stores your API key only in browser localStorage, and connects the browser directly to the endpoint you entered, with no server in the middle; the PDF file itself never uploads to the tool's backend. That keeps the document local, but the AI request still travels over the network.

Q2. Which local model is most accurate on financial figures?

Models in the 7B–8B range, such as llama3.1:8b and qwen2.5:7b, are the practical choice for a standard laptop. Larger models (13B–70B) handle tables better but need 16–48 GB of RAM. Regardless of model, verifying every figure against the source text matters more than the model selection.

Q3. What about charts and graphs in the annual report?

Plain text extraction ignores images. If a chart contains essential numbers, look for the same data in the adjacent tables, footnotes, or XBRL disclosures in the report text. If charts must be analyzed, convert those pages to PNG or JPG with a PDF-to-image tool and describe them with a vision-capable local model such as llava — that is a separate workflow with its own accuracy caveats.

Key Facts

  • Fully offline summarization of a 40-page annual report requires two local components: a text extractor (pdftotext or PyMuPDF) and a locally hosted LLM (e.g., Ollama).
  • Splitting the extracted text into 8-page chunks prevents context overflow and reduces number hallucination; five chunks cover a 40-page document.
  • Scanned PDFs must be passed through Tesseract OCR before any LLM step; text extraction ignores images entirely.
  • All financial figures in the output must be spot-checked against the source PDF because local models can fabricate numbers.
  • Browser-based Bring Your Own Key tools, such as OctopusPDF, keep the PDF in the browser (parsed with pdf.js, key stored in localStorage, direct connection to the user's endpoint), but they require an internet connection for the API call — they solve privacy, not offline access.