---
status: pending
priority: p3
issue_id: "008"
tags: [code-review, performance, python]
---

# Two-pass heading detection: cache `get_text("dict")` from first pass

## Problem Statement

`_detect_from_headings()` calls `page.get_text("dict")` twice per page — once in the font-size collection pass and once in the heading-candidate pass. This discards all the parsing work done in the first pass.

## Findings

**`condensr/parser.py:194-215`:**
```python
# Pass 1: collect sizes
for page in doc:
    blocks = page.get_text("dict")["blocks"]   # parse page
    ...

# Pass 2: find headings
for page_idx in range(len(doc)):
    page = doc[page_idx]
    blocks = page.get_text("dict")["blocks"]   # parse page again
```

## Proposed Solution

Store dicts from the first pass:

```python
page_dicts = []
all_sizes_set: set[float] = set()

for page in doc:
    d = page.get_text("dict")
    page_dicts.append(d)
    for block in d["blocks"]:
        if block.get("type") != 0:
            continue
        for line in block["lines"]:
            for span in line["spans"]:
                if span["text"].strip():
                    all_sizes_set.add(span["size"])

if not all_sizes_set:
    return []

median_size = statistics.median(sorted(all_sizes_set))
size_threshold = median_size * 1.3

candidates = []
for page_idx, d in enumerate(page_dicts):   # reuse cached dicts
    for block in d["blocks"]:
        ...
```

**Memory tradeoff:** Holding all `page_dicts` in memory is the cost. For a 500-page book this is typically 5–20 MB depending on content density.

## Technical Details

- **File:** `condensr/parser.py`
- **Lines:** 192–253

## Acceptance Criteria

- [ ] `page.get_text("dict")` called once per page, not twice
- [ ] Existing heading-detection tests still pass
- [ ] No change to detection results
