Raheel Abdul Rehman commited on
Commit
c31d1ca
·
1 Parent(s): 1606aeb

Initial Push

Browse files
.dockerignore ADDED
File without changes
.gitignore ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py,cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ #pdm.lock
113
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
114
+ # in version control.
115
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
116
+ .pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
121
+ __pypackages__/
122
+
123
+ # Celery stuff
124
+ celerybeat-schedule
125
+ celerybeat.pid
126
+
127
+ # SageMath parsed files
128
+ *.sage.py
129
+
130
+ # Environments
131
+ .env
132
+ .venv
133
+ env/
134
+ venv/
135
+ ENV/
136
+ env.bak/
137
+ venv.bak/
138
+
139
+ # Spyder project settings
140
+ .spyderproject
141
+ .spyproject
142
+
143
+ # Rope project settings
144
+ .ropeproject
145
+
146
+ # mkdocs documentation
147
+ /site
148
+
149
+ # mypy
150
+ .mypy_cache/
151
+ .dmypy.json
152
+ dmypy.json
153
+
154
+ # Pyre type checker
155
+ .pyre/
156
+
157
+ # pytype static type analyzer
158
+ .pytype/
159
+
160
+ # Cython debug symbols
161
+ cython_debug/
162
+
163
+ # PyCharm
164
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
165
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
166
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
167
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
168
+ #.idea/
169
+
170
+ # Ruff stuff:
171
+ .ruff_cache/
172
+
173
+ # PyPI configuration file
174
+ .pypirc
175
+
176
+ # Cursor
177
+ # Cursor is an AI-powered code editor.`.cursorignore` specifies files/directories to
178
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
179
+ # refer to https://docs.cursor.com/context/ignore-files
180
+ .cursorignore
181
+ .cursorindexingignore
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ RUN useradd -m -u 1000 user
4
+ USER user
5
+ ENV PATH="/home/user/.local/bin:$PATH"
6
+
7
+ WORKDIR /app
8
+
9
+ COPY --chown=user requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY --chown=user ./src ./src
13
+ COPY --chown=user ./data/processed ./data/processed
14
+
15
+ RUN mkdir -p /app/logs
16
+
17
+ EXPOSE 7860
18
+
19
+ CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "7860"]
20
+
README.md CHANGED
@@ -1,12 +1,22 @@
 
 
 
 
 
1
  ---
2
- title: Marks.Guitar Assistant
3
- emoji: 📊
4
- colorFrom: green
5
- colorTo: yellow
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: A guitar ML assistant which is tailored to the users skill
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
+ # 🎸 Marks : AI-Powered Guitar Exercise Recommender
2
+
3
+ An AI-driven music recommendation system that suggests guitar exercises and practice routines based on user skill level, tempo preferences, and chord progression complexity.
4
+ This project leverages **machine learning, FastAPI, and Docker** to provide a scalable production-ready API for real-time recommendations.
5
+
6
  ---
7
+
8
+ ## 🚀 Features
9
+ - **Intelligent Recommendations** – Suggests exercises based on chord progressions, difficulty, and tempo.
10
+ - **Clustering & ML Models** – Groups exercises dynamically by difficulty using machine learning techniques.
11
+ - **REST API with FastAPI** – Lightweight, high-performance backend for serving recommendations.
12
+ - **Scalable & Containerized** – Fully Dockerized for easy deployment across cloud platforms.
13
+ - **Interactive Data Handling** – Uses `pandas`, `numpy`, and `scikit-learn` for data processing and ML.
14
+ - **Spotify Integration (Optional)** Pulls metadata via [Spotipy](https://spotipy.readthedocs.io/) to enrich recommendations.
15
+
16
  ---
17
 
18
+ ## 🏗️ Tech Stack
19
+ - **Backend:** FastAPI, Uvicorn
20
+ - **ML / Data Science:** Scikit-learn, Pandas, Numpy, Matplotlib
21
+ - **Deployment:** Docker, Render (or any cloud hosting)
22
+ - **Testing:** Pytest
data/processed/chord_exercises.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6411955b2b75db12e9636397d87fc97b144a9042834e26a6f0116cd1ebaf3d12
3
+ size 13013
data/processed/prod_data.parquet ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:73006bd788a51c4df96131171459c721b6bc187cb47c549c128998af458ec328
3
+ size 24639044
notebooks/data_visualisation.ipynb ADDED
@@ -0,0 +1,177 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "id": "88f47d25",
7
+ "metadata": {},
8
+ "outputs": [],
9
+ "source": [
10
+ "import pandas as pd\n",
11
+ "import numpy as np\n",
12
+ "from pathlib import Path\n",
13
+ "from sklearn.metrics import silhouette_score\n",
14
+ "import matplotlib.pyplot as plt\n",
15
+ "from sklearn.decomposition import PCA\n",
16
+ "from sklearn.cluster import MiniBatchKMeans\n",
17
+ "from sklearn.metrics.pairwise import cosine_similarity"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": null,
23
+ "id": "b34b67b4",
24
+ "metadata": {},
25
+ "outputs": [],
26
+ "source": [
27
+ "marks_file_path = Path(\"..\",\"data\",\"processed\",\"marks_data.parquet\")#songs_df = pd.read_parquet(marks_file_path)\n",
28
+ "exercise_data_path = Path(\"..\",\"data\",\"processed\",\"chord_exercises.parquet\")\n",
29
+ "exercise_df = pd.read_parquet(exercise_data_path)\n",
30
+ "prod_data_path = Path(\"..\",\"data\",\"processed\",\"prod_data.parquet\")\n",
31
+ "prod_data = pd.read_parquet(prod_data_path)"
32
+ ]
33
+ },
34
+ {
35
+ "cell_type": "code",
36
+ "execution_count": 6,
37
+ "id": "2422497a",
38
+ "metadata": {},
39
+ "outputs": [
40
+ {
41
+ "name": "stdout",
42
+ "output_type": "stream",
43
+ "text": [
44
+ "Running PCA to reduce from 3024 → 100 dimensions...\n",
45
+ "PCA complete. Shape: (168630, 100)\n",
46
+ "Explained variance ratio (first 10 comps): [0.13954444 0.10823043 0.06132166 0.04828419 0.03467049 0.03131611\n",
47
+ " 0.02613195 0.02348917 0.02307746 0.02195262]\n",
48
+ "Running clustering for different k values...\n",
49
+ "k=2 → inertia=695637.38, silhouette=0.1310\n",
50
+ "k=3 → inertia=644517.48, silhouette=0.1130\n",
51
+ "k=4 → inertia=618581.60, silhouette=0.1005\n",
52
+ "k=5 → inertia=585383.74, silhouette=0.1049\n",
53
+ "k=6 → inertia=566055.58, silhouette=0.1035\n",
54
+ "k=7 → inertia=545570.39, silhouette=0.1041\n",
55
+ "k=8 → inertia=536465.33, silhouette=0.1070\n",
56
+ "k=9 → inertia=532107.15, silhouette=0.0945\n",
57
+ "k=10 → inertia=528159.87, silhouette=0.0938\n",
58
+ "k=11 → inertia=517973.04, silhouette=0.0972\n",
59
+ "k=12 → inertia=510710.85, silhouette=0.0988\n",
60
+ "k=13 → inertia=505189.73, silhouette=0.1004\n",
61
+ "k=14 → inertia=498099.02, silhouette=0.1037\n",
62
+ "k=15 → inertia=485065.30, silhouette=0.0970\n",
63
+ "k=16 → inertia=476857.74, silhouette=0.0964\n",
64
+ "k=17 → inertia=472460.50, silhouette=0.0972\n",
65
+ "k=18 → inertia=468655.84, silhouette=0.0975\n",
66
+ "k=19 → inertia=467330.06, silhouette=0.0960\n"
67
+ ]
68
+ },
69
+ {
70
+ "data": {
71
+ "image/png": "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joyMFAcMGCCam5uLRkZGYps2bcSdO3cW+uydO3fE3r17i8bGxqKVlZU4ceJEMSQkpNjln4mIiKqr3bt3i++++67YsGFD0cTERDQwMBDd3d3FCRMmiAkJCVpjnZ2dxWHDhknvDxw4UOje2rFjR7Fx48aFzjNs2DDR2dlZa1tCQoI4YsQI0crKSjQwMBC9vLy0nqcKPHz4UOzfv79obGws1qpVS3zvvffES5cuFXr+EsWXfy6Jj48Xe/bsKZqamooAtJ5FHj9+LAYHB4vu7u6igYGBaGVlJb7yyivivHnzxJycnGKv8bOuy9MKrueWLVsKXadx48aJTk5Oor6+vmhrayt27txZXLFihda4P/74Q/Tw8BD19PS0rk1Z/E6SkpLEd955RzQzMxNVKpX4zjvviOfOnSvyd0JUFQmiyI5sRERERERERERUvthTioiIiIiIiIiIyh2TUkREREREREREVO6YlCIiIiIiIiIionLHpBQREREREREREZU7JqWIiIiIiIiIiKjcMSlFRERERERERETlTk/uAKoTjUaD+/fvw9TUFIIgyB0OERERvQRRFPH48WPY29tDoeD3fC+Lz0lERERVR0mfk5iUKkf379+Hk5OT3GEQERGRDt29exeOjo5yh1Hp8TmJiIio6nnecxKTUuXI1NQUQP4vxczMTOZoiIiI6GWkpaXByclJur/Ty+FzEhERUdVR0uckJqXKUUEpupmZGR+2iIiIqghONdMNPicRERFVPc97TmIDBCIiIiIiIiIiKndMShERERERERERUbljUoqIiIiIiIiIiMode0oRERFVImq1Grm5uXKHUS3o6+tDqVTKHQYREVGR+ExActLVc5KsSSkXFxfcuXOn0Pb//e9/WLJkCbKysvDhhx/il19+QXZ2NgICArB06VLY2NhIY2NiYjB27FgcOHAAJiYmGDZsGGbPng09vX9/tIMHDyIoKAiXL1+Gk5MTPvvsMwwfPlzrnEuWLME333yD+Ph4NG3aFN999x3atGkj7S9JLERERGVFFEXEx8cjJSVF7lCqFXNzc9ja2rKZORERVRh8JqCKQhfPSbImpU6fPg21Wi29v3TpErp06YI33ngDADBp0iT89ddf2LJlC1QqFcaPH49+/frh2LFjAPIzwz179oStrS2OHz+OuLg4DB06FPr6+vjqq68AAFFRUejZsyfef/99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BSvwsa8UERFR2WFSil5aS2cLvOPjDAD4v+0X8SRHLXNERERERC+nICl1PZGVUkRERGWFSSnSicndGsJOZYSY5Ews2MdvFImIiKhyK5i+dyOZzzVERERlhUkp0gkTQz18+bonAODHI7dx4V6KvAERERERvYSC5uaslCIiIio7TEqRzrza0Aa9m9pDIwKTt15Arlojd0hEREREpVJQKZWQkYDUrFSZoyEiIqqamJQinZraywPmxvq4Fv8YKw7fljscIiIiolIxMzSDrYktADY7JyIiKitMSpFOWZkYYuprHgCARaE3EfkwXeaIiIiIiEqnoFrqehKn8BEREZUFJqVI515v7oAO9WsjJ0+D4G0XodGIcodERERE9MIKVuBjpRQREVHZYFKKdE4QBHzZ1xPGBkqcikrGptMxcodERERE9MIKklKslCIiIiobTEpRmXCyMMZHXfMf5Obsuob41CyZIyIiIiJ6MQXT91gpRUREVDaYlKIyM+wVFzRzMsfj7Dx89vsliCKn8REREVHl0cDq3+l7GpGrChMREekak1JUZpQKAV/3bwJ9pYB9VxOw62K83CERERERlZiruSv0FHrIzM3E/cf35Q6HiIioymFSispUA1tTjPVzBwBM23EJKZk5MkdEREREVDL6Sn3UrVUXAHA9kX2liIiIdI1JKSpz4zq5wd3aBInpOfjyr6tyh0NERERUYgV9pdjsnIiISPeYlKIyZ6inxNf9vSAIwJbwezh6M1HukIiIiKq0JUuWwMXFBUZGRvD29sapU6eKHXv58mX0798fLi4uEAQBCxcuLDRm9uzZaN26NUxNTWFtbY2+ffvi+nXtJE1WVhbGjRsHS0tLmJiYoH///khISND1j1buClbgY7NzIiIi3WNSispFS2cLvOPjDAAI3n4BT3LUMkdERERUNW3evBlBQUGYNm0azp49i6ZNmyIgIAAPHjwocnxmZibq1q2LOXPmwNbWtsgxhw4dwrhx43DixAns3bsXubm56Nq1KzIyMqQxkyZNwp9//oktW7bg0KFDuH//Pvr161cmP2N5YqUUERFR2RFELolWbtLS0qBSqZCamgozMzO5wyl36dl56PrtIdxPzcLo9q74tKeH3CERERGVWkW9r3t7e6N169b4/vvvAQAajQZOTk6YMGECpkyZ8szPuri4IDAwEIGBgc8c9/DhQ1hbW+PQ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H6v6GDBggPjll1+qnm/dulU0MjISf/jhB/HKlSvi1KlTRQsLCzEtLU3n51cTlV2LwsJCcfjw4aKDg4MYGxur9n9IQUGBqo8nr0VVP2t1VWXXIicnR3znnXfEqKgoMSEhQTx8+LDo6ekptmvXTszPz1f10RjeF6WysrLEpk2bimvXrtXYR0N5X1DDxnFSGY6TyuM4ieMkjpPKcJxUhuOk2sGkVAMFQONj48aNqjZ9+/YVJ06cqHo+e/Zs0cnJSTQ0NBRtbGzEF154QYyJidF98Fo2ZswYUS6Xi4aGhqK9vb04ZswY8fr166rXn7wOoiiK27dvF9u3by8aGhqKXbp0EX/99VcdR127fv/9dxGAGB8fX+61hvy+CA8P1/hzUXq+SqVSXLRokWhjYyMaGRmJAwcOLHeNnJ2dxZCQELVtX375peoa9ezZUzx58qSOzujpVXYtEhISKvw/JDw8XNXHk9eiqp+1uqqya5GXlyc+//zzorW1tWhgYCA6OzuLU6ZMKTdoagzvi1Lr168XTUxMxMzMTI19NJT3BTVsHCeV4TipPI6TOE7iOKkMx0llOE6qHYIoiuLTzrIiIiIiIiIiIiJ6GqwpRUREREREREREOsekFBERERERERER6RyTUkREREREREREpHNMShERERERERERkc4xKUVERERERERERDrHpBQREREREREREekck1JERERERERERKRzTEoREREREREREZHOMSlFREREREREREQ6x6QUEdV5iYmJEAQBsbGx+g5FJS4uDj4+PjA2NoaHh0eN96+L5/SsvvvuOzz//POq56+99hpGjBhRYft169Zh2LBhOoiMiIio4aqLYwqOk8rjOIlIMyaliKhKr732GgRBwPLly9W27969G4Ig6Ckq/QoJCYGpqSni4+MRFham73Dwww8/wMLCQm/Hz8/Px6JFixASElLtfV5//XXExMQgMjKyFiMjIiKqXRwnlcdxkjqOk4gqxqQUEVWLsbExVqxYgYcPH+o7FK0pLCx86n1v3LiBPn36wNnZGVZWVlqMSr8UCgWUSmWN99u5cyfMzc3Ru3fvau9jaGiIl19+Gf/9739rfDwiIqK6hOMkdRwnqeM4iahiTEoRUbX4+/vD1tYWy5Ytq7DN4sWLy03RXrVqFVxcXFTPS6cqL126FDY2NrCwsMCHH36I4uJivPvuu7C0tISD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8d1332ls7+3tjaZNm+Lf//43bty4gc2bN+OHH35Qvf748WPMnDkTERERuHXrFo4fP44zZ86oBpsuLi7Izc1FWFgY7t+/j7y8PLRv3x7jx4/HhAkTEBoaioSEBJw+fRrLli3Dr7/+WmHsCQkJWLhwIaKionDr1i388ccfuHbtmupYmgQEBODYsWMaX2vZsiWOHDmCuLg4jBs3DsXFxarXIiMj0bp160qn1xMREdUnHCdVjeOkMhwnUWPHpBQRPZUPP/yw3LTxTp064euvv8aaNWvg7u6O06dPP1NNhSctX74cy5cvh7u7O44dO4a9e/eiRYsWAKD61k6hUOD555+Hm5sbZs+eDQsLC7W6DNXx9ttvY+7cuZg3bx7c3Nxw8OBB7N27F+3atatRP4sWLcK8efPwwQcfoFOnThgzZky5+g+lLC0tsWnTJvz2229wc3PDli1b1JZQlkqlyMjIwIQJE9C+fXuMHj0agwcPxpIlSwAAvXr1wrRp0zBmzBhYW1vjk08+AVAyvXzChAmYN28eOnTogBEjRuDMmTNwcnKqMO6mTZsiLi4OI0eORPv27TF16lTMmDEDb775ZoX7TJ48Gb/99huysrI0vm5ra4sjR47g4sWLGD9+vGoAvGXLFkyZMqXS60hERFTfcJxUNY6TynCcRI2ZID55cy4REdFTeOmll+Dp6amxjoYmly9fxoABA/DXX39BJpPVcnRERERE+sNxEpFmnClFRERasXLlSo2r+lQkNTUVP/30EwdaRERE1OBxnESkGWdKERERERERERGRznGmFBERERERERER6RyTUkREREREREREpHNMShERERERERERkc4xKUVERERERERERDrHpBQREREREREREekck1JERERERERERKRzTEoREREREREREZHOMSlFREREREREREQ6x6QUERERERERERHp3P8D78s6bgT+QBgAAAAASUVORK5CYII=",
72
+ "text/plain": [
73
+ "<Figure size 1200x500 with 2 Axes>"
74
+ ]
75
+ },
76
+ "metadata": {},
77
+ "output_type": "display_data"
78
+ },
79
+ {
80
+ "name": "stdout",
81
+ "output_type": "stream",
82
+ "text": [
83
+ "Final model trained with k=5\n",
84
+ "Centroids shape: (5, 100)\n"
85
+ ]
86
+ }
87
+ ],
88
+ "source": [
89
+ "\n",
90
+ "X = np.array(songs_df['feature_vector'].to_list())\n",
91
+ "\n",
92
+ "# --------------------------\n",
93
+ "# Step 1: Dimensionality Reduction with PCA\n",
94
+ "pca_components = 100 # adjust based on explained variance\n",
95
+ "print(f\"Running PCA to reduce from {X.shape[1]} → {pca_components} dimensions...\")\n",
96
+ "pca = PCA(n_components=pca_components, random_state=42)\n",
97
+ "X_reduced = pca.fit_transform(X)\n",
98
+ "\n",
99
+ "print(f\"PCA complete. Shape: {X_reduced.shape}\")\n",
100
+ "print(f\"Explained variance ratio (first 10 comps): {pca.explained_variance_ratio_[:10]}\")\n",
101
+ "\n",
102
+ "# --------------------------\n",
103
+ "# Step 2: Find optimal K with inertia + silhouette\n",
104
+ "inertias = []\n",
105
+ "silhouettes = []\n",
106
+ "K_range = range(2, 20) # You can extend this if needed\n",
107
+ "\n",
108
+ "print(\"Running clustering for different k values...\")\n",
109
+ "for k in K_range:\n",
110
+ " kmeans = MiniBatchKMeans(n_clusters=k, random_state=42, batch_size=2048, n_init=\"auto\")\n",
111
+ " labels = kmeans.fit_predict(X_reduced)\n",
112
+ " \n",
113
+ " inertia = kmeans.inertia_\n",
114
+ " inertias.append(inertia)\n",
115
+ " \n",
116
+ " sil_score = silhouette_score(X_reduced, labels, sample_size=10000, random_state=42)\n",
117
+ " silhouettes.append(sil_score)\n",
118
+ " \n",
119
+ " print(f\"k={k} → inertia={inertia:.2f}, silhouette={sil_score:.4f}\")\n",
120
+ "\n",
121
+ "# --------------------------\n",
122
+ "# Step 3: Plot inertia & silhouette to decide optimal k\n",
123
+ "plt.figure(figsize=(12,5))\n",
124
+ "\n",
125
+ "plt.subplot(1,2,1)\n",
126
+ "plt.plot(K_range, inertias, 'o-', label='Inertia')\n",
127
+ "plt.xlabel(\"Number of clusters (k)\")\n",
128
+ "plt.ylabel(\"Inertia\")\n",
129
+ "plt.title(\"Elbow Method\")\n",
130
+ "plt.legend()\n",
131
+ "\n",
132
+ "plt.subplot(1,2,2)\n",
133
+ "plt.plot(K_range, silhouettes, 'o-', color='green', label='Silhouette Score')\n",
134
+ "plt.xlabel(\"Number of clusters (k)\")\n",
135
+ "plt.ylabel(\"Silhouette\")\n",
136
+ "plt.title(\"Silhouette Method\")\n",
137
+ "plt.legend()\n",
138
+ "\n",
139
+ "plt.tight_layout()\n",
140
+ "plt.show()\n",
141
+ "\n",
142
+ "# --------------------------\n",
143
+ "# Step 4: Fit final model with optimal k (replace with your choice)\n",
144
+ "optimal_k = 5 # <-- set manually after looking at the plots\n",
145
+ "final_kmeans = MiniBatchKMeans(n_clusters=optimal_k, random_state=42, batch_size=2048, n_init=\"auto\")\n",
146
+ "final_labels = final_kmeans.fit_predict(X_reduced)\n",
147
+ "\n",
148
+ "# Get final centroids in PCA space\n",
149
+ "centroids = final_kmeans.cluster_centers_\n",
150
+ "\n",
151
+ "print(f\"Final model trained with k={optimal_k}\")\n",
152
+ "print(\"Centroids shape:\", centroids.shape)\n"
153
+ ]
154
+ }
155
+ ],
156
+ "metadata": {
157
+ "kernelspec": {
158
+ "display_name": "Python 3",
159
+ "language": "python",
160
+ "name": "python3"
161
+ },
162
+ "language_info": {
163
+ "codemirror_mode": {
164
+ "name": "ipython",
165
+ "version": 3
166
+ },
167
+ "file_extension": ".py",
168
+ "mimetype": "text/x-python",
169
+ "name": "python",
170
+ "nbconvert_exporter": "python",
171
+ "pygments_lexer": "ipython3",
172
+ "version": "3.11.9"
173
+ }
174
+ },
175
+ "nbformat": 4,
176
+ "nbformat_minor": 5
177
+ }
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ scikit-learn
4
+ pandas
5
+ numpy
6
+ pyarrow
src/__init__.py ADDED
File without changes
src/data_input.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pandas as pd
2
+ from logger import get_logger # pylint: disable=import-error
3
+
4
+ logger = get_logger(__name__)
5
+
6
+ def read_chord_data(url : str) -> pd.DataFrame:
7
+ """
8
+ Function to read songs chord ata
9
+
10
+ Args:
11
+ url (str): Web api link to chord dataset
12
+
13
+ Returns:
14
+ pd.DataFrame: Pandas read dataframe
15
+ """
16
+ try:
17
+ logger.info('Reading Chord Data')
18
+ dataset = pd.read_csv(url, dtype=str)
19
+ return dataset
20
+ except Exception as e:
21
+ logger.error("Error reading chord data : %s", e)
22
+ raise
23
+
24
+ if __name__ == '__main__':
25
+ chord_data_url = "hf://datasets/ailsntua/Chordonomicon/chordonomicon_v2.csv"
26
+ df = read_chord_data(url= chord_data_url)
27
+ df.to_parquet("data/raw/songs_data.parquet", engine="pyarrow", index=False)
src/data_processing.py ADDED
@@ -0,0 +1,317 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import re
3
+ import pandas as pd
4
+ from sklearn.preprocessing import MinMaxScaler
5
+ from logger import get_logger # pylint: disable=import-error
6
+
7
+
8
+ logger = get_logger(__name__)
9
+
10
+ def clean_string(vars : list) -> list: # pylint: disable=redefined-builtin
11
+ """
12
+ Funciton to manipulate and "clean" string values
13
+
14
+ Args:
15
+ vars (list): list of string variables to manipulate
16
+
17
+ Returns:
18
+ list: cleaned list of strings
19
+ """
20
+ try:
21
+ clean_vars = []
22
+ if isinstance(vars, str):
23
+ vars = [vars]
24
+ for s in vars:
25
+ temp = re.sub(r'[^a-z0-9\s]','',s.strip().lower())
26
+ temp = re.sub(r'\s+', '_', temp)
27
+ clean_vars.append(temp)
28
+ return clean_vars
29
+ except Exception as e:
30
+ logger.error("Error cleaning string : %s", e)
31
+ raise
32
+
33
+ def mandatory_column_check(df : pd.DataFrame, columns : list) -> bool:
34
+ """
35
+ Function to check if the given columnsa re present in the dataframe
36
+
37
+ Args:
38
+ df (pd.DataFrame): Dataframe to check for columns presence
39
+ columns (list): Mandatory columns to check for
40
+
41
+ Returns:
42
+ bool: Boolean value representing status
43
+ """
44
+ try:
45
+ if isinstance(columns, str):
46
+ columns = [columns]
47
+ missing_col = []
48
+ df_headers_list = df.columns.to_list()
49
+ mandatory_columns = clean_string(vars = columns)
50
+ df_headers = clean_string(vars= df_headers_list)
51
+ for col in mandatory_columns:
52
+ if col.lower() not in [s.lower() for s in df_headers]:
53
+ missing_col.append(col)
54
+ if len(missing_col) >= 1:
55
+ return False
56
+ else:
57
+ return True
58
+ except Exception as e:
59
+ logger.error("Error checking for mandatory columns : %s", e)
60
+ raise
61
+
62
+ def drop_dataframe_columns(df : pd.DataFrame, columns: list) -> pd.DataFrame:
63
+ """
64
+ Function to drop columns form a dataframe
65
+
66
+ Args:
67
+ df (pd.DataFrame): Dataframe to manipulate
68
+ columns (list): Columns to be dropped from the dataframe
69
+
70
+ Returns:
71
+ pd.DataFrame: Formatted fataframe
72
+ """
73
+ try:
74
+ final_df = df.drop(columns=columns)
75
+ return final_df
76
+ except Exception as e:
77
+ logger.error("Error droping columns : %s", e)
78
+ raise
79
+
80
+ def dataframe_join(df1 :pd.DataFrame, df2 : pd.DataFrame,
81
+ join_column: str) -> pd.DataFrame:
82
+ """
83
+ Function to join different dataframes on a paticular column
84
+
85
+ Args:
86
+ df1 (pd.DataFrame): Left dataframe
87
+ df2 (pd.DataFrame): Right dataframe
88
+ join_column (str): Join column
89
+
90
+ Returns:
91
+ pd.DataFrame: Cross join of both dataframes
92
+ """
93
+ try:
94
+ formatted_join_col = clean_string(join_column)[0]
95
+ df_list = [df1, df2]
96
+ for i in range(len(df_list)): # pylint: disable=consider-using-enumerate
97
+ for col in df_list[i]:
98
+ formatted_col_name = clean_string(col)
99
+ df_list[i].rename(columns={col: formatted_col_name[0]}, inplace=True)
100
+ merged_df = pd.merge(df1, df2, on=formatted_join_col, how ='inner')
101
+ return merged_df
102
+ except Exception as e:
103
+ logger.error("Error merging dataframes : %s", e)
104
+ raise
105
+
106
+ def marks_custom_encoder(df : pd.DataFrame) -> pd.DataFrame:
107
+ """
108
+ Function to implement custom one hot encoding for categorical variables
109
+
110
+ Args:
111
+ df (pd.DataFrame): Dataframe to manipulate
112
+
113
+ Returns:
114
+ pd.DataFrame: Updated dataframe with one hot encoded columns
115
+ """
116
+ try:
117
+ barre_chords_temp = ["F", "F♯", "G♯", "A♯", "B","Fm", "F♯m", "Gm", "G♯m","A♯m",
118
+ "Bm","Bb", "Cm", "C♯m", "D♯m","F7", "F♯7", "G♯7", "A♯7",
119
+ "C♯7", "D♯7","Fm7","F♯m7", "Gm7", "G♯m7", "Am7", "A♯m7",
120
+ "Bm7", "Cm7", "C♯m7", "Dm7", "D♯m7", "E7"]
121
+ major_minor_chords_temp = ["A", "C", "D", "E", "G", "Am", "Dm", "Em"]
122
+ special_chords = []
123
+ df['formatted_chords'] = df['chords'].apply(lambda x:
124
+ re.sub(r'\s+', ' ', re.sub(r'<[^>]+>', '', x)).strip())
125
+ all_chords = set(chord for row in df['formatted_chords'] for chord in row.split()) # pylint: disable=redefined-outer-name
126
+ barre_chords = [x.lower() for x in barre_chords_temp]
127
+ barre_chords = [x for x in barre_chords if x not in major_minor_chords_temp]
128
+ major_minor_chords = [x.lower() for x in major_minor_chords_temp]
129
+ for chord in all_chords:
130
+ if chord.lower() not in barre_chords and chord.lower() not in major_minor_chords:
131
+ special_chords.append(chord)
132
+
133
+ df['song_length_temp'] = df['formatted_chords'].apply(lambda x: len(x)) # pylint: disable=unnecessary-lambda
134
+ df['song_length'] = (((df['song_length_temp'] - df['song_length_temp'].min())/
135
+ (df['song_length_temp'].max()-df['song_length_temp'].min()))) * 15
136
+ df['distinct_chords'] = df["formatted_chords"].apply(lambda x: set(x.split()))
137
+ df['barre_chords_metric'] = (df['distinct_chords'].apply(lambda x:
138
+ sum(chord in barre_chords_temp for chord in x)) * 2)
139
+ df['major_minor_chords_metric'] = df['distinct_chords'].apply(lambda x:
140
+ sum(chord in major_minor_chords_temp for chord in x))
141
+ df['special_chords'] = (df['distinct_chords'].apply(lambda x:
142
+ sum(chord in special_chords for chord in x)) * 3)
143
+ return df
144
+ except Exception as e:
145
+ logger.error("Error one hot encoding data : %s", e)
146
+ raise
147
+
148
+ def exercise_custom_encoder(df: pd.DataFrame)-> pd.DataFrame :
149
+ """
150
+ Function to encode features in the exercise dataframe
151
+
152
+ Args:
153
+ df (pd.DataFrame): Dataframe to manipulate
154
+
155
+ Returns:
156
+ pd.DataFrame: Resultant dtaframe
157
+ """
158
+ try:
159
+ barre_chords_temp = ["F", "F♯", "G♯", "A♯", "B","Fm", "F♯m", "Gm", "G♯m","A♯m",
160
+ "Bm","Bb", "Cm", "C♯m", "D♯m","F7", "F♯7", "G♯7", "A♯7",
161
+ "C♯7", "D♯7","Fm7","F♯m7", "Gm7", "G♯m7", "Am7", "A♯m7",
162
+ "Bm7", "Cm7", "C♯m7", "Dm7", "D♯m7", "E7"]
163
+ major_minor_chords_temp = ["A", "C", "D", "E", "G", "Am", "Dm", "Em"]
164
+ special_chords = []
165
+ df_exploded = df['chord_progression'].str.split(',').explode()
166
+ all_chords = df_exploded.unique().tolist() # pylint: disable=redefined-outer-name
167
+ barre_chords = [x.lower() for x in barre_chords_temp]
168
+ barre_chords = [x for x in barre_chords if x not in major_minor_chords_temp]
169
+ major_minor_chords = [x.lower() for x in major_minor_chords_temp]
170
+ for chord in all_chords:
171
+ if chord.lower() not in barre_chords and chord.lower() not in major_minor_chords:
172
+ special_chords.append(chord)
173
+ df['barre_chords_metric'] = (df['chord_progression'].apply(lambda x:
174
+ sum(chord in barre_chords_temp for chord in x)) * 2)
175
+ df['major_minor_chords_metric'] = df['chord_progression'].apply(lambda x:
176
+ sum(chord in major_minor_chords_temp for chord in x))
177
+ df['special_chords'] = (df['chord_progression'].apply(lambda x:
178
+ sum(chord in special_chords for chord in x)) * 3)
179
+ df['tempo_mattric'] = ((df['tempo'] - 40) / (200 - 40))
180
+ return df
181
+ except Exception as e:
182
+ logger.error("Error encoding exercise data : %s", e)
183
+ raise
184
+
185
+ def get_universal_chords (df: pd.DataFrame) -> list:
186
+ """
187
+ Function to get a list of all chords
188
+
189
+ Args:
190
+ df (pd.DataFrame): Dtaframe to extract chords cfrom
191
+ columns (list): Chord column name
192
+
193
+ Returns:
194
+ list: List of all unqieu chords
195
+ """
196
+ try:
197
+ df['formatted_chords'] = df['chords'].apply(lambda x:
198
+ re.sub(r'\s+', ' ', re.sub(r'<[^>]+>', '', x)).strip())
199
+ all_chords_list = set(chord for row in df['formatted_chords'] for chord in row.split())
200
+ return all_chords_list
201
+ except Exception as e:
202
+ logger.error("Error getting universal chords list : %s", e)
203
+ raise
204
+
205
+ def chords_to_vector(chord_list, universal_chords):
206
+ """
207
+ Convert list of chords into a binary vector based on universal chords.
208
+ """
209
+ try:
210
+ return [1 if chord in chord_list else 0 for chord in universal_chords]
211
+ except Exception as e:
212
+ logger.error("Error in coverting chord to vector : 5s", e)
213
+ raise
214
+
215
+ def scaler_function(df:pd.DataFrame, columns:list)-> pd.DataFrame:
216
+ """
217
+ Function to create and scale feature vectors
218
+
219
+ Args:
220
+ df (pd.DataFrame): Dataframe to manipulate
221
+ columns (list): Columns to engineer
222
+
223
+ Returns:
224
+ pd.DataFrame: Resultant Dataframe
225
+ """
226
+ try:
227
+ if isinstance(columns, str):
228
+ columns = [columns]
229
+ scaler = MinMaxScaler()
230
+ chords_scaled = scaler.fit_transform(df[columns])
231
+ df_scaled = pd.DataFrame(chords_scaled,columns=[c + "_scaled" for c in columns])
232
+ df = pd.concat([df.reset_index(drop=True), df_scaled.reset_index(drop=True)], axis=1)
233
+ return df
234
+ except Exception as e:
235
+ logger.error("Error in scaling columns: 5s", e)
236
+ raise
237
+
238
+ def create_feature_vector(df:pd.DataFrame, columns:list)-> pd.DataFrame:
239
+ """
240
+ Function to create final feature vector
241
+
242
+ Args:
243
+ df (pd.DataFrame): Dataframe to manipulate
244
+ columns (list): _descriColumns to engineerption_
245
+
246
+ Returns:
247
+ pd.DataFrame: Resultant Dataframe
248
+ """
249
+ try:
250
+ df['feature_vector'] = df.apply(
251
+ lambda row: row['chord_vector'] + [row[col] for col in columns],
252
+ axis=1)
253
+ return df
254
+ except Exception as e:
255
+ logger.error("Error in creating feature vectors: 5s", e)
256
+ raise
257
+
258
+ def exercise_build_vector(row): # pylint: disable=missing-function-docstring
259
+ try:
260
+ feature_cols = ['barre_chords_metric_scaled','major_minor_chords_metric_scaled',
261
+ 'special_chords_scaled','tempo_mattric_scaled']
262
+ chord_vec = list(row['chord_vector'])
263
+ extra = [float(row[col]) for col in feature_cols]
264
+ return chord_vec + extra
265
+ except Exception as e:
266
+ logger.error("Error in building feature vector for exercise df: 5s", e)
267
+ raise
268
+
269
+ def marks_build_vector(row): # pylint: disable=missing-function-docstring
270
+ try:
271
+ feature_cols = ['barre_chords_metric_scaled','major_minor_chords_metric_scaled',
272
+ 'special_chords_scaled','song_length_scaled']
273
+ chord_vec = list(row['chord_vector'])
274
+ extra = [float(row[col]) for col in feature_cols]
275
+ return chord_vec + extra
276
+ except Exception as e:
277
+ logger.error("Error in c\building feature vector for marks df: 5s", e)
278
+ raise
279
+
280
+
281
+ if __name__ == '__main__':
282
+ base_dir = os.path.dirname(os.path.abspath(__file__))
283
+ song_data_path = os.path.join(base_dir, '..','data','raw','songs_data.parquet')
284
+ spotify_data_path = os.path.join(base_dir,'..','data','raw','spotify_tracks.parquet')
285
+ exercise_data_path = os.path.join(base_dir, '..','data','raw','chord_exercises.csv')
286
+
287
+ song_data = pd.read_parquet(song_data_path)
288
+ spotify_data = pd.read_parquet(spotify_data_path)
289
+ exercise_data = pd.read_csv(exercise_data_path)
290
+ marks_data = dataframe_join(song_data, spotify_data, join_column='spotify_song_id')
291
+ marks_data = drop_dataframe_columns(marks_data, columns=['id','releasedate','decade',
292
+ 'rockgenre','artistid',
293
+ 'spotifysongid','spotifyartistid'])
294
+ marks_file_path = os.path.join(base_dir, '..', 'data', 'raw', 'marks_data.parquet')
295
+ marks_data_ohe = marks_custom_encoder(df=marks_data)
296
+ exercise_data_ohe = exercise_custom_encoder(exercise_data)
297
+ all_chords = get_universal_chords(marks_data_ohe)
298
+ marks_data_ohe['chord_vector'] = marks_data_ohe['distinct_chords'].apply(
299
+ lambda x: chords_to_vector(x, all_chords))
300
+ exercise_data_ohe['chord_vector'] = exercise_data_ohe['chord_progression'].apply(
301
+ lambda x: chords_to_vector(x.split(','), all_chords)
302
+ )
303
+ exercise_data_final = scaler_function(
304
+ df = exercise_data_ohe, columns=['barre_chords_metric',
305
+ 'major_minor_chords_metric',
306
+ 'special_chords','tempo_mattric'])
307
+ exercise_data_final['feature_vector'] = exercise_data_final.apply(exercise_build_vector, axis=1)
308
+ marks_data_final = scaler_function(df = marks_data_ohe, columns=
309
+ ['barre_chords_metric', 'major_minor_chords_metric',
310
+ 'special_chords','song_length'])
311
+ marks_data_final['feature_vector'] = marks_data_final.apply(marks_build_vector, axis=1)
312
+
313
+ marks_data_final.to_parquet(os.path.join(
314
+ base_dir, '..', 'data', 'processed', 'marks_data.parquet'))
315
+ exercise_data_ohe_path = os.path.join(
316
+ base_dir, '..','data','processed','chord_exercises.parquet')
317
+ exercise_data_final.to_parquet(exercise_data_ohe_path)
src/logger.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+
4
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
5
+
6
+ LOG_FILE = os.path.join(BASE_DIR, "..", "logs","app.log")
7
+
8
+ # Configure logging
9
+ logging.basicConfig(
10
+ level=logging.INFO,
11
+ format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
12
+ handlers=[
13
+ logging.FileHandler(LOG_FILE), # Writes logs to logs/app.log
14
+ logging.StreamHandler() # Also prints logs to console
15
+ ]
16
+ )
17
+
18
+ # Function to get logger for each module
19
+ def get_logger(name: str):
20
+ """
21
+ Logger functoin to capture all data runs
22
+
23
+ Args:
24
+ name (str): Receives the log frome ach function
25
+ """
26
+ return logging.getLogger(name)
src/main.py ADDED
@@ -0,0 +1,74 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import pandas as pd
4
+ from fastapi import FastAPI, Query
5
+ import uvicorn
6
+
7
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
8
+ from model import recommend_songs as model1 # pylint: disable=import-error
9
+ from model import recommend_songs_random as model2 # pylint: disable=import-error
10
+ from logger import get_logger # pylint: disable=import-error
11
+
12
+ logger = get_logger(__name__)
13
+
14
+ base_dir = os.path.dirname(os.path.abspath(__file__))
15
+ data_dir = os.path.join(base_dir, "..", "data", "processed")
16
+
17
+ prod_file = os.path.join(data_dir, "prod_data.parquet")
18
+ exercise_file = os.path.join(data_dir, "chord_exercises.parquet")
19
+ recommended_history = set()
20
+
21
+ app = FastAPI(title="Exercise Recommendation API")
22
+
23
+ @app.get("/")
24
+ def home():
25
+ return {"message": "Welcome to the Exercise Recommendation API"}
26
+
27
+ @app.get("/random_exercises")
28
+ def random_exercises(genre: str = Query(..., description="Genre of exercises")):
29
+ """Return n random exercises in batches to reduce memory usage."""
30
+ try:
31
+ recommended_temp = set()
32
+ prod_df = pd.read_parquet(
33
+ prod_file,
34
+ filters=[("maingenre", "=", genre)])
35
+ result = model2(genre=genre, songs_df=prod_df, recommended_cache=recommended_temp)
36
+
37
+ recommended_history.update(recommended_temp)
38
+ return result
39
+ except Exception as e:
40
+ logger.error("Error fetching API: %s", e)
41
+ return {"error": str(e)}
42
+
43
+
44
+ @app.get("/recommendations")
45
+ def recommendations(
46
+ tempo: int = Query(..., description="Tempo value"),
47
+ exercise_id: int = Query(..., description="Exercise ID"),
48
+ genre: str = Query(..., description="Genre"),
49
+ ):
50
+ """Return top N recommended songs for a given exercise and tempo using batch processing."""
51
+ try:
52
+ exercise_df = pd.read_parquet(
53
+ exercise_file,
54
+ filters=[("exercise_id", "=", exercise_id)],
55
+ )
56
+ prod_df = pd.read_parquet(
57
+ prod_file,
58
+ filters=[("maingenre", "=", genre)])
59
+
60
+ result = model1(
61
+ exercise_df=exercise_df,
62
+ prod_df=prod_df,
63
+ tempo=tempo,
64
+ exercise_id=exercise_id,
65
+ genre=genre,
66
+ )
67
+ return result
68
+
69
+ except Exception as e:
70
+ logger.error("Error fetching API: %s", e)
71
+ return {"error": str(e)}
72
+
73
+ if __name__ == "__main__":
74
+ uvicorn.run("main:app", host="0.0.0.0", port=7860)
src/model.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import pandas as pd
3
+ import numpy as np
4
+ from sklearn.decomposition import PCA
5
+ from sklearn.cluster import MiniBatchKMeans
6
+
7
+ from logger import get_logger # pylint: disable=import-error
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ def cluster_function(df : pd.DataFrame)-> pd.DataFrame:
12
+ """
13
+ Function to cluster marks dataframe
14
+
15
+ Args:
16
+ df (pd.DataFrame): Dataframe to predict
17
+
18
+ Returns:
19
+ pd.DataFrame: Resultant dataframe
20
+ """
21
+ try:
22
+ X = np.array(df['feature_vector'].to_list())
23
+ pca_components = 100
24
+ pca = PCA(n_components=pca_components, random_state=42)
25
+ X_reduced = pca.fit_transform(X)
26
+
27
+ kmeans = MiniBatchKMeans(n_clusters=14, random_state=42, batch_size=2048, n_init="auto")
28
+ df['cluster'] = kmeans.fit_predict(X_reduced)
29
+
30
+ df['difficulty_score'] = (df['barre_chords_metric_scaled'] +
31
+ df['major_minor_chords_metric_scaled'] +
32
+ df['special_chords_scaled'] +
33
+ df['song_length_scaled'])
34
+
35
+ cluster_difficulty = df.groupby("cluster")["difficulty_score"].mean().reset_index()
36
+ cluster_difficulty = cluster_difficulty.sort_values("difficulty_score").reset_index(drop=True)
37
+
38
+ difficulty_levels = ["Beginner", "Intermediate", "Advanced"]
39
+ bins = pd.qcut(cluster_difficulty["difficulty_score"], q=len(difficulty_levels), labels=difficulty_levels)
40
+
41
+ cluster_difficulty["difficulty_level"] = bins
42
+
43
+ cluster_map = dict(zip(cluster_difficulty["cluster"], cluster_difficulty["difficulty_level"]))
44
+ df["difficulty_level"] = df["cluster"].map(cluster_map)
45
+ return df
46
+ except Exception as e:
47
+ logger.error("Error in clustering marks dataset : %s", e)
48
+ raise
49
+
50
+ def recommend_songs(exercise_df, prod_df,exercise_id, tempo, genre, top_n=5):
51
+ """
52
+ Recommend top_n songs similar to the given exercise and tempo.
53
+ Works on PCA-reduced vectors.
54
+ """
55
+ try:
56
+ exercise_row = exercise_df[
57
+ (exercise_df['exercise_id'] == exercise_id) &
58
+ (exercise_df['tempo'] == tempo)
59
+ ]
60
+ if exercise_row.empty:
61
+ raise ValueError("No exercise found with given ID and tempo")
62
+
63
+ exercise_vector = np.array(exercise_row['feature_vector'].iloc[0]).reshape(1, -1)
64
+ filtered_prod_df = prod_df[prod_df['maingenre'] == genre]
65
+ if filtered_prod_df.empty:
66
+ raise ValueError(f"No songs found in genre '{genre}'")
67
+
68
+ similarities = []
69
+ for vec in filtered_prod_df['feature_vector'].values:
70
+ sin = np.dot(exercise_vector, vec) / (np.linalg.norm(exercise_vector) * np.linalg.norm(vec))
71
+ similarities.append(sin)
72
+
73
+ filtered_prod_df = filtered_prod_df.copy()
74
+ filtered_prod_df['similarity'] = similarities
75
+ top_recommendations = filtered_prod_df.sort_values(by='similarity', ascending=False).head(top_n)
76
+ return top_recommendations[['trackname', 'artistnames', 'maingenre', 'chords', 'difficulty_level']]
77
+ except Exception as e:
78
+ logger.error("Error in generating recommendations : %s", e)
79
+ raise
80
+
81
+ def recommend_songs_random(genre,songs_df, recommended_cache, n=5) -> list:
82
+ """
83
+ Cluster function to retrieve random songs
84
+
85
+ Args:
86
+ genre (_type_): String value
87
+ n (int, optional): Number of records to retrieve Defaults to 5.
88
+
89
+ Returns:
90
+ list: _description_
91
+ """
92
+ try:
93
+ if songs_df.empty:
94
+ return {"error": "Dataset not loaded"}
95
+
96
+ genre_songs = songs_df[songs_df["maingenre"] == genre]
97
+
98
+ available_songs = genre_songs[~genre_songs["trackname"].isin(recommended_cache)]
99
+
100
+ if available_songs.empty:
101
+ return {"error": f"No new songs available for genre: {genre}"}
102
+
103
+ selected = available_songs.sample(min(n, len(available_songs)), replace=False)
104
+
105
+ recommended_cache.update(selected["trackname"].tolist())
106
+
107
+ return selected[["trackname", "artistnames", "maingenre", "chords",
108
+ "difficulty_level"]].to_dict(orient="records"),recommended_cache
109
+ except Exception as e:
110
+ logger.error("Error retrieving random recommendations: %s", e)
111
+ raise
112
+
113
+ if __name__ == '__main__':
114
+ base_dir = os.path.dirname(os.path.abspath(__file__))
115
+ marks_data_file_path = os.path.join(base_dir, '..', 'data', 'processed', 'marks_data.parquet')
116
+ exercise_data_ohe_path = os.path.join(base_dir, '..','data','processed','chord_exercises.parquet')
117
+ marks_df = pd.read_parquet(marks_data_file_path)
118
+ exercise_df = pd.read_parquet(exercise_data_ohe_path)
119
+ df_prod = cluster_function(marks_df)
120
+ df_prod_file_path = os.path.join(base_dir, '..', 'data', 'processed', 'prod_data.parquet')
121
+ df_prod.to_parquet(df_prod_file_path)
src/spotify_data_fetch.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import random
4
+ import spotipy
5
+ import pandas as pd
6
+ from spotipy.oauth2 import SpotifyClientCredentials
7
+ from logger import get_logger # pylint: disable=import-error
8
+
9
+ logger = get_logger(__name__)
10
+
11
+ def read_spotify_track_data(track_ids: list,
12
+ output_file: str,
13
+ save_every: int = 5000,
14
+ max_records: int = 25000) -> pd.DataFrame:
15
+ """
16
+ Fetches Spotify track data in batches with a tiny delay between batches to avoid rate limits.
17
+
18
+ Args:
19
+ track_ids (list): Spotify track ids
20
+ save_every (int): Save checkpoint after this many records
21
+ output_file (str): File path for partial results
22
+ max_records (int): Max number of tracks to fetch in one run
23
+
24
+ Returns:
25
+ pd.DataFrame: Track data (song, artist, id, etc.)
26
+ """
27
+ try:
28
+ client_id = "754888cc4fa4486daea9cb7917e176fc"
29
+ client_secret = "3f383e012f7442c18851a668b63849dc"
30
+
31
+ sp = spotipy.Spotify(
32
+ auth_manager=SpotifyClientCredentials(
33
+ client_id=client_id,
34
+ client_secret=client_secret
35
+ )
36
+ )
37
+
38
+ # Load partial results if they exist
39
+ if os.path.exists(output_file):
40
+ existing = pd.read_parquet(output_file)
41
+ processed_ids = set(existing["spotify_song_id"])
42
+ else:
43
+ existing = pd.DataFrame()
44
+ processed_ids = set()
45
+
46
+ # Filter only unprocessed IDs
47
+ remaining_ids = [tid for tid in track_ids if tid not in processed_ids]
48
+
49
+ # Limit to max_records this run
50
+ remaining_ids = remaining_ids[:max_records]
51
+
52
+ results = []
53
+ total = len(remaining_ids)
54
+
55
+ for i in range(0, total, 50):
56
+ batch = remaining_ids[i:i+50]
57
+ response = sp.tracks(batch)
58
+
59
+ for track in response["tracks"]:
60
+ if track is None:
61
+ continue
62
+ track_id = track["id"]
63
+ track_name = track["name"]
64
+ artist_names = ", ".join([artist["name"] for artist in track["artists"]])
65
+ results.append({
66
+ "spotify_song_id": track_id,
67
+ "track_name": track_name,
68
+ "artist_names": artist_names
69
+ })
70
+
71
+ # Tiny random delay to avoid hitting rate limit
72
+ time.sleep(random.uniform(0.2, 0.5))
73
+
74
+ if (i // 50) % (save_every // 50) == 0 and results:
75
+ df_partial = pd.concat([existing, pd.DataFrame(results)], ignore_index=True)
76
+ df_partial.to_parquet(output_file, index=False)
77
+
78
+ # Final save
79
+ final_df = pd.concat([existing, pd.DataFrame(results)], ignore_index=True)
80
+ final_df.to_parquet(output_file, index=False)
81
+ print(f"Run complete. Total saved: {len(final_df)} tracks")
82
+
83
+ return final_df
84
+ except Exception as e:
85
+ logger.error("Failed fetching spotify data : %s", e)
86
+ raise
87
+
88
+ if __name__ == '__main__':
89
+ BASE_DIR = os.path.dirname(os.path.abspath(__file__))
90
+ file_path = os.path.join(BASE_DIR, "..", "data", "raw", "songs_data.parquet")
91
+
92
+ chord_data = pd.read_parquet(file_path)
93
+ track_id_list = [str(id) for id in chord_data["spotify_song_id"] if str(id).lower() != 'none']
94
+ output_path = os.path.join(BASE_DIR, "..", "data", "raw", "spotify_tracks.parquet")
95
+ spotify_track_data = read_spotify_track_data(track_id_list, output_path)
96
+
97
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
98
+ spotify_track_data.to_parquet(output_path, index=False)
src/utils.py ADDED
File without changes
tests/test_utils.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi.testclient import TestClient
2
+ from src.main import app # adjust import if needed
3
+
4
+ client = TestClient(app)
5
+
6
+ def test_recommend_random():
7
+ """Test that /recommend/random runs and returns a response"""
8
+ response = client.get("/recommend/random?genre=rock&n=5")
9
+ assert response.status_code == 200
10
+ assert "recommendations" in response.json()
11
+
12
+ def test_recommend_filtered():
13
+ """Test that /recommend/filtered runs with tempo + exercise_id"""
14
+ payload = {
15
+ "tempo": 120,
16
+ "exercise_id": 1,
17
+ "genre": "rock"
18
+ }
19
+ response = client.post("/recommend/filtered", json=payload)
20
+ assert response.status_code == 200
21
+ assert "recommendations" in response.json()