mg643 commited on
Commit
bb231cc
·
2 Parent(s): f1406ff8d29213

Add HF config

Browse files
Files changed (2) hide show
  1. .gitattributes +35 -0
  2. README.md +7 -591
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,592 +1,8 @@
1
- # BrewMatch
2
-
3
- A machine learning-powered coffee recommendation system that matches users with coffee beans based on their taste
4
- preferences. Built for the Computer Vision module project, this system implements three distinct modeling approaches and
5
- includes a production-ready Flask API.
6
-
7
- ## Table of Contents
8
-
9
- - [Overview](#overview)
10
- - [Installation](#installation)
11
- - [Quick Start](#quick-start)
12
- - [Project Structure](#project-structure)
13
- - [Data Pipeline](#data-pipeline)
14
- - [Models](#models)
15
- - [Evaluation](#evaluation)
16
- - [Experiment: Sensitivity Analysis](#experiment-sensitivity-analysis)
17
- - [API Reference](#api-reference)
18
- - [Deployment](#deployment)
19
-
20
- ## Overview
21
-
22
- BrewMatch recommends coffee beans by learning taste profile similarities from the Coffee Quality Institute (CQI)dataset.
23
- Given a user's preferred taste characteristics (aroma, flavor, acidity, body, etc.), the system finds coffees with
24
- matching profiles.
25
-
26
- ### Key Features
27
-
28
- - **Three modeling approaches**: Naive baseline, classical ML (KNN), and deep learning (neural embeddings)
29
- - **Comprehensive evaluation**: Precision@K, Recall@K, NDCG@K, MSE, MAE
30
- - **Error analysis**: Identifies mispredictions, patterns, and mitigation strategies
31
- - **Sensitivity analysis experiment**: Measures performance vs. training set size
32
- - **Production-ready API**: Flask REST API with validation and error handling
33
-
34
- ### Taste Profile Features
35
-
36
- The system uses 9 sensory evaluation scores (0-10 scale):
37
-
38
- | Feature | Description |
39
- |------------|--------------------------------------------------------|
40
- | Aroma | Scent/fragrance of the coffee |
41
- | Flavor | Overall taste including sweetness, bitterness, acidity |
42
- | Aftertaste | Lingering taste after swallowing |
43
- | Acidity | Brightness and liveliness of taste |
44
- | Body | Thickness/viscosity of the coffee |
45
- | Balance | How well flavor components work together |
46
- | Uniformity | Consistency from cup to cup |
47
- | Clean Cup | Absence of off-flavors or defects |
48
- | Sweetness | Caramel-like, fruity, or floral notes |
49
-
50
- ## Installation
51
-
52
- ### Prerequisites
53
-
54
- - Python 3.13+
55
- - [uv](https://docs.astral.sh/uv/) package manager
56
- - GPU (optional): NVIDIA CUDA or Apple Silicon MPS for faster training
57
- - Kaggle account (for dataset download)
58
-
59
- ### Setup
60
-
61
- 1. **Clone the repository**
62
- ```bash
63
- git clone https://github.com/MrinalGoel643/BrewMatch.git
64
- cd BrewMatch
65
- ```
66
-
67
- 2. **Install dependencies**
68
- ```bash
69
- # CPU-only or Apple Silicon (MPS)
70
- uv sync
71
-
72
- # With NVIDIA CUDA support
73
- uv sync --extra cuda
74
- ```
75
-
76
- 3. **Configure Kaggle credentials**
77
-
78
- Create `~/.kaggle/kaggle.json` with your API credentials:
79
- ```json
80
- {"username": "your_username", "key": "your_api_key"}
81
- ```
82
-
83
- Get your API key from [Kaggle Account Settings](https://www.kaggle.com/settings/account).
84
-
85
- ## Quick Start
86
-
87
- ```bash
88
- # 1. Download the CQI coffee dataset
89
- uv run download
90
-
91
- # 2. Preprocess the data
92
- uv run preprocess
93
-
94
- # 3. Train all models (with default hyperparameters)
95
- uv run train
96
-
97
- # 4. OR: Tune hyperparameters first, then train (recommended)
98
- uv run train --tune
99
-
100
- # 5. Evaluate model performance
101
- uv run evaluate --error-analysis
102
-
103
- # 6. Start the API server
104
- uv run serve
105
- ```
106
-
107
- ## Project Structure
108
-
109
- ```
110
- brewmatch/
111
- ├── pyproject.toml # Project config and dependencies
112
- ├── README.md # This file
113
- ├── data/
114
- │ ├── raw/ # Downloaded CSV files
115
- │ └── processed/ # Train/test parquet + scaler
116
- ├── models/
117
- │ └── checkpoints/ # Saved model files
118
- ├── experiments/ # Experiment results and plots
119
- └── src/brewmatch/
120
- ├── __init__.py
121
- ├── config.py # Configuration settings
122
- ├── device.py # Device detection (CUDA/MPS/CPU)
123
- ├── utils.py # Utility functions
124
- ├── train.py # Training script (includes Optuna tuning)
125
- ├── evaluate.py # Evaluation script
126
- ├── experiment.py # Sensitivity analysis
127
- ├── data/
128
- │ ├── __init__.py
129
- │ ├── download.py # Kaggle dataset downloader
130
- │ ├── preprocess.py # Data cleaning and splitting
131
- │ └── dataset.py # PyTorch Dataset classes
132
- ├── models/
133
- │ ├── __init__.py
134
- │ ├── base.py # Abstract base class
135
- │ ├── baseline.py # Naive baseline recommender
136
- │ ├── classical.py # KNN/cosine similarity
137
- │ └── neural.py # Neural embedding model
138
- ├── evaluation/
139
- │ ├��─ __init__.py
140
- │ ├── metrics.py # Ranking and regression metrics
141
- │ └── error_analysis.py # Error pattern detection
142
- └── api/
143
- ├── __init__.py
144
- ├── app.py # Flask application
145
- └── schemas.py # Request validation
146
- ```
147
-
148
- ## Data Pipeline
149
-
150
- ### Download Dataset
151
-
152
- ```bash
153
- uv run download [--force]
154
- ```
155
-
156
- Downloads the [CQI Coffee Quality Database](https://www.kaggle.com/datasets/volpatto/coffee-quality-database-from-cqi)
157
- from Kaggle
158
- to `data/raw/`. This dataset contains ~1,340 coffee samples (Arabica + Robusta) with sensory evaluations.
159
-
160
- | Option | Description |
161
- |-----------|---------------------------------|
162
- | `--force` | Re-download even if data exists |
163
-
164
- ### Preprocess Data
165
-
166
- ```bash
167
- uv run preprocess [--test-size 0.2] [--seed 42]
168
- ```
169
-
170
- Processes raw data and creates train/test splits:
171
-
172
- 1. Loads CSV files from `data/raw/`
173
- 2. Selects taste features and metadata columns
174
- 3. Drops rows with missing quality scores
175
- 4. Normalizes features using StandardScaler (fit on train only)
176
- 5. Splits data 80/20 train/test
177
- 6. Saves to `data/processed/`:
178
- - `train.parquet` - Training data
179
- - `test.parquet` - Test data
180
- - `scaler.pkl` - Fitted scaler for inference
181
-
182
- | Option | Description |
183
- |---------------|-----------------------------------------------|
184
- | `--test-size` | Fraction for test set (default: 0.2) |
185
- | `--seed` | Random seed for reproducibility (default: 42) |
186
-
187
- ## Models
188
-
189
- ### 1. Naive Baseline (`NaiveBaselineRecommender`)
190
-
191
- Establishes a performance floor using simple heuristics.
192
-
193
- **Strategies:**
194
-
195
- - `mean`: Recommends coffees closest to the global mean taste profile (ignores user preferences)
196
- - `weighted_random`: Random sampling weighted by Total Cup Points
197
-
198
- **When to use:** Sanity check; any useful model should beat this.
199
-
200
- ### 2. Classical ML (`ClassicalMLRecommender`)
201
-
202
- Uses traditional similarity-based methods.
203
-
204
- **Methods:**
205
-
206
- - `knn`: K-Nearest Neighbors with Euclidean distance (sklearn NearestNeighbors)
207
- - `cosine`: Cosine similarity ranking
208
-
209
- **Features:**
210
-
211
- - Optional feature normalization via StandardScaler
212
- - Configurable number of neighbors
213
-
214
- **When to use:** Fast inference, interpretable results, works well with small datasets.
215
-
216
- ### 3. Neural Network (`NeuralRecommender`)
217
-
218
- Learns taste embeddings via contrastive learning.
219
-
220
- **Architecture:**
221
-
222
- - MLP encoder with residual connections
223
- - Maps 9 taste features to 32-dimensional embedding space
224
- - L2-normalized embeddings for cosine similarity
225
-
226
- **Training:**
227
-
228
- - Triplet loss with margin
229
- - AdamW optimizer with cosine annealing
230
- - Automatic positive/negative mining based on taste distance
231
-
232
- **When to use:** Best performance with sufficient data; captures non-linear relationships.
233
-
234
- ### Training Models
235
-
236
- ```bash
237
- uv run train [--models baseline classical neural] [--device cuda]
238
- ```
239
-
240
- | Option | Description |
241
- |------------|--------------------------------------------------------------|
242
- | `--models` | Models to train: `baseline`, `classical`, `neural`, or `all` |
243
- | `--device` | PyTorch device: `cuda` or `cpu` (auto-detected) |
244
-
245
- Models are saved to `models/checkpoints/`:
246
-
247
- - `baseline.pkl` - Pickled baseline model
248
- - `classical.pkl` - Pickled KNN model
249
- - `neural.pt` - PyTorch neural model
250
-
251
- ## Hyperparameter Tuning
252
-
253
- BrewMatch includes automated hyperparameter optimization using [Optuna](https://optuna.org/), a Bayesian optimization
254
- framework with tree-structured Parzen estimators (TPE). Tuning is integrated into the training script.
255
-
256
- ### Training Workflow
257
-
258
- ```bash
259
- # First run: uses default hyperparameters
260
- uv run train
261
-
262
- # Run with Optuna tuning (saves best params for future runs)
263
- uv run train --tune
264
-
265
- # Subsequent runs: automatically uses previously tuned hyperparameters
266
- uv run train
267
-
268
- # Re-tune anytime with --tune flag
269
- uv run train --tune --neural-trials 100
270
- ```
271
-
272
- | Option | Description |
273
- |----------------------|----------------------------------------------------------------|
274
- | `--tune` | Run Optuna tuning before training |
275
- | `--models` | Models to train/tune: `baseline`, `classical`, `neural`, `all` |
276
- | `--neural-trials` | Number of Optuna trials for neural network (default: 50) |
277
- | `--classical-trials` | Number of Optuna trials for classical ML (default: 30) |
278
- | `--cv-folds` | Cross-validation folds for tuning (default: 3) |
279
- | `--device` | PyTorch device: `cuda`, `mps`, or `cpu` (auto-detected) |
280
-
281
- ### Tuned Hyperparameters
282
-
283
- **Neural Network:**
284
-
285
- - `embedding_dim`: Embedding space dimension (16-128)
286
- - `hidden_dim`: Hidden layer size (32-256)
287
- - `learning_rate`: Adam learning rate (1e-4 to 1e-2, log scale)
288
- - `margin`: Triplet loss margin (0.1-1.0)
289
- - `batch_size`: Training batch size (16, 32, 64, 128)
290
-
291
- **Classical ML:**
292
-
293
- - `method`: Similarity method (`knn` or `cosine`)
294
- - `n_neighbors`: Number of neighbors for KNN (5-100)
295
- - `normalize`: Feature normalization (True/False)
296
-
297
- ### Outputs
298
-
299
- Tuned hyperparameters are saved to `models/checkpoints/hyperparameters.json` and automatically loaded on subsequent
300
- training runs
301
-
302
- ## Evaluation
303
-
304
- ### Metrics
305
-
306
- | Metric | Description |
307
- |-----------------|----------------------------------------------------------------------|
308
- | **Precision@K** | Proportion of top-K recommendations that are relevant |
309
- | **Recall@K** | Proportion of relevant items found in top-K |
310
- | **NDCG@K** | Normalized Discounted Cumulative Gain (rewards early relevant items) |
311
- | **MSE** | Mean Squared Error of taste profile predictions |
312
- | **MAE** | Mean Absolute Error of taste profile predictions |
313
-
314
- **Relevance definition:** A coffee is relevant if it shares the same country AND processing method as the query, OR has
315
- cosine similarity >= 0.95.
316
-
317
- ### Running Evaluation
318
-
319
- ```bash
320
- uv run evaluate [--models all] [--error-analysis] [--output results.json]
321
- ```
322
-
323
- | Option | Description |
324
- |--------------------|-----------------------------------------------------------------|
325
- | `--models` | Models to evaluate: `baseline`, `classical`, `neural`, or `all` |
326
- | `--error-analysis` | Generate detailed error analysis |
327
- | `--output` | Save results to JSON file |
328
-
329
- ### Error Analysis
330
-
331
- The error analysis module identifies:
332
-
333
- 1. **5 Worst Mispredictions** with root cause analysis:
334
- - Origin mismatch
335
- - Processing method mismatch
336
- - Large taste profile deviations
337
-
338
- 2. **Common Error Patterns**:
339
- - Failures by country of origin
340
- - Failures by processing method
341
- - Cross-origin confusion (e.g., confusing Ethiopia with Kenya)
342
- - Taste profile edge cases (high acidity, low body)
343
-
344
- 3. **Mitigation Strategies**:
345
- - Origin-aware embeddings
346
- - Processing method features
347
- - Contrastive learning for confused origins
348
- - Re-ranking stages
349
-
350
- ## Experiment: Sensitivity Analysis
351
-
352
- Investigates how model performance varies with training set size.
353
-
354
- ### Hypothesis
355
-
356
- Deep learning models benefit more from additional data, while classical models plateau earlier.
357
-
358
- ### Running the Experiment
359
-
360
- ```bash
361
- uv run experiment [--fractions 0.1 0.2 ... 1.0] [--trials 3] [--device cuda]
362
- ```
363
-
364
- | Option | Description |
365
- |----------------|----------------------------------------------------------|
366
- | `--fractions` | Training set fractions to test (default: 0.1 to 1.0) |
367
- | `--trials` | Trials per fraction for variance estimation (default: 3) |
368
- | `--device` | PyTorch device |
369
- | `--output-dir` | Directory for results (default: `experiments/`) |
370
-
371
- ### Outputs
372
-
373
- - `raw_results.json` - Per-trial metrics
374
- - `aggregated_results.csv` - Mean and std per model/fraction
375
- - `sensitivity_analysis.png` - Performance vs. training size plot
376
- - `sensitivity_analysis_multi.png` - Multi-metric comparison
377
- - `experiment_report.txt` - Text summary with findings
378
-
379
- ## API Reference
380
-
381
- ### Starting the Server
382
-
383
- ```bash
384
- uv run serve
385
- ```
386
-
387
- Or with environment variables:
388
-
389
- ```bash
390
- FLASK_HOST=0.0.0.0 FLASK_PORT=8000 FLASK_DEBUG=true uv run serve
391
- ```
392
-
393
- ### Endpoints
394
-
395
- #### Health Check
396
-
397
- ```http
398
- GET /health
399
- ```
400
-
401
- **Response:**
402
-
403
- ```json
404
- {
405
- "status": "healthy",
406
- "models_loaded": 3,
407
- "available_models": [
408
- "baseline",
409
- "classical",
410
- "neural"
411
- ]
412
- }
413
- ```
414
-
415
- #### List Models
416
-
417
- ```http
418
- GET /api/models
419
- ```
420
-
421
- **Response:**
422
-
423
- ```json
424
- {
425
- "models": [
426
- {
427
- "name": "baseline",
428
- "available": true,
429
- "is_fitted": true
430
- },
431
- {
432
- "name": "classical",
433
- "available": true,
434
- "is_fitted": true
435
- },
436
- {
437
- "name": "neural",
438
- "available": true,
439
- "is_fitted": true
440
- }
441
- ]
442
- }
443
- ```
444
-
445
- #### Get Recommendations
446
-
447
- ```http
448
- POST /api/recommend
449
- Content-Type: application/json
450
-
451
- {
452
- "preferences": {
453
- "aroma": 8.0,
454
- "flavor": 7.5,
455
- "aftertaste": 7.0,
456
- "acidity": 7.5,
457
- "body": 8.0,
458
- "balance": 7.5,
459
- "uniformity": 10.0,
460
- "clean_cup": 10.0,
461
- "sweetness": 10.0
462
- },
463
- "model": "neural",
464
- "k": 5
465
- }
466
- ```
467
-
468
- **Response:**
469
-
470
- ```json
471
- {
472
- "recommendations": [
473
- {
474
- "id": 42,
475
- "similarity": 0.95,
476
- "scores": {
477
- "aroma": 7.92,
478
- "flavor": 7.58
479
- },
480
- "country": "Ethiopia",
481
- "metadata": {}
482
- }
483
- ],
484
- "model_used": "neural",
485
- "k": 5
486
- }
487
- ```
488
-
489
- | Field | Type | Description |
490
- |---------------|---------|--------------------------------------------------------------------|
491
- | `preferences` | object | Required. All 9 taste features (0-10 scale) |
492
- | `model` | string | Optional. `baseline`, `classical`, or `neural` (default: `neural`) |
493
- | `k` | integer | Optional. Number of recommendations (1-100, default: 5) |
494
-
495
- #### Get Coffee Details
496
-
497
- ```http
498
- GET /api/coffee/{id}
499
- ```
500
-
501
- **Response:**
502
-
503
- ```json
504
- {
505
- "id": 42,
506
- "metadata": {
507
- "Country.of.Origin": "Ethiopia",
508
- "Processing.Method": "Washed / Wet"
509
- },
510
- "taste_profile": {
511
- "aroma": 7.92
512
- }
513
- }
514
- ```
515
-
516
- #### Get Statistics
517
-
518
- ```http
519
- GET /api/stats
520
- ```
521
-
522
- **Response:**
523
-
524
- ```json
525
- {
526
- "total_coffees": 1200,
527
- "models": {
528
- "baseline": {
529
- "is_fitted": true,
530
- "training_samples": 960
531
- },
532
- "classical": {
533
- "is_fitted": true,
534
- "training_samples": 960
535
- },
536
- "neural": {
537
- "is_fitted": true,
538
- "training_samples": 960
539
- }
540
- }
541
- }
542
- ```
543
-
544
- ### Error Responses
545
-
546
- | Status | Description |
547
- |--------|-------------------------------------------|
548
- | 400 | Validation error (missing/invalid fields) |
549
- | 404 | Resource not found |
550
- | 503 | No models loaded |
551
- | 500 | Internal server error |
552
-
553
- ## Deployment
554
-
555
- ### Production with Gunicorn
556
-
557
- ```bash
558
- uv run gunicorn "brewmatch.api.app:create_app()" \
559
- --bind 0.0.0.0:8000 \
560
- --workers 4 \
561
- --timeout 120
562
- ```
563
-
564
- ### Docker
565
-
566
- ```dockerfile
567
- FROM python:3.13-slim
568
-
569
- WORKDIR /app
570
- COPY . .
571
-
572
- RUN pip install uv && uv sync --frozen
573
-
574
- # Download and preprocess data, train models
575
- RUN uv run download && uv run preprocess && uv run train
576
-
577
- EXPOSE 8000
578
- CMD ["uv", "run", "gunicorn", "brewmatch.api.app:create_app()", "--bind", "0.0.0.0:8000"]
579
- ```
580
-
581
- ### Environment Variables
582
-
583
- | Variable | Description | Default |
584
- |---------------|---------------------|-------------|
585
- | `FLASK_HOST` | Server bind address | `127.0.0.1` |
586
- | `FLASK_PORT` | Server port | `5000` |
587
- | `FLASK_DEBUG` | Enable debug mode | `false` |
588
-
589
  ---
590
-
591
- **Dataset:** [Coffee Quality Database (CQI)](https://www.kaggle.com/datasets/volpatto/coffee-quality-database-from-cqi)
592
- by Diego Volpatto
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: BrewMatch API
3
+ emoji:
4
+ colorFrom: brown
5
+ colorTo: beige
6
+ sdk: docker
7
+ pinned: false
8
+ ---