D0nG4667 commited on
Commit ·
c843a81
1
Parent(s): 64c3ddb
feat(api): implement auto-download artifacts and fix CI configuration
Browse files- CI: Fix HF_TOKEN usage in GitHub Actions and add workflow_dispatch
- Docs: Update READMEs with Hugging Face Spaces metadata (Docker SDK, port 7860)
- API: Implement auto-download of ML artifacts from GitHub if missing locally
- API: Update Base Settings to resolve paths correctly in Docker/Local envs
- API: Standardize default port to 7860 and fix shutdown bug in ML system
- Deps: Add 'requests' dependency for artifact downloading"
- LICENSES
- LICENSE.md +102 -0
- core/config.py +19 -6
- models/artifacts_loader.py +27 -0
- pyproject.toml +1 -0
- requirements.txt +1 -0
- uv.lock +2 -0
LICENSE.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Creative Commons Attribution-NonCommercial 4.0 International License
|
| 2 |
+
|
| 3 |
+
## CC BY-NC 4.0
|
| 4 |
+
|
| 5 |
+
This work is licensed under the Creative Commons Attribution-NonCommercial 4.0 International License.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## You are free to:
|
| 10 |
+
|
| 11 |
+
**Share** — copy and redistribute the material in any medium or format
|
| 12 |
+
|
| 13 |
+
**Adapt** — remix, transform, and build upon the material
|
| 14 |
+
|
| 15 |
+
The licensor cannot revoke these freedoms as long as you follow the license terms.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## Under the following terms:
|
| 20 |
+
|
| 21 |
+
### Attribution
|
| 22 |
+
|
| 23 |
+
You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
| 24 |
+
|
| 25 |
+
**Required Attribution:**
|
| 26 |
+
|
| 27 |
+
```reference
|
| 28 |
+
Heart Disease Healthcare AI Systems, D0nG4667
|
| 29 |
+
Licensed under CC BY-NC 4.0
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
### NonCommercial
|
| 33 |
+
|
| 34 |
+
You may not use the material for commercial purposes.
|
| 35 |
+
|
| 36 |
+
**Commercial purposes include, but are not limited to:**
|
| 37 |
+
|
| 38 |
+
- Selling the code or derivatives as a product
|
| 39 |
+
- Using the code in client work for profit
|
| 40 |
+
- Creating paid courses or templates based on this code
|
| 41 |
+
- Using in a commercial SaaS product
|
| 42 |
+
- Using in a commercial healthcare application
|
| 43 |
+
|
| 44 |
+
---
|
| 45 |
+
|
| 46 |
+
## You CAN:
|
| 47 |
+
|
| 48 |
+
- ✅ Use this code for **personal learning** and education
|
| 49 |
+
- ✅ Use this code in your **portfolio** to showcase your skills
|
| 50 |
+
- ✅ **Modify and adapt** the code for personal projects
|
| 51 |
+
- ✅ **Share** your modifications (with proper attribution)
|
| 52 |
+
- ✅ Use in **open-source** non-commercial projects
|
| 53 |
+
- ✅ Use for **academic** and research purposes
|
| 54 |
+
- ✅ Create **tutorials** and educational content referencing this code
|
| 55 |
+
- ✅ Build upon this work for **non-commercial healthcare applications**
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## You CANNOT:
|
| 60 |
+
|
| 61 |
+
- ❌ **Sell** this code or any derivatives
|
| 62 |
+
- ❌ Use in **commercial products** or services
|
| 63 |
+
- ❌ Use for **paid client work**
|
| 64 |
+
- ❌ Create **commercial courses** or tutorials based on this code
|
| 65 |
+
- ❌ **Remove** or obscure the attribution to the original project
|
| 66 |
+
- ❌ **Sublicense** or transfer the code to third parties for commercial use
|
| 67 |
+
- ❌ Use in any **revenue-generating** application
|
| 68 |
+
- ❌ Deploy this code in a **commercial healthcare service** without a separate commercial license
|
| 69 |
+
|
| 70 |
+
---
|
| 71 |
+
|
| 72 |
+
## No Warranty
|
| 73 |
+
|
| 74 |
+
THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
| 75 |
+
|
| 76 |
+
---
|
| 77 |
+
|
| 78 |
+
## Full Legal Text
|
| 79 |
+
|
| 80 |
+
To view a copy of this license, visit:
|
| 81 |
+
[https://creativecommons.org/licenses/by-nc/4.0/legalcode](https://creativecommons.org/licenses/by-nc/4.0/legalcode)
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## Summary
|
| 86 |
+
|
| 87 |
+
This is a human-readable summary of (and not a substitute for) the [full license](https://creativecommons.org/licenses/by-nc/4.0/legalcode).
|
| 88 |
+
|
| 89 |
+
**Key Points:**
|
| 90 |
+
|
| 91 |
+
1. You must give credit to the Author- D0nG4667 of this repository
|
| 92 |
+
2. You cannot use this for commercial purposes
|
| 93 |
+
3. You can share and adapt the work for non-commercial use
|
| 94 |
+
4. The same license terms apply to derivative works
|
| 95 |
+
|
| 96 |
+
---
|
| 97 |
+
|
| 98 |
+
Copyright (c) 2026 Diabetes Disease Outcome ML DL
|
| 99 |
+
|
| 100 |
+
This work is licensed under a [Creative Commons Attribution-NonCommercial 4.0 International License](http://creativecommons.org/licenses/by-nc/4.0/).
|
| 101 |
+
|
| 102 |
+
[](http://creativecommons.org/licenses/by-nc/4.0/)
|
core/config.py
CHANGED
|
@@ -9,12 +9,8 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|
| 9 |
|
| 10 |
# Let's assume the user wants the root of the "system" which is usually where artifacts might be or one level up.
|
| 11 |
# Previous BASE_DIR was 'api' folder. ARTIFACTS_DIR was sibling to 'api'.
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
# parent -> core
|
| 15 |
-
# parent.parent -> api
|
| 16 |
-
# parent.parent.parent -> dl_system
|
| 17 |
-
# parent.parent.parent.parent -> dl_system/api
|
| 18 |
|
| 19 |
NAMING_CONVENTION = "Deep Learning Diabetes Risk Prediction System"
|
| 20 |
|
|
@@ -51,6 +47,7 @@ class Settings(BaseSettings):
|
|
| 51 |
# Paths
|
| 52 |
# We define base directories as Path objects
|
| 53 |
BASE_DIR: Path = ROOT_DIR
|
|
|
|
| 54 |
ARTIFACTS_DIR: Path = BASE_DIR / "artifacts/dl"
|
| 55 |
MODEL_DIR: Path = BASE_DIR / "models/dl"
|
| 56 |
|
|
@@ -62,6 +59,22 @@ class Settings(BaseSettings):
|
|
| 62 |
THRESHOLD_FILENAME: str = "dl_threshold.json"
|
| 63 |
BACKGROUND_DATA_FILENAME: str = "background_data.csv"
|
| 64 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
@property
|
| 66 |
def model_path_abs(self) -> Path:
|
| 67 |
return self.MODEL_DIR / self.MODEL_FILENAME
|
|
|
|
| 9 |
|
| 10 |
# Let's assume the user wants the root of the "system" which is usually where artifacts might be or one level up.
|
| 11 |
# Previous BASE_DIR was 'api' folder. ARTIFACTS_DIR was sibling to 'api'.
|
| 12 |
+
# api/core/config.py -> api/core -> api
|
| 13 |
+
ROOT_DIR = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
|
| 15 |
NAMING_CONVENTION = "Deep Learning Diabetes Risk Prediction System"
|
| 16 |
|
|
|
|
| 47 |
# Paths
|
| 48 |
# We define base directories as Path objects
|
| 49 |
BASE_DIR: Path = ROOT_DIR
|
| 50 |
+
# These paths are relative to the API root
|
| 51 |
ARTIFACTS_DIR: Path = BASE_DIR / "artifacts/dl"
|
| 52 |
MODEL_DIR: Path = BASE_DIR / "models/dl"
|
| 53 |
|
|
|
|
| 59 |
THRESHOLD_FILENAME: str = "dl_threshold.json"
|
| 60 |
BACKGROUND_DATA_FILENAME: str = "background_data.csv"
|
| 61 |
|
| 62 |
+
# Github URL
|
| 63 |
+
GITHUB_RAW_URL_BASE: str = "https://github.com/D0nG4667/Diabetes_Disease_Outcome_AI_System/raw/main"
|
| 64 |
+
|
| 65 |
+
@property
|
| 66 |
+
def ARTIFACT_URLS(self):
|
| 67 |
+
"""Map local path properties to their remote URLs"""
|
| 68 |
+
return {
|
| 69 |
+
self.model_path_abs: f"{self.GITHUB_RAW_URL_BASE}/models/dl/{self.MODEL_FILENAME}",
|
| 70 |
+
self.scaler_path_abs: f"{self.GITHUB_RAW_URL_BASE}/models/dl/{self.SCALER_FILENAME}",
|
| 71 |
+
self.preprocessor_path_abs: f"{self.GITHUB_RAW_URL_BASE}/models/dl/{self.PREPROCESSOR_FILENAME}",
|
| 72 |
+
self.feature_creator_path_abs: f"{self.GITHUB_RAW_URL_BASE}/models/dl/{self.FEATURE_CREATOR_FILENAME}",
|
| 73 |
+
self.threshold_path_abs: f"{self.GITHUB_RAW_URL_BASE}/artifacts/dl/{self.THRESHOLD_FILENAME}",
|
| 74 |
+
self.background_data_path_abs: f"{self.GITHUB_RAW_URL_BASE}/artifacts/dl/{self.BACKGROUND_DATA_FILENAME}",
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
|
| 78 |
@property
|
| 79 |
def model_path_abs(self) -> Path:
|
| 80 |
return self.MODEL_DIR / self.MODEL_FILENAME
|
models/artifacts_loader.py
CHANGED
|
@@ -3,9 +3,13 @@ import pandas as pd
|
|
| 3 |
import tensorflow as tf
|
| 4 |
import logging
|
| 5 |
import json
|
|
|
|
| 6 |
from core.config import settings
|
| 7 |
from core.exceptions import ArtifactLoadError
|
| 8 |
from utils.temperature_scaling import TemperatureScaler
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
logger = logging.getLogger(__name__)
|
| 11 |
|
|
@@ -19,6 +23,23 @@ class ModelArtifacts:
|
|
| 19 |
self.background_data = None
|
| 20 |
self.is_loaded = False
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
def load_artifacts(self):
|
| 23 |
"""
|
| 24 |
Loads all ML artifacts into memory.
|
|
@@ -28,7 +49,13 @@ class ModelArtifacts:
|
|
| 28 |
return
|
| 29 |
|
| 30 |
logger.info("Loading artifacts...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
try:
|
|
|
|
| 32 |
# 1. Load Keras Model
|
| 33 |
if not settings.model_path_abs.exists():
|
| 34 |
logger.warning(f"Model file not found at {settings.model_path_abs}. Skipping load.")
|
|
|
|
| 3 |
import tensorflow as tf
|
| 4 |
import logging
|
| 5 |
import json
|
| 6 |
+
from pathlib import Path
|
| 7 |
from core.config import settings
|
| 8 |
from core.exceptions import ArtifactLoadError
|
| 9 |
from utils.temperature_scaling import TemperatureScaler
|
| 10 |
+
import requests
|
| 11 |
+
import shutil
|
| 12 |
+
|
| 13 |
|
| 14 |
logger = logging.getLogger(__name__)
|
| 15 |
|
|
|
|
| 23 |
self.background_data = None
|
| 24 |
self.is_loaded = False
|
| 25 |
|
| 26 |
+
def download_if_missing(self, path: Path, url: str):
|
| 27 |
+
if not path.exists():
|
| 28 |
+
logger.info(f"Downloading {path.name} from {url}...")
|
| 29 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 30 |
+
try:
|
| 31 |
+
response = requests.get(url, stream=True)
|
| 32 |
+
response.raise_for_status()
|
| 33 |
+
with open(path, 'wb') as f:
|
| 34 |
+
shutil.copyfileobj(response.raw, f)
|
| 35 |
+
logger.info(f"Downloaded {path.name}")
|
| 36 |
+
except Exception as e:
|
| 37 |
+
logger.error(f"Failed to download {path.name}: {e}")
|
| 38 |
+
# We don't raise here, we let the load fail naturally or warn
|
| 39 |
+
else:
|
| 40 |
+
logger.info(f"Artifact {path.name} found locally. Skipping download.")
|
| 41 |
+
|
| 42 |
+
|
| 43 |
def load_artifacts(self):
|
| 44 |
"""
|
| 45 |
Loads all ML artifacts into memory.
|
|
|
|
| 49 |
return
|
| 50 |
|
| 51 |
logger.info("Loading artifacts...")
|
| 52 |
+
|
| 53 |
+
# Ensure all artifacts are present
|
| 54 |
+
for path, url in settings.ARTIFACT_URLS.items():
|
| 55 |
+
self.download_if_missing(path, url)
|
| 56 |
+
|
| 57 |
try:
|
| 58 |
+
|
| 59 |
# 1. Load Keras Model
|
| 60 |
if not settings.model_path_abs.exists():
|
| 61 |
logger.warning(f"Model file not found at {settings.model_path_abs}. Skipping load.")
|
pyproject.toml
CHANGED
|
@@ -20,4 +20,5 @@ dependencies = [
|
|
| 20 |
"shap>=0.49.1",
|
| 21 |
"tensorflow>=2.20.0",
|
| 22 |
"pydantic-settings>=2.0.0",
|
|
|
|
| 23 |
]
|
|
|
|
| 20 |
"shap>=0.49.1",
|
| 21 |
"tensorflow>=2.20.0",
|
| 22 |
"pydantic-settings>=2.0.0",
|
| 23 |
+
"requests>=2.32.5",
|
| 24 |
]
|
requirements.txt
CHANGED
|
@@ -13,3 +13,4 @@ scikit-learn>=1.8.0
|
|
| 13 |
shap>=0.49.1
|
| 14 |
tensorflow>=2.20.0
|
| 15 |
pydantic-settings>=2.0.0
|
|
|
|
|
|
| 13 |
shap>=0.49.1
|
| 14 |
tensorflow>=2.20.0
|
| 15 |
pydantic-settings>=2.0.0
|
| 16 |
+
requests>=2.32.3
|
uv.lock
CHANGED
|
@@ -70,6 +70,7 @@ dependencies = [
|
|
| 70 |
{ name = "pyarrow" },
|
| 71 |
{ name = "pydantic-settings" },
|
| 72 |
{ name = "python-dotenv" },
|
|
|
|
| 73 |
{ name = "scikit-learn" },
|
| 74 |
{ name = "shap" },
|
| 75 |
{ name = "tensorflow" },
|
|
@@ -89,6 +90,7 @@ requires-dist = [
|
|
| 89 |
{ name = "pyarrow", specifier = ">=22.0.0" },
|
| 90 |
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
| 91 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
|
|
|
| 92 |
{ name = "scikit-learn", specifier = ">=1.8.0" },
|
| 93 |
{ name = "shap", specifier = ">=0.49.1" },
|
| 94 |
{ name = "tensorflow", specifier = ">=2.20.0" },
|
|
|
|
| 70 |
{ name = "pyarrow" },
|
| 71 |
{ name = "pydantic-settings" },
|
| 72 |
{ name = "python-dotenv" },
|
| 73 |
+
{ name = "requests" },
|
| 74 |
{ name = "scikit-learn" },
|
| 75 |
{ name = "shap" },
|
| 76 |
{ name = "tensorflow" },
|
|
|
|
| 90 |
{ name = "pyarrow", specifier = ">=22.0.0" },
|
| 91 |
{ name = "pydantic-settings", specifier = ">=2.0.0" },
|
| 92 |
{ name = "python-dotenv", specifier = ">=1.2.1" },
|
| 93 |
+
{ name = "requests", specifier = ">=2.32.5" },
|
| 94 |
{ name = "scikit-learn", specifier = ">=1.8.0" },
|
| 95 |
{ name = "shap", specifier = ">=0.49.1" },
|
| 96 |
{ name = "tensorflow", specifier = ">=2.20.0" },
|