Vincentran commited on
Commit
c82f84d
·
1 Parent(s): aee0892

Upload E-Commerce Product Intelligence Dashboard (frontend + backend)

Browse files
Files changed (1) hide show
  1. backend/app.py +12 -35
backend/app.py CHANGED
@@ -13,16 +13,13 @@ logger = logging.getLogger(__name__)
13
 
14
  app = FastAPI(title="E-Commerce Product Intelligence Platform")
15
 
16
- # HF Dataset config
17
  HF_DATASET_ID = "Vincentran/ecommerce-dataset"
18
  HF_CSV_PATH = "data/ecommerce_products.csv"
19
 
20
- # Cache DataFrame
21
  _data_cache = None
22
 
23
 
24
  def load_data():
25
- """Load CSV từ HF Dataset with cache."""
26
  try:
27
  if _data_cache is not None:
28
  logger.info("Using cached DataFrame")
@@ -44,7 +41,6 @@ def load_data():
44
  df = pd.read_csv(local_csv_path)
45
  logger.info(f"Loaded {len(df)} rows, columns: {list(df.columns)}")
46
 
47
- # Cache DataFrame
48
  _data_cache = df
49
  return df
50
 
@@ -54,7 +50,6 @@ def load_data():
54
 
55
 
56
  def refresh_cache():
57
- """Refresh data cache."""
58
  _data_cache = None
59
  return load_data()
60
 
@@ -65,10 +60,7 @@ def root():
65
 
66
 
67
  @app.get("/data")
68
- def get_data(
69
- page: int = Query(1, ge=1, description="Page number"),
70
- limit: int = Query(100, ge=1, le=500, description="Items per page")
71
- ):
72
  df = load_data()
73
  total = len(df)
74
  start = (page - 1) * limit
@@ -123,7 +115,6 @@ def stats_rating():
123
 
124
  @app.get("/stats/price-range")
125
  def stats_price_range():
126
- """Price distribution by range."""
127
  df = load_data()
128
  if "price" not in df.columns:
129
  raise HTTPException(status_code=400, detail="Missing 'price' column")
@@ -153,15 +144,10 @@ def insights():
153
 
154
 
155
  @app.get("/search")
156
- def search(
157
- query: str = Query(..., description="Search query"),
158
- page: int = Query(1, ge=1, description="Page number"),
159
- limit: int = Query(100, ge=1, le=500, description="Items per page")
160
- ):
161
  df = load_data()
162
  q = query.lower()
163
 
164
- # Search only in important columns
165
  search_cols = ["product_name", "category", "brand", "description"]
166
  search_cols = [col for col in search_cols if col in df.columns]
167
 
@@ -192,16 +178,15 @@ def search(
192
 
193
  @app.get("/filter")
194
  def filter_products(
195
- category: Optional[str] = Query(None, description="Filter by category"),
196
- min_price: Optional[float] = Query(None, description="Min price"),
197
- max_price: Optional[float] = Query(None, description="Max price"),
198
- min_rating: Optional[float] = Query(None, description="Min rating"),
199
- page: int = Query(1, ge=1, description="Page number"),
200
- limit: int = Query(100, ge=1, le=500, description="Items per page")
201
  ):
202
  df = load_data()
203
 
204
- # Apply filters
205
  if category and "category" in df.columns:
206
  df = df[df["category"] == category]
207
  if min_price and "price" in df.columns:
@@ -221,12 +206,7 @@ def filter_products(
221
  data = df.iloc[start:end].to_dict("records")
222
  return {
223
  "data": data,
224
- "filters": {
225
- "category": category,
226
- "min_price": min_price,
227
- "max_price": max_price,
228
- "min_rating": min_rating
229
- },
230
  "page": page,
231
  "limit": limit,
232
  "total": total,
@@ -235,7 +215,7 @@ def filter_products(
235
 
236
 
237
  @app.get("/recommend")
238
- def recommend(category: str, limit: int = Query(10, ge=1, le=50, description="Number of recommendations")):
239
  df = load_data()
240
  if "category" not in df.columns:
241
  raise HTTPException(status_code=400, detail="Missing 'category' column")
@@ -252,7 +232,6 @@ def recommend(category: str, limit: int = Query(10, ge=1, le=50, description="Nu
252
 
253
  @app.post("/refresh-data")
254
  def refresh_data():
255
- """Refresh data cache from HF Dataset."""
256
  try:
257
  df = refresh_cache()
258
  return {"status": "Data refreshed successfully", "rows": len(df)}
@@ -262,21 +241,19 @@ def refresh_data():
262
 
263
  @app.post("/run-scraper")
264
  def trigger_scraper():
265
- """Trigger download Kaggle → save CSV → upload to HF."""
266
  import subprocess
267
  result = subprocess.run(["python", "backend/scraper.py"], capture_output=True, text=True)
268
  if result.returncode == 0:
269
- # Refresh cache after scraper
270
  refresh_cache()
271
  return {"status": "Scraper completed successfully", "output": result.stdout}
272
  else:
273
  return {"status": "Scraper failed", "error": result.stderr}
274
 
275
 
276
- # Mount frontend
277
  frontend_dir = Path("frontend")
278
  if frontend_dir.exists():
279
- app.mount("/", StaticFiles(directory=str(frontend_dir), html=True), name="frontend")
280
  else:
281
  @app.get("/")
282
  def frontend_placeholder():
 
13
 
14
  app = FastAPI(title="E-Commerce Product Intelligence Platform")
15
 
 
16
  HF_DATASET_ID = "Vincentran/ecommerce-dataset"
17
  HF_CSV_PATH = "data/ecommerce_products.csv"
18
 
 
19
  _data_cache = None
20
 
21
 
22
  def load_data():
 
23
  try:
24
  if _data_cache is not None:
25
  logger.info("Using cached DataFrame")
 
41
  df = pd.read_csv(local_csv_path)
42
  logger.info(f"Loaded {len(df)} rows, columns: {list(df.columns)}")
43
 
 
44
  _data_cache = df
45
  return df
46
 
 
50
 
51
 
52
  def refresh_cache():
 
53
  _data_cache = None
54
  return load_data()
55
 
 
60
 
61
 
62
  @app.get("/data")
63
+ def get_data(page: int = Query(1, ge=1), limit: int = Query(100, ge=1, le=500)):
 
 
 
64
  df = load_data()
65
  total = len(df)
66
  start = (page - 1) * limit
 
115
 
116
  @app.get("/stats/price-range")
117
  def stats_price_range():
 
118
  df = load_data()
119
  if "price" not in df.columns:
120
  raise HTTPException(status_code=400, detail="Missing 'price' column")
 
144
 
145
 
146
  @app.get("/search")
147
+ def search(query: str = Query(...), page: int = Query(1, ge=1), limit: int = Query(100, ge=1, le=500)):
 
 
 
 
148
  df = load_data()
149
  q = query.lower()
150
 
 
151
  search_cols = ["product_name", "category", "brand", "description"]
152
  search_cols = [col for col in search_cols if col in df.columns]
153
 
 
178
 
179
  @app.get("/filter")
180
  def filter_products(
181
+ category: Optional[str] = Query(None),
182
+ min_price: Optional[float] = Query(None),
183
+ max_price: Optional[float] = Query(None),
184
+ min_rating: Optional[float] = Query(None),
185
+ page: int = Query(1, ge=1),
186
+ limit: int = Query(100, ge=1, le=500)
187
  ):
188
  df = load_data()
189
 
 
190
  if category and "category" in df.columns:
191
  df = df[df["category"] == category]
192
  if min_price and "price" in df.columns:
 
206
  data = df.iloc[start:end].to_dict("records")
207
  return {
208
  "data": data,
209
+ "filters": {"category": category, "min_price": min_price, "max_price": max_price, "min_rating": min_rating},
 
 
 
 
 
210
  "page": page,
211
  "limit": limit,
212
  "total": total,
 
215
 
216
 
217
  @app.get("/recommend")
218
+ def recommend(category: str, limit: int = Query(10, ge=1, le=50)):
219
  df = load_data()
220
  if "category" not in df.columns:
221
  raise HTTPException(status_code=400, detail="Missing 'category' column")
 
232
 
233
  @app.post("/refresh-data")
234
  def refresh_data():
 
235
  try:
236
  df = refresh_cache()
237
  return {"status": "Data refreshed successfully", "rows": len(df)}
 
241
 
242
  @app.post("/run-scraper")
243
  def trigger_scraper():
 
244
  import subprocess
245
  result = subprocess.run(["python", "backend/scraper.py"], capture_output=True, text=True)
246
  if result.returncode == 0:
 
247
  refresh_cache()
248
  return {"status": "Scraper completed successfully", "output": result.stdout}
249
  else:
250
  return {"status": "Scraper failed", "error": result.stderr}
251
 
252
 
253
+ # Mount frontend at /frontend (not /)
254
  frontend_dir = Path("frontend")
255
  if frontend_dir.exists():
256
+ app.mount("/frontend", StaticFiles(directory=str(frontend_dir), html=True), name="frontend")
257
  else:
258
  @app.get("/")
259
  def frontend_placeholder():