2. jobFinder

jobFinder is a program that I developed to help me find and apply to available jobs that fit my profile. Professional information about me was gathered and analysed in order to identify seven areas, or lanes, of expertise where my professional competencies match with tasks that I actually want to work with. Once a day the program collects every new job ad in the fields I look in from the Swedish JobTech and JobAd Links APIs. After the relevant ads are collected, a lightweight AI model reads each ad and rates it twice: how much I would want the job, and how likely I am to get it. The best fitting jobs are shown first, and from there I can start an application with one click.

I built this program for my own job search, because I was dissatisfied with how similar existing services functioned and how expensive they were to use. About 1000 new ads arrive every day in the fields I look in, and I felt that the filters on the existing job boards could not effectively tell me which of them actually fit me. The same work can be advertised under many different titles, and what I care about in a job, such as being given a problem and deciding for myself how to solve it, is not something a keyword can find.

The workflow is simple. I read the best new jobs and the model’s reasons for each rating, and when a job is worth applying for, Claude Code drafts a CV and a cover letter for it from claims about me that I have already checked. In the middle of September, out of about 16,600 ads, it picked out around 200 as jobs I would want. Included in this set of 200 were many jobs that LinkedIn, Glassdoor or Indeed did not recommend to me, but that turned out to be extremely fitting and interesting for me.

For
My own job search, with me as the only user
Built with
Python 3.13, SQLite, Claude Haiku 4.5, React 19, Vite, TypeScript, Typst, Claude Code
Runs on
My personal computer: a run once a day, and a self-hosted web application
Code
github.com/strm0/jobFinder
Dates
Started 4 September 2026, in daily use since 14 September 2026

2.1 New job listings every day

Each day the system brings in new ads automatically as soon as my computer is connected to power. Closed ads are removed and ads that pass the preliminary filters get passed to the model for rating. Every list is sorted with the best job for me first. When I click on a job, the two ratings and the model’s reasons appear next to the list in short form, together with a link to the original ad and one button “Write brief”, which starts an application. Most days I only look at what is new, save a few jobs and start an application for one or two. About once a week I go through everything more slowly, including the jobs the model rejected, in case it has missed something that I would find interesting.

The job title is no help in sorting them, because the same work is often advertised under different titles. For example “Systemförvaltare”, “Business Systems Analyst” and “Analytics Engineer” might get flagged by the model for multiple lanes, which is also presented when opening the job card. See below for an example.

The jobFinder dashboard. A header with a logo, the name jobFinder and the last run's counts: 933 job listings added, 612 removed, 16,614 open. A toolbar with sliders for the views, a New filter, a place switch and a source switch. At the left a grid of job cards, each with a score, a title, an employer, a city, a one-line summary and a lane tag. At the right the opened job, UI/UX Engineer (Founding) at Halcy, with its score of 8.7, the buttons Write brief, Open posting and Save, and under Why this score the model's reasons for want 9 of 10 and reach 7 of 10.
The dashboard on 17 September 2026, with the best jobs first. The opened job shows the two ratings, the model’s reasons and the Write brief button. The line at the top of the dashboard says how many ads came and went since the last run.

2.2 Want and reach

Most job boards rank ads by how well they match the keywords you use while searching. What I needed was a ranking by how well each job fits me as a whole. So the model reads every ad next to a written description of me and gives it two ratings from 0 to 10. Want is how much I would want the job. Reach is how likely I am to be hired.

The description states my limits as plainly as my strengths, because a description that oversells me would put jobs I will not get at the top. It also says what I want from a job, such as owning a process from start to finish, senior people to learn from and a slight preference for smaller organisations. The model can read those things in an ad, where a keyword filter cannot.

At first the model mixed the two ratings up. A job far above my experience came back as a job I would not want, and telling the model that the two ratings were separate did not help. What worked was asking a different question. The instructions now say: “Imagine he has already been offered this job and has already accepted it. He starts on Monday. How glad is he, on his first morning?”

Every list in the dashboard is sorted by one score made from the two ratings, and that score is not an average. An average would put a job with very high want and low reach next to a job as a sales assistant in a shop, because the two come out almost the same, even though I would take the first any day and never the second. So want counts the most, and a low reach only lowers the score, by up to about half.

Two ratings

The model reads one ad against a written brief of me and returns two numbers: how much I would want the job, and how likely I am to get it.

The Monday question

Asked to rate the job as if I already had it and started on Monday, the model stopped mixing the two ratings up. Want went up, and reach stayed where it was.

Not an average

An average ranks these two almost the same. I would take the first any day and never the second.

One score

Want counts the most, and reach can only lower the score. Every list in the dashboard is sorted by this one number.

The score

Terminal output of the scorer's status command. The formula, then 8,297 scored, 3,752 eligible and 4,545 rejected, by reason: 2,702 hard filter, 1,681 no lane matched, 161 commission only. 260 citizenship hits dropped for naming no real bar and 118 reposts folded. Five top industry jobs and five top doctoral calls with their want, reach and signal values. A histogram of scores: 3 jobs between 8 and 9, 12 between 7 and 8, 1,878 between 1 and 2.
The summary of the scores on 17 September 2026: how many ads were rated, why the rejected ones were rejected, the best jobs and doctoral positions, and how the scores are spread.

2.3 Cheap to run

Every ad the model reads costs money, about a third of a cent each, so before anything was built I set a limit of 40 USD a month and designed the system to stay well below it. The money is paid in advance with automatic top ups turned off, so the system can never spend more than what is already there.

The first saving is to skip the ads that cannot fit before paying for them. Every ad is stored, since storing costs nothing, but only ads from the job categories that could fit me are sent to the model. I went through all 159 of Arbetsförmedlingen’s job categories and marked 96 of them to be skipped, so about 40 percent of the ads reach the model. The categories are sometimes wrong, and a trainee AI consultant ad that was close to a perfect match had been filed under telephone sales. So an ad from a skipped category is let through anyway when its headline names the kind of work I am looking for. To test the skipping, 100 skipped ads were rated anyway, and none of them scored higher than 3 out of 10 on want.

The second saving is a cache. The same instructions and the same description of me are sent with every ad, and together they are longer than the ad itself. Because they are stored and reused, they are billed at a tenth of the normal price.

Third, nothing was rated in full until a smaller test had been approved: first 17 ads that I had rated by hand, then 400, and only then all 6,571 open ads that got past the skipping, on 12 September 2026, for 20.43 USD. And no ad is paid for twice. Only ads the model has never read are sent to it, however many arrive in a day. Between 14 and 17 September 2026, rating a day’s new ads cost between 0.13 and 1.62 USD.

Skip first

Every ad is kept, but only ads from the job categories that could fit me are sent to the model. About 40 percent get through.

The rescue

An ad filed under the wrong category would be skipped, so it is let through when its headline names one of the kinds of work I look for.

A sample first

The model’s instructions were tried on 17 ads, then on 400. Only when I had approved the results was every ad rated.

Paid once

Only an ad the model has never read is paid for, however many arrive in a day.

What it cost

Terminal output of the classifier's status command, first 22 lines. The version in use: claude-haiku-4-5, 8,298 ads on the direct API, 85 million input tokens of which 75 million were cache reads, cache hit rate 87.8 percent, cost 25.76 USD. Below it, marked stale: 417 ads direct for 1.24 USD, 400 ads via batch for 0.72 USD, and the start of a round of 17 ads.
The cost report. Each version of the model’s instructions keeps its own count of ads and money.

2.4 Once a day

I will be running this every day until I get a job offer, so it has to take in new ads on its own and send only the new ones to the model. One command runs all the steps in order: it brings in the new ads, marks the closed ones, skips what cannot fit, has the model rate what is new and rebuilds the ranking. Every run ends with a short report of what arrived, what closed and what it cost, compared with the run before.

From 14 September 2026 the run started at 06:00. My laptop is almost never awake and plugged in at that hour, so most of the runs that finished took six hours or more, and two broke off after the first step. Since 22 September 2026 it starts instead the first time each day the laptop is plugged in, and keeps the laptop awake until it is done. Nothing was lost on the bad days, because every run picks up from where the last finished run stopped.

Since 16 September 2026 it also reads ads from other Swedish job sites, through a second open service called JobAd Links. It was said to add around 30 percent more ads. When I counted, it was 4 percent, but they were ads I would not otherwise see, such as startups on thehub.io and junior roles on studentjob.se. Those sites only share the first few lines of each ad, and I did not want the model rating a snippet, so the full ad is read once from the site itself. An ad that is also on the main API is dropped before it costs anything. On 17 September the two jobs at the top of the whole list both came from thehub.io. Future development on this software will add APIs from other countries as well.

The run

Once a day, the first time the laptop is plugged in, one command runs every step in order and writes down what changed since the last run.

Check or report

Two different questions: is anything broken, and what has changed since the last run. An earlier version mixed them up, and its checks failed every night while nothing was wrong.

The whole ad

Ads from other job sites arrive cut to their first few lines, so the full ad is read once from the site itself.

A twin

The same ad on two sites, with the employer’s name spelled two ways. The copy is dropped before it costs anything.

The report

Terminal output of the nightly report for run 6: 22,795 ads in the store, 16,614 open, 933 arrived, 612 closed, 6,925 reaching the model. From other sites: none arrived, 7 held back with no full text read. 353 pending before, 353 classified, 1.0812 USD spent this run, and the classifier and score versions.
The report after the run of 17 September 2026: what arrived, what closed and what the run cost, compared with the run before.

2.5 From wireframe to dashboard

While designing the front end UI I wrote down what the dashboard needed: a switch between the best jobs overall and the best in each lane, a star to save a job, the last day to apply, and how many ads had come and gone since yesterday. I also wanted it to live as files in my own folder and run on my own laptop, not as something published online. I wanted the dashboard to be very simple and usable, without any distractions or any unnecessary features.

The first step was a plain sketch with real ads in it. The information on each card was right, but too much showed at once. All seven kinds of work were stacked on one page, and each list was filled with up to 20 jobs even when only a few of them were jobs I wanted, which made every list look fuller than it was.

First, I went through the wireframe build and changed what did not work. Then I went ahead and built the real dashboard in React, since this is the front end framework that I know the best. Usability is key, one of the areas that I focused heavily on over the past years, and wanted to focus on here as well. The main inspiration for this dashboard is a very stripped down version of Airbnb adapted to include only the functions that I needed. A clean and modern dashboard aesthetic.

During the build a serif typeface replaced the first one, the tile with the employer’s initials went, the header got the name jobFinder and a logo picked from four options, and the tall cards became compact ones, with each row only as tall as its longest card. Loading more jobs as I scrolled filled the large monitor with 60 cards at once, so the list now opens with ten and a button adds ten more in order to reduce cognitive load.

I also added a view of what is new since I last looked, a switch between Stockholm county and everywhere else, since any job outside Stockholm means moving, and a list of the jobs the model rejected, so that nothing it read disappears without my seeing it. Then I paused the design work, because I reached a place where the dashboard did what I needed.

Wireframe

A plain wireframe page titled Job search. A box with the last run's counts: 21 ads added, 55 removed, 15,948 open, 512 classified for 1.62 USD. Tabs for Industry, PhD, Favourites and Rejected, a toggle between top picks overall and best in each lane, yellow notes explaining each part, and two columns of job cards with score, title, employer, lanes, summary, apply-by date and three buttons.
The first sketch, 14 September 2026: real ads and notes, with the layout and the content but no design yet.

First build

The dashboard as first built. A sans serif header reading Job search, tabs at the top right, a New filter and a place switch, a row of lane chips, and upright job cards four across. Each card has a tinted tile with the employer's initials, a score, a New badge, a title, the employer, a city and a summary, with empty space above the footer.
The first build, on the night of 15 September 2026: a sans serif typeface, tall cards and a tile with the employer’s initials above each score. All three changed in the next round.

Four logos

A page titled jobFinder logo options with four candidates, each shown in the header in light and dark and at 16, 24, 48 and 96 pixels. A: a grid of nine squares with one filled. B: a magnifier over text lines. C: a compass. D: three steps up to a dot.
The four options for the logo, 15 September 2026. I picked D, the steps.

The dashboard

The jobFinder dashboard with no job open. The header with the logo, the name jobFinder and the last run's counts: 933 job listings added, 612 removed, 16,614 open. The toolbar with Top picks, By lane, PhD and Rejected, Saved, Applied and Removed, a New filter with 181, a place switch and a source switch. Eight job cards four across, each with a score from 8.8 down to 7.3, a title, an employer, a city, where the ad came from, a one line summary and a lane tag.
The dashboard as it stands, with no job open: the best jobs first, and on each card the score, where the job is, a short summary and the lanes it fits.

Dark

The dashboard's top picks in its dark theme: dark ground, slightly lighter cards with hairline edges, light serif text, the same header, sliders and card layout.
The dark version, 17 September 2026. It follows the computer’s setting until I choose, and one button switches it.

Phone size

The dashboard at phone width: the header and run line, the view sliders scrolling sideways, the New filter and the place switch, and job cards in one column.
The same page on a phone. It works there, but I only use it on my laptop and a large monitor, so the phone got no work of its own.

A job open

An opened job at phone width: a link back to All jobs, Halcy in Stockholm, UI/UX Engineer (Founding) via thehub.io, apply by not stated, score 8.7, place 2 in the list, the buttons Write brief, Open posting and Save, a note that an application folder already exists, and under Why this score want 9 of 10 and reach 7 of 10 with the model's reasons.
A job opened on the phone. The ratings and the model’s reasons take the whole screen, with a way back to all jobs at the top.

2.6 From job listing to CV and cover letter

The dashboard never sends applications in my name. When I click Write brief on a job, it makes a folder on my computer for that job with a short brief in it, holding the ad, the ratings and the model’s reasons, and then it stops.

I then open Claude Code in that folder and ask it for a CV and then for a letter. Both draw on a bank of about a hundred short claims about me. Each claim is something I would stand behind, together with the evidence for it and the question it is likely to raise in an interview. The bank also lists what must not be claimed, so that wrong claims cannot find their way into a CV.

The CV is put together from those claims, but the letter is not. Letters assembled from ready made parts had once given me two letters, to two different employers, that were the same letter with a few words swapped. So each letter is written new for one job, using the claims only for facts, and it draws on the claims the CV had no room for. It is held to a description of how I write, built from references of my own writing. I read and edit every CV and letter before it goes anywhere, since I consider AI writing sloppy at times.

Write brief

One click writes a folder for the job, with the ad, the score and the model's reasons in it. Nothing else happens until I open it.

The CV

The CV is put together from a bank of claims about me that I have already checked.

The letter

The letter is written new for each job, from what the CV had no room for.

Read first

I read and edit both before anything is sent.

Write brief

The dashboard with a job opened in the panel at the right: UI/UX Engineer (Founding) at Halcy. Under the buttons a blue note reads Brief written, with the path of brief.md in an applications folder named for the company, the title and the ad number, a cd command that opens Claude Code in that folder, and the commands /cv and /letter, each with a Copy button. Below it the model's reasons for want 9 and reach 7.
The same job after Write brief: where the brief went, the command that opens Claude Code in that folder, and the two commands to run there.

The CV

A one page CV set in a serif face. Oscar Ström, AI Developer, Stockholm, with a phone number blurred out. A profile paragraph, then experience at Film and Tell, Leštnice, Moderna Dansteatern and Sentieri di Stelle, a Master of Science in Media Technology from Södertörn University and a Bachelor of Arts in Digital Cultures from Lund University, skills and languages.
The CV Claude Code put together on 15 September 2026 for an AI developer job, from the bank of checked claims.

The letter

A one page letter set in a serif face. Oscar Ström, Stockholm, with a phone number blurred out, addressed to a company whose name is blurred out, dated 20 September 2026. Five paragraphs: why he is applying and the tool he found the ad with; building it in Claude Code and what the ratings cost; matching conference attendees against a CRM at Film and Tell with a review stop; the question he would ask about where Claude's code ends; and an offer to show the system running. Signed Kind regards, Oscar Ström.
The letter for the same job, 20 September 2026, written new against the ad. It was written in Swedish and is translated here.

2.7 Credits

Design and every decision: Oscar Ström. It is my own project, started on 4 September 2026. The code is written with Claude Code from my design.

Job ads: the open data of Arbetsförmedlingen through JobTech Dev, and other Swedish job sites through JobAd Links. Ratings: Claude Haiku 4.5.

Design process: Impeccable. Typeface: Newsreader. Icons: lucide. The CVs and letters are set with Typst.

The screenshots on this page were taken on 17 September 2026 from a copy of the data, with my saved jobs cleared. The ads are real and public. The terminal images are the real output of the commands, set as text.