Skip to content

feat: preprocess fetch+format module (PR4 of OpenAI Agents SDK migration) - #75

Merged
keli-wen merged 5 commits into
masterfrom
feat/preprocess
May 6, 2026
Merged

feat: preprocess fetch+format module (PR4 of OpenAI Agents SDK migration)#75
keli-wen merged 5 commits into
masterfrom
feat/preprocess

Conversation

@keli-wen

@keli-wen keli-wen commented May 6, 2026

Copy link
Copy Markdown
Contributor

Summary

PR4 of the OpenAI Agents SDK migration. Lands quantmind/preprocess/ as a leaf module that turns raw inputs (arxiv id, URL, DOI, local file path) into LLM-ready text/markdown plus typed metadata, with no LLM calls of its own. Removes the now-superseded quantmind/parsers/ + quantmind/sources/ modules and quantmind/utils/tmp.py.

Part of #71.

What landed

quantmind/preprocess/

File Surface
fetch/_types.py Fetched, RawPaper frozen slotted dataclasses
fetch/arxiv.py async fetch_arxiv(id_or_url) -> RawPaper (arxiv lib via to_thread + httpx for PDF)
fetch/http.py async fetch_url(url, *, timeout, max_bytes) with body cap + UA + selected headers
fetch/doi.py async resolve_doi(doi) -> CrossrefMetadata (Crossref API, no key)
fetch/local.py async read_local_file(path) -> Fetched with content-type by suffix
format/pdf.py async pdf_to_markdown(pdf_bytes) -> str via PyMuPDF
format/html.py async html_to_markdown(html, *, strip_boilerplate) -> str via trafilatura
clean.py normalize_unicode / collapse_whitespace / dedupe_lines (sync, pure)
time.py to_utc / parse_filing_date / business_days_between (sync, pure)

Errors: stdlib + httpx + 2 narrow ValueError subclasses (ArxivIdParseError, PdfParseError). No PreprocessError hierarchy.

Architectural compliance

  • import-linter: 4th contract added — quantmind.preprocess is a leaf, may only depend on quantmind.utils. Existing 3 contracts updated to swap deleted parsers/sources entries for preprocess.
  • basedpyright: drops parsers/ and sources/ from exclude (deleted); preprocess/ auto-included at standard mode.
  • coverage: floor 60 → 65; transitional modules slated for PR5 deletion (flow/, llm/, four files inside config/ and models/analysis.py) get added to coverage.run.omit to mirror the basedpyright exclude philosophy. Floor ratchets to 75 in PR5 once those are gone.
  • deps: add trafilatura>=1.10 (HTML extraction); add dev respx>=0.21 + pytest-asyncio>=0.23. Drop llama-cloud-services and marker (only callers were the deleted parsers/ files); drop top-level requests (only callers were parsers/sources).

Deletions

  • quantmind/parsers/ — replaced by preprocess/format/pdf.py
  • quantmind/sources/ — replaced by preprocess/fetch/{arxiv,http,doi,local}.py
  • quantmind/utils/tmp.pyQuantMindTemplate had no surviving callers
  • tests/parsers/, tests/sources/, tests/flow/, tests/llm/ — imported deleted modules

quantmind/flow/, quantmind/llm/, quantmind/config/, and quantmind/models/{content,paper,analysis}.py stay in the tree until PR5 (their migration target is the new flows/ surface).

Out of scope (follow-up issues to open on merge)

  1. Add format/markdown.py helpers (split_by_heading, strip_frontmatter, normalize_links) — defer until PR5 paper_flow has a real consumer
  2. Add preprocess/chunk.py with tiktoken — ship with PR7 storage layer
  3. Add marker-pdf engine option to format/pdf.py — high-quality alternative
  4. Add llama-parse engine option to format/pdf.py — paid API
  5. Add unpaywall fallback in fetch/doi.py — OA PDF discovery
  6. Holiday-aware business_days_between in time.py

Test plan

  • bash scripts/verify.sh exits 0 (5/5 green)
  • 252 tests pass; total branch coverage 86.01% (preprocess/ at >95% line)
  • import-linter: 4/4 contracts kept
  • grep -r "from quantmind.parsers\|from quantmind.sources" quantmind/ tests/ returns 0 results
  • No test touches the network — respx mocks every httpx call; arxiv lib metadata fetch is patched at the module helper level
  • CLAUDE.md state table + roadmap updated; README runbook example replaced with a working preprocess.fetch_arxiv snippet

Commit-by-commit

  1. feat(preprocess): add fetch + format two-layer module — new files + tests
  2. refactor: remove parsers/, sources/, utils/tmp.py and stale tests
  3. chore(verify): update toolchain config for preprocess landing — pyproject deps, basedpyright exclude, 4th import-linter contract, coverage floor + omit list
  4. docs: mark preprocess landed; refresh runbook example for PR4 — CLAUDE.md + README.md

keli-wen added 5 commits May 6, 2026 14:14
PR4 of the OpenAI Agents SDK migration. Lands quantmind/preprocess/ as
a leaf module that turns raw inputs (arxiv id, URL, DOI, local file
path) into LLM-ready text/markdown plus typed metadata, with no LLM
calls of its own.

Surface:
- preprocess.fetch.{arxiv,http,doi,local}: async I/O. fetch_arxiv
  reuses the arxiv lib via asyncio.to_thread + httpx for the PDF
  download. fetch_url enforces a max_bytes cap and returns selected
  response headers. resolve_doi calls Crossref's open API. Errors stay
  stdlib + httpx; two narrow ValueError subclasses (ArxivIdParseError,
  PdfParseError) cover the parser-level failures.
- preprocess.format.{pdf,html}: pdf_to_markdown via PyMuPDF (single
  deterministic engine; marker / llama-parse engines deferred to
  follow-up issues), html_to_markdown via trafilatura (boilerplate
  stripping + markdown serialisation in one call).
- preprocess.clean: normalize_unicode (NFKC + ligature/quote
  replacement + control-char drop), collapse_whitespace,
  dedupe_lines.
- preprocess.time: to_utc, parse_filing_date (ISO + journal long
  forms), business_days_between (no holiday calendar yet).

All fetch + format functions are async; CPU-bound work runs in
asyncio.to_thread to keep the event loop responsive when the future
batch_run path lands in PR5. Output types (Fetched, RawPaper,
CrossrefMetadata) are frozen slotted dataclasses — internal plumbing,
not LLM boundary, so we skip Pydantic.

Tests cover every public function with happy-path + edge cases:
- Real fixtures committed for format tests (tiny.pdf via PyMuPDF,
  hand-written sample.html, sample.md)
- respx mocks every httpx call so no test touches the network
- arxiv test patches the metadata fetch helper to bypass the lib
- 252 tests pass; preprocess/ at >95% line / >85% branch
Replaced by quantmind/preprocess/:
- parsers/{base,pdf_parser,llama_parser}.py -> preprocess/format/pdf.py
- sources/{base,arxiv_source}.py -> preprocess/fetch/{arxiv,http,doi,local}.py
- utils/tmp.py: standalone Jinja templating helper, no surviving caller

Also drops tests/{parsers,sources,flow,llm}/* — those tests imported the
deleted modules (or, in the flow/llm case, modules whose only callers
were the parsers/sources tests). flow/, llm/, config/ themselves stay
in the tree and get deleted in PR5 alongside the new flows/ surface.

utils/__init__.py loses the QuantMindTemplate / T re-export since the
module is gone.
- dependencies: add trafilatura>=1.10 (HTML extraction); drop
  llama-cloud-services and marker (only callers were the deleted
  parsers/llama_parser.py and the marker branch in parsers/pdf_parser.py);
  drop top-level requests (only callers were parsers/sources)
- dev deps: add respx>=0.21 (httpx mocking) and pytest-asyncio>=0.23
  (IsolatedAsyncioTestCase + asyncio_mode=auto)
- basedpyright exclude: drop quantmind/parsers and quantmind/sources
  (deleted); preprocess/ auto-included at standard mode
- import-linter: add 4th contract — preprocess is a leaf, may only
  depend on quantmind.utils. Existing 3 contracts swap deleted
  parsers/sources entries for the new preprocess module
- pytest: enable asyncio_mode=auto so the new IsolatedAsyncioTestCase
  test classes don't need explicit decorators
- coverage: floor 60->65; add omit list for transitional flow/, llm/,
  and a few config/ + models/ files that PR4 leaves untested (their
  tests went away with parsers/sources). Floor ratchets to 75 in PR5
  once those modules themselves are deleted.
- CLAUDE.md state table: add preprocess/ landed row; remove
  parsers/sources/utils-tmp rows (deleted); clarify that the coverage.run
  omit list mirrors the basedpyright exclude philosophy for transitional
  modules
- CLAUDE.md verify step description: 60% -> 65% (with the rationale
  for the next bump in PR5)
- CLAUDE.md roadmap: PR3 -> #74 merged; PR4 marked as this PR; PR5
  expanded to include the model deletions; PR7 mentions chunk.py
  arrival alongside the storage layer
- README.md: replace the broken `quantmind.sources.ArxivSource` example
  (sources was just deleted) with a minimal preprocess.fetch_arxiv +
  pdf_to_markdown runbook; flag the migration in progress
CI pinned pymupdf 1.27.2.3 whose stubs declare
Page.get_text(option) -> str | list | dict (overload by Literal). The
older 1.26 stubs basedpyright was reading locally only mis-typed the
method as missing, which my earlier `# pyright: ignore` masked.

Drop the explicit "text" argument (default mode already returns plain
text), narrow the result with isinstance(..., str), and fall back to
"" so subsequent .strip() / list[str].append() calls always see str.
Runtime behavior unchanged; the ignore is preserved for the older
stubs path that still misses the attribute.
@keli-wen keli-wen self-assigned this May 6, 2026
@keli-wen keli-wen added type: feature Adds a new capability or observable behavior labels May 6, 2026
@keli-wen
keli-wen merged commit a7019a8 into master May 6, 2026
2 checks passed
@keli-wen
keli-wen deleted the feat/preprocess branch May 6, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

type: feature Adds a new capability or observable behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant