My first instinct was obvious: move the whole Virya CRM into PostgreSQL and build a proper administration panel. Clean schema, relations, constraints, nice architecture diagram. Very developer. :)

Then I looked at the people who actually need to correct the data. Musicians and collaborators want filters, comments, colours and a table they already understand. Nobody wanted to wait for me to ship version two of an internal product before changing a festival deadline.

So the spreadsheet stayed

So Google Sheets stayed.

Then I gave it some discipline

What changed was the discipline around it. I split organisations, people, opportunities, Meta targets, collected items, workflow runs and outbound communication into separate tabs. One giant sheet containing a venue, its contact person, a submission deadline and three workflow logs may look convenient at first, but very quickly nobody knows which cell describes what.

Row numbers are not identities

Row numbers also had to go as identifiers. Sorting a sheet should not change the identity of a festival. Important records receive stable IDs, and records created from public links use deterministic keys wherever possible. That became the base for duplicate detection and idempotency.

Colours are not state

Colours remain useful for people, but they no longer mean state. Yellow can mean waiting, important, reviewed or simply "Jakub liked this shade". The workflows read explicit fields such as status, approval, verification and next step.

Current truth and execution history are different things

I separate current state from execution history too. A target says what is true now: when it was last checked, whether the API is available and what the latest bounded error was. A run says what happened during one execution: how many rows came in, how many succeeded, what failed and what was new. Without that split, today's successful run quietly erases the fact that the same integration had been broken for three days.

Sheets will accept almost anything, so I added a small schema around it: stable headers, ISO dates, normalised booleans, one value per machine-readable cell, raw data separated from normalised data and a clear place for every automation to report an error.

Manual correction is a feature

The greatest advantage remains manual correction. When a workflow misclassifies a festival, somebody changes one cell and the next run uses the corrected value. No deployment, no migration, no custom UI release.

Move only what actually needs moving

I will move more operational state into a database when concurrent writes, scale or access control truly demand it. I may still keep Sheets on top as the human-facing layer.

The cleanest architecture is not always the one people can live with. In this case the spreadsheet won, provided I stopped treating it like a loose collection of colourful rows.