Building a Personal Film System
A build log on turning a recommendation website and a messy markdown watchlist into a system that answers the question "what should I watch tonight?".
Have you ever wondered if there is a better system for deciding what to watch? This is the story of how that question led me to build a personal film system and what I learned along the way.
Most media platforms lack deep recommendation systems, not being incentivised to provide them. My dissatisfaction with platform recommendation systems eventually led me to search for a better solution, which led me to criticker.com a year ago.
Criticker is a cinephile film recommendation site. It works by a user watching a film and uploading a rating, the site then finds users who rated that film similarly to you and suggests films that they also rated highly. It is essentially a sophisticated version of the ‘customers that bought X also bought Y’ recommendation model operated at a higher granularity and scaled - with thousands of users and hundreds of thousands of reviews. With enough data Criticker excels at answering the question “which films will I like?”.

Whilst Criticker provided good recommendations the user experience did not serve my needs. The site has a mediocre UI, no mobile app and lacks an API. This motivated me to start building infrastructure around my film consumption loop to compensate.
v1 - Manual System Solving Capture
This started with a simple Obsidian markdown note. The note had two sections:
- To Watch: Films I heard about and wanted to watch, quickly captured.
- Watched: After I watched a film in ‘To Watch’ I would promote it to here and rate it.
Then I would manually upload the ratings in Watched to Criticker in batch. This system solved the problem of quick capture but lacked film metadata to help me choose which film in my watchlist to watch. v1 solved capture but revealed a missing context requirement.
v2 - ChatGPT Mediated System Solving Context
The added requirement of deciding which film to watch on a given evening was not well served by Criticker. It required access to metadata that Criticker did not collect. I was already a heavy ChatGPT user, hence it was an accessible solution.
I decided on a set of characteristics that would aid me in film selection: synopsis, darkness, tone and cognitive demand. I used ChatGPT to synthesise this data. This improved my ability to make an informed choice and thus improved my viewing experience.
However, the addition of metadata added more friction to the system. The file began to balloon in size, growing to over a thousand lines, making manual processing more difficult. And the act of copying the metadata in and out from ChatGPT became laborious. v2 solved the context requirement but revealed a need for automation.
v3 - Claude Mediated System Solving Automation
v3 aimed to solve the automation problem. I scaffolded an automated system using Claude commands. I decomposed the system from one file into four separate markdown files in Obsidian each with a distinct role:
Inbox.md: The user-edited file where I add films that I want to watch.To Watch.md: The machine generated watch list complete with metadata.Review Log.md: The user-edited file where a film is added with a rating after viewing.Watched.md: The machine generated file which stores an archive of all reviewed films and their score.
I defined two Claude commands which automated this process:
/process-inbox- parseInbox.md, disambiguate entries, enrich with metadata and promote them toTo Watch.md/process-review-log- parseReview Log.md, disambiguate entries, append toWatched.md, output a .csv file for batch uploading reviews to Criticker.
v3 — Claude commands process quick capture markdown files and generate the canonical representation
This system resolved the manual busy work of v2 and normalised the format. It also enabled me to start to use Claude to explore my data unlocking many possible features. However, there were two main problems:
- Markdown was a poor format for a source of truth - difficult to search and not scalable.
- The black box nature of Claude commands - they were non-deterministic and lacked an audit trail.
v3 solved the automation requirement - it revealed the shape that a more deterministic extensible platform could take.
v4 - Python Platform Solving Determinism and Scalability
To resolve the data storage problem I introduced an SQLite DB as the canonical source of truth for the film system. Inbox.md and Review Log.md remained as quick capture surfaces and To Watch.md and Watched.md became purely rendered views of the database for accessible viewing by the user.
v4 — Python converts user input to the DB representation; the markdown files become rendered views.
I rewrote the commands in deterministic python, calling out to TMDB API instead of relying upon Claude WebSearch. I incorporated bounded Anthropic API calls only for the messy parts which truly still benefited from machine judgement which were:
- Disambiguation - resolving film names to IMDb IDs during
/process-inboxand/process-review-logprocessing. - Synthesis - generating tone, darkness, cognitive demand and additional metadata which was not directly available and had to be synthesised from pre-existing TMDB metadata.
After allowing the model to make such disambiguation judgements the system presented the user with those decisions in batch to verify them. Hence, responsibility was not surrendered to the model. I also recorded the decisions in the database for audit purposes so I could assess efficacy of the model judgement.
v4 resolved the determinism and platform requirements providing a solid foundation for me to build a recommend command to help me answer the question - “what should I watch now?”.
v4 recommend command — constraints filter the queue deterministically (218 → 51). The model is fed the top 10 ranked by Criticker score (51 → 10). The model takes your input into account and re-ranks the slice according to its judgement, it then presents you its recommendations and reasoning.
This project demonstrated the power of AI assisted development, how the lowered cost of production makes such friction solving systems viable and compelling. It also demonstrates the potential that getting your data into an accessible format with LLM access unlocks, evidenced by the recommend command.
The project brought into focus the question of “where should the model live?”. It becomes very tempting to hand the model responsibility for everything, rather than writing messy python. The model feels effortless, but that lack of friction also hides subtle failure modes. For example during a v3 /process-inbox run Claude hallucinated unrelated IMDb IDs resulting in the wrong film and wrong metadata inserted into my watchlist. Where should the model live? is an increasingly salient topic which warrants its own future article.
This is a story of how a watchlist can evolve into film infrastructure by the iterative revealing of requirements. It is a lesson reminding us that the user interfaces and feedback loops of the systems that we use regularly matter - they shape our experience and our subsequent behaviour and engagement. It also reminds us that the question “what will I rate highly?” is not the same question as “what should I watch now?”.
‘Enumeration is no substitute for discernment’ — a distinction I drew in an earlier essay, The Failure of Fridge Futurism. Just as the smart fridge solved inventory when the real question was “what should I cook?”, Criticker answers “what will I rate highly?” while leaving the question of “what should I watch now?” unanswered. The film system closes that gap.