An Operations Analyst project on Kream sneakers resale: collect trade history by size, clean it into a sales fact, and analyze premiums and signals in Power BI.
Source code stays private. The live dashboard below is shared with Publish to web.
Problem
Resale decisions need how often a model×size trades, at what price, and at what premium to retail. A few recent rows on a product page are not enough for seasonality or size gaps. Histories are long and easy to interrupt, so a one-shot scrape is not enough.
How collection works
Playwright attaches to an already-logged-in Chrome and runs each product like this:
| Step | What happens |
|---|---|
| 1 | Start from a product page or the queue |
| 2 | Identify the model and expected volume per size |
| 3 | Collect only sizes that are still incomplete |
| 4 | Save size, price, date, delivery type to CSV |
| 5 | Merge by model → sales fact → Power BI |
| Run type | Description |
|---|---|
| Single | The product open in the browser |
| Batch | Walk a queue of products unattended |
| Queue adder | Pull model / URL from the current tab into the list |
Resume, update, fill gaps
If there is no file yet, collect from newest trades downward.
If a file already exists, collect only newer trades and stop when they overlap the old data.
If a run was interrupted, resume from a temp file. If the newest side is filled but the oldest side is missing, fill that gap only.
“Done” is not just a raw row count. A size counts as complete when it is close to the expected volume on the page (~90%), so a temporary stall is not treated as finished.
When something blocks the run
| Situation | Response |
|---|---|
| No new trades appear | Wait, reload, then retry remaining sizes |
| Session expired | Log in again and continue |
| Scroll looks stuck | Nudge upward; skip the size if it still fails |
| Wrong size or bad date | Drop those rows; stop that size if needed |
| Size volumes look wrong | Re-run size analysis |
| Volume is extremely high | Skip that size for safety |
Batch mode: read the queue → fill missing sizes → merge and remove from the queue on success → notify on stalls or skips.
Keeping the data clean
| Rule | Why |
|---|---|
| Size match check | Keep other sizes from mixing into a batch |
| Compare to expected volume | Decide whether a size is complete enough |
| Relative → absolute dates | Turn “3 hours ago” into real dates at fact export |
| Deduplicate on re-run | Keep only new trades |
| Master cache | Reuse brand, release date, retail, size mix |
| Split size keys | Same mm with different US labels stay separate |
From CSV to fact
One row = one trade.
| Step | Output |
|---|---|
| 1 | CSV per size |
| 2 | One merged file per model (+ SKU) |
| 3 | Cross-model sales_fact (SKU, Size, Price, Date, DeliveryType) |
Data modeling (star schema)
In Power BI, a trade fact joins product, brand, and date dimensions.
| Table | Role |
|---|---|
| fact_sales | One trade (SKU, Size, Price, DeliveryType, DateKey …) |
| dim_product | Model, names, category, retail, release date |
| dim_brand | Brand |
| dim_date | Date |
| What-if | Analysis period, profit target, Sales/Qty, signal toggles |
Key measures: %Average Premium, Daily Avg Price, Bollinger / RSI / Trend signals, KREAM Price Forecast.
Dashboard
Report pages:
| Page | Contents |
|---|---|
| Overview | Sales, quantity, average premium; sales by date and brand |
| Price trend | Daily average price by brand / product / size |
| Signals | Quantity vs price, Bollinger bands, period / profit-target what-if |
| Premium by brand | Brand premium, retail vs resale, weekday vs weekend |
| Size heatmap | Size × brand premium, trend by delivery type |
Sample observations:
- About 3M trades, ~912bn KRW sales, ~59% average premium
- Sales concentration: Jordan → Nike → Adidas
- Jordan is a clear premium outlier versus retail
- Premium also splits by size, delivery type, and collaboration
DAX / modeling notes
%Average Premium =
DIVIDE (
[Daily Avg Price] - SELECTEDVALUE ( dim_product[Retail_Price] ),
SELECTEDVALUE ( dim_product[Retail_Price] )
)
- Attributes come through relationships; calculations live in measures.
- Signals combine fact measures with what-if period and profit target.
Stack
| Layer | Tools |
|---|---|
| Collection | Python, Playwright (attach to Chrome) |
| Ops | Resume, incremental update, wait/retry on stalls, re-login |
| ETL | Per-size CSV → merge → sales_fact |
| Analytics | Power BI (star schema, DAX, what-if) |
Results
Built a trade collector that can resume and update by size, exported a sales fact, and connected it to a Power BI report for premium, price, and signals — embedded above.