diff --git a/src/_posts/parking-expenses-automation/2025-03-02-parking-expenses-automation.md b/src/_posts/parking-expenses-automation/2025-03-02-parking-expenses-automation.md new file mode 100644 index 0000000..b450921 --- /dev/null +++ b/src/_posts/parking-expenses-automation/2025-03-02-parking-expenses-automation.md @@ -0,0 +1,102 @@ +--- +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. + +![foo](ticket.png) _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! diff --git a/src/_posts/parking-expenses-automation/ticket.png b/src/_posts/parking-expenses-automation/ticket.png new file mode 100644 index 0000000..2665ac3 Binary files /dev/null and b/src/_posts/parking-expenses-automation/ticket.png differ