In the previous article, we built the full pipeline to extract content from SAS Visual Analytics, optionally enrich it with a local LLM, and generate polished PDFs using ReportLab.
At that point, everything was working — but still organized as individual steps. In this post, we take the next logical step: wrapping the entire workflow into a single function: generate_report. Thus automating the process.
Instead of executing steps one by one manually in main.py, the full workflow is now encapsulated into a reusable function. This makes it possible to execute the same pipeline multiple times with different configurations.
This transformation may seem simple, but it introduces two key capabilities:
One important improvement introduced in generate_report is how it handles parameterized reports.
Instead of modifying the original SAS Visual Analytics report, the function:
This design ensures that:
The function begins by duplicating the original report:
new_report_id = duplicate_report(
session=session,
source_report_id=report_config.source_report_id,
new_report_name=new_report_name,
result_name_conflict=ResultNameConflict.replace,
)
Once the copy is created, its parameters are updated dynamically:
update_report_parameters(
session=session,
report_id=new_report_id,
report_name=new_report_name,
result_name_conflict=ResultNameConflict.replace,
parameters=[SetParameterValueRequest(name=param["name"], value=param["value"]) for param in report_config.parameters]
)
This is where the pipeline becomes fully dynamic.
All extraction steps (image + data) now target the temporary report.
Once the PDF has been generated:
delete_report(
session=session,
report_id=new_report_id,
)
Here is a simplified version of the orchestration:
def generate_report(report_config: ReportConfig) -> None:
with create_session(...) as session:
new_report_id = duplicate_report(...)
update_report_parameters(...)
image = retrieve_image_from_report_object_uri(...)
data = retrieve_data_from_report_object_uri(...)
create_single_page_pdf_report(...)
delete_report(..., report_id=new_report_id)
With generate_report in place, adding new reports becomes straightforward.
Instead of modifying code logic, you simply define configurations:
report_list: list[ReportConfig] = [
ReportConfig(source_report_id="c81f64e1-46fd-48c9-a396-c3c85e27cb6d",
visual_element_name="ve26",
parameters=[{"name": "Origin Parameter", "value": "Europe"}, {"name": "Type Parameter", "value": "Sedan"}],
pdf_report_name="Report Summary for Sedan in Europe"),
ReportConfig(source_report_id="c81f64e1-46fd-48c9-a396-c3c85e27cb6d",
visual_element_name="ve26",
parameters=[{"name": "Origin Parameter", "value": "Asia"}, {"name": "Type Parameter", "value": "SUV"}],
pdf_report_name="Report Summary for SUV in Asia"),
]
for report in report_list:
generate_report(report)
This evolution brings several key advantages:
…without duplicating code.
Using temporary reports ensures:
This pattern is ready for:
Wrapping the pipeline into generate_report is more than a refactoring step.
It introduces a new way of thinking about reporting:
Treat reports as templates, and generate outputs dynamically through parameters.
Combined with:
…it becomes a powerful foundation for automated and industrialized reporting.
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 3): Automating the Process (this article)
Find more articles from SAS Global Enablement and Learning here.
Visit the Tips & Tricks page for setup guidance, demos, and practical examples that show how Copilot supports your workflows.
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.