Upload 76 files
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- all_combine_code/.env +59 -0
- all_combine_code/.gitignore +28 -0
- all_combine_code/Dockerfile +19 -0
- all_combine_code/__init__.py +0 -0
- all_combine_code/agents_/__init__.py +0 -0
- all_combine_code/agents_/__pycache__/__init__.cpython-313.pyc +0 -0
- all_combine_code/agents_/delveant_prompts/__init__.py +0 -0
- all_combine_code/agents_/delveant_prompts/crm_leads_agents.py +157 -0
- all_combine_code/agents_/delveant_prompts/db_collection_name_finder.py +113 -0
- all_combine_code/agents_/delveant_prompts/tool_clasification_agent.py +327 -0
- all_combine_code/all_services_/__pycache__/delveant_core_services.cpython-313.pyc +0 -0
- all_combine_code/all_services_/delveant_core_services.py +1011 -0
- all_combine_code/app.py +21 -0
- all_combine_code/azure-pipelines.yml +176 -0
- all_combine_code/genre_classification/genre_.ipynb +611 -0
- all_combine_code/genre_classification/main_file.ipynb +2110 -0
- all_combine_code/genre_classification/test1_file.ipynb +325 -0
- all_combine_code/genreclassification/.env +1 -0
- all_combine_code/genreclassification/Dockerfile +22 -0
- all_combine_code/genreclassification/chartmetric_summary_generator3.py +264 -0
- all_combine_code/genreclassification/main_app.py +71 -0
- all_combine_code/genreclassification/requirements.txt +8 -0
- all_combine_code/genreclassification/songs_report_generator1.py +66 -0
- all_combine_code/genreclassification/viberate_api_report_generator2.py +205 -0
- all_combine_code/local_model_to_s3/bucket_inside_file_delete.py +30 -0
- all_combine_code/local_model_to_s3/local_model_to_s3_bucket.py +28 -0
- all_combine_code/memory_/__init__.py +0 -0
- all_combine_code/memory_/__pycache__/__init__.cpython-313.pyc +0 -0
- all_combine_code/memory_/__pycache__/chat_history_handle.cpython-313.pyc +0 -0
- all_combine_code/memory_/__pycache__/prompt_agents.cpython-313.pyc +0 -0
- all_combine_code/memory_/chat_history_handle.py +194 -0
- all_combine_code/memory_/prompt_agents.py +82 -0
- all_combine_code/models_/__init__.py +0 -0
- all_combine_code/models_/__pycache__/__init__.cpython-313.pyc +0 -0
- all_combine_code/models_/__pycache__/code_generation_model.cpython-313.pyc +0 -0
- all_combine_code/models_/code_generation_model.py +90 -0
- all_combine_code/models_/delveant_llm_model_/__init__.py +0 -0
- all_combine_code/models_/delveant_llm_model_/__pycache__/__init__.cpython-313.pyc +0 -0
- all_combine_code/models_/delveant_llm_model_/__pycache__/multi_agent_llm.cpython-313.pyc +0 -0
- all_combine_code/models_/delveant_llm_model_/multi_agent_llm.py +264 -0
- all_combine_code/models_/delveant_llm_model_/tool_calling_model.py +117 -0
- all_combine_code/mongodb_data_chat/delveant_chatbot.py +152 -0
- all_combine_code/recommedation_engine/__pycache__/app.cpython-313.pyc +0 -0
- all_combine_code/recommedation_engine/__pycache__/embeddings_.cpython-313.pyc +0 -0
- all_combine_code/recommedation_engine/__pycache__/s3_file_uploader.cpython-313.pyc +0 -0
- all_combine_code/recommedation_engine/app.py +85 -0
- all_combine_code/recommedation_engine/binary_classifier/__pycache__/classify.cpython-313.pyc +0 -0
- all_combine_code/recommedation_engine/binary_classifier/classify.py +100 -0
- all_combine_code/recommedation_engine/embeddings_.py +98 -0
.gitattributes
CHANGED
|
@@ -107,3 +107,5 @@ genre_finder/find_genre_different_apporoch/features_dataset.csv filter=lfs diff=
|
|
| 107 |
genre_finder/find_genre_different_apporoch/all_genre.csv filter=lfs diff=lfs merge=lfs -text
|
| 108 |
rocketship-ml-model-train/assets/all_genres_clean.csv filter=lfs diff=lfs merge=lfs -text
|
| 109 |
rocketship-ml-model-train/assets/features_dataset.csv filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 107 |
genre_finder/find_genre_different_apporoch/all_genre.csv filter=lfs diff=lfs merge=lfs -text
|
| 108 |
rocketship-ml-model-train/assets/all_genres_clean.csv filter=lfs diff=lfs merge=lfs -text
|
| 109 |
rocketship-ml-model-train/assets/features_dataset.csv filter=lfs diff=lfs merge=lfs -text
|
| 110 |
+
all_combine_code/stage_extra_files/dev/sample.txt filter=lfs diff=lfs merge=lfs -text
|
| 111 |
+
all_combine_code/stage_extra_files/output.json filter=lfs diff=lfs merge=lfs -text
|
all_combine_code/.env
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# GOOGLE_API_KEY=AIzaSyAzo1_bhEC8ASX5V-POgXQYArc12eS7Ln4 #AIzaSyAnXCwQEpRmekZOH_l6Pbpm2ccQpEWxMEw
|
| 2 |
+
# GOOGLE_CX=479d684fcf07c49ed #e01019bc2a8e944dc
|
| 3 |
+
# OPENAI_API_KEY=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
|
| 4 |
+
|
| 5 |
+
#LUSA_API_KEY=bd3e6a99-874e-474a-b238-3d2a9e8bf6bf
|
| 6 |
+
GOOGLE_API_KEY=AIzaSyAnXCwQEpRmekZOH_l6Pbpm2ccQpEWxMEw
|
| 7 |
+
GOOGLE_CX=479d684fcf07c49ed
|
| 8 |
+
# OPENAI_API_KEY=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
|
| 9 |
+
OPENAI_API_KEY=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
|
| 10 |
+
OPENROUTER_API_KEY=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
|
| 11 |
+
OPENROUTER_URL=https://openrouter.ai/api/v1
|
| 12 |
+
GEMINI_API_KEY=AIzaSyAwpQ8oPIc9AywvbrT8Jdd-FMzjLzosfyI
|
| 13 |
+
GROQ_API_KEY=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
|
| 14 |
+
TOOL_CALL_MODEL=openai/gpt-5.1-chat
|
| 15 |
+
#CODE_CREATE_MODEL=x-ai/grok-code-fast-1
|
| 16 |
+
CODE_CREATE_MODEL=gpt-4.1-2025-04-14
|
| 17 |
+
|
| 18 |
+
AI_CHAT_HISTORY_S3=delvant-ai-all-models
|
| 19 |
+
HISTORY_MAX_FOLDER_COUNT=500
|
| 20 |
+
PL_HISTORY_MAX_FOLDER_COUNT=200
|
| 21 |
+
|
| 22 |
+
MONGO_URI=mongodb+srv://dev:zzVIduQiBkc8Wstz@delveant.wg3g6m.mongodb.net/
|
| 23 |
+
SUB_DOMAIN=kovaioncrmstage94320
|
| 24 |
+
|
| 25 |
+
FLOW_URL=https://integrate.delveant.cloud/api/v1/flow/executeflow
|
| 26 |
+
CORE_API_URL=https://coreapi.delveant.cloud/api/v1
|
| 27 |
+
FLOW_URL_API=https://coreapi.delveant.cloud/api/v1/automatelead
|
| 28 |
+
|
| 29 |
+
TENANT_ID=a758fb60-2fef-4a98-be9d-a1820297556b
|
| 30 |
+
CLIENT_ID=686034a6-11ee-4659-a225-5b56915828e0
|
| 31 |
+
CLIENT_SECRET=F-X8Q~CAScz7.2imThjYs7inSAStZ8cFLYtxGceR
|
| 32 |
+
|
| 33 |
+
SANITIZER_SALT=e7576e0f19400cd8347050bcd67a2dd583286c909aac0a822a2bbc65ddb2b22e
|
| 34 |
+
SANITIZER_SECRET_KEY=wuo1phSbKySaacnzy-iS_Jji2TiGnEpnb5lvN6uOsrQ=
|
| 35 |
+
|
| 36 |
+
EMAIL_API_MICROSOFT=https://coreapi.delveant.cloud/api/v1/oauth/microsoft/send-email
|
| 37 |
+
EMAIL_API_GOOGLE=https://coreapi.delveant.cloud/api/v1/oauth/google/send-email
|
| 38 |
+
|
| 39 |
+
AUNTHENTICATE_URL=https://coreapi.delveant.cloud/api/v1/oauth/authenticated-email
|
| 40 |
+
SCHEDULE_MICROSOFT=https://coreapi.delveant.cloud/api/v1/oauth/microsoft/schedule-meeting
|
| 41 |
+
SCHEDULE_GOOGLE=https://coreapi.delveant.cloud/api/v1/oauth/google/schedule-meeting
|
| 42 |
+
LEAD_API_URL=https://integrate.kovaion.cloud/api/v1/permission/updatedUpload
|
| 43 |
+
|
| 44 |
+
AWS_ACCESS_KEY=AKIAZEBAZEFJRQURGGYZ
|
| 45 |
+
AWS_SECRET_KEY=195RaA3wVK2j8UlQA9rCGPMHDe7TeZb0OjVqfZmP
|
| 46 |
+
AWS_REGION=ap-south-1
|
| 47 |
+
BEDROCK_API_KEY=ABSKQmVkcm9ja0FQSUtleS13bnd6LWF0LTYyNzEzMzk4MTAxMTpZOTRJaEp0R3RCOHRTMzY2S1hzOTNuVXB3b2FvZ3JBdkRHQTh2Vi9hWlFPNWppV0hYT1N0S1I1dmpEST0=
|
| 48 |
+
|
| 49 |
+
FINANCE_MONGO_URI=mongodb+srv://preprod:pfPDxshbRQ1p6qYV@cluster0.nnian.mongodb.net/
|
| 50 |
+
FINANCE_DB_NAME="engage"
|
| 51 |
+
FINANCE_COLLECTION_NAME="FinanceHub_PL Report"
|
| 52 |
+
PL_HISTORY_S3=delveant-ai
|
| 53 |
+
|
| 54 |
+
SPORTZIA_MONGO_URI=mongodb+srv://preprod:48Kzz9AwoKvZqn4C@preprod.wg3g6m.mongodb.net/
|
| 55 |
+
SPORTZIA_DB_NAME=builderpreprod
|
| 56 |
+
SPORTZI_COLLECTION_NMAE=Sportzia_ChatBot
|
| 57 |
+
|
| 58 |
+
VITE_RESPONSE_ENCRYPTION_KEY=default_secret_key_32_chars_long!!
|
| 59 |
+
VITE_PAYLOAD_ENCRYPTION_KEY=default_secret_key_32_chars_long!!
|
all_combine_code/.gitignore
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python compiled files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
*.pyo
|
| 5 |
+
*.pyd
|
| 6 |
+
|
| 7 |
+
# Ignore pycache specifically in tools_ folder
|
| 8 |
+
tools_/__pycache__/
|
| 9 |
+
|
| 10 |
+
# Virtual environment
|
| 11 |
+
venv/
|
| 12 |
+
.venv
|
| 13 |
+
|
| 14 |
+
# IDE files (PyCharm)
|
| 15 |
+
.idea/
|
| 16 |
+
*.iml
|
| 17 |
+
|
| 18 |
+
# Logs
|
| 19 |
+
*.log
|
| 20 |
+
|
| 21 |
+
# OS files
|
| 22 |
+
.DS_Store
|
| 23 |
+
Thumbs.db
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
#git rm -r --cached .
|
| 27 |
+
#git add .
|
| 28 |
+
|
all_combine_code/Dockerfile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.10-bullseye
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
RUN mkdir -p \
|
| 6 |
+
/app/tmp \
|
| 7 |
+
/app/vector_dbs \
|
| 8 |
+
&& chmod -R 775 /app
|
| 9 |
+
|
| 10 |
+
#ENV TORCHINDUCTOR_CACHE_DIR=/app/tmp/torchinductor
|
| 11 |
+
|
| 12 |
+
COPY . /app
|
| 13 |
+
|
| 14 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 15 |
+
|
| 16 |
+
EXPOSE 5000
|
| 17 |
+
|
| 18 |
+
CMD ["gunicorn","app:app","-k","uvicorn.workers.UvicornWorker","-w","8","--preload","--timeout","300","--keep-alive","10","--bind","0.0.0.0:5000"]
|
| 19 |
+
|
all_combine_code/__init__.py
ADDED
|
File without changes
|
all_combine_code/agents_/__init__.py
ADDED
|
File without changes
|
all_combine_code/agents_/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (176 Bytes). View file
|
|
|
all_combine_code/agents_/delveant_prompts/__init__.py
ADDED
|
File without changes
|
all_combine_code/agents_/delveant_prompts/crm_leads_agents.py
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def mongodb_query_generator_leads(
|
| 5 |
+
input_message,
|
| 6 |
+
data,
|
| 7 |
+
user_id,
|
| 8 |
+
data_with_datatype,
|
| 9 |
+
db_name,
|
| 10 |
+
collection_name, source
|
| 11 |
+
):
|
| 12 |
+
return f"""
|
| 13 |
+
You are a MongoDB query expert.
|
| 14 |
+
|
| 15 |
+
Your task:
|
| 16 |
+
Generate a valid PyMongo query JSON based on the user request and collection sample documents.
|
| 17 |
+
|
| 18 |
+
- All records inside `isDeleted` available, **isDeleted=true means it's deleted records**, Your developed all pipeline code inside must Not consider this records.
|
| 19 |
+
- Strictly use all pipeline inside, follow concepts = {{isDeleted:{{$ne:true}}}}
|
| 20 |
+
- NEVER output Markdown, text, or comments.
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
### INPUTS
|
| 24 |
+
|
| 25 |
+
User Query:
|
| 26 |
+
{input_message}
|
| 27 |
+
|
| 28 |
+
Database: {db_name}
|
| 29 |
+
Collection: {collection_name}
|
| 30 |
+
|
| 31 |
+
FIELDS & TYPES (STRICT – CASE SENSITIVE):
|
| 32 |
+
{data_with_datatype}
|
| 33 |
+
|
| 34 |
+
SCHEMA SAMPLE (2 records only, may not be complete):
|
| 35 |
+
{data}
|
| 36 |
+
|
| 37 |
+
SOURCE field(key) inside present names:
|
| 38 |
+
{source}
|
| 39 |
+
|
| 40 |
+
*Note source related queries clearly understand to make the response.*
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
### GENERAL RULES:
|
| 44 |
+
|
| 45 |
+
1) Always return ONLY a valid JSON object.
|
| 46 |
+
2) Do NOT include explanations.
|
| 47 |
+
3) Do NOT wrap response in markdown.
|
| 48 |
+
4) JSON must be directly parsable by json.loads().
|
| 49 |
+
5) If aggregation is required, use "operation": "aggregate" and provide pipeline.
|
| 50 |
+
6) If simple filtering is enough, use "operation": "find".
|
| 51 |
+
|
| 52 |
+
-----------------------------------
|
| 53 |
+
DATE RELATED QUERIES (CRITICAL):
|
| 54 |
+
|
| 55 |
+
- Must follow the rules **date related queries inside** year not mention strictly use current year.
|
| 56 |
+
eg . 23 march create lead names (date 23, month march, year not mention use current year)
|
| 57 |
+
|
| 58 |
+
If the user request involves:
|
| 59 |
+
- last N days
|
| 60 |
+
- last month
|
| 61 |
+
- this month
|
| 62 |
+
- today
|
| 63 |
+
- yesterday
|
| 64 |
+
- specific date range
|
| 65 |
+
- between two dates
|
| 66 |
+
|
| 67 |
+
You MUST:
|
| 68 |
+
|
| 69 |
+
1) Use this import:
|
| 70 |
+
from datetime import datetime, timedelta, timezone
|
| 71 |
+
|
| 72 |
+
2) Always define:
|
| 73 |
+
now = datetime.now(timezone.utc)
|
| 74 |
+
|
| 75 |
+
3) Dynamically calculate date ranges using timedelta.
|
| 76 |
+
|
| 77 |
+
4) NEVER:
|
| 78 |
+
- Compare date fields with raw strings
|
| 79 |
+
- Use ISODate()
|
| 80 |
+
- Use $dateSubtract
|
| 81 |
+
- Hardcode year unless user explicitly provides year
|
| 82 |
+
- Generate invalid python
|
| 83 |
+
|
| 84 |
+
5) If multiple date fields exist:
|
| 85 |
+
- MUST use "creationdate" if present
|
| 86 |
+
- Otherwise fallback in this order:
|
| 87 |
+
contacteddate → engageddate → closeddate
|
| 88 |
+
|
| 89 |
+
6) Date condition must be placed:
|
| 90 |
+
- inside "filter" (for find)
|
| 91 |
+
- inside "$match" (for aggregate)
|
| 92 |
+
|
| 93 |
+
7) For date queries:
|
| 94 |
+
- python_code MUST define variables: start_date, end_date, now
|
| 95 |
+
- In filter/pipeline, reference them as plain strings: "start_date", "end_date"
|
| 96 |
+
- These will be resolved at runtime by the caller
|
| 97 |
+
- Example:
|
| 98 |
+
python_code: "from datetime import datetime, timedelta, timezone\\nnow = datetime.now(timezone.utc)\\nstart_date = datetime(now.year, 2, 23, tzinfo=timezone.utc)\\nend_date = datetime(now.year, 2, 27, tzinfo=timezone.utc)"
|
| 99 |
+
filter: {{"creationdate": {{"$gte": "start_date", "$lt": "end_date"}}
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
8) TOP / BEST LEADS RULE (CRITICAL):
|
| 103 |
+
|
| 104 |
+
- Any query containing **“top / best / top N leads”, Must follow the below pipeline concept.
|
| 105 |
+
- pipeline = [{{'$addFields': {{'leadscore_num': {{'$convert': {{'input': {{'$trim': {{'input': {{'$toString': '$leadscore'}}}}}}, 'to': 'double', 'onError': None, 'onNull': None}}}}, 'firstname_str': {{'$trim': {{'input': {{'$toString': {{'$ifNull': ['$firstname', '']}}}}}}}}, 'lastname_str': {{'$trim': {{'input': {{'$toString': {{'$ifNull': ['$lastname', '']}}}}}}}}}}}}, {{'$addFields': {{'full_name': {{'$trim': {{'input': {{'$concat': [{{'$ifNull': ['$firstname_str', '']}}, {{'$cond': [{{'$and': [{{'$ne': ['$firstname_str', '']}}, {{'$ne': ['$lastname_str', '']}}]}}, ' ', '']}}, {{'$ifNull': ['$lastname_str', '']}}]}}}}}}}}}}, {{'$sort': {{'leadscore_num': -1}}}}, {{'$limit': 3}}, {{'$project': {{'_id': 0, 'full_name': 1, 'email': 1, 'phone': 1}}}}]
|
| 106 |
+
|
| 107 |
+
9) pipeline or find query inside , user message need to set the limits.
|
| 108 |
+
|
| 109 |
+
10) DATE OUTPUT (STRICT – MUST FOLLOW)
|
| 110 |
+
- NEVER project date fields directly (e.g., "creationdate": 1 ❌)
|
| 111 |
+
- ALWAYS use:
|
| 112 |
+
"$dateToString": {{ "format": "%d-%m-%Y", "date": "$<field>" }}
|
| 113 |
+
- Output must be string only.
|
| 114 |
+
- Your response inside all date must present "%d-%m-%Y" this format only.
|
| 115 |
+
|
| 116 |
+
11) All response inside strictly avoid `_id` this field.
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
### OUTPUT FORMAT:
|
| 120 |
+
|
| 121 |
+
If NOT a date query:
|
| 122 |
+
|
| 123 |
+
{{
|
| 124 |
+
"operation": "find" | "aggregate",
|
| 125 |
+
"python_code" : null
|
| 126 |
+
"filter": {{}},
|
| 127 |
+
"projection": {{}},
|
| 128 |
+
"pipeline": [],
|
| 129 |
+
"sort": {{}},
|
| 130 |
+
"limit": null or int value
|
| 131 |
+
}}
|
| 132 |
+
|
| 133 |
+
If it IS a date query:
|
| 134 |
+
|
| 135 |
+
{{
|
| 136 |
+
"operation": "find" | "aggregate",
|
| 137 |
+
"python_code": "from datetime import datetime, timedelta, timezone\\nnow = datetime.now(timezone.utc)\\n# date calculation here",
|
| 138 |
+
"filter": {{}},
|
| 139 |
+
"projection": {{}},
|
| 140 |
+
"pipeline": [],
|
| 141 |
+
"sort": {{}},
|
| 142 |
+
"limit": null or int value
|
| 143 |
+
}}
|
| 144 |
+
|
| 145 |
+
-----------------------------------
|
| 146 |
+
STRICT REQUIREMENTS:
|
| 147 |
+
|
| 148 |
+
- Output must be valid JSON
|
| 149 |
+
- Do not include comments
|
| 150 |
+
- Do not include explanation text
|
| 151 |
+
- Do not include markdown formatting
|
| 152 |
+
- Field names must match exactly
|
| 153 |
+
- Use double quotes only
|
| 154 |
+
- Ensure JSON is syntactically correct
|
| 155 |
+
|
| 156 |
+
Return ONLY the JSON object.
|
| 157 |
+
"""
|
all_combine_code/agents_/delveant_prompts/db_collection_name_finder.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
def db_collection_find(input, user_id, person_id, collection_keywords, priveous_chat_history_query) -> str:
|
| 2 |
+
return f"""
|
| 3 |
+
You are the **Accurate CRM Query Formatter and Database Collection Name Finder Agent**.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
### USER INPUT
|
| 8 |
+
{input}
|
| 9 |
+
|
| 10 |
+
### PREVIOUS CHAT HISTORY QUERY:
|
| 11 |
+
{priveous_chat_history_query}
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
## PRIMARY OBJECTIVE
|
| 15 |
+
|
| 16 |
+
Your task is to generate:
|
| 17 |
+
1. User input query clearly understand any spell mistake or sentence mistake present time correct it. otherwise return the same user query.
|
| 18 |
+
2. The **correct CRM database collection name**, based on the user’s intent and query context.
|
| 19 |
+
3. If the user’s input query is **self-related** (e.g., contains words like *my*, *personal*, etc.) — for example, “show my details” or “please give my details” — then `{user_id}` or `{person_id}` this two id's any one added to make the professional questions format.
|
| 20 |
+
4. **User Input** suppose not clear format/meaning, kindly use **Previous chat history query**
|
| 21 |
+
---
|
| 22 |
+
|
| 23 |
+
## STEP-BY-STEP LOGIC
|
| 24 |
+
|
| 25 |
+
### 1. Understand USER QUERY
|
| 26 |
+
- Interpret the intent of the user query carefully.
|
| 27 |
+
- Rephrase it to a **professional CRM-style analytic question** (e.g., "Show me my best lead source" → "Identify the best-performing lead source for user {user_id} based on lead scoring metrics.").
|
| 28 |
+
|
| 29 |
+
---
|
| 30 |
+
|
| 31 |
+
## 2. DATABASE COLLECTION MAPPING LOGIC:
|
| 32 |
+
{collection_keywords}
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## 3. Self-Related (Me / My) Handling:
|
| 36 |
+
**Decision Table**
|
| 37 |
+
|
| 38 |
+
| Step | Check | Requirement | If Fails |
|
| 39 |
+
| ---- | ------------------------- | ---------------------------------------------------------------------------------------------- | --------------- |
|
| 40 |
+
| 1 | Self reference present | `my`, `mine`, `myself`, `for me`, `about me`, `show me my`, `give me my`, `tell me my`, or `I` | ❌ Do NOT inject |
|
| 41 |
+
| 2 | Other identifiers absent | No names, no user IDs, no `by/for/assigned to <person>` | ❌ Do NOT inject |
|
| 42 |
+
| 3 | Personal ownership intent | Refers to user's own records (tasks, deals, contacts, opportunities, etc.) | ❌ Do NOT inject |
|
| 43 |
+
| ✅ | All 3 pass | Inject `{user_id}` | ✅ Inject |
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
**Examples**
|
| 47 |
+
|
| 48 |
+
| Query | Inject `{user_id}`? | Output |
|
| 49 |
+
| ---------------------------------- | ------------------- | --------------------------------------------- |
|
| 50 |
+
| show my deals | ✅ Yes | Show the deals for user `{user_id}` |
|
| 51 |
+
| give me my tasks for today | ✅ Yes | Give the tasks for today for user `{user_id}` |
|
| 52 |
+
| list my opportunities | ✅ Yes | List the opportunities for user `{user_id}` |
|
| 53 |
+
| show deals closed by Satheesh | ❌ No | No injection |
|
| 54 |
+
| list top 10 deals | ❌ No | No injection |
|
| 55 |
+
| show opportunities for account ABC | ❌ No | No injection |
|
| 56 |
+
| Show me deals won in the last 6 months | ❌ No | No injection |
|
| 57 |
+
| Please give the details | ❌ No | No injection |
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
## 4.Database Collection Classification (Strict):
|
| 62 |
+
| Priority | Intent Type | Keywords / Signals | Collection |
|
| 63 |
+
|----------|-------------|-------------------|-------------|
|
| 64 |
+
| 1 | Audit / Logs / Tracking | meeting, schedule, participants, logs, email status, sent, failed, email logs, not contacted | admin.audit.CRM_View |
|
| 65 |
+
| 2 | Tasks / Activities / Work | task, tasks, follow-up, followups, activities, to-do, reminder, pending, assigned, today, overdue | CRM_Task_View |
|
| 66 |
+
| 3 | Revenue / Deals / Sales | revenue, amount, payment, deal, deals, quote, quotation, pipeline, opportunity, sales | CRM_Opportunity_View |
|
| 67 |
+
| 4 | Accounts / Companies | account, accounts, company, companies, client, customer, firm, organization, business | CRM_Accounts_View |
|
| 68 |
+
| 5 | Contacts / Events | contact, contacts, event, event name, event register | CRM_Contacts_View |
|
| 69 |
+
| 6 | Leads / Person Specific | person name, leads info, personal email id | CRM_Leads_View |
|
| 70 |
+
| 7 | Comparative (Dynamic) | best, top, highest, maximum, most | decide by entity |
|
| 71 |
+
|
| 72 |
+
Apply in this exact order:
|
| 73 |
+
|
| 74 |
+
1. If meeting/email logs/audit → admin.audit.CRM_View (ALWAYS override)
|
| 75 |
+
2. If task/activity keywords → CRM_Task_View
|
| 76 |
+
3. If revenue/deal keywords → CRM_Opportunity_View
|
| 77 |
+
4. If account/company keywords → CRM_Accounts_View
|
| 78 |
+
5. If contact/event keywords → CRM_Contacts_View
|
| 79 |
+
6. If person name or leads data info → CRM_Leads_View
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
EXTRA EMAIL RULES (STRICT)
|
| 83 |
+
|
| 84 |
+
• Email ID of a person → CRM_Leads_View (eg . show my mail id, show ravi mail id)
|
| 85 |
+
• Contact details (contains word "contact") → CRM_Contacts_View
|
| 86 |
+
• Email status/log/count → admin.audit.CRM_View
|
| 87 |
+
|
| 88 |
+
### 5. OUTPUT RULES
|
| 89 |
+
|
| 90 |
+
- Return **only valid JSON**, formatted exactly as below.
|
| 91 |
+
- Do **not** include explanations, markdown, or extra text.
|
| 92 |
+
- Do **not** repeat input or chat history in the output.
|
| 93 |
+
- If query inside 'leads or lead' words mention, use the 'CRM_Leads_View'
|
| 94 |
+
|
| 95 |
+
**Output JSON structure:**
|
| 96 |
+
|
| 97 |
+
```json
|
| 98 |
+
{{
|
| 99 |
+
"query": "corrected query --> (keep in mind user query 98% don't change.only correct the spell mistake or self related query add {user_id}).",
|
| 100 |
+
"db_collection_name": "CRM_Collection_Name"
|
| 101 |
+
}}
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### Notes:
|
| 105 |
+
- If the user input query is at least 45% clear and meaningful, use the current `user input` query.
|
| 106 |
+
- If the user input query is less than 45% clear, incomplete, vague, or not meaningful, then use the Previous Chat History Query instead.
|
| 107 |
+
- Query inside any mailid or name or comany name don't correct the spell mistake, because this is case-sensitive.
|
| 108 |
+
eg . user_input inside : `manoiiifecs@hgamil.com` this present some spell mistake, strictly this format don't correct it.
|
| 109 |
+
|
| 110 |
+
- If a company name is mentioned in short form (e.g., CDSL, AI), DO NOT expand it to its full form.
|
| 111 |
+
Always preserve the exact company name format as provided in the user input.
|
| 112 |
+
|
| 113 |
+
"""
|
all_combine_code/agents_/delveant_prompts/tool_clasification_agent.py
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from textwrap import dedent
|
| 2 |
+
|
| 3 |
+
TOOLS = [
|
| 4 |
+
# 1. CRM Info
|
| 5 |
+
{
|
| 6 |
+
"type": "function",
|
| 7 |
+
"function": {
|
| 8 |
+
"name": "get_crm_info",
|
| 9 |
+
"description": "Retrieve CRM Database DATA/MongoDB information such as leads, contacts, tasks, meetings, opportunities, accounts, revenue,comapny details ,analytics, and reports.",
|
| 10 |
+
"parameters": {
|
| 11 |
+
"type": "object",
|
| 12 |
+
"properties": {
|
| 13 |
+
"user_message": {"type": "string", "description": "The original user query"},
|
| 14 |
+
"collection": {
|
| 15 |
+
"type": "string",
|
| 16 |
+
"enum": [
|
| 17 |
+
"CRM_Task_View",
|
| 18 |
+
"CRM_Leads_View",
|
| 19 |
+
"CRM_Contacts_View",
|
| 20 |
+
"CRM_Opportunity_View",
|
| 21 |
+
"CRM_Accounts_View",
|
| 22 |
+
"admin.audit.CRM_View"
|
| 23 |
+
],
|
| 24 |
+
"description": "The CRM/MongoDB collection to query"
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
"required": ["user_message", "collection"]
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
},
|
| 31 |
+
|
| 32 |
+
# 2. Send_email_api
|
| 33 |
+
{
|
| 34 |
+
"type": "function",
|
| 35 |
+
"function": {
|
| 36 |
+
"name": "send_email_api",
|
| 37 |
+
"description": "Send an email with subject, body, optional CC, and attachments.",
|
| 38 |
+
"parameters": {
|
| 39 |
+
"type": "object",
|
| 40 |
+
"properties": {
|
| 41 |
+
"to_email": {"type": "string"},
|
| 42 |
+
"subject": {"type": "string"},
|
| 43 |
+
"body": {"type": "string"},
|
| 44 |
+
"provider": {
|
| 45 |
+
"type": "string",
|
| 46 |
+
"description": "Email provider (google or microsoft)",
|
| 47 |
+
"enum": ["google", "microsoft"],
|
| 48 |
+
},
|
| 49 |
+
"cc_email": {"type": "string"},
|
| 50 |
+
"attachments": {
|
| 51 |
+
"type": "array",
|
| 52 |
+
"items": {
|
| 53 |
+
"type": "object",
|
| 54 |
+
"properties": {
|
| 55 |
+
"url": {"type": "string"},
|
| 56 |
+
"filename": {"type": "string"},
|
| 57 |
+
"mimeType": {"type": "string"}
|
| 58 |
+
},
|
| 59 |
+
"required": ["url", "filename", "mimeType"]
|
| 60 |
+
}
|
| 61 |
+
}
|
| 62 |
+
},
|
| 63 |
+
"required": ["to_email", "subject", "body", "provider"]
|
| 64 |
+
}
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
# 3. Create lead
|
| 68 |
+
{
|
| 69 |
+
"type": "function",
|
| 70 |
+
"function": {
|
| 71 |
+
"name": "create_lead",
|
| 72 |
+
"description": "Create a new lead in CRM. Requires email, company name, and the lead's requirement.",
|
| 73 |
+
"parameters": {
|
| 74 |
+
"type": "object",
|
| 75 |
+
"properties": {
|
| 76 |
+
"email": {"type": "string"},
|
| 77 |
+
"company_name": {"type": "string"},
|
| 78 |
+
"requirement": {"type": "string"}
|
| 79 |
+
},
|
| 80 |
+
"required": ["email", "company_name", "requirement"]
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
},
|
| 84 |
+
|
| 85 |
+
# 4.Schedule_meeting_api
|
| 86 |
+
{
|
| 87 |
+
"type": "function",
|
| 88 |
+
"function": {
|
| 89 |
+
"name": "schedule_meeting_api",
|
| 90 |
+
"description": "Schedule a meeting.",
|
| 91 |
+
"parameters": {
|
| 92 |
+
"type": "object",
|
| 93 |
+
"properties": {
|
| 94 |
+
"title": {"type": "string"},
|
| 95 |
+
"recipient": {"type": "string"},
|
| 96 |
+
"description": {"type": "string"},
|
| 97 |
+
"start_time": {"type": "string"},
|
| 98 |
+
"end_time": {"type": "string"},
|
| 99 |
+
"time_zone": {"type": "string"}
|
| 100 |
+
},
|
| 101 |
+
"required": ["title", "recipient", "description", "start_time", "end_time"]
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
},
|
| 105 |
+
# 5. Update query
|
| 106 |
+
{
|
| 107 |
+
"type": "function",
|
| 108 |
+
"function": {
|
| 109 |
+
"name": "update_query",
|
| 110 |
+
"description": "Update an existing CRM record.",
|
| 111 |
+
"parameters": {
|
| 112 |
+
"type": "object",
|
| 113 |
+
"properties": {
|
| 114 |
+
"field_name": {"type": "string"},
|
| 115 |
+
"new_value": {"type": "string"},
|
| 116 |
+
"collection": {"type": "string"}
|
| 117 |
+
},
|
| 118 |
+
"required": ["field_name", "new_value", "collection"]
|
| 119 |
+
}
|
| 120 |
+
}
|
| 121 |
+
},
|
| 122 |
+
|
| 123 |
+
# 6. Greetings
|
| 124 |
+
{
|
| 125 |
+
"type": "function",
|
| 126 |
+
"function": {
|
| 127 |
+
"name": "greetings_api",
|
| 128 |
+
"description": "Handle greetings, small talk, or identity questions.",
|
| 129 |
+
"parameters": {
|
| 130 |
+
"type": "object",
|
| 131 |
+
"properties": {
|
| 132 |
+
"message": {"type": "string"}
|
| 133 |
+
},
|
| 134 |
+
"required": ["message"]
|
| 135 |
+
}
|
| 136 |
+
}
|
| 137 |
+
},
|
| 138 |
+
|
| 139 |
+
# 7. PDF File QA
|
| 140 |
+
{
|
| 141 |
+
"type": "function",
|
| 142 |
+
"function": {
|
| 143 |
+
"name": "pdf_file_api",
|
| 144 |
+
"description": "Extract or explain information from uploaded PDF files.",
|
| 145 |
+
"parameters": {
|
| 146 |
+
"type": "object",
|
| 147 |
+
"properties": {
|
| 148 |
+
"question": {"type": "string"}
|
| 149 |
+
},
|
| 150 |
+
"required": ["question"]
|
| 151 |
+
}
|
| 152 |
+
}
|
| 153 |
+
},
|
| 154 |
+
|
| 155 |
+
# 8. General Chat
|
| 156 |
+
{
|
| 157 |
+
"type": "function",
|
| 158 |
+
"function": {
|
| 159 |
+
"name": "general_chatbot_api",
|
| 160 |
+
"description": "MANDATORY fallback tool. Must be called for any non-CRM, non-email, non-meeting, non-PDF, non-greeting question. Text responses are forbidden.",
|
| 161 |
+
"parameters": {
|
| 162 |
+
"type": "object",
|
| 163 |
+
"properties": {
|
| 164 |
+
"question": {"type": "string"}
|
| 165 |
+
},
|
| 166 |
+
"required": ["question"]
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
]
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
def tool_classification_agent():
|
| 174 |
+
return """
|
| 175 |
+
# You are a strict enterprise tool **Router** that analyzes intent, selects exactly one correct below given tool, never responds in text, and always outputs a valid tool call or asks for missing required fields draft text format.
|
| 176 |
+
|
| 177 |
+
**Available tools:**
|
| 178 |
+
|
| 179 |
+
| **No.** | **Tool Name** | **When to Use / Description** |
|
| 180 |
+
| ------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| 181 |
+
| 1 | **send_email_api** | Use for any requests related to sending emails, including subject and content. |
|
| 182 |
+
| 2 | **get_crm_info** | Use for CRM /MongoDB/ database related queries such as: <br>• Leads, contacts, opportunities, tasks <br>• Salary, revenue, sales revenue <br>• Email or company lookups <br>• Events or task related queries <br>• Analytics, updates, or retrieving database info <br>• MongoDB data extraction, counts,sums,totals |
|
| 183 |
+
| 3 | **create_lead** | Use for creating a new lead in the CRM system using email and company name. |
|
| 184 |
+
| 4 | **schedule_meeting_api** | Use for scheduling meetings in the calendar. |
|
| 185 |
+
| 5 | **update_query** | Use for updating or changing existing CRM records. |
|
| 186 |
+
| 6 | **greetings_api** | Use when the user input is a greeting, polite message, small talk, or asks about the assistant’s identity. <br>Examples: “hi”, “hello”, “how are you”, “what is your name”, “who are you” |
|
| 187 |
+
| 7 | **pdf_file_api** | Use for questions related to extracting or retrieving information from PDF files. |
|
| 188 |
+
| 8 | **general_chatbot_api** | General questions answering like (eg. what is java?, what is oops, how to train the ml models? |
|
| 189 |
+
|
| 190 |
+
# **CRM Query Classification Table (6 Collections) this based in tool name is `get_crm_info`**
|
| 191 |
+
|
| 192 |
+
| **Rule No** | **Query Type / Condition** | **Keywords / Indicators** | **Collection to Use** | **Notes / Examples** |
|
| 193 |
+
| ----------- | --------------------------------- | -------------------------------------------------------------------------------------------------- | ----------------------------- | -------------------------------------------------- |
|
| 194 |
+
| **1** | Task-related queries | tasks, follow-ups, activities, current work, pending actions, to-do | **CRM_Task_View** | "Show my pending tasks", "List today’s follow-ups" |
|
| 195 |
+
| **2A** | Person name mentioned | Any human name or mobile, phone, contact number, mail, email details | **CRM_Leads_View** | "Show details of Rahul", "Lead info for Anitha" |
|
| 196 |
+
| **2B** | Communication-related queries | events, event register details | **CRM_Contacts_View** | Contacts or event extraction strictly uses this |
|
| 197 |
+
| **3** | Meeting & email audit queries | meeting schedule, next meeting, meeting time, participants, email status, sent/failed emails, logs | **admin.audit.CRM_View** | Includes “leads not contacted in last 10 days” |
|
| 198 |
+
| **4** | Opportunity & revenue queries | revenue, amount, payment, deal, deals, quotation, quote, pipeline | **CRM_Opportunity_View** | "Show total revenue this month", "List open deals" |
|
| 199 |
+
| **5** | Best / top / highest analysis | best, top, highest, maximum, most | **Dynamic (based on entity)** | Compute using keys like score, amount, probability |
|
| 200 |
+
| **6** | Account / company-related queries | accounts, companies, organizations, clients, customers, firms, businesses, enterprises | **CRM_Accounts_View** | "How many accounts assigned in Jan 2026?" |
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
**Tool selection rules (VERY IMPORTANT)**
|
| 204 |
+
- If the user’s request is about CRM data in any way (leads, contacts, opportunities, tasks, meetings, emails, accounts, metrics, reports), you MUST use 'get_crm_info' unless the user is clearly asking to create/update something."
|
| 205 |
+
- Use 'get_crm_info' when the user asks to *summarize, list, show, get, fetch, report, analyze, understand, group, classify, categorize, segment, score, or suggest next actions* for meetings, leads, contacts, opportunities, tasks, emails, or any CRM-related data.
|
| 206 |
+
|
| 207 |
+
1)`get_crm_info`: (below rules understand to must return `get_crm_info`):
|
| 208 |
+
|
| 209 |
+
- If user queries is related to the CRM/MongoDB/Database data or information extraction releted strictly return this tool.
|
| 210 |
+
- Query word is "count,sum, leads name, event registrations, register records, analytics, meetings calls details, etc..
|
| 211 |
+
- If query is Task data extraction related, Lead data extractions related, Contacts data extractions related, Opportunities data extractions related, Acoounts data extractions related.
|
| 212 |
+
- **show, list, get, fetch, summarize, analyze, report, count, total, pending, upcoming, status, details, history, dashboard, metrics, performance, pipeline, revenue, sales, events, meetings (if not creating), emails (if not sending), tasks, leads, contacts, accounts, opportunities**
|
| 213 |
+
- **Important Note:** `What is my mail id or Show my mail id or show my details for U0006` this format self related queries must return "get_crm_info" tool.
|
| 214 |
+
|
| 215 |
+
Sample Example Questions:
|
| 216 |
+
* Summarize today’s meetings
|
| 217 |
+
* What is my mail ID / show my mail ID
|
| 218 |
+
* Show my pending tasks
|
| 219 |
+
* Give me lead status report
|
| 220 |
+
* Analyze my pipeline for this quarter
|
| 221 |
+
* Group my leads into cold, warm, and hot categories and suggest next actions for each
|
| 222 |
+
* Which leads should I follow up with today
|
| 223 |
+
* Tell me which opportunities are most likely to close this month
|
| 224 |
+
* Show the top 5 sources with count of contacts registered for the event *“Intelligent HR & Payroll for NextGen Workforce 2026”* based on source
|
| 225 |
+
* What is the total registrations for the event *“Intelligent HR & Payroll for NextGen Workforce 2026”*
|
| 226 |
+
* How many emails has been add from santhosh last year?
|
| 227 |
+
* Please give the `Martinez20 Technologies` company all detail like employee count,etc
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
2)`send_email_api`: (below rules understand to must return `send_email_api`):
|
| 231 |
+
|
| 232 |
+
- User query related for send email ,email send, compose, or draft emails or this similar.
|
| 233 |
+
- **Mandatory fields are:** 'to_email', 'subject', and 'body'. Optional: 'cc_email', 'attachments'
|
| 234 |
+
- If above mention `Mandatory fields` are missing, ask ONLY for the missing ones in a single message, then generate the draft.
|
| 235 |
+
- Please provide the following details so I can draft the email for you: \n -**to_mail** \n \n- **Subject** of the email \n- **Body/content** of the email.
|
| 236 |
+
|
| 237 |
+
3)`create_lead`: (below rules understand to must return `create_lead`):
|
| 238 |
+
|
| 239 |
+
- Use 'create_lead' when the user asks to create, add, register, or open a new lead
|
| 240 |
+
- Mandatory fields: 'email', 'company_name', and 'requirement'
|
| 241 |
+
- If the user provides multiple fields in one request, extract ALL of them and call the API immediately
|
| 242 |
+
- If above mention `Mandatory fields` fields are missing, ask ONLY for the missing ones in a single message, then generate the draft..
|
| 243 |
+
- If the lead already exists, respond with details and provide the view link.
|
| 244 |
+
- Once all fields are collected, call the API immediately.
|
| 245 |
+
- Confirm success or failure clearly to the user.
|
| 246 |
+
- When the user message contains multiple data points (emails, company names, requirements).
|
| 247 |
+
|
| 248 |
+
4)`schedule_meeting_api`:( below rules understand to must return `schedule_meeting_api`):
|
| 249 |
+
|
| 250 |
+
- When the user explicitly wants to schedule/arrange/book/assign a meeting (e.g., 'schedule a meeting', 'book a demo').
|
| 251 |
+
- **Mandatory fields:** 'title/summary', 'recipient/attendees', 'date', 'startTime', 'endTime'. Optional: 'description', 'time_zone'
|
| 252 |
+
- If above mention `Mandatory fields` are missing, ask ONLY for the missing ones in a single message, then generate the draft.
|
| 253 |
+
|
| 254 |
+
- CRITICAL: If the user mentions a timezone (e.g., 'Europe/London', 'time zone is Europe/London'), extract ONLY the timezone value (e.g., 'Europe/London') and pass it as the 'time_zone' parameter. DO NOT include timezone information in the 'description' field.\n"
|
| 255 |
+
- IMPORTANT: For dates in DD/MM/YYYY format (e.g., '05/12/2025'), interpret as day/month/year (5th December 2025, not May 12th). When passing to start_time and end_time, use clear format like '2025-12-05 1:00 PM'.\n"
|
| 256 |
+
- The 'description' field should contain meeting details but NEVER timezone information.
|
| 257 |
+
- If no timezone is mentioned, default to 'Asia/Kolkata'
|
| 258 |
+
|
| 259 |
+
- **IMPORTANT NOTE:** Do NOT use this tool when the user asks to summarize, list, or check meetings; in those cases, use 'get_crm_info'.
|
| 260 |
+
|
| 261 |
+
*Note: User sometimes give the very short format query like only `schedule meeting` or `book meeting` this similar very short format time, above mention "mandatory fields" what is missing that used to send the draft message.*
|
| 262 |
+
|
| 263 |
+
5)`greetings_api`:
|
| 264 |
+
|
| 265 |
+
- If the user input is a greeting, polite message, small talk, or asks about the assistant’s identity.
|
| 266 |
+
- ALSO route here when the query specifically mentions "Delvant CRM" as a product/platform explanation request.
|
| 267 |
+
|
| 268 |
+
Special Condition:
|
| 269 |
+
- If the phrase "Delvant CRM" appears together in the query
|
| 270 |
+
AND the intent is explain/what/how/about/usage/guide/help,
|
| 271 |
+
ALWAYS use greetings_api.
|
| 272 |
+
|
| 273 |
+
Examples:
|
| 274 |
+
- What is Delvant CRM
|
| 275 |
+
- Explain Delvant CRM
|
| 276 |
+
- How to use Delvant CRM
|
| 277 |
+
- About Delvant CRM
|
| 278 |
+
- Help me understand Delvant CRM
|
| 279 |
+
|
| 280 |
+
Important:
|
| 281 |
+
- Only trigger when both words appear together: "Delvant CRM"
|
| 282 |
+
- If only "CRM" or only "Delvant" appears → follow normal routing rules.
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
6)`pdf_file_api`: (below rules understand to must return `pdf_file_api`):
|
| 286 |
+
|
| 287 |
+
- ** **only** for questions that require information from the uploaded PDF files inside the data.
|
| 288 |
+
|
| 289 |
+
- The query **must explicitly or implicitly refer to PDF content**, such as:
|
| 290 |
+
* Asking for personal details found in the PDF (email, phone number, address, etc.)
|
| 291 |
+
* Asking questions like:
|
| 292 |
+
|
| 293 |
+
* “Please give my email and phone number from the PDF”
|
| 294 |
+
* “What is a transformer according to my PDF data?”
|
| 295 |
+
* “Explain this topic using my PDF file”
|
| 296 |
+
* "What is my name use pdf file"
|
| 297 |
+
|
| 298 |
+
* If the question mentions:
|
| 299 |
+
* *my pdf file*
|
| 300 |
+
* *pdf data*
|
| 301 |
+
* *pdf document*
|
| 302 |
+
* *this pdf*
|
| 303 |
+
* *use pdf*
|
| 304 |
+
* or asks to extract / explain information **from the PDF**
|
| 305 |
+
|
| 306 |
+
*Note: query inside `not use pdf` or `not consider pdf` or this similar word mention time strictly avoid `pdf_file_api`*
|
| 307 |
+
|
| 308 |
+
7)`general_chatbot_api`: (below rules understand to must return `general_chatbot_api`):
|
| 309 |
+
|
| 310 |
+
- Use when the user asks general, informational, or explanatory questions (what is, how to, explain, define, usage, guide, integrate, examples).
|
| 311 |
+
- If no other tool matches, ALWAYS call this tool.
|
| 312 |
+
- NEVER answer in text.
|
| 313 |
+
- Action: call general_chatbot_api({"question": "<user message>"}).
|
| 314 |
+
|
| 315 |
+
|
| 316 |
+
### VERY CRITICAL NOTES:
|
| 317 |
+
1. If query contains "lead" AND ("create" OR "new" OR "add") → call create_lead
|
| 318 |
+
2. Else if query contains other create intents → return draft message
|
| 319 |
+
3. Lead creation has highest priority: if the request mentions "lead" with create/add/new, ALWAYS call `create_lead` tool and NEVER return the draft message.
|
| 320 |
+
|
| 321 |
+
| Condition (User Intent) | Action |
|
| 322 |
+
| ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
|
| 323 |
+
| Request is **create new task / account / contact / any create except lead** | Return exactly: `"Development is in progress by the technical team. Launching soon."` |
|
| 324 |
+
| Request is **create lead / new lead / add lead / register lead / any lead creation** | **Do NOT return message** → strictly call `create_lead` tool |
|
| 325 |
+
|
| 326 |
+
"""
|
| 327 |
+
|
all_combine_code/all_services_/__pycache__/delveant_core_services.cpython-313.pyc
ADDED
|
Binary file (42.7 kB). View file
|
|
|
all_combine_code/all_services_/delveant_core_services.py
ADDED
|
@@ -0,0 +1,1011 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import asyncio,time
|
| 2 |
+
import json
|
| 3 |
+
import warnings
|
| 4 |
+
|
| 5 |
+
warnings.filterwarnings("ignore")
|
| 6 |
+
import subprocess, sys, os, re
|
| 7 |
+
import datetime,threading
|
| 8 |
+
import logging,requests,httpx
|
| 9 |
+
|
| 10 |
+
from
|
| 11 |
+
from models_.delveant_llm_model_.multi_agent_llm import (MultiAgentLLM,
|
| 12 |
+
CodeErrorFixer)
|
| 13 |
+
from tools_.delveant_all_tools_.extracted_json_output import (extract_json_outputs)
|
| 14 |
+
from tools_.delveant_all_tools_.s3_history_id_details import get_sorted_memory_ids
|
| 15 |
+
|
| 16 |
+
from tools_.delveant_all_tools_.encrypt_and_decrypt import encrypt_payload,decrypt_payload
|
| 17 |
+
|
| 18 |
+
from agents_.plot_finder import plot_finder_agent
|
| 19 |
+
from agents_.coding_error_fix_agent import code_error_resolver
|
| 20 |
+
from models_.delveant_llm_model_.tool_calling_model import tool_finder_llm
|
| 21 |
+
from rag_method.chat_with_pdf import question_and_answering_rag
|
| 22 |
+
|
| 23 |
+
from dotenv import load_dotenv
|
| 24 |
+
load_dotenv()
|
| 25 |
+
|
| 26 |
+
from pymongo import MongoClient
|
| 27 |
+
import mimetypes
|
| 28 |
+
from urllib.parse import urlparse
|
| 29 |
+
import dateparser
|
| 30 |
+
import pytz
|
| 31 |
+
from datetime import datetime,timezone
|
| 32 |
+
|
| 33 |
+
from textwrap import dedent
|
| 34 |
+
|
| 35 |
+
MONGO_URI = os.getenv("MONGO_URI")
|
| 36 |
+
client = MongoClient(MONGO_URI)
|
| 37 |
+
|
| 38 |
+
def convert_mongo_python_repr(text):
|
| 39 |
+
text = re.sub(
|
| 40 |
+
r"\{\s*['\"]?\$oid['\"]?\s*:\s*['\"]([0-9a-fA-F]+)['\"]\s*\}",
|
| 41 |
+
r'"\1"',
|
| 42 |
+
text
|
| 43 |
+
)
|
| 44 |
+
text = re.sub(
|
| 45 |
+
r"ObjectId\(['\"]([0-9a-fA-F]+)['\"]\)",
|
| 46 |
+
r'"\1"',
|
| 47 |
+
text
|
| 48 |
+
)
|
| 49 |
+
def datetime_replacer(match):
|
| 50 |
+
parts = match.group(1).split(",")
|
| 51 |
+
parts = [int(p.strip()) for p in parts[:3]]
|
| 52 |
+
dt = datetime.date(*parts)
|
| 53 |
+
return '"' + dt.strftime("%Y-%m-%d") + '"'
|
| 54 |
+
text = re.sub(
|
| 55 |
+
r"datetime\.datetime\(([^)]+)\)",
|
| 56 |
+
datetime_replacer,
|
| 57 |
+
text
|
| 58 |
+
)
|
| 59 |
+
text = re.sub(
|
| 60 |
+
r"(['\"]?)([A-Za-z0-9_]+)\1\s*:",
|
| 61 |
+
r'"\2": ',
|
| 62 |
+
text
|
| 63 |
+
)
|
| 64 |
+
def value_replacer(match):
|
| 65 |
+
value = match.group(1).strip()
|
| 66 |
+
if re.match(r"^-?\d+(\.\d+)?$", value):
|
| 67 |
+
return value
|
| 68 |
+
if value in ["true", "false", "null"]:
|
| 69 |
+
return value
|
| 70 |
+
return '"' + value.strip("'\"") + '"'
|
| 71 |
+
text = re.sub(
|
| 72 |
+
r":\s*([^,{}\s][^,{}]*)",
|
| 73 |
+
lambda m: ": " + value_replacer(m),
|
| 74 |
+
text
|
| 75 |
+
)
|
| 76 |
+
return text
|
| 77 |
+
|
| 78 |
+
async def plot_finder(prompt):
|
| 79 |
+
llm = MultiAgentLLM()
|
| 80 |
+
output = await llm.plot_finder(prompt)
|
| 81 |
+
return output
|
| 82 |
+
|
| 83 |
+
async def greetings_api(prompt):
|
| 84 |
+
llm = MultiAgentLLM()
|
| 85 |
+
output = await llm.generate_ai_response_greetings(prompt)
|
| 86 |
+
return output
|
| 87 |
+
|
| 88 |
+
async def pdf_file_api(query,id):
|
| 89 |
+
output = await question_and_answering_rag(query,id)
|
| 90 |
+
return output
|
| 91 |
+
|
| 92 |
+
async def empty_responser(prompt):
|
| 93 |
+
llm = MultiAgentLLM()
|
| 94 |
+
output = await llm.empty_list_responser(prompt)
|
| 95 |
+
return output
|
| 96 |
+
|
| 97 |
+
async def error_code_handler(prompt):
|
| 98 |
+
llm = CodeErrorFixer()
|
| 99 |
+
output = await llm.code_debuger(prompt)
|
| 100 |
+
return output
|
| 101 |
+
|
| 102 |
+
async def general_chatbot_api(prompt):
|
| 103 |
+
llm = MultiAgentLLM()
|
| 104 |
+
output = await llm.general_questions_answer(prompt)
|
| 105 |
+
return output
|
| 106 |
+
|
| 107 |
+
def extract_timezone_from_message(message):
|
| 108 |
+
"""Extract timezone from user message"""
|
| 109 |
+
tz_patterns = [
|
| 110 |
+
r'time zone is ([A-Za-z/]+)',
|
| 111 |
+
r'timezone is ([A-Za-z/]+)',
|
| 112 |
+
r'in ([A-Za-z]+/[A-Za-z]+)',
|
| 113 |
+
r'([A-Za-z]+/[A-Za-z]+) time',
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
for pattern in tz_patterns:
|
| 117 |
+
match = re.search(pattern, message, re.IGNORECASE)
|
| 118 |
+
if match:
|
| 119 |
+
return match.group(1)
|
| 120 |
+
return None
|
| 121 |
+
|
| 122 |
+
def run_python_code(path, timeout=40):
|
| 123 |
+
"""
|
| 124 |
+
Run Python code with timeout of 60 seconds (1 minute)
|
| 125 |
+
Returns dict with either 'answer' or 'error' key
|
| 126 |
+
"""
|
| 127 |
+
try:
|
| 128 |
+
result = subprocess.run(
|
| 129 |
+
[sys.executable, path],
|
| 130 |
+
capture_output=True,
|
| 131 |
+
text=True,
|
| 132 |
+
timeout=80,
|
| 133 |
+
encoding='utf-8'
|
| 134 |
+
)
|
| 135 |
+
if result.returncode != 0:
|
| 136 |
+
error_message = result.stderr.strip() if result.stderr else "Unknown error"
|
| 137 |
+
return {"error": error_message}
|
| 138 |
+
final_responser = result.stdout.strip()
|
| 139 |
+
return {"answer": final_responser}
|
| 140 |
+
except subprocess.TimeoutExpired:
|
| 141 |
+
return {"error": "Script execution timeout (exceeded 1 minute)"}
|
| 142 |
+
except subprocess.CalledProcessError as e:
|
| 143 |
+
error_message = e.stderr.strip() if e.stderr else str(e)
|
| 144 |
+
return {"error": error_message}
|
| 145 |
+
except Exception as e:
|
| 146 |
+
return {"error": str(e)}
|
| 147 |
+
|
| 148 |
+
async def process_successful_result(final_responser, corrected_query):
|
| 149 |
+
import regex as rex
|
| 150 |
+
matches = rex.findall(r'\{(?:[^{}]|(?R))*\}', str(final_responser), rex.DOTALL)
|
| 151 |
+
first_two_records = matches[:2]
|
| 152 |
+
plot_finder_prompt = plot_finder_agent(corrected_query, str(first_two_records))
|
| 153 |
+
decision_find = await plot_finder(plot_finder_prompt)
|
| 154 |
+
clean_final = extract_json_outputs(decision_find)
|
| 155 |
+
matches = re.findall(r'"(.*?)":"(.*?)"', clean_final)
|
| 156 |
+
matches_dict = dict(matches)
|
| 157 |
+
output_format = matches_dict["format_data"]
|
| 158 |
+
plot_output = matches_dict["plot"]
|
| 159 |
+
|
| 160 |
+
if output_format.strip() == "empty":
|
| 161 |
+
final_response_1 = await empty_responser(corrected_query)
|
| 162 |
+
return {"status": "success", "view": "empty", "output": final_response_1,
|
| 163 |
+
"plot": "not_required","corrected_query": corrected_query}
|
| 164 |
+
|
| 165 |
+
elif output_format.strip() == "card":
|
| 166 |
+
return {"status": "success", "view": "card", "output": final_responser,
|
| 167 |
+
"plot": "not_required","corrected_query": corrected_query}
|
| 168 |
+
|
| 169 |
+
elif output_format.strip() =="data":
|
| 170 |
+
return {"status": "success", "view": "data", "plot": plot_output,
|
| 171 |
+
"output": final_responser,"corrected_query": corrected_query}
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
def parse_datetime_to_iso(dt_str, tz="Asia/Kolkata"):
|
| 175 |
+
if not dt_str:
|
| 176 |
+
return None
|
| 177 |
+
|
| 178 |
+
settings = {
|
| 179 |
+
"RETURN_AS_TIMEZONE_AWARE": True,
|
| 180 |
+
"TIMEZONE": tz,
|
| 181 |
+
"DATE_ORDER": "YMD",
|
| 182 |
+
}
|
| 183 |
+
|
| 184 |
+
dt = dateparser.parse(dt_str, settings=settings)
|
| 185 |
+
|
| 186 |
+
if not dt:
|
| 187 |
+
return None
|
| 188 |
+
|
| 189 |
+
if dt.tzinfo is None:
|
| 190 |
+
tz_obj = pytz.timezone(tz)
|
| 191 |
+
dt = tz_obj.localize(dt)
|
| 192 |
+
dt_utc = dt.astimezone(pytz.UTC)
|
| 193 |
+
return dt_utc.strftime("%Y-%m-%dT%H:%M:%S.000Z")
|
| 194 |
+
|
| 195 |
+
def normalize_attendees(attendees):
|
| 196 |
+
if isinstance(attendees, list) and len(attendees) == 1 and "," in attendees[0]:
|
| 197 |
+
return [email.strip() for email in attendees[0].split(",")]
|
| 198 |
+
return attendees
|
| 199 |
+
|
| 200 |
+
async def get_crm_info(input_query, previous_chat_history_query, db_name, user_id, person_id):
|
| 201 |
+
script_path = None
|
| 202 |
+
corrected_query = None
|
| 203 |
+
try:
|
| 204 |
+
obj = DelveAnt()
|
| 205 |
+
final_output, corrected_query, first_python_code, path = await obj.run_file(
|
| 206 |
+
input_query, previous_chat_history_query, db_name, user_id, person_id
|
| 207 |
+
)
|
| 208 |
+
if final_output == "successfully_file_created":
|
| 209 |
+
try:
|
| 210 |
+
script_path = path
|
| 211 |
+
result = run_python_code(script_path, timeout=40)
|
| 212 |
+
if "answer" in result:
|
| 213 |
+
final_responser = result["answer"]
|
| 214 |
+
return await process_successful_result(final_responser, corrected_query)
|
| 215 |
+
elif "error" in result:
|
| 216 |
+
max_retries = 3
|
| 217 |
+
for attempt in range(max_retries):
|
| 218 |
+
print(f"Error fixing attempt {attempt + 1}/{max_retries}")
|
| 219 |
+
with open(script_path, "r", encoding="utf-8") as f:
|
| 220 |
+
file_contents = f.read()
|
| 221 |
+
error_message = result["error"]
|
| 222 |
+
prompt = code_error_resolver(file_contents, error_message, input_query)
|
| 223 |
+
print(prompt)
|
| 224 |
+
chat_response = await error_code_handler(prompt)
|
| 225 |
+
filter_python_code = re.sub(r"```(?:python)?|```", "", chat_response).strip()
|
| 226 |
+
with open(script_path, "w", encoding="utf-8") as f:
|
| 227 |
+
f.write(filter_python_code)
|
| 228 |
+
result = run_python_code(script_path, timeout=40)
|
| 229 |
+
if "answer" in result:
|
| 230 |
+
final_responser = result["answer"]
|
| 231 |
+
return await process_successful_result(final_responser, corrected_query)
|
| 232 |
+
return {
|
| 233 |
+
"status": "error",
|
| 234 |
+
"view": "text",
|
| 235 |
+
"output": f"Failed to execute after {max_retries} attempts. Last error: {result.get('error', 'Unknown error')}",
|
| 236 |
+
"plot": "not_required",
|
| 237 |
+
"corrected_query": corrected_query,
|
| 238 |
+
}
|
| 239 |
+
except Exception as e:
|
| 240 |
+
return {
|
| 241 |
+
"status": "error",
|
| 242 |
+
"view": "text",
|
| 243 |
+
"output": f"Server error: {str(e)}",
|
| 244 |
+
"plot": "not_required",
|
| 245 |
+
"corrected_query": corrected_query if 'corrected_query' in locals() else "query not found error",
|
| 246 |
+
}
|
| 247 |
+
|
| 248 |
+
finally:
|
| 249 |
+
if script_path and os.path.exists(script_path):
|
| 250 |
+
try:
|
| 251 |
+
os.remove(path)
|
| 252 |
+
print(f"Cleaned up: {script_path}")
|
| 253 |
+
except Exception as e:
|
| 254 |
+
print(f"Error deleting {script_path}:", e)
|
| 255 |
+
|
| 256 |
+
else:
|
| 257 |
+
return {"status": "success", "view": "text", "output": final_output, "plot": "not_required","corrected_query": corrected_query}
|
| 258 |
+
|
| 259 |
+
except Exception as e:
|
| 260 |
+
logging.exception("Error processing request")
|
| 261 |
+
return {
|
| 262 |
+
"status": "error",
|
| 263 |
+
"view": "text",
|
| 264 |
+
"output": f"Server error: {str(e)}",
|
| 265 |
+
"plot": "not_required",
|
| 266 |
+
"corrected_query": corrected_query if 'corrected_query' in locals() else "query not found error",
|
| 267 |
+
}
|
| 268 |
+
|
| 269 |
+
async def create_lead(email, company_name, requirement, subdomain, user_email,headers):
|
| 270 |
+
flow_url = os.getenv("FLOW_URL_API")
|
| 271 |
+
|
| 272 |
+
payload = {
|
| 273 |
+
"widgetid": "6880c0ecb384e4a0fe7c901f",
|
| 274 |
+
"subdomain": subdomain,
|
| 275 |
+
"createtype": "manual",
|
| 276 |
+
"assignee": user_email,
|
| 277 |
+
"email": email,
|
| 278 |
+
"company": company_name,
|
| 279 |
+
"requirement": requirement
|
| 280 |
+
}
|
| 281 |
+
|
| 282 |
+
encrypted_string = await encrypt_payload(payload)
|
| 283 |
+
|
| 284 |
+
final_payload = json.dumps({
|
| 285 |
+
"payload": encrypted_string
|
| 286 |
+
})
|
| 287 |
+
|
| 288 |
+
|
| 289 |
+
timeout = httpx.Timeout(120.0)
|
| 290 |
+
|
| 291 |
+
for attempt in range(3):
|
| 292 |
+
try:
|
| 293 |
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 294 |
+
response = await client.post(
|
| 295 |
+
flow_url,
|
| 296 |
+
content=final_payload, # or data=final_payload
|
| 297 |
+
headers=headers
|
| 298 |
+
)
|
| 299 |
+
response.raise_for_status()
|
| 300 |
+
final_answer,_ = await decrypt_payload(response.text)
|
| 301 |
+
return {"success": final_answer}
|
| 302 |
+
|
| 303 |
+
except httpx.TimeoutException:
|
| 304 |
+
if attempt == 2:
|
| 305 |
+
answer = {
|
| 306 |
+
"error": "Lead creation API timed out after retries"
|
| 307 |
+
}
|
| 308 |
+
return answer
|
| 309 |
+
await asyncio.sleep(2 ** attempt)
|
| 310 |
+
|
| 311 |
+
except httpx.HTTPStatusError as e:
|
| 312 |
+
answer = {
|
| 313 |
+
"error": f"Lead creation API failed: {e.response.text}"
|
| 314 |
+
}
|
| 315 |
+
return answer
|
| 316 |
+
|
| 317 |
+
except Exception as e:
|
| 318 |
+
answer = {
|
| 319 |
+
"error": f"Internal server error: {str(e)}"
|
| 320 |
+
}
|
| 321 |
+
return answer
|
| 322 |
+
|
| 323 |
+
|
| 324 |
+
def build_attachments(attachment_links):
|
| 325 |
+
attachments = []
|
| 326 |
+
|
| 327 |
+
for item in attachment_links:
|
| 328 |
+
if isinstance(item, str):
|
| 329 |
+
# Just a raw URL string
|
| 330 |
+
url = item
|
| 331 |
+
parsed = urlparse(url)
|
| 332 |
+
filename = parsed.path.split("/")[-1]
|
| 333 |
+
mime_type, _ = mimetypes.guess_type(url)
|
| 334 |
+
mime_type = mime_type or "application/octet-stream"
|
| 335 |
+
|
| 336 |
+
elif isinstance(item, dict):
|
| 337 |
+
# Dictionary payload (preferred)
|
| 338 |
+
url = item.get("url")
|
| 339 |
+
filename = item.get("filename")
|
| 340 |
+
mime_type = item.get("mimeType")
|
| 341 |
+
|
| 342 |
+
# Fallbacks if not provided
|
| 343 |
+
if not filename and url:
|
| 344 |
+
parsed = urlparse(url)
|
| 345 |
+
filename = parsed.path.split("/")[-1]
|
| 346 |
+
if not mime_type and url:
|
| 347 |
+
mime_type, _ = mimetypes.guess_type(url)
|
| 348 |
+
mime_type = mime_type or "application/octet-stream"
|
| 349 |
+
|
| 350 |
+
else:
|
| 351 |
+
continue
|
| 352 |
+
|
| 353 |
+
if url:
|
| 354 |
+
attachments.append({
|
| 355 |
+
"filename": filename,
|
| 356 |
+
"url": url,
|
| 357 |
+
"mimetype": mime_type
|
| 358 |
+
})
|
| 359 |
+
|
| 360 |
+
return attachments
|
| 361 |
+
|
| 362 |
+
|
| 363 |
+
async def send_email_api(
|
| 364 |
+
to_email,
|
| 365 |
+
subject,
|
| 366 |
+
body,
|
| 367 |
+
subdomain,
|
| 368 |
+
provider,
|
| 369 |
+
user_id,
|
| 370 |
+
lead_id,
|
| 371 |
+
entityname,
|
| 372 |
+
cc_email,
|
| 373 |
+
headers,
|
| 374 |
+
attachment_links,
|
| 375 |
+
bcc=None,
|
| 376 |
+
):
|
| 377 |
+
def delayed_send_email(frommail, to_email, subject, subdomain):
|
| 378 |
+
time.sleep(10)
|
| 379 |
+
# send_email(from_email=frommail, to_email=to_email, keyword=subject, subdomain=subdomain)
|
| 380 |
+
|
| 381 |
+
try:
|
| 382 |
+
AUNTHENTICATE_URL = os.getenv("AUNTHENTICATE_URL")
|
| 383 |
+
EMAIL_API_MICROSOFT = os.getenv("EMAIL_API_MICROSOFT")
|
| 384 |
+
EMAIL_API_GOOGLE = os.getenv("EMAIL_API_GOOGLE")
|
| 385 |
+
|
| 386 |
+
if not AUNTHENTICATE_URL:
|
| 387 |
+
return {"error": "AUNTHENTICATE_URL environment variable not set"}
|
| 388 |
+
if not EMAIL_API_MICROSOFT or not EMAIL_API_GOOGLE:
|
| 389 |
+
return {"error": "Email API environment variables not set"}
|
| 390 |
+
|
| 391 |
+
timeout = httpx.Timeout(120.0)
|
| 392 |
+
|
| 393 |
+
sample_data = {
|
| 394 |
+
"subDomain": subdomain,
|
| 395 |
+
"systemUserId": user_id,
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
encrypted = await encrypt_payload(sample_data, compress=False)
|
| 399 |
+
|
| 400 |
+
final_payload = json.dumps({
|
| 401 |
+
"payload": encrypted
|
| 402 |
+
})
|
| 403 |
+
|
| 404 |
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 405 |
+
auth_response = await client.post(
|
| 406 |
+
AUNTHENTICATE_URL,
|
| 407 |
+
content=final_payload,
|
| 408 |
+
headers=headers
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
auth_response.raise_for_status()
|
| 412 |
+
encrypt_response = auth_response.text
|
| 413 |
+
decrypted_response, check_true_false = await decrypt_payload(encrypt_response)
|
| 414 |
+
|
| 415 |
+
if check_true_false == True:
|
| 416 |
+
resp_json = decrypted_response
|
| 417 |
+
|
| 418 |
+
if isinstance(resp_json, dict) and isinstance(resp_json.get("data"), list):
|
| 419 |
+
auth_list = resp_json["data"]
|
| 420 |
+
elif isinstance(resp_json, list):
|
| 421 |
+
auth_list = resp_json
|
| 422 |
+
else:
|
| 423 |
+
answer = {"error": "Invalid authentication response format", "auth_response": resp_json}
|
| 424 |
+
return answer
|
| 425 |
+
|
| 426 |
+
if not auth_list:
|
| 427 |
+
answer = {"error": "Authentication list is empty", "auth_response": resp_json}
|
| 428 |
+
return answer
|
| 429 |
+
|
| 430 |
+
first_auth = auth_list[0]
|
| 431 |
+
|
| 432 |
+
frommail = first_auth.get("email") or first_auth.get("userId")
|
| 433 |
+
provider = first_auth.get("provider")
|
| 434 |
+
|
| 435 |
+
if not frommail:
|
| 436 |
+
return {"error": "No authenticated email found", "auth_response": resp_json}
|
| 437 |
+
|
| 438 |
+
if not provider:
|
| 439 |
+
return {"error": "Provider missing in authentication response", "auth_response": resp_json}
|
| 440 |
+
|
| 441 |
+
provider = provider.lower()
|
| 442 |
+
|
| 443 |
+
if provider not in ("microsoft", "google"):
|
| 444 |
+
return {"error": f"Unsupported provider '{provider}'"}
|
| 445 |
+
|
| 446 |
+
payload = {
|
| 447 |
+
"userId": frommail,
|
| 448 |
+
"provider": provider,
|
| 449 |
+
"subDomain": subdomain,
|
| 450 |
+
"to": to_email,
|
| 451 |
+
"cc": cc_email,
|
| 452 |
+
"bcc": bcc,
|
| 453 |
+
"subject": subject,
|
| 454 |
+
"systemUserId": user_id,
|
| 455 |
+
"body": body,
|
| 456 |
+
"leadId": lead_id,
|
| 457 |
+
"attachments": attachment_links,
|
| 458 |
+
"entityName": entityname,
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
if cc_email:
|
| 462 |
+
payload["cc"] = cc_email
|
| 463 |
+
if attachment_links:
|
| 464 |
+
payload["attachments"] = attachment_links
|
| 465 |
+
|
| 466 |
+
url = EMAIL_API_MICROSOFT if provider.lower() == "microsoft" else EMAIL_API_GOOGLE
|
| 467 |
+
|
| 468 |
+
encrypted1 = await encrypt_payload(payload, compress=False)
|
| 469 |
+
|
| 470 |
+
final_payload1 = json.dumps({
|
| 471 |
+
"payload": encrypted1
|
| 472 |
+
})
|
| 473 |
+
|
| 474 |
+
async with httpx.AsyncClient(timeout=timeout) as client:
|
| 475 |
+
response = await client.post(url, content=final_payload1, headers=headers)
|
| 476 |
+
|
| 477 |
+
response.raise_for_status()
|
| 478 |
+
|
| 479 |
+
threading.Thread(
|
| 480 |
+
target=delayed_send_email,
|
| 481 |
+
args=(frommail, to_email, subject, subdomain)
|
| 482 |
+
).start()
|
| 483 |
+
|
| 484 |
+
response_json,check_true_false = await decrypt_payload(response.text)
|
| 485 |
+
if check_true_false == True:
|
| 486 |
+
return response_json
|
| 487 |
+
else:
|
| 488 |
+
return {"error": "response return JSONDecodeEror"}
|
| 489 |
+
|
| 490 |
+
except httpx.RequestError as e:
|
| 491 |
+
return {"error": f"Email sending failed: {str(e)}"}
|
| 492 |
+
|
| 493 |
+
except Exception as e:
|
| 494 |
+
return {"error": f"Unexpected error in send_email_api: {str(e)}"}
|
| 495 |
+
|
| 496 |
+
|
| 497 |
+
async def schedule_meeting_api(title, description, recipient, start_time,
|
| 498 |
+
end_time, provider, subdomain, user_id, headers ,time_zone="Asia/Kolkata",
|
| 499 |
+
person_id=""):
|
| 500 |
+
try:
|
| 501 |
+
AUNTHENTICATE_URL = os.getenv("AUNTHENTICATE_URL")
|
| 502 |
+
SCHEDULE_MICROSOFT = os.getenv("SCHEDULE_MICROSOFT")
|
| 503 |
+
SCHEDULE_GOOGLE = os.getenv("SCHEDULE_GOOGLE")
|
| 504 |
+
|
| 505 |
+
if not AUNTHENTICATE_URL:
|
| 506 |
+
return {"error": "AUNTHENTICATE_URL environment variable not set"}
|
| 507 |
+
if not SCHEDULE_MICROSOFT or not SCHEDULE_GOOGLE:
|
| 508 |
+
return {"error": "SCHEDULE_MICROSOFT / SCHEDULE_GOOGLE environment variables not set"}
|
| 509 |
+
start_iso = parse_datetime_to_iso(start_time, tz=time_zone)
|
| 510 |
+
end_iso = parse_datetime_to_iso(end_time, tz=time_zone)
|
| 511 |
+
print(f"start_iso={start_iso}, end_iso={end_iso}")
|
| 512 |
+
|
| 513 |
+
if not start_iso or not end_iso:
|
| 514 |
+
return {"error": "Invalid start or end time format."}
|
| 515 |
+
|
| 516 |
+
auth_payload = {"subDomain": subdomain, "systemUserId": user_id}
|
| 517 |
+
|
| 518 |
+
encrypted = await encrypt_payload(auth_payload, compress=False)
|
| 519 |
+
|
| 520 |
+
|
| 521 |
+
final_payload = {
|
| 522 |
+
"payload": encrypted
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
try:
|
| 526 |
+
getfromemail = requests.post(
|
| 527 |
+
AUNTHENTICATE_URL,
|
| 528 |
+
json=final_payload,
|
| 529 |
+
headers=headers,
|
| 530 |
+
timeout=60
|
| 531 |
+
)
|
| 532 |
+
|
| 533 |
+
except Exception as e:
|
| 534 |
+
answer = {"error": f"Network error calling AUNTHENTICATE_URL: {e}"}
|
| 535 |
+
return answer
|
| 536 |
+
|
| 537 |
+
decrypt_response, check_status = await decrypt_payload(getfromemail.text)
|
| 538 |
+
|
| 539 |
+
if check_status == True:
|
| 540 |
+
resp_json = decrypt_response
|
| 541 |
+
else:
|
| 542 |
+
resp_json = None
|
| 543 |
+
|
| 544 |
+
print({"auth_response_json": resp_json})
|
| 545 |
+
|
| 546 |
+
if not getfromemail.ok:
|
| 547 |
+
return {
|
| 548 |
+
"error": "Authenticated email API returned an error",
|
| 549 |
+
"status_code": getfromemail.status_code,
|
| 550 |
+
"response_text": getfromemail.text,
|
| 551 |
+
"parsed_json": resp_json,
|
| 552 |
+
"sent_payload": auth_payload,
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
if isinstance(resp_json, dict) and isinstance(resp_json.get("data"), list):
|
| 556 |
+
auth_list = resp_json["data"]
|
| 557 |
+
elif isinstance(resp_json, list):
|
| 558 |
+
auth_list = resp_json
|
| 559 |
+
else:
|
| 560 |
+
return {"error": "Invalid authentication response format", "auth_response": resp_json}
|
| 561 |
+
|
| 562 |
+
if not auth_list:
|
| 563 |
+
return {"error": "Authentication list is empty", "auth_response": resp_json}
|
| 564 |
+
|
| 565 |
+
first_auth = auth_list[0]
|
| 566 |
+
frommail = first_auth.get("email") or first_auth.get("userId")
|
| 567 |
+
provider = first_auth.get("provider")
|
| 568 |
+
|
| 569 |
+
if not frommail:
|
| 570 |
+
return {"error": "No authenticated email found", "auth_response": resp_json}
|
| 571 |
+
|
| 572 |
+
if not provider:
|
| 573 |
+
return {"error": "Provider missing in authentication response", "auth_response": resp_json}
|
| 574 |
+
|
| 575 |
+
provider = provider.lower()
|
| 576 |
+
if provider not in ("microsoft", "google"):
|
| 577 |
+
return {"error": f"Unsupported provider '{provider}'"}
|
| 578 |
+
|
| 579 |
+
attendees = normalize_attendees(
|
| 580 |
+
[recipient] if isinstance(recipient, str) else recipient
|
| 581 |
+
)
|
| 582 |
+
|
| 583 |
+
payload = {
|
| 584 |
+
"userId": frommail,
|
| 585 |
+
"provider": provider,
|
| 586 |
+
"subDomain": subdomain,
|
| 587 |
+
"summary": title,
|
| 588 |
+
"description": description or "",
|
| 589 |
+
"startTime": start_iso,
|
| 590 |
+
"endTime": end_iso,
|
| 591 |
+
"systemUserId": user_id,
|
| 592 |
+
"personId": person_id or recipient,
|
| 593 |
+
"attendees": attendees,
|
| 594 |
+
"timeZone": time_zone,
|
| 595 |
+
}
|
| 596 |
+
encrypted1 = await encrypt_payload(payload, compress=False)
|
| 597 |
+
|
| 598 |
+
final_payload1 = {
|
| 599 |
+
"payload": encrypted1
|
| 600 |
+
}
|
| 601 |
+
|
| 602 |
+
url = SCHEDULE_MICROSOFT if provider == "microsoft" else SCHEDULE_GOOGLE
|
| 603 |
+
response = requests.post(url, json=final_payload1, timeout=120,headers=headers)
|
| 604 |
+
response.raise_for_status()
|
| 605 |
+
try:
|
| 606 |
+
response_, check_status = await decrypt_payload(response.text)
|
| 607 |
+
if check_status == True:
|
| 608 |
+
schedule_json = response_
|
| 609 |
+
else:
|
| 610 |
+
schedule_json = None
|
| 611 |
+
|
| 612 |
+
except ValueError:
|
| 613 |
+
schedule_json = None
|
| 614 |
+
|
| 615 |
+
if not response.ok:
|
| 616 |
+
return {
|
| 617 |
+
"error": "Meeting scheduling failed at schedule API",
|
| 618 |
+
"status_code": response.status_code,
|
| 619 |
+
"response_text": response.text,
|
| 620 |
+
"parsed_json": schedule_json,
|
| 621 |
+
"sent_payload": payload,
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
return schedule_json or {"message": "Meeting scheduled, but response was not JSON."}
|
| 625 |
+
|
| 626 |
+
except Exception as e:
|
| 627 |
+
answer = {"error": f"Network error: {e}"}
|
| 628 |
+
return answer
|
| 629 |
+
|
| 630 |
+
|
| 631 |
+
class ToolClasifier:
|
| 632 |
+
def __init__(self):
|
| 633 |
+
pass
|
| 634 |
+
|
| 635 |
+
def ensure_dict(self,data):
|
| 636 |
+
if isinstance(data, dict):
|
| 637 |
+
return data
|
| 638 |
+
if isinstance(data, str):
|
| 639 |
+
return json.loads(data)
|
| 640 |
+
raise TypeError(f"Unsupported tool_args type: {type(data)}")
|
| 641 |
+
|
| 642 |
+
async def handle_crm_chat(self,input_query,
|
| 643 |
+
previous_chat_history_query,
|
| 644 |
+
db_name, user_id,person_id,
|
| 645 |
+
lead_id,leademail,leadname,
|
| 646 |
+
user_email, session_id,
|
| 647 |
+
loggedUserRole,
|
| 648 |
+
loggedUserMail,provider,
|
| 649 |
+
your_id,entityname,
|
| 650 |
+
attachments,rag_uuid,headers):
|
| 651 |
+
|
| 652 |
+
tool_name, tool_args = await tool_finder_llm(previous_chat_history_query)
|
| 653 |
+
logging.info(f"tool_name: {tool_name}")
|
| 654 |
+
logging.info(f"tool_args: {tool_args}")
|
| 655 |
+
steps = []
|
| 656 |
+
step_counter = 1
|
| 657 |
+
output_result = tool_args
|
| 658 |
+
|
| 659 |
+
def add_step(desc: str):
|
| 660 |
+
nonlocal step_counter
|
| 661 |
+
steps.append({f"step{step_counter}": desc})
|
| 662 |
+
step_counter += 1
|
| 663 |
+
|
| 664 |
+
add_step(f"User asked: '{input_query}'")
|
| 665 |
+
add_step(f"Before Chat based query: {previous_chat_history_query}")
|
| 666 |
+
|
| 667 |
+
all_names = ["send_email_api", "get_crm_info", "create_lead","schedule_meeting_api","update_query",
|
| 668 |
+
"greetings_api","pdf_file_api","general_chatbot_api","no_tool_name","no_tool_args"
|
| 669 |
+
]
|
| 670 |
+
if tool_name in all_names:
|
| 671 |
+
add_step(f"AI selected tool: {tool_name}")
|
| 672 |
+
add_step(f"Tool called with arguments: {tool_args}")
|
| 673 |
+
logging.info(f"Tool called : {tool_name}")
|
| 674 |
+
|
| 675 |
+
if tool_name.strip().strip() == "get_crm_info":
|
| 676 |
+
answer = await get_crm_info(
|
| 677 |
+
input_query=input_query,
|
| 678 |
+
previous_chat_history_query=previous_chat_history_query,
|
| 679 |
+
db_name=db_name,
|
| 680 |
+
user_id=user_id,
|
| 681 |
+
person_id=person_id
|
| 682 |
+
)
|
| 683 |
+
data = answer
|
| 684 |
+
if data.get("status") == "success":
|
| 685 |
+
view = data.get("view")
|
| 686 |
+
plot = data.get("plot")
|
| 687 |
+
status = data.get("status")
|
| 688 |
+
corrected_query = data.get("corrected_query")
|
| 689 |
+
json_str = json.dumps(data, ensure_ascii=False)
|
| 690 |
+
json_bytes = json_str.encode("utf-8")
|
| 691 |
+
decoded_str = json_bytes.decode("utf-8")
|
| 692 |
+
data = json.loads(decoded_str)
|
| 693 |
+
tool_output = {
|
| 694 |
+
"result": data["output"],
|
| 695 |
+
"pipeline": None,
|
| 696 |
+
"plot": plot,
|
| 697 |
+
"view": view,
|
| 698 |
+
"status": status
|
| 699 |
+
}
|
| 700 |
+
reply_text = json.dumps(tool_output, ensure_ascii=False)
|
| 701 |
+
|
| 702 |
+
elif data.get("status") == "error":
|
| 703 |
+
tool_output = answer
|
| 704 |
+
reply_text = json.dumps(tool_output, ensure_ascii=False)
|
| 705 |
+
add_step(f"Corrected query: {corrected_query}")
|
| 706 |
+
add_step(f"Received CRM response: {tool_output}")
|
| 707 |
+
|
| 708 |
+
|
| 709 |
+
elif tool_name.strip().lower() == "create_lead":
|
| 710 |
+
data_dict = json.loads(tool_args)
|
| 711 |
+
email = data_dict["email"] or "".strip()
|
| 712 |
+
company_name = data_dict["company_name"] or "".strip()
|
| 713 |
+
requirement = data_dict["requirement"] or "".strip()
|
| 714 |
+
missing = None
|
| 715 |
+
if not email:
|
| 716 |
+
missing = "email"
|
| 717 |
+
elif not company_name:
|
| 718 |
+
missing = "company_name"
|
| 719 |
+
elif not requirement:
|
| 720 |
+
missing = "requirement"
|
| 721 |
+
|
| 722 |
+
if missing:
|
| 723 |
+
field_labels = {
|
| 724 |
+
"email": "lead's email address",
|
| 725 |
+
"company_name": "lead's company name",
|
| 726 |
+
"requirement": "the lead's requirement/request (e.g., 'Sportizia demo')"
|
| 727 |
+
}
|
| 728 |
+
prompt = f"Please provide {field_labels[missing]}."
|
| 729 |
+
add_step(f"Missing '{missing}'. Asking user for it.")
|
| 730 |
+
reply_text = prompt
|
| 731 |
+
tool_output = None
|
| 732 |
+
|
| 733 |
+
else:
|
| 734 |
+
tool_output = await create_lead(
|
| 735 |
+
email=email,
|
| 736 |
+
company_name=company_name,
|
| 737 |
+
requirement=requirement,
|
| 738 |
+
subdomain=db_name,
|
| 739 |
+
user_email=user_email,
|
| 740 |
+
headers=headers
|
| 741 |
+
)
|
| 742 |
+
add_step(
|
| 743 |
+
f"Lead creation API called with payload {{'email': '{email}', 'company_name': '{company_name}', 'requirement': '{requirement}'}}")
|
| 744 |
+
add_step(f"Received Lead creation response: {tool_output}")
|
| 745 |
+
if tool_output.get("success"):
|
| 746 |
+
reply_text = tool_output["success"]
|
| 747 |
+
tool_output = tool_output["success"]
|
| 748 |
+
else:
|
| 749 |
+
reply_text = tool_output["error"]
|
| 750 |
+
tool_output = tool_output["error"]
|
| 751 |
+
|
| 752 |
+
elif tool_name.strip().lower() == "send_email_api":
|
| 753 |
+
response_json =json.loads(tool_args)
|
| 754 |
+
to_email = response_json.get("to_email", "").strip()
|
| 755 |
+
subject = response_json.get("subject", "").strip()
|
| 756 |
+
body = response_json.get("body", "").strip()
|
| 757 |
+
cc_email = response_json.get("cc_email", "").strip()
|
| 758 |
+
db = client[db_name]
|
| 759 |
+
audit_collection = db["admin.audit.CRM"]
|
| 760 |
+
missing = None
|
| 761 |
+
if not to_email:
|
| 762 |
+
missing = "to_email"
|
| 763 |
+
elif not subject:
|
| 764 |
+
missing = "subject"
|
| 765 |
+
elif not body:
|
| 766 |
+
missing = "body"
|
| 767 |
+
|
| 768 |
+
if missing:
|
| 769 |
+
field_labels = {
|
| 770 |
+
"to_email": "recipient email address",
|
| 771 |
+
"subject": "email subject",
|
| 772 |
+
"body": "email message/content"
|
| 773 |
+
}
|
| 774 |
+
prompt = f"Please provide {field_labels[missing]}."
|
| 775 |
+
add_step(f"Missing '{missing}'. Asking user for it.")
|
| 776 |
+
reply_text = prompt
|
| 777 |
+
tool_output = None
|
| 778 |
+
|
| 779 |
+
else:
|
| 780 |
+
tool_output = await send_email_api(
|
| 781 |
+
to_email=to_email,
|
| 782 |
+
subject=subject,
|
| 783 |
+
body=body,
|
| 784 |
+
subdomain=db_name,
|
| 785 |
+
provider=provider,
|
| 786 |
+
user_id=user_id,
|
| 787 |
+
lead_id=lead_id,
|
| 788 |
+
entityname =entityname,
|
| 789 |
+
cc_email=cc_email,
|
| 790 |
+
headers=headers,
|
| 791 |
+
attachment_links=attachments,
|
| 792 |
+
)
|
| 793 |
+
|
| 794 |
+
add_step(
|
| 795 |
+
f"Email API called with payload {{'to_email': '{to_email}', 'subject': '{subject}', 'body': '{body[:50]}...'}}")
|
| 796 |
+
add_step(f"Received Email response: {tool_output}")
|
| 797 |
+
|
| 798 |
+
if tool_output and not tool_output.get("error"):
|
| 799 |
+
reply_text = dedent(f"""
|
| 800 |
+
<div style="font-family: 'Inter', sans-serif;">
|
| 801 |
+
<h3 style="color:#1e293b; margin:0; font-size:13.5px;">✅ Email Sent Successfully</h3>
|
| 802 |
+
<p style="color:#1e293b; margin:8px 0; font-size:13.5px;">
|
| 803 |
+
<span><b>To:</b> {to_email}</span>
|
| 804 |
+
</p>
|
| 805 |
+
<p style="color:#1e293b; margin:8px 0; font-size:13.5px;">
|
| 806 |
+
<span><b>Subject:</b> {subject}</span>
|
| 807 |
+
</p>
|
| 808 |
+
{f'<p style="color:#1e293b; margin:8px 0; font-size:13.5px;"><b>CC:</b> {cc_email}</p>' if cc_email else ''}
|
| 809 |
+
</div>
|
| 810 |
+
""")
|
| 811 |
+
if lead_id:
|
| 812 |
+
audit_data = {
|
| 813 |
+
"ApplicationName": "CRM",
|
| 814 |
+
"formName": "Leads",
|
| 815 |
+
"createdId": lead_id,
|
| 816 |
+
"Action": "SENDEMAIL",
|
| 817 |
+
"data": {
|
| 818 |
+
"emailStatus": "send",
|
| 819 |
+
"subject": subject,
|
| 820 |
+
"to": to_email,
|
| 821 |
+
"cc": cc_email,
|
| 822 |
+
"attachments": attachments,
|
| 823 |
+
"body": body,
|
| 824 |
+
"leadEmail":to_email,
|
| 825 |
+
"sentAt": datetime.now(timezone.utc)
|
| 826 |
+
},
|
| 827 |
+
"created_by": person_id,
|
| 828 |
+
"created_at": datetime.now(timezone.utc)
|
| 829 |
+
}
|
| 830 |
+
print(audit_data, "audit_data")
|
| 831 |
+
try:
|
| 832 |
+
result = audit_collection.insert_one(audit_data)
|
| 833 |
+
except Exception as e:
|
| 834 |
+
return {"error": str(e)}
|
| 835 |
+
else:
|
| 836 |
+
reply_text = f"""
|
| 837 |
+
<div style="font-family: 'Inter', sans-serif;">
|
| 838 |
+
<h3 style="color:#f44336; margin:0; font-size:13.5px;">❌ Failed to Send Email</h3>
|
| 839 |
+
<p style="color:#1e293b; margin:8px 0; font-size:13.5px;">
|
| 840 |
+
{tool_output.get("error", "Unknown error")}
|
| 841 |
+
</p>
|
| 842 |
+
</div>
|
| 843 |
+
"""
|
| 844 |
+
|
| 845 |
+
elif tool_name.strip().lower() == "schedule_meeting_api":
|
| 846 |
+
response_json = json.loads(tool_args)
|
| 847 |
+
recipient = response_json.get("recipient", "").strip()
|
| 848 |
+
subject = response_json.get("subject", "").strip()
|
| 849 |
+
title = response_json.get("title", "").strip()
|
| 850 |
+
description = response_json.get("description", "").strip()
|
| 851 |
+
start_time = response_json.get("start_time", "").strip()
|
| 852 |
+
end_time = response_json.get("end_time", "").strip()
|
| 853 |
+
time_zone = response_json.get("time_zone", "").strip()
|
| 854 |
+
if not time_zone:
|
| 855 |
+
extracted_tz = extract_timezone_from_message(input_query)
|
| 856 |
+
time_zone = extracted_tz if extracted_tz else "Asia/Kolkata"
|
| 857 |
+
db = client[db_name]
|
| 858 |
+
audit_collection = db["admin.audit.CRM"]
|
| 859 |
+
missing = None
|
| 860 |
+
if not recipient:
|
| 861 |
+
missing = "recipient"
|
| 862 |
+
elif not title:
|
| 863 |
+
missing = "title"
|
| 864 |
+
elif not start_time or not end_time:
|
| 865 |
+
missing = "datetime"
|
| 866 |
+
|
| 867 |
+
if missing:
|
| 868 |
+
prompts = {
|
| 869 |
+
"recipient": "recipient email",
|
| 870 |
+
"title": "meeting title",
|
| 871 |
+
"datetime": "meeting start and end time (e.g., 'Sep 5 2025 10:00pm to 11:00pm')"
|
| 872 |
+
}
|
| 873 |
+
reply_text = f"Please provide {prompts[missing]}."
|
| 874 |
+
tool_output = None
|
| 875 |
+
|
| 876 |
+
else:
|
| 877 |
+
tool_output = await schedule_meeting_api(
|
| 878 |
+
title, description, recipient, start_time, end_time, provider,
|
| 879 |
+
db_name, user_id, headers ,time_zone, person_id,
|
| 880 |
+
)
|
| 881 |
+
add_step(
|
| 882 |
+
f"Meeting API called with payload {{'recipient': '{recipient}', 'title': '{title}'}}")
|
| 883 |
+
|
| 884 |
+
add_step(f"Received Meeting response: {tool_output}")
|
| 885 |
+
|
| 886 |
+
if tool_output and not tool_output.get("error"):
|
| 887 |
+
reply_text = f"""
|
| 888 |
+
<div style="font-family: 'Inter', sans-serif;">
|
| 889 |
+
<h3 style="color:#1e293b; margin:0; font-size:13.5px;">✅ Meeting Scheduled Successfully</h3>
|
| 890 |
+
</div>
|
| 891 |
+
"""
|
| 892 |
+
if lead_id:
|
| 893 |
+
audit_data = {
|
| 894 |
+
"ApplicationName": "CRM",
|
| 895 |
+
"formName": "Leads",
|
| 896 |
+
"createdId": lead_id,
|
| 897 |
+
"Action": "SCHEDULEMEETING",
|
| 898 |
+
"data": {
|
| 899 |
+
"summary": title,
|
| 900 |
+
"description": description,
|
| 901 |
+
"startTime": start_time,
|
| 902 |
+
"endTime": end_time,
|
| 903 |
+
"attendees": recipient,
|
| 904 |
+
"timeZone": time_zone,
|
| 905 |
+
"leadEmail": leademail,
|
| 906 |
+
"leadName": leadname
|
| 907 |
+
},
|
| 908 |
+
"created_by": person_id,
|
| 909 |
+
"created_at": datetime.now(timezone.utc)
|
| 910 |
+
}
|
| 911 |
+
|
| 912 |
+
audit_collection.insert_one(audit_data)
|
| 913 |
+
else:
|
| 914 |
+
reply_text = f"""
|
| 915 |
+
<div style="font-family: 'Inter', sans-serif;">
|
| 916 |
+
<h3 style="color:#f44336; margin:0; font-size:13.5px;">
|
| 917 |
+
Failed to Schedule Meeting
|
| 918 |
+
</h3>
|
| 919 |
+
<p style="color:#6b7280; margin:5px 0; font-size:13.5px;">
|
| 920 |
+
We couldn't schedule your meeting because your email account is not connected.
|
| 921 |
+
Please connect your email in the Settings page to continue.
|
| 922 |
+
</p>
|
| 923 |
+
<!-- Error message hidden
|
| 924 |
+
<p style="color:#6b7280; margin:6px 0; font-size:13px;">
|
| 925 |
+
Error: {tool_output.get("error", "Unknown error")}
|
| 926 |
+
</p>
|
| 927 |
+
-->
|
| 928 |
+
|
| 929 |
+
<a data-action="navigate-settings" data-tab="email"
|
| 930 |
+
style="display:inline-block; margin-top:8px; padding:6px 10px; background:#875255;
|
| 931 |
+
color:white; text-decoration:none; border-radius:6px; font-size:13px; cursor:pointer;">
|
| 932 |
+
Go to Settings
|
| 933 |
+
</a>
|
| 934 |
+
|
| 935 |
+
</div>
|
| 936 |
+
"""
|
| 937 |
+
|
| 938 |
+
elif tool_name.strip().lower() == "update_query":
|
| 939 |
+
response_json = self.ensure_dict(tool_args)
|
| 940 |
+
field_name = response_json.get("field_name", "").strip()
|
| 941 |
+
new_value = response_json.get("new_value", "").strip()
|
| 942 |
+
collection_name = response_json.get("collection", "").strip()
|
| 943 |
+
missing = None
|
| 944 |
+
if not field_name:
|
| 945 |
+
missing = "field_name"
|
| 946 |
+
elif not new_value:
|
| 947 |
+
missing = "new_value"
|
| 948 |
+
|
| 949 |
+
if missing:
|
| 950 |
+
field_labels = {
|
| 951 |
+
"field_name": "the field name (e.g., status, lead_name, company_name)",
|
| 952 |
+
"new_value": "the new value for that field"
|
| 953 |
+
}
|
| 954 |
+
prompt = f"Please provide {field_labels[missing]}."
|
| 955 |
+
add_step(f"Missing '{missing}'. Asking user for it.")
|
| 956 |
+
reply_text = prompt
|
| 957 |
+
tool_output = None
|
| 958 |
+
else:
|
| 959 |
+
tool_output = "Our technical team is actively working on the development. The update will be released soon."
|
| 960 |
+
reply_text = "Our technical team is actively working on the development. The update will be released soon."
|
| 961 |
+
|
| 962 |
+
elif tool_name.strip().lower() == "greetings_api":
|
| 963 |
+
ans = await greetings_api(input_query)
|
| 964 |
+
tool_output = {"status": "success", "view": "text", "plot": "not_required", "output": ans}
|
| 965 |
+
reply_text = ans
|
| 966 |
+
|
| 967 |
+
elif tool_name.strip().lower() == "pdf_file_api":
|
| 968 |
+
output_result_ans = await pdf_file_api(input_query,rag_uuid)
|
| 969 |
+
tool_output = {"status": "success", "view": "text", "plot": "not_required", "output": output_result_ans}
|
| 970 |
+
reply_text = output_result_ans
|
| 971 |
+
|
| 972 |
+
elif tool_name.strip().lower() == "general_chatbot_api":
|
| 973 |
+
answer = await general_chatbot_api(input_query)
|
| 974 |
+
tool_output = {"status": "success", "view": "text", "plot": "not_required", "output": answer}
|
| 975 |
+
reply_text = answer
|
| 976 |
+
|
| 977 |
+
elif tool_name.strip().lower() == "no_tool_name" and tool_args != "no_tool_args":
|
| 978 |
+
answer = tool_args
|
| 979 |
+
tool_output = {"status": "success", "view": "text", "plot": "not_required", "output": answer}
|
| 980 |
+
reply_text = answer
|
| 981 |
+
|
| 982 |
+
elif tool_name.strip().lower() == "no_tool_name" and tool_args == "no_tool_args":
|
| 983 |
+
answer = "Could you please clarify your requirement? You may request CRM-related data (leads, tasks, meetings, accounts) or actions such as sending an email or creating a lead."
|
| 984 |
+
tool_output = {"status": "success", "view": "text", "plot": "not_required", "output": answer}
|
| 985 |
+
reply_text = answer
|
| 986 |
+
|
| 987 |
+
else:
|
| 988 |
+
tool_name = "no_tool_name"
|
| 989 |
+
tool_output = {
|
| 990 |
+
"status": "success",
|
| 991 |
+
"view": "text",
|
| 992 |
+
"plot": "not_required",
|
| 993 |
+
"output": "No tool action was needed. The query has been processed and a textual response is provided."
|
| 994 |
+
}
|
| 995 |
+
reply_text = "No tool action was needed. The query has been processed and a textual response is provided."
|
| 996 |
+
|
| 997 |
+
s3_bucket_ids = get_sorted_memory_ids()
|
| 998 |
+
return {
|
| 999 |
+
"session_id": session_id,
|
| 1000 |
+
"selected_tool": tool_name,
|
| 1001 |
+
"tool_args": tool_args,
|
| 1002 |
+
"result": tool_output,
|
| 1003 |
+
"chat_reply": reply_text,
|
| 1004 |
+
"needs_input": (
|
| 1005 |
+
(tool_name is not None and tool_output is None) or
|
| 1006 |
+
(tool_name is None and tool_output is None)
|
| 1007 |
+
),
|
| 1008 |
+
"steps": steps,
|
| 1009 |
+
"your_id":your_id,
|
| 1010 |
+
"history_all_ids":s3_bucket_ids,
|
| 1011 |
+
}
|
all_combine_code/app.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, sys, uuid
|
| 2 |
+
|
| 3 |
+
os.environ.setdefault("TORCHINDUCTOR_CACHE_DIR", "/app/tmp/torchinductor")
|
| 4 |
+
os.environ.setdefault("TORCHDYNAMO_DISABLE", "1")
|
| 5 |
+
|
| 6 |
+
import warnings, shutil
|
| 7 |
+
|
| 8 |
+
warnings.simplefilter("ignore", FutureWarning)
|
| 9 |
+
from fastapi import FastAPI, Request, HTTPException
|
| 10 |
+
from fastapi.responses import PlainTextResponse
|
| 11 |
+
from fastapi import UploadFile, File
|
| 12 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 13 |
+
import logging
|
| 14 |
+
from memory_.chat_history_handle import generate_response, store_final_chat
|
| 15 |
+
from tools_.delveant_all_tools_.encrypt_and_decrypt import encrypt_payload, decrypt_payload
|
| 16 |
+
|
| 17 |
+
from all_services_.delveant_core_services import ToolClasifier
|
| 18 |
+
|
| 19 |
+
print("successfully imported")
|
| 20 |
+
|
| 21 |
+
|
all_combine_code/azure-pipelines.yml
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
trigger:
|
| 2 |
+
branches:
|
| 3 |
+
include:
|
| 4 |
+
- dev
|
| 5 |
+
|
| 6 |
+
# pool:
|
| 7 |
+
# vmImage: ubuntu-latest
|
| 8 |
+
pool:
|
| 9 |
+
name: Default
|
| 10 |
+
demands:
|
| 11 |
+
- agent.name -equals stage1-newdevtest
|
| 12 |
+
|
| 13 |
+
variables:
|
| 14 |
+
AWS_DEFAULT_REGION: 'ap-south-1'
|
| 15 |
+
AWS_ACCOUNT_ID: '627133981011'
|
| 16 |
+
IMAGE_REPO_NAME: 'delveant_agent'
|
| 17 |
+
|
| 18 |
+
steps:
|
| 19 |
+
# Install AWS CLI (if not already available on your agent)
|
| 20 |
+
- script: |
|
| 21 |
+
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
|
| 22 |
+
unzip awscliv2.zip
|
| 23 |
+
sudo ./aws/install --update
|
| 24 |
+
displayName: 'Install AWS CLI'
|
| 25 |
+
condition: not(succeeded())
|
| 26 |
+
|
| 27 |
+
# Configure AWS credentials and login to ECR
|
| 28 |
+
- script: |
|
| 29 |
+
aws configure set aws_access_key_id $(AWS_ACCESS_KEY_ID)
|
| 30 |
+
aws configure set aws_secret_access_key $(AWS_SECRET_ACCESS_KEY)
|
| 31 |
+
aws configure set default.region $(AWS_DEFAULT_REGION)
|
| 32 |
+
|
| 33 |
+
# Get ECR login token and login to Docker
|
| 34 |
+
aws ecr get-login-password --region $(AWS_DEFAULT_REGION) | docker login --username AWS --password-stdin $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_DEFAULT_REGION).amazonaws.com
|
| 35 |
+
displayName: 'Configure AWS and Login to ECR'
|
| 36 |
+
# Create reports directory
|
| 37 |
+
- script: |
|
| 38 |
+
mkdir -p $(Build.SourcesDirectory)/security-reports
|
| 39 |
+
displayName: 'Create Reports Directory'
|
| 40 |
+
|
| 41 |
+
# Secret detection scan with Gitleaks
|
| 42 |
+
- script: |
|
| 43 |
+
echo "🔍 Scanning for secrets in code..."
|
| 44 |
+
docker run --rm -v $(Build.SourcesDirectory):/code zricethezav/gitleaks:latest detect --source="/code" -f json -r /code/security-reports/gitleaks-report.json
|
| 45 |
+
if [ -s /code/security-reports/gitleaks-report.json ]; then
|
| 46 |
+
echo "❌ CRITICAL: Secrets detected in code! Build failed for security."
|
| 47 |
+
exit 1
|
| 48 |
+
else
|
| 49 |
+
echo "✅ No secrets found in code."
|
| 50 |
+
fi
|
| 51 |
+
displayName: '🔐 Secret Detection Scan'
|
| 52 |
+
continueOnError: false
|
| 53 |
+
|
| 54 |
+
# Dependency vulnerability scan with Trivy
|
| 55 |
+
- script: |
|
| 56 |
+
echo "🔍 Scanning dependencies for vulnerabilities with Trivy..."
|
| 57 |
+
mkdir -p /tmp/trivy-cache
|
| 58 |
+
|
| 59 |
+
docker run --rm \
|
| 60 |
+
-v $(Build.SourcesDirectory):/workspace \
|
| 61 |
+
-v /tmp/trivy-cache:/root/.cache/trivy \
|
| 62 |
+
aquasec/trivy:latest fs \
|
| 63 |
+
--format json \
|
| 64 |
+
--output /workspace/security-reports/dependency-vulnerabilities.json \
|
| 65 |
+
--severity HIGH,CRITICAL \
|
| 66 |
+
/workspace
|
| 67 |
+
|
| 68 |
+
docker run --rm \
|
| 69 |
+
-v $(Build.SourcesDirectory):/workspace \
|
| 70 |
+
-v /tmp/trivy-cache:/root/.cache/trivy \
|
| 71 |
+
aquasec/trivy:latest fs \
|
| 72 |
+
--format template \
|
| 73 |
+
--template "@contrib/html.tpl" \
|
| 74 |
+
--output /workspace/security-reports/dependency-vulnerabilities.html \
|
| 75 |
+
--severity HIGH,CRITICAL \
|
| 76 |
+
/workspace
|
| 77 |
+
|
| 78 |
+
VULN_COUNT=$(docker run --rm \
|
| 79 |
+
-v $(Build.SourcesDirectory):/workspace \
|
| 80 |
+
-v /tmp/trivy-cache:/root/.cache/trivy \
|
| 81 |
+
aquasec/trivy:latest fs \
|
| 82 |
+
--format json \
|
| 83 |
+
--severity HIGH,CRITICAL \
|
| 84 |
+
--quiet \
|
| 85 |
+
/workspace | jq -r '.Results[]?.Vulnerabilities // [] | length' | awk '{sum += $1} END {print sum+0}')
|
| 86 |
+
|
| 87 |
+
if [ "$VULN_COUNT" -gt 0 ]; then
|
| 88 |
+
echo "❌ CRITICAL: $VULN_COUNT high/critical dependency vulnerabilities found! Build failed."
|
| 89 |
+
exit 1
|
| 90 |
+
else
|
| 91 |
+
echo "✅ No critical dependency vulnerabilities found."
|
| 92 |
+
fi
|
| 93 |
+
displayName: '📦 Dependency Vulnerability Scan (Trivy)'
|
| 94 |
+
continueOnError: true
|
| 95 |
+
|
| 96 |
+
# Static Application Security Testing (SAST) - CORRECTED VERSION
|
| 97 |
+
- script: |
|
| 98 |
+
echo "🔍 Running Static Application Security Testing with Semgrep..."
|
| 99 |
+
|
| 100 |
+
# Create output directory
|
| 101 |
+
mkdir -p $(Build.SourcesDirectory)/security-reports
|
| 102 |
+
|
| 103 |
+
# Run Semgrep with explicit entrypoint
|
| 104 |
+
docker run --rm \
|
| 105 |
+
-v $(Build.SourcesDirectory):/src \
|
| 106 |
+
--entrypoint semgrep \
|
| 107 |
+
returntocorp/semgrep \
|
| 108 |
+
--config=auto \
|
| 109 |
+
--json \
|
| 110 |
+
--output=/src/security-reports/sast-report.json \
|
| 111 |
+
/src
|
| 112 |
+
|
| 113 |
+
# Check if report was generated and analyze results
|
| 114 |
+
if [ -f $(Build.SourcesDirectory)/security-reports/sast-report.json ]; then
|
| 115 |
+
CRITICAL_COUNT=$(jq '[.results[] | select(.extra.severity == "ERROR")] | length' $(Build.SourcesDirectory)/security-reports/sast-report.json 2>/dev/null || echo "0")
|
| 116 |
+
|
| 117 |
+
echo "📊 SAST Analysis Results:"
|
| 118 |
+
echo " Critical Issues Found: $CRITICAL_COUNT"
|
| 119 |
+
|
| 120 |
+
if [ "$CRITICAL_COUNT" -gt 0 ]; then
|
| 121 |
+
echo "❌ CRITICAL: $CRITICAL_COUNT critical security issues found!"
|
| 122 |
+
exit 1
|
| 123 |
+
else
|
| 124 |
+
echo "✅ No critical security issues found in static analysis."
|
| 125 |
+
fi
|
| 126 |
+
else
|
| 127 |
+
echo "⚠️ SAST report not generated. Check Semgrep execution."
|
| 128 |
+
fi
|
| 129 |
+
displayName: '🔍 Static Application Security Testing (SAST)'
|
| 130 |
+
continueOnError: true # Keep true for testing
|
| 131 |
+
|
| 132 |
+
# Publish security reports - ALWAYS RUN EVEN IF BUILD FAILS
|
| 133 |
+
- task: PublishBuildArtifacts@1
|
| 134 |
+
condition: always() # ← This ensures reports are published even if scans fail
|
| 135 |
+
inputs:
|
| 136 |
+
pathToPublish: '$(Build.SourcesDirectory)/security-reports'
|
| 137 |
+
artifactName: 'security-reports-$(Build.BuildId)'
|
| 138 |
+
publishLocation: 'Container'
|
| 139 |
+
displayName: '📊 Publish Security Reports'
|
| 140 |
+
|
| 141 |
+
# Build Docker image
|
| 142 |
+
- script: |
|
| 143 |
+
docker build -t $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_DEFAULT_REGION).amazonaws.com/$(IMAGE_REPO_NAME):$(Build.BuildId) .
|
| 144 |
+
displayName: 'Build Docker Image'
|
| 145 |
+
|
| 146 |
+
# Container scan (only if image built successfully)
|
| 147 |
+
# - script: |
|
| 148 |
+
# docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
| 149 |
+
# -v $(Build.SourcesDirectory):/reports \
|
| 150 |
+
# aquasec/trivy:latest image \
|
| 151 |
+
# --exit-code 1 \
|
| 152 |
+
# --severity CRITICAL \
|
| 153 |
+
# $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_DEFAULT_REGION).amazonaws.com/$(IMAGE_REPO_NAME):$(Build.BuildId)
|
| 154 |
+
# displayName: '🐳 Container Vulnerability Scan'
|
| 155 |
+
# List images for verification
|
| 156 |
+
- script: |
|
| 157 |
+
docker images
|
| 158 |
+
displayName: 'List Docker Images'
|
| 159 |
+
|
| 160 |
+
# Push to ECR
|
| 161 |
+
- script: |
|
| 162 |
+
docker push $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_DEFAULT_REGION).amazonaws.com/$(IMAGE_REPO_NAME):$(Build.BuildId)
|
| 163 |
+
displayName: 'Push Docker Image to ECR'
|
| 164 |
+
|
| 165 |
+
# Update deployment YAML
|
| 166 |
+
- script: |
|
| 167 |
+
git config --global user.email "devops@kovaion.ai"
|
| 168 |
+
git config --global user.name "Azure DevOps"
|
| 169 |
+
git clone https://$(AZURE_PAT)@dev.azure.com/kovaionai/Deployment/_git/Deployment
|
| 170 |
+
cd Deployment
|
| 171 |
+
git checkout delveant_cloud
|
| 172 |
+
sed -i 's|627133981011.dkr.ecr.ap-south-1.amazonaws.com/delveant_agent:.*|627133981011.dkr.ecr.ap-south-1.amazonaws.com/delveant_agent:$(Build.BuildId)|' agents.yaml
|
| 173 |
+
git add agents.yaml
|
| 174 |
+
git commit -m "update yaml with build ids"
|
| 175 |
+
git push origin delveant_cloud
|
| 176 |
+
displayName: 'Update YAML with Latest Image Tag and Push'
|
all_combine_code/genre_classification/genre_.ipynb
ADDED
|
@@ -0,0 +1,611 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"metadata": {
|
| 5 |
+
"ExecuteTime": {
|
| 6 |
+
"end_time": "2026-03-27T13:35:25.148983400Z",
|
| 7 |
+
"start_time": "2026-03-27T13:35:25.121163900Z"
|
| 8 |
+
}
|
| 9 |
+
},
|
| 10 |
+
"cell_type": "code",
|
| 11 |
+
"source": [
|
| 12 |
+
"#! pip install xgboost\n",
|
| 13 |
+
"#! pip install lightgbm"
|
| 14 |
+
],
|
| 15 |
+
"id": "989cd81ab35b2992",
|
| 16 |
+
"outputs": [],
|
| 17 |
+
"execution_count": 17
|
| 18 |
+
},
|
| 19 |
+
{
|
| 20 |
+
"metadata": {
|
| 21 |
+
"ExecuteTime": {
|
| 22 |
+
"end_time": "2026-03-27T14:16:27.830605300Z",
|
| 23 |
+
"start_time": "2026-03-27T14:15:36.993019800Z"
|
| 24 |
+
}
|
| 25 |
+
},
|
| 26 |
+
"cell_type": "code",
|
| 27 |
+
"source": [
|
| 28 |
+
"import pandas as pd\n",
|
| 29 |
+
"import numpy as np\n",
|
| 30 |
+
"import re\n",
|
| 31 |
+
"import ast\n",
|
| 32 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 33 |
+
"from sklearn.preprocessing import StandardScaler, MultiLabelBinarizer\n",
|
| 34 |
+
"from sklearn.multioutput import MultiOutputClassifier\n",
|
| 35 |
+
"from xgboost import XGBClassifier\n",
|
| 36 |
+
"\n",
|
| 37 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\model_ready_dataset_final.csv\"\n",
|
| 38 |
+
"df = pd.read_csv(path)\n",
|
| 39 |
+
"\n",
|
| 40 |
+
"def parse_subgenres(text):\n",
|
| 41 |
+
" if pd.isna(text) or text == \"\":\n",
|
| 42 |
+
" return []\n",
|
| 43 |
+
" match = re.search(r\"\\[(.*?)\\]\", str(text))\n",
|
| 44 |
+
" if match:\n",
|
| 45 |
+
" try:\n",
|
| 46 |
+
" genre_list = ast.literal_eval(\"[\" + match.group(1) + \"]\")\n",
|
| 47 |
+
" return [g.strip() for g in genre_list if g.strip()]\n",
|
| 48 |
+
" except:\n",
|
| 49 |
+
" return []\n",
|
| 50 |
+
" return []\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"df['subgenres_list'] = df['genre_subgenre'].apply(parse_subgenres)\n",
|
| 53 |
+
"\n",
|
| 54 |
+
"mlb = MultiLabelBinarizer()\n",
|
| 55 |
+
"y = mlb.fit_transform(df['subgenres_list'])\n",
|
| 56 |
+
"\n",
|
| 57 |
+
"drop_cols = ['_id', 'genre', 'genre_subgenre', 'subgenres_list', 'labels']\n",
|
| 58 |
+
"feature_cols = [c for c in df.columns if c not in drop_cols]\n",
|
| 59 |
+
"X = df[feature_cols].copy()\n",
|
| 60 |
+
"\n",
|
| 61 |
+
"X = X.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 64 |
+
"\n",
|
| 65 |
+
"scaler = StandardScaler()\n",
|
| 66 |
+
"X_train_scaled = scaler.fit_transform(X_train)\n",
|
| 67 |
+
"X_test_scaled = scaler.transform(X_test)\n",
|
| 68 |
+
"\n",
|
| 69 |
+
"model = MultiOutputClassifier(\n",
|
| 70 |
+
" XGBClassifier(\n",
|
| 71 |
+
" n_estimators=600,\n",
|
| 72 |
+
" learning_rate=0.08,\n",
|
| 73 |
+
" max_depth=6,\n",
|
| 74 |
+
" subsample=0.85,\n",
|
| 75 |
+
" colsample_bytree=0.85,\n",
|
| 76 |
+
" random_state=42,\n",
|
| 77 |
+
" eval_metric='logloss'\n",
|
| 78 |
+
" ),\n",
|
| 79 |
+
" n_jobs=-1\n",
|
| 80 |
+
")\n",
|
| 81 |
+
"\n",
|
| 82 |
+
"model.fit(X_train_scaled, y_train)\n",
|
| 83 |
+
"\n",
|
| 84 |
+
"def predict_genre_subgenre(idx):\n",
|
| 85 |
+
" pred = model.predict(X_test_scaled[idx].reshape(1, -1))[0]\n",
|
| 86 |
+
" subgenres = mlb.inverse_transform(pred.reshape(1, -1))[0]\n",
|
| 87 |
+
" if len(subgenres) == 0:\n",
|
| 88 |
+
" return \"Rock -- None\"\n",
|
| 89 |
+
" return f\"Rock -- {', '.join(subgenres)}\"\n",
|
| 90 |
+
"\n",
|
| 91 |
+
"print(\"FINAL PREDICTIONS:\")\n",
|
| 92 |
+
"for i in range(10):\n",
|
| 93 |
+
" print(predict_genre_subgenre(i))"
|
| 94 |
+
],
|
| 95 |
+
"id": "f1eebe48c0a482c3",
|
| 96 |
+
"outputs": [
|
| 97 |
+
{
|
| 98 |
+
"name": "stdout",
|
| 99 |
+
"output_type": "stream",
|
| 100 |
+
"text": [
|
| 101 |
+
"FINAL PREDICTIONS:\n",
|
| 102 |
+
"Rock -- Contemporary Singer Songwriter\n",
|
| 103 |
+
"Rock -- None\n",
|
| 104 |
+
"Rock -- None\n",
|
| 105 |
+
"Rock -- None\n",
|
| 106 |
+
"Rock -- Classic Alternative Rock\n",
|
| 107 |
+
"Rock -- None\n",
|
| 108 |
+
"Rock -- Indie Rock\n",
|
| 109 |
+
"Rock -- Americana\n",
|
| 110 |
+
"Rock -- Indie Rock\n",
|
| 111 |
+
"Rock -- None\n"
|
| 112 |
+
]
|
| 113 |
+
}
|
| 114 |
+
],
|
| 115 |
+
"execution_count": 68
|
| 116 |
+
},
|
| 117 |
+
{
|
| 118 |
+
"metadata": {
|
| 119 |
+
"ExecuteTime": {
|
| 120 |
+
"end_time": "2026-03-27T14:19:25.780735200Z",
|
| 121 |
+
"start_time": "2026-03-27T14:18:17.557680700Z"
|
| 122 |
+
}
|
| 123 |
+
},
|
| 124 |
+
"cell_type": "code",
|
| 125 |
+
"source": [
|
| 126 |
+
"import pandas as pd\n",
|
| 127 |
+
"import numpy as np\n",
|
| 128 |
+
"import re\n",
|
| 129 |
+
"import ast\n",
|
| 130 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 131 |
+
"from sklearn.preprocessing import StandardScaler, MultiLabelBinarizer\n",
|
| 132 |
+
"from sklearn.multioutput import MultiOutputClassifier\n",
|
| 133 |
+
"from xgboost import XGBClassifier\n",
|
| 134 |
+
"\n",
|
| 135 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\model_ready_dataset_final.csv\"\n",
|
| 136 |
+
"df = pd.read_csv(path)\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"def parse_subgenres(text):\n",
|
| 139 |
+
" if pd.isna(text) or text == \"\":\n",
|
| 140 |
+
" return []\n",
|
| 141 |
+
" match = re.search(r\"\\[(.*?)\\]\", str(text))\n",
|
| 142 |
+
" if match:\n",
|
| 143 |
+
" try:\n",
|
| 144 |
+
" genre_list = ast.literal_eval(\"[\" + match.group(1) + \"]\")\n",
|
| 145 |
+
" return [g.strip() for g in genre_list if g.strip()]\n",
|
| 146 |
+
" except:\n",
|
| 147 |
+
" return []\n",
|
| 148 |
+
" return []\n",
|
| 149 |
+
"\n",
|
| 150 |
+
"df['subgenres_list'] = df['genre_subgenre'].apply(parse_subgenres)\n",
|
| 151 |
+
"\n",
|
| 152 |
+
"mlb = MultiLabelBinarizer()\n",
|
| 153 |
+
"y = mlb.fit_transform(df['subgenres_list'])\n",
|
| 154 |
+
"\n",
|
| 155 |
+
"drop_cols = ['_id', 'genre', 'genre_subgenre', 'subgenres_list', 'labels']\n",
|
| 156 |
+
"feature_cols = [c for c in df.columns if c not in drop_cols]\n",
|
| 157 |
+
"X = df[feature_cols].copy()\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"X = X.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 160 |
+
"\n",
|
| 161 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 162 |
+
"\n",
|
| 163 |
+
"scaler = StandardScaler()\n",
|
| 164 |
+
"X_train_scaled = scaler.fit_transform(X_train)\n",
|
| 165 |
+
"X_test_scaled = scaler.transform(X_test)\n",
|
| 166 |
+
"\n",
|
| 167 |
+
"model = MultiOutputClassifier(\n",
|
| 168 |
+
" XGBClassifier(\n",
|
| 169 |
+
" n_estimators=800,\n",
|
| 170 |
+
" learning_rate=0.05,\n",
|
| 171 |
+
" max_depth=7,\n",
|
| 172 |
+
" subsample=0.8,\n",
|
| 173 |
+
" colsample_bytree=0.8,\n",
|
| 174 |
+
" random_state=42,\n",
|
| 175 |
+
" eval_metric='logloss'\n",
|
| 176 |
+
" ),\n",
|
| 177 |
+
" n_jobs=-1\n",
|
| 178 |
+
")\n",
|
| 179 |
+
"\n",
|
| 180 |
+
"model.fit(X_train_scaled, y_train)"
|
| 181 |
+
],
|
| 182 |
+
"id": "b73adeba8b23236b",
|
| 183 |
+
"outputs": [
|
| 184 |
+
{
|
| 185 |
+
"name": "stdout",
|
| 186 |
+
"output_type": "stream",
|
| 187 |
+
"text": [
|
| 188 |
+
"FINAL PREDICTIONS:\n",
|
| 189 |
+
"Rock -- Contemporary Singer Songwriter\n",
|
| 190 |
+
"Rock -- Indie Rock\n",
|
| 191 |
+
"Rock -- Indie Rock\n",
|
| 192 |
+
"Rock -- Indie Pop\n",
|
| 193 |
+
"Rock -- Classic Alternative Rock, Indie Pop, Modern Alternative Rock\n",
|
| 194 |
+
"Rock -- Indie Rock\n",
|
| 195 |
+
"Rock -- Classic Alternative Rock, Indie Rock\n",
|
| 196 |
+
"Rock -- Americana\n",
|
| 197 |
+
"Rock -- Classic Alternative Rock, Indie Rock\n",
|
| 198 |
+
"Rock -- Contemporary Country\n",
|
| 199 |
+
"Rock -- Indie Rock\n",
|
| 200 |
+
"Rock -- Indie Pop, Modern Alternative Rock\n",
|
| 201 |
+
"Rock -- Contemporary Singer Songwriter, Indie Pop\n",
|
| 202 |
+
"Rock -- Contemporary Singer Songwriter, Indie Electronic, Indie Pop\n",
|
| 203 |
+
"Rock -- Classic Alternative Rock, Indie Pop, Indie Rock\n"
|
| 204 |
+
]
|
| 205 |
+
}
|
| 206 |
+
],
|
| 207 |
+
"execution_count": 69
|
| 208 |
+
},
|
| 209 |
+
{
|
| 210 |
+
"metadata": {
|
| 211 |
+
"ExecuteTime": {
|
| 212 |
+
"end_time": "2026-03-27T14:23:46.993341800Z",
|
| 213 |
+
"start_time": "2026-03-27T14:23:36.236284900Z"
|
| 214 |
+
}
|
| 215 |
+
},
|
| 216 |
+
"cell_type": "code",
|
| 217 |
+
"source": [
|
| 218 |
+
"def get_accuracy(X_scaled, y_true, name=\"\"):\n",
|
| 219 |
+
" y_pred = model.predict(X_scaled)\n",
|
| 220 |
+
" subset_acc = np.mean(np.all(y_pred == y_true, axis=1))\n",
|
| 221 |
+
" micro_f1 = f1_score(y_true, y_pred, average='micro')\n",
|
| 222 |
+
" print(f\"{name} Subset Accuracy: {subset_acc:.4f}\")\n",
|
| 223 |
+
" print(f\"{name} Micro F1 Score: {micro_f1:.4f}\")\n",
|
| 224 |
+
"\n",
|
| 225 |
+
"print(\"=== MODEL ACCURACY ===\")\n",
|
| 226 |
+
"get_accuracy(X_train_scaled, y_train, \"Train\")\n",
|
| 227 |
+
"get_accuracy(X_test_scaled, y_test, \"Test\")"
|
| 228 |
+
],
|
| 229 |
+
"id": "5f4d018a7e7c5f21",
|
| 230 |
+
"outputs": [
|
| 231 |
+
{
|
| 232 |
+
"name": "stdout",
|
| 233 |
+
"output_type": "stream",
|
| 234 |
+
"text": [
|
| 235 |
+
"=== MODEL ACCURACY ===\n",
|
| 236 |
+
"Train Subset Accuracy: 0.9568\n",
|
| 237 |
+
"Train Micro F1 Score: 0.9862\n",
|
| 238 |
+
"Test Subset Accuracy: 0.2026\n",
|
| 239 |
+
"Test Micro F1 Score: 0.2830\n"
|
| 240 |
+
]
|
| 241 |
+
}
|
| 242 |
+
],
|
| 243 |
+
"execution_count": 72
|
| 244 |
+
},
|
| 245 |
+
{
|
| 246 |
+
"metadata": {
|
| 247 |
+
"ExecuteTime": {
|
| 248 |
+
"end_time": "2026-03-27T14:24:41.561225300Z",
|
| 249 |
+
"start_time": "2026-03-27T14:24:41.503715100Z"
|
| 250 |
+
}
|
| 251 |
+
},
|
| 252 |
+
"cell_type": "code",
|
| 253 |
+
"source": [
|
| 254 |
+
"def manual_predict(row_features):\n",
|
| 255 |
+
" input_df = pd.DataFrame([row_features])\n",
|
| 256 |
+
" input_numeric = input_df.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 257 |
+
" for col in X.columns:\n",
|
| 258 |
+
" if col not in input_numeric.columns:\n",
|
| 259 |
+
" input_numeric[col] = 0\n",
|
| 260 |
+
" input_numeric = input_numeric[X.columns]\n",
|
| 261 |
+
" input_scaled = scaler.transform(input_numeric)\n",
|
| 262 |
+
" probas = model.predict_proba(input_scaled)\n",
|
| 263 |
+
" threshold = 0.25\n",
|
| 264 |
+
" pred_binary = np.zeros((1, y.shape[1]), dtype=int)\n",
|
| 265 |
+
" for i in range(len(probas)):\n",
|
| 266 |
+
" if probas[i][0][1] > threshold:\n",
|
| 267 |
+
" pred_binary[0, i] = 1\n",
|
| 268 |
+
" subgenres = mlb.inverse_transform(pred_binary)[0]\n",
|
| 269 |
+
" if len(subgenres) == 0:\n",
|
| 270 |
+
" return \"None\"\n",
|
| 271 |
+
" return {', '.join(sorted(subgenres))}"
|
| 272 |
+
],
|
| 273 |
+
"id": "d759399759dbe475",
|
| 274 |
+
"outputs": [],
|
| 275 |
+
"execution_count": 73
|
| 276 |
+
},
|
| 277 |
+
{
|
| 278 |
+
"metadata": {
|
| 279 |
+
"ExecuteTime": {
|
| 280 |
+
"end_time": "2026-03-27T14:27:13.980046900Z",
|
| 281 |
+
"start_time": "2026-03-27T14:27:13.695114900Z"
|
| 282 |
+
}
|
| 283 |
+
},
|
| 284 |
+
"cell_type": "code",
|
| 285 |
+
"source": [
|
| 286 |
+
"print(\"\\n=== FIRST ROW PREDICTION ===\")\n",
|
| 287 |
+
"first_row = df.iloc[0].to_dict()\n",
|
| 288 |
+
"pred_first = manual_predict(first_row)\n",
|
| 289 |
+
"print(pred_first)\n",
|
| 290 |
+
"\n",
|
| 291 |
+
"print(\"\\n=== LAST ROW PREDICTION ===\")\n",
|
| 292 |
+
"last_row = df.iloc[-1].to_dict()\n",
|
| 293 |
+
"pred_last = manual_predict(last_row)\n",
|
| 294 |
+
"print(pred_last)"
|
| 295 |
+
],
|
| 296 |
+
"id": "59c4c35de1bde5ee",
|
| 297 |
+
"outputs": [
|
| 298 |
+
{
|
| 299 |
+
"name": "stdout",
|
| 300 |
+
"output_type": "stream",
|
| 301 |
+
"text": [
|
| 302 |
+
"\n",
|
| 303 |
+
"=== FIRST ROW PREDICTION ===\n",
|
| 304 |
+
"Rock -- Classic Alternative Rock, Indie Rock, Noise Pop\n",
|
| 305 |
+
"\n",
|
| 306 |
+
"=== LAST ROW PREDICTION ===\n",
|
| 307 |
+
"Rock -- Gothic Metal, Heavy Metal, Metalcore\n"
|
| 308 |
+
]
|
| 309 |
+
}
|
| 310 |
+
],
|
| 311 |
+
"execution_count": 76
|
| 312 |
+
},
|
| 313 |
+
{
|
| 314 |
+
"metadata": {
|
| 315 |
+
"ExecuteTime": {
|
| 316 |
+
"end_time": "2026-03-27T14:25:23.755520400Z",
|
| 317 |
+
"start_time": "2026-03-27T14:25:23.711767500Z"
|
| 318 |
+
}
|
| 319 |
+
},
|
| 320 |
+
"cell_type": "code",
|
| 321 |
+
"source": "df.tail(1)",
|
| 322 |
+
"id": "ed40ccd2d8a6769e",
|
| 323 |
+
"outputs": [
|
| 324 |
+
{
|
| 325 |
+
"data": {
|
| 326 |
+
"text/plain": [
|
| 327 |
+
" _id genre \\\n",
|
| 328 |
+
"41934 698c479e0fc160cc5e51bc64 Metal \n",
|
| 329 |
+
"\n",
|
| 330 |
+
" genre_subgenre \\\n",
|
| 331 |
+
"41934 Metal --- ['Gothic Metal', 'Heavy Metal', 'Met... \n",
|
| 332 |
+
"\n",
|
| 333 |
+
" trimmed_audio_duration_sec syllable_count word_count \\\n",
|
| 334 |
+
"41934 112.72127 573.0 455.0 \n",
|
| 335 |
+
"\n",
|
| 336 |
+
" spectral_contrast_mean (mix) chroma_mean (mix) \\\n",
|
| 337 |
+
"41934 19.482281 0.559816 \n",
|
| 338 |
+
"\n",
|
| 339 |
+
" melody_variability (vocals) rhythm_onset_rate (mix) ... \\\n",
|
| 340 |
+
"41934 0.693639 5.48255 ... \n",
|
| 341 |
+
"\n",
|
| 342 |
+
" vocab_richness loudness_integrated_lufs custom (mix) \\\n",
|
| 343 |
+
"41934 0.495 -14.21983 \n",
|
| 344 |
+
"\n",
|
| 345 |
+
" readability_score energy_essentia (mix) energy_librosa (mix) \\\n",
|
| 346 |
+
"41934 16.4 0.354772 0.558767 \n",
|
| 347 |
+
"\n",
|
| 348 |
+
" rms_energy_mean (mix) sentiment_score melody_complexity (vocals) \\\n",
|
| 349 |
+
"41934 0.562382 -0.002 2.916667 \n",
|
| 350 |
+
"\n",
|
| 351 |
+
" avg_word_length subgenres_list \n",
|
| 352 |
+
"41934 3.63 [Gothic Metal, Heavy Metal, Metalcore] \n",
|
| 353 |
+
"\n",
|
| 354 |
+
"[1 rows x 29 columns]"
|
| 355 |
+
],
|
| 356 |
+
"text/html": [
|
| 357 |
+
"<div>\n",
|
| 358 |
+
"<style scoped>\n",
|
| 359 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 360 |
+
" vertical-align: middle;\n",
|
| 361 |
+
" }\n",
|
| 362 |
+
"\n",
|
| 363 |
+
" .dataframe tbody tr th {\n",
|
| 364 |
+
" vertical-align: top;\n",
|
| 365 |
+
" }\n",
|
| 366 |
+
"\n",
|
| 367 |
+
" .dataframe thead th {\n",
|
| 368 |
+
" text-align: right;\n",
|
| 369 |
+
" }\n",
|
| 370 |
+
"</style>\n",
|
| 371 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 372 |
+
" <thead>\n",
|
| 373 |
+
" <tr style=\"text-align: right;\">\n",
|
| 374 |
+
" <th></th>\n",
|
| 375 |
+
" <th>_id</th>\n",
|
| 376 |
+
" <th>genre</th>\n",
|
| 377 |
+
" <th>genre_subgenre</th>\n",
|
| 378 |
+
" <th>trimmed_audio_duration_sec</th>\n",
|
| 379 |
+
" <th>syllable_count</th>\n",
|
| 380 |
+
" <th>word_count</th>\n",
|
| 381 |
+
" <th>spectral_contrast_mean (mix)</th>\n",
|
| 382 |
+
" <th>chroma_mean (mix)</th>\n",
|
| 383 |
+
" <th>melody_variability (vocals)</th>\n",
|
| 384 |
+
" <th>rhythm_onset_rate (mix)</th>\n",
|
| 385 |
+
" <th>...</th>\n",
|
| 386 |
+
" <th>vocab_richness</th>\n",
|
| 387 |
+
" <th>loudness_integrated_lufs custom (mix)</th>\n",
|
| 388 |
+
" <th>readability_score</th>\n",
|
| 389 |
+
" <th>energy_essentia (mix)</th>\n",
|
| 390 |
+
" <th>energy_librosa (mix)</th>\n",
|
| 391 |
+
" <th>rms_energy_mean (mix)</th>\n",
|
| 392 |
+
" <th>sentiment_score</th>\n",
|
| 393 |
+
" <th>melody_complexity (vocals)</th>\n",
|
| 394 |
+
" <th>avg_word_length</th>\n",
|
| 395 |
+
" <th>subgenres_list</th>\n",
|
| 396 |
+
" </tr>\n",
|
| 397 |
+
" </thead>\n",
|
| 398 |
+
" <tbody>\n",
|
| 399 |
+
" <tr>\n",
|
| 400 |
+
" <th>41934</th>\n",
|
| 401 |
+
" <td>698c479e0fc160cc5e51bc64</td>\n",
|
| 402 |
+
" <td>Metal</td>\n",
|
| 403 |
+
" <td>Metal --- ['Gothic Metal', 'Heavy Metal', 'Met...</td>\n",
|
| 404 |
+
" <td>112.72127</td>\n",
|
| 405 |
+
" <td>573.0</td>\n",
|
| 406 |
+
" <td>455.0</td>\n",
|
| 407 |
+
" <td>19.482281</td>\n",
|
| 408 |
+
" <td>0.559816</td>\n",
|
| 409 |
+
" <td>0.693639</td>\n",
|
| 410 |
+
" <td>5.48255</td>\n",
|
| 411 |
+
" <td>...</td>\n",
|
| 412 |
+
" <td>0.495</td>\n",
|
| 413 |
+
" <td>-14.21983</td>\n",
|
| 414 |
+
" <td>16.4</td>\n",
|
| 415 |
+
" <td>0.354772</td>\n",
|
| 416 |
+
" <td>0.558767</td>\n",
|
| 417 |
+
" <td>0.562382</td>\n",
|
| 418 |
+
" <td>-0.002</td>\n",
|
| 419 |
+
" <td>2.916667</td>\n",
|
| 420 |
+
" <td>3.63</td>\n",
|
| 421 |
+
" <td>[Gothic Metal, Heavy Metal, Metalcore]</td>\n",
|
| 422 |
+
" </tr>\n",
|
| 423 |
+
" </tbody>\n",
|
| 424 |
+
"</table>\n",
|
| 425 |
+
"<p>1 rows × 29 columns</p>\n",
|
| 426 |
+
"</div>"
|
| 427 |
+
]
|
| 428 |
+
},
|
| 429 |
+
"execution_count": 75,
|
| 430 |
+
"metadata": {},
|
| 431 |
+
"output_type": "execute_result"
|
| 432 |
+
}
|
| 433 |
+
],
|
| 434 |
+
"execution_count": 75
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
"metadata": {
|
| 438 |
+
"ExecuteTime": {
|
| 439 |
+
"end_time": "2026-03-27T14:32:46.590643700Z",
|
| 440 |
+
"start_time": "2026-03-27T14:32:46.540506300Z"
|
| 441 |
+
}
|
| 442 |
+
},
|
| 443 |
+
"cell_type": "code",
|
| 444 |
+
"source": "df.columns",
|
| 445 |
+
"id": "f581dd69d03fec27",
|
| 446 |
+
"outputs": [
|
| 447 |
+
{
|
| 448 |
+
"data": {
|
| 449 |
+
"text/plain": [
|
| 450 |
+
"Index(['_id', 'genre', 'genre_subgenre', 'trimmed_audio_duration_sec',\n",
|
| 451 |
+
" 'syllable_count', 'word_count', 'spectral_contrast_mean (mix)',\n",
|
| 452 |
+
" 'chroma_mean (mix)', 'melody_variability (vocals)',\n",
|
| 453 |
+
" 'rhythm_onset_rate (mix)', 'spectral_centroid_mean custom (mix)',\n",
|
| 454 |
+
" 'repetition_score custom (mix)', 'pitch_std (mix)',\n",
|
| 455 |
+
" 'tempo_bpm_corrected (mix)', 'tempo_bpm_original (mix)',\n",
|
| 456 |
+
" 'mfcc_mean_1 (mix)', 'mfcc_mean_2 (mix)',\n",
|
| 457 |
+
" 'loudness_range_lu custom (mix)', 'zero_crossing_rate (mix)',\n",
|
| 458 |
+
" 'vocab_richness', 'loudness_integrated_lufs custom (mix)',\n",
|
| 459 |
+
" 'readability_score', 'energy_essentia (mix)', 'energy_librosa (mix)',\n",
|
| 460 |
+
" 'rms_energy_mean (mix)', 'sentiment_score',\n",
|
| 461 |
+
" 'melody_complexity (vocals)', 'avg_word_length', 'subgenres_list'],\n",
|
| 462 |
+
" dtype='object')"
|
| 463 |
+
]
|
| 464 |
+
},
|
| 465 |
+
"execution_count": 77,
|
| 466 |
+
"metadata": {},
|
| 467 |
+
"output_type": "execute_result"
|
| 468 |
+
}
|
| 469 |
+
],
|
| 470 |
+
"execution_count": 77
|
| 471 |
+
},
|
| 472 |
+
{
|
| 473 |
+
"metadata": {},
|
| 474 |
+
"cell_type": "code",
|
| 475 |
+
"outputs": [],
|
| 476 |
+
"execution_count": null,
|
| 477 |
+
"source": "",
|
| 478 |
+
"id": "9b8e0e646877dffd"
|
| 479 |
+
},
|
| 480 |
+
{
|
| 481 |
+
"metadata": {},
|
| 482 |
+
"cell_type": "code",
|
| 483 |
+
"outputs": [],
|
| 484 |
+
"execution_count": null,
|
| 485 |
+
"source": "",
|
| 486 |
+
"id": "a2c6e3303920e7b1"
|
| 487 |
+
},
|
| 488 |
+
{
|
| 489 |
+
"metadata": {},
|
| 490 |
+
"cell_type": "code",
|
| 491 |
+
"outputs": [],
|
| 492 |
+
"execution_count": null,
|
| 493 |
+
"source": "",
|
| 494 |
+
"id": "f9be1b4975b13c44"
|
| 495 |
+
},
|
| 496 |
+
{
|
| 497 |
+
"metadata": {},
|
| 498 |
+
"cell_type": "code",
|
| 499 |
+
"outputs": [],
|
| 500 |
+
"execution_count": null,
|
| 501 |
+
"source": "",
|
| 502 |
+
"id": "561bf73932a24d3a"
|
| 503 |
+
},
|
| 504 |
+
{
|
| 505 |
+
"metadata": {},
|
| 506 |
+
"cell_type": "code",
|
| 507 |
+
"outputs": [],
|
| 508 |
+
"execution_count": null,
|
| 509 |
+
"source": "",
|
| 510 |
+
"id": "ed7e357983c3b734"
|
| 511 |
+
},
|
| 512 |
+
{
|
| 513 |
+
"metadata": {
|
| 514 |
+
"ExecuteTime": {
|
| 515 |
+
"end_time": "2026-03-29T11:07:41.453680300Z",
|
| 516 |
+
"start_time": "2026-03-29T11:07:41.256698100Z"
|
| 517 |
+
}
|
| 518 |
+
},
|
| 519 |
+
"cell_type": "code",
|
| 520 |
+
"source": [
|
| 521 |
+
"import ast, re\n",
|
| 522 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 523 |
+
"from sklearn.preprocessing import StandardScaler, MultiLabelBinarizer\n",
|
| 524 |
+
"from sklearn.multioutput import MultiOutputClassifier\n",
|
| 525 |
+
"from xgboost import XGBClassifier\n",
|
| 526 |
+
"from sklearn.metrics import f1_score\n",
|
| 527 |
+
"import pandas as pd\n",
|
| 528 |
+
"\n",
|
| 529 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\model_ready_dataset_final.csv\"\n",
|
| 530 |
+
"df = pd.read_csv(path)\n",
|
| 531 |
+
"\n",
|
| 532 |
+
"def parse_subgenres_fast(text):\n",
|
| 533 |
+
" if pd.isna(text) or text == \"\":\n",
|
| 534 |
+
" return []\n",
|
| 535 |
+
"\n",
|
| 536 |
+
" text = str(text)\n",
|
| 537 |
+
" parts = text.split('---')\n",
|
| 538 |
+
" main_genre = parts[0].strip()\n",
|
| 539 |
+
"\n",
|
| 540 |
+
" if len(parts) > 1:\n",
|
| 541 |
+
" sub = parts[1].strip()\n",
|
| 542 |
+
" sub = sub.strip('[]')\n",
|
| 543 |
+
" if sub:\n",
|
| 544 |
+
" subgenres = [s.strip().strip(\"'\").strip('\"') for s in sub.split(',')]\n",
|
| 545 |
+
" else:\n",
|
| 546 |
+
" subgenres = []\n",
|
| 547 |
+
" else:\n",
|
| 548 |
+
" subgenres = []\n",
|
| 549 |
+
" return [main_genre] + subgenres\n",
|
| 550 |
+
"\n",
|
| 551 |
+
"df['subgenres_list'] = df['genre_subgenre'].apply(parse_subgenres_fast)"
|
| 552 |
+
],
|
| 553 |
+
"id": "573c53470f0ef01f",
|
| 554 |
+
"outputs": [],
|
| 555 |
+
"execution_count": 8
|
| 556 |
+
},
|
| 557 |
+
{
|
| 558 |
+
"metadata": {
|
| 559 |
+
"ExecuteTime": {
|
| 560 |
+
"end_time": "2026-03-29T11:07:43.398574800Z",
|
| 561 |
+
"start_time": "2026-03-29T11:07:43.351108Z"
|
| 562 |
+
}
|
| 563 |
+
},
|
| 564 |
+
"cell_type": "code",
|
| 565 |
+
"source": "df['subgenres_list'][0]",
|
| 566 |
+
"id": "1b59c31bd0b780e0",
|
| 567 |
+
"outputs": [
|
| 568 |
+
{
|
| 569 |
+
"data": {
|
| 570 |
+
"text/plain": [
|
| 571 |
+
"['Rock', 'Noise Pop', 'Classic Alternative Rock', 'Indie Rock']"
|
| 572 |
+
]
|
| 573 |
+
},
|
| 574 |
+
"execution_count": 9,
|
| 575 |
+
"metadata": {},
|
| 576 |
+
"output_type": "execute_result"
|
| 577 |
+
}
|
| 578 |
+
],
|
| 579 |
+
"execution_count": 9
|
| 580 |
+
},
|
| 581 |
+
{
|
| 582 |
+
"metadata": {},
|
| 583 |
+
"cell_type": "code",
|
| 584 |
+
"outputs": [],
|
| 585 |
+
"execution_count": null,
|
| 586 |
+
"source": "",
|
| 587 |
+
"id": "9b1fa1d3b08915da"
|
| 588 |
+
}
|
| 589 |
+
],
|
| 590 |
+
"metadata": {
|
| 591 |
+
"kernelspec": {
|
| 592 |
+
"display_name": "Python 3",
|
| 593 |
+
"language": "python",
|
| 594 |
+
"name": "python3"
|
| 595 |
+
},
|
| 596 |
+
"language_info": {
|
| 597 |
+
"codemirror_mode": {
|
| 598 |
+
"name": "ipython",
|
| 599 |
+
"version": 2
|
| 600 |
+
},
|
| 601 |
+
"file_extension": ".py",
|
| 602 |
+
"mimetype": "text/x-python",
|
| 603 |
+
"name": "python",
|
| 604 |
+
"nbconvert_exporter": "python",
|
| 605 |
+
"pygments_lexer": "ipython2",
|
| 606 |
+
"version": "2.7.6"
|
| 607 |
+
}
|
| 608 |
+
},
|
| 609 |
+
"nbformat": 4,
|
| 610 |
+
"nbformat_minor": 5
|
| 611 |
+
}
|
all_combine_code/genre_classification/main_file.ipynb
ADDED
|
@@ -0,0 +1,2110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"id": "initial_id",
|
| 6 |
+
"metadata": {
|
| 7 |
+
"collapsed": true,
|
| 8 |
+
"ExecuteTime": {
|
| 9 |
+
"end_time": "2026-03-30T08:55:12.053860800Z",
|
| 10 |
+
"start_time": "2026-03-30T08:55:06.050743500Z"
|
| 11 |
+
}
|
| 12 |
+
},
|
| 13 |
+
"source": [
|
| 14 |
+
"import pandas as pd\n",
|
| 15 |
+
"import numpy as np\n",
|
| 16 |
+
"import re\n",
|
| 17 |
+
"import ast\n",
|
| 18 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 19 |
+
"from sklearn.preprocessing import StandardScaler, MultiLabelBinarizer\n",
|
| 20 |
+
"from sklearn.multioutput import MultiOutputClassifier\n",
|
| 21 |
+
"from xgboost import XGBClassifier\n",
|
| 22 |
+
"from sklearn.metrics import f1_score\n",
|
| 23 |
+
"pd.set_option(\"display.max_columns\", None)\n",
|
| 24 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\model_ready_dataset_final.csv\"\n",
|
| 25 |
+
"df = pd.read_csv(path)\n",
|
| 26 |
+
"df.sample(3)"
|
| 27 |
+
],
|
| 28 |
+
"outputs": [
|
| 29 |
+
{
|
| 30 |
+
"data": {
|
| 31 |
+
"text/plain": [
|
| 32 |
+
" _id genre \\\n",
|
| 33 |
+
"27949 69b29e6c05f5ce265b882008 Rock \n",
|
| 34 |
+
"40690 69b12fabebd9b3c1ca39e859 Country \n",
|
| 35 |
+
"18977 692969b1c5db350c7bdaf13d Rock \n",
|
| 36 |
+
"\n",
|
| 37 |
+
" genre_subgenre \\\n",
|
| 38 |
+
"27949 Rock --- ['Indie Folk', 'Country Folk', 'Indie... \n",
|
| 39 |
+
"40690 Country --- ['Americana', 'Country Rock', 'Alt... \n",
|
| 40 |
+
"18977 Rock --- ['Noise Pop', 'Classic Alternative Ro... \n",
|
| 41 |
+
"\n",
|
| 42 |
+
" trimmed_audio_duration_sec syllable_count word_count \\\n",
|
| 43 |
+
"27949 191.332000 287.0 197.0 \n",
|
| 44 |
+
"40690 215.146667 245.0 200.0 \n",
|
| 45 |
+
"18977 203.948458 145.0 118.0 \n",
|
| 46 |
+
"\n",
|
| 47 |
+
" spectral_contrast_mean (mix) chroma_mean (mix) \\\n",
|
| 48 |
+
"27949 19.647192 0.453619 \n",
|
| 49 |
+
"40690 19.253367 0.484467 \n",
|
| 50 |
+
"18977 18.226969 0.532658 \n",
|
| 51 |
+
"\n",
|
| 52 |
+
" melody_variability (vocals) rhythm_onset_rate (mix) \\\n",
|
| 53 |
+
"27949 0.479049 1.927778 \n",
|
| 54 |
+
"40690 0.600583 4.220377 \n",
|
| 55 |
+
"18977 0.647673 1.694444 \n",
|
| 56 |
+
"\n",
|
| 57 |
+
" spectral_centroid_mean custom (mix) repetition_score custom (mix) \\\n",
|
| 58 |
+
"27949 0.093420 0.006936 \n",
|
| 59 |
+
"40690 0.111674 0.005806 \n",
|
| 60 |
+
"18977 0.124794 0.006610 \n",
|
| 61 |
+
"\n",
|
| 62 |
+
" pitch_std (mix) tempo_bpm_corrected (mix) tempo_bpm_original (mix) \\\n",
|
| 63 |
+
"27949 56.905636 123.635239 123.635239 \n",
|
| 64 |
+
"40690 70.522621 84.574928 84.574928 \n",
|
| 65 |
+
"18977 145.266922 85.543114 85.543114 \n",
|
| 66 |
+
"\n",
|
| 67 |
+
" mfcc_mean_1 (mix) mfcc_mean_2 (mix) loudness_range_lu custom (mix) \\\n",
|
| 68 |
+
"27949 194.688721 -3.499274 6.624297 \n",
|
| 69 |
+
"40690 178.628433 -50.666416 3.653197 \n",
|
| 70 |
+
"18977 164.830765 -53.442635 1.909515 \n",
|
| 71 |
+
"\n",
|
| 72 |
+
" zero_crossing_rate (mix) vocab_richness \\\n",
|
| 73 |
+
"27949 0.032766 0.325 \n",
|
| 74 |
+
"40690 0.049714 0.345 \n",
|
| 75 |
+
"18977 0.059000 0.542 \n",
|
| 76 |
+
"\n",
|
| 77 |
+
" loudness_integrated_lufs custom (mix) readability_score \\\n",
|
| 78 |
+
"27949 -16.190006 76.6 \n",
|
| 79 |
+
"40690 -13.794414 2.1 \n",
|
| 80 |
+
"18977 -11.325106 1.4 \n",
|
| 81 |
+
"\n",
|
| 82 |
+
" energy_essentia (mix) energy_librosa (mix) rms_energy_mean (mix) \\\n",
|
| 83 |
+
"27949 0.259487 0.465622 0.466699 \n",
|
| 84 |
+
"40690 0.330070 0.547306 0.547923 \n",
|
| 85 |
+
"18977 0.439053 0.604893 0.661765 \n",
|
| 86 |
+
"\n",
|
| 87 |
+
" sentiment_score melody_complexity (vocals) avg_word_length \n",
|
| 88 |
+
"27949 0.035 2.666667 4.67 \n",
|
| 89 |
+
"40690 0.336 3.250000 3.76 \n",
|
| 90 |
+
"18977 -0.013 2.916667 3.42 "
|
| 91 |
+
],
|
| 92 |
+
"text/html": [
|
| 93 |
+
"<div>\n",
|
| 94 |
+
"<style scoped>\n",
|
| 95 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 96 |
+
" vertical-align: middle;\n",
|
| 97 |
+
" }\n",
|
| 98 |
+
"\n",
|
| 99 |
+
" .dataframe tbody tr th {\n",
|
| 100 |
+
" vertical-align: top;\n",
|
| 101 |
+
" }\n",
|
| 102 |
+
"\n",
|
| 103 |
+
" .dataframe thead th {\n",
|
| 104 |
+
" text-align: right;\n",
|
| 105 |
+
" }\n",
|
| 106 |
+
"</style>\n",
|
| 107 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 108 |
+
" <thead>\n",
|
| 109 |
+
" <tr style=\"text-align: right;\">\n",
|
| 110 |
+
" <th></th>\n",
|
| 111 |
+
" <th>_id</th>\n",
|
| 112 |
+
" <th>genre</th>\n",
|
| 113 |
+
" <th>genre_subgenre</th>\n",
|
| 114 |
+
" <th>trimmed_audio_duration_sec</th>\n",
|
| 115 |
+
" <th>syllable_count</th>\n",
|
| 116 |
+
" <th>word_count</th>\n",
|
| 117 |
+
" <th>spectral_contrast_mean (mix)</th>\n",
|
| 118 |
+
" <th>chroma_mean (mix)</th>\n",
|
| 119 |
+
" <th>melody_variability (vocals)</th>\n",
|
| 120 |
+
" <th>rhythm_onset_rate (mix)</th>\n",
|
| 121 |
+
" <th>spectral_centroid_mean custom (mix)</th>\n",
|
| 122 |
+
" <th>repetition_score custom (mix)</th>\n",
|
| 123 |
+
" <th>pitch_std (mix)</th>\n",
|
| 124 |
+
" <th>tempo_bpm_corrected (mix)</th>\n",
|
| 125 |
+
" <th>tempo_bpm_original (mix)</th>\n",
|
| 126 |
+
" <th>mfcc_mean_1 (mix)</th>\n",
|
| 127 |
+
" <th>mfcc_mean_2 (mix)</th>\n",
|
| 128 |
+
" <th>loudness_range_lu custom (mix)</th>\n",
|
| 129 |
+
" <th>zero_crossing_rate (mix)</th>\n",
|
| 130 |
+
" <th>vocab_richness</th>\n",
|
| 131 |
+
" <th>loudness_integrated_lufs custom (mix)</th>\n",
|
| 132 |
+
" <th>readability_score</th>\n",
|
| 133 |
+
" <th>energy_essentia (mix)</th>\n",
|
| 134 |
+
" <th>energy_librosa (mix)</th>\n",
|
| 135 |
+
" <th>rms_energy_mean (mix)</th>\n",
|
| 136 |
+
" <th>sentiment_score</th>\n",
|
| 137 |
+
" <th>melody_complexity (vocals)</th>\n",
|
| 138 |
+
" <th>avg_word_length</th>\n",
|
| 139 |
+
" </tr>\n",
|
| 140 |
+
" </thead>\n",
|
| 141 |
+
" <tbody>\n",
|
| 142 |
+
" <tr>\n",
|
| 143 |
+
" <th>27949</th>\n",
|
| 144 |
+
" <td>69b29e6c05f5ce265b882008</td>\n",
|
| 145 |
+
" <td>Rock</td>\n",
|
| 146 |
+
" <td>Rock --- ['Indie Folk', 'Country Folk', 'Indie...</td>\n",
|
| 147 |
+
" <td>191.332000</td>\n",
|
| 148 |
+
" <td>287.0</td>\n",
|
| 149 |
+
" <td>197.0</td>\n",
|
| 150 |
+
" <td>19.647192</td>\n",
|
| 151 |
+
" <td>0.453619</td>\n",
|
| 152 |
+
" <td>0.479049</td>\n",
|
| 153 |
+
" <td>1.927778</td>\n",
|
| 154 |
+
" <td>0.093420</td>\n",
|
| 155 |
+
" <td>0.006936</td>\n",
|
| 156 |
+
" <td>56.905636</td>\n",
|
| 157 |
+
" <td>123.635239</td>\n",
|
| 158 |
+
" <td>123.635239</td>\n",
|
| 159 |
+
" <td>194.688721</td>\n",
|
| 160 |
+
" <td>-3.499274</td>\n",
|
| 161 |
+
" <td>6.624297</td>\n",
|
| 162 |
+
" <td>0.032766</td>\n",
|
| 163 |
+
" <td>0.325</td>\n",
|
| 164 |
+
" <td>-16.190006</td>\n",
|
| 165 |
+
" <td>76.6</td>\n",
|
| 166 |
+
" <td>0.259487</td>\n",
|
| 167 |
+
" <td>0.465622</td>\n",
|
| 168 |
+
" <td>0.466699</td>\n",
|
| 169 |
+
" <td>0.035</td>\n",
|
| 170 |
+
" <td>2.666667</td>\n",
|
| 171 |
+
" <td>4.67</td>\n",
|
| 172 |
+
" </tr>\n",
|
| 173 |
+
" <tr>\n",
|
| 174 |
+
" <th>40690</th>\n",
|
| 175 |
+
" <td>69b12fabebd9b3c1ca39e859</td>\n",
|
| 176 |
+
" <td>Country</td>\n",
|
| 177 |
+
" <td>Country --- ['Americana', 'Country Rock', 'Alt...</td>\n",
|
| 178 |
+
" <td>215.146667</td>\n",
|
| 179 |
+
" <td>245.0</td>\n",
|
| 180 |
+
" <td>200.0</td>\n",
|
| 181 |
+
" <td>19.253367</td>\n",
|
| 182 |
+
" <td>0.484467</td>\n",
|
| 183 |
+
" <td>0.600583</td>\n",
|
| 184 |
+
" <td>4.220377</td>\n",
|
| 185 |
+
" <td>0.111674</td>\n",
|
| 186 |
+
" <td>0.005806</td>\n",
|
| 187 |
+
" <td>70.522621</td>\n",
|
| 188 |
+
" <td>84.574928</td>\n",
|
| 189 |
+
" <td>84.574928</td>\n",
|
| 190 |
+
" <td>178.628433</td>\n",
|
| 191 |
+
" <td>-50.666416</td>\n",
|
| 192 |
+
" <td>3.653197</td>\n",
|
| 193 |
+
" <td>0.049714</td>\n",
|
| 194 |
+
" <td>0.345</td>\n",
|
| 195 |
+
" <td>-13.794414</td>\n",
|
| 196 |
+
" <td>2.1</td>\n",
|
| 197 |
+
" <td>0.330070</td>\n",
|
| 198 |
+
" <td>0.547306</td>\n",
|
| 199 |
+
" <td>0.547923</td>\n",
|
| 200 |
+
" <td>0.336</td>\n",
|
| 201 |
+
" <td>3.250000</td>\n",
|
| 202 |
+
" <td>3.76</td>\n",
|
| 203 |
+
" </tr>\n",
|
| 204 |
+
" <tr>\n",
|
| 205 |
+
" <th>18977</th>\n",
|
| 206 |
+
" <td>692969b1c5db350c7bdaf13d</td>\n",
|
| 207 |
+
" <td>Rock</td>\n",
|
| 208 |
+
" <td>Rock --- ['Noise Pop', 'Classic Alternative Ro...</td>\n",
|
| 209 |
+
" <td>203.948458</td>\n",
|
| 210 |
+
" <td>145.0</td>\n",
|
| 211 |
+
" <td>118.0</td>\n",
|
| 212 |
+
" <td>18.226969</td>\n",
|
| 213 |
+
" <td>0.532658</td>\n",
|
| 214 |
+
" <td>0.647673</td>\n",
|
| 215 |
+
" <td>1.694444</td>\n",
|
| 216 |
+
" <td>0.124794</td>\n",
|
| 217 |
+
" <td>0.006610</td>\n",
|
| 218 |
+
" <td>145.266922</td>\n",
|
| 219 |
+
" <td>85.543114</td>\n",
|
| 220 |
+
" <td>85.543114</td>\n",
|
| 221 |
+
" <td>164.830765</td>\n",
|
| 222 |
+
" <td>-53.442635</td>\n",
|
| 223 |
+
" <td>1.909515</td>\n",
|
| 224 |
+
" <td>0.059000</td>\n",
|
| 225 |
+
" <td>0.542</td>\n",
|
| 226 |
+
" <td>-11.325106</td>\n",
|
| 227 |
+
" <td>1.4</td>\n",
|
| 228 |
+
" <td>0.439053</td>\n",
|
| 229 |
+
" <td>0.604893</td>\n",
|
| 230 |
+
" <td>0.661765</td>\n",
|
| 231 |
+
" <td>-0.013</td>\n",
|
| 232 |
+
" <td>2.916667</td>\n",
|
| 233 |
+
" <td>3.42</td>\n",
|
| 234 |
+
" </tr>\n",
|
| 235 |
+
" </tbody>\n",
|
| 236 |
+
"</table>\n",
|
| 237 |
+
"</div>"
|
| 238 |
+
]
|
| 239 |
+
},
|
| 240 |
+
"execution_count": 1,
|
| 241 |
+
"metadata": {},
|
| 242 |
+
"output_type": "execute_result"
|
| 243 |
+
}
|
| 244 |
+
],
|
| 245 |
+
"execution_count": 1
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"metadata": {
|
| 249 |
+
"ExecuteTime": {
|
| 250 |
+
"end_time": "2026-03-30T08:56:11.085130700Z",
|
| 251 |
+
"start_time": "2026-03-30T08:56:10.592907600Z"
|
| 252 |
+
}
|
| 253 |
+
},
|
| 254 |
+
"cell_type": "code",
|
| 255 |
+
"source": [
|
| 256 |
+
"def parse_subgenres(text):\n",
|
| 257 |
+
" if pd.isna(text) or text == \"\":\n",
|
| 258 |
+
" return []\n",
|
| 259 |
+
" match = re.search(r\"\\[(.*?)\\]\", str(text))\n",
|
| 260 |
+
" if match:\n",
|
| 261 |
+
" try:\n",
|
| 262 |
+
" genre_list = ast.literal_eval(\"[\" + match.group(1) + \"]\")\n",
|
| 263 |
+
" return [g.strip() for g in genre_list if g.strip()]\n",
|
| 264 |
+
" except:\n",
|
| 265 |
+
" return []\n",
|
| 266 |
+
" return []\n",
|
| 267 |
+
"\n",
|
| 268 |
+
"df['subgenres_list'] = df['genre_subgenre'].apply(parse_subgenres)\n",
|
| 269 |
+
"\n"
|
| 270 |
+
],
|
| 271 |
+
"id": "67098b6deadbc3ef",
|
| 272 |
+
"outputs": [],
|
| 273 |
+
"execution_count": 2
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"metadata": {
|
| 277 |
+
"ExecuteTime": {
|
| 278 |
+
"end_time": "2026-03-30T08:56:23.372487600Z",
|
| 279 |
+
"start_time": "2026-03-30T08:56:23.315805900Z"
|
| 280 |
+
}
|
| 281 |
+
},
|
| 282 |
+
"cell_type": "code",
|
| 283 |
+
"source": "df['subgenres_list'][0]",
|
| 284 |
+
"id": "e0fc9a9de1291e67",
|
| 285 |
+
"outputs": [
|
| 286 |
+
{
|
| 287 |
+
"data": {
|
| 288 |
+
"text/plain": [
|
| 289 |
+
"['Noise Pop', 'Classic Alternative Rock', 'Indie Rock']"
|
| 290 |
+
]
|
| 291 |
+
},
|
| 292 |
+
"execution_count": 3,
|
| 293 |
+
"metadata": {},
|
| 294 |
+
"output_type": "execute_result"
|
| 295 |
+
}
|
| 296 |
+
],
|
| 297 |
+
"execution_count": 3
|
| 298 |
+
},
|
| 299 |
+
{
|
| 300 |
+
"metadata": {},
|
| 301 |
+
"cell_type": "code",
|
| 302 |
+
"outputs": [],
|
| 303 |
+
"execution_count": null,
|
| 304 |
+
"source": [
|
| 305 |
+
"mlb = MultiLabelBinarizer()\n",
|
| 306 |
+
"y = mlb.fit_transform(df['subgenres_list'])\n",
|
| 307 |
+
"\n",
|
| 308 |
+
"drop_cols = ['_id', 'genre', 'genre_subgenre', 'subgenres_list', 'labels',\n",
|
| 309 |
+
" 'trimmed_audio_duration_sec', 'syllable_count', 'word_count',\n",
|
| 310 |
+
" 'sentiment_score', 'avg_word_length']\n",
|
| 311 |
+
"\n",
|
| 312 |
+
"feature_cols = [c for c in df.columns if c not in drop_cols]\n",
|
| 313 |
+
"X = df[feature_cols].copy()\n",
|
| 314 |
+
"X = X.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 315 |
+
"\n",
|
| 316 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 317 |
+
"\n",
|
| 318 |
+
"scaler = StandardScaler()\n",
|
| 319 |
+
"X_train_scaled = scaler.fit_transform(X_train)\n",
|
| 320 |
+
"X_test_scaled = scaler.transform(X_test)\n",
|
| 321 |
+
"\n",
|
| 322 |
+
"model = MultiOutputClassifier(\n",
|
| 323 |
+
" XGBClassifier(\n",
|
| 324 |
+
" n_estimators=1500,\n",
|
| 325 |
+
" learning_rate=0.03,\n",
|
| 326 |
+
" max_depth=8,\n",
|
| 327 |
+
" subsample=0.85,\n",
|
| 328 |
+
" colsample_bytree=0.8,\n",
|
| 329 |
+
" random_state=42,\n",
|
| 330 |
+
" eval_metric='logloss'\n",
|
| 331 |
+
" ),\n",
|
| 332 |
+
" n_jobs=-1\n",
|
| 333 |
+
")\n",
|
| 334 |
+
"\n",
|
| 335 |
+
"model.fit(X_train_scaled, y_train)\n",
|
| 336 |
+
"\n",
|
| 337 |
+
"\n",
|
| 338 |
+
"def get_accuracy(X_scaled, y_true, name=\"\"):\n",
|
| 339 |
+
" y_pred = model.predict(X_scaled)\n",
|
| 340 |
+
" subset_acc = np.mean(np.all(y_pred == y_true, axis=1))\n",
|
| 341 |
+
" micro_f1 = f1_score(y_true, y_pred, average='micro')\n",
|
| 342 |
+
" print(f\"{name} Subset Accuracy: {subset_acc:.4f}\")\n",
|
| 343 |
+
" print(f\"{name} Micro F1 Score: {micro_f1:.4f}\")\n",
|
| 344 |
+
"\n",
|
| 345 |
+
"print(\"=== MODEL ACCURACY ===\")\n",
|
| 346 |
+
"get_accuracy(X_train_scaled, y_train, \"Train\")\n",
|
| 347 |
+
"get_accuracy(X_test_scaled, y_test, \"Test\")\n",
|
| 348 |
+
"\n",
|
| 349 |
+
"def manual_predict(row):\n",
|
| 350 |
+
" input_df = pd.DataFrame([row])\n",
|
| 351 |
+
" input_numeric = input_df.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 352 |
+
"\n",
|
| 353 |
+
" for col in X.columns:\n",
|
| 354 |
+
" if col not in input_numeric.columns:\n",
|
| 355 |
+
" input_numeric[col] = 0\n",
|
| 356 |
+
"\n",
|
| 357 |
+
" input_numeric = input_numeric[X.columns]\n",
|
| 358 |
+
" input_scaled = scaler.transform(input_numeric)\n",
|
| 359 |
+
"\n",
|
| 360 |
+
" probas = model.predict_proba(input_scaled)\n",
|
| 361 |
+
" threshold = 0.20\n",
|
| 362 |
+
" pred_binary = np.zeros((1, y.shape[1]), dtype=int)\n",
|
| 363 |
+
"\n",
|
| 364 |
+
" for i in range(len(probas)):\n",
|
| 365 |
+
" if probas[i][0][1] > threshold:\n",
|
| 366 |
+
" pred_binary[0, i] = 1\n",
|
| 367 |
+
"\n",
|
| 368 |
+
" subgenres = mlb.inverse_transform(pred_binary)[0]\n",
|
| 369 |
+
" #main_genre = row.get('genre', 'Rock')\n",
|
| 370 |
+
"\n",
|
| 371 |
+
" if len(subgenres) == 0:\n",
|
| 372 |
+
" return f\"None\"\n",
|
| 373 |
+
" return f\"{', '.join(sorted(subgenres))}\""
|
| 374 |
+
],
|
| 375 |
+
"id": "c1086565f89c486b"
|
| 376 |
+
},
|
| 377 |
+
{
|
| 378 |
+
"metadata": {
|
| 379 |
+
"ExecuteTime": {
|
| 380 |
+
"end_time": "2026-03-27T14:45:34.721003500Z",
|
| 381 |
+
"start_time": "2026-03-27T14:45:34.665870Z"
|
| 382 |
+
}
|
| 383 |
+
},
|
| 384 |
+
"cell_type": "code",
|
| 385 |
+
"source": "X.head()",
|
| 386 |
+
"id": "7036f929b47b9932",
|
| 387 |
+
"outputs": [
|
| 388 |
+
{
|
| 389 |
+
"data": {
|
| 390 |
+
"text/plain": [
|
| 391 |
+
" spectral_contrast_mean (mix) chroma_mean (mix) \\\n",
|
| 392 |
+
"0 18.938621 0.462013 \n",
|
| 393 |
+
"1 18.361412 0.515950 \n",
|
| 394 |
+
"2 17.545991 0.505095 \n",
|
| 395 |
+
"3 19.065528 0.532723 \n",
|
| 396 |
+
"4 18.549622 0.560616 \n",
|
| 397 |
+
"\n",
|
| 398 |
+
" melody_variability (vocals) rhythm_onset_rate (mix) \\\n",
|
| 399 |
+
"0 0.887239 0.550994 \n",
|
| 400 |
+
"1 0.553844 1.785610 \n",
|
| 401 |
+
"2 0.887161 1.691063 \n",
|
| 402 |
+
"3 0.917722 4.237960 \n",
|
| 403 |
+
"4 0.967337 0.847676 \n",
|
| 404 |
+
"\n",
|
| 405 |
+
" spectral_centroid_mean custom (mix) repetition_score custom (mix) \\\n",
|
| 406 |
+
"0 0.124953 0.015320 \n",
|
| 407 |
+
"1 0.178827 0.010278 \n",
|
| 408 |
+
"2 0.115442 0.010617 \n",
|
| 409 |
+
"3 0.134564 0.009808 \n",
|
| 410 |
+
"4 0.179751 0.008122 \n",
|
| 411 |
+
"\n",
|
| 412 |
+
" pitch_std (mix) tempo_bpm_corrected (mix) tempo_bpm_original (mix) \\\n",
|
| 413 |
+
"0 170.196686 117.459671 117.459671 \n",
|
| 414 |
+
"1 174.526428 93.132881 93.132881 \n",
|
| 415 |
+
"2 98.435074 105.041779 105.041779 \n",
|
| 416 |
+
"3 144.853897 94.973419 94.973419 \n",
|
| 417 |
+
"4 94.283890 108.527031 108.527031 \n",
|
| 418 |
+
"\n",
|
| 419 |
+
" mfcc_mean_1 (mix) mfcc_mean_2 (mix) loudness_range_lu custom (mix) \\\n",
|
| 420 |
+
"0 159.208038 -47.135399 17.099110 \n",
|
| 421 |
+
"1 124.982330 -54.048897 10.356828 \n",
|
| 422 |
+
"2 178.284927 -41.860600 4.583549 \n",
|
| 423 |
+
"3 166.110214 -79.556442 3.334763 \n",
|
| 424 |
+
"4 118.163094 -54.811043 0.855955 \n",
|
| 425 |
+
"\n",
|
| 426 |
+
" zero_crossing_rate (mix) vocab_richness \\\n",
|
| 427 |
+
"0 0.065750 0.842 \n",
|
| 428 |
+
"1 0.115698 0.312 \n",
|
| 429 |
+
"2 0.050672 0.462 \n",
|
| 430 |
+
"3 0.085350 0.273 \n",
|
| 431 |
+
"4 0.104765 0.350 \n",
|
| 432 |
+
"\n",
|
| 433 |
+
" loudness_integrated_lufs custom (mix) readability_score \\\n",
|
| 434 |
+
"0 -17.450430 1.6 \n",
|
| 435 |
+
"1 -14.291489 5.2 \n",
|
| 436 |
+
"2 -14.021948 18.8 \n",
|
| 437 |
+
"3 -11.496668 1.7 \n",
|
| 438 |
+
"4 -10.031396 52.0 \n",
|
| 439 |
+
"\n",
|
| 440 |
+
" energy_essentia (mix) energy_librosa (mix) rms_energy_mean (mix) \\\n",
|
| 441 |
+
"0 0.400498 0.541834 0.545006 \n",
|
| 442 |
+
"1 0.286286 0.491097 0.492117 \n",
|
| 443 |
+
"2 0.330273 0.539636 0.541554 \n",
|
| 444 |
+
"3 0.525465 0.693286 0.702081 \n",
|
| 445 |
+
"4 0.498986 0.696552 0.698920 \n",
|
| 446 |
+
"\n",
|
| 447 |
+
" melody_complexity (vocals) \n",
|
| 448 |
+
"0 2.833333 \n",
|
| 449 |
+
"1 2.333333 \n",
|
| 450 |
+
"2 3.083333 \n",
|
| 451 |
+
"3 3.166667 \n",
|
| 452 |
+
"4 3.500000 "
|
| 453 |
+
],
|
| 454 |
+
"text/html": [
|
| 455 |
+
"<div>\n",
|
| 456 |
+
"<style scoped>\n",
|
| 457 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 458 |
+
" vertical-align: middle;\n",
|
| 459 |
+
" }\n",
|
| 460 |
+
"\n",
|
| 461 |
+
" .dataframe tbody tr th {\n",
|
| 462 |
+
" vertical-align: top;\n",
|
| 463 |
+
" }\n",
|
| 464 |
+
"\n",
|
| 465 |
+
" .dataframe thead th {\n",
|
| 466 |
+
" text-align: right;\n",
|
| 467 |
+
" }\n",
|
| 468 |
+
"</style>\n",
|
| 469 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 470 |
+
" <thead>\n",
|
| 471 |
+
" <tr style=\"text-align: right;\">\n",
|
| 472 |
+
" <th></th>\n",
|
| 473 |
+
" <th>spectral_contrast_mean (mix)</th>\n",
|
| 474 |
+
" <th>chroma_mean (mix)</th>\n",
|
| 475 |
+
" <th>melody_variability (vocals)</th>\n",
|
| 476 |
+
" <th>rhythm_onset_rate (mix)</th>\n",
|
| 477 |
+
" <th>spectral_centroid_mean custom (mix)</th>\n",
|
| 478 |
+
" <th>repetition_score custom (mix)</th>\n",
|
| 479 |
+
" <th>pitch_std (mix)</th>\n",
|
| 480 |
+
" <th>tempo_bpm_corrected (mix)</th>\n",
|
| 481 |
+
" <th>tempo_bpm_original (mix)</th>\n",
|
| 482 |
+
" <th>mfcc_mean_1 (mix)</th>\n",
|
| 483 |
+
" <th>mfcc_mean_2 (mix)</th>\n",
|
| 484 |
+
" <th>loudness_range_lu custom (mix)</th>\n",
|
| 485 |
+
" <th>zero_crossing_rate (mix)</th>\n",
|
| 486 |
+
" <th>vocab_richness</th>\n",
|
| 487 |
+
" <th>loudness_integrated_lufs custom (mix)</th>\n",
|
| 488 |
+
" <th>readability_score</th>\n",
|
| 489 |
+
" <th>energy_essentia (mix)</th>\n",
|
| 490 |
+
" <th>energy_librosa (mix)</th>\n",
|
| 491 |
+
" <th>rms_energy_mean (mix)</th>\n",
|
| 492 |
+
" <th>melody_complexity (vocals)</th>\n",
|
| 493 |
+
" </tr>\n",
|
| 494 |
+
" </thead>\n",
|
| 495 |
+
" <tbody>\n",
|
| 496 |
+
" <tr>\n",
|
| 497 |
+
" <th>0</th>\n",
|
| 498 |
+
" <td>18.938621</td>\n",
|
| 499 |
+
" <td>0.462013</td>\n",
|
| 500 |
+
" <td>0.887239</td>\n",
|
| 501 |
+
" <td>0.550994</td>\n",
|
| 502 |
+
" <td>0.124953</td>\n",
|
| 503 |
+
" <td>0.015320</td>\n",
|
| 504 |
+
" <td>170.196686</td>\n",
|
| 505 |
+
" <td>117.459671</td>\n",
|
| 506 |
+
" <td>117.459671</td>\n",
|
| 507 |
+
" <td>159.208038</td>\n",
|
| 508 |
+
" <td>-47.135399</td>\n",
|
| 509 |
+
" <td>17.099110</td>\n",
|
| 510 |
+
" <td>0.065750</td>\n",
|
| 511 |
+
" <td>0.842</td>\n",
|
| 512 |
+
" <td>-17.450430</td>\n",
|
| 513 |
+
" <td>1.6</td>\n",
|
| 514 |
+
" <td>0.400498</td>\n",
|
| 515 |
+
" <td>0.541834</td>\n",
|
| 516 |
+
" <td>0.545006</td>\n",
|
| 517 |
+
" <td>2.833333</td>\n",
|
| 518 |
+
" </tr>\n",
|
| 519 |
+
" <tr>\n",
|
| 520 |
+
" <th>1</th>\n",
|
| 521 |
+
" <td>18.361412</td>\n",
|
| 522 |
+
" <td>0.515950</td>\n",
|
| 523 |
+
" <td>0.553844</td>\n",
|
| 524 |
+
" <td>1.785610</td>\n",
|
| 525 |
+
" <td>0.178827</td>\n",
|
| 526 |
+
" <td>0.010278</td>\n",
|
| 527 |
+
" <td>174.526428</td>\n",
|
| 528 |
+
" <td>93.132881</td>\n",
|
| 529 |
+
" <td>93.132881</td>\n",
|
| 530 |
+
" <td>124.982330</td>\n",
|
| 531 |
+
" <td>-54.048897</td>\n",
|
| 532 |
+
" <td>10.356828</td>\n",
|
| 533 |
+
" <td>0.115698</td>\n",
|
| 534 |
+
" <td>0.312</td>\n",
|
| 535 |
+
" <td>-14.291489</td>\n",
|
| 536 |
+
" <td>5.2</td>\n",
|
| 537 |
+
" <td>0.286286</td>\n",
|
| 538 |
+
" <td>0.491097</td>\n",
|
| 539 |
+
" <td>0.492117</td>\n",
|
| 540 |
+
" <td>2.333333</td>\n",
|
| 541 |
+
" </tr>\n",
|
| 542 |
+
" <tr>\n",
|
| 543 |
+
" <th>2</th>\n",
|
| 544 |
+
" <td>17.545991</td>\n",
|
| 545 |
+
" <td>0.505095</td>\n",
|
| 546 |
+
" <td>0.887161</td>\n",
|
| 547 |
+
" <td>1.691063</td>\n",
|
| 548 |
+
" <td>0.115442</td>\n",
|
| 549 |
+
" <td>0.010617</td>\n",
|
| 550 |
+
" <td>98.435074</td>\n",
|
| 551 |
+
" <td>105.041779</td>\n",
|
| 552 |
+
" <td>105.041779</td>\n",
|
| 553 |
+
" <td>178.284927</td>\n",
|
| 554 |
+
" <td>-41.860600</td>\n",
|
| 555 |
+
" <td>4.583549</td>\n",
|
| 556 |
+
" <td>0.050672</td>\n",
|
| 557 |
+
" <td>0.462</td>\n",
|
| 558 |
+
" <td>-14.021948</td>\n",
|
| 559 |
+
" <td>18.8</td>\n",
|
| 560 |
+
" <td>0.330273</td>\n",
|
| 561 |
+
" <td>0.539636</td>\n",
|
| 562 |
+
" <td>0.541554</td>\n",
|
| 563 |
+
" <td>3.083333</td>\n",
|
| 564 |
+
" </tr>\n",
|
| 565 |
+
" <tr>\n",
|
| 566 |
+
" <th>3</th>\n",
|
| 567 |
+
" <td>19.065528</td>\n",
|
| 568 |
+
" <td>0.532723</td>\n",
|
| 569 |
+
" <td>0.917722</td>\n",
|
| 570 |
+
" <td>4.237960</td>\n",
|
| 571 |
+
" <td>0.134564</td>\n",
|
| 572 |
+
" <td>0.009808</td>\n",
|
| 573 |
+
" <td>144.853897</td>\n",
|
| 574 |
+
" <td>94.973419</td>\n",
|
| 575 |
+
" <td>94.973419</td>\n",
|
| 576 |
+
" <td>166.110214</td>\n",
|
| 577 |
+
" <td>-79.556442</td>\n",
|
| 578 |
+
" <td>3.334763</td>\n",
|
| 579 |
+
" <td>0.085350</td>\n",
|
| 580 |
+
" <td>0.273</td>\n",
|
| 581 |
+
" <td>-11.496668</td>\n",
|
| 582 |
+
" <td>1.7</td>\n",
|
| 583 |
+
" <td>0.525465</td>\n",
|
| 584 |
+
" <td>0.693286</td>\n",
|
| 585 |
+
" <td>0.702081</td>\n",
|
| 586 |
+
" <td>3.166667</td>\n",
|
| 587 |
+
" </tr>\n",
|
| 588 |
+
" <tr>\n",
|
| 589 |
+
" <th>4</th>\n",
|
| 590 |
+
" <td>18.549622</td>\n",
|
| 591 |
+
" <td>0.560616</td>\n",
|
| 592 |
+
" <td>0.967337</td>\n",
|
| 593 |
+
" <td>0.847676</td>\n",
|
| 594 |
+
" <td>0.179751</td>\n",
|
| 595 |
+
" <td>0.008122</td>\n",
|
| 596 |
+
" <td>94.283890</td>\n",
|
| 597 |
+
" <td>108.527031</td>\n",
|
| 598 |
+
" <td>108.527031</td>\n",
|
| 599 |
+
" <td>118.163094</td>\n",
|
| 600 |
+
" <td>-54.811043</td>\n",
|
| 601 |
+
" <td>0.855955</td>\n",
|
| 602 |
+
" <td>0.104765</td>\n",
|
| 603 |
+
" <td>0.350</td>\n",
|
| 604 |
+
" <td>-10.031396</td>\n",
|
| 605 |
+
" <td>52.0</td>\n",
|
| 606 |
+
" <td>0.498986</td>\n",
|
| 607 |
+
" <td>0.696552</td>\n",
|
| 608 |
+
" <td>0.698920</td>\n",
|
| 609 |
+
" <td>3.500000</td>\n",
|
| 610 |
+
" </tr>\n",
|
| 611 |
+
" </tbody>\n",
|
| 612 |
+
"</table>\n",
|
| 613 |
+
"</div>"
|
| 614 |
+
]
|
| 615 |
+
},
|
| 616 |
+
"execution_count": 6,
|
| 617 |
+
"metadata": {},
|
| 618 |
+
"output_type": "execute_result"
|
| 619 |
+
}
|
| 620 |
+
],
|
| 621 |
+
"execution_count": 6
|
| 622 |
+
},
|
| 623 |
+
{
|
| 624 |
+
"metadata": {
|
| 625 |
+
"ExecuteTime": {
|
| 626 |
+
"end_time": "2026-03-27T14:55:56.357032800Z",
|
| 627 |
+
"start_time": "2026-03-27T14:55:56.275652600Z"
|
| 628 |
+
}
|
| 629 |
+
},
|
| 630 |
+
"cell_type": "code",
|
| 631 |
+
"source": "X.shape",
|
| 632 |
+
"id": "f7d2011c6b5f8699",
|
| 633 |
+
"outputs": [
|
| 634 |
+
{
|
| 635 |
+
"data": {
|
| 636 |
+
"text/plain": [
|
| 637 |
+
"(41935, 20)"
|
| 638 |
+
]
|
| 639 |
+
},
|
| 640 |
+
"execution_count": 15,
|
| 641 |
+
"metadata": {},
|
| 642 |
+
"output_type": "execute_result"
|
| 643 |
+
}
|
| 644 |
+
],
|
| 645 |
+
"execution_count": 15
|
| 646 |
+
},
|
| 647 |
+
{
|
| 648 |
+
"metadata": {
|
| 649 |
+
"ExecuteTime": {
|
| 650 |
+
"end_time": "2026-03-27T15:10:02.865313500Z",
|
| 651 |
+
"start_time": "2026-03-27T15:10:02.807027900Z"
|
| 652 |
+
}
|
| 653 |
+
},
|
| 654 |
+
"cell_type": "code",
|
| 655 |
+
"source": "df[[\"subgenres_list\",\"genre\"]].iloc[502,]",
|
| 656 |
+
"id": "d462801f323e88a",
|
| 657 |
+
"outputs": [
|
| 658 |
+
{
|
| 659 |
+
"data": {
|
| 660 |
+
"text/plain": [
|
| 661 |
+
"subgenres_list [Modern Alternative Rock, Indie Rock]\n",
|
| 662 |
+
"genre Rock\n",
|
| 663 |
+
"Name: 502, dtype: object"
|
| 664 |
+
]
|
| 665 |
+
},
|
| 666 |
+
"execution_count": 43,
|
| 667 |
+
"metadata": {},
|
| 668 |
+
"output_type": "execute_result"
|
| 669 |
+
}
|
| 670 |
+
],
|
| 671 |
+
"execution_count": 43
|
| 672 |
+
},
|
| 673 |
+
{
|
| 674 |
+
"metadata": {
|
| 675 |
+
"ExecuteTime": {
|
| 676 |
+
"end_time": "2026-03-27T15:13:50.114563800Z",
|
| 677 |
+
"start_time": "2026-03-27T15:13:50.060507800Z"
|
| 678 |
+
}
|
| 679 |
+
},
|
| 680 |
+
"cell_type": "code",
|
| 681 |
+
"source": [
|
| 682 |
+
"pd.set_option('display.max_columns', None)\n",
|
| 683 |
+
"df[(df[\"genre\"]==\"Metal\")].head(2)"
|
| 684 |
+
],
|
| 685 |
+
"id": "1fbd1d7876fb8996",
|
| 686 |
+
"outputs": [
|
| 687 |
+
{
|
| 688 |
+
"data": {
|
| 689 |
+
"text/plain": [
|
| 690 |
+
" _id genre \\\n",
|
| 691 |
+
"41856 691b00908e8610886093475b Metal \n",
|
| 692 |
+
"41857 691c4b322bf3d72bd0be023e Metal \n",
|
| 693 |
+
"\n",
|
| 694 |
+
" genre_subgenre \\\n",
|
| 695 |
+
"41856 Metal --- ['Black Metal', 'Death Metal', 'Doom... \n",
|
| 696 |
+
"41857 Metal --- ['Heavy Metal', 'Thrash Metal'] \n",
|
| 697 |
+
"\n",
|
| 698 |
+
" trimmed_audio_duration_sec syllable_count word_count \\\n",
|
| 699 |
+
"41856 185.602971 185.0 158.0 \n",
|
| 700 |
+
"41857 131.819683 353.0 253.0 \n",
|
| 701 |
+
"\n",
|
| 702 |
+
" spectral_contrast_mean (mix) chroma_mean (mix) \\\n",
|
| 703 |
+
"41856 20.152063 0.408171 \n",
|
| 704 |
+
"41857 19.495984 0.517214 \n",
|
| 705 |
+
"\n",
|
| 706 |
+
" melody_variability (vocals) rhythm_onset_rate (mix) \\\n",
|
| 707 |
+
"41856 0.374856 2.027778 \n",
|
| 708 |
+
"41857 0.475671 5.803382 \n",
|
| 709 |
+
"\n",
|
| 710 |
+
" spectral_centroid_mean custom (mix) repetition_score custom (mix) \\\n",
|
| 711 |
+
"41856 0.093899 0.007882 \n",
|
| 712 |
+
"41857 0.168338 0.007993 \n",
|
| 713 |
+
"\n",
|
| 714 |
+
" pitch_std (mix) tempo_bpm_corrected (mix) tempo_bpm_original (mix) \\\n",
|
| 715 |
+
"41856 108.278755 104.605293 104.605293 \n",
|
| 716 |
+
"41857 134.086761 138.027847 138.027847 \n",
|
| 717 |
+
"\n",
|
| 718 |
+
" mfcc_mean_1 (mix) mfcc_mean_2 (mix) loudness_range_lu custom (mix) \\\n",
|
| 719 |
+
"41856 163.308655 22.928051 7.131201 \n",
|
| 720 |
+
"41857 113.692780 -26.532072 2.994879 \n",
|
| 721 |
+
"\n",
|
| 722 |
+
" zero_crossing_rate (mix) vocab_richness \\\n",
|
| 723 |
+
"41856 0.026933 0.582 \n",
|
| 724 |
+
"41857 0.069337 0.352 \n",
|
| 725 |
+
"\n",
|
| 726 |
+
" loudness_integrated_lufs custom (mix) readability_score \\\n",
|
| 727 |
+
"41856 -18.566132 27.8 \n",
|
| 728 |
+
"41857 -13.850066 3.0 \n",
|
| 729 |
+
"\n",
|
| 730 |
+
" energy_essentia (mix) energy_librosa (mix) rms_energy_mean (mix) \\\n",
|
| 731 |
+
"41856 0.153738 0.335069 0.346289 \n",
|
| 732 |
+
"41857 0.266081 0.441120 0.467461 \n",
|
| 733 |
+
"\n",
|
| 734 |
+
" sentiment_score melody_complexity (vocals) avg_word_length \\\n",
|
| 735 |
+
"41856 0.050 1.916667 3.97 \n",
|
| 736 |
+
"41857 0.128 2.250000 4.24 \n",
|
| 737 |
+
"\n",
|
| 738 |
+
" subgenres_list \n",
|
| 739 |
+
"41856 [Black Metal, Death Metal, Doom Metal, Heavy M... \n",
|
| 740 |
+
"41857 [Heavy Metal, Thrash Metal] "
|
| 741 |
+
],
|
| 742 |
+
"text/html": [
|
| 743 |
+
"<div>\n",
|
| 744 |
+
"<style scoped>\n",
|
| 745 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 746 |
+
" vertical-align: middle;\n",
|
| 747 |
+
" }\n",
|
| 748 |
+
"\n",
|
| 749 |
+
" .dataframe tbody tr th {\n",
|
| 750 |
+
" vertical-align: top;\n",
|
| 751 |
+
" }\n",
|
| 752 |
+
"\n",
|
| 753 |
+
" .dataframe thead th {\n",
|
| 754 |
+
" text-align: right;\n",
|
| 755 |
+
" }\n",
|
| 756 |
+
"</style>\n",
|
| 757 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 758 |
+
" <thead>\n",
|
| 759 |
+
" <tr style=\"text-align: right;\">\n",
|
| 760 |
+
" <th></th>\n",
|
| 761 |
+
" <th>_id</th>\n",
|
| 762 |
+
" <th>genre</th>\n",
|
| 763 |
+
" <th>genre_subgenre</th>\n",
|
| 764 |
+
" <th>trimmed_audio_duration_sec</th>\n",
|
| 765 |
+
" <th>syllable_count</th>\n",
|
| 766 |
+
" <th>word_count</th>\n",
|
| 767 |
+
" <th>spectral_contrast_mean (mix)</th>\n",
|
| 768 |
+
" <th>chroma_mean (mix)</th>\n",
|
| 769 |
+
" <th>melody_variability (vocals)</th>\n",
|
| 770 |
+
" <th>rhythm_onset_rate (mix)</th>\n",
|
| 771 |
+
" <th>spectral_centroid_mean custom (mix)</th>\n",
|
| 772 |
+
" <th>repetition_score custom (mix)</th>\n",
|
| 773 |
+
" <th>pitch_std (mix)</th>\n",
|
| 774 |
+
" <th>tempo_bpm_corrected (mix)</th>\n",
|
| 775 |
+
" <th>tempo_bpm_original (mix)</th>\n",
|
| 776 |
+
" <th>mfcc_mean_1 (mix)</th>\n",
|
| 777 |
+
" <th>mfcc_mean_2 (mix)</th>\n",
|
| 778 |
+
" <th>loudness_range_lu custom (mix)</th>\n",
|
| 779 |
+
" <th>zero_crossing_rate (mix)</th>\n",
|
| 780 |
+
" <th>vocab_richness</th>\n",
|
| 781 |
+
" <th>loudness_integrated_lufs custom (mix)</th>\n",
|
| 782 |
+
" <th>readability_score</th>\n",
|
| 783 |
+
" <th>energy_essentia (mix)</th>\n",
|
| 784 |
+
" <th>energy_librosa (mix)</th>\n",
|
| 785 |
+
" <th>rms_energy_mean (mix)</th>\n",
|
| 786 |
+
" <th>sentiment_score</th>\n",
|
| 787 |
+
" <th>melody_complexity (vocals)</th>\n",
|
| 788 |
+
" <th>avg_word_length</th>\n",
|
| 789 |
+
" <th>subgenres_list</th>\n",
|
| 790 |
+
" </tr>\n",
|
| 791 |
+
" </thead>\n",
|
| 792 |
+
" <tbody>\n",
|
| 793 |
+
" <tr>\n",
|
| 794 |
+
" <th>41856</th>\n",
|
| 795 |
+
" <td>691b00908e8610886093475b</td>\n",
|
| 796 |
+
" <td>Metal</td>\n",
|
| 797 |
+
" <td>Metal --- ['Black Metal', 'Death Metal', 'Doom...</td>\n",
|
| 798 |
+
" <td>185.602971</td>\n",
|
| 799 |
+
" <td>185.0</td>\n",
|
| 800 |
+
" <td>158.0</td>\n",
|
| 801 |
+
" <td>20.152063</td>\n",
|
| 802 |
+
" <td>0.408171</td>\n",
|
| 803 |
+
" <td>0.374856</td>\n",
|
| 804 |
+
" <td>2.027778</td>\n",
|
| 805 |
+
" <td>0.093899</td>\n",
|
| 806 |
+
" <td>0.007882</td>\n",
|
| 807 |
+
" <td>108.278755</td>\n",
|
| 808 |
+
" <td>104.605293</td>\n",
|
| 809 |
+
" <td>104.605293</td>\n",
|
| 810 |
+
" <td>163.308655</td>\n",
|
| 811 |
+
" <td>22.928051</td>\n",
|
| 812 |
+
" <td>7.131201</td>\n",
|
| 813 |
+
" <td>0.026933</td>\n",
|
| 814 |
+
" <td>0.582</td>\n",
|
| 815 |
+
" <td>-18.566132</td>\n",
|
| 816 |
+
" <td>27.8</td>\n",
|
| 817 |
+
" <td>0.153738</td>\n",
|
| 818 |
+
" <td>0.335069</td>\n",
|
| 819 |
+
" <td>0.346289</td>\n",
|
| 820 |
+
" <td>0.050</td>\n",
|
| 821 |
+
" <td>1.916667</td>\n",
|
| 822 |
+
" <td>3.97</td>\n",
|
| 823 |
+
" <td>[Black Metal, Death Metal, Doom Metal, Heavy M...</td>\n",
|
| 824 |
+
" </tr>\n",
|
| 825 |
+
" <tr>\n",
|
| 826 |
+
" <th>41857</th>\n",
|
| 827 |
+
" <td>691c4b322bf3d72bd0be023e</td>\n",
|
| 828 |
+
" <td>Metal</td>\n",
|
| 829 |
+
" <td>Metal --- ['Heavy Metal', 'Thrash Metal']</td>\n",
|
| 830 |
+
" <td>131.819683</td>\n",
|
| 831 |
+
" <td>353.0</td>\n",
|
| 832 |
+
" <td>253.0</td>\n",
|
| 833 |
+
" <td>19.495984</td>\n",
|
| 834 |
+
" <td>0.517214</td>\n",
|
| 835 |
+
" <td>0.475671</td>\n",
|
| 836 |
+
" <td>5.803382</td>\n",
|
| 837 |
+
" <td>0.168338</td>\n",
|
| 838 |
+
" <td>0.007993</td>\n",
|
| 839 |
+
" <td>134.086761</td>\n",
|
| 840 |
+
" <td>138.027847</td>\n",
|
| 841 |
+
" <td>138.027847</td>\n",
|
| 842 |
+
" <td>113.692780</td>\n",
|
| 843 |
+
" <td>-26.532072</td>\n",
|
| 844 |
+
" <td>2.994879</td>\n",
|
| 845 |
+
" <td>0.069337</td>\n",
|
| 846 |
+
" <td>0.352</td>\n",
|
| 847 |
+
" <td>-13.850066</td>\n",
|
| 848 |
+
" <td>3.0</td>\n",
|
| 849 |
+
" <td>0.266081</td>\n",
|
| 850 |
+
" <td>0.441120</td>\n",
|
| 851 |
+
" <td>0.467461</td>\n",
|
| 852 |
+
" <td>0.128</td>\n",
|
| 853 |
+
" <td>2.250000</td>\n",
|
| 854 |
+
" <td>4.24</td>\n",
|
| 855 |
+
" <td>[Heavy Metal, Thrash Metal]</td>\n",
|
| 856 |
+
" </tr>\n",
|
| 857 |
+
" </tbody>\n",
|
| 858 |
+
"</table>\n",
|
| 859 |
+
"</div>"
|
| 860 |
+
]
|
| 861 |
+
},
|
| 862 |
+
"execution_count": 50,
|
| 863 |
+
"metadata": {},
|
| 864 |
+
"output_type": "execute_result"
|
| 865 |
+
}
|
| 866 |
+
],
|
| 867 |
+
"execution_count": 50
|
| 868 |
+
},
|
| 869 |
+
{
|
| 870 |
+
"metadata": {
|
| 871 |
+
"ExecuteTime": {
|
| 872 |
+
"end_time": "2026-03-27T15:24:32.000214700Z",
|
| 873 |
+
"start_time": "2026-03-27T15:24:31.938664600Z"
|
| 874 |
+
}
|
| 875 |
+
},
|
| 876 |
+
"cell_type": "code",
|
| 877 |
+
"source": "X.iloc[41856].to_dict()",
|
| 878 |
+
"id": "37ef9304bdae300c",
|
| 879 |
+
"outputs": [
|
| 880 |
+
{
|
| 881 |
+
"data": {
|
| 882 |
+
"text/plain": [
|
| 883 |
+
"{'spectral_contrast_mean (mix)': 20.1520629356514,\n",
|
| 884 |
+
" 'chroma_mean (mix)': 0.408170998096466,\n",
|
| 885 |
+
" 'melody_variability (vocals)': 0.3748557548841115,\n",
|
| 886 |
+
" 'rhythm_onset_rate (mix)': 2.0277777777777777,\n",
|
| 887 |
+
" 'spectral_centroid_mean custom (mix)': 0.0938994687064515,\n",
|
| 888 |
+
" 'repetition_score custom (mix)': 0.0078822056596288,\n",
|
| 889 |
+
" 'pitch_std (mix)': 108.27875518798828,\n",
|
| 890 |
+
" 'tempo_bpm_corrected (mix)': 104.60529327392578,\n",
|
| 891 |
+
" 'tempo_bpm_original (mix)': 104.60529327392578,\n",
|
| 892 |
+
" 'mfcc_mean_1 (mix)': 163.30865478515625,\n",
|
| 893 |
+
" 'mfcc_mean_2 (mix)': 22.928050994873047,\n",
|
| 894 |
+
" 'loudness_range_lu custom (mix)': 7.131200790405273,\n",
|
| 895 |
+
" 'zero_crossing_rate (mix)': 0.02693298086524,\n",
|
| 896 |
+
" 'vocab_richness': 0.582,\n",
|
| 897 |
+
" 'loudness_integrated_lufs custom (mix)': -18.566131591796875,\n",
|
| 898 |
+
" 'readability_score': 27.8,\n",
|
| 899 |
+
" 'energy_essentia (mix)': 0.1537376961168162,\n",
|
| 900 |
+
" 'energy_librosa (mix)': 0.335069430512918,\n",
|
| 901 |
+
" 'rms_energy_mean (mix)': 0.3462888819034169,\n",
|
| 902 |
+
" 'melody_complexity (vocals)': 1.9166666666666667}"
|
| 903 |
+
]
|
| 904 |
+
},
|
| 905 |
+
"execution_count": 58,
|
| 906 |
+
"metadata": {},
|
| 907 |
+
"output_type": "execute_result"
|
| 908 |
+
}
|
| 909 |
+
],
|
| 910 |
+
"execution_count": 58
|
| 911 |
+
},
|
| 912 |
+
{
|
| 913 |
+
"metadata": {
|
| 914 |
+
"ExecuteTime": {
|
| 915 |
+
"end_time": "2026-03-27T15:32:09.375740200Z",
|
| 916 |
+
"start_time": "2026-03-27T15:32:08.891439Z"
|
| 917 |
+
}
|
| 918 |
+
},
|
| 919 |
+
"cell_type": "code",
|
| 920 |
+
"source": [
|
| 921 |
+
"print(\"\\n=== FIRST ROW PREDICTION ===\")\n",
|
| 922 |
+
"print(manual_predict(X.iloc[508].to_dict()))\n",
|
| 923 |
+
"\n",
|
| 924 |
+
"print(\"\\n=== LAST ROW PREDICTION ===\")\n",
|
| 925 |
+
"print(manual_predict(X.iloc[-1].to_dict()))"
|
| 926 |
+
],
|
| 927 |
+
"id": "fef35c101a9a6ad",
|
| 928 |
+
"outputs": [
|
| 929 |
+
{
|
| 930 |
+
"name": "stdout",
|
| 931 |
+
"output_type": "stream",
|
| 932 |
+
"text": [
|
| 933 |
+
"\n",
|
| 934 |
+
"=== FIRST ROW PREDICTION ===\n",
|
| 935 |
+
"Indie Pop, Indie Rock, Modern Alternative Rock\n",
|
| 936 |
+
"\n",
|
| 937 |
+
"=== LAST ROW PREDICTION ===\n",
|
| 938 |
+
"Gothic Metal, Heavy Metal, Metalcore\n"
|
| 939 |
+
]
|
| 940 |
+
}
|
| 941 |
+
],
|
| 942 |
+
"execution_count": 63
|
| 943 |
+
},
|
| 944 |
+
{
|
| 945 |
+
"metadata": {
|
| 946 |
+
"ExecuteTime": {
|
| 947 |
+
"end_time": "2026-03-27T15:31:39.900012500Z",
|
| 948 |
+
"start_time": "2026-03-27T15:31:39.638999300Z"
|
| 949 |
+
}
|
| 950 |
+
},
|
| 951 |
+
"cell_type": "code",
|
| 952 |
+
"source": [
|
| 953 |
+
"audio_features_ = {\n",
|
| 954 |
+
" 'spectral_contrast_mean (mix)': 17.245,\n",
|
| 955 |
+
" 'chroma_mean (mix)': 0.482,\n",
|
| 956 |
+
" 'melody_variability (vocals)': 0.612,\n",
|
| 957 |
+
" 'rhythm_onset_rate (mix)': 2.145,\n",
|
| 958 |
+
" 'spectral_centroid_mean custom (mix)': 0.165,\n",
|
| 959 |
+
" 'repetition_score custom (mix)': 0.015,\n",
|
| 960 |
+
" 'pitch_std (mix)': 120.75,\n",
|
| 961 |
+
" 'tempo_bpm_corrected (mix)': 98.34,\n",
|
| 962 |
+
" 'tempo_bpm_original (mix)': 100.12,\n",
|
| 963 |
+
" 'mfcc_mean_1 (mix)': 128.56,\n",
|
| 964 |
+
" 'mfcc_mean_2 (mix)': -35.78,\n",
|
| 965 |
+
" 'loudness_range_lu custom (mix)': 8.45,\n",
|
| 966 |
+
" 'zero_crossing_rate (mix)': 0.089,\n",
|
| 967 |
+
" 'vocab_richness': 0.29,\n",
|
| 968 |
+
" 'loudness_integrated_lufs custom (mix)': -12.85,\n",
|
| 969 |
+
" 'readability_score': 48.5,\n",
|
| 970 |
+
" 'energy_essentia (mix)': 0.41,\n",
|
| 971 |
+
" 'energy_librosa (mix)': 0.52,\n",
|
| 972 |
+
" 'rms_energy_mean (mix)': 0.50,\n",
|
| 973 |
+
" 'melody_complexity (vocals)': 2.75\n",
|
| 974 |
+
"}\n",
|
| 975 |
+
"\n",
|
| 976 |
+
"answer = manual_predict(audio_features_)\n",
|
| 977 |
+
"print(answer)"
|
| 978 |
+
],
|
| 979 |
+
"id": "6d8f315863783a68",
|
| 980 |
+
"outputs": [
|
| 981 |
+
{
|
| 982 |
+
"name": "stdout",
|
| 983 |
+
"output_type": "stream",
|
| 984 |
+
"text": [
|
| 985 |
+
"Indie Pop, Indie Rock, Modern Alternative Rock\n"
|
| 986 |
+
]
|
| 987 |
+
}
|
| 988 |
+
],
|
| 989 |
+
"execution_count": 61
|
| 990 |
+
},
|
| 991 |
+
{
|
| 992 |
+
"metadata": {
|
| 993 |
+
"ExecuteTime": {
|
| 994 |
+
"end_time": "2026-03-27T15:31:41.650948200Z",
|
| 995 |
+
"start_time": "2026-03-27T15:31:41.421694900Z"
|
| 996 |
+
}
|
| 997 |
+
},
|
| 998 |
+
"cell_type": "code",
|
| 999 |
+
"source": [
|
| 1000 |
+
"fake_audio_features_2 = {'spectral_contrast_mean (mix)': 20.1520629356514,\n",
|
| 1001 |
+
" 'chroma_mean (mix)': 0.408170998096466,\n",
|
| 1002 |
+
" 'melody_variability (vocals)': 0.3748557548841115,\n",
|
| 1003 |
+
" 'rhythm_onset_rate (mix)': 2.0277777777777777,\n",
|
| 1004 |
+
" 'spectral_centroid_mean custom (mix)': 0.0938994687064515,\n",
|
| 1005 |
+
" 'repetition_score custom (mix)': 0.0078822056596288,\n",
|
| 1006 |
+
" 'pitch_std (mix)': 108.27875518798828,\n",
|
| 1007 |
+
" 'tempo_bpm_corrected (mix)': 104.60529327392578,\n",
|
| 1008 |
+
" 'tempo_bpm_original (mix)': 104.60529327392578,\n",
|
| 1009 |
+
" 'mfcc_mean_1 (mix)': 163.30865478515625,\n",
|
| 1010 |
+
" 'mfcc_mean_2 (mix)': 22.928050994873047,\n",
|
| 1011 |
+
" 'loudness_range_lu custom (mix)': 7.131200790405273,\n",
|
| 1012 |
+
" 'zero_crossing_rate (mix)': 0.02693298086524,\n",
|
| 1013 |
+
" 'vocab_richness': 0.582,\n",
|
| 1014 |
+
" 'loudness_integrated_lufs custom (mix)': -18.566131591796875,\n",
|
| 1015 |
+
" 'readability_score': 27.8,\n",
|
| 1016 |
+
" 'energy_essentia (mix)': 0.1537376961168162,\n",
|
| 1017 |
+
" 'energy_librosa (mix)': 0.335069430512918,\n",
|
| 1018 |
+
" 'rms_energy_mean (mix)': 0.3462888819034169,\n",
|
| 1019 |
+
" 'melody_complexity (vocals)': 1.9166666666666667}\n",
|
| 1020 |
+
"answer = manual_predict(fake_audio_features_2)\n",
|
| 1021 |
+
"print(answer)"
|
| 1022 |
+
],
|
| 1023 |
+
"id": "b998289b1d156dc2",
|
| 1024 |
+
"outputs": [
|
| 1025 |
+
{
|
| 1026 |
+
"name": "stdout",
|
| 1027 |
+
"output_type": "stream",
|
| 1028 |
+
"text": [
|
| 1029 |
+
"Black Metal, Death Metal, Doom Metal, Heavy Metal, Metalcore, Thrash Metal\n"
|
| 1030 |
+
]
|
| 1031 |
+
}
|
| 1032 |
+
],
|
| 1033 |
+
"execution_count": 62
|
| 1034 |
+
},
|
| 1035 |
+
{
|
| 1036 |
+
"metadata": {
|
| 1037 |
+
"ExecuteTime": {
|
| 1038 |
+
"end_time": "2026-03-27T15:24:02.082478Z",
|
| 1039 |
+
"start_time": "2026-03-27T15:24:01.848007Z"
|
| 1040 |
+
}
|
| 1041 |
+
},
|
| 1042 |
+
"cell_type": "code",
|
| 1043 |
+
"source": [
|
| 1044 |
+
"fake_metal_audio = {\n",
|
| 1045 |
+
" 'spectral_contrast_mean (mix)': 24.85,\n",
|
| 1046 |
+
" 'chroma_mean (mix)': 0.38,\n",
|
| 1047 |
+
" 'melody_variability (vocals)': 0.72,\n",
|
| 1048 |
+
" 'rhythm_onset_rate (mix)': 4.95,\n",
|
| 1049 |
+
" 'spectral_centroid_mean custom (mix)': 0.29,\n",
|
| 1050 |
+
" 'repetition_score custom (mix)': 0.006,\n",
|
| 1051 |
+
" 'pitch_std (mix)': 210.45,\n",
|
| 1052 |
+
" 'tempo_bpm_corrected (mix)': 145.60,\n",
|
| 1053 |
+
" 'tempo_bpm_original (mix)': 147.20,\n",
|
| 1054 |
+
" 'mfcc_mean_1 (mix)': 142.75,\n",
|
| 1055 |
+
" 'mfcc_mean_2 (mix)': -18.34,\n",
|
| 1056 |
+
" 'loudness_range_lu custom (mix)': 6.25,\n",
|
| 1057 |
+
" 'zero_crossing_rate (mix)': 0.18,\n",
|
| 1058 |
+
" 'vocab_richness': 0.27,\n",
|
| 1059 |
+
" 'loudness_integrated_lufs custom (mix)': -8.95,\n",
|
| 1060 |
+
" 'readability_score': 42.0,\n",
|
| 1061 |
+
" 'energy_essentia (mix)': 0.78,\n",
|
| 1062 |
+
" 'energy_librosa (mix)': 0.82,\n",
|
| 1063 |
+
" 'rms_energy_mean (mix)': 0.80,\n",
|
| 1064 |
+
" 'melody_complexity (vocals)': 3.95\n",
|
| 1065 |
+
"}\n",
|
| 1066 |
+
"\n",
|
| 1067 |
+
"answer = manual_predict(fake_metal_audio)\n",
|
| 1068 |
+
"print(answer)"
|
| 1069 |
+
],
|
| 1070 |
+
"id": "b265ee987709c2a3",
|
| 1071 |
+
"outputs": [
|
| 1072 |
+
{
|
| 1073 |
+
"name": "stdout",
|
| 1074 |
+
"output_type": "stream",
|
| 1075 |
+
"text": [
|
| 1076 |
+
"Rock -- Americana\n"
|
| 1077 |
+
]
|
| 1078 |
+
}
|
| 1079 |
+
],
|
| 1080 |
+
"execution_count": 57
|
| 1081 |
+
},
|
| 1082 |
+
{
|
| 1083 |
+
"metadata": {
|
| 1084 |
+
"ExecuteTime": {
|
| 1085 |
+
"end_time": "2026-03-27T15:38:00.278309800Z",
|
| 1086 |
+
"start_time": "2026-03-27T15:37:57.734946600Z"
|
| 1087 |
+
}
|
| 1088 |
+
},
|
| 1089 |
+
"cell_type": "code",
|
| 1090 |
+
"source": "",
|
| 1091 |
+
"id": "228a47b066312e75",
|
| 1092 |
+
"outputs": [
|
| 1093 |
+
{
|
| 1094 |
+
"name": "stdout",
|
| 1095 |
+
"output_type": "stream",
|
| 1096 |
+
"text": [
|
| 1097 |
+
"✅ Model saved as genre_model.pkl\n"
|
| 1098 |
+
]
|
| 1099 |
+
}
|
| 1100 |
+
],
|
| 1101 |
+
"execution_count": 64
|
| 1102 |
+
},
|
| 1103 |
+
{
|
| 1104 |
+
"metadata": {
|
| 1105 |
+
"ExecuteTime": {
|
| 1106 |
+
"end_time": "2026-03-27T15:49:22.832888800Z",
|
| 1107 |
+
"start_time": "2026-03-27T15:49:20.228927800Z"
|
| 1108 |
+
}
|
| 1109 |
+
},
|
| 1110 |
+
"cell_type": "code",
|
| 1111 |
+
"source": [
|
| 1112 |
+
"import joblib\n",
|
| 1113 |
+
"bundle = {\n",
|
| 1114 |
+
" \"model\": model,\n",
|
| 1115 |
+
" \"scaler\": scaler,\n",
|
| 1116 |
+
" \"mlb\": mlb,\n",
|
| 1117 |
+
" \"feature_cols\": X.columns.tolist()\n",
|
| 1118 |
+
"}\n",
|
| 1119 |
+
"joblib.dump(bundle, \"genre_model1.pkl\")\n",
|
| 1120 |
+
"print(\"Model saved successfully!\")"
|
| 1121 |
+
],
|
| 1122 |
+
"id": "694614771e1c62a9",
|
| 1123 |
+
"outputs": [
|
| 1124 |
+
{
|
| 1125 |
+
"name": "stdout",
|
| 1126 |
+
"output_type": "stream",
|
| 1127 |
+
"text": [
|
| 1128 |
+
"Model saved successfully!\n"
|
| 1129 |
+
]
|
| 1130 |
+
}
|
| 1131 |
+
],
|
| 1132 |
+
"execution_count": 68
|
| 1133 |
+
},
|
| 1134 |
+
{
|
| 1135 |
+
"metadata": {
|
| 1136 |
+
"ExecuteTime": {
|
| 1137 |
+
"end_time": "2026-03-27T15:49:54.380812800Z",
|
| 1138 |
+
"start_time": "2026-03-27T15:49:51.308892Z"
|
| 1139 |
+
}
|
| 1140 |
+
},
|
| 1141 |
+
"cell_type": "code",
|
| 1142 |
+
"source": [
|
| 1143 |
+
"import joblib\n",
|
| 1144 |
+
"bundle = joblib.load(\"genre_model1.pkl\")\n",
|
| 1145 |
+
"model = bundle[\"model\"]\n",
|
| 1146 |
+
"scaler = bundle[\"scaler\"]\n",
|
| 1147 |
+
"mlb = bundle[\"mlb\"]\n",
|
| 1148 |
+
"feature_cols = bundle[\"feature_cols\"]"
|
| 1149 |
+
],
|
| 1150 |
+
"id": "bb9f24b4e0947c22",
|
| 1151 |
+
"outputs": [],
|
| 1152 |
+
"execution_count": 70
|
| 1153 |
+
},
|
| 1154 |
+
{
|
| 1155 |
+
"metadata": {
|
| 1156 |
+
"ExecuteTime": {
|
| 1157 |
+
"end_time": "2026-03-27T15:51:05.220558400Z",
|
| 1158 |
+
"start_time": "2026-03-27T15:51:05.157646Z"
|
| 1159 |
+
}
|
| 1160 |
+
},
|
| 1161 |
+
"cell_type": "code",
|
| 1162 |
+
"source": [
|
| 1163 |
+
"def predict_from_loaded(row, model, scaler, mlb, feature_cols):\n",
|
| 1164 |
+
" import pandas as pd\n",
|
| 1165 |
+
" import numpy as np\n",
|
| 1166 |
+
" input_df = pd.DataFrame([row])\n",
|
| 1167 |
+
" input_numeric = input_df.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 1168 |
+
" for col in feature_cols:\n",
|
| 1169 |
+
" if col not in input_numeric.columns:\n",
|
| 1170 |
+
" input_numeric[col] = 0\n",
|
| 1171 |
+
" input_numeric = input_numeric[feature_cols]\n",
|
| 1172 |
+
" input_scaled = scaler.transform(input_numeric)\n",
|
| 1173 |
+
" probas = model.predict_proba(input_scaled)\n",
|
| 1174 |
+
" threshold = 0.20\n",
|
| 1175 |
+
" pred_binary = np.zeros((1, len(probas)), dtype=int)\n",
|
| 1176 |
+
" for i in range(len(probas)):\n",
|
| 1177 |
+
" if probas[i][0][1] > threshold:\n",
|
| 1178 |
+
" pred_binary[0, i] = 1\n",
|
| 1179 |
+
" subgenres = mlb.inverse_transform(pred_binary)[0]\n",
|
| 1180 |
+
" if len(subgenres) == 0:\n",
|
| 1181 |
+
" return \"None\"\n",
|
| 1182 |
+
" return \", \".join(sorted(subgenres))"
|
| 1183 |
+
],
|
| 1184 |
+
"id": "5391f7697e93bcad",
|
| 1185 |
+
"outputs": [],
|
| 1186 |
+
"execution_count": 71
|
| 1187 |
+
},
|
| 1188 |
+
{
|
| 1189 |
+
"metadata": {
|
| 1190 |
+
"ExecuteTime": {
|
| 1191 |
+
"end_time": "2026-03-27T15:51:42.665671400Z",
|
| 1192 |
+
"start_time": "2026-03-27T15:51:42.509361Z"
|
| 1193 |
+
}
|
| 1194 |
+
},
|
| 1195 |
+
"cell_type": "code",
|
| 1196 |
+
"source": [
|
| 1197 |
+
"audio_features_ = {'spectral_contrast_mean (mix)': 20.1520629356514,\n",
|
| 1198 |
+
" 'chroma_mean (mix)': 0.408170998096466,\n",
|
| 1199 |
+
" 'melody_variability (vocals)': 0.3748557548841115,\n",
|
| 1200 |
+
" 'rhythm_onset_rate (mix)': 2.0277777777777777,\n",
|
| 1201 |
+
" 'spectral_centroid_mean custom (mix)': 0.0938994687064515,\n",
|
| 1202 |
+
" 'repetition_score custom (mix)': 0.0078822056596288,\n",
|
| 1203 |
+
" 'pitch_std (mix)': 108.27875518798828,\n",
|
| 1204 |
+
" 'tempo_bpm_corrected (mix)': 104.60529327392578,\n",
|
| 1205 |
+
" 'tempo_bpm_original (mix)': 104.60529327392578,\n",
|
| 1206 |
+
" 'mfcc_mean_1 (mix)': 163.30865478515625,\n",
|
| 1207 |
+
" 'mfcc_mean_2 (mix)': 22.928050994873047,\n",
|
| 1208 |
+
" 'loudness_range_lu custom (mix)': 7.131200790405273,\n",
|
| 1209 |
+
" 'zero_crossing_rate (mix)': 0.02693298086524,\n",
|
| 1210 |
+
" 'vocab_richness': 0.582,\n",
|
| 1211 |
+
" 'loudness_integrated_lufs custom (mix)': -18.566131591796875,\n",
|
| 1212 |
+
" 'readability_score': 27.8,\n",
|
| 1213 |
+
" 'energy_essentia (mix)': 0.1537376961168162,\n",
|
| 1214 |
+
" 'energy_librosa (mix)': 0.335069430512918,\n",
|
| 1215 |
+
" 'rms_energy_mean (mix)': 0.3462888819034169,\n",
|
| 1216 |
+
" 'melody_complexity (vocals)': 1.9166666666666667}\n",
|
| 1217 |
+
"\n",
|
| 1218 |
+
"result = predict_from_loaded(audio_features_, model, scaler, mlb, feature_cols)\n",
|
| 1219 |
+
"print(result)"
|
| 1220 |
+
],
|
| 1221 |
+
"id": "cbc80439273b814f",
|
| 1222 |
+
"outputs": [
|
| 1223 |
+
{
|
| 1224 |
+
"name": "stdout",
|
| 1225 |
+
"output_type": "stream",
|
| 1226 |
+
"text": [
|
| 1227 |
+
"Black Metal, Death Metal, Doom Metal, Heavy Metal, Metalcore, Thrash Metal\n"
|
| 1228 |
+
]
|
| 1229 |
+
}
|
| 1230 |
+
],
|
| 1231 |
+
"execution_count": 73
|
| 1232 |
+
},
|
| 1233 |
+
{
|
| 1234 |
+
"metadata": {},
|
| 1235 |
+
"cell_type": "markdown",
|
| 1236 |
+
"source": "### 1) All Data",
|
| 1237 |
+
"id": "9a576cffaecfd507"
|
| 1238 |
+
},
|
| 1239 |
+
{
|
| 1240 |
+
"metadata": {
|
| 1241 |
+
"ExecuteTime": {
|
| 1242 |
+
"end_time": "2026-03-30T09:59:36.204702500Z",
|
| 1243 |
+
"start_time": "2026-03-30T09:59:34.858890200Z"
|
| 1244 |
+
}
|
| 1245 |
+
},
|
| 1246 |
+
"cell_type": "code",
|
| 1247 |
+
"source": [
|
| 1248 |
+
"import pandas as pd\n",
|
| 1249 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\features_dataset.csv\"\n",
|
| 1250 |
+
"pd.set_option(\"display.max_columns\", None)\n",
|
| 1251 |
+
"data = pd.read_csv(path,low_memory=False)\n",
|
| 1252 |
+
"data"
|
| 1253 |
+
],
|
| 1254 |
+
"id": "16dcec42226f1c5d",
|
| 1255 |
+
"outputs": [
|
| 1256 |
+
{
|
| 1257 |
+
"data": {
|
| 1258 |
+
"text/plain": [
|
| 1259 |
+
" track_url genre \\\n",
|
| 1260 |
+
"0 https://open.spotify.com/track/38RJG8nLAwGycit... Country \n",
|
| 1261 |
+
"1 https://open.spotify.com/track/2uqYupMHANxnwge... Country \n",
|
| 1262 |
+
"2 https://open.spotify.com/track/48X5k2vce5rXckg... Country \n",
|
| 1263 |
+
"3 https://open.spotify.com/track/3SaS0Va6o6jYkWv... Country \n",
|
| 1264 |
+
"4 https://open.spotify.com/track/09hVlIVHtEE1nLU... Country \n",
|
| 1265 |
+
"... ... ... \n",
|
| 1266 |
+
"138685 https://open.spotify.com/track/6rZno3nqeT7hv2P... Latin Urban \n",
|
| 1267 |
+
"138686 https://open.spotify.com/track/0O4ZJKR2QDLGrBZ... Latin Urban \n",
|
| 1268 |
+
"138687 https://open.spotify.com/track/4oV82xuYkYgyM2n... Latin Urban \n",
|
| 1269 |
+
"138688 https://open.spotify.com/track/3jztoVZMBEaTX0b... Latin Urban \n",
|
| 1270 |
+
"138689 https://open.spotify.com/track/3mUr9hl2VhqorCu... Latin Urban \n",
|
| 1271 |
+
"\n",
|
| 1272 |
+
" name features.melody_complexity (vocals) \\\n",
|
| 1273 |
+
"0 Forever To Me 3.500000 \n",
|
| 1274 |
+
"1 Austin 3.250000 \n",
|
| 1275 |
+
"2 Dirt Cheap 2.750000 \n",
|
| 1276 |
+
"3 I Am Not Okay 2.333333 \n",
|
| 1277 |
+
"4 Burn It Down 3.166667 \n",
|
| 1278 |
+
"... ... ... \n",
|
| 1279 |
+
"138685 YOGURCITO NaN \n",
|
| 1280 |
+
"138686 Me Arriesgo Contigo NaN \n",
|
| 1281 |
+
"138687 Vocales NaN \n",
|
| 1282 |
+
"138688 Tímida NaN \n",
|
| 1283 |
+
"138689 Romeo Y Julieta NaN \n",
|
| 1284 |
+
"\n",
|
| 1285 |
+
" features.melody_range (vocals) features.melody_variability (vocals) \\\n",
|
| 1286 |
+
"0 41.0 0.751713 \n",
|
| 1287 |
+
"1 35.0 0.715152 \n",
|
| 1288 |
+
"2 34.0 0.453325 \n",
|
| 1289 |
+
"3 34.0 0.436993 \n",
|
| 1290 |
+
"4 38.0 0.531330 \n",
|
| 1291 |
+
"... ... ... \n",
|
| 1292 |
+
"138685 NaN NaN \n",
|
| 1293 |
+
"138686 NaN NaN \n",
|
| 1294 |
+
"138687 NaN NaN \n",
|
| 1295 |
+
"138688 NaN NaN \n",
|
| 1296 |
+
"138689 NaN NaN \n",
|
| 1297 |
+
"\n",
|
| 1298 |
+
" features.tempo_bpm_original (mix) features.danceability custom (mix) \\\n",
|
| 1299 |
+
"0 141.384491 0.580731 \n",
|
| 1300 |
+
"1 105.666534 0.747039 \n",
|
| 1301 |
+
"2 110.124130 0.694952 \n",
|
| 1302 |
+
"3 141.088547 0.602134 \n",
|
| 1303 |
+
"4 148.782501 0.603468 \n",
|
| 1304 |
+
"... ... ... \n",
|
| 1305 |
+
"138685 NaN NaN \n",
|
| 1306 |
+
"138686 NaN NaN \n",
|
| 1307 |
+
"138687 NaN NaN \n",
|
| 1308 |
+
"138688 NaN NaN \n",
|
| 1309 |
+
"138689 NaN NaN \n",
|
| 1310 |
+
"\n",
|
| 1311 |
+
" features.loudness_integrated_lufs custom (mix) \\\n",
|
| 1312 |
+
"0 -15.467463 \n",
|
| 1313 |
+
"1 -12.137867 \n",
|
| 1314 |
+
"2 -14.214710 \n",
|
| 1315 |
+
"3 -12.991043 \n",
|
| 1316 |
+
"4 -12.433587 \n",
|
| 1317 |
+
"... ... \n",
|
| 1318 |
+
"138685 NaN \n",
|
| 1319 |
+
"138686 NaN \n",
|
| 1320 |
+
"138687 NaN \n",
|
| 1321 |
+
"138688 NaN \n",
|
| 1322 |
+
"138689 NaN \n",
|
| 1323 |
+
"\n",
|
| 1324 |
+
" features.loudness_range_lu custom (mix) \\\n",
|
| 1325 |
+
"0 11.282878 \n",
|
| 1326 |
+
"1 3.665868 \n",
|
| 1327 |
+
"2 8.500771 \n",
|
| 1328 |
+
"3 3.841528 \n",
|
| 1329 |
+
"4 5.801809 \n",
|
| 1330 |
+
"... ... \n",
|
| 1331 |
+
"138685 NaN \n",
|
| 1332 |
+
"138686 NaN \n",
|
| 1333 |
+
"138687 NaN \n",
|
| 1334 |
+
"138688 NaN \n",
|
| 1335 |
+
"138689 NaN \n",
|
| 1336 |
+
"\n",
|
| 1337 |
+
" features.energy_librosa (mix) features.energy_librosa_std (mix) \\\n",
|
| 1338 |
+
"0 0.419014 0.191760 \n",
|
| 1339 |
+
"1 0.469624 0.169444 \n",
|
| 1340 |
+
"2 0.427623 0.194988 \n",
|
| 1341 |
+
"3 0.524690 0.185032 \n",
|
| 1342 |
+
"4 0.566157 0.145061 \n",
|
| 1343 |
+
"... ... ... \n",
|
| 1344 |
+
"138685 NaN NaN \n",
|
| 1345 |
+
"138686 NaN NaN \n",
|
| 1346 |
+
"138687 NaN NaN \n",
|
| 1347 |
+
"138688 NaN NaN \n",
|
| 1348 |
+
"138689 NaN NaN \n",
|
| 1349 |
+
"\n",
|
| 1350 |
+
" features.energy_essentia (mix) features.energy_essentia_std (mix) \\\n",
|
| 1351 |
+
"0 0.225257 0.167297 \n",
|
| 1352 |
+
"1 0.255954 0.162414 \n",
|
| 1353 |
+
"2 0.226500 0.159222 \n",
|
| 1354 |
+
"3 0.321317 0.172458 \n",
|
| 1355 |
+
"4 0.355688 0.138871 \n",
|
| 1356 |
+
"... ... ... \n",
|
| 1357 |
+
"138685 NaN NaN \n",
|
| 1358 |
+
"138686 NaN NaN \n",
|
| 1359 |
+
"138687 NaN NaN \n",
|
| 1360 |
+
"138688 NaN NaN \n",
|
| 1361 |
+
"138689 NaN NaN \n",
|
| 1362 |
+
"\n",
|
| 1363 |
+
" features.energy_combined (mix) \\\n",
|
| 1364 |
+
"0 0.322135 \n",
|
| 1365 |
+
"1 0.362789 \n",
|
| 1366 |
+
"2 0.327062 \n",
|
| 1367 |
+
"3 0.423004 \n",
|
| 1368 |
+
"4 0.460922 \n",
|
| 1369 |
+
"... ... \n",
|
| 1370 |
+
"138685 NaN \n",
|
| 1371 |
+
"138686 NaN \n",
|
| 1372 |
+
"138687 NaN \n",
|
| 1373 |
+
"138688 NaN \n",
|
| 1374 |
+
"138689 NaN \n",
|
| 1375 |
+
"\n",
|
| 1376 |
+
" features.spectral_centroid_mean custom (mix) \\\n",
|
| 1377 |
+
"0 0.128541 \n",
|
| 1378 |
+
"1 0.121481 \n",
|
| 1379 |
+
"2 0.120138 \n",
|
| 1380 |
+
"3 0.140651 \n",
|
| 1381 |
+
"4 0.127909 \n",
|
| 1382 |
+
"... ... \n",
|
| 1383 |
+
"138685 NaN \n",
|
| 1384 |
+
"138686 NaN \n",
|
| 1385 |
+
"138687 NaN \n",
|
| 1386 |
+
"138688 NaN \n",
|
| 1387 |
+
"138689 NaN \n",
|
| 1388 |
+
"\n",
|
| 1389 |
+
" features.mfcc_mean_1 (mix) features.mfcc_mean_2 (mix) \\\n",
|
| 1390 |
+
"0 144.864594 -7.119251 \n",
|
| 1391 |
+
"1 160.318756 -3.738415 \n",
|
| 1392 |
+
"2 156.815781 -13.236840 \n",
|
| 1393 |
+
"3 143.763428 -21.374804 \n",
|
| 1394 |
+
"4 156.998581 -24.430223 \n",
|
| 1395 |
+
"... ... ... \n",
|
| 1396 |
+
"138685 NaN NaN \n",
|
| 1397 |
+
"138686 NaN NaN \n",
|
| 1398 |
+
"138687 NaN NaN \n",
|
| 1399 |
+
"138688 NaN NaN \n",
|
| 1400 |
+
"138689 NaN NaN \n",
|
| 1401 |
+
"\n",
|
| 1402 |
+
" features.chroma_mean (mix) features.spectral_contrast_mean (mix) \\\n",
|
| 1403 |
+
"0 0.482208 19.678119 \n",
|
| 1404 |
+
"1 0.501811 19.144755 \n",
|
| 1405 |
+
"2 0.452644 19.641929 \n",
|
| 1406 |
+
"3 0.434028 19.570383 \n",
|
| 1407 |
+
"4 0.493009 19.436073 \n",
|
| 1408 |
+
"... ... ... \n",
|
| 1409 |
+
"138685 NaN NaN \n",
|
| 1410 |
+
"138686 NaN NaN \n",
|
| 1411 |
+
"138687 NaN NaN \n",
|
| 1412 |
+
"138688 NaN NaN \n",
|
| 1413 |
+
"138689 NaN NaN \n",
|
| 1414 |
+
"\n",
|
| 1415 |
+
" features.repetition_score custom (mix) features.pitch_mean (mix) \\\n",
|
| 1416 |
+
"0 0.007355 188.741852 \n",
|
| 1417 |
+
"1 0.007224 225.259796 \n",
|
| 1418 |
+
"2 0.006500 184.371185 \n",
|
| 1419 |
+
"3 0.006950 232.608765 \n",
|
| 1420 |
+
"4 0.005922 241.072388 \n",
|
| 1421 |
+
"... ... ... \n",
|
| 1422 |
+
"138685 NaN NaN \n",
|
| 1423 |
+
"138686 NaN NaN \n",
|
| 1424 |
+
"138687 NaN NaN \n",
|
| 1425 |
+
"138688 NaN NaN \n",
|
| 1426 |
+
"138689 NaN NaN \n",
|
| 1427 |
+
"\n",
|
| 1428 |
+
" features.pitch_std (mix) features.rms_energy_mean (mix) \\\n",
|
| 1429 |
+
"0 54.632015 0.432170 \n",
|
| 1430 |
+
"1 90.800995 0.477567 \n",
|
| 1431 |
+
"2 49.438427 0.436157 \n",
|
| 1432 |
+
"3 60.514282 0.537289 \n",
|
| 1433 |
+
"4 98.557098 0.579285 \n",
|
| 1434 |
+
"... ... ... \n",
|
| 1435 |
+
"138685 NaN NaN \n",
|
| 1436 |
+
"138686 NaN NaN \n",
|
| 1437 |
+
"138687 NaN NaN \n",
|
| 1438 |
+
"138688 NaN NaN \n",
|
| 1439 |
+
"138689 NaN NaN \n",
|
| 1440 |
+
"\n",
|
| 1441 |
+
" features.rms_energy_std (mix) features.zero_crossing_rate (mix) \\\n",
|
| 1442 |
+
"0 0.194504 0.042678 \n",
|
| 1443 |
+
"1 0.166947 0.040959 \n",
|
| 1444 |
+
"2 0.192781 0.046456 \n",
|
| 1445 |
+
"3 0.180169 0.056903 \n",
|
| 1446 |
+
"4 0.140620 0.053897 \n",
|
| 1447 |
+
"... ... ... \n",
|
| 1448 |
+
"138685 NaN NaN \n",
|
| 1449 |
+
"138686 NaN NaN \n",
|
| 1450 |
+
"138687 NaN NaN \n",
|
| 1451 |
+
"138688 NaN NaN \n",
|
| 1452 |
+
"138689 NaN NaN \n",
|
| 1453 |
+
"\n",
|
| 1454 |
+
" sub_genre[0] sub_genre[1] sub_genre[2] \\\n",
|
| 1455 |
+
"0 Contemporary Country NaN NaN \n",
|
| 1456 |
+
"1 NaN NaN NaN \n",
|
| 1457 |
+
"2 Modern Neo-Traditional Contemporary Country NaN \n",
|
| 1458 |
+
"3 Americana Country Rock Country Folk \n",
|
| 1459 |
+
"4 NaN NaN NaN \n",
|
| 1460 |
+
"... ... ... ... \n",
|
| 1461 |
+
"138685 Latin Urban NaN NaN \n",
|
| 1462 |
+
"138686 Latin Urban NaN NaN \n",
|
| 1463 |
+
"138687 Latin Urban NaN NaN \n",
|
| 1464 |
+
"138688 Latin Urban NaN NaN \n",
|
| 1465 |
+
"138689 Latin Urban NaN NaN \n",
|
| 1466 |
+
"\n",
|
| 1467 |
+
" sub_genre[3] sub_genre[4] sub_genre[5] sub_genre[6] \\\n",
|
| 1468 |
+
"0 NaN NaN NaN NaN \n",
|
| 1469 |
+
"1 NaN NaN NaN NaN \n",
|
| 1470 |
+
"2 NaN NaN NaN NaN \n",
|
| 1471 |
+
"3 Contemporary Country NaN NaN NaN \n",
|
| 1472 |
+
"4 NaN NaN NaN NaN \n",
|
| 1473 |
+
"... ... ... ... ... \n",
|
| 1474 |
+
"138685 NaN NaN NaN NaN \n",
|
| 1475 |
+
"138686 NaN NaN NaN NaN \n",
|
| 1476 |
+
"138687 NaN NaN NaN NaN \n",
|
| 1477 |
+
"138688 NaN NaN NaN NaN \n",
|
| 1478 |
+
"138689 NaN NaN NaN NaN \n",
|
| 1479 |
+
"\n",
|
| 1480 |
+
" sub_genre[7] sub_genre[8] sub_genre[9] sub_genre[10] sub_genre[11] \\\n",
|
| 1481 |
+
"0 NaN NaN NaN NaN NaN \n",
|
| 1482 |
+
"1 NaN NaN NaN NaN NaN \n",
|
| 1483 |
+
"2 NaN NaN NaN NaN NaN \n",
|
| 1484 |
+
"3 NaN NaN NaN NaN NaN \n",
|
| 1485 |
+
"4 NaN NaN NaN NaN NaN \n",
|
| 1486 |
+
"... ... ... ... ... ... \n",
|
| 1487 |
+
"138685 NaN NaN NaN NaN NaN \n",
|
| 1488 |
+
"138686 NaN NaN NaN NaN NaN \n",
|
| 1489 |
+
"138687 NaN NaN NaN NaN NaN \n",
|
| 1490 |
+
"138688 NaN NaN NaN NaN NaN \n",
|
| 1491 |
+
"138689 NaN NaN NaN NaN NaN \n",
|
| 1492 |
+
"\n",
|
| 1493 |
+
" sub_genre[12] sub_genre[13] sub_genre[14] sub_genre[15] \n",
|
| 1494 |
+
"0 NaN NaN NaN NaN \n",
|
| 1495 |
+
"1 NaN NaN NaN NaN \n",
|
| 1496 |
+
"2 NaN NaN NaN NaN \n",
|
| 1497 |
+
"3 NaN NaN NaN NaN \n",
|
| 1498 |
+
"4 NaN NaN NaN NaN \n",
|
| 1499 |
+
"... ... ... ... ... \n",
|
| 1500 |
+
"138685 NaN NaN NaN NaN \n",
|
| 1501 |
+
"138686 NaN NaN NaN NaN \n",
|
| 1502 |
+
"138687 NaN NaN NaN NaN \n",
|
| 1503 |
+
"138688 NaN NaN NaN NaN \n",
|
| 1504 |
+
"138689 NaN NaN NaN NaN \n",
|
| 1505 |
+
"\n",
|
| 1506 |
+
"[138690 rows x 42 columns]"
|
| 1507 |
+
],
|
| 1508 |
+
"text/html": [
|
| 1509 |
+
"<div>\n",
|
| 1510 |
+
"<style scoped>\n",
|
| 1511 |
+
" .dataframe tbody tr th:only-of-type {\n",
|
| 1512 |
+
" vertical-align: middle;\n",
|
| 1513 |
+
" }\n",
|
| 1514 |
+
"\n",
|
| 1515 |
+
" .dataframe tbody tr th {\n",
|
| 1516 |
+
" vertical-align: top;\n",
|
| 1517 |
+
" }\n",
|
| 1518 |
+
"\n",
|
| 1519 |
+
" .dataframe thead th {\n",
|
| 1520 |
+
" text-align: right;\n",
|
| 1521 |
+
" }\n",
|
| 1522 |
+
"</style>\n",
|
| 1523 |
+
"<table border=\"1\" class=\"dataframe\">\n",
|
| 1524 |
+
" <thead>\n",
|
| 1525 |
+
" <tr style=\"text-align: right;\">\n",
|
| 1526 |
+
" <th></th>\n",
|
| 1527 |
+
" <th>track_url</th>\n",
|
| 1528 |
+
" <th>genre</th>\n",
|
| 1529 |
+
" <th>name</th>\n",
|
| 1530 |
+
" <th>features.melody_complexity (vocals)</th>\n",
|
| 1531 |
+
" <th>features.melody_range (vocals)</th>\n",
|
| 1532 |
+
" <th>features.melody_variability (vocals)</th>\n",
|
| 1533 |
+
" <th>features.tempo_bpm_original (mix)</th>\n",
|
| 1534 |
+
" <th>features.danceability custom (mix)</th>\n",
|
| 1535 |
+
" <th>features.loudness_integrated_lufs custom (mix)</th>\n",
|
| 1536 |
+
" <th>features.loudness_range_lu custom (mix)</th>\n",
|
| 1537 |
+
" <th>features.energy_librosa (mix)</th>\n",
|
| 1538 |
+
" <th>features.energy_librosa_std (mix)</th>\n",
|
| 1539 |
+
" <th>features.energy_essentia (mix)</th>\n",
|
| 1540 |
+
" <th>features.energy_essentia_std (mix)</th>\n",
|
| 1541 |
+
" <th>features.energy_combined (mix)</th>\n",
|
| 1542 |
+
" <th>features.spectral_centroid_mean custom (mix)</th>\n",
|
| 1543 |
+
" <th>features.mfcc_mean_1 (mix)</th>\n",
|
| 1544 |
+
" <th>features.mfcc_mean_2 (mix)</th>\n",
|
| 1545 |
+
" <th>features.chroma_mean (mix)</th>\n",
|
| 1546 |
+
" <th>features.spectral_contrast_mean (mix)</th>\n",
|
| 1547 |
+
" <th>features.repetition_score custom (mix)</th>\n",
|
| 1548 |
+
" <th>features.pitch_mean (mix)</th>\n",
|
| 1549 |
+
" <th>features.pitch_std (mix)</th>\n",
|
| 1550 |
+
" <th>features.rms_energy_mean (mix)</th>\n",
|
| 1551 |
+
" <th>features.rms_energy_std (mix)</th>\n",
|
| 1552 |
+
" <th>features.zero_crossing_rate (mix)</th>\n",
|
| 1553 |
+
" <th>sub_genre[0]</th>\n",
|
| 1554 |
+
" <th>sub_genre[1]</th>\n",
|
| 1555 |
+
" <th>sub_genre[2]</th>\n",
|
| 1556 |
+
" <th>sub_genre[3]</th>\n",
|
| 1557 |
+
" <th>sub_genre[4]</th>\n",
|
| 1558 |
+
" <th>sub_genre[5]</th>\n",
|
| 1559 |
+
" <th>sub_genre[6]</th>\n",
|
| 1560 |
+
" <th>sub_genre[7]</th>\n",
|
| 1561 |
+
" <th>sub_genre[8]</th>\n",
|
| 1562 |
+
" <th>sub_genre[9]</th>\n",
|
| 1563 |
+
" <th>sub_genre[10]</th>\n",
|
| 1564 |
+
" <th>sub_genre[11]</th>\n",
|
| 1565 |
+
" <th>sub_genre[12]</th>\n",
|
| 1566 |
+
" <th>sub_genre[13]</th>\n",
|
| 1567 |
+
" <th>sub_genre[14]</th>\n",
|
| 1568 |
+
" <th>sub_genre[15]</th>\n",
|
| 1569 |
+
" </tr>\n",
|
| 1570 |
+
" </thead>\n",
|
| 1571 |
+
" <tbody>\n",
|
| 1572 |
+
" <tr>\n",
|
| 1573 |
+
" <th>0</th>\n",
|
| 1574 |
+
" <td>https://open.spotify.com/track/38RJG8nLAwGycit...</td>\n",
|
| 1575 |
+
" <td>Country</td>\n",
|
| 1576 |
+
" <td>Forever To Me</td>\n",
|
| 1577 |
+
" <td>3.500000</td>\n",
|
| 1578 |
+
" <td>41.0</td>\n",
|
| 1579 |
+
" <td>0.751713</td>\n",
|
| 1580 |
+
" <td>141.384491</td>\n",
|
| 1581 |
+
" <td>0.580731</td>\n",
|
| 1582 |
+
" <td>-15.467463</td>\n",
|
| 1583 |
+
" <td>11.282878</td>\n",
|
| 1584 |
+
" <td>0.419014</td>\n",
|
| 1585 |
+
" <td>0.191760</td>\n",
|
| 1586 |
+
" <td>0.225257</td>\n",
|
| 1587 |
+
" <td>0.167297</td>\n",
|
| 1588 |
+
" <td>0.322135</td>\n",
|
| 1589 |
+
" <td>0.128541</td>\n",
|
| 1590 |
+
" <td>144.864594</td>\n",
|
| 1591 |
+
" <td>-7.119251</td>\n",
|
| 1592 |
+
" <td>0.482208</td>\n",
|
| 1593 |
+
" <td>19.678119</td>\n",
|
| 1594 |
+
" <td>0.007355</td>\n",
|
| 1595 |
+
" <td>188.741852</td>\n",
|
| 1596 |
+
" <td>54.632015</td>\n",
|
| 1597 |
+
" <td>0.432170</td>\n",
|
| 1598 |
+
" <td>0.194504</td>\n",
|
| 1599 |
+
" <td>0.042678</td>\n",
|
| 1600 |
+
" <td>Contemporary Country</td>\n",
|
| 1601 |
+
" <td>NaN</td>\n",
|
| 1602 |
+
" <td>NaN</td>\n",
|
| 1603 |
+
" <td>NaN</td>\n",
|
| 1604 |
+
" <td>NaN</td>\n",
|
| 1605 |
+
" <td>NaN</td>\n",
|
| 1606 |
+
" <td>NaN</td>\n",
|
| 1607 |
+
" <td>NaN</td>\n",
|
| 1608 |
+
" <td>NaN</td>\n",
|
| 1609 |
+
" <td>NaN</td>\n",
|
| 1610 |
+
" <td>NaN</td>\n",
|
| 1611 |
+
" <td>NaN</td>\n",
|
| 1612 |
+
" <td>NaN</td>\n",
|
| 1613 |
+
" <td>NaN</td>\n",
|
| 1614 |
+
" <td>NaN</td>\n",
|
| 1615 |
+
" <td>NaN</td>\n",
|
| 1616 |
+
" </tr>\n",
|
| 1617 |
+
" <tr>\n",
|
| 1618 |
+
" <th>1</th>\n",
|
| 1619 |
+
" <td>https://open.spotify.com/track/2uqYupMHANxnwge...</td>\n",
|
| 1620 |
+
" <td>Country</td>\n",
|
| 1621 |
+
" <td>Austin</td>\n",
|
| 1622 |
+
" <td>3.250000</td>\n",
|
| 1623 |
+
" <td>35.0</td>\n",
|
| 1624 |
+
" <td>0.715152</td>\n",
|
| 1625 |
+
" <td>105.666534</td>\n",
|
| 1626 |
+
" <td>0.747039</td>\n",
|
| 1627 |
+
" <td>-12.137867</td>\n",
|
| 1628 |
+
" <td>3.665868</td>\n",
|
| 1629 |
+
" <td>0.469624</td>\n",
|
| 1630 |
+
" <td>0.169444</td>\n",
|
| 1631 |
+
" <td>0.255954</td>\n",
|
| 1632 |
+
" <td>0.162414</td>\n",
|
| 1633 |
+
" <td>0.362789</td>\n",
|
| 1634 |
+
" <td>0.121481</td>\n",
|
| 1635 |
+
" <td>160.318756</td>\n",
|
| 1636 |
+
" <td>-3.738415</td>\n",
|
| 1637 |
+
" <td>0.501811</td>\n",
|
| 1638 |
+
" <td>19.144755</td>\n",
|
| 1639 |
+
" <td>0.007224</td>\n",
|
| 1640 |
+
" <td>225.259796</td>\n",
|
| 1641 |
+
" <td>90.800995</td>\n",
|
| 1642 |
+
" <td>0.477567</td>\n",
|
| 1643 |
+
" <td>0.166947</td>\n",
|
| 1644 |
+
" <td>0.040959</td>\n",
|
| 1645 |
+
" <td>NaN</td>\n",
|
| 1646 |
+
" <td>NaN</td>\n",
|
| 1647 |
+
" <td>NaN</td>\n",
|
| 1648 |
+
" <td>NaN</td>\n",
|
| 1649 |
+
" <td>NaN</td>\n",
|
| 1650 |
+
" <td>NaN</td>\n",
|
| 1651 |
+
" <td>NaN</td>\n",
|
| 1652 |
+
" <td>NaN</td>\n",
|
| 1653 |
+
" <td>NaN</td>\n",
|
| 1654 |
+
" <td>NaN</td>\n",
|
| 1655 |
+
" <td>NaN</td>\n",
|
| 1656 |
+
" <td>NaN</td>\n",
|
| 1657 |
+
" <td>NaN</td>\n",
|
| 1658 |
+
" <td>NaN</td>\n",
|
| 1659 |
+
" <td>NaN</td>\n",
|
| 1660 |
+
" <td>NaN</td>\n",
|
| 1661 |
+
" </tr>\n",
|
| 1662 |
+
" <tr>\n",
|
| 1663 |
+
" <th>2</th>\n",
|
| 1664 |
+
" <td>https://open.spotify.com/track/48X5k2vce5rXckg...</td>\n",
|
| 1665 |
+
" <td>Country</td>\n",
|
| 1666 |
+
" <td>Dirt Cheap</td>\n",
|
| 1667 |
+
" <td>2.750000</td>\n",
|
| 1668 |
+
" <td>34.0</td>\n",
|
| 1669 |
+
" <td>0.453325</td>\n",
|
| 1670 |
+
" <td>110.124130</td>\n",
|
| 1671 |
+
" <td>0.694952</td>\n",
|
| 1672 |
+
" <td>-14.214710</td>\n",
|
| 1673 |
+
" <td>8.500771</td>\n",
|
| 1674 |
+
" <td>0.427623</td>\n",
|
| 1675 |
+
" <td>0.194988</td>\n",
|
| 1676 |
+
" <td>0.226500</td>\n",
|
| 1677 |
+
" <td>0.159222</td>\n",
|
| 1678 |
+
" <td>0.327062</td>\n",
|
| 1679 |
+
" <td>0.120138</td>\n",
|
| 1680 |
+
" <td>156.815781</td>\n",
|
| 1681 |
+
" <td>-13.236840</td>\n",
|
| 1682 |
+
" <td>0.452644</td>\n",
|
| 1683 |
+
" <td>19.641929</td>\n",
|
| 1684 |
+
" <td>0.006500</td>\n",
|
| 1685 |
+
" <td>184.371185</td>\n",
|
| 1686 |
+
" <td>49.438427</td>\n",
|
| 1687 |
+
" <td>0.436157</td>\n",
|
| 1688 |
+
" <td>0.192781</td>\n",
|
| 1689 |
+
" <td>0.046456</td>\n",
|
| 1690 |
+
" <td>Modern Neo-Traditional</td>\n",
|
| 1691 |
+
" <td>Contemporary Country</td>\n",
|
| 1692 |
+
" <td>NaN</td>\n",
|
| 1693 |
+
" <td>NaN</td>\n",
|
| 1694 |
+
" <td>NaN</td>\n",
|
| 1695 |
+
" <td>NaN</td>\n",
|
| 1696 |
+
" <td>NaN</td>\n",
|
| 1697 |
+
" <td>NaN</td>\n",
|
| 1698 |
+
" <td>NaN</td>\n",
|
| 1699 |
+
" <td>NaN</td>\n",
|
| 1700 |
+
" <td>NaN</td>\n",
|
| 1701 |
+
" <td>NaN</td>\n",
|
| 1702 |
+
" <td>NaN</td>\n",
|
| 1703 |
+
" <td>NaN</td>\n",
|
| 1704 |
+
" <td>NaN</td>\n",
|
| 1705 |
+
" <td>NaN</td>\n",
|
| 1706 |
+
" </tr>\n",
|
| 1707 |
+
" <tr>\n",
|
| 1708 |
+
" <th>3</th>\n",
|
| 1709 |
+
" <td>https://open.spotify.com/track/3SaS0Va6o6jYkWv...</td>\n",
|
| 1710 |
+
" <td>Country</td>\n",
|
| 1711 |
+
" <td>I Am Not Okay</td>\n",
|
| 1712 |
+
" <td>2.333333</td>\n",
|
| 1713 |
+
" <td>34.0</td>\n",
|
| 1714 |
+
" <td>0.436993</td>\n",
|
| 1715 |
+
" <td>141.088547</td>\n",
|
| 1716 |
+
" <td>0.602134</td>\n",
|
| 1717 |
+
" <td>-12.991043</td>\n",
|
| 1718 |
+
" <td>3.841528</td>\n",
|
| 1719 |
+
" <td>0.524690</td>\n",
|
| 1720 |
+
" <td>0.185032</td>\n",
|
| 1721 |
+
" <td>0.321317</td>\n",
|
| 1722 |
+
" <td>0.172458</td>\n",
|
| 1723 |
+
" <td>0.423004</td>\n",
|
| 1724 |
+
" <td>0.140651</td>\n",
|
| 1725 |
+
" <td>143.763428</td>\n",
|
| 1726 |
+
" <td>-21.374804</td>\n",
|
| 1727 |
+
" <td>0.434028</td>\n",
|
| 1728 |
+
" <td>19.570383</td>\n",
|
| 1729 |
+
" <td>0.006950</td>\n",
|
| 1730 |
+
" <td>232.608765</td>\n",
|
| 1731 |
+
" <td>60.514282</td>\n",
|
| 1732 |
+
" <td>0.537289</td>\n",
|
| 1733 |
+
" <td>0.180169</td>\n",
|
| 1734 |
+
" <td>0.056903</td>\n",
|
| 1735 |
+
" <td>Americana</td>\n",
|
| 1736 |
+
" <td>Country Rock</td>\n",
|
| 1737 |
+
" <td>Country Folk</td>\n",
|
| 1738 |
+
" <td>Contemporary Country</td>\n",
|
| 1739 |
+
" <td>NaN</td>\n",
|
| 1740 |
+
" <td>NaN</td>\n",
|
| 1741 |
+
" <td>NaN</td>\n",
|
| 1742 |
+
" <td>NaN</td>\n",
|
| 1743 |
+
" <td>NaN</td>\n",
|
| 1744 |
+
" <td>NaN</td>\n",
|
| 1745 |
+
" <td>NaN</td>\n",
|
| 1746 |
+
" <td>NaN</td>\n",
|
| 1747 |
+
" <td>NaN</td>\n",
|
| 1748 |
+
" <td>NaN</td>\n",
|
| 1749 |
+
" <td>NaN</td>\n",
|
| 1750 |
+
" <td>NaN</td>\n",
|
| 1751 |
+
" </tr>\n",
|
| 1752 |
+
" <tr>\n",
|
| 1753 |
+
" <th>4</th>\n",
|
| 1754 |
+
" <td>https://open.spotify.com/track/09hVlIVHtEE1nLU...</td>\n",
|
| 1755 |
+
" <td>Country</td>\n",
|
| 1756 |
+
" <td>Burn It Down</td>\n",
|
| 1757 |
+
" <td>3.166667</td>\n",
|
| 1758 |
+
" <td>38.0</td>\n",
|
| 1759 |
+
" <td>0.531330</td>\n",
|
| 1760 |
+
" <td>148.782501</td>\n",
|
| 1761 |
+
" <td>0.603468</td>\n",
|
| 1762 |
+
" <td>-12.433587</td>\n",
|
| 1763 |
+
" <td>5.801809</td>\n",
|
| 1764 |
+
" <td>0.566157</td>\n",
|
| 1765 |
+
" <td>0.145061</td>\n",
|
| 1766 |
+
" <td>0.355688</td>\n",
|
| 1767 |
+
" <td>0.138871</td>\n",
|
| 1768 |
+
" <td>0.460922</td>\n",
|
| 1769 |
+
" <td>0.127909</td>\n",
|
| 1770 |
+
" <td>156.998581</td>\n",
|
| 1771 |
+
" <td>-24.430223</td>\n",
|
| 1772 |
+
" <td>0.493009</td>\n",
|
| 1773 |
+
" <td>19.436073</td>\n",
|
| 1774 |
+
" <td>0.005922</td>\n",
|
| 1775 |
+
" <td>241.072388</td>\n",
|
| 1776 |
+
" <td>98.557098</td>\n",
|
| 1777 |
+
" <td>0.579285</td>\n",
|
| 1778 |
+
" <td>0.140620</td>\n",
|
| 1779 |
+
" <td>0.053897</td>\n",
|
| 1780 |
+
" <td>NaN</td>\n",
|
| 1781 |
+
" <td>NaN</td>\n",
|
| 1782 |
+
" <td>NaN</td>\n",
|
| 1783 |
+
" <td>NaN</td>\n",
|
| 1784 |
+
" <td>NaN</td>\n",
|
| 1785 |
+
" <td>NaN</td>\n",
|
| 1786 |
+
" <td>NaN</td>\n",
|
| 1787 |
+
" <td>NaN</td>\n",
|
| 1788 |
+
" <td>NaN</td>\n",
|
| 1789 |
+
" <td>NaN</td>\n",
|
| 1790 |
+
" <td>NaN</td>\n",
|
| 1791 |
+
" <td>NaN</td>\n",
|
| 1792 |
+
" <td>NaN</td>\n",
|
| 1793 |
+
" <td>NaN</td>\n",
|
| 1794 |
+
" <td>NaN</td>\n",
|
| 1795 |
+
" <td>NaN</td>\n",
|
| 1796 |
+
" </tr>\n",
|
| 1797 |
+
" <tr>\n",
|
| 1798 |
+
" <th>...</th>\n",
|
| 1799 |
+
" <td>...</td>\n",
|
| 1800 |
+
" <td>...</td>\n",
|
| 1801 |
+
" <td>...</td>\n",
|
| 1802 |
+
" <td>...</td>\n",
|
| 1803 |
+
" <td>...</td>\n",
|
| 1804 |
+
" <td>...</td>\n",
|
| 1805 |
+
" <td>...</td>\n",
|
| 1806 |
+
" <td>...</td>\n",
|
| 1807 |
+
" <td>...</td>\n",
|
| 1808 |
+
" <td>...</td>\n",
|
| 1809 |
+
" <td>...</td>\n",
|
| 1810 |
+
" <td>...</td>\n",
|
| 1811 |
+
" <td>...</td>\n",
|
| 1812 |
+
" <td>...</td>\n",
|
| 1813 |
+
" <td>...</td>\n",
|
| 1814 |
+
" <td>...</td>\n",
|
| 1815 |
+
" <td>...</td>\n",
|
| 1816 |
+
" <td>...</td>\n",
|
| 1817 |
+
" <td>...</td>\n",
|
| 1818 |
+
" <td>...</td>\n",
|
| 1819 |
+
" <td>...</td>\n",
|
| 1820 |
+
" <td>...</td>\n",
|
| 1821 |
+
" <td>...</td>\n",
|
| 1822 |
+
" <td>...</td>\n",
|
| 1823 |
+
" <td>...</td>\n",
|
| 1824 |
+
" <td>...</td>\n",
|
| 1825 |
+
" <td>...</td>\n",
|
| 1826 |
+
" <td>...</td>\n",
|
| 1827 |
+
" <td>...</td>\n",
|
| 1828 |
+
" <td>...</td>\n",
|
| 1829 |
+
" <td>...</td>\n",
|
| 1830 |
+
" <td>...</td>\n",
|
| 1831 |
+
" <td>...</td>\n",
|
| 1832 |
+
" <td>...</td>\n",
|
| 1833 |
+
" <td>...</td>\n",
|
| 1834 |
+
" <td>...</td>\n",
|
| 1835 |
+
" <td>...</td>\n",
|
| 1836 |
+
" <td>...</td>\n",
|
| 1837 |
+
" <td>...</td>\n",
|
| 1838 |
+
" <td>...</td>\n",
|
| 1839 |
+
" <td>...</td>\n",
|
| 1840 |
+
" <td>...</td>\n",
|
| 1841 |
+
" </tr>\n",
|
| 1842 |
+
" <tr>\n",
|
| 1843 |
+
" <th>138685</th>\n",
|
| 1844 |
+
" <td>https://open.spotify.com/track/6rZno3nqeT7hv2P...</td>\n",
|
| 1845 |
+
" <td>Latin Urban</td>\n",
|
| 1846 |
+
" <td>YOGURCITO</td>\n",
|
| 1847 |
+
" <td>NaN</td>\n",
|
| 1848 |
+
" <td>NaN</td>\n",
|
| 1849 |
+
" <td>NaN</td>\n",
|
| 1850 |
+
" <td>NaN</td>\n",
|
| 1851 |
+
" <td>NaN</td>\n",
|
| 1852 |
+
" <td>NaN</td>\n",
|
| 1853 |
+
" <td>NaN</td>\n",
|
| 1854 |
+
" <td>NaN</td>\n",
|
| 1855 |
+
" <td>NaN</td>\n",
|
| 1856 |
+
" <td>NaN</td>\n",
|
| 1857 |
+
" <td>NaN</td>\n",
|
| 1858 |
+
" <td>NaN</td>\n",
|
| 1859 |
+
" <td>NaN</td>\n",
|
| 1860 |
+
" <td>NaN</td>\n",
|
| 1861 |
+
" <td>NaN</td>\n",
|
| 1862 |
+
" <td>NaN</td>\n",
|
| 1863 |
+
" <td>NaN</td>\n",
|
| 1864 |
+
" <td>NaN</td>\n",
|
| 1865 |
+
" <td>NaN</td>\n",
|
| 1866 |
+
" <td>NaN</td>\n",
|
| 1867 |
+
" <td>NaN</td>\n",
|
| 1868 |
+
" <td>NaN</td>\n",
|
| 1869 |
+
" <td>NaN</td>\n",
|
| 1870 |
+
" <td>Latin Urban</td>\n",
|
| 1871 |
+
" <td>NaN</td>\n",
|
| 1872 |
+
" <td>NaN</td>\n",
|
| 1873 |
+
" <td>NaN</td>\n",
|
| 1874 |
+
" <td>NaN</td>\n",
|
| 1875 |
+
" <td>NaN</td>\n",
|
| 1876 |
+
" <td>NaN</td>\n",
|
| 1877 |
+
" <td>NaN</td>\n",
|
| 1878 |
+
" <td>NaN</td>\n",
|
| 1879 |
+
" <td>NaN</td>\n",
|
| 1880 |
+
" <td>NaN</td>\n",
|
| 1881 |
+
" <td>NaN</td>\n",
|
| 1882 |
+
" <td>NaN</td>\n",
|
| 1883 |
+
" <td>NaN</td>\n",
|
| 1884 |
+
" <td>NaN</td>\n",
|
| 1885 |
+
" <td>NaN</td>\n",
|
| 1886 |
+
" </tr>\n",
|
| 1887 |
+
" <tr>\n",
|
| 1888 |
+
" <th>138686</th>\n",
|
| 1889 |
+
" <td>https://open.spotify.com/track/0O4ZJKR2QDLGrBZ...</td>\n",
|
| 1890 |
+
" <td>Latin Urban</td>\n",
|
| 1891 |
+
" <td>Me Arriesgo Contigo</td>\n",
|
| 1892 |
+
" <td>NaN</td>\n",
|
| 1893 |
+
" <td>NaN</td>\n",
|
| 1894 |
+
" <td>NaN</td>\n",
|
| 1895 |
+
" <td>NaN</td>\n",
|
| 1896 |
+
" <td>NaN</td>\n",
|
| 1897 |
+
" <td>NaN</td>\n",
|
| 1898 |
+
" <td>NaN</td>\n",
|
| 1899 |
+
" <td>NaN</td>\n",
|
| 1900 |
+
" <td>NaN</td>\n",
|
| 1901 |
+
" <td>NaN</td>\n",
|
| 1902 |
+
" <td>NaN</td>\n",
|
| 1903 |
+
" <td>NaN</td>\n",
|
| 1904 |
+
" <td>NaN</td>\n",
|
| 1905 |
+
" <td>NaN</td>\n",
|
| 1906 |
+
" <td>NaN</td>\n",
|
| 1907 |
+
" <td>NaN</td>\n",
|
| 1908 |
+
" <td>NaN</td>\n",
|
| 1909 |
+
" <td>NaN</td>\n",
|
| 1910 |
+
" <td>NaN</td>\n",
|
| 1911 |
+
" <td>NaN</td>\n",
|
| 1912 |
+
" <td>NaN</td>\n",
|
| 1913 |
+
" <td>NaN</td>\n",
|
| 1914 |
+
" <td>NaN</td>\n",
|
| 1915 |
+
" <td>Latin Urban</td>\n",
|
| 1916 |
+
" <td>NaN</td>\n",
|
| 1917 |
+
" <td>NaN</td>\n",
|
| 1918 |
+
" <td>NaN</td>\n",
|
| 1919 |
+
" <td>NaN</td>\n",
|
| 1920 |
+
" <td>NaN</td>\n",
|
| 1921 |
+
" <td>NaN</td>\n",
|
| 1922 |
+
" <td>NaN</td>\n",
|
| 1923 |
+
" <td>NaN</td>\n",
|
| 1924 |
+
" <td>NaN</td>\n",
|
| 1925 |
+
" <td>NaN</td>\n",
|
| 1926 |
+
" <td>NaN</td>\n",
|
| 1927 |
+
" <td>NaN</td>\n",
|
| 1928 |
+
" <td>NaN</td>\n",
|
| 1929 |
+
" <td>NaN</td>\n",
|
| 1930 |
+
" <td>NaN</td>\n",
|
| 1931 |
+
" </tr>\n",
|
| 1932 |
+
" <tr>\n",
|
| 1933 |
+
" <th>138687</th>\n",
|
| 1934 |
+
" <td>https://open.spotify.com/track/4oV82xuYkYgyM2n...</td>\n",
|
| 1935 |
+
" <td>Latin Urban</td>\n",
|
| 1936 |
+
" <td>Vocales</td>\n",
|
| 1937 |
+
" <td>NaN</td>\n",
|
| 1938 |
+
" <td>NaN</td>\n",
|
| 1939 |
+
" <td>NaN</td>\n",
|
| 1940 |
+
" <td>NaN</td>\n",
|
| 1941 |
+
" <td>NaN</td>\n",
|
| 1942 |
+
" <td>NaN</td>\n",
|
| 1943 |
+
" <td>NaN</td>\n",
|
| 1944 |
+
" <td>NaN</td>\n",
|
| 1945 |
+
" <td>NaN</td>\n",
|
| 1946 |
+
" <td>NaN</td>\n",
|
| 1947 |
+
" <td>NaN</td>\n",
|
| 1948 |
+
" <td>NaN</td>\n",
|
| 1949 |
+
" <td>NaN</td>\n",
|
| 1950 |
+
" <td>NaN</td>\n",
|
| 1951 |
+
" <td>NaN</td>\n",
|
| 1952 |
+
" <td>NaN</td>\n",
|
| 1953 |
+
" <td>NaN</td>\n",
|
| 1954 |
+
" <td>NaN</td>\n",
|
| 1955 |
+
" <td>NaN</td>\n",
|
| 1956 |
+
" <td>NaN</td>\n",
|
| 1957 |
+
" <td>NaN</td>\n",
|
| 1958 |
+
" <td>NaN</td>\n",
|
| 1959 |
+
" <td>NaN</td>\n",
|
| 1960 |
+
" <td>Latin Urban</td>\n",
|
| 1961 |
+
" <td>NaN</td>\n",
|
| 1962 |
+
" <td>NaN</td>\n",
|
| 1963 |
+
" <td>NaN</td>\n",
|
| 1964 |
+
" <td>NaN</td>\n",
|
| 1965 |
+
" <td>NaN</td>\n",
|
| 1966 |
+
" <td>NaN</td>\n",
|
| 1967 |
+
" <td>NaN</td>\n",
|
| 1968 |
+
" <td>NaN</td>\n",
|
| 1969 |
+
" <td>NaN</td>\n",
|
| 1970 |
+
" <td>NaN</td>\n",
|
| 1971 |
+
" <td>NaN</td>\n",
|
| 1972 |
+
" <td>NaN</td>\n",
|
| 1973 |
+
" <td>NaN</td>\n",
|
| 1974 |
+
" <td>NaN</td>\n",
|
| 1975 |
+
" <td>NaN</td>\n",
|
| 1976 |
+
" </tr>\n",
|
| 1977 |
+
" <tr>\n",
|
| 1978 |
+
" <th>138688</th>\n",
|
| 1979 |
+
" <td>https://open.spotify.com/track/3jztoVZMBEaTX0b...</td>\n",
|
| 1980 |
+
" <td>Latin Urban</td>\n",
|
| 1981 |
+
" <td>Tímida</td>\n",
|
| 1982 |
+
" <td>NaN</td>\n",
|
| 1983 |
+
" <td>NaN</td>\n",
|
| 1984 |
+
" <td>NaN</td>\n",
|
| 1985 |
+
" <td>NaN</td>\n",
|
| 1986 |
+
" <td>NaN</td>\n",
|
| 1987 |
+
" <td>NaN</td>\n",
|
| 1988 |
+
" <td>NaN</td>\n",
|
| 1989 |
+
" <td>NaN</td>\n",
|
| 1990 |
+
" <td>NaN</td>\n",
|
| 1991 |
+
" <td>NaN</td>\n",
|
| 1992 |
+
" <td>NaN</td>\n",
|
| 1993 |
+
" <td>NaN</td>\n",
|
| 1994 |
+
" <td>NaN</td>\n",
|
| 1995 |
+
" <td>NaN</td>\n",
|
| 1996 |
+
" <td>NaN</td>\n",
|
| 1997 |
+
" <td>NaN</td>\n",
|
| 1998 |
+
" <td>NaN</td>\n",
|
| 1999 |
+
" <td>NaN</td>\n",
|
| 2000 |
+
" <td>NaN</td>\n",
|
| 2001 |
+
" <td>NaN</td>\n",
|
| 2002 |
+
" <td>NaN</td>\n",
|
| 2003 |
+
" <td>NaN</td>\n",
|
| 2004 |
+
" <td>NaN</td>\n",
|
| 2005 |
+
" <td>Latin Urban</td>\n",
|
| 2006 |
+
" <td>NaN</td>\n",
|
| 2007 |
+
" <td>NaN</td>\n",
|
| 2008 |
+
" <td>NaN</td>\n",
|
| 2009 |
+
" <td>NaN</td>\n",
|
| 2010 |
+
" <td>NaN</td>\n",
|
| 2011 |
+
" <td>NaN</td>\n",
|
| 2012 |
+
" <td>NaN</td>\n",
|
| 2013 |
+
" <td>NaN</td>\n",
|
| 2014 |
+
" <td>NaN</td>\n",
|
| 2015 |
+
" <td>NaN</td>\n",
|
| 2016 |
+
" <td>NaN</td>\n",
|
| 2017 |
+
" <td>NaN</td>\n",
|
| 2018 |
+
" <td>NaN</td>\n",
|
| 2019 |
+
" <td>NaN</td>\n",
|
| 2020 |
+
" <td>NaN</td>\n",
|
| 2021 |
+
" </tr>\n",
|
| 2022 |
+
" <tr>\n",
|
| 2023 |
+
" <th>138689</th>\n",
|
| 2024 |
+
" <td>https://open.spotify.com/track/3mUr9hl2VhqorCu...</td>\n",
|
| 2025 |
+
" <td>Latin Urban</td>\n",
|
| 2026 |
+
" <td>Romeo Y Julieta</td>\n",
|
| 2027 |
+
" <td>NaN</td>\n",
|
| 2028 |
+
" <td>NaN</td>\n",
|
| 2029 |
+
" <td>NaN</td>\n",
|
| 2030 |
+
" <td>NaN</td>\n",
|
| 2031 |
+
" <td>NaN</td>\n",
|
| 2032 |
+
" <td>NaN</td>\n",
|
| 2033 |
+
" <td>NaN</td>\n",
|
| 2034 |
+
" <td>NaN</td>\n",
|
| 2035 |
+
" <td>NaN</td>\n",
|
| 2036 |
+
" <td>NaN</td>\n",
|
| 2037 |
+
" <td>NaN</td>\n",
|
| 2038 |
+
" <td>NaN</td>\n",
|
| 2039 |
+
" <td>NaN</td>\n",
|
| 2040 |
+
" <td>NaN</td>\n",
|
| 2041 |
+
" <td>NaN</td>\n",
|
| 2042 |
+
" <td>NaN</td>\n",
|
| 2043 |
+
" <td>NaN</td>\n",
|
| 2044 |
+
" <td>NaN</td>\n",
|
| 2045 |
+
" <td>NaN</td>\n",
|
| 2046 |
+
" <td>NaN</td>\n",
|
| 2047 |
+
" <td>NaN</td>\n",
|
| 2048 |
+
" <td>NaN</td>\n",
|
| 2049 |
+
" <td>NaN</td>\n",
|
| 2050 |
+
" <td>Latin Urban</td>\n",
|
| 2051 |
+
" <td>NaN</td>\n",
|
| 2052 |
+
" <td>NaN</td>\n",
|
| 2053 |
+
" <td>NaN</td>\n",
|
| 2054 |
+
" <td>NaN</td>\n",
|
| 2055 |
+
" <td>NaN</td>\n",
|
| 2056 |
+
" <td>NaN</td>\n",
|
| 2057 |
+
" <td>NaN</td>\n",
|
| 2058 |
+
" <td>NaN</td>\n",
|
| 2059 |
+
" <td>NaN</td>\n",
|
| 2060 |
+
" <td>NaN</td>\n",
|
| 2061 |
+
" <td>NaN</td>\n",
|
| 2062 |
+
" <td>NaN</td>\n",
|
| 2063 |
+
" <td>NaN</td>\n",
|
| 2064 |
+
" <td>NaN</td>\n",
|
| 2065 |
+
" <td>NaN</td>\n",
|
| 2066 |
+
" </tr>\n",
|
| 2067 |
+
" </tbody>\n",
|
| 2068 |
+
"</table>\n",
|
| 2069 |
+
"<p>138690 rows × 42 columns</p>\n",
|
| 2070 |
+
"</div>"
|
| 2071 |
+
]
|
| 2072 |
+
},
|
| 2073 |
+
"execution_count": 2,
|
| 2074 |
+
"metadata": {},
|
| 2075 |
+
"output_type": "execute_result"
|
| 2076 |
+
}
|
| 2077 |
+
],
|
| 2078 |
+
"execution_count": 2
|
| 2079 |
+
},
|
| 2080 |
+
{
|
| 2081 |
+
"metadata": {},
|
| 2082 |
+
"cell_type": "code",
|
| 2083 |
+
"outputs": [],
|
| 2084 |
+
"execution_count": null,
|
| 2085 |
+
"source": "",
|
| 2086 |
+
"id": "a73b0a2127420c43"
|
| 2087 |
+
}
|
| 2088 |
+
],
|
| 2089 |
+
"metadata": {
|
| 2090 |
+
"kernelspec": {
|
| 2091 |
+
"display_name": "Python 3",
|
| 2092 |
+
"language": "python",
|
| 2093 |
+
"name": "python3"
|
| 2094 |
+
},
|
| 2095 |
+
"language_info": {
|
| 2096 |
+
"codemirror_mode": {
|
| 2097 |
+
"name": "ipython",
|
| 2098 |
+
"version": 2
|
| 2099 |
+
},
|
| 2100 |
+
"file_extension": ".py",
|
| 2101 |
+
"mimetype": "text/x-python",
|
| 2102 |
+
"name": "python",
|
| 2103 |
+
"nbconvert_exporter": "python",
|
| 2104 |
+
"pygments_lexer": "ipython2",
|
| 2105 |
+
"version": "2.7.6"
|
| 2106 |
+
}
|
| 2107 |
+
},
|
| 2108 |
+
"nbformat": 4,
|
| 2109 |
+
"nbformat_minor": 5
|
| 2110 |
+
}
|
all_combine_code/genre_classification/test1_file.ipynb
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"metadata": {
|
| 5 |
+
"ExecuteTime": {
|
| 6 |
+
"end_time": "2026-03-30T08:57:26.406625300Z",
|
| 7 |
+
"start_time": "2026-03-30T08:57:24.333887600Z"
|
| 8 |
+
}
|
| 9 |
+
},
|
| 10 |
+
"cell_type": "code",
|
| 11 |
+
"source": [
|
| 12 |
+
"import pandas as pd\n",
|
| 13 |
+
"import numpy as np\n",
|
| 14 |
+
"import re\n",
|
| 15 |
+
"import ast\n",
|
| 16 |
+
"from sklearn.model_selection import train_test_split\n",
|
| 17 |
+
"from sklearn.preprocessing import StandardScaler, MultiLabelBinarizer\n",
|
| 18 |
+
"from sklearn.multioutput import MultiOutputClassifier\n",
|
| 19 |
+
"from xgboost import XGBClassifier\n",
|
| 20 |
+
"from sklearn.metrics import f1_score\n",
|
| 21 |
+
"\n",
|
| 22 |
+
"path = r\"C:\\Users\\VigneshSubramani\\Downloads\\model_ready_dataset_final.csv\"\n",
|
| 23 |
+
"df = pd.read_csv(path)\n",
|
| 24 |
+
"\n"
|
| 25 |
+
],
|
| 26 |
+
"id": "89e4f464fdeba6a0",
|
| 27 |
+
"outputs": [],
|
| 28 |
+
"execution_count": 1
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"metadata": {
|
| 32 |
+
"ExecuteTime": {
|
| 33 |
+
"end_time": "2026-03-30T08:57:32.746112300Z",
|
| 34 |
+
"start_time": "2026-03-30T08:57:32.668847500Z"
|
| 35 |
+
}
|
| 36 |
+
},
|
| 37 |
+
"cell_type": "code",
|
| 38 |
+
"source": [
|
| 39 |
+
"def parse_subgenres_fast(text):\n",
|
| 40 |
+
" if pd.isna(text) or text == \"\":\n",
|
| 41 |
+
" return []\n",
|
| 42 |
+
" text = str(text)\n",
|
| 43 |
+
" parts = text.split('---')\n",
|
| 44 |
+
" main_genre = parts[0].strip()\n",
|
| 45 |
+
" if len(parts) > 1:\n",
|
| 46 |
+
" sub = parts[1].strip()\n",
|
| 47 |
+
" sub = sub.strip('[]')\n",
|
| 48 |
+
" if sub:\n",
|
| 49 |
+
" subgenres = [s.strip().strip(\"'\").strip('\"') for s in sub.split(',')]\n",
|
| 50 |
+
" else:\n",
|
| 51 |
+
" subgenres = []\n",
|
| 52 |
+
" else:\n",
|
| 53 |
+
" subgenres = []\n",
|
| 54 |
+
" return [main_genre] + subgenres\n",
|
| 55 |
+
"\n",
|
| 56 |
+
"df['subgenres_list'] = df['genre_subgenre'].apply(parse_subgenres_fast)\n",
|
| 57 |
+
"\n"
|
| 58 |
+
],
|
| 59 |
+
"id": "a0e5f938aaea7104",
|
| 60 |
+
"outputs": [],
|
| 61 |
+
"execution_count": 2
|
| 62 |
+
},
|
| 63 |
+
{
|
| 64 |
+
"metadata": {
|
| 65 |
+
"ExecuteTime": {
|
| 66 |
+
"end_time": "2026-03-30T08:57:56.751354200Z",
|
| 67 |
+
"start_time": "2026-03-30T08:57:56.682503800Z"
|
| 68 |
+
}
|
| 69 |
+
},
|
| 70 |
+
"cell_type": "code",
|
| 71 |
+
"source": "df['subgenres_list'][0]",
|
| 72 |
+
"id": "a361e82d41548901",
|
| 73 |
+
"outputs": [
|
| 74 |
+
{
|
| 75 |
+
"data": {
|
| 76 |
+
"text/plain": [
|
| 77 |
+
"['Rock', 'Noise Pop', 'Classic Alternative Rock', 'Indie Rock']"
|
| 78 |
+
]
|
| 79 |
+
},
|
| 80 |
+
"execution_count": 3,
|
| 81 |
+
"metadata": {},
|
| 82 |
+
"output_type": "execute_result"
|
| 83 |
+
}
|
| 84 |
+
],
|
| 85 |
+
"execution_count": 3
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"metadata": {
|
| 89 |
+
"ExecuteTime": {
|
| 90 |
+
"end_time": "2026-03-30T08:58:43.550962900Z",
|
| 91 |
+
"start_time": "2026-03-30T08:58:43.487876100Z"
|
| 92 |
+
}
|
| 93 |
+
},
|
| 94 |
+
"cell_type": "code",
|
| 95 |
+
"source": [
|
| 96 |
+
"mlb = MultiLabelBinarizer()\n",
|
| 97 |
+
"y = mlb.fit_transform(df['subgenres_list'])\n",
|
| 98 |
+
"y"
|
| 99 |
+
],
|
| 100 |
+
"id": "895aab114eb128a5",
|
| 101 |
+
"outputs": [
|
| 102 |
+
{
|
| 103 |
+
"data": {
|
| 104 |
+
"text/plain": [
|
| 105 |
+
"array([[0, 0, 0, ..., 0, 0, 0],\n",
|
| 106 |
+
" [0, 0, 0, ..., 0, 0, 0],\n",
|
| 107 |
+
" [0, 0, 0, ..., 0, 0, 0],\n",
|
| 108 |
+
" ...,\n",
|
| 109 |
+
" [0, 0, 0, ..., 0, 0, 0],\n",
|
| 110 |
+
" [0, 0, 0, ..., 1, 0, 0],\n",
|
| 111 |
+
" [0, 0, 0, ..., 0, 0, 0]], shape=(41935, 112))"
|
| 112 |
+
]
|
| 113 |
+
},
|
| 114 |
+
"execution_count": 4,
|
| 115 |
+
"metadata": {},
|
| 116 |
+
"output_type": "execute_result"
|
| 117 |
+
}
|
| 118 |
+
],
|
| 119 |
+
"execution_count": 4
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"metadata": {
|
| 123 |
+
"ExecuteTime": {
|
| 124 |
+
"end_time": "2026-03-30T09:02:12.058921100Z",
|
| 125 |
+
"start_time": "2026-03-30T08:59:11.763576600Z"
|
| 126 |
+
}
|
| 127 |
+
},
|
| 128 |
+
"cell_type": "code",
|
| 129 |
+
"source": [
|
| 130 |
+
"drop_cols = ['_id', 'genre', 'genre_subgenre', 'subgenres_list', 'labels',\n",
|
| 131 |
+
" 'trimmed_audio_duration_sec', 'syllable_count', 'word_count',\n",
|
| 132 |
+
" 'sentiment_score', 'avg_word_length']\n",
|
| 133 |
+
"\n",
|
| 134 |
+
"feature_cols = [c for c in df.columns if c not in drop_cols]\n",
|
| 135 |
+
"X = df[feature_cols].copy()\n",
|
| 136 |
+
"X = X.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 137 |
+
"\n",
|
| 138 |
+
"X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n",
|
| 139 |
+
"\n",
|
| 140 |
+
"scaler = StandardScaler()\n",
|
| 141 |
+
"X_train_scaled = scaler.fit_transform(X_train)\n",
|
| 142 |
+
"X_test_scaled = scaler.transform(X_test)\n",
|
| 143 |
+
"\n",
|
| 144 |
+
"model = MultiOutputClassifier(\n",
|
| 145 |
+
" XGBClassifier(\n",
|
| 146 |
+
" n_estimators=1500,\n",
|
| 147 |
+
" learning_rate=0.03,\n",
|
| 148 |
+
" max_depth=8,\n",
|
| 149 |
+
" subsample=0.85,\n",
|
| 150 |
+
" colsample_bytree=0.8,\n",
|
| 151 |
+
" random_state=42,\n",
|
| 152 |
+
" eval_metric='logloss'\n",
|
| 153 |
+
" ),\n",
|
| 154 |
+
" n_jobs=-1\n",
|
| 155 |
+
")\n",
|
| 156 |
+
"\n",
|
| 157 |
+
"model.fit(X_train_scaled, y_train)\n",
|
| 158 |
+
"\n",
|
| 159 |
+
"def get_accuracy(X_scaled, y_true, name=\"\"):\n",
|
| 160 |
+
" y_pred = model.predict(X_scaled)\n",
|
| 161 |
+
" subset_acc = np.mean(np.all(y_pred == y_true, axis=1))\n",
|
| 162 |
+
" micro_f1 = f1_score(y_true, y_pred, average='micro')\n",
|
| 163 |
+
" print(f\"{name} Subset Accuracy: {subset_acc:.4f}\")\n",
|
| 164 |
+
" print(f\"{name} Micro F1 Score: {micro_f1:.4f}\")\n",
|
| 165 |
+
"\n",
|
| 166 |
+
"print(\"=== MODEL ACCURACY ===\")\n",
|
| 167 |
+
"get_accuracy(X_train_scaled, y_train, \"Train\")\n",
|
| 168 |
+
"get_accuracy(X_test_scaled, y_test, \"Test\")\n",
|
| 169 |
+
"\n",
|
| 170 |
+
"def manual_predict(row):\n",
|
| 171 |
+
" input_df = pd.DataFrame([row])\n",
|
| 172 |
+
" input_numeric = input_df.select_dtypes(include=[np.number]).fillna(0)\n",
|
| 173 |
+
"\n",
|
| 174 |
+
" for col in X.columns:\n",
|
| 175 |
+
" if col not in input_numeric.columns:\n",
|
| 176 |
+
" input_numeric[col] = 0\n",
|
| 177 |
+
" input_numeric = input_numeric[X.columns]\n",
|
| 178 |
+
" input_scaled = scaler.transform(input_numeric)\n",
|
| 179 |
+
" probas = model.predict_proba(input_scaled)\n",
|
| 180 |
+
" threshold = 0.20\n",
|
| 181 |
+
" pred_binary = np.zeros((1, y.shape[1]), dtype=int)\n",
|
| 182 |
+
" for i in range(len(probas)):\n",
|
| 183 |
+
" if probas[i][0][1] > threshold:\n",
|
| 184 |
+
" pred_binary[0, i] = 1\n",
|
| 185 |
+
" subgenres = mlb.inverse_transform(pred_binary)[0]\n",
|
| 186 |
+
" if len(subgenres) == 0:\n",
|
| 187 |
+
" return f\"None\"\n",
|
| 188 |
+
" return f\"{', '.join(sorted(subgenres))}\""
|
| 189 |
+
],
|
| 190 |
+
"id": "8ff23ad3daa3533f",
|
| 191 |
+
"outputs": [
|
| 192 |
+
{
|
| 193 |
+
"name": "stdout",
|
| 194 |
+
"output_type": "stream",
|
| 195 |
+
"text": [
|
| 196 |
+
"=== MODEL ACCURACY ===\n",
|
| 197 |
+
"Train Subset Accuracy: 0.9961\n",
|
| 198 |
+
"Train Micro F1 Score: 0.9992\n",
|
| 199 |
+
"Test Subset Accuracy: 0.1909\n",
|
| 200 |
+
"Test Micro F1 Score: 0.5527\n"
|
| 201 |
+
]
|
| 202 |
+
}
|
| 203 |
+
],
|
| 204 |
+
"execution_count": 5
|
| 205 |
+
},
|
| 206 |
+
{
|
| 207 |
+
"metadata": {
|
| 208 |
+
"ExecuteTime": {
|
| 209 |
+
"end_time": "2026-03-30T09:03:23.140843Z",
|
| 210 |
+
"start_time": "2026-03-30T09:03:23.087981400Z"
|
| 211 |
+
}
|
| 212 |
+
},
|
| 213 |
+
"cell_type": "code",
|
| 214 |
+
"source": "df[[\"subgenres_list\",\"genre\"]].iloc[501,]",
|
| 215 |
+
"id": "d8574c668e09217f",
|
| 216 |
+
"outputs": [
|
| 217 |
+
{
|
| 218 |
+
"data": {
|
| 219 |
+
"text/plain": [
|
| 220 |
+
"subgenres_list [Rock, Classic Alternative Rock]\n",
|
| 221 |
+
"genre Rock\n",
|
| 222 |
+
"Name: 501, dtype: object"
|
| 223 |
+
]
|
| 224 |
+
},
|
| 225 |
+
"execution_count": 8,
|
| 226 |
+
"metadata": {},
|
| 227 |
+
"output_type": "execute_result"
|
| 228 |
+
}
|
| 229 |
+
],
|
| 230 |
+
"execution_count": 8
|
| 231 |
+
},
|
| 232 |
+
{
|
| 233 |
+
"metadata": {
|
| 234 |
+
"ExecuteTime": {
|
| 235 |
+
"end_time": "2026-03-30T09:03:31.570593500Z",
|
| 236 |
+
"start_time": "2026-03-30T09:03:31.234859400Z"
|
| 237 |
+
}
|
| 238 |
+
},
|
| 239 |
+
"cell_type": "code",
|
| 240 |
+
"source": [
|
| 241 |
+
"print(\"\\n=== FIRST ROW PREDICTION ===\")\n",
|
| 242 |
+
"print(manual_predict(X.iloc[501].to_dict()))\n",
|
| 243 |
+
"\n",
|
| 244 |
+
"print(\"\\n=== LAST ROW PREDICTION ===\")\n",
|
| 245 |
+
"print(manual_predict(X.iloc[-1].to_dict()))"
|
| 246 |
+
],
|
| 247 |
+
"id": "d311cd5e9a4aa105",
|
| 248 |
+
"outputs": [
|
| 249 |
+
{
|
| 250 |
+
"name": "stdout",
|
| 251 |
+
"output_type": "stream",
|
| 252 |
+
"text": [
|
| 253 |
+
"\n",
|
| 254 |
+
"=== FIRST ROW PREDICTION ===\n",
|
| 255 |
+
"Modern Alternative Rock, Rock\n",
|
| 256 |
+
"\n",
|
| 257 |
+
"=== LAST ROW PREDICTION ===\n",
|
| 258 |
+
"Gothic Metal, Heavy Metal, Metal, Metalcore\n"
|
| 259 |
+
]
|
| 260 |
+
}
|
| 261 |
+
],
|
| 262 |
+
"execution_count": 9
|
| 263 |
+
},
|
| 264 |
+
{
|
| 265 |
+
"metadata": {
|
| 266 |
+
"ExecuteTime": {
|
| 267 |
+
"end_time": "2026-03-30T09:04:23.092797900Z",
|
| 268 |
+
"start_time": "2026-03-30T09:04:20.630659500Z"
|
| 269 |
+
}
|
| 270 |
+
},
|
| 271 |
+
"cell_type": "code",
|
| 272 |
+
"source": [
|
| 273 |
+
"import joblib\n",
|
| 274 |
+
"bundle = {\n",
|
| 275 |
+
" \"model\": model,\n",
|
| 276 |
+
" \"scaler\": scaler,\n",
|
| 277 |
+
" \"mlb\": mlb,\n",
|
| 278 |
+
" \"feature_cols\": X.columns.tolist()\n",
|
| 279 |
+
"}\n",
|
| 280 |
+
"joblib.dump(bundle, \"genre_model1.pkl\")\n",
|
| 281 |
+
"print(\"Model saved successfully!\")"
|
| 282 |
+
],
|
| 283 |
+
"id": "58b9f6372caf42f",
|
| 284 |
+
"outputs": [
|
| 285 |
+
{
|
| 286 |
+
"name": "stdout",
|
| 287 |
+
"output_type": "stream",
|
| 288 |
+
"text": [
|
| 289 |
+
"Model saved successfully!\n"
|
| 290 |
+
]
|
| 291 |
+
}
|
| 292 |
+
],
|
| 293 |
+
"execution_count": 10
|
| 294 |
+
},
|
| 295 |
+
{
|
| 296 |
+
"metadata": {},
|
| 297 |
+
"cell_type": "code",
|
| 298 |
+
"outputs": [],
|
| 299 |
+
"execution_count": null,
|
| 300 |
+
"source": "",
|
| 301 |
+
"id": "c3b93bc28cd98983"
|
| 302 |
+
}
|
| 303 |
+
],
|
| 304 |
+
"metadata": {
|
| 305 |
+
"kernelspec": {
|
| 306 |
+
"display_name": "Python 3",
|
| 307 |
+
"language": "python",
|
| 308 |
+
"name": "python3"
|
| 309 |
+
},
|
| 310 |
+
"language_info": {
|
| 311 |
+
"codemirror_mode": {
|
| 312 |
+
"name": "ipython",
|
| 313 |
+
"version": 2
|
| 314 |
+
},
|
| 315 |
+
"file_extension": ".py",
|
| 316 |
+
"mimetype": "text/x-python",
|
| 317 |
+
"name": "python",
|
| 318 |
+
"nbconvert_exporter": "python",
|
| 319 |
+
"pygments_lexer": "ipython2",
|
| 320 |
+
"version": "2.7.6"
|
| 321 |
+
}
|
| 322 |
+
},
|
| 323 |
+
"nbformat": 4,
|
| 324 |
+
"nbformat_minor": 5
|
| 325 |
+
}
|
all_combine_code/genreclassification/.env
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
VIBERATE_API=rot6LA7vWzUCdb2ZxmjOQSiUYyPuXcZI
|
all_combine_code/genreclassification/Dockerfile
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Copy requirements.txt and install dependencies
|
| 6 |
+
COPY requirements.txt .
|
| 7 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
| 8 |
+
|
| 9 |
+
# Copy the entire project
|
| 10 |
+
COPY . .
|
| 11 |
+
|
| 12 |
+
# Ensure the songs_data directory exists and has write permissions
|
| 13 |
+
RUN mkdir -p songs_data && chmod -R 755 songs_data
|
| 14 |
+
|
| 15 |
+
# Set environment variable for unbuffered output
|
| 16 |
+
ENV PYTHONUNBUFFERED=1
|
| 17 |
+
|
| 18 |
+
# Expose port
|
| 19 |
+
EXPOSE 5003
|
| 20 |
+
|
| 21 |
+
# Command to run the app
|
| 22 |
+
CMD ["uvicorn", "main_app:app", "--host", "0.0.0.0", "--port", "5003"]
|
all_combine_code/genreclassification/chartmetric_summary_generator3.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, re, json
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import difflib
|
| 5 |
+
|
| 6 |
+
from viberate_api_report_generator2 import (
|
| 7 |
+
potential_fanbase_count,
|
| 8 |
+
similarity_songs_report,
|
| 9 |
+
)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def chartmetric_manual_generator(task_id: str, session_dir: str):
|
| 13 |
+
songs_list, df = similarity_songs_report(task_id)
|
| 14 |
+
if df is None or df.empty:
|
| 15 |
+
return None, None
|
| 16 |
+
|
| 17 |
+
excel_path = potential_fanbase_count(songs_list, session_dir)
|
| 18 |
+
|
| 19 |
+
if not isinstance(excel_path, str) or not excel_path.endswith(".xlsx"):
|
| 20 |
+
return None, None
|
| 21 |
+
|
| 22 |
+
if os.path.exists(excel_path) and os.path.getsize(excel_path) > 0:
|
| 23 |
+
try:
|
| 24 |
+
pd.ExcelFile(excel_path)
|
| 25 |
+
return excel_path, df
|
| 26 |
+
except Exception:
|
| 27 |
+
return None, None
|
| 28 |
+
|
| 29 |
+
return None, None
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def final_score_calculation(task_id: str, session_dir: str) -> int | None:
|
| 33 |
+
workbook_path, similarity_dataframe = chartmetric_manual_generator(task_id, session_dir)
|
| 34 |
+
if workbook_path is None:
|
| 35 |
+
return None
|
| 36 |
+
|
| 37 |
+
ALIASES = {
|
| 38 |
+
"spotify_streams": ["spotify streams", "spotify stream", "spotify s"],
|
| 39 |
+
"youtube_views": ["youtube views", "youtube view", "youtube video"],
|
| 40 |
+
"soundcloud_streams": ["soundcloud streams", "soundcloud plays", "sound"],
|
| 41 |
+
}
|
| 42 |
+
RPF_LOW, RPF_MID, RPF_HIGH = 4.0, 2.5, 1.5
|
| 43 |
+
USE_DECAY = True
|
| 44 |
+
HALF_LIFE_YRS = 5
|
| 45 |
+
LIFT_WEIGHTS = {"youtube_views": 0.60, "soundcloud_streams": 0.40}
|
| 46 |
+
LAMBDA = 0.10
|
| 47 |
+
LIFT_MIN_MAX = (0.7, 1.5)
|
| 48 |
+
OVERLAP_BOUNDS = (0.30, 0.60)
|
| 49 |
+
OVERLAP_PENALTY_K = 2.5
|
| 50 |
+
RECENCY_EXPONENT = 1.0
|
| 51 |
+
AUDIENCE_SMOOTHING_LAMBDA = 0.15
|
| 52 |
+
|
| 53 |
+
# ── helpers ─────────────────────────────────────────────────────────────
|
| 54 |
+
def _norm(s):
|
| 55 |
+
return re.sub(r"\s+", " ", str(s).replace("\n", " ").strip().lower())
|
| 56 |
+
|
| 57 |
+
def resolve_col(cols, aliases):
|
| 58 |
+
norm_cols = {c: _norm(c) for c in cols}
|
| 59 |
+
alias_norms = [_norm(a) for a in aliases]
|
| 60 |
+
for c, nc in norm_cols.items():
|
| 61 |
+
for a in alias_norms:
|
| 62 |
+
if nc == a or nc.startswith(a) or a in nc:
|
| 63 |
+
return c
|
| 64 |
+
best_col, best_ratio = None, 0.0
|
| 65 |
+
for c, nc in norm_cols.items():
|
| 66 |
+
for a in alias_norms:
|
| 67 |
+
r = difflib.SequenceMatcher(None, nc, a).ratio()
|
| 68 |
+
if r > best_ratio:
|
| 69 |
+
best_ratio, best_col = r, c
|
| 70 |
+
return best_col if best_ratio >= 0.60 else None
|
| 71 |
+
|
| 72 |
+
def last_non_null(series):
|
| 73 |
+
s = pd.to_numeric(series, errors="coerce").dropna()
|
| 74 |
+
return s.iloc[-1] if not s.empty else np.nan
|
| 75 |
+
|
| 76 |
+
def robust_z(series):
|
| 77 |
+
s = pd.to_numeric(series, errors="coerce")
|
| 78 |
+
x = s.dropna()
|
| 79 |
+
if len(x) < 3:
|
| 80 |
+
return pd.Series(0.0, index=s.index)
|
| 81 |
+
med = x.median()
|
| 82 |
+
mad = (x - med).abs().median()
|
| 83 |
+
if mad == 0 or np.isnan(mad):
|
| 84 |
+
return pd.Series(0.0, index=s.index)
|
| 85 |
+
return (0.6745 * (s - med) / mad).clip(-5, 5).fillna(0.0)
|
| 86 |
+
|
| 87 |
+
def simpson_overlap(weights, bounds=(0.30, 0.60)):
|
| 88 |
+
w = pd.to_numeric(weights, errors="coerce").fillna(0.0).values
|
| 89 |
+
if w.sum() <= 0:
|
| 90 |
+
return float(np.mean(bounds))
|
| 91 |
+
w = w / w.sum()
|
| 92 |
+
K = len(w)
|
| 93 |
+
if K <= 1:
|
| 94 |
+
return float(bounds[1])
|
| 95 |
+
C = float((w ** 2).sum())
|
| 96 |
+
base = 1.0 / K
|
| 97 |
+
low, high = bounds
|
| 98 |
+
return float(np.clip(low + (high - low) * ((C - base) / (1 - base)), low, high))
|
| 99 |
+
|
| 100 |
+
def ensure_weights(sel_df, use_decay=True, half_life=5):
|
| 101 |
+
sel = sel_df.copy()
|
| 102 |
+
if "release_date" in sel.columns:
|
| 103 |
+
sel["release_date"] = (
|
| 104 |
+
pd.to_datetime(sel["release_date"], errors="coerce").dt.tz_localize(None)
|
| 105 |
+
)
|
| 106 |
+
age_years = (pd.Timestamp.today().normalize() - sel["release_date"]).dt.days / 365.25
|
| 107 |
+
if use_decay and sel["release_date"].notna().any():
|
| 108 |
+
sel["recency_decay"] = (
|
| 109 |
+
np.exp(-0.693 * (age_years.fillna(age_years.median()) / half_life))
|
| 110 |
+
** RECENCY_EXPONENT
|
| 111 |
+
)
|
| 112 |
+
else:
|
| 113 |
+
sel["recency_decay"] = 1.0
|
| 114 |
+
else:
|
| 115 |
+
sel["recency_decay"] = 1.0
|
| 116 |
+
|
| 117 |
+
if "raw_weight" not in sel.columns:
|
| 118 |
+
sel["raw_weight"] = sel["final_boosted_score"] * sel["recency_decay"]
|
| 119 |
+
|
| 120 |
+
tot = sel["raw_weight"].sum()
|
| 121 |
+
sel["weight_norm"] = sel["raw_weight"] / tot if tot > 0 else 1.0 / len(sel)
|
| 122 |
+
sel = sel.sort_values("weight_norm", ascending=False)
|
| 123 |
+
sel["cum_weight"] = sel["weight_norm"].cumsum()
|
| 124 |
+
return sel
|
| 125 |
+
|
| 126 |
+
all_tracks = pd.read_excel(workbook_path, sheet_name=None)
|
| 127 |
+
rows = []
|
| 128 |
+
for sheet_name, df in all_tracks.items():
|
| 129 |
+
df.columns = (
|
| 130 |
+
df.columns.astype(str)
|
| 131 |
+
.str.replace("\n", " ", regex=True)
|
| 132 |
+
.str.replace(r"\s+", " ", regex=True)
|
| 133 |
+
.str.strip()
|
| 134 |
+
)
|
| 135 |
+
col_spotify = resolve_col(df.columns, ALIASES["spotify_streams"])
|
| 136 |
+
col_youtube = resolve_col(df.columns, ALIASES["youtube_views"])
|
| 137 |
+
col_soundcloud = resolve_col(df.columns, ALIASES["soundcloud_streams"])
|
| 138 |
+
|
| 139 |
+
spotify_streams = last_non_null(df[col_spotify]) if col_spotify else np.nan
|
| 140 |
+
youtube_views = last_non_null(df[col_youtube]) if col_youtube else np.nan
|
| 141 |
+
soundcloud_streams = last_non_null(df[col_soundcloud]) if col_soundcloud else np.nan
|
| 142 |
+
|
| 143 |
+
if pd.notna(spotify_streams):
|
| 144 |
+
listeners_low = spotify_streams / RPF_LOW
|
| 145 |
+
listeners_mid = spotify_streams / RPF_MID
|
| 146 |
+
listeners_high = spotify_streams / RPF_HIGH
|
| 147 |
+
else:
|
| 148 |
+
listeners_low = listeners_mid = listeners_high = np.nan
|
| 149 |
+
|
| 150 |
+
rows.append({
|
| 151 |
+
"track": sheet_name,
|
| 152 |
+
"spotify_streams": spotify_streams,
|
| 153 |
+
"listeners_low": listeners_low,
|
| 154 |
+
"listeners_mid": listeners_mid,
|
| 155 |
+
"listeners_high": listeners_high,
|
| 156 |
+
"youtube_views": youtube_views,
|
| 157 |
+
"soundcloud_streams": soundcloud_streams,
|
| 158 |
+
})
|
| 159 |
+
|
| 160 |
+
summary_df = pd.DataFrame(rows)
|
| 161 |
+
|
| 162 |
+
# ── part 3 – weighted score ──────────────────────────────────────────────
|
| 163 |
+
selected_tracks = similarity_dataframe.copy()
|
| 164 |
+
selected_tracks.columns = (
|
| 165 |
+
selected_tracks.columns.astype(str)
|
| 166 |
+
.str.replace("\n", " ", regex=True)
|
| 167 |
+
.str.replace(r"\s+", " ", regex=True)
|
| 168 |
+
.str.strip()
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
sel = ensure_weights(selected_tracks, use_decay=USE_DECAY, half_life=HALF_LIFE_YRS)
|
| 172 |
+
sel = sel.sort_values("weight_norm", ascending=False).reset_index(drop=True)
|
| 173 |
+
sel["rank"] = np.arange(1, len(sel) + 1)
|
| 174 |
+
|
| 175 |
+
p2 = summary_df.copy()
|
| 176 |
+
p2["rank"] = p2["track"].str.extract(r"^(\d+)").astype(float).astype("Int64")
|
| 177 |
+
if p2["rank"].isna().any():
|
| 178 |
+
seq = np.arange(1, len(p2) + 1)
|
| 179 |
+
p2.loc[p2["rank"].isna(), "rank"] = seq[p2["rank"].isna()]
|
| 180 |
+
p2["rank"] = p2["rank"].astype(int)
|
| 181 |
+
|
| 182 |
+
cols_keep_sel = [
|
| 183 |
+
"rank", "artist_names", "name", "album", "final_boosted_score",
|
| 184 |
+
"release_date", "weight_norm", "cum_weight", "track_url",
|
| 185 |
+
]
|
| 186 |
+
merged = pd.merge(
|
| 187 |
+
sel[cols_keep_sel],
|
| 188 |
+
p2[["rank", "track",
|
| 189 |
+
"spotify_streams", "listeners_low", "listeners_mid", "listeners_high",
|
| 190 |
+
"youtube_views", "soundcloud_streams"]],
|
| 191 |
+
on="rank", how="inner",
|
| 192 |
+
)
|
| 193 |
+
|
| 194 |
+
for col in ["youtube_views", "soundcloud_streams"]:
|
| 195 |
+
merged[f"z_{col}"] = robust_z(merged[col])
|
| 196 |
+
|
| 197 |
+
def weighted_z(row):
|
| 198 |
+
num = den = 0.0
|
| 199 |
+
for col, w in LIFT_WEIGHTS.items():
|
| 200 |
+
z = row.get(f"z_{col}", 0.0)
|
| 201 |
+
if pd.notna(z):
|
| 202 |
+
num += w * z
|
| 203 |
+
den += w
|
| 204 |
+
return num / den if den > 0 else 0.0
|
| 205 |
+
|
| 206 |
+
merged["z_weighted"] = merged.apply(weighted_z, axis=1)
|
| 207 |
+
lo, hi = LIFT_MIN_MAX
|
| 208 |
+
merged["lift"] = (1.0 + LAMBDA * merged["z_weighted"]).clip(lower=lo, upper=hi)
|
| 209 |
+
|
| 210 |
+
def safe_mul(a, b, c):
|
| 211 |
+
if pd.isna(b): b = 0.0
|
| 212 |
+
return float(a) * float(b) * float(c)
|
| 213 |
+
|
| 214 |
+
merged["contrib_low"] = merged.apply(lambda r: safe_mul(r["weight_norm"], r["listeners_low"], r["lift"]), axis=1)
|
| 215 |
+
merged["contrib_mid"] = merged.apply(lambda r: safe_mul(r["weight_norm"], r["listeners_mid"], r["lift"]), axis=1)
|
| 216 |
+
merged["contrib_high"] = merged.apply(lambda r: safe_mul(r["weight_norm"], r["listeners_high"], r["lift"]), axis=1)
|
| 217 |
+
|
| 218 |
+
raw = {
|
| 219 |
+
"low": merged["contrib_low"].sum(),
|
| 220 |
+
"mid": merged["contrib_mid"].sum(),
|
| 221 |
+
"high": merged["contrib_high"].sum(),
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
analog_totals = (merged["listeners_mid"] * merged["lift"]).fillna(0)
|
| 225 |
+
AUDIENCE_BASELINE = int(np.median(analog_totals) * 20)
|
| 226 |
+
|
| 227 |
+
overlap = simpson_overlap(merged["weight_norm"], OVERLAP_BOUNDS)
|
| 228 |
+
overlap_penalty = min(1.0, OVERLAP_PENALTY_K * overlap)
|
| 229 |
+
final = {k: v * (1.0 - overlap_penalty) for k, v in raw.items()}
|
| 230 |
+
|
| 231 |
+
pred_audience = (
|
| 232 |
+
(1 - AUDIENCE_SMOOTHING_LAMBDA) * final["mid"] +
|
| 233 |
+
AUDIENCE_SMOOTHING_LAMBDA * AUDIENCE_BASELINE
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
summary_df.to_csv(os.path.join(session_dir, "part2_chartmetric_summary.csv"), index=False)
|
| 237 |
+
|
| 238 |
+
detail_cols = [
|
| 239 |
+
"rank", "artist_names", "name", "track", "weight_norm",
|
| 240 |
+
"listeners_low", "listeners_mid", "listeners_high",
|
| 241 |
+
"youtube_views", "soundcloud_streams",
|
| 242 |
+
"z_youtube_views", "z_soundcloud_streams",
|
| 243 |
+
"z_weighted", "lift", "contrib_low", "contrib_mid", "contrib_high",
|
| 244 |
+
]
|
| 245 |
+
detail_df = merged[detail_cols].sort_values("rank")
|
| 246 |
+
for col in ["listeners_low", "listeners_mid", "listeners_high",
|
| 247 |
+
"contrib_low", "contrib_mid", "contrib_high"]:
|
| 248 |
+
detail_df[col] = detail_df[col].round(0).astype("Int64")
|
| 249 |
+
detail_df["weight_norm"] = detail_df["weight_norm"].round(4)
|
| 250 |
+
detail_df["lift"] = detail_df["lift"].round(3)
|
| 251 |
+
detail_df["z_weighted"] = detail_df["z_weighted"].round(3)
|
| 252 |
+
detail_df.to_csv(os.path.join(session_dir, "part3_contributions_detail.csv"), index=False)
|
| 253 |
+
|
| 254 |
+
summary_out = {
|
| 255 |
+
"overlap_weights_based": float(round(overlap, 6)),
|
| 256 |
+
"totals_raw": raw,
|
| 257 |
+
"totals_final": final,
|
| 258 |
+
"AUDIENCE_BASELINE": AUDIENCE_BASELINE,
|
| 259 |
+
"pred_audience_smooth": int(pred_audience),
|
| 260 |
+
}
|
| 261 |
+
with open(os.path.join(session_dir, "part3_summary.json"), "w") as f:
|
| 262 |
+
json.dump(summary_out, f, indent=2)
|
| 263 |
+
|
| 264 |
+
return int(pred_audience)
|
all_combine_code/genreclassification/main_app.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gc
|
| 2 |
+
import logging
|
| 3 |
+
import os
|
| 4 |
+
import shutil
|
| 5 |
+
import tempfile
|
| 6 |
+
import time
|
| 7 |
+
|
| 8 |
+
from fastapi import FastAPI, Request
|
| 9 |
+
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
+
|
| 11 |
+
from chartmetric_summary_generator3 import final_score_calculation
|
| 12 |
+
|
| 13 |
+
app = FastAPI()
|
| 14 |
+
app.add_middleware(
|
| 15 |
+
CORSMiddleware,
|
| 16 |
+
allow_origins=["*"],
|
| 17 |
+
allow_credentials=True,
|
| 18 |
+
allow_methods=["*"],
|
| 19 |
+
allow_headers=["*"],
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 23 |
+
SESSIONS_ROOT = os.path.join(BASE_DIR, "sessions") # all per-request dirs live here
|
| 24 |
+
os.makedirs(SESSIONS_ROOT, exist_ok=True)
|
| 25 |
+
|
| 26 |
+
logging.basicConfig(
|
| 27 |
+
level=logging.INFO,
|
| 28 |
+
format="%(asctime)s - %(levelname)s - %(message)s",
|
| 29 |
+
)
|
| 30 |
+
logger = logging.getLogger(__name__)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _cleanup_session(session_dir: str) -> None:
|
| 34 |
+
"""Remove the entire per-request temp directory, tolerating locked files."""
|
| 35 |
+
if not os.path.exists(session_dir):
|
| 36 |
+
return
|
| 37 |
+
try:
|
| 38 |
+
gc.collect()
|
| 39 |
+
time.sleep(0.1)
|
| 40 |
+
shutil.rmtree(session_dir, ignore_errors=True)
|
| 41 |
+
logger.info(f"Cleaned up session dir: {session_dir}")
|
| 42 |
+
except Exception as e:
|
| 43 |
+
logger.warning(f"Could not fully clean up {session_dir}: {e}")
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
@app.get("/")
|
| 47 |
+
def read_root():
|
| 48 |
+
return {"Hello": "200 ok"}
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
@app.post("/fanbase_score")
|
| 52 |
+
async def fanbase_score(request: Request):
|
| 53 |
+
session_dir = tempfile.mkdtemp(dir=SESSIONS_ROOT)
|
| 54 |
+
logger.info(f"Created session dir: {session_dir}")
|
| 55 |
+
|
| 56 |
+
try:
|
| 57 |
+
data = await request.json()
|
| 58 |
+
task_id = data["track_id"].strip()
|
| 59 |
+
logger.info(f"Processing start – task_id={task_id} session={session_dir}")
|
| 60 |
+
|
| 61 |
+
fanbase_score_count = final_score_calculation(task_id, session_dir)
|
| 62 |
+
logger.info(f"Fan Base Count: {fanbase_score_count}")
|
| 63 |
+
|
| 64 |
+
return {"fanbase_count": fanbase_score_count}
|
| 65 |
+
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.error(f"Request failed: {e}")
|
| 68 |
+
return {"error": str(e)}
|
| 69 |
+
|
| 70 |
+
finally:
|
| 71 |
+
_cleanup_session(session_dir)
|
all_combine_code/genreclassification/requirements.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi==0.119.0
|
| 2 |
+
uvicorn==0.37.0
|
| 3 |
+
pandas==2.3.3
|
| 4 |
+
XlsxWriter==3.2.9
|
| 5 |
+
aiohttp==3.12.15
|
| 6 |
+
python-dotenv== 1.1.1
|
| 7 |
+
openpyxl==3.1.5
|
| 8 |
+
requests==2.32.5
|
all_combine_code/genreclassification/songs_report_generator1.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
warnings.filterwarnings("ignore")
|
| 3 |
+
|
| 4 |
+
import requests
|
| 5 |
+
import pandas as pd
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def fetch_similar_tracks_df(task_id):
|
| 9 |
+
"""
|
| 10 |
+
Fetch similar tracks for a given task_id and return as a pandas DataFrame.
|
| 11 |
+
"""
|
| 12 |
+
url = f"https://aimusic-reprocessing.kovaionplay.com/api/v1/progress/task/{task_id}"
|
| 13 |
+
headers = {
|
| 14 |
+
"x-api-key": "$2b$12$VXkJYUlA2ZMxX25dZ47g7uNVfERnghpvd690vgpy0wVHef/KSDMPq",
|
| 15 |
+
"x-user-id": "6819fa6f63df2ba55d256ee1"
|
| 16 |
+
}
|
| 17 |
+
response = requests.get(url, headers=headers)
|
| 18 |
+
|
| 19 |
+
if response.status_code == 404:
|
| 20 |
+
url = f"https://aimusic-backend-stage.kovaionplay.com/api/v1/progress/task/{task_id}"
|
| 21 |
+
response = requests.get(url, headers=headers)
|
| 22 |
+
|
| 23 |
+
if response.status_code != 200:
|
| 24 |
+
raise Exception(f"Error: Received status code {response.status_code}, {response.text}")
|
| 25 |
+
|
| 26 |
+
data = response.json()
|
| 27 |
+
similar_tracks = data.get("similar_tracks", [])
|
| 28 |
+
|
| 29 |
+
if not similar_tracks:
|
| 30 |
+
return pd.DataFrame()
|
| 31 |
+
|
| 32 |
+
rows = []
|
| 33 |
+
for track in similar_tracks:
|
| 34 |
+
track_info = track.get("track_info", {})
|
| 35 |
+
similarity_scores = track.get("similarity_scores", {})
|
| 36 |
+
artist_info_list = track.get("artist_info", [])
|
| 37 |
+
row = {
|
| 38 |
+
"final_boosted_score": track.get("final_boosted_score", "-"),
|
| 39 |
+
"album": track_info.get("album", "-"),
|
| 40 |
+
"track_url": track_info.get("track_url", "-"),
|
| 41 |
+
"name": track_info.get("name", "-"),
|
| 42 |
+
"release_date": track_info.get("release_date", "-"),
|
| 43 |
+
"top_song": track_info.get("top_song", "-"),
|
| 44 |
+
"artist_names": "|".join(artist.get("artist_name", "-") for artist in artist_info_list),
|
| 45 |
+
"chord_progression_similarity": similarity_scores.get("chord_progression_similarity", "-"),
|
| 46 |
+
"general_audio_similarity": similarity_scores.get("general_audio_similarity", "-"),
|
| 47 |
+
"lyrics_similarity": similarity_scores.get("lyrics_similarity", "-"),
|
| 48 |
+
"timber_similarity": similarity_scores.get("timber_similarity", "-"),
|
| 49 |
+
"vocal_melody_similarity": similarity_scores.get("vocal_melody_similarity", "-"),
|
| 50 |
+
"rhythm_similarity": similarity_scores.get("rhythm_similarity", "-"),
|
| 51 |
+
"harmonic_similarity": similarity_scores.get("harmonic_similarity", "-"),
|
| 52 |
+
}
|
| 53 |
+
rows.append(row)
|
| 54 |
+
|
| 55 |
+
df = pd.DataFrame(rows)
|
| 56 |
+
return df
|
| 57 |
+
|
| 58 |
+
# if __name__ == "__main__":
|
| 59 |
+
# task_id = "69afc81760ed587a34dfc57a"
|
| 60 |
+
# df = fetch_similar_tracks_df(task_id)
|
| 61 |
+
# print(df)
|
| 62 |
+
#
|
| 63 |
+
# print(df.columns)
|
| 64 |
+
#
|
| 65 |
+
#
|
| 66 |
+
# print(df["name"].tolist())
|
all_combine_code/genreclassification/viberate_api_report_generator2.py
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, re, logging
|
| 2 |
+
import pandas as pd
|
| 3 |
+
import numpy as np
|
| 4 |
+
import aiohttp
|
| 5 |
+
import asyncio
|
| 6 |
+
from datetime import datetime, timedelta
|
| 7 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
|
| 10 |
+
load_dotenv()
|
| 11 |
+
|
| 12 |
+
pd.set_option('display.max_columns', None)
|
| 13 |
+
|
| 14 |
+
api_token = os.getenv("VIBERATE_API")
|
| 15 |
+
date_to = datetime.today().date()
|
| 16 |
+
date_from = date_to - timedelta(days=365)
|
| 17 |
+
|
| 18 |
+
logging.basicConfig(level=logging.INFO)
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
from songs_report_generator1 import fetch_similar_tracks_df
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def similarity_songs_report(task_id):
|
| 25 |
+
df = fetch_similar_tracks_df(task_id)
|
| 26 |
+
if df.empty:
|
| 27 |
+
return {"message": "No similar tracks found"}, None
|
| 28 |
+
return df["name"].tolist(), df
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
class ViberateAPI:
|
| 32 |
+
"""
|
| 33 |
+
All file I/O is scoped to `session_dir` so concurrent requests
|
| 34 |
+
never collide on the filesystem.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(self, session_dir: str):
|
| 38 |
+
self.uuid_cache = {}
|
| 39 |
+
self.base_folder = os.path.join(session_dir, "songs_data")
|
| 40 |
+
os.makedirs(self.base_folder, exist_ok=True)
|
| 41 |
+
|
| 42 |
+
async def get_uuid_from_song_name(self, session, song_name):
|
| 43 |
+
if song_name in self.uuid_cache:
|
| 44 |
+
return self.uuid_cache[song_name]
|
| 45 |
+
try:
|
| 46 |
+
url = "https://data.viberate.com/api/v1/track/search"
|
| 47 |
+
params = {"q": song_name, "limit": 5, "offset": 0}
|
| 48 |
+
headers = {"Access-Key": api_token}
|
| 49 |
+
async with session.get(url, params=params, headers=headers) as response:
|
| 50 |
+
if response.status == 200:
|
| 51 |
+
data = await response.json()
|
| 52 |
+
if not data or "data" not in data or not isinstance(data["data"], list) or not data["data"]:
|
| 53 |
+
return {"error": "Empty or invalid response"}
|
| 54 |
+
first_track = data["data"][0]
|
| 55 |
+
uuid = first_track["uuid"]
|
| 56 |
+
artist_name = first_track.get("artist_name", "Unknown Artist")
|
| 57 |
+
self.uuid_cache[song_name] = {"uuid": uuid, "name": artist_name}
|
| 58 |
+
return {"uuid": uuid, "name": artist_name}
|
| 59 |
+
return {"error": "Invalid response"}
|
| 60 |
+
except Exception as e:
|
| 61 |
+
return {"error": str(e)}
|
| 62 |
+
|
| 63 |
+
def is_valid_uuid(self, uuid_string):
|
| 64 |
+
uuid_pattern = re.compile(
|
| 65 |
+
r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'
|
| 66 |
+
)
|
| 67 |
+
return uuid_string if uuid_pattern.fullmatch(uuid_string) else "UUID NOT VALID"
|
| 68 |
+
|
| 69 |
+
async def fetch_platform_data(self, session, uuid, artist_name, platform):
|
| 70 |
+
try:
|
| 71 |
+
url_map = {
|
| 72 |
+
"spotify": "https://data.viberate.com/api/v1/track/{uuid}/spotify/streams-historical",
|
| 73 |
+
"soundcloud": "https://data.viberate.com/api/v1/track/{uuid}/soundcloud/plays-historical",
|
| 74 |
+
"youtube": "https://data.viberate.com/api/v1/track/{uuid}/youtube/views-historical",
|
| 75 |
+
"beatport": "https://data.viberate.com/api/v1/track/{uuid}/beatport/ranks-historical",
|
| 76 |
+
}
|
| 77 |
+
column_name_map = {
|
| 78 |
+
"spotify": "spotify_streams",
|
| 79 |
+
"soundcloud": "soundcloud_streams",
|
| 80 |
+
"youtube": "youtube_streams",
|
| 81 |
+
"beatport": "beatport_streams",
|
| 82 |
+
}
|
| 83 |
+
column_name = column_name_map.get(platform, f"{platform}_streams")
|
| 84 |
+
|
| 85 |
+
if self.is_valid_uuid(uuid) == "UUID NOT VALID":
|
| 86 |
+
return {"error": "UUID NOT VALID"}, artist_name
|
| 87 |
+
|
| 88 |
+
url = url_map[platform].format(uuid=uuid)
|
| 89 |
+
headers = {"Accept": "application/json", "Access-Key": api_token}
|
| 90 |
+
params = {"date-from": date_from.isoformat(), "date-to": date_to.isoformat()}
|
| 91 |
+
|
| 92 |
+
async with session.get(url, headers=headers, params=params) as response:
|
| 93 |
+
if response.status == 200:
|
| 94 |
+
data = await response.json()
|
| 95 |
+
if data and "data" in data and "data" in data["data"]:
|
| 96 |
+
streams_data = data["data"]["data"]
|
| 97 |
+
df = pd.DataFrame(list(streams_data.items()), columns=["date", column_name])
|
| 98 |
+
df["date"] = pd.to_datetime(df["date"])
|
| 99 |
+
return df, artist_name
|
| 100 |
+
return pd.DataFrame(columns=["date", column_name]), artist_name
|
| 101 |
+
return {"error": "Invalid response"}, artist_name
|
| 102 |
+
except Exception as e:
|
| 103 |
+
return {"error": str(e)}, artist_name
|
| 104 |
+
|
| 105 |
+
async def final_result_from_song(self, song_name):
|
| 106 |
+
try:
|
| 107 |
+
async with aiohttp.ClientSession() as session:
|
| 108 |
+
uuid_data = await self.get_uuid_from_song_name(session, song_name)
|
| 109 |
+
if "error" in uuid_data:
|
| 110 |
+
return {"error": uuid_data["error"]}
|
| 111 |
+
|
| 112 |
+
uuid = uuid_data["uuid"]
|
| 113 |
+
artist_name = uuid_data["name"]
|
| 114 |
+
platforms = ["spotify", "youtube", "soundcloud"]
|
| 115 |
+
tasks = [
|
| 116 |
+
self.fetch_platform_data(session, uuid, artist_name, p)
|
| 117 |
+
for p in platforms
|
| 118 |
+
]
|
| 119 |
+
results = await asyncio.gather(*tasks, return_exceptions=True)
|
| 120 |
+
|
| 121 |
+
final_output = None
|
| 122 |
+
for result, _ in results:
|
| 123 |
+
if isinstance(result, dict) and "error" in result:
|
| 124 |
+
return {"error": result["error"]}
|
| 125 |
+
if isinstance(result, pd.DataFrame):
|
| 126 |
+
if final_output is None:
|
| 127 |
+
final_output = result
|
| 128 |
+
else:
|
| 129 |
+
final_output = final_output.merge(result, on='date', how='outer')
|
| 130 |
+
|
| 131 |
+
if final_output is None:
|
| 132 |
+
return {"error": "No data retrieved"}
|
| 133 |
+
|
| 134 |
+
return final_output.sort_values(by='date').reset_index(drop=True)
|
| 135 |
+
except Exception as e:
|
| 136 |
+
return {"error": str(e)}
|
| 137 |
+
|
| 138 |
+
def process_song(self, song_name):
|
| 139 |
+
try:
|
| 140 |
+
loop = asyncio.new_event_loop()
|
| 141 |
+
asyncio.set_event_loop(loop)
|
| 142 |
+
df = loop.run_until_complete(self.final_result_from_song(song_name))
|
| 143 |
+
loop.close()
|
| 144 |
+
|
| 145 |
+
if isinstance(df, dict) and "error" in df:
|
| 146 |
+
return song_name, df
|
| 147 |
+
|
| 148 |
+
safe_song_name = re.sub(r'[\\/*?:"<>|]', "", song_name)
|
| 149 |
+
csv_path = os.path.join(self.base_folder, f"{safe_song_name}.csv")
|
| 150 |
+
df.to_csv(csv_path, index=False)
|
| 151 |
+
return song_name, {"success": csv_path}
|
| 152 |
+
except Exception as e:
|
| 153 |
+
return song_name, {"error": str(e)}
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def combine_csv_to_excel(songs_list: list, session_dir: str) -> str:
|
| 157 |
+
"""
|
| 158 |
+
Downloads per-song CSVs into `session_dir/songs_data/` and writes
|
| 159 |
+
`session_dir/songs_data_combined.xlsx`. Returns the xlsx path on
|
| 160 |
+
success or an error string.
|
| 161 |
+
"""
|
| 162 |
+
try:
|
| 163 |
+
api = ViberateAPI(session_dir=session_dir)
|
| 164 |
+
|
| 165 |
+
if not songs_list:
|
| 166 |
+
return "No songs to process"
|
| 167 |
+
|
| 168 |
+
results = list(
|
| 169 |
+
ThreadPoolExecutor(max_workers=5).map(api.process_song, songs_list)
|
| 170 |
+
)
|
| 171 |
+
|
| 172 |
+
errors = [
|
| 173 |
+
f"Error for {song}: {res['error']}"
|
| 174 |
+
for song, res in results if "error" in res
|
| 175 |
+
]
|
| 176 |
+
if errors:
|
| 177 |
+
logger.error("\n".join(errors))
|
| 178 |
+
|
| 179 |
+
successes = [res["success"] for _, res in results if "success" in res]
|
| 180 |
+
if not successes:
|
| 181 |
+
return "No CSV files generated"
|
| 182 |
+
|
| 183 |
+
excel_path = os.path.join(session_dir, "songs_data_combined.xlsx")
|
| 184 |
+
with pd.ExcelWriter(excel_path, engine='openpyxl') as writer:
|
| 185 |
+
for csv_file in successes:
|
| 186 |
+
df = pd.read_csv(csv_file, encoding='utf-8')
|
| 187 |
+
if df.empty:
|
| 188 |
+
continue
|
| 189 |
+
sheet_name = os.path.splitext(os.path.basename(csv_file))[0][:31]
|
| 190 |
+
df.to_excel(writer, sheet_name=sheet_name, index=False)
|
| 191 |
+
|
| 192 |
+
return excel_path # ← return path instead of a bare string
|
| 193 |
+
|
| 194 |
+
except Exception as e:
|
| 195 |
+
logger.error(f"combine_csv_to_excel failed: {e}")
|
| 196 |
+
return f"Process failed: {str(e)}"
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
def potential_fanbase_count(songs_list: list, session_dir: str):
|
| 200 |
+
"""Returns the xlsx path on success or an error string."""
|
| 201 |
+
try:
|
| 202 |
+
os.makedirs(session_dir, exist_ok=True)
|
| 203 |
+
return combine_csv_to_excel(songs_list, session_dir)
|
| 204 |
+
except Exception as e:
|
| 205 |
+
return f"Process failed: {str(e)}"
|
all_combine_code/local_model_to_s3/bucket_inside_file_delete.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import boto3
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY")
|
| 8 |
+
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_KEY")
|
| 9 |
+
AWS_REGION = os.getenv("AWS_REGION")
|
| 10 |
+
|
| 11 |
+
BUCKET_NAME = "delvant-ai-all-models"
|
| 12 |
+
|
| 13 |
+
s3 = boto3.client(
|
| 14 |
+
"s3",
|
| 15 |
+
aws_access_key_id=AWS_ACCESS_KEY_ID,
|
| 16 |
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
| 17 |
+
region_name=AWS_REGION
|
| 18 |
+
)
|
| 19 |
+
|
| 20 |
+
# List and delete all objects
|
| 21 |
+
def delete_all_objects(bucket):
|
| 22 |
+
paginator = s3.get_paginator("list_objects_v2")
|
| 23 |
+
for page in paginator.paginate(Bucket=bucket):
|
| 24 |
+
if "Contents" in page:
|
| 25 |
+
for obj in page["Contents"]:
|
| 26 |
+
print(f"Deleting {obj['Key']}")
|
| 27 |
+
s3.delete_object(Bucket=bucket, Key=obj["Key"])
|
| 28 |
+
print("✅ All objects deleted")
|
| 29 |
+
|
| 30 |
+
delete_all_objects(BUCKET_NAME)
|
all_combine_code/local_model_to_s3/local_model_to_s3_bucket.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dotenv import load_dotenv
|
| 2 |
+
load_dotenv()
|
| 3 |
+
import os
|
| 4 |
+
import boto3
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY")
|
| 8 |
+
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_KEY")
|
| 9 |
+
AWS_REGION = os.getenv("AWS_REGION")
|
| 10 |
+
|
| 11 |
+
s3 = boto3.client(
|
| 12 |
+
"s3",
|
| 13 |
+
aws_access_key_id=AWS_ACCESS_KEY_ID,
|
| 14 |
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
| 15 |
+
region_name=AWS_REGION
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
folder = r"C:\Users\VigneshSubramani\PycharmProjects\delveant-ai-crm\all-MiniLM-L6-v2.pt"
|
| 19 |
+
bucket = "delvant-ai-all-models"
|
| 20 |
+
|
| 21 |
+
for root, dirs, files in os.walk(folder):
|
| 22 |
+
for file in files:
|
| 23 |
+
full_path = os.path.join(root, file)
|
| 24 |
+
s3_path = os.path.relpath(full_path, folder)
|
| 25 |
+
|
| 26 |
+
s3.upload_file(full_path, bucket, s3_path)
|
| 27 |
+
|
| 28 |
+
print("Upload complete 🚀")
|
all_combine_code/memory_/__init__.py
ADDED
|
File without changes
|
all_combine_code/memory_/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (176 Bytes). View file
|
|
|
all_combine_code/memory_/__pycache__/chat_history_handle.cpython-313.pyc
ADDED
|
Binary file (9.31 kB). View file
|
|
|
all_combine_code/memory_/__pycache__/prompt_agents.cpython-313.pyc
ADDED
|
Binary file (4.28 kB). View file
|
|
|
all_combine_code/memory_/chat_history_handle.py
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os,json,re
|
| 2 |
+
import tempfile
|
| 3 |
+
import shutil
|
| 4 |
+
from uuid import uuid4
|
| 5 |
+
from typing import Dict, List
|
| 6 |
+
import boto3
|
| 7 |
+
import tiktoken
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from dotenv import load_dotenv
|
| 10 |
+
|
| 11 |
+
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
| 12 |
+
from models_.delveant_llm_model_.multi_agent_llm import ChatSynthesizer
|
| 13 |
+
from .prompt_agents import chat_history
|
| 14 |
+
|
| 15 |
+
load_dotenv()
|
| 16 |
+
|
| 17 |
+
AWS_ACCESS_KEY_ID = os.getenv("AWS_ACCESS_KEY")
|
| 18 |
+
AWS_SECRET_ACCESS_KEY = os.getenv("AWS_SECRET_KEY")
|
| 19 |
+
AWS_REGION = os.getenv("AWS_REGION")
|
| 20 |
+
|
| 21 |
+
S3_BUCKET = os.getenv("AI_CHAT_HISTORY_S3")
|
| 22 |
+
S3_BASE_PREFIX = "long_term_memory"
|
| 23 |
+
|
| 24 |
+
MAX_HISTORY = 25
|
| 25 |
+
MAX_FOLDERS = int(os.getenv("HISTORY_MAX_FOLDER_COUNT"))
|
| 26 |
+
|
| 27 |
+
ENCODING = tiktoken.get_encoding("o200k_base")
|
| 28 |
+
|
| 29 |
+
s3 = boto3.client(
|
| 30 |
+
"s3",
|
| 31 |
+
aws_access_key_id=AWS_ACCESS_KEY_ID,
|
| 32 |
+
aws_secret_access_key=AWS_SECRET_ACCESS_KEY,
|
| 33 |
+
region_name=AWS_REGION
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
user_sessions: Dict[str, List] = {}
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def truncate_to_tokens(text: str, max_tokens: int = 50) -> str:
|
| 41 |
+
if not text:
|
| 42 |
+
return ""
|
| 43 |
+
if not isinstance(text, str):
|
| 44 |
+
text = json.dumps(text, ensure_ascii=False)
|
| 45 |
+
tokens = ENCODING.encode(text)
|
| 46 |
+
return ENCODING.decode(tokens[:max_tokens])
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
async def llm(prompt):
|
| 50 |
+
llm = ChatSynthesizer()
|
| 51 |
+
return await llm.chat_synthesizer(prompt)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def messages_s3_key(user_id: str):
|
| 55 |
+
return f"{S3_BASE_PREFIX}/{user_id}/messages.json"
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def new_session_id():
|
| 59 |
+
return str(uuid4())
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def get_user_messages(user_id: str):
|
| 63 |
+
if user_id not in user_sessions:
|
| 64 |
+
user_sessions[user_id] = [SystemMessage(content="You are a helpful AI assistant.")]
|
| 65 |
+
load_messages_from_s3(user_id)
|
| 66 |
+
return user_sessions[user_id]
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def trim_history(messages):
|
| 70 |
+
system_msg = messages[0]
|
| 71 |
+
history = messages[-MAX_HISTORY:]
|
| 72 |
+
return [system_msg] + history
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def save_messages_to_s3(user_id: str):
|
| 76 |
+
temp = tempfile.mkdtemp()
|
| 77 |
+
path = os.path.join(temp, "messages.json")
|
| 78 |
+
|
| 79 |
+
user_sessions[user_id] = trim_history(user_sessions[user_id])
|
| 80 |
+
|
| 81 |
+
with open(path, "w") as f:
|
| 82 |
+
json.dump(
|
| 83 |
+
[{"type": m.__class__.__name__, "content": m.content} for m in user_sessions[user_id]],
|
| 84 |
+
f
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
s3.upload_file(path, S3_BUCKET, messages_s3_key(user_id))
|
| 88 |
+
shutil.rmtree(temp)
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def load_messages_from_s3(user_id: str):
|
| 92 |
+
try:
|
| 93 |
+
temp = tempfile.mkdtemp()
|
| 94 |
+
path = os.path.join(temp, "messages.json")
|
| 95 |
+
|
| 96 |
+
s3.download_file(S3_BUCKET, messages_s3_key(user_id), path)
|
| 97 |
+
|
| 98 |
+
with open(path) as f:
|
| 99 |
+
data = json.load(f)
|
| 100 |
+
|
| 101 |
+
for m in data[1:]:
|
| 102 |
+
if m["type"] == "HumanMessage":
|
| 103 |
+
user_sessions[user_id].append(HumanMessage(content=m["content"]))
|
| 104 |
+
elif m["type"] == "AIMessage":
|
| 105 |
+
user_sessions[user_id].append(AIMessage(content=m["content"]))
|
| 106 |
+
|
| 107 |
+
shutil.rmtree(temp)
|
| 108 |
+
except:
|
| 109 |
+
pass
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def delete_prefix(prefix: str):
|
| 113 |
+
paginator = s3.get_paginator("list_objects_v2")
|
| 114 |
+
for page in paginator.paginate(Bucket=S3_BUCKET, Prefix=prefix):
|
| 115 |
+
if "Contents" not in page:
|
| 116 |
+
continue
|
| 117 |
+
s3.delete_objects(
|
| 118 |
+
Bucket=S3_BUCKET,
|
| 119 |
+
Delete={"Objects": [{"Key": obj["Key"]} for obj in page["Contents"]]}
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
|
| 123 |
+
def cleanup_old_user_folders():
|
| 124 |
+
paginator = s3.get_paginator("list_objects_v2")
|
| 125 |
+
|
| 126 |
+
folders = []
|
| 127 |
+
for page in paginator.paginate(Bucket=S3_BUCKET, Prefix=f"{S3_BASE_PREFIX}/", Delimiter="/"):
|
| 128 |
+
for p in page.get("CommonPrefixes", []):
|
| 129 |
+
prefix = p["Prefix"]
|
| 130 |
+
resp = s3.list_objects_v2(Bucket=S3_BUCKET, Prefix=prefix)
|
| 131 |
+
if "Contents" in resp:
|
| 132 |
+
last_modified = max(obj["LastModified"] for obj in resp["Contents"])
|
| 133 |
+
folders.append((last_modified, prefix))
|
| 134 |
+
|
| 135 |
+
folders.sort(reverse=True)
|
| 136 |
+
|
| 137 |
+
for _, prefix in folders[MAX_FOLDERS:]:
|
| 138 |
+
delete_prefix(prefix)
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def delete_user_history(user_id: str):
|
| 142 |
+
delete_prefix(f"{S3_BASE_PREFIX}/{user_id}/")
|
| 143 |
+
user_sessions.pop(user_id, None)
|
| 144 |
+
|
| 145 |
+
def create_runtime_user_id() -> str:
|
| 146 |
+
return f"{uuid4()}_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
|
| 147 |
+
|
| 148 |
+
async def generate_response(user_input: str, user_id: str):
|
| 149 |
+
try:
|
| 150 |
+
if user_id == "new_chat":
|
| 151 |
+
cleanup_old_user_folders()
|
| 152 |
+
user_id = create_runtime_user_id()
|
| 153 |
+
|
| 154 |
+
elif re.fullmatch(r"[a-f0-9\-]{36}_\d{8}_\d{6}", user_id, re.IGNORECASE):
|
| 155 |
+
pass
|
| 156 |
+
|
| 157 |
+
else:
|
| 158 |
+
cleanup_old_user_folders()
|
| 159 |
+
user_id = create_runtime_user_id()
|
| 160 |
+
|
| 161 |
+
except Exception:
|
| 162 |
+
cleanup_old_user_folders()
|
| 163 |
+
user_id = create_runtime_user_id()
|
| 164 |
+
|
| 165 |
+
messages = get_user_messages(user_id)
|
| 166 |
+
messages.append(HumanMessage(content=user_input))
|
| 167 |
+
user_sessions[user_id] = trim_history(messages)
|
| 168 |
+
#print( "trim messages:" ,user_sessions[user_id])
|
| 169 |
+
prompt = chat_history(user_input, user_sessions[user_id])
|
| 170 |
+
response_content = await llm(prompt)
|
| 171 |
+
return {"user_id": user_id, "chat_history_query": response_content}
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
async def store_final_chat(user_id: str, user_input: str, final_reply: str):
|
| 175 |
+
messages = get_user_messages(user_id)
|
| 176 |
+
truncated_reply = truncate_to_tokens(final_reply)
|
| 177 |
+
messages.append(HumanMessage(content=user_input))
|
| 178 |
+
messages.append(AIMessage(content=truncated_reply))
|
| 179 |
+
user_sessions[user_id] = trim_history(messages)
|
| 180 |
+
save_messages_to_s3(user_id)
|
| 181 |
+
|
| 182 |
+
|
| 183 |
+
# async def chat():#
|
| 184 |
+
# user_id = "703af383-6b70-4b68-979c-f005d7ff866f"#
|
| 185 |
+
# while True:
|
| 186 |
+
# user_input = input("You: ")#
|
| 187 |
+
# if user_input.lower() == "exit":
|
| 188 |
+
# break#
|
| 189 |
+
# res = await generate_response(user_input, user_id)#
|
| 190 |
+
# user_id = res["user_id"]
|
| 191 |
+
# reply = res["chat_history_query"]#
|
| 192 |
+
# print("Bot:", reply)#
|
| 193 |
+
# await store_final_chat(user_id, user_input, reply)#
|
| 194 |
+
# asyncio.run(chat())
|
all_combine_code/memory_/prompt_agents.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from textwrap import dedent
|
| 2 |
+
def chat_history(user_input, history):
|
| 3 |
+
prompt = dedent(f"""
|
| 4 |
+
You are a 20+ award-winning elite Conversational Intent Synthesizer and Question Generated Architect.
|
| 5 |
+
|
| 6 |
+
You are given:
|
| 7 |
+
- The full previous conversation history
|
| 8 |
+
- The current user input
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## PREVIOUS CONVERSATION HISTORY:
|
| 13 |
+
{history}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
## CURRENT USER INPUT:
|
| 17 |
+
{user_input}
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
Your task is to generate exactly ONE precise, fully-qualified, executable command-style query that completely and accurately captures the user's active intent by intelligently synthesizing the current input with all relevant prior conversation context, without omission, paraphrasing of intent verbs, explanation, or additional commentary.
|
| 22 |
+
|
| 23 |
+
**Core behavior:**
|
| 24 |
+
|
| 25 |
+
1. Deeply understand the `user input` and all Conversional history inside intent. Accurately find relation user input and previous chat history to make 100% required response.
|
| 26 |
+
2. If the current user input does NOT introduce a clear new intent, assume it continues the previous active intent.
|
| 27 |
+
3. Extract and accumulate all relevant entities and details across conversation turns (names, companies, attributes, requirements, conditions, constraints, etc.).
|
| 28 |
+
4. Merge the active intent and all accumulated entities into a single, complete command-style query.
|
| 29 |
+
5. Analyze the current user query in the context of previous conversation history to fully understand their relationship and intent before generating a response.
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
**Intent handling rules:**
|
| 33 |
+
|
| 34 |
+
- If the user clearly provides a new intent, discard the previous intent and start fresh.
|
| 35 |
+
- If the user input only provides details, corrections, or values, apply them to the existing intent.
|
| 36 |
+
- Do NOT drop previously collected information unless the user explicitly corrects or removes it.
|
| 37 |
+
- **If the user query contains explicit indicators like this is "new chat", "new query", "new question", or similar phrases, treat it as a completely new intent query and do NOT use Previous Chat History Query.**
|
| 38 |
+
eg. total lead count new chat ( or use new chat/ not consider previous chat history ot this similar)
|
| 39 |
+
your answer : total lead count?
|
| 40 |
+
|
| 41 |
+
**Output rules:**
|
| 42 |
+
|
| 43 |
+
- Preserve the original intent wording as much as possible.
|
| 44 |
+
- Do NOT paraphrase intent verbs.
|
| 45 |
+
- Do NOT generate explanations, summaries, or conversational responses.
|
| 46 |
+
- Do NOT ask questions.
|
| 47 |
+
- Do NOT output multiple options.
|
| 48 |
+
- Do NOT include system text or reasoning.
|
| 49 |
+
- Return ONLY the final synthesized query.
|
| 50 |
+
|
| 51 |
+
**Greeting and small-talk handling:**
|
| 52 |
+
|
| 53 |
+
- If the user input is a greeting, acknowledgement, or small talk
|
| 54 |
+
(e.g., hi, hello, hey, good morning, thanks, ok),
|
| 55 |
+
treat it as a NEW intent.
|
| 56 |
+
- In this case, return the greeting exactly as provided.
|
| 57 |
+
- Do NOT reuse or continue any previous intent.
|
| 58 |
+
|
| 59 |
+
**Special cases:**
|
| 60 |
+
|
| 61 |
+
- If the current input is already a complete standalone query, return it as-is.
|
| 62 |
+
- If the current input is unrelated to the previous conversation, treat it as a new intent.
|
| 63 |
+
- If names or entities were previously changed or corrected, always use the latest valid value.
|
| 64 |
+
|
| 65 |
+
**Very strict rule:**
|
| 66 |
+
- Before whole chat history and current message clearly understand to make the smart and professional query format.
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
Your output must always be a single, professional, command-style query that reflects the full conversation context.
|
| 70 |
+
|
| 71 |
+
*(NOTE: query inside any spell mistake, please correct it.)*
|
| 72 |
+
|
| 73 |
+
### **VERY IMPORTANT NOTES:**
|
| 74 |
+
|
| 75 |
+
- Query inside mention fresh or new not consider history, this similar consider that is **new one query**.
|
| 76 |
+
- Query inside "name" or "mail" mention time return same mail and name. Never assume field name like (firstname, lastname, email 1).
|
| 77 |
+
- Strictly Follow this : always prioritize the most recent/last user message while accurately synthesizing it with relevant prior conversation context, unless explicitly instructed to treat it as a new query.
|
| 78 |
+
- Strictly avoid the SQL/MySQL format query.
|
| 79 |
+
|
| 80 |
+
Return ONLY the final query with plain/text.
|
| 81 |
+
""")
|
| 82 |
+
return prompt
|
all_combine_code/models_/__init__.py
ADDED
|
File without changes
|
all_combine_code/models_/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (176 Bytes). View file
|
|
|
all_combine_code/models_/__pycache__/code_generation_model.cpython-313.pyc
ADDED
|
Binary file (4.68 kB). View file
|
|
|
all_combine_code/models_/code_generation_model.py
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, re, sys, json,httpx
|
| 2 |
+
from openai import AsyncOpenAI
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import asyncio
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"),
|
| 8 |
+
timeout=240.0)
|
| 9 |
+
|
| 10 |
+
class CodeGenerationLLM:
|
| 11 |
+
async def generate_response(self,prompt: str) -> str:
|
| 12 |
+
full_text = []
|
| 13 |
+
response = await client.chat.completions.create(
|
| 14 |
+
model="gpt-5-chat-latest",
|
| 15 |
+
temperature=0.1,
|
| 16 |
+
messages=[
|
| 17 |
+
{"role": "system", "content": "You are an expert in PyMongo query generation with valid JSON format."},
|
| 18 |
+
{"role": "user", "content": prompt}
|
| 19 |
+
],
|
| 20 |
+
stream=True,
|
| 21 |
+
max_tokens=2500,
|
| 22 |
+
#max_completion_tokens=3800
|
| 23 |
+
response_format={"type": "json_object"}
|
| 24 |
+
)
|
| 25 |
+
async for chunk in response:
|
| 26 |
+
delta = chunk.choices[0].delta
|
| 27 |
+
if delta and delta.content:
|
| 28 |
+
token = delta.content
|
| 29 |
+
print(token, end="", flush=True)
|
| 30 |
+
full_text.append(token)
|
| 31 |
+
return "".join(full_text).strip()
|
| 32 |
+
|
| 33 |
+
async def generate_final_output(self,prompt) -> str:
|
| 34 |
+
llm_output = await self.generate_response(prompt)
|
| 35 |
+
return llm_output
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
class OpenRouterClient:
|
| 39 |
+
def __init__(self):
|
| 40 |
+
load_dotenv()
|
| 41 |
+
|
| 42 |
+
self.api_key = os.getenv("OPENROUTER_API_KEY")
|
| 43 |
+
|
| 44 |
+
self.url = "https://openrouter.ai/api/v1/chat/completions"
|
| 45 |
+
|
| 46 |
+
self.headers = {
|
| 47 |
+
"Authorization": f"Bearer {self.api_key}",
|
| 48 |
+
"Content-Type": "application/json",
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
async def stream_chat(self, prompt):
|
| 52 |
+
payload = {
|
| 53 |
+
"model": "mistralai/codestral-2508", # anthropic/claude-sonnet-4.6 ,mistralai/codestral-2508
|
| 54 |
+
"stream": True,
|
| 55 |
+
"max_tokens": 3000,
|
| 56 |
+
"temperature": 0.1,
|
| 57 |
+
"messages": [
|
| 58 |
+
{"role": "system", "content": "You are an expert in PyMongo query generation with valid JSON format."},
|
| 59 |
+
{"role": "user", "content": prompt}
|
| 60 |
+
]
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
full_text = ""
|
| 64 |
+
|
| 65 |
+
async with httpx.AsyncClient(timeout=60) as client:
|
| 66 |
+
async with client.stream(
|
| 67 |
+
"POST",
|
| 68 |
+
self.url,
|
| 69 |
+
headers=self.headers,
|
| 70 |
+
json=payload
|
| 71 |
+
) as r:
|
| 72 |
+
|
| 73 |
+
async for line in r.aiter_lines():
|
| 74 |
+
|
| 75 |
+
if not line or not line.startswith("data:"):
|
| 76 |
+
continue
|
| 77 |
+
|
| 78 |
+
data = line.removeprefix("data: ").strip()
|
| 79 |
+
|
| 80 |
+
if data == "[DONE]":
|
| 81 |
+
break
|
| 82 |
+
|
| 83 |
+
chunk = json.loads(data)
|
| 84 |
+
delta = chunk["choices"][0]["delta"].get("content")
|
| 85 |
+
|
| 86 |
+
if delta:
|
| 87 |
+
print(delta, end="", flush=True)
|
| 88 |
+
full_text += delta
|
| 89 |
+
|
| 90 |
+
return full_text
|
all_combine_code/models_/delveant_llm_model_/__init__.py
ADDED
|
File without changes
|
all_combine_code/models_/delveant_llm_model_/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (192 Bytes). View file
|
|
|
all_combine_code/models_/delveant_llm_model_/__pycache__/multi_agent_llm.cpython-313.pyc
ADDED
|
Binary file (15.8 kB). View file
|
|
|
all_combine_code/models_/delveant_llm_model_/multi_agent_llm.py
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, re, sys, json, gc
|
| 2 |
+
import asyncio,logging,random,time
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
from openai import AsyncOpenAI
|
| 5 |
+
load_dotenv()
|
| 6 |
+
|
| 7 |
+
from textwrap import dedent
|
| 8 |
+
LLM_SEMAPHORE = asyncio.Semaphore(8)
|
| 9 |
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
| 10 |
+
OPENROUTER_URL = os.getenv("OPENROUTER_URL")
|
| 11 |
+
REQUEST_TIMEOUT = 40.0
|
| 12 |
+
|
| 13 |
+
client = AsyncOpenAI(
|
| 14 |
+
#base_url=OPENROUTER_URL,
|
| 15 |
+
api_key=os.getenv("OPENAI_API_KEY"),
|
| 16 |
+
timeout=120,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class MultiAgentLLM:
|
| 21 |
+
async def general_questions_answer(self,prompt):
|
| 22 |
+
response = await client.chat.completions.create(
|
| 23 |
+
model="gpt-3.5-turbo",
|
| 24 |
+
messages=[
|
| 25 |
+
{"role": "system", "content": "You are very helful AI assistant.Following Questions understand to make the smart response"},
|
| 26 |
+
{"role": "user", "content": prompt}
|
| 27 |
+
],
|
| 28 |
+
)
|
| 29 |
+
message = response.choices[0].message.content
|
| 30 |
+
return message
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
async def generate_ai_response_greetings(self,user_prompt) -> str:
|
| 34 |
+
async with LLM_SEMAPHORE:
|
| 35 |
+
for attempt in range(3):
|
| 36 |
+
try:
|
| 37 |
+
system_prompt = dedent("""
|
| 38 |
+
You are DelveAnt CRM’s AI assistant developed by kovaion.ai.
|
| 39 |
+
|
| 40 |
+
Rules:
|
| 41 |
+
- Keep responses short, professional, and helpful.
|
| 42 |
+
- If the query mentions "Delvant CRM" or asks about the product,
|
| 43 |
+
give a brief 2–3 line overview of DelveAnt CRM, its purpose, and key benefits.
|
| 44 |
+
- Do not give long explanations.
|
| 45 |
+
- Do not sound casual or chatty.
|
| 46 |
+
- Always use correct spelling: "DelveAnt".
|
| 47 |
+
""").strip()
|
| 48 |
+
response = await client.chat.completions.create(
|
| 49 |
+
model="gpt-3.5-turbo",
|
| 50 |
+
messages=[
|
| 51 |
+
{"role": "system", "content":system_prompt},
|
| 52 |
+
{"role": "user", "content": user_prompt},
|
| 53 |
+
])
|
| 54 |
+
return response.choices[0].message.content
|
| 55 |
+
|
| 56 |
+
except Exception as e:
|
| 57 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 58 |
+
if attempt == 2:
|
| 59 |
+
raise
|
| 60 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 61 |
+
|
| 62 |
+
async def delveant_chatbot(self,user_prompt) -> str:
|
| 63 |
+
async with LLM_SEMAPHORE:
|
| 64 |
+
for attempt in range(3):
|
| 65 |
+
try:
|
| 66 |
+
system_prompt = dedent("""
|
| 67 |
+
You are the owner and official AI assistant of DelveAnt CRM.
|
| 68 |
+
Rules:
|
| 69 |
+
- Answer the user question briefly and professionally.
|
| 70 |
+
- Maximum 3 short sentences.
|
| 71 |
+
- Do NOT include lists, steps, or examples.
|
| 72 |
+
- Do NOT use line breaks.
|
| 73 |
+
- End the response with a full stop.
|
| 74 |
+
- Always use correct spelling: "DelveAnt".
|
| 75 |
+
""").strip()
|
| 76 |
+
response = await client.chat.completions.create(
|
| 77 |
+
model="gpt-4o-mini",
|
| 78 |
+
messages=[
|
| 79 |
+
{"role": "system", "content": system_prompt},
|
| 80 |
+
{"role": "user", "content": user_prompt},
|
| 81 |
+
],
|
| 82 |
+
temperature=0.0,
|
| 83 |
+
max_tokens=150,
|
| 84 |
+
)
|
| 85 |
+
return response.choices[0].message.content
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 89 |
+
if attempt == 2:
|
| 90 |
+
raise
|
| 91 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
async def plot_finder(self,user_prompt) -> str:
|
| 95 |
+
async with LLM_SEMAPHORE:
|
| 96 |
+
for attempt in range(3):
|
| 97 |
+
try:
|
| 98 |
+
system_prompt = "You are very highly Rule based **Decision Find Agent**."
|
| 99 |
+
response = await client.chat.completions.create(
|
| 100 |
+
model="gpt-5-chat-latest",
|
| 101 |
+
messages=[
|
| 102 |
+
{"role": "system", "content": system_prompt},
|
| 103 |
+
{"role": "user", "content": user_prompt},
|
| 104 |
+
],
|
| 105 |
+
temperature=0.0,
|
| 106 |
+
max_tokens=150,
|
| 107 |
+
)
|
| 108 |
+
return response.choices[0].message.content
|
| 109 |
+
|
| 110 |
+
except Exception as e:
|
| 111 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 112 |
+
if attempt == 2:
|
| 113 |
+
raise
|
| 114 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 115 |
+
|
| 116 |
+
async def empty_list_responser(self,user_prompt) -> str:
|
| 117 |
+
async with LLM_SEMAPHORE:
|
| 118 |
+
prompt = dedent(f"""
|
| 119 |
+
# You are a professional and well-structured responder. small or medium format text not long enough.
|
| 120 |
+
|
| 121 |
+
**User Input:**
|
| 122 |
+
- {user_prompt}
|
| 123 |
+
|
| 124 |
+
**MongoDB Query Output:**
|
| 125 |
+
[]
|
| 126 |
+
|
| 127 |
+
**System Context:**
|
| 128 |
+
- The MongoDB query for the above user input returned an empty list ("[]").
|
| 129 |
+
- This means that no matching data was found in the database for the given query.
|
| 130 |
+
|
| 131 |
+
**Your Task:**
|
| 132 |
+
- Provide a clear, professional, and informative response to the user **based on the query and the empty result**.
|
| 133 |
+
- Clearly explain that no data was found for the requested query.
|
| 134 |
+
- If the user’s query is unclear or incomplete, politely suggest a refined version of the query or possible next steps.
|
| 135 |
+
- Keep the response concise, neutral, and easy to understand.
|
| 136 |
+
- Do **not** include any code, technical details, or system-related text in your final response.
|
| 137 |
+
- Your response must directly relate to the user’s query and the fact that the result was empty.
|
| 138 |
+
- Your response inside don't mention "Unfortunately" this word.
|
| 139 |
+
""")
|
| 140 |
+
for attempt in range(3):
|
| 141 |
+
try:
|
| 142 |
+
response = await client.chat.completions.create(
|
| 143 |
+
model="gpt-4o-mini",
|
| 144 |
+
messages=[
|
| 145 |
+
{"role": "system", "content": "You are a helpful AI assistant."},
|
| 146 |
+
{"role": "user", "content": prompt},
|
| 147 |
+
],
|
| 148 |
+
temperature=0.0,
|
| 149 |
+
max_tokens=100,
|
| 150 |
+
)
|
| 151 |
+
return response.choices[0].message.content
|
| 152 |
+
|
| 153 |
+
except Exception as e:
|
| 154 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 155 |
+
if attempt == 2:
|
| 156 |
+
raise
|
| 157 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 158 |
+
|
| 159 |
+
|
| 160 |
+
class CodeErrorFixer:
|
| 161 |
+
def __init__(self):
|
| 162 |
+
self.system_prompt = dedent("""
|
| 163 |
+
You are a senior Python engineer and runtime error fixer.
|
| 164 |
+
Your job is to:
|
| 165 |
+
1. Analyze the error
|
| 166 |
+
2. Identify the root cause
|
| 167 |
+
3. Fix the error.
|
| 168 |
+
4. Return corrected code.
|
| 169 |
+
5. Do not add explanations unless asked
|
| 170 |
+
6. Return corrected python code.
|
| 171 |
+
""")
|
| 172 |
+
|
| 173 |
+
async def code_debuger(self,prompt: str):
|
| 174 |
+
full_text = []
|
| 175 |
+
stream = await client.chat.completions.create(
|
| 176 |
+
model="gpt-4.1-2025-04-14",
|
| 177 |
+
messages=[
|
| 178 |
+
{"role": "system", "content": self.system_prompt},
|
| 179 |
+
{"role": "user", "content": prompt}
|
| 180 |
+
],
|
| 181 |
+
max_tokens=4000,
|
| 182 |
+
stream=True,
|
| 183 |
+
)
|
| 184 |
+
async for chunk in stream:
|
| 185 |
+
delta = chunk.choices[0].delta
|
| 186 |
+
if delta and delta.content:
|
| 187 |
+
token = delta.content
|
| 188 |
+
print(token, end="", flush=True)
|
| 189 |
+
full_text.append(token)
|
| 190 |
+
return "".join(full_text)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class ChatSynthesizer:
|
| 194 |
+
def __init__(self):
|
| 195 |
+
self.model = "gpt-5.1"
|
| 196 |
+
async def chat_synthesizer(self,user_prompt) -> str:
|
| 197 |
+
async with LLM_SEMAPHORE:
|
| 198 |
+
for attempt in range(3):
|
| 199 |
+
try:
|
| 200 |
+
response = await client.chat.completions.create(
|
| 201 |
+
model=self.model,
|
| 202 |
+
messages=[
|
| 203 |
+
{"role": "system", "content":"You are 20+ award-winning Intelligent Conversational Intent Synthesizer"},
|
| 204 |
+
{"role": "user", "content": user_prompt},
|
| 205 |
+
],
|
| 206 |
+
temperature=0,
|
| 207 |
+
top_p=1,
|
| 208 |
+
#reasoning_effort=None,
|
| 209 |
+
max_completion_tokens=300,
|
| 210 |
+
)
|
| 211 |
+
return response.choices[0].message.content
|
| 212 |
+
|
| 213 |
+
except Exception as e:
|
| 214 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 215 |
+
if attempt == 2:
|
| 216 |
+
raise
|
| 217 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 218 |
+
|
| 219 |
+
class CollectionAndQueryFinder:
|
| 220 |
+
def __init__(self):
|
| 221 |
+
self.model = "gpt-5-chat-latest"
|
| 222 |
+
self.system_prompt = dedent("""
|
| 223 |
+
You are 20+ years award winning Accurate CRM Query Formatter and Database Collection Name Finder Agent
|
| 224 |
+
""")
|
| 225 |
+
|
| 226 |
+
async def generate_response(self,prompt,agents,user_id,person_id,collection_keywords,priveous_chat_history_query) -> str:
|
| 227 |
+
async with LLM_SEMAPHORE:
|
| 228 |
+
for attempt in range(3):
|
| 229 |
+
try:
|
| 230 |
+
formatted_prompt = agents(prompt, user_id, person_id, collection_keywords,
|
| 231 |
+
priveous_chat_history_query)
|
| 232 |
+
response = await client.chat.completions.create(
|
| 233 |
+
model=self.model,
|
| 234 |
+
messages=[
|
| 235 |
+
{"role": "system", "content":self.system_prompt},
|
| 236 |
+
{"role": "user", "content": formatted_prompt},
|
| 237 |
+
],
|
| 238 |
+
temperature=0.1,
|
| 239 |
+
max_tokens=300,
|
| 240 |
+
#reasoning_effort="none"
|
| 241 |
+
)
|
| 242 |
+
return response.choices[0].message.content
|
| 243 |
+
|
| 244 |
+
except Exception as e:
|
| 245 |
+
logging.info(f"LLM attempt {attempt + 1} failed: {e}")
|
| 246 |
+
if attempt == 2:
|
| 247 |
+
raise
|
| 248 |
+
await asyncio.sleep(2 ** attempt + random.uniform(0.2, 0.6))
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# async def main():
|
| 252 |
+
# chat = ChatSynthesizer()
|
| 253 |
+
# user_prompt = "what is java?"
|
| 254 |
+
#
|
| 255 |
+
# try:
|
| 256 |
+
# response = await chat.chat_synthesizer(user_prompt)
|
| 257 |
+
# print("LLM Response:", response)
|
| 258 |
+
# except Exception as e:
|
| 259 |
+
# print("Error:", e)
|
| 260 |
+
#
|
| 261 |
+
#
|
| 262 |
+
# # Run the async main function
|
| 263 |
+
# if __name__ == "__main__":
|
| 264 |
+
# asyncio.run(main())
|
all_combine_code/models_/delveant_llm_model_/tool_calling_model.py
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import warnings
|
| 2 |
+
warnings.filterwarnings("ignore")
|
| 3 |
+
|
| 4 |
+
import os
|
| 5 |
+
import json
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
from openai import AsyncOpenAI
|
| 8 |
+
from agents_.delveant_prompts.tool_clasification_agent import TOOLS, tool_classification_agent
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
|
| 12 |
+
OPENROUTER_BASE_URL = os.getenv("OPENROUTER_URL")
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
client = AsyncOpenAI(
|
| 16 |
+
api_key=OPENROUTER_API_KEY,
|
| 17 |
+
base_url=OPENROUTER_BASE_URL,
|
| 18 |
+
default_headers={"X-Title": "CRM-Tool-Agent"},
|
| 19 |
+
timeout=30.0
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
async def classify_and_call_tool(user_query):
|
| 23 |
+
response = await client.chat.completions.create(
|
| 24 |
+
model="anthropic/claude-sonnet-4.6",
|
| 25 |
+
messages=[
|
| 26 |
+
{"role": "system", "content": tool_classification_agent()},
|
| 27 |
+
{"role": "user", "content": user_query},
|
| 28 |
+
],
|
| 29 |
+
tools=TOOLS,
|
| 30 |
+
tool_choice="auto",
|
| 31 |
+
temperature=0.1,
|
| 32 |
+
)
|
| 33 |
+
return response
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def parse_tool_response(response):
|
| 37 |
+
message = response.choices[0].message
|
| 38 |
+
if hasattr(message, "tool_calls") and message.tool_calls:
|
| 39 |
+
tool_calls = []
|
| 40 |
+
for call in message.tool_calls:
|
| 41 |
+
args = call.function.arguments
|
| 42 |
+
if isinstance(args, str):
|
| 43 |
+
try:
|
| 44 |
+
args1 = json.loads(args)
|
| 45 |
+
except json.JSONDecodeError:
|
| 46 |
+
args1 = {}
|
| 47 |
+
|
| 48 |
+
tool_calls.append({
|
| 49 |
+
"tool_name": call.function.name,
|
| 50 |
+
"arguments": args,
|
| 51 |
+
})
|
| 52 |
+
return tool_calls, None
|
| 53 |
+
return None, message.content
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
async def tool_finder_llm1(previous_chat_history_query):
|
| 57 |
+
try:
|
| 58 |
+
response = await classify_and_call_tool(previous_chat_history_query)
|
| 59 |
+
tool_calls, content = parse_tool_response(response)
|
| 60 |
+
if tool_calls:
|
| 61 |
+
for call in tool_calls:
|
| 62 |
+
tool_name = call["tool_name"]
|
| 63 |
+
tool_args = call["arguments"]
|
| 64 |
+
return tool_name, tool_args
|
| 65 |
+
|
| 66 |
+
else:
|
| 67 |
+
if content:
|
| 68 |
+
return "no_tool_name", content
|
| 69 |
+
|
| 70 |
+
else:
|
| 71 |
+
return "no_tool_name", "no_tool_args"
|
| 72 |
+
|
| 73 |
+
except Exception as e:
|
| 74 |
+
return "no_tool_name", "Our techinical team working this related process, update soon."
|
| 75 |
+
|
| 76 |
+
from openai import AsyncOpenAI
|
| 77 |
+
from dotenv import load_dotenv
|
| 78 |
+
load_dotenv()
|
| 79 |
+
import os
|
| 80 |
+
|
| 81 |
+
client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
| 82 |
+
|
| 83 |
+
async def tool_finder_llm(previous_chat_history_query):
|
| 84 |
+
response = await client.chat.completions.create(
|
| 85 |
+
model="gpt-5.2",
|
| 86 |
+
messages=[
|
| 87 |
+
{"role": "system", "content": tool_classification_agent()},
|
| 88 |
+
{"role": "user", "content": previous_chat_history_query}
|
| 89 |
+
],
|
| 90 |
+
tools=TOOLS,
|
| 91 |
+
tool_choice="auto",
|
| 92 |
+
#temperature=0.0,
|
| 93 |
+
timeout=120.0,
|
| 94 |
+
reasoning_effort='none',
|
| 95 |
+
)
|
| 96 |
+
message = response.choices[0].message
|
| 97 |
+
if message.tool_calls and len(message.tool_calls) > 0:
|
| 98 |
+
tool_call = message.tool_calls[0]
|
| 99 |
+
tool_name = tool_call.function.name
|
| 100 |
+
tool_args = tool_call.function.arguments
|
| 101 |
+
return tool_name, tool_args
|
| 102 |
+
else:
|
| 103 |
+
content = message.content
|
| 104 |
+
if content:
|
| 105 |
+
return "no_tool_name", content
|
| 106 |
+
|
| 107 |
+
else:
|
| 108 |
+
return "no_tool_name", "no_tool_args"
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
# async def main():
|
| 112 |
+
# query = "create lead with email 'vicky@gmail.com', IBM"
|
| 113 |
+
# tool_name, tool_args = await tool_finder_llm(query)
|
| 114 |
+
# print("Tool:", tool_name)
|
| 115 |
+
# print("Arguments:", tool_args)
|
| 116 |
+
#
|
| 117 |
+
# asyncio.run(main())
|
all_combine_code/mongodb_data_chat/delveant_chatbot.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
|
| 4 |
+
from pymongo import MongoClient
|
| 5 |
+
from dotenv import load_dotenv
|
| 6 |
+
|
| 7 |
+
project_root = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
| 8 |
+
sys.path.append(project_root)
|
| 9 |
+
|
| 10 |
+
from tools_.delveant_all_tools_.crm_data_extraction import (extract_crm_lead)
|
| 11 |
+
|
| 12 |
+
from models_.code_generation_model import CodeGenerationLLM
|
| 13 |
+
from models_.delveant_llm_model_.multi_agent_llm import CollectionAndQueryFinder
|
| 14 |
+
|
| 15 |
+
load_dotenv()
|
| 16 |
+
|
| 17 |
+
MONGO_URI = os.getenv("MONGO_URI")
|
| 18 |
+
mongo_db_client = MongoClient(MONGO_URI)
|
| 19 |
+
|
| 20 |
+
class Delveant:
|
| 21 |
+
def __init__(self):
|
| 22 |
+
self.mongo_db_client = mongo_db_client
|
| 23 |
+
|
| 24 |
+
def extract_keywords(self,db_name):
|
| 25 |
+
db = self.mongo_db_client[db_name]
|
| 26 |
+
crm_leads_collection = db["CRM_AICollection_Keys"]
|
| 27 |
+
first_two = list(crm_leads_collection.find().sort([("_id", 1)]).limit(2))
|
| 28 |
+
last_two = list(crm_leads_collection.find().sort([("_id", -1)]).limit(2))
|
| 29 |
+
documents = first_two + last_two
|
| 30 |
+
formatted_output = []
|
| 31 |
+
for doc in documents:
|
| 32 |
+
for item in doc.get("keywords", []):
|
| 33 |
+
collection_name = item.get("collection_name", "")
|
| 34 |
+
keywords = item.get("keywords", [])
|
| 35 |
+
|
| 36 |
+
formatted_output.append({
|
| 37 |
+
"collection_name": collection_name,
|
| 38 |
+
"keywords": keywords
|
| 39 |
+
})
|
| 40 |
+
return formatted_output
|
| 41 |
+
|
| 42 |
+
def extract_lead_data_(self,input_message,collection_name,db_name,user_id,perso_id):
|
| 43 |
+
documents, field_name_datatypes, status, source = extract_crm_lead(
|
| 44 |
+
collection_name=collection_name,
|
| 45 |
+
db_name=db_name,
|
| 46 |
+
)
|
| 47 |
+
prompt = mongodb_query_generator_leads(input_message,documents,user_id,
|
| 48 |
+
field_name_datatypes,db_name,collection_name,source)
|
| 49 |
+
return prompt
|
| 50 |
+
|
| 51 |
+
def resolve_date_variables(self,final_json) -> dict:
|
| 52 |
+
python_code = final_json.get("python_code")
|
| 53 |
+
if not python_code:
|
| 54 |
+
return final_json
|
| 55 |
+
local_vars = {}
|
| 56 |
+
exec(python_code, {"datetime": datetime, "timedelta": timedelta, "timezone": timezone}, local_vars)
|
| 57 |
+
|
| 58 |
+
def replace_vars(obj):
|
| 59 |
+
if isinstance(obj, dict):
|
| 60 |
+
return {k: replace_vars(v) for k, v in obj.items()}
|
| 61 |
+
elif isinstance(obj, list):
|
| 62 |
+
return [replace_vars(i) for i in obj]
|
| 63 |
+
elif isinstance(obj, str) and obj in local_vars:
|
| 64 |
+
return local_vars[obj]
|
| 65 |
+
return obj
|
| 66 |
+
|
| 67 |
+
final_json["filter"] = replace_vars(final_json.get("filter", {}))
|
| 68 |
+
final_json["pipeline"] = replace_vars(final_json.get("pipeline", []))
|
| 69 |
+
return final_json
|
| 70 |
+
|
| 71 |
+
async def final_response_executer(self,prompt):
|
| 72 |
+
try:
|
| 73 |
+
answer = generate_response(prompt)
|
| 74 |
+
final_json = json.loads(answer)
|
| 75 |
+
final_json = resolve_date_variables(final_json)
|
| 76 |
+
|
| 77 |
+
pipeline = final_json.get("pipeline", [])
|
| 78 |
+
filter_ = final_json.get("filter", {})
|
| 79 |
+
operation = final_json.get("operation", "find")
|
| 80 |
+
projection = final_json.get("projection", {})
|
| 81 |
+
sort = final_json.get("sort", {})
|
| 82 |
+
limit = final_json.get("limit", 0)
|
| 83 |
+
|
| 84 |
+
if operation.lower() == "find":
|
| 85 |
+
cursor = crm_leads_collection.find(filter_, projection)
|
| 86 |
+
if sort:
|
| 87 |
+
cursor = cursor.sort(list(sort.items()))
|
| 88 |
+
if limit:
|
| 89 |
+
cursor = cursor.limit(limit)
|
| 90 |
+
results = list(cursor)
|
| 91 |
+
|
| 92 |
+
elif operation.lower() == "aggregate":
|
| 93 |
+
if sort and not any("$sort" in s for s in pipeline):
|
| 94 |
+
pipeline.append({"$sort": sort})
|
| 95 |
+
if limit and not any("$limit" in s for s in pipeline):
|
| 96 |
+
pipeline.append({"$limit": limit})
|
| 97 |
+
results = list(crm_leads_collection.aggregate(pipeline))
|
| 98 |
+
|
| 99 |
+
else:
|
| 100 |
+
print("Unknown operation:", operation)
|
| 101 |
+
results = []
|
| 102 |
+
|
| 103 |
+
json_data = json.loads(json_util.dumps(results))
|
| 104 |
+
cleaned_documents = []
|
| 105 |
+
for doc in json_data:
|
| 106 |
+
cleaned_doc = {
|
| 107 |
+
k: v
|
| 108 |
+
for k, v in doc.items()
|
| 109 |
+
if v not in (None, '') and k != "_id"
|
| 110 |
+
}
|
| 111 |
+
if cleaned_doc:
|
| 112 |
+
cleaned_documents.append(cleaned_doc)
|
| 113 |
+
return cleaned_documents
|
| 114 |
+
|
| 115 |
+
except Exception as e:
|
| 116 |
+
return "error"
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
async def generate_response(self,prompt,model="openai"):
|
| 120 |
+
if model == "openai":
|
| 121 |
+
print("openai model call")
|
| 122 |
+
llm = CodeGenerationLLM()
|
| 123 |
+
response = await llm.generate_final_output(prompt)
|
| 124 |
+
|
| 125 |
+
if model == "openrouter":
|
| 126 |
+
print("openrouter model call")
|
| 127 |
+
llm = OpenRouterClient()
|
| 128 |
+
response = await llm.stream_chat(prompt)
|
| 129 |
+
|
| 130 |
+
return response
|
| 131 |
+
|
| 132 |
+
async def collection_query_finader(self,prompt,agents,user_id,person_id,collection_keywords,priveous_chat_history_query):
|
| 133 |
+
llm = CollectionAndQueryFinder()
|
| 134 |
+
return await llm.generate_response(prompt,agents,user_id,person_id,collection_keywords,priveous_chat_history_query)
|
| 135 |
+
|
| 136 |
+
async def db_and_collection(self,db_name,collection_name):
|
| 137 |
+
db = self.mongo_db_client[db_name]
|
| 138 |
+
collection_name= db[collection_name]
|
| 139 |
+
return db, collection_name
|
| 140 |
+
|
| 141 |
+
async def final_output_extractor_and_responser(self, prompt,priveous_chat_history_query ,agents,db_name,user_id,person_id):
|
| 142 |
+
pass
|
| 143 |
+
|
| 144 |
+
async def run_file(self,user_input,priveous_chat_history_query,db_name, user_id, person_id):
|
| 145 |
+
answer = await self.final_output_extractor_and_responser(
|
| 146 |
+
user_input,priveous_chat_history_query, db_collection_find, db_name, user_id, person_id
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
|
all_combine_code/recommedation_engine/__pycache__/app.cpython-313.pyc
ADDED
|
Binary file (4.77 kB). View file
|
|
|
all_combine_code/recommedation_engine/__pycache__/embeddings_.cpython-313.pyc
ADDED
|
Binary file (4.95 kB). View file
|
|
|
all_combine_code/recommedation_engine/__pycache__/s3_file_uploader.cpython-313.pyc
ADDED
|
Binary file (4.21 kB). View file
|
|
|
all_combine_code/recommedation_engine/app.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import uuid
|
| 3 |
+
import asyncio
|
| 4 |
+
from typing import List
|
| 5 |
+
from fastapi import FastAPI, UploadFile, File, HTTPException
|
| 6 |
+
from dotenv import load_dotenv
|
| 7 |
+
|
| 8 |
+
from binary_classifier.classify import GeminiSportsClassifier
|
| 9 |
+
from embeddings_ import process_inputs
|
| 10 |
+
from s3_file_uploader import upload_to_s3
|
| 11 |
+
|
| 12 |
+
load_dotenv()
|
| 13 |
+
|
| 14 |
+
app = FastAPI(title="Sports Classification API")
|
| 15 |
+
classifier = GeminiSportsClassifier()
|
| 16 |
+
|
| 17 |
+
UPLOAD_DIR = "temp_uploads"
|
| 18 |
+
os.makedirs(UPLOAD_DIR, exist_ok=True)
|
| 19 |
+
|
| 20 |
+
@app.post("/classify")
|
| 21 |
+
async def classify_media(files: List[UploadFile] = File(...)):
|
| 22 |
+
if not files:
|
| 23 |
+
raise HTTPException(status_code=400, detail="No files uploaded")
|
| 24 |
+
file_paths = []
|
| 25 |
+
path_to_original = {}
|
| 26 |
+
try:
|
| 27 |
+
for file in files:
|
| 28 |
+
ext = os.path.splitext(file.filename)[1]
|
| 29 |
+
temp_name = f"{uuid.uuid4()}{ext}"
|
| 30 |
+
temp_path = os.path.join(UPLOAD_DIR, temp_name)
|
| 31 |
+
with open(temp_path, "wb") as f:
|
| 32 |
+
content = await file.read()
|
| 33 |
+
f.write(content)
|
| 34 |
+
file_paths.append(temp_path)
|
| 35 |
+
path_to_original[temp_path] = file.filename
|
| 36 |
+
first_level_results = await classifier.classify_batch(file_paths)
|
| 37 |
+
allowed = {"not_sports", "error"}
|
| 38 |
+
filtered_items = []
|
| 39 |
+
|
| 40 |
+
for item in first_level_results:
|
| 41 |
+
if item.get("answer") not in allowed:
|
| 42 |
+
path = item["file"]
|
| 43 |
+
file_name = path_to_original.get(path)
|
| 44 |
+
ext = os.path.splitext(path)[1].lower()
|
| 45 |
+
|
| 46 |
+
media_type = "video" if ext in [".mp4", ".avi", ".mov"] else "image"
|
| 47 |
+
|
| 48 |
+
filtered_items.append({
|
| 49 |
+
"file_path": path,
|
| 50 |
+
"file_name": file_name,
|
| 51 |
+
"media_type": media_type,
|
| 52 |
+
"answer": item.get("answer")
|
| 53 |
+
})
|
| 54 |
+
|
| 55 |
+
if not filtered_items:
|
| 56 |
+
return []
|
| 57 |
+
|
| 58 |
+
embed_paths = [item["file_path"] for item in filtered_items]
|
| 59 |
+
embedding_result = process_inputs(embed_paths)
|
| 60 |
+
image_embeddings = embedding_result.get("images", [])
|
| 61 |
+
video_embeddings = embedding_result.get("videos", [])
|
| 62 |
+
img_idx = 0
|
| 63 |
+
vid_idx = 0
|
| 64 |
+
|
| 65 |
+
for item in filtered_items:
|
| 66 |
+
if item["media_type"] == "image":
|
| 67 |
+
item["embedding"] = image_embeddings[img_idx].tolist() if len(image_embeddings) > 0 else None
|
| 68 |
+
img_idx += 1
|
| 69 |
+
else:
|
| 70 |
+
item["embedding"] = video_embeddings[vid_idx].tolist() if len(video_embeddings) > 0 else None
|
| 71 |
+
vid_idx += 1
|
| 72 |
+
|
| 73 |
+
async def upload_single(item):
|
| 74 |
+
url = await upload_to_s3(item["file_path"], item["file_name"])
|
| 75 |
+
item["s3_url"] = url
|
| 76 |
+
return item
|
| 77 |
+
|
| 78 |
+
uploaded_results = await asyncio.gather(*[upload_single(item) for item in filtered_items])
|
| 79 |
+
|
| 80 |
+
return uploaded_results
|
| 81 |
+
|
| 82 |
+
finally:
|
| 83 |
+
for path in file_paths:
|
| 84 |
+
if os.path.exists(path):
|
| 85 |
+
os.remove(path)
|
all_combine_code/recommedation_engine/binary_classifier/__pycache__/classify.cpython-313.pyc
ADDED
|
Binary file (6.61 kB). View file
|
|
|
all_combine_code/recommedation_engine/binary_classifier/classify.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os, cv2,json
|
| 2 |
+
import asyncio
|
| 3 |
+
from PIL import Image
|
| 4 |
+
from google import genai
|
| 5 |
+
from google.genai import types
|
| 6 |
+
from textwrap import dedent
|
| 7 |
+
|
| 8 |
+
from dotenv import load_dotenv
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))
|
| 12 |
+
|
| 13 |
+
class GeminiSportsClassifier:
|
| 14 |
+
def __init__(self):
|
| 15 |
+
self.categories = [
|
| 16 |
+
"a photo of a marathon race on city roads", "a photo of long distance marathon runners with bib numbers",
|
| 17 |
+
"a photo of a large group running marathon event", "a photo of Olympic marathon race",
|
| 18 |
+
"a photo of sprint running race on track", "a photo of Olympic track and field athletics",
|
| 19 |
+
"a photo of relay race baton passing", "a photo of hurdles race on track",
|
| 20 |
+
"a photo of high jump Olympic event",
|
| 21 |
+
"a photo of pole vault Olympic event", "a photo of long jump Olympic event",
|
| 22 |
+
"a photo of javelin throw Olympic event",
|
| 23 |
+
"a photo of discus throw Olympic event", "a photo of shot put Olympic event",
|
| 24 |
+
"a photo of Olympic swimming competition",
|
| 25 |
+
"a photo of Olympic cycling road race", "a photo of Olympic track cycling",
|
| 26 |
+
"a photo of Olympic gymnastics performance",
|
| 27 |
+
"a photo of Olympic boxing match", "a photo of Olympic wrestling match",
|
| 28 |
+
"a photo of Olympic weightlifting competition",
|
| 29 |
+
"a photo of cricket match on stadium", "a photo of cricket players batting and bowling",
|
| 30 |
+
"a photo of football match in stadium",
|
| 31 |
+
"a photo of soccer players playing match", "a photo of football goal scoring moment",
|
| 32 |
+
"a photo of basketball game in indoor court",
|
| 33 |
+
"a photo of volleyball match on court", "a photo of tennis match on court",
|
| 34 |
+
"a photo of badminton match indoor stadium"
|
| 35 |
+
]
|
| 36 |
+
def _prepare_image(self, img):
|
| 37 |
+
img.thumbnail((512, 512))
|
| 38 |
+
return img
|
| 39 |
+
|
| 40 |
+
def _extract_frames(self, video_path, num_frames=2):
|
| 41 |
+
cap = cv2.VideoCapture(video_path)
|
| 42 |
+
total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 43 |
+
frames = []
|
| 44 |
+
for i in range(num_frames):
|
| 45 |
+
pos = int((i + 1) * total_frames / (num_frames + 1))
|
| 46 |
+
cap.set(cv2.CAP_PROP_POS_FRAMES, pos)
|
| 47 |
+
ret, frame = cap.read()
|
| 48 |
+
if ret:
|
| 49 |
+
frame = cv2.resize(frame, (480, 270))
|
| 50 |
+
img = Image.fromarray(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
|
| 51 |
+
frames.append(img)
|
| 52 |
+
cap.release()
|
| 53 |
+
return frames
|
| 54 |
+
|
| 55 |
+
async def classify(self, file_path):
|
| 56 |
+
ext = os.path.splitext(file_path)[1].lower()
|
| 57 |
+
try:
|
| 58 |
+
media = []
|
| 59 |
+
if ext in ['.jpg', '.jpeg', '.png', ".webp"]:
|
| 60 |
+
media.append(self._prepare_image(Image.open(file_path)))
|
| 61 |
+
elif ext in ['.mp4', '.avi', '.mov']:
|
| 62 |
+
media = self._extract_frames(file_path)
|
| 63 |
+
else:
|
| 64 |
+
return {"file": file_path, "answer": "not_sports"}
|
| 65 |
+
|
| 66 |
+
prompt = dedent(f"""
|
| 67 |
+
Analyze the provided media (image or video frames).
|
| 68 |
+
1. If it matches one of these categories, return the category: {self.categories}
|
| 69 |
+
2. If it is not related to these sports, return "not_sports".
|
| 70 |
+
Output strictly in JSON: {{"answer": "category_name"}}
|
| 71 |
+
""")
|
| 72 |
+
|
| 73 |
+
response = await client.aio.models.generate_content(
|
| 74 |
+
model="gemini-2.0-flash",
|
| 75 |
+
contents=[prompt, *media],
|
| 76 |
+
config=types.GenerateContentConfig(
|
| 77 |
+
response_mime_type='application/json'
|
| 78 |
+
)
|
| 79 |
+
)
|
| 80 |
+
|
| 81 |
+
data = json.loads(response.text)
|
| 82 |
+
if isinstance(data, dict):
|
| 83 |
+
return {"file": file_path, **data}
|
| 84 |
+
else:
|
| 85 |
+
return {"file": file_path, "answer": str(data)}
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
return {"file": file_path, "answer": "error", "details": str(e)}
|
| 89 |
+
|
| 90 |
+
async def classify_batch(self, file_paths, max_concurrent=5):
|
| 91 |
+
semaphore = asyncio.Semaphore(max_concurrent)
|
| 92 |
+
|
| 93 |
+
async def classify_with_limit(path):
|
| 94 |
+
async with semaphore:
|
| 95 |
+
return await self.classify(path)
|
| 96 |
+
|
| 97 |
+
tasks = [classify_with_limit(path) for path in file_paths]
|
| 98 |
+
return await asyncio.gather(*tasks)
|
| 99 |
+
|
| 100 |
+
|
all_combine_code/recommedation_engine/embeddings_.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import clip
|
| 3 |
+
import cv2
|
| 4 |
+
from PIL import Image
|
| 5 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 6 |
+
|
| 7 |
+
device = "cpu"
|
| 8 |
+
|
| 9 |
+
model, preprocess = clip.load("ViT-B/16", device=device)
|
| 10 |
+
model.eval()
|
| 11 |
+
|
| 12 |
+
SUPPORTED_IMAGE_EXTS = (".jpg", ".jpeg", ".png")
|
| 13 |
+
SUPPORTED_VIDEO_EXTS = (".mp4", ".avi", ".mov")
|
| 14 |
+
|
| 15 |
+
def preprocess_image(path):
|
| 16 |
+
img = Image.open(path).convert("RGB")
|
| 17 |
+
return preprocess(img)
|
| 18 |
+
|
| 19 |
+
def load_images_parallel(image_paths, max_workers=4):
|
| 20 |
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
| 21 |
+
images = list(executor.map(preprocess_image, image_paths))
|
| 22 |
+
return torch.stack(images)
|
| 23 |
+
|
| 24 |
+
def images_to_embeddings_cpu(image_paths, batch_size=32):
|
| 25 |
+
outputs = []
|
| 26 |
+
|
| 27 |
+
for i in range(0, len(image_paths), batch_size):
|
| 28 |
+
batch_paths = image_paths[i:i + batch_size]
|
| 29 |
+
images = load_images_parallel(batch_paths)
|
| 30 |
+
|
| 31 |
+
with torch.no_grad():
|
| 32 |
+
emb = model.encode_image(images)
|
| 33 |
+
emb = emb / emb.norm(dim=-1, keepdim=True)
|
| 34 |
+
outputs.append(emb)
|
| 35 |
+
return torch.cat(outputs).numpy()
|
| 36 |
+
|
| 37 |
+
def extract_frames(video_path, sample_rate=1):
|
| 38 |
+
cap = cv2.VideoCapture(video_path)
|
| 39 |
+
fps = cap.get(cv2.CAP_PROP_FPS)
|
| 40 |
+
interval = int(max(1, fps * sample_rate))
|
| 41 |
+
|
| 42 |
+
frames = []
|
| 43 |
+
count = 0
|
| 44 |
+
|
| 45 |
+
while cap.isOpened():
|
| 46 |
+
ret, frame = cap.read()
|
| 47 |
+
if not ret:
|
| 48 |
+
break
|
| 49 |
+
|
| 50 |
+
if count % interval == 0:
|
| 51 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 52 |
+
frames.append(Image.fromarray(frame))
|
| 53 |
+
|
| 54 |
+
count += 1
|
| 55 |
+
|
| 56 |
+
cap.release()
|
| 57 |
+
return frames
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def video_to_embedding_cpu(video_path):
|
| 61 |
+
frames = extract_frames(video_path)
|
| 62 |
+
|
| 63 |
+
if not frames:
|
| 64 |
+
return None
|
| 65 |
+
|
| 66 |
+
images = torch.stack([preprocess(f) for f in frames])
|
| 67 |
+
|
| 68 |
+
with torch.no_grad():
|
| 69 |
+
emb = model.encode_image(images)
|
| 70 |
+
emb = emb / emb.norm(dim=-1, keepdim=True)
|
| 71 |
+
|
| 72 |
+
return emb.mean(dim=0).numpy()
|
| 73 |
+
|
| 74 |
+
|
| 75 |
+
def process_videos_parallel(video_files, max_workers=2):
|
| 76 |
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
| 77 |
+
return list(executor.map(video_to_embedding_cpu, video_files))
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
def process_inputs(files):
|
| 81 |
+
image_files = []
|
| 82 |
+
video_files = []
|
| 83 |
+
|
| 84 |
+
for f in files:
|
| 85 |
+
f_lower = f.lower()
|
| 86 |
+
if f_lower.endswith(SUPPORTED_IMAGE_EXTS):
|
| 87 |
+
image_files.append(f)
|
| 88 |
+
elif f_lower.endswith(SUPPORTED_VIDEO_EXTS):
|
| 89 |
+
video_files.append(f)
|
| 90 |
+
|
| 91 |
+
results = {}
|
| 92 |
+
if image_files:
|
| 93 |
+
results["images"] = images_to_embeddings_cpu(image_files)
|
| 94 |
+
if video_files:
|
| 95 |
+
results["videos"] = process_videos_parallel(video_files)
|
| 96 |
+
return results
|
| 97 |
+
|
| 98 |
+
|