House ELO Ranking
A pairwise comparison tool that lets you rank houses using the ELO rating system. Built to rank Funda listings during a personal house search, but adaptable to any listing dataset stored in PostgreSQL.
Two listings are shown side-by-side. You pick the one you prefer, and both receive updated ELO ratings. Over time this produces a reliable ranking of all listings based on your personal preferences.
Features
- Matchmaking – weighted-random pairing that avoids recent duplicates and favours less-compared listings
- ELO ratings – standard ELO formula with configurable K-factor
- Rankings – sorted table of all sampled listings by ELO
- History – browse every comparison you have made
- Statistics – total comparisons, ELO distribution chart, aggregates
- Image carousel – listing photos pulled from a JSON column in the database
Architecture
┌──────────┐ ┌──────────┐ ┌────────────┐
│ Frontend │─nginx──│ Backend │────────│ PostgreSQL │
│ React/TS │ :80 │ FastAPI │ :5432 │ │
└──────────┘ └──────────┘ └────────────┘
| Layer | Stack |
|---|---|
| Frontend | React 18, TypeScript, Vite, Tailwind CSS |
| Backend | FastAPI, SQLAlchemy, Pydantic, Python 3.12, uv |
| Database | PostgreSQL (external, not managed by this project) |
| Infra | Docker Compose, nginx reverse proxy |
Requirements
- Docker and Docker Compose
- An existing PostgreSQL database with the tables described below
- uv (for local development outside Docker)
Database schema
The application reads listings from a configurable schema/table and manages its own ELO tables. The required structures are:
Listings table (read-only, provided by you)
The table configured via LISTINGS_SCHEMA / LISTINGS_TABLE must contain at least these columns:
| Column | Type | Description |
|---|---|---|
global_id |
text | Unique listing identifier |
tiny_id |
text | Short identifier |
url |
text | Listing URL |
title |
text | Listing title |
city |
text | City name |
postcode |
text | Postal code |
province |
text | Province |
neighbourhood |
text | Neighbourhood name |
municipality |
text | Municipality |
latitude |
numeric | Latitude |
longitude |
numeric | Longitude |
object_type |
text | e.g. apartment, house |
house_type |
text | e.g. upstairs, detached |
offering_type |
text | e.g. buy, rent |
construction_type |
text | e.g. existing, new |
construction_year |
text | Year of construction |
energy_label |
text | Energy label (A, B, …) |
living_area |
integer | Living area in m² |
plot_area |
integer | Plot area in m² |
bedrooms |
integer | Number of bedrooms |
rooms |
integer | Total rooms |
has_garden |
boolean | Garden present |
has_balcony |
boolean | Balcony present |
has_solar_panels |
boolean | Solar panels present |
has_heat_pump |
boolean | Heat pump present |
has_roof_terrace |
boolean | Roof terrace present |
is_energy_efficient |
boolean | Energy efficient |
is_monument |
boolean | Monument status |
current_price |
integer | Price in euros |
status |
text | e.g. available, sold |
price_per_sqm |
numeric | Price per m² |
publication_date |
text | Publication date |
Sample listings table
The schema configured via ELO_SCHEMA must contain a sample_listings table with a global_id column. Only listings present in this table are shown during comparisons and ranking.
Images table (optional)
The table configured via IMAGES_SCHEMA / IMAGES_TABLE must contain:
| Column | Type | Description |
|---|---|---|
global_id |
text | Listing identifier |
raw_json |
jsonb | Must contain a photo_urls key with a list of image URLs |
ELO tables (auto-managed)
The application creates and manages ratings and comparisons tables inside the ELO_SCHEMA. These are created automatically on first run via SQLAlchemy ORM.
Getting started
1. Clone and configure
git clone https://github.com/<you>/house-elo-ranking.git
cd house-elo-ranking
cp .env.example .env
# Edit .env with your database credentials and schema names
2. Run with Docker
make build
make up
The frontend is available at http://localhost:8888 (or whatever FRONTEND_PORT you set).
3. Local development
make install # install Python deps
make test # run unit tests
make lint # lint Python + SQL
make format # auto-format code
Environment variables
| Variable | Default | Description |
|---|---|---|
POSTGRES_HOST |
localhost |
PostgreSQL host |
POSTGRES_PORT |
5432 |
PostgreSQL port |
POSTGRES_USER |
postgres |
Database user |
POSTGRES_PASSWORD |
postgres |
Database password |
POSTGRES_DB |
postgres |
Database name |
DATABASE_URL |
— | Full connection string (overrides above) |
LISTINGS_SCHEMA |
marts |
Schema containing the listings table |
LISTINGS_TABLE |
funda_listings |
Table name for listings |
ELO_SCHEMA |
elo |
Schema for ELO rating tables |
IMAGES_SCHEMA |
raw_funda |
Schema for the images table |
IMAGES_TABLE |
listing_details |
Table containing raw_json with photos |
ELO_K_FACTOR |
32 |
ELO K-factor (higher = bigger swings) |
ELO_DEFAULT_RATING |
1500 |
Starting ELO for unrated listings |
FRONTEND_PORT |
8888 |
Port the frontend is served on |
Makefile commands
make help Show all available commands
make install Install backend dependencies
make lint Run all linters (ruff + sqlfluff)
make format Auto-format Python and SQL
make test Run unit tests
make build Build Docker images
make up Start services
make down Stop services
make logs Tail service logs
make clean Remove caches and build artifacts