Predict the profitability of potential coffee shop locations using SQL and Python. Combines data engineering with feature-rich regression modeling, visual analytics, and business insights to support data-driven site selection and retail decision-making.
A machine learning project that turns coffee-shop location data into a monthly-profit estimate, using SQL feature engineering, a scikit-learn regression workflow, honest holdout evaluation, multi-model comparison, business-readable candidate ranking with risk bands, and an installable command-line package.
Important: This project is a site-selection decision-support demo, not a production financial forecasting system.>
It estimates likely monthly profit from the available location features and should be read alongside the limitations below. It does not guarantee real-world profitability, does not include costs such as labor or goods sold, and should never be the sole basis for a lease or investment decision.
Table of Contents
- Project Overview
- What This Project Does
- What This Project Does Not Do
- Key Features
- System Workflow
- Project Structure
- Installation
- Quick Start
- Generating Data
- Building the Database
- Training the Model
- Scoring Candidate Sites
- SQL Feature Engineering
- Model Output
- Prediction Intervals
- Model Artifacts and Loading Safety
- Evaluation Metrics
- Visual Reports
- Testing and CI
- Code Quality
- Data Statement
- Limitations
- Responsible Use
- Future Improvements
- Tech Stack
- Author
- License
Project Overview
Coffee-shop site selection is often presented as if a model can simply pick a winning location. In reality, a regression model cannot guarantee future profit; it can only estimate likely profit from the features it was given. A profit estimate is only useful if it can support a defensible action:
- rank candidate locations by predicted monthly profit
- flag candidates whose feature profile is far from the training data
- explain which features push a prediction up or down
- communicate uncertainty and limitations clearly
The goal is to show how a regression model can be turned into a responsible decision-support tool, not just a single Rยฒ or MAE score.
What This Project Does
This project can:
- Load training and candidate-location data from CSV files
- Store the data in a SQLite database
- Engineer reusable features through SQL views
- Compare multiple regression models with cross-validation
- Compare every model against a mean baseline
- Tune hyperparameters for Ridge and ElasticNet
- Evaluate the selected model on a separate holdout set
- Save trained model artifacts with metadata
- Score new candidate locations and rank them by predicted profit
- Add risk bands and positive/negative driver explanations
- Generate diagnostic charts
- Run automated tests and CI smoke workflows
What This Project Does Not Do
This project does not:
- Guarantee actual future profit
- Replace a full real-estate or finance analysis
- Include labor cost, cost of goods sold, store size, opening hours, or seasonality
- Use live market data
- Validate predictions against real store openings
- Provide a formal statistical prediction interval
- Prove that a feature causally increases or decreases profit
Key Features
- SQLite feature store built from CSV inputs
- SQL feature engineering through reusable views, so training and candidate sites are transformed identically (no train/serve skew)
- Centralized configuration in
config.pyfor features, ranges, labels, and output filenames - Input validation for schema, missing values, numeric types, and impossible ranges
- Multi-model comparison across Linear Regression, Ridge, ElasticNet, Random Forest, and Gradient Boosting
- Mean-baseline comparison so improvements are read honestly
- Cross-validation and hyperparameter tuning for Ridge and ElasticNet
- Holdout test-set evaluation, with all diagnostic plots drawn from unseen data only
- Candidate ranking with heuristic risk bands and business-readable positive/negative drivers
- Saved model artifacts via joblib, plus a metadata sidecar describing the training feature profile
- Installable package exposing
coffee-build-db,coffee-train, andcoffee-scorecommands - Ruff + Black + mypy quality gates with pre-commit hooks
- Unit tests and GitHub Actions CI across Python 3.10โ3.12
- Honest documentation of data and model limitations
System Workflow
CSV location data
โ
SQLite database
โ
SQL feature views (shared by train and candidates)
โ
Input validation
โ
Train/holdout split
โ
Model comparison + cross-validation
โ
Selected regression pipeline
โ
Holdout evaluation
โ
Candidate-site scoring
โ
Ranked recommendations with risk bands and drivers
Project Structure
Coffee-Shop-Profit-Predictor/
โ
โโโ .github/
โ โโโ workflows/
โ โโโ ci.yml
โ
โโโ data/
โ โโโ locations_train.csv
โ โโโ locations_candidates.csv
โ
โโโ outputs/
โ โโโ charts/
โ โ โโโ actualvspredicted.png
โ โ โโโ feature_importance.png
โ โ โโโ modelcomparisonmae.png
โ โ โโโ residuals_hist.png
โ โโโ feature_importance.csv
โ โโโ metrics.json
โ โโโ model.joblib
โ โโโ model_comparison.csv
โ โโโ model_metadata.json
โ โโโ predictions_all.csv
โ โโโ predictions_test.csv
โ โโโ scored_candidates.csv
โ
โโโ src/
โ โโโ coffeeshoppredictor/
โ โโโ init.py
โ โโโ config.py
โ โโโ create_db.py
โ โโโ generate_data.py
โ โโโ queries.sql
โ โโโ run_pipeline.py
โ โโโ scorenewsites.py
โ โโโ train_regression.py
โ โโโ utils.py
โ
โโโ tests/
โ โโโ testgeneratedata.py
โ โโโ test_pipeline.py
โ โโโ test_scoring.py
โ โโโ test_training.py
โ โโโ test_workflow.py
โ
โโโ .gitignore
โโโ .pre-commit-config.yaml
โโโ Makefile
โโโ pyproject.toml
โโโ README.md
โโโ requirements.txt
The outputs/ directory is regenerated by training and is not tracked in version control. CI rebuilds it on every run and uploads it as a build artifact.
Installation
1. Clone the Repository
git clone https://github.com/AmirhosseinHonardoust/Coffee-Shop-Profit-Predictor.git
cd Coffee-Shop-Profit-Predictor
2. Create a Virtual Environment
On Windows CMD:
python -m venv .venv
.venv\Scripts\activate
On macOS/Linux:
python -m venv .venv
source .venv/bin/activate
3. Install the Package
Install in editable mode. This pulls in all dependencies and registers the coffee-generate-data, coffee-build-db, coffee-train, coffee-score, and coffee-pipeline commands:
pip install -e .
For development tools (Ruff, Black, mypy, pre-commit):
pip install -e ".[dev]"
If you have make, make dev does the same and installs the pre-commit hooks.
Quick Start
Run the entire workflow (build database, train, score) with one command:
coffee-pipeline
Or with make:
make pipeline
To run the stages individually instead:
Build the database:
coffee-build-db --train data/locationstrain.csv --candidates data/locationscandidates.csv
Train and evaluate the model:
coffee-train
Score candidate sites:
coffee-score
Generating Data
The bundled CSVs are reproducible through a seeded generator. It samples each feature from a documented distribution and synthesizes profit from a transparent linear formula plus Gaussian noise (full specification in the generate_data.py module docstring).
coffee-generate-data --n-train 220 --n-candidates 60 --seed 42 --out-dir data/generated
- The same
--seedalways produces the same data. - Calibrated to the reference sample: a model trained on generated data reaches a
- Writing to
--out-dir dataoverwrites the bundled CSVs, so the example points
The signs in the profit formula match the documented business priors: rent and
competition reduce profit, every other feature increases it.
Building the Database
coffee-build-db --train data/locationstrain.csv --candidates data/locationscandidates.csv --db coffee.db
This will:
- Read the training and candidate CSV files
- Validate required columns and numeric ranges
- Create a SQLite database
- Save the data into database tables
coffee.db
Training the Model
coffee-train --db coffee.db --outdir outputs
This will:
- Load SQL feature views
- Split the training data into train and holdout sets
- Compare multiple regression models with cross-validation
- Select the best non-baseline model by cross-validated MAE
- Evaluate the selected model on the holdout test set
- Refit the selected model on all data for candidate scoring
- Save predictions, metrics, comparison results, and charts
outputs/model.joblib
outputs/metrics.json
outputs/model_metadata.json
outputs/model_comparison.csv
outputs/feature_importance.csv
outputs/predictions_test.csv
outputs/predictions_all.csv
outputs/charts/
The packaged SQL is located relative to the installed code, so--sqlis optional. Pass it only to override the bundledqueries.sql.
Scoring Candidate Sites
coffee-score --db coffee.db --model outputs/model.joblib --outdir outputs
This will:
- Load candidate-site features from SQLite
- Apply the trained model
- Predict monthly profit and rank candidates
- Add risk bands and business-readable drivers
- Save the scored candidate file
outputs/scored_candidates.csv
SQL Feature Engineering
Feature creation lives in queries.sql, which builds two SQLite views so the same transformations are applied to training locations and candidate sites:
features_train
features_candidates
Engineered features:
| Feature | Formula | Purpose | |---|---|---| | demandadj | foottraffic / (1 + competition) | Demand adjusted for competitive intensity | | wkndtraffic | weekendactivity ร foot_traffic | Weekend demand potential | | priceincome | coffeeprice ร (median_income / 1000) | Price-income fit / affordability signal | | promocompadj | promo_spend / (1 + competition) | Promotion adjusted for competition |
Keeping feature logic in SQL means a candidate site is never transformed differently from the training data it is compared against.
Model Output
Candidate scoring returns ranked results with the following fields:
| Field | Meaning | |---|---| | rank | Candidate rank by predicted monthly profit | | lat, lon | Candidate coordinates | | predicted_profit | Point estimate of monthly profit in euros | | predictionloweur, predictionhigheur | Lower/upper bound of the conformal prediction interval | | interval_coverage | Target coverage of the interval (e.g. 0.80) | | intervalhalfwidth_eur | Half-width of the interval (the ยฑ value) | | risk_band | Extrapolation-risk level from feature-profile distance | | profile_distance | Distance from the training-data feature profile | | mainpositivedrivers | Business-readable features helping the score | | mainnegativedrivers | Business-readable features hurting the score |
Example ranked result:
Rank: 1
Predicted profit: โฌ2,004.10
80% interval: โฌ1,415.21 - โฌ2,592.99 (half-width ยฑโฌ588.89)
Risk band: high (profile distance 1.32)
Main positive drivers: high competition-adjusted promotion; high competition-adjusted demand; high coffee price
Main negative drivers: no major red flags
Two complementary signals: the prediction interval quantifies uncertainty around the point estimate, while the risk band flags how far the candidate sits from the training profile (extrapolation risk). A candidate can have a tight interval but still be high-risk if its feature profile is unusual.
Prediction Intervals
Each prediction comes with an interval, not just a point estimate. The interval is built with split-conformal prediction on the holdout residuals:
- compute the absolute residuals on the holdout set
- take the quantile of those residuals at the target coverage level
- report each prediction as
predictedprofit ยฑ halfwidth
interval_coverage of true values are expected to fall
inside the interval. The empirical coverage on the holdout set is recorded in
metrics.json as a sanity check.
The default target coverage is 80% (PREDICTIONINTERVALCOVERAGE in config.py). The interval width is the same for every candidate, which is the standard behaviour of basic split conformal; per-candidate extrapolation risk is communicated separately through the risk band.
Model Artifacts and Loading Safety
The trained model is stored as a joblib (pickle) file, alongside a metadata sidecar describing the training feature profile used for risk bands:
outputs/model.joblib
outputs/model_metadata.json
Deserializing a pickle executes arbitrary code, so only load model files you produced yourself or fully trust โ never one downloaded from an untrusted source. Candidate scoring reads model_metadata.json to compute feature-profile distance; if it is missing, scoring falls back to conservative default thresholds.
Evaluation Metrics
Evaluation uses a train/holdout split with cross-validation on the training split, plus a mean-baseline comparison so improvements are read honestly.
| Metric | Why it matters | |---|---| | Holdout Rยฒ | Variance explained on unseen data | | Holdout MAE | Average euro error on unseen data | | Mean-baseline Rยฒ / MAE | Honest floor: what predicting the average achieves | | 5-fold CV Rยฒ / MAE | Stability of the estimate across folds | | Interval coverage | Empirical share of holdout actuals inside the 80% interval |
The current run selects ElasticNet (alpha = 0.5, l1_ratio = 0.8):
| Metric | Value | |---|---| | Holdout Rยฒ | 0.647 | | Holdout MAE | โฌ383.58 | | Mean baseline Rยฒ | -0.030 | | Mean baseline MAE | โฌ699.39 | | 5-fold CV Rยฒ | 0.530 ยฑ 0.110 | | 5-fold CV MAE | โฌ413.67 ยฑ โฌ46.65 | | 80% interval half-width | ยฑโฌ588.89 | | Empirical interval coverage (holdout) | 81.8% |
The model improves clearly over the mean baseline, but the cross-validation spread shows results should be read carefully because the dataset is small.
Visual Reports
Model diagnostics
| Actual vs Predicted | Residuals Histogram | |---|---| | |
| | Analysis: Holdout-only predictions plotted against actual profit, with a y=x reference line. Using unseen data makes this diagnostic more honest than plotting on training data. | Analysis: The distribution of holdout residuals (actual โ predicted). A narrower, centered spread means lower and less biased prediction error. |
Feature signal and model selection
| Feature Importance | Model Comparison (MAE) | |---|---| | |
| | Analysis: Standardized coefficients for the selected ElasticNet model. These are model associations, not causal proof. | Analysis: Cross-validated MAE per model on the training split. This shows whether the selected model meaningfully beats simpler alternatives and the mean baseline. |
Testing and CI
The test suite covers both the workflow and the business logic that candidate scoring exposes:
- source files compile and the CSV-to-SQLite load builds the expected views
- input validation rejects impossible values
- the data generator is deterministic for a fixed seed and passes validation
- the data generator is deterministic for a fixed seed and passes validation
- feature z-scores honor the metadata mean/std (with a zero-std fallback)
- risk bands map distances to low / medium / high and apply the error multipliers
- driver explanations surface high competition and low rent correctly, cap at
top_n, and fall back to "balanced profile" / "no major red flags"
- a training smoke run writes every artifact and beats the mean baseline
- an end-to-end pipeline run produces ranked candidates with prediction intervals
python -m unittest discover -s tests -v
Lint, format, and type check:
ruff check src tests
black --check src tests
mypy src
The GitHub Actions workflow checks:
- package installation in editable mode
- linting with Ruff
- format checking with Black
- type checking with mypy
- unit tests
- a full training and scoring smoke workflow
- training artifact and output validation
- artifact upload across Python 3.10, 3.11, and 3.12
.github/workflows/ci.yml
Code Quality
The project separates responsibilities across modules:
| Module | Purpose | |---|---| | config.py | Central settings: features, ranges, labels, and output filenames | | utils.py | Validation, JSON/CSV IO, and headless-safe plotting helpers | | create_db.py | Loads and validates CSVs into SQLite tables | | train_regression.py | Model comparison, cross-validation, holdout evaluation, and artifacts | | scorenewsites.py | Candidate scoring, risk bands, and driver explanations | | queries.sql | SQL feature views shared by training and candidate sites |
Tooling is configured through pyproject.toml (Ruff, Black, mypy) with pre-commit hooks in .pre-commit-config.yaml.
Data Statement
This repository is presented as a demonstration / simulated retail analytics project.
The bundled dataset contains:
220 training locations
60 candidate locations
The bundled CSVs are reproducible through coffee-generate-data, which samples each feature from a documented distribution and synthesizes profit from a transparent linear formula plus noise. This keeps the dataset parameterizable (size and seed) and makes the generative process auditable rather than opaque.
The data is suitable for demonstrating a portfolio machine learning workflow, but it should not be treated as verified real business data. If the project is later connected to real data, this section should be updated with the data source, collection period, geographic coverage, feature definitions, usage permissions, and known data quality issues.
Limitations
This project has important limitations:
- The dataset is small and simulated, not a production corpus
- The model does not include store size, total rent, labor cost, or cost of goods sold
- The model does not include opening hours, seasonality, competitor quality, or brand effects
- Risk bands are heuristic, not formal confidence intervals
- The model may perform poorly on locations outside the training profile
- High accuracy on the bundled data does not imply real-world accuracy
- Predictions are decision-support estimates, not final answers
Responsible Use
This repository is intended for:
- machine learning and data-analytics education
- SQL feature engineering and regression practice
- demonstrating honest holdout evaluation
- retail analytics workflow design
- responsible-ML documentation practice
- portfolio demonstration
- real lease-signing decisions without deeper analysis
- financial forecasting without validation
- automated investment decisions
- replacing expert real-estate judgment
- any high-stakes business decision without human review
Future Improvements
Potential next improvements:
- Add real store size and compute total monthly rent
- Add labor cost, cost of goods sold, opening hours, and seasonality
- Add competitor density and quality using geographic distance
- Add SHAP or permutation-importance explanations
- Add map-based candidate visualization
- Add a Streamlit dashboard and Docker support
Tech Stack
- Python
- pandas
- NumPy
- SQLite
- scikit-learn
- matplotlib
- joblib
- unittest
- Ruff
- Black
- mypy
- GitHub Actions
Author
Amir Honardoust
GitHub: @AmirhosseinHonardoust
License
This project is intended for educational, research, and portfolio purposes.
If you use or modify this project, please keep the responsible-use notes and limitations clear.