deeploy-adubowski commited on
Commit
9be1e6c
β€’
1 Parent(s): cc03baf

Upload credit scoring app

Browse files
Files changed (10) hide show
  1. .gitignore +2 -0
  2. .streamlit/config.toml +7 -0
  3. README.md +6 -4
  4. app.py +409 -0
  5. constants.py +109 -0
  6. deeploy_logo_wide.png +0 -0
  7. poetry.lock +0 -0
  8. pyproject.toml +23 -0
  9. requirements.txt +12 -0
  10. style.css +0 -28
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ __pycache__
2
+ .venv
.streamlit/config.toml ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ base="light"
3
+ primaryColor="#00052D"
4
+ backgroundColor="#FFFFFF"
5
+ secondaryBackgroundColor="#F4F5F7"
6
+ textColor="#00052D"
7
+ font="sans serif"
README.md CHANGED
@@ -1,9 +1,11 @@
1
  ---
2
- title: Credit Scoring V2
3
- emoji: πŸš€
4
  colorFrom: yellow
5
- colorTo: purple
6
- sdk: static
 
 
7
  pinned: false
8
  license: mit
9
  ---
 
1
  ---
2
+ title: Credit Scoring
3
+ emoji: πŸ†
4
  colorFrom: yellow
5
+ colorTo: yellow
6
+ sdk: streamlit
7
+ sdk_version: 1.29
8
+ app_file: app.py
9
  pinned: false
10
  license: mit
11
  ---
app.py ADDED
@@ -0,0 +1,409 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # type: ignore -- ignores linting import issues when using multiple virtual environments
2
+ import streamlit.components.v1 as components
3
+ import streamlit as st
4
+ import pandas as pd
5
+ import logging
6
+ from deeploy import Client
7
+ from constants import (
8
+ relationship_dict,
9
+ occupation_dict,
10
+ education_dict,
11
+ workclass_dict,
12
+ countries_dict,
13
+ marital_status_dict,
14
+ )
15
+
16
+ # reset Plotly theme after streamlit import
17
+ import plotly.io as pio
18
+
19
+
20
+ pio.templates.default = "plotly"
21
+
22
+ logging.basicConfig(level=logging.INFO)
23
+
24
+ st.set_page_config(layout="wide")
25
+
26
+ st.title("Credit-Scoring Model Explainability")
27
+
28
+ st.markdown(
29
+ """
30
+ <style>
31
+ section[data-testid="stSidebar"] {
32
+ width: 300px !important; # Set the width to your desired value
33
+ }
34
+ </style>
35
+ """,
36
+ unsafe_allow_html=True,
37
+ )
38
+
39
+
40
+ def send_evaluation(
41
+ client, deployment_id, request_log_id, prediction_log_id, evaluation_input
42
+ ):
43
+ """Send evaluation to Deeploy."""
44
+ try:
45
+ with st.spinner("Submitting response..."):
46
+ # Call the explain endpoint as it also includes the prediction
47
+ client.evaluate(
48
+ deployment_id, request_log_id, prediction_log_id, evaluation_input
49
+ )
50
+ return True
51
+ except Exception as e:
52
+ logging.error(e)
53
+ st.error(
54
+ "Failed to submit feedback."
55
+ + "Check whether you are using the right model URL and Token. "
56
+ + "Contact Deeploy if the problem persists."
57
+ )
58
+ st.write(f"Error message: {e}")
59
+
60
+
61
+ def get_model_url():
62
+ model_url = st.text_area(
63
+ "Model URL (without the /explain endpoint, default is the demo deployment)",
64
+ "https://api.app.deeploy.ml/workspaces/708b5808-27af-461a-8ee5-80add68384c7/deployments/dc8c359d-5f61-4107-8b0f-de97ec120289/",
65
+ height=125,
66
+ )
67
+ elems = model_url.split("/")
68
+ try:
69
+ workspace_id = elems[4]
70
+ deployment_id = elems[6]
71
+ except IndexError:
72
+ workspace_id = ""
73
+ deployment_id = ""
74
+ return model_url, workspace_id, deployment_id
75
+
76
+
77
+ def ChangeButtonColour(widget_label, font_color, background_color="transparent"):
78
+ # func to change button colors
79
+ htmlstr = f"""
80
+ <script>
81
+ var elements = window.parent.document.querySelectorAll('button');
82
+ for (var i = 0; i < elements.length; ++i) {{
83
+ if (elements[i].innerText == '{widget_label}') {{
84
+ elements[i].style.color ='{font_color}';
85
+ elements[i].style.background = '{background_color}'
86
+ }}
87
+ }}
88
+ </script>
89
+ """
90
+ components.html(f"{htmlstr}", height=0, width=0)
91
+
92
+
93
+ with st.sidebar:
94
+ st.image("deeploy_logo_wide.png", width=250)
95
+
96
+ # Ask for model URL and token
97
+ host = st.text_input("Host (Changing is optional)", "app.deeploy.ml")
98
+ model_url, workspace_id, deployment_id = get_model_url()
99
+ deployment_token = st.text_input("Deeploy Model Token", "my-secret-token")
100
+ if deployment_token == "my-secret-token":
101
+ st.warning("Please enter Deeploy API token.")
102
+ # Split model URL into workspace and deployment ID
103
+
104
+ # st.write("Values below are for debug only:")
105
+ # st.write("Workspace ID: ", workspace_id)
106
+ # st.write("Deployment ID: ", deployment_id)
107
+
108
+ client_options = {
109
+ "host": host,
110
+ "deployment_token": deployment_token,
111
+ "workspace_id": workspace_id,
112
+ }
113
+ client = Client(**client_options)
114
+
115
+ # with tabs[0] as loan_application:
116
+ selected = "Loan Application"
117
+ # if selected == "Loan Application":
118
+ # Attributes
119
+ st.subheader("Loan Application")
120
+ with st.expander("Application form", expanded=False):
121
+ # Split view in 2 columns
122
+ col1, col2 = st.columns(2)
123
+ with col1:
124
+ # Create input fields for attributes from constant dicts
125
+ age = st.number_input("Age", min_value=0, max_value=100, value=30)
126
+ marital_status = st.selectbox("Marital Status", marital_status_dict.keys())
127
+ marital_status_id = marital_status_dict[marital_status]
128
+ native_country = st.selectbox(
129
+ "Native Country", countries_dict.keys(), index=len(countries_dict) - 1
130
+ )
131
+ native_country_id = countries_dict[native_country]
132
+ relationship = st.selectbox("Relative", relationship_dict.keys(), index=1)
133
+ relationship_id = relationship_dict[relationship]
134
+ occupation = st.selectbox("Occupation", occupation_dict.keys(), index=1)
135
+ occupation_id = occupation_dict[occupation]
136
+
137
+ with col2:
138
+ education = st.selectbox("Education", education_dict.keys(), index=4)
139
+ education_id = education_dict[education]
140
+ workclass = st.selectbox("Workclass", workclass_dict.keys())
141
+ workclass_id = workclass_dict[workclass]
142
+ hours_per_week = st.number_input(
143
+ "Hours per week", min_value=0, max_value=100, value=40
144
+ )
145
+ capital_gain = st.number_input(
146
+ "Yearly income [€]", min_value=0, max_value=1000000, value=70000
147
+ )
148
+ capital_loss = st.number_input(
149
+ "Yearly expenditures [€]", min_value=0, max_value=1000000, value=60000
150
+ )
151
+ data_df = pd.DataFrame(
152
+ [
153
+ [
154
+ age,
155
+ workclass,
156
+ education,
157
+ marital_status,
158
+ occupation,
159
+ relationship,
160
+ capital_gain,
161
+ capital_loss,
162
+ hours_per_week,
163
+ native_country,
164
+ ]
165
+ ],
166
+ columns=[
167
+ "Age",
168
+ "Workclass",
169
+ "Education",
170
+ "Marital Status",
171
+ "Occupation",
172
+ "Relative",
173
+ "Yearly Income [€]",
174
+ "Yearly expenditures [€]",
175
+ "Hours per week",
176
+ "Native Country",
177
+ ],
178
+ )
179
+ data_df_t = data_df.T
180
+ request_body = {
181
+ "instances": [
182
+ [
183
+ age,
184
+ workclass_id,
185
+ education_id,
186
+ marital_status_id,
187
+ occupation_id,
188
+ relationship_id,
189
+ capital_gain,
190
+ capital_loss,
191
+ hours_per_week,
192
+ native_country_id,
193
+ ]
194
+ ]
195
+ }
196
+ if "predict_button_clicked" not in st.session_state:
197
+ st.session_state.predict_button_clicked = False
198
+ if deployment_token != "my-secret-token":
199
+ predict_button = st.button(
200
+ "Predict", key="predict_button", help="Click to get the AI prediction."
201
+ )
202
+ if predict_button:
203
+ st.session_state.predict_button_clicked = True
204
+ selected = "Loan Decision"
205
+
206
+
207
+ if selected == "Loan Decision":
208
+ # If no prediction, show "loading..."
209
+ try:
210
+ with st.spinner("Loading..."):
211
+ # Call the explain endpoint as it also includes the prediction
212
+ exp = client.explain(
213
+ request_body=request_body, deployment_id=deployment_id
214
+ )
215
+ # Read explanation to dataframe from json
216
+ predictions = exp["predictions"]
217
+ request_log_id = exp["requestLogId"]
218
+ prediction_log_id = exp["predictionLogIds"][0]
219
+
220
+ exp_df = pd.DataFrame(
221
+ [exp["explanations"][0]["shap_values"]], columns=exp["featureLabels"]
222
+ )
223
+
224
+ exp_df.columns = data_df.columns
225
+
226
+ exp_df_t = exp_df.T
227
+
228
+ # Merge data and explanation
229
+ exp_df_t = data_df_t.merge(exp_df_t, left_index=True, right_index=True)
230
+
231
+ weight_feat = "Weight"
232
+ exp_df_t.columns = ["Feature value", weight_feat]
233
+ exp_df_t["Feature"] = exp_df_t.index
234
+ exp_df_t = exp_df_t[["Feature", "Feature value", weight_feat]]
235
+ exp_df_t["Feature value"] = exp_df_t["Feature value"].astype(str)
236
+
237
+ # Filter values below 0.01
238
+ exp_df_t = exp_df_t[
239
+ (exp_df_t[weight_feat] > 0.01) | (exp_df_t[weight_feat] < -0.01)
240
+ ]
241
+ exp_df_t[weight_feat] = exp_df_t[weight_feat].astype(float).round(2)
242
+
243
+ pos_exp_df_t = exp_df_t[exp_df_t[weight_feat] > 0]
244
+ pos_exp_df_t = pos_exp_df_t.sort_values(by=weight_feat, ascending=False)
245
+
246
+ neg_exp_df_t = exp_df_t[exp_df_t[weight_feat] < 0]
247
+ neg_exp_df_t = neg_exp_df_t.sort_values(by=weight_feat, ascending=True)
248
+ neg_exp_df_t[weight_feat] = neg_exp_df_t[weight_feat].abs()
249
+
250
+ # Get 3 features with highest positive relevance score
251
+ pos_feats = pos_exp_df_t[weight_feat].nlargest(3).index.tolist()
252
+ # For feature, get feature value and concatenate into a single string
253
+ pos_feats = [
254
+ f"{feat}: {pos_exp_df_t.loc[feat, 'Feature value']}"
255
+ for feat in pos_feats
256
+ ]
257
+ # Get 3 features with highest negative relevance score
258
+ neg_feats = neg_exp_df_t[weight_feat].nlargest(3).index.tolist()
259
+ # For feature, get feature value and concatenate into a single string
260
+ neg_feats = [
261
+ f"{feat}: {neg_exp_df_t.loc[feat, 'Feature value']}"
262
+ for feat in neg_feats
263
+ ]
264
+
265
+ if predictions[0]:
266
+ # Show prediction
267
+ st.subheader("Loan Decision: :green[POSITIVE]", divider="green")
268
+ # Format subheader to green
269
+ st.markdown(
270
+ "<style>.css-1v3fvcr{color: green;}</style>", unsafe_allow_html=True
271
+ )
272
+
273
+ # If prediction is positive, first show positive features, then negative features
274
+ st.success(
275
+ "**Positive credit suitability**. This is primarily attributed to: \n - "
276
+ + " \n- ".join(pos_feats)
277
+ )
278
+ st.warning(
279
+ "However, the following features weight ***against*** the loan application: \n - "
280
+ + " \n- ".join(neg_feats)
281
+ + " \n See explanation below for more details.",
282
+ icon="⚠️",
283
+ )
284
+ else:
285
+ st.subheader("Loan Decision: :red[NEGATIVE]", divider="red")
286
+ # If prediction is negative, first show negative features, then positive features
287
+ st.error(
288
+ "**Negative credit suitability**. This is primarily attributed to: \n - "
289
+ + " \n - ".join(neg_feats)
290
+ + "."
291
+ )
292
+ st.warning(
293
+ "However, the following factors weigh ***in favor*** of the loan applicant: \n - "
294
+ + " \n - ".join(pos_feats)
295
+ + ". \n See below for more details.",
296
+ icon="⚠️",
297
+ )
298
+ explanation_expander = st.expander("Show explanation")
299
+ with explanation_expander:
300
+ # Show explanation
301
+ col_pos, col_neg = st.columns(2)
302
+
303
+ with col_pos:
304
+ st.subheader("Factors :green[in favor] of loan approval")
305
+ # st.success("**Factors in favor of loan approval**")
306
+ st.dataframe(
307
+ pos_exp_df_t,
308
+ hide_index=True,
309
+ width=600,
310
+ column_config={
311
+ "Weight": st.column_config.ProgressColumn(
312
+ "Weight",
313
+ width="small",
314
+ format=" ",
315
+ min_value=0,
316
+ max_value=1,
317
+ )
318
+ },
319
+ )
320
+
321
+ with col_neg:
322
+ st.subheader("Factors :red[against] loan approval")
323
+ # st.error("**Factors against loan approval**")
324
+ st.dataframe(
325
+ neg_exp_df_t,
326
+ hide_index=True,
327
+ width=600,
328
+ column_config={
329
+ "Weight": st.column_config.ProgressColumn(
330
+ "Weight",
331
+ width="small",
332
+ format=" ",
333
+ min_value=0,
334
+ max_value=1,
335
+ )
336
+ },
337
+ )
338
+
339
+ st.divider()
340
+
341
+ # Add prediction evaluation
342
+ st.subheader("Prediction Evaluation: Do you agree with the AI prediction?")
343
+ st.info(
344
+ "AI model predictions always come with a certain level of uncertainty. Evaluate the correctness of the prediction based on your expertise and experience."
345
+ )
346
+ cols = st.columns(4)
347
+ col_yes, col_no = cols[:2]
348
+ with col_yes:
349
+ yes_button = st.button(
350
+ "Yes, I agree",
351
+ key="yes_button",
352
+ use_container_width=True,
353
+ help="Click if you agree with the prediction",
354
+ )
355
+ ChangeButtonColour("Yes, I agree", "white", "green")
356
+ with col_no:
357
+ no_button = st.button(
358
+ "No, I disagree",
359
+ key="no_button",
360
+ use_container_width=True,
361
+ help="Click if you disagree with the prediction",
362
+ type="primary",
363
+ )
364
+ ChangeButtonColour("No, I disagree", "white", "#DD360C")
365
+ # ChangeButtonColour("No, I disagree", "#DD360C", "#F0F0F0")
366
+ if "eval_selected" not in st.session_state:
367
+ st.session_state["eval_selected"] = False
368
+ if yes_button:
369
+ st.session_state.eval_selected = True
370
+ st.session_state.evaluation_input = {
371
+ "result": 0 # Agree with the prediction
372
+ }
373
+ if no_button:
374
+ st.session_state.eval_selected = True
375
+ desired_output = not predictions[0]
376
+ st.session_state.evaluation_input = {
377
+ "result": 1, # Disagree with the prediction
378
+ "value": {"predictions": [desired_output]},
379
+ }
380
+
381
+ success = False
382
+ if st.session_state.eval_selected:
383
+ comment = st.text_input("Would you like to add a comment?")
384
+ if comment:
385
+ st.session_state.evaluation_input["explanation"] = comment
386
+ logging.debug(
387
+ "Selected feedback:" + str(st.session_state.evaluation_input)
388
+ )
389
+ if st.button("Submit", key="submit_button"):
390
+ st.session_state.eval_selected = False
391
+ success = send_evaluation(
392
+ client,
393
+ deployment_id,
394
+ request_log_id,
395
+ prediction_log_id,
396
+ st.session_state.evaluation_input,
397
+ )
398
+ if success:
399
+ st.session_state.eval_selected = False
400
+ st.success("Feedback submitted successfully.")
401
+
402
+ except Exception as e:
403
+ logging.error(e)
404
+ st.error(
405
+ "Failed to retrieve the prediction or explanation."
406
+ + "Check whether you are using the right model URL and Token. "
407
+ + "Contact Deeploy if the problem persists."
408
+ )
409
+ # st.write(f"Error message: {e}")
constants.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ countries_dict = {
2
+ "United-States": 0,
3
+ "Cambodia": 1,
4
+ "England": 2,
5
+ "Puerto-Rico": 3,
6
+ "Canada": 4,
7
+ "Germany": 5,
8
+ "Outlying-US (Guam - USVI - etc.)": 6,
9
+ "India": 7,
10
+ "Japan": 8,
11
+ "Greece": 9,
12
+ "South": 10,
13
+ "China": 11,
14
+ "Cuba": 12,
15
+ "Iran": 13,
16
+ "Honduras": 14,
17
+ "Philippines": 15,
18
+ "Italy": 16,
19
+ "Poland": 17,
20
+ "Jamaica": 18,
21
+ "Vietnam": 19,
22
+ "Mexico": 20,
23
+ "Portugal": 21,
24
+ "Ireland": 22,
25
+ "France": 23,
26
+ "Dominican Republic": 24,
27
+ "Laos": 25,
28
+ "Ecuador": 26,
29
+ "Taiwan": 27,
30
+ "Haiti": 28,
31
+ "Columbia": 29,
32
+ "Hungary": 30,
33
+ "Guatemala": 31,
34
+ "Nicaragua": 32,
35
+ "Scotland": 33,
36
+ "Thailand": 34,
37
+ "Yugoslavia": 35,
38
+ "El Salvador": 36,
39
+ "Trinadad & Tobago": 37,
40
+ "Peru": 38,
41
+ "Hong": 39,
42
+ "Holland - Netherlands": 40,
43
+ }
44
+
45
+ relationship_dict = {
46
+ "Unmarried": 5,
47
+ "Not in Family": 3,
48
+ "Wife": 0,
49
+ "Own child": 1,
50
+ "Husband": 2,
51
+ "Other Relative": 4,
52
+ }
53
+
54
+ occupation_dict = {
55
+ "Prof Specialty": 5,
56
+ "Tech Support": 0,
57
+ "Craft Repair": 1,
58
+ "Other Service": 2,
59
+ "Sales": 3,
60
+ "Executive Managerial": 4,
61
+ "Handlers Cleaners": 6,
62
+ "Machine Op Inspect": 7,
63
+ "Adm Clerical": 8,
64
+ "Farming Fishing": 9,
65
+ "Transport Moving": 10,
66
+ "Priv House Serv": 11,
67
+ "Protective Serv": 12,
68
+ "Armed Forces": 13,
69
+ }
70
+
71
+ education_dict = {
72
+ "Associate Academic": 5,
73
+ "Associate Vocational": 6,
74
+ "Prof School": 4,
75
+ "Doctorate": 13,
76
+ "Masters": 10,
77
+ "Bachelors": 0,
78
+ "Some College": 1,
79
+ "High School Graduate": 3,
80
+ "12th Grade": 9,
81
+ "11th Grade": 2,
82
+ "10th Grade": 12,
83
+ "9th Grade": 7,
84
+ "7th-8th Grade": 8,
85
+ "5th-6th Grade": 14,
86
+ "1st-4th Grade": 11,
87
+ "Preschool": 15,
88
+ }
89
+
90
+ workclass_dict = {
91
+ "Private": 0,
92
+ "Self Employed Not Incorporated": 1,
93
+ "Self Employed Incorporated": 2,
94
+ "Federal Government": 3,
95
+ "Local Government": 4,
96
+ "State Government": 5,
97
+ "Without Pay": 6,
98
+ "Never Worked": 7,
99
+ }
100
+
101
+ marital_status_dict = {
102
+ "Never-married": 2,
103
+ "Married-civ-spouse": 0,
104
+ "Divorced": 1,
105
+ "Separated": 3,
106
+ "Widowed": 4,
107
+ "Married-spouse-absent": 5,
108
+ "Married-AF-spouse": 6,
109
+ }
deeploy_logo_wide.png ADDED
poetry.lock ADDED
The diff for this file is too large to render. See raw diff
 
pyproject.toml ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "credit-scoring"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["Adam Dubowski <adubowski@deeploy.ml>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = ">=3.8, !=3.9.7, <3.11" # Required to avoid conflicts with numpy, scipy and streamlit
10
+ scikit-learn = "1.3.0"
11
+ shap = "0.42.0"
12
+ dill = "0.3.6"
13
+ matplotlib = "3.7.0"
14
+ boto3 = "1.28.0"
15
+ joblib = "1.3.2"
16
+ scipy = "1.10.1"
17
+ plotly = "5.18.0"
18
+ watchdog = "3.0.0"
19
+ streamlit = "1.29.0"
20
+
21
+ [build-system]
22
+ requires = ["poetry-core"]
23
+ build-backend = "poetry.core.masonry.api"
requirements.txt ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ scikit-learn==1.3.0
2
+ shap==0.42.0
3
+ dill==0.3.6
4
+ matplotlib==3.7.0
5
+ boto3==1.28.0
6
+ joblib==1.3.2
7
+ streamlit==1.29
8
+ scipy==1.10.1
9
+ shap==0.42.0
10
+ plotly==5.18.0
11
+ watchdog==3.0.0
12
+ deeploy==1.2.1
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }