Files
house-elo-ranking/README.md
2026-03-06 14:51:26 +00:00

193 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# House ELO Ranking
A pairwise comparison tool that lets you rank houses using the [ELO rating system](https://en.wikipedia.org/wiki/Elo_rating_system). Built to rank [Funda](https://www.funda.nl/) 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
```bash
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
```bash
make build
make up
```
The frontend is available at `http://localhost:8888` (or whatever `FRONTEND_PORT` you set).
### 3. Local development
```bash
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
```
## Project structure
```
├── .env.example # Environment variable template
├── .github/workflows/ci.yaml # CI pipeline (lint + test)
├── .pre-commit-config.yaml # Pre-commit hooks
├── Makefile # Developer commands
├── docker-compose.yaml # Container orchestration
├── backend/
│ ├── Dockerfile
│ ├── pyproject.toml # Python deps and tool config
│ ├── .sqlfluff # SQL linter config
│ ├── app/
│ │ ├── config.py # Settings and SQL loader
│ │ ├── database.py # DB engine and session
│ │ ├── elo.py # ELO calculation
│ │ ├── main.py # FastAPI application
│ │ ├── models.py # SQLAlchemy ORM models
│ │ ├── queries.py # Shared query helpers
│ │ ├── schemas.py # Pydantic request/response models
│ │ ├── routers/ # API route handlers
│ │ └── sql/ # SQL query templates
│ └── tests/ # Unit tests
└── frontend/
├── Dockerfile
├── nginx.conf # Reverse proxy config
└── src/ # React application
```
## License
MIT