Rasmus J.

Software development student

Here are some smaller projects I have made for school or just for fun. I am looking for real work experience in a software team.

Projects

Group trip expenses

Closed source

Shared trip costs, settled in as few payments as possible

Log what each person paid, split it any way you like, and the app works out who pays whom. A .NET modular monolith with a separate Vue client.

ASP.NET Core, PostgreSQL, Vue 3, Docker

How it's built

Architecture

A Vue client calls one ASP.NET Core app with three modules: Users, Trips and Expenses. The modules talk through MediatR, and each has its own PostgreSQL schema.Vue 3 clientTypeScript, Pinia, AxiosREST + JWTOne ASP.NET Core appUsers4 layersTrips4 layersExpenses4 layersMediatRMediatRschema: usersschema: tripsschema: expensesOne PostgreSQL database
  • Three modules, each with its own schema. No joins between them.
  • Modules talk only through MediatR, for example to check the payer is in the trip.

Authentication

Login returns a JWT and a refresh token. On a 401 the client refreshes once, gets new tokens and repeats the request.Vue clientAPIUsers schemaloginstore refresh tokenJWT + refresh tokenrequest + JWT401, JWT expiredrefresh (both tokens)rotate tokennew tokenssame request again200 OKretries only oncechecks signature, not expiry
  • JWT for 30 minutes, refresh token for 7 days, rotated on every use.
  • Known gap: tokens sit in localStorage. Next step is an httpOnly cookie.

Job application tracker

Closed source

Job applications, their next steps and when to follow up

Track job applications, next steps and follow-ups on your phone. A Flutter app for Android, with the data kept on the device.

Flutter, Dart, Material 3, shared_preferences, Android

How it's built

State and rules

Flutter widgets call a repository that holds every application, and pass the list and today's date to rule functions for follow-ups and stats. The repository saves the list as one versioned JSON string on the device, and export and import use the same JSON.Flutter widgetsList, follow up, statsRule functionsFollow-ups and statslist and todayRepositoryHolds every applicationcallsnotifiesJSON formatVersioned, rejects bad inputencode, decodeDevice storageSharedPreferencesone JSON stringExport and importCopy or paste JSON textread, replace
  • One class holds all applications and saves them as JSON.
  • Follow-up and stats rules are plain functions with unit tests.
  • No backend or sync yet.

Language practice partner

Closed source

Practise a market or café conversation with a live talking face

Pick a language and a situation, then type or talk with a video face that answers in real time. Mistakes get corrected and goals tick off.

Python, FastAPI, Gemini Live API, Simli, Vue 3, TypeScript, WebRTC

How it's built

How a turn works

The FastAPI backend keeps the API keys and gives the browser short-lived tokens for Gemini Live and Simli. The browser then talks to Gemini Live over a WebSocket and gets the talking face from Simli over WebRTC.Gemini LiveSpeaks and correctsFastAPI backendKeeps the API keysSimliLip-synced facetokensessionBrowser frontVue 3, talk or typetokens,setupWebSocketspeech and repliesWebRTCvoice in, face out
  • The API keys stay on the backend, and the browser only gets short-lived tokens.
  • Each turn, a tool call reports the correction and goals, and the browser checks it.
  • The browser resamples Gemini's voice for Simli, which lip-syncs the face to it.

World population globe

Closed source

Drag the world under a circle and count the people inside

Spin the globe and resize the circle to see how many people live inside, from 1980 to 2030. Built from the JRC population grid with Canvas and d3-geo.

JavaScript, Canvas 2D, d3-geo, Python, NumPy, rasterio

A globe centred on Tallinn, where a 2,000 km circle holds 557 million people, 7.1% of everyone in 2020.
How it's built

From grid to globe

A Python build sums the 1 km population grid into 50 km cells for six years and saves them as static files. In the browser, the page counts the people inside the circle for each year and redraws the globe and the d3-geo map.Population grid1 km, 1980 to 2030Python buildSums into 50 km cellsStatic files50k cells, 6 yearsUser inputDrag, tap, keys, slidersCount peopleIn the circle, each yearEach frameRedraw globe and readoutWorld map with d3-geoCoastlines, country at the centre, rings
  • The 1 km grid is summed into 50 km cells, about 0.8 MB.
  • A grid search at build time finds the smallest circle holding half of everyone.
  • The figures are modelled estimates, and 2030 is a projection.

Football expected goals model

Closed source

The chance that a shot becomes a goal, learned from 41,190 real shots

Move the ball, the keeper or a defender and the expected goals update. A LightGBM model trained on open event data, running in the browser.

Python, LightGBM, pandas, scikit-learn, JavaScript, SVG

A half pitch with an expected goals heat map, the ball inside the penalty area and a value of 0.10 xG.
How it's built

From events to the browser

Python turns StatsBomb shots into 19 features and trains a LightGBM model, exported as JSON. A JavaScript copy runs it on the demo page, and a parity test checks it matches Python on every test shot.StatsBomb dataShots, player positionsFeatures19 per shot, in PythonLightGBMTested on 2 tournamentsExportTrees saved as JSONJavaScript modelSame features and treesDemo pageHeat map and shot mapsParity testJavaScript matches Python on every test shot
  • Features come from the shot, the pass before it and where players stood.
  • Log loss is 0.249 on two unseen tournaments, against 0.275 for distance and angle alone.
  • Constraints stop xG from rising with distance or with more defenders in the way.

Pickup game organiser

Closed source

A weekly game with friends: places, waitlist, cost split and fair teams

Join the weekly game, get on the waitlist when it is full, and get even teams. A Spring Boot API with a React client that updates live over SSE.

Java 21, Spring Boot, Spring Security, PostgreSQL, React, TypeScript, TanStack Query, Server-Sent Events

How it's built

How it fits together

A React front calls a Spring Boot API, which saves sign-ups and emails to PostgreSQL in one transaction, then updates open pages over SSE. A scheduler, email link sign-in and a dev Outbox page also use the database.React frontVite, TanStack QuerySpring Boot APILocks the gameMock emailSaved, never sentSSE after commitPostgreSQLGames, sign-ups, emailsone transactionSchedulerWeekly games, remindersSign-in by linkSpring Security, sessionOutbox pageShows emails, dev only
  • Sign-in uses one-time email links, then a session cookie and a CSRF token.
  • Live updates use their own thread, so a slow connection never blocks a change.
  • Emails are saved in the same transaction as the change that caused them.

Sign-ups and teams

Two players press for the last place at once. A locks the game first and gets the place, B waits for the lock and goes to the waitlist.Player ALocks the game first9 of 10 takenA gets the placeCommitLock releasedB goes nextPlayer BTries to lock it tooWaitsA holds the lock10 of 10 takenB is waitlistedWaitlist numbers are given under the lock, so the order is kept.
  • Joining and cancelling lock the game, so its sign-ups run one at a time.
  • A test sends 50 sign-ups at once: one gets the place, 49 queue in order.
  • An exact search over all splits picks teams with the closest level totals.

IT employers in Estonia

Closed source

Find growing software companies in Estonia from public tax data

Find growing IT companies in Estonia by county and size, from Tax and Customs Board open data. A Go command builds a static page.

Go, JavaScript, HTML and CSS, Open data (CSV)

How it's built

Data pipeline

Tax data and each company's main activity are downloaded, joined by company and filtered to IT companies. A static page then lets the browser search, filter and sort.Tax data (EMTA)Employees, taxes, turnoverBusiness RegisterEach company's main activityDownloadFiles cached locallyJoin by companyKeep IT companies onlyMetricsEmployees, growth, turnoverStatic pageHTML, JavaScript and JSONBrowserSearch, filter, sort
  • IT means a main activity of programming, IT services, hosting or software publishing.
  • Growth compares headcount in 2026 Q3 with a year earlier.
  • Sources: EMTA (CC BY-SA 3.0) and the e-Business Register (CC BY 4.0).

Video subtitle translator

Closed source

Live translated subtitles over web videos in Chrome

A Chrome extension that translates a video's captions line by line and shows them over the player. Sample video: Tears of Steel, (CC) Blender Foundation.

JavaScript, Chrome Manifest V3, Web Audio, WebVTT, Whisper, DeepL

How it's built

How a line gets translated

Caption lines go from the content script to the service worker, which translates them and falls back to Google on error. Tab audio can instead go to Whisper or Deepgram, and the transcript is translated the same way.Content scriptCaptions and overlayService workerKeeps order, cacheslinessubtitleChosen translatorDeepL, Chrome AI or LLMGoogle (no key)Default and fallbackon errorOffscreen documentCaptures tab audiotranscriptSpeech to textWhisper or Deepgramaudiotext
  • Repeated lines come from the cache, not a new request.
  • The LLM option also gets a glossary and the previous lines.
  • Videos without captions can use tab audio with Whisper or Deepgram.

Screen capture tool for Windows

Closed source

Drag a box on the screen, get the image on the clipboard

Drag a box on the dimmed screen and the image goes to the clipboard and a PNG file. One C# file, no packages.

C#, WinForms, GDI+, .NET Framework, Win32 P/Invoke

How it's built

From click to clipboard

The screen is captured once and the user drags a box on a dimmed copy, or takes the full screen. The crop is copied and saved, old shots are cleaned up and a toast confirms.full screenCapture screenAll monitors, onceOverlayDimmed, drag a boxCropCuts the selectionCopy and saveClipboard and PNG fileCleanupOld shots, newest keptToastClick shows the file
  • Capturing the screen once means the overlay and the crop share one image.
  • Cleanup only deletes old shots the tool named itself, never other files.
  • The video frames are drawn by the tool's own code, not recorded.