Branding e-policies (OSAGO) with a Telegram bot
for accident commissioners: contacts on the form, data and QR code untouched
A Telegram bot stamps an accident commissioner's contacts onto an electronic OSAGO policy in seconds, leaving data, the QR code and the signature block untouched. Four insurers' forms. Python, PyMuPDF, aiogram 3.
The task
Accident commissioners work on call: a client has an accident, takes out the OSAGO policy (Russia's compulsory motor third-party liability insurance) and phones. Previously the firm stuck a label with its phone number onto the printed form and added a stamp. An electronic policy cannot be labelled that way, and most clients keep it on their phone.
The goal is the same PDF with the commissioner's contacts in the right places: within a couple of seconds and without manual layout work. The volume is small, up to 20 forms a day, handled by three people. The key requirement is not to damage the document itself: the form number, the holder's and the car's data, the QR code and the insurer's signature must stay untouched.
The solution
A Telegram bot accepts a finished electronic OSAGO policy and returns the same one with branding: a yellow card with the commissioner's phone number in place of the insurer's service block, and a “CALL IN CASE OF AN ACCIDENT” stamp over the paragraph with the terms. What is implemented:
- protected zones: the form number, holder's name, VIN, plate number, premium, QR code and electronic signature block, as well as the insurer's logo and hotline, stay where they are;
- automatic placement: the card and the stamp are located from the page text, not from fixed coordinates;
- recognition of forms from four insurance companies, with a dedicated placement strategy for one of them;
- file-size control: a 380 KB policy comes out at roughly 520 KB;
- processing without storage: the document lives in memory and goes straight back;
- an allow-list plus owner approval by button, and processing statistics for the day, week, month, year and per person;
- button menus, a progress bar by processing stage and a hidden
/versioncommand for the owner; inspect,checkandbrandcommand-line tools for checking the layout on your own form without the bot.
How it works
Protected PDF zones: what must never be covered
The first version placed blocks at coordinates from a config file and fell apart on the second form. Now the bot analyzes the page first: text blocks, lines and words, images, the QR code. Anything that looks like policy data goes into a list of forbidden rectangles. The card is then shrunk until it stops overlapping them: the side with the smaller overlap is cut off. The card's width is capped at 45% of the page so that it does not stretch across the whole sheet.
Finding a block by its text in a PDF
The bot finds the place for the card by meaning: every form has a paragraph about the notice of the financial ombudsman, which means nothing to the client. It is found with a regular expression, and its rectangle becomes the card's place. If the paragraph is missing, the insurer's header is used instead. The stamp goes over the paragraph with clauses 4 and 5, which is also located by its start and end.
An occupancy map: where the page is free
If the target paragraph is not found, free space is chosen from an occupancy map. The page is cut into cells of 1.5 points, a table of sums is built over them, and any rectangle can be checked for emptiness in a single step. The search goes to the right first, then downward.
PDF size after the overlay
By default, erasing text under the card also redraws images. On a real form a full-page raster background sits under the text, and the file was being recompressed from 380 KB to 6.5 MB. The fix is to erase only the text and leave images untouched: nothing changes visually, since the card is opaque anyway. Fonts are trimmed to the glyphs actually used, otherwise each would add about 700 KB. The project has a test for this.
Card text by priority
The card text is assembled in priority groups. When space is tight, the insurer's phone, the website, the list of services and the spare number go first, while the main phone number and the name always stay. The font size is fitted to the block height.
Automated acceptance check of the result
The check command is an automated verification of the result: it reports if the card spilled outside its block, the stamp went beyond the clauses 4 and 5 paragraph, or a protected zone was touched. The heavy work runs in a separate thread so that it does not block receiving Telegram messages.
Results
- A policy with the commissioner's contacts is ready within a couple of seconds after the file is sent, with no manual layout.
- The form number, the holder's and car's data, the premium and the QR code are never touched.
- The file size after branding stays comparable to the original, about +40%, not several times larger.
- Clients' documents are not stored anywhere: only counters remain on disk.
- The first release shipped with 42 tests on a synthetic form; there is no automated check on real forms.
Technologies and why
- Python 3.13 — the service language.
- PyMuPDF — page analysis and redrawing: text blocks, images, erasing and overlaying.
- aiogram 3 — the Telegram bot: menu, processing progress, statistics, access.
- segno — the QR code on the card.
- SQLite — processing counters, with no files or their content.
- Docker Compose — deployment: a read-only container with no open ports, the bot connects to Telegram itself.
Status
Version 1.1.1 of 20 September 2026, in production at the client. Known limitations: after branding the insurer's built-in signature can no longer be verified (authenticity is confirmed through the QR code and on the RSA website); only four insurance companies' forms are supported; two instances must not run with one bot token; the backup of the counters volume is made manually. Other details about the client are not disclosed.
Questions about this project
How do I add an accident commissioner's contacts to an electronic OSAGO policy?
Can the branding cover the form number, the QR code or the policy data?
What happens to the insurer's built-in electronic signature?
Why does the file not grow several times larger after branding?
Where are clients' policies stored?
How does the bot tell whose form it is, and does it work with other insurers?
More in this area
Finance Assistant: PDF bank statements to a Telegram budget
our own product: a bot and web dashboard, four banks' statements, Gemini
Telegram ticketing system for an IT department
for a museum complex in Moscow: tickets, asset tracking, knowledge base and analytics
Tech Poly VPN: VPN subscription billing in Telegram
our own product: SBP payments, key issuing in Marzban, a backup VKontakte bot
Need something similar?
Tell us about the task — we'll show how we solved it and estimate the scope.