Compare commits
No commits in common. "master" and "master" have entirely different histories.
3 changed files with 3 additions and 107 deletions
|
@ -1,102 +0,0 @@
|
||||||
---
|
|
||||||
layout: post
|
|
||||||
title: Automating a Parking Expenses Report
|
|
||||||
date: 2025-03-02 14:38:00 Europe/Amsterdam
|
|
||||||
categories: paperless paperless-ngx nix python python3
|
|
||||||
---
|
|
||||||
|
|
||||||
For the past year, I was consulting at a place where I could request
|
|
||||||
reimbursement of parking expenses. This had to be claimed using a standard Excel
|
|
||||||
template.
|
|
||||||
|
|
||||||
Doing this manually every month seemed boring, so I automated a significant part
|
|
||||||
of that:
|
|
||||||
|
|
||||||
- Fetch parking costs for a certain month from my Paperless-ngx instance
|
|
||||||
- Calculate costs after taxes and a grand total
|
|
||||||
- Fill in an Excel sheet with the details
|
|
||||||
- Convert the Excel sheet to PDF and append photos of parking costs
|
|
||||||
|
|
||||||
Note: I don't expect anybody to have the same requirements as me here, but
|
|
||||||
hopefully pieces can be useful to some. The open-sourced project can be found
|
|
||||||
[here](https://git.kun.is/pim/parking-expenses).
|
|
||||||
|
|
||||||
# Organizing the Parking Costs
|
|
||||||
|
|
||||||
Each time I would park, I would accumulate a parking ticket. This ticket
|
|
||||||
contains the following information:
|
|
||||||
|
|
||||||
- Date and time
|
|
||||||
- Car license plate
|
|
||||||
- Price
|
|
||||||
- VAT
|
|
||||||
|
|
||||||
In order to automate the report, I had to have this information in a structured
|
|
||||||
way. Therefore, I turned to [Paperless-ngx](https://docs.paperless-ngx.com/).
|
|
||||||
Paperless-ngx is an self-hostable open-source service that helps digitalizing
|
|
||||||
paper documents. Third-party mobile apps help quickly scanning documents and
|
|
||||||
uploading them to the server. This is exactly what I did with the parking
|
|
||||||
tickets as well: each time I received a ticket, I would scan it with the
|
|
||||||
Paperless app.
|
|
||||||
|
|
||||||
Paperless-ngx uses Optical Character Recognition (OCR) to cleverly extract the
|
|
||||||
date of the ticket automatically. I also created a custom label to indicate a
|
|
||||||
parking ticket, and I added custom fields to indicate a ticket's cost and VAT
|
|
||||||
amount. You can see this information in the screenshot below.
|
|
||||||
|
|
||||||
 _Translated from Dutch: aanmaakdatum means creation date,
|
|
||||||
parkeerkaart means parking ticket, BTW means VAT and bedrag means cost_
|
|
||||||
|
|
||||||
Paperless-ngx will also automatically learn to label the tickets with the
|
|
||||||
"parking ticket" label, based on the document's content. That just leaves the
|
|
||||||
VAT and price data points. Unfortunately I found the OCR to be too unreliable to
|
|
||||||
extract that text from the documents. Therefore, I had to manually set those two
|
|
||||||
data points for each ticket.
|
|
||||||
|
|
||||||
Neat, we now have all parking tickets organized on Paperless-ngx! In order to
|
|
||||||
create a report, we can just query the Paperless-ngx API!
|
|
||||||
|
|
||||||
# Using the Paperless-ngx API
|
|
||||||
|
|
||||||
The remaining part of this post will be code explanations. You can find the full
|
|
||||||
open-sourced Python code
|
|
||||||
[here](https://git.kun.is/pim/parking-expenses/src/commit/645fd8b4e4e46bc8f3d8ba831ca28261cd3edb39/main.py).
|
|
||||||
|
|
||||||
Paperless-ngx has [an API](https://docs.paperless-ngx.com/api/) we can now use
|
|
||||||
to query our parking tickets. I found the filtering logic pretty hard to
|
|
||||||
understand. Therefore I simply looked at what API calls the web UI makes with
|
|
||||||
the query I needed. This turned out to be something like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
response = requests.get(
|
|
||||||
f"{paperless_ngx_url}/api/documents/?page_size=50&query=created:[{start_date} TO {end_date}]&tags__id__all={FILTER_TAG_ID}&correspondent__id__in={FILTER_CORRESPONDENT_ID}",
|
|
||||||
headers={"Authorization": f"Token {token}"},
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
In the above code snippet, `FILTER_TAG_ID` is the ID of the "Parking ticket"
|
|
||||||
label and `FILTER_CORRESPONDENT_ID` is the ID of the correspondent (I included
|
|
||||||
this to filter out potential unrelated parking tickets).
|
|
||||||
|
|
||||||
Perfect, this returns a JSON list of parking tickets we can use to create the
|
|
||||||
report!
|
|
||||||
|
|
||||||
# Creating the Report
|
|
||||||
|
|
||||||
All that's left is to fill in an Excel template now. This actually proved to be
|
|
||||||
the most annoying part but also the most boring part. I used the
|
|
||||||
[OpenPyXL](https://openpyxl.readthedocs.io/en/stable/) Python library to
|
|
||||||
manipulate the Excel sheets and basically just add a row for each parking
|
|
||||||
ticket. For code, check
|
|
||||||
[here](https://git.kun.is/pim/parking-expenses/src/commit/645fd8b4e4e46bc8f3d8ba831ca28261cd3edb39/main.py#L104).
|
|
||||||
|
|
||||||
To convert the Excel sheet to a PDF, I used the (apparently now deprecated)
|
|
||||||
[unoconv](https://github.com/unoconv/unoconv) utility. It uses LibreOffice under
|
|
||||||
the hood for the conversion, which adds a 2GiB dependency for the project 🫠.
|
|
||||||
|
|
||||||
Finally, to merge the report PDF with photos of the parking tickets, I used the
|
|
||||||
[PyPDF2](https://pypdf2.readthedocs.io/en/3.x/) Python library.
|
|
||||||
|
|
||||||
I would now show some example output, but it contains quite some sensitive
|
|
||||||
information so unfortunately I can't... But I never had any complaints from the
|
|
||||||
finance department, so it worked great!
|
|
Binary file not shown.
Before Width: | Height: | Size: 73 KiB |
|
@ -6,9 +6,7 @@ excerpt: Things I am working on now
|
||||||
comments: false
|
comments: false
|
||||||
---
|
---
|
||||||
|
|
||||||
_Last updated: 2025-03-02_
|
*Last updated: 2024-05-11*
|
||||||
|
|
||||||
- I'm **hacking** on my [k3s](https://k3s.io/) cluster using
|
- I'm **hacking** on my [k3s](https://k3s.io/) cluster using [❄️ NixOS ❄️](https://nixos.org/) and [Kubenix](https://kubenix.org/)
|
||||||
[❄️ NixOS ❄️](https://nixos.org/) and [Kubenix](https://kubenix.org/)
|
- I'm **playing** [🧩 The Talos Principle 2 🤖](https://store.steampowered.com/app/835960/The_Talos_Principle_2/)
|
||||||
- I'm **playing**
|
|
||||||
[🧩 The Binding of Isaac: Repentance 🔪](https://www.nicalis.com/games/thebindingofisaacrepentance)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue