feat: export analysis reports as PDF (closes #138)#154
Merged
Conversation
Add a third report format alongside Markdown and JSON so users can hand formatted PDF output to stakeholders. - `ReportExporter.save_pdf()` renders a header, query, response body, optional Trade Thesis section, and successful data sources using fpdf2. The import is lazy so `ReportExporter` stays usable when the optional dep isn't installed — callers get a clear ImportError with install instructions instead. - `ConversationEngine.save_last_report(fmt="pdf")` routes to the new method. - REPL gains `save pdf` / `/save pdf` commands with a friendly error when fpdf2 is missing; help text and banner updated. - `fpdf2>=2.7.0` registered as a new `[pdf]` optional extra and added to the dev dependency group so CI exercises the feature. - Tests cover the basic write, Trade Thesis section, data-sources filtering (only successful tools), and general-ticker fallback. Since fpdf2 compresses page streams, a small helper decompresses FlateDecode blocks so assertions can look for literal text without pulling in a PDF parser. Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a PDF export option to
ReportExporterso analysis results can be shared as formatted PDFs alongside the existing Markdown and JSON formats. This is a small, focused re-implementation targeting currentmainafter #144 was closed for being branched from the pre-refactorsrc/tracerlayout.Closes #138.
What changed
qracer/conversation/report_exporter.py— newsave_pdf()method. Renders header, query, response body, optional Trade Thesis, and successful data sources viafpdf2. The import is lazy soReportExporterkeeps working without the optional dep; callers get a clearImportErrorwith install instructions instead of a hard crash at module import.qracer/conversation/engine.py—save_last_report(fmt="pdf")routes to the new method.qracer/cli.py—save pdf//save pdfREPL commands, with a friendly message whenfpdf2is missing. Help text and banner updated.pyproject.toml— new[pdf] = ["fpdf2>=2.7.0"]optional extra, plusfpdf2added to thedevdependency group so CI exercises the feature end-to-end (CI runsuv sync --extra web, which pulls in the dev group).tests/conversation/test_report_exporter.py— 4 new tests covering basic write, Trade Thesis rendering, data-sources filtering (only successful tools), and the general-ticker fallback. Becausefpdf2zlib-compresses page streams, a small_extract_pdf_texthelper decompressesFlateDecodeblocks so assertions can look for literal text without pulling in a PDF parser. Each PDF test usespytest.importorskip("fpdf")to stay opt-in on environments without the extra.How to test
Or exercise the new REPL command manually:
The generated PDF contains the header, query, response, Trade Thesis (when present), and the list of successful data sources.
Notes
pytest --cov=qracer --cov-fail-under=80) passes locally at 80.37% coverage with all 711 tests green.ruff check,ruff format --check, andpyrightall clean.save_pdf()intentionally mirrorssave_markdown()'s "successful tools only" behavior for the Data Sources section so the three formats stay consistent.