164 lines
7.4 KiB
Markdown
164 lines
7.4 KiB
Markdown
# 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
|
||
```
|
||
|
||
## License
|
||
|
||
[MIT](LICENSE)
|