Khor Kean Teng commited on
Commit ·
a17bdfd
1
Parent(s): 2ddf01d
stage for update
Browse files- .gitignore +26 -0
- LICENSE +21 -0
- app.py +103 -0
- backend/utils.py +14 -0
- data/sample_data.csv +0 -0
- data/sample_output.csv +0 -0
- git.sh +25 -0
- model/model.pkl +3 -0
- pages/documentation.py +20 -0
- requirements.txt +6 -0
- run.sh +2 -0
- test.py +17 -0
.gitignore
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Python cache files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# Jupyter Notebook checkpoints
|
| 7 |
+
.ipynb_checkpoints
|
| 8 |
+
|
| 9 |
+
# Environment variables
|
| 10 |
+
.env
|
| 11 |
+
|
| 12 |
+
# Streamlit specific files
|
| 13 |
+
.streamlit/
|
| 14 |
+
|
| 15 |
+
draft.ipynb
|
| 16 |
+
|
| 17 |
+
# Model files
|
| 18 |
+
|
| 19 |
+
*.h5
|
| 20 |
+
|
| 21 |
+
# Logs
|
| 22 |
+
*.log
|
| 23 |
+
|
| 24 |
+
# Virtual environment
|
| 25 |
+
venv/
|
| 26 |
+
env/
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 keanteng
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
app.py
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
from backend.utils import *
|
| 3 |
+
import pandas as pd
|
| 4 |
+
from datetime import datetime, timedelta
|
| 5 |
+
import time
|
| 6 |
+
import joblib
|
| 7 |
+
import google.generativeai as genai
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
|
| 10 |
+
# page layout
|
| 11 |
+
st.set_page_config(page_title="Telco Churn Engine", page_icon="🧊", layout="wide")
|
| 12 |
+
|
| 13 |
+
# title
|
| 14 |
+
st.title("Telco Churn Engine")
|
| 15 |
+
st.write("Speed up predicting customer churn in the telecommunications industry. Powered by Generative AI and Job Schedule Function.")
|
| 16 |
+
|
| 17 |
+
# sidebar
|
| 18 |
+
with st.sidebar:
|
| 19 |
+
with st.expander("⏰ Schedule Run (Demo)", expanded=False):
|
| 20 |
+
st.caption("Schedule a run for the app.")
|
| 21 |
+
run_date = st.date_input("Select Date")
|
| 22 |
+
run_time = st.time_input("Select Time")
|
| 23 |
+
countdown_placeholder = st.empty()
|
| 24 |
+
if st.button("Schedule Run", type='secondary'):
|
| 25 |
+
run_datetime = datetime.combine(run_date, run_time)
|
| 26 |
+
# scheduler.add_job(scheduled_task, 'date', run_date=run_datetime)
|
| 27 |
+
st.success(f"App scheduled to run on {run_datetime}.")
|
| 28 |
+
|
| 29 |
+
# Countdown logic
|
| 30 |
+
while True:
|
| 31 |
+
now = datetime.now()
|
| 32 |
+
time_left = run_datetime - now
|
| 33 |
+
if time_left.total_seconds() <= 0:
|
| 34 |
+
countdown_placeholder.write("Scheduled task is running!")
|
| 35 |
+
break
|
| 36 |
+
countdown_placeholder.write(f"Time left: {time_left}")
|
| 37 |
+
time.sleep(1)
|
| 38 |
+
|
| 39 |
+
with st.expander("⚙️ Generative AI", expanded=True):
|
| 40 |
+
st.caption("API token can be obtained at https://aistudio.google.com/.")
|
| 41 |
+
gemini_api = st.text_input("Gemini Token", "", type='password')
|
| 42 |
+
if authenticate_gemini(gemini_api):
|
| 43 |
+
st.success("Gemini API token is valid.")
|
| 44 |
+
else:
|
| 45 |
+
st.error("Gemini API token is invalid.")
|
| 46 |
+
|
| 47 |
+
with st.expander("🗳️ Sample Data Download", expanded=False):
|
| 48 |
+
st.caption("Download sample data for testing.")
|
| 49 |
+
sample_data = load_data("data/sample_data.csv")
|
| 50 |
+
st.download_button("Download Sample Data", sample_data.to_csv(), "sample_data.csv", "text/csv")
|
| 51 |
+
|
| 52 |
+
st.divider()
|
| 53 |
+
|
| 54 |
+
st.caption("MIT License 2025 © Khor Kean Teng, Loong Shih-Wai, Tioh Zi Cong, Yee See Marn")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
# main content
|
| 58 |
+
with st.chat_message("assistant", avatar="https://static.vecteezy.com/system/resources/previews/035/010/451/non_2x/bionic-zombie-infusion-design-zombie-cyborg-evolution-icon-vector.jpg"):
|
| 59 |
+
response = st.write("Hello admin! I am Arnold. How can I automate so that you might lost your job?")
|
| 60 |
+
st.caption("If you use predefined data, the file upload step will be hidden.")
|
| 61 |
+
toggle = st.toggle('Use Predefined Data', True)
|
| 62 |
+
data = load_data("data/sample_data.csv")
|
| 63 |
+
|
| 64 |
+
if toggle == False:
|
| 65 |
+
uploaded_file = st.file_uploader("Upload a CSV file", type=["csv"])
|
| 66 |
+
if uploaded_file is not None:
|
| 67 |
+
data = pd.read_csv(uploaded_file)
|
| 68 |
+
|
| 69 |
+
submit = st.button("Execute", type='primary')
|
| 70 |
+
|
| 71 |
+
if submit:
|
| 72 |
+
# show preview in table in expander
|
| 73 |
+
with st.status("Preview Data", expanded=True):
|
| 74 |
+
st.write(data.head())
|
| 75 |
+
|
| 76 |
+
model = joblib.load("model/model.pkl")
|
| 77 |
+
prediction = model.predict(data)
|
| 78 |
+
data["Churn Prediction"] = prediction
|
| 79 |
+
|
| 80 |
+
# count how many churn
|
| 81 |
+
churn_count = data["Churn Prediction"].value_counts()
|
| 82 |
+
|
| 83 |
+
# show prview in table in expander
|
| 84 |
+
with st.status("Prediction", expanded=True):
|
| 85 |
+
st.write("The prediction is done. There are {} churn customers out of the total {} customers.".format(churn_count[1], len(data)))
|
| 86 |
+
st.write(data.head())
|
| 87 |
+
|
| 88 |
+
# plot a pie chart
|
| 89 |
+
with st.status("Churn Pie Chart", expanded=True):
|
| 90 |
+
st.write("The pie chart shows the distribution of churn customers.")
|
| 91 |
+
fig, ax = plt.subplots()
|
| 92 |
+
# resize the pie chart
|
| 93 |
+
fig.set_size_inches(3, 3)
|
| 94 |
+
ax.pie(churn_count, labels=["Churn", "Non-Churn"], autopct='%1.1f%%', startangle=90)
|
| 95 |
+
st.pyplot(fig)
|
| 96 |
+
|
| 97 |
+
with st.status("AI Opinion", expanded=True):
|
| 98 |
+
try:
|
| 99 |
+
ai_model = genai.GenerativeModel("gemini-1.5-flash")
|
| 100 |
+
response = ai_model.generate_content(f"Give some opinions in about 100 word based on the prediction results where there are {churn_count[1]} cases of attrition out of the total {len(data)} number of customers.")
|
| 101 |
+
st.write(response.text)
|
| 102 |
+
except Exception as e:
|
| 103 |
+
st.write("You don't have access to this feature. Please authenticate to use this feature.")
|
backend/utils.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
def authenticate_gemini(api_key):
|
| 5 |
+
try:
|
| 6 |
+
genai.configure(api_key=api_key)
|
| 7 |
+
ai_model = genai.GenerativeModel("gemini-1.5-flash")
|
| 8 |
+
test = ai_model.generate_content("Explain how AI works")
|
| 9 |
+
return True
|
| 10 |
+
except Exception as e:
|
| 11 |
+
return False
|
| 12 |
+
|
| 13 |
+
def load_data(path):
|
| 14 |
+
return pd.read_csv(path)
|
data/sample_data.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
data/sample_output.csv
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
git.sh
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
# Get the current commit count
|
| 4 |
+
commit_count=$(git rev-list --count HEAD)
|
| 5 |
+
|
| 6 |
+
# Increment the commit count
|
| 7 |
+
next_commit_count=$((commit_count + 1))
|
| 8 |
+
|
| 9 |
+
# Add all changes
|
| 10 |
+
git add .
|
| 11 |
+
|
| 12 |
+
# Check if a custom message is provided
|
| 13 |
+
if [ -z "$1" ]; then
|
| 14 |
+
commit_message="auto commit #$next_commit_count"
|
| 15 |
+
else
|
| 16 |
+
commit_message="$1 #$next_commit_count"
|
| 17 |
+
fi
|
| 18 |
+
|
| 19 |
+
# Commit with the message
|
| 20 |
+
git commit -m "$commit_message"
|
| 21 |
+
|
| 22 |
+
# Push the changes
|
| 23 |
+
git push
|
| 24 |
+
|
| 25 |
+
# to run ./git_auto.sh "Your custom message"
|
model/model.pkl
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:07e0fdb36fa70e97c0bcaeb438056e7bb6d87f82af1df4c262ce1b1ada9c8ff4
|
| 3 |
+
size 193308
|
pages/documentation.py
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
|
| 3 |
+
st.set_page_config(page_title='Documentation', layout='wide')
|
| 4 |
+
|
| 5 |
+
with st.sidebar:
|
| 6 |
+
with st.expander("⚠️ Disclaimer", expanded=True):
|
| 7 |
+
st.write("This web app is intended for prediction purposes only. The results are based on the input data provided and \
|
| 8 |
+
the performance of the machine learning model. The accuracy of the predictions may vary depending on data quality \
|
| 9 |
+
and model reliability.")
|
| 10 |
+
|
| 11 |
+
st.caption("MIT License 2025 © Khor Kean Teng, Loong Shih-Wai, Tioh Zi Cong, Yee See Marn")
|
| 12 |
+
|
| 13 |
+
st.title("📄 Documentation")
|
| 14 |
+
st.markdown("""
|
| 15 |
+
To learn more about the project, please refer to the sections below.
|
| 16 |
+
""")
|
| 17 |
+
st.subheader("About Telco Churn")
|
| 18 |
+
st.write("""Customer churn is a critical issue in the telecommunications industry. \
|
| 19 |
+
It refers to the percentage of customers who discontinue their services with a company within a given time period.
|
| 20 |
+
The churn rate is a key metric for businesses to measure customer satisfaction and loyalty.""")
|
requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
streamlit
|
| 2 |
+
pandas
|
| 3 |
+
joblib
|
| 4 |
+
scikit-learn==1.3.1
|
| 5 |
+
google-generativeai
|
| 6 |
+
matplotlib
|
run.sh
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# only work if use python 3.12 on bash shell
|
| 2 |
+
py -3.12 -m streamlit run app.py
|
test.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import joblib
|
| 2 |
+
import pandas as pd
|
| 3 |
+
|
| 4 |
+
# Load the model
|
| 5 |
+
model = joblib.load('model/model.pkl')
|
| 6 |
+
|
| 7 |
+
# Load the data
|
| 8 |
+
data = pd.read_csv('data/sample_data.csv')
|
| 9 |
+
|
| 10 |
+
# Make predictions
|
| 11 |
+
predictions = model.predict(data)
|
| 12 |
+
|
| 13 |
+
# save the predictions
|
| 14 |
+
data['Churn'] = predictions
|
| 15 |
+
|
| 16 |
+
# save as sample_output.csv
|
| 17 |
+
data.to_csv('data/sample_output.csv', index=False)
|