Repository: dscc-admin-ch/lomas (language: python)

Issue:
verify user and dataset are allowed for query



Discussion / hints:

Fix patch:
diff --git a/.github/workflows/check_security_codeQL.yml b/.github/workflows/check_security_codeQL.yml
index 123c8936..396fb0de 100644
--- a/.github/workflows/check_security_codeQL.yml
+++ b/.github/workflows/check_security_codeQL.yml
@@ -1,4 +1,4 @@
-name: "CodeQL"
+name: "CodeQL Extended"
 
 on:
   pull_request:
diff --git a/.gitignore b/.gitignore
index f6a67b4a..c6cfc265 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,9 @@
 server/lomas_server/tests/test_data/local_db_file_*
 server/lomas_server/.streamlit/*
+server/venv/*
 client/notebooks/personal_notebooks/*
+client/configs/lab/*
+client/venv/*
 
 # Byte-compiled / optimized / DLL files
 __pycache__/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 5ac8b0e9..0903336d 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -16,23 +16,43 @@ part of the project, refer to:
 * **release/vx.y.z**: These are release branches (for version vx.y.z). They always branch off from develop.
   Once the release process is complete (see below), the release branch is merged to both master and develop via GitHub pull requests.
 
-## Linting and other checks
-
-Here is a list of the checks that should be performed:
-
-    - Use black to automatically format the code: `black .`
-    - Use flake to verify formating and performing a static code analysis: `flake8 .`
-    - Use mypy for static type checking: `mypy .`
-    - Use pylint for further static analysis: `pylint --disable=E0401 --disable=C0114 --disable=R0903 --disable=E0611 --disable=W0511 --disable=duplicate-code tests/ .`
-    
-        - `--disable=E0401` to ignore import-error
-        - `--disable=C0114` to ignore missing-module-docstring
-        - `--disable=R0903` to ignore too-few-public-methods
-        - `--disable=E0611` to ignore no-name-in-module
-        - `--disable=W0511` to ignore fixme (TODOs)
-        - `--disable=duplicate-code tests/` to ignore duplicate-code statements related to the tests
-        
-As detailed below, we rely on GitHub workflows to automatically run the checks on pull requests.
+## Linting and Other Checks
+
+To ensure code quality and consistency, we perform several checks using various tools. Below is a list of the checks that should be performed:
+
+- **Code Formatting:** Use `black` to automatically format the code. In `lomas/server/lomas_server` and `lomas/client/lomas_client`:
+  ```bash
+  black .
+  ```
+
+- **Code Style and Static Analysis**: Use flake8 to verify formatting and perform static code analysis. In `lomas/server/lomas_server` and `lomas/client/lomas_client`:
+ ```bash
+  flake8 .
+  ```
+
+- **Static Type Checking**: Use mypy for static type checking. Note that both the server and the client have their own mypi.ini files to ignore specific warnings. In `lomas/server` and `lomas/client`:
+ ```bash
+  mypy .
+  ```
+
+- **Additional Static Analysis**: Use pylint for further static analysis. Note that both the server and the client have their own .pylintrc files to ignore specific warnings. In `lomas/server/lomas_server` and `lomas/client/lomas_client`:
+ ```bash
+  pylint .
+  ```
+
+To streamline the process, you can use the `run_linter.sh` script in ``lomas`. The first time you run this script, use the following command to install dependencies:
+```bash
+chmod +x run_linter.sh
+./run_linter.sh  --install-deps
+```
+For subsequent runs, simply execute:
+```bash
+./run_linter.sh
+```
+
+There should be no error or warning, otherwise the linting github action will fail. All configurations are in `lomas/server/pyproject.toml` and `lomas/client/pyproject.toml`.
+
+As detailed below, we rely on GitHub workflows to automatically run these checks on pull requests, ensuring consistency and quality across all contributions.
 
 ## GitHub Workflows
 
diff --git a/README.md b/README.md
index fb4e6b1c..dfab7bfd 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,10 @@
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 [![Python](https://img.shields.io/badge/python-3.11-blue)](https://www.python.org/)
 [![Documentation](https://img.shields.io/badge/docs-Read%20the%20Docs-blue)](https://dscc-admin-ch.github.io/lomas-docs/index.html)
+[![Tests](https://github.com/dscc-admin-ch/lomas/actions/workflows/test_and_coverage_server.yml/badge.svg)](https://github.com/dscc-admin-ch/lomas/actions/workflows/test_and_coverage_server.yml)
 [![Coverage badge](https://raw.githubusercontent.com/dscc-admin-ch/lomas/python-coverage-comment-action-data/badge.svg)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/dscc-admin-ch/lomas/python-coverage-comment-action-data/htmlcov/index.html)
+[![CodeQL](https://github.com/dscc-admin-ch/lomas/actions/workflows/check_security_codeQL.yml/badge.svg)](https://github.com/dscc-admin-ch/lomas/actions/workflows/check_security_codeQL.yml)
+[![PyPi version](https://img.shields.io/pypi/v/lomas_client.svg)](https://pypi.org/project/lomas_client/)
 
 
 
@@ -14,19 +17,23 @@
 
 Lomas is a platform for remote data science, enabling sensitive data to be queried remotely while staying protected by a layer of differential privacy.
 
+#### Technical Overview:
+
 The lomas platform follows a classic server/client model.
 On the client side, the user prepares queries for statistical analyses which are sent to the service's REST API via HTTP. The user never has direct access to the sensitive data.
 On the server side, the service is implemented in a micro-service architecture and is thus split into two parts: the administration database and the client-facing HTTP server (which we call server for brevity) that implements the service logic.
 The server is responsible for processing the client requests and updating its own state as well as administrative data (users data, budgets, query archives, etc.) in the administration database.
 
-
 The service is not responsible for storing and managing private datasets, these are usually already stored on the provider's infrastructure.
 
-<!-- See our white paper (TODO link) for detailed explanation of the platform. -->
-## Useful links
+#### Detailed description:
+
+For a detailed description, please see the links below.
+
+* **Lomas Project White Paper**: https://arxiv.org/abs/2406.17087
+* **Swiss Federal Statistical Office Blog**: https://www.bfs.admin.ch/bfs/en/home/dscc/blog/2024-03-lomas.html
+* **Technical Documentation**: https://dscc-admin-ch.github.io/lomas-docs/index.html
 
-* **Documentation**: https://dscc-admin-ch.github.io/lomas-docs/index.html
-* **Bug report**: https://github.com/dscc-admin-ch/lomas/issues
 
 ## Client package `lomas_client`
 
@@ -61,6 +68,12 @@ For extensive informations about how to deploy, please refer to:
 Finally, the service provider is responsible for deploying the service and managing users and private datasets by adding, modifying or deleting information in the administration database.
 It is important to note that the service is not responsible for storing and managing private datasets, these are usually already stored on the provider's infrastructure.
 
+## Disclaimer
+Lomas is a Proof of Concept that is still under development. 
+
+The overall infrastructure security is not our current priority.  While attention has been given to the 'logical' aspects within the server, many security aspects are not handled. For example, user authentication is not implemented. However, Lomas can be integrated into other secure infrastructures.
+
+We welcome any feedback or suggestions for future improvements. External input is valuable as we continue to enhance the security and functionality of Lomas. Please open a bug report or issue here: https://github.com/dscc-admin-ch/lomas/issues.open.
 
 ## History
 The starting point of our platform was the code shared to us by [Oblivious](https://www.oblivious.com/). They originally developed a client/server platform for the [UN PET Lab Hackathon 2022](https://petlab.officialstatistics.org/).
diff --git a/client/Dockerfile b/client/Dockerfile
index 9a7f09e5..979f4875 100644
--- a/client/Dockerfile
+++ b/client/Dockerfile
@@ -5,7 +5,7 @@ WORKDIR /code
 COPY ./requirements.txt /code/requirements.txt
 RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
 
-COPY ./lomas_client/ /code/lomas_client
+COPY ./lomas_client/ /code/lomas_client/
 COPY ./setup.cfg /code/setup.cfg
 COPY ./setup.py /code/setup.py
 COPY ./README.md /code/README.md
@@ -19,7 +19,8 @@ FROM lomas_client_base AS lomas_client
 COPY ./notebooks/images/ /code/notebooks/images/
 COPY ./LICENSE /code/LICENSE
 COPY ./notebooks/Demo_Client_Notebook.ipynb /code/notebooks/Demo_Client_Notebook.ipynb
+COPY ./notebooks/Demo_Client_Notebook_DiffPrivLib.ipynb /code/notebooks/Demo_Client_Notebook_DiffPrivLib.ipynb
 
-RUN pip install /code/
+RUN pip install -e /code/
 
 CMD ["jupyter", "notebook", "--ip", "0.0.0.0", "--no-browser", "--allow-root"]
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
index 9452876e..cdd07cb6 100644
--- a/client/README.md
+++ b/client/README.md
@@ -1,3 +1,10 @@
+[![PyPi version](https://img.shields.io/pypi/v/lomas_client.svg)](https://pypi.org/project/lomas_client/)
+[![PyPi status](https://img.shields.io/pypi/status/lomas_client.svg)](https://pypi.org/project/lomas_client/)
+
+<h1 align="center">
+<img src="https://github.com/dscc-admin-ch/lomas/blob/develop/images/lomas_logo_txt.png?raw=true" width="300">
+</h1><br>
+
 # Lomas Client
 
 The `lomas_client` library is a client to interact with the Lomas server.
diff --git a/client/deploy/helm/charts/lomas_client/Chart.yaml b/client/deploy/helm/charts/lomas_client/Chart.yaml
index 50b99b6d..efca9f72 100644
--- a/client/deploy/helm/charts/lomas_client/Chart.yaml
+++ b/client/deploy/helm/charts/lomas_client/Chart.yaml
@@ -4,6 +4,6 @@ description: Lomas's Secure Data Disclosure deployment chart for the client envi
 
 type: application
 
-version: 0.1.0
+version: 0.2.0
 
-appVersion: "0.1.1"
+appVersion: "0.2.0"
diff --git a/client/lomas_client/client.py b/client/lomas_client/client.py
index da228004..f136b9a9 100644
--- a/client/lomas_client/client.py
+++ b/client/lomas_client/client.py
@@ -1,3 +1,4 @@
+import base64
 import json
 from enum import StrEnum
 from io import StringIO
@@ -6,9 +7,10 @@
 import opendp as dp
 import pandas as pd
 import requests
+from diffprivlib_logger import serialise_pipeline
 from opendp.mod import enable_features
 from opendp_logger import enable_logging, make_load_json
-
+from sklearn.pipeline import Pipeline
 
 # Opendp_logger
 enable_logging()
@@ -17,6 +19,7 @@
 # Client constants: may be modified
 DUMMY_NB_ROWS = 100
 DUMMY_SEED = 42
+HTTP_200_OK = 200
 
 
 class DPLibraries(StrEnum):
@@ -26,6 +29,7 @@ class DPLibraries(StrEnum):
 
     SMARTNOISE_SQL = "smartnoise_sql"
     OPENDP = "opendp"
+    DIFFPRIVLIB = "diffprivlib"
 
 
 def error_message(res: requests.Response) -> str:
@@ -71,7 +75,7 @@ def get_dataset_metadata(
         res = self._exec(
             "get_dataset_metadata", {"dataset_name": self.dataset_name}
         )
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             data = res.content.decode("utf8")
             metadata = json.loads(data)
             return metadata
@@ -107,7 +111,7 @@ def get_dummy_dataset(
             },
         )
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             data = res.content.decode("utf8")
             df = pd.read_csv(StringIO(data))
             return df
@@ -172,7 +176,7 @@ def smartnoise_query(
 
         res = self._exec(endpoint, body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             data = res.content.decode("utf8")
             response_dict = json.loads(data)
             response_dict["query_response"] = pd.DataFrame.from_dict(
@@ -214,7 +218,7 @@ def estimate_smartnoise_cost(
         }
         res = self._exec("estimate_smartnoise_cost", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             return json.loads(res.content.decode("utf8"))
         print(error_message(res))
         return None
@@ -264,20 +268,9 @@ def opendp_query(
             endpoint = "opendp_query"
 
         res = self._exec(endpoint, body_json)
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             data = res.content.decode("utf8")
             response_dict = json.loads(data)
-
-            # Opendp outputs can be single numbers or dataframes,
-            # we handle the latter here.
-            # This is a hack for now, maybe use parquet to send results over.
-            # if isinstance(response_dict["query_response"], str):
-            #     raise Exception("Not implemented: should not return dataframes")
-            # Note: leaving this here. Support for opendp_polars
-            # response_dict["query_response"] = polars.read_json(
-            #    StringIO(response_dict["query_response"])
-            # )
-
             return response_dict
 
         print(error_message(res))
@@ -312,12 +305,142 @@ def estimate_opendp_cost(
         }
         res = self._exec("estimate_opendp_cost", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             return json.loads(res.content.decode("utf8"))
 
         print(error_message(res))
         return None
 
+    def diffprivlib_query(
+        self,
+        pipeline: Pipeline,
+        feature_columns: List[str],
+        target_columns: Optional[List[str]] = None,
+        test_size: float = 0.2,
+        test_train_split_seed: int = 1,
+        imputer_strategy: str = "drop",
+        dummy: bool = False,
+        nb_rows: int = DUMMY_NB_ROWS,
+        seed: int = DUMMY_SEED,
+    ) -> Pipeline:
+        """This function trains a DiffPrivLib pipeline on the sensitive data
+        and return a trained Pipeline.
+
+        Args:
+            pipeline (sklearn.pipeline): DiffPrivLib pipeline with three conditions:
+                - The pipeline MUST start with a `models.StandardScaler`.
+                  Otherwise a PrivacyLeakWarning is raised by DiffPrivLib library and
+                  is treated as an error in lomas server.
+                - `random_state` fields can only be int (`RandomState` will not work).
+                - `accountant` fields must be None.
+                Note: as in DiffPrivLib, avoid any DiffprivlibCompatibilityWarning
+                to ensure that the pipeline does what is intended.
+            feature_columns (list[str]): the list of feature column to train
+            target_columns (list[str], optional): the list of target column to predict \
+                May be None for certain models.
+            test_size (float, optional): proportion of the test set \
+                Defaults to 0.2.
+            test_train_split_seed (int, optional): seed for random train test split \
+                Defaults to 1.
+            imputer_strategy (str, optional): imputation strategy. Defaults to "drop".
+                "drop": will drop all rows with missing values
+                "mean": will replace values by the mean of the column values
+                "median": will replace values by the median of the column values
+                "most_frequent": : will replace values by the most frequent values
+            dummy (bool, optional): Whether to use a dummy dataset. Defaults to False.
+            nb_rows (int, optional): The number of rows in the dummy dataset.\
+                Defaults to DUMMY_NB_ROWS.
+            seed (int, optional): The random seed for generating the dummy dataset.\
+                Defaults to DUMMY_SEED.
+
+        Returns:
+            Optional[Pipeline]: A trained DiffPrivLip pipeline
+        """
+        dpl_json = serialise_pipeline(pipeline)
+        body_json = {
+            "dataset_name": self.dataset_name,
+            "diffprivlib_json": dpl_json,
+            "feature_columns": feature_columns,
+            "target_columns": target_columns,
+            "test_size": test_size,
+            "test_train_split_seed": test_train_split_seed,
+            "imputer_strategy": imputer_strategy,
+        }
+        if dummy:
+            endpoint = "dummy_diffprivlib_query"
+            body_json["dummy_nb_rows"] = nb_rows
+            body_json["dummy_seed"] = seed
+        else:
+            endpoint = "diffprivlib_query"
+
+        res = self._exec(endpoint, body_json)
+        if res.status_code == HTTP_200_OK:
+            response = res.json()
+            model = base64.b64decode(response["query_response"]["model"])
+            response["query_response"]["model"] = json.loads(model)
+            return response
+        print(
+            f"Error while processing DiffPrivLib request in server \
+                status code: {res.status_code} message: {res.text}"
+        )
+        return res.text
+
+    def estimate_diffprivlib_cost(
+        self,
+        pipeline: Pipeline,
+        feature_columns: List[str] = [""],
+        target_columns: List[str] = [""],
+        test_size: float = 0.2,
+        test_train_split_seed: int = 1,
+        imputer_strategy: str = "drop",
+    ) -> dict:
+        """This function estimates the cost of executing a DiffPrivLib query.
+
+        Args:
+            pipeline (sklearn.pipeline): DiffPrivLib pipeline with three conditions:
+                - The pipeline MUST start with a `models.StandardScaler`.
+                  Otherwise a PrivacyLeakWarning is raised by DiffPrivLib library and
+                  is treated as an error in lomas server.
+                - `random_state` fields can only be int (`RandomState` will not work).
+                - `accountant` fields must be None.
+                Note: as in DiffPrivLib, avoid any DiffprivlibCompatibilityWarning
+                to ensure that the pipeline does what is intended.
+            feature_columns (list[str]): the list of feature column to train
+            target_columns (list[str], optional): the list of target column to predict \
+                May be None for certain models.
+            test_size (float, optional): proportion of the test set \
+                Defaults to 0.2.
+            test_train_split_seed (int, optional): seed for random train test split \
+                Defaults to 1.
+            imputer_strategy (str, optional): imputation strategy. Defaults to "drop".
+                "drop": will drop all rows with missing values
+                "mean": will replace values by the mean of the column values
+                "median": will replace values by the median of the column values
+                "most_frequent": : will replace values by the most frequent values
+
+        Returns:
+            Optional[dict[str, float]]: A dictionary containing the estimated cost.
+        """
+        dpl_json = serialise_pipeline(pipeline)
+        body_json = {
+            "dataset_name": self.dataset_name,
+            "diffprivlib_json": dpl_json,
+            "feature_columns": feature_columns,
+            "target_columns": target_columns,
+            "test_size": test_size,
+            "test_train_split_seed": test_train_split_seed,
+            "imputer_strategy": imputer_strategy,
+        }
+        res = self._exec("estimate_diffprivlib_cost", body_json)
+
+        if res.status_code == HTTP_200_OK:
+            return json.loads(res.content.decode("utf8"))
+        print(
+            f"Error while executing provided query in server:\n"
+            f"status code: {res.status_code} message: {res.text}"
+        )
+        return res.text
+
     def get_initial_budget(self) -> Optional[dict[str, float]]:
         """This function retrieves the initial budget.
 
@@ -329,7 +452,7 @@ def get_initial_budget(self) -> Optional[dict[str, float]]:
         }
         res = self._exec("get_initial_budget", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             return json.loads(res.content.decode("utf8"))
 
         print(error_message(res))
@@ -346,7 +469,7 @@ def get_total_spent_budget(self) -> Optional[dict[str, float]]:
         }
         res = self._exec("get_total_spent_budget", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             return json.loads(res.content.decode("utf8"))
 
         print(error_message(res))
@@ -363,7 +486,7 @@ def get_remaining_budget(self) -> Optional[dict[str, float]]:
         }
         res = self._exec("get_remaining_budget", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             return json.loads(res.content.decode("utf8"))
 
         print(error_message(res))
@@ -384,7 +507,7 @@ def get_previous_queries(self) -> Optional[List[dict]]:
         }
         res = self._exec("get_previous_queries", body_json)
 
-        if res.status_code == 200:
+        if res.status_code == HTTP_200_OK:
             queries = json.loads(res.content.decode("utf8"))[
                 "previous_queries"
             ]
@@ -402,6 +525,13 @@ def get_previous_queries(self) -> Optional[List[dict]]:
                             query["client_input"]["opendp_json"]
                         )
                         query["client_input"]["opendp_json"] = opdp_query
+                    case DPLibraries.DIFFPRIVLIB:
+                        model = base64.b64decode(
+                            query["response"]["query_response"]["model"]
+                        )
+                        query["response"]["query_response"]["model"] = (
+                            json.loads(model)
+                        )
                     case _:
                         raise ValueError(
                             "Cannot deserialise unknown query type:"
diff --git a/client/mypy.ini b/client/mypy.ini
deleted file mode 100644
index 521305b8..00000000
--- a/client/mypy.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[mypy]
-disable_error_code = import-untyped, import-not-found, attr-defined
diff --git a/client/notebooks/Demo_Client_Notebook.ipynb b/client/notebooks/Demo_Client_Notebook.ipynb
index 46d87c0c..af501f3d 100644
--- a/client/notebooks/Demo_Client_Notebook.ipynb
+++ b/client/notebooks/Demo_Client_Notebook.ipynb
@@ -74,17 +74,53 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 2,
+   "execution_count": 4,
    "id": "28fbdd79-8c15-49a9-bcf9-fcdeac09d2b5",
    "metadata": {},
-   "outputs": [],
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Collecting lomas-client\n",
+      "  Downloading lomas_client-0.1.0.tar.gz (8.0 kB)\n",
+      "  Installing build dependencies ... \u001b[?25ldone\n",
+      "\u001b[?25h  Getting requirements to build wheel ... \u001b[?25ldone\n",
+      "\u001b[?25h  Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n",
+      "\u001b[?25hRequirement already satisfied: opendp==0.8.0 in /usr/local/lib/python3.11/site-packages (from lomas-client) (0.8.0)\n",
+      "Requirement already satisfied: opendp-logger==0.3.0 in /usr/local/lib/python3.11/site-packages (from lomas-client) (0.3.0)\n",
+      "Requirement already satisfied: numpy==1.26.4 in /usr/local/lib/python3.11/site-packages (from lomas-client) (1.26.4)\n",
+      "Requirement already satisfied: requests==2.32.0 in /usr/local/lib/python3.11/site-packages (from lomas-client) (2.32.0)\n",
+      "Requirement already satisfied: pandas==2.2.2 in /usr/local/lib/python3.11/site-packages (from lomas-client) (2.2.2)\n",
+      "Requirement already satisfied: python-dateutil>=2.8.2 in /usr/local/lib/python3.11/site-packages (from pandas==2.2.2->lomas-client) (2.9.0.post0)\n",
+      "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.11/site-packages (from pandas==2.2.2->lomas-client) (2024.1)\n",
+      "Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.11/site-packages (from pandas==2.2.2->lomas-client) (2024.1)\n",
+      "Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.11/site-packages (from requests==2.32.0->lomas-client) (3.3.2)\n",
+      "Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.11/site-packages (from requests==2.32.0->lomas-client) (3.7)\n",
+      "Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.11/site-packages (from requests==2.32.0->lomas-client) (2.2.2)\n",
+      "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/lib/python3.11/site-packages (from requests==2.32.0->lomas-client) (2024.6.2)\n",
+      "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.11/site-packages (from python-dateutil>=2.8.2->pandas==2.2.2->lomas-client) (1.16.0)\n",
+      "Building wheels for collected packages: lomas-client\n",
+      "  Building wheel for lomas-client (pyproject.toml) ... \u001b[?25ldone\n",
+      "\u001b[?25h  Created wheel for lomas-client: filename=lomas_client-0.1.0-py3-none-any.whl size=7982 sha256=81a9dd4be09f50b39028e877a467f2ba48accfb55ffeea655a491dc1f0d28ccb\n",
+      "  Stored in directory: /root/.cache/pip/wheels/36/0b/91/29d8725d273c025f2a67ea520ca72b23e56eb47ee032bdfdeb\n",
+      "Successfully built lomas-client\n",
+      "Installing collected packages: lomas-client\n",
+      "Successfully installed lomas-client-0.1.0\n",
+      "\u001b[33mWARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv\u001b[0m\u001b[33m\n",
+      "\u001b[0m\n",
+      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m24.0\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m24.1\u001b[0m\n",
+      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpip install --upgrade pip\u001b[0m\n"
+     ]
+    }
+   ],
    "source": [
-    "#!pip install lomas-client"
+    "!pip install lomas-client"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 13,
    "id": "6fb569fc",
    "metadata": {},
    "outputs": [],
@@ -112,7 +148,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 6,
    "id": "941991f7",
    "metadata": {},
    "outputs": [],
@@ -153,7 +189,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 7,
    "id": "0fdebac9-57fc-4410-878b-5a77425af634",
    "metadata": {},
    "outputs": [
@@ -178,7 +214,7 @@
        "   'categories': ['MALE', 'FEMALE']}}}"
       ]
      },
-     "execution_count": 5,
+     "execution_count": 7,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -213,7 +249,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 8,
    "id": "01f4365a",
    "metadata": {},
    "outputs": [],
@@ -224,7 +260,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 9,
    "id": "3f553b29",
    "metadata": {},
    "outputs": [
@@ -321,8 +357,8 @@
        "</div>"
       ],
       "text/plain": [
-       "     species     island  bill_length_mm  bill_depth_mm  flipper_length_mm   \n",
-       "0     Gentoo     Biscoe       49.208473      16.117959         190.125950  \\\n",
+       "     species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  \\\n",
+       "0     Gentoo     Biscoe       49.208473      16.117959         190.125950   \n",
        "1     Gentoo  Torgersen       55.031628      19.963435         242.929142   \n",
        "2  Chinstrap  Torgersen       51.096718      16.777518         159.961493   \n",
        "3     Adelie     Biscoe       49.070911      14.796037         244.530153   \n",
@@ -336,7 +372,7 @@
        "4  5036.246870  FEMALE  "
       ]
      },
-     "execution_count": 7,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -391,7 +427,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 10,
    "id": "3946425d",
    "metadata": {},
    "outputs": [],
@@ -404,7 +440,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 11,
    "id": "90cf2a6d",
    "metadata": {},
    "outputs": [],
@@ -422,7 +458,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 10,
+   "execution_count": 12,
    "id": "a30f277e",
    "metadata": {},
    "outputs": [
@@ -430,7 +466,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "Average bill length in remote dummy: 47.52mm.\n",
+      "Average bill length in remote dummy: 47.51mm.\n",
       "Number of rows in remote dummy: 199.\n"
      ]
     }
diff --git a/client/notebooks/Demo_Client_Notebook_DiffPrivLib.ipynb b/client/notebooks/Demo_Client_Notebook_DiffPrivLib.ipynb
new file mode 100644
index 00000000..0c4c779b
--- /dev/null
+++ b/client/notebooks/Demo_Client_Notebook_DiffPrivLib.ipynb
@@ -0,0 +1,3257 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "id": "3f18d338",
+   "metadata": {},
+   "source": [
+    "# Secure Data Disclosure: Client side"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "1582a2ae",
+   "metadata": {},
+   "source": [
+    "This notebook showcases how researcher could use the Secure Data Disclosure system. It explains the different functionnalities provided by the `fso-sdd-demo` client library to interact with the secure server.\n",
+    "\n",
+    "The secure data are never visible by researchers. They can only access to differentially private responses via queries to the server.\n",
+    "\n",
+    "Each user has access to one or multiple projects and for each dataset has a limited budget with $\\epsilon$ and $\\delta$ values."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "id": "23bb4f13-7800-41b2-b429-68c2d02243d0",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "image/png": "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P/i1WfzOMjsFhZBuOCe6juo8qnMqAABR0XeGX/z8D3YMQDEioEOr+fPfsEOJXnlloR1CsXhr4VLnJj5fIZ1+nYsHc012CoBC+MtfnkqozpoOBUAP/flJO4Z8Uxiabjjn9ecHn8j7DysAACSj7/b8YAQULwI6OJ5/boFzkxdEJTlU/bWcqAixqlO6f1FUb82WbuLvueePdgxAOXh70bt2KE5V2z/xiQ8lnK++dPvN5ripE+0z4nSephRdYSx6K3EfqcrQNde833z7O//Suo++9n8/by686Ez7jDhdY59/foEdAwAge/qe4P2O4P3TdWnOB893mlrwWvT2UjsEoNjQBl0BlEIbdKoyqVJZLt38ecd1o3HVVZfYsY4rlrbfikW620PPW7QouNRGPtosKNR+og264kIbdOEKdR73fyb05TqsDdB//ep3EkrbJTsX6DM9b97LZtmy1a0/xtTXD4md6yeZc889PWU7o25nCMuXrUp4T81j3PjR5uyzZwV2hJBuGyupzgXex3U++sAHzzN/e3xe63VKy3HBBbPN6WfMdMZFpdWeeur52HOWtC6zblLGjx9pZs8+qcPnTf86KUD1vq+XvypssmuptvGjj8519pFbelnhn5b34ovPCu1owr88Ombuv/9hszC2bbSv3XmoiQSXttfXv/FFO5ZIP9j98pdtVaD0feALX7jRjrXp6DHVkX2ZD7RBB5Q+2qBL7zu6/1qU7DUdvRaJ+9pknVulao8122u3e21qbNyUUBPIXYewtrz921XX62FDB5u//W1e63LoenjBhbMT2vDL5fej0aNHtFt3zWf27Fl522733/+IefyxZ+1YfDlWrFhj5se+M2j7aR5TY+t91VWXtl7Xw7axlrW+oa7g7aVnqtjboCOgK4BiD+j0gf7SF79px+InH32QvScpndS+852v2LHk9OV+wSsLW79suydEfbBXrVqT9KISdHLUF/1UJyv3pOSeTETzPiP2BT/oS346Fzf/TYQed09I3hsSd3t15ESUznJ4qYTMt+/8mR2LO2nW8YEdOLjbZMXy1QkXANH7BPXm5F+eIP5lDLsYuxeFZBfjoJtyraNu1txt7F4YZp95UtJGbTuyvl7F8KUiagR04Qp1Hle7j94vOzq/XHHFhVn1AvrjH/82IZzx0zF67TWXhQYiqgqj6pnuOS+MSpH5PyP5COh0/hfv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",
+      "text/plain": [
+       "<IPython.core.display.Image object>"
+      ]
+     },
+     "execution_count": 1,
+     "metadata": {
+      "image/png": {
+       "width": 800
+      }
+     },
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "from IPython.display import Image\n",
+    "Image(filename=\"images/image_demo_client.png\", width=800)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5b73135c",
+   "metadata": {},
+   "source": [
+    "🐧🐧🐧\n",
+    "In this notebook the researcher is a penguin researcher named Dr. Antarctica. She aims to do a grounbdbreaking research on various penguins dimensions.\n",
+    "\n",
+    "Therefore, the powerful queen Icerbegina 👑 had the data collected. But in order to get the penguins to agree to participate she promised them that no one would be able to look at the data and that no one would be able to guess the bill width of any specific penguin (which is very sensitive information) from the data. Nobody! Not even the researchers. The queen hence stored the data on the Secure Data Disclosure Server and only gave a small budget to Dr. Antarctica.\n",
+    "\n",
+    "This is not a problem for Dr. Antarctica as she does not need to see the data to make statistics thanks to the Secure Data Disclosure Client library `fso-sdd-demo`. \n",
+    "🐧🐧🐧"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "01ae30d2",
+   "metadata": {},
+   "source": [
+    "## Step 1: Install the library\n",
+    "To interact with the secure server on which the data is stored, Dr.Antartica first needs to install the library `fso-sdd-demo` on her local developping environment. \n",
+    "\n",
+    "It can be installed via the pip command:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 2,
+   "id": "36d508bf-6cc3-4034-8e11-fffe858552f9",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import sys\n",
+    "import os\n",
+    "sys.path.append(os.path.abspath(os.path.join('..')))\n",
+    "# !pip install lomas_client"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "id": "9535e92e-620e-4df4-92dd-4ea2c653e4ab",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from lomas_client import Client\n",
+    "import numpy as np"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9c63718b",
+   "metadata": {},
+   "source": [
+    "## Step 2: Initialise the client\n",
+    "\n",
+    "Once the library is installed, a Client object must be created. It is responsible for sending sending requests to the server and processing responses in the local environment. It enables a seamless interaction with the server. \n",
+    "\n",
+    "To create the client, Dr. Antartica needs to give it a few parameters:\n",
+    "- a url: the root application endpoint to the remote secure server.\n",
+    "- user_name: her name as registered in the database (Dr. Alice Antartica)\n",
+    "- dataset_name: the name of the dataset that she wants to query (PENGUIN)\n",
+    "\n",
+    "She will only be able to query on the real dataset if the queen Icergina has previously made her an account in the database, given her access to the PENGUIN dataset and has given her some epsilon and delta credit (as is done in the Admin Notebook for Users and Datasets management)."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "id": "941991f7",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "APP_URL = \"http://lomas_server\"\n",
+    "USER_NAME = \"Dr. Antartica\"\n",
+    "DATASET_NAME = \"PENGUIN\"\n",
+    "client = Client(url=APP_URL, user_name = USER_NAME, dataset_name = DATASET_NAME)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "0ec400c8",
+   "metadata": {},
+   "source": [
+    "And that's it for the preparation. She is now ready to use the various functionnalities offered by `fso_sdd_demo`."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9b9a5f13",
+   "metadata": {},
+   "source": [
+    "## Step 3: Metadata and dummy dataset"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c7cb5531",
+   "metadata": {},
+   "source": [
+    "### Getting dataset metadata\n",
+    "\n",
+    "Dr. Antartica has never seen the data and as a first step to understand what is available to her, she would like to check the metadata of the dataset. Therefore, she just needs to call the `get_dataset_metadata()` function of the client. As this is public information, this does not cost any budget.\n",
+    "\n",
+    "This function returns metadata information in a format based on [SmartnoiseSQL dictionary format](https://docs.smartnoise.org/sql/metadata.html#dictionary-format), where among other, there is information about all the available columns, their type, bound values (see Smartnoise page for more details). Any metadata is required for Smartnoise-SQL is also required here and additional information such that the different categories in a string type column column can be added."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "id": "0fdebac9-57fc-4410-878b-5a77425af634",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'max_ids': 1,\n",
+       " 'row_privacy': True,\n",
+       " 'censor_dims': False,\n",
+       " 'columns': {'species': {'type': 'string',\n",
+       "   'cardinality': 3,\n",
+       "   'categories': ['Adelie', 'Chinstrap', 'Gentoo']},\n",
+       "  'island': {'type': 'string',\n",
+       "   'cardinality': 3,\n",
+       "   'categories': ['Torgersen', 'Biscoe', 'Dream']},\n",
+       "  'bill_length_mm': {'type': 'float', 'lower': 30.0, 'upper': 65.0},\n",
+       "  'bill_depth_mm': {'type': 'float', 'lower': 13.0, 'upper': 23.0},\n",
+       "  'flipper_length_mm': {'type': 'float', 'lower': 150.0, 'upper': 250.0},\n",
+       "  'body_mass_g': {'type': 'float', 'lower': 2000.0, 'upper': 7000.0},\n",
+       "  'sex': {'type': 'string',\n",
+       "   'cardinality': 2,\n",
+       "   'categories': ['MALE', 'FEMALE']}}}"
+      ]
+     },
+     "execution_count": 5,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "penguin_metadata = client.get_dataset_metadata()\n",
+    "penguin_metadata"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "d338ed96",
+   "metadata": {},
+   "source": [
+    "Based on this Dr. Antartica knows that there are 7 columns, 3 of string type (species, island, sex) with their associated categories (i.e. the species column has 3 possibilities: 'Adelie', 'Chinstrap', 'Gentoo') and 4 of float type (bill length, bill depth, flipper length and body mass) with their associated bounds (i.e. the body mass of penguin ranges from 2000 to 7000 gramms). She also knows based on the field `max_ids: 1` that each penguin can only be once in the dataset and on the field `row_privacy: True` that each row represents a single penguin. "
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "5a3c899d",
+   "metadata": {},
+   "source": [
+    "### Get a dummy dataset\n",
+    "\n",
+    "Now, that she has seen and understood the metadata, she wants to get an even better understanding of the dataset (but is still not able to see it). A solution to have an idea of what the dataset looks like it to create a dummy dataset. \n",
+    "\n",
+    "Based on the public metadata of the dataset, a random dataframe can be created created. By default, there will be 100 rows and the seed is set to 42 to ensure reproducibility, but these 2 variables can be changed to obtain different dummy datasets.\n",
+    "Getting a dummy dataset does not affect the budget as there is no differential privacy here. It is not a synthetic dataset and all that could be learn here is already present in the public metadata (it is created randomly on the fly based on the metadata).\n",
+    "\n",
+    "Dr. Antartica first create a dummy dataset with 200 rows and chooses a seed of 0."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "id": "01f4365a",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "NB_ROWS = 200\n",
+    "SEED = 0"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "id": "3f553b29",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(200, 7)\n"
+     ]
+    },
+    {
+     "data": {
+      "text/html": [
+       "<div>\n",
+       "<style scoped>\n",
+       "    .dataframe tbody tr th:only-of-type {\n",
+       "        vertical-align: middle;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe tbody tr th {\n",
+       "        vertical-align: top;\n",
+       "    }\n",
+       "\n",
+       "    .dataframe thead th {\n",
+       "        text-align: right;\n",
+       "    }\n",
+       "</style>\n",
+       "<table border=\"1\" class=\"dataframe\">\n",
+       "  <thead>\n",
+       "    <tr style=\"text-align: right;\">\n",
+       "      <th></th>\n",
+       "      <th>species</th>\n",
+       "      <th>island</th>\n",
+       "      <th>bill_length_mm</th>\n",
+       "      <th>bill_depth_mm</th>\n",
+       "      <th>flipper_length_mm</th>\n",
+       "      <th>body_mass_g</th>\n",
+       "      <th>sex</th>\n",
+       "    </tr>\n",
+       "  </thead>\n",
+       "  <tbody>\n",
+       "    <tr>\n",
+       "      <th>0</th>\n",
+       "      <td>Gentoo</td>\n",
+       "      <td>Biscoe</td>\n",
+       "      <td>49.208473</td>\n",
+       "      <td>16.117959</td>\n",
+       "      <td>190.125950</td>\n",
+       "      <td>2873.291927</td>\n",
+       "      <td>FEMALE</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>1</th>\n",
+       "      <td>Gentoo</td>\n",
+       "      <td>Torgersen</td>\n",
+       "      <td>55.031628</td>\n",
+       "      <td>19.963435</td>\n",
+       "      <td>242.929142</td>\n",
+       "      <td>3639.940005</td>\n",
+       "      <td>FEMALE</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>2</th>\n",
+       "      <td>Chinstrap</td>\n",
+       "      <td>Torgersen</td>\n",
+       "      <td>51.096718</td>\n",
+       "      <td>16.777518</td>\n",
+       "      <td>159.961493</td>\n",
+       "      <td>5401.743330</td>\n",
+       "      <td>MALE</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>3</th>\n",
+       "      <td>Adelie</td>\n",
+       "      <td>Biscoe</td>\n",
+       "      <td>49.070911</td>\n",
+       "      <td>14.796037</td>\n",
+       "      <td>244.530153</td>\n",
+       "      <td>2316.038092</td>\n",
+       "      <td>MALE</td>\n",
+       "    </tr>\n",
+       "    <tr>\n",
+       "      <th>4</th>\n",
+       "      <td>Chinstrap</td>\n",
+       "      <td>Biscoe</td>\n",
+       "      <td>44.827918</td>\n",
+       "      <td>13.246787</td>\n",
+       "      <td>236.948853</td>\n",
+       "      <td>5036.246870</td>\n",
+       "      <td>FEMALE</td>\n",
+       "    </tr>\n",
+       "  </tbody>\n",
+       "</table>\n",
+       "</div>"
+      ],
+      "text/plain": [
+       "     species     island  bill_length_mm  bill_depth_mm  flipper_length_mm  \\\n",
+       "0     Gentoo     Biscoe       49.208473      16.117959         190.125950   \n",
+       "1     Gentoo  Torgersen       55.031628      19.963435         242.929142   \n",
+       "2  Chinstrap  Torgersen       51.096718      16.777518         159.961493   \n",
+       "3     Adelie     Biscoe       49.070911      14.796037         244.530153   \n",
+       "4  Chinstrap     Biscoe       44.827918      13.246787         236.948853   \n",
+       "\n",
+       "   body_mass_g     sex  \n",
+       "0  2873.291927  FEMALE  \n",
+       "1  3639.940005  FEMALE  \n",
+       "2  5401.743330    MALE  \n",
+       "3  2316.038092    MALE  \n",
+       "4  5036.246870  FEMALE  "
+      ]
+     },
+     "execution_count": 7,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "df_dummy = client.get_dummy_dataset(\n",
+    "    nb_rows = NB_ROWS, \n",
+    "    seed = SEED\n",
+    ")\n",
+    "\n",
+    "print(df_dummy.shape)\n",
+    "df_dummy.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9e7ca7ae-bf17-40c8-aa75-2d72fcdd3088",
+   "metadata": {},
+   "source": [
+    "## Step 4: Train Logistic Regression model with DiffPrivLib"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "2de1389c-53a7-4098-bc3c-397c12a4b869",
+   "metadata": {},
+   "source": [
+    "We want to train an ML model to guess the species of penguins based on their bill length and depth, flipper length and body mass.\n",
+    "\n",
+    "Therefore, we use a DiffPrivLib pipeline which:\n",
+    "- standard scales the dimensions between the metadata bounds\n",
+    "- and then performs a logistic regression\n",
+    "to predict the species of penguins."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "id": "2864729f-2ce4-4d81-a446-8e3f2c1493b3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from sklearn.pipeline import Pipeline\n",
+    "from diffprivlib import models\n",
+    "import pandas as pd"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a06365e9-4076-4592-871a-31af91d6a05d",
+   "metadata": {},
+   "source": [
+    "### Classification: Logistic Regression"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "id": "32e81639-2a52-4a92-aead-785a30e5483f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import warnings\n",
+    "from diffprivlib import models\n",
+    "from diffprivlib.utils import (\n",
+    "    DiffprivlibCompatibilityWarning,\n",
+    "    PrivacyLeakWarning,\n",
+    ")\n",
+    "warnings.simplefilter(\"error\", DiffprivlibCompatibilityWarning)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "eead3541-66e5-4c0f-aa5b-7b97821afe39",
+   "metadata": {},
+   "source": [
+    "#### DiffprivlibCompatibilityWarning will not work"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "id": "804f31cd-f277-47d4-9648-a51872eccf29",
+   "metadata": {},
+   "outputs": [
+    {
+     "ename": "DiffprivlibCompatibilityWarning",
+     "evalue": "Parameter 'svd_solver' is not functional in diffprivlib.  Remove this parameter to suppress this warning.",
+     "output_type": "error",
+     "traceback": [
+      "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
+      "\u001b[0;31mDiffprivlibCompatibilityWarning\u001b[0m           Traceback (most recent call last)",
+      "Cell \u001b[0;32mIn[10], line 3\u001b[0m\n\u001b[1;32m      1\u001b[0m dpl_pipeline \u001b[38;5;241m=\u001b[39m Pipeline([\n\u001b[1;32m      2\u001b[0m     (\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mscaler\u001b[39m\u001b[38;5;124m'\u001b[39m, models\u001b[38;5;241m.\u001b[39mStandardScaler(epsilon \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.5\u001b[39m)),\n\u001b[0;32m----> 3\u001b[0m     (\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mclassifier\u001b[39m\u001b[38;5;124m'\u001b[39m, \u001b[43mmodels\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mLogisticRegression\u001b[49m\u001b[43m(\u001b[49m\u001b[43mepsilon\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;241;43m1.0\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43msvd_solver\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mfull\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m)\u001b[49m)\n\u001b[1;32m      4\u001b[0m ])\n",
+      "File \u001b[0;32m/usr/local/lib/python3.11/site-packages/diffprivlib/models/logistic_regression.py:194\u001b[0m, in \u001b[0;36mLogisticRegression.__init__\u001b[0;34m(self, epsilon, data_norm, tol, C, fit_intercept, max_iter, verbose, warm_start, n_jobs, random_state, accountant, **unused_args)\u001b[0m\n\u001b[1;32m    191\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mclasses_ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m    192\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39maccountant \u001b[38;5;241m=\u001b[39m BudgetAccountant\u001b[38;5;241m.\u001b[39mload_default(accountant)\n\u001b[0;32m--> 194\u001b[0m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_warn_unused_args\u001b[49m\u001b[43m(\u001b[49m\u001b[43munused_args\u001b[49m\u001b[43m)\u001b[49m\n",
+      "File \u001b[0;32m/usr/local/lib/python3.11/site-packages/diffprivlib/utils.py:71\u001b[0m, in \u001b[0;36mwarn_unused_args\u001b[0;34m(args)\u001b[0m\n\u001b[1;32m     68\u001b[0m     args \u001b[38;5;241m=\u001b[39m [args]\n\u001b[1;32m     70\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m arg \u001b[38;5;129;01min\u001b[39;00m args:\n\u001b[0;32m---> 71\u001b[0m     \u001b[43mwarnings\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mwarn\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mParameter \u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43marg\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m is not functional in diffprivlib.  Remove this parameter to suppress this \u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\n\u001b[1;32m     72\u001b[0m \u001b[43m                  \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mwarning.\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mDiffprivlibCompatibilityWarning\u001b[49m\u001b[43m)\u001b[49m\n",
+      "\u001b[0;31mDiffprivlibCompatibilityWarning\u001b[0m: Parameter 'svd_solver' is not functional in diffprivlib.  Remove this parameter to suppress this warning."
+     ]
+    }
+   ],
+   "source": [
+    "# DiffprivlibCompatibilityWarning Error expected\n",
+    "dpl_pipeline = Pipeline([\n",
+    "    ('scaler', models.StandardScaler(epsilon = 0.5)),\n",
+    "    ('classifier', models.LogisticRegression(epsilon = 1.0, svd_solver='full'))\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "15dd0c42-3e20-4b27-95b3-9b55622b4bfd",
+   "metadata": {},
+   "source": [
+    "To resolve the DiffprivlibCompatibilityWarning issue, the svd_solver should not be set as it i incompatible with diffprivlib."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9c5d7fa4-cfe6-4a4f-88ff-8e88ca31dfed",
+   "metadata": {},
+   "source": [
+    "#### PrivacyLeakWarning will not be processed within server"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 13,
+   "id": "f1c9cffb-8327-400d-ab9d-35c5450fd4d6",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('scaler', models.StandardScaler(epsilon = 0.5, bounds=BOUNDS)),\n",
+    "    ('classifier', models.LogisticRegression(epsilon = 1.0))\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 14,
+   "id": "db1ddfbc-de3e-43fe-9958-49f9e6dad89f",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "Error while processing DiffPrivLib request in server                 status code: 422 message: {\"ExternalLibraryException\":\"PrivacyLeakWarning: Data norm has not been specified and will be calculated on the data provided.  This will result in additional privacy leakage. To ensure differential privacy and no additional privacy leakage, specify `data_norm` at initialisation.. Lomas server cannot fit pipeline on data, PrivacyLeakWarning is a blocker.\",\"library\":\"diffprivlib\"}\n"
+     ]
+    }
+   ],
+   "source": [
+    "# Expect PrivacyLeakWarning Error\n",
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1,\n",
+    "    dummy = True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c9d58006-89d5-4110-9657-641256bafaf9",
+   "metadata": {},
+   "source": [
+    "Diffprivlib requests that have **PrivacyLeakWarning** will not be processed in the server. \n",
+    "In lomas, **the standard scaler must always be used as a first step** and filled based on the metadata values."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 15,
+   "id": "12d1faa1-f88a-49bb-911a-83f879ca10b6",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])"
+      ]
+     },
+     "execution_count": 15,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "bl_l = penguin_metadata['columns']['bill_length_mm']['lower']\n",
+    "bl_u = penguin_metadata['columns']['bill_length_mm']['upper']\n",
+    "bd_l = penguin_metadata['columns']['bill_depth_mm']['lower']\n",
+    "bd_u = penguin_metadata['columns']['bill_depth_mm']['upper']\n",
+    "fl_l = penguin_metadata['columns']['flipper_length_mm']['lower']\n",
+    "fl_u = penguin_metadata['columns']['flipper_length_mm']['upper']\n",
+    "bm_l = penguin_metadata['columns']['body_mass_g']['lower']\n",
+    "bm_u = penguin_metadata['columns']['body_mass_g']['upper']\n",
+    "\n",
+    "BOUNDS = ([bl_l, bd_l, fl_l, bm_l], [bl_u, bd_u, fl_u, bm_u])\n",
+    "BOUNDS"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c47aec3e-15ea-476d-9dcb-5b2f4888e355",
+   "metadata": {},
+   "source": [
+    "For the same reason, the data_norm should be computed based on metadata and given as argument."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 18,
+   "id": "25a72ad9-d3f3-478a-8d33-c07bedbf4f66",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "83.69469642643347"
+      ]
+     },
+     "execution_count": 18,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# The max l2 norm of any row of the data. This defines the spread of data that will be protected by differential privacy.\n",
+    "data_norm = np.sqrt(np.linalg.norm([bl_u, bd_u, fl_u, bm_u]))\n",
+    "data_norm"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 19,
+   "id": "828a743e-960f-4713-a6ed-a0bd243a13e3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('scaler', models.StandardScaler(epsilon = 0.5, bounds=([bl_l, bd_l, fl_l, bm_l], [bl_u, bd_u, fl_u, bm_u]))),\n",
+    "    ('classifier', models.LogisticRegression(epsilon = 1.0, data_norm = data_norm))\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 20,
+   "id": "c9c13dfe-0266-4cd8-b126-2accee6c1136",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1,\n",
+    "    dummy = True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 21,
+   "id": "b3e7ceae-2813-47fe-b96f-dcc0d6bb207e",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "The accuracy score of the dummy_response is 0.3.\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(f\"The accuracy score of the dummy_response is {dummy_response['query_response']['score']}.\")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 22,
+   "id": "e036b55f-6c3d-4212-8d91-9ece8223cf69",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'epsilon_cost': 1.5, 'delta_cost': 0.0}"
+      ]
+     },
+     "execution_count": 22,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "res = client.estimate_diffprivlib_cost(\n",
+    "    dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1\n",
+    ")\n",
+    "res"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 23,
+   "id": "4398755f-348f-47e2-9329-80c34897e16b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>#sk-container-id-1 {\n",
+       "  /* Definition of color scheme common for light and dark mode */\n",
+       "  --sklearn-color-text: black;\n",
+       "  --sklearn-color-line: gray;\n",
+       "  /* Definition of color scheme for unfitted estimators */\n",
+       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
+       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
+       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
+       "  --sklearn-color-unfitted-level-3: chocolate;\n",
+       "  /* Definition of color scheme for fitted estimators */\n",
+       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
+       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
+       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
+       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
+       "\n",
+       "  /* Specific color for light theme */\n",
+       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
+       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-icon: #696969;\n",
+       "\n",
+       "  @media (prefers-color-scheme: dark) {\n",
+       "    /* Redefinition of color scheme for dark theme */\n",
+       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
+       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-icon: #878787;\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 pre {\n",
+       "  padding: 0;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 input.sk-hidden--visually {\n",
+       "  border: 0;\n",
+       "  clip: rect(1px 1px 1px 1px);\n",
+       "  clip: rect(1px, 1px, 1px, 1px);\n",
+       "  height: 1px;\n",
+       "  margin: -1px;\n",
+       "  overflow: hidden;\n",
+       "  padding: 0;\n",
+       "  position: absolute;\n",
+       "  width: 1px;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
+       "  border: 1px dashed var(--sklearn-color-line);\n",
+       "  margin: 0 0.4em 0.5em 0.4em;\n",
+       "  box-sizing: border-box;\n",
+       "  padding-bottom: 0.4em;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-container {\n",
+       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
+       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
+       "     so we also need the `!important` here to be able to override the\n",
+       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
+       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
+       "  display: inline-block !important;\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
+       "  display: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-parallel-item,\n",
+       "div.sk-serial,\n",
+       "div.sk-item {\n",
+       "  /* draw centered vertical line to link estimators */\n",
+       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
+       "  background-size: 2px 100%;\n",
+       "  background-repeat: no-repeat;\n",
+       "  background-position: center center;\n",
+       "}\n",
+       "\n",
+       "/* Parallel-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel-item::after {\n",
+       "  content: \"\";\n",
+       "  width: 100%;\n",
+       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
+       "  flex-grow: 1;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel {\n",
+       "  display: flex;\n",
+       "  align-items: stretch;\n",
+       "  justify-content: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel-item {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
+       "  align-self: flex-end;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
+       "  align-self: flex-start;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
+       "  width: 0;\n",
+       "}\n",
+       "\n",
+       "/* Serial-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-serial {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "  align-items: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  padding-right: 1em;\n",
+       "  padding-left: 1em;\n",
+       "}\n",
+       "\n",
+       "\n",
+       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
+       "clickable and can be expanded/collapsed.\n",
+       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
+       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
+       "*/\n",
+       "\n",
+       "/* Pipeline and ColumnTransformer style (default) */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-toggleable {\n",
+       "  /* Default theme specific background. It is overwritten whether we have a\n",
+       "  specific estimator or a Pipeline/ColumnTransformer */\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable label */\n",
+       "#sk-container-id-1 label.sk-toggleable__label {\n",
+       "  cursor: pointer;\n",
+       "  display: block;\n",
+       "  width: 100%;\n",
+       "  margin-bottom: 0;\n",
+       "  padding: 0.5em;\n",
+       "  box-sizing: border-box;\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
+       "  /* Arrow on the left of the label */\n",
+       "  content: \"▸\";\n",
+       "  float: left;\n",
+       "  margin-right: 0.25em;\n",
+       "  color: var(--sklearn-color-icon);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable content - dropdown */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-toggleable__content {\n",
+       "  max-height: 0;\n",
+       "  max-width: 0;\n",
+       "  overflow: hidden;\n",
+       "  text-align: left;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
+       "  margin: 0.2em;\n",
+       "  border-radius: 0.25em;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
+       "  /* Expand drop-down */\n",
+       "  max-height: 200px;\n",
+       "  max-width: 100%;\n",
+       "  overflow: auto;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
+       "  content: \"▾\";\n",
+       "}\n",
+       "\n",
+       "/* Pipeline/ColumnTransformer-specific style */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific style */\n",
+       "\n",
+       "/* Colorize estimator box */\n",
+       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
+       "#sk-container-id-1 div.sk-label label {\n",
+       "  /* The background is the default theme color */\n",
+       "  color: var(--sklearn-color-text-on-default-background);\n",
+       "}\n",
+       "\n",
+       "/* On hover, darken the color of the background */\n",
+       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Label box, darken color on hover, fitted */\n",
+       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator label */\n",
+       "\n",
+       "#sk-container-id-1 div.sk-label label {\n",
+       "  font-family: monospace;\n",
+       "  font-weight: bold;\n",
+       "  display: inline-block;\n",
+       "  line-height: 1.2em;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-label-container {\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific */\n",
+       "#sk-container-id-1 div.sk-estimator {\n",
+       "  font-family: monospace;\n",
+       "  border: 1px dotted var(--sklearn-color-border-box);\n",
+       "  border-radius: 0.25em;\n",
+       "  box-sizing: border-box;\n",
+       "  margin-bottom: 0.5em;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-estimator.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "/* on hover */\n",
+       "#sk-container-id-1 div.sk-estimator:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
+       "\n",
+       "/* Common style for \"i\" and \"?\" */\n",
+       "\n",
+       ".sk-estimator-doc-link,\n",
+       "a:link.sk-estimator-doc-link,\n",
+       "a:visited.sk-estimator-doc-link {\n",
+       "  float: right;\n",
+       "  font-size: smaller;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1em;\n",
+       "  height: 1em;\n",
+       "  width: 1em;\n",
+       "  text-decoration: none !important;\n",
+       "  margin-left: 1ex;\n",
+       "  /* unfitted */\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted,\n",
+       "a:link.sk-estimator-doc-link.fitted,\n",
+       "a:visited.sk-estimator-doc-link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "/* Span, style for the box shown on hovering the info icon */\n",
+       ".sk-estimator-doc-link span {\n",
+       "  display: none;\n",
+       "  z-index: 9999;\n",
+       "  position: relative;\n",
+       "  font-weight: normal;\n",
+       "  right: .2ex;\n",
+       "  padding: .5ex;\n",
+       "  margin: .5ex;\n",
+       "  width: min-content;\n",
+       "  min-width: 20ex;\n",
+       "  max-width: 50ex;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  box-shadow: 2pt 2pt 4pt #999;\n",
+       "  /* unfitted */\n",
+       "  background: var(--sklearn-color-unfitted-level-0);\n",
+       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted span {\n",
+       "  /* fitted */\n",
+       "  background: var(--sklearn-color-fitted-level-0);\n",
+       "  border: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link:hover span {\n",
+       "  display: block;\n",
+       "}\n",
+       "\n",
+       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
+       "\n",
+       "#sk-container-id-1 a.estimator_doc_link {\n",
+       "  float: right;\n",
+       "  font-size: 1rem;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1rem;\n",
+       "  height: 1rem;\n",
+       "  width: 1rem;\n",
+       "  text-decoration: none;\n",
+       "  /* unfitted */\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>Pipeline(steps=[(&#x27;scaler&#x27;,\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                (&#x27;classifier&#x27;,\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" ><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;Pipeline<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.4/modules/generated/sklearn.pipeline.Pipeline.html\">?<span>Documentation for Pipeline</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>Pipeline(steps=[(&#x27;scaler&#x27;,\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                (&#x27;classifier&#x27;,\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])</pre></div> </div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-2\" type=\"checkbox\" ><label for=\"sk-estimator-id-2\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">StandardScaler</label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "               bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                       array([  65.,   23.,  250., 7000.])),\n",
+       "               epsilon=0.5)</pre></div> </div></div><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-3\" type=\"checkbox\" ><label for=\"sk-estimator-id-3\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">LogisticRegression</label><div class=\"sk-toggleable__content fitted\"><pre>LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                   data_norm=83.69469642643347)</pre></div> </div></div></div></div></div></div>"
+      ],
+      "text/plain": [
+       "Pipeline(steps=[('scaler',\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                ('classifier',\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])"
+      ]
+     },
+     "execution_count": 23,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dummy_response['query_response'][\"model\"]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "8aad5f86-20f7-42fe-8efd-56c96f6beb41",
+   "metadata": {},
+   "source": [
+    "Now we train the same pipeline on the real dataset."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 24,
+   "id": "b823db85-d993-4a36-9dea-bf5989c543f8",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'requested_by': 'Dr. Antartica',\n",
+       " 'query_response': {'score': 0.13432835820895522,\n",
+       "  'model': Pipeline(steps=[('scaler',\n",
+       "                   StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                  bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                          array([  65.,   23.,  250., 7000.])),\n",
+       "                                  epsilon=0.5)),\n",
+       "                  ('classifier',\n",
+       "                   LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                      data_norm=83.69469642643347))])},\n",
+       " 'spent_epsilon': 1.5,\n",
+       " 'spent_delta': 0.0}"
+      ]
+     },
+     "execution_count": 24,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "res = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1\n",
+    ")\n",
+    "res"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "id": "6c071425-5364-4d79-8ccf-4f46018ac849",
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "The accuracy score of the model trained on real data is 0.13432835820895522.\n"
+     ]
+    }
+   ],
+   "source": [
+    "print(f\"The accuracy score of the model trained on real data is {res['query_response']['score']}.\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "9cea3fc7-4331-4387-a19f-6afcaa21b6bc",
+   "metadata": {},
+   "source": [
+    "The model is of course the same but with different trained parameters."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "id": "b646cd08-504b-4846-88ba-a3557ee4b2fd",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "model = res['query_response']['model']"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "a581f47d-f1c7-4121-9f8f-e496e9023d58",
+   "metadata": {},
+   "source": [
+    "We predict what would be the specy of the smallest possible penguin in all dimension versus to biggest possible penguin in all dimensions."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "id": "8cd3c335-f3b3-4f64-b708-737ae4623a5f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "x_to_predict = pd.DataFrame({\n",
+    "    'bill_length_mm': [bl_l, bl_u], \n",
+    "    'bill_depth_mm': [bd_l, bd_u] , \n",
+    "    'flipper_length_mm': [fl_l, fl_u], \n",
+    "    'body_mass_g': [bm_l, bm_u]\n",
+    "})"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "id": "65413ef0-317b-431d-9ed8-6ffabe85c1b2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(['Chinstrap', 'Gentoo'], dtype=object)"
+      ]
+     },
+     "execution_count": 28,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c1e968c0-f319-48c5-89cb-b8a255e822cb",
+   "metadata": {},
+   "source": [
+    "Indeed, Chinstrap penguins are typically smaller than Gentoo penguins."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "839ba199-3014-4e39-8f0a-aecf15241b11",
+   "metadata": {},
+   "source": [
+    "## Step 5: Train other models with DiffPrivLib"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fef35343-cdf6-4e86-a54a-4bcf6568f2ac",
+   "metadata": {},
+   "source": [
+    "### Classification: Gaussian Naive Bayes"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 25,
+   "id": "9dd37857-0ef2-44d5-b206-092262c9cea1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('scaler', models.StandardScaler(epsilon = 0.5, bounds=BOUNDS)),\n",
+    "    ('gaussian', models.GaussianNB(epsilon = 1.0, bounds=BOUNDS, priors = (0.3, 0.3, 0.4))),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 26,
+   "id": "8e2c07c6-20f4-473c-80f6-b4b558191530",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    dummy = True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 27,
+   "id": "550868df-5476-496e-849d-8787da9eb27e",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'epsilon_cost': 1.5, 'delta_cost': 0.0}"
+      ]
+     },
+     "execution_count": 27,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cost_res = client.estimate_diffprivlib_cost(\n",
+    "    dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1\n",
+    ")\n",
+    "cost_res"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 28,
+   "id": "934a1237-68d1-4796-838e-e8c370068f5d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 29,
+   "id": "ec3c98d3-cb5d-4a96-9ada-ea9904326aec",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(['Adelie', 'Chinstrap'], dtype=object)"
+      ]
+     },
+     "execution_count": 29,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model = res['query_response']['model']\n",
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "c1224c94-d995-40f1-967d-b7afb21bce48",
+   "metadata": {},
+   "source": [
+    "### Random Forest"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 30,
+   "id": "d4f036a7-0405-4f4b-a4a0-3fe1b0b8d4d0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('rf', models.RandomForestClassifier(n_estimators=10, epsilon = 2.0, bounds=BOUNDS, classes=['Adelie', 'Chinstrap', 'Gentoo'])),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 31,
+   "id": "c969d488-e1a2-445a-a269-681d96343b9f",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    dummy = True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 32,
+   "id": "de94f0a5-6e9d-4eac-aa77-b98892ed41fc",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'epsilon_cost': 2.0, 'delta_cost': 0.0}"
+      ]
+     },
+     "execution_count": 32,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "cost_res = client.estimate_diffprivlib_cost(\n",
+    "    dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1\n",
+    ")\n",
+    "cost_res"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 33,
+   "id": "40f25e33-a644-4938-8adb-a7bbe5306ee2",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 34,
+   "id": "4f5fad30-991e-45f3-a7e3-1ae04bedd8a1",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>#sk-container-id-2 {\n",
+       "  /* Definition of color scheme common for light and dark mode */\n",
+       "  --sklearn-color-text: black;\n",
+       "  --sklearn-color-line: gray;\n",
+       "  /* Definition of color scheme for unfitted estimators */\n",
+       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
+       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
+       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
+       "  --sklearn-color-unfitted-level-3: chocolate;\n",
+       "  /* Definition of color scheme for fitted estimators */\n",
+       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
+       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
+       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
+       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
+       "\n",
+       "  /* Specific color for light theme */\n",
+       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
+       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-icon: #696969;\n",
+       "\n",
+       "  @media (prefers-color-scheme: dark) {\n",
+       "    /* Redefinition of color scheme for dark theme */\n",
+       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
+       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-icon: #878787;\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 pre {\n",
+       "  padding: 0;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 input.sk-hidden--visually {\n",
+       "  border: 0;\n",
+       "  clip: rect(1px 1px 1px 1px);\n",
+       "  clip: rect(1px, 1px, 1px, 1px);\n",
+       "  height: 1px;\n",
+       "  margin: -1px;\n",
+       "  overflow: hidden;\n",
+       "  padding: 0;\n",
+       "  position: absolute;\n",
+       "  width: 1px;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-dashed-wrapped {\n",
+       "  border: 1px dashed var(--sklearn-color-line);\n",
+       "  margin: 0 0.4em 0.5em 0.4em;\n",
+       "  box-sizing: border-box;\n",
+       "  padding-bottom: 0.4em;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-container {\n",
+       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
+       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
+       "     so we also need the `!important` here to be able to override the\n",
+       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
+       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
+       "  display: inline-block !important;\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-text-repr-fallback {\n",
+       "  display: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-parallel-item,\n",
+       "div.sk-serial,\n",
+       "div.sk-item {\n",
+       "  /* draw centered vertical line to link estimators */\n",
+       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
+       "  background-size: 2px 100%;\n",
+       "  background-repeat: no-repeat;\n",
+       "  background-position: center center;\n",
+       "}\n",
+       "\n",
+       "/* Parallel-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel-item::after {\n",
+       "  content: \"\";\n",
+       "  width: 100%;\n",
+       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
+       "  flex-grow: 1;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel {\n",
+       "  display: flex;\n",
+       "  align-items: stretch;\n",
+       "  justify-content: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel-item {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel-item:first-child::after {\n",
+       "  align-self: flex-end;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel-item:last-child::after {\n",
+       "  align-self: flex-start;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-parallel-item:only-child::after {\n",
+       "  width: 0;\n",
+       "}\n",
+       "\n",
+       "/* Serial-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-serial {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "  align-items: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  padding-right: 1em;\n",
+       "  padding-left: 1em;\n",
+       "}\n",
+       "\n",
+       "\n",
+       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
+       "clickable and can be expanded/collapsed.\n",
+       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
+       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
+       "*/\n",
+       "\n",
+       "/* Pipeline and ColumnTransformer style (default) */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-toggleable {\n",
+       "  /* Default theme specific background. It is overwritten whether we have a\n",
+       "  specific estimator or a Pipeline/ColumnTransformer */\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable label */\n",
+       "#sk-container-id-2 label.sk-toggleable__label {\n",
+       "  cursor: pointer;\n",
+       "  display: block;\n",
+       "  width: 100%;\n",
+       "  margin-bottom: 0;\n",
+       "  padding: 0.5em;\n",
+       "  box-sizing: border-box;\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 label.sk-toggleable__label-arrow:before {\n",
+       "  /* Arrow on the left of the label */\n",
+       "  content: \"▸\";\n",
+       "  float: left;\n",
+       "  margin-right: 0.25em;\n",
+       "  color: var(--sklearn-color-icon);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 label.sk-toggleable__label-arrow:hover:before {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable content - dropdown */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-toggleable__content {\n",
+       "  max-height: 0;\n",
+       "  max-width: 0;\n",
+       "  overflow: hidden;\n",
+       "  text-align: left;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-toggleable__content.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-toggleable__content pre {\n",
+       "  margin: 0.2em;\n",
+       "  border-radius: 0.25em;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-toggleable__content.fitted pre {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
+       "  /* Expand drop-down */\n",
+       "  max-height: 200px;\n",
+       "  max-width: 100%;\n",
+       "  overflow: auto;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
+       "  content: \"▾\";\n",
+       "}\n",
+       "\n",
+       "/* Pipeline/ColumnTransformer-specific style */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific style */\n",
+       "\n",
+       "/* Colorize estimator box */\n",
+       "#sk-container-id-2 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-label label.sk-toggleable__label,\n",
+       "#sk-container-id-2 div.sk-label label {\n",
+       "  /* The background is the default theme color */\n",
+       "  color: var(--sklearn-color-text-on-default-background);\n",
+       "}\n",
+       "\n",
+       "/* On hover, darken the color of the background */\n",
+       "#sk-container-id-2 div.sk-label:hover label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Label box, darken color on hover, fitted */\n",
+       "#sk-container-id-2 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator label */\n",
+       "\n",
+       "#sk-container-id-2 div.sk-label label {\n",
+       "  font-family: monospace;\n",
+       "  font-weight: bold;\n",
+       "  display: inline-block;\n",
+       "  line-height: 1.2em;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-label-container {\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific */\n",
+       "#sk-container-id-2 div.sk-estimator {\n",
+       "  font-family: monospace;\n",
+       "  border: 1px dotted var(--sklearn-color-border-box);\n",
+       "  border-radius: 0.25em;\n",
+       "  box-sizing: border-box;\n",
+       "  margin-bottom: 0.5em;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-estimator.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "/* on hover */\n",
+       "#sk-container-id-2 div.sk-estimator:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 div.sk-estimator.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
+       "\n",
+       "/* Common style for \"i\" and \"?\" */\n",
+       "\n",
+       ".sk-estimator-doc-link,\n",
+       "a:link.sk-estimator-doc-link,\n",
+       "a:visited.sk-estimator-doc-link {\n",
+       "  float: right;\n",
+       "  font-size: smaller;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1em;\n",
+       "  height: 1em;\n",
+       "  width: 1em;\n",
+       "  text-decoration: none !important;\n",
+       "  margin-left: 1ex;\n",
+       "  /* unfitted */\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted,\n",
+       "a:link.sk-estimator-doc-link.fitted,\n",
+       "a:visited.sk-estimator-doc-link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "/* Span, style for the box shown on hovering the info icon */\n",
+       ".sk-estimator-doc-link span {\n",
+       "  display: none;\n",
+       "  z-index: 9999;\n",
+       "  position: relative;\n",
+       "  font-weight: normal;\n",
+       "  right: .2ex;\n",
+       "  padding: .5ex;\n",
+       "  margin: .5ex;\n",
+       "  width: min-content;\n",
+       "  min-width: 20ex;\n",
+       "  max-width: 50ex;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  box-shadow: 2pt 2pt 4pt #999;\n",
+       "  /* unfitted */\n",
+       "  background: var(--sklearn-color-unfitted-level-0);\n",
+       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted span {\n",
+       "  /* fitted */\n",
+       "  background: var(--sklearn-color-fitted-level-0);\n",
+       "  border: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link:hover span {\n",
+       "  display: block;\n",
+       "}\n",
+       "\n",
+       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
+       "\n",
+       "#sk-container-id-2 a.estimator_doc_link {\n",
+       "  float: right;\n",
+       "  font-size: 1rem;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1rem;\n",
+       "  height: 1rem;\n",
+       "  width: 1rem;\n",
+       "  text-decoration: none;\n",
+       "  /* unfitted */\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 a.estimator_doc_link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "#sk-container-id-2 a.estimator_doc_link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-2 a.estimator_doc_link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "</style><div id=\"sk-container-id-2\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>Pipeline(steps=[(&#x27;scaler&#x27;,\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                (&#x27;classifier&#x27;,\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-4\" type=\"checkbox\" ><label for=\"sk-estimator-id-4\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;Pipeline<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.4/modules/generated/sklearn.pipeline.Pipeline.html\">?<span>Documentation for Pipeline</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>Pipeline(steps=[(&#x27;scaler&#x27;,\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                (&#x27;classifier&#x27;,\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])</pre></div> </div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-5\" type=\"checkbox\" ><label for=\"sk-estimator-id-5\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">StandardScaler</label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "               bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                       array([  65.,   23.,  250., 7000.])),\n",
+       "               epsilon=0.5)</pre></div> </div></div><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-6\" type=\"checkbox\" ><label for=\"sk-estimator-id-6\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">LogisticRegression</label><div class=\"sk-toggleable__content fitted\"><pre>LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                   data_norm=83.69469642643347)</pre></div> </div></div></div></div></div></div>"
+      ],
+      "text/plain": [
+       "Pipeline(steps=[('scaler',\n",
+       "                 StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                        array([  65.,   23.,  250., 7000.])),\n",
+       "                                epsilon=0.5)),\n",
+       "                ('classifier',\n",
+       "                 LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                    data_norm=83.69469642643347))])"
+      ]
+     },
+     "execution_count": 34,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model = res['query_response']['model']\n",
+    "model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 35,
+   "id": "96562928-73e8-48b1-b8a5-97245c7da8d2",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(['Adelie', 'Chinstrap'], dtype=object)"
+      ]
+     },
+     "execution_count": 35,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "020ceeb1-7a78-4036-8ece-0afa88e49342",
+   "metadata": {},
+   "source": [
+    "### Decision Tree Classifier"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 36,
+   "id": "22b5c5ae-b008-4980-8767-47e7a1ad3301",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('dtc', models.DecisionTreeClassifier(epsilon = 2.0, bounds=BOUNDS, classes=['Adelie', 'Chinstrap', 'Gentoo'])),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 37,
+   "id": "70b73ba0-67dc-4594-820c-0128916fafc8",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns = ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    "    test_train_split_seed = 1,\n",
+    "    dummy = True,\n",
+    "    nb_rows = 100,\n",
+    "    seed = 42\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 38,
+   "id": "736e8283-604a-4e45-9e98-7da5cc03e1b0",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = ['species'],\n",
+    "    test_size = 0.2,\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 39,
+   "id": "a3719359-58a6-4271-9a34-4b49e0d7188e",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array(['Adelie', 'Chinstrap'], dtype=object)"
+      ]
+     },
+     "execution_count": 39,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "model = res['query_response']['model']\n",
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "4e72e9fc-bb5b-49d9-b4e6-543bdaf93f69",
+   "metadata": {},
+   "source": [
+    "### Regression: Linear Regression"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 40,
+   "id": "907e6f6c-a3da-4f7c-8d85-b0a7b8c018d1",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('lr', models.LinearRegression(epsilon = 2.0, bounds_X=(bl_l, bl_u), bounds_y=(bd_l, bd_u))),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 41,
+   "id": "58011a7d-7c03-4872-a032-5d93c55c1d5f",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>#sk-container-id-3 {\n",
+       "  /* Definition of color scheme common for light and dark mode */\n",
+       "  --sklearn-color-text: black;\n",
+       "  --sklearn-color-line: gray;\n",
+       "  /* Definition of color scheme for unfitted estimators */\n",
+       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
+       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
+       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
+       "  --sklearn-color-unfitted-level-3: chocolate;\n",
+       "  /* Definition of color scheme for fitted estimators */\n",
+       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
+       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
+       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
+       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
+       "\n",
+       "  /* Specific color for light theme */\n",
+       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
+       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-icon: #696969;\n",
+       "\n",
+       "  @media (prefers-color-scheme: dark) {\n",
+       "    /* Redefinition of color scheme for dark theme */\n",
+       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
+       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-icon: #878787;\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 pre {\n",
+       "  padding: 0;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 input.sk-hidden--visually {\n",
+       "  border: 0;\n",
+       "  clip: rect(1px 1px 1px 1px);\n",
+       "  clip: rect(1px, 1px, 1px, 1px);\n",
+       "  height: 1px;\n",
+       "  margin: -1px;\n",
+       "  overflow: hidden;\n",
+       "  padding: 0;\n",
+       "  position: absolute;\n",
+       "  width: 1px;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-dashed-wrapped {\n",
+       "  border: 1px dashed var(--sklearn-color-line);\n",
+       "  margin: 0 0.4em 0.5em 0.4em;\n",
+       "  box-sizing: border-box;\n",
+       "  padding-bottom: 0.4em;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-container {\n",
+       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
+       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
+       "     so we also need the `!important` here to be able to override the\n",
+       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
+       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
+       "  display: inline-block !important;\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-text-repr-fallback {\n",
+       "  display: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-parallel-item,\n",
+       "div.sk-serial,\n",
+       "div.sk-item {\n",
+       "  /* draw centered vertical line to link estimators */\n",
+       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
+       "  background-size: 2px 100%;\n",
+       "  background-repeat: no-repeat;\n",
+       "  background-position: center center;\n",
+       "}\n",
+       "\n",
+       "/* Parallel-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel-item::after {\n",
+       "  content: \"\";\n",
+       "  width: 100%;\n",
+       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
+       "  flex-grow: 1;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel {\n",
+       "  display: flex;\n",
+       "  align-items: stretch;\n",
+       "  justify-content: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel-item {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel-item:first-child::after {\n",
+       "  align-self: flex-end;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel-item:last-child::after {\n",
+       "  align-self: flex-start;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-parallel-item:only-child::after {\n",
+       "  width: 0;\n",
+       "}\n",
+       "\n",
+       "/* Serial-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-serial {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "  align-items: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  padding-right: 1em;\n",
+       "  padding-left: 1em;\n",
+       "}\n",
+       "\n",
+       "\n",
+       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
+       "clickable and can be expanded/collapsed.\n",
+       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
+       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
+       "*/\n",
+       "\n",
+       "/* Pipeline and ColumnTransformer style (default) */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-toggleable {\n",
+       "  /* Default theme specific background. It is overwritten whether we have a\n",
+       "  specific estimator or a Pipeline/ColumnTransformer */\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable label */\n",
+       "#sk-container-id-3 label.sk-toggleable__label {\n",
+       "  cursor: pointer;\n",
+       "  display: block;\n",
+       "  width: 100%;\n",
+       "  margin-bottom: 0;\n",
+       "  padding: 0.5em;\n",
+       "  box-sizing: border-box;\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 label.sk-toggleable__label-arrow:before {\n",
+       "  /* Arrow on the left of the label */\n",
+       "  content: \"▸\";\n",
+       "  float: left;\n",
+       "  margin-right: 0.25em;\n",
+       "  color: var(--sklearn-color-icon);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 label.sk-toggleable__label-arrow:hover:before {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable content - dropdown */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-toggleable__content {\n",
+       "  max-height: 0;\n",
+       "  max-width: 0;\n",
+       "  overflow: hidden;\n",
+       "  text-align: left;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-toggleable__content.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-toggleable__content pre {\n",
+       "  margin: 0.2em;\n",
+       "  border-radius: 0.25em;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-toggleable__content.fitted pre {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
+       "  /* Expand drop-down */\n",
+       "  max-height: 200px;\n",
+       "  max-width: 100%;\n",
+       "  overflow: auto;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
+       "  content: \"▾\";\n",
+       "}\n",
+       "\n",
+       "/* Pipeline/ColumnTransformer-specific style */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific style */\n",
+       "\n",
+       "/* Colorize estimator box */\n",
+       "#sk-container-id-3 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-label label.sk-toggleable__label,\n",
+       "#sk-container-id-3 div.sk-label label {\n",
+       "  /* The background is the default theme color */\n",
+       "  color: var(--sklearn-color-text-on-default-background);\n",
+       "}\n",
+       "\n",
+       "/* On hover, darken the color of the background */\n",
+       "#sk-container-id-3 div.sk-label:hover label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Label box, darken color on hover, fitted */\n",
+       "#sk-container-id-3 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator label */\n",
+       "\n",
+       "#sk-container-id-3 div.sk-label label {\n",
+       "  font-family: monospace;\n",
+       "  font-weight: bold;\n",
+       "  display: inline-block;\n",
+       "  line-height: 1.2em;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-label-container {\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific */\n",
+       "#sk-container-id-3 div.sk-estimator {\n",
+       "  font-family: monospace;\n",
+       "  border: 1px dotted var(--sklearn-color-border-box);\n",
+       "  border-radius: 0.25em;\n",
+       "  box-sizing: border-box;\n",
+       "  margin-bottom: 0.5em;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-estimator.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "/* on hover */\n",
+       "#sk-container-id-3 div.sk-estimator:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 div.sk-estimator.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
+       "\n",
+       "/* Common style for \"i\" and \"?\" */\n",
+       "\n",
+       ".sk-estimator-doc-link,\n",
+       "a:link.sk-estimator-doc-link,\n",
+       "a:visited.sk-estimator-doc-link {\n",
+       "  float: right;\n",
+       "  font-size: smaller;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1em;\n",
+       "  height: 1em;\n",
+       "  width: 1em;\n",
+       "  text-decoration: none !important;\n",
+       "  margin-left: 1ex;\n",
+       "  /* unfitted */\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted,\n",
+       "a:link.sk-estimator-doc-link.fitted,\n",
+       "a:visited.sk-estimator-doc-link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "/* Span, style for the box shown on hovering the info icon */\n",
+       ".sk-estimator-doc-link span {\n",
+       "  display: none;\n",
+       "  z-index: 9999;\n",
+       "  position: relative;\n",
+       "  font-weight: normal;\n",
+       "  right: .2ex;\n",
+       "  padding: .5ex;\n",
+       "  margin: .5ex;\n",
+       "  width: min-content;\n",
+       "  min-width: 20ex;\n",
+       "  max-width: 50ex;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  box-shadow: 2pt 2pt 4pt #999;\n",
+       "  /* unfitted */\n",
+       "  background: var(--sklearn-color-unfitted-level-0);\n",
+       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted span {\n",
+       "  /* fitted */\n",
+       "  background: var(--sklearn-color-fitted-level-0);\n",
+       "  border: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link:hover span {\n",
+       "  display: block;\n",
+       "}\n",
+       "\n",
+       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
+       "\n",
+       "#sk-container-id-3 a.estimator_doc_link {\n",
+       "  float: right;\n",
+       "  font-size: 1rem;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1rem;\n",
+       "  height: 1rem;\n",
+       "  width: 1rem;\n",
+       "  text-decoration: none;\n",
+       "  /* unfitted */\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 a.estimator_doc_link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "#sk-container-id-3 a.estimator_doc_link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-3 a.estimator_doc_link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "</style><div id=\"sk-container-id-3\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>Pipeline(steps=[(&#x27;lr&#x27;,\n",
+       "                 LinearRegression(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                                  bounds_X=(array([30.]), array([65.])),\n",
+       "                                  bounds_y=(array([13.]), array([23.])),\n",
+       "                                  epsilon=2.0))])</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-7\" type=\"checkbox\" ><label for=\"sk-estimator-id-7\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;Pipeline<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.4/modules/generated/sklearn.pipeline.Pipeline.html\">?<span>Documentation for Pipeline</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>Pipeline(steps=[(&#x27;lr&#x27;,\n",
+       "                 LinearRegression(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                                  bounds_X=(array([30.]), array([65.])),\n",
+       "                                  bounds_y=(array([13.]), array([23.])),\n",
+       "                                  epsilon=2.0))])</pre></div> </div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-8\" type=\"checkbox\" ><label for=\"sk-estimator-id-8\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">LinearRegression</label><div class=\"sk-toggleable__content fitted\"><pre>LinearRegression(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                 bounds_X=(array([30.]), array([65.])),\n",
+       "                 bounds_y=(array([13.]), array([23.])), epsilon=2.0)</pre></div> </div></div></div></div></div></div>"
+      ],
+      "text/plain": [
+       "Pipeline(steps=[('lr',\n",
+       "                 LinearRegression(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                                  bounds_X=(array([30.]), array([65.])),\n",
+       "                                  bounds_y=(array([13.]), array([23.])),\n",
+       "                                  epsilon=2.0))])"
+      ]
+     },
+     "execution_count": 41,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm'],\n",
+    "    target_columns = ['bill_depth_mm'],\n",
+    "    dummy = True\n",
+    ")\n",
+    "model = dummy_response['query_response']['model']\n",
+    "model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 42,
+   "id": "e28b1bd0-0e73-49db-a2d3-e7f06dc39d49",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([[18.84279457],\n",
+       "       [18.24687217]])"
+      ]
+     },
+     "execution_count": 42,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# Dummy model predictions\n",
+    "x_to_predict = pd.DataFrame({'bill_length_mm': [bl_l, bl_u]})\n",
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "ec9e921d-1640-4379-84e5-65b93a1ff203",
+   "metadata": {},
+   "source": [
+    "### Clustering: K-Means"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 54,
+   "id": "1e830a01-7800-4701-aef2-11dbfc517f61",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('kmeans', models.KMeans(n_clusters = 8, epsilon = 2.0, bounds=BOUNDS)),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 55,
+   "id": "cb5e3e1f-2d48-4d5e-992a-c0e0cb6add3b",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/html": [
+       "<style>#sk-container-id-5 {\n",
+       "  /* Definition of color scheme common for light and dark mode */\n",
+       "  --sklearn-color-text: black;\n",
+       "  --sklearn-color-line: gray;\n",
+       "  /* Definition of color scheme for unfitted estimators */\n",
+       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
+       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
+       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
+       "  --sklearn-color-unfitted-level-3: chocolate;\n",
+       "  /* Definition of color scheme for fitted estimators */\n",
+       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
+       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
+       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
+       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
+       "\n",
+       "  /* Specific color for light theme */\n",
+       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
+       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
+       "  --sklearn-color-icon: #696969;\n",
+       "\n",
+       "  @media (prefers-color-scheme: dark) {\n",
+       "    /* Redefinition of color scheme for dark theme */\n",
+       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
+       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
+       "    --sklearn-color-icon: #878787;\n",
+       "  }\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 pre {\n",
+       "  padding: 0;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 input.sk-hidden--visually {\n",
+       "  border: 0;\n",
+       "  clip: rect(1px 1px 1px 1px);\n",
+       "  clip: rect(1px, 1px, 1px, 1px);\n",
+       "  height: 1px;\n",
+       "  margin: -1px;\n",
+       "  overflow: hidden;\n",
+       "  padding: 0;\n",
+       "  position: absolute;\n",
+       "  width: 1px;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-dashed-wrapped {\n",
+       "  border: 1px dashed var(--sklearn-color-line);\n",
+       "  margin: 0 0.4em 0.5em 0.4em;\n",
+       "  box-sizing: border-box;\n",
+       "  padding-bottom: 0.4em;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-container {\n",
+       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
+       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
+       "     so we also need the `!important` here to be able to override the\n",
+       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
+       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
+       "  display: inline-block !important;\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-text-repr-fallback {\n",
+       "  display: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-parallel-item,\n",
+       "div.sk-serial,\n",
+       "div.sk-item {\n",
+       "  /* draw centered vertical line to link estimators */\n",
+       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
+       "  background-size: 2px 100%;\n",
+       "  background-repeat: no-repeat;\n",
+       "  background-position: center center;\n",
+       "}\n",
+       "\n",
+       "/* Parallel-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel-item::after {\n",
+       "  content: \"\";\n",
+       "  width: 100%;\n",
+       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
+       "  flex-grow: 1;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel {\n",
+       "  display: flex;\n",
+       "  align-items: stretch;\n",
+       "  justify-content: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  position: relative;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel-item {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel-item:first-child::after {\n",
+       "  align-self: flex-end;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel-item:last-child::after {\n",
+       "  align-self: flex-start;\n",
+       "  width: 50%;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-parallel-item:only-child::after {\n",
+       "  width: 0;\n",
+       "}\n",
+       "\n",
+       "/* Serial-specific style estimator block */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-serial {\n",
+       "  display: flex;\n",
+       "  flex-direction: column;\n",
+       "  align-items: center;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  padding-right: 1em;\n",
+       "  padding-left: 1em;\n",
+       "}\n",
+       "\n",
+       "\n",
+       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
+       "clickable and can be expanded/collapsed.\n",
+       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
+       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
+       "*/\n",
+       "\n",
+       "/* Pipeline and ColumnTransformer style (default) */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-toggleable {\n",
+       "  /* Default theme specific background. It is overwritten whether we have a\n",
+       "  specific estimator or a Pipeline/ColumnTransformer */\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable label */\n",
+       "#sk-container-id-5 label.sk-toggleable__label {\n",
+       "  cursor: pointer;\n",
+       "  display: block;\n",
+       "  width: 100%;\n",
+       "  margin-bottom: 0;\n",
+       "  padding: 0.5em;\n",
+       "  box-sizing: border-box;\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 label.sk-toggleable__label-arrow:before {\n",
+       "  /* Arrow on the left of the label */\n",
+       "  content: \"▸\";\n",
+       "  float: left;\n",
+       "  margin-right: 0.25em;\n",
+       "  color: var(--sklearn-color-icon);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 label.sk-toggleable__label-arrow:hover:before {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "}\n",
+       "\n",
+       "/* Toggleable content - dropdown */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-toggleable__content {\n",
+       "  max-height: 0;\n",
+       "  max-width: 0;\n",
+       "  overflow: hidden;\n",
+       "  text-align: left;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-toggleable__content.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-toggleable__content pre {\n",
+       "  margin: 0.2em;\n",
+       "  border-radius: 0.25em;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-toggleable__content.fitted pre {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
+       "  /* Expand drop-down */\n",
+       "  max-height: 200px;\n",
+       "  max-width: 100%;\n",
+       "  overflow: auto;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
+       "  content: \"▾\";\n",
+       "}\n",
+       "\n",
+       "/* Pipeline/ColumnTransformer-specific style */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific style */\n",
+       "\n",
+       "/* Colorize estimator box */\n",
+       "#sk-container-id-5 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-label label.sk-toggleable__label,\n",
+       "#sk-container-id-5 div.sk-label label {\n",
+       "  /* The background is the default theme color */\n",
+       "  color: var(--sklearn-color-text-on-default-background);\n",
+       "}\n",
+       "\n",
+       "/* On hover, darken the color of the background */\n",
+       "#sk-container-id-5 div.sk-label:hover label.sk-toggleable__label {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Label box, darken color on hover, fitted */\n",
+       "#sk-container-id-5 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Estimator label */\n",
+       "\n",
+       "#sk-container-id-5 div.sk-label label {\n",
+       "  font-family: monospace;\n",
+       "  font-weight: bold;\n",
+       "  display: inline-block;\n",
+       "  line-height: 1.2em;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-label-container {\n",
+       "  text-align: center;\n",
+       "}\n",
+       "\n",
+       "/* Estimator-specific */\n",
+       "#sk-container-id-5 div.sk-estimator {\n",
+       "  font-family: monospace;\n",
+       "  border: 1px dotted var(--sklearn-color-border-box);\n",
+       "  border-radius: 0.25em;\n",
+       "  box-sizing: border-box;\n",
+       "  margin-bottom: 0.5em;\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-estimator.fitted {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-0);\n",
+       "}\n",
+       "\n",
+       "/* on hover */\n",
+       "#sk-container-id-5 div.sk-estimator:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 div.sk-estimator.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-2);\n",
+       "}\n",
+       "\n",
+       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
+       "\n",
+       "/* Common style for \"i\" and \"?\" */\n",
+       "\n",
+       ".sk-estimator-doc-link,\n",
+       "a:link.sk-estimator-doc-link,\n",
+       "a:visited.sk-estimator-doc-link {\n",
+       "  float: right;\n",
+       "  font-size: smaller;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1em;\n",
+       "  height: 1em;\n",
+       "  width: 1em;\n",
+       "  text-decoration: none !important;\n",
+       "  margin-left: 1ex;\n",
+       "  /* unfitted */\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted,\n",
+       "a:link.sk-estimator-doc-link.fitted,\n",
+       "a:visited.sk-estimator-doc-link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
+       ".sk-estimator-doc-link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover,\n",
+       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
+       ".sk-estimator-doc-link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "/* Span, style for the box shown on hovering the info icon */\n",
+       ".sk-estimator-doc-link span {\n",
+       "  display: none;\n",
+       "  z-index: 9999;\n",
+       "  position: relative;\n",
+       "  font-weight: normal;\n",
+       "  right: .2ex;\n",
+       "  padding: .5ex;\n",
+       "  margin: .5ex;\n",
+       "  width: min-content;\n",
+       "  min-width: 20ex;\n",
+       "  max-width: 50ex;\n",
+       "  color: var(--sklearn-color-text);\n",
+       "  box-shadow: 2pt 2pt 4pt #999;\n",
+       "  /* unfitted */\n",
+       "  background: var(--sklearn-color-unfitted-level-0);\n",
+       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link.fitted span {\n",
+       "  /* fitted */\n",
+       "  background: var(--sklearn-color-fitted-level-0);\n",
+       "  border: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "\n",
+       ".sk-estimator-doc-link:hover span {\n",
+       "  display: block;\n",
+       "}\n",
+       "\n",
+       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
+       "\n",
+       "#sk-container-id-5 a.estimator_doc_link {\n",
+       "  float: right;\n",
+       "  font-size: 1rem;\n",
+       "  line-height: 1em;\n",
+       "  font-family: monospace;\n",
+       "  background-color: var(--sklearn-color-background);\n",
+       "  border-radius: 1rem;\n",
+       "  height: 1rem;\n",
+       "  width: 1rem;\n",
+       "  text-decoration: none;\n",
+       "  /* unfitted */\n",
+       "  color: var(--sklearn-color-unfitted-level-1);\n",
+       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 a.estimator_doc_link.fitted {\n",
+       "  /* fitted */\n",
+       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
+       "  color: var(--sklearn-color-fitted-level-1);\n",
+       "}\n",
+       "\n",
+       "/* On hover */\n",
+       "#sk-container-id-5 a.estimator_doc_link:hover {\n",
+       "  /* unfitted */\n",
+       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
+       "  color: var(--sklearn-color-background);\n",
+       "  text-decoration: none;\n",
+       "}\n",
+       "\n",
+       "#sk-container-id-5 a.estimator_doc_link.fitted:hover {\n",
+       "  /* fitted */\n",
+       "  background-color: var(--sklearn-color-fitted-level-3);\n",
+       "}\n",
+       "</style><div id=\"sk-container-id-5\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>Pipeline(steps=[(&#x27;kmeans&#x27;,\n",
+       "                 KMeans(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                        bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                array([  65.,   23.,  250., 7000.])),\n",
+       "                        epsilon=2.0))])</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item sk-dashed-wrapped\"><div class=\"sk-label-container\"><div class=\"sk-label fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-11\" type=\"checkbox\" ><label for=\"sk-estimator-id-11\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">&nbsp;&nbsp;Pipeline<a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.4/modules/generated/sklearn.pipeline.Pipeline.html\">?<span>Documentation for Pipeline</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></label><div class=\"sk-toggleable__content fitted\"><pre>Pipeline(steps=[(&#x27;kmeans&#x27;,\n",
+       "                 KMeans(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                        bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                array([  65.,   23.,  250., 7000.])),\n",
+       "                        epsilon=2.0))])</pre></div> </div></div><div class=\"sk-serial\"><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-12\" type=\"checkbox\" ><label for=\"sk-estimator-id-12\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow fitted\">KMeans</label><div class=\"sk-toggleable__content fitted\"><pre>KMeans(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "       bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "               array([  65.,   23.,  250., 7000.])),\n",
+       "       epsilon=2.0)</pre></div> </div></div></div></div></div></div>"
+      ],
+      "text/plain": [
+       "Pipeline(steps=[('kmeans',\n",
+       "                 KMeans(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                        bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                array([  65.,   23.,  250., 7000.])),\n",
+       "                        epsilon=2.0))])"
+      ]
+     },
+     "execution_count": 55,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    dummy = True\n",
+    ")\n",
+    "model = dummy_response['query_response']['model']\n",
+    "model"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 56,
+   "id": "a0612b10-de7a-4355-8268-09a46c30056a",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "array([1, 2], dtype=int32)"
+      ]
+     },
+     "execution_count": 56,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "# Dummy model predictions\n",
+    "x_to_predict = pd.DataFrame({\n",
+    "    'bill_length_mm': [bl_l, bl_u], \n",
+    "    'bill_depth_mm': [bd_l, bd_u] , \n",
+    "    'flipper_length_mm': [fl_l, fl_u], \n",
+    "    'body_mass_g': [bm_l, bm_u]\n",
+    "})\n",
+    "predictions = model.predict(x_to_predict)\n",
+    "predictions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "6021b034-b15d-4826-8de3-80b99afc838d",
+   "metadata": {},
+   "source": [
+    "### Dimensionality Reduction: PCA"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 57,
+   "id": "f9a4cbaf-b994-41c0-a0e9-81674c6657fb",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dpl_pipeline = Pipeline([\n",
+    "    ('pca', models.PCA(n_components=None, epsilon = 2.0, bounds=BOUNDS, data_norm=100, centered=False)),\n",
+    "])"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 58,
+   "id": "4681cc17-6285-4b9b-a26d-837d6a79e1c3",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dummy_response = client.diffprivlib_query(\n",
+    "    pipeline = dpl_pipeline,\n",
+    "    feature_columns =  ['bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g'],\n",
+    "    target_columns = None, # TODO enable empty list\n",
+    "    dummy = True\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 59,
+   "id": "b1ac9784-bd0e-41c6-bb54-8201d9ab13ad",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'query_response': {'score': -3532.5573958633877,\n",
+       "  'model': Pipeline(steps=[('pca',\n",
+       "                   PCA(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                       bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                               array([  65.,   23.,  250., 7000.])),\n",
+       "                       data_norm=100, epsilon=2.0))])}}"
+      ]
+     },
+     "execution_count": 59,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "dummy_response"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "94eaf59b-c108-424c-8978-b1c86e141ccb",
+   "metadata": {},
+   "source": [
+    "## Step 6: See archives of queries"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "64003c53-de56-4bdc-a3c2-0c3e40031919",
+   "metadata": {},
+   "source": [
+    "She now wants to verify all the queries that she did on the real data. It is possible because an archive of all queries is kept in a secure database. With a function call she can see her queries, budget and associated responses."
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 93,
+   "id": "008fd230-cdfd-4e03-91ce-5a60b06c106d",
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "previous_queries = client.get_previous_queries()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "f2a34bc3-d1a5-4124-983f-ddc09dd1af7b",
+   "metadata": {},
+   "source": [
+    "Let's check the first query, which was with smartnoise-sql:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 94,
+   "id": "1795a54b-d04e-4687-8649-93982c84ad30",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'user_name': 'Dr. Antartica',\n",
+       " 'dataset_name': 'PENGUIN',\n",
+       " 'dp_librairy': 'diffprivlib',\n",
+       " 'client_input': {'dataset_name': 'PENGUIN',\n",
+       "  'diffprivlib_json': '{\"module\": \"diffprivlib\", \"version\": \"0.6.4\", \"pipeline\": [{\"type\": \"_dpl_type:StandardScaler\", \"name\": \"scaler\", \"params\": {\"with_mean\": true, \"with_std\": true, \"copy\": true, \"epsilon\": 0.5, \"bounds\": {\"_tuple\": true, \"_items\": [[30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0]]}, \"random_state\": null, \"accountant\": \"_dpl_instance:BudgetAccountant\"}}, {\"type\": \"_dpl_type:LogisticRegression\", \"name\": \"classifier\", \"params\": {\"tol\": 0.0001, \"C\": 1.0, \"fit_intercept\": true, \"random_state\": null, \"max_iter\": 100, \"verbose\": 0, \"warm_start\": false, \"n_jobs\": null, \"epsilon\": 1.0, \"data_norm\": 83.69469642643347, \"accountant\": \"_dpl_instance:BudgetAccountant\"}}]}',\n",
+       "  'feature_columns': ['bill_length_mm',\n",
+       "   'bill_depth_mm',\n",
+       "   'flipper_length_mm',\n",
+       "   'body_mass_g'],\n",
+       "  'target_columns': ['species'],\n",
+       "  'test_size': 0.2,\n",
+       "  'test_train_split_seed': 1,\n",
+       "  'imputer_strategy': 'drop'},\n",
+       " 'response': {'requested_by': 'Dr. Antartica',\n",
+       "  'query_response': {'score': 0.3880597014925373,\n",
+       "   'model': Pipeline(steps=[('scaler',\n",
+       "                    StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                   bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                           array([  65.,   23.,  250., 7000.])),\n",
+       "                                   epsilon=0.5)),\n",
+       "                   ('classifier',\n",
+       "                    LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                       data_norm=83.69469642643347))])},\n",
+       "  'spent_epsilon': 1.5,\n",
+       "  'spent_delta': 0.0},\n",
+       " 'timestamp': 1719828430.3111608}"
+      ]
+     },
+     "execution_count": 94,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "query_1 = previous_queries[0]\n",
+    "query_1"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "7ba3baec-bbf9-49fa-a211-57d88cbc0e3a",
+   "metadata": {},
+   "source": [
+    "The second with opendp:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 95,
+   "id": "ef251e47-67d8-426b-9655-c16d32778579",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'user_name': 'Dr. Antartica',\n",
+       " 'dataset_name': 'PENGUIN',\n",
+       " 'dp_librairy': 'diffprivlib',\n",
+       " 'client_input': {'dataset_name': 'PENGUIN',\n",
+       "  'diffprivlib_json': '{\"module\": \"diffprivlib\", \"version\": \"0.6.4\", \"pipeline\": [{\"type\": \"_dpl_type:StandardScaler\", \"name\": \"scaler\", \"params\": {\"with_mean\": true, \"with_std\": true, \"copy\": true, \"epsilon\": 0.5, \"bounds\": {\"_tuple\": true, \"_items\": [[30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0]]}, \"random_state\": null, \"accountant\": \"_dpl_instance:BudgetAccountant\"}}, {\"type\": \"_dpl_type:LogisticRegression\", \"name\": \"classifier\", \"params\": {\"tol\": 0.0001, \"C\": 1.0, \"fit_intercept\": true, \"random_state\": null, \"max_iter\": 100, \"verbose\": 0, \"warm_start\": false, \"n_jobs\": null, \"epsilon\": 1.0, \"data_norm\": 83.69469642643347, \"accountant\": \"_dpl_instance:BudgetAccountant\"}}]}',\n",
+       "  'feature_columns': ['bill_length_mm',\n",
+       "   'bill_depth_mm',\n",
+       "   'flipper_length_mm',\n",
+       "   'body_mass_g'],\n",
+       "  'target_columns': ['species'],\n",
+       "  'test_size': 0.2,\n",
+       "  'test_train_split_seed': 1,\n",
+       "  'imputer_strategy': 'drop'},\n",
+       " 'response': {'requested_by': 'Dr. Antartica',\n",
+       "  'query_response': {'score': 0.3880597014925373,\n",
+       "   'model': Pipeline(steps=[('scaler',\n",
+       "                    StandardScaler(accountant=BudgetAccountant(spent_budget=[(0.5, 0)]),\n",
+       "                                   bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                           array([  65.,   23.,  250., 7000.])),\n",
+       "                                   epsilon=0.5)),\n",
+       "                   ('classifier',\n",
+       "                    LogisticRegression(accountant=BudgetAccountant(spent_budget=[(1.0, 0)]),\n",
+       "                                       data_norm=83.69469642643347))])},\n",
+       "  'spent_epsilon': 1.5,\n",
+       "  'spent_delta': 0.0},\n",
+       " 'timestamp': 1719828430.3111608}"
+      ]
+     },
+     "execution_count": 95,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "query_2 = previous_queries[1]\n",
+    "query_1"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "id": "fe6332c1-932c-4912-a06d-4e66f5dada65",
+   "metadata": {},
+   "source": [
+    "And the last one with DiffPrivLib:"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 96,
+   "id": "b2fa8943-f0e0-4902-9001-bd10fb22a653",
+   "metadata": {},
+   "outputs": [
+    {
+     "data": {
+      "text/plain": [
+       "{'user_name': 'Dr. Antartica',\n",
+       " 'dataset_name': 'PENGUIN',\n",
+       " 'dp_librairy': 'diffprivlib',\n",
+       " 'client_input': {'dataset_name': 'PENGUIN',\n",
+       "  'diffprivlib_json': '{\"module\": \"diffprivlib\", \"version\": \"0.6.4\", \"pipeline\": [{\"type\": \"_dpl_type:RandomForestClassifier\", \"name\": \"rf\", \"params\": {\"n_estimators\": 10, \"n_jobs\": 1, \"random_state\": null, \"verbose\": 0, \"warm_start\": false, \"max_depth\": 5, \"epsilon\": 2.0, \"bounds\": {\"_tuple\": true, \"_items\": [[30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0]]}, \"classes\": [\"Adelie\", \"Chinstrap\", \"Gentoo\"], \"shuffle\": false, \"accountant\": \"_dpl_instance:BudgetAccountant\"}}]}',\n",
+       "  'feature_columns': ['bill_length_mm',\n",
+       "   'bill_depth_mm',\n",
+       "   'flipper_length_mm',\n",
+       "   'body_mass_g'],\n",
+       "  'target_columns': ['species'],\n",
+       "  'test_size': 0.2,\n",
+       "  'test_train_split_seed': 1,\n",
+       "  'imputer_strategy': 'drop'},\n",
+       " 'response': {'requested_by': 'Dr. Antartica',\n",
+       "  'query_response': {'score': 0.6268656716417911,\n",
+       "   'model': Pipeline(steps=[('rf',\n",
+       "                    RandomForestClassifier(accountant=BudgetAccountant(spent_budget=[(2.0, 0)]),\n",
+       "                                           bounds=(array([  30.,   13.,  150., 2000.]),\n",
+       "                                                   array([  65.,   23.,  250., 7000.])),\n",
+       "                                           classes=['Adelie', 'Chinstrap',\n",
+       "                                                    'Gentoo'],\n",
+       "                                           epsilon=2.0))])},\n",
+       "  'spent_epsilon': 2.0,\n",
+       "  'spent_delta': 0.0},\n",
+       " 'timestamp': 1719829029.0730438}"
+      ]
+     },
+     "execution_count": 96,
+     "metadata": {},
+     "output_type": "execute_result"
+    }
+   ],
+   "source": [
+    "query_3 = previous_queries[-1]\n",
+    "query_3"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "e2c8d40d-94b3-4d69-af99-0ec2936f233e",
+   "metadata": {},
+   "outputs": [],
+   "source": []
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.11.9"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/client/pyproject.toml b/client/pyproject.toml
index 08ff32f8..7cfecd1a 100644
--- a/client/pyproject.toml
+++ b/client/pyproject.toml
@@ -1,4 +1,28 @@
 [tool.black]
 line-length = 79
 include = '\.pyi?$'
-reportMissingImports = false
+
+[tool.isort]
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+line_length = 79
+split_on_trailing_comma = true
+src_paths = ["lomas_client"]
+
+[tool.mypy]
+disable_error_code = ["import-untyped", "import-not-found", "attr-defined"]
+
+[tool.pylint.MASTER]
+ignore-patterns = ""
+
+[tool.pylint."MESSAGES CONTROL"]
+disable = [
+    "E0401",  # import-error
+    "C0114",  # missing-module-docstring
+    "W0102",  # dangerous-default-value
+    "R0913",  # too-many-arguments
+    "R0914"   # too-many-locals
+]
\ No newline at end of file
diff --git a/client/requirements.txt b/client/requirements.txt
index c5c3d5f7..06b352e6 100644
--- a/client/requirements.txt
+++ b/client/requirements.txt
@@ -1,8 +1,9 @@
-#diffprivlib==0.6.0
+diffprivlib==0.6.4
+diffprivlib_logger==0.0.3
 jupyter==1.0.0
-numpy==1.26.4
+numpy==1.23.2
 opendp==0.8.0
 opendp_logger==0.3.0
 pandas==2.2.2
 requests==2.32.0
-#scikit-learn>=1.1.2
\ No newline at end of file
+scikit-learn==1.4.0
diff --git a/client/setup.py b/client/setup.py
index 08f78856..4d003bce 100644
--- a/client/setup.py
+++ b/client/setup.py
@@ -10,7 +10,7 @@
 setup(
     name="lomas_client",
     packages=find_packages(),
-    version="0.1.2",
+    version="0.2.0",
     description="A client to interact with the Lomas server.",
     long_description=long_description,
     long_description_content_type="text/markdown",
@@ -19,19 +19,35 @@
     author_email="dscc@bfs.admin.ch",
     license="MIT",
     classifiers=[
-        "Development Status :: 3 - Alpha",
+        "Development Status :: 4 - Beta",
         "Intended Audience :: Developers",
+        "Intended Audience :: Science/Research",
         "License :: OSI Approved :: MIT License",
-        "Programming Language :: Python :: 3.8",
-        "Programming Language :: Python :: 3.9",
+        "Natural Language :: English",
+        "Programming Language :: Python :: 3.11",
+        "Topic :: Software Development :: Libraries",
+        "Topic :: Software Development :: Libraries :: Python Modules",
+        "Topic :: Scientific/Engineering",
+        "Topic :: Security",
     ],
-    keywords=["opendp", "smartnoise-sql", "logger", "ast"],
-    python_requires=">=3.8, <4",
+    keywords=[
+        "differential privacy",
+        "DP",
+        "diffprivlib",
+        "logger",
+        "opendp",
+        "privacy",
+        "smartnoise-sql",
+    ],
+    python_requires=">=3.10, <4",
     install_requires=[
+        "diffprivlib>=0.6.4",
+        "diffprivlib_logger>=0.0.3",
+        "numpy>=1.23.2",
         "opendp==0.8.0",
         "opendp_logger==0.3.0",
-        "numpy==1.26.4",
-        "requests==2.32.0",
-        "pandas==2.2.2",
+        "pandas>=2.2.2",
+        "requests>=2.32.0",
+        "scikit-learn==1.4.0",
     ],
 )
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 0f2e93a3..d18c78d1 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -5,26 +5,25 @@
 
 Lomas: The Data Oases Hidden Behind the Mist.
 ========================================
-Lomas is a platform for remote data science, enabling sensitive data to be queried remotely 
-while staying protected by a layer of differential privacy.
+Lomas is a platform for remote data science, enabling sensitive data to be queried remotely while staying protected by a layer of differential privacy.
 
-The lomas platform follows a classic server/client model. On the client side, the 
-user prepares queries for statistical analyses which are sent to the 
-service's REST API via HTTP. The user never has direct access to the sensitive 
-data. On the server side, the service is implemented in a micro-service architecture and is 
-thus split into two parts: the administration database and the client-facing HTTP server 
-(which we call server for brevity) that implements the service logic. The server 
-is responsible for processing the client requests and updating its own state as 
-well as administrative data (users data, budgets, query archives, etc.) in 
-the administration database.
+#### Technical Overview:
 
-The service is not responsible for storing and managing private datasets, 
-these are usually already stored on the provider's infrastructure. 
+The lomas platform follows a classic server/client model.
+On the client side, the user prepares queries for statistical analyses which are sent to the service's REST API via HTTP. The user never has direct access to the sensitive data.
+On the server side, the service is implemented in a micro-service architecture and is thus split into two parts: the administration database and the client-facing HTTP server (which we call server for brevity) that implements the service logic.
+The server is responsible for processing the client requests and updating its own state as well as administrative data (users data, budgets, query archives, etc.) in the administration database.
 
-.. See our white paper (TODO link) for detailed explanation of the platform.
+The service is not responsible for storing and managing private datasets, these are usually already stored on the provider's infrastructure.
+
+#### Detailed description:
+
+For a detailed description, please see the links below.
+
+* **Lomas Project White Paper**: https://arxiv.org/abs/2406.17087
+* **Swiss Federal Statistical Office Blog**: https://www.bfs.admin.ch/bfs/en/home/dscc/blog/2024-03-lomas.html
+* **GitHub repository**: https://github.com/dscc-admin-ch/lomas/tree/master/
 
-You can find our `GitHub repository <https://github.com/dscc-admin-ch/lomas/tree/master/>`_
-following this link.
 
 Client
 ========
@@ -70,6 +69,13 @@ MongoDB database) and a client development environment in a Kubernetes cluster.
 For extensive informations about how to deploy, please refer to :doc:`Deployment <server_deployment>`
 documentation.
 
+## Disclaimer
+Lomas is a Proof of Concept that is still under development. 
+
+The overall infrastructure security is not our current priority.  While attention has been given to the 'logical' aspects within the server, many security aspects are not handled. For example, user authentication is not implemented. However, Lomas can be integrated into other secure infrastructures.
+
+We welcome any feedback or suggestions for future improvements. External input is valuable as we continue to enhance the security and functionality of Lomas. Please open a bug report or issue here: https://github.com/dscc-admin-ch/lomas/issues.open.
+
 History
 ========
 The starting point of our platform was the code shared to us by `Oblivious <https://www.oblivious.com/>`_.
diff --git a/docs/versions.yaml b/docs/versions.yaml
index 8a1588ef..dfe0b516 100644
--- a/docs/versions.yaml
+++ b/docs/versions.yaml
@@ -16,5 +16,9 @@
     - "en"
 "v0.1.2":
    tag: "v0.1.2"
+   languages:
+    - "en"
+"v0.2.0":
+   tag: "v0.2.0"
    languages:
     - "en"
\ No newline at end of file
diff --git a/run_linter.sh b/run_linter.sh
new file mode 100755
index 00000000..16535cdb
--- /dev/null
+++ b/run_linter.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+install_dependencies() {
+    pip install isort==5.13.2
+    pip install black==24.4.2
+    pip install flake8==7.1.0
+    pip install mypy==1.10.0
+    pip install pylint==3.1.0
+}
+
+# Parse command line arguments for --install-deps flag
+INSTALL_DEPS=false
+for arg in "$@"; do
+    if [ "$arg" == "--install-deps" ]; then
+        INSTALL_DEPS=true
+        break
+    fi
+done
+
+# Install dependencies if flag is set
+if [ "$INSTALL_DEPS" == true ]; then
+    install_dependencies
+fi
+
+
+cd server/lomas_server
+isort .
+black .
+flake8 .
+pylint .
+
+cd ..
+mypy .
+
+cd ../client/lomas_client
+isort .
+black .
+flake8 .
+pylint .
+
+cd ..
+mypy .
diff --git a/server/README.md b/server/README.md
index 76ac08dd..55b34f39 100644
--- a/server/README.md
+++ b/server/README.md
@@ -1,1 +1,9 @@
+[![Tests](https://github.com/dscc-admin-ch/lomas/actions/workflows/test_and_coverage_server.yml/badge.svg)](https://github.com/dscc-admin-ch/lomas/actions/workflows/test_and_coverage_server.yml)
+[![Coverage badge](https://raw.githubusercontent.com/dscc-admin-ch/lomas/python-coverage-comment-action-data/badge.svg)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/dscc-admin-ch/lomas/python-coverage-comment-action-data/htmlcov/index.html)
+
+<h1 align="center">
+<img src="https://github.com/dscc-admin-ch/lomas/blob/develop/images/lomas_logo_txt.png?raw=true" width="300">
+</h1><br>
+
 # Server
+See the technical documentation of the server: https://dscc-admin-ch.github.io/lomas-docs/server_api.html.
diff --git a/server/deploy/helm/charts/lomas_server/Chart.yaml b/server/deploy/helm/charts/lomas_server/Chart.yaml
index b1ecea26..d471b085 100644
--- a/server/deploy/helm/charts/lomas_server/Chart.yaml
+++ b/server/deploy/helm/charts/lomas_server/Chart.yaml
@@ -4,9 +4,9 @@ description: Lomas deployment chart
 
 type: application
 
-version: 0.1.2
+version: 0.2.0
 
-appVersion: "0.1.2"
+appVersion: "0.2.0"
 
 dependencies:
   - name: mongodb
diff --git a/server/lomas_server/admin_database/admin_database.py b/server/lomas_server/admin_database/admin_database.py
index ae0c3198..0c983fe2 100644
--- a/server/lomas_server/admin_database/admin_database.py
+++ b/server/lomas_server/admin_database/admin_database.py
@@ -1,12 +1,11 @@
 import argparse
-import functools
 import time
 from abc import ABC, abstractmethod
+from functools import wraps
 from typing import Callable, Dict, List
 
-from constants import DPLibraries
+from constants import MODEL_INPUT_TO_LIB
 from utils.error_handler import (
-    InternalServerException,
     InvalidQueryException,
     UnauthorizedAccessException,
 )
@@ -29,7 +28,7 @@ def user_must_exist(func: Callable) -> Callable:  # type: ignore
             before calling func.
     """
 
-    @functools.wraps(func)
+    @wraps(func)
     def wrapper_decorator(
         self, *args: argparse.Namespace, **kwargs: Dict[str, str]
     ) -> None:
@@ -61,14 +60,14 @@ def dataset_must_exist(func: Callable) -> Callable:  # type: ignore
             before calling the wrapped function.
     """
 
-    @functools.wraps(func)
+    @wraps(func)
     def wrapper_decorator(
         self, *args: argparse.Namespace, **kwargs: Dict[str, str]
     ) -> None:
         dataset_name = args[0]
         if not self.does_dataset_exist(dataset_name):
             raise InvalidQueryException(
-                f"Dataset {dataset_name} does not exists. "
+                f"Dataset {dataset_name} does not exist. "
                 + "Please, verify the client object initialisation.",
             )
         return func(self, *args, **kwargs)
@@ -97,7 +96,7 @@ def user_must_have_access_to_dataset(
             to the dataset before calling the wrapped function.
     """
 
-    @functools.wraps(func)
+    @wraps(func)
     def wrapper_decorator(
         self, *args: argparse.Namespace, **kwargs: Dict[str, str]
     ) -> None:
@@ -442,22 +441,16 @@ def prepare_save_query(
         Returns:
             dict: The query archive dictionary.
         """
+        model_input = query_json.__class__.__name__
         to_archive = {
             "user_name": user_name,
             "dataset_name": query_json.dataset_name,
+            "dp_librairy": MODEL_INPUT_TO_LIB[model_input],
             "client_input": query_json.model_dump(),
             "response": response,
             "timestamp": time.time(),
         }
-        match query_json.__class__.__name__:
-            case "SNSQLInp":
-                to_archive["dp_librairy"] = DPLibraries.SMARTNOISE_SQL
-            case "OpenDPInp":
-                to_archive["dp_librairy"] = DPLibraries.OPENDP
-            case _:
-                raise InternalServerException(
-                    f"Unknown query input: {query_json.__class__.__name__}"
-                )
+
         return to_archive
 
     @abstractmethod
diff --git a/server/lomas_server/admin_database/mongodb_database.py b/server/lomas_server/admin_database/mongodb_database.py
index 34634867..0f8509f3 100644
--- a/server/lomas_server/admin_database/mongodb_database.py
+++ b/server/lomas_server/admin_database/mongodb_database.py
@@ -11,6 +11,7 @@
     user_must_exist,
     user_must_have_access_to_dataset,
 )
+from utils.error_handler import InvalidQueryException
 
 
 class AdminMongoDatabase(AdminDatabase):
@@ -134,6 +135,11 @@ def has_user_access_to_dataset(
         Returns:
             bool: True if the user has access, False otherwise.
         """
+        if not self.does_dataset_exist(dataset_name):
+            raise InvalidQueryException(
+                f"Dataset {dataset_name} does not exist. "
+                + "Please, verify the client object initialisation.",
+            )
         doc_count = self.db.users.count_documents(
             {
                 "user_name": f"{user_name}",
diff --git a/server/lomas_server/admin_database/yaml_database.py b/server/lomas_server/admin_database/yaml_database.py
index a2cf84da..94cc12e6 100644
--- a/server/lomas_server/admin_database/yaml_database.py
+++ b/server/lomas_server/admin_database/yaml_database.py
@@ -9,7 +9,7 @@
     user_must_exist,
     user_must_have_access_to_dataset,
 )
-from utils.error_handler import InternalServerException
+from utils.error_handler import InternalServerException, InvalidQueryException
 
 
 class AdminYamlDatabase(AdminDatabase):
@@ -142,6 +142,11 @@ def has_user_access_to_dataset(
         Returns:
             bool: True if the user has access, False otherwise.
         """
+        if not self.does_dataset_exist(dataset_name):
+            raise InvalidQueryException(
+                f"Dataset {dataset_name} does not exist. "
+                + "Please, verify the client object initialisation.",
+            )
         for user in self.database["users"]:
             if user["user_name"] == user_name:
                 for dataset in user["datasets_list"]:
diff --git a/server/lomas_server/administration/dashboard/config.py b/server/lomas_server/administration/dashboard/config.py
index 1556f3ab..76011ed8 100644
--- a/server/lomas_server/administration/dashboard/config.py
+++ b/server/lomas_server/administration/dashboard/config.py
@@ -1,6 +1,7 @@
 import os
-from pydantic import BaseModel
+
 import yaml
+from pydantic import BaseModel
 
 from utils.error_handler import InternalServerException
 
diff --git a/server/lomas_server/administration/dashboard/pages/01_server_overview.py b/server/lomas_server/administration/dashboard/pages/01_server_overview.py
index e18d2a98..0480d1b6 100644
--- a/server/lomas_server/administration/dashboard/pages/01_server_overview.py
+++ b/server/lomas_server/administration/dashboard/pages/01_server_overview.py
@@ -1,7 +1,7 @@
 import requests
 import streamlit as st
-
 from config import get_config
+
 from constants import AdminDBType, DatasetStoreType
 from utils.config import get_config as get_server_config
 from utils.error_handler import InternalServerException
diff --git a/server/lomas_server/administration/dashboard/pages/02_database_administration.py b/server/lomas_server/administration/dashboard/pages/02_database_administration.py
index cfb51891..c0dc966c 100644
--- a/server/lomas_server/administration/dashboard/pages/02_database_administration.py
+++ b/server/lomas_server/administration/dashboard/pages/02_database_administration.py
@@ -3,6 +3,7 @@
 import yaml
 
 from admin_database.utils import get_mongodb
+from constants import DELTA_LIMIT, EPSILON_LIMIT, PrivateDatabaseType
 from mongodb_admin import (
     add_dataset,
     add_dataset_to_user,
@@ -25,7 +26,6 @@
     show_metadata_of_dataset,
     show_user,
 )
-from constants import DELTA_LIMIT, EPSILON_LIMIT, PrivateDatabaseType
 
 EPSILON_STEP = 0.01
 DELTA_STEP = 0.00001
diff --git a/server/lomas_server/app.py b/server/lomas_server/app.py
index 1a09b1d0..8ba2675a 100644
--- a/server/lomas_server/app.py
+++ b/server/lomas_server/app.py
@@ -2,9 +2,10 @@
 from contextlib import asynccontextmanager
 from typing import Callable
 
-from fastapi import Body, Depends, FastAPI, Header, Request, Response
-from fastapi.responses import JSONResponse, StreamingResponse, RedirectResponse
+from fastapi import FastAPI, Request, Response
 
+import routes_admin
+import routes_dp
 from admin_database.utils import database_factory
 from constants import (
     CONFIG_NOT_LOADED,
@@ -12,43 +13,15 @@
     QUERY_HANDLER_NOT_LOADED,
     SERVER_LIVE,
     AdminDBType,
-    DPLibraries,
 )
 from dataset_store.utils import dataset_store_factory
 from dp_queries.dp_libraries.opendp import set_opendp_features_config
-from dp_queries.dp_libraries.utils import querier_factory
 from dp_queries.dp_logic import QueryHandler
-from dp_queries.dummy_dataset import (
-    get_dummy_dataset_for_query,
-    make_dummy_dataset,
-)
 from utils.anti_timing_att import anti_timing_att
 from utils.config import get_config
-from utils.error_handler import (
-    KNOWN_EXCEPTIONS,
-    InternalServerException,
-    add_exception_handlers,
-)
-from utils.example_inputs import (
-    example_dummy_opendp,
-    example_dummy_smartnoise_sql,
-    example_get_admin_db_data,
-    example_get_dummy_dataset,
-    example_opendp,
-    example_smartnoise_sql,
-    example_smartnoise_sql_cost,
-)
-from utils.input_models import (
-    DummyOpenDPInp,
-    DummySNSQLInp,
-    GetDbData,
-    GetDummyDataset,
-    OpenDPInp,
-    SNSQLInp,
-    SNSQLInpCost,
-)
+from utils.error_handler import InternalServerException, add_exception_handlers
 from utils.loggr import LOG
-from utils.utils import add_demo_data_to_admindb, server_live, stream_dataframe
+from utils.utils import add_demo_data_to_admindb
 
 
 @asynccontextmanager
@@ -186,706 +159,6 @@ async def middleware(
 # Add custom exception handlers
 add_exception_handlers(app)
 
-# API Endpoints
-# -----------------------------------------------------------------------------
-
-
-@app.get("/")
-async def root():
-    """Redirect root endpoint to the state endpoint
-    Returns:
-        JSONResponse: The state of the server instance.
-    """
-    return RedirectResponse(url="/state")
-
-
-# Get server state
-@app.get("/state", tags=["ADMIN_USER"])
-async def get_state(
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """Returns the current state dict of this server instance.
-
-    Args:
-        user_name (str, optional): The user name. Defaults to Header(None).
-
-    Returns:
-        JSONResponse: The state of the server instance.
-    """
-    return JSONResponse(
-        content={
-            "requested_by": user_name,
-            "state": app.state.server_state,
-        }
-    )
-
-
-@app.get(
-    "/get_memory_usage",
-    dependencies=[Depends(server_live)],
-    tags=["ADMIN_USER"],
-)
-async def get_memory_usage() -> JSONResponse:
-    """Return the dataset store object memory usage
-    Args:
-        user_name (str, optional): The user name. Defaults to Header(None).
-
-    Returns:
-        JSONResponse: with DatasetStore object memory usage
-    """
-    return JSONResponse(
-        content={
-            "memory_usage": app.state.dataset_store.memory_usage,
-        }
-    )
-
-
-# Metadata query
-@app.post(
-    "/get_dataset_metadata",
-    dependencies=[Depends(server_live)],
-    tags=["USER_METADATA"],
-)
-def get_dataset_metadata(
-    _request: Request,
-    query_json: GetDbData = Body(example_get_admin_db_data),
-) -> JSONResponse:
-    """
-    Retrieves metadata for a given dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDbData, optional): A JSON object containing
-            the dataset_name key for indicating the dataset.
-            Defaults to Body(example_get_admin_db_data).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-
-    Returns:
-        JSONResponse: The metadata dictionary for the specified
-            dataset_name.
-    """
-    try:
-        ds_metadata = app.state.admin_database.get_dataset_metadata(
-            query_json.dataset_name
-        )
-
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return ds_metadata
-
-
-# Dummy dataset query
-@app.post(
-    "/get_dummy_dataset",
-    dependencies=[Depends(server_live)],
-    tags=["USER_DUMMY"],
-)
-def get_dummy_dataset(
-    _request: Request,
-    query_json: GetDummyDataset = Body(example_get_dummy_dataset),
-) -> StreamingResponse:
-    """
-    Generates and returns a dummy dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDummyDataset, optional):
-            A JSON object containing the following:
-                - nb_rows (int, optional): The number of rows in the
-                  dummy dataset (default: 100).
-                - seed (int, optional): The random seed for generating
-                  the dummy dataset (default: 42).
-            Defaults to Body(example_get_dummy_dataset).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-
-    Returns:
-        StreamingResponse: a pd.DataFrame representing the dummy dataset.
-    """
-    try:
-        ds_metadata = app.state.admin_database.get_dataset_metadata(
-            query_json.dataset_name
-        )
-
-        dummy_df = make_dummy_dataset(
-            ds_metadata, query_json.dummy_nb_rows, query_json.dummy_seed
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return stream_dataframe(dummy_df)
-
-
-# Smartnoise SQL query
-@app.post(
-    "/smartnoise_query",
-    dependencies=[Depends(server_live)],
-    tags=["USER_QUERY"],
-)
-def smartnoise_sql_handler(
-    _request: Request,
-    query_json: SNSQLInp = Body(example_smartnoise_sql),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Handles queries for the SmartNoiseSQL library.
-
-    Args:
-        request (Request): Raw request object
-        query_json (SNSQLInp): A JSON object containing:
-            - query: The SQL query to execute. NOTE: the table name is "df",
-              the query must end with "FROM df".
-            - epsilon (float): Privacy parameter (e.g., 0.1).
-            - delta (float): Privacy parameter (e.g., 1e-5).
-            - mechanisms (dict, optional): Dictionary of mechanisms for the
-              query (default: {}). See "Smartnoise-SQL mechanisms documentation
-              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
-            - postprocess (bool, optional): Whether to postprocess the query
-              results (default: True).
-              See "Smartnoise-SQL postprocessing documentation
-              https://docs.smartnoise.org/sql/advanced.html#postprocess.
-
-            Defaults to Body(example_smartnoise_sql).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: If there is not enough budget or the dataset
-            does not exist.
-        UnauthorizedAccessException: A query is already ongoing for this user,
-            the user does not exist or does not have access to the dataset.
-
-    Returns:
-        JSONResponse: A JSON object containing the following:
-            - requested_by (str): The user name.
-            - query_response (pd.DataFrame): A DataFrame containing
-              the query response.
-            - spent_epsilon (float): The amount of epsilon budget spent
-              for the query.
-            - spent_delta (float): The amount of delta budget spent
-              for the query.
-    """
-    try:
-        response = app.state.query_handler.handle_query(
-            DPLibraries.SMARTNOISE_SQL, query_json, user_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return response
-
-
-# Smartnoise SQL Dummy query
-@app.post(
-    "/dummy_smartnoise_query",
-    dependencies=[Depends(server_live)],
-    tags=["USER_DUMMY"],
-)
-def dummy_smartnoise_sql_handler(
-    _request: Request,
-    query_json: DummySNSQLInp = Body(example_dummy_smartnoise_sql),
-) -> JSONResponse:
-    """
-    Handles queries on dummy datasets for the SmartNoiseSQL library.
-
-    Args:
-        request (Request): Raw request object
-        query_json (DummySNSQLInp, optional): A JSON object containing:
-            - query: The SQL query to execute. NOTE: the table name is "df",
-              the query must end with "FROM df".
-            - epsilon (float): Privacy parameter (e.g., 0.1).
-            - delta (float): Privacy parameter (e.g., 1e-5).
-            - mechanisms (dict, optional): Dictionary of mechanisms for the
-              query (default: {}). See Smartnoise-SQL mechanisms documentation
-              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
-            - postprocess (bool, optional): Whether to postprocess the query
-              results (default: True).
-              See Smartnoise-SQL postprocessing documentation
-              https://docs.smartnoise.org/sql/advanced.html#postprocess.
-            - dummy (bool, optional): Whether to use a dummy dataset
-              (default: False).
-            - nb_rows (int, optional): The number of rows in the dummy dataset
-              (default: 100).
-            - seed (int, optional): The random seed for generating
-              the dummy dataset (default: 42).
-
-            Defaults to Body(example_dummy_smartnoise_sql).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: If there is not enough budget or the dataset
-            does not exist.
-
-    Returns:
-        JSONResponse: A JSON object containing:
-            - query_response (pd.DataFrame): a DataFrame containing
-              the query response.
-    """
-    ds_private_dataset = get_dummy_dataset_for_query(
-        app.state.admin_database, query_json
-    )
-    dummy_querier = querier_factory(
-        DPLibraries.SMARTNOISE_SQL, private_dataset=ds_private_dataset
-    )
-    try:
-        _ = dummy_querier.cost(query_json)  # verify cost works
-        response_df = dummy_querier.query(query_json)
-        response = JSONResponse(content={"query_response": response_df})
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return response
-
-
-@app.post(
-    "/estimate_smartnoise_cost",
-    dependencies=[Depends(server_live)],
-    tags=["USER_QUERY"],
-)
-def estimate_smartnoise_cost(
-    _request: Request,
-    query_json: SNSQLInpCost = Body(example_smartnoise_sql_cost),
-) -> JSONResponse:
-    """
-    Estimates the privacy loss budget cost of a SmartNoiseSQL query.
-
-    Args:
-        request (Request): Raw request object
-        query_json (SNSQLInpCost, optional):
-            A JSON object containing the following:
-            - query: The SQL query to estimate the cost for.
-              NOTE: the table name is "df", the query must end with "FROM df".
-            - epsilon (float): Privacy parameter (e.g., 0.1).
-            - delta (float): Privacy parameter (e.g., 1e-5).
-            - mechanisms (dict, optional): Dictionary of mechanisms
-              for the query (default: {}).
-              See Smartnoise-SQL mechanisms documentation
-              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
-
-            Defaults to Body(example_smartnoise_sql_cost).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist.
-
-    Returns:
-        JSONResponse: A JSON object containing:
-            - epsilon_cost (float): The estimated epsilon cost.
-            - delta_cost (float): The estimated delta cost.
-    """
-    try:
-        response = app.state.query_handler.estimate_cost(
-            DPLibraries.SMARTNOISE_SQL,
-            query_json,
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(content=response)
-
-
-@app.post(
-    "/opendp_query", dependencies=[Depends(server_live)], tags=["USER_QUERY"]
-)
-def opendp_query_handler(
-    _request: Request,
-    query_json: OpenDPInp = Body(example_opendp),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Handles queries for the OpenDP Library.
-
-    Args:
-        request (Request): Raw request object.
-        query_json (OpenDPInp, optional): A JSON object containing the following:
-            - opendp_pipeline: The OpenDP pipeline for the query.
-            - fixed_delta: If the pipeline measurement is of type
-                "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then it is
-                converted to "SmoothedMaxDivergence" with "make_zCDP_to_approxDP"
-                (see "opendp measurements documentation at
-                https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301
-                In that case a "fixed_delta" must be provided by the user.
-
-            Defaults to Body(example_opendp).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The pipeline does not contain a "measurement",
-            there is not enough budget or the dataset does not exist.
-        UnauthorizedAccessException: A query is already ongoing for this user,
-            the user does not exist or does not have access to the dataset.
-
-    Returns:
-        JSONResponse: A JSON object containing the following:
-            - requested_by (str): The user name.
-            - query_response (pd.DataFrame): A DataFrame containing
-              the query response.
-            - spent_epsilon (float): The amount of epsilon budget spent
-              for the query.
-            - spent_delta (float): The amount of delta budget spent
-              for the query.
-    """
-    try:
-        response = app.state.query_handler.handle_query(
-            DPLibraries.OPENDP, query_json, user_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(content=response)
-
-
-@app.post(
-    "/dummy_opendp_query",
-    dependencies=[Depends(server_live)],
-    tags=["USER_DUMMY"],
-)
-def dummy_opendp_query_handler(
-    _request: Request,
-    query_json: DummyOpenDPInp = Body(example_dummy_opendp),
-) -> JSONResponse:
-    """
-    Handles queries on dummy datasets for the OpenDP library.
-
-    Args:
-        request (Request): Raw request object.
-        query_json (DummyOpenDPInp, optional):
-            A JSON object containing the following:
-            - opendp_pipeline: The OpenDP pipeline for the query.
-            - fixed_delta: If the pipeline measurement is of type\
-              "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then
-              it is converted to "SmoothedMaxDivergence" with
-              "make_zCDP_to_approxDP" (see opendp measurements documentation at
-              https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301
-              In that case a "fixed_delta" must be provided by the user.
-            - dummy (bool, optional): Whether to use a dummy dataset
-              (default: False).
-            - nb_rows (int, optional): The number of rows
-              in the dummy dataset (default: 100).
-            - seed (int, optional): The random seed for generating
-              the dummy dataset (default: 42).
-
-            Defaults to Body(example_dummy_opendp).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: If there is not enough budget or the dataset
-            does not exist.
-
-    Returns:
-        JSONResponse: A JSON object containing:
-            - query_response (pd.DataFrame): a DataFrame containing
-              the query response.
-    """
-    ds_private_dataset = get_dummy_dataset_for_query(
-        app.state.admin_database, query_json
-    )
-    dummy_querier = querier_factory(
-        DPLibraries.OPENDP, private_dataset=ds_private_dataset
-    )
-
-    try:
-        _ = dummy_querier.cost(query_json)  # verify cost works
-        response_df = dummy_querier.query(query_json)
-        response = {"query_response": response_df}
-
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(content=response)
-
-
-@app.post(
-    "/estimate_opendp_cost",
-    dependencies=[Depends(server_live)],
-    tags=["USER_QUERY"],
-)
-def estimate_opendp_cost(
-    _request: Request,
-    query_json: OpenDPInp = Body(example_opendp),
-) -> JSONResponse:
-    """
-    Estimates the privacy loss budget cost of an OpenDP query.
-
-    Args:
-        request (Request): Raw request object
-        query_json (OpenDPInp, optional):
-            A JSON object containing the following:
-            - "opendp_pipeline": The OpenDP pipeline for the query.
-
-            Defaults to Body(example_opendp).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist or the
-            pipeline does not contain a measurement.
-
-    Returns:
-        JSONResponse: A JSON object containing:
-            - epsilon_cost (float): The estimated epsilon cost.
-            - delta_cost (float): The estimated delta cost.
-    """
-    try:
-        response = app.state.query_handler.estimate_cost(
-            DPLibraries.OPENDP,
-            query_json,
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(content=response)
-
-
-# MongoDB get initial budget
-@app.post(
-    "/get_initial_budget",
-    dependencies=[Depends(server_live)],
-    tags=["USER_BUDGET"],
-)
-def get_initial_budget(
-    _request: Request,
-    query_json: GetDbData = Body(example_get_admin_db_data),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Returns the initial budget for a user and dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDbData, optional): A JSON object containing:
-            - dataset_name (str): The name of the dataset.
-
-            Defaults to Body(example_get_admin_db_data).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist.
-        UnauthorizedAccessException: The user does not exist or
-            the user does not have access to the dataset.
-    Returns:
-        JSONResponse: a JSON object with:
-            - initial_epsilon (float): initial epsilon budget.
-            - initial_delta (float): initial delta budget.
-    """
-    try:
-        (
-            initial_epsilon,
-            initial_delta,
-        ) = app.state.admin_database.get_initial_budget(
-            user_name, query_json.dataset_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(
-        content={
-            "initial_epsilon": initial_epsilon,
-            "initial_delta": initial_delta,
-        }
-    )
-
-
-# MongoDB get total spent budget
-@app.post(
-    "/get_total_spent_budget",
-    dependencies=[Depends(server_live)],
-    tags=["USER_BUDGET"],
-)
-def get_total_spent_budget(
-    _request: Request,
-    query_json: GetDbData = Body(example_get_admin_db_data),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Returns the spent budget for a user and dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDbData, optional): A JSON object containing:
-            - dataset_name (str): The name of the dataset.
-
-            Defaults to Body(example_get_admin_db_data).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist.
-        UnauthorizedAccessException: The user does not exist or
-            the user does not have access to the dataset.
-    Returns:
-        JSONResponse: a JSON object with:
-            - total_spent_epsilon (float): total spent epsilon budget.
-            - total_spent_delta (float): total spent delta budget.
-    """
-    try:
-        (
-            total_spent_epsilon,
-            total_spent_delta,
-        ) = app.state.admin_database.get_total_spent_budget(
-            user_name, query_json.dataset_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(
-        content={
-            "total_spent_epsilon": total_spent_epsilon,
-            "total_spent_delta": total_spent_delta,
-        }
-    )
-
-
-# MongoDB get remaining budget
-@app.post(
-    "/get_remaining_budget",
-    dependencies=[Depends(server_live)],
-    tags=["USER_BUDGET"],
-)
-def get_remaining_budget(
-    _request: Request,
-    query_json: GetDbData = Body(example_get_admin_db_data),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Returns the remaining budget for a user and dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDbData, optional): A JSON object containing:
-            - dataset_name (str): The name of the dataset.
-
-            Defaults to Body(example_get_admin_db_data).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist.
-        UnauthorizedAccessException: The user does not exist or
-            the user does not have access to the dataset.
-    Returns:
-        JSONResponse: a JSON object with:
-            - remaining_epsilon (float): remaining epsilon budget.
-            - remaining_delta (float): remaining delta budget.
-    """
-    try:
-        rem_epsilon, rem_delta = app.state.admin_database.get_remaining_budget(
-            user_name, query_json.dataset_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(
-        content={
-            "remaining_epsilon": rem_epsilon,
-            "remaining_delta": rem_delta,
-        }
-    )
-
-
-# MongoDB get archives
-@app.post(
-    "/get_previous_queries",
-    dependencies=[Depends(server_live)],
-    tags=["USER_BUDGET"],
-)
-def get_user_previous_queries(
-    _request: Request,
-    query_json: GetDbData = Body(example_get_admin_db_data),
-    user_name: str = Header(None),
-) -> JSONResponse:
-    """
-    Returns the query history of a user on a specific dataset.
-
-    Args:
-        request (Request): Raw request object
-        query_json (GetDbData, optional): A JSON object containing:
-            - dataset_name (str): The name of the dataset.
-
-            Defaults to Body(example_get_admin_db_data).
-
-        user_name (str, optional): The user name.
-            Defaults to Header(None).
-
-    Raises:
-        ExternalLibraryException: For exceptions from libraries
-            external to this package.
-        InternalServerException: For any other unforseen exceptions.
-        InvalidQueryException: The dataset does not exist.
-        UnauthorizedAccessException: The user does not exist or
-            the user does not have access to the dataset.
-
-    Returns:
-        JSONResponse: A JSON object containing:
-            - previous_queries (list[dict]): a list of dictionaries
-              containing the previous queries.
-    """
-    try:
-        previous_queries = app.state.admin_database.get_user_previous_queries(
-            user_name, query_json.dataset_name
-        )
-    except KNOWN_EXCEPTIONS as e:
-        raise e
-    except Exception as e:
-        raise InternalServerException(e) from e
-
-    return JSONResponse(content={"previous_queries": previous_queries})
+# Add endpoints
+app.include_router(routes_dp.router)
+app.include_router(routes_admin.router)
diff --git a/server/lomas_server/constants.py b/server/lomas_server/constants.py
index 694e3d6a..9f1e67d3 100644
--- a/server/lomas_server/constants.py
+++ b/server/lomas_server/constants.py
@@ -78,6 +78,15 @@ class DPLibraries(StrEnum):
 
     SMARTNOISE_SQL = "smartnoise_sql"
     OPENDP = "opendp"
+    DIFFPRIVLIB = "diffprivlib"
+
+
+# Query model input to DP librairy
+MODEL_INPUT_TO_LIB = {
+    "SNSQLInp": DPLibraries.SMARTNOISE_SQL,
+    "OpenDPInp": DPLibraries.OPENDP,
+    "DiffPrivLibInp": DPLibraries.DIFFPRIVLIB,
+}
 
 
 # Private Databases
@@ -130,3 +139,7 @@ class OpenDPDatasetInputMetric(StrEnum):
 # Smartnoise sql
 STATS = ["count", "sum_int", "sum_large_int", "sum_float", "threshold"]
 MAX_NAN_ITERATION = 5
+
+
+# Data preprocessing
+NUMERICAL_DTYPES = ["int16", "int32", "int64", "float16", "float32", "float64"]
diff --git a/server/lomas_server/dp_queries/dp_libraries/diffprivlib.py b/server/lomas_server/dp_queries/dp_libraries/diffprivlib.py
new file mode 100644
index 00000000..e5dac0c7
--- /dev/null
+++ b/server/lomas_server/dp_queries/dp_libraries/diffprivlib.py
@@ -0,0 +1,125 @@
+import pickle
+import warnings
+from base64 import b64encode
+from typing import Dict
+
+import pandas as pd
+from diffprivlib.utils import PrivacyLeakWarning
+from diffprivlib_logger import deserialise_pipeline
+from sklearn.pipeline import Pipeline
+
+from constants import DPLibraries
+from dp_queries.dp_libraries.diffprivlib_utils import (
+    handle_missing_data,
+    split_train_test_data,
+)
+from dp_queries.dp_querier import DPQuerier
+from utils.error_handler import ExternalLibraryException
+from utils.input_models import DiffPrivLibInp
+
+
+class DiffPrivLibQuerier(DPQuerier):
+    """
+    Concrete implementation of the DPQuerier ABC for the DiffPrivLib library.
+    """
+
+    def fit_model_on_data(
+        self, query_json: DiffPrivLibInp
+    ) -> tuple[Pipeline, pd.DataFrame, pd.DataFrame]:
+        """Perform necessary steps to fit the model on the data
+
+        Args:
+            query_json (BaseModel): The JSON request object for the query.
+
+        Raises:
+            ExternalLibraryException: For exceptions from libraries
+                external to this package.
+
+        Returns:
+            fitted_dpl_pipeline (dpl model): the fitted model on the training data
+            x_test (pd.DataFrame): test data feature
+            y_test (pd.DataFrame): test data target
+        """
+        # Prepare data
+        raw_data = self.private_dataset.get_pandas_df()
+        data = handle_missing_data(raw_data, query_json.imputer_strategy)
+        x_train, x_test, y_train, y_test = split_train_test_data(
+            data, query_json
+        )
+
+        # Prepare DiffPrivLib pipeline
+        dpl_pipeline = deserialise_pipeline(query_json.diffprivlib_json)
+
+        # Fit the pipeline on the training set
+        warnings.simplefilter("error", PrivacyLeakWarning)
+        try:
+            fitted_dpl_pipeline = dpl_pipeline.fit(x_train, y_train)
+        except PrivacyLeakWarning as e:
+            raise ExternalLibraryException(
+                DPLibraries.DIFFPRIVLIB,
+                f"PrivacyLeakWarning: {e}. "
+                + "Lomas server cannot fit pipeline on data, "
+                + "PrivacyLeakWarning is a blocker.",
+            ) from e
+        except Exception as e:
+            raise ExternalLibraryException(
+                DPLibraries.DIFFPRIVLIB,
+                f"Cannot fit pipeline on data because {e}",
+            ) from e
+
+        return fitted_dpl_pipeline, x_test, y_test
+
+    def cost(self, query_json: DiffPrivLibInp) -> tuple[float, float]:
+        """Estimate cost of query
+
+        Args:
+            query_json (BaseModel): The JSON request object for the query.
+
+        Raises:
+            ExternalLibraryException: For exceptions from libraries
+                external to this package.
+
+        Returns:
+            tuple[float, float]: The tuple of costs, the first value
+                is the epsilon cost, the second value is the delta value.
+        """
+        fitted_dpl_pipeline, _, _ = self.fit_model_on_data(query_json)
+
+        # Compute budget
+        spent_epsilon = 0.0
+        spent_delta = 0.0
+        for step in fitted_dpl_pipeline.steps:
+            spent_epsilon += step[1].accountant.spent_budget[0][0]
+            spent_delta += step[1].accountant.spent_budget[0][1]
+        return spent_epsilon, spent_delta
+
+    def query(self, query_json: DiffPrivLibInp) -> Dict:
+        """Perform the query and return the response.
+
+        Args:
+            query_json (BaseModel): The JSON request object for the query.
+
+        Raises:
+            ExternalLibraryException: For exceptions from libraries
+                external to this package.
+            InvalidQueryException: If the budget values are too small to
+                perform the query.
+
+        Returns:
+            dict: The dictionary encoding of the resulting pd.DataFrame.
+        """
+        fitted_dpl_pipeline, x_test, y_test = self.fit_model_on_data(
+            query_json
+        )
+
+        # Model accuracy
+        score = fitted_dpl_pipeline.score(x_test, y_test)
+
+        # Serialise model
+        pickled_model = b64encode(pickle.dumps(fitted_dpl_pipeline))
+
+        query_response = {
+            "score": score,
+            "model": pickled_model.decode("utf-8"),
+        }
+        return query_response
diff --git a/server/lomas_server/dp_queries/dp_libraries/diffprivlib_utils.py b/server/lomas_server/dp_queries/dp_libraries/diffprivlib_utils.py
new file mode 100644
index 00000000..d817bd49
--- /dev/null
+++ b/server/lomas_server/dp_queries/dp_libraries/diffprivlib_utils.py
@@ -0,0 +1,111 @@
+import numpy as np
+import pandas as pd
+from sklearn.impute import SimpleImputer
+from sklearn.model_selection import train_test_split
+
+from constants import NUMERICAL_DTYPES
+from utils.error_handler import InvalidQueryException
+from utils.input_models import DiffPrivLibInp
+
+
+def handle_missing_data(
+    df: pd.DataFrame, imputer_strategy: str
+) -> pd.DataFrame:
+    """Impute missing data based on given imputation strategy for NaNs
+    Args:
+        df (pd.DataFrame): dataframe with the data
+        imputer_strategy (str): string to indicate imputatation for NaNs
+            "drop": will drop all rows with missing values
+            "mean": will replace values by the mean of the column values
+            "median": will replace values by the median of the column values
+            "most_frequent": : will replace values by the most frequent values
+
+    Raises:
+        InvalidQueryException: If the "imputer_strategy" does not exist
+
+    Returns:
+        df (pd.DataFrame): dataframe with the imputed data
+    """
+    dtypes = df.dtypes
+
+    if imputer_strategy == "drop":
+        df = df.dropna()
+    elif imputer_strategy in ["mean", "median"]:
+        numerical_cols = df.select_dtypes(
+            include=NUMERICAL_DTYPES
+        ).columns.tolist()
+        categorical_cols = [
+            col for col in df.columns if col not in numerical_cols
+        ]
+
+        # Impute numerical features using given strategy
+        imp_mean = SimpleImputer(strategy=imputer_strategy)
+        df_num_imputed = imp_mean.fit_transform(df[numerical_cols])
+
+        # Impute categorical features with most frequent value
+        imp_most_frequent = SimpleImputer(strategy="most_frequent")
+        df[categorical_cols] = df[categorical_cols].astype("object")
+        df[categorical_cols] = df[categorical_cols].replace({pd.NA: np.nan})
+        df_cat_imputed = imp_most_frequent.fit_transform(df[categorical_cols])
+
+        # Combine imputed dataframes
+        df = pd.concat(
+            [
+                pd.DataFrame(df_num_imputed, columns=numerical_cols),
+                pd.DataFrame(df_cat_imputed, columns=categorical_cols),
+            ],
+            axis=1,
+        )
+    elif imputer_strategy == "most_frequent":
+        # Impute all features with most frequent value
+        imp_most_frequent = SimpleImputer(strategy=imputer_strategy)
+        df[df.columns] = df[df.columns].astype("object")
+        df[df.columns] = df[df.columns].replace({pd.NA: np.nan})
+        df = pd.DataFrame(
+            imp_most_frequent.fit_transform(df), columns=df.columns
+        )
+    else:
+        raise InvalidQueryException(
+            f"Imputation strategy {imputer_strategy} not supported."
+        )
+
+    df = df.astype(dtype=dtypes)
+    return df
+
+
+def split_train_test_data(
+    df: pd.DataFrame, query_json: DiffPrivLibInp
+) -> tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame, pd.DataFrame]:
+    """Split the data between train and test set
+    Args:
+        df (pd.DataFrame): dataframe with the data
+        query_json (DiffPrivLibInp): user input query indication
+            feature_columns (list[str]): columns from data to use as features
+            target_columns (list[str]): columns from data to use as target (to predict)
+            test_size (float): proportion of data in the test set
+            test_train_split_seed (int): seed for the random train-test split
+
+    Returns:
+        x_train (pd.DataFrame): training data features
+        x_test (pd.DataFrame): testing data features
+        y_train (pd.DataFrame): training data target
+        y_test (pd.DataFrame): testing data target
+    """
+    feature_data = df[query_json.feature_columns]
+
+    if query_json.target_columns is None:
+        x_train, x_test = train_test_split(
+            feature_data,
+            test_size=query_json.test_size,
+            random_state=query_json.test_train_split_seed,
+        )
+        y_train, y_test = None, None
+    else:
+        label_data = df[query_json.target_columns]
+        x_train, x_test, y_train, y_test = train_test_split(
+            feature_data,
+            label_data,
+            test_size=query_json.test_size,
+            random_state=query_json.test_train_split_seed,
+        )
+    return x_train, x_test, y_train, y_test
diff --git a/server/lomas_server/dp_queries/dp_libraries/utils.py b/server/lomas_server/dp_queries/dp_libraries/utils.py
index ccfb3f11..674653c0 100644
--- a/server/lomas_server/dp_queries/dp_libraries/utils.py
+++ b/server/lomas_server/dp_queries/dp_libraries/utils.py
@@ -1,4 +1,5 @@
 from constants import DPLibraries
+from dp_queries.dp_libraries.diffprivlib import DiffPrivLibQuerier
 from dp_queries.dp_libraries.opendp import OpenDPQuerier
 from dp_queries.dp_libraries.smartnoise_sql import SmartnoiseSQLQuerier
 from dp_queries.dp_querier import DPQuerier
@@ -27,6 +28,9 @@ def querier_factory(lib: str, private_dataset: PrivateDataset) -> DPQuerier:
         case DPLibraries.OPENDP:
             querier = OpenDPQuerier(private_dataset)
 
+        case DPLibraries.DIFFPRIVLIB:
+            querier = DiffPrivLibQuerier(private_dataset)
+
         case _:
             raise InternalServerException(f"Unknown library: {lib}")
     return querier
diff --git a/server/lomas_server/routes_admin.py b/server/lomas_server/routes_admin.py
new file mode 100644
index 00000000..345bdf0e
--- /dev/null
+++ b/server/lomas_server/routes_admin.py
@@ -0,0 +1,406 @@
+from fastapi import APIRouter, Body, Depends, Header, Request
+from fastapi.responses import JSONResponse, RedirectResponse, StreamingResponse
+
+from dp_queries.dummy_dataset import make_dummy_dataset
+from utils.error_handler import (
+    KNOWN_EXCEPTIONS,
+    InternalServerException,
+    UnauthorizedAccessException,
+)
+from utils.example_inputs import (
+    example_get_admin_db_data,
+    example_get_dummy_dataset,
+)
+from utils.input_models import GetDbData, GetDummyDataset
+from utils.utils import server_live, stream_dataframe
+
+router = APIRouter()
+
+
+@router.get("/")
+async def root():
+    """Redirect root endpoint to the state endpoint
+    Returns:
+        JSONResponse: The state of the server instance.
+    """
+    return RedirectResponse(url="/state")
+
+
+# Get server state
+@router.get("/state", tags=["ADMIN_USER"])
+async def get_state(
+    request: Request,
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """Returns the current state dict of this server instance.
+
+    Args:
+        request (Request): Raw request object
+        user_name (str, optional): The user name. Defaults to Header(None).
+
+    Returns:
+        JSONResponse: The state of the server instance.
+    """
+    app = request.app
+
+    return JSONResponse(
+        content={
+            "requested_by": user_name,
+            "state": app.state.server_state,
+        }
+    )
+
+
+@router.get(
+    "/get_memory_usage",
+    dependencies=[Depends(server_live)],
+    tags=["ADMIN_USER"],
+)
+async def get_memory_usage(request: Request) -> JSONResponse:
+    """Return the dataset store object memory usage
+    Args:
+        request (Request): Raw request object
+        user_name (str, optional): The user name. Defaults to Header(None).
+
+    Returns:
+        JSONResponse: with DatasetStore object memory usage
+    """
+    app = request.app
+
+    return JSONResponse(
+        content={
+            "memory_usage": app.state.dataset_store.memory_usage,
+        }
+    )
+
+
+# Metadata query
+@router.post(
+    "/get_dataset_metadata",
+    dependencies=[Depends(server_live)],
+    tags=["USER_METADATA"],
+)
+def get_dataset_metadata(
+    request: Request,
+    query_json: GetDbData = Body(example_get_admin_db_data),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Retrieves metadata for a given dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDbData, optional): A JSON object containing
+            the dataset_name key for indicating the dataset.
+            Defaults to Body(example_get_admin_db_data).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+
+    Returns:
+        JSONResponse: The metadata dictionary for the specified
+            dataset_name.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    try:
+        ds_metadata = app.state.admin_database.get_dataset_metadata(
+            dataset_name
+        )
+
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return ds_metadata
+
+
+# Dummy dataset query
+@router.post(
+    "/get_dummy_dataset",
+    dependencies=[Depends(server_live)],
+    tags=["USER_DUMMY"],
+)
+def get_dummy_dataset(
+    request: Request,
+    query_json: GetDummyDataset = Body(example_get_dummy_dataset),
+    user_name: str = Header(None),
+) -> StreamingResponse:
+    """
+    Generates and returns a dummy dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDummyDataset, optional):
+            A JSON object containing the following:
+                - nb_rows (int, optional): The number of rows in the
+                  dummy dataset (default: 100).
+                - seed (int, optional): The random seed for generating
+                  the dummy dataset (default: 42).
+            Defaults to Body(example_get_dummy_dataset).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+
+    Returns:
+        StreamingResponse: a pd.DataFrame representing the dummy dataset.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    try:
+        ds_metadata = app.state.admin_database.get_dataset_metadata(
+            query_json.dataset_name
+        )
+
+        dummy_df = make_dummy_dataset(
+            ds_metadata, query_json.dummy_nb_rows, query_json.dummy_seed
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return stream_dataframe(dummy_df)
+
+
+# MongoDB get initial budget
+@router.post(
+    "/get_initial_budget",
+    dependencies=[Depends(server_live)],
+    tags=["USER_BUDGET"],
+)
+def get_initial_budget(
+    request: Request,
+    query_json: GetDbData = Body(example_get_admin_db_data),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Returns the initial budget for a user and dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDbData, optional): A JSON object containing:
+            - dataset_name (str): The name of the dataset.
+
+            Defaults to Body(example_get_admin_db_data).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist.
+        UnauthorizedAccessException: The user does not exist or
+            the user does not have access to the dataset.
+    Returns:
+        JSONResponse: a JSON object with:
+            - initial_epsilon (float): initial epsilon budget.
+            - initial_delta (float): initial delta budget.
+    """
+    app = request.app
+
+    try:
+        (
+            initial_epsilon,
+            initial_delta,
+        ) = app.state.admin_database.get_initial_budget(
+            user_name, query_json.dataset_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(
+        content={
+            "initial_epsilon": initial_epsilon,
+            "initial_delta": initial_delta,
+        }
+    )
+
+
+# MongoDB get total spent budget
+@router.post(
+    "/get_total_spent_budget",
+    dependencies=[Depends(server_live)],
+    tags=["USER_BUDGET"],
+)
+def get_total_spent_budget(
+    request: Request,
+    query_json: GetDbData = Body(example_get_admin_db_data),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Returns the spent budget for a user and dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDbData, optional): A JSON object containing:
+            - dataset_name (str): The name of the dataset.
+
+            Defaults to Body(example_get_admin_db_data).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist.
+        UnauthorizedAccessException: The user does not exist or
+            the user does not have access to the dataset.
+    Returns:
+        JSONResponse: a JSON object with:
+            - total_spent_epsilon (float): total spent epsilon budget.
+            - total_spent_delta (float): total spent delta budget.
+    """
+    app = request.app
+
+    try:
+        (
+            total_spent_epsilon,
+            total_spent_delta,
+        ) = app.state.admin_database.get_total_spent_budget(
+            user_name, query_json.dataset_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(
+        content={
+            "total_spent_epsilon": total_spent_epsilon,
+            "total_spent_delta": total_spent_delta,
+        }
+    )
+
+
+# MongoDB get remaining budget
+@router.post(
+    "/get_remaining_budget",
+    dependencies=[Depends(server_live)],
+    tags=["USER_BUDGET"],
+)
+def get_remaining_budget(
+    request: Request,
+    query_json: GetDbData = Body(example_get_admin_db_data),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Returns the remaining budget for a user and dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDbData, optional): A JSON object containing:
+            - dataset_name (str): The name of the dataset.
+
+            Defaults to Body(example_get_admin_db_data).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist.
+        UnauthorizedAccessException: The user does not exist or
+            the user does not have access to the dataset.
+    Returns:
+        JSONResponse: a JSON object with:
+            - remaining_epsilon (float): remaining epsilon budget.
+            - remaining_delta (float): remaining delta budget.
+    """
+    app = request.app
+
+    try:
+        rem_epsilon, rem_delta = app.state.admin_database.get_remaining_budget(
+            user_name, query_json.dataset_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(
+        content={
+            "remaining_epsilon": rem_epsilon,
+            "remaining_delta": rem_delta,
+        }
+    )
+
+
+# MongoDB get archives
+@router.post(
+    "/get_previous_queries",
+    dependencies=[Depends(server_live)],
+    tags=["USER_BUDGET"],
+)
+def get_user_previous_queries(
+    request: Request,
+    query_json: GetDbData = Body(example_get_admin_db_data),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Returns the query history of a user on a specific dataset.
+
+    Args:
+        request (Request): Raw request object
+        query_json (GetDbData, optional): A JSON object containing:
+            - dataset_name (str): The name of the dataset.
+
+            Defaults to Body(example_get_admin_db_data).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist.
+        UnauthorizedAccessException: The user does not exist or
+            the user does not have access to the dataset.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - previous_queries (list[dict]): a list of dictionaries
+              containing the previous queries.
+    """
+    app = request.app
+
+    try:
+        previous_queries = app.state.admin_database.get_user_previous_queries(
+            user_name, query_json.dataset_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content={"previous_queries": previous_queries})
diff --git a/server/lomas_server/routes_dp.py b/server/lomas_server/routes_dp.py
new file mode 100644
index 00000000..68e59b2a
--- /dev/null
+++ b/server/lomas_server/routes_dp.py
@@ -0,0 +1,618 @@
+from fastapi import APIRouter, Body, Depends, Header, Request
+from fastapi.responses import JSONResponse
+
+from constants import DPLibraries
+from dp_queries.dp_libraries.utils import querier_factory
+from dp_queries.dummy_dataset import get_dummy_dataset_for_query
+from utils.error_handler import (
+    KNOWN_EXCEPTIONS,
+    InternalServerException,
+    UnauthorizedAccessException,
+)
+from utils.example_inputs import (
+    example_diffprivlib,
+    example_dummy_diffprivlib,
+    example_dummy_opendp,
+    example_dummy_smartnoise_sql,
+    example_opendp,
+    example_smartnoise_sql,
+    example_smartnoise_sql_cost,
+)
+from utils.input_models import (
+    DiffPrivLibInp,
+    DummyDiffPrivLibInp,
+    DummyOpenDPInp,
+    DummySNSQLInp,
+    OpenDPInp,
+    SNSQLInp,
+    SNSQLInpCost,
+)
+from utils.utils import server_live
+
+router = APIRouter()
+
+
+@router.post(
+    "/smartnoise_query",
+    dependencies=[Depends(server_live)],
+    tags=["USER_QUERY"],
+)
+def smartnoise_sql_handler(
+    request: Request,
+    query_json: SNSQLInp = Body(example_smartnoise_sql),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Handles queries for the SmartNoiseSQL library.
+
+    Args:
+        request (Request): Raw request object
+        query_json (SNSQLInp): A JSON object containing:
+            - query: The SQL query to execute. NOTE: the table name is "df",
+              the query must end with "FROM df".
+            - epsilon (float): Privacy parameter (e.g., 0.1).
+            - delta (float): Privacy parameter (e.g., 1e-5).
+            - mechanisms (dict, optional): Dictionary of mechanisms for the
+              query (default: {}). See "Smartnoise-SQL mechanisms documentation
+              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
+            - postprocess (bool, optional): Whether to postprocess the query
+              results (default: True).
+              See "Smartnoise-SQL postprocessing documentation
+              https://docs.smartnoise.org/sql/advanced.html#postprocess.
+
+            Defaults to Body(example_smartnoise_sql).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: If there is not enough budget or the dataset
+            does not exist.
+        UnauthorizedAccessException: A query is already ongoing for this user,
+            the user does not exist or does not have access to the dataset.
+
+    Returns:
+        JSONResponse: A JSON object containing the following:
+            - requested_by (str): The user name.
+            - query_response (pd.DataFrame): A DataFrame containing
+              the query response.
+            - spent_epsilon (float): The amount of epsilon budget spent
+              for the query.
+            - spent_delta (float): The amount of delta budget spent
+              for the query.
+    """
+    app = request.app
+
+    try:
+        response = app.state.query_handler.handle_query(
+            DPLibraries.SMARTNOISE_SQL, query_json, user_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return response
+
+
+# Smartnoise SQL Dummy query
+@router.post(
+    "/dummy_smartnoise_query",
+    dependencies=[Depends(server_live)],
+    tags=["USER_DUMMY"],
+)
+def dummy_smartnoise_sql_handler(
+    request: Request,
+    query_json: DummySNSQLInp = Body(example_dummy_smartnoise_sql),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Handles queries on dummy datasets for the SmartNoiseSQL library.
+
+    Args:
+        request (Request): Raw request object
+        query_json (DummySNSQLInp, optional): A JSON object containing:
+            - query: The SQL query to execute. NOTE: the table name is "df",
+              the query must end with "FROM df".
+            - epsilon (float): Privacy parameter (e.g., 0.1).
+            - delta (float): Privacy parameter (e.g., 1e-5).
+            - mechanisms (dict, optional): Dictionary of mechanisms for the
+              query (default: {}). See Smartnoise-SQL mechanisms documentation
+              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
+            - postprocess (bool, optional): Whether to postprocess the query
+              results (default: True).
+              See Smartnoise-SQL postprocessing documentation
+              https://docs.smartnoise.org/sql/advanced.html#postprocess.
+            - dummy (bool, optional): Whether to use a dummy dataset
+              (default: False).
+            - nb_rows (int, optional): The number of rows in the dummy dataset
+              (default: 100).
+            - seed (int, optional): The random seed for generating
+              the dummy dataset (default: 42).
+
+            Defaults to Body(example_dummy_smartnoise_sql).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: If there is not enough budget or the dataset
+            does not exist.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - query_response (pd.DataFrame): a DataFrame containing
+              the query response.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    ds_private_dataset = get_dummy_dataset_for_query(
+        app.state.admin_database, query_json
+    )
+    dummy_querier = querier_factory(
+        DPLibraries.SMARTNOISE_SQL, private_dataset=ds_private_dataset
+    )
+    try:
+        _ = dummy_querier.cost(query_json)  # verify cost works
+        response_df = dummy_querier.query(query_json)
+        response = JSONResponse(content={"query_response": response_df})
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return response
+
+
+@router.post(
+    "/estimate_smartnoise_cost",
+    dependencies=[Depends(server_live)],
+    tags=["USER_QUERY"],
+)
+def estimate_smartnoise_cost(
+    request: Request,
+    query_json: SNSQLInpCost = Body(example_smartnoise_sql_cost),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Estimates the privacy loss budget cost of a SmartNoiseSQL query.
+
+    Args:
+        request (Request): Raw request object
+        query_json (SNSQLInpCost, optional):
+            A JSON object containing the following:
+            - query: The SQL query to estimate the cost for.
+              NOTE: the table name is "df", the query must end with "FROM df".
+            - epsilon (float): Privacy parameter (e.g., 0.1).
+            - delta (float): Privacy parameter (e.g., 1e-5).
+            - mechanisms (dict, optional): Dictionary of mechanisms
+              for the query (default: {}).
+              See Smartnoise-SQL mechanisms documentation
+              https://docs.smartnoise.org/sql/advanced.html#overriding-mechanisms.
+
+            Defaults to Body(example_smartnoise_sql_cost).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - epsilon_cost (float): The estimated epsilon cost.
+            - delta_cost (float): The estimated delta cost.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    try:
+        response = app.state.query_handler.estimate_cost(
+            DPLibraries.SMARTNOISE_SQL,
+            query_json,
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content=response)
+
+
+@router.post(
+    "/opendp_query", dependencies=[Depends(server_live)], tags=["USER_QUERY"]
+)
+def opendp_query_handler(
+    request: Request,
+    query_json: OpenDPInp = Body(example_opendp),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Handles queries for the OpenDP Library.
+
+    Args:
+        request (Request): Raw request object.
+        query_json (OpenDPInp, optional): A JSON object containing the following:
+            - opendp_pipeline: The OpenDP pipeline for the query.
+            - fixed_delta: If the pipeline measurement is of type
+                "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then it is
+                converted to "SmoothedMaxDivergence" with "make_zCDP_to_approxDP"
+                (see "opendp measurements documentation at
+                https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301
+                In that case a "fixed_delta" must be provided by the user.
+
+            Defaults to Body(example_opendp).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The pipeline does not contain a "measurement",
+            there is not enough budget or the dataset does not exist.
+        UnauthorizedAccessException: A query is already ongoing for this user,
+            the user does not exist or does not have access to the dataset.
+
+    Returns:
+        JSONResponse: A JSON object containing the following:
+            - requested_by (str): The user name.
+            - query_response (pd.DataFrame): A DataFrame containing
+              the query response.
+            - spent_epsilon (float): The amount of epsilon budget spent
+              for the query.
+            - spent_delta (float): The amount of delta budget spent
+              for the query.
+    """
+    app = request.app
+
+    try:
+        response = app.state.query_handler.handle_query(
+            DPLibraries.OPENDP, query_json, user_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content=response)
+
+
+@router.post(
+    "/dummy_opendp_query",
+    dependencies=[Depends(server_live)],
+    tags=["USER_DUMMY"],
+)
+def dummy_opendp_query_handler(
+    request: Request,
+    query_json: DummyOpenDPInp = Body(example_dummy_opendp),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Handles queries on dummy datasets for the OpenDP library.
+
+    Args:
+        request (Request): Raw request object.
+        query_json (DummyOpenDPInp, optional):
+            A JSON object containing the following:
+            - opendp_pipeline: The OpenDP pipeline for the query.
+            - fixed_delta: If the pipeline measurement is of type\
+              "ZeroConcentratedDivergence" (e.g. with "make_gaussian") then
+              it is converted to "SmoothedMaxDivergence" with
+              "make_zCDP_to_approxDP" (see opendp measurements documentation at
+              https://docs.opendp.org/en/stable/api/python/opendp.combinators.html#opendp.combinators.make_zCDP_to_approxDP). # noqa # pylint: disable=C0301
+              In that case a "fixed_delta" must be provided by the user.
+            - dummy (bool, optional): Whether to use a dummy dataset
+              (default: False).
+            - nb_rows (int, optional): The number of rows
+              in the dummy dataset (default: 100).
+            - seed (int, optional): The random seed for generating
+              the dummy dataset (default: 42).
+
+            Defaults to Body(example_dummy_opendp).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: If there is not enough budget or the dataset
+            does not exist.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - query_response (pd.DataFrame): a DataFrame containing
+              the query response.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    ds_private_dataset = get_dummy_dataset_for_query(
+        app.state.admin_database, query_json
+    )
+    dummy_querier = querier_factory(
+        DPLibraries.OPENDP, private_dataset=ds_private_dataset
+    )
+
+    try:
+        _ = dummy_querier.cost(query_json)  # verify cost works
+        response_df = dummy_querier.query(query_json)
+        response = {"query_response": response_df}
+
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content=response)
+
+
+@router.post(
+    "/estimate_opendp_cost",
+    dependencies=[Depends(server_live)],
+    tags=["USER_QUERY"],
+)
+def estimate_opendp_cost(
+    request: Request,
+    query_json: OpenDPInp = Body(example_opendp),
+    user_name: str = Header(None),
+) -> JSONResponse:
+    """
+    Estimates the privacy loss budget cost of an OpenDP query.
+
+    Args:
+        request (Request): Raw request object
+        query_json (OpenDPInp, optional):
+            A JSON object containing the following:
+            - "opendp_pipeline": The OpenDP pipeline for the query.
+
+            Defaults to Body(example_opendp).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist or the
+            pipeline does not contain a measurement.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - epsilon_cost (float): The estimated epsilon cost.
+            - delta_cost (float): The estimated delta cost.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    try:
+        response = app.state.query_handler.estimate_cost(
+            DPLibraries.OPENDP,
+            query_json,
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content=response)
+
+
+@router.post(
+    "/diffprivlib_query",
+    dependencies=[Depends(server_live)],
+    tags=["USER_QUERY"],
+)
+def diffprivlib_query_handler(
+    request: Request,
+    query_json: DiffPrivLibInp = Body(example_diffprivlib),
+    user_name: str = Header(None),
+):
+    """
+    Handles queries for the DiffPrivLib Library.
+
+    Args:
+        request (Request): Raw request object.
+        query_json (OpenDPInp, optional): A JSON object containing the following:
+            - pipeline: The DiffPrivLib pipeline for the query.
+            - feature_columns: the list of feature column to train
+            - target_columns: the list of target column to predict
+            - test_size: proportion of the test set
+            - test_train_split_seed: seed for the random train test split,
+            - imputer_strategy: imputation strategy
+
+            Defaults to Body(example_diffprivlib).
+
+        user_name (str, optional): The user name.
+            Defaults to Header(None).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The pipeline does not contain a "measurement",
+            there is not enough budget or the dataset does not exist.
+        UnauthorizedAccessException: A query is already ongoing for this user,
+            the user does not exist or does not have access to the dataset.
+
+    Returns:
+        JSONResponse: A JSON object containing the following:
+            - requested_by (str): The user name.
+            - query_response (pd.DataFrame): A DataFrame containing
+              the query response.
+            - spent_epsilon (float): The amount of epsilon budget spent
+              for the query.
+            - spent_delta (float): The amount of delta budget spent
+              for the query.
+    """
+    app = request.app
+
+    try:
+        response = app.state.query_handler.handle_query(
+            DPLibraries.DIFFPRIVLIB, query_json, user_name
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return response
+
+
+@router.post(
+    "/dummy_diffprivlib_query",
+    dependencies=[Depends(server_live)],
+    tags=["USER_DUMMY"],
+)
+def dummy_diffprivlib_query_handler(
+    request: Request,
+    query_json: DummyDiffPrivLibInp = Body(example_dummy_diffprivlib),
+    user_name: str = Header(None),
+):
+    """
+    Handles queries on dummy datasets for the DiffPrivLib library.
+
+    Args:
+        request (Request): Raw request object.
+        query_json (DiffPrivLibInp, optional): A JSON object containing the following:
+            - pipeline: The DiffPrivLib pipeline for the query.
+            - feature_columns: the list of feature column to train
+            - target_columns: the list of target column to predict
+            - test_size: proportion of the test set
+            - test_train_split_seed: seed for the random train test split,
+            - imputer_strategy: imputation strategy
+            - nb_rows (int, optional): The number of rows in the dummy dataset
+              (default: 100).
+            - seed (int, optional): The random seed for generating
+              the dummy dataset (default: 42).
+              Defaults to Body(example_dummy_diffprivlib)
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: If there is not enough budget or the dataset
+            does not exist.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - query_response (pd.DataFrame): a DataFrame containing
+              the query response.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    ds_private_dataset = get_dummy_dataset_for_query(
+        app.state.admin_database, query_json
+    )
+    dummy_querier = querier_factory(
+        DPLibraries.DIFFPRIVLIB, private_dataset=ds_private_dataset
+    )
+
+    try:
+        _ = dummy_querier.cost(query_json)  # verify cost works
+        response = dummy_querier.query(query_json)
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return JSONResponse(content={"query_response": response})
+
+
+@router.post(
+    "/estimate_diffprivlib_cost",
+    dependencies=[Depends(server_live)],
+    tags=["USER_QUERY"],
+)
+def estimate_diffprivlib_cost(
+    request: Request,
+    query_json: DiffPrivLibInp = Body(example_diffprivlib),
+    user_name: str = Header(None),
+):
+    """
+    Estimates the privacy loss budget cost of an DiffPrivLib query.
+
+    Args:
+        request (Request): Raw request object
+        query_json (DiffPrivLibInp, optional):
+        A JSON object containing the following:
+            - pipeline: The DiffPrivLib pipeline for the query.
+            - feature_columns: the list of feature column to train
+            - target_columns: the list of target column to predict
+            - test_size: proportion of the test set
+            - test_train_split_seed: seed for the random train test split,
+            - imputer_strategy: imputation strategy
+
+            Defaults to Body(example_dummy_diffprivlib).
+
+    Raises:
+        ExternalLibraryException: For exceptions from libraries
+            external to this package.
+        InternalServerException: For any other unforseen exceptions.
+        InvalidQueryException: The dataset does not exist or the
+            pipeline does not contain a measurement.
+
+    Returns:
+        JSONResponse: A JSON object containing:
+            - epsilon_cost (float): The estimated epsilon cost.
+            - delta_cost (float): The estimated delta cost.
+    """
+    app = request.app
+
+    dataset_name = query_json.dataset_name
+    if not app.state.admin_database.has_user_access_to_dataset(
+        user_name, dataset_name
+    ):
+        raise UnauthorizedAccessException(
+            f"{user_name} does not have access to {dataset_name}.",
+        )
+
+    try:
+        response = app.state.query_handler.estimate_cost(
+            DPLibraries.DIFFPRIVLIB,
+            query_json,
+        )
+    except KNOWN_EXCEPTIONS as e:
+        raise e
+    except Exception as e:
+        raise InternalServerException(e) from e
+
+    return response
diff --git a/server/lomas_server/utils/config.py b/server/lomas_server/utils/config.py
index 484f2949..fd3c1dda 100644
--- a/server/lomas_server/utils/config.py
+++ b/server/lomas_server/utils/config.py
@@ -1,13 +1,12 @@
 import yaml
 from pydantic import BaseModel
-
 from typing_extensions import Dict
 
 from constants import (
-    ConfigKeys,
     CONFIG_PATH,
     SECRETS_PATH,
     AdminDBType,
+    ConfigKeys,
     DatasetStoreType,
     TimeAttackMethod,
 )
diff --git a/server/lomas_server/utils/example_inputs.py b/server/lomas_server/utils/example_inputs.py
index 0da8dadf..0e7aa6b8 100644
--- a/server/lomas_server/utils/example_inputs.py
+++ b/server/lomas_server/utils/example_inputs.py
@@ -119,3 +119,75 @@
     "dummy_nb_rows": DUMMY_NB_ROWS,
     "dummy_seed": DUMMY_SEED,
 }
+
+DIFFPRIVLIB_JSON = (
+    '{"module": "diffprivlib", '
+    '"version": "0.6.4", '
+    '"pipeline": ['
+    "{"
+    '"type": "_dpl_type:StandardScaler", '
+    '"name": "scaler", '
+    '"params": {'
+    '"with_mean": true, '
+    '"with_std": true, '
+    '"copy": true, '
+    '"epsilon": 0.5, '
+    '"bounds": {'
+    '"_tuple": true, '
+    '"_items": [[30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0]]'
+    "}, "
+    '"random_state": null, '
+    '"accountant": "_dpl_instance:BudgetAccountant"'
+    "}"
+    "}, "
+    "{"
+    '"type": "_dpl_type:LogisticRegression", '
+    '"name": "classifier", '
+    '"params": {'
+    '"tol": 0.0001, '
+    '"C": 1.0, '
+    '"fit_intercept": true, '
+    '"random_state": null, '
+    '"max_iter": 100, '
+    '"verbose": 0, '
+    '"warm_start": false, '
+    '"n_jobs": null, '
+    '"epsilon": 1.0, '
+    '"data_norm": 83.69469642643347, '
+    '"accountant": "_dpl_instance:BudgetAccountant"'
+    "}"
+    "}"
+    "]"
+    "}"
+)
+example_diffprivlib = {
+    "dataset_name": PENGUIN_DATASET,
+    "diffprivlib_json": DIFFPRIVLIB_JSON,
+    "feature_columns": [
+        "bill_length_mm",
+        "bill_depth_mm",
+        "flipper_length_mm",
+        "body_mass_g",
+    ],
+    "target_columns": ["species"],
+    "test_size": 0.2,
+    "test_train_split_seed": 1,
+    "imputer_strategy": "drop",
+}
+
+example_dummy_diffprivlib = {
+    "dataset_name": PENGUIN_DATASET,
+    "diffprivlib_json": DIFFPRIVLIB_JSON,
+    "feature_columns": [
+        "bill_length_mm",
+        "bill_depth_mm",
+        "flipper_length_mm",
+        "body_mass_g",
+    ],
+    "target_columns": ["species"],
+    "test_train_split_seed": 1,
+    "test_size": 0.2,
+    "imputer_strategy": "drop",
+    "dummy_nb_rows": DUMMY_NB_ROWS,
+    "dummy_seed": DUMMY_SEED,
+}
diff --git a/server/lomas_server/utils/input_models.py b/server/lomas_server/utils/input_models.py
index 4e5db0b5..34fabba8 100644
--- a/server/lomas_server/utils/input_models.py
+++ b/server/lomas_server/utils/input_models.py
@@ -77,3 +77,29 @@ class DummyOpenDPInp(BaseModel):
     dummy_nb_rows: int
     dummy_seed: int
     fixed_delta: Optional[float] = None
+
+
+class DiffPrivLibInp(BaseModel):
+    """Model input for a diffprivlib query"""
+
+    dataset_name: str
+    diffprivlib_json: str
+    feature_columns: list
+    target_columns: Optional[list]
+    test_size: float
+    test_train_split_seed: int
+    imputer_strategy: str
+
+
+class DummyDiffPrivLibInp(BaseModel):
+    """Model input for a dummy diffprivlib query"""
+
+    dataset_name: str
+    diffprivlib_json: str
+    feature_columns: list
+    target_columns: Optional[list]
+    test_size: float
+    test_train_split_seed: int
+    imputer_strategy: str
+    dummy_nb_rows: int
+    dummy_seed: int
diff --git a/server/mypy.ini b/server/mypy.ini
deleted file mode 100644
index 672cf62b..00000000
--- a/server/mypy.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[mypy]
-disable_error_code = import-untyped, import-not-found, attr-defined
\ No newline at end of file
diff --git a/server/pyproject.toml b/server/pyproject.toml
index 08ff32f8..a5ce0933 100644
--- a/server/pyproject.toml
+++ b/server/pyproject.toml
@@ -1,4 +1,30 @@
 [tool.black]
 line-length = 79
 include = '\.pyi?$'
-reportMissingImports = false
+
+[tool.isort]
+multi_line_output = 3
+include_trailing_comma = true
+force_grid_wrap = 0
+use_parentheses = true
+ensure_newline_before_comments = true
+line_length = 79
+split_on_trailing_comma = true
+src_paths = ["lomas_server"]
+
+[tool.mypy]
+disable_error_code = ["import-untyped", "import-not-found", "attr-defined"]
+
+[tool.pylint.MASTER]
+ignore-patterns = "test_*.py"
+
+[tool.pylint."MESSAGES CONTROL"]
+disable = [
+    "E0401",  # import-error
+    "C0114",  # missing-module-docstring
+    "R0903",  # too-few-public-methods
+    "E0611",  # no-name-in-module
+    "W0511",  # fixme
+    "C0302",  # too-many-lines
+    "duplicate-code"  # duplicate-code
+]
diff --git a/server/requirements.txt b/server/requirements.txt
index 0b615a40..963b18c0 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -1,11 +1,14 @@
 boto3==1.34.115
+diffprivlib==0.6.4
+diffprivlib_logger==0.0.3
 fastapi==0.109.1
 httpx==0.27.0
-numpy==1.26.4
+numpy==1.23.2
 opendp==0.8.0
 opendp-logger==0.3.0
 pandas==2.2.2
 pyaml==23.9.5
 pymongo==4.6.3
+scikit-learn==1.4.0
 smartnoise-sql==1.0.3
-uvicorn==0.29.0
+uvicorn==0.29.0
\ No newline at end of file


Test patch:
diff --git a/.github/workflows/test_and_coverage_server.yml b/.github/workflows/test_and_coverage_server.yml
index 0394505e..01083f6c 100644
--- a/.github/workflows/test_and_coverage_server.yml
+++ b/.github/workflows/test_and_coverage_server.yml
@@ -1,4 +1,4 @@
-name: Server Integration Testing
+name: Server Tests
 
 on: 
   pull_request:
@@ -38,4 +38,6 @@ jobs:
       uses: py-cov-action/python-coverage-comment-action@v3
       with:
         GITHUB_TOKEN: ${{ secrets.COVERAGE_REPORT }}
-        COVERAGE_PATH: server/lomas_server/
\ No newline at end of file
+        COVERAGE_PATH: server/lomas_server/
+        MINIMUM_GREEN: 95
+        MINIMUM_ORANGE: 75
\ No newline at end of file
diff --git a/.github/workflows/test_linters.yml b/.github/workflows/test_linters.yml
index ffca8830..d58e6cb3 100644
--- a/.github/workflows/test_linters.yml
+++ b/.github/workflows/test_linters.yml
@@ -39,6 +39,6 @@ jobs:
       - name: pylint
         run: |
           cd client/lomas_client
-          pylint --disable=E0401 --disable=C0114 --disable=W0102 --disable=R0913 .
+          pylint .
           cd ../../server/lomas_server
-          pylint --disable=E0401 --disable=C0114 --disable=R0903 --disable=E0611 --disable=W0511 --disable=duplicate-code tests/ .
+          pylint .
diff --git a/client/tests/test_opendp.py b/client/tests/test_opendp.py
deleted file mode 100644
index 45e5ba54..00000000
--- a/client/tests/test_opendp.py
+++ /dev/null
@@ -1,92 +0,0 @@
-import opendp.transformations as trans
-from opendp.mod import enable_features
-from opendp_logger import enable_logging, make_load_json
-
-enable_logging()
-enable_features("contrib")
-
-
-def test_serialize() -> None:
-    # Random pipeline of transformation
-    pipeline = (
-        # Convert data into a dataframe where columns are of type Vec<str>
-        trans.make_split_dataframe(separator=",", col_names=["hello", "world"])
-        >>
-        # Selects a column of df, Vec<str>
-        trans.make_select_column(key="income", TOA=str)
-    )
-
-    # Ast to json to be sent
-    json_obj = pipeline.to_json()
-
-    expected_json = (
-        '{"version": "0.8.0", '
-        '"ast": {'
-        '"_type": "constructor", '
-        '"func": "make_chain_tt", '
-        '"module": "combinators", '
-        '"args": ['
-        "{"
-        '"_type": "constructor", '
-        '"func": "make_select_column", '
-        '"module": "transformations", '
-        '"kwargs": {"key": "income", "TOA": "String"}'
-        "}, "
-        "{"
-        '"_type": "constructor", '
-        '"func": "make_split_dataframe", '
-        '"module": "transformations", '
-        '"kwargs": {"separator": ",", "col_names": {'
-        '"_type": "list", '
-        '"_items": ["hello", "world"]'
-        "}}"
-        "}"
-        "]"
-        "}"
-        "}"
-    )
-
-    # Assert expected value
-    assert json_obj == expected_json
-
-
-def test_deserialize() -> None:
-    pipeline_json = (
-        '{"version": "0.8.0", '
-        '"ast": {'
-        '"_type": "constructor", '
-        '"func": "make_chain_tt", '
-        '"module": "combinators", '
-        '"args": ['
-        "{"
-        '"_type": "constructor", '
-        '"func": "make_select_column", '
-        '"module": "transformations", '
-        '"kwargs": {"key": "income", "TOA": "String"}'
-        "}, "
-        "{"
-        '"_type": "constructor", '
-        '"func": "make_split_dataframe", '
-        '"module": "transformations", '
-        '"kwargs": {"separator": ",", "col_names": {'
-        '"_type": "list", '
-        '"_items": ["hello", "world"]'
-        "}}"
-        "}"
-        "]"
-        "}"
-        "}"
-    )
-
-    pipeline = make_load_json(pipeline_json)
-
-    expected_pipeline = (
-        # Convert data into a dataframe where columns are of type Vec<str>
-        trans.make_split_dataframe(separator=",", col_names=["hello", "world"])
-        >>
-        # Selects a column of df, Vec<str>
-        trans.make_select_column(key="income", TOA=str)
-    )
-
-    # Assert expected value
-    assert str(pipeline) == str(expected_pipeline)
diff --git a/server/lomas_server/tests/test_api.py b/server/lomas_server/tests/test_api.py
index 7b487815..fec7db85 100644
--- a/server/lomas_server/tests/test_api.py
+++ b/server/lomas_server/tests/test_api.py
@@ -3,22 +3,22 @@
 import unittest
 from io import StringIO
 
+import opendp.prelude as dp_p
+import pandas as pd
 from fastapi import status
 from fastapi.testclient import TestClient
 from opendp.mod import enable_features
-import opendp.prelude as dp_p
 from opendp_logger import enable_logging
-import pandas as pd
 from pymongo.database import Database
 
-from admin_database.utils import get_mongodb, database_factory
+from admin_database.utils import database_factory, get_mongodb
+from app import app
+from constants import EPSILON_LIMIT, DatasetStoreType, DPLibraries
 from mongodb_admin import (
     add_datasets_via_yaml,
     add_users_via_yaml,
     drop_collection,
 )
-from app import app
-from constants import DatasetStoreType, EPSILON_LIMIT, DPLibraries
 from tests.constants import (
     ENV_MONGO_INTEGRATION,
     ENV_S3_INTEGRATION,
@@ -39,6 +39,7 @@
     example_smartnoise_sql,
     example_smartnoise_sql_cost,
 )
+from utils.loggr import LOG
 
 INITAL_EPSILON = 10
 INITIAL_DELTA = 0.005
@@ -75,7 +76,7 @@ def tearDownClass(cls) -> None:
         pass
 
     def setUp(self) -> None:
-        """_summary_"""
+        """Set Up Header and DB for test"""
         self.user_name = "Dr. Antartica"
         self.dataset = PENGUIN_DATASET
         self.headers = {
@@ -208,7 +209,20 @@ def test_get_dataset_metadata(self) -> None:
             assert response.status_code == status.HTTP_400_BAD_REQUEST
             assert response.json() == {
                 "InvalidQueryException": f"Dataset {fake_dataset} does not "
-                + "exists. Please, verify the client object initialisation."
+                + "exist. Please, verify the client object initialisation."
+            }
+
+            # Expect to fail: user does have access to dataset
+            other_dataset = "IRIS"
+            response = client.post(
+                "/get_dataset_metadata",
+                json={"dataset_name": other_dataset},
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to {other_dataset}."
             }
 
     def test_get_dummy_dataset(self) -> None:
@@ -216,7 +230,9 @@ def test_get_dummy_dataset(self) -> None:
         with TestClient(app) as client:
             # Expect to work
             response = client.post(
-                "/get_dummy_dataset", json=example_get_dummy_dataset
+                "/get_dummy_dataset",
+                json=example_get_dummy_dataset,
+                headers=self.headers,
             )
             assert response.status_code == status.HTTP_200_OK
 
@@ -243,7 +259,7 @@ def test_get_dummy_dataset(self) -> None:
             assert response.status_code == status.HTTP_400_BAD_REQUEST
             assert response.json() == {
                 "InvalidQueryException": f"Dataset {fake_dataset} does not "
-                + "exists. Please, verify the client object initialisation."
+                + "exist. Please, verify the client object initialisation."
             }
 
             # Expect to fail: missing argument dummy_nb_rows
@@ -256,6 +272,38 @@ def test_get_dummy_dataset(self) -> None:
             )
             assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
 
+            # Expect to fail: user does have access to dataset
+            other_dataset = "IRIS"
+            response = client.post(
+                "/get_dummy_dataset",
+                json={
+                    "dataset_name": other_dataset,
+                    "dummy_nb_rows": DUMMY_NB_ROWS,
+                    "dummy_seed": 0,
+                },
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to {other_dataset}."
+            }
+
+            # Expect to fail: user does not exist
+            fake_user = "fake_user"
+            new_headers = self.headers
+            new_headers["user-name"] = fake_user
+            response = client.post(
+                "/get_dummy_dataset",
+                json=example_get_dummy_dataset,
+                headers=new_headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": f"User {fake_user} does not "
+                + "exist. Please, verify the client object initialisation."
+            }
+
     def test_smartnoise_query(self) -> None:
         """Test smartnoise-sql query"""
         with TestClient(app, headers=self.headers) as client:
@@ -355,7 +403,7 @@ def test_smartnoise_query(self) -> None:
             assert response.status_code == status.HTTP_400_BAD_REQUEST
             assert response.json() == {
                 "InvalidQueryException": ""
-                + "Dataset I_do_not_exist does not exists. "
+                + "Dataset I_do_not_exist does not exist. "
                 + "Please, verify the client object initialisation."
             }
 
@@ -401,7 +449,9 @@ def test_dummy_smartnoise_query(self) -> None:
         with TestClient(app) as client:
             # Expect to work
             response = client.post(
-                "/dummy_smartnoise_query", json=example_dummy_smartnoise_sql
+                "/dummy_smartnoise_query",
+                json=example_dummy_smartnoise_sql,
+                headers=self.headers,
             )
             assert response.status_code == status.HTTP_200_OK
 
@@ -410,12 +460,38 @@ def test_dummy_smartnoise_query(self) -> None:
             assert response_dict["query_response"]["data"][0][0] > 0
             assert response_dict["query_response"]["data"][0][0] < 200
 
+            # Should fail: no header
+            response = client.post(
+                "/dummy_smartnoise_query", json=example_dummy_smartnoise_sql
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": "User None does not exist."
+                + " Please, verify the client object initialisation."
+            }
+
+            # Should fail: user does not have access to dataset
+            body = dict(example_dummy_smartnoise_sql)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/dummy_smartnoise_query",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
+
     def test_smartnoise_cost(self) -> None:
         """test_smartnoise_cost"""
         with TestClient(app) as client:
             # Expect to work
             response = client.post(
-                "/estimate_smartnoise_cost", json=example_smartnoise_sql_cost
+                "/estimate_smartnoise_cost",
+                json=example_smartnoise_sql_cost,
+                headers=self.headers,
             )
             assert response.status_code == status.HTTP_200_OK
 
@@ -423,6 +499,20 @@ def test_smartnoise_cost(self) -> None:
             assert response_dict["epsilon_cost"] == SMARTNOISE_QUERY_EPSILON
             assert response_dict["delta_cost"] > SMARTNOISE_QUERY_DELTA
 
+            # Should fail: user does not have access to dataset
+            body = dict(example_smartnoise_sql_cost)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/estimate_smartnoise_cost",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
+
     def test_opendp_query(self) -> None:  # pylint: disable=R0915
         """test_opendp_query"""
         enable_logging()
@@ -572,18 +662,36 @@ def test_dummy_opendp_query(self) -> None:
         with TestClient(app) as client:
             # Expect to work
             response = client.post(
-                "/dummy_opendp_query", json=example_dummy_opendp
+                "/dummy_opendp_query",
+                json=example_dummy_opendp,
+                headers=self.headers,
             )
             assert response.status_code == status.HTTP_200_OK
             response_dict = json.loads(response.content.decode("utf8"))
             assert response_dict["query_response"] > 0
 
+            # Should fail: user does not have access to dataset
+            body = dict(example_dummy_opendp)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/dummy_opendp_query",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
+
     def test_opendp_cost(self) -> None:
         """test_opendp_cost"""
         with TestClient(app) as client:
             # Expect to work
             response = client.post(
-                "/estimate_opendp_cost", json=example_opendp
+                "/estimate_opendp_cost",
+                json=example_opendp,
+                headers=self.headers,
             )
             assert response.status_code == status.HTTP_200_OK
 
@@ -591,6 +699,20 @@ def test_opendp_cost(self) -> None:
             assert response_dict["epsilon_cost"] > 0.1
             assert response_dict["delta_cost"] == 0
 
+            # Should fail: user does not have access to dataset
+            body = dict(example_opendp)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/estimate_opendp_cost",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
+
     def test_get_initial_budget(self) -> None:
         """test_get_initial_budget"""
         with TestClient(app, headers=self.headers) as client:
@@ -716,6 +838,9 @@ def test_get_previous_queries(self) -> None:
             response_2 = client.post(
                 "/get_previous_queries", json=example_get_admin_db_data
             )
+            LOG.error(response_2)
+            response_dict = json.loads(response.content.decode("utf8"))
+            LOG.error(response_dict)
             assert response_2.status_code == status.HTTP_200_OK
 
             response_dict_2 = json.loads(response_2.content.decode("utf8"))
diff --git a/server/lomas_server/tests/test_api_diffprivlib.py b/server/lomas_server/tests/test_api_diffprivlib.py
new file mode 100644
index 00000000..94db1846
--- /dev/null
+++ b/server/lomas_server/tests/test_api_diffprivlib.py
@@ -0,0 +1,378 @@
+import json
+import warnings
+
+from diffprivlib import models
+from diffprivlib.utils import (
+    DiffprivlibCompatibilityWarning,
+    PrivacyLeakWarning,
+)
+from diffprivlib_logger import serialise_pipeline
+from fastapi import status
+from fastapi.testclient import TestClient
+from sklearn.pipeline import Pipeline
+
+from app import app
+from constants import DPLibraries
+from tests.test_api import TestRootAPIEndpoint
+from utils.example_inputs import example_diffprivlib, example_dummy_diffprivlib
+
+
+def validate_pipeline(response):
+    """Validate that the pipeline ran successfully
+    Returns a model and a score.
+    """
+    assert response.status_code == status.HTTP_200_OK
+    response_dict = json.loads(response.content.decode("utf8"))
+    assert response_dict["query_response"]["score"]
+    assert response_dict["query_response"]["model"]
+
+
+class TestDiffPrivLibEndpoint(TestRootAPIEndpoint):  # pylint: disable=R0904
+    """
+    Test DiffPrivLib Endpoint with different models
+    """
+
+    def test_diffprivlib_query(self) -> None:
+        """Test diffprivlib query"""
+        with TestClient(app, headers=self.headers) as client:
+            # Expect to work
+            response = client.post(
+                "/diffprivlib_query",
+                json=example_diffprivlib,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_200_OK
+
+            response_dict = json.loads(response.content.decode("utf8"))
+            assert response_dict["requested_by"] == self.user_name
+            assert response_dict["query_response"]["score"] >= 0
+            assert response_dict["query_response"]["model"]
+            assert response_dict["spent_epsilon"] > 0
+            assert response_dict["spent_delta"] == 0
+
+            # # Should work for different imputation strategy (but does not yet #255)
+            def test_imputation(diffprivlib_body, imputer_strategy):
+                diffprivlib_body = dict(diffprivlib_body)
+                diffprivlib_body["imputer_strategy"] = imputer_strategy
+                response = client.post(
+                    "/diffprivlib_query",
+                    json=diffprivlib_body,
+                    headers=self.headers,
+                )
+                return response
+
+            response = test_imputation(example_diffprivlib, "mean")
+            response_dict = json.loads(response.content.decode("utf8"))
+            assert response.status_code == status.HTTP_200_OK
+
+            response = test_imputation(example_diffprivlib, "median")
+            assert response.status_code == status.HTTP_200_OK
+
+            response = test_imputation(example_diffprivlib, "most_frequent")
+            assert response.status_code == status.HTTP_200_OK
+
+            # Should not work unknow imputation strategy
+            response = test_imputation(example_diffprivlib, "i_do_not_exist")
+            assert response.status_code == status.HTTP_400_BAD_REQUEST
+            assert response.json() == {
+                "InvalidQueryException": ""
+                + "Imputation strategy i_do_not_exist not supported."
+            }
+
+            # Should not work: Privacy Leak Warning
+            warnings.simplefilter("error", PrivacyLeakWarning)
+            diffprivlib_body = dict(example_diffprivlib)
+            dpl_pipeline = Pipeline(
+                [
+                    ("scaler", models.StandardScaler(epsilon=0.5)),
+                    ("classifier", models.LogisticRegression(epsilon=1.0)),
+                ]
+            )
+            dpl_string = serialise_pipeline(dpl_pipeline)
+            diffprivlib_body["diffprivlib_json"] = dpl_string
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
+            assert response.json() == {
+                "ExternalLibraryException": "PrivacyLeakWarning: "
+                + "Bounds parameter hasn't been specified, so falling back to "
+                + "determining bounds from the data.\n "
+                + "This will result in additional privacy leakage.  "
+                + "To ensure differential privacy with no additional privacy "
+                + "loss, specify `bounds` for each valued returned by "
+                + "np.mean().. "
+                + "Lomas server cannot fit pipeline on data, "
+                + "PrivacyLeakWarning is a blocker.",
+                "library": DPLibraries.DIFFPRIVLIB,
+            }
+
+            # Should not work: Compatibility Warning
+            warnings.simplefilter("error", DiffprivlibCompatibilityWarning)
+            with self.assertRaises(DiffprivlibCompatibilityWarning):
+                Pipeline(
+                    [
+                        ("scaler", models.StandardScaler(epsilon=0.5)),
+                        (
+                            "classifier",
+                            models.LogisticRegression(
+                                epsilon=1.0, svd_solver="full"
+                            ),
+                        ),
+                    ]
+                )
+
+    def test_logistic_regression_models(self) -> None:
+        """Test diffprivlib query: Logistic Regression"""
+        with TestClient(app, headers=self.headers) as client:
+            bounds = ([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])
+
+            # Test Logistic Regression
+            pipeline = Pipeline(
+                [
+                    (
+                        "scaler",
+                        models.StandardScaler(epsilon=0.5, bounds=bounds),
+                    ),
+                    (
+                        "classifier",
+                        models.LogisticRegression(
+                            epsilon=1.0, data_norm=83.69
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_linear_regression_models(self) -> None:
+        """Test diffprivlib query: Linear Regression"""
+        with TestClient(app, headers=self.headers) as client:
+
+            # Test Linear Regression
+            pipeline = Pipeline(
+                [
+                    (
+                        "lr",
+                        models.LinearRegression(
+                            epsilon=2.0,
+                            bounds_X=(30.0, 65.0),
+                            bounds_y=(13.0, 23.0),
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            diffprivlib_body["feature_columns"] = ["bill_length_mm"]
+            diffprivlib_body["target_columns"] = ["bill_length_mm"]
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_naives_bayes_model(self) -> None:
+        """Test diffprivlib query: Gaussian Naives Bayes"""
+        with TestClient(app, headers=self.headers) as client:
+            bounds = ([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])
+            # Test Gaussian Naives Bayes
+            pipeline = Pipeline(
+                [
+                    (
+                        "scaler",
+                        models.StandardScaler(epsilon=0.5, bounds=bounds),
+                    ),
+                    (
+                        "gaussian",
+                        models.GaussianNB(
+                            epsilon=1.0, bounds=bounds, priors=(0.3, 0.3, 0.4)
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_trees_models(self) -> None:
+        """Test diffprivlib query: Random Forest, Decision Tree"""
+        with TestClient(app, headers=self.headers) as client:
+            bounds = ([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])
+
+            # Test Random Forest
+            pipeline = Pipeline(
+                [
+                    (
+                        "rf",
+                        models.RandomForestClassifier(
+                            n_estimators=10,
+                            epsilon=2.0,
+                            bounds=bounds,
+                            classes=["Adelie", "Chinstrap", "Gentoo"],
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+            # Test Decision Tree Classifier
+            pipeline = Pipeline(
+                [
+                    (
+                        "dtc",
+                        models.DecisionTreeClassifier(
+                            epsilon=2.0,
+                            bounds=bounds,
+                            classes=["Adelie", "Chinstrap", "Gentoo"],
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_clustering_models(self) -> None:
+        """Test diffprivlib query: K-Means"""
+        with TestClient(app, headers=self.headers) as client:
+            bounds = ([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])
+
+            # Test K-MEANS
+            pipeline = Pipeline(
+                [
+                    (
+                        "kmeans",
+                        models.KMeans(
+                            n_clusters=8, epsilon=2.0, bounds=bounds
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+            diffprivlib_body["target_columns"] = None
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_dimension_reduction_models(self) -> None:
+        """Test diffprivlib query: PCA"""
+        with TestClient(app, headers=self.headers) as client:
+            bounds = ([30.0, 13.0, 150.0, 2000.0], [65.0, 23.0, 250.0, 7000.0])
+            # Test PCA
+            pipeline = Pipeline(
+                [
+                    (
+                        "pca",
+                        models.PCA(
+                            n_components=8,
+                            epsilon=2.0,
+                            bounds=bounds,
+                            data_norm=100,
+                        ),
+                    ),
+                ]
+            )
+            diffprivlib_body = dict(example_diffprivlib)
+            diffprivlib_body["diffprivlib_json"] = serialise_pipeline(pipeline)
+            response = client.post(
+                "/diffprivlib_query",
+                json=diffprivlib_body,
+                headers=self.headers,
+            )
+            validate_pipeline(response)
+
+    def test_dummy_diffprivlib_query(self) -> None:
+        """test_dummy_diffprivlib_query"""
+        with TestClient(app) as client:
+            # Expect to work
+            response = client.post(
+                "/dummy_diffprivlib_query",
+                json=example_dummy_diffprivlib,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_200_OK
+
+            response_dict = json.loads(response.content.decode("utf8"))
+            assert response_dict["query_response"]["score"] > 0
+            assert response_dict["query_response"]["model"]
+
+            # Expect to fail: user does have access to dataset
+            body = dict(example_dummy_diffprivlib)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/dummy_diffprivlib_query",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
+
+    def test_diffprivlib_cost(self) -> None:
+        """test_diffprivlib_cost"""
+        with TestClient(app) as client:
+            # Expect to work
+            response = client.post(
+                "/estimate_diffprivlib_cost",
+                json=example_diffprivlib,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_200_OK
+
+            response_dict = json.loads(response.content.decode("utf8"))
+            assert response_dict["epsilon_cost"] == 1.5
+            assert response_dict["delta_cost"] == 0
+
+            # Expect to fail: user does have access to dataset
+            body = dict(example_diffprivlib)
+            body["dataset_name"] = "IRIS"
+            response = client.post(
+                "/estimate_diffprivlib_cost",
+                json=body,
+                headers=self.headers,
+            )
+            assert response.status_code == status.HTTP_403_FORBIDDEN
+            assert response.json() == {
+                "UnauthorizedAccessException": ""
+                + f"{self.user_name} does not have access to IRIS."
+            }
diff --git a/server/lomas_server/tests/test_mongodb_admin.py b/server/lomas_server/tests/test_mongodb_admin.py
index b0cbad34..b238526c 100644
--- a/server/lomas_server/tests/test_mongodb_admin.py
+++ b/server/lomas_server/tests/test_mongodb_admin.py
@@ -8,6 +8,7 @@
 from pymongo import MongoClient
 
 from admin_database.utils import get_mongodb_url
+from constants import PrivateDatabaseType
 from mongodb_admin import (
     add_dataset,
     add_dataset_to_user,
@@ -30,12 +31,11 @@
     show_metadata_of_dataset,
     show_user,
 )
-from constants import PrivateDatabaseType
 from tests.constants import (
     ENV_MONGO_INTEGRATION,
     ENV_S3_INTEGRATION,
-    TRUE_VALUES,
     FALSE_VALUES,
+    TRUE_VALUES,
 )
 from utils.config import CONFIG_LOADER, get_config
 from utils.utils import add_demo_data_to_admindb