← All work

Personal project · public, source-available · 2026

Kira

Kira pulls rental listings from five Romanian property sites into one place and adds the three filters that actually decide whether a flat is liveable for a student: how it is heated, whether you can park, and how far it is on foot from your faculty.

Proven~70 backend tests · ruff, tsc --strict and ESLint clean
  • Python 3.12
  • FastAPI
  • SQLAlchemy 2.0
  • APScheduler
  • SQLite
  • React 18
  • TypeScript
  • Tailwind v4
  • TanStack Query
  • pytest

The problem

Looking for a rental in Cluj means keeping six tabs open — Storia, OLX, Imobiliare, Publi24, Lajumate — and re-reading the same ads on each of them. None of those sites can answer the questions that decide the actual cost of living somewhere.

Whether a flat has its own gas boiler or district heating changes the winter bill by a factor. Whether the parking is included, rentable nearby, or nonexistent changes whether you can keep a car at all. And the distance that matters is not the distance to the city centre, it is the walk to your own faculty. Kira exists because those three filters do not exist anywhere else.

How it runs

A polite scraping worker downloads public listing pages. A regex pipeline pulls the facts out of the text. Everything is geocoded and matched against rentable parking nearby. A FastAPI backend serves it, a React single-page app displays it.

No listings ship in the repository. Each user generates their own data on first run — about ten to twelve minutes for a city, with live progress — and after that it is instant. Every card links back to the original ad on the source site.

Reading facts out of badly written Romanian

Rental ads are written by people in a hurry. The heating type might be a sentence, a single word, or an abbreviation. Prices are written with a comma, a dot, or neither, so 350.000 and 350,00 have to be told apart before either becomes a number. Parking might be a garage, an assigned spot, a paid subscription, or a hopeful sentence about the neighbourhood.

The extraction is pure regex rather than a model, deliberately. It is auditable — when a listing gets classified wrong I can see the rule that did it — it runs offline, and it costs nothing per listing. The classifiers cover a heating taxonomy, a parking taxonomy, price-separator disambiguation, street names and landmarks.

A rental ad, and the record built from it

Închiriez apartament 2 camere, decomandat, zona Mărăști, et. 3/4, centrală proprie, mobilat și utilat complet, loc de parcare inclus, liber de la 1 septembrie. Preț 350 euro + garanție.

rooms
2
zone
Mărăști
heating
own_boiler
parking
included
price_eur
350 / month
walk_to_utcn
18 min derived

Geocoding without burning the budget

Addresses in ads are approximate. The geocoder walks a ladder: exact street first, then a landmark, then a zone, then the city as a last resort, so a vague ad still lands somewhere useful instead of being dropped. The Nominatim client is budgeted and caches every answer in the database, because a public geocoder is a shared resource and a scraper that hammers one is the reason those services add limits.

Once a listing has coordinates, it gets a walking-time estimate to an address you enter, a link straight into Google Maps, and a match against rentable parking spots within walking distance.

The parts nobody sees

The unglamorous half is what makes it safe to run:

Per-IP rate limiting
on the API, so a loop in a browser tab cannot turn into a scrape of the source sites.
An SSRF-safe image proxy
— listing images come from arbitrary third-party hosts, so the proxy validates before it fetches.
Data minimisation
— phone numbers and email addresses are stripped from ad text before anything is stored.
Polite scraping
with delays, page caps and an on-disk cache, so a re-run costs the source sites nothing.

Why it runs on your machine and not on mine

Kira is source-available under a personal-use licence: clone it, run it, change it, tell a friend. What the licence does not allow is hosting it publicly or selling it, and that restriction is mine, written on purpose.

The listings belong to the sites they came from, and their terms of service forbid aggregating and republishing them. One person running the tool for themselves is private use. A public instance serving everyone is a different thing legally, and pretending otherwise would be the kind of shortcut this project is not for.

The demo, and what it is careful about

So that the interface can be looked at without installing anything, there is a demo — and everything in it is invented. A generator composes Romanian ad text from templates and runs that text through the app's own extractors and matching service, which means the demo exercises the real pipeline: the same routes, the same queries, the same heating and parking classifiers, the same parking-proximity matching. Only the ads underneath are made up, and a banner says so on every screen.

Two routes are closed in demo mode, because they are the only ones that can reach a third party. Scraping returns 403, so a public instance can never pull from a listing site. The image proxy returns 404, because an image proxy on a public host is an open door and the invented listings have no photos anyway. Geocoding is answered from a bundled table of well-known places rather than from Nominatim — a public instance should not send a shared community service whatever strangers type into a search box.

For real listings, the answer is still to run it locally. That is what the tool is for.