Skip to content

Case study

BooxPlanner

A bidirectional handwriting loop between an e-ink tablet and Notion.

Year
since 2025
Status
in progress
Role
Architect & Implementer
Stack
Python, WeasyPrint, Jinja2, n8n, Gemini Vision, Notion API, Google Fit

The business problem

I have always written by hand: todos, plans, notes. Past a certain load that stopped scaling. Notion held the structure, a phone app held the convenience, and the paper notebook held the one thing that actually mattered, the physical act of writing. Each solved part of the problem and broke another.

The hard constraint is the hierarchy. A quarterly goal that appears word for word on the monthly, weekly and daily pages defeats the reason for printing a planner. An LLM that breaks goals into level-appropriate detail is therefore not decoration. It is the part that makes the printed page worth printing.

The solution

Each morning a service reads the goals from Notion, has a language model break them down into a monthly focus, weekly goals and a daily focus, and delivers the planner as a PDF to an e-ink tablet. The pen stays the input and Notion stays the record. Reading the handwriting back in the evening is partly built but not yet live.

At a glance: Notion goals become a daily PDF, written on an e-ink tablet, then read back into Notion.
At a glance

Before → after

Long-term goals
Beforelost from sight in paper notebooks
Afterbroken into every day's page automatically

2026-05-12

first production run

The outcome

The morning generation half is deployed and runs, first production run 2026-05-12. The evening read-back is built in pieces but not wired live, /process-annotations is still a stub, and there is no OCR-accuracy measurement yet.

The honest open problem is not the code. It is the habit. Daily adoption is as much behavioural as technical, and that is the part I have not solved.

The engineering behind itExpand · ~2 min

The architecture

A morning pipeline behind a FastAPI service in a Docker container on a home server. A cron POSTs to /generate. The service reads the Notion databases, asks an LLM to break quarterly goals into a monthly focus, weekly goals and a daily focus, renders a Jinja2 template, hands the HTML to WeasyPrint, and uploads a weekly PDF to a cloud-drive folder the tablet pulls. The breakdown is cached weekly, so the daily run is mostly free.

The morning generation path is deployed: it reads Notion, breaks goals down with an LLM, renders a PDF and pushes it to the e-ink tablet via one cloud folder. The evening read-back path is drawn dashed and still planned: it returns annotations through a separate folder, pixel-diffs the changed pages, and OCRs them back into Notion.
The morning generation path is deployed: it reads Notion, breaks goals down with an LLM, renders a PDF and pushes it to the e-ink tablet via one cloud folder. The evening read-back path is drawn dashed and still planned: it returns annotations through a separate folder, pixel-diffs the changed pages, and OCRs them back into Notion.

The evening half, planned and not yet live, reads the annotations back: a drive-change trigger, a pixel-diff to find only the pages I actually wrote on, then vision OCR into Notion. Underneath sits a reverse-engineered .note point-binary parser: 16-byte stroke records behind an 80-byte header, with an inter-stroke separator that shifts alignment and a resync on impossible coordinates.

Decisions & trade-offs

Two folders, not one. Generated pages go down one cloud folder, annotated pages come back up a different one. They physically cannot feed each other, so the morning generator can never re-ingest its own output. It is the simplest possible defence against a sync loop.

Diff before you spend. Before sending anything to a vision model, the system pixel-diffs the clean and annotated PDFs and sends only the changed pages, typically one to three of twelve. Cost tracks edits, not document size.

Most of AI engineering on a budget is knowing where not to call the model.

Evening deferred until morning is solid. The /process-annotations endpoint is a 501 stub on purpose. Bringing both halves online at once doubles the surface for half-working dependencies, and a flaky OCR step would mask a flaky generation step. One direction has to be solid in production before the other depends on it.

What broke

The health-data path took two attempts. A direct device login prompted for an MFA code, which works at a terminal but not under cron, so the pull moved behind a REST API.

The tablet's annotation storage turned out to be hostile to overwrites: the app rewrites its caches from memory unless it is closed. A weekly filename rotation became the cleanest path out.

And the .note parser walks off by four bytes whenever its inter-stroke separator happens to look like the start of a record, which is why it resyncs on coordinates that cannot be on screen.

Same problem in your own operation?

What I do for companies

Hiring for an applied-AI or solutions-architecture role?

The full track record