Skip to content

Commit 7c45369

Browse files
Merge pull request #3 from Sebastijan-Dominis/ml-service-tests
This PR adds a lot of unit tests for the ml_service/ code - unit tests cover over 99% of lines and 96% of branches. It also adds more integration and e2e tests for proper coverage. Finally, it improves on the existing README.md in a meaningful way, and updates the documentation in general.
2 parents 8080dc0 + 7fd87c6 commit 7c45369

170 files changed

Lines changed: 9429 additions & 249 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ jobs:
3636
conda run -n hotel_management pip install -r requirements.txt
3737
conda run -n hotel_management pip install pytest
3838
conda run -n hotel_management pip install coverage
39+
conda run -n hotel_management pip install httpx
3940
4041
- name: Show environment
4142
run: conda run -n hotel_management conda list

.gitignore

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ htmlcov/
4646
nosetests.xml
4747
coverage.xml
4848
coverage.json
49+
coverage_ml_service.xml
50+
coverage_e2e.xml
51+
coverage_unit.xml
52+
coverage_integration.xml
4953
*.cover
5054
*.py.cover
5155
.hypothesis/
@@ -230,4 +234,7 @@ __marimo__/
230234
/predictions/
231235
/monitoring/
232236
/orchestration_logs/
233-
/scripts_logs/
237+
/scripts_logs/
238+
239+
# tools
240+
/tools/

.pre-commit-config.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ repos:
1010
pass_filenames: false
1111

1212
- id: mypy
13-
name: mypy (ml + pipelines + ml_service)
13+
name: mypy type checking
1414
entry: mypy
1515
language: system
1616
pass_filenames: false
17-
args: ["ml", "pipelines", "ml_service"]
17+
args: ["ml", "pipelines", "scripts", "ml_service", "tests"]
1818

1919
- id: import-layers
2020
name: import layer guardrails

README.md

Lines changed: 116 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,132 @@
22

33
## Overview
44

5-
### A reproducible ML experimentation and model lifecycle system.
5+
### An end-to-end ML platform that guarantees reproducibility across datasets, features, and models — with full lineage tracking and validation.
66

77
- Currently supports the modeling of regression and classification tasks using the CatBoost algorithm.
88
- Was initially formed based on a hotel_bookings dataset:
9-
- located in `data/raw/hotel_bookings/v1/2026-02-25T22-43-23_732dfdb7/data.csv`
10-
- originally from https://www.kaggle.com/datasets/mojtaba142/hotel-booking
11-
- Current architecture expanded to support many datasets.
12-
- The ml workflow covers everything from the registration of a raw data snapshot to model monitoring.
9+
- From: https://www.kaggle.com/datasets/mojtaba142/hotel-booking
10+
- The architecture has since been expanded to support many datasets with minimal code changes.
11+
- The ML workflow covers everything from the registration of a raw data snapshot to model monitoring.
12+
- Designed with **production ML system constraints in mind**: reproducibility, traceability, validation, and modularity.
1313
> Note: The repo was previously named `hotel_management`, so you will see that name around the repo; renamed for clarity
1414
> on what the project does.
1515
1616
> Another note: A few artifacts are intentionally included, along with their respective logs.
1717
> This enables quick inspection of expected outputs of each pipeline, without having to run anything.
1818
19+
## Why?
20+
21+
1. Many ML platforms are either overengineered for small teams, or lack essential safeguards:
22+
- For small teams, overengineering can be an issue:
23+
- Most small teams (1-5 developers) do not need worry about run conditions, very scalable storage, and so on
24+
- They need a simple, but strong and reliable platform
25+
- Some teams fail in the other direction:
26+
- They fully rely on notebooks
27+
- They forget about validation and lineage tracking
28+
- They avoid elementary checks in order to "keep it simple"
29+
30+
This project keeps the workflow simple, while still providing the most important sanity checks
31+
across the entire ML workflow. With minor modifications (dataset specificities, different algorithms),
32+
this tool can be used by an individual, or a small team of data scientists.
33+
34+
2. Most learning courses are too specific:
35+
- There are many courses on how to do regression or classification, or how to write python code
36+
- There are many tutorials on how to use specific algorithms, and how they work under the hood
37+
- There are very few courses/tutorials explaining the ML workflow in a simple manner
38+
- It is very hard to find a platform for quick experimentation to understand how ML workflows work
39+
40+
This project can also serve as a learning tool for understanding ML workflows beyond notebook-based experimentation.
41+
It is easy to set up, and comes with a friendly UI, as well as some pre-saved artifacts for quick inspection.
42+
Users can quickly experiment and learn on their own, and the only assumption is that they know how
43+
to either set up Docker, or python and conda.
44+
45+
## Inspiration
46+
47+
This project started as part of my master's thesis, where the initial goal was to train several models on a hotel booking
48+
dataset and expose them as tools for an LLM.
49+
50+
While working on that, I quickly ran into practical issues that are common in real-world ML work but rarely addressed in tutorials:
51+
- Repetitive boilerplate for training and evaluation
52+
- Difficulty reusing pipelines across slightly different setups
53+
- Fragile experiment tracking (risk of losing artifacts or overwriting results)
54+
- Inability to reliably pause and resume long-running experiments
55+
- Lack of structure when working beyond notebooks
56+
57+
To address these problems, I started building small utilities to make experimentation more reliable and less error-prone. Over time,
58+
this evolved into a broader system focused on reproducibility, modularity, and traceability across the entire ML lifecycle.
59+
60+
At some point, it became clear that building a proper ML workflow system was a more meaningful direction than the original project idea,
61+
so I leaned into it and expanded the architecture into what it is today.
62+
63+
## Key Achievements
64+
65+
- **~17,500** lines of production code
66+
- **~29,000** lines of tests (auto-generated + custom)
67+
- **Fully reproducible pipelines** via artifact hashing
68+
- **End-to-end ML lifecycle support**
69+
- **4,000+** lines of pre-included configurations
70+
- Easy-to-use **ML service** (as a local web app)
71+
- Comprehensive documentation (**3,000+** lines of Markdown)
72+
1973
## Features
2074

21-
Pipelines for every part of the ml workflow:
22-
- Data preprocessing
23-
- Register raw data snapshots
24-
- Build interim and processed datasets
75+
### End-to-End ML Pipelines:
76+
- Data registration and preprocessing
2577
- Feature (set) freezing
2678
- Hyperparameter search
27-
- Model training
28-
- Model evaluation
29-
- Model explainability
30-
- Model promotion
31-
- Includes model registry for staging and production
32-
- Archives past production models
33-
- Model inference
34-
- Model monitoring
35-
36-
Maximum **decoupling** of datasets, feature sets, and modeling
37-
- Datasets merge at runtime, using predefined configs and DAG for ordering
38-
- Feature sets merge at runtime using a predefined entity key
39-
- Models can use any snapshots of datasets and feature sets via snapshot bindings registry
40-
- Validation ensures consistency and predefined minimum row presence
41-
42-
Full **reproducibility**
43-
- Hashing and downstream validation of relevant `artifacts` and `configs`
44-
- Runtime info validation (hardware, git commit, environment...)
45-
46-
Code **quality** ensured by CI, which includes:
47-
- `ruff` checks
48-
- `mypy` checks (moderate strictness)
49-
- import layer checks
50-
- naming conventions checks
51-
- **1235 tests** -> fails if coverage drops below 90%
79+
- Model training, evaluation and explainability
80+
- Model promotion and archiving
81+
- Model inference and monitoring
82+
83+
### Reproducibility & Validation
84+
- Artifact hashing across pipelines
85+
- Environment & runtime validation
86+
- Heavy versioning:
87+
- All configurations
88+
- Interim and processed data configurations
89+
- Feature registry
90+
- Global and algorithm defaults
91+
- Model specifications + search and training configurations
92+
- Pipeline configurations
93+
- Environment overlay
94+
- Promotion thresholds
95+
- Snapshot bindings
96+
- Target creation
97+
- Splitting and target creation performed at runtime, based on model specifications
98+
- Inference predictions schema
99+
- Heavily snapshot-based:
100+
- datasets
101+
- feature sets
102+
- training, evaluation, and explainability runs
103+
- promotion and post-promotion runs
104+
105+
### Modular Architecture
106+
- Decoupled datasets, features, and models
107+
- Runtime datasets (DAG + configurations) and feature sets (entity key + configurations) merging
108+
- Flexible snapshot bindings
109+
110+
### Reliability
111+
- Atomic file writing
112+
- Runtime saving of best hyperparameters from each search phase (broad + narrow)
113+
- Runtime saving of model snapshots during training (e.g. every 30 seconds)
114+
115+
### Code Quality
116+
- CI with linting (ruff), typing (mypy), and structure checks
117+
- **90%+** coverage enforced by CI across **1,500+** tests
118+
119+
## Example Use Case
120+
121+
A data scientist can:
122+
1. Register a new dataset snapshot
123+
2. Optimize its memory in one or more ways
124+
3. Process the dataset in one or more ways
125+
4. Define and freeze many feature sets, each based on one or more related datasets
126+
5. Perform one or more hyperparameter searches
127+
6. Train models based on the hyperparameter search results (many training runs allowed per each search)
128+
7. Evaluate and explain the trained models, however many times
129+
8. Stage, promote, and archive models
130+
9. Run inference and monitoring on incoming data
52131

53132
## Installation
54133

@@ -68,7 +147,9 @@ Two options:
68147

69148
See the [usage guide](docs/usage.md) for instructions on running the workflow.
70149

71-
### Usage examples (via `ml_service`):
150+
### Usage examples:
151+
152+
The system includes a browser-based interface (`ml_service`) for interacting with pipelines and configurations:
72153

73154
#### Configs Writing, Validation, Saving, and Viewing - Interim Data Configs Example
74155

docs/architecture/boundaries.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
## New shared code goes into domain package first
3131

3232
- avoid placing shared code in `ml.utils`
33-
- instead, try placing it where it logically belongs, e.g. in `ml.runners`, `ml.modeling`, `ml.promotion`, etc.
33+
- place it where it logically belongs, e.g. in `ml.runners`, `ml.modeling`, `ml.promotion`, etc.
3434
- `ml.utils` should only contain code that is genuinely reusable across multiple different domains
3535
- for instance, loading json and yaml files, getting the current git commit, and setting up a pipeline runner belong to `ml.utils`
3636
- `get_trainer.py` is only used by trainer, so it does not belong in `ml.utils`; instead it belongs to `ml.runners.training.utils`

docs/architecture/decisions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,18 @@
22

33
This file records key architectural decisions, their rationale, and alternatives considered.
44

5+
## Key Architectural Decisions (Summary)
6+
7+
The system is built around a few core principles:
8+
9+
- **Immutability of artifacts** (datasets, features, experiments)
10+
- **Full reproducibility via configs + snapshot IDs**
11+
- **Decoupling of datasets, features, and models**
12+
- **Snapshot-based versioning instead of mutable state**
13+
- **Filesystem-based storage with strict validation**
14+
15+
These decisions shape the entire architecture. Detailed breakdowns are provided below.
16+
517
## Decision Classification
618

719
Each decision is classified as one of:

docs/architecture/system_invariants.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,4 @@
110110

111111
- If any of the above differ, reproducibility is not fully guaranteed.
112112
- Config hash match is very important for reproducibility; python version, conda environment hash and git commit matches are moderately important; os and hardware matches are the least important.
113-
- It is technically possible to get the same results with config hash match alone, but the user assummes responsibility for any unexpected results in that case.
113+
- It is technically possible to get the same results with config hash match alone, but the user assumes responsibility for any unexpected results in that case.

docs/architecture/validation_guarantees.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
- Metrics suitability for business objective
5454
- Absence of data leakage during CV
5555
- Param distribution quality
56-
- Compatibility between scoring function and specific algorithm beyong supported enum check
56+
- Compatibility between scoring function and specific algorithm beyond supported enum check
5757

5858
## Promotion Validation
5959

docs/testing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
This document describes the testing strategy, conventions, and instructions for this ML project.
44
> Note: Most of the tests currently found in the repo were AI-generated (with careful prompting)
5-
> Note: only the folders that constitute the main focus of the repo are tested (`ml/`, `pipelines/`, `scripts/` (excluding fake data generator))
5+
> Note: the following directories are tested: `ml/`, `pipelines/`, `ml_service`, `scripts/` (excluding fake data generator)
66
77
## Environment Setup
88

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
profile = "black"
33
line_length = 100
44
py_version = 311
5-
src_paths = ["ml", "pipelines", "scripts", "ml_service"]
5+
src_paths = ["ml", "pipelines", "scripts", "ml_service", "tests"]
66
skip = [".git", ".venv", "env", "__pycache__", ".pytest_cache"]
77

88
[tool.ruff]
99
line-length = 100
1010
target-version = "py311"
11-
src = ["ml", "pipelines", "scripts", "ml_service"]
11+
src = ["ml", "pipelines", "scripts", "ml_service", "tests"]
1212
exclude = [
1313
".git",
1414
".venv",
@@ -39,7 +39,7 @@ exclude = "(^notebooks/|^feature_store/|^data/|^experiments/)"
3939

4040
[tool.coverage.run]
4141
branch = true
42-
source = ["ml", "pipelines", "scripts"]
42+
source = ["ml", "pipelines", "scripts", "ml_service"]
4343
omit = [
4444
"tests/*",
4545
"notebooks/*",

0 commit comments

Comments
 (0)