Deck Builder: From a Manual Presentation to a Small Python Service

Monday, Sep 28, 2026 | 10 minute read

Most of my small projects don’t start with me wanting to code something. They start with a recurring task at work. In the case of deck-builder, that task was a monthly presentation of upcoming book releases that we used to put together manually.

I decided to automate it. In this post, I’ll explain why I needed deck-builder, how it grew from a Google Colab script into a service my team can use, and why its architecture is the way it is today.

Once a month, my team prepares a digest of upcoming releases—a presentation featuring the books coming out the following month.

It used to be assembled manually by a junior editor. First, they had to export the publishing plan from 1C. Then they would find the cover for each book in our database, transfer the information into PowerPoint, arrange everything according to the template, and build the presentation. After that, they would show it to the editors responsible for those books, collect corrections, and manually update the file. The next month, the process would start all over again.

The process itself wasn’t particularly complicated. That was exactly the problem: most of the work consisted of mechanically transforming structured data that already existed into another format.

A monthly plan could contain around 90 new releases. Building a presentation of that size manually would take too much time, so only a small share of the books made it into the digest.

Then there were the usual consequences of manual work: errors when transferring data and a layout that would gradually drift from the original design. I wanted a complete digest that could be generated quickly and would always follow the same branded style.

That’s how deck-builder came about.

What exactly I wanted to automate

Once you take PowerPoint out of the equation, the task is fairly simple.

The input is a spreadsheet where each row represents one book. It already contains the ISBN, title, authors, annotation, series, format, page count, print run, and other attributes. The book’s item code can be used to retrieve its cover from a CDN.

The output is a presentation with:

  • a title slide;
  • one slide for each row, containing the cover, book information, and a couple of additional visual elements;
  • the same structure for every book slide.

In simplified form, the entire pipeline looks like this:

Excel
  ↓
data validation and normalization
  ↓
book data + cover
  ↓
slide layout
  ↓
PowerPoint

There are no creative decisions that someone needs to make for each individual slide. If the result is determined by a set of formal rules, it’s a good candidate for automation.

The alpha version

I built the first version in Google Colab. It was very much an MVP: I wanted to find out whether I could take an Excel file and use python-pptx to generate the presentation I needed.

It worked, but a proof of concept and a tool you can trust with 90 real books are two different things.

The first version, for example, couldn’t deal with long text properly. A title or annotation could spill outside its intended area. Covers turned out to be more complicated than I expected as well: books have different proportions, so you can’t simply give every image the same width and height without distorting it.

That’s how input validation, handling for missing covers, slide configuration, and export logic appeared.

I quickly realized that, in my case, code living in a Colab notebook wasn’t much better than the manual process. First, the junior editor doesn’t know how to run code from a notebook, and doing it myself would simply create more work for me. Second, my editorial team produces several other recurring presentations in the same branded style, which means those can potentially be automated as well.

How deck-builder is structured today

The project currently relies on four main libraries:

polars + fastexcel   → Excel and data
Pillow               → images
python-pptx          → PowerPoint
Typer                → CLI

The most important part for me is separating responsibilities between different parts of the application. Its architecture can be represented roughly like this:

                     ┌──────────────┐
                     │     CLI      │
                     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │ TitlesDigest │
                     └──────┬───────┘
                            │
             ┌──────────────┼──────────────┐
             │              │              │
             ▼              ▼              ▼
        ┌─────────┐   ┌──────────┐   ┌──────────┐
        │  Excel  │   │  Config  │   │  Assets  │
        │ loading │   │ / layout │   │ / images │
        └────┬────┘   └─────┬────┘   └────┬─────┘
             │              │              │
             └──────────────┼──────────────┘
                            ▼
                     ┌──────────────┐
                     │    Build     │
                     │ python-pptx  │
                     └──────┬───────┘
                            │
                            ▼
                     ┌──────────────┐
                     │    Export    │
                     │    .pptx     │
                     └──────────────┘

1. Excel is the input contract

A presentation generator shouldn’t have to guess what the input data is supposed to mean, so Excel effectively acts as an API here. It has a defined schema: required columns and expected data types.

When loading the file, deck-builder checks that the required fields exist and tries to cast them strictly to the expected types. Extra columns are fine; the generator simply ignores them. For example:

Код ном-ры                → str
ISBN                      → str
Наименование на обложку   → str
Авторы на обложку         → str
Кол-во стр                → Int16
Тираж                     → Int64

This is one of the most important parts of the system: the earlier it rejects invalid data, the easier it is to understand what went wrong.

2. TitlesDigest brings the process together

The central object in the project today is TitlesDigest. At the Python API level, the workflow looks roughly like this:

digest = TitlesDigest.from_excel("books.xlsx")

if digest.df is None:
    raise ValueError("Invalid spreadsheet")

digest.build(
    title_text="New Releases Digest",
    suptitle_text="October 2026",
).to_pptx("digest.pptx")

I like this part of the architecture because the process is fairly easy to read: from_excel() creates the digest from the source data, build() builds the presentation, and to_pptx() saves it.

At the same time, TitlesDigest doesn’t know how to calculate an image size or which exact python-pptx call creates a particular element. Those details belong to lower-level components.

3. Design is data

While building the MVP, I quickly ran into a classic programming problem: magic numbers.

PowerPoint works with coordinates. Expressions like these work perfectly well until the first design update:

left = Mm(31)
top = Mm(42)
width = Mm(76)

If values like these are scattered across functions, every design change turns into a search for numbers throughout the entire code base.

I now keep presentation parameters in a separate configuration. PresentationConfig combines slide dimensions, margins, typography, colors, assets, and the positions of elements for different slide types. The configuration objects are built with dataclass and are immutable.

This means that the code responsible for building the presentation doesn’t also have to define its design. If I want to change the font, margins, or cover position, for example, I don’t need to touch the slide-building algorithm itself.

4. The builder handles composition

The next layer is the actual presentation assembly.

TitlesDigest creates a python-pptx Presentation, sets the required slide dimensions, adds the title slide, and then iterates over the DataFrame rows to build the individual book slides. Here, too, I tried not to put everything into one large function.

Repeated operations such as adding an image, creating a text box, or changing the order of shapes are better kept separately. One reason is very practical: small functions like these are much easier to test.

5. Working with images

The cover is downloaded from a CDN using the book’s item code. If the image isn’t available, the generator can use a placeholder instead. Downloaded results are cached in memory, so the application doesn’t request the same image from the CDN more than once.

Once the image is downloaded, there is another problem to solve: fitting it into the space defined by the design without changing its proportions. With book covers, this is particularly noticeable because publication formats vary considerably. A stretched cover immediately makes an automatically generated presentation look like an automatically generated presentation. I wanted the opposite.

6. Export has its own boundary too

Once the presentation has been built, a separate layer is responsible for writing the .pptx file. It checks the extension, creates any required directories, and only then passes the presentation to python-pptx.

In my code, this is a very small module, and these are exactly the kinds of small modules I find myself liking most now.

The longer I worked on the project, the less I wanted functions like:

make_everything(...)

A set of small components, each doing one fairly boring thing, will make it much easier for me to build new pipelines later.

CLI as the first interface

On top of this, I added a small CLI using Typer.

Locally, the generator can be called roughly like this:

uv run deck-builder \
    input.xlsx \
    output.pptx \
    --title "New Book Releases" \
    --suptitle "October 2026"

The CLI was an important intermediate step for me because it separated the application from the particular way it’s launched. The generator shouldn’t care whether its data came from a terminal, a chat, or somewhere else. That became useful in the next stage.

From CLI to service

I wasn’t supposed to be the end user of the project. But if saving an editor’s time requires me to ask them to install Python, uv, the project’s dependencies, open a terminal, and learn how to use a CLI, then I’ve automated the wrong part of the process.

Today, the team doesn’t interact with deck-builder directly at all. The generator is deployed to Yandex Cloud, while its user interface is our corporate chat in Loop.

Inside, it remains a fairly ordinary Python application that turns one file into another. Outside, there is a small integration layer that accepts a request from Loop, passes the file to the generator, and returns the result to the user.

I think this is the right boundary for a project like this. I can change the way people interact with the application without rewriting the presentation generator. And the reverse is also true: I can change the internal architecture of deck-builder without forcing users to learn a new tool.

Why tests matter here

The hardest part of the project, unexpectedly, wasn’t PowerPoint. It was testing.

I had never studied testing particularly systematically before, so this is where I used AI: I asked Codex to help me write tests, understand fixtures, identify checks that weren’t actually useful, and explain why a particular test should be structured in a particular way.

Tests also had a significant influence on the architecture itself. Testing a function called calculate_image_fit(...) is much easier than testing build_the_entire_presentation_and_do_everything(...). The need to test the code naturally pushes you towards smaller components with clearly defined inputs and outputs.

Conclusion

In terms of sheer amount of code, deck-builder is a fairly small project: it reads Excel and makes PowerPoint. But I like it as an example of a slightly different way of looking at internal tools at work.

It didn’t start with an idea to write a program. There was a process in which someone regularly took structured data, transferred it manually, applied the same set of rules, and produced a structured result. That’s the main sign that something may be worth automating.

The first version proved that the process could be automated. The next task was an engineering one: to separate data from presentation, presentation from configuration, generation from interface, and the tool itself from the environment in which people use it.

As a result, the project now has several fairly clear boundaries:

Excel          → input data
loader         → validation and transformation
config         → presentation rules
builder        → presentation assembly
core           → reusable low-level operations
export         → finished PPTX

────────────────────────────────────

Loop.ru        → user interface
Yandex Cloud   → execution
deck-builder   → domain logic

This is the part of the project I’m more satisfied with than the presentation generation itself. I’m not a developer; my main job is managing products and teams in publishing. But code gives me an additional way to solve management problems.

Sometimes optimization means introducing a new process. Sometimes it means changing responsibilities. And sometimes it’s more useful to write a program and make sure the process you were about to optimize no longer needs to exist at all.

© 2022 - 2026 Kirill Ignatyev

🌱 Powered by Hugo with theme Dream.

About me

I’ve worked in publishing for more than a decade, progressing from editor to business unit leader.

Today, I’m responsible for the strategic and operational management of a business unit, developing its product portfolio, launching new product lines, and scaling established categories.

I’m particularly interested in problems at the intersection of product, business, and data. I work with large product portfolios and build my own Python tools to analyze and manage them.

Work Experience

I currently lead a business unit at AST Publishing Group, with full P&L responsibility and ownership of product strategy and portfolio development.

I oversee a portfolio of more than 5,000 SKUs, lead a team of 28, and manage a business generating over RUB 800 million in annual revenue.

My work focuses on portfolio strategy, demand and inventory turnover analysis, unit economics, and margin management. I also develop international licensing and partnerships across Asia, the United States, and Europe.

Between 2021 and 2025, the unit’s revenue grew by 146%, profit by 168%, and return on sales by 8 percentage points. Market share in one of our key categories increased from 26% to 55%.

Education
  • MBA Strategic Management
    Financial University under the Government of the Russian Federation
  • Publishing Management
    Moscow State University of Printing Arts
  • MA in Linguistics
    Moscow City University
  • English and Curriculum
    Moscow City University
Courses
  • Applied Data Science Lab
    WorldQuant University
  • Data Analysis with Python
    University of Helsinki, Department of Computer Science
  • Advanced Course in Python Programming
    University of Helsinki, Department of Computer Science
  • Introduction to Python Programming
    University of Helsinki, Department of Computer Science
Professional Engagements

LTI Korea Translation Academy (Seoul, Republic of Korea), mentor for Russian-language translators, 2021–2026

KPIPA Overseas Publishing Market Report 2021 (Seoul, Republic of Korea), author of the report on the Russian book market, 2022

KPIPA Overseas Publishing Market Report 2018 (Seoul, Republic of Korea), author of the report on the Russian book market, 2019

Korean Literature Showcase 2019 (Seoul, Republic of Korea), speaker, 2019

International Translation & Publication Workshop (Seoul, Republic of Korea), mentor for Russian-language translators, 2019

Social Links