Kiuyha commited on
Commit
eab27c6
·
verified ·
1 Parent(s): 113745b

Upload app.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. app.py +229 -0
app.py ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import numpy as np
4
+ import joblib
5
+ import plotly.express as px
6
+ import re
7
+ import string
8
+ import nltk
9
+ import os
10
+ from datetime import datetime
11
+ from nltk.corpus import stopwords
12
+
13
+ st.set_page_config(page_title="Tokopedia Project Dashboard", layout="wide")
14
+
15
+ ARTIFACTS_DIR = 'artifacts'
16
+
17
+ @st.cache_resource
18
+ def load_resources():
19
+ try:
20
+ nltk.download('stopwords')
21
+ indo_stopwords = set(stopwords.words('indonesian'))
22
+ custom_stopwords = {
23
+ 'yg', 'dg', 'dgn', 'ny', 'nya', 'kalo', 'klo', 'gan', 'sis', 'kak', 'bro',
24
+ 'bosh', 'boss', 'nomor', 'wa', 'hub', 'cod', 'ready', 'stock', 'stok',
25
+ 'promo', 'murah', 'diskon', 'termurah', 'terlaris', 'bestseller'
26
+ }
27
+ final_stopwords = indo_stopwords.union(custom_stopwords)
28
+
29
+ # Load core artifacts
30
+ vectorizer = joblib.load(os.path.join(ARTIFACTS_DIR, 'tfidf_vectorizer.pkl'))
31
+
32
+ # Load Price Artifacts
33
+ price_scaler = joblib.load(os.path.join(ARTIFACTS_DIR, 'scaler.pkl'))
34
+ price_cols = joblib.load(os.path.join(ARTIFACTS_DIR, 'feature_columns.pkl'))
35
+
36
+ # Load Models
37
+ price_models = {
38
+ "Random Forest": joblib.load(os.path.join(ARTIFACTS_DIR, 'Random_Forest_price.pkl')),
39
+ "XGBoost": joblib.load(os.path.join(ARTIFACTS_DIR, 'XGBoost_price.pkl')),
40
+ "MLP": joblib.load(os.path.join(ARTIFACTS_DIR, 'MLP_price.pkl'))
41
+ }
42
+
43
+ return vectorizer, price_scaler, price_cols, price_models, final_stopwords
44
+ except Exception as e:
45
+ st.error(f"Error loading resources: {e}")
46
+ return None, None, None, None, None
47
+
48
+ (vectorizer, price_scaler, price_cols,
49
+ price_models, final_stopwords) = load_resources()
50
+
51
+ @st.cache_data
52
+ def get_categories():
53
+ columns = ['Other']
54
+ if price_cols:
55
+ cat_cols = [c for c in price_cols if c.startswith('cat_')]
56
+ columns = [c.replace('cat_', '') for c in cat_cols]
57
+
58
+ return sorted(columns)
59
+
60
+ def clean_text(text):
61
+ if not isinstance(text, str): return ""
62
+ text = text.lower()
63
+ text = re.sub(r'[^a-z0-9\s]', ' ', text)
64
+ text = re.sub(r'\s+', ' ', text).strip()
65
+ words = text.split()
66
+ return " ".join([w for w in words if w not in final_stopwords])
67
+
68
+ def get_date_features(date_obj, prefix):
69
+ return {
70
+ f"{prefix}_dayofweek": date_obj.weekday(),
71
+ f"{prefix}_month": date_obj.month,
72
+ f"{prefix}_is_weekend": 1 if date_obj.weekday() >= 5 else 0
73
+ }
74
+
75
+ def prepare_raw_dataframe(user_input, target_columns, vectorizer):
76
+ input_df = pd.DataFrame(0, index=[0], columns=target_columns)
77
+
78
+ # Text
79
+ combined_text = clean_text(user_input.get('product_name', '')) + " " + clean_text(user_input.get('description', ''))
80
+ tfidf_matrix = vectorizer.transform([combined_text])
81
+ tfidf_df = pd.DataFrame(tfidf_matrix.toarray(), columns=[f"word_{w}" for w in vectorizer.get_feature_names_out()])
82
+
83
+ common_text_cols = input_df.columns.intersection(tfidf_df.columns)
84
+ input_df[common_text_cols] = tfidf_df[common_text_cols]
85
+
86
+ # Categories
87
+ cat_col = f"cat_{user_input.get('category', 'Other')}"
88
+ if cat_col in input_df.columns:
89
+ input_df[cat_col] = 1
90
+ elif 'cat_Other' in input_df.columns:
91
+ input_df['cat_Other'] = 1
92
+
93
+ # Numerical
94
+ for key, value in user_input.items():
95
+ if key in input_df.columns:
96
+ input_df[key] = value
97
+
98
+ return input_df
99
+
100
+ def load_and_plot_importance(model_name, target_name):
101
+ filename = f"importance_data_{target_name}_{model_name.replace(' ', '_')}.csv"
102
+ path = os.path.join(ARTIFACTS_DIR, filename)
103
+ if os.path.exists(path):
104
+ df = pd.read_csv(path)
105
+ fig = px.bar(
106
+ df.head(15), x='Importance', y='Feature', orientation='h', error_x='Std',
107
+ title=f"Feature Importance ({model_name}) - {target_name.title()}", color='Importance'
108
+ )
109
+ fig.update_layout(yaxis=dict(autorange="reversed"))
110
+ st.plotly_chart(fig)
111
+
112
+ st.title("🛍️ Advanced E-Commerce Predictor")
113
+
114
+ with st.sidebar:
115
+ st.header("1. Product Info")
116
+ p_name = st.text_input("Product Name", "Samsung Galaxy S24 Ultra")
117
+ p_desc = st.text_area("Description", "Original SEIN, Garansi Resmi 1 Tahun")
118
+
119
+ cat_options = get_categories()
120
+ category = st.selectbox("Category", cat_options)
121
+
122
+ condition = st.radio("Condition", ["New", "Used"])
123
+ is_regular = st.checkbox("Regular Merchant?", value=True)
124
+ is_discount = st.checkbox("Is Discounted?", value=False)
125
+
126
+ st.header("2. Physical & Stock")
127
+ col1, col2 = st.columns(2)
128
+ with col1:
129
+ weight = st.number_input("Weight (g)", value=500)
130
+ min_order = st.number_input("Min Order", value=1)
131
+ with col2:
132
+ max_order = st.number_input("Max Order", value=100)
133
+ stock = st.number_input("Stock", value=50)
134
+ sold_input = st.number_input("Sold (Current)", value=0)
135
+
136
+ st.header("3. Media")
137
+ vid_count = st.number_input("Video Count", 0)
138
+ img_count = st.number_input("Image Count", 1)
139
+
140
+ with st.expander("Detailed Ratings", expanded=True):
141
+ c1, c2, c3, c4, c5 = st.columns(5)
142
+ r5 = c1.number_input("5 Star Count", value=10)
143
+ r4 = c2.number_input("4 Star Count", value=2)
144
+ r3 = c3.number_input("3 Star Count", value=0)
145
+ r2 = c4.number_input("2 Star Count", value=0)
146
+ r1 = c5.number_input("1 Star Count", value=0)
147
+
148
+ total_rating = r5 + r4 + r3 + r2 + r1
149
+
150
+ c_qual, c_srv, c_ship = st.columns(3)
151
+ rev_qual = c_qual.number_input("Reviews: Quality", 0)
152
+ rev_srv = c_srv.number_input("Reviews: Service", 0)
153
+ rev_ship = c_ship.number_input("Reviews: Shipping", 0)
154
+
155
+ with st.expander("Shipping & Shop Metadata", expanded=False):
156
+ c1, c2 = st.columns(2)
157
+ with c1:
158
+ ship_sameday = st.checkbox("Same Day", True)
159
+ ship_reg = st.checkbox("Regular", True)
160
+ ship_cargo = st.checkbox("Cargo", False)
161
+ ship_eco = st.checkbox("Economy", True)
162
+ with c2:
163
+ shop_rating = st.slider("Shop Rating Score", 0.0, 5.0, 4.8)
164
+ shop_age = st.number_input("Shop Age (Days)", 365)
165
+ shop_pop = st.number_input("Shop City Popularity", 0.0)
166
+
167
+ date_listing = st.date_input("Listing Created", datetime.now())
168
+ date_shop_open = st.date_input("Shop Open Since", datetime(2020, 1, 1))
169
+
170
+ feat_created = get_date_features(date_listing, "created_at")
171
+ feat_shop = get_date_features(date_shop_open, "shop_open_since")
172
+ listing_age_calc = (datetime.now().date() - date_listing).days
173
+ total_shipping = sum([ship_sameday, ship_reg, ship_cargo, ship_eco])
174
+
175
+ base_input = {
176
+ 'product_name': p_name, 'description': p_desc, 'category': category,
177
+ 'condition_encoded': 1 if condition == 'New' else 0,
178
+ 'is_regular_merchant': 1 if is_regular else 0,
179
+ 'is_discount': 1 if is_discount else 0,
180
+ 'weight_grams': weight,
181
+ 'min_order': min_order, 'max_order': max_order,
182
+ 'stock': stock,
183
+ 'video_count': vid_count, 'image_count': img_count,
184
+ 'can_shipping_sameday': 1 if ship_sameday else 0,
185
+ 'can_shipping_regular': 1 if ship_reg else 0,
186
+ 'can_shipping_cargo': 1 if ship_cargo else 0,
187
+ 'can_shipping_economy': 1 if ship_eco else 0,
188
+ 'total_shipping_types': total_shipping,
189
+ 'shop_rating_score': shop_rating,
190
+ 'shop_age_days': shop_age,
191
+ 'shop_city_popularity': shop_pop,
192
+ 'listing_age_days': listing_age_calc,
193
+ 'product_rating_5_star_count': r5,
194
+ 'product_rating_4_star_count': r4,
195
+ 'product_rating_3_star_count': r3,
196
+ 'product_rating_2_star_count': r2,
197
+ 'product_rating_1_star_count': r1,
198
+ 'total_product_rating': total_rating,
199
+ 'total_review_about_kualitas': rev_qual,
200
+ 'total_review_about_pelayanan': rev_srv,
201
+ 'total_review_about_pengiriman': rev_ship,
202
+ **feat_created, **feat_shop
203
+ }
204
+
205
+ if sold_input:
206
+ base_input['sold'] = sold_input
207
+
208
+ # Log Transforms (Stock & Sold)
209
+ base_input['Log_stock'] = np.log1p(stock)
210
+ base_input['Log_sold'] = np.log1p(sold_input)
211
+
212
+ st.divider()
213
+
214
+ st.subheader("💰 Price Prediction")
215
+ if price_models:
216
+ model_name = st.selectbox("Select Price Model", list(price_models.keys()), key="p_model")
217
+
218
+ if st.button("Predict Price", type="primary"):
219
+ raw_df = prepare_raw_dataframe(base_input, price_cols, vectorizer)
220
+ X_scaled = price_scaler.transform(raw_df)
221
+
222
+ log_pred = price_models[model_name].predict(X_scaled)[0]
223
+ price_pred = np.expm1(log_pred)
224
+
225
+ st.success(f"Estimated Price: **Rp {price_pred:,.0f}**")
226
+ st.markdown("### Feature Importance")
227
+ load_and_plot_importance(model_name, "price")
228
+ else:
229
+ st.error("Price models not loaded.")