madwatch 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ strategy:
12
+ matrix:
13
+ python-version: ['3.10', '3.11', '3.12', '3.13']
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - run: pip install -e '.[dev]'
20
+ - run: ruff check src tests
21
+ - run: pytest -q
@@ -0,0 +1,21 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags: ['v*']
6
+
7
+ permissions:
8
+ id-token: write
9
+ contents: read
10
+
11
+ jobs:
12
+ publish:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: actions/setup-python@v5
17
+ with:
18
+ python-version: '3.12'
19
+ - run: pip install build
20
+ - run: python -m build
21
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,8 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.egg-info/
4
+ dist/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ .DS_Store
8
+ build/
madwatch-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Efecan Küçük
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,152 @@
1
+ Metadata-Version: 2.4
2
+ Name: madwatch
3
+ Version: 0.1.0
4
+ Summary: Robust anomaly detection on MAD and the Modified Z-Score, with seasonal baselines
5
+ Project-URL: Repository, https://github.com/efekckk/madwatch
6
+ Project-URL: Why MAD?, https://efekckk.github.io/blog/why-mad
7
+ Author-email: Efecan Küçük <efe.kckk@gmail.com>
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Efecan Küçük
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: anomaly-detection,mad,modified-z-score,monitoring,time-series
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.10
37
+ Classifier: Programming Language :: Python :: 3.11
38
+ Classifier: Programming Language :: Python :: 3.12
39
+ Classifier: Programming Language :: Python :: 3.13
40
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
41
+ Requires-Python: >=3.10
42
+ Requires-Dist: numpy<3,>=1.24
43
+ Provides-Extra: cli
44
+ Requires-Dist: matplotlib>=3.7; extra == 'cli'
45
+ Requires-Dist: pandas>=2.0; extra == 'cli'
46
+ Provides-Extra: dev
47
+ Requires-Dist: matplotlib>=3.7; extra == 'dev'
48
+ Requires-Dist: pandas>=2.0; extra == 'dev'
49
+ Requires-Dist: pytest>=8; extra == 'dev'
50
+ Requires-Dist: ruff>=0.4; extra == 'dev'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # madwatch
54
+
55
+ [![CI](https://github.com/efekckk/madwatch/actions/workflows/ci.yml/badge.svg)](https://github.com/efekckk/madwatch/actions/workflows/ci.yml)
56
+ [![PyPI](https://img.shields.io/pypi/v/madwatch)](https://pypi.org/project/madwatch/)
57
+ [![Python](https://img.shields.io/pypi/pyversions/madwatch)](https://pypi.org/project/madwatch/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
59
+
60
+ Robust anomaly detection that doesn't panic at paydays.
61
+
62
+ `madwatch` scores time-series values with the **Modified Z-Score over MAD**
63
+ (Median Absolute Deviation) instead of mean/standard deviation. Medians don't
64
+ care about your whale transaction: one huge outlier can't inflate the baseline
65
+ and mask the next real anomaly.
66
+
67
+ ![demo](docs/assets/demo.png)
68
+
69
+ ## Install
70
+
71
+ ```bash
72
+ pip install madwatch # core, numpy only
73
+ pip install 'madwatch[cli]' # + CLI, pandas, matplotlib
74
+ ```
75
+
76
+ ## Quickstart
77
+
78
+ ```python
79
+ from madwatch import RollingDetector
80
+
81
+ det = RollingDetector(window=40, threshold=3.5, min_samples=10)
82
+ for value in stream:
83
+ score = det.update(value)
84
+ if score.is_anomaly:
85
+ alert(value, score.z)
86
+ ```
87
+
88
+ ## Why MAD?
89
+
90
+ Standard deviation has a design flaw for anomaly detection: the anomaly you are
91
+ trying to catch is *inside* the calculation. One spike inflates sigma, the
92
+ threshold stretches, and the next three real anomalies walk through undetected.
93
+ MAD is median-based, so a single outlier in the window barely moves the
94
+ baseline. The `0.6745` constant makes the score comparable to a classic z-score
95
+ on normal data, so the usual "flag at 3.5" rule still reads naturally.
96
+
97
+ Longer version: [Why MAD instead of standard deviation?](https://efekckk.github.io/blog/why-mad)
98
+
99
+ ## Seasonal baselines
100
+
101
+ Mondays don't look like Saturdays and 9 AM doesn't look like midnight. Comparing
102
+ a value against a global baseline produces false alarms at every weekly rhythm.
103
+ `SeasonalBaseline` buckets history by day-of-week and/or hour and scores each
104
+ point against its own bucket:
105
+
106
+ ```python
107
+ from madwatch import SeasonalBaseline
108
+
109
+ sb = SeasonalBaseline(granularity="dow_hour").fit(timestamps, values)
110
+ z = sb.score(new_timestamps, new_values)
111
+ ```
112
+
113
+ In production use on financial streams, this combination cut false positives by
114
+ roughly 60% compared to a naive z-score.
115
+
116
+ ## CLI
117
+
118
+ ```bash
119
+ madwatch data.csv --column amount --window 40 --threshold 3.5 --plot out.png
120
+ madwatch data.csv --column amount --timestamp ts --seasonal dow_hour
121
+ ```
122
+
123
+ ```
124
+ where value z
125
+ 2026-02-07T12:00:00 512.00 9.41
126
+ 1 anomalies in 312 points
127
+ ```
128
+
129
+ ## API
130
+
131
+ | Name | What it does |
132
+ |---|---|
133
+ | `mad(x)` | Median Absolute Deviation of an array |
134
+ | `modified_zscore(x, scale=0.6745)` | Per-element robust z-score |
135
+ | `RollingDetector(window, threshold, min_samples)` | Streaming detection over a trailing window |
136
+ | `SeasonalBaseline(granularity)` | Per-bucket (dow/hour) baselines with global fallback |
137
+
138
+ Behavior notes: constant windows (MAD = 0) score `z = 0`; the detector stays
139
+ silent until `min_samples` values have arrived; `NaN` input raises `ValueError`
140
+ (the CLI skips NaN rows with a warning).
141
+
142
+ ## Development
143
+
144
+ ```bash
145
+ uv venv && uv pip install -e '.[dev]'
146
+ pytest
147
+ ruff check src tests
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
@@ -0,0 +1,100 @@
1
+ # madwatch
2
+
3
+ [![CI](https://github.com/efekckk/madwatch/actions/workflows/ci.yml/badge.svg)](https://github.com/efekckk/madwatch/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/madwatch)](https://pypi.org/project/madwatch/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/madwatch)](https://pypi.org/project/madwatch/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
7
+
8
+ Robust anomaly detection that doesn't panic at paydays.
9
+
10
+ `madwatch` scores time-series values with the **Modified Z-Score over MAD**
11
+ (Median Absolute Deviation) instead of mean/standard deviation. Medians don't
12
+ care about your whale transaction: one huge outlier can't inflate the baseline
13
+ and mask the next real anomaly.
14
+
15
+ ![demo](docs/assets/demo.png)
16
+
17
+ ## Install
18
+
19
+ ```bash
20
+ pip install madwatch # core, numpy only
21
+ pip install 'madwatch[cli]' # + CLI, pandas, matplotlib
22
+ ```
23
+
24
+ ## Quickstart
25
+
26
+ ```python
27
+ from madwatch import RollingDetector
28
+
29
+ det = RollingDetector(window=40, threshold=3.5, min_samples=10)
30
+ for value in stream:
31
+ score = det.update(value)
32
+ if score.is_anomaly:
33
+ alert(value, score.z)
34
+ ```
35
+
36
+ ## Why MAD?
37
+
38
+ Standard deviation has a design flaw for anomaly detection: the anomaly you are
39
+ trying to catch is *inside* the calculation. One spike inflates sigma, the
40
+ threshold stretches, and the next three real anomalies walk through undetected.
41
+ MAD is median-based, so a single outlier in the window barely moves the
42
+ baseline. The `0.6745` constant makes the score comparable to a classic z-score
43
+ on normal data, so the usual "flag at 3.5" rule still reads naturally.
44
+
45
+ Longer version: [Why MAD instead of standard deviation?](https://efekckk.github.io/blog/why-mad)
46
+
47
+ ## Seasonal baselines
48
+
49
+ Mondays don't look like Saturdays and 9 AM doesn't look like midnight. Comparing
50
+ a value against a global baseline produces false alarms at every weekly rhythm.
51
+ `SeasonalBaseline` buckets history by day-of-week and/or hour and scores each
52
+ point against its own bucket:
53
+
54
+ ```python
55
+ from madwatch import SeasonalBaseline
56
+
57
+ sb = SeasonalBaseline(granularity="dow_hour").fit(timestamps, values)
58
+ z = sb.score(new_timestamps, new_values)
59
+ ```
60
+
61
+ In production use on financial streams, this combination cut false positives by
62
+ roughly 60% compared to a naive z-score.
63
+
64
+ ## CLI
65
+
66
+ ```bash
67
+ madwatch data.csv --column amount --window 40 --threshold 3.5 --plot out.png
68
+ madwatch data.csv --column amount --timestamp ts --seasonal dow_hour
69
+ ```
70
+
71
+ ```
72
+ where value z
73
+ 2026-02-07T12:00:00 512.00 9.41
74
+ 1 anomalies in 312 points
75
+ ```
76
+
77
+ ## API
78
+
79
+ | Name | What it does |
80
+ |---|---|
81
+ | `mad(x)` | Median Absolute Deviation of an array |
82
+ | `modified_zscore(x, scale=0.6745)` | Per-element robust z-score |
83
+ | `RollingDetector(window, threshold, min_samples)` | Streaming detection over a trailing window |
84
+ | `SeasonalBaseline(granularity)` | Per-bucket (dow/hour) baselines with global fallback |
85
+
86
+ Behavior notes: constant windows (MAD = 0) score `z = 0`; the detector stays
87
+ silent until `min_samples` values have arrived; `NaN` input raises `ValueError`
88
+ (the CLI skips NaN rows with a warning).
89
+
90
+ ## Development
91
+
92
+ ```bash
93
+ uv venv && uv pip install -e '.[dev]'
94
+ pytest
95
+ ruff check src tests
96
+ ```
97
+
98
+ ## License
99
+
100
+ MIT
Binary file