feat(F-09): rich parsing — formula + figure extraction

- codex/parsing/mathpix.py: pix2tex (local, CPU) primary + MathPix API
  optional; bbox heuristic h>15px, math-char-count>5; singleton model cache
- codex/parsing/figures.py: pymupdf embedded-image extraction → PNG;
  caption detection via proximity + "Figure/Fig./Abbildung" prefix
- codex/models.py: FormulaChunk + FigureChunk dataclasses (R-10/R-11)
- codex/ingest.py: --rich flag wires formula+figure extraction post-ingest
- codex/cli.py: search_app sub-typer (paper + formula subcommands),
  --rich flag on ingest; wiki_app from F-12 preserved intact
- codex/config.py: mathpix_app_id/key, pix2tex_fallback, figures_dir
- infra/schema.sql: formulas + figures tables with HNSW pgvector indexes
- pyproject.toml: pymupdf>=1.24, pix2tex>=0.1.4
- tests/parsing/test_mathpix.py + test_figures.py: 31 tests (mock pix2tex
  + MathPix HTTP, real pymupdf on synthetic PDF)

Gate: 158 passed, ruff clean, mypy clean (20 files)
Requirements: R-10 R-11 R-12 R-13 R-14 → done

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tarik Moussa
2026-06-14 01:16:24 +02:00
parent d2f9141a5c
commit 1df9be6563
13 changed files with 1930 additions and 26 deletions

View File

@@ -123,6 +123,41 @@ class Settings(BaseSettings):
),
)
# ------------------------------------------------------------------
# F-09 Rich Parsing
# ------------------------------------------------------------------
mathpix_app_id: str | None = Field(
default=None,
description=(
"MathPix App ID for cloud formula extraction. "
"When None, pix2tex local OCR is used as fallback."
),
)
mathpix_app_key: str | None = Field(
default=None,
description=(
"MathPix App Key for cloud formula extraction. "
"Must be set together with MATHPIX_APP_ID."
),
)
pix2tex_fallback: bool = Field(
default=True,
description=(
"Enable pix2tex (local LaTeX OCR) as fallback when MathPix credentials "
"are absent. Set to False to disable formula extraction entirely without creds."
),
)
figures_dir: str = Field(
default="figures/",
description=(
"Directory where extracted figure images are written. "
"Relative paths are resolved from the current working directory."
),
)
@lru_cache(maxsize=1)
def get_settings() -> Settings: