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

Files changed (6) hide show
  1. LICENSE.md +102 -0
  2. core/config.py +19 -6
  3. models/artifacts_loader.py +27 -0
  4. pyproject.toml +1 -0
  5. requirements.txt +1 -0
  6. 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
+ [![CC BY-NC 4.0](https://licensebuttons.net/l/by-nc/4.0/88x31.png)](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
- ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent
13
- # If file is c:\...\api\core\config.py
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" },