Lossless Changelog
component

New Module: Research PDF Scraper

A complete standalone module for parsing market research PDFs (McKinsey, CB Insights, PitchBook, Gartner, etc.) into markdown with properly formatted Obsidian-style citations.

Changelog - 2026-01-28

New Module: Research PDF Scraper

A complete standalone module for parsing market research PDFs (McKinsey, CB Insights, PitchBook, Gartner, etc.) into markdown with properly formatted Obsidian-style citations.


Architecture Overview

Module Structure

src/
├── schemas/
│   └── research_pdf.py          # TypedDict schemas (standalone, no dependencies)
│
├── scrapers/
│   └── research_pdf/
│       ├── __init__.py          # Public exports
│       ├── extractor.py         # PDF text/structure extraction (PyMuPDF)
│       ├── citation_scraper.py  # Citation detection and format conversion
│       └── converter.py         # Main orchestrator, markdown generation
│
└── agents/
    └── citation_corrector.py    # LLM-powered citation correction agent

cli/
├── parse_research_pdf.py        # CLI for scraper
└── correct_pdf_citations.py     # CLI for correction agent

context-v/
├── PDF-Parser-Agent-Spec.md     # Full specification document
└── reminders/
    └── Extended-Markdown-Citation-System-Syntax.md  # Citation format spec

Design Decision: Scraper vs Agent

The module distinguishes between:

  • Scraper (deterministic): src/scrapers/research_pdf/ - No LLM calls, pattern-based extraction

  • Agent (LLM-driven): src/agents/citation_corrector.py - Uses Claude for correction and recovery

This separation allows:

  1. Fast, cost-free PDF parsing for most cases

  2. Optional LLM enhancement when higher accuracy is needed

  3. Clear separation of concerns


Data Flow Diagram

┌─────────────────────────────────────────────────────────────────────────────┐
│                           Research PDF Scraper                               │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────┐     ┌──────────────┐     ┌─────────────────┐     ┌──────────┐ │
│  │  PDF    │────▶│  extractor   │────▶│ citation_scraper│────▶│converter │ │
│  │  File   │     │   (fitz)     │     │   (regex/NLP)   │     │(markdown)│ │
│  └─────────┘     └──────────────┘     └─────────────────┘     └──────────┘ │
│                         │                      │                     │      │
│                         ▼                      ▼                     ▼      │
│                  ┌──────────────┐       ┌───────────┐         ┌──────────┐ │
│                  │ Extracted:   │       │ Citations │         │ Output:  │ │
│                  │ - text       │       │ in our    │         │ - .md    │ │
│                  │ - structure  │       │ [^n]      │         │ - .json  │ │
│                  │ - footnotes  │       │ format    │         │ files    │ │
│                  └──────────────┘       └───────────┘         └──────────┘ │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                      Citation Corrector Agent (Optional)                     │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌─────────┐     ┌──────────────┐     ┌─────────────────┐     ┌──────────┐ │
│  │ Scraper │────▶│   Claude     │────▶│  Find Missing   │────▶│ Corrected│ │
│  │ Output  │     │  Correction  │     │   Citations     │     │  Output  │ │
│  └─────────┘     └──────────────┘     └─────────────────┘     └──────────┘ │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘

Citation Format Conversion

Input Formats Detected

The scraper handles multiple citation styles:

FormatExampleDetection Pattern
LaTeX inlinemarkets.1 2 3 followed by 1U.S. Food and Drug...^\d{1,3}[A-Z]
Superscripttext¹²³Unicode superscript chars
Bracketed[1], [2,3], [1-5]\[\d+\] pattern
Endnotes1. Author, "Title"...Numbered list in refs section

Output Format (Obsidian-style)

Inline references:

MARKDOWN
Text with claim. [^10] [^11] [^12] More text continues here.

Reference definitions:

MARKDOWN
[^10]: 2018. [Chronic nicotinamide riboside supplementation...](). Published: 2018 | Updated: N/A

Spacing Rules (Critical)

Per context-v/reminders/Extended-Markdown-Citation-System-Syntax.md:

  • One space after punctuation before citation: text. [^1]

  • One space between multiple citations: [^1] [^2] [^3]

  • One space after last citation before text: [^3] Text continues

  • No space before opening bracket on definition: \n[^1]: ...


Key Implementation Details

1. PDF Text Extraction (extractor.py)

Uses PyMuPDF (fitz) for text extraction with structure detection:

PYTHON
class PDFExtractor:
    def extract(self, pdf_path: str) -> Dict[str, Any]:
        """
        Extract all content from a PDF.

        Returns:
            Dict with:
            - metadata: PDF properties
            - pages: List[ExtractedPage] with text per page
            - full_text: Complete document text
            - structure: DocumentStructure (sections, TOC, refs location)
            - footnotes: Position-detected footnotes
            - references_text: Text from references section
            - tables: Extracted tables
            - is_image_based: Whether OCR would be needed
        """

2. LaTeX Footnote Detection (citation_scraper.py)

The key innovation for handling LaTeX-generated PDFs where footnotes appear inline:

PYTHON
def extract_latex_footnotes(self, text: str) -> List[Dict[str, Any]]:
    """
    Extract footnotes from LaTeX-generated PDFs where footnotes are
    embedded inline in the extracted text.

    LaTeX footnotes appear as: "1U.S. Food and Drug Administration..."
    where the number is immediately followed by the citation text.
    """
    # Pattern: number at start of line followed by capital letter
    # Examples:
    #   1U.S. Food and Drug Administration, "FDA Approves..."
    #   10Martens CR, et al., "Chronic nicotinamide..."

    match = re.match(r'^(\d{1,3})([A-Z][A-Za-z].*)', line)

3. Markdown Citation Cleaning (converter.py)

The _clean_markdown_citations() method handles the critical conversion:

PYTHON
def _clean_markdown_citations(self, text: str, citations: List[Dict]) -> str:
    """
    Clean up markdown text by:
    1. Converting inline citation refs (e.g., "text.10 11 12" -> "text. [^10] [^11] [^12] ")
    2. Removing footnote definition lines from body text
    """

    # Key challenges solved:
    # - Distinguish decimals ($1.5 billion) from citations (text.15)
    # - Handle citation clusters (10 11 12) vs single refs (15)
    # - Remove footnote definitions that appear inline in LaTeX PDFs
    # - Proper spacing around citations

Decimal vs Citation Detection:

PYTHON
def replace_end_of_sentence_refs(match):
    before = match.group(1) or ""  # Character before punct
    punct = match.group(2)  # . or , or ; or :
    numbers_str = match.group(3)
    after = match.string[match.end():match.end()+20]

    ref_nums = re.findall(r'\d+', numbers_str)

    # Check if this looks like a decimal number
    if before and before.isdigit() and punct == '.':
        # Check for monetary/unit context
        if re.match(r'\s*(billion|million|thousand|percent|%)', after, re.IGNORECASE):
            return match.group(0)  # It's a decimal with units

        # Single digit after period is likely decimal
        if len(ref_nums) == 1 and int(ref_nums[0]) < 10:
            return match.group(0)  # Keep as decimal

    # Convert to citation format with proper spacing
    valid_refs = [n for n in ref_nums if n in known_refs]
    if valid_refs:
        refs_formatted = ' '.join(f'[^{n}]' for n in valid_refs)
        return (before or '') + punct + ' ' + refs_formatted + ' '

4. Citation Corrector Agent (citation_corrector.py)

LLM-powered correction that runs after the scraper:

PYTHON
class CitationCorrectorAgent:
    def process_pdf(self, pdf_path: str) -> Dict[str, Any]:
        """
        Process a PDF: scrape then correct citations.

        Steps:
        1. Run the scraper to extract citations
        2. Review each citation with Claude to fix parsing errors
        3. Identify missing citations from the document text
        4. Output corrected citations in our standard format
        """

Schema Definitions (src/schemas/research_pdf.py)

Standalone TypedDict schemas with no dependencies on the memo system:

PYTHON
# Citation format types
CitationFormat = Literal[
    "footnotes",      # Numbered footnotes at bottom of page
    "endnotes",       # Numbered endnotes at end of document
    "bibliography",   # Author-date style with bibliography
    "bracketed",      # [1], [2], [3] style inline
    "superscript",    # Superscript numbers
    "mixed",          # Multiple formats detected
    "unknown"
]

class ParsedCitation(TypedDict, total=False):
    """A citation extracted from a PDF and converted to our standard format."""
    original_ref: str           # Original reference marker
    original_text: str          # Full original citation text
    title: str                  # Parsed title
    author: Optional[str]
    date: Optional[str]         # YYYY-MM-DD format
    date_display: Optional[str] # Human-readable date
    url: Optional[str]
    our_format: str             # Citation in our [^n]: format
    confidence: float           # 0.0-1.0 parsing confidence

class ParsedPDFData(TypedDict, total=False):
    """Complete data from a parsed research PDF."""
    pdf_id: str
    full_markdown: str
    sections: List[ParsedPDFSection]
    citations: List[ParsedCitation]
    citations_markdown: str
    parsing_confidence: float

CLI Usage

Scraper CLI

BASH
# Parse a single PDF
python cli/parse_research_pdf.py path/to/research.pdf

# Parse to specific output directory
python cli/parse_research_pdf.py report.pdf -o output/parsed/

# Parse multiple PDFs
python cli/parse_research_pdf.py data/research/*.pdf -o output/parsed/

# Skip table extraction (faster)
python cli/parse_research_pdf.py report.pdf --no-tables

Correction Agent CLI

BASH
# Scrape and correct citations
python cli/correct_pdf_citations.py path/to/research.pdf

# With custom output directory
python cli/correct_pdf_citations.py report.pdf -o output/corrected/

Output Structure

output/parsed/{pdf-slug}/
├── content.md           # Full markdown with inline [^n] citations
├── content.json         # Complete structured data
├── citations.json       # Extracted citations only
├── citations.md         # Citations in our format (for copy/paste)
├── metadata.json        # PDF metadata and parsing stats
└── sections/            # Individual sections (if structure detected)
    ├── 01-executive-summary.md
    ├── 02-market-overview.md
    └── ...

# After running correction agent:
├── citations-corrected.json   # LLM-corrected citations
└── citations-corrected.md     # Corrected citations markdown

Test Results

Tested on Dark Matter Bio Longevity Fund PPM 2026 (132 pages):

MetricBefore FixesAfter Fixes
Citations found137
Citation refs converted0%95%+
Decimal numbers preservedNo ($1.[^5])Yes ($1.5 billion)
Footnote defs removedNoYes
Proper spacingNoYes

Example output (before):

tion.10 11 12
$1.[^5] billion
10Martens CR, et al., "Chronic nicotinamide..."

Example output (after):

tion. [^10] [^11] [^12]
$1.5 billion
[Footnote definitions removed from body]

Files Created

New Files

FilePurpose
src/schemas/research_pdf.pyTypedDict schemas for PDF parsing
src/scrapers/research_pdf/__init__.pyModule exports
src/scrapers/research_pdf/extractor.pyPyMuPDF text extraction
src/scrapers/research_pdf/citation_scraper.pyCitation detection and parsing
src/scrapers/research_pdf/converter.pyMain orchestrator
src/agents/citation_corrector.pyLLM correction agent
cli/parse_research_pdf.pyScraper CLI
cli/correct_pdf_citations.pyCorrection agent CLI
context-v/PDF-Parser-Agent-Spec.mdFull specification
context-v/reminders/Extended-Markdown-Citation-System-Syntax.mdCitation format spec

Bug Fixes During Development

1. LaTeX Footnote Detection

  • Problem: Position-based footnote detection failed because PyMuPDF extracts text linearly

  • Solution: Added extract_latex_footnotes() for pattern-based detection of \d+[A-Z] patterns

  • Result: Citation detection improved from 1 to 37 citations

2. Decimal vs Citation Confusion

  • Problem: $1.5 billion was converting to $1.[^5] billion

  • Solution: Added context checking for currency symbols, unit words (billion/million), and single-digit heuristics

  • Result: Decimal numbers preserved correctly

3. Citation Spacing

  • Problem: Citations rendered as text.[^10][^11][^12]Text

  • Solution: Updated regex replacement to add proper spacing

  • Result: Proper format: text. [^10] [^11] [^12] Text

4. Footnote Definitions in Body

  • Problem: Footnote text like 10Martens CR, et al... appearing in body content

  • Solution: Added footnote definition detection and removal in _clean_markdown_citations()

  • Result: Clean body text with definitions only in References section


Integration Points

With Memo Generation System

The scraper can prep research PDFs for memo generation:

PYTHON
# In company data file (data/{Company}.json)
{
  "research_pdfs": [
    "data/research/McKinsey-Report.pdf",
    "data/research/Industry-Analysis.pdf"
  ]
}

# Parsed citations available for citation_enrichment agent

Standalone Usage

PYTHON
from src.scrapers.research_pdf import scrape_research_pdf

result = scrape_research_pdf("path/to/research.pdf")
if result["success"]:
    citations = result["data"]["citations"]
    markdown = result["data"]["full_markdown"]

Known Limitations

  1. Image-based PDFs: Requires OCR (not yet implemented)

  2. Complex tables: Table extraction is basic

  3. Non-numeric citations: Author-date style (Smith, 2024) not fully supported

  4. Missing citations: Some refs may not be detected if pattern doesn't match


Performance

PDF SizePagesProcessing TimeCitations Found
2.5 MB1327.3 seconds37

Future Enhancements

  • OCR fallback for image-based PDFs (Claude Vision)

  • Author-date citation style support

  • Table extraction improvements

  • Integration into main workflow graph

  • Batch processing with manifest file


Contributors

  • Architecture design and implementation

  • LaTeX footnote detection algorithm

  • Citation format conversion with spacing rules

  • LLM correction agent


  • context-v/PDF-Parser-Agent-Spec.md - Full specification

  • context-v/reminders/Extended-Markdown-Citation-System-Syntax.md - Citation format

  • src/agents/citation_enrichment.py - How citations are used downstream

  • templates/style-guide.md - Writing standards