paudelapil commited on
Commit
dc2bc5a
·
verified ·
1 Parent(s): 18fcfa2

Update README.md

Browse files
Files changed (1) hide show
  1. README.md +7 -374
README.md CHANGED
@@ -1,375 +1,8 @@
1
- # ThreadHouse — Backend
2
-
3
- A FastAPI application that powers both halves of the project: the **shop** (auth, products, orders, analytics) and the **customer-intelligence engine** (CSV upload → RFM / CLV / anomaly / insights ML pipeline). Both halves talk to the same Postgres database; the shop half uses raw `asyncpg`, the intel half uses SQLAlchemy 2.
4
-
5
  ---
6
-
7
- ## Quick start
8
-
9
- ### 1. Prerequisites
10
-
11
- - **Python 3.11+** (3.10 also works)
12
- - **Postgres 14+** running locally
13
- - **Git** (for cloning)
14
-
15
- ### 2. Create the database
16
-
17
- Open `psql` or pgAdmin and create an empty database called `mindless`:
18
-
19
- ```sql
20
- CREATE DATABASE mindless;
21
- ```
22
-
23
- You don't need to create any tables — the app auto-creates them on first boot.
24
-
25
- ### 3. Set up the Python environment
26
-
27
- From inside the `backend/` folder:
28
-
29
- ```bash
30
- # Create a virtual environment (recommended)
31
- python -m venv .venv
32
-
33
- # Activate it
34
- # Windows (PowerShell):
35
- .venv\Scripts\Activate.ps1
36
- # Windows (cmd):
37
- .venv\Scripts\activate.bat
38
- # macOS / Linux:
39
- source .venv/bin/activate
40
-
41
- # Install dependencies
42
- pip install -r requirements.txt
43
- ```
44
-
45
- `torch` is the largest dependency (~700 MB on Windows). If your network is slow, run `pip install torch --index-url https://download.pytorch.org/whl/cpu` first to grab the smaller CPU-only build, then `pip install -r requirements.txt`.
46
-
47
- ### 4. Configure `.env`
48
-
49
- Copy the template (or just create `.env` directly) inside `backend/`:
50
-
51
- ```
52
- DB_USER=postgres
53
- DB_PASSWORD=YOUR_POSTGRES_PASSWORD
54
- DB_NAME=mindless
55
- DB_HOST=localhost
56
- DB_PORT=5432
57
- DATABASE_URL=postgresql+psycopg2://postgres:YOUR_POSTGRES_PASSWORD@localhost:5432/mindless
58
-
59
- # JWT secrets must be >= 32 chars. Generate with:
60
- # python -c "import secrets; print(secrets.token_urlsafe(48))"
61
- JWT_SECRET=PUT_A_LONG_RANDOM_STRING_HERE
62
- SECRET_KEY=PUT_A_LONG_RANDOM_STRING_HERE
63
-
64
- UPLOAD_DIR=uploads
65
- MODEL_DIR=app/ML/artifacts
66
- PRODUCTS_JSON=products.json
67
-
68
- # Optional — only needed for /api/results/{id}/query and LLM-narrated insights.
69
- GROQ_API_KEY=
70
- ```
71
-
72
- The app refuses to boot if `JWT_SECRET` is shorter than 32 characters.
73
-
74
- ### 5. Run the server
75
-
76
- ```bash
77
- uvicorn app.main:app --reload --port 8000
78
- ```
79
-
80
- You should see:
81
-
82
- ```
83
- SQLAlchemy tables ready: jobs, customer_profiles, insights.
84
- asyncpg pool ready: users, orders, analytics_events, products tables verified.
85
- INFO: Application startup complete.
86
- ```
87
-
88
- Test it:
89
-
90
- - API root: <http://localhost:8000>
91
- - Health check: <http://localhost:8000/health>
92
- - Interactive API docs: <http://localhost:8000/docs>
93
-
94
- ### 6. Create an admin user
95
-
96
- ```bash
97
- python scripts/create_admin.py --email you@example.com --password StrongPass1
98
- ```
99
-
100
- Use a public-TLD email — pydantic's email-validator rejects `.local` / `.test`.
101
-
102
- You can now log into the admin panel with these credentials.
103
-
104
- ---
105
-
106
- ## How it works (architecture)
107
-
108
- ```
109
- ┌─────────────┐ POST /api/analytics/event ┌────────────────────┐
110
- │ Shop SPA │ ────────────────────────────────► │ │
111
- │ (React) │ POST /api/orders/ │ FastAPI │
112
- └─────────────┘ ────────────────────────────────► │ (this folder) │
113
- │ │
114
- ┌─────────────┐ POST /api/order/list │ │
115
- │ Admin SPA │ ────────────────────────────────► │ asyncpg pool ────┐│
116
- │ (React) │ WS /api/analytics/ws ◄────────►│ SQLAlchemy ────┐ ││
117
- └─────────────┘ └──────────────┬──┴─┘
118
- │ │
119
- ┌──────▼──▼──┐
120
- │ Postgres │
121
- │ (mindless) │
122
- └────────────┘
123
- ```
124
-
125
- ### Two database drivers, one database
126
-
127
- - **`asyncpg` pool** — for the shop half (auth, products, orders, analytics, audit log). Async, fast, hand-written SQL. Tables: `users`, `orders`, `analytics_events`, `products`, `audit_log`.
128
- - **SQLAlchemy 2** — for the intel half (the CSV → ML pipeline). Synchronous, ORM-driven. Tables: `jobs`, `customer_profiles`, `insights`.
129
-
130
- Both are bootstrapped in `app/main.py`'s `lifespan` handler on startup. The shop tables are created/migrated via idempotent `CREATE TABLE … IF NOT EXISTS` and `ALTER TABLE … ADD COLUMN IF NOT EXISTS`, so re-running on a populated DB is safe.
131
-
132
- ### The ML pipeline
133
-
134
- ```
135
- CSV upload ──► schema_detection ──► rfm_extraction ──► segmentation
136
-
137
- ┌─────────────────────────┤
138
- ▼ ▼
139
- HVR prediction CLV (BG/NBD + Gamma-Gamma)
140
- │ │
141
- └────────────┬────────────┘
142
-
143
- anomaly detection
144
- (PyTorch autoencoder)
145
-
146
-
147
- insights (LLM)
148
-
149
-
150
- write CustomerProfile + Insight
151
- mark Job complete
152
- ```
153
-
154
- Each stage lives in `app/pipeline/`. The orchestrator is `app/services/ml_services.py::run_full_pipeline`, kicked off as a FastAPI `BackgroundTask` by either `POST /api/upload` (CSV) or `POST /api/intel/run-on-current-users` (live orders).
155
-
156
- ### Real-time analytics (WebSocket)
157
-
158
- `app/live_tracking.py` is an in-process pub/sub built on `asyncio.Queue`. Every `POST /api/analytics/event` insert is followed by `publish()` which fans the event to every subscribed queue. The `/api/analytics/ws` WebSocket endpoint holds one queue per connected admin and forwards events as JSON.
159
-
160
- ⚠️ **Single-process only.** If you run multiple uvicorn workers, an event posted on worker A won't reach a WebSocket on worker B. For production, swap the queue for Redis pub/sub or NATS.
161
-
162
- ---
163
-
164
- ## File map
165
-
166
- ```
167
- backend/
168
- ├── .env # secrets (NOT committed)
169
- ├── requirements.txt
170
- ├── app/
171
- │ ├── main.py # FastAPI app, router wiring, lifespan
172
- │ ├── auth_deps.py # JWT dependencies (user / admin)
173
- │ ├── audit.py # audit_log helper
174
- │ ├── live_tracking.py # WebSocket pub/sub
175
- │ ├── core/config.py # pydantic-settings Settings
176
- │ ├── db/
177
- │ │ ├── session.py # SQLAlchemy engine + SessionLocal
178
- │ │ ├── models.py # ORM: Job, CustomerProfile, Insight
179
- │ │ └── asyncpg_pool.py# pool + DDL for shop tables
180
- │ ├── routers/
181
- │ │ ├── auth.py # /api/auth/{signup,login,admin/login,me,profile,logout}
182
- │ │ ├── orders.py # /api/orders/ + /me
183
- │ │ ├── admin_orders.py# /api/order/{list,status} (admin UI alias)
184
- │ │ ├── analytics.py # /api/analytics/{event,summary,segments,live,customer/{id},ws}
185
- │ │ ├── products.py # /api/product/{list,add,remove,{id},seed}
186
- │ │ ├── upload.py # POST /api/upload (CSV → pipeline)
187
- │ │ ├── intel_live.py # POST /api/intel/run-on-current-users
188
- │ │ ├── results.py # GET /api/results/{id}/{status,overview,customers,insights,top-customers}
189
- │ │ ├── query.py # POST /api/results/{id}/query (NL Q&A)
190
- │ │ └── admin.py # POST /api/admin/train (retrain HVR model)
191
- │ ├── schemas/
192
- │ │ ├── shop.py # AnalyticsEvent, SignUpRequest, LoginRequest, AuthResponse, …
193
- │ │ └── customers.py # QueryRequest, QueryResponse
194
- │ ├── services/ml_services.py # run_full_pipeline orchestrator
195
- │ ├── pipeline/
196
- │ │ ├── schema_detection.py
197
- │ │ ├── rfm_extraction.py
198
- │ │ ├── segmentation.py
199
- │ │ ├── clv.py
200
- │ │ ├── prediction.py
201
- │ │ ├── anomaly.py
202
- │ │ └── insights.py
203
- │ └── ML/artifacts/ # hvr_model.pkl + hvr_scaler.pkl + hvr_features.pkl
204
- ├── scripts/
205
- │ └── create_admin.py # CLI to create/promote an admin user
206
- ├── static/images/ # uploaded product images (served at /static/images/...)
207
- └── uploads/ # uploaded CSVs (gitignored)
208
- ```
209
-
210
- ---
211
-
212
- ## API reference
213
-
214
- Open <http://localhost:8000/docs> for interactive Swagger UI. Quick summary:
215
-
216
- ### Public
217
-
218
- | Method | Path | Purpose |
219
- |---|---|---|
220
- | GET | `/health` | Liveness probe |
221
- | POST | `/api/auth/signup` | Register; returns JWT |
222
- | POST | `/api/auth/login` | Login; returns JWT |
223
- | POST | `/api/auth/admin/login` | Admin login; returns JWT (role-checked) |
224
- | POST | `/api/user/admin` | Legacy alias for the admin UI |
225
- | POST | `/api/analytics/event` | Ingest one analytics event (no auth — best-effort) |
226
- | POST | `/api/orders/` | Place an order (optional auth — guests allowed) |
227
- | GET | `/api/product/list` | Public catalogue |
228
- | GET | `/api/product/{id}` | One product |
229
- | POST | `/api/product/seed` | Dev importer from `products.json` |
230
-
231
- ### Authenticated user
232
-
233
- | Method | Path | Purpose |
234
- |---|---|---|
235
- | GET | `/api/auth/me` | Current user from JWT |
236
- | PATCH | `/api/auth/profile` | Update name / password |
237
- | POST | `/api/auth/logout` | Symbolic (JWT is stateless) |
238
- | GET | `/api/orders/me` | List my orders newest-first |
239
-
240
- ### Admin only
241
-
242
- | Method | Path | Purpose |
243
- |---|---|---|
244
- | POST | `/api/product/add` | Multipart with up to 4 images |
245
- | PATCH | `/api/product/{id}` | Edit product |
246
- | POST | `/api/product/remove` | Delete product |
247
- | POST | `/api/order/list` | List every order in admin shape |
248
- | POST | `/api/order/status` | Update order status + audit |
249
- | GET | `/api/analytics/summary` | Aggregations (totals, top pages, funnel, …) |
250
- | GET | `/api/analytics/segments` | Live RFM segmentation over `orders` |
251
- | GET | `/api/analytics/live?minutes=5` | Last-N-minutes snapshot |
252
- | GET | `/api/analytics/customer/{user_id}` | Per-user drilldown |
253
- | WS | `/api/analytics/ws?token=<JWT>` | Real-time event stream |
254
- | POST | `/api/upload` | CSV upload → pipeline |
255
- | POST | `/api/intel/run-on-current-users` | Build CSV from live orders → pipeline |
256
- | GET | `/api/results/{job_id}/status` | Poll job status |
257
- | GET | `/api/results/{job_id}/overview` | KPIs + segment distribution |
258
- | GET | `/api/results/{job_id}/customers` | Filterable customer table |
259
- | GET | `/api/results/{job_id}/insights` | LLM insight cards |
260
- | GET | `/api/results/{job_id}/top-customers` | Top N by 12-month CLV |
261
- | POST | `/api/results/{job_id}/query` | Natural-language Q&A (Groq) |
262
- | POST | `/api/admin/train` | Retrain HVR model from `models/train_data.csv` |
263
-
264
- ### Auth header conventions
265
-
266
- Most endpoints accept **either**:
267
-
268
- ```
269
- Authorization: Bearer <JWT>
270
- ```
271
-
272
- **or** (for the legacy admin UI):
273
-
274
- ```
275
- token: <JWT>
276
- ```
277
-
278
- `app/auth_deps.py::_strip_bearer` handles both.
279
-
280
- ---
281
-
282
- ## Database schema
283
-
284
- The first time you start the server it creates these tables.
285
-
286
- **Shop side (asyncpg, hand-written SQL):**
287
-
288
- ```
289
- users(id, name, email UNIQUE, password_hash, role, created_at)
290
- orders(order_id UNIQUE, user_id FK, items JSONB, delivery_info JSONB,
291
- payment_method, status, total, created_at)
292
- products(name, description, price, image JSONB, category, sub_category,
293
- sizes JSONB, bestseller, date, stock, created_at)
294
- analytics_events(session_id, user_id, event_type, page, element, value,
295
- monetary_value, timestamp, created_at)
296
- audit_log(actor_id, actor_email, action, target, detail JSONB, created_at)
297
- ```
298
-
299
- **Intel side (SQLAlchemy ORM):**
300
-
301
- ```
302
- jobs(id UUID PK, status, filename, row_count, customer_count,
303
- error_message, created_at, completed_at)
304
- customer_profiles(id UUID, job_id FK, customer_id,
305
- recency, frequency, monetary, avg_order_value,
306
- total_items, distinct_products, tenure_days,
307
- avg_items_per_order,
308
- r_score, f_score, m_score, segment,
309
- clv_12months, clv_segment, prob_alive, predicted_purchases_90d,
310
- hvr_probability, hvr_potential,
311
- anomaly_score, is_anomaly, anomaly_severity, anomaly_type)
312
- insights(id UUID, job_id FK, category, title, body, priority)
313
- ```
314
-
315
- ---
316
-
317
- ## How the ML pipeline works
318
-
319
- The pipeline accepts any reasonably-named transactions CSV (`customer_id`, `date`, `amount`, `quantity`, `invoice_id` — exact names auto-detected by fuzzy matching).
320
-
321
- 1. **`schema_detection.py`** — `detect_schema(df)` matches your CSV's columns against the canonical names using `rapidfuzz`. Falls back to an LLM call if confidence is low.
322
- 2. **`rfm_extraction.py`** — groups by `CustomerID`, computes Recency, Frequency, Monetary plus `AvgOrderValue / TotalItems / DistinctProducts / TenureDays / AvgItemsPerOrder`.
323
- 3. **`segmentation.py`** — quintile-scores R, F, M into 1–5 with the small-sample-safe `_safe_score`. Maps `(r, f, m)` onto 11 named segments.
324
- 4. **`prediction.py`** — loads `hvr_model.pkl` and adds `hvr_probability` + `hvr_potential` (High/Med/Low). Silent no-op if the model file isn't there.
325
- 5. **`clv.py`** — BG/NBD + Gamma-Gamma from `lifetimes`. Predicts 12-month value, `prob_alive`, `predicted_purchases_90d`.
326
- 6. **`anomaly.py`** — small PyTorch autoencoder (8→16→8→2→8→16→8). Reconstruction MSE → anomaly score. Top quantile flagged. Type assigned rule-based (Whale / Dormant / Bot-like …).
327
- 7. **`insights.py`** — Groq (qwen3-32b) generates Executive Summary, Anomaly Report, Segment Spotlight. Skipped entirely if `GROQ_API_KEY` is empty.
328
-
329
- The orchestrator (`services/ml_services.py`) writes one `CustomerProfile` row per customer and one `Insight` row per card, then marks the job `complete`.
330
-
331
- ### Retraining the HVR model
332
-
333
- If you have a transactions CSV at `app/ML/artifacts/train_data.csv`, you can retrain:
334
-
335
- ```
336
- POST /api/admin/train
337
- ```
338
-
339
- The handler does a temporal split (first 8 months �� features, remainder → label = high future spend AND ≥2 future orders), engineers extra features (`monetary_per_day`, `orders_per_day`, `avg_gap`, `spend_diversity`, `basket_value`), clips outliers at the 99.9th percentile, fits a `GradientBoostingClassifier` (200 estimators, depth 3, LR 0.05, positives weighted 2×), and saves `hvr_model.pkl + hvr_scaler.pkl + hvr_features.pkl`. Test AUC is returned in the response.
340
-
341
- ---
342
-
343
- ## Troubleshooting
344
-
345
- | Symptom | Fix |
346
- |---|---|
347
- | `RuntimeError: JWT_SECRET must be set to a secure value` on boot | Set `JWT_SECRET` in `.env` to a string ≥32 chars |
348
- | `asyncpg.InvalidPasswordError` | `DB_PASSWORD` in `.env` doesn't match your Postgres install |
349
- | `Connection refused on localhost:5432` | Postgres isn't running |
350
- | `ModuleNotFoundError: No module named 'torch'` | `pip install -r requirements.txt` (torch is the big one) |
351
- | `/api/intel/run-on-current-users` returns *No orders with linked user_id* | Place at least one order while logged in |
352
- | `/api/results/{id}/query` returns 503 | `GROQ_API_KEY` not set in `.env` |
353
- | CORS error in browser | Make sure you're hitting localhost / 127.0.0.1 — regex allows any port |
354
- | WebSocket closes immediately with code 4401 | Token missing/invalid in the query string |
355
- | WebSocket closes with code 4403 | User isn't an admin — run `scripts/create_admin.py --email …` |
356
-
357
- ---
358
-
359
- ## Notes for collaborators
360
-
361
- - **Don't commit `.env`** — it's gitignored on purpose. Share secrets out of band.
362
- - The `static/images/` and `uploads/` folders are created on first run. The latter is gitignored; the former contains product images uploaded via the admin panel.
363
- - The OpenAPI schema at `/openapi.json` is the source of truth. Generate client SDKs from it if you build new frontends.
364
- - If you're seeing a Pydantic validation error on a JSON request, check that field names match `schemas/shop.py`. The frontend sometimes sends `subCategory` but the backend uses `sub_category` in some places — `routers/products.py` normalises this for the admin form.
365
-
366
- ---
367
-
368
- ## Stack
369
-
370
- - Python 3.11, FastAPI, Uvicorn, Pydantic v2
371
- - SQLAlchemy 2 + asyncpg (dual-driver Postgres)
372
- - bcrypt, PyJWT, email-validator
373
- - scikit-learn, PyTorch (CPU), `lifetimes`, shap, rapidfuzz, joblib
374
- - Groq SDK (`qwen/qwen3-32b`)
375
- - python-dotenv, python-decouple, pydantic-settings
 
 
 
 
 
1
  ---
2
+ title: ThreadHouse
3
+ emoji: 🧵
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ pinned: false
8
+ ---