Mauro Carlucci commited on
Commit
36092d6
·
unverified ·
2 Parent(s): 76ac0e3 c654a00

Merge pull request #37 from se4ai2526-uniba/aggiornamento-doc

Browse files

Refactor repository structure for Docker integration and update documentation

.github/workflows/ci.yml CHANGED
@@ -86,8 +86,34 @@ jobs:
86
 
87
  - name: Pull Models with DVC
88
  run: |
89
- dvc pull models/random_forest_embedding_gridsearch.pkl models/label_names.pkl
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
 
91
  - name: Build Docker Image
92
  run: |
93
- docker build . -t hopcroft-app:latest
 
86
 
87
  - name: Pull Models with DVC
88
  run: |
89
+ # Clean any potentially corrupted cache
90
+ rm -rf .dvc/cache/files/md5/e1 || true
91
+
92
+ # Retry logic for DVC pull to handle intermittent server errors
93
+ max_attempts=3
94
+ attempt=0
95
+ until [ $attempt -ge $max_attempts ]
96
+ do
97
+ dvc pull models/random_forest_embedding_gridsearch.pkl models/label_names.pkl && break
98
+ attempt=$((attempt+1))
99
+ echo "DVC pull attempt $attempt failed. Retrying in 10 seconds..."
100
+ # Clean cache on retry
101
+ rm -rf .dvc/cache || true
102
+ sleep 10
103
+ done
104
+ if [ $attempt -ge $max_attempts ]; then
105
+ echo "DVC pull failed after $max_attempts attempts"
106
+ exit 1
107
+ fi
108
+
109
+ - name: Verify Models Downloaded
110
+ run: |
111
+ if [ ! -f "models/random_forest_embedding_gridsearch.pkl" ] || [ ! -f "models/label_names.pkl" ]; then
112
+ echo "ERROR: Required model files not found after DVC pull"
113
+ exit 1
114
+ fi
115
+ echo "All required model files present"
116
 
117
  - name: Build Docker Image
118
  run: |
119
+ docker build -t hopcroft-app:latest .
Dockerfile CHANGED
@@ -59,14 +59,14 @@ COPY --chown=user:user . .
59
  RUN chown -R user:user /app
60
 
61
  # Fix line endings and permissions for the start script
62
- RUN dos2unix scripts/start_space.sh && \
63
- chmod +x scripts/start_space.sh
64
 
65
  # Install the project itself
66
  RUN pip install --no-cache-dir .
67
 
68
  # Make start script executable
69
- RUN chmod +x scripts/start_space.sh
70
 
71
  # Switch to non-root user
72
  USER user
@@ -75,4 +75,4 @@ USER user
75
  EXPOSE 7860
76
 
77
  # Command to run the application
78
- CMD ["./scripts/start_space.sh"]
 
59
  RUN chown -R user:user /app
60
 
61
  # Fix line endings and permissions for the start script
62
+ RUN dos2unix docker/scripts/start_space.sh && \
63
+ chmod +x docker/scripts/start_space.sh
64
 
65
  # Install the project itself
66
  RUN pip install --no-cache-dir .
67
 
68
  # Make start script executable
69
+ RUN chmod +x docker/scripts/start_space.sh
70
 
71
  # Switch to non-root user
72
  USER user
 
75
  EXPOSE 7860
76
 
77
  # Command to run the application
78
+ CMD ["./docker/scripts/start_space.sh"]
README.md CHANGED
@@ -249,7 +249,7 @@ make test-api-list # List predictions
249
  #### Docker
250
  Build and run the API in a container:
251
  ```bash
252
- docker build -t hopcroft-api .
253
  docker run --rm --name hopcroft-api -p 8080:8080 hopcroft-api
254
  ```
255
 
@@ -257,6 +257,80 @@ Endpoints:
257
  - Swagger UI: [http://localhost:8080/docs](http://localhost:8080/docs)
258
  - Health check: [http://localhost:8080/health](http://localhost:8080/health)
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  ---
261
 
262
  ## Docker Compose Usage
@@ -283,7 +357,7 @@ Docker Compose orchestrates both the **API backend** and **Streamlit GUI** servi
283
  #### 1. Build and Start All Services
284
  Build both images and start the containers:
285
  ```bash
286
- docker-compose up -d --build
287
  ```
288
 
289
  | Flag | Description |
@@ -299,7 +373,7 @@ docker-compose up -d --build
299
  #### 2. Stop All Services
300
  Stop and remove containers and networks:
301
  ```bash
302
- docker-compose down
303
  ```
304
 
305
  | Flag | Description |
@@ -310,19 +384,19 @@ docker-compose down
310
  #### 3. Restart Services
311
  After updating `.env` or configuration files:
312
  ```bash
313
- docker-compose restart
314
  ```
315
 
316
  Or for a full restart with environment reload:
317
  ```bash
318
- docker-compose down
319
- docker-compose up -d
320
  ```
321
 
322
  #### 4. Check Status
323
  View the status of all running services:
324
  ```bash
325
- docker-compose ps
326
  ```
327
 
328
  Or use Docker commands:
@@ -333,13 +407,13 @@ docker ps
333
  #### 5. View Logs
334
  Tail logs from both services in real-time:
335
  ```bash
336
- docker-compose logs -f
337
  ```
338
 
339
  View logs from a specific service:
340
  ```bash
341
- docker-compose logs -f hopcroft-api
342
- docker-compose logs -f hopcroft-gui
343
  ```
344
 
345
  | Flag | Description |
@@ -350,8 +424,8 @@ docker-compose logs -f hopcroft-gui
350
  #### 6. Execute Commands in Container
351
  Open an interactive shell inside a running container:
352
  ```bash
353
- docker-compose exec hopcroft-api /bin/bash
354
- docker-compose exec hopcroft-gui /bin/bash
355
  ```
356
 
357
  Examples of useful commands inside the API container:
@@ -375,9 +449,9 @@ printenv | grep MLFLOW
375
  **Docker Compose orchestrates two services:**
376
 
377
  ```
378
- docker-compose.yml
379
  ├── hopcroft-api (FastAPI Backend)
380
- │ ├── Build: ./Dockerfile
381
  │ ├── Port: 8080:8080
382
  │ ├── Network: hopcroft-net
383
  │ ├── Environment: .env (MLflow credentials)
@@ -387,7 +461,7 @@ docker-compose.yml
387
  │ └── Health Check: /health endpoint
388
 
389
  ├── hopcroft-gui (Streamlit Frontend)
390
- │ ├── Build: ./Dockerfile.streamlit
391
  │ ├── Port: 8501:8501
392
  │ ├── Network: hopcroft-net
393
  │ ├── Environment: API_BASE_URL=http://hopcroft-api:8080
@@ -409,7 +483,7 @@ docker-compose.yml
409
 
410
  **hopcroft-api (FastAPI Backend)**
411
  - Purpose: FastAPI backend serving the ML model for skill classification
412
- - Image: Built from `Dockerfile`
413
  - Port: 8080 (maps to host 8080)
414
  - Features:
415
  - Random Forest model with embedding features
@@ -419,7 +493,7 @@ docker-compose.yml
419
 
420
  **hopcroft-gui (Streamlit Frontend)**
421
  - Purpose: Streamlit web interface for interactive predictions
422
- - Image: Built from `Dockerfile.streamlit`
423
  - Port: 8501 (maps to host 8501)
424
  - Features:
425
  - User-friendly interface for skill prediction
@@ -441,7 +515,7 @@ docker-compose.yml
441
  - Use Dockerfile's CMD
442
  - GUI → API via Docker network
443
 
444
- For **production deployment**, modify `docker-compose.yml` to remove bind mounts and disable reload.
445
 
446
  ### Troubleshooting
447
 
@@ -450,19 +524,19 @@ For **production deployment**, modify `docker-compose.yml` to remove bind mounts
450
  1. Wait 30-60 seconds for API to fully initialize and become healthy
451
  2. Refresh the GUI page (F5)
452
  3. Check API health: `curl http://localhost:8080/health`
453
- 4. Check logs: `docker-compose logs hopcroft-api`
454
 
455
  #### Issue: "500 Internal Server Error" on predictions
456
  **Solution:**
457
  1. Verify MLflow credentials in `.env` are correct
458
- 2. Restart services: `docker-compose down && docker-compose up -d`
459
  3. Check environment variables: `docker exec hopcroft-api printenv | grep MLFLOW`
460
 
461
  #### Issue: Changes to code not reflected
462
  **Solution:**
463
  - For Python code changes: Auto-reload is enabled, wait a few seconds
464
- - For Dockerfile changes: Rebuild with `docker-compose up -d --build`
465
- - For `.env` changes: Restart with `docker-compose down && docker-compose up -d`
466
 
467
  #### Issue: Port already in use
468
  **Solution:**
@@ -472,9 +546,9 @@ netstat -ano | findstr :8080
472
  netstat -ano | findstr :8501
473
 
474
  # Stop existing containers
475
- docker-compose down
476
 
477
- # Or change ports in docker-compose.yml
478
  ```
479
 
480
 
@@ -498,12 +572,12 @@ To enable the application to pull models from DagsHub via DVC, you must configur
498
  | `DAGSHUB_TOKEN` | Secret | Your DagsHub access token (Settings -> Tokens). |
499
 
500
  > [!IMPORTANT]
501
- > These secrets are injected into the container at runtime. The `scripts/start_space.sh` script uses them to authenticate DVC and pull the required model files (`.pkl`) before starting the API and GUI.
502
 
503
  ### 3. Automated Startup
504
  The deployment follows this automated flow:
505
- 1. **Dockerfile**: Builds the environment, installs dependencies, and sets up Nginx.
506
- 2. **scripts/start_space.sh**:
507
  - Configures DVC with your secrets.
508
  - Pulls models from the DagsHub remote.
509
  - Starts the **FastAPI** backend (port 8000).
@@ -531,7 +605,7 @@ The Streamlit GUI provides an interactive web interface for the skill classifica
531
  - Responsive design
532
 
533
  ### Usage
534
- 1. Ensure both services are running: `docker-compose up -d`
535
  2. Open the GUI in your browser: [http://localhost:8501](http://localhost:8501)
536
  3. Enter a GitHub issue description in the text area
537
  4. Click "Predict Skills" to get predictions
 
249
  #### Docker
250
  Build and run the API in a container:
251
  ```bash
252
+ docker build -f docker/Dockerfile -t hopcroft-api .
253
  docker run --rm --name hopcroft-api -p 8080:8080 hopcroft-api
254
  ```
255
 
 
257
  - Swagger UI: [http://localhost:8080/docs](http://localhost:8080/docs)
258
  - Health check: [http://localhost:8080/health](http://localhost:8080/health)
259
 
260
+ ### Milestone 5 (Deployment)
261
+ We implemented a complete containerized deployment pipeline for production-ready delivery:
262
+
263
+ 1. **Docker Containerization**
264
+ - `docker/Dockerfile`: Multi-stage Python 3.10 slim image with non-root user, system dependencies (git, nginx, curl), DVC integration, and automated startup script.
265
+ - `docker/Dockerfile.streamlit`: Lightweight container for Streamlit GUI with minimal dependencies.
266
+ - `docker/.dockerignore`: Optimized build context excluding unnecessary files.
267
+
268
+ 2. **Docker Compose Orchestration**
269
+ - Multi-service architecture: API backend (`hopcroft-api`), Streamlit frontend (`hopcroft-gui`), and monitoring stack.
270
+ - Bridge network (`hopcroft-net`) for inter-service communication.
271
+ - Health checks with automatic restart policies.
272
+ - Bind mounts for development hot-reload, named volumes for persistent storage (`hopcroft-logs`).
273
+
274
+ 3. **Hugging Face Spaces Deployment**
275
+ - Docker SDK configuration with port 7860.
276
+ - `docker/scripts/start_space.sh`: Automated startup script that configures DVC credentials, pulls models from DagsHub, and starts FastAPI + Streamlit + Nginx.
277
+ - Secrets management via HF Spaces Variables (`DAGSHUB_USERNAME`, `DAGSHUB_TOKEN`).
278
+ - Live deployment: `https://huggingface.co/spaces/se4ai2526-uniba/Hopcroft`
279
+
280
+ 4. **Nginx Reverse Proxy**
281
+ - `docker/nginx.conf`: Routes traffic to API (port 8000) and Streamlit (port 8501) on single port 7860.
282
+ - Path-based routing for API docs, metrics, and web interface.
283
+
284
+ 5. **Environment Configuration**
285
+ - `.env.example` template with MLflow and DagsHub credentials.
286
+ - Automatic environment variable injection via `env_file` directive.
287
+
288
+ ### Milestone 6 (Monitoring)
289
+ We implemented comprehensive observability and load testing infrastructure:
290
+
291
+ 1. **Prometheus Metrics Collection**
292
+ - `prometheus.yml`: Scrape configuration for API metrics (10s interval), self-monitoring, and Pushgateway.
293
+ - Custom metrics: `hopcroft_requests_total`, `hopcroft_request_duration_seconds`, `hopcroft_in_progress_requests`, `hopcroft_prediction_processing_seconds`.
294
+ - PromQL queries for request rate, latency percentiles, and in-progress tracking.
295
+
296
+ 2. **Grafana Dashboards**
297
+ - Auto-provisioned datasources and dashboards via `provisioning/` directory.
298
+ - `hopcroft_dashboard.json`: Real-time visualization of API request rate, latency, drift status, and p-value metrics.
299
+ - Credentials: `admin/admin` on port 3000.
300
+
301
+ 3. **Alerting System**
302
+ - `alert_rules.yml`: Prometheus alert rules for `ServiceDown`, `HighErrorRate` (>10% 5xx), `SlowRequests` (p95 > 2s).
303
+ - Alertmanager configuration with severity-based routing and inhibition rules.
304
+ - Webhook integration for alert notifications.
305
+
306
+ 4. **Data Drift Detection**
307
+ - `prepare_baseline.py`: Extracts 1000-sample reference dataset from training data.
308
+ - `run_drift_check.py`: Kolmogorov-Smirnov two-sample test with Bonferroni correction (p < 0.05).
309
+ - Metrics pushed to Pushgateway: `drift_detected`, `drift_p_value`, `drift_distance`, `drift_check_timestamp`.
310
+ - JSON reports saved to `monitoring/drift/reports/`.
311
+
312
+ 5. **Locust Load Testing**
313
+ - `locustfile.py`: Simulated user behavior with weighted tasks (60% single prediction, 20% batch, 20% monitoring).
314
+ - Configurable wait times (1-5s) for realistic traffic simulation.
315
+ - Web UI on port 8089, headless mode support, CSV export for results.
316
+ - Pre-configured for HF Spaces and local Docker environments.
317
+
318
+ 6. **Uptime Monitoring (Better Stack)**
319
+ - External monitoring of production endpoints (`/health`, `/openapi.json`, `/docs`).
320
+ - Multi-location checks with email notifications.
321
+ - Incident tracking and resolution screenshots in `monitoring/screenshots/`.
322
+
323
+ 7. **CI/CD Pipeline**
324
+ - `.github/workflows/ci.yml`: GitHub Actions workflow triggered on push/PR to main and feature branches.
325
+ - Jobs: Ruff linting, pytest unit tests with HTML reports, DVC model pulling, Docker image build.
326
+ - Secrets: `DAGSHUB_USERNAME`, `DAGSHUB_TOKEN` for model access.
327
+ - Disk space optimization for CI runner.
328
+
329
+ 8. **Pushgateway Integration**
330
+ - Collects metrics from short-lived jobs (drift detection scripts).
331
+ - Persistent storage with 5-minute intervals.
332
+ - Scraped by Prometheus for long-term storage and Grafana visualization.
333
+
334
  ---
335
 
336
  ## Docker Compose Usage
 
357
  #### 1. Build and Start All Services
358
  Build both images and start the containers:
359
  ```bash
360
+ docker compose -f docker/docker-compose.yml up -d --build
361
  ```
362
 
363
  | Flag | Description |
 
373
  #### 2. Stop All Services
374
  Stop and remove containers and networks:
375
  ```bash
376
+ docker compose -f docker/docker-compose.yml down
377
  ```
378
 
379
  | Flag | Description |
 
384
  #### 3. Restart Services
385
  After updating `.env` or configuration files:
386
  ```bash
387
+ docker compose -f docker/docker-compose.yml restart
388
  ```
389
 
390
  Or for a full restart with environment reload:
391
  ```bash
392
+ docker compose -f docker/docker-compose.yml down
393
+ docker compose -f docker/docker-compose.yml up -d
394
  ```
395
 
396
  #### 4. Check Status
397
  View the status of all running services:
398
  ```bash
399
+ docker compose -f docker/docker-compose.yml ps
400
  ```
401
 
402
  Or use Docker commands:
 
407
  #### 5. View Logs
408
  Tail logs from both services in real-time:
409
  ```bash
410
+ docker compose -f docker/docker-compose.yml logs -f
411
  ```
412
 
413
  View logs from a specific service:
414
  ```bash
415
+ docker compose -f docker/docker-compose.yml logs -f hopcroft-api
416
+ docker compose -f docker/docker-compose.yml logs -f hopcroft-gui
417
  ```
418
 
419
  | Flag | Description |
 
424
  #### 6. Execute Commands in Container
425
  Open an interactive shell inside a running container:
426
  ```bash
427
+ docker compose -f docker/docker-compose.yml exec hopcroft-api /bin/bash
428
+ docker compose -f docker/docker-compose.yml exec hopcroft-gui /bin/bash
429
  ```
430
 
431
  Examples of useful commands inside the API container:
 
449
  **Docker Compose orchestrates two services:**
450
 
451
  ```
452
+ docker/docker-compose.yml
453
  ├── hopcroft-api (FastAPI Backend)
454
+ │ ├── Build: docker/Dockerfile
455
  │ ├── Port: 8080:8080
456
  │ ├── Network: hopcroft-net
457
  │ ├── Environment: .env (MLflow credentials)
 
461
  │ └── Health Check: /health endpoint
462
 
463
  ├── hopcroft-gui (Streamlit Frontend)
464
+ │ ├── Build: docker/Dockerfile.streamlit
465
  │ ├── Port: 8501:8501
466
  │ ├── Network: hopcroft-net
467
  │ ├── Environment: API_BASE_URL=http://hopcroft-api:8080
 
483
 
484
  **hopcroft-api (FastAPI Backend)**
485
  - Purpose: FastAPI backend serving the ML model for skill classification
486
+ - Image: Built from `docker/Dockerfile`
487
  - Port: 8080 (maps to host 8080)
488
  - Features:
489
  - Random Forest model with embedding features
 
493
 
494
  **hopcroft-gui (Streamlit Frontend)**
495
  - Purpose: Streamlit web interface for interactive predictions
496
+ - Image: Built from `docker/Dockerfile.streamlit`
497
  - Port: 8501 (maps to host 8501)
498
  - Features:
499
  - User-friendly interface for skill prediction
 
515
  - Use Dockerfile's CMD
516
  - GUI → API via Docker network
517
 
518
+ For **production deployment**, modify `docker/docker-compose.yml` to remove bind mounts and disable reload.
519
 
520
  ### Troubleshooting
521
 
 
524
  1. Wait 30-60 seconds for API to fully initialize and become healthy
525
  2. Refresh the GUI page (F5)
526
  3. Check API health: `curl http://localhost:8080/health`
527
+ 4. Check logs: `docker compose -f docker/docker-compose.yml logs hopcroft-api`
528
 
529
  #### Issue: "500 Internal Server Error" on predictions
530
  **Solution:**
531
  1. Verify MLflow credentials in `.env` are correct
532
+ 2. Restart services: `docker compose -f docker/docker-compose.yml down && docker compose -f docker/docker-compose.yml up -d`
533
  3. Check environment variables: `docker exec hopcroft-api printenv | grep MLFLOW`
534
 
535
  #### Issue: Changes to code not reflected
536
  **Solution:**
537
  - For Python code changes: Auto-reload is enabled, wait a few seconds
538
+ - For Dockerfile changes: Rebuild with `docker compose -f docker/docker-compose.yml up -d --build`
539
+ - For `.env` changes: Restart with `docker compose -f docker/docker-compose.yml down && docker compose -f docker/docker-compose.yml up -d`
540
 
541
  #### Issue: Port already in use
542
  **Solution:**
 
546
  netstat -ano | findstr :8501
547
 
548
  # Stop existing containers
549
+ docker compose -f docker/docker-compose.yml down
550
 
551
+ # Or change ports in docker/docker-compose.yml
552
  ```
553
 
554
 
 
572
  | `DAGSHUB_TOKEN` | Secret | Your DagsHub access token (Settings -> Tokens). |
573
 
574
  > [!IMPORTANT]
575
+ > These secrets are injected into the container at runtime. The `docker/scripts/start_space.sh` script uses them to authenticate DVC and pull the required model files (`.pkl`) before starting the API and GUI.
576
 
577
  ### 3. Automated Startup
578
  The deployment follows this automated flow:
579
+ 1. **docker/Dockerfile**: Builds the environment, installs dependencies, and sets up Nginx.
580
+ 2. **docker/scripts/start_space.sh**:
581
  - Configures DVC with your secrets.
582
  - Pulls models from the DagsHub remote.
583
  - Starts the **FastAPI** backend (port 8000).
 
605
  - Responsive design
606
 
607
  ### Usage
608
+ 1. Ensure both services are running: `docker compose -f docker/docker-compose.yml up -d`
609
  2. Open the GUI in your browser: [http://localhost:8501](http://localhost:8501)
610
  3. Enter a GitHub issue description in the text area
611
  4. Click "Predict Skills" to get predictions
.dockerignore → docker/.dockerignore RENAMED
@@ -27,5 +27,5 @@ notebooks/
27
  reports/
28
  docs/
29
  tests/
30
- scripts/
31
- !scripts/start_space.sh
 
27
  reports/
28
  docs/
29
  tests/
30
+ docker/scripts/
31
+ !docker/scripts/start_space.sh
Dockerfile.streamlit → docker/Dockerfile.streamlit RENAMED
File without changes
docker/README.md ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Docker Configuration
2
+
3
+ This directory contains all Docker-related configuration files for the Hopcroft Skill Classification project.
4
+
5
+ ## Directory Structure
6
+
7
+ ```
8
+ docker/
9
+ ├── Dockerfile # Main API container (Python 3.10, FastAPI, Nginx)
10
+ ├── Dockerfile.streamlit # Streamlit GUI container
11
+ ├── docker-compose.yml # Multi-service orchestration
12
+ ├── nginx.conf # Reverse proxy configuration for HF Spaces
13
+ ├── .dockerignore # Build context exclusions
14
+ ├── scripts/
15
+ │ └── start_space.sh # Startup script for Hugging Face Spaces
16
+ └── README.md # This file
17
+ ```
18
+
19
+ ## File Descriptions
20
+
21
+ | File | Purpose |
22
+ |------|---------|
23
+ | `Dockerfile` | Main container with FastAPI API, DVC, Nginx. Used for HF Spaces deployment. |
24
+ | `Dockerfile.streamlit` | Lightweight Streamlit-only container for GUI service. |
25
+ | `docker-compose.yml` | Orchestrates API, GUI, Prometheus, Grafana, Alertmanager, Pushgateway. |
26
+ | `nginx.conf` | Reverse proxy routing traffic to API (8000) and Streamlit (8501) on port 7860. |
27
+ | `.dockerignore` | Excludes data/, tests/, docs/ etc. from Docker build context. |
28
+ | `start_space.sh` | Configures DVC, pulls models, starts FastAPI + Streamlit + Nginx. |
29
+
30
+ ## Quick Start
31
+
32
+ ### Local Development
33
+
34
+ ```bash
35
+ # From the docker/ directory
36
+ docker compose up -d --build
37
+
38
+ # Or from repository root
39
+ docker compose -f docker/docker-compose.yml up -d --build
40
+ ```
41
+
42
+ **Available Services:**
43
+ | Service | URL |
44
+ |---------|-----|
45
+ | API (FastAPI) | http://localhost:8080/docs |
46
+ | GUI (Streamlit) | http://localhost:8501 |
47
+ | Prometheus | http://localhost:9090 |
48
+ | Grafana | http://localhost:3000 (admin/admin) |
49
+ | Pushgateway | http://localhost:9091 |
50
+ | Alertmanager | http://localhost:9093 |
51
+
52
+ ### Stop Services
53
+
54
+ ```bash
55
+ docker compose -f docker/docker-compose.yml down
56
+ ```
57
+
58
+ ### View Logs
59
+
60
+ ```bash
61
+ docker compose -f docker/docker-compose.yml logs -f
62
+ ```
63
+
64
+ ## Building Individual Images
65
+
66
+ ```bash
67
+ # From repository root
68
+ docker build -f docker/Dockerfile -t hopcroft-api:latest .
69
+ docker build -f docker/Dockerfile.streamlit -t hopcroft-gui:latest .
70
+ ```
71
+
72
+ ## Environment Variables
73
+
74
+ The `.env` file in the repository root is automatically loaded. Required variables:
75
+
76
+ ```env
77
+ MLFLOW_TRACKING_URI=https://dagshub.com/se4ai2526-uniba/Hopcroft.mlflow
78
+ MLFLOW_TRACKING_USERNAME=your_username
79
+ MLFLOW_TRACKING_PASSWORD=your_token
80
+ ```
81
+
82
+ ## Hugging Face Spaces
83
+
84
+ The `Dockerfile` and `start_space.sh` are configured for HF Spaces deployment:
85
+ - Exposes port 7860 (HF Spaces requirement)
86
+ - Uses Nginx as reverse proxy
87
+ - Pulls models from DagsHub via DVC on startup
88
+
89
+ **Secrets required on HF Spaces:**
90
+ - `DAGSHUB_USERNAME`
91
+ - `DAGSHUB_TOKEN`
docker-compose.yml → docker/docker-compose.yml RENAMED
@@ -1,18 +1,18 @@
1
  services:
2
  hopcroft-api:
3
  build:
4
- context: .
5
  dockerfile: Dockerfile
6
  container_name: hopcroft-api
7
  ports:
8
  - "8080:8080"
9
  env_file:
10
- - .env
11
  environment:
12
  - PROJECT_NAME=Hopcroft
13
  volumes:
14
  # Bind mount: enables live code reloading for development
15
- - ./hopcroft_skill_classification_tool_competition:/app/hopcroft_skill_classification_tool_competition
16
  # Named volume: persistent storage for application logs
17
  - hopcroft-logs:/app/logs
18
  networks:
@@ -30,8 +30,8 @@ services:
30
 
31
  hopcroft-gui:
32
  build:
33
- context: .
34
- dockerfile: Dockerfile.streamlit
35
  container_name: hopcroft-gui
36
  ports:
37
  - "8501:8501"
@@ -39,7 +39,7 @@ services:
39
  - API_BASE_URL=http://hopcroft-api:8080
40
  volumes:
41
  # Bind mount for development hot-reload
42
- - ./hopcroft_skill_classification_tool_competition/streamlit_app.py:/app/streamlit_app.py
43
  networks:
44
  - hopcroft-net
45
  depends_on:
@@ -51,8 +51,8 @@ services:
51
  image: prom/prometheus:latest
52
  container_name: prometheus
53
  volumes:
54
- - ./monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
55
- - ./monitoring/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml
56
  ports:
57
  - "9090:9090"
58
  networks:
@@ -65,7 +65,7 @@ services:
65
  image: prom/alertmanager:latest
66
  container_name: alertmanager
67
  volumes:
68
- - ./monitoring/alertmanager/config.yml:/etc/alertmanager/config.yml
69
  ports:
70
  - "9093:9093"
71
  networks:
@@ -84,9 +84,9 @@ services:
84
  - GF_SERVER_ROOT_URL=http://localhost:3000
85
  volumes:
86
  # Provisioning: auto-configure datasources and dashboards
87
- - ./monitoring/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
88
- - ./monitoring/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards
89
- - ./monitoring/grafana/dashboards:/var/lib/grafana/dashboards
90
  # Persistent storage for Grafana data
91
  - grafana-data:/var/lib/grafana
92
  networks:
@@ -95,7 +95,7 @@ services:
95
  - prometheus
96
  restart: unless-stopped
97
  healthcheck:
98
- test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
99
  interval: 30s
100
  timeout: 10s
101
  retries: 3
@@ -115,7 +115,6 @@ services:
115
  volumes:
116
  - pushgateway-data:/data
117
 
118
-
119
  networks:
120
  hopcroft-net:
121
  driver: bridge
 
1
  services:
2
  hopcroft-api:
3
  build:
4
+ context: ..
5
  dockerfile: Dockerfile
6
  container_name: hopcroft-api
7
  ports:
8
  - "8080:8080"
9
  env_file:
10
+ - ../.env
11
  environment:
12
  - PROJECT_NAME=Hopcroft
13
  volumes:
14
  # Bind mount: enables live code reloading for development
15
+ - ../hopcroft_skill_classification_tool_competition:/app/hopcroft_skill_classification_tool_competition
16
  # Named volume: persistent storage for application logs
17
  - hopcroft-logs:/app/logs
18
  networks:
 
30
 
31
  hopcroft-gui:
32
  build:
33
+ context: ..
34
+ dockerfile: docker/Dockerfile.streamlit
35
  container_name: hopcroft-gui
36
  ports:
37
  - "8501:8501"
 
39
  - API_BASE_URL=http://hopcroft-api:8080
40
  volumes:
41
  # Bind mount for development hot-reload
42
+ - ../hopcroft_skill_classification_tool_competition/streamlit_app.py:/app/streamlit_app.py
43
  networks:
44
  - hopcroft-net
45
  depends_on:
 
51
  image: prom/prometheus:latest
52
  container_name: prometheus
53
  volumes:
54
+ - ../monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
55
+ - ../monitoring/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml
56
  ports:
57
  - "9090:9090"
58
  networks:
 
65
  image: prom/alertmanager:latest
66
  container_name: alertmanager
67
  volumes:
68
+ - ../monitoring/alertmanager/config.yml:/etc/alertmanager/config.yml
69
  ports:
70
  - "9093:9093"
71
  networks:
 
84
  - GF_SERVER_ROOT_URL=http://localhost:3000
85
  volumes:
86
  # Provisioning: auto-configure datasources and dashboards
87
+ - ../monitoring/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
88
+ - ../monitoring/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards
89
+ - ../monitoring/grafana/dashboards:/var/lib/grafana/dashboards
90
  # Persistent storage for Grafana data
91
  - grafana-data:/var/lib/grafana
92
  networks:
 
95
  - prometheus
96
  restart: unless-stopped
97
  healthcheck:
98
+ test: [ "CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1" ]
99
  interval: 30s
100
  timeout: 10s
101
  retries: 3
 
115
  volumes:
116
  - pushgateway-data:/data
117
 
 
118
  networks:
119
  hopcroft-net:
120
  driver: bridge
nginx.conf → docker/nginx.conf RENAMED
File without changes
{scripts → docker/scripts}/start_space.sh RENAMED
@@ -106,7 +106,7 @@ if ! command -v nginx &> /dev/null; then
106
  echo "$(date) - ERROR: nginx not found in PATH"
107
  exit 1
108
  fi
109
- nginx -c /app/nginx.conf -g "daemon off;" >> /tmp/nginx_startup.log 2>&1 &
110
 
111
  echo "$(date) - Waiting for Nginx to initialize..."
112
  sleep 5
 
106
  echo "$(date) - ERROR: nginx not found in PATH"
107
  exit 1
108
  fi
109
+ nginx -c /app/docker/nginx.conf -g "daemon off;" >> /tmp/nginx_startup.log 2>&1 &
110
 
111
  echo "$(date) - Waiting for Nginx to initialize..."
112
  sleep 5
tests/integration/test_feature_pipeline.py CHANGED
@@ -130,7 +130,8 @@ class TestDataFlowConsistency:
130
  "Normal clean text",
131
  ]
132
 
133
- features_dirty, _ = extract_tfidf_features(dirty_df, max_features=50)
 
134
 
135
  # Clean version
136
  clean_df = sample_dataframe.copy()
@@ -142,7 +143,8 @@ class TestDataFlowConsistency:
142
  "Normal clean text",
143
  ]
144
 
145
- features_clean, _ = extract_tfidf_features(clean_df, max_features=50)
 
146
 
147
  # Features should be similar (cleaning is applied to both)
148
  # But not necessarily identical due to stemming
@@ -283,19 +285,20 @@ class TestErrorHandlingInPipeline:
283
  extract_tfidf_features(df)
284
 
285
  def test_pipeline_with_all_nan_text(self):
286
- """Test pipeline with all NaN text values."""
 
 
 
 
287
  df = pd.DataFrame({
288
  'issue text': [None, None, None],
289
  'issue description': [None, None, None],
290
  'Label1': [1, 0, 1],
291
  })
292
 
293
- # Should handle NaN values without crashing
294
- features, _ = extract_tfidf_features(df, max_features=50)
295
-
296
- # May result in zero features for all samples
297
- assert features.shape[0] == 3
298
- assert not np.any(np.isnan(features))
299
 
300
  def test_pipeline_with_empty_labels(self):
301
  """Test pipeline when no labels are present."""
 
130
  "Normal clean text",
131
  ]
132
 
133
+ # Use min_df=1 and max_df=1.0 for small test datasets to avoid empty vocabulary
134
+ features_dirty, _ = extract_tfidf_features(dirty_df, max_features=50, min_df=1, max_df=1.0)
135
 
136
  # Clean version
137
  clean_df = sample_dataframe.copy()
 
143
  "Normal clean text",
144
  ]
145
 
146
+ # Use min_df=1 and max_df=1.0 for small test datasets
147
+ features_clean, _ = extract_tfidf_features(clean_df, max_features=50, min_df=1, max_df=1.0)
148
 
149
  # Features should be similar (cleaning is applied to both)
150
  # But not necessarily identical due to stemming
 
285
  extract_tfidf_features(df)
286
 
287
  def test_pipeline_with_all_nan_text(self):
288
+ """Test pipeline with all NaN text values raises appropriate error.
289
+
290
+ TF-IDF cannot build a vocabulary from empty/NaN documents,
291
+ so it should raise a ValueError with a descriptive message.
292
+ """
293
  df = pd.DataFrame({
294
  'issue text': [None, None, None],
295
  'issue description': [None, None, None],
296
  'Label1': [1, 0, 1],
297
  })
298
 
299
+ # TF-IDF should raise ValueError for empty vocabulary
300
+ with pytest.raises(ValueError, match="empty vocabulary"):
301
+ extract_tfidf_features(df, max_features=50)
 
 
 
302
 
303
  def test_pipeline_with_empty_labels(self):
304
  """Test pipeline when no labels are present."""