Laptop Fair-Price Analyzer → Laptop Advisor
A laptop-buying assistant for India with two independent parts: a machine-learning price estimator trained on historical data, and a deterministic, rule-based recommender that matches a plain-English description of your needs to 67 verified, currently listed laptop configurations. This page explains both, with the actual numbers from the notebooks, code and deployment.
1. Project objective and motivation
The project started (V1) as a supervised regression problem: given a laptop's hardware specification, what reference price would a model expect, based on historical listings? It estimates a price from specifications, so the exact listing need not exist anywhere, and it can compare a seller's listed price with the model's estimate as a neutral reference point.
While building it, a clear limitation emerged: the training data describes laptops of roughly 2016–2018, so it cannot tell a buyer what to buy today. V2 therefore added Laptop Advisor ("Find My Laptop"): the user describes their course, job, software, games, budget and preferences; the app derives the hardware they need and ranks real, currently listed configurations whose prices and sources were verified. The original model is kept, clearly labelled, as the Price Lab.
src/→Streamlit Cloud→V2: recommender + catalogue→67 configurations→redesign→Railway2. Two systems, not one
- What: XGBoost regressor in a scikit-learn pipeline.
- Learns from: 1,273 historical laptops (prices = euros × 53.28).
- Output: a reference price in rupees (
expof a log-price prediction). - Validated by: group-aware holdout and repeated group-aware CV.
- Limitation: 2016–18 hardware and prices; newer hardware is extrapolation.
- Code:
src/features.py,src/train.py,src/predict.py.
- What: regex-based requirement extraction, per-workload spec tables, hard filters, weighted score.
- Uses: 67 configurations verified on 26 Oct 2026 (brand stores + Vijay Sales).
- Output: top three laptops with reasons, trade-offs, score breakdown and seller links.
- Validated by: unit tests (budget parsing, extraction, personas, invariants) and headless app tests.
- Limitation: small catalogue; tiers are chip-family rules, not benchmarks; prices not live.
- Code:
src/recommender.py,src/catalogue.py,catalogue/laptops.json.
3. The historical dataset and preprocessing ML
Source. The Kaggle dataset Laptop Price by Muhammet Varlı (1,303 laptops, prices in euros). The repository's
laptop_data.csv is a widely circulated derivative: all 1,303 rows match across the 10 specification columns, and every price
equals Price_euros × 53.28. Columns: Company, TypeName, Inches, ScreenResolution, Cpu, Ram, Memory, Gpu, OpSys, Weight, Price.
| Step (notebooks 01, 02, 05) | Result |
|---|---|
| Raw rows | 1,303 |
| Remove the single ARM laptop (Samsung, Chrome OS, ARM Mali GPU) — out of scope for an x86 price model | 1,302 |
| Remove exact duplicate copies (14 groups; 29 extra rows) | 1,273 usable |
| Unique configurations among the 1,273 | 1,228 |
| Configurations listed more than once at different prices | 45 groups (median max/min price gap 1.086) |
Parsing. "8GB" → 8; "2.2kg" → 2.2; the resolution string yields touchscreen and IPS flags plus X_res and Y_res; "Memory" ("256GB SSD + 1TB HDD") is split into SSD and HDD capacities (Flash and Hybrid were dropped after checking correlation); OpSys is grouped into Windows / Mac / Others-No OS-Linux.
4. Exploratory analysis and feature engineering ML
EDA plotted price against every feature (company, type, screen size, touchscreen, IPS, RAM, CPU and GPU brand, OS, weight) and checked correlations. The engineered features:
| Feature | Rule (from src/features.py) |
|---|---|
| Cpu brand | First three words: Intel Core i7 / i5 / i3 kept; other Intel → "Other Intel Processor"; anything else → "AMD Processor" (true in this data). |
| cpu_type | Model-number suffix: HQ/HK → High-performance (H); U → Low-power (U); Y → Ultra-low-power (Y); else Other. |
| cpu_gen | Intel Core generation from the first digit of the model number (7200U → Gen 7); otherwise Unknown. |
| cpu_ghz | Clock speed parsed from the CPU text ("… 2.8GHz"). |
| gpu_tier | Nvidia GTX / Nvidia Quadro / Nvidia other (MX, GT) / AMD Pro-FirePro / AMD other / Intel Iris / Intel HD-UHD. |
| Company_grouped | Companies with fewer than 10 training laptops (and unseen companies) → "Other". Fitted inside the pipeline (CompanyGrouper), so the list comes from training data only. |
Final 18 features. Categorical: TypeName, Cpu brand, os, cpu_type, cpu_gen, gpu_tier, Company_grouped. Numerical: Ram, Weight, Touchscreen, Ips, ppi, SSD, HDD, cpu_ghz, Inches, X_res, Y_res. Notebook 04 tested clock speed, CPU power class, CPU generation, GPU tier and rare-company grouping with repeated cross-validation; together they added about +0.02 CV R².
5. Why the model predicts log(Price) ML
Prices are strongly right-skewed (skewness 1.52); on the log scale the skewness is −0.18. Training on raw prices would let a few expensive laptops dominate a squared-error loss. On the log scale, errors become relative: a ₹5,000 error on a ₹30,000 laptop matters more than on a ₹2,00,000 one, which matches how buyers think about price.
Because exp is applied to the predicted mean log price, the back-transformed estimate is closer to a median price than a
mean price (no smearing correction was applied). R² is therefore reported on both scales.
6. Model selection and the XGBoost pipeline ML
Notebook 03 compared 13 tuned models plus voting and stacking ensembles (cross-validated grid or random search). These scores are from that earlier random split, before the integrity audit, so they are slightly optimistic:
| Model (notebook 03) | CV R² (log) | Test R² (log) | Test MAE (₹) |
|---|---|---|---|
| Voting (top 4) | 0.8969 | 0.9098 | 7,934 |
| Hist Gradient Boosting | 0.8878 | 0.9082 | 8,198 |
| Gradient Boosting | 0.8912 | 0.9057 | 8,398 |
| Stacking (top 4 → Ridge) | 0.8978 | 0.9047 | 8,130 |
| Random Forest | 0.8798 | 0.8987 | 8,633 |
| XGBoost | 0.8972 | 0.8958 | 8,306 |
| SVR · KNN · Decision tree | 0.875 · 0.855 · 0.825 | 0.878 · 0.875 · 0.821 | 9,302 · 9,044 · 11,242 |
| Linear / Ridge / Lasso / ElasticNet | ≈ 0.818 | ≈ 0.808 | ≈ 12,580 |
Notebook 05 re-tuned the three serious candidates (XGBoost, Gradient Boosting, a 4-model voting ensemble) under the clean protocol. Their accuracy was statistically tied, so practicality decided:
| Candidate (notebook 05) | Holdout R² (log) | Holdout MAE (₹) | Repeated CV R² (log) | Size | Predict 1 laptop |
|---|---|---|---|---|---|
| XGBoost | 0.9160 | 8,700 | 0.8974 ± 0.0138 | 2,003 KB | 1.81 ms |
| Gradient Boosting | 0.9146 | 8,691 | 0.8951 ± 0.0136 | 1,609 KB | 1.87 ms |
| Voting (XGB+GB+HistGB+RF) | 0.9150 | 8,646 | 0.8959 ± 0.0129 | 43,334 KB | 60.17 ms |
Voting − XGBoost on the holdout: R² −0.0010 (95% CI −0.0073 to +0.0051), MAE −₹57 (95% CI −375 to +250). Both intervals include zero, so XGBoost was chosen: same accuracy, about 20× smaller and 30× faster, and the best repeated-CV mean.
Pipeline: CompanyGrouper(min_count=10)
→ ColumnTransformer( OneHotEncoder(drop='first', handle_unknown) on 7 categoricals,
passthrough on 11 numericals )
→ XGBRegressor(n_estimators=300, learning_rate=0.02, max_depth=8, subsample=0.8,
colsample_bytree=0.4, min_child_weight=1, reg_lambda=0.5,
tree_method='hist', random_state=2)
7. Training/testing methodology and leakage prevention ML
Notebook 05 was an integrity audit of the earlier results. The risks found and the fixes:
- Exact duplicates could sit on both sides of a split, so the model would be tested on rows it had memorised. They were removed before splitting.
- Identical configurations at different prices (45 groups) are genuine listings and were kept, but grouped: a configuration is always entirely in train or entirely in test (
GroupShuffleSplitby full raw specification, test size 0.20, seed 42 → 1,015 train / 258 holdout rows; 982 / 246 configurations). - Tuning used group-aware 5-fold CV on the training rows only; the holdout was scored exactly once, after model selection.
- Target leakage made structurally impossible: features are built from a copy of the data with
Priceremoved. - Fitted transforms (company grouping, one-hot categories) are learned inside the pipeline from training folds only.
- 14 automated integrity checks passed (disjoint indices, no shared configurations, Price not among inputs, folds index only training rows, and so on).
| Effect of the fixes (XGBoost, 5-fold CV) | Rows | CV R² (log) | CV MAE (₹) |
|---|---|---|---|
| With duplicates, random KFold (old) | 1,302 | 0.9148 | 8,623 |
| Exact duplicates removed, random KFold | 1,273 | 0.9113 | 8,752 |
| Duplicates removed + same-spec grouped | 1,273 | 0.9096 | 8,905 |
Honest caveat recorded in the notebook: the feature set and search spaces were chosen in notebook 04 using CV on the old training rows, so the new holdout is fresh for fitting and tuning but not 100% independent of earlier feature decisions.
Final model. After evaluation, the chosen pipeline was refit on all 1,273 rows. That refit has no independent test score of its own; the holdout metrics are the best estimate of its performance. The production code reproduced the notebook 05 holdout result exactly (R² 0.915952, MAE ₹8,700.48) before refitting.
8. Evaluation metrics and their interpretation ML
| Metric (258-laptop holdout, XGBoost) | Value | Meaning |
|---|---|---|
| R² on log(Price) | 0.916 (bootstrap 95% CI 0.897–0.933) | Explains ~92% of the variance in log price. |
| R² on Price (₹) | 0.823 | Lower: a few expensive laptops have large absolute errors. |
| MAE | ₹8,700 (95% CI ₹7,294–₹10,470) | Average absolute miss in rupees. |
| RMSE | ₹15,275 | Much larger than MAE → a heavy tail of big errors. |
| MAPE / median APE | 13.9% / 12.2% | Half of predictions are within about 12% of the actual price. |
| Within 10% / 20% / 30% | 45% / 76% / 93% | Share of holdout laptops inside each error band. |
| Repeated group CV (5 folds × 5 repeats) | R² log 0.897 ± 0.014; MAE ₹9,565 ± 1,002 | Stability across splits. |
Error analysis. Ordinary notebooks are predicted best (MAE ≈ ₹5,988); workstations worst (≈ ₹19,276). Predictions shrink towards the middle: budget laptops are over-predicted (≈ −11% mean signed error) and laptops above ₹1 lakh under-predicted (≈ +12%); the signed error correlates with actual price (r = 0.41). A noise floor exists: identical configurations already differ from their group mean by about 4.1% (median), so no model can be exact.
9. The production pipeline ML
predict_laptop_price() validation + warnings→engineer_features()→saved pipeline (.joblib)→exp → ₹ estimate- One source of truth: training, prediction and tests all use
src/features.py; the app contains no feature logic. - Validated interface: invalid input raises
InvalidSpecError; unseen categories and out-of-range values produce warnings. - Dropdowns are limited to values seen in training (117 CPUs, 105 GPUs, 15 resolutions).
- Listed-price comparison is worded neutrally ("x% above this model's estimate"), with no buy/sell advice.
- Environment: saved with XGBoost 3.4.2 (conda, Python 3.11); deployed with Python 3.12 +
xgboost-cpu3.4.1; predictions verified bit-identical on all 1,273 laptops.
10. EUR→INR conversion, and why the model cannot price today's market ML
The rupee prices are not Indian prices: every value is the original euro price × 53.28, a single fixed rate. So the model's "₹" is a rescaled 2016–18 European price. This ignores Indian taxes, import duties, regional pricing and exchange-rate changes since then.
- Distribution shift: it has only seen Intel 6th–8th gen and GTX 10-series era hardware. Apple Silicon, Ryzen 5000+, Core Ultra and RTX 40/50 GPUs were never in training; their features collapse into coarse buckets (any non-Intel CPU becomes "AMD Processor", an RTX GPU falls into "Nvidia other").
- Trees cannot extrapolate: gradient-boosted trees predict within the range of training targets; a new flagship cannot be priced above what the trees have seen.
- Market changes: inflation, currency, launch cycles, discounts and stock are not modelled.
- Fixed conversion: 53.28 is not today's EUR→INR rate, and European prices are not Indian prices.
11. Modern laptop recommendation architecture Rules
free text ──► extract_requirements() regex rules → editable Requirements (workloads, budget, OS, priorities)
──► spec_profile() most demanding min/recommended spec across the selected workloads
──► recommend() hard filters (OS → budget → minimum specs)
→ weighted score → diverse top 3 → pros, cons, trade-offs
catalogue/laptops.json ──► load_catalogue() rejects entries without an allowed source URL or verification date
Everything is deterministic: the same input always gives the same output, every number in a score can be traced to a rule, and no paid AI API or trained model is involved. The user can edit every extracted requirement and the shortlist updates instantly.
12. Natural-language requirement extraction Rules
- Workloads from keyword regexes: courses and professions (CS, mechanical, MBA…), software (SolidWorks, MATLAB, Premiere Pro, PyTorch, Blender…), and games (Valorant → esports; Cyberpunk, GTA V → AAA). Each match is shown as evidence ("valorant" → Esports & casual gaming).
- Budget parsing handles Indian formats: "₹80,000", "80k", "1.2 lakh", "Rs. 80,000/-", "0.8 lakh", ranges ("between 70k and 90k" → upper bound 90,000), and "around 80k" → 88,000 (+10% tolerance). A guard ignores numbers followed by words such as "Hz", "GB" or "display".
- OS rules: software that requires an OS sets a hard requirement (SolidWorks / CATIA / ANSYS → Windows; Final Cut Pro / Logic Pro / Xcode → macOS). "I prefer a Mac" is only a preference.
- Priorities: portability and battery (Low/Medium/High) and screen size from phrases such as "travel a lot" or "light".
- Conflicts are surfaced: e.g. "MacBook" + SolidWorks produces a note explaining why only Windows laptops are considered.
13. Workload-specific minimum and recommended specifications Rules
Each workload has a minimum (hard filter) and a recommended level (scored). CPU and GPU levels are 1–5 class tiers from a documented chip-family rule, not benchmarks. With several workloads, the most demanding value of each spec is used.
| Workload | RAM (GB) min / rec | CPU tier | GPU tier | SSD (GB) | Extra |
|---|---|---|---|---|---|
| Everyday use & study | 8 / 16 | 1 / 2 | 1 / 1 | 256 / 512 | |
| Business & office | 8 / 16 | 1 / 2 | 1 / 1 | 256 / 512 | |
| Programming | 16 / 16 | 2 / 3 | 1 / 1 | 512 / 1024 | |
| Data science & ML | 16 / 32 | 3 / 4 | 1 / 3 | 512 / 1024 | CUDA preferred |
| Engineering & CAD | 16 / 32 | 3 / 4 | 2 / 3 | 512 / 1024 | |
| Graphic & UI design | 16 / 16 | 2 / 3 | 1 / 2 | 512 / 1024 | |
| Video editing | 16 / 32 | 3 / 4 | 2 / 3 | 512 / 1024 | |
| 3D & game development | 16 / 32 | 3 / 4 | 3 / 4 | 512 / 1024 | |
| Esports & casual gaming | 16 / 16 | 2 / 3 | 3 / 3 | 512 / 1024 | 144 Hz preferred |
| AAA gaming | 16 / 16 | 3 / 4 | 3 / 4 | 512 / 1024 | 144 Hz preferred |
GPU tiers: 1 basic integrated; 2 strong integrated (Intel Arc, Radeon 760M–890M, Apple base M-series) or RTX 2050; 3 entry dedicated (RTX 3050/4050/5050) or Apple Pro integrated GPU; 4 mid-range dedicated (RTX 4060/5060); 5 upper-mid (RTX 4070/5070+). The tier is a performance class only. The display label follows the architecture flag, so Apple silicon is never called a dedicated GPU, and only NVIDIA laptops are marked CUDA-capable.
14. Hard budget and operating-system filters Rules
- OS: the required OS (from software), or the preferred OS if one was chosen. A laptop on the wrong OS is never returned.
- Budget: price must be ≤ the budget; a laptop whose price is not verified never "fits" a budget.
- Minimum specs: RAM, CPU tier, GPU tier and storage must meet the workload minimums.
Honest fallbacks. If nothing passes all three, the app says so: "below minimum" shows the closest in-budget options with their shortfalls listed; "over budget" shows no cards, names the cheapest verified option and keeps the spec recommendations visible.
15. Weighted scoring and ranking Rules
| Component | Default weight | Score sk |
|---|---|---|
| Workload fit | 0.50 | Mean of min(1, have/recommended) for RAM, CPU tier, GPU tier and storage; plus CUDA (1/0) when preferred and refresh/144 for gaming. |
| Value for money | 0.15 (0.10 with a budget) | (fit ÷ price), normalised by the best value among the candidates. |
| Portability | 0.05 / 0.15 / 0.30 | clamp((2.6 − kg) / 1.4); unknown weight = 0.5 (neutral). |
| Battery friendliness | 0.03 / 0.10 / 0.25 | 0.4 × clamp((Wh − 40)/45) + 0.6 × power factor (efficient 1.0, performance 0.65, gaming 0.3); unknown Wh = 0.5. An estimate, not a measured runtime. |
| Screen match | 0.10 (0 if "Any") | 1.0 if the size preference is met, else 0.3. |
Weights are set from the user's Low/Medium/High priorities and renormalised to sum to 1. Ties are broken by lower price. A diversity step skips near-identical variants (same brand and series) while other good options exist, so the top three are genuinely different.
def workload_fit(lap, p):
parts = [clamp(lap.ram_gb / p.rec_ram), clamp(lap.cpu_tier / p.rec_cpu),
clamp(lap.gpu_tier / p.rec_gpu), clamp(lap.storage_gb / p.rec_storage)]
if p.prefer_cuda:
parts.append(1.0 if lap.cuda else 0.0)
if p.prefer_refresh:
parts.append(clamp((lap.refresh_hz or 60) / p.prefer_refresh))
return sum(parts) / len(parts)
16. Explanation generation and trade-offs Rules
- "Why it fits" lists only facts that pass a rule: RAM meets the recommendation, GPU meets the level, CPU in the recommended class, high refresh rate, required OS.
- "What you give up" lists shortfalls: RAM only at the minimum, GPU below the recommendation, no CUDA for ML, weight over 2 kg, small SSD, high-power hardware that hurts battery, weight or battery "not stated".
- Trade-off line for options 2 and 3 compares them with the top pick (for example "₹80,000 cheaper, 0.14 kg lighter, weaker graphics").
- Score breakdown ("How this score was built") shows each component and its weight; Compare can show "Meets your recommended specs" when a brief is active.
17. The 67-laptop verified catalogue Rules
By source: 41 Vijay Sales (authorised retailer) · 26 manufacturer stores (HP India 23, Apple India 3)
Price range: ₹37,599 – ₹2,99,900 · verified 26 Oct 2026
Every record: brand, model, part number, CPU/GPU and tiers, RAM, SSD, display, refresh, weight, battery, price, source, URL, verification date, availability.
Collected honestly: only from structured data published on official product pages (JSON-LD price, availability and specification
lists), respecting robots.txt and without bypassing anti-bot measures. Colour variants and duplicate part numbers were removed.
load_catalogue() rejects any record that lacks an allowed URL prefix, an ISO verification date (not in the future), valid 1–5 tiers
or positive numbers, or that claims CUDA without a dedicated GPU. It also rejects duplicate IDs, part numbers and URLs.
18. Handling missing or uncertain product information Rules
- Never guessed: weight, battery capacity and price may be
null; the UI says "not stated". - Neutral scoring: unknown weight and battery score 0.5, neither rewarded nor penalised, and the cons list flags them.
- Unknown price: excluded whenever a budget is set and given zero value for money.
- Exact CPU not stated by the retailer: recorded as "(exact model not stated)", and the lower, low-power tier is assumed.
- Dated prices: every price shows its source and verification date, with a reminder to confirm on the seller page.
19. The 91 automated tests
| File | Tests | What it covers |
|---|---|---|
test_pipeline.py | 18 | Known-row prediction; no target leakage; exact reproduction of the notebook 05 holdout; reload in a fresh process; invalid inputs; unknown categories; sanity checks (more RAM never cheaper, and so on). |
test_app.py | 19 | INR formatting, resolution labels, listed-price wording, spec packing; headless Streamlit (AppTest) checks that the app's estimates are bit-identical to direct predictions. |
test_catalogue.py | 11 | Every record validates; allowed sources; no duplicate IDs, parts or URLs; nullable fields; future dates and bad tiers rejected. |
test_recommender.py | 32 | Indian budget phrasings; workload and OS extraction; spec profiles; OS and budget never violated; monotonicity (more RAM never lowers fit; a higher price never raises the score); weights sum to 1; 10 personas; unverified prices; diverse top three; GPU classification (Apple integrated, CUDA only for NVIDIA, no Macs for gaming). |
test_find_my_laptop_app.py | 11 | Headless app: five sections, results match the engine, official links, editing updates results, Compare preselects the shortlist, scores only with an active brief, low-budget state, OS-conflict note, brand filter, retailer labels, estimator still separate. |
The first 37 (pipeline + app) date from V1 and were kept unchanged; V2 added 54. All 91 pass, run with python -m unittest discover -s tests.
20. Streamlit application and Railway deployment
- App (V2): one Streamlit file with five sections: Find, Explore (a filterable board of all 67 laptops), Compare (2–4 side by side), Price Lab, and How it works. Original SVG laptop illustrations are generated from each laptop's real screen size, aspect ratio, category and brand (no product photos, for rights reasons). All catalogue and user text is HTML-escaped.
- Deployment: Railway service built by Railpack from the GitHub branch
laptop-advisor-v2(auto-deploy on push); Python 3.12 pinned by.python-version; start commandstreamlit run app.py --server.port $PORT --server.address 0.0.0.0 --server.headless true; health check/_stcore/health; 1 vCPU / 1 GB. - Final production deploy: commit
8c97b42→ deployment SUCCESS; checked live in a real headless browser at 1440, 768, 390 and 360 px. - V1 remains on Streamlit Community Cloud from
main, unchanged.
21. Strengths, limitations and future improvements
- Leakage-audited evaluation with grouped splits, confidence intervals and error analysis.
- Model chosen on evidence (a statistical tie → the simpler, faster model).
- One feature-engineering module shared by training and serving; bit-identical deployment check.
- A transparent, editable, explainable recommender with no paid API and no hallucinated products.
- Verified data with provenance; missing data shown, never invented.
- 91 tests, including invariants and headless UI tests.
- The price model is historical (2016–18, EUR × 53.28) and unreliable for current hardware.
- Point estimates only, with no prediction intervals; shrinkage towards the middle.
- Catalogue: 67 configurations, 6 brands, only 3 under ₹50k; prices dated 26 Oct 2026.
- CPU/GPU tiers are name-based rules, not benchmarks; the battery score is an estimate.
- Extraction is keyword-based: unusual phrasing can be missed (the user can edit the result).
Future improvements: quantile models for calibrated price ranges; retraining on recent Indian listings; benchmark-based CPU/GPU scores; scheduled re-verification of catalogue prices and availability; more retailers and brands; an optional, constrained language model only for parsing, with the rule engine still deciding.
22. Viva questions with answers
Part A · The ML price estimator
Q1What exactly does your ML model predict, and what does it not predict?
It predicts ln(Price) for a laptop described by 18 engineered features, and the app shows exp(prediction) as a historical reference price in rupees. It does not predict today's Indian market price, discounts, availability, or the price of a specific listing: it learned how price related to hardware in a 2016–18 European dataset converted at a fixed 53.28 rate.
Q2Why did you train on log(Price) instead of Price?
Price is right-skewed (skewness 1.52; −0.18 after the log). With raw prices, squared-error training is dominated by a few expensive laptops. On the log scale, errors are relative (percentage-like), the residuals are closer to symmetric, and the model focuses equally on cheap and expensive laptops. Predictions are mapped back with exp().
Q3Does exp(ŷ) give an unbiased estimate of the mean price?
No. ŷ estimates the conditional mean of ln(Price), and exp(E[ln P]) ≤ E[P] by Jensen's inequality, so exp(ŷ) is closer to a conditional median. A smearing correction (e.g. Duan's) could estimate the mean; it was not applied. For a "typical reference price" the median-like estimate is reasonable, and the metrics were computed on the back-transformed rupee values.
Q4Why is R² 0.916 on log(Price) but only 0.823 on Price?
They measure different things. On the log scale, a 10% error on a ₹3 lakh laptop counts the same as on a ₹30k laptop. In rupees, the expensive laptop's error is ten times larger in absolute terms, and squared errors amplify it, so the few expensive laptops (which the model under-predicts) pull rupee R² down. RMSE ₹15,275 versus MAE ₹8,700 shows the same heavy tail.
Q5What data leakage did you find, and how did you fix it?
Two kinds. (1) 29 exact duplicate rows could fall on both sides of a random split, so the model was partly tested on memorised rows; they were removed before splitting. (2) 45 configurations appear several times at different prices; they were kept (genuine listings) but grouped, so a configuration is always entirely in train or entirely in test (GroupShuffleSplit and GroupKFold on the full raw specification). Features are also built from data with Price removed, and fitted transforms live inside the pipeline. The CV R² dropped from 0.9148 to 0.9096, showing the earlier number was slightly optimistic.
Q6Why a group-aware split rather than a stratified or random split?
The goal is to estimate performance on new configurations. If the same specification is in both train and test, a tree model can recall its price, which inflates scores without reflecting generalisation. Grouping by specification simulates seeing an unseen laptop. Stratification on price would balance the target distribution but would not stop configuration overlap.
Q7Your notebook 03 voting ensemble had a higher test R² (0.910 vs 0.896). Why not deploy it?
Notebook 03 used a single random split that still contained duplicates, so differences of about 0.01 were within noise. Under the clean protocol (notebook 05), voting minus XGBoost was R² −0.0010 with a 95% CI of −0.0073 to +0.0051, and MAE −₹57 with a CI of −375 to +250. Both include zero: a statistical tie. XGBoost had the best repeated-CV mean and is about 20× smaller (2 MB vs 43 MB) and 30× faster (1.8 ms vs 60 ms per laptop), with a single model to maintain.
Q8How was hyperparameter tuning done without touching the holdout?
RandomizedSearchCV (40 sampled combinations) with group-aware 5-fold CV on the 1,015 training rows only. The holdout (258 rows, 246 configurations) was scored once, after the model was selected by repeated CV. An automated check confirmed that the tuning and CV folds index only training rows. Final XGBoost parameters: 300 trees, learning rate 0.02, max depth 8, subsample 0.8, colsample_bytree 0.4, min_child_weight 1, reg_lambda 0.5.
Q9Is your holdout truly independent?
Almost, and the notebook says so. It is fresh for fitting, tuning and model selection, but the feature set and search spaces were chosen in notebook 04 using CV on the earlier training rows, which overlap with some new holdout rows (211 of 258 were in the old training set). So it is not 100% independent of feature decisions; the repeated group CV (0.897 ± 0.014) is a second, consistent estimate.
Q10What is the model's typical error, and what does that mean for a user?
On the holdout: MAE ≈ ₹8,700, median absolute percentage error ≈ 12%; 45% of laptops are within 10%, 76% within 20% and 93% within 30%. For a user, a ₹50,000 historical-style estimate typically means roughly ±₹6,000. It is a reference point, not a valuation; the app shows the error next to every estimate.
Q11Where does the model fail most, and why?
On workstations (MAE ≈ ₹19,276) and laptops above ₹1 lakh. It shrinks towards the middle: budget laptops are over-predicted by about 11% and expensive ones under-predicted by about 12%. Causes: few training examples at the extremes, tree ensembles averaging leaf values, and premium pricing (brand, build quality) not captured by the features. There is also a noise floor of about 4% between identical configurations.
Q12Explain ppi and why you engineered it.
Pixels per inch = √(X_res² + Y_res²) / Inches. Resolution and screen size separately are confounded (a big screen at 1080p is not "better" than a small one); pixel density captures display sharpness, which correlates with premium positioning. The raw X_res, Y_res and Inches were kept as well, because trees can use them in interactions.
Q13How do you handle a category the model has never seen?
Companies with fewer than 10 training laptops, and unseen companies, map to "Other" (fitted in the pipeline). The OneHotEncoder uses handle_unknown, so an unseen category becomes all zeros, which with drop='first' looks like the dropped baseline category. That is a documented limitation, and the prediction interface warns the user about unseen or out-of-range inputs.
Q14Why does 53.28 matter?
The "rupee" prices are the Kaggle euro prices × 53.28, so the target is a rescaled European price from around 2017, not an Indian price. It ignores Indian taxes and duties, regional pricing and exchange-rate movement. Any claim like "this laptop costs ₹X in India" from this model would be wrong in principle.
Q15Could you just retrain the model to recommend today's laptops?
Not with this data. Trees cannot extrapolate beyond training targets or to unseen hardware: Apple Silicon, Ryzen 5000+ and RTX GPUs are absent and collapse into coarse buckets. A useful modern price model needs current, Indian, dated listings at scale. Even then, predicting a price is a different task from recommending what to buy; recommendation needs requirements, constraints and real availability, which is why V2 is a separate rule-based system.
Q16How did you make sure the deployed model behaves exactly like the notebook model?
A single feature module (src/features.py) is used by training, serving and tests. The frozen training script first reproduced the notebook 05 holdout exactly (R² 0.915952, MAE ₹8,700.48) before refitting. In deployment (Python 3.12, XGBoost 3.4.1 rather than 3.4.2), predictions were verified bit-identical on all 1,273 laptops, and AppTest checks that the website shows the same value as a direct call.
Q17What does a bootstrap confidence interval tell you here?
Resampling the 258 holdout predictions with replacement (2,000 bootstrap resamples) gives the sampling variability of each metric: R² log 0.897–0.933 and MAE ₹7,294–₹10,470. Paired bootstrap differences between models (voting − XGBoost) were used to show the candidates were statistically tied.
Part B · The rule-based recommender
Q18Is Laptop Advisor machine learning?
No. It is deterministic and rule-based: regex extraction, hand-written per-workload spec tables, hard filters and a weighted score. Nothing is trained, and it never calls the XGBoost model. That was deliberate: the recommendations must be explainable, reproducible and grounded in verified listings, and there was no labelled dataset of "correct recommendations" to train on.
Q19Walk me through what happens when I type "CS student, Python and Java, budget ₹75,000".
Extraction finds the programming workload and a budget of 75,000. The spec profile becomes min 16 GB RAM / CPU tier 2 / 512 GB SSD, recommended 16 GB / tier 3 / 1 TB. Filters remove over-budget laptops and those below the minimums; in production 3 of 67 passed. Each is scored (fit 0.5 adjusted, value 0.10 because a budget is set, portability, battery), diversity is applied, and the top three are shown with reasons, trade-offs and seller links: HP 14 (₹67,999), ASUS Vivobook Go 15 (₹71,990) and HP 15 (₹71,999).
Q20Why use hard filters for OS and budget instead of just scoring them?
Because they are constraints, not preferences. A laptop that cannot run SolidWorks, or costs more than the user can pay, is wrong regardless of its score; a soft penalty could still let it win. Hard filters guarantee that OS and budget are never violated (both are tested as invariants). When nothing passes, the app says so explicitly instead of quietly relaxing the constraint.
Q21How were the weights chosen? Aren't they arbitrary?
They are design choices, not learned parameters, and the app says so. Workload fit dominates (0.5) because meeting the hardware need matters most. Value for money drops from 0.15 to 0.10 when a budget is set, because the filter already handles price. Portability and battery scale with the user's stated priorities. They are made defensible by transparency (the breakdown is shown), editability, monotonicity tests and persona tests rather than by fitting; with user feedback data they could later be calibrated.
Q22How is the "value for money" score computed, and what is its weakness?
Value = (workload fit ÷ price), normalised by the best value among the candidates, so it lies in [0, 1]. Weaknesses: it is relative to the current candidate pool (adding a laptop can change other scores), and fit saturates at the recommended level, so extra performance beyond it earns no value credit.
Q23What if two workloads conflict, e.g. gaming and portability?
Specs take the most demanding value across workloads, so gaming's GPU minimum still applies; portability raises its weight to 0.30. If every laptop meeting the performance needs weighs over 2 kg, the result message says so explicitly, and options 2 and 3 carry trade-off lines (lighter or heavier than the top pick) so the user can choose.
Q24How do you parse Indian budgets reliably?
A dedicated parser handles ₹ and Rs/INR prefixes, comma grouping, k / thousand / lakh / lac units ("1.2 lakh" → 1,20,000), ranges (upper bound) and "around" (+10%). A guard prevents false matches such as "144 Hz" or "16 GB" being read as money. Each format is covered by tests.
Q25The MacBook Pro M5 Pro was once labelled "Dedicated GPU". What was wrong, and how did you fix it?
The data was right (gpu_dedicated = false, cuda = false), but the comparison table derived its label from the GPU performance tier (3 = "entry dedicated-GPU class or Apple Pro GPU") and shortened it to "Dedicated GPU". The fix: a gpu_class_label() function that takes the architecture from the dedicated flag ("Integrated, Pro-class performance"), plus regression tests: Apple laptops are integrated without CUDA, CUDA only for NVIDIA, Macs are flagged for CUDA workloads, and gaming queries do not recommend Macs. The scoring engine was not rewritten.
Q26How do you handle a laptop whose weight or battery the retailer does not state?
The field stays null and is never estimated. The UI shows "not stated", scoring uses a neutral 0.5 for that component, and a con flags it. Unknown price is stricter: such a laptop never fits a budget. This avoids both rewarding and punishing missing data while being honest about it.
Q27How did you make sure the 67 laptops are real and correctly priced?
Each record came from structured data on official pages (HP India and Apple India stores, and Vijay Sales, an authorised retailer): price, availability and specifications from JSON-LD and spec lists, with the part number, URL, source and verification date stored. Nothing was fabricated; colour variants and duplicates were removed; robots.txt was respected. The loader rejects records without an allowed URL or a valid date. On the day of the final deployment all 67 links returned HTTP 200, and the recommended links showed the matching part number.
Q28CPU/GPU tiers are not benchmarks. Isn't that a serious flaw?
It is a real limitation. Tiers come from documented naming rules (e.g. a 7-class HX → tier 4; Apple base M-series CPU → 3, Pro → 4; RTX 4060/5060 → GPU tier 4). They are transparent and consistent but coarse: two chips in one tier can differ meaningfully, and the U/H suffix is assumed when a retailer omits it. Benchmark scores would be the improvement; I chose rules I could verify and explain over numbers I could not source reliably.
Q29Why not use an LLM to understand the user's text?
Constraints and goals: no paid API, deterministic and testable behaviour, and no risk of hallucinated products or specs. Regex extraction is limited in coverage, but every interpretation is shown with its evidence and is editable. A constrained LLM could improve parsing in future, with the rule engine still making the decisions.
Q30How do you keep the top three from being near-duplicates?
After sorting by score, a diversity step takes the best laptop from each brand+series (e.g. "asus vivobook 15") before filling with further variants. This prevents three colour or storage variants of one model taking all the slots, while still returning the highest scorers.
Q31What does "Meets your recommended specs: 100/100" mean in Compare?
The share of the brief's recommended RAM, CPU tier, GPU tier and storage that the laptop reaches, capped at 100 and ignoring budget. It is shown only when a search is active. For the "Business user" brief (office work: 16 GB, CPU tier 2, GPU tier 1, 512 GB), all three MacBooks exceed every recommendation, so they all show 100. It is a coverage measure, not overall quality, and the caption explains this.
Engineering, testing and deployment
Q32What do your 91 tests prove, and what can't they prove?
They prove reproducibility (the notebook 05 holdout is reproduced exactly), no target leakage, interface robustness, catalogue integrity, recommender invariants (OS and budget never violated; monotonicity; weights sum to 1), persona behaviour, the GPU classification rules and app behaviour in a headless run. They cannot prove the recommendations are "best" for real users or that prices are still current; that needs user studies and re-verification.
Q33What is a monotonicity test and why include one?
A property test: improving a laptop in one dimension should never make it worse in the logic. For example, more RAM never lowers workload fit, and a higher price never raises the score. Such tests catch scoring bugs that example-based tests miss and make the weighted score behave sensibly.
Q34Describe the deployment and how you verified it.
Railway builds the GitHub branch laptop-advisor-v2 with Railpack (Python 3.12 pinned) and runs Streamlit on $PORT, with health check /_stcore/health. A push triggers the deploy. After commit 8c97b42 the deployment reported SUCCESS, the health check returned "ok", and a headless browser tested the live site: three queries matched the local engine exactly, Explore filters and sorting worked, seller links showed the right part numbers, the Price Lab outputs were unchanged, and there was no horizontal overflow at 1440, 768, 390 and 360 px.
Q35Why two environments (conda Python 3.11 and pip Python 3.12)?
Research and training used conda (Python 3.11, XGBoost 3.4.2 from conda-forge). That exact XGBoost version is not on PyPI, and PyPI's XGBoost 3.3+ needs Python ≥ 3.12. So deployment uses Python 3.12 with xgboost-cpu 3.4.1 (no CUDA libraries); bit-identical predictions were verified, and a harmless version warning appears only in server logs.
Q36If you had two more weeks, what would you do first?
(1) Scheduled re-verification of catalogue prices and availability, plus more retailers to cover the under-₹50k segment; (2) benchmark-based CPU/GPU scores in place of name tiers; (3) prediction intervals (quantile XGBoost) for the Price Lab, or retraining it on current Indian listings; (4) a small user study to calibrate the recommender weights.
23. Quick-revision cheat sheet
ML price model (Price Lab)
- Kaggle "Laptop Price", 1,303 rows → 1,273 (−1 ARM, −29 duplicates)
- ₹ = € × 53.28 (fixed), hardware from ~2016–18
- Target ln(Price); skew 1.52 → −0.18
- 18 features; ppi = √(X²+Y²)/inches
- XGBoost: 300 trees, lr 0.02, depth 8
Evaluation numbers
- Group split 1,015 / 258 (seed 42, by spec)
- Holdout R² log 0.916 · R² ₹ 0.823
- MAE ₹8,700 · RMSE ₹15,275 · median APE 12.2%
- Within 10/20/30%: 45/76/93%
- Repeated group CV R² 0.897 ± 0.014
- Tie with voting → XGBoost (20× smaller, 30× faster)
Leakage controls
- Duplicates removed before the split
- Same configuration → same side (GroupShuffleSplit / GroupKFold)
- Features built without Price
- Transforms fitted inside the pipeline
- Holdout used once; 14 checks PASS
Recommender (Laptop Advisor)
- Rules, not ML; never calls the price model
- Text → workloads, budget, OS, priorities
- Specs = max over workloads (min + recommended)
- Filters: OS → budget → minimum specs
- Score = 0.5 fit + 0.15 value + 0.15 port + 0.10 battery + 0.10 screen (adjusted)
- Diverse top 3, pros/cons, trade-offs
Catalogue
- 67: HP 32, ASUS 11, Dell 9, Lenovo 8, Acer 4, Apple 3
- 41 Vijay Sales + 26 brand stores
- ₹37,599 – ₹2,99,900, verified 26 Oct 2026
- Unknown weight/battery → "not stated", neutral 0.5
- Unknown price never fits a budget
Engineering
- 91 tests: pipeline 18, app 19, catalogue 11, recommender 32, Find-app 11
- Railway, branch laptop-advisor-v2, Python 3.12
- Health check /_stcore/health; commit 8c97b42
- V1 still on Streamlit Cloud from main