A full-stack web application for conducting beer preference studies with real-time data collection and analysis.
# Automatically detect and configure your IP address
./configure-ip.sh detect# Terminal 1: Start the backend
cd backend
./start.sh
# Terminal 2: Start the frontend
./start-frontend.sh- Frontend: http://YOUR_IP:3000
- Backend API: http://YOUR_IP:5000
All network configuration is centralized in config.env. You only need to update the IP address in one place:
# config.env
IP_ADDRESS=10.100.91.169 # <- Change this to your IP address
BACKEND_PORT=5000
FRONTEND_PORT=3000The scripts automatically generate the necessary environment files from this central configuration.
- Frontend: Next.js 14 with App Router, TypeScript, TailwindCSS (
beer-ml-frontend/) - Backend: Flask with Gunicorn, SQLAlchemy ORM (
backend/) - Database: SQLite with WAL mode for concurrency
- Map Integration: Leaflet.js for location selection
- State Management: Zustand for React state
Beer-ML/
├── backend/ # Flask backend application
│ ├── app.py # Main Flask application
│ ├── requirements.txt # Python dependencies
│ ├── start.sh # Backend startup script
│ └── venv/ # Python virtual environment
├── beer-ml-frontend/ # Next.js frontend application
│ ├── src/
│ │ ├── app/ # Next.js App Router pages
│ │ │ ├── page.tsx # Home page
│ │ │ ├── profile/ # Profile setup page
│ │ │ ├── preferences/ # Taste preferences page
│ │ │ ├── dashboard/ # Beer selection dashboard
│ │ │ ├── beer_[id]/ # Individual beer rating pages
│ │ │ └── complete/ # Completion page
│ │ ├── components/ # React components
│ │ ├── lib/ # Utilities and configurations
│ │ └── store/ # State management
│ ├── .env.local # Environment variables (gitignored)
│ └── package.json # Node.js dependencies
├── .github/
│ └── copilot-instructions.md # GitHub Copilot instructions
├── .env.example # Environment template
├── .env.local.example # Frontend environment template
├── start-frontend.sh # Frontend startup script
├── PROJECT_OVERVIEW.md # Detailed project overview
└── README.md # This file
- Python 3.8+ (for backend)
- Node.js 18+ (for frontend)
- npm or yarn (for frontend dependencies)
-
Clone the repository:
git clone <your-repo-url> cd Beer-ML
-
Set up environment variables:
# Copy example files and customize cp .env.example .env.local cp .env.local.example beer-ml-frontend/.env.local # Edit the files to match your network configuration
-
Start the backend:
cd backend ./start.shThe backend will be available at
http://localhost:5000 -
Start the frontend (in a new terminal):
cd Beer-ML # back to root directory ./start-frontend.sh
The frontend will be available at
http://localhost:3000
cd backend
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python3 app.py # For development
# OR for production:
gunicorn -w 4 -b 0.0.0.0:5000 app:appcd beer-ml-frontend
npm install
npm run build
npm start -- --hostname 0.0.0.0 --port 3000- Use App Router with TypeScript
- Implement pages for:
/profile,/preferences,/dashboard,/beer_[id],/complete - Use React Context or Zustand for state management
- Include map picker using Leaflet.js for location selection
- Send complete JSON payload with each beer rating
- Individual beer pages allow flexible rating order
The BeerRating table stores each beer rating as a separate row:
CREATE TABLE beer_ratings (
id INTEGER PRIMARY KEY AUTOINCREMENT,
beer_name VARCHAR(100) NOT NULL,
rating INTEGER NOT NULL,
age INTEGER NOT NULL,
gender VARCHAR(20) NOT NULL,
latitude FLOAT NOT NULL,
longitude FLOAT NOT NULL,
white_dark INTEGER NOT NULL,
curry_soup INTEGER NOT NULL,
lemon_vanilla INTEGER NOT NULL,
salmon_chicken INTEGER NOT NULL,
cucumber_pumpkin INTEGER NOT NULL,
espresso_latte INTEGER NOT NULL,
chili_risotto INTEGER NOT NULL,
grapefruit_banana INTEGER NOT NULL,
cheese_mozzarella INTEGER NOT NULL,
almonds_honey INTEGER NOT NULL,
submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP
);Accepts a complete beer rating with user profile and preferences.
Request Body:
{
"beer_name": "Pale Ale",
"rating": 8,
"age": 27,
"gender": "female",
"latitude": 52.5200,
"longitude": 13.4050,
"white_dark": 7,
"curry_soup": 3,
"lemon_vanilla": 5,
"salmon_chicken": 2,
"cucumber_pumpkin": 8,
"espresso_latte": 6,
"chili_risotto": 4,
"grapefruit_banana": 7,
"cheese_mozzarella": 1,
"almonds_honey": 9
}Response:
{
"status": "ok",
"id": 123
}Health check endpoint.
Returns basic statistics about collected data.
- White chocolate ↔ Dark chocolate
- Curry ↔ Potato soup
- Lemon sorbet ↔ Vanilla ice cream
- Smoked salmon ↔ Grilled chicken
- Pickled cucumbers ↔ Roasted pumpkin
- Espresso ↔ Café latte
- Chili con carne ↔ Mushroom risotto
- Grapefruit ↔ Banana
- Blue cheese ↔ Fresh mozzarella
- Salted almonds ↔ Honey-glazed nuts
- Pale Ale
- IPA (India Pale Ale)
- Lager
- Pilsner
- Wheat Beer
- Stout
- Porter
- Belgian Blonde
- Saison
- Amber Ale
-
Find your laptop's IP address:
# Linux/Mac ip addr show | grep inet # or ifconfig | grep inet
-
Update environment variables:
- Frontend: Update
NEXT_PUBLIC_API_URLin.env.local - Backend: No changes needed (binds to 0.0.0.0)
- Frontend: Update
-
Start both services:
# Terminal 1 - Backend cd backend && ./start.sh # Terminal 2 - Frontend ./start-frontend.sh
-
Access from other devices:
- Frontend:
http://<your-ip>:3000 - Backend API:
http://<your-ip>:5000
- Frontend:
- Concurrency: Configured for ~20 concurrent users with 4 Gunicorn workers
- Database: SQLite with WAL mode enables concurrent reads/writes
- Security: Add HTTPS, rate limiting, and input sanitization for production
- Monitoring: Consider adding logging and error tracking
The SQLite database can be analyzed directly with SQL queries:
-- Total users and ratings
SELECT COUNT(DISTINCT age, gender, latitude, longitude) as unique_users,
COUNT(*) as total_ratings
FROM beer_ratings;
-- Average ratings by beer type
SELECT beer_name, AVG(rating) as avg_rating, COUNT(*) as rating_count
FROM beer_ratings
GROUP BY beer_name
ORDER BY avg_rating DESC;
-- Age distribution
SELECT
CASE
WHEN age < 25 THEN '18-24'
WHEN age < 35 THEN '25-34'
WHEN age < 45 THEN '35-44'
WHEN age < 55 THEN '45-54'
ELSE '55+'
END as age_group,
COUNT(*) as count
FROM beer_ratings
GROUP BY age_group;-
Port already in use:
# Kill processes on specific ports lsof -ti:3000 | xargs kill -9 # Frontend lsof -ti:5000 | xargs kill -9 # Backend
-
Database locked errors:
- Ensure WAL mode is enabled
- Check file permissions on
backend/beer_study.db
-
Map not loading:
- Check network connectivity
- Verify Leaflet CSS is loaded
-
API connection issues:
- Verify backend is running on port 5000
- Check
NEXT_PUBLIC_API_URLin frontend environment
- Hot reload: Use
npm run devinstead ofnpm startfor frontend development - Backend debugging: Set
debug=Trueinapp.pyfor development - Database inspection: Use SQLite browser tools to inspect data
This project is created for research purposes. Please ensure compliance with data protection regulations when collecting user data.
- Fork the repository
- Create a feature branch
- Make your changes
- Test thoroughly
- Submit a pull request
For technical issues or questions, please create an issue in the project repository.