Hiring means comparing people you meet days apart, on the strength of notes jotted down on the fly and an impression that fades fast. By the end of a run of interviews, the first candidate is already a blur, the notes are scattered between a notebook and a file, and the decision ends up tilting toward whoever you saw last or liked most. That is exactly the problem we set out to avoid when hiring our junior developer in Tallinn.
Rather than keep our notes in a spreadsheet, we built a small web application to run the process. It gathers in one place what used to be scattered: the role profile, the candidate profile, note-taking during the interview, the numerical scoring, and, at the end, a weighted ranking that makes the comparison explicit. This article describes what it does and how it is designed. It is a tool we built for our own use, and we open its code on GitHub: nothing stops you from running it on your own machine.
The Context Always in View
The application follows the real hierarchy of a hiring process: a company, roles, and for each role its candidates. That cascade stays visible in the sidebar, so you always know where you are without having to retrace your steps.
The company profile is defined once and serves every role. You paste a web address or some text, and the application produces a clean overview that you can then refine. Each role works the same way: a job description, imported from a posting or written by hand, and above all selection criteria specific to the role. Those are what will give the scores their meaning later on.
The candidate profile pulls together the identity (last name, first name, date of birth from which the age is calculated, contact details, status), the CVs uploaded as PDFs, and a profile summary. None of this is left to chance at interview time: before scoring anything, you have in front of you who you are meeting and for which role.
The Interview: Capturing What Matters, As You Hear It
The interview sheet is the heart of the tool. It places the hiring context (company, role, criteria, candidate identity) right next to the scoring grid, so you never have to switch screens in the middle of an answer.
Scoring rests on four base criteria, marked from 0 to 10: ease with the language, professionalism, technical skills, and foreign languages. Each score comes with a free-text field, because a number on its own does not say why you gave it. On top of that come custom evaluations in any number you like: a title, a mark out of 10, a note. That is where you put whatever is specific to a given role and does not fit the four standard boxes.
Two free-text fields round out the sheet: the candidate’s expectations and their distinctive traits. The point is not to reduce everything to numbers, but to keep a faithful record of the exchange while it is happening, ready to be reread once the dust has settled.
The AI Assists, It Does Not Decide
The application leans on a language model in several places, always in support and never in place of the recruiter.
It reads the CVs (PDFs included, through a model able to “see” the page) and proposes an identity: last name, first name, date of birth. The key word is “proposes”: these fields are submitted for validation, never written over an existing entry. It also drafts a profile summary from the CVs, which you can correct or regenerate. And at the end, it produces an interview summary that recalls the context, measures the candidate against the selection criteria, and offers a recommendation.
Two principles guide this assistance. First, everything stays editable: no generated text is locked in. Second, everything is transparent: a window lets you read the exact prompt sent to the model before triggering a new generation. You see what the machine has in front of it, and you keep control over what it does with it.
The interface comes in five languages: English, French, Estonian, German and Dutch. The AI follows that choice and writes its summaries in the display language. A recruiter running the interview in Estonian gets an Estonian summary; a colleague who switches the interface to French rereads it in French, from the same notes.
On the infrastructure side, the application talks to any OpenAI-compatible endpoint. You choose the URL, the key, and the model in the configuration, whether it is a hosted service or a model you run yourself.
Ranking and Weightings
This is where the tool earns its keep when the time comes to decide. A role’s dashboard ranks candidates by descending overall score, each with a score badge and small per-criterion bars to see at a glance where their strengths lie.
The overall score is not a blind average. Each criterion gets a weighting from 0 to 3, specific to the role, adjustable with a slider. The score recalculates live as you move a slider, which lets you answer concrete questions: what if technical skills counted double? What if ease with the language mattered less for this particular role? Unscored criteria are simply ignored, without penalising the candidate.
Friday, end of the run of interviews. Three candidates sit within half a point of one another. You raise the weighting on technical skills, and the ranking reshuffles: the one who spoke best slips behind the one who coded best. The decision did not change its mood, it changed its criterion, and you can explain it.
The tool also lets you compare two candidates side by side: a radar chart overlays their profiles, a bar chart breaks down the gap criterion by criterion, and their interview summaries appear next to each other. The comparison stops being a feat of memory and becomes a piece of reading.
Light and Local, Where Conventional Tools Are Heavy
Most recruitment software comes as online platforms billed by the month, where your candidate data goes off to live on a third party’s servers. For a role you fill now and then, that is a lot of subscription, a lot of features you never touch, and CVs of real people handed to an outside service.
We took the opposite path. The tool fits in a single container that you launch on your own machine; the data stays local, in a file-based database that never leaves your computer. No subscription, no account to create, no CV slipping off to a platform. The only outbound calls are the ones you decide, toward the AI service of your choice, and even that one can be a model you host yourself. The same conviction guides our approach to automation: handle data as close to home as possible, and outsource only what truly must be.
Built to Adapt
Because we were hiring in Estonia, the tool knows how to handle the isikukood, the Estonian identity code, with its own validation. This is not a quirky detail: it is proof that the tool moulds itself to a context rather than imposing its own. Where a one-size-fits-all program forces you to squeeze your particulars into its boxes, a tool you control bends to yours. Your criteria, your weightings, your fields: all of it is shapeable because the code is yours.
Under the Hood
The application fits in a single container. The server is written in Python with FastAPI, the data lives in a SQLite database (in WAL mode to stay reliable with a single process), and the interface is a React application served by the same server. The whole thing launches with Docker and a volume for persistence.
These are the building blocks we use on our production projects, and they are precisely the kind of stack we wanted to introduce to a junior profile. The tool we built to hire him is, in a sense, already a sample of what he will be working on.
Why We Are Sharing It
It is a tool we first built for ourselves, and we open its code on GitHub: you can read it, run it locally for your own interviews, and adapt it to your process. It is a good illustration of how we work, the same way we apply to business process automation or to the intravitreal injection registry: a focused, sober tool that makes a decision clearer without taking it out of your hands.
If you want a tool of this kind shaped around your own process, book a conversation with transtorm.ai below.
Book a call