psiwatch 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.
psiwatch-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tharun (Naeris / Aevra Studio)
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,353 @@
1
+ Metadata-Version: 2.4
2
+ Name: psiwatch
3
+ Version: 0.1.0
4
+ Summary: A zero-dependency Python library and CLI for detecting dataset drift in ML pipelines.
5
+ Author-email: Tharun <neetibyaevra@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/tharunstryker/psiwatch
8
+ Project-URL: Repository, https://github.com/tharunstryker/psiwatch
9
+ Keywords: data drift,machine learning,data science,monitoring,csv,statistics
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Dynamic: license-file
19
+
20
+ <p align="center">
21
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=7C3AED&height=140&section=header&text=psiwatch&fontColor=ffffff&fontSize=40&fontAlignY=35&desc=Dataset+drift+detection+for+ML+pipelines&descAlignY=55&descSize=16&animation=fadeIn" width="100%" />
22
+ </p>
23
+
24
+ <p align="center">
25
+ <a href="https://git.io/typing-svg">
26
+ <img src="https://readme-typing-svg.demolab.com?font=Syncopate&weight=700&size=18&pause=1000&color=7C3AED&center=true&vCenter=true&width=820&lines=Know+when+your+data+changes.;Before+your+model+breaks.;PSI+%C2%B7+Chi-Square+%C2%B7+Mean+Shift+%C2%B7+Std+Shift;Zero+dependencies.+Pure+Python." alt="Typing SVG" />
27
+ </a>
28
+ </p>
29
+
30
+ <p align="center">
31
+ <img src="https://img.shields.io/badge/Python-3.8+-3776AB?style=flat-square&logo=python&logoColor=white&labelColor=0D0D1A" />
32
+ &nbsp;
33
+ <img src="https://img.shields.io/badge/dependencies-zero-22c55e?style=flat-square&labelColor=0D0D1A" />
34
+ &nbsp;
35
+ <img src="https://img.shields.io/badge/license-MIT-7C3AED?style=flat-square&labelColor=0D0D1A" />
36
+ &nbsp;
37
+ <img src="https://img.shields.io/badge/version-0.1.0-00D4FF?style=flat-square&labelColor=0D0D1A" />
38
+ &nbsp;
39
+ <img src="https://img.shields.io/badge/platform-Termux%20%7C%20Linux%20%7C%20macOS%20%7C%20Windows-7C3AED?style=flat-square&labelColor=0D0D1A" />
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://github.com/tharunstryker/psiwatch">
44
+ <img src="https://img.shields.io/badge/GitHub-tharunstryker%2Fpsiwatch-7C3AED?style=for-the-badge&logo=github&logoColor=white&labelColor=0D0D1A" />
45
+ </a>
46
+ &nbsp;
47
+ <a href="https://naeris.vercel.app">
48
+ <img src="https://img.shields.io/badge/Portfolio-naeris.vercel.app-00D4FF?style=for-the-badge&logo=vercel&logoColor=white&labelColor=0D0D1A" />
49
+ </a>
50
+ &nbsp;
51
+ <a href="mailto:neetibyaevra@gmail.com">
52
+ <img src="https://img.shields.io/badge/Email-neetibyaevra%40gmail.com-7C3AED?style=for-the-badge&logo=gmail&logoColor=white&labelColor=0D0D1A" />
53
+ </a>
54
+ </p>
55
+
56
+ ---
57
+
58
+ ## ◈ What is psiwatch?
59
+
60
+ You train a model on last year's data. Six months later it starts making wrong predictions — and nobody knows why.
61
+
62
+ The reason? **Your data changed.** Scores dropped. New cities appeared. Distributions shifted. Your model never got the memo.
63
+
64
+ `psiwatch` catches this. Point it at your old data and your new data — it tells you exactly what drifted, how badly, and gives you a single health score for the whole dataset.
65
+
66
+ ```bash
67
+ psiwatch compare train.csv production.csv
68
+ ```
69
+
70
+ ```
71
+ ════════════════════════════════════════════════════════
72
+ PSIWATCH REPORT
73
+ ════════════════════════════════════════════════════════
74
+
75
+ [HIGH] age [numeric]
76
+ → Mean shifted by 3.14 std devs (22.40 → 29.50)
77
+ → PSI = 0.87 (significant drift)
78
+ ┌ Mean: 22.40 → 29.50
79
+ ├ Std: 1.02 → 1.26
80
+ └ PSI: 0.87
81
+
82
+ [HIGH] city [categorical]
83
+ → New categories found: ['Bangalore', 'Hyderabad']
84
+ ┌ PSI: 1.34
85
+ ├ Chi-square: 0.82
86
+ └ New cats: ['Bangalore', 'Hyderabad']
87
+
88
+ [MEDIUM] score [numeric]
89
+ → Mean shifted by 0.31 std devs (78.50 → 74.20)
90
+
91
+ [PASS] grade [categorical]
92
+ → No drift detected
93
+
94
+ ────────────────────────────────────────────────────────
95
+ HIGH: 2 MEDIUM: 1 PASS: 1
96
+
97
+ Drift Health Score: 25/100 (Significant Drift)
98
+ ════════════════════════════════════════════════════════
99
+ ```
100
+
101
+ ---
102
+
103
+ ## ◈ Install
104
+
105
+ ```bash
106
+ pip install psiwatch
107
+ ```
108
+
109
+ **Or from source:**
110
+
111
+ ```bash
112
+ git clone https://github.com/tharunstryker/psiwatch
113
+ cd psiwatch
114
+ pip install -e .
115
+ ```
116
+
117
+ > No numpy. No pandas. No scipy. Pure Python standard library only.
118
+ > Runs anywhere Python runs — including **Termux on Android.**
119
+
120
+ ---
121
+
122
+ ## ◈ Quickstart
123
+
124
+ ```bash
125
+ git clone https://github.com/tharunstryker/psiwatch
126
+ cd psiwatch
127
+ pip install -e .
128
+ psiwatch compare samples/train.csv samples/new.csv
129
+ ```
130
+
131
+ ---
132
+
133
+ ## ◈ Usage
134
+
135
+ <details>
136
+ <summary><b>CLI</b></summary>
137
+ <br/>
138
+
139
+ ```bash
140
+ # Compare two CSV files
141
+ psiwatch compare old.csv new.csv
142
+
143
+ # Save as HTML report
144
+ psiwatch compare old.csv new.csv --output report.html
145
+
146
+ # Save as JSON
147
+ psiwatch compare old.csv new.csv --output report.json
148
+
149
+ # Save as plain text
150
+ psiwatch compare old.csv new.csv --output report.txt
151
+
152
+ # Compare specific columns only
153
+ psiwatch compare old.csv new.csv --columns age,score,city
154
+ ```
155
+
156
+ </details>
157
+
158
+ <details>
159
+ <summary><b>Python Library</b></summary>
160
+ <br/>
161
+
162
+ **From your own CSV files:**
163
+
164
+ ```python
165
+ import psiwatch
166
+
167
+ # Pass the path to any CSV file on your machine
168
+ psiwatch.compare("/your/path/old_data.csv", "/your/path/new_data.csv")
169
+
170
+ # Save report
171
+ psiwatch.compare("old.csv", "new.csv", output="report.html")
172
+
173
+ # Specific columns only
174
+ psiwatch.compare("old.csv", "new.csv", columns=["age", "score"])
175
+ ```
176
+
177
+ **From Python dicts — no files needed:**
178
+
179
+ ```python
180
+ psiwatch.compare_data(
181
+ old={"age": [22, 23, 21, 24], "city": ["Chennai", "Delhi", "Mumbai", "Delhi"]},
182
+ new={"age": [28, 30, 29, 31], "city": ["Chennai", "Bangalore", "Hyderabad", "Mumbai"]}
183
+ )
184
+ ```
185
+
186
+ **From plain lists — single column:**
187
+
188
+ ```python
189
+ psiwatch.compare_columns(
190
+ old_list=[22, 23, 21, 24, 22],
191
+ new_list=[28, 30, 29, 31, 27],
192
+ name="age"
193
+ )
194
+ ```
195
+
196
+ **Raw results — no printing:**
197
+
198
+ ```python
199
+ result = psiwatch.analyze("old.csv", "new.csv")
200
+
201
+ print(result["health_score"])
202
+ # 25
203
+
204
+ for column, data in result["columns"].items():
205
+ print(column, data["severity"], data["metrics"]["psi"])
206
+ # age HIGH 0.87
207
+ # city HIGH 1.34
208
+ # score MEDIUM 0.14
209
+ # grade PASS 0.02
210
+ ```
211
+
212
+ </details>
213
+
214
+ ---
215
+
216
+ ## ◈ Detection Methods
217
+
218
+ <div align="center">
219
+
220
+ | Column Type | Method | What It Checks |
221
+ |---|---|---|
222
+ | Numeric | Mean Shift | Did the average move significantly? |
223
+ | Numeric | Std Deviation Shift | Did the spread of values change? |
224
+ | Numeric | PSI | Did the overall distribution shape change? |
225
+ | Numeric | Percentile Comparison | Did the median and quartiles shift? |
226
+ | Categorical | New Category Detection | Did values appear that never existed before? |
227
+ | Categorical | Frequency Shift | Did category proportions change? |
228
+ | Categorical | PSI | Did the overall distribution change? |
229
+ | Categorical | Chi-Square | Is the frequency mismatch statistically significant? |
230
+
231
+ </div>
232
+
233
+ ---
234
+
235
+ ## ◈ PSI Reference
236
+
237
+ <div align="center">
238
+
239
+ | PSI Value | Status | Action |
240
+ |---|---|---|
241
+ | `< 0.10` | Stable | Model is fine |
242
+ | `0.10 – 0.25` | Moderate Drift | Investigate |
243
+ | `> 0.25` | Significant Drift | Retrain your model |
244
+
245
+ </div>
246
+
247
+ PSI (Population Stability Index) is the industry standard metric for production data drift monitoring. `psiwatch` uses it as the primary signal for every column — numeric and categorical.
248
+
249
+ ---
250
+
251
+ ## ◈ Drift Health Score
252
+
253
+ <div align="center">
254
+
255
+ | Score | Status | Meaning |
256
+ |---|---|---|
257
+ | `80 – 100` | Healthy | Data is stable, model is likely fine |
258
+ | `50 – 79` | Moderate Drift | Some columns changed — investigate |
259
+ | `0 – 49` | Significant Drift | Major shifts — consider retraining |
260
+
261
+ </div>
262
+
263
+ ---
264
+
265
+ ## ◈ Output Formats
266
+
267
+ <div align="center">
268
+
269
+ | Format | Command | Best For |
270
+ |---|---|---|
271
+ | Terminal | default | Dev checks |
272
+ | HTML | `--output report.html` | Sharing, presentations |
273
+ | JSON | `--output report.json` | Pipelines, CI/CD |
274
+ | TXT | `--output report.txt` | Logs, servers |
275
+
276
+ </div>
277
+
278
+ ---
279
+
280
+ ## ◈ Project Structure
281
+
282
+ ```
283
+ psiwatch/
284
+ ├── src/psiwatch/
285
+ │ ├── __init__.py ← public API
286
+ │ ├── loader.py ← CSV · dict · list input modes
287
+ │ ├── analyzer.py ← PSI · mean/std · chi-square · frequency
288
+ │ ├── reporter.py ← terminal · HTML · JSON · TXT
289
+ │ └── cli.py ← psiwatch compare command
290
+ ├── samples/
291
+ │ ├── train.csv ← baseline dataset example
292
+ │ └── new.csv ← drifted dataset example
293
+ ├── tests/
294
+ │ └── test_analyzer.py
295
+ ├── pyproject.toml
296
+ └── README.md
297
+ ```
298
+
299
+ ---
300
+
301
+ ## ◈ Tests
302
+
303
+ ```bash
304
+ python tests/test_analyzer.py
305
+ ```
306
+
307
+ ```
308
+ Running psiwatch tests...
309
+
310
+ ✓ numeric high drift detected
311
+ ✓ numeric no drift — PASS
312
+ ✓ categorical new category detected
313
+ ✓ categorical no drift — PASS
314
+ ✓ full analyze — health score: 0/100
315
+ ✓ health score clean data: 100/100
316
+ ✓ column filter works
317
+
318
+ All tests passed.
319
+ ```
320
+
321
+ ---
322
+
323
+ ## ◈ Why Zero Dependencies?
324
+
325
+ Most drift tools require `scipy`, `pandas`, or `scikit-learn` — heavy installs, version conflicts, blocked on restricted systems.
326
+
327
+ `psiwatch` uses only `csv`, `math`, `json`, `os`, `argparse` — all built into Python. No install friction. No conflicts. Works on bare servers, CI runners, and Termux.
328
+
329
+ ---
330
+
331
+ ```yaml
332
+ built_by: Tharun
333
+ entity: Naeris · Aevra Studio
334
+ course: B.Tech AI & Data Science
335
+ built_on: Android · Termux
336
+ philosophy: "No framework. No build step. No shortcuts."
337
+ ```
338
+
339
+ ---
340
+
341
+ <p align="center">
342
+ <a href="https://github.com/tharunstryker/psiwatch">
343
+ <img src="https://img.shields.io/badge/Star_this_repo-7C3AED?style=for-the-badge&labelColor=0D0D1A" />
344
+ </a>
345
+ </p>
346
+
347
+ <p align="center">
348
+ <i>MIT © 2026 Tharun · <a href="https://naeris.vercel.app">Naeris</a></i>
349
+ </p>
350
+
351
+ <p align="center">
352
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=7C3AED&height=100&section=footer&animation=fadeIn" width="100%" />
353
+ </p>
@@ -0,0 +1,334 @@
1
+ <p align="center">
2
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=7C3AED&height=140&section=header&text=psiwatch&fontColor=ffffff&fontSize=40&fontAlignY=35&desc=Dataset+drift+detection+for+ML+pipelines&descAlignY=55&descSize=16&animation=fadeIn" width="100%" />
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://git.io/typing-svg">
7
+ <img src="https://readme-typing-svg.demolab.com?font=Syncopate&weight=700&size=18&pause=1000&color=7C3AED&center=true&vCenter=true&width=820&lines=Know+when+your+data+changes.;Before+your+model+breaks.;PSI+%C2%B7+Chi-Square+%C2%B7+Mean+Shift+%C2%B7+Std+Shift;Zero+dependencies.+Pure+Python." alt="Typing SVG" />
8
+ </a>
9
+ </p>
10
+
11
+ <p align="center">
12
+ <img src="https://img.shields.io/badge/Python-3.8+-3776AB?style=flat-square&logo=python&logoColor=white&labelColor=0D0D1A" />
13
+ &nbsp;
14
+ <img src="https://img.shields.io/badge/dependencies-zero-22c55e?style=flat-square&labelColor=0D0D1A" />
15
+ &nbsp;
16
+ <img src="https://img.shields.io/badge/license-MIT-7C3AED?style=flat-square&labelColor=0D0D1A" />
17
+ &nbsp;
18
+ <img src="https://img.shields.io/badge/version-0.1.0-00D4FF?style=flat-square&labelColor=0D0D1A" />
19
+ &nbsp;
20
+ <img src="https://img.shields.io/badge/platform-Termux%20%7C%20Linux%20%7C%20macOS%20%7C%20Windows-7C3AED?style=flat-square&labelColor=0D0D1A" />
21
+ </p>
22
+
23
+ <p align="center">
24
+ <a href="https://github.com/tharunstryker/psiwatch">
25
+ <img src="https://img.shields.io/badge/GitHub-tharunstryker%2Fpsiwatch-7C3AED?style=for-the-badge&logo=github&logoColor=white&labelColor=0D0D1A" />
26
+ </a>
27
+ &nbsp;
28
+ <a href="https://naeris.vercel.app">
29
+ <img src="https://img.shields.io/badge/Portfolio-naeris.vercel.app-00D4FF?style=for-the-badge&logo=vercel&logoColor=white&labelColor=0D0D1A" />
30
+ </a>
31
+ &nbsp;
32
+ <a href="mailto:neetibyaevra@gmail.com">
33
+ <img src="https://img.shields.io/badge/Email-neetibyaevra%40gmail.com-7C3AED?style=for-the-badge&logo=gmail&logoColor=white&labelColor=0D0D1A" />
34
+ </a>
35
+ </p>
36
+
37
+ ---
38
+
39
+ ## ◈ What is psiwatch?
40
+
41
+ You train a model on last year's data. Six months later it starts making wrong predictions — and nobody knows why.
42
+
43
+ The reason? **Your data changed.** Scores dropped. New cities appeared. Distributions shifted. Your model never got the memo.
44
+
45
+ `psiwatch` catches this. Point it at your old data and your new data — it tells you exactly what drifted, how badly, and gives you a single health score for the whole dataset.
46
+
47
+ ```bash
48
+ psiwatch compare train.csv production.csv
49
+ ```
50
+
51
+ ```
52
+ ════════════════════════════════════════════════════════
53
+ PSIWATCH REPORT
54
+ ════════════════════════════════════════════════════════
55
+
56
+ [HIGH] age [numeric]
57
+ → Mean shifted by 3.14 std devs (22.40 → 29.50)
58
+ → PSI = 0.87 (significant drift)
59
+ ┌ Mean: 22.40 → 29.50
60
+ ├ Std: 1.02 → 1.26
61
+ └ PSI: 0.87
62
+
63
+ [HIGH] city [categorical]
64
+ → New categories found: ['Bangalore', 'Hyderabad']
65
+ ┌ PSI: 1.34
66
+ ├ Chi-square: 0.82
67
+ └ New cats: ['Bangalore', 'Hyderabad']
68
+
69
+ [MEDIUM] score [numeric]
70
+ → Mean shifted by 0.31 std devs (78.50 → 74.20)
71
+
72
+ [PASS] grade [categorical]
73
+ → No drift detected
74
+
75
+ ────────────────────────────────────────────────────────
76
+ HIGH: 2 MEDIUM: 1 PASS: 1
77
+
78
+ Drift Health Score: 25/100 (Significant Drift)
79
+ ════════════════════════════════════════════════════════
80
+ ```
81
+
82
+ ---
83
+
84
+ ## ◈ Install
85
+
86
+ ```bash
87
+ pip install psiwatch
88
+ ```
89
+
90
+ **Or from source:**
91
+
92
+ ```bash
93
+ git clone https://github.com/tharunstryker/psiwatch
94
+ cd psiwatch
95
+ pip install -e .
96
+ ```
97
+
98
+ > No numpy. No pandas. No scipy. Pure Python standard library only.
99
+ > Runs anywhere Python runs — including **Termux on Android.**
100
+
101
+ ---
102
+
103
+ ## ◈ Quickstart
104
+
105
+ ```bash
106
+ git clone https://github.com/tharunstryker/psiwatch
107
+ cd psiwatch
108
+ pip install -e .
109
+ psiwatch compare samples/train.csv samples/new.csv
110
+ ```
111
+
112
+ ---
113
+
114
+ ## ◈ Usage
115
+
116
+ <details>
117
+ <summary><b>CLI</b></summary>
118
+ <br/>
119
+
120
+ ```bash
121
+ # Compare two CSV files
122
+ psiwatch compare old.csv new.csv
123
+
124
+ # Save as HTML report
125
+ psiwatch compare old.csv new.csv --output report.html
126
+
127
+ # Save as JSON
128
+ psiwatch compare old.csv new.csv --output report.json
129
+
130
+ # Save as plain text
131
+ psiwatch compare old.csv new.csv --output report.txt
132
+
133
+ # Compare specific columns only
134
+ psiwatch compare old.csv new.csv --columns age,score,city
135
+ ```
136
+
137
+ </details>
138
+
139
+ <details>
140
+ <summary><b>Python Library</b></summary>
141
+ <br/>
142
+
143
+ **From your own CSV files:**
144
+
145
+ ```python
146
+ import psiwatch
147
+
148
+ # Pass the path to any CSV file on your machine
149
+ psiwatch.compare("/your/path/old_data.csv", "/your/path/new_data.csv")
150
+
151
+ # Save report
152
+ psiwatch.compare("old.csv", "new.csv", output="report.html")
153
+
154
+ # Specific columns only
155
+ psiwatch.compare("old.csv", "new.csv", columns=["age", "score"])
156
+ ```
157
+
158
+ **From Python dicts — no files needed:**
159
+
160
+ ```python
161
+ psiwatch.compare_data(
162
+ old={"age": [22, 23, 21, 24], "city": ["Chennai", "Delhi", "Mumbai", "Delhi"]},
163
+ new={"age": [28, 30, 29, 31], "city": ["Chennai", "Bangalore", "Hyderabad", "Mumbai"]}
164
+ )
165
+ ```
166
+
167
+ **From plain lists — single column:**
168
+
169
+ ```python
170
+ psiwatch.compare_columns(
171
+ old_list=[22, 23, 21, 24, 22],
172
+ new_list=[28, 30, 29, 31, 27],
173
+ name="age"
174
+ )
175
+ ```
176
+
177
+ **Raw results — no printing:**
178
+
179
+ ```python
180
+ result = psiwatch.analyze("old.csv", "new.csv")
181
+
182
+ print(result["health_score"])
183
+ # 25
184
+
185
+ for column, data in result["columns"].items():
186
+ print(column, data["severity"], data["metrics"]["psi"])
187
+ # age HIGH 0.87
188
+ # city HIGH 1.34
189
+ # score MEDIUM 0.14
190
+ # grade PASS 0.02
191
+ ```
192
+
193
+ </details>
194
+
195
+ ---
196
+
197
+ ## ◈ Detection Methods
198
+
199
+ <div align="center">
200
+
201
+ | Column Type | Method | What It Checks |
202
+ |---|---|---|
203
+ | Numeric | Mean Shift | Did the average move significantly? |
204
+ | Numeric | Std Deviation Shift | Did the spread of values change? |
205
+ | Numeric | PSI | Did the overall distribution shape change? |
206
+ | Numeric | Percentile Comparison | Did the median and quartiles shift? |
207
+ | Categorical | New Category Detection | Did values appear that never existed before? |
208
+ | Categorical | Frequency Shift | Did category proportions change? |
209
+ | Categorical | PSI | Did the overall distribution change? |
210
+ | Categorical | Chi-Square | Is the frequency mismatch statistically significant? |
211
+
212
+ </div>
213
+
214
+ ---
215
+
216
+ ## ◈ PSI Reference
217
+
218
+ <div align="center">
219
+
220
+ | PSI Value | Status | Action |
221
+ |---|---|---|
222
+ | `< 0.10` | Stable | Model is fine |
223
+ | `0.10 – 0.25` | Moderate Drift | Investigate |
224
+ | `> 0.25` | Significant Drift | Retrain your model |
225
+
226
+ </div>
227
+
228
+ PSI (Population Stability Index) is the industry standard metric for production data drift monitoring. `psiwatch` uses it as the primary signal for every column — numeric and categorical.
229
+
230
+ ---
231
+
232
+ ## ◈ Drift Health Score
233
+
234
+ <div align="center">
235
+
236
+ | Score | Status | Meaning |
237
+ |---|---|---|
238
+ | `80 – 100` | Healthy | Data is stable, model is likely fine |
239
+ | `50 – 79` | Moderate Drift | Some columns changed — investigate |
240
+ | `0 – 49` | Significant Drift | Major shifts — consider retraining |
241
+
242
+ </div>
243
+
244
+ ---
245
+
246
+ ## ◈ Output Formats
247
+
248
+ <div align="center">
249
+
250
+ | Format | Command | Best For |
251
+ |---|---|---|
252
+ | Terminal | default | Dev checks |
253
+ | HTML | `--output report.html` | Sharing, presentations |
254
+ | JSON | `--output report.json` | Pipelines, CI/CD |
255
+ | TXT | `--output report.txt` | Logs, servers |
256
+
257
+ </div>
258
+
259
+ ---
260
+
261
+ ## ◈ Project Structure
262
+
263
+ ```
264
+ psiwatch/
265
+ ├── src/psiwatch/
266
+ │ ├── __init__.py ← public API
267
+ │ ├── loader.py ← CSV · dict · list input modes
268
+ │ ├── analyzer.py ← PSI · mean/std · chi-square · frequency
269
+ │ ├── reporter.py ← terminal · HTML · JSON · TXT
270
+ │ └── cli.py ← psiwatch compare command
271
+ ├── samples/
272
+ │ ├── train.csv ← baseline dataset example
273
+ │ └── new.csv ← drifted dataset example
274
+ ├── tests/
275
+ │ └── test_analyzer.py
276
+ ├── pyproject.toml
277
+ └── README.md
278
+ ```
279
+
280
+ ---
281
+
282
+ ## ◈ Tests
283
+
284
+ ```bash
285
+ python tests/test_analyzer.py
286
+ ```
287
+
288
+ ```
289
+ Running psiwatch tests...
290
+
291
+ ✓ numeric high drift detected
292
+ ✓ numeric no drift — PASS
293
+ ✓ categorical new category detected
294
+ ✓ categorical no drift — PASS
295
+ ✓ full analyze — health score: 0/100
296
+ ✓ health score clean data: 100/100
297
+ ✓ column filter works
298
+
299
+ All tests passed.
300
+ ```
301
+
302
+ ---
303
+
304
+ ## ◈ Why Zero Dependencies?
305
+
306
+ Most drift tools require `scipy`, `pandas`, or `scikit-learn` — heavy installs, version conflicts, blocked on restricted systems.
307
+
308
+ `psiwatch` uses only `csv`, `math`, `json`, `os`, `argparse` — all built into Python. No install friction. No conflicts. Works on bare servers, CI runners, and Termux.
309
+
310
+ ---
311
+
312
+ ```yaml
313
+ built_by: Tharun
314
+ entity: Naeris · Aevra Studio
315
+ course: B.Tech AI & Data Science
316
+ built_on: Android · Termux
317
+ philosophy: "No framework. No build step. No shortcuts."
318
+ ```
319
+
320
+ ---
321
+
322
+ <p align="center">
323
+ <a href="https://github.com/tharunstryker/psiwatch">
324
+ <img src="https://img.shields.io/badge/Star_this_repo-7C3AED?style=for-the-badge&labelColor=0D0D1A" />
325
+ </a>
326
+ </p>
327
+
328
+ <p align="center">
329
+ <i>MIT © 2026 Tharun · <a href="https://naeris.vercel.app">Naeris</a></i>
330
+ </p>
331
+
332
+ <p align="center">
333
+ <img src="https://capsule-render.vercel.app/api?type=waving&color=7C3AED&height=100&section=footer&animation=fadeIn" width="100%" />
334
+ </p>