An end-to-end machine learning pipeline for fine-grained housing price prediction in France, from raw data preprocessing to production deployment.
π Full documentation: aiml4os.github.io/funathon-project1
This project walks through the complete lifecycle of a machine learning application applied to real estate tabular data. Using synthetic data reproducing the French DVF+ (Demandes de Valeurs Foncières) and land registry dataset, the goal is to build a model that predicts housing prices at a fine geographic level, and then deploy it as a production-ready API.
The project is structured as a progressive, hands-on tutorial organized in five parts:
- Data description β understanding the input variables
- Pre-processing β cleaning and preparing the dataset
- ML model training β training and comparing gradient boosting models
- Model logging β loggingt.
- Model deployment β API deployment.
.
βββ starting_point/ # Notebooks with exercises (to be completed)
βββ intermediate_solutions/ # Step-by-step partial solutions
βββ solution/ # Full reference solutions
βββ subject/ # Quarto files for website
βββ pyproject.toml # Python dependencies (managed with uv)
βββ _quarto.yaml # Quarto site configuration
The dataset is based on synthetic data mimicking the DVF+ (French land registry transactions) and land registry. Each row represents a single real estate sale and contains ~47 variables describing the property, the transaction, and its geographic location.
Key variables include:
| Variable | Description |
|---|---|
price |
Transaction value (target) |
farea |
Floor area (mΒ²) |
prop_type |
Property type (1 = house, 2 = flat) |
prop_loc_citycode |
Municipality code |
prop_loc_x, prop_loc_y |
Geographic coordinates |
n_mrooms |
Number of main rooms |
n_slr |
Number of bedrooms |
prop_year_harm |
Year of construction |
trans_year |
Year of transaction |
dist_tosea |
Distance to the coastline |
n_garage, n_pool, n_terrace, ... |
Outbuildings and amenities |
See the full variable dictionary in the dedicated page.
Tools: pandas
This step covers:
- Handling missing values and outliers (e.g. filtering extreme
price_per_sqmvalues) - Selecting a relevant feature subset from the 47 available variables
- Computing derived features such as
price_per_sqm - Exploratory data analysis with
seaborn.pairplotandpandas.DataFrame.hist
Tools: scikit-learn
Reference: INSEE working document on bagging and boosting methods, crospint
Goals:
- Split data into training and test sets
- Train and compare three models:
GradientBoostingRegressor,HistGradientBoostingRegressor,RandomForestRegressor - Explore location encoding strategies (One-Hot Encoding, native categorical support)
- Hyperparameter tuning via cross-validation (grid search, random search, optionally Optuna)
- Apply early stopping and metric logging during training
- Evaluate models using MAPE and RΒ²
This part covers:
- Experiment tracking with MLFlow: saving all runs, models, and associated metrics
- API deployment with FastAPI: expose a prediction endpoint that takes property attributes (surface, location, number of rooms, etc.) and returns a predicted price
- Python >= 3.13
uv- SSPCloud account (recommended)
- GitHub account (recommended)
- MLFlow
# Fork the repository
git clone https://github.com/AIML4OS/funathon-project1.git
cd funathon-project1
# Install dependencies with uv
uv sync# Render the full Quarto website locally
uv run quarto render
# Or preview it
uv run quarto previewData are synthetic data.
French version of the data is stored in two files in the projet-funathon/2026/project1/data/0_raw/ folder : 2026/project1/data/0_raw/transactions_flats_FR_raw.parquet and 2026/project1/data/0_raw/transactions_houses_FR_raw.parquet.
The script to convert French labelled data to English is stored in temp/0_generate_input.py.
They are two sources of truth (where the data science is done) in the folder :
- the Qmd files;
- the scripts in the solution folder.
A script transforms qmd files into scripts that will be used.
A script transforms .qmd files from subject/file.qmd into intermediate_solutions/script.py.
The script extracts all code in the listed qmd files that are in executable code cells, meaning starting with the exact set of character: \``{python. Code cell starting with ``` python` for example won't be pasted into the intermediate solution scripts.
To run it :
bash solution/admin/extract_all.sh false # extract and not testing scripts
bash solution/admin/extract_all.sh # extract and not testing scripts
bash solution/admin/extract_all.sh true # extract and testing scriptsThe extract_all.sh can also test if the script run properly.
To do so, pass on a true argument to the script. Any other argument (and by default, none) will not test the scripts.
Back up data is generated using the admin/save_data.py script.
It depends on the interim scripts : these scripts need to be up to date for back up models to work properly.
It also uses path generator stored in the solution/utils functions
To run a back up script, do it with uv run solution/admin/save_data.py.
Default path of the script that generates the data is intermediate_solutions/2_preprocessing.py.
Back up models are generated using the admin/save_models.py script.
It depends on the interim scripts : these scripts need to be up to date for back up models to work properly.
To run a back up script, do it with uv run solution/admin/save_models.py intermediate_solutions/3_RF.py rf_model_final rf_model.joblib :
intermediate_solutions/3_RF.py: is the script to run where the models are generated.rf_model_final: is the name in the script of the model to storerf_model.joblib: is the name of the back up in the S3
The solution is coded with more advanced set-up that haven't been covered in the tutorial. For example, it includes logging module, the logging to MLFlow is done along the training. Function are split over several files to adopt a modular organization. Solutions are updated by hand and not from a script.
In a similar way, the fallback script is adapted manually.
To run the solution, run uv run solution/main.py.
This script runs all subscripts, logs models to MLFlow, updates data and back-up models in the S3 storage. It doesn't launch a local API.
To launch a local API, run uv run uvicorn solution.api:app --reload. You need to have your models stored in MLFlow for it to run properly.
You need to follow the next steps in this order :
- Update your QMD files and make sure the cells work from there;
- Update the solution in the
solution/folder; - Test the solution by running
uv run solution/main.py; - extract and test code using
bash solution/admin/extract_all.sh true; - commit the updated interim scripts;
- generate back-up data with
uv run solution/admin/save_data.py; - generate back-up models with
uv run solution/admin/save_models.py intermediate_solutions/3_RF.py rf_model_final rf_model.joblib && uv run solution/admin/save_models.py intermediate_solutions/3_GB.py gb_model_final gb_model.joblib; - push to github.
You need to have up-to-date back-up date and back-up models when rendering.
Contributions are welcome! Whether you spotted a bug, a typo, an outdated dependency, or have an idea to improve the tutorial, here's how to get involved:
- Open an issue β head to the Issues tab and describe what you found or what you'd like to see. Please check that a similar issue doesn't already exist before opening a new one.
- Submit a Pull Request β fork the repository, make your changes on a dedicated branch, and open a PR against
main. Briefly describe what you changed and why. Linking the PR to the relevant issue is appreciated.
No contribution is too small β fixing a broken link or clarifying a comment is just as valuable as adding a new feature.
This project was developed as part of the AIML4OS Funathon β a collaborative hackathon focused on applying AI and machine learning methods to open statistical data.
π AIML4OS Organization