WaveGuardClient 2.0.0__py3-none-any.whl

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.
waveguard/__init__.py ADDED
@@ -0,0 +1,53 @@
1
+ """
2
+ WaveGuard — Physics-based anomaly detection SDK.
3
+
4
+ Quick start::
5
+
6
+ from waveguard import WaveGuard
7
+
8
+ wg = WaveGuard(api_key="YOUR_KEY")
9
+ result = wg.scan(training=normal_data, test=new_data)
10
+
11
+ for r in result.results:
12
+ print(r.is_anomaly, r.score, r.confidence)
13
+ """
14
+
15
+ from .client import ( # noqa: F401
16
+ WaveGuard,
17
+ ScanResult,
18
+ SampleResult,
19
+ ScanSummary,
20
+ FeatureInfo,
21
+ EngineInfo,
22
+ HealthStatus,
23
+ TierInfo,
24
+ __version__,
25
+ )
26
+ from .exceptions import ( # noqa: F401
27
+ WaveGuardError,
28
+ AuthenticationError,
29
+ ValidationError,
30
+ RateLimitError,
31
+ ServerError,
32
+ )
33
+
34
+ __all__ = [
35
+ # Client
36
+ "WaveGuard",
37
+ # Results
38
+ "ScanResult",
39
+ "SampleResult",
40
+ "ScanSummary",
41
+ "FeatureInfo",
42
+ "EngineInfo",
43
+ "HealthStatus",
44
+ "TierInfo",
45
+ # Exceptions
46
+ "WaveGuardError",
47
+ "AuthenticationError",
48
+ "ValidationError",
49
+ "RateLimitError",
50
+ "ServerError",
51
+ # Meta
52
+ "__version__",
53
+ ]
waveguard/client.py ADDED
@@ -0,0 +1,363 @@
1
+ """
2
+ WaveGuard Python SDK — stateless anomaly detection via wave physics.
3
+
4
+ Usage::
5
+
6
+ from waveguard import WaveGuard
7
+
8
+ wg = WaveGuard(api_key="YOUR_KEY")
9
+
10
+ result = wg.scan(
11
+ training=[
12
+ {"cpu": 45, "memory": 62, "errors": 0},
13
+ {"cpu": 48, "memory": 63, "errors": 0},
14
+ {"cpu": 42, "memory": 61, "errors": 1},
15
+ {"cpu": 50, "memory": 64, "errors": 0},
16
+ ],
17
+ test=[
18
+ {"cpu": 46, "memory": 62, "errors": 0}, # normal
19
+ {"cpu": 99, "memory": 95, "errors": 150}, # anomaly
20
+ ],
21
+ )
22
+
23
+ for r in result.results:
24
+ print(r.is_anomaly, r.score, r.confidence)
25
+ """
26
+
27
+ from __future__ import annotations
28
+
29
+ import requests
30
+ from dataclasses import dataclass, field
31
+ from typing import Any, Dict, List, Optional
32
+
33
+ from .exceptions import (
34
+ WaveGuardError,
35
+ AuthenticationError,
36
+ ValidationError,
37
+ RateLimitError,
38
+ ServerError,
39
+ )
40
+
41
+ __version__ = "2.0.0"
42
+
43
+
44
+ # ─────────────────────────────── Data Classes ─────────────────────────────
45
+
46
+
47
+ @dataclass
48
+ class FeatureInfo:
49
+ """A single top-contributing feature in anomaly scoring."""
50
+
51
+ dimension: int
52
+ label: str
53
+ z_score: float
54
+
55
+
56
+ @dataclass
57
+ class EngineInfo:
58
+ """Physics engine configuration for this scan."""
59
+
60
+ grid_size: int
61
+ evolution_steps: int
62
+ fingerprint_dims: int
63
+
64
+
65
+ @dataclass
66
+ class SampleResult:
67
+ """Result of anomaly detection on a single test sample."""
68
+
69
+ score: float
70
+ is_anomaly: bool
71
+ threshold: float
72
+ mahalanobis_distance: float
73
+ confidence: float
74
+ top_features: List[FeatureInfo]
75
+ latency_ms: float
76
+ engine: EngineInfo
77
+ raw: Dict[str, Any] = field(default_factory=dict)
78
+
79
+
80
+ @dataclass
81
+ class ScanSummary:
82
+ """Aggregate statistics from a scan."""
83
+
84
+ total_test_samples: int
85
+ total_training_samples: int
86
+ anomalies_found: int
87
+ anomaly_rate: float
88
+ mean_score: float
89
+ max_score: float
90
+ total_latency_ms: float
91
+ encoder_type: str
92
+
93
+
94
+ @dataclass
95
+ class ScanResult:
96
+ """Complete result of a ``/v1/scan`` call.
97
+
98
+ Attributes
99
+ ----------
100
+ results : list[SampleResult]
101
+ One entry per test sample, in order.
102
+ summary : ScanSummary
103
+ Aggregate statistics across all test samples.
104
+ raw : dict
105
+ The full JSON response from the API.
106
+ """
107
+
108
+ results: List[SampleResult]
109
+ summary: ScanSummary
110
+ raw: Dict[str, Any] = field(default_factory=dict)
111
+
112
+
113
+ @dataclass
114
+ class HealthStatus:
115
+ """API health information."""
116
+
117
+ status: str
118
+ version: str
119
+ gpu: str
120
+ mode: str
121
+ uptime_seconds: float
122
+ raw: Dict[str, Any] = field(default_factory=dict)
123
+
124
+
125
+ @dataclass
126
+ class TierInfo:
127
+ """Current subscription tier and rate limits."""
128
+
129
+ tier: str
130
+ limits: Dict[str, int]
131
+ raw: Dict[str, Any] = field(default_factory=dict)
132
+
133
+
134
+ # ─────────────────────────────── Client ───────────────────────────────────
135
+
136
+
137
+ class WaveGuard:
138
+ """WaveGuard Anomaly Detection client.
139
+
140
+ Parameters
141
+ ----------
142
+ api_key : str
143
+ Your WaveGuard API key.
144
+ base_url : str, optional
145
+ API base URL. Defaults to the production Modal endpoint.
146
+ timeout : float, optional
147
+ Request timeout in seconds. Default ``120`` (generous for GPU
148
+ cold starts).
149
+
150
+ Examples
151
+ --------
152
+ >>> wg = WaveGuard(api_key="wg_test_key")
153
+ >>> result = wg.scan(
154
+ ... training=[{"a": 1}, {"a": 2}, {"a": 3}],
155
+ ... test=[{"a": 100}],
156
+ ... )
157
+ >>> result.results[0].is_anomaly
158
+ True
159
+ """
160
+
161
+ DEFAULT_URL = "https://gpartin--waveguard-api-fastapi-app.modal.run"
162
+
163
+ def __init__(
164
+ self,
165
+ api_key: str,
166
+ base_url: str = DEFAULT_URL,
167
+ timeout: float = 120.0,
168
+ ):
169
+ self.api_key = api_key
170
+ self.base_url = base_url.rstrip("/")
171
+ self.timeout = timeout
172
+ self._session = requests.Session()
173
+ self._session.headers.update(
174
+ {
175
+ "X-API-Key": api_key,
176
+ "Content-Type": "application/json",
177
+ "User-Agent": f"waveguard-python/{__version__}",
178
+ }
179
+ )
180
+
181
+ # ── Core API ──────────────────────────────────────────────────────
182
+
183
+ def scan(
184
+ self,
185
+ training: List[Any],
186
+ test: List[Any],
187
+ encoder_type: Optional[str] = None,
188
+ sensitivity: Optional[float] = None,
189
+ ) -> ScanResult:
190
+ """Scan test data for anomalies against a training baseline.
191
+
192
+ This is the only method you need. Send training + test data in a
193
+ single call, get anomaly scores back, and everything is torn down.
194
+ Fully stateless — nothing persists between calls.
195
+
196
+ Parameters
197
+ ----------
198
+ training : list
199
+ 2+ examples of **normal** data. All entries must be the same
200
+ type and shape.
201
+ test : list
202
+ 1+ samples to check for anomalies.
203
+ encoder_type : str, optional
204
+ Force a specific encoder: ``"json"``, ``"numeric"``,
205
+ ``"text"``, ``"timeseries"``, ``"tabular"``.
206
+ Leave *None* for auto-detection (recommended).
207
+ sensitivity : float, optional
208
+ Detection sensitivity in the range 0.5–3.0.
209
+ Lower values are more sensitive (flag more anomalies).
210
+
211
+ Returns
212
+ -------
213
+ ScanResult
214
+ ``.results`` has one :class:`SampleResult` per test sample.
215
+ ``.summary`` has aggregate stats.
216
+ """
217
+ body: Dict[str, Any] = {
218
+ "training": training,
219
+ "test": test,
220
+ }
221
+ if encoder_type is not None:
222
+ body["encoder_type"] = encoder_type
223
+ if sensitivity is not None:
224
+ body["sensitivity"] = sensitivity
225
+
226
+ resp = self._post("/v1/scan", body)
227
+ return self._parse_scan(resp, len(training), len(test))
228
+
229
+ # ── Utility ───────────────────────────────────────────────────────
230
+
231
+ def health(self) -> HealthStatus:
232
+ """Check API health and GPU status. No auth required."""
233
+ resp = self._get("/v1/health")
234
+ return HealthStatus(
235
+ status=resp.get("status", "unknown"),
236
+ version=resp.get("version", ""),
237
+ gpu=resp.get("gpu", ""),
238
+ mode=resp.get("mode", ""),
239
+ uptime_seconds=resp.get("uptime_seconds", 0.0),
240
+ raw=resp,
241
+ )
242
+
243
+ def tier(self) -> TierInfo:
244
+ """Get current subscription tier and rate limits."""
245
+ resp = self._get("/v1/tier")
246
+ return TierInfo(
247
+ tier=resp.get("tier", ""),
248
+ limits=resp.get("limits", {}),
249
+ raw=resp,
250
+ )
251
+
252
+ # ── Response parsing ──────────────────────────────────────────────
253
+
254
+ def _parse_scan(
255
+ self, resp: dict, n_train: int, n_test: int
256
+ ) -> ScanResult:
257
+ results = []
258
+ for r in resp.get("results", []):
259
+ features = [
260
+ FeatureInfo(
261
+ dimension=f.get("dimension", 0),
262
+ label=f.get("label", ""),
263
+ z_score=f.get("z_score", 0.0),
264
+ )
265
+ for f in r.get("top_features", [])
266
+ ]
267
+ eng = r.get("engine", {})
268
+ results.append(
269
+ SampleResult(
270
+ score=r.get("score", 0.0),
271
+ is_anomaly=r.get("is_anomaly", False),
272
+ threshold=r.get("threshold", 0.0),
273
+ mahalanobis_distance=r.get("mahalanobis_distance", 0.0),
274
+ confidence=r.get("confidence", 0.0),
275
+ top_features=features,
276
+ latency_ms=r.get("latency_ms", 0.0),
277
+ engine=EngineInfo(
278
+ grid_size=eng.get("grid_size", 32),
279
+ evolution_steps=eng.get("evolution_steps", 150),
280
+ fingerprint_dims=eng.get("fingerprint_dims", 52),
281
+ ),
282
+ raw=r,
283
+ )
284
+ )
285
+
286
+ s = resp.get("summary", {})
287
+ summary = ScanSummary(
288
+ total_test_samples=s.get("total_test_samples", n_test),
289
+ total_training_samples=s.get("total_training_samples", n_train),
290
+ anomalies_found=s.get("anomalies_found", 0),
291
+ anomaly_rate=s.get("anomaly_rate", 0.0),
292
+ mean_score=s.get("mean_score", 0.0),
293
+ max_score=s.get("max_score", 0.0),
294
+ total_latency_ms=s.get("total_latency_ms", 0.0),
295
+ encoder_type=s.get("encoder_type", "auto"),
296
+ )
297
+
298
+ return ScanResult(results=results, summary=summary, raw=resp)
299
+
300
+ # ── Internal HTTP ─────────────────────────────────────────────────
301
+
302
+ def _post(self, path: str, body: dict) -> dict:
303
+ url = f"{self.base_url}{path}"
304
+ try:
305
+ r = self._session.post(url, json=body, timeout=self.timeout)
306
+ except requests.ConnectionError:
307
+ raise WaveGuardError(f"Cannot connect to {self.base_url}")
308
+ except requests.Timeout:
309
+ raise WaveGuardError(
310
+ f"Request timed out after {self.timeout}s"
311
+ )
312
+ return self._handle(r)
313
+
314
+ def _get(self, path: str) -> dict:
315
+ url = f"{self.base_url}{path}"
316
+ try:
317
+ r = self._session.get(url, timeout=self.timeout)
318
+ except requests.ConnectionError:
319
+ raise WaveGuardError(f"Cannot connect to {self.base_url}")
320
+ except requests.Timeout:
321
+ raise WaveGuardError(
322
+ f"Request timed out after {self.timeout}s"
323
+ )
324
+ return self._handle(r)
325
+
326
+ def _handle(self, r: requests.Response) -> dict:
327
+ if r.status_code == 401:
328
+ raise AuthenticationError(
329
+ "Invalid or missing API key",
330
+ status_code=401,
331
+ detail=r.text,
332
+ )
333
+ if r.status_code == 422:
334
+ raise ValidationError(
335
+ f"Validation failed: {r.text}",
336
+ status_code=422,
337
+ detail=r.text,
338
+ )
339
+ if r.status_code == 429:
340
+ raise RateLimitError(
341
+ f"Rate or tier limit exceeded: {r.text}",
342
+ status_code=429,
343
+ detail=r.text,
344
+ )
345
+ if r.status_code >= 500:
346
+ raise ServerError(
347
+ f"Server error {r.status_code}: {r.text}",
348
+ status_code=r.status_code,
349
+ detail=r.text,
350
+ )
351
+ if r.status_code >= 400:
352
+ raise WaveGuardError(
353
+ f"API error {r.status_code}: {r.text}",
354
+ status_code=r.status_code,
355
+ detail=r.text,
356
+ )
357
+ try:
358
+ return r.json()
359
+ except ValueError:
360
+ return {"raw": r.text}
361
+
362
+ def __repr__(self) -> str:
363
+ return f"WaveGuard(base_url='{self.base_url}')"
@@ -0,0 +1,63 @@
1
+ """
2
+ WaveGuard exception hierarchy.
3
+
4
+ All exceptions inherit from WaveGuardError so you can catch them with a
5
+ single ``except WaveGuardError`` block, or handle specific cases granularly.
6
+ """
7
+
8
+ __all__ = [
9
+ "WaveGuardError",
10
+ "AuthenticationError",
11
+ "ValidationError",
12
+ "RateLimitError",
13
+ "ServerError",
14
+ ]
15
+
16
+
17
+ class WaveGuardError(Exception):
18
+ """Base exception for all WaveGuard SDK errors.
19
+
20
+ Attributes
21
+ ----------
22
+ message : str
23
+ Human-readable error description.
24
+ status_code : int
25
+ HTTP status code (0 if not from an HTTP response).
26
+ detail : str
27
+ Raw error body from the API, if available.
28
+ """
29
+
30
+ def __init__(self, message: str, status_code: int = 0, detail: str = ""):
31
+ self.message = message
32
+ self.status_code = status_code
33
+ self.detail = detail
34
+ super().__init__(message)
35
+
36
+
37
+ class AuthenticationError(WaveGuardError):
38
+ """API key is invalid, expired, or missing (HTTP 401)."""
39
+ pass
40
+
41
+
42
+ class ValidationError(WaveGuardError):
43
+ """Request data failed server-side validation (HTTP 422).
44
+
45
+ Check ``detail`` for specifics (e.g. empty training array, type mismatch).
46
+ """
47
+ pass
48
+
49
+
50
+ class RateLimitError(WaveGuardError):
51
+ """Rate limit or subscription tier limit exceeded (HTTP 429).
52
+
53
+ Back off and retry, or upgrade your tier.
54
+ """
55
+ pass
56
+
57
+
58
+ class ServerError(WaveGuardError):
59
+ """Internal server error (HTTP 5xx).
60
+
61
+ Usually transient — retry after a short delay.
62
+ """
63
+ pass
@@ -0,0 +1,306 @@
1
+ Metadata-Version: 2.4
2
+ Name: WaveGuardClient
3
+ Version: 2.0.0
4
+ Summary: Python SDK for WaveGuard — physics-based anomaly detection API
5
+ Author: Greg Partin
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/gpartin/WaveGuardClient
8
+ Project-URL: Documentation, https://github.com/gpartin/WaveGuardClient/tree/main/docs
9
+ Project-URL: Repository, https://github.com/gpartin/WaveGuardClient
10
+ Project-URL: Issues, https://github.com/gpartin/WaveGuardClient/issues
11
+ Project-URL: API, https://gpartin--waveguard-api-fastapi-app.modal.run/docs
12
+ Keywords: anomaly-detection,api-client,sdk,waveguard,physics-based,mcp,model-context-protocol,azure-migration
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: System Administrators
16
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
17
+ Classifier: Topic :: System :: Monitoring
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: requests>=2.28
28
+ Provides-Extra: mcp
29
+ Requires-Dist: requests>=2.28; extra == "mcp"
30
+ Requires-Dist: fastapi>=0.100; extra == "mcp"
31
+ Requires-Dist: uvicorn>=0.20; extra == "mcp"
32
+ Provides-Extra: dev
33
+ Requires-Dist: pytest>=7.0; extra == "dev"
34
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ <p align="center">
38
+ <img src="https://img.shields.io/pypi/v/WaveGuardClient?style=for-the-badge&color=blueviolet" alt="PyPI">
39
+ <img src="https://img.shields.io/badge/API-v2.0.0_stateless-brightgreen?style=for-the-badge" alt="v2.0.0">
40
+ <img src="https://img.shields.io/badge/GPU-CUDA_accelerated-76B900?style=for-the-badge&logo=nvidia" alt="CUDA">
41
+ <img src="https://img.shields.io/badge/MCP-Claude_Desktop-orange?style=for-the-badge" alt="MCP">
42
+ </p>
43
+
44
+ <h1 align="center">WaveGuard Python SDK</h1>
45
+
46
+ <p align="center">
47
+ <strong>Anomaly detection powered by wave physics. Not machine learning.</strong><br>
48
+ One API call. Fully stateless. Works on any data type.
49
+ </p>
50
+
51
+ <p align="center">
52
+ <a href="#quickstart">Quickstart</a> •
53
+ <a href="#use-cases">Use Cases</a> •
54
+ <a href="#examples">Examples</a> •
55
+ <a href="docs/api-reference.md">API Reference</a> •
56
+ <a href="docs/mcp-integration.md">MCP / Claude</a> •
57
+ <a href="docs/azure-migration.md">Azure Migration</a>
58
+ </p>
59
+
60
+ ---
61
+
62
+ ## What is WaveGuard?
63
+
64
+ WaveGuard is a **general-purpose anomaly detection API**. Send it any data — server metrics, financial transactions, log files, sensor readings, time series — and get back anomaly scores, confidence levels, and explanations of *which features* triggered the alert.
65
+
66
+ **No training pipelines. No model management. No state. One API call.**
67
+
68
+ ```
69
+ Your data → WaveGuard API (GPU) → Anomaly scores + explanations
70
+ ```
71
+
72
+ Under the hood, it uses GPU-accelerated wave physics instead of machine learning. You don't need to know or care about the physics — it's all server-side.
73
+
74
+ <details>
75
+ <summary><strong>How does it actually work?</strong></summary>
76
+
77
+ Your data is encoded onto a 32³ lattice and run through coupled wave equation simulations on GPU. Normal data produces stable wave patterns; anomalies produce divergent ones. A 52-dimensional statistical fingerprint is compared between training and test data. Everything is torn down after each call — nothing is stored.
78
+
79
+ The key advantage over ML: no training data requirements (2+ samples is enough), no model drift, no retraining, no hyperparameter tuning. Same API call works on structured data, text, numbers, and time series.
80
+
81
+ </details>
82
+
83
+ ## Install
84
+
85
+ ```bash
86
+ pip install WaveGuardClient
87
+ ```
88
+
89
+ That's it. The only dependency is `requests`. All physics runs server-side on GPU.
90
+
91
+ ## Quickstart
92
+
93
+ The same `scan()` call works on any data type. Here are three different industries — same API:
94
+
95
+ ### Detect a compromised server
96
+
97
+ ```python
98
+ from waveguard import WaveGuard
99
+
100
+ wg = WaveGuard(api_key="YOUR_KEY")
101
+
102
+ result = wg.scan(
103
+ training=[
104
+ {"cpu": 45, "memory": 62, "disk_io": 120, "errors": 0},
105
+ {"cpu": 48, "memory": 63, "disk_io": 115, "errors": 0},
106
+ {"cpu": 42, "memory": 61, "disk_io": 125, "errors": 1},
107
+ ],
108
+ test=[
109
+ {"cpu": 46, "memory": 62, "disk_io": 119, "errors": 0}, # ✅ normal
110
+ {"cpu": 99, "memory": 95, "disk_io": 800, "errors": 150}, # 🚨 anomaly
111
+ ],
112
+ )
113
+
114
+ for r in result.results:
115
+ print(f"{'🚨' if r.is_anomaly else '✅'} score={r.score:.1f} confidence={r.confidence:.0%}")
116
+ ```
117
+
118
+ ### Flag a fraudulent transaction
119
+
120
+ ```python
121
+ result = wg.scan(
122
+ training=[
123
+ {"amount": 74.50, "items": 3, "session_sec": 340, "returning": 1},
124
+ {"amount": 52.00, "items": 2, "session_sec": 280, "returning": 1},
125
+ {"amount": 89.99, "items": 4, "session_sec": 410, "returning": 0},
126
+ ],
127
+ test=[
128
+ {"amount": 68.00, "items": 2, "session_sec": 300, "returning": 1}, # ✅ normal
129
+ {"amount": 4200.00, "items": 25, "session_sec": 8, "returning": 0}, # 🚨 fraud
130
+ ],
131
+ )
132
+ ```
133
+
134
+ ### Catch a security event in logs
135
+
136
+ ```python
137
+ result = wg.scan(
138
+ training=[
139
+ "2026-02-24 10:15:03 INFO Request processed in 45ms [200 OK]",
140
+ "2026-02-24 10:15:04 INFO Request processed in 52ms [200 OK]",
141
+ "2026-02-24 10:15:05 INFO Cache hit ratio=0.94 ttl=300s",
142
+ ],
143
+ test=[
144
+ "2026-02-24 10:20:03 INFO Request processed in 48ms [200 OK]", # ✅ normal
145
+ "2026-02-24 10:20:04 CRIT xmrig consuming 98% CPU, port 45678 open", # 🚨 crypto miner
146
+ "2026-02-24 10:20:05 WARN GET /api/users?id=1;DROP TABLE users-- from 185.x.x", # 🚨 SQL injection
147
+ ],
148
+ encoder_type="text",
149
+ )
150
+ ```
151
+
152
+ **Same client. Same `scan()` call. Any data.**
153
+
154
+ ## Use Cases
155
+
156
+ WaveGuard works on **any structured, numeric, or text data**. If you can describe "normal," it can detect deviations.
157
+
158
+ | Industry | What You Scan | What It Catches |
159
+ |----------|---------------|------------------|
160
+ | **DevOps** | Server metrics (CPU, memory, latency) | Memory leaks, DDoS attacks, runaway processes |
161
+ | **Fintech** | Transactions (amount, velocity, location) | Fraud, money laundering, account takeover |
162
+ | **Security** | Log files, access events | SQL injection, crypto miners, privilege escalation |
163
+ | **IoT / Manufacturing** | Sensor readings (temp, pressure, vibration) | Equipment failure, calibration drift |
164
+ | **E-commerce** | User behavior (session time, cart, clicks) | Bot traffic, bulk purchase fraud, scraping |
165
+ | **Healthcare** | Lab results, vitals, biomarkers | Abnormal readings, data entry errors |
166
+ | **Time Series** | Metric windows (latency, throughput) | Spikes, flatlines, seasonal breaks |
167
+
168
+ **The API doesn't know your domain.** It just knows what "normal" looks like (your training data) and flags anything that deviates. This makes it general — you bring the context, it brings the detection.
169
+
170
+ ### Supported Data Types
171
+
172
+ All auto-detected from data shape. No configuration needed:
173
+
174
+ | Type | Example | Use When |
175
+ |------|---------|----------|
176
+ | JSON objects | `{"cpu": 45, "memory": 62}` | Structured records with named fields |
177
+ | Numeric arrays | `[1.0, 1.2, 5.8, 1.1]` | Feature vectors, embeddings |
178
+ | Text strings | `"ERROR segfault at 0x0"` | Logs, messages, free text |
179
+ | Time series | `[100, 102, 98, 105, 99]` | Metric windows, sequential readings |
180
+
181
+ ## Examples
182
+
183
+ Every example is a runnable Python script. They span **6 industries and 4 data types** to show WaveGuard isn't tied to one domain:
184
+
185
+ | # | Example | Industry | Data Type | What It Shows |
186
+ |---|---------|----------|-----------|---------------|
187
+ | 01 | [Quickstart](examples/01_quickstart.py) | General | JSON | Minimal scan in 10 lines |
188
+ | 02 | [Server Monitoring](examples/02_server_monitoring.py) | DevOps | JSON | Memory leak + DDoS detection |
189
+ | 03 | [Log Analysis](examples/03_log_analysis.py) | Security | Text | SQL injection, crypto miner, stack traces |
190
+ | 04 | [Time Series](examples/04_time_series.py) | Monitoring | Numeric | Latency spikes, flatline detection |
191
+ | 05 | [Azure Migration](examples/05_azure_migration.py) | Enterprise | JSON | Side-by-side Azure replacement |
192
+ | 06 | [Batch Scanning](examples/06_batch_scanning.py) | E-commerce | JSON | 20 transactions, fraud flagging |
193
+ | 07 | [Error Handling](examples/07_error_handling.py) | Production | — | Retry logic, exponential backoff |
194
+
195
+ ## MCP Server (Claude Desktop)
196
+
197
+ Give Claude the ability to detect anomalies. Add to your Claude Desktop config:
198
+
199
+ ```json
200
+ {
201
+ "mcpServers": {
202
+ "waveguard": {
203
+ "command": "python",
204
+ "args": ["/path/to/WaveGuardClient/mcp_server/server.py"],
205
+ "env": {
206
+ "WAVEGUARD_API_KEY": "your-key"
207
+ }
208
+ }
209
+ }
210
+ }
211
+ ```
212
+
213
+ Then ask Claude: *"Check if these server metrics are anomalous..."*
214
+
215
+ See [MCP Integration Guide](docs/mcp-integration.md) for full setup.
216
+
217
+ ## Azure Migration
218
+
219
+ **Azure Anomaly Detector retires October 2026.** WaveGuard is a drop-in replacement:
220
+
221
+ ```python
222
+ # Before (Azure) — 3+ API calls, stateful, time-series only
223
+ client = AnomalyDetectorClient(endpoint, credential)
224
+ model = client.train_multivariate_model(request) # minutes
225
+ result = client.detect_multivariate_batch_anomaly(model_id, data)
226
+ client.delete_multivariate_model(model_id)
227
+
228
+ # After (WaveGuard) — 1 API call, stateless, any data type
229
+ wg = WaveGuard(api_key="YOUR_KEY")
230
+ result = wg.scan(training=normal_data, test=new_data) # seconds
231
+ ```
232
+
233
+ See [Azure Migration Guide](docs/azure-migration.md) for details.
234
+
235
+ ## API Reference
236
+
237
+ ### `wg.scan(training, test, encoder_type=None, sensitivity=None)`
238
+
239
+ | Parameter | Type | Description |
240
+ |-----------|------|-------------|
241
+ | `training` | `list` | 2+ examples of normal data |
242
+ | `test` | `list` | 1+ samples to check |
243
+ | `encoder_type` | `str` | Force: `"json"`, `"numeric"`, `"text"`, `"timeseries"` (default: auto) |
244
+ | `sensitivity` | `float` | 0.5–3.0, lower = more sensitive (default: 1.0) |
245
+
246
+ Returns `ScanResult` with `.results` (per-sample) and `.summary` (aggregate).
247
+
248
+ ### `wg.health()` / `wg.tier()`
249
+
250
+ Health check (no auth) and subscription tier info.
251
+
252
+ ### Error Handling
253
+
254
+ ```python
255
+ from waveguard import WaveGuard, AuthenticationError, RateLimitError
256
+
257
+ try:
258
+ result = wg.scan(training=data, test=new_data)
259
+ except AuthenticationError:
260
+ print("Bad API key")
261
+ except RateLimitError:
262
+ print("Too many requests — back off and retry")
263
+ ```
264
+
265
+ Full API reference: [docs/api-reference.md](docs/api-reference.md)
266
+
267
+ ## Project Structure
268
+
269
+ ```
270
+ WaveGuardClient/
271
+ ├── waveguard/ # Python SDK package
272
+ │ ├── __init__.py # Public API exports
273
+ │ ├── client.py # WaveGuard client class
274
+ │ └── exceptions.py # Exception hierarchy
275
+ ├── mcp_server/ # MCP server for Claude Desktop
276
+ │ ├── server.py # stdio + HTTP transport
277
+ │ └── README.md # MCP setup guide
278
+ ├── examples/ # 7 runnable examples
279
+ ├── docs/ # Documentation
280
+ │ ├── getting-started.md
281
+ │ ├── api-reference.md
282
+ │ ├── mcp-integration.md
283
+ │ └── azure-migration.md
284
+ ├── tests/ # Test suite (runs offline)
285
+ ├── pyproject.toml # Package config (pip install -e .)
286
+ └── CHANGELOG.md
287
+ ```
288
+
289
+ ## Development
290
+
291
+ ```bash
292
+ git clone https://github.com/gpartin/WaveGuardClient.git
293
+ cd WaveGuardClient
294
+ pip install -e ".[dev]"
295
+ pytest
296
+ ```
297
+
298
+ ## Links
299
+
300
+ - **Live API**: https://gpartin--waveguard-api-fastapi-app.modal.run
301
+ - **Interactive Docs (Swagger)**: https://gpartin--waveguard-api-fastapi-app.modal.run/docs
302
+ - **PyPI**: https://pypi.org/project/WaveGuardClient/
303
+
304
+ ## License
305
+
306
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,8 @@
1
+ waveguard/__init__.py,sha256=FONe580D6xKXbfreD9l6tRvIgU2LGucadVsIlowZv_k,971
2
+ waveguard/client.py,sha256=OCuUwec3Z0HGat2PYob7OEGz4Vc--GqdJNmaFOF1QQU,11440
3
+ waveguard/exceptions.py,sha256=WX9vtgk_YptWU0d_JHGofsixL_UEBLDAasdS5YNeQic,1499
4
+ waveguardclient-2.0.0.dist-info/licenses/LICENSE,sha256=yV4vV4gmeJzzKCuJZPkv6esNWoR78ZIUHBIyq5vrSHA,1073
5
+ waveguardclient-2.0.0.dist-info/METADATA,sha256=zie4uYNXKAgdQFhAOFqAKBO-xvwNjpwe3xo3-zUf4dc,11943
6
+ waveguardclient-2.0.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
7
+ waveguardclient-2.0.0.dist-info/top_level.txt,sha256=JxjhFv0TRFvmE4KlZrJMq9F65YkIx9TodmCSOO5Mzvo,10
8
+ waveguardclient-2.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026 Greg Partin
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 @@
1
+ waveguard