This application demonstrates how to build a sophisticated report generation pipeline that integrates with SAS Visual Analytics. It retrieves report objects (images and underlying data) from a SAS Viya environment and generates professional single-page PDF reports with customizable content—either a formatted data table or an AI-generated summary powered by a local Ollama model.
Articles in this series:
From SAS Visual Analytics to Pixel‑Perfect PDFs with a Local LLM (Part 1): Meet the Tech Stack
From SAS Visual Analytics to Pixel‑Perfect PDFs with a Local LLM (Part 2): Understanding the Buildin... (this article)
Select any image to see a larger version.
Mobile users: To view the images, select the "Full" version at the bottom of the page.
Purpose: Establish a secure, authenticated HTTP session with SAS Viya.
Key Function: create_session(base_url, client_id, client_secret, username, password, cert_loc)
How it works:
Example:
with create_session(...) as session:
# session is now authenticated and ready for API calls
response = session.get("/visualAnalytics/reports/...")
This context manager approach ensures that the HTTP connection is properly closed after use, preventing resource leaks.
Purpose: Interact with the SAS Visual Analytics API to extract report objects.
Key Functions:
Converts a SAS "directImage" URL to a standardized object URI.
https://server.demo.sas.com/reportImages/directImage?reportUri=%2Freports%2Freports%2F<id>&visualElementName=ve22&...
Why this matters: The directImage URL is what you get when sharing a report visualization in SAS Viya, but the VA API expects the normalized object URI format. This function bridges that gap by extracting the reportUri and visualElementName query parameters and restructuring them.
Fetches the rendered image of a report object.
Fetches the underlying data table for a report object.
Key Insight: Both functions poll the same report object URI but request different representations—one visual (image), one tabular (data). This separation allows you to use both the visual and the raw data to build your report.
Purpose: Create professional, single-page PDF reports with flexible layout and styling.
Architecture: The module uses a layered approach with private helpers and public APIs.
The page is divided into fixed regions:
These constants ensure all content fits on one page with consistent spacing.
_dataframe_to_table_rows(dataframe)
Converts a pandas DataFrame into a list of rows (header + data rows). This is the bridge between pandas data and ReportLab's table rendering.
_build_table(rows, available_width, font_size, cell_padding)
Creates a styled ReportLab Table from row data.
_fit_table_to_height(rows, available_width, max_height)
Intelligently shrinks a table to fit within a height constraint.
This function is crucial for ensuring the table never overflows the page while remaining readable.
_compose_summary_prompt_from_dataframe(dataframe, prompt_instructions, max_rows=15)
Builds a detailed prompt for the Ollama AI model.
_fit_summary_paragraph(text, available_width, max_height)
Fits a text summary into the available space.
generate_table_from_dataframe(dataframe: pd.DataFrame) -> Table
Converts a DataFrame directly into a styled ReportLab Table optimized for the report's height constraint. Call this when you want to display structured data in table form.
generate_summary_paragraph_from_dataframe(dataframe, prompt_instructions, summary_model, ...) Generates an AI-powered text summary of the data.
create_single_page_pdf_report(title, image_path, bottom_content, output_path) The main public function that assembles and renders the PDF.
Rendering pipeline:
Design principle: The layout is adaptive. If you provide a tall image, the bottom content shrinks. If you provide short content, the image gets more space. The function ensures everything always fits on one page.
Purpose: Generate summaries and insights using a locally-running Ollama model.
Key Function: generate_text_with_ollama(prompt, model, ollama_base_url, timeout_seconds)
How it works:
Why Ollama?
Integration point: This function is called by generate_summary_paragraph_from_dataframe()to produce AI summaries of your data. The prompt and model choice are entirely controllable from main.py.
This is where everything comes together. The main() function demonstrates the complete workflow:
content_mode = "summary" # or "table"
summary_model = "qwen3"
summary_prompt_instructions = "You are writing a short business report summary..."
Configure these variables to control the report's content and tone.
with create_session(...credentials...) as session:
Establishes the authenticated connection to SAS Viya.
object_link = "https://server.demo.sas.com/reportImages/directImage?..."
object_uri = direct_image_url_to_object_uri(object_link)
image = retrieve_image_from_report_object_uri(session, object_uri)
# Save to disk
data = retrieve_data_from_report_object_uri(session, object_uri)
# Convert to DataFrame
report_df = pd.read_csv(...)
Fetches both the visualization and the underlying data from SAS Visual Analytic's report.
if content_mode == "summary":
bottom_content = generate_summary_paragraph_from_dataframe(...)
else:
bottom_content = generate_table_from_dataframe(report_df)
Chooses between a data table or an AI-generated summary based on configuration.
create_single_page_pdf_report(
title="Xavier's Report Object Summary",
image_path="output/report_object_image.png",
bottom_content=bottom_content,
output_path="output/report_object_summary.pdf"
)
Assembles the final PDF and writes it to disk.
The pdf_report.py module is designed for easy customization:
Change the summary_model variable in main():
summary_model = "llama2" # or any model you've pulled into Ollama
Ensure the model is already installed in your local Ollama instance:
ollama pull llama2
ollama run llama2 # Test that it works
The create_session() function uses Python's context manager protocol (with statement) to ensure HTTP connections are properly closed.
The PDF layout is adaptive: images and tables dynamically size themselves to fit the available space without overflowing.
The Ollama module is imported only when needed, reducing startup time if not using AI features.
Each module has a clear, single responsibility:
This modularity makes testing and extending each component straightforward.
This application demonstrates a complete pipeline for intelligent report generation:
By understanding these core components and their interactions, you can adapt this pipeline to generate reports for any dataset accessible through SAS Viya, and customize the styling, content, and tone to meet your specific requirements.
The code related to this article is available here.
Articles in this series:
From SAS Visual Analytics to Pixel‑Perfect PDFs with a Local LLM (Part 1): Meet the Tech Stack
From SAS Visual Analytics to Pixel‑Perfect PDFs with a Local LLM (Part 2): Understanding the Buildin... (this article)
Find more articles from SAS Global Enablement and Learning here.
Dive into keynotes, announcements and breakthroughs on demand.
Explore Now →The rapid growth of AI technologies is driving an AI skills gap and demand for AI talent. Ready to grow your AI literacy? SAS offers free ways to get started for beginners, business leaders, and analytics professionals of all skill levels. Your future self will thank you.