XRachel commited on
Commit
f9d2ce5
·
verified ·
1 Parent(s): 6146a68

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +17 -2
  2. app.py +83 -38
  3. requirements.txt +20 -18
  4. style.css +33 -19
Dockerfile CHANGED
@@ -1,4 +1,3 @@
1
-
2
  FROM python:3.11-slim
3
 
4
  ENV PYTHONDONTWRITEBYTECODE=1
@@ -10,7 +9,23 @@ WORKDIR /app
10
 
11
  COPY requirements.txt /app/requirements.txt
12
 
13
- RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
15
  COPY . /app
16
 
 
 
1
  FROM python:3.11-slim
2
 
3
  ENV PYTHONDONTWRITEBYTECODE=1
 
9
 
10
  COPY requirements.txt /app/requirements.txt
11
 
12
+ RUN apt-get update && apt-get install -y --no-install-recommends \
13
+ build-essential \
14
+ curl \
15
+ git \
16
+ r-base \
17
+ r-base-dev \
18
+ libcurl4-openssl-dev \
19
+ libssl-dev \
20
+ libxml2-dev \
21
+ && rm -rf /var/lib/apt/lists/*
22
+
23
+ RUN pip install --no-cache-dir --upgrade pip && \
24
+ pip install --no-cache-dir -r requirements.txt && \
25
+ python -m ipykernel install --sys-prefix --name python3 --display-name "Python 3"
26
+
27
+ RUN Rscript -e "install.packages(c('IRkernel','ggplot2','dplyr','readr','forecast','tseries'), repos='https://cloud.r-project.org')" && \
28
+ Rscript -e "IRkernel::installspec(user = FALSE)"
29
 
30
  COPY . /app
31
 
app.py CHANGED
@@ -1,12 +1,14 @@
1
  import os
2
  import time
3
  import traceback
 
4
  from pathlib import Path
5
 
6
  import gradio as gr
7
  import pandas as pd
8
  import joblib
9
  import papermill as pm
 
10
 
11
  BASE_DIR = Path(__file__).resolve().parent
12
 
@@ -20,7 +22,7 @@ PIPELINE_CANDIDATES = [
20
  ]
21
 
22
  RUNS_DIR = BASE_DIR / "runs"
23
- PAPERMILL_TIMEOUT = 1800
24
 
25
 
26
  def ensure_dirs():
@@ -31,16 +33,42 @@ def stamp():
31
  return time.strftime("%Y%m%d-%H%M%S")
32
 
33
 
34
- def run_notebook(nb_path: Path, label: str) -> str:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  ensure_dirs()
36
  if not nb_path.exists():
37
  return f"❌ {label} not found: {nb_path.name}"
 
 
38
  try:
39
- out_path = RUNS_DIR / f"{stamp()}_{nb_path.name}"
40
  pm.execute_notebook(
41
  input_path=str(nb_path),
42
  output_path=str(out_path),
43
  cwd=str(BASE_DIR),
 
44
  log_output=True,
45
  progress_bar=False,
46
  request_save_on_cell_execute=True,
@@ -52,11 +80,11 @@ def run_notebook(nb_path: Path, label: str) -> str:
52
 
53
 
54
  def run_python():
55
- return run_notebook(PY_NOTEBOOK, "Python notebook")
56
 
57
 
58
  def run_r():
59
- return run_notebook(R_NOTEBOOK, "R notebook")
60
 
61
 
62
  def run_pipeline():
@@ -68,7 +96,6 @@ def run_pipeline():
68
  if target is None:
69
  return "❌ pipeline.py not found."
70
  try:
71
- import subprocess
72
  proc = subprocess.run(
73
  ["python", str(target)],
74
  cwd=str(BASE_DIR),
@@ -85,17 +112,17 @@ def run_pipeline():
85
 
86
 
87
  def run_all():
88
- py_log = run_python()
89
- r_log = run_r()
90
- pipe_log = run_pipeline()
91
- return "\n\n".join([
92
  "=== Run Python ===",
93
- py_log,
 
94
  "=== Run R ===",
95
- r_log,
 
96
  "=== Run Pipeline ===",
97
- pipe_log,
98
- ])
 
99
 
100
 
101
  def load_model():
@@ -143,48 +170,65 @@ def dashboard_data():
143
 
144
  age_df = pd.DataFrame({"AgeBand": [], "churn_rate": []})
145
  if {"Age", "Exited"}.issubset(df.columns):
146
- temp = df.copy()
147
- temp["AgeBand"] = pd.cut(
148
- temp["Age"],
149
  bins=[18, 30, 40, 50, 60, 70],
150
  include_lowest=True
151
  )
152
- age_df = temp.groupby("AgeBand").agg(churn_rate=("Exited", "mean")).reset_index()
153
  age_df["AgeBand"] = age_df["AgeBand"].astype(str)
154
  age_df["churn_rate"] = (age_df["churn_rate"] * 100).round(2)
155
 
 
 
 
156
  return summary, geo_df, age_df, df
157
 
158
 
159
  def build_ui():
160
- css = (BASE_DIR / "style.css").read_text(encoding="utf-8")
 
161
 
162
  with gr.Blocks(title="Bank Churn Dashboard") as demo:
163
  gr.HTML(f"<style>{css}</style>")
164
 
165
- gr.Markdown("# 🏦 Bank Churn Dashboard")
 
 
 
 
166
 
167
  with gr.Tab("Run Analyses"):
168
- gr.Markdown("Run Python analysis, R analysis, or all analyses before reviewing the dashboard.")
169
  with gr.Row():
170
- btn_py = gr.Button("Run Python")
171
- btn_r = gr.Button("Run R")
172
- btn_all = gr.Button("Run All")
173
- out_py = gr.Textbox(lines=14, label="Python Log")
174
- out_r = gr.Textbox(lines=14, label="R Log")
175
- out_all = gr.Textbox(lines=18, label="Run All Log")
 
176
 
177
- btn_py.click(run_python, outputs=out_py)
178
- btn_r.click(run_r, outputs=out_r)
179
- btn_all.click(run_all, outputs=out_all)
180
 
181
  with gr.Tab("Dashboard"):
182
- refresh_btn = gr.Button("Refresh Dashboard")
183
  summary_md = gr.Markdown()
184
  with gr.Row():
185
- geo_plot = gr.BarPlot(x="Geography", y="Exited", title="Churn by Geography (%)")
186
- age_plot = gr.LinePlot(x="AgeBand", y="churn_rate", title="Churn by Age Band (%)")
187
- data_table = gr.Dataframe(interactive=True, label="Customer Data")
 
 
 
 
 
 
 
 
 
188
 
189
  refresh_btn.click(
190
  dashboard_data,
@@ -196,11 +240,12 @@ def build_ui():
196
  )
197
 
198
  with gr.Tab("Prediction"):
199
- age = gr.Number(label="Age", value=35)
200
- balance = gr.Number(label="Balance", value=5000)
201
- pred_btn = gr.Button("Predict")
 
202
  pred_out = gr.Textbox(label="Prediction Result")
203
- pred_btn.click(predict, inputs=[age, balance], outputs=pred_out)
204
 
205
  return demo
206
 
 
1
  import os
2
  import time
3
  import traceback
4
+ import subprocess
5
  from pathlib import Path
6
 
7
  import gradio as gr
8
  import pandas as pd
9
  import joblib
10
  import papermill as pm
11
+ from jupyter_client.kernelspec import KernelSpecManager
12
 
13
  BASE_DIR = Path(__file__).resolve().parent
14
 
 
22
  ]
23
 
24
  RUNS_DIR = BASE_DIR / "runs"
25
+ PAPERMILL_TIMEOUT = int(os.environ.get("PAPERMILL_TIMEOUT", "1800"))
26
 
27
 
28
  def ensure_dirs():
 
33
  return time.strftime("%Y%m%d-%H%M%S")
34
 
35
 
36
+ def available_kernels():
37
+ try:
38
+ return KernelSpecManager().find_kernel_specs()
39
+ except Exception:
40
+ return {}
41
+
42
+
43
+ def python_kernel_name():
44
+ kernels = available_kernels()
45
+ for name in ["python3", "python", "python-3"]:
46
+ if name in kernels:
47
+ return name
48
+ return None
49
+
50
+
51
+ def r_kernel_name():
52
+ kernels = available_kernels()
53
+ for name in ["ir", "irkernel", "r"]:
54
+ if name in kernels:
55
+ return name
56
+ return None
57
+
58
+
59
+ def run_notebook(nb_path: Path, label: str, kernel_name: str | None) -> str:
60
  ensure_dirs()
61
  if not nb_path.exists():
62
  return f"❌ {label} not found: {nb_path.name}"
63
+ if not kernel_name:
64
+ return f"❌ {label} kernel is not available."
65
  try:
66
+ out_path = RUNS_DIR / f"run_{stamp()}_{nb_path.name}"
67
  pm.execute_notebook(
68
  input_path=str(nb_path),
69
  output_path=str(out_path),
70
  cwd=str(BASE_DIR),
71
+ kernel_name=kernel_name,
72
  log_output=True,
73
  progress_bar=False,
74
  request_save_on_cell_execute=True,
 
80
 
81
 
82
  def run_python():
83
+ return run_notebook(PY_NOTEBOOK, "Python notebook", python_kernel_name())
84
 
85
 
86
  def run_r():
87
+ return run_notebook(R_NOTEBOOK, "R notebook", r_kernel_name())
88
 
89
 
90
  def run_pipeline():
 
96
  if target is None:
97
  return "❌ pipeline.py not found."
98
  try:
 
99
  proc = subprocess.run(
100
  ["python", str(target)],
101
  cwd=str(BASE_DIR),
 
112
 
113
 
114
  def run_all():
115
+ parts = [
 
 
 
116
  "=== Run Python ===",
117
+ run_python(),
118
+ "",
119
  "=== Run R ===",
120
+ run_r(),
121
+ "",
122
  "=== Run Pipeline ===",
123
+ run_pipeline(),
124
+ ]
125
+ return "\n".join(parts)
126
 
127
 
128
  def load_model():
 
170
 
171
  age_df = pd.DataFrame({"AgeBand": [], "churn_rate": []})
172
  if {"Age", "Exited"}.issubset(df.columns):
173
+ tmp = df.copy()
174
+ tmp["AgeBand"] = pd.cut(
175
+ tmp["Age"],
176
  bins=[18, 30, 40, 50, 60, 70],
177
  include_lowest=True
178
  )
179
+ age_df = tmp.groupby("AgeBand").agg(churn_rate=("Exited", "mean")).reset_index()
180
  age_df["AgeBand"] = age_df["AgeBand"].astype(str)
181
  age_df["churn_rate"] = (age_df["churn_rate"] * 100).round(2)
182
 
183
+ kernel_info = ", ".join(sorted(available_kernels().keys())) or "none"
184
+ summary += f"\n- Available Kernels: **{kernel_info}**"
185
+
186
  return summary, geo_df, age_df, df
187
 
188
 
189
  def build_ui():
190
+ css_path = BASE_DIR / "style.css"
191
+ css = css_path.read_text(encoding="utf-8") if css_path.exists() else ""
192
 
193
  with gr.Blocks(title="Bank Churn Dashboard") as demo:
194
  gr.HTML(f"<style>{css}</style>")
195
 
196
+ gr.Markdown(
197
+ "# 🏦 Bank Churn Dashboard\n"
198
+ "*Run the Python and R analyses first, then review the dashboard and predictions.*",
199
+ elem_id="escp_title",
200
+ )
201
 
202
  with gr.Tab("Run Analyses"):
 
203
  with gr.Row():
204
+ btn_py = gr.Button("Run Python", variant="secondary")
205
+ btn_r = gr.Button("Run R", variant="secondary")
206
+ btn_all = gr.Button("Run All", variant="primary")
207
+
208
+ py_log = gr.Textbox(label="Python Log", lines=12, max_lines=18, interactive=False)
209
+ r_log = gr.Textbox(label="R Log", lines=12, max_lines=18, interactive=False)
210
+ all_log = gr.Textbox(label="Run All Log", lines=18, max_lines=28, interactive=False)
211
 
212
+ btn_py.click(run_python, outputs=[py_log])
213
+ btn_r.click(run_r, outputs=[r_log])
214
+ btn_all.click(run_all, outputs=[all_log])
215
 
216
  with gr.Tab("Dashboard"):
217
+ refresh_btn = gr.Button("Refresh Dashboard", variant="primary")
218
  summary_md = gr.Markdown()
219
  with gr.Row():
220
+ geo_plot = gr.BarPlot(
221
+ x="Geography",
222
+ y="Exited",
223
+ title="Churn by Geography (%)",
224
+ vertical=False
225
+ )
226
+ age_plot = gr.LinePlot(
227
+ x="AgeBand",
228
+ y="churn_rate",
229
+ title="Churn by Age Band (%)"
230
+ )
231
+ data_table = gr.Dataframe(label="Customer Data", interactive=True)
232
 
233
  refresh_btn.click(
234
  dashboard_data,
 
240
  )
241
 
242
  with gr.Tab("Prediction"):
243
+ with gr.Row():
244
+ age = gr.Number(label="Age", value=35)
245
+ balance = gr.Number(label="Balance", value=5000)
246
+ pred_btn = gr.Button("Predict", variant="primary")
247
  pred_out = gr.Textbox(label="Prediction Result")
248
+ pred_btn.click(predict, inputs=[age, balance], outputs=[pred_out])
249
 
250
  return demo
251
 
requirements.txt CHANGED
@@ -1,18 +1,20 @@
1
- gradio>=4.0
2
- pandas
3
- numpy
4
- matplotlib
5
- seaborn
6
- statsmodels
7
- scikit-learn
8
- scipy
9
- joblib
10
- papermill
11
- nbformat
12
- pillow
13
- requests
14
- beautifulsoup4
15
- vaderSentiment
16
- huggingface_hub
17
- textblob
18
- faker
 
 
 
1
+ gradio==6.0.0
2
+ pandas>=2.0.0
3
+ numpy>=1.24.0
4
+ matplotlib>=3.7.0
5
+ seaborn>=0.13.0
6
+ statsmodels>=0.14.0
7
+ scikit-learn>=1.3.0
8
+ scipy>=1.10.0
9
+ joblib>=1.3.0
10
+ papermill>=2.5.0
11
+ nbformat>=5.9.0
12
+ ipykernel>=6.29.0
13
+ jupyter_client>=8.6.0
14
+ pillow>=10.0.0
15
+ requests>=2.31.0
16
+ beautifulsoup4>=4.12.0
17
+ vaderSentiment>=3.3.2
18
+ huggingface_hub>=0.20.0
19
+ textblob>=0.18.0
20
+ faker>=20.0.0
style.css CHANGED
@@ -1,4 +1,4 @@
1
- /* Background logic restored from the first app style */
2
  gradio-app,
3
  .gradio-app,
4
  .main,
@@ -23,32 +23,37 @@ html, body {
23
  min-height: 100vh !important;
24
  }
25
 
26
- /* Restore the top banner spacing like the first app */
27
  .gradio-container {
28
  max-width: 1400px !important;
29
  width: 94vw !important;
30
  margin: 0 auto !important;
31
  padding-top: 220px !important;
32
- padding-bottom: 120px !important;
33
  background: transparent !important;
34
  }
35
 
36
  /* Title */
37
- h1 {
38
- color: #ffffff !important;
39
  font-size: 3rem !important;
40
  font-weight: 800 !important;
41
  text-align: center !important;
42
  margin: 0 0 12px 0 !important;
43
- text-shadow: 0 2px 10px rgba(0, 0, 0, 0.35) !important;
44
  }
45
 
46
- /* Tab bar */
 
 
 
 
 
 
47
  .tabs > .tab-nav,
48
  .tab-nav,
49
  div[role="tablist"],
50
  .svelte-tabs > .tab-nav {
51
- background: rgba(15, 23, 42, 0.58) !important;
52
  border-radius: 10px 10px 0 0 !important;
53
  padding: 4px !important;
54
  }
@@ -79,29 +84,38 @@ div[role="tablist"] button[aria-selected="true"],
79
  background: rgba(255, 255, 255, 0.12) !important;
80
  }
81
 
82
- /* Panels */
 
 
 
 
 
 
 
 
 
83
  .gradio-container .gr-block,
84
  .gradio-container .gr-box,
85
  .gradio-container .gr-panel,
86
- .gradio-container .gr-group,
87
- .tabitem {
88
- background: rgba(255, 255, 255, 0.96) !important;
89
- border-radius: 12px !important;
90
  }
91
 
92
  .tabitem {
 
 
93
  padding: 16px !important;
94
  }
95
 
96
- /* Inputs and buttons */
97
- input,
98
- textarea,
99
- select {
100
- background: #ffffff !important;
101
- color: #111827 !important;
102
  border-radius: 10px !important;
103
  }
104
 
 
105
  button:not([role="tab"]) {
106
  border-radius: 10px !important;
107
  font-weight: 700 !important;
 
1
+ /* Background restored to first-app style logic */
2
  gradio-app,
3
  .gradio-app,
4
  .main,
 
23
  min-height: 100vh !important;
24
  }
25
 
26
+ /* Top spacing like the first app */
27
  .gradio-container {
28
  max-width: 1400px !important;
29
  width: 94vw !important;
30
  margin: 0 auto !important;
31
  padding-top: 220px !important;
32
+ padding-bottom: 150px !important;
33
  background: transparent !important;
34
  }
35
 
36
  /* Title */
37
+ #escp_title h1 {
38
+ color: rgb(242, 198, 55) !important;
39
  font-size: 3rem !important;
40
  font-weight: 800 !important;
41
  text-align: center !important;
42
  margin: 0 0 12px 0 !important;
 
43
  }
44
 
45
+ #escp_title p,
46
+ #escp_title em {
47
+ color: rgba(255, 255, 255, 0.90) !important;
48
+ text-align: center !important;
49
+ }
50
+
51
+ /* Tabs */
52
  .tabs > .tab-nav,
53
  .tab-nav,
54
  div[role="tablist"],
55
  .svelte-tabs > .tab-nav {
56
+ background: rgba(15, 23, 42, 0.60) !important;
57
  border-radius: 10px 10px 0 0 !important;
58
  padding: 4px !important;
59
  }
 
84
  background: rgba(255, 255, 255, 0.12) !important;
85
  }
86
 
87
+ .tabs > .tab-nav button:not(.selected),
88
+ .tab-nav button:not(.selected),
89
+ button[role="tab"][aria-selected="false"],
90
+ button[role="tab"]:not(.selected),
91
+ div[role="tablist"] button:not([aria-selected="true"]) {
92
+ color: #ffffff !important;
93
+ opacity: 1 !important;
94
+ }
95
+
96
+ /* White cards */
97
  .gradio-container .gr-block,
98
  .gradio-container .gr-box,
99
  .gradio-container .gr-panel,
100
+ .gradio-container .gr-group {
101
+ background: #ffffff !important;
102
+ border-radius: 10px !important;
 
103
  }
104
 
105
  .tabitem {
106
+ background: rgba(255, 255, 255, 0.96) !important;
107
+ border-radius: 0 0 10px 10px !important;
108
  padding: 16px !important;
109
  }
110
 
111
+ /* Inputs */
112
+ .gradio-container input,
113
+ .gradio-container textarea,
114
+ .gradio-container select {
 
 
115
  border-radius: 10px !important;
116
  }
117
 
118
+ /* Buttons */
119
  button:not([role="tab"]) {
120
  border-radius: 10px !important;
121
  font-weight: 700 !important;