Spaces:
Sleeping
Sleeping
github-actions[bot]
commited on
Commit
·
c0ba94e
1
Parent(s):
9791c30
Sync from main@8265c31
Browse files- Dockerfile +2 -1
- README.md +277 -99
- predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/drift_runner.py +145 -0
- predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/production_data_collector.py +44 -0
- predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/scheduler.py +55 -0
- predicting_outcomes_in_heart_failure/app/main.py +10 -0
- predicting_outcomes_in_heart_failure/app/monitoring.py +95 -0
- predicting_outcomes_in_heart_failure/app/routers/prediction.py +123 -35
- predicting_outcomes_in_heart_failure/config.py +18 -0
- prometheus.yml +16 -0
- pyproject.toml +8 -2
- uv.lock +538 -33
Dockerfile
CHANGED
|
@@ -28,7 +28,8 @@ RUN uv sync --locked --no-install-project
|
|
| 28 |
# copy the rest of the files needed for inference
|
| 29 |
COPY --chown=user . .
|
| 30 |
|
| 31 |
-
RUN
|
|
|
|
| 32 |
|
| 33 |
EXPOSE 7860
|
| 34 |
|
|
|
|
| 28 |
# copy the rest of the files needed for inference
|
| 29 |
COPY --chown=user . .
|
| 30 |
|
| 31 |
+
RUN sed -i 's/\r$//' predicting_outcomes_in_heart_failure/app/entrypoint.sh \
|
| 32 |
+
&& chmod +x predicting_outcomes_in_heart_failure/app/entrypoint.sh
|
| 33 |
|
| 34 |
EXPOSE 7860
|
| 35 |
|
README.md
CHANGED
|
@@ -6,126 +6,256 @@ colorTo: gray
|
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
---
|
|
|
|
| 9 |
# Predicting Outcomes in Heart Failure
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
## Table of Contents
|
| 12 |
-
1. [Project
|
| 13 |
-
2. [
|
| 14 |
-
3. [
|
| 15 |
-
4. [
|
|
|
|
| 16 |
- [Milestone 1 - Inception](#milestone-1---inception)
|
| 17 |
- [Milestone 2 - Reproducibility](#milestone-2---reproducibility)
|
| 18 |
- [Milestone 3 - Quality Assurance](#milestone-3---quality-assurance)
|
| 19 |
- [Milestone 4 - API Integration](#milestone-4---API-Integration)
|
| 20 |
- [Milestone 5 - Deployment](#milestone-5---Deployment)
|
|
|
|
| 21 |
|
| 22 |
-
## Project
|
| 23 |
-
<a target="_blank" href="https://cookiecutter-data-science.drivendata.org/">
|
| 24 |
-
<img src="https://img.shields.io/badge/CCDS-Project%20template-328F97?logo=cookiecutter" />
|
| 25 |
-
</a>
|
| 26 |
|
| 27 |
-
|
| 28 |
-
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/pynblint.yml)
|
| 29 |
-
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/pytestAndGX.yml)
|
| 30 |
-
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/deploy.yml)
|
| 31 |
-
[](https://www.python.org/)
|
| 32 |
|
|
|
|
| 33 |
|
| 34 |
-
|
|
|
|
| 35 |
|
| 36 |
-
|
|
|
|
|
|
|
|
|
|
| 37 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 38 |
```
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
├── docs <- A default mkdocs project; see www.mkdocs.org for details
|
| 49 |
-
│
|
| 50 |
-
├── models <- Trained and serialized models, model predictions, or model summaries
|
| 51 |
-
│
|
| 52 |
-
├── notebooks <- Jupyter notebooks. Naming convention is a number (for ordering),
|
| 53 |
-
│ the creator's initials, and a short `-` delimited description, e.g.
|
| 54 |
-
│ `1.0-jqp-initial-data-exploration`.
|
| 55 |
-
│
|
| 56 |
-
├── pyproject.toml <- Project configuration file with package metadata for
|
| 57 |
-
│ predicting_outcomes_in_heart_failure and configuration for tools like black
|
| 58 |
-
│
|
| 59 |
-
├── references <- Data dictionaries, manuals, and all other explanatory materials.
|
| 60 |
-
│
|
| 61 |
-
├── reports <- Generated analysis as HTML, PDF, LaTeX, etc.
|
| 62 |
-
│ └── figures <- Generated graphics and figures to be used in reporting
|
| 63 |
-
│
|
| 64 |
-
├── requirements.txt <- The requirements file for reproducing the analysis environment, e.g.
|
| 65 |
-
│ generated with `pip freeze > requirements.txt`
|
| 66 |
-
│
|
| 67 |
-
├── setup.cfg <- Configuration file for flake8
|
| 68 |
-
│
|
| 69 |
-
└── predicting_outcomes_in_heart_failure <- Source code for use in this project.
|
| 70 |
-
│
|
| 71 |
-
├── __init__.py <- Makes predicting_outcomes_in_heart_failure a Python module
|
| 72 |
-
│
|
| 73 |
-
├── config.py <- Store useful variables and configuration
|
| 74 |
-
│
|
| 75 |
-
├── data
|
| 76 |
-
│ ├── __init__.py
|
| 77 |
-
│ ├── dataset.py <- Scripts to download or generate data
|
| 78 |
-
| ├── preprocess.py <- Data preprocessing code
|
| 79 |
-
│ └── split_data.py <- Split dataset into train and test code
|
| 80 |
-
│
|
| 81 |
-
├── features.py <- Code to create features for modeling
|
| 82 |
-
│
|
| 83 |
-
├── modeling
|
| 84 |
-
│ ├── __init__.py
|
| 85 |
-
│ ├── predict.py <- Code to run model inference with trained models
|
| 86 |
-
│ └── train.py <- Code to train models
|
| 87 |
-
│
|
| 88 |
-
└── plots.py <- Code to create visualizations
|
| 89 |
```
|
| 90 |
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
```
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
*
|
| 99 |
-
+---------------+
|
| 100 |
-
| preprocessing |
|
| 101 |
-
+---------------+
|
| 102 |
-
*
|
| 103 |
-
*
|
| 104 |
-
*
|
| 105 |
-
+------------+
|
| 106 |
-
| split_data |
|
| 107 |
-
+------------+
|
| 108 |
-
*** ***
|
| 109 |
-
* *
|
| 110 |
-
** ***
|
| 111 |
-
+----------+ *
|
| 112 |
-
| training | ***
|
| 113 |
-
+----------+ *
|
| 114 |
-
*** ***
|
| 115 |
-
* *
|
| 116 |
-
** **
|
| 117 |
-
+------------+
|
| 118 |
-
| evaluation |
|
| 119 |
-
+------------+
|
| 120 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 123 |
|
| 124 |
### Milestone 1 - Inception
|
| 125 |
During this milestone, the **CCDS Project Template** was used as the foundation for organizing the project.
|
| 126 |
The main conceptual and structural components of the system were defined, following the template guidelines to ensure consistency and traceability.
|
| 127 |
|
| 128 |
-
Additionally, a **Machine Learning Canvas** has been added
|
| 129 |
It outlines the model objectives, the data to be used, and the key methodological aspects planned for the next phases of the project.
|
| 130 |
|
| 131 |
### Milestone 2 - Reproducibility
|
|
@@ -141,7 +271,7 @@ We initialized **DVC** and configured a full pipeline to automate the main steps
|
|
| 141 |
- **Data splitting**
|
| 142 |
- **Training** and **evaluation**
|
| 143 |
|
| 144 |
-
The pipeline is fully reproducible and version-controlled through DVC.
|
| 145 |
|
| 146 |
#### Model Training and Experiment Tracking
|
| 147 |
We implemented the **training scripts** and integrated **MLflow** for experiment tracking.
|
|
@@ -150,7 +280,7 @@ Three models are trained and evaluated within this workflow:
|
|
| 150 |
- Random Forest
|
| 151 |
- Logistic Regression
|
| 152 |
|
| 153 |
-
Each experiment is logged to MLflow.
|
| 154 |
|
| 155 |
#### Model Registry and Thresholds
|
| 156 |
Models that reach or exceed the predefined **performance thresholds** (as defined in the ML Canvas) are automatically **saved to the model registry**.
|
|
@@ -172,10 +302,18 @@ These validations help to:
|
|
| 172 |
|
| 173 |
- detect anomalies or invalid values at the data source
|
| 174 |
- prevent the propagation of data issues into downstream processes
|
|
|
|
| 175 |
|
| 176 |
-
####
|
|
|
|
|
|
|
| 177 |
We added automated **unit and integration tests** using **pytest**, covering the main modules and functionalities of the system.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
|
|
|
|
| 179 |
|
| 180 |
#### ML Pipeline Enhancements
|
| 181 |
we applied the following enhancements to the ML pipeline:
|
|
@@ -197,7 +335,7 @@ We applied an explainability module:
|
|
| 197 |
|
| 198 |
#### Risk Classification
|
| 199 |
We added a **Risk Classification** analysis for the system in accordance with **IMDRF** and **AI Act** regulations.
|
| 200 |
-
The
|
| 201 |
|
| 202 |
|
| 203 |
### Milestone 4 - API Integration
|
|
@@ -271,8 +409,48 @@ The overall codebase quality was improved through automated linting and formatti
|
|
| 271 |
- Introduction of *pytest* for automated testing.
|
| 272 |
- Integration of *Great Expectations* for automated data quality checks.
|
| 273 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
#### Continuos Deployment
|
| 276 |
Automated deployment to *Hugging Face* was implemented through Github Actions workflow
|
| 277 |
*Hugging Face Space*: [Check Here](https://huggingface.co/spaces/CardioTrack/CardioTrack)
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
sdk: docker
|
| 7 |
app_port: 7860
|
| 8 |
---
|
| 9 |
+
|
| 10 |
# Predicting Outcomes in Heart Failure
|
| 11 |
+
<a target="_blank" href="https://cookiecutter-data-science.drivendata.org/"><img src="https://img.shields.io/badge/CCDS-Project%20template-328F97?logo=cookiecutter" /></a>
|
| 12 |
+
[](https://www.python.org/)
|
| 13 |
+
[](https://huggingface.co/spaces/CardioTrack/CardioTrack)
|
| 14 |
+
[](https://uptime.betterstack.com/?utm_source=status_badge)
|
| 15 |
+
|
| 16 |
+
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/ruff-linter.yml)
|
| 17 |
+
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/pynblint.yml)
|
| 18 |
+
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/pytestAndGX.yml)
|
| 19 |
+
[](https://github.com/se4ai2526-uniba/CardioTrack/actions/workflows/deploy.yml)
|
| 20 |
|
| 21 |
## Table of Contents
|
| 22 |
+
1. [Project Summary](#project-summary)
|
| 23 |
+
2. [Quick Start Guide](#quick-start-guide)
|
| 24 |
+
3. [Project Organization](#project-organization)
|
| 25 |
+
4. [CardioTrack Architecture](#cardiotrack-architecture)
|
| 26 |
+
5. [Milestones Description](#milestones-description)
|
| 27 |
- [Milestone 1 - Inception](#milestone-1---inception)
|
| 28 |
- [Milestone 2 - Reproducibility](#milestone-2---reproducibility)
|
| 29 |
- [Milestone 3 - Quality Assurance](#milestone-3---quality-assurance)
|
| 30 |
- [Milestone 4 - API Integration](#milestone-4---API-Integration)
|
| 31 |
- [Milestone 5 - Deployment](#milestone-5---Deployment)
|
| 32 |
+
- [Milestone 6 - Monitoring](#milestone-6---Monitoring)
|
| 33 |
|
| 34 |
+
## Project Summary
|
|
|
|
|
|
|
|
|
|
| 35 |
|
| 36 |
+
This project develops a complete, reproducible pipeline for predicting patient outcomes in heart failure, leveraging a publicly available clinical dataset. It addresses the challenges of heterogeneous data and ensures consistent preprocessing, model training, and evaluation, with a strong focus on transparency, reliability, and clinical relevance. The system provides explainable predictions and risk classifications, making it both interpretable and trustworthy. A user-friendly interface allows easy interaction with the models, and the entire pipeline is deployed on a publicly accessible [Hugging Face Space](https://huggingface.co/spaces/CardioTrack/CardioTrack). Below, an example of interaction with the system is shown:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
+

|
| 39 |
|
| 40 |
+
## Quick Start Guide
|
| 41 |
+
### Prerequisites
|
| 42 |
|
| 43 |
+
- **Python 3.11**
|
| 44 |
+
- **uv** - Fast Python package manager ([Official website](https://docs.astral.sh/uv/getting-started/installation/))
|
| 45 |
+
- **DVC** - Data Version Control ([Official website](https://dvc.org/))
|
| 46 |
+
- **Docker** ([Official website](https://www.docker.com/))
|
| 47 |
|
| 48 |
+
### 1. Clone the Repository
|
| 49 |
+
|
| 50 |
+
```bash
|
| 51 |
+
git clone https://github.com/se4ai2526-uniba/CardioTrack.git
|
| 52 |
+
cd CardioTrack
|
| 53 |
```
|
| 54 |
+
|
| 55 |
+
### 2. Environment Variables
|
| 56 |
+
|
| 57 |
+
Create a `.env` file in the project root with the following variables:
|
| 58 |
+
|
| 59 |
+
```bash
|
| 60 |
+
RUN_DVC_PULL=1
|
| 61 |
+
AWS_ACCESS_KEY_ID=<your_dagshub_token>
|
| 62 |
+
AWS_SECRET_ACCESS_KEY=<your_dagshub_token>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
```
|
| 64 |
|
| 65 |
+
### 3. Launch the API Locally
|
| 66 |
+
|
| 67 |
+
#### Docker Compose (Full Stack)
|
| 68 |
+
|
| 69 |
+
This starts the API along with Prometheus, Grafana, and Locust for monitoring:
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
docker-compose up --build
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
Services available:
|
| 76 |
+
|
| 77 |
+
| Service | URL | Description |
|
| 78 |
+
|---------|-----|-------------|
|
| 79 |
+
| **CardioTrack API** | http://localhost:7860 | Main application with Gradio UI |
|
| 80 |
+
| **Prometheus** | http://localhost:9090 | Metrics collection |
|
| 81 |
+
| **Grafana** | http://localhost:4444 | Metrics dashboard |
|
| 82 |
+
| **Locust** | http://localhost:8089 | Load testing interface |
|
| 83 |
+
|
| 84 |
+
To stop all services:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
docker-compose down
|
| 88 |
```
|
| 89 |
+
|
| 90 |
+
> **Important:** For a more in-depth guide, if you want to modify code see the Developer Guide at [docs/Developer_Guide.md](docs/Developer_Guide.md).
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
## Project Organization
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
```
|
| 95 |
+
├── Makefile <- Makefile with convenience commands
|
| 96 |
+
├── README.md <- The top-level README for developers
|
| 97 |
+
├── pyproject.toml <- Project configuration and dependencies
|
| 98 |
+
├── uv.lock <- Lock file for uv package manager
|
| 99 |
+
├── Dockerfile <- Docker container configuration
|
| 100 |
+
├── docker-compose.yml <- Multi-service stack (API, Prometheus, Grafana, Locust)
|
| 101 |
+
├── prometheus.yml <- Prometheus scraping configuration
|
| 102 |
+
├── dvc.yaml <- DVC pipeline configuration
|
| 103 |
+
├── dvc.lock <- DVC pipeline lock file
|
| 104 |
+
├── .env.example <- Environment variables template
|
| 105 |
+
├── .dvc/ <- DVC internal configuration
|
| 106 |
+
├── .github/workflows/ <- GitHub Actions CI/CD workflows
|
| 107 |
+
│ ├── deploy.yml <- Deployment workflow
|
| 108 |
+
│ ├── pynblint.yml <- Notebook linting workflow
|
| 109 |
+
│ ├── pytestAndGX.yml <- Testing and Great Expectations workflow
|
| 110 |
+
│ └── ruff-linter.yml <- Ruff code linting workflow
|
| 111 |
+
├── data/
|
| 112 |
+
│ ├── raw/ <- Original, immutable data dump
|
| 113 |
+
│ ├── interim/ <- Intermediate transformed data
|
| 114 |
+
│ │ └── preprocess_artifacts/ <- Preprocessing artifacts (scaler.joblib)
|
| 115 |
+
│ └── processed <- Final datasets for modeling (train/test splits)
|
| 116 |
+
├── docs/ <- Project documentation
|
| 117 |
+
│ ├── CardioTrack_ML_Canvas.md <- ML project canvas
|
| 118 |
+
│ ├── Developer_Guide.md <- Developer setup guide
|
| 119 |
+
│ └── Risk_Classification.md <- Risk classification methodology
|
| 120 |
+
├── grafana/
|
| 121 |
+
│ ├── dashboards/ <- Grafana dashboard definitions
|
| 122 |
+
│ └── provisioning/ <- Datasources and dashboard provisioning
|
| 123 |
+
├── locust/
|
| 124 |
+
│ ├── Dockerfile <- Locust container build
|
| 125 |
+
│ └── locustfile.py <- Load-testing scenarios
|
| 126 |
+
├── metrics/test <- Model evaluation metrics (JSON)
|
| 127 |
+
├── models <- Trained models (.joblib files)
|
| 128 |
+
├── notebooks/ <- Jupyter notebooks for exploration
|
| 129 |
+
├── references/ <- Data dictionaries and explanatory materials
|
| 130 |
+
├── reports/
|
| 131 |
+
│ ├── figures/ <- Generated graphics and figures
|
| 132 |
+
│ ├── great_expectations_reports/ <- Data quality validation reports
|
| 133 |
+
│ ├── pytest_report/ <- Pytest HTML test reports
|
| 134 |
+
│ ├── locust_reports/ <- Load testing reports
|
| 135 |
+
│ └── deepchecks_data_drift_reports/ <- Data drift analysis outputs
|
| 136 |
+
├── predicting_outcomes_in_heart_failure/ <- Source code
|
| 137 |
+
│ ├── __init__.py
|
| 138 |
+
│ ├── config.py <- Configuration variables
|
| 139 |
+
│ ├── app/ <- FastAPI application
|
| 140 |
+
│ │ ├── main.py <- Application entry point
|
| 141 |
+
│ │ ├── monitoring.py <- Prometheus metrics
|
| 142 |
+
│ │ ├── schema.py <- Pydantic schemas
|
| 143 |
+
│ │ ├── utils.py <- Utility functions
|
| 144 |
+
│ │ ├── wrapper.py <- Wrapper class for UI
|
| 145 |
+
│ │ ├── entrypoint.sh <- Container entrypoint script
|
| 146 |
+
│ │ ├── routers/ <- API route handlers
|
| 147 |
+
│ │ │ ├── cards.py <- Cards endpoints
|
| 148 |
+
│ │ │ ├── general.py <- General endpoints
|
| 149 |
+
│ │ │ ├── model_info.py <- Model info endpoints
|
| 150 |
+
│ │ │ └── prediction.py <- Prediction endpoints
|
| 151 |
+
│ │ └── deepchecks_monitoring/ <- Data drift monitoring
|
| 152 |
+
│ │ ├── drift_runner.py <- Drift computation
|
| 153 |
+
│ │ ├── production_data_collector.py <- Production data logging
|
| 154 |
+
│ │ └── scheduler.py <- Scheduled drift jobs
|
| 155 |
+
│ ├── data/ <- Data processing modules
|
| 156 |
+
│ │ ├── dataset.py <- Data download scripts
|
| 157 |
+
│ │ ├── preprocess.py <- Preprocessing code
|
| 158 |
+
│ │ └── split_data.py <- Train/test splitting
|
| 159 |
+
│ └── modeling/ <- Model training and evaluation
|
| 160 |
+
│ ├── train.py <- Training code
|
| 161 |
+
│ ├── predict.py <- Inference code
|
| 162 |
+
│ ├── evaluate.py <- Evaluation metrics
|
| 163 |
+
│ └── explainability.py <- SHAP explainability
|
| 164 |
+
└── tests/ <- Test suite
|
| 165 |
+
├── test_behavioral_model/ <- Behavioral testing
|
| 166 |
+
│ ├── directional_test.py <- Directional expectations
|
| 167 |
+
│ ├── invariance_test.py <- Model invariance tests
|
| 168 |
+
│ └── minimum_functionality_test.py <- Minimum functionality tests
|
| 169 |
+
├── test_heart_data/ <- Data validation tests
|
| 170 |
+
│ ├── raw_test.py <- Raw data quality tests
|
| 171 |
+
│ ├── processed_test.py <- Processed data quality tests
|
| 172 |
+
│ └── util.py <- Testing utilities
|
| 173 |
+
└── test_predicting_outcomes_in_heart_failure/ <- Unit tests
|
| 174 |
+
├── app/ <- API tests
|
| 175 |
+
│ ├── schema_test.py <- Schema validation tests
|
| 176 |
+
│ └── routers/ <- Router tests
|
| 177 |
+
│ ├── model_info_test.py <- Model info tests
|
| 178 |
+
│ └── prediction_test.py <- Prediction tests
|
| 179 |
+
├── data/ <- Data module tests
|
| 180 |
+
│ ├── preprocess_test.py <- Preprocessing tests
|
| 181 |
+
│ └── split_data_test.py <- Data splitting tests
|
| 182 |
+
└── modeling/ <- Modeling tests
|
| 183 |
+
├── conftest.py <- Pytest fixtures
|
| 184 |
+
├── test_train.py <- Training tests
|
| 185 |
+
├── test_predict.py <- Prediction tests
|
| 186 |
+
├── test_evaluate.py <- Evaluation tests
|
| 187 |
+
└── test_explainability.py <- Explainability tests
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
## CardioTrack Architecture
|
| 191 |
+

|
| 192 |
+
|
| 193 |
+
### DVC Pipeline Defined
|
| 194 |
+
The project implements a **fully automated ML pipeline** using **DVC (Data Version Control)** to ensure reproducibility and traceability across all stages. The pipeline is structured into five sequential stages, each with a specific responsibility in the machine learning workflow.
|
| 195 |
+
|
| 196 |
+
1. **download_data**
|
| 197 |
+
Automatically download the raw dataset from Kaggle, eliminating manual download steps and ensuring control of the exact data used.
|
| 198 |
+
2. **preprocessing**
|
| 199 |
+
Applies data transformations including cleaning invalid values, encoding categorical variables, and standardizing numerical features.
|
| 200 |
+
3. **split_data**
|
| 201 |
+
Divides the preprocessed data into **training (70%)** and **test (30%)** sets using stratified sampling. Splitting after preprocessing prevents data leakage by ensuring tuning hyperparameters is computed only on training data.
|
| 202 |
+
4. **training**
|
| 203 |
+
Trains three models (Decision Tree, Random Forest, Logistic Regression) with a **cross-validation strategy** for hyperparameter tuning. **RandomOverSampler** addresses class imbalance.
|
| 204 |
+
5. **evaluation**
|
| 205 |
+
Assesses model performance on the independent test set, computing F1 Score, Recall, Accuracy, and ROC-AUC.
|
| 206 |
|
| 207 |
+
### Experiments
|
| 208 |
+
|
| 209 |
+
All experiments were tracked using **MLflow** and are available on [DagsHub platform](https://dagshub.com/se4ai2526-uniba/CardioTrack/experiments). For detailed metrics and run comparisons, please refer to the MLflow experiments dashboard.
|
| 210 |
+
|
| 211 |
+
#### Experimental Setup
|
| 212 |
+
|
| 213 |
+
We evaluated three classification algorithms:
|
| 214 |
+
|
| 215 |
+
- **Random Forest**
|
| 216 |
+
- **Decision Tree**
|
| 217 |
+
- **Logistic Regression**
|
| 218 |
+
|
| 219 |
+
##### Handling Class Imbalance
|
| 220 |
+
|
| 221 |
+
The target variable presented a significant class imbalance. To address this issue, we applied **Random Oversampling** to balance data, ensuring the models could learn effectively from both classes.
|
| 222 |
+
|
| 223 |
+
Additionally, the **"sex" feature showed a severe imbalance** in the dataset. After analyzing the model performance with and without this feature, we found that it provided minimal predictive value while potentially introducing unnecessary gender bias. We also trained the models separately on only males and only females, but the performance was very poor, particularly for females. Consequently, we decided to remove the "sex" feature from the final model to ensure fairness without sacrificing performance.
|
| 224 |
+
|
| 225 |
+
#### Results Summary
|
| 226 |
+
|
| 227 |
+
| Model | Accuracy | F1 Score | Recall | ROC AUC |
|
| 228 |
+
|-------|----------|----------|--------|---------|
|
| 229 |
+
| **Random Forest** | ~0.87 | ~0.89 | ~0.88 | ~0.91 |
|
| 230 |
+
| Decision Tree | ~0.79 | ~0.75 | ~0.77 | ~0.81 |
|
| 231 |
+
| Logistic Regression | ~0.84 | ~0.81 | ~0.82 | ~0.89 |
|
| 232 |
+
|
| 233 |
+
#### Selected Model
|
| 234 |
+
|
| 235 |
+
The model deployed in production is **Random Forest without the "sex" feature**.
|
| 236 |
+
|
| 237 |
+
| Model | Accuracy | F1 Score | Recall | ROC AUC |
|
| 238 |
+
|-------|----------|----------|--------|---------|
|
| 239 |
+
| **Random Forest No Sex** | 0.8877 | 0.8990 | 0.9020 | 0.9400 |
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
##### Rationale:
|
| 243 |
+
|
| 244 |
+
1. **Best overall performance**: Random Forest consistently outperformed Decision Tree and Logistic Regression across all metrics.
|
| 245 |
+
|
| 246 |
+
2. **Fairness considerations**: Removing the "sex" feature eliminates potential gender bias in predictions. The performance difference between the model with all features and the one without "sex" was negligible (< 1%).
|
| 247 |
+
|
| 248 |
+
3. **Robustness**: Models trained on gender-specific subsets showed highly imbalanced performance, particularly poor results on the female subset due to data scarcity. The model without the "sex" feature generalizes better across both genders.
|
| 249 |
+
|
| 250 |
+
4. **Ethical AI practices**: In medical applications, avoiding unnecessary use of sensitive attributes aligns with responsible AI principles and regulatory guidelines.
|
| 251 |
+
|
| 252 |
+
## Milestones Description
|
| 253 |
|
| 254 |
### Milestone 1 - Inception
|
| 255 |
During this milestone, the **CCDS Project Template** was used as the foundation for organizing the project.
|
| 256 |
The main conceptual and structural components of the system were defined, following the template guidelines to ensure consistency and traceability.
|
| 257 |
|
| 258 |
+
Additionally, a **Machine Learning Canvas** has been added. To see it [docs/CardioTrack_ML_Canvas.md](docs/CardioTrack_ML_Canvas.md).
|
| 259 |
It outlines the model objectives, the data to be used, and the key methodological aspects planned for the next phases of the project.
|
| 260 |
|
| 261 |
### Milestone 2 - Reproducibility
|
|
|
|
| 271 |
- **Data splitting**
|
| 272 |
- **Training** and **evaluation**
|
| 273 |
|
| 274 |
+
The pipeline is fully reproducible and version-controlled through DVC. Morover, dvc pipeline defined uses `foreach` directive for parallelization across 4 data variants (all, female, male, nosex) and 3 models. All dependencies are automatically tracked ensuring the correct execution order.
|
| 275 |
|
| 276 |
#### Model Training and Experiment Tracking
|
| 277 |
We implemented the **training scripts** and integrated **MLflow** for experiment tracking.
|
|
|
|
| 280 |
- Random Forest
|
| 281 |
- Logistic Regression
|
| 282 |
|
| 283 |
+
Each experiment is logged to MLflow and they are all available [here](https://dagshub.com/se4ai2526-uniba/CardioTrack.mlflow).
|
| 284 |
|
| 285 |
#### Model Registry and Thresholds
|
| 286 |
Models that reach or exceed the predefined **performance thresholds** (as defined in the ML Canvas) are automatically **saved to the model registry**.
|
|
|
|
| 302 |
|
| 303 |
- detect anomalies or invalid values at the data source
|
| 304 |
- prevent the propagation of data issues into downstream processes
|
| 305 |
+
> **Important**: Great Expectation reports are available here [reports/great_expectations_reports](reports/great_expectations_reports)
|
| 306 |
|
| 307 |
+
#### Tests
|
| 308 |
+
|
| 309 |
+
##### Code Quality
|
| 310 |
We added automated **unit and integration tests** using **pytest**, covering the main modules and functionalities of the system.
|
| 311 |
+
> **Important**: Pytest report is available here [reports/pytest_report](reports/pytest_report/)
|
| 312 |
+
|
| 313 |
+
##### Model Behavioral Testing
|
| 314 |
+
We implemented **behavioral tests** to validate clinical correctness of predictions.
|
| 315 |
|
| 316 |
+
> **Important**: Pytest report is available here [reports/pytest_report](reports/pytest_report/)
|
| 317 |
|
| 318 |
#### ML Pipeline Enhancements
|
| 319 |
we applied the following enhancements to the ML pipeline:
|
|
|
|
| 335 |
|
| 336 |
#### Risk Classification
|
| 337 |
We added a **Risk Classification** analysis for the system in accordance with **IMDRF** and **AI Act** regulations.
|
| 338 |
+
> **Important**: The Risk Classification is available here: [docs/Risk_Classification.md](docs/Risk_Classification.md) folder.
|
| 339 |
|
| 340 |
|
| 341 |
### Milestone 4 - API Integration
|
|
|
|
| 409 |
- Introduction of *pytest* for automated testing.
|
| 410 |
- Integration of *Great Expectations* for automated data quality checks.
|
| 411 |
|
| 412 |
+
**CI Workflows:**
|
| 413 |
+
|
| 414 |
+
| Workflow | Trigger | Purpose |
|
| 415 |
+
|----------|---------|---------|
|
| 416 |
+
| ruff-linter.yml | Pull Request | Autofix + push style corrections |
|
| 417 |
+
| pynblint.yml | PR on notebooks/** | Notebook-specific linting |
|
| 418 |
+
| pytestAndGX.yml | PR (excludes main) | Tests + data quality validation |
|
| 419 |
+
| deploy.yml | Push to main | sync → test → deploy to HF |
|
| 420 |
+
|
| 421 |
|
| 422 |
#### Continuos Deployment
|
| 423 |
Automated deployment to *Hugging Face* was implemented through Github Actions workflow
|
| 424 |
*Hugging Face Space*: [Check Here](https://huggingface.co/spaces/CardioTrack/CardioTrack)
|
| 425 |
|
| 426 |
+
**CD Workflow:**
|
| 427 |
+
1. **test**: Run full test suite
|
| 428 |
+
2. **sync**: Copy files from main to deploy branch
|
| 429 |
+
3. **deploy-to-hf**: Push to HF Space + health check
|
| 430 |
+
|
| 431 |
+
### Milestone 6 - Monitoring
|
| 432 |
+
|
| 433 |
+
In this milestone, we implemented:
|
| 434 |
+
|
| 435 |
+
### Infrastructure
|
| 436 |
+
|
| 437 |
+
A multi-container monitoring stack was deployed using Docker Compose:
|
| 438 |
+
- **Prometheus** for metrics collection
|
| 439 |
+
- **Grafana** for visualization through a custom dashboard
|
| 440 |
+
- **Locust** for load testing
|
| 441 |
+
|
| 442 |
+
### Resource Monitoring
|
| 443 |
+
Using the infrastructure defined above, we perform internal resource monitoring:
|
| 444 |
+
- **Prometheus** collects application metrics in real-time
|
| 445 |
+
- **Locust** simulates user traffic to evaluate system performance under load
|
| 446 |
+
- **Grafana** aggregates the most relevant metrics and displays them in a purpose-built dashboard for analysis
|
| 447 |
+
|
| 448 |
+
Additionally, we use **Uptime - Better Stack** for external uptime monitoring.
|
| 449 |
+
|
| 450 |
+
### Performance Monitoring
|
| 451 |
+
|
| 452 |
+
Automated data drift detection was implemented:
|
| 453 |
+
- **APScheduler** for scheduled data collection from production
|
| 454 |
+
- **Deepchecks** for drift analysis on incoming data
|
| 455 |
+
|
| 456 |
+
> **Important**: Further information about tests and monitoring can be found in [reports/README.md](reports/README.md)
|
predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/drift_runner.py
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from datetime import UTC, datetime
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
|
| 7 |
+
from deepchecks.tabular import Dataset
|
| 8 |
+
from deepchecks.tabular.checks import FeatureDrift
|
| 9 |
+
from loguru import logger
|
| 10 |
+
import pandas as pd
|
| 11 |
+
from predicting_outcomes_in_heart_failure.config import CAT_FEATURES
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _read_state(state_path: Path) -> dict:
|
| 15 |
+
if not state_path.exists():
|
| 16 |
+
return {"last_processed_rows": 0}
|
| 17 |
+
try:
|
| 18 |
+
return json.loads(state_path.read_text(encoding="utf-8"))
|
| 19 |
+
except Exception:
|
| 20 |
+
return {"last_processed_rows": 0}
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _write_state(state_path: Path, state: dict) -> None:
|
| 24 |
+
state_path.parent.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
state_path.write_text(json.dumps(state, indent=2), encoding="utf-8")
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def _coerce_bool_like(df: pd.DataFrame) -> pd.DataFrame:
|
| 29 |
+
"""Converts columns with True/False values to 0/1 int."""
|
| 30 |
+
bool_map = {"true": 1, "false": 0, "1": 1, "0": 0, True: 1, False: 0}
|
| 31 |
+
for col in df.columns:
|
| 32 |
+
s = df[col]
|
| 33 |
+
|
| 34 |
+
if s.dtype == "object":
|
| 35 |
+
lowered = s.astype(str).str.strip().str.lower()
|
| 36 |
+
uniq = set(lowered.dropna().unique().tolist())
|
| 37 |
+
if uniq.issubset({"true", "false", "0", "1"}):
|
| 38 |
+
df[col] = lowered.map(bool_map).astype("int64")
|
| 39 |
+
elif s.dtype == "bool":
|
| 40 |
+
df[col] = s.astype("int64")
|
| 41 |
+
return df
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _drift_score_to_float(drift_score: object) -> float:
|
| 45 |
+
if isinstance(drift_score, (int, float)):
|
| 46 |
+
return float(drift_score)
|
| 47 |
+
|
| 48 |
+
if isinstance(drift_score, dict):
|
| 49 |
+
if "value" in drift_score:
|
| 50 |
+
return float(drift_score["value"])
|
| 51 |
+
for v in drift_score.values():
|
| 52 |
+
if isinstance(v, (int, float)):
|
| 53 |
+
return float(v)
|
| 54 |
+
|
| 55 |
+
raise TypeError(f"Unsupported drift_score format: {type(drift_score)} -> {drift_score}")
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def run_drift_if_enough_rows(
|
| 59 |
+
*,
|
| 60 |
+
reference_csv: Path,
|
| 61 |
+
production_csv: Path,
|
| 62 |
+
reports_dir: Path,
|
| 63 |
+
min_rows: int = 20,
|
| 64 |
+
feature_columns: list[str] | None = None,
|
| 65 |
+
state_path: Path | None = None,
|
| 66 |
+
) -> dict:
|
| 67 |
+
if not production_csv.exists():
|
| 68 |
+
return {"ran": False, "reason": "production_csv_missing", "path": str(production_csv)}
|
| 69 |
+
|
| 70 |
+
try:
|
| 71 |
+
n_rows = sum(1 for _ in production_csv.open("r", encoding="utf-8")) - 1
|
| 72 |
+
except Exception as e:
|
| 73 |
+
logger.exception(f"Failed counting rows in {production_csv}: {e}")
|
| 74 |
+
return {"ran": False, "reason": "count_failed", "error": str(e)}
|
| 75 |
+
|
| 76 |
+
if n_rows < min_rows:
|
| 77 |
+
return {"ran": False, "reason": "not_enough_rows", "n_rows": n_rows, "min_rows": min_rows}
|
| 78 |
+
|
| 79 |
+
last_processed = 0
|
| 80 |
+
if state_path is not None:
|
| 81 |
+
state = _read_state(state_path)
|
| 82 |
+
last_processed = int(state.get("last_processed_rows", 0))
|
| 83 |
+
if n_rows == last_processed:
|
| 84 |
+
return {"ran": False, "reason": "no_new_rows", "n_rows": n_rows}
|
| 85 |
+
|
| 86 |
+
ref_df = pd.read_csv(reference_csv)
|
| 87 |
+
prod_df = pd.read_csv(production_csv)
|
| 88 |
+
|
| 89 |
+
ref_df = _coerce_bool_like(ref_df)
|
| 90 |
+
prod_df = _coerce_bool_like(prod_df)
|
| 91 |
+
|
| 92 |
+
if feature_columns is not None:
|
| 93 |
+
ref_df = ref_df[feature_columns]
|
| 94 |
+
prod_df = prod_df[feature_columns]
|
| 95 |
+
|
| 96 |
+
ref_ds = Dataset(ref_df, label=None, cat_features=CAT_FEATURES)
|
| 97 |
+
prod_ds = Dataset(prod_df, label=None, cat_features=CAT_FEATURES)
|
| 98 |
+
|
| 99 |
+
check = FeatureDrift()
|
| 100 |
+
|
| 101 |
+
try:
|
| 102 |
+
result = check.run(train_dataset=ref_ds, test_dataset=prod_ds)
|
| 103 |
+
except TypeError:
|
| 104 |
+
result = check.run(ref_ds, prod_ds)
|
| 105 |
+
|
| 106 |
+
raw = result.value
|
| 107 |
+
|
| 108 |
+
features = {name: _drift_score_to_float(info.get("Drift score")) for name, info in raw.items()}
|
| 109 |
+
|
| 110 |
+
threshold = 0.2
|
| 111 |
+
above = [k for k, v in features.items() if v >= threshold]
|
| 112 |
+
|
| 113 |
+
output = {
|
| 114 |
+
"check": "FeatureDrift",
|
| 115 |
+
"timestamp_utc": datetime.now(UTC).isoformat(),
|
| 116 |
+
"data": {
|
| 117 |
+
"reference_rows": len(ref_df),
|
| 118 |
+
"production_rows": len(prod_df),
|
| 119 |
+
},
|
| 120 |
+
"features": features,
|
| 121 |
+
"summary": {
|
| 122 |
+
"max_drift": max(features.values()),
|
| 123 |
+
"mean_drift": sum(features.values()) / len(features),
|
| 124 |
+
"threshold": threshold,
|
| 125 |
+
"features_above_threshold": above,
|
| 126 |
+
"n_features_above_threshold": len(above),
|
| 127 |
+
},
|
| 128 |
+
}
|
| 129 |
+
reports_dir.mkdir(parents=True, exist_ok=True)
|
| 130 |
+
ts = datetime.now(UTC).strftime("%Y-%m-%d_%H-%M-%S")
|
| 131 |
+
|
| 132 |
+
report_path = reports_dir / f"drift_result_{ts}.json"
|
| 133 |
+
report_path.write_text(json.dumps(output, indent=2), encoding="utf-8")
|
| 134 |
+
|
| 135 |
+
logger.success(f"Deepchecks drift report generated: {report_path}")
|
| 136 |
+
|
| 137 |
+
if state_path is not None:
|
| 138 |
+
_write_state(state_path, {"last_processed_rows": n_rows, "last_report": str(report_path)})
|
| 139 |
+
|
| 140 |
+
return {
|
| 141 |
+
"ran": True,
|
| 142 |
+
"n_rows": n_rows,
|
| 143 |
+
"report_path": str(report_path),
|
| 144 |
+
"passed": bool(getattr(result, "passed", True)),
|
| 145 |
+
}
|
predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/production_data_collector.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from datetime import UTC, datetime
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
|
| 6 |
+
from loguru import logger
|
| 7 |
+
import pandas as pd
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def append_predictions_to_csv(
|
| 11 |
+
*,
|
| 12 |
+
csv_path: Path,
|
| 13 |
+
endpoint: str,
|
| 14 |
+
X: pd.DataFrame,
|
| 15 |
+
y_pred: list[int] | int,
|
| 16 |
+
feature_columns: list[str] | None = None,
|
| 17 |
+
) -> None:
|
| 18 |
+
"""
|
| 19 |
+
Appende su CSV i dati di produzione (input + prediction).
|
| 20 |
+
"""
|
| 21 |
+
csv_path.parent.mkdir(parents=True, exist_ok=True)
|
| 22 |
+
|
| 23 |
+
# Normalize predictions
|
| 24 |
+
y_list = [y_pred] if isinstance(y_pred, int) else y_pred
|
| 25 |
+
|
| 26 |
+
df = X.copy()
|
| 27 |
+
|
| 28 |
+
if feature_columns is not None:
|
| 29 |
+
missing = [c for c in feature_columns if c not in df.columns]
|
| 30 |
+
if missing:
|
| 31 |
+
raise ValueError(f"Missing expected feature columns in production batch: {missing}")
|
| 32 |
+
df = df[feature_columns]
|
| 33 |
+
|
| 34 |
+
if len(df) != len(y_list):
|
| 35 |
+
raise ValueError(
|
| 36 |
+
f"Row mismatch: X has {len(df)} rows but predictions has {len(y_list)} items"
|
| 37 |
+
)
|
| 38 |
+
df.insert(0, "timestamp_utc", datetime.now(UTC).isoformat())
|
| 39 |
+
df.insert(1, "endpoint", endpoint)
|
| 40 |
+
df["prediction"] = y_list
|
| 41 |
+
|
| 42 |
+
write_header = not csv_path.exists()
|
| 43 |
+
df.to_csv(csv_path, mode="a", index=False, header=write_header)
|
| 44 |
+
logger.info(f"Appended {len(df)} rows to production CSV: {csv_path}")
|
predicting_outcomes_in_heart_failure/app/deepchecks_monitoring/scheduler.py
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
| 4 |
+
from apscheduler.triggers.cron import CronTrigger
|
| 5 |
+
from loguru import logger
|
| 6 |
+
from predicting_outcomes_in_heart_failure.app.deepchecks_monitoring.drift_runner import (
|
| 7 |
+
run_drift_if_enough_rows,
|
| 8 |
+
)
|
| 9 |
+
from predicting_outcomes_in_heart_failure.config import (
|
| 10 |
+
INPUT_COLUMNS,
|
| 11 |
+
PRODUCTION_CSV_PATH,
|
| 12 |
+
REFERENCE_CSV,
|
| 13 |
+
REPORTS_DIR,
|
| 14 |
+
STATE_PATH,
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
scheduler = AsyncIOScheduler()
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def drift_job() -> None:
|
| 21 |
+
try:
|
| 22 |
+
out = run_drift_if_enough_rows(
|
| 23 |
+
reference_csv=REFERENCE_CSV,
|
| 24 |
+
production_csv=PRODUCTION_CSV_PATH,
|
| 25 |
+
reports_dir=REPORTS_DIR,
|
| 26 |
+
min_rows=20,
|
| 27 |
+
feature_columns=list(INPUT_COLUMNS),
|
| 28 |
+
state_path=STATE_PATH,
|
| 29 |
+
)
|
| 30 |
+
logger.info(f"Drift job result: {out}")
|
| 31 |
+
except Exception as e:
|
| 32 |
+
logger.exception(f"Drift job failed: {e}")
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def start_scheduler() -> None:
|
| 36 |
+
if scheduler.running:
|
| 37 |
+
return
|
| 38 |
+
|
| 39 |
+
scheduler.add_job(
|
| 40 |
+
drift_job,
|
| 41 |
+
trigger=CronTrigger(hour=21, minute=0),
|
| 42 |
+
id="deepchecks_drift_job",
|
| 43 |
+
replace_existing=True,
|
| 44 |
+
misfire_grace_time=120,
|
| 45 |
+
max_instances=1,
|
| 46 |
+
coalesce=True,
|
| 47 |
+
)
|
| 48 |
+
scheduler.start()
|
| 49 |
+
logger.info("Deepchecks drift scheduler started (every 5 minutes).")
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def shutdown_scheduler() -> None:
|
| 53 |
+
if scheduler.running:
|
| 54 |
+
scheduler.shutdown(wait=False)
|
| 55 |
+
logger.info("Deepchecks drift scheduler stopped.")
|
predicting_outcomes_in_heart_failure/app/main.py
CHANGED
|
@@ -6,6 +6,11 @@ import gradio as gr
|
|
| 6 |
import joblib
|
| 7 |
from loguru import logger
|
| 8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
from predicting_outcomes_in_heart_failure.app.routers import cards, model_info, prediction
|
| 10 |
from predicting_outcomes_in_heart_failure.app.utils import load_page, update_patient_index_choices
|
| 11 |
from predicting_outcomes_in_heart_failure.app.wrapper import Wrapper
|
|
@@ -22,10 +27,14 @@ async def lifespan(app: FastAPI):
|
|
| 22 |
logger.info(f"Loading default model from {MODEL_PATH} ...")
|
| 23 |
app.state.model = joblib.load(MODEL_PATH)
|
| 24 |
logger.success(f"Default model loaded from {MODEL_PATH}")
|
|
|
|
|
|
|
| 25 |
|
| 26 |
try:
|
| 27 |
yield
|
| 28 |
finally:
|
|
|
|
|
|
|
| 29 |
app.state.model = None
|
| 30 |
logger.info("Default model cleared on application shutdown")
|
| 31 |
|
|
@@ -45,6 +54,7 @@ app.include_router(prediction.router)
|
|
| 45 |
app.include_router(model_info.router)
|
| 46 |
app.include_router(cards.router)
|
| 47 |
|
|
|
|
| 48 |
|
| 49 |
# UI Definition
|
| 50 |
with gr.Blocks(title="CardioTrack") as io:
|
|
|
|
| 6 |
import joblib
|
| 7 |
from loguru import logger
|
| 8 |
|
| 9 |
+
from predicting_outcomes_in_heart_failure.app.deepchecks_monitoring.scheduler import (
|
| 10 |
+
shutdown_scheduler,
|
| 11 |
+
start_scheduler,
|
| 12 |
+
)
|
| 13 |
+
from predicting_outcomes_in_heart_failure.app.monitoring import instrumentator
|
| 14 |
from predicting_outcomes_in_heart_failure.app.routers import cards, model_info, prediction
|
| 15 |
from predicting_outcomes_in_heart_failure.app.utils import load_page, update_patient_index_choices
|
| 16 |
from predicting_outcomes_in_heart_failure.app.wrapper import Wrapper
|
|
|
|
| 27 |
logger.info(f"Loading default model from {MODEL_PATH} ...")
|
| 28 |
app.state.model = joblib.load(MODEL_PATH)
|
| 29 |
logger.success(f"Default model loaded from {MODEL_PATH}")
|
| 30 |
+
start_scheduler()
|
| 31 |
+
logger.info("Deepchecks scheduler started")
|
| 32 |
|
| 33 |
try:
|
| 34 |
yield
|
| 35 |
finally:
|
| 36 |
+
shutdown_scheduler()
|
| 37 |
+
logger.info("Deepchecks scheduler stopped")
|
| 38 |
app.state.model = None
|
| 39 |
logger.info("Default model cleared on application shutdown")
|
| 40 |
|
|
|
|
| 54 |
app.include_router(model_info.router)
|
| 55 |
app.include_router(cards.router)
|
| 56 |
|
| 57 |
+
instrumentator.instrument(app).expose(app, include_in_schema=False, should_gzip=True)
|
| 58 |
|
| 59 |
# UI Definition
|
| 60 |
with gr.Blocks(title="CardioTrack") as io:
|
predicting_outcomes_in_heart_failure/app/monitoring.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
from prometheus_client import Counter, Histogram
|
| 4 |
+
from prometheus_fastapi_instrumentator import Instrumentator, metrics
|
| 5 |
+
|
| 6 |
+
NAMESPACE = os.environ.get("METRICS_NAMESPACE", "cardiotrack")
|
| 7 |
+
SUBSYSTEM = os.environ.get("METRICS_SUBSYSTEM", "api")
|
| 8 |
+
|
| 9 |
+
instrumentator = Instrumentator(
|
| 10 |
+
should_group_status_codes=True,
|
| 11 |
+
should_ignore_untemplated=True,
|
| 12 |
+
should_instrument_requests_inprogress=True,
|
| 13 |
+
excluded_handlers=["/metrics"],
|
| 14 |
+
inprogress_name="fastapi_inprogress",
|
| 15 |
+
inprogress_labels=True,
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
instrumentator.add(
|
| 19 |
+
metrics.request_size(
|
| 20 |
+
should_include_handler=True,
|
| 21 |
+
should_include_method=True,
|
| 22 |
+
should_include_status=True,
|
| 23 |
+
metric_namespace=NAMESPACE,
|
| 24 |
+
metric_subsystem=SUBSYSTEM,
|
| 25 |
+
)
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
instrumentator.add(
|
| 29 |
+
metrics.response_size(
|
| 30 |
+
should_include_handler=True,
|
| 31 |
+
should_include_method=True,
|
| 32 |
+
should_include_status=True,
|
| 33 |
+
metric_namespace=NAMESPACE,
|
| 34 |
+
metric_subsystem=SUBSYSTEM,
|
| 35 |
+
)
|
| 36 |
+
)
|
| 37 |
+
|
| 38 |
+
instrumentator.add(
|
| 39 |
+
metrics.latency(
|
| 40 |
+
should_include_handler=True,
|
| 41 |
+
should_include_method=True,
|
| 42 |
+
should_include_status=True,
|
| 43 |
+
metric_namespace=NAMESPACE,
|
| 44 |
+
metric_subsystem=SUBSYSTEM,
|
| 45 |
+
buckets=[0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5],
|
| 46 |
+
)
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
instrumentator.add(
|
| 50 |
+
metrics.requests(
|
| 51 |
+
should_include_handler=True,
|
| 52 |
+
should_include_method=True,
|
| 53 |
+
should_include_status=True,
|
| 54 |
+
metric_namespace=NAMESPACE,
|
| 55 |
+
metric_subsystem=SUBSYSTEM,
|
| 56 |
+
)
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
prediction_counter = Counter(
|
| 60 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_predictions_total",
|
| 61 |
+
documentation="Total number of prediction requests",
|
| 62 |
+
labelnames=["prediction_type", "endpoint"],
|
| 63 |
+
)
|
| 64 |
+
|
| 65 |
+
prediction_result_counter = Counter(
|
| 66 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_prediction_results_total",
|
| 67 |
+
documentation="Count of prediction results by class",
|
| 68 |
+
labelnames=["prediction_class", "endpoint"],
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
model_error_counter = Counter(
|
| 72 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_model_errors_total",
|
| 73 |
+
documentation="Total number of model loading or prediction errors",
|
| 74 |
+
labelnames=["error_type", "endpoint"],
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
explanation_counter = Counter(
|
| 78 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_explanations_total",
|
| 79 |
+
documentation="Total number of explanation requests",
|
| 80 |
+
labelnames=["status", "endpoint"],
|
| 81 |
+
)
|
| 82 |
+
|
| 83 |
+
batch_size_histogram = Histogram(
|
| 84 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_batch_size",
|
| 85 |
+
documentation="Distribution of batch prediction sizes",
|
| 86 |
+
labelnames=["endpoint"],
|
| 87 |
+
buckets=[1, 5, 10, 20, 50, 100, 200, 500],
|
| 88 |
+
)
|
| 89 |
+
|
| 90 |
+
prediction_processing_time = Histogram(
|
| 91 |
+
name=f"{NAMESPACE}_{SUBSYSTEM}_prediction_processing_seconds",
|
| 92 |
+
documentation="Time spent on prediction processing (excluding HTTP overhead)",
|
| 93 |
+
labelnames=["prediction_type", "endpoint"],
|
| 94 |
+
buckets=[0.01, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0, 120.0],
|
| 95 |
+
)
|
predicting_outcomes_in_heart_failure/app/routers/prediction.py
CHANGED
|
@@ -1,15 +1,32 @@
|
|
| 1 |
from http import HTTPStatus
|
|
|
|
| 2 |
from typing import Any
|
| 3 |
|
| 4 |
from fastapi import APIRouter, Request
|
| 5 |
from loguru import logger
|
| 6 |
import pandas as pd
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
from predicting_outcomes_in_heart_failure.app.schema import HeartSample
|
| 8 |
from predicting_outcomes_in_heart_failure.app.utils import (
|
| 9 |
construct_response,
|
| 10 |
get_model_from_state,
|
| 11 |
)
|
| 12 |
-
from predicting_outcomes_in_heart_failure.config import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
from predicting_outcomes_in_heart_failure.modeling.explainability import (
|
| 14 |
explain_prediction,
|
| 15 |
save_shap_waterfall_plot,
|
|
@@ -22,68 +39,127 @@ router = APIRouter()
|
|
| 22 |
@router.post("/predictions", tags=["Prediction"])
|
| 23 |
@construct_response
|
| 24 |
def predict(request: Request, payload: HeartSample):
|
|
|
|
|
|
|
| 25 |
model = get_model_from_state(request)
|
| 26 |
if model is None:
|
|
|
|
| 27 |
return {
|
| 28 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 29 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
| 30 |
"data": {"detail": "Model is not loaded."},
|
| 31 |
}
|
| 32 |
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
|
|
|
|
|
|
| 36 |
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
|
| 50 |
@router.post("/batch-predictions", tags=["Prediction"])
|
| 51 |
@construct_response
|
| 52 |
def predict_batch(request: Request, payload: list[HeartSample]):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
model = get_model_from_state(request)
|
| 54 |
if model is None:
|
|
|
|
|
|
|
|
|
|
| 55 |
return {
|
| 56 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 57 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
| 58 |
"data": {"detail": "Model is not loaded."},
|
| 59 |
}
|
| 60 |
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
|
| 66 |
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
"prediction": pred,
|
| 74 |
-
}
|
| 75 |
)
|
| 76 |
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 85 |
-
|
| 86 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
|
| 88 |
|
| 89 |
@router.post("/explanations", tags=["Explainability"])
|
|
@@ -91,6 +167,7 @@ def predict_batch(request: Request, payload: list[HeartSample]):
|
|
| 91 |
def explain(request: Request, payload: HeartSample):
|
| 92 |
model = get_model_from_state(request)
|
| 93 |
if model is None:
|
|
|
|
| 94 |
return {
|
| 95 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 96 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
|
@@ -102,6 +179,7 @@ def explain(request: Request, payload: HeartSample):
|
|
| 102 |
|
| 103 |
data: dict[str, Any] = {"input": payload.model_dump()}
|
| 104 |
model_type = MODEL_PATH.stem
|
|
|
|
| 105 |
|
| 106 |
try:
|
| 107 |
logger.info("Computing explanation for default model prediction...")
|
|
@@ -109,10 +187,14 @@ def explain(request: Request, payload: HeartSample):
|
|
| 109 |
if explanations:
|
| 110 |
data["explanations"] = explanations
|
| 111 |
logger.success("Explanation computed successfully for default model.")
|
|
|
|
| 112 |
else:
|
| 113 |
logger.warning("No explanation available for default model.")
|
| 114 |
except Exception as e:
|
| 115 |
logger.exception(f"Failed to compute explanation: {e}")
|
|
|
|
|
|
|
|
|
|
| 116 |
|
| 117 |
try:
|
| 118 |
plot_path = FIGURES_DIR / f"shap_waterfall_default_{model_type}.png"
|
|
@@ -126,6 +208,12 @@ def explain(request: Request, payload: HeartSample):
|
|
| 126 |
data["explanation_plot_url"] = f"/figures/{saved_path.name}"
|
| 127 |
except Exception as e:
|
| 128 |
logger.exception(f"Failed to generate explanation plot: {e}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
logger.success("Explanation completed successfully for /explanations")
|
| 131 |
return {
|
|
|
|
| 1 |
from http import HTTPStatus
|
| 2 |
+
import time
|
| 3 |
from typing import Any
|
| 4 |
|
| 5 |
from fastapi import APIRouter, Request
|
| 6 |
from loguru import logger
|
| 7 |
import pandas as pd
|
| 8 |
+
from predicting_outcomes_in_heart_failure.app.deepchecks_monitoring import (
|
| 9 |
+
production_data_collector as pdc,
|
| 10 |
+
)
|
| 11 |
+
from predicting_outcomes_in_heart_failure.app.monitoring import (
|
| 12 |
+
batch_size_histogram,
|
| 13 |
+
explanation_counter,
|
| 14 |
+
model_error_counter,
|
| 15 |
+
prediction_counter,
|
| 16 |
+
prediction_processing_time,
|
| 17 |
+
prediction_result_counter,
|
| 18 |
+
)
|
| 19 |
from predicting_outcomes_in_heart_failure.app.schema import HeartSample
|
| 20 |
from predicting_outcomes_in_heart_failure.app.utils import (
|
| 21 |
construct_response,
|
| 22 |
get_model_from_state,
|
| 23 |
)
|
| 24 |
+
from predicting_outcomes_in_heart_failure.config import (
|
| 25 |
+
FIGURES_DIR,
|
| 26 |
+
INPUT_COLUMNS,
|
| 27 |
+
MODEL_PATH,
|
| 28 |
+
PRODUCTION_CSV_PATH,
|
| 29 |
+
)
|
| 30 |
from predicting_outcomes_in_heart_failure.modeling.explainability import (
|
| 31 |
explain_prediction,
|
| 32 |
save_shap_waterfall_plot,
|
|
|
|
| 39 |
@router.post("/predictions", tags=["Prediction"])
|
| 40 |
@construct_response
|
| 41 |
def predict(request: Request, payload: HeartSample):
|
| 42 |
+
prediction_counter.labels(prediction_type="single", endpoint="/predictions").inc()
|
| 43 |
+
|
| 44 |
model = get_model_from_state(request)
|
| 45 |
if model is None:
|
| 46 |
+
model_error_counter.labels(error_type="model_not_loaded", endpoint="/predictions").inc()
|
| 47 |
return {
|
| 48 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 49 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
| 50 |
"data": {"detail": "Model is not loaded."},
|
| 51 |
}
|
| 52 |
|
| 53 |
+
start_time = time.time()
|
| 54 |
+
try:
|
| 55 |
+
X_raw = payload.to_dataframe()
|
| 56 |
+
X = preprocessing(X_raw)
|
| 57 |
+
y_pred = int(model.predict(X)[0])
|
| 58 |
|
| 59 |
+
pdc.append_predictions_to_csv(
|
| 60 |
+
csv_path=PRODUCTION_CSV_PATH,
|
| 61 |
+
endpoint="/predictions",
|
| 62 |
+
X=X,
|
| 63 |
+
y_pred=y_pred,
|
| 64 |
+
feature_columns=list(INPUT_COLUMNS),
|
| 65 |
+
)
|
| 66 |
|
| 67 |
+
processing_time = time.time() - start_time
|
| 68 |
+
prediction_processing_time.labels(
|
| 69 |
+
prediction_type="single", endpoint="/predictions"
|
| 70 |
+
).observe(processing_time)
|
| 71 |
+
|
| 72 |
+
prediction_result_counter.labels(
|
| 73 |
+
prediction_class=str(y_pred), endpoint="/predictions"
|
| 74 |
+
).inc()
|
| 75 |
+
|
| 76 |
+
data: dict[str, Any] = {
|
| 77 |
+
"input": payload.model_dump(),
|
| 78 |
+
"prediction": y_pred,
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
logger.success("Prediction completed successfully for /predictions")
|
| 82 |
+
return {
|
| 83 |
+
"message": HTTPStatus.OK.phrase,
|
| 84 |
+
"status-code": HTTPStatus.OK,
|
| 85 |
+
"data": data,
|
| 86 |
+
}
|
| 87 |
+
except Exception as e:
|
| 88 |
+
model_error_counter.labels(error_type="prediction_error", endpoint="/predictions").inc()
|
| 89 |
+
logger.exception(f"Prediction error: {e}")
|
| 90 |
+
raise
|
| 91 |
|
| 92 |
|
| 93 |
@router.post("/batch-predictions", tags=["Prediction"])
|
| 94 |
@construct_response
|
| 95 |
def predict_batch(request: Request, payload: list[HeartSample]):
|
| 96 |
+
prediction_counter.labels(prediction_type="batch", endpoint="/batch-predictions").inc()
|
| 97 |
+
batch_size = len(payload)
|
| 98 |
+
batch_size_histogram.labels(endpoint="/batch-predictions").observe(batch_size)
|
| 99 |
+
|
| 100 |
model = get_model_from_state(request)
|
| 101 |
if model is None:
|
| 102 |
+
model_error_counter.labels(
|
| 103 |
+
error_type="model_not_loaded", endpoint="/batch-predictions"
|
| 104 |
+
).inc()
|
| 105 |
return {
|
| 106 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 107 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
| 108 |
"data": {"detail": "Model is not loaded."},
|
| 109 |
}
|
| 110 |
|
| 111 |
+
start_time = time.time()
|
| 112 |
+
try:
|
| 113 |
+
X_raw_list = [sample.to_dataframe() for sample in payload]
|
| 114 |
+
X_raw = pd.concat(X_raw_list, ignore_index=True)
|
| 115 |
+
X = preprocessing(X_raw)
|
| 116 |
|
| 117 |
+
y_pred = [int(y) for y in model.predict(X)]
|
| 118 |
|
| 119 |
+
pdc.append_predictions_to_csv(
|
| 120 |
+
csv_path=PRODUCTION_CSV_PATH,
|
| 121 |
+
endpoint="/batch-predictions",
|
| 122 |
+
X=X,
|
| 123 |
+
y_pred=y_pred,
|
| 124 |
+
feature_columns=list(INPUT_COLUMNS),
|
|
|
|
|
|
|
| 125 |
)
|
| 126 |
|
| 127 |
+
processing_time = time.time() - start_time
|
| 128 |
+
prediction_processing_time.labels(
|
| 129 |
+
prediction_type="batch", endpoint="/batch-predictions"
|
| 130 |
+
).observe(processing_time)
|
| 131 |
+
|
| 132 |
+
for pred in y_pred:
|
| 133 |
+
prediction_result_counter.labels(
|
| 134 |
+
prediction_class=str(pred), endpoint="/batch-predictions"
|
| 135 |
+
).inc()
|
| 136 |
+
|
| 137 |
+
results: list[dict[str, Any]] = []
|
| 138 |
+
for idx, (sample, pred) in enumerate(zip(payload, y_pred, strict=True)):
|
| 139 |
+
results.append(
|
| 140 |
+
{
|
| 141 |
+
"index": idx,
|
| 142 |
+
"input": sample.model_dump(),
|
| 143 |
+
"prediction": pred,
|
| 144 |
+
}
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
data: dict[str, Any] = {
|
| 148 |
+
"results": results,
|
| 149 |
+
"batch_size": len(results),
|
| 150 |
+
}
|
| 151 |
|
| 152 |
+
return {
|
| 153 |
+
"message": HTTPStatus.OK.phrase,
|
| 154 |
+
"status-code": HTTPStatus.OK,
|
| 155 |
+
"data": data,
|
| 156 |
+
}
|
| 157 |
+
except Exception as e:
|
| 158 |
+
model_error_counter.labels(
|
| 159 |
+
error_type="prediction_error", endpoint="/batch-predictions"
|
| 160 |
+
).inc()
|
| 161 |
+
logger.exception(f"Batch prediction error: {e}")
|
| 162 |
+
raise
|
| 163 |
|
| 164 |
|
| 165 |
@router.post("/explanations", tags=["Explainability"])
|
|
|
|
| 167 |
def explain(request: Request, payload: HeartSample):
|
| 168 |
model = get_model_from_state(request)
|
| 169 |
if model is None:
|
| 170 |
+
explanation_counter.labels(status="error_model_not_loaded", endpoint="/explanations").inc()
|
| 171 |
return {
|
| 172 |
"message": HTTPStatus.SERVICE_UNAVAILABLE.phrase,
|
| 173 |
"status-code": HTTPStatus.SERVICE_UNAVAILABLE,
|
|
|
|
| 179 |
|
| 180 |
data: dict[str, Any] = {"input": payload.model_dump()}
|
| 181 |
model_type = MODEL_PATH.stem
|
| 182 |
+
explanation_success = False
|
| 183 |
|
| 184 |
try:
|
| 185 |
logger.info("Computing explanation for default model prediction...")
|
|
|
|
| 187 |
if explanations:
|
| 188 |
data["explanations"] = explanations
|
| 189 |
logger.success("Explanation computed successfully for default model.")
|
| 190 |
+
explanation_success = True
|
| 191 |
else:
|
| 192 |
logger.warning("No explanation available for default model.")
|
| 193 |
except Exception as e:
|
| 194 |
logger.exception(f"Failed to compute explanation: {e}")
|
| 195 |
+
explanation_counter.labels(
|
| 196 |
+
status="error_computation_failed", endpoint="/explanations"
|
| 197 |
+
).inc()
|
| 198 |
|
| 199 |
try:
|
| 200 |
plot_path = FIGURES_DIR / f"shap_waterfall_default_{model_type}.png"
|
|
|
|
| 208 |
data["explanation_plot_url"] = f"/figures/{saved_path.name}"
|
| 209 |
except Exception as e:
|
| 210 |
logger.exception(f"Failed to generate explanation plot: {e}")
|
| 211 |
+
explanation_counter.labels(
|
| 212 |
+
status="error_plot_generation_failed", endpoint="/explanations"
|
| 213 |
+
).inc()
|
| 214 |
+
|
| 215 |
+
if explanation_success:
|
| 216 |
+
explanation_counter.labels(status="success", endpoint="/explanations").inc()
|
| 217 |
|
| 218 |
logger.success("Explanation completed successfully for /explanations")
|
| 219 |
return {
|
predicting_outcomes_in_heart_failure/config.py
CHANGED
|
@@ -123,6 +123,24 @@ CARD_PATHS = {
|
|
| 123 |
"model_card": MODELS_DIR / "README.md",
|
| 124 |
}
|
| 125 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 126 |
# ----------------------------
|
| 127 |
# API
|
| 128 |
# ----------------------------
|
|
|
|
| 123 |
"model_card": MODELS_DIR / "README.md",
|
| 124 |
}
|
| 125 |
|
| 126 |
+
MONITORING_DIR = DATA_DIR / "monitoring"
|
| 127 |
+
PRODUCTION_CSV_PATH = MONITORING_DIR / "production_inputs.csv"
|
| 128 |
+
REFERENCE_CSV = PROCESSED_DATA_DIR / "nosex" / "train.csv"
|
| 129 |
+
REPORTS_DIR = MONITORING_DIR / "reports"
|
| 130 |
+
STATE_PATH = MONITORING_DIR / "state.json"
|
| 131 |
+
|
| 132 |
+
CAT_FEATURES = [
|
| 133 |
+
"ChestPainType_ASY",
|
| 134 |
+
"ChestPainType_ATA",
|
| 135 |
+
"ChestPainType_NAP",
|
| 136 |
+
"ChestPainType_TA",
|
| 137 |
+
"RestingECG_LVH",
|
| 138 |
+
"RestingECG_Normal",
|
| 139 |
+
"RestingECG_ST",
|
| 140 |
+
"ST_Slope_Down",
|
| 141 |
+
"ST_Slope_Flat",
|
| 142 |
+
"ST_Slope_Up",
|
| 143 |
+
]
|
| 144 |
# ----------------------------
|
| 145 |
# API
|
| 146 |
# ----------------------------
|
prometheus.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
global:
|
| 2 |
+
scrape_interval: 15s
|
| 3 |
+
|
| 4 |
+
external_labels:
|
| 5 |
+
monitor: "codelab-monitor"
|
| 6 |
+
|
| 7 |
+
scrape_configs:
|
| 8 |
+
- job_name: "fastapi"
|
| 9 |
+
scrape_interval: 5s
|
| 10 |
+
static_configs:
|
| 11 |
+
- targets: ["app:7860"]
|
| 12 |
+
metrics_path: /metrics
|
| 13 |
+
|
| 14 |
+
- job_name: "prometheus"
|
| 15 |
+
static_configs:
|
| 16 |
+
- targets: ["localhost:9090"]
|
pyproject.toml
CHANGED
|
@@ -16,11 +16,14 @@ classifiers = [
|
|
| 16 |
|
| 17 |
]
|
| 18 |
dependencies = [
|
|
|
|
| 19 |
"asttokens>=3.0.0",
|
| 20 |
"boto3>=1.36.0",
|
| 21 |
"botocore>=1.36.0",
|
| 22 |
"dagshub>=0.6.3",
|
|
|
|
| 23 |
"dvc-s3>=3.2.2",
|
|
|
|
| 24 |
"gradio>=6.0.2",
|
| 25 |
"great-expectations>=1.9.0",
|
| 26 |
"httpx>=0.28.1",
|
|
@@ -32,15 +35,18 @@ dependencies = [
|
|
| 32 |
"matplotlib>=3.10.7",
|
| 33 |
"mkdocs",
|
| 34 |
"mlflow==2.22.0",
|
| 35 |
-
"numpy
|
| 36 |
"pandas>=2.3.3",
|
| 37 |
"pip",
|
|
|
|
|
|
|
| 38 |
"pytest",
|
|
|
|
| 39 |
"python-dotenv",
|
| 40 |
"ruff",
|
| 41 |
"scikit-learn>=1.7.2",
|
| 42 |
"seaborn>=0.13.2",
|
| 43 |
-
"shap
|
| 44 |
"tqdm",
|
| 45 |
"typer",
|
| 46 |
]
|
|
|
|
| 16 |
|
| 17 |
]
|
| 18 |
dependencies = [
|
| 19 |
+
"apscheduler>=3.11.2",
|
| 20 |
"asttokens>=3.0.0",
|
| 21 |
"boto3>=1.36.0",
|
| 22 |
"botocore>=1.36.0",
|
| 23 |
"dagshub>=0.6.3",
|
| 24 |
+
"deepchecks>=0.19.1",
|
| 25 |
"dvc-s3>=3.2.2",
|
| 26 |
+
"dvc[s3]>=3.64.2",
|
| 27 |
"gradio>=6.0.2",
|
| 28 |
"great-expectations>=1.9.0",
|
| 29 |
"httpx>=0.28.1",
|
|
|
|
| 35 |
"matplotlib>=3.10.7",
|
| 36 |
"mkdocs",
|
| 37 |
"mlflow==2.22.0",
|
| 38 |
+
"numpy<2",
|
| 39 |
"pandas>=2.3.3",
|
| 40 |
"pip",
|
| 41 |
+
"prometheus-client>=0.23.1",
|
| 42 |
+
"prometheus-fastapi-instrumentator>=7.1.0",
|
| 43 |
"pytest",
|
| 44 |
+
"pytest-html>=4.1.1",
|
| 45 |
"python-dotenv",
|
| 46 |
"ruff",
|
| 47 |
"scikit-learn>=1.7.2",
|
| 48 |
"seaborn>=0.13.2",
|
| 49 |
+
"shap<0.50",
|
| 50 |
"tqdm",
|
| 51 |
"typer",
|
| 52 |
]
|
uv.lock
CHANGED
|
@@ -214,6 +214,64 @@ wheels = [
|
|
| 214 |
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
|
| 215 |
]
|
| 216 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 217 |
[[package]]
|
| 218 |
name = "asttokens"
|
| 219 |
version = "3.0.0"
|
|
@@ -366,6 +424,23 @@ wheels = [
|
|
| 366 |
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
|
| 367 |
]
|
| 368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
[[package]]
|
| 370 |
name = "celery"
|
| 371 |
version = "5.6.0"
|
|
@@ -716,6 +791,35 @@ wheels = [
|
|
| 716 |
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
|
| 717 |
]
|
| 718 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 719 |
[[package]]
|
| 720 |
name = "defusedxml"
|
| 721 |
version = "0.7.1"
|
|
@@ -846,6 +950,11 @@ wheels = [
|
|
| 846 |
{ url = "https://files.pythonhosted.org/packages/e1/e6/1782bcb8cdf82a971c1447d83f69efb3356945b68e28a28708fa4ddfa3c3/dvc-3.64.2-py3-none-any.whl", hash = "sha256:14e76baaef50cc10a43aaea788b49ea965835a59c16d0d63693a9ba1c2001090", size = 467981, upload-time = "2025-12-06T05:10:58.565Z" },
|
| 847 |
]
|
| 848 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 849 |
[[package]]
|
| 850 |
name = "dvc-data"
|
| 851 |
version = "3.16.12"
|
|
@@ -1078,6 +1187,15 @@ wheels = [
|
|
| 1078 |
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" },
|
| 1079 |
]
|
| 1080 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1081 |
[[package]]
|
| 1082 |
name = "frozenlist"
|
| 1083 |
version = "1.8.0"
|
|
@@ -1570,6 +1688,34 @@ wheels = [
|
|
| 1570 |
{ url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" },
|
| 1571 |
]
|
| 1572 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1573 |
[[package]]
|
| 1574 |
name = "iterative-telemetry"
|
| 1575 |
version = "0.0.10"
|
|
@@ -1636,6 +1782,24 @@ wheels = [
|
|
| 1636 |
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
|
| 1637 |
]
|
| 1638 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1639 |
[[package]]
|
| 1640 |
name = "jsonschema"
|
| 1641 |
version = "4.25.1"
|
|
@@ -1651,6 +1815,19 @@ wheels = [
|
|
| 1651 |
{ url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
|
| 1652 |
]
|
| 1653 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1654 |
[[package]]
|
| 1655 |
name = "jsonschema-specifications"
|
| 1656 |
version = "2025.9.1"
|
|
@@ -1692,6 +1869,68 @@ wheels = [
|
|
| 1692 |
{ url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
|
| 1693 |
]
|
| 1694 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1695 |
[[package]]
|
| 1696 |
name = "jupyterlab-pygments"
|
| 1697 |
version = "0.3.0"
|
|
@@ -1701,6 +1940,15 @@ wheels = [
|
|
| 1701 |
{ url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
|
| 1702 |
]
|
| 1703 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1704 |
[[package]]
|
| 1705 |
name = "kagglehub"
|
| 1706 |
version = "0.3.13"
|
|
@@ -1757,6 +2005,15 @@ wheels = [
|
|
| 1757 |
{ url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" },
|
| 1758 |
]
|
| 1759 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1760 |
[[package]]
|
| 1761 |
name = "llvmlite"
|
| 1762 |
version = "0.45.1"
|
|
@@ -2074,6 +2331,15 @@ wheels = [
|
|
| 2074 |
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
| 2075 |
]
|
| 2076 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2077 |
[[package]]
|
| 2078 |
name = "nbclient"
|
| 2079 |
version = "0.10.2"
|
|
@@ -2166,28 +2432,18 @@ wheels = [
|
|
| 2166 |
|
| 2167 |
[[package]]
|
| 2168 |
name = "numpy"
|
| 2169 |
-
version = "
|
| 2170 |
-
source = { registry = "https://pypi.org/simple" }
|
| 2171 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 2172 |
-
wheels = [
|
| 2173 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2174 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2175 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2176 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2177 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2178 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2179 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2180 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 2181 |
-
{ url = "https://files.pythonhosted.org/packages/cf/85/f6f00d019b0cc741e64b4e00ce865a57b6bed945d1bbeb1ccadbc647959b/numpy-2.3.4-cp311-cp311-win32.whl", hash = "sha256:ed759bf7a70342f7817d88376eb7142fab9fef8320d6019ef87fae05a99874e1", size = 6570076, upload-time = "2025-10-15T16:15:38.225Z" },
|
| 2182 |
-
{ url = "https://files.pythonhosted.org/packages/7d/10/f8850982021cb90e2ec31990291f9e830ce7d94eef432b15066e7cbe0bec/numpy-2.3.4-cp311-cp311-win_amd64.whl", hash = "sha256:faba246fb30ea2a526c2e9645f61612341de1a83fb1e0c5edf4ddda5a9c10996", size = 13089358, upload-time = "2025-10-15T16:15:40.404Z" },
|
| 2183 |
-
{ url = "https://files.pythonhosted.org/packages/d1/ad/afdd8351385edf0b3445f9e24210a9c3971ef4de8fd85155462fc4321d79/numpy-2.3.4-cp311-cp311-win_arm64.whl", hash = "sha256:4c01835e718bcebe80394fd0ac66c07cbb90147ebbdad3dcecd3f25de2ae7e2c", size = 10462292, upload-time = "2025-10-15T16:15:42.896Z" },
|
| 2184 |
-
{ url = "https://files.pythonhosted.org/packages/b1/b6/64898f51a86ec88ca1257a59c1d7fd077b60082a119affefcdf1dd0df8ca/numpy-2.3.4-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:6e274603039f924c0fe5cb73438fa9246699c78a6df1bd3decef9ae592ae1c05", size = 21131552, upload-time = "2025-10-15T16:17:55.845Z" },
|
| 2185 |
-
{ url = "https://files.pythonhosted.org/packages/ce/4c/f135dc6ebe2b6a3c77f4e4838fa63d350f85c99462012306ada1bd4bc460/numpy-2.3.4-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:d149aee5c72176d9ddbc6803aef9c0f6d2ceeea7626574fc68518da5476fa346", size = 14377796, upload-time = "2025-10-15T16:17:58.308Z" },
|
| 2186 |
-
{ url = "https://files.pythonhosted.org/packages/d0/a4/f33f9c23fcc13dd8412fc8614559b5b797e0aba9d8e01dfa8bae10c84004/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_arm64.whl", hash = "sha256:6d34ed9db9e6395bb6cd33286035f73a59b058169733a9db9f85e650b88df37e", size = 5306904, upload-time = "2025-10-15T16:18:00.596Z" },
|
| 2187 |
-
{ url = "https://files.pythonhosted.org/packages/28/af/c44097f25f834360f9fb960fa082863e0bad14a42f36527b2a121abdec56/numpy-2.3.4-pp311-pypy311_pp73-macosx_14_0_x86_64.whl", hash = "sha256:fdebe771ca06bb8d6abce84e51dca9f7921fe6ad34a0c914541b063e9a68928b", size = 6819682, upload-time = "2025-10-15T16:18:02.32Z" },
|
| 2188 |
-
{ url = "https://files.pythonhosted.org/packages/c5/8c/cd283b54c3c2b77e188f63e23039844f56b23bba1712318288c13fe86baf/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:957e92defe6c08211eb77902253b14fe5b480ebc5112bc741fd5e9cd0608f847", size = 14422300, upload-time = "2025-10-15T16:18:04.271Z" },
|
| 2189 |
-
{ url = "https://files.pythonhosted.org/packages/b0/f0/8404db5098d92446b3e3695cf41c6f0ecb703d701cb0b7566ee2177f2eee/numpy-2.3.4-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13b9062e4f5c7ee5c7e5be96f29ba71bc5a37fed3d1d77c37390ae00724d296d", size = 16760806, upload-time = "2025-10-15T16:18:06.668Z" },
|
| 2190 |
-
{ url = "https://files.pythonhosted.org/packages/95/8e/2844c3959ce9a63acc7c8e50881133d86666f0420bcde695e115ced0920f/numpy-2.3.4-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:81b3a59793523e552c4a96109dde028aa4448ae06ccac5a76ff6532a85558a7f", size = 12973130, upload-time = "2025-10-15T16:18:09.397Z" },
|
| 2191 |
]
|
| 2192 |
|
| 2193 |
[[package]]
|
|
@@ -2266,6 +2522,15 @@ wheels = [
|
|
| 2266 |
{ url = "https://files.pythonhosted.org/packages/0f/dc/9484127cc1aa213be398ed735f5f270eedcb0c0977303a6f6ddc46b60204/orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045", size = 126252, upload-time = "2025-10-24T15:49:08.869Z" },
|
| 2267 |
]
|
| 2268 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2269 |
[[package]]
|
| 2270 |
name = "packaging"
|
| 2271 |
version = "24.2"
|
|
@@ -2332,6 +2597,18 @@ wheels = [
|
|
| 2332 |
{ url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" },
|
| 2333 |
]
|
| 2334 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2335 |
[[package]]
|
| 2336 |
name = "pexpect"
|
| 2337 |
version = "4.9.0"
|
|
@@ -2388,6 +2665,19 @@ wheels = [
|
|
| 2388 |
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
| 2389 |
]
|
| 2390 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2391 |
[[package]]
|
| 2392 |
name = "pluggy"
|
| 2393 |
version = "1.6.0"
|
|
@@ -2402,10 +2692,13 @@ name = "predicting-outcomes-in-heart-failure"
|
|
| 2402 |
version = "0.0.1"
|
| 2403 |
source = { editable = "." }
|
| 2404 |
dependencies = [
|
|
|
|
| 2405 |
{ name = "asttokens" },
|
| 2406 |
{ name = "boto3" },
|
| 2407 |
{ name = "botocore" },
|
| 2408 |
{ name = "dagshub" },
|
|
|
|
|
|
|
| 2409 |
{ name = "dvc-s3" },
|
| 2410 |
{ name = "gradio" },
|
| 2411 |
{ name = "great-expectations" },
|
|
@@ -2421,7 +2714,10 @@ dependencies = [
|
|
| 2421 |
{ name = "numpy" },
|
| 2422 |
{ name = "pandas" },
|
| 2423 |
{ name = "pip" },
|
|
|
|
|
|
|
| 2424 |
{ name = "pytest" },
|
|
|
|
| 2425 |
{ name = "python-dotenv" },
|
| 2426 |
{ name = "ruff" },
|
| 2427 |
{ name = "scikit-learn" },
|
|
@@ -2439,10 +2735,13 @@ dev = [
|
|
| 2439 |
|
| 2440 |
[package.metadata]
|
| 2441 |
requires-dist = [
|
|
|
|
| 2442 |
{ name = "asttokens", specifier = ">=3.0.0" },
|
| 2443 |
{ name = "boto3", specifier = ">=1.36.0" },
|
| 2444 |
{ name = "botocore", specifier = ">=1.36.0" },
|
| 2445 |
{ name = "dagshub", specifier = ">=0.6.3" },
|
|
|
|
|
|
|
| 2446 |
{ name = "dvc-s3", specifier = ">=3.2.2" },
|
| 2447 |
{ name = "gradio", specifier = ">=6.0.2" },
|
| 2448 |
{ name = "great-expectations", specifier = ">=1.9.0" },
|
|
@@ -2455,15 +2754,18 @@ requires-dist = [
|
|
| 2455 |
{ name = "matplotlib", specifier = ">=3.10.7" },
|
| 2456 |
{ name = "mkdocs" },
|
| 2457 |
{ name = "mlflow", specifier = "==2.22.0" },
|
| 2458 |
-
{ name = "numpy", specifier = "
|
| 2459 |
{ name = "pandas", specifier = ">=2.3.3" },
|
| 2460 |
{ name = "pip" },
|
|
|
|
|
|
|
| 2461 |
{ name = "pytest" },
|
|
|
|
| 2462 |
{ name = "python-dotenv" },
|
| 2463 |
{ name = "ruff" },
|
| 2464 |
{ name = "scikit-learn", specifier = ">=1.7.2" },
|
| 2465 |
{ name = "seaborn", specifier = ">=0.13.2" },
|
| 2466 |
-
{ name = "shap", specifier = "
|
| 2467 |
{ name = "tqdm" },
|
| 2468 |
{ name = "typer" },
|
| 2469 |
]
|
|
@@ -2474,6 +2776,28 @@ dev = [
|
|
| 2474 |
{ name = "ruff", specifier = ">=0.14.2" },
|
| 2475 |
]
|
| 2476 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2477 |
[[package]]
|
| 2478 |
name = "prompt-toolkit"
|
| 2479 |
version = "3.0.52"
|
|
@@ -2749,6 +3073,19 @@ wheels = [
|
|
| 2749 |
{ url = "https://files.pythonhosted.org/packages/86/30/9bcd030408ae80e3a516da13834065d667798a622309fb891d50e77d30d6/pynblint-0.1.6-py3-none-any.whl", hash = "sha256:8bb972696431144768ba6bf238a83f646c3faa4dac2810338ef87fb24d91742c", size = 24356, upload-time = "2024-08-12T08:53:20.164Z" },
|
| 2750 |
]
|
| 2751 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2752 |
[[package]]
|
| 2753 |
name = "pyparsing"
|
| 2754 |
version = "3.2.5"
|
|
@@ -2774,6 +3111,32 @@ wheels = [
|
|
| 2774 |
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
| 2775 |
]
|
| 2776 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2777 |
[[package]]
|
| 2778 |
name = "python-dateutil"
|
| 2779 |
version = "2.9.0.post0"
|
|
@@ -2795,6 +3158,15 @@ wheels = [
|
|
| 2795 |
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
| 2796 |
]
|
| 2797 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2798 |
[[package]]
|
| 2799 |
name = "python-multipart"
|
| 2800 |
version = "0.0.20"
|
|
@@ -2804,6 +3176,18 @@ wheels = [
|
|
| 2804 |
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
| 2805 |
]
|
| 2806 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2807 |
[[package]]
|
| 2808 |
name = "pytz"
|
| 2809 |
version = "2025.2"
|
|
@@ -2823,6 +3207,15 @@ wheels = [
|
|
| 2823 |
{ url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
|
| 2824 |
]
|
| 2825 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2826 |
[[package]]
|
| 2827 |
name = "pyyaml"
|
| 2828 |
version = "6.0.3"
|
|
@@ -2929,6 +3322,39 @@ wheels = [
|
|
| 2929 |
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
| 2930 |
]
|
| 2931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2932 |
[[package]]
|
| 2933 |
name = "rich"
|
| 2934 |
version = "13.9.4"
|
|
@@ -3176,6 +3602,15 @@ wheels = [
|
|
| 3176 |
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
| 3177 |
]
|
| 3178 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3179 |
[[package]]
|
| 3180 |
name = "setuptools"
|
| 3181 |
version = "80.9.0"
|
|
@@ -3187,7 +3622,7 @@ wheels = [
|
|
| 3187 |
|
| 3188 |
[[package]]
|
| 3189 |
name = "shap"
|
| 3190 |
-
version = "0.
|
| 3191 |
source = { registry = "https://pypi.org/simple" }
|
| 3192 |
dependencies = [
|
| 3193 |
{ name = "cloudpickle" },
|
|
@@ -3201,15 +3636,14 @@ dependencies = [
|
|
| 3201 |
{ name = "tqdm" },
|
| 3202 |
{ name = "typing-extensions" },
|
| 3203 |
]
|
| 3204 |
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
| 3205 |
wheels = [
|
| 3206 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3207 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3208 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3209 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3210 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3211 |
-
{ url = "https://files.pythonhosted.org/packages/
|
| 3212 |
-
{ url = "https://files.pythonhosted.org/packages/77/03/58e199cf59056d68b4a227ce4b2b09eeb0c9bd1d002b9e28fb574eed6200/shap-0.50.0-cp311-cp311-win_amd64.whl", hash = "sha256:f5d0e2df7142393791c10a201947ca06972eb3c599c48a33697ef6c405413267", size = 547991, upload-time = "2025-11-11T18:36:07.402Z" },
|
| 3213 |
]
|
| 3214 |
|
| 3215 |
[[package]]
|
|
@@ -3355,6 +3789,27 @@ wheels = [
|
|
| 3355 |
{ url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z" },
|
| 3356 |
]
|
| 3357 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3358 |
[[package]]
|
| 3359 |
name = "tabulate"
|
| 3360 |
version = "0.9.0"
|
|
@@ -3373,6 +3828,20 @@ wheels = [
|
|
| 3373 |
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
| 3374 |
]
|
| 3375 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3376 |
[[package]]
|
| 3377 |
name = "threadpoolctl"
|
| 3378 |
version = "3.6.0"
|
|
@@ -3547,6 +4016,15 @@ wheels = [
|
|
| 3547 |
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
| 3548 |
]
|
| 3549 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3550 |
[[package]]
|
| 3551 |
name = "urllib3"
|
| 3552 |
version = "2.5.0"
|
|
@@ -3626,6 +4104,15 @@ wheels = [
|
|
| 3626 |
{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
|
| 3627 |
]
|
| 3628 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3629 |
[[package]]
|
| 3630 |
name = "webencodings"
|
| 3631 |
version = "0.5.1"
|
|
@@ -3635,6 +4122,15 @@ wheels = [
|
|
| 3635 |
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
|
| 3636 |
]
|
| 3637 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3638 |
[[package]]
|
| 3639 |
name = "werkzeug"
|
| 3640 |
version = "3.1.3"
|
|
@@ -3647,6 +4143,15 @@ wheels = [
|
|
| 3647 |
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" },
|
| 3648 |
]
|
| 3649 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3650 |
[[package]]
|
| 3651 |
name = "win32-setctime"
|
| 3652 |
version = "1.2.0"
|
|
|
|
| 214 |
{ url = "https://files.pythonhosted.org/packages/81/29/5ecc3a15d5a33e31b26c11426c45c501e439cb865d0bff96315d86443b78/appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c", size = 4321, upload-time = "2024-02-06T09:43:09.663Z" },
|
| 215 |
]
|
| 216 |
|
| 217 |
+
[[package]]
|
| 218 |
+
name = "apscheduler"
|
| 219 |
+
version = "3.11.2"
|
| 220 |
+
source = { registry = "https://pypi.org/simple" }
|
| 221 |
+
dependencies = [
|
| 222 |
+
{ name = "tzlocal" },
|
| 223 |
+
]
|
| 224 |
+
sdist = { url = "https://files.pythonhosted.org/packages/07/12/3e4389e5920b4c1763390c6d371162f3784f86f85cd6d6c1bfe68eef14e2/apscheduler-3.11.2.tar.gz", hash = "sha256:2a9966b052ec805f020c8c4c3ae6e6a06e24b1bf19f2e11d91d8cca0473eef41", size = 108683, upload-time = "2025-12-22T00:39:34.884Z" }
|
| 225 |
+
wheels = [
|
| 226 |
+
{ url = "https://files.pythonhosted.org/packages/9f/64/2e54428beba8d9992aa478bb8f6de9e4ecaa5f8f513bcfd567ed7fb0262d/apscheduler-3.11.2-py3-none-any.whl", hash = "sha256:ce005177f741409db4e4dd40a7431b76feb856b9dd69d57e0da49d6715bfd26d", size = 64439, upload-time = "2025-12-22T00:39:33.303Z" },
|
| 227 |
+
]
|
| 228 |
+
|
| 229 |
+
[[package]]
|
| 230 |
+
name = "argon2-cffi"
|
| 231 |
+
version = "25.1.0"
|
| 232 |
+
source = { registry = "https://pypi.org/simple" }
|
| 233 |
+
dependencies = [
|
| 234 |
+
{ name = "argon2-cffi-bindings" },
|
| 235 |
+
]
|
| 236 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0e/89/ce5af8a7d472a67cc819d5d998aa8c82c5d860608c4db9f46f1162d7dab9/argon2_cffi-25.1.0.tar.gz", hash = "sha256:694ae5cc8a42f4c4e2bf2ca0e64e51e23a040c6a517a85074683d3959e1346c1", size = 45706, upload-time = "2025-06-03T06:55:32.073Z" }
|
| 237 |
+
wheels = [
|
| 238 |
+
{ url = "https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl", hash = "sha256:fdc8b074db390fccb6eb4a3604ae7231f219aa669a2652e0f20e16ba513d5741", size = 14657, upload-time = "2025-06-03T06:55:30.804Z" },
|
| 239 |
+
]
|
| 240 |
+
|
| 241 |
+
[[package]]
|
| 242 |
+
name = "argon2-cffi-bindings"
|
| 243 |
+
version = "25.1.0"
|
| 244 |
+
source = { registry = "https://pypi.org/simple" }
|
| 245 |
+
dependencies = [
|
| 246 |
+
{ name = "cffi" },
|
| 247 |
+
]
|
| 248 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5c/2d/db8af0df73c1cf454f71b2bbe5e356b8c1f8041c979f505b3d3186e520a9/argon2_cffi_bindings-25.1.0.tar.gz", hash = "sha256:b957f3e6ea4d55d820e40ff76f450952807013d361a65d7f28acc0acbf29229d", size = 1783441, upload-time = "2025-07-30T10:02:05.147Z" }
|
| 249 |
+
wheels = [
|
| 250 |
+
{ url = "https://files.pythonhosted.org/packages/1d/57/96b8b9f93166147826da5f90376e784a10582dd39a393c99bb62cfcf52f0/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:aecba1723ae35330a008418a91ea6cfcedf6d31e5fbaa056a166462ff066d500", size = 54121, upload-time = "2025-07-30T10:01:50.815Z" },
|
| 251 |
+
{ url = "https://files.pythonhosted.org/packages/0a/08/a9bebdb2e0e602dde230bdde8021b29f71f7841bd54801bcfd514acb5dcf/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2630b6240b495dfab90aebe159ff784d08ea999aa4b0d17efa734055a07d2f44", size = 29177, upload-time = "2025-07-30T10:01:51.681Z" },
|
| 252 |
+
{ url = "https://files.pythonhosted.org/packages/b6/02/d297943bcacf05e4f2a94ab6f462831dc20158614e5d067c35d4e63b9acb/argon2_cffi_bindings-25.1.0-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:7aef0c91e2c0fbca6fc68e7555aa60ef7008a739cbe045541e438373bc54d2b0", size = 31090, upload-time = "2025-07-30T10:01:53.184Z" },
|
| 253 |
+
{ url = "https://files.pythonhosted.org/packages/c1/93/44365f3d75053e53893ec6d733e4a5e3147502663554b4d864587c7828a7/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1e021e87faa76ae0d413b619fe2b65ab9a037f24c60a1e6cc43457ae20de6dc6", size = 81246, upload-time = "2025-07-30T10:01:54.145Z" },
|
| 254 |
+
{ url = "https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d3e924cfc503018a714f94a49a149fdc0b644eaead5d1f089330399134fa028a", size = 87126, upload-time = "2025-07-30T10:01:55.074Z" },
|
| 255 |
+
{ url = "https://files.pythonhosted.org/packages/72/70/7a2993a12b0ffa2a9271259b79cc616e2389ed1a4d93842fac5a1f923ffd/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:c87b72589133f0346a1cb8d5ecca4b933e3c9b64656c9d175270a000e73b288d", size = 80343, upload-time = "2025-07-30T10:01:56.007Z" },
|
| 256 |
+
{ url = "https://files.pythonhosted.org/packages/78/9a/4e5157d893ffc712b74dbd868c7f62365618266982b64accab26bab01edc/argon2_cffi_bindings-25.1.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1db89609c06afa1a214a69a462ea741cf735b29a57530478c06eb81dd403de99", size = 86777, upload-time = "2025-07-30T10:01:56.943Z" },
|
| 257 |
+
{ url = "https://files.pythonhosted.org/packages/74/cd/15777dfde1c29d96de7f18edf4cc94c385646852e7c7b0320aa91ccca583/argon2_cffi_bindings-25.1.0-cp39-abi3-win32.whl", hash = "sha256:473bcb5f82924b1becbb637b63303ec8d10e84c8d241119419897a26116515d2", size = 27180, upload-time = "2025-07-30T10:01:57.759Z" },
|
| 258 |
+
{ url = "https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl", hash = "sha256:a98cd7d17e9f7ce244c0803cad3c23a7d379c301ba618a5fa76a67d116618b98", size = 31715, upload-time = "2025-07-30T10:01:58.56Z" },
|
| 259 |
+
{ url = "https://files.pythonhosted.org/packages/42/b9/f8d6fa329ab25128b7e98fd83a3cb34d9db5b059a9847eddb840a0af45dd/argon2_cffi_bindings-25.1.0-cp39-abi3-win_arm64.whl", hash = "sha256:b0fdbcf513833809c882823f98dc2f931cf659d9a1429616ac3adebb49f5db94", size = 27149, upload-time = "2025-07-30T10:01:59.329Z" },
|
| 260 |
+
]
|
| 261 |
+
|
| 262 |
+
[[package]]
|
| 263 |
+
name = "arrow"
|
| 264 |
+
version = "1.4.0"
|
| 265 |
+
source = { registry = "https://pypi.org/simple" }
|
| 266 |
+
dependencies = [
|
| 267 |
+
{ name = "python-dateutil" },
|
| 268 |
+
{ name = "tzdata" },
|
| 269 |
+
]
|
| 270 |
+
sdist = { url = "https://files.pythonhosted.org/packages/b9/33/032cdc44182491aa708d06a68b62434140d8c50820a087fac7af37703357/arrow-1.4.0.tar.gz", hash = "sha256:ed0cc050e98001b8779e84d461b0098c4ac597e88704a655582b21d116e526d7", size = 152931, upload-time = "2025-10-18T17:46:46.761Z" }
|
| 271 |
+
wheels = [
|
| 272 |
+
{ url = "https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl", hash = "sha256:749f0769958ebdc79c173ff0b0670d59051a535fa26e8eba02953dc19eb43205", size = 68797, upload-time = "2025-10-18T17:46:45.663Z" },
|
| 273 |
+
]
|
| 274 |
+
|
| 275 |
[[package]]
|
| 276 |
name = "asttokens"
|
| 277 |
version = "3.0.0"
|
|
|
|
| 424 |
{ url = "https://files.pythonhosted.org/packages/72/76/20fa66124dbe6be5cafeb312ece67de6b61dd91a0247d1ea13db4ebb33c2/cachetools-5.5.2-py3-none-any.whl", hash = "sha256:d26a22bcc62eb95c3beabd9f1ee5e820d3d2704fe2967cbe350e20c8ffcd3f0a", size = 10080, upload-time = "2025-02-20T21:01:16.647Z" },
|
| 425 |
]
|
| 426 |
|
| 427 |
+
[[package]]
|
| 428 |
+
name = "category-encoders"
|
| 429 |
+
version = "2.9.0"
|
| 430 |
+
source = { registry = "https://pypi.org/simple" }
|
| 431 |
+
dependencies = [
|
| 432 |
+
{ name = "numpy" },
|
| 433 |
+
{ name = "pandas" },
|
| 434 |
+
{ name = "patsy" },
|
| 435 |
+
{ name = "scikit-learn" },
|
| 436 |
+
{ name = "scipy" },
|
| 437 |
+
{ name = "statsmodels" },
|
| 438 |
+
]
|
| 439 |
+
sdist = { url = "https://files.pythonhosted.org/packages/29/59/1184ce74dca0c3e3450bccbb16edfce56f559c76dc794e2d52e1e63b467d/category_encoders-2.9.0.tar.gz", hash = "sha256:659311786e909013b8e8715fd1271244789a1dea278da44058828f88eeab5b40", size = 58005, upload-time = "2025-11-02T18:13:36.929Z" }
|
| 440 |
+
wheels = [
|
| 441 |
+
{ url = "https://files.pythonhosted.org/packages/a6/06/afcae4dab08612dac244ace7f478543f4fb83bea94177231ef9b4f7bfa06/category_encoders-2.9.0-py3-none-any.whl", hash = "sha256:49c0e49cd3bd93b21c0bcc928ecbe9b3d09951a6f7fff8cc67f1f33967887227", size = 85859, upload-time = "2025-11-02T18:13:35.388Z" },
|
| 442 |
+
]
|
| 443 |
+
|
| 444 |
[[package]]
|
| 445 |
name = "celery"
|
| 446 |
version = "5.6.0"
|
|
|
|
| 791 |
{ url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" },
|
| 792 |
]
|
| 793 |
|
| 794 |
+
[[package]]
|
| 795 |
+
name = "deepchecks"
|
| 796 |
+
version = "0.19.1"
|
| 797 |
+
source = { registry = "https://pypi.org/simple" }
|
| 798 |
+
dependencies = [
|
| 799 |
+
{ name = "beautifulsoup4" },
|
| 800 |
+
{ name = "category-encoders" },
|
| 801 |
+
{ name = "ipykernel" },
|
| 802 |
+
{ name = "ipython" },
|
| 803 |
+
{ name = "ipywidgets" },
|
| 804 |
+
{ name = "jsonpickle" },
|
| 805 |
+
{ name = "jupyter-server" },
|
| 806 |
+
{ name = "matplotlib" },
|
| 807 |
+
{ name = "numpy" },
|
| 808 |
+
{ name = "pandas" },
|
| 809 |
+
{ name = "plotly" },
|
| 810 |
+
{ name = "pynomaly" },
|
| 811 |
+
{ name = "requests" },
|
| 812 |
+
{ name = "scikit-learn" },
|
| 813 |
+
{ name = "scipy" },
|
| 814 |
+
{ name = "statsmodels" },
|
| 815 |
+
{ name = "tqdm" },
|
| 816 |
+
{ name = "typing-extensions" },
|
| 817 |
+
]
|
| 818 |
+
sdist = { url = "https://files.pythonhosted.org/packages/ca/3b/a7154865564d939bd9eb1c6fd40132f3756aa6e66227dc4af5e47f224824/deepchecks-0.19.1.tar.gz", hash = "sha256:12d5602cc404c81050a47b5135b66c33322dd752f8e6cad4558add049292e763", size = 7463007, upload-time = "2024-12-15T15:25:33.973Z" }
|
| 819 |
+
wheels = [
|
| 820 |
+
{ url = "https://files.pythonhosted.org/packages/93/33/5af4ad37a258db5aa60b73fda875f362ef29a7e4b6b5f7760367113aa9ee/deepchecks-0.19.1-py3-none-any.whl", hash = "sha256:839926b338aa76f97a0d1220fe0a9f7cf59c88de08fd06f228ef3aa9dd27abf4", size = 7815823, upload-time = "2024-12-15T15:25:31.176Z" },
|
| 821 |
+
]
|
| 822 |
+
|
| 823 |
[[package]]
|
| 824 |
name = "defusedxml"
|
| 825 |
version = "0.7.1"
|
|
|
|
| 950 |
{ url = "https://files.pythonhosted.org/packages/e1/e6/1782bcb8cdf82a971c1447d83f69efb3356945b68e28a28708fa4ddfa3c3/dvc-3.64.2-py3-none-any.whl", hash = "sha256:14e76baaef50cc10a43aaea788b49ea965835a59c16d0d63693a9ba1c2001090", size = 467981, upload-time = "2025-12-06T05:10:58.565Z" },
|
| 951 |
]
|
| 952 |
|
| 953 |
+
[package.optional-dependencies]
|
| 954 |
+
s3 = [
|
| 955 |
+
{ name = "dvc-s3" },
|
| 956 |
+
]
|
| 957 |
+
|
| 958 |
[[package]]
|
| 959 |
name = "dvc-data"
|
| 960 |
version = "3.16.12"
|
|
|
|
| 1187 |
{ url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" },
|
| 1188 |
]
|
| 1189 |
|
| 1190 |
+
[[package]]
|
| 1191 |
+
name = "fqdn"
|
| 1192 |
+
version = "1.5.1"
|
| 1193 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1194 |
+
sdist = { url = "https://files.pythonhosted.org/packages/30/3e/a80a8c077fd798951169626cde3e239adeba7dab75deb3555716415bd9b0/fqdn-1.5.1.tar.gz", hash = "sha256:105ed3677e767fb5ca086a0c1f4bb66ebc3c100be518f0e0d755d9eae164d89f", size = 6015, upload-time = "2021-03-11T07:16:29.08Z" }
|
| 1195 |
+
wheels = [
|
| 1196 |
+
{ url = "https://files.pythonhosted.org/packages/cf/58/8acf1b3e91c58313ce5cb67df61001fc9dcd21be4fadb76c1a2d540e09ed/fqdn-1.5.1-py3-none-any.whl", hash = "sha256:3a179af3761e4df6eb2e026ff9e1a3033d3587bf980a0b1b2e1e5d08d7358014", size = 9121, upload-time = "2021-03-11T07:16:28.351Z" },
|
| 1197 |
+
]
|
| 1198 |
+
|
| 1199 |
[[package]]
|
| 1200 |
name = "frozenlist"
|
| 1201 |
version = "1.8.0"
|
|
|
|
| 1688 |
{ url = "https://files.pythonhosted.org/packages/91/d0/274fbf7b0b12643cbbc001ce13e6a5b1607ac4929d1b11c72460152c9fc3/ipython-8.37.0-py3-none-any.whl", hash = "sha256:ed87326596b878932dbcb171e3e698845434d8c61b8d8cd474bf663041a9dcf2", size = 831864, upload-time = "2025-05-31T16:39:06.38Z" },
|
| 1689 |
]
|
| 1690 |
|
| 1691 |
+
[[package]]
|
| 1692 |
+
name = "ipywidgets"
|
| 1693 |
+
version = "8.1.8"
|
| 1694 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1695 |
+
dependencies = [
|
| 1696 |
+
{ name = "comm" },
|
| 1697 |
+
{ name = "ipython" },
|
| 1698 |
+
{ name = "jupyterlab-widgets" },
|
| 1699 |
+
{ name = "traitlets" },
|
| 1700 |
+
{ name = "widgetsnbextension" },
|
| 1701 |
+
]
|
| 1702 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4c/ae/c5ce1edc1afe042eadb445e95b0671b03cee61895264357956e61c0d2ac0/ipywidgets-8.1.8.tar.gz", hash = "sha256:61f969306b95f85fba6b6986b7fe45d73124d1d9e3023a8068710d47a22ea668", size = 116739, upload-time = "2025-11-01T21:18:12.393Z" }
|
| 1703 |
+
wheels = [
|
| 1704 |
+
{ url = "https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl", hash = "sha256:ecaca67aed704a338f88f67b1181b58f821ab5dc89c1f0f5ef99db43c1c2921e", size = 139808, upload-time = "2025-11-01T21:18:10.956Z" },
|
| 1705 |
+
]
|
| 1706 |
+
|
| 1707 |
+
[[package]]
|
| 1708 |
+
name = "isoduration"
|
| 1709 |
+
version = "20.11.0"
|
| 1710 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1711 |
+
dependencies = [
|
| 1712 |
+
{ name = "arrow" },
|
| 1713 |
+
]
|
| 1714 |
+
sdist = { url = "https://files.pythonhosted.org/packages/7c/1a/3c8edc664e06e6bd06cce40c6b22da5f1429aa4224d0c590f3be21c91ead/isoduration-20.11.0.tar.gz", hash = "sha256:ac2f9015137935279eac671f94f89eb00584f940f5dc49462a0c4ee692ba1bd9", size = 11649, upload-time = "2020-11-01T11:00:00.312Z" }
|
| 1715 |
+
wheels = [
|
| 1716 |
+
{ url = "https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl", hash = "sha256:b2904c2a4228c3d44f409c8ae8e2370eb21a26f7ac2ec5446df141dde3452042", size = 11321, upload-time = "2020-11-01T10:59:58.02Z" },
|
| 1717 |
+
]
|
| 1718 |
+
|
| 1719 |
[[package]]
|
| 1720 |
name = "iterative-telemetry"
|
| 1721 |
version = "0.0.10"
|
|
|
|
| 1782 |
{ url = "https://files.pythonhosted.org/packages/1e/e8/685f47e0d754320684db4425a0967f7d3fa70126bffd76110b7009a0090f/joblib-1.5.2-py3-none-any.whl", hash = "sha256:4e1f0bdbb987e6d843c70cf43714cb276623def372df3c22fe5266b2670bc241", size = 308396, upload-time = "2025-08-27T12:15:45.188Z" },
|
| 1783 |
]
|
| 1784 |
|
| 1785 |
+
[[package]]
|
| 1786 |
+
name = "jsonpickle"
|
| 1787 |
+
version = "4.1.1"
|
| 1788 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1789 |
+
sdist = { url = "https://files.pythonhosted.org/packages/e4/a6/d07afcfdef402900229bcca795f80506b207af13a838d4d99ad45abf530c/jsonpickle-4.1.1.tar.gz", hash = "sha256:f86e18f13e2b96c1c1eede0b7b90095bbb61d99fedc14813c44dc2f361dbbae1", size = 316885, upload-time = "2025-06-02T20:36:11.57Z" }
|
| 1790 |
+
wheels = [
|
| 1791 |
+
{ url = "https://files.pythonhosted.org/packages/c1/73/04df8a6fa66d43a9fd45c30f283cc4afff17da671886e451d52af60bdc7e/jsonpickle-4.1.1-py3-none-any.whl", hash = "sha256:bb141da6057898aa2438ff268362b126826c812a1721e31cf08a6e142910dc91", size = 47125, upload-time = "2025-06-02T20:36:08.647Z" },
|
| 1792 |
+
]
|
| 1793 |
+
|
| 1794 |
+
[[package]]
|
| 1795 |
+
name = "jsonpointer"
|
| 1796 |
+
version = "3.0.0"
|
| 1797 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1798 |
+
sdist = { url = "https://files.pythonhosted.org/packages/6a/0a/eebeb1fa92507ea94016a2a790b93c2ae41a7e18778f85471dc54475ed25/jsonpointer-3.0.0.tar.gz", hash = "sha256:2b2d729f2091522d61c3b31f82e11870f60b68f43fbc705cb76bf4b832af59ef", size = 9114, upload-time = "2024-06-10T19:24:42.462Z" }
|
| 1799 |
+
wheels = [
|
| 1800 |
+
{ url = "https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl", hash = "sha256:13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942", size = 7595, upload-time = "2024-06-10T19:24:40.698Z" },
|
| 1801 |
+
]
|
| 1802 |
+
|
| 1803 |
[[package]]
|
| 1804 |
name = "jsonschema"
|
| 1805 |
version = "4.25.1"
|
|
|
|
| 1815 |
{ url = "https://files.pythonhosted.org/packages/bf/9c/8c95d856233c1f82500c2450b8c68576b4cf1c871db3afac5c34ff84e6fd/jsonschema-4.25.1-py3-none-any.whl", hash = "sha256:3fba0169e345c7175110351d456342c364814cfcf3b964ba4587f22915230a63", size = 90040, upload-time = "2025-08-18T17:03:48.373Z" },
|
| 1816 |
]
|
| 1817 |
|
| 1818 |
+
[package.optional-dependencies]
|
| 1819 |
+
format-nongpl = [
|
| 1820 |
+
{ name = "fqdn" },
|
| 1821 |
+
{ name = "idna" },
|
| 1822 |
+
{ name = "isoduration" },
|
| 1823 |
+
{ name = "jsonpointer" },
|
| 1824 |
+
{ name = "rfc3339-validator" },
|
| 1825 |
+
{ name = "rfc3986-validator" },
|
| 1826 |
+
{ name = "rfc3987-syntax" },
|
| 1827 |
+
{ name = "uri-template" },
|
| 1828 |
+
{ name = "webcolors" },
|
| 1829 |
+
]
|
| 1830 |
+
|
| 1831 |
[[package]]
|
| 1832 |
name = "jsonschema-specifications"
|
| 1833 |
version = "2025.9.1"
|
|
|
|
| 1869 |
{ url = "https://files.pythonhosted.org/packages/e7/e7/80988e32bf6f73919a113473a604f5a8f09094de312b9d52b79c2df7612b/jupyter_core-5.9.1-py3-none-any.whl", hash = "sha256:ebf87fdc6073d142e114c72c9e29a9d7ca03fad818c5d300ce2adc1fb0743407", size = 29032, upload-time = "2025-10-16T19:19:16.783Z" },
|
| 1870 |
]
|
| 1871 |
|
| 1872 |
+
[[package]]
|
| 1873 |
+
name = "jupyter-events"
|
| 1874 |
+
version = "0.12.0"
|
| 1875 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1876 |
+
dependencies = [
|
| 1877 |
+
{ name = "jsonschema", extra = ["format-nongpl"] },
|
| 1878 |
+
{ name = "packaging" },
|
| 1879 |
+
{ name = "python-json-logger" },
|
| 1880 |
+
{ name = "pyyaml" },
|
| 1881 |
+
{ name = "referencing" },
|
| 1882 |
+
{ name = "rfc3339-validator" },
|
| 1883 |
+
{ name = "rfc3986-validator" },
|
| 1884 |
+
{ name = "traitlets" },
|
| 1885 |
+
]
|
| 1886 |
+
sdist = { url = "https://files.pythonhosted.org/packages/9d/c3/306d090461e4cf3cd91eceaff84bede12a8e52cd821c2d20c9a4fd728385/jupyter_events-0.12.0.tar.gz", hash = "sha256:fc3fce98865f6784c9cd0a56a20644fc6098f21c8c33834a8d9fe383c17e554b", size = 62196, upload-time = "2025-02-03T17:23:41.485Z" }
|
| 1887 |
+
wheels = [
|
| 1888 |
+
{ url = "https://files.pythonhosted.org/packages/e2/48/577993f1f99c552f18a0428731a755e06171f9902fa118c379eb7c04ea22/jupyter_events-0.12.0-py3-none-any.whl", hash = "sha256:6464b2fa5ad10451c3d35fabc75eab39556ae1e2853ad0c0cc31b656731a97fb", size = 19430, upload-time = "2025-02-03T17:23:38.643Z" },
|
| 1889 |
+
]
|
| 1890 |
+
|
| 1891 |
+
[[package]]
|
| 1892 |
+
name = "jupyter-server"
|
| 1893 |
+
version = "2.17.0"
|
| 1894 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1895 |
+
dependencies = [
|
| 1896 |
+
{ name = "anyio" },
|
| 1897 |
+
{ name = "argon2-cffi" },
|
| 1898 |
+
{ name = "jinja2" },
|
| 1899 |
+
{ name = "jupyter-client" },
|
| 1900 |
+
{ name = "jupyter-core" },
|
| 1901 |
+
{ name = "jupyter-events" },
|
| 1902 |
+
{ name = "jupyter-server-terminals" },
|
| 1903 |
+
{ name = "nbconvert" },
|
| 1904 |
+
{ name = "nbformat" },
|
| 1905 |
+
{ name = "overrides" },
|
| 1906 |
+
{ name = "packaging" },
|
| 1907 |
+
{ name = "prometheus-client" },
|
| 1908 |
+
{ name = "pywinpty", marker = "os_name == 'nt'" },
|
| 1909 |
+
{ name = "pyzmq" },
|
| 1910 |
+
{ name = "send2trash" },
|
| 1911 |
+
{ name = "terminado" },
|
| 1912 |
+
{ name = "tornado" },
|
| 1913 |
+
{ name = "traitlets" },
|
| 1914 |
+
{ name = "websocket-client" },
|
| 1915 |
+
]
|
| 1916 |
+
sdist = { url = "https://files.pythonhosted.org/packages/5b/ac/e040ec363d7b6b1f11304cc9f209dac4517ece5d5e01821366b924a64a50/jupyter_server-2.17.0.tar.gz", hash = "sha256:c38ea898566964c888b4772ae1ed58eca84592e88251d2cfc4d171f81f7e99d5", size = 731949, upload-time = "2025-08-21T14:42:54.042Z" }
|
| 1917 |
+
wheels = [
|
| 1918 |
+
{ url = "https://files.pythonhosted.org/packages/92/80/a24767e6ca280f5a49525d987bf3e4d7552bf67c8be07e8ccf20271f8568/jupyter_server-2.17.0-py3-none-any.whl", hash = "sha256:e8cb9c7db4251f51ed307e329b81b72ccf2056ff82d50524debde1ee1870e13f", size = 388221, upload-time = "2025-08-21T14:42:52.034Z" },
|
| 1919 |
+
]
|
| 1920 |
+
|
| 1921 |
+
[[package]]
|
| 1922 |
+
name = "jupyter-server-terminals"
|
| 1923 |
+
version = "0.5.3"
|
| 1924 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1925 |
+
dependencies = [
|
| 1926 |
+
{ name = "pywinpty", marker = "os_name == 'nt'" },
|
| 1927 |
+
{ name = "terminado" },
|
| 1928 |
+
]
|
| 1929 |
+
sdist = { url = "https://files.pythonhosted.org/packages/fc/d5/562469734f476159e99a55426d697cbf8e7eb5efe89fb0e0b4f83a3d3459/jupyter_server_terminals-0.5.3.tar.gz", hash = "sha256:5ae0295167220e9ace0edcfdb212afd2b01ee8d179fe6f23c899590e9b8a5269", size = 31430, upload-time = "2024-03-12T14:37:03.049Z" }
|
| 1930 |
+
wheels = [
|
| 1931 |
+
{ url = "https://files.pythonhosted.org/packages/07/2d/2b32cdbe8d2a602f697a649798554e4f072115438e92249624e532e8aca6/jupyter_server_terminals-0.5.3-py3-none-any.whl", hash = "sha256:41ee0d7dc0ebf2809c668e0fc726dfaf258fcd3e769568996ca731b6194ae9aa", size = 13656, upload-time = "2024-03-12T14:37:00.708Z" },
|
| 1932 |
+
]
|
| 1933 |
+
|
| 1934 |
[[package]]
|
| 1935 |
name = "jupyterlab-pygments"
|
| 1936 |
version = "0.3.0"
|
|
|
|
| 1940 |
{ url = "https://files.pythonhosted.org/packages/b1/dd/ead9d8ea85bf202d90cc513b533f9c363121c7792674f78e0d8a854b63b4/jupyterlab_pygments-0.3.0-py3-none-any.whl", hash = "sha256:841a89020971da1d8693f1a99997aefc5dc424bb1b251fd6322462a1b8842780", size = 15884, upload-time = "2023-11-23T09:26:34.325Z" },
|
| 1941 |
]
|
| 1942 |
|
| 1943 |
+
[[package]]
|
| 1944 |
+
name = "jupyterlab-widgets"
|
| 1945 |
+
version = "3.0.16"
|
| 1946 |
+
source = { registry = "https://pypi.org/simple" }
|
| 1947 |
+
sdist = { url = "https://files.pythonhosted.org/packages/26/2d/ef58fed122b268c69c0aa099da20bc67657cdfb2e222688d5731bd5b971d/jupyterlab_widgets-3.0.16.tar.gz", hash = "sha256:423da05071d55cf27a9e602216d35a3a65a3e41cdf9c5d3b643b814ce38c19e0", size = 897423, upload-time = "2025-11-01T21:11:29.724Z" }
|
| 1948 |
+
wheels = [
|
| 1949 |
+
{ url = "https://files.pythonhosted.org/packages/ab/b5/36c712098e6191d1b4e349304ef73a8d06aed77e56ceaac8c0a306c7bda1/jupyterlab_widgets-3.0.16-py3-none-any.whl", hash = "sha256:45fa36d9c6422cf2559198e4db481aa243c7a32d9926b500781c830c80f7ecf8", size = 914926, upload-time = "2025-11-01T21:11:28.008Z" },
|
| 1950 |
+
]
|
| 1951 |
+
|
| 1952 |
[[package]]
|
| 1953 |
name = "kagglehub"
|
| 1954 |
version = "0.3.13"
|
|
|
|
| 2005 |
{ url = "https://files.pythonhosted.org/packages/14/d6/943cf84117cd9ddecf6e1707a3f712a49fc64abdb8ac31b19132871af1dd/kombu-5.6.1-py3-none-any.whl", hash = "sha256:b69e3f5527ec32fc5196028a36376501682973e9620d6175d1c3d4eaf7e95409", size = 214141, upload-time = "2025-11-25T11:07:31.54Z" },
|
| 2006 |
]
|
| 2007 |
|
| 2008 |
+
[[package]]
|
| 2009 |
+
name = "lark"
|
| 2010 |
+
version = "1.3.1"
|
| 2011 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2012 |
+
sdist = { url = "https://files.pythonhosted.org/packages/da/34/28fff3ab31ccff1fd4f6c7c7b0ceb2b6968d8ea4950663eadcb5720591a0/lark-1.3.1.tar.gz", hash = "sha256:b426a7a6d6d53189d318f2b6236ab5d6429eaf09259f1ca33eb716eed10d2905", size = 382732, upload-time = "2025-10-27T18:25:56.653Z" }
|
| 2013 |
+
wheels = [
|
| 2014 |
+
{ url = "https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl", hash = "sha256:c629b661023a014c37da873b4ff58a817398d12635d3bbb2c5a03be7fe5d1e12", size = 113151, upload-time = "2025-10-27T18:25:54.882Z" },
|
| 2015 |
+
]
|
| 2016 |
+
|
| 2017 |
[[package]]
|
| 2018 |
name = "llvmlite"
|
| 2019 |
version = "0.45.1"
|
|
|
|
| 2331 |
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
|
| 2332 |
]
|
| 2333 |
|
| 2334 |
+
[[package]]
|
| 2335 |
+
name = "narwhals"
|
| 2336 |
+
version = "2.14.0"
|
| 2337 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2338 |
+
sdist = { url = "https://files.pythonhosted.org/packages/4a/84/897fe7b6406d436ef312e57e5a1a13b4a5e7e36d1844e8d934ce8880e3d3/narwhals-2.14.0.tar.gz", hash = "sha256:98be155c3599db4d5c211e565c3190c398c87e7bf5b3cdb157dece67641946e0", size = 600648, upload-time = "2025-12-16T11:29:13.458Z" }
|
| 2339 |
+
wheels = [
|
| 2340 |
+
{ url = "https://files.pythonhosted.org/packages/79/3e/b8ecc67e178919671695f64374a7ba916cf0adbf86efedc6054f38b5b8ae/narwhals-2.14.0-py3-none-any.whl", hash = "sha256:b56796c9a00179bd757d15282c540024e1d5c910b19b8c9944d836566c030acf", size = 430788, upload-time = "2025-12-16T11:29:11.699Z" },
|
| 2341 |
+
]
|
| 2342 |
+
|
| 2343 |
[[package]]
|
| 2344 |
name = "nbclient"
|
| 2345 |
version = "0.10.2"
|
|
|
|
| 2432 |
|
| 2433 |
[[package]]
|
| 2434 |
name = "numpy"
|
| 2435 |
+
version = "1.26.4"
|
| 2436 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2437 |
+
sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" }
|
| 2438 |
+
wheels = [
|
| 2439 |
+
{ url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" },
|
| 2440 |
+
{ url = "https://files.pythonhosted.org/packages/1a/2e/151484f49fd03944c4a3ad9c418ed193cfd02724e138ac8a9505d056c582/numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef", size = 13997127, upload-time = "2024-02-05T23:52:15.314Z" },
|
| 2441 |
+
{ url = "https://files.pythonhosted.org/packages/79/ae/7e5b85136806f9dadf4878bf73cf223fe5c2636818ba3ab1c585d0403164/numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e", size = 14222994, upload-time = "2024-02-05T23:52:47.569Z" },
|
| 2442 |
+
{ url = "https://files.pythonhosted.org/packages/3a/d0/edc009c27b406c4f9cbc79274d6e46d634d139075492ad055e3d68445925/numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5", size = 18252005, upload-time = "2024-02-05T23:53:15.637Z" },
|
| 2443 |
+
{ url = "https://files.pythonhosted.org/packages/09/bf/2b1aaf8f525f2923ff6cfcf134ae5e750e279ac65ebf386c75a0cf6da06a/numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a", size = 13885297, upload-time = "2024-02-05T23:53:42.16Z" },
|
| 2444 |
+
{ url = "https://files.pythonhosted.org/packages/df/a0/4e0f14d847cfc2a633a1c8621d00724f3206cfeddeb66d35698c4e2cf3d2/numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a", size = 18093567, upload-time = "2024-02-05T23:54:11.696Z" },
|
| 2445 |
+
{ url = "https://files.pythonhosted.org/packages/d2/b7/a734c733286e10a7f1a8ad1ae8c90f2d33bf604a96548e0a4a3a6739b468/numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20", size = 5968812, upload-time = "2024-02-05T23:54:26.453Z" },
|
| 2446 |
+
{ url = "https://files.pythonhosted.org/packages/3f/6b/5610004206cf7f8e7ad91c5a85a8c71b2f2f8051a0c0c4d5916b76d6cbb2/numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2", size = 15811913, upload-time = "2024-02-05T23:54:53.933Z" },
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2447 |
]
|
| 2448 |
|
| 2449 |
[[package]]
|
|
|
|
| 2522 |
{ url = "https://files.pythonhosted.org/packages/0f/dc/9484127cc1aa213be398ed735f5f270eedcb0c0977303a6f6ddc46b60204/orjson-3.11.4-cp311-cp311-win_arm64.whl", hash = "sha256:6bb6bb41b14c95d4f2702bce9975fda4516f1db48e500102fc4d8119032ff045", size = 126252, upload-time = "2025-10-24T15:49:08.869Z" },
|
| 2523 |
]
|
| 2524 |
|
| 2525 |
+
[[package]]
|
| 2526 |
+
name = "overrides"
|
| 2527 |
+
version = "7.7.0"
|
| 2528 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2529 |
+
sdist = { url = "https://files.pythonhosted.org/packages/36/86/b585f53236dec60aba864e050778b25045f857e17f6e5ea0ae95fe80edd2/overrides-7.7.0.tar.gz", hash = "sha256:55158fa3d93b98cc75299b1e67078ad9003ca27945c76162c1c0766d6f91820a", size = 22812, upload-time = "2024-01-27T21:01:33.423Z" }
|
| 2530 |
+
wheels = [
|
| 2531 |
+
{ url = "https://files.pythonhosted.org/packages/2c/ab/fc8290c6a4c722e5514d80f62b2dc4c4df1a68a41d1364e625c35990fcf3/overrides-7.7.0-py3-none-any.whl", hash = "sha256:c7ed9d062f78b8e4c1a7b70bd8796b35ead4d9f510227ef9c5dc7626c60d7e49", size = 17832, upload-time = "2024-01-27T21:01:31.393Z" },
|
| 2532 |
+
]
|
| 2533 |
+
|
| 2534 |
[[package]]
|
| 2535 |
name = "packaging"
|
| 2536 |
version = "24.2"
|
|
|
|
| 2597 |
{ url = "https://files.pythonhosted.org/packages/9a/70/875f4a23bfc4731703a5835487d0d2fb999031bd415e7d17c0ae615c18b7/pathvalidate-3.3.1-py3-none-any.whl", hash = "sha256:5263baab691f8e1af96092fa5137ee17df5bdfbd6cff1fcac4d6ef4bc2e1735f", size = 24305, upload-time = "2025-06-15T09:07:19.117Z" },
|
| 2598 |
]
|
| 2599 |
|
| 2600 |
+
[[package]]
|
| 2601 |
+
name = "patsy"
|
| 2602 |
+
version = "1.0.2"
|
| 2603 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2604 |
+
dependencies = [
|
| 2605 |
+
{ name = "numpy" },
|
| 2606 |
+
]
|
| 2607 |
+
sdist = { url = "https://files.pythonhosted.org/packages/be/44/ed13eccdd0519eff265f44b670d46fbb0ec813e2274932dc1c0e48520f7d/patsy-1.0.2.tar.gz", hash = "sha256:cdc995455f6233e90e22de72c37fcadb344e7586fb83f06696f54d92f8ce74c0", size = 399942, upload-time = "2025-10-20T16:17:37.535Z" }
|
| 2608 |
+
wheels = [
|
| 2609 |
+
{ url = "https://files.pythonhosted.org/packages/f1/70/ba4b949bdc0490ab78d545459acd7702b211dfccf7eb89bbc1060f52818d/patsy-1.0.2-py2.py3-none-any.whl", hash = "sha256:37bfddbc58fcf0362febb5f54f10743f8b21dd2aa73dec7e7ef59d1b02ae668a", size = 233301, upload-time = "2025-10-20T16:17:36.563Z" },
|
| 2610 |
+
]
|
| 2611 |
+
|
| 2612 |
[[package]]
|
| 2613 |
name = "pexpect"
|
| 2614 |
version = "4.9.0"
|
|
|
|
| 2665 |
{ url = "https://files.pythonhosted.org/packages/73/cb/ac7874b3e5d58441674fb70742e6c374b28b0c7cb988d37d991cde47166c/platformdirs-4.5.0-py3-none-any.whl", hash = "sha256:e578a81bb873cbb89a41fcc904c7ef523cc18284b7e3b3ccf06aca1403b7ebd3", size = 18651, upload-time = "2025-10-08T17:44:47.223Z" },
|
| 2666 |
]
|
| 2667 |
|
| 2668 |
+
[[package]]
|
| 2669 |
+
name = "plotly"
|
| 2670 |
+
version = "6.5.0"
|
| 2671 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2672 |
+
dependencies = [
|
| 2673 |
+
{ name = "narwhals" },
|
| 2674 |
+
{ name = "packaging" },
|
| 2675 |
+
]
|
| 2676 |
+
sdist = { url = "https://files.pythonhosted.org/packages/94/05/1199e2a03ce6637960bc1e951ca0f928209a48cfceb57355806a88f214cf/plotly-6.5.0.tar.gz", hash = "sha256:d5d38224883fd38c1409bef7d6a8dc32b74348d39313f3c52ca998b8e447f5c8", size = 7013624, upload-time = "2025-11-17T18:39:24.523Z" }
|
| 2677 |
+
wheels = [
|
| 2678 |
+
{ url = "https://files.pythonhosted.org/packages/e7/c3/3031c931098de393393e1f93a38dc9ed6805d86bb801acc3cf2d5bd1e6b7/plotly-6.5.0-py3-none-any.whl", hash = "sha256:5ac851e100367735250206788a2b1325412aa4a4917a4fe3e6f0bc5aa6f3d90a", size = 9893174, upload-time = "2025-11-17T18:39:20.351Z" },
|
| 2679 |
+
]
|
| 2680 |
+
|
| 2681 |
[[package]]
|
| 2682 |
name = "pluggy"
|
| 2683 |
version = "1.6.0"
|
|
|
|
| 2692 |
version = "0.0.1"
|
| 2693 |
source = { editable = "." }
|
| 2694 |
dependencies = [
|
| 2695 |
+
{ name = "apscheduler" },
|
| 2696 |
{ name = "asttokens" },
|
| 2697 |
{ name = "boto3" },
|
| 2698 |
{ name = "botocore" },
|
| 2699 |
{ name = "dagshub" },
|
| 2700 |
+
{ name = "deepchecks" },
|
| 2701 |
+
{ name = "dvc", extra = ["s3"] },
|
| 2702 |
{ name = "dvc-s3" },
|
| 2703 |
{ name = "gradio" },
|
| 2704 |
{ name = "great-expectations" },
|
|
|
|
| 2714 |
{ name = "numpy" },
|
| 2715 |
{ name = "pandas" },
|
| 2716 |
{ name = "pip" },
|
| 2717 |
+
{ name = "prometheus-client" },
|
| 2718 |
+
{ name = "prometheus-fastapi-instrumentator" },
|
| 2719 |
{ name = "pytest" },
|
| 2720 |
+
{ name = "pytest-html" },
|
| 2721 |
{ name = "python-dotenv" },
|
| 2722 |
{ name = "ruff" },
|
| 2723 |
{ name = "scikit-learn" },
|
|
|
|
| 2735 |
|
| 2736 |
[package.metadata]
|
| 2737 |
requires-dist = [
|
| 2738 |
+
{ name = "apscheduler", specifier = ">=3.11.2" },
|
| 2739 |
{ name = "asttokens", specifier = ">=3.0.0" },
|
| 2740 |
{ name = "boto3", specifier = ">=1.36.0" },
|
| 2741 |
{ name = "botocore", specifier = ">=1.36.0" },
|
| 2742 |
{ name = "dagshub", specifier = ">=0.6.3" },
|
| 2743 |
+
{ name = "deepchecks", specifier = ">=0.19.1" },
|
| 2744 |
+
{ name = "dvc", extras = ["s3"], specifier = ">=3.64.2" },
|
| 2745 |
{ name = "dvc-s3", specifier = ">=3.2.2" },
|
| 2746 |
{ name = "gradio", specifier = ">=6.0.2" },
|
| 2747 |
{ name = "great-expectations", specifier = ">=1.9.0" },
|
|
|
|
| 2754 |
{ name = "matplotlib", specifier = ">=3.10.7" },
|
| 2755 |
{ name = "mkdocs" },
|
| 2756 |
{ name = "mlflow", specifier = "==2.22.0" },
|
| 2757 |
+
{ name = "numpy", specifier = "<2" },
|
| 2758 |
{ name = "pandas", specifier = ">=2.3.3" },
|
| 2759 |
{ name = "pip" },
|
| 2760 |
+
{ name = "prometheus-client", specifier = ">=0.23.1" },
|
| 2761 |
+
{ name = "prometheus-fastapi-instrumentator", specifier = ">=7.1.0" },
|
| 2762 |
{ name = "pytest" },
|
| 2763 |
+
{ name = "pytest-html", specifier = ">=4.1.1" },
|
| 2764 |
{ name = "python-dotenv" },
|
| 2765 |
{ name = "ruff" },
|
| 2766 |
{ name = "scikit-learn", specifier = ">=1.7.2" },
|
| 2767 |
{ name = "seaborn", specifier = ">=0.13.2" },
|
| 2768 |
+
{ name = "shap", specifier = "<0.50" },
|
| 2769 |
{ name = "tqdm" },
|
| 2770 |
{ name = "typer" },
|
| 2771 |
]
|
|
|
|
| 2776 |
{ name = "ruff", specifier = ">=0.14.2" },
|
| 2777 |
]
|
| 2778 |
|
| 2779 |
+
[[package]]
|
| 2780 |
+
name = "prometheus-client"
|
| 2781 |
+
version = "0.23.1"
|
| 2782 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2783 |
+
sdist = { url = "https://files.pythonhosted.org/packages/23/53/3edb5d68ecf6b38fcbcc1ad28391117d2a322d9a1a3eff04bfdb184d8c3b/prometheus_client-0.23.1.tar.gz", hash = "sha256:6ae8f9081eaaaf153a2e959d2e6c4f4fb57b12ef76c8c7980202f1e57b48b2ce", size = 80481, upload-time = "2025-09-18T20:47:25.043Z" }
|
| 2784 |
+
wheels = [
|
| 2785 |
+
{ url = "https://files.pythonhosted.org/packages/b8/db/14bafcb4af2139e046d03fd00dea7873e48eafe18b7d2797e73d6681f210/prometheus_client-0.23.1-py3-none-any.whl", hash = "sha256:dd1913e6e76b59cfe44e7a4b83e01afc9873c1bdfd2ed8739f1e76aeca115f99", size = 61145, upload-time = "2025-09-18T20:47:23.875Z" },
|
| 2786 |
+
]
|
| 2787 |
+
|
| 2788 |
+
[[package]]
|
| 2789 |
+
name = "prometheus-fastapi-instrumentator"
|
| 2790 |
+
version = "7.1.0"
|
| 2791 |
+
source = { registry = "https://pypi.org/simple" }
|
| 2792 |
+
dependencies = [
|
| 2793 |
+
{ name = "prometheus-client" },
|
| 2794 |
+
{ name = "starlette" },
|
| 2795 |
+
]
|
| 2796 |
+
sdist = { url = "https://files.pythonhosted.org/packages/69/6d/24d53033cf93826aa7857699a4450c1c67e5b9c710e925b1ed2b320c04df/prometheus_fastapi_instrumentator-7.1.0.tar.gz", hash = "sha256:be7cd61eeea4e5912aeccb4261c6631b3f227d8924542d79eaf5af3f439cbe5e", size = 20220, upload-time = "2025-03-19T19:35:05.351Z" }
|
| 2797 |
+
wheels = [
|
| 2798 |
+
{ url = "https://files.pythonhosted.org/packages/27/72/0824c18f3bc75810f55dacc2dd933f6ec829771180245ae3cc976195dec0/prometheus_fastapi_instrumentator-7.1.0-py3-none-any.whl", hash = "sha256:978130f3c0bb7b8ebcc90d35516a6fe13e02d2eb358c8f83887cdef7020c31e9", size = 19296, upload-time = "2025-03-19T19:35:04.323Z" },
|
| 2799 |
+
]
|
| 2800 |
+
|
| 2801 |
[[package]]
|
| 2802 |
name = "prompt-toolkit"
|
| 2803 |
version = "3.0.52"
|
|
|
|
| 3073 |
{ url = "https://files.pythonhosted.org/packages/86/30/9bcd030408ae80e3a516da13834065d667798a622309fb891d50e77d30d6/pynblint-0.1.6-py3-none-any.whl", hash = "sha256:8bb972696431144768ba6bf238a83f646c3faa4dac2810338ef87fb24d91742c", size = 24356, upload-time = "2024-08-12T08:53:20.164Z" },
|
| 3074 |
]
|
| 3075 |
|
| 3076 |
+
[[package]]
|
| 3077 |
+
name = "pynomaly"
|
| 3078 |
+
version = "0.3.4"
|
| 3079 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3080 |
+
dependencies = [
|
| 3081 |
+
{ name = "numpy" },
|
| 3082 |
+
{ name = "python-utils" },
|
| 3083 |
+
]
|
| 3084 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bb/6c/bb98854999794f1d11d08885f19a740bf6650ce6f2bfd4db6235311d4ad3/PyNomaly-0.3.4.tar.gz", hash = "sha256:90c5e3b58fa3dc144cdcb145afdecb212131e269a5be67173293e18acdd73c6f", size = 8374, upload-time = "2024-10-18T15:46:06.537Z" }
|
| 3085 |
+
wheels = [
|
| 3086 |
+
{ url = "https://files.pythonhosted.org/packages/03/00/0af76dc221eb34f0d98af54024955ddc16080057a69d4c19329811aabc03/PyNomaly-0.3.4-py3-none-any.whl", hash = "sha256:e653f1e1c9a70c92170829f7440841e3b004528e1a1bfaea5e830e53fca26822", size = 9149, upload-time = "2024-10-18T15:45:30.207Z" },
|
| 3087 |
+
]
|
| 3088 |
+
|
| 3089 |
[[package]]
|
| 3090 |
name = "pyparsing"
|
| 3091 |
version = "3.2.5"
|
|
|
|
| 3111 |
{ url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
|
| 3112 |
]
|
| 3113 |
|
| 3114 |
+
[[package]]
|
| 3115 |
+
name = "pytest-html"
|
| 3116 |
+
version = "4.1.1"
|
| 3117 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3118 |
+
dependencies = [
|
| 3119 |
+
{ name = "jinja2" },
|
| 3120 |
+
{ name = "pytest" },
|
| 3121 |
+
{ name = "pytest-metadata" },
|
| 3122 |
+
]
|
| 3123 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bb/ab/4862dcb5a8a514bd87747e06b8d55483c0c9e987e1b66972336946e49b49/pytest_html-4.1.1.tar.gz", hash = "sha256:70a01e8ae5800f4a074b56a4cb1025c8f4f9b038bba5fe31e3c98eb996686f07", size = 150773, upload-time = "2023-11-07T15:44:28.975Z" }
|
| 3124 |
+
wheels = [
|
| 3125 |
+
{ url = "https://files.pythonhosted.org/packages/c8/c7/c160021cbecd956cc1a6f79e5fe155f7868b2e5b848f1320dad0b3e3122f/pytest_html-4.1.1-py3-none-any.whl", hash = "sha256:c8152cea03bd4e9bee6d525573b67bbc6622967b72b9628dda0ea3e2a0b5dd71", size = 23491, upload-time = "2023-11-07T15:44:27.149Z" },
|
| 3126 |
+
]
|
| 3127 |
+
|
| 3128 |
+
[[package]]
|
| 3129 |
+
name = "pytest-metadata"
|
| 3130 |
+
version = "3.1.1"
|
| 3131 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3132 |
+
dependencies = [
|
| 3133 |
+
{ name = "pytest" },
|
| 3134 |
+
]
|
| 3135 |
+
sdist = { url = "https://files.pythonhosted.org/packages/a6/85/8c969f8bec4e559f8f2b958a15229a35495f5b4ce499f6b865eac54b878d/pytest_metadata-3.1.1.tar.gz", hash = "sha256:d2a29b0355fbc03f168aa96d41ff88b1a3b44a3b02acbe491801c98a048017c8", size = 9952, upload-time = "2024-02-12T19:38:44.887Z" }
|
| 3136 |
+
wheels = [
|
| 3137 |
+
{ url = "https://files.pythonhosted.org/packages/3e/43/7e7b2ec865caa92f67b8f0e9231a798d102724ca4c0e1f414316be1c1ef2/pytest_metadata-3.1.1-py3-none-any.whl", hash = "sha256:c8e0844db684ee1c798cfa38908d20d67d0463ecb6137c72e91f418558dd5f4b", size = 11428, upload-time = "2024-02-12T19:38:42.531Z" },
|
| 3138 |
+
]
|
| 3139 |
+
|
| 3140 |
[[package]]
|
| 3141 |
name = "python-dateutil"
|
| 3142 |
version = "2.9.0.post0"
|
|
|
|
| 3158 |
{ url = "https://files.pythonhosted.org/packages/14/1b/a298b06749107c305e1fe0f814c6c74aea7b2f1e10989cb30f544a1b3253/python_dotenv-1.2.1-py3-none-any.whl", hash = "sha256:b81ee9561e9ca4004139c6cbba3a238c32b03e4894671e181b671e8cb8425d61", size = 21230, upload-time = "2025-10-26T15:12:09.109Z" },
|
| 3159 |
]
|
| 3160 |
|
| 3161 |
+
[[package]]
|
| 3162 |
+
name = "python-json-logger"
|
| 3163 |
+
version = "4.0.0"
|
| 3164 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3165 |
+
sdist = { url = "https://files.pythonhosted.org/packages/29/bf/eca6a3d43db1dae7070f70e160ab20b807627ba953663ba07928cdd3dc58/python_json_logger-4.0.0.tar.gz", hash = "sha256:f58e68eb46e1faed27e0f574a55a0455eecd7b8a5b88b85a784519ba3cff047f", size = 17683, upload-time = "2025-10-06T04:15:18.984Z" }
|
| 3166 |
+
wheels = [
|
| 3167 |
+
{ url = "https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl", hash = "sha256:af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2", size = 15548, upload-time = "2025-10-06T04:15:17.553Z" },
|
| 3168 |
+
]
|
| 3169 |
+
|
| 3170 |
[[package]]
|
| 3171 |
name = "python-multipart"
|
| 3172 |
version = "0.0.20"
|
|
|
|
| 3176 |
{ url = "https://files.pythonhosted.org/packages/45/58/38b5afbc1a800eeea951b9285d3912613f2603bdf897a4ab0f4bd7f405fc/python_multipart-0.0.20-py3-none-any.whl", hash = "sha256:8a62d3a8335e06589fe01f2a3e178cdcc632f3fbe0d492ad9ee0ec35aab1f104", size = 24546, upload-time = "2024-12-16T19:45:44.423Z" },
|
| 3177 |
]
|
| 3178 |
|
| 3179 |
+
[[package]]
|
| 3180 |
+
name = "python-utils"
|
| 3181 |
+
version = "3.9.1"
|
| 3182 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3183 |
+
dependencies = [
|
| 3184 |
+
{ name = "typing-extensions" },
|
| 3185 |
+
]
|
| 3186 |
+
sdist = { url = "https://files.pythonhosted.org/packages/13/4c/ef8b7b1046d65c1f18ca31e5235c7d6627ca2b3f389ab1d44a74d22f5cc9/python_utils-3.9.1.tar.gz", hash = "sha256:eb574b4292415eb230f094cbf50ab5ef36e3579b8f09e9f2ba74af70891449a0", size = 35403, upload-time = "2024-11-26T00:38:58.736Z" }
|
| 3187 |
+
wheels = [
|
| 3188 |
+
{ url = "https://files.pythonhosted.org/packages/d4/69/31c82567719b34d8f6b41077732589104883771d182a9f4ff3e71430999a/python_utils-3.9.1-py2.py3-none-any.whl", hash = "sha256:0273d7363c7ad4b70999b2791d5ba6b55333d6f7a4e4c8b6b39fb82b5fab4613", size = 32078, upload-time = "2024-11-26T00:38:57.488Z" },
|
| 3189 |
+
]
|
| 3190 |
+
|
| 3191 |
[[package]]
|
| 3192 |
name = "pytz"
|
| 3193 |
version = "2025.2"
|
|
|
|
| 3207 |
{ url = "https://files.pythonhosted.org/packages/44/7b/9c2ab54f74a138c491aba1b1cd0795ba61f144c711daea84a88b63dc0f6c/pywin32-311-cp311-cp311-win_arm64.whl", hash = "sha256:a733f1388e1a842abb67ffa8e7aad0e70ac519e09b0f6a784e65a136ec7cefd2", size = 8703930, upload-time = "2025-07-14T20:13:16.945Z" },
|
| 3208 |
]
|
| 3209 |
|
| 3210 |
+
[[package]]
|
| 3211 |
+
name = "pywinpty"
|
| 3212 |
+
version = "3.0.2"
|
| 3213 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3214 |
+
sdist = { url = "https://files.pythonhosted.org/packages/f3/bb/a7cc2967c5c4eceb6cc49cfe39447d4bfc56e6c865e7c2249b6eb978935f/pywinpty-3.0.2.tar.gz", hash = "sha256:1505cc4cb248af42cb6285a65c9c2086ee9e7e574078ee60933d5d7fa86fb004", size = 30669, upload-time = "2025-10-03T21:16:29.205Z" }
|
| 3215 |
+
wheels = [
|
| 3216 |
+
{ url = "https://files.pythonhosted.org/packages/a6/a1/409c1651c9f874d598c10f51ff586c416625601df4bca315d08baec4c3e3/pywinpty-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:327790d70e4c841ebd9d0f295a780177149aeb405bca44c7115a3de5c2054b23", size = 2050304, upload-time = "2025-10-03T21:19:29.466Z" },
|
| 3217 |
+
]
|
| 3218 |
+
|
| 3219 |
[[package]]
|
| 3220 |
name = "pyyaml"
|
| 3221 |
version = "6.0.3"
|
|
|
|
| 3322 |
{ url = "https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06", size = 54481, upload-time = "2023-05-01T04:11:28.427Z" },
|
| 3323 |
]
|
| 3324 |
|
| 3325 |
+
[[package]]
|
| 3326 |
+
name = "rfc3339-validator"
|
| 3327 |
+
version = "0.1.4"
|
| 3328 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3329 |
+
dependencies = [
|
| 3330 |
+
{ name = "six" },
|
| 3331 |
+
]
|
| 3332 |
+
sdist = { url = "https://files.pythonhosted.org/packages/28/ea/a9387748e2d111c3c2b275ba970b735e04e15cdb1eb30693b6b5708c4dbd/rfc3339_validator-0.1.4.tar.gz", hash = "sha256:138a2abdf93304ad60530167e51d2dfb9549521a836871b88d7f4695d0022f6b", size = 5513, upload-time = "2021-05-12T16:37:54.178Z" }
|
| 3333 |
+
wheels = [
|
| 3334 |
+
{ url = "https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl", hash = "sha256:24f6ec1eda14ef823da9e36ec7113124b39c04d50a4d3d3a3c2859577e7791fa", size = 3490, upload-time = "2021-05-12T16:37:52.536Z" },
|
| 3335 |
+
]
|
| 3336 |
+
|
| 3337 |
+
[[package]]
|
| 3338 |
+
name = "rfc3986-validator"
|
| 3339 |
+
version = "0.1.1"
|
| 3340 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3341 |
+
sdist = { url = "https://files.pythonhosted.org/packages/da/88/f270de456dd7d11dcc808abfa291ecdd3f45ff44e3b549ffa01b126464d0/rfc3986_validator-0.1.1.tar.gz", hash = "sha256:3d44bde7921b3b9ec3ae4e3adca370438eccebc676456449b145d533b240d055", size = 6760, upload-time = "2019-10-28T16:00:19.144Z" }
|
| 3342 |
+
wheels = [
|
| 3343 |
+
{ url = "https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl", hash = "sha256:2f235c432ef459970b4306369336b9d5dbdda31b510ca1e327636e01f528bfa9", size = 4242, upload-time = "2019-10-28T16:00:13.976Z" },
|
| 3344 |
+
]
|
| 3345 |
+
|
| 3346 |
+
[[package]]
|
| 3347 |
+
name = "rfc3987-syntax"
|
| 3348 |
+
version = "1.1.0"
|
| 3349 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3350 |
+
dependencies = [
|
| 3351 |
+
{ name = "lark" },
|
| 3352 |
+
]
|
| 3353 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2c/06/37c1a5557acf449e8e406a830a05bf885ac47d33270aec454ef78675008d/rfc3987_syntax-1.1.0.tar.gz", hash = "sha256:717a62cbf33cffdd16dfa3a497d81ce48a660ea691b1ddd7be710c22f00b4a0d", size = 14239, upload-time = "2025-07-18T01:05:05.015Z" }
|
| 3354 |
+
wheels = [
|
| 3355 |
+
{ url = "https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl", hash = "sha256:6c3d97604e4c5ce9f714898e05401a0445a641cfa276432b0a648c80856f6a3f", size = 8046, upload-time = "2025-07-18T01:05:03.843Z" },
|
| 3356 |
+
]
|
| 3357 |
+
|
| 3358 |
[[package]]
|
| 3359 |
name = "rich"
|
| 3360 |
version = "13.9.4"
|
|
|
|
| 3602 |
{ url = "https://files.pythonhosted.org/packages/a6/24/4d91e05817e92e3a61c8a21e08fd0f390f5301f1c448b137c57c4bc6e543/semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746", size = 17912, upload-time = "2025-01-24T13:19:24.949Z" },
|
| 3603 |
]
|
| 3604 |
|
| 3605 |
+
[[package]]
|
| 3606 |
+
name = "send2trash"
|
| 3607 |
+
version = "2.0.0"
|
| 3608 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3609 |
+
sdist = { url = "https://files.pythonhosted.org/packages/62/6e/421803dec0c0dfbf5a27e66491ebe6643a461e4f90422f00ea4c68ae24aa/send2trash-2.0.0.tar.gz", hash = "sha256:1761421da3f9930bfe51ed7c45343948573383ad4c27e3acebc91be324e7770d", size = 17206, upload-time = "2025-12-31T04:12:48.664Z" }
|
| 3610 |
+
wheels = [
|
| 3611 |
+
{ url = "https://files.pythonhosted.org/packages/1b/5a/f2f2e5eda25579f754acd83399c522ee03d6acbe001dfe53c8a1ec928b44/send2trash-2.0.0-py3-none-any.whl", hash = "sha256:e70d5ce41dbb890882cc78bc25d137478330b39a391e756fadf82e34da4d85b8", size = 17642, upload-time = "2025-12-31T04:12:45.336Z" },
|
| 3612 |
+
]
|
| 3613 |
+
|
| 3614 |
[[package]]
|
| 3615 |
name = "setuptools"
|
| 3616 |
version = "80.9.0"
|
|
|
|
| 3622 |
|
| 3623 |
[[package]]
|
| 3624 |
name = "shap"
|
| 3625 |
+
version = "0.49.1"
|
| 3626 |
source = { registry = "https://pypi.org/simple" }
|
| 3627 |
dependencies = [
|
| 3628 |
{ name = "cloudpickle" },
|
|
|
|
| 3636 |
{ name = "tqdm" },
|
| 3637 |
{ name = "typing-extensions" },
|
| 3638 |
]
|
| 3639 |
+
sdist = { url = "https://files.pythonhosted.org/packages/dc/c6/9823a7f483aa9f3179fc359c10d22da9e418b1a7a3fc99a42b705d05e82a/shap-0.49.1.tar.gz", hash = "sha256:1114ecd804fff29f50d522ce6031082fcf42fe4a32fb1b5da233b2415d784c8c", size = 4084725, upload-time = "2025-10-14T10:04:49.75Z" }
|
| 3640 |
wheels = [
|
| 3641 |
+
{ url = "https://files.pythonhosted.org/packages/1d/08/d433b7d18a8b51a7d10477120f78877d806d2eb86283cb1661318d865f3d/shap-0.49.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1e208a0129c721bd0eba6268a9ffac4610dbc8a833d07d2ad9f39541bb737f06", size = 558742, upload-time = "2025-10-14T10:04:17.45Z" },
|
| 3642 |
+
{ url = "https://files.pythonhosted.org/packages/c2/35/72929fdad25e055aff9dfbeb48c044682fc3b815d90cee4036b90bd65f4c/shap-0.49.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b878470bdf6800069c25d2a8598eb0548aa1e6826becd39cca253521cc14866", size = 556486, upload-time = "2025-10-14T10:04:18.934Z" },
|
| 3643 |
+
{ url = "https://files.pythonhosted.org/packages/02/be/d92623be2c584784e99a8eb9a6cd02263b4eb363c9e49fa14c20f824bcbb/shap-0.49.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:118577d40c53f005268024e59f6a10cbcafbb6d03b3d97dce7c0c7510190ebaa", size = 1025978, upload-time = "2025-10-14T10:04:20.096Z" },
|
| 3644 |
+
{ url = "https://files.pythonhosted.org/packages/14/e9/e4079b5de26a8269121ce38125e130c147dac7b59611e0bd94be10f9444e/shap-0.49.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f424465699aa2dda8057656c6b6d3cb927cf29b054c5bb01cfffcb9efa5dbf98", size = 1027831, upload-time = "2025-10-14T10:04:21.666Z" },
|
| 3645 |
+
{ url = "https://files.pythonhosted.org/packages/49/ff/e22e1d899ed56384a2395d6121d6e21833c518c01c5b6c52fce3c0b0cbab/shap-0.49.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d505834fdf2a159e88b1dcdeddfd79f101fd789ba89d589faf0aaec060c0bad9", size = 2092627, upload-time = "2025-10-14T10:04:22.894Z" },
|
| 3646 |
+
{ url = "https://files.pythonhosted.org/packages/17/48/bbcd638a391ac0fb30033398a3cca60ba5c36941d962dd74958e67069108/shap-0.49.1-cp311-cp311-win_amd64.whl", hash = "sha256:897c7e6fa98d66482282c8f898c97ade181d714ecaf581da0dab5c49adb9f62c", size = 546845, upload-time = "2025-10-14T10:04:24.238Z" },
|
|
|
|
| 3647 |
]
|
| 3648 |
|
| 3649 |
[[package]]
|
|
|
|
| 3789 |
{ url = "https://files.pythonhosted.org/packages/51/da/545b75d420bb23b5d494b0517757b351963e974e79933f01e05c929f20a6/starlette-0.49.1-py3-none-any.whl", hash = "sha256:d92ce9f07e4a3caa3ac13a79523bd18e3bc0042bb8ff2d759a8e7dd0e1859875", size = 74175, upload-time = "2025-10-28T17:34:09.13Z" },
|
| 3790 |
]
|
| 3791 |
|
| 3792 |
+
[[package]]
|
| 3793 |
+
name = "statsmodels"
|
| 3794 |
+
version = "0.14.6"
|
| 3795 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3796 |
+
dependencies = [
|
| 3797 |
+
{ name = "numpy" },
|
| 3798 |
+
{ name = "packaging" },
|
| 3799 |
+
{ name = "pandas" },
|
| 3800 |
+
{ name = "patsy" },
|
| 3801 |
+
{ name = "scipy" },
|
| 3802 |
+
]
|
| 3803 |
+
sdist = { url = "https://files.pythonhosted.org/packages/0d/81/e8d74b34f85285f7335d30c5e3c2d7c0346997af9f3debf9a0a9a63de184/statsmodels-0.14.6.tar.gz", hash = "sha256:4d17873d3e607d398b85126cd4ed7aad89e4e9d89fc744cdab1af3189a996c2a", size = 20689085, upload-time = "2025-12-05T23:08:39.522Z" }
|
| 3804 |
+
wheels = [
|
| 3805 |
+
{ url = "https://files.pythonhosted.org/packages/a9/4d/df4dd089b406accfc3bb5ee53ba29bb3bdf5ae61643f86f8f604baa57656/statsmodels-0.14.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ad5c2810fc6c684254a7792bf1cbaf1606cdee2a253f8bd259c43135d87cfb4", size = 10121514, upload-time = "2025-12-05T19:28:16.521Z" },
|
| 3806 |
+
{ url = "https://files.pythonhosted.org/packages/82/af/ec48daa7f861f993b91a0dcc791d66e1cf56510a235c5cbd2ab991a31d5c/statsmodels-0.14.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:341fa68a7403e10a95c7b6e41134b0da3a7b835ecff1eb266294408535a06eb6", size = 10003346, upload-time = "2025-12-05T19:28:29.568Z" },
|
| 3807 |
+
{ url = "https://files.pythonhosted.org/packages/a9/2c/c8f7aa24cd729970728f3f98822fb45149adc216f445a9301e441f7ac760/statsmodels-0.14.6-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bdf1dfe2a3ca56f5529118baf33a13efed2783c528f4a36409b46bbd2d9d48eb", size = 10129872, upload-time = "2025-12-05T23:09:25.724Z" },
|
| 3808 |
+
{ url = "https://files.pythonhosted.org/packages/40/c6/9ae8e9b0721e9b6eb5f340c3a0ce8cd7cce4f66e03dd81f80d60f111987f/statsmodels-0.14.6-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a3764ba8195c9baf0925a96da0743ff218067a269f01d155ca3558deed2658ca", size = 10381964, upload-time = "2025-12-05T23:09:41.326Z" },
|
| 3809 |
+
{ url = "https://files.pythonhosted.org/packages/28/8c/cf3d30c8c2da78e2ad1f50ade8b7fabec3ff4cdfc56fbc02e097c4577f90/statsmodels-0.14.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9e8d2e519852adb1b420e018f5ac6e6684b2b877478adf7fda2cfdb58f5acb5d", size = 10409611, upload-time = "2025-12-05T23:09:57.131Z" },
|
| 3810 |
+
{ url = "https://files.pythonhosted.org/packages/bf/cc/018f14ecb58c6cb89de9d52695740b7d1f5a982aa9ea312483ea3c3d5f77/statsmodels-0.14.6-cp311-cp311-win_amd64.whl", hash = "sha256:2738a00fca51196f5a7d44b06970ace6b8b30289839e4808d656f8a98e35faa7", size = 9580385, upload-time = "2025-12-05T19:28:42.778Z" },
|
| 3811 |
+
]
|
| 3812 |
+
|
| 3813 |
[[package]]
|
| 3814 |
name = "tabulate"
|
| 3815 |
version = "0.9.0"
|
|
|
|
| 3828 |
{ url = "https://files.pythonhosted.org/packages/e5/30/643397144bfbfec6f6ef821f36f33e57d35946c44a2352d3c9f0ae847619/tenacity-9.1.2-py3-none-any.whl", hash = "sha256:f77bf36710d8b73a50b2dd155c97b870017ad21afe6ab300326b0371b3b05138", size = 28248, upload-time = "2025-04-02T08:25:07.678Z" },
|
| 3829 |
]
|
| 3830 |
|
| 3831 |
+
[[package]]
|
| 3832 |
+
name = "terminado"
|
| 3833 |
+
version = "0.18.1"
|
| 3834 |
+
source = { registry = "https://pypi.org/simple" }
|
| 3835 |
+
dependencies = [
|
| 3836 |
+
{ name = "ptyprocess", marker = "os_name != 'nt'" },
|
| 3837 |
+
{ name = "pywinpty", marker = "os_name == 'nt'" },
|
| 3838 |
+
{ name = "tornado" },
|
| 3839 |
+
]
|
| 3840 |
+
sdist = { url = "https://files.pythonhosted.org/packages/8a/11/965c6fd8e5cc254f1fe142d547387da17a8ebfd75a3455f637c663fb38a0/terminado-0.18.1.tar.gz", hash = "sha256:de09f2c4b85de4765f7714688fff57d3e75bad1f909b589fde880460c753fd2e", size = 32701, upload-time = "2024-03-12T14:34:39.026Z" }
|
| 3841 |
+
wheels = [
|
| 3842 |
+
{ url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154, upload-time = "2024-03-12T14:34:36.569Z" },
|
| 3843 |
+
]
|
| 3844 |
+
|
| 3845 |
[[package]]
|
| 3846 |
name = "threadpoolctl"
|
| 3847 |
version = "3.6.0"
|
|
|
|
| 4016 |
{ url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026, upload-time = "2025-03-05T21:17:39.857Z" },
|
| 4017 |
]
|
| 4018 |
|
| 4019 |
+
[[package]]
|
| 4020 |
+
name = "uri-template"
|
| 4021 |
+
version = "1.3.0"
|
| 4022 |
+
source = { registry = "https://pypi.org/simple" }
|
| 4023 |
+
sdist = { url = "https://files.pythonhosted.org/packages/31/c7/0336f2bd0bcbada6ccef7aaa25e443c118a704f828a0620c6fa0207c1b64/uri-template-1.3.0.tar.gz", hash = "sha256:0e00f8eb65e18c7de20d595a14336e9f337ead580c70934141624b6d1ffdacc7", size = 21678, upload-time = "2023-06-21T01:49:05.374Z" }
|
| 4024 |
+
wheels = [
|
| 4025 |
+
{ url = "https://files.pythonhosted.org/packages/e7/00/3fca040d7cf8a32776d3d81a00c8ee7457e00f80c649f1e4a863c8321ae9/uri_template-1.3.0-py3-none-any.whl", hash = "sha256:a44a133ea12d44a0c0f06d7d42a52d71282e77e2f937d8abd5655b8d56fc1363", size = 11140, upload-time = "2023-06-21T01:49:03.467Z" },
|
| 4026 |
+
]
|
| 4027 |
+
|
| 4028 |
[[package]]
|
| 4029 |
name = "urllib3"
|
| 4030 |
version = "2.5.0"
|
|
|
|
| 4104 |
{ url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" },
|
| 4105 |
]
|
| 4106 |
|
| 4107 |
+
[[package]]
|
| 4108 |
+
name = "webcolors"
|
| 4109 |
+
version = "25.10.0"
|
| 4110 |
+
source = { registry = "https://pypi.org/simple" }
|
| 4111 |
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/7a/eb316761ec35664ea5174709a68bbd3389de60d4a1ebab8808bfc264ed67/webcolors-25.10.0.tar.gz", hash = "sha256:62abae86504f66d0f6364c2a8520de4a0c47b80c03fc3a5f1815fedbef7c19bf", size = 53491, upload-time = "2025-10-31T07:51:03.977Z" }
|
| 4112 |
+
wheels = [
|
| 4113 |
+
{ url = "https://files.pythonhosted.org/packages/e2/cc/e097523dd85c9cf5d354f78310927f1656c422bd7b2613b2db3e3f9a0f2c/webcolors-25.10.0-py3-none-any.whl", hash = "sha256:032c727334856fc0b968f63daa252a1ac93d33db2f5267756623c210e57a4f1d", size = 14905, upload-time = "2025-10-31T07:51:01.778Z" },
|
| 4114 |
+
]
|
| 4115 |
+
|
| 4116 |
[[package]]
|
| 4117 |
name = "webencodings"
|
| 4118 |
version = "0.5.1"
|
|
|
|
| 4122 |
{ url = "https://files.pythonhosted.org/packages/f4/24/2a3e3df732393fed8b3ebf2ec078f05546de641fe1b667ee316ec1dcf3b7/webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", size = 11774, upload-time = "2017-04-05T20:21:32.581Z" },
|
| 4123 |
]
|
| 4124 |
|
| 4125 |
+
[[package]]
|
| 4126 |
+
name = "websocket-client"
|
| 4127 |
+
version = "1.9.0"
|
| 4128 |
+
source = { registry = "https://pypi.org/simple" }
|
| 4129 |
+
sdist = { url = "https://files.pythonhosted.org/packages/2c/41/aa4bf9664e4cda14c3b39865b12251e8e7d239f4cd0e3cc1b6c2ccde25c1/websocket_client-1.9.0.tar.gz", hash = "sha256:9e813624b6eb619999a97dc7958469217c3176312b3a16a4bd1bc7e08a46ec98", size = 70576, upload-time = "2025-10-07T21:16:36.495Z" }
|
| 4130 |
+
wheels = [
|
| 4131 |
+
{ url = "https://files.pythonhosted.org/packages/34/db/b10e48aa8fff7407e67470363eac595018441cf32d5e1001567a7aeba5d2/websocket_client-1.9.0-py3-none-any.whl", hash = "sha256:af248a825037ef591efbf6ed20cc5faa03d3b47b9e5a2230a529eeee1c1fc3ef", size = 82616, upload-time = "2025-10-07T21:16:34.951Z" },
|
| 4132 |
+
]
|
| 4133 |
+
|
| 4134 |
[[package]]
|
| 4135 |
name = "werkzeug"
|
| 4136 |
version = "3.1.3"
|
|
|
|
| 4143 |
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498, upload-time = "2024-11-08T15:52:16.132Z" },
|
| 4144 |
]
|
| 4145 |
|
| 4146 |
+
[[package]]
|
| 4147 |
+
name = "widgetsnbextension"
|
| 4148 |
+
version = "4.0.15"
|
| 4149 |
+
source = { registry = "https://pypi.org/simple" }
|
| 4150 |
+
sdist = { url = "https://files.pythonhosted.org/packages/bd/f4/c67440c7fb409a71b7404b7aefcd7569a9c0d6bd071299bf4198ae7a5d95/widgetsnbextension-4.0.15.tar.gz", hash = "sha256:de8610639996f1567952d763a5a41af8af37f2575a41f9852a38f947eb82a3b9", size = 1097402, upload-time = "2025-11-01T21:15:55.178Z" }
|
| 4151 |
+
wheels = [
|
| 4152 |
+
{ url = "https://files.pythonhosted.org/packages/3f/0e/fa3b193432cfc60c93b42f3be03365f5f909d2b3ea410295cf36df739e31/widgetsnbextension-4.0.15-py3-none-any.whl", hash = "sha256:8156704e4346a571d9ce73b84bee86a29906c9abfd7223b7228a28899ccf3366", size = 2196503, upload-time = "2025-11-01T21:15:53.565Z" },
|
| 4153 |
+
]
|
| 4154 |
+
|
| 4155 |
[[package]]
|
| 4156 |
name = "win32-setctime"
|
| 4157 |
version = "1.2.0"
|