returnguard 1.0.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.
- returnguard-1.0.0/PKG-INFO +209 -0
- returnguard-1.0.0/README.md +180 -0
- returnguard-1.0.0/returnguard/__init__.py +68 -0
- returnguard-1.0.0/returnguard/advanced.py +408 -0
- returnguard-1.0.0/returnguard/exceptions.py +17 -0
- returnguard-1.0.0/returnguard/models.py +107 -0
- returnguard-1.0.0/returnguard/scorer.py +136 -0
- returnguard-1.0.0/returnguard.egg-info/PKG-INFO +209 -0
- returnguard-1.0.0/returnguard.egg-info/SOURCES.txt +13 -0
- returnguard-1.0.0/returnguard.egg-info/dependency_links.txt +1 -0
- returnguard-1.0.0/returnguard.egg-info/requires.txt +1 -0
- returnguard-1.0.0/returnguard.egg-info/top_level.txt +1 -0
- returnguard-1.0.0/setup.cfg +4 -0
- returnguard-1.0.0/setup.py +37 -0
- returnguard-1.0.0/tests/test_returnguard.py +297 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: returnguard
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: AI-powered returns fraud detection for retail and eCommerce — score return requests, detect wardrobing, serial returners, and policy abuse
|
|
5
|
+
Home-page: https://github.com/returnguard-py/returnguard
|
|
6
|
+
Author:
|
|
7
|
+
Keywords: returns fraud detection,ecommerce fraud,retail fraud,wardrobing detection,return policy abuse,refund fraud,fraud scoring python,return rate analysis,customer fraud detection,shopify fraud detection
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Requires-Python: >=3.8
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: pydantic>=2.0
|
|
21
|
+
Dynamic: classifier
|
|
22
|
+
Dynamic: description
|
|
23
|
+
Dynamic: description-content-type
|
|
24
|
+
Dynamic: home-page
|
|
25
|
+
Dynamic: keywords
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|
|
29
|
+
|
|
30
|
+
# returnguard
|
|
31
|
+
|
|
32
|
+
**AI-powered returns fraud detection for retail and eCommerce** — score return requests, detect wardrobing, serial returners, bot patterns, and policy abuse. No $50K enterprise contract required.
|
|
33
|
+
|
|
34
|
+
[](https://pypi.org/project/returnguard/)
|
|
35
|
+
[](https://www.python.org/)
|
|
36
|
+
|
|
37
|
+
## The Problem
|
|
38
|
+
|
|
39
|
+
Returns fraud costs US retailers $101B/year. AI-generated fraud has "exploded overnight." Enterprise solutions (Happy Returns, Narvar) target only large retail — Shopify has **zero** built-in fraud scoring. Mid-market merchants are on their own.
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install returnguard
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from returnguard import FraudScorer, ReturnRequest, ReturnReason
|
|
51
|
+
from datetime import datetime, timedelta
|
|
52
|
+
|
|
53
|
+
scorer = FraudScorer(
|
|
54
|
+
return_rate_threshold=0.30,
|
|
55
|
+
high_value_threshold=150.0,
|
|
56
|
+
velocity_limit=3,
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
request = ReturnRequest(
|
|
60
|
+
return_id="RET-001",
|
|
61
|
+
order_id="ORD-5521",
|
|
62
|
+
customer_id="CUST-42",
|
|
63
|
+
sku="SKU-JACKET-XL",
|
|
64
|
+
quantity=1,
|
|
65
|
+
reason=ReturnReason.CHANGED_MIND,
|
|
66
|
+
order_date=datetime.utcnow() - timedelta(days=3),
|
|
67
|
+
order_value=220.00,
|
|
68
|
+
channel="shopify",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
result = scorer.score(request)
|
|
72
|
+
print(result.risk_level) # RiskLevel.HIGH
|
|
73
|
+
print(result.score) # 0.6
|
|
74
|
+
print(result.signals) # [FraudSignal.WARDROBING]
|
|
75
|
+
print(result.recommended_action) # "Require photo evidence + manual review"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Fraud Signals Detected
|
|
79
|
+
|
|
80
|
+
| Signal | Description |
|
|
81
|
+
|---|---|
|
|
82
|
+
| `HIGH_RETURN_RATE` | Customer's return rate exceeds threshold |
|
|
83
|
+
| `WARDROBING` | Use-and-return: changed_mind + < 7 days + high-value item |
|
|
84
|
+
| `VELOCITY` | Too many returns in a short window |
|
|
85
|
+
| `SERIAL_RETURNER` | Customer has been flagged multiple times |
|
|
86
|
+
| `POLICY_ABUSE` | Return submitted after policy window |
|
|
87
|
+
|
|
88
|
+
## Customer Profile Tracking
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from returnguard import CustomerProfile
|
|
92
|
+
|
|
93
|
+
profile = CustomerProfile(
|
|
94
|
+
customer_id="CUST-42",
|
|
95
|
+
total_orders=20,
|
|
96
|
+
total_returns=8,
|
|
97
|
+
flagged_count=2,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
result = scorer.score(request, profile=profile)
|
|
101
|
+
# Score accounts for historical return behaviour
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Risk Levels & Actions
|
|
105
|
+
|
|
106
|
+
| Risk Level | Score | Recommended Action |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| LOW | 0.0–0.30 | Auto-approve |
|
|
109
|
+
| MEDIUM | 0.30–0.55 | Flag for manual review |
|
|
110
|
+
| HIGH | 0.55–0.75 | Require photo evidence |
|
|
111
|
+
| CRITICAL | 0.75–1.0 | Block + escalate to fraud team |
|
|
112
|
+
|
|
113
|
+
## Batch Scoring
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from returnguard import batch_score, abatch_score
|
|
117
|
+
|
|
118
|
+
# Sync
|
|
119
|
+
scores = batch_score(requests, scorer.score, max_workers=8)
|
|
120
|
+
|
|
121
|
+
# Async
|
|
122
|
+
scores = await abatch_score(requests, scorer.score, max_concurrency=8)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Advanced Features
|
|
126
|
+
|
|
127
|
+
### Pipeline
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from returnguard import FraudPipeline
|
|
131
|
+
|
|
132
|
+
pipeline = (
|
|
133
|
+
FraudPipeline()
|
|
134
|
+
.filter(lambda s: s.score > 0.5, name="high_risk_only")
|
|
135
|
+
.map(lambda scores: sorted(scores, key=lambda s: -s.score), name="sort_by_risk")
|
|
136
|
+
.with_retry(count=2)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
high_risk = pipeline.run(all_scores)
|
|
140
|
+
print(pipeline.audit_log())
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Caching
|
|
144
|
+
|
|
145
|
+
```python
|
|
146
|
+
from returnguard import FraudCache
|
|
147
|
+
|
|
148
|
+
cache = FraudCache(max_size=1000, ttl_seconds=600)
|
|
149
|
+
|
|
150
|
+
@cache.memoize
|
|
151
|
+
def score_with_cache(request):
|
|
152
|
+
return scorer.score(request)
|
|
153
|
+
|
|
154
|
+
print(cache.stats())
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Validation
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from returnguard import ReturnValidator, ReturnRule
|
|
161
|
+
|
|
162
|
+
validator = ReturnValidator()
|
|
163
|
+
validator.add_rule(ReturnRule("max_days", 60, "Return window expired"))
|
|
164
|
+
validator.add_rule(ReturnRule("max_order_value", 1000, "High-value item requires manual review"))
|
|
165
|
+
|
|
166
|
+
valid, errors = validator.validate(request)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Diff & Trend
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from returnguard import diff_scores, RiskTrend
|
|
173
|
+
|
|
174
|
+
diff = diff_scores(previous_scores, current_scores)
|
|
175
|
+
print(diff.summary()) # {'added': 3, 'removed': 1, 'modified': 2}
|
|
176
|
+
|
|
177
|
+
trend = RiskTrend(window=20)
|
|
178
|
+
for score in historical_scores:
|
|
179
|
+
trend.record(score.score)
|
|
180
|
+
print(trend.trend()) # "increasing"
|
|
181
|
+
print(trend.volatility()) # 0.12
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Streaming & NDJSON
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from returnguard import stream_scores, scores_to_ndjson
|
|
188
|
+
|
|
189
|
+
for score in stream_scores(results):
|
|
190
|
+
process(score)
|
|
191
|
+
|
|
192
|
+
for line in scores_to_ndjson(results):
|
|
193
|
+
file.write(line)
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Audit Log
|
|
197
|
+
|
|
198
|
+
```python
|
|
199
|
+
from returnguard import AuditLog
|
|
200
|
+
|
|
201
|
+
log = AuditLog()
|
|
202
|
+
log.record("scored", "RET-001", detail="risk=high")
|
|
203
|
+
log.record("blocked", "RET-001")
|
|
204
|
+
entries = log.export()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## License
|
|
208
|
+
|
|
209
|
+
MIT
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# returnguard
|
|
2
|
+
|
|
3
|
+
**AI-powered returns fraud detection for retail and eCommerce** — score return requests, detect wardrobing, serial returners, bot patterns, and policy abuse. No $50K enterprise contract required.
|
|
4
|
+
|
|
5
|
+
[](https://pypi.org/project/returnguard/)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
|
|
8
|
+
## The Problem
|
|
9
|
+
|
|
10
|
+
Returns fraud costs US retailers $101B/year. AI-generated fraud has "exploded overnight." Enterprise solutions (Happy Returns, Narvar) target only large retail — Shopify has **zero** built-in fraud scoring. Mid-market merchants are on their own.
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install returnguard
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
from returnguard import FraudScorer, ReturnRequest, ReturnReason
|
|
22
|
+
from datetime import datetime, timedelta
|
|
23
|
+
|
|
24
|
+
scorer = FraudScorer(
|
|
25
|
+
return_rate_threshold=0.30,
|
|
26
|
+
high_value_threshold=150.0,
|
|
27
|
+
velocity_limit=3,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
request = ReturnRequest(
|
|
31
|
+
return_id="RET-001",
|
|
32
|
+
order_id="ORD-5521",
|
|
33
|
+
customer_id="CUST-42",
|
|
34
|
+
sku="SKU-JACKET-XL",
|
|
35
|
+
quantity=1,
|
|
36
|
+
reason=ReturnReason.CHANGED_MIND,
|
|
37
|
+
order_date=datetime.utcnow() - timedelta(days=3),
|
|
38
|
+
order_value=220.00,
|
|
39
|
+
channel="shopify",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
result = scorer.score(request)
|
|
43
|
+
print(result.risk_level) # RiskLevel.HIGH
|
|
44
|
+
print(result.score) # 0.6
|
|
45
|
+
print(result.signals) # [FraudSignal.WARDROBING]
|
|
46
|
+
print(result.recommended_action) # "Require photo evidence + manual review"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Fraud Signals Detected
|
|
50
|
+
|
|
51
|
+
| Signal | Description |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `HIGH_RETURN_RATE` | Customer's return rate exceeds threshold |
|
|
54
|
+
| `WARDROBING` | Use-and-return: changed_mind + < 7 days + high-value item |
|
|
55
|
+
| `VELOCITY` | Too many returns in a short window |
|
|
56
|
+
| `SERIAL_RETURNER` | Customer has been flagged multiple times |
|
|
57
|
+
| `POLICY_ABUSE` | Return submitted after policy window |
|
|
58
|
+
|
|
59
|
+
## Customer Profile Tracking
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from returnguard import CustomerProfile
|
|
63
|
+
|
|
64
|
+
profile = CustomerProfile(
|
|
65
|
+
customer_id="CUST-42",
|
|
66
|
+
total_orders=20,
|
|
67
|
+
total_returns=8,
|
|
68
|
+
flagged_count=2,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
result = scorer.score(request, profile=profile)
|
|
72
|
+
# Score accounts for historical return behaviour
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Risk Levels & Actions
|
|
76
|
+
|
|
77
|
+
| Risk Level | Score | Recommended Action |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| LOW | 0.0–0.30 | Auto-approve |
|
|
80
|
+
| MEDIUM | 0.30–0.55 | Flag for manual review |
|
|
81
|
+
| HIGH | 0.55–0.75 | Require photo evidence |
|
|
82
|
+
| CRITICAL | 0.75–1.0 | Block + escalate to fraud team |
|
|
83
|
+
|
|
84
|
+
## Batch Scoring
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from returnguard import batch_score, abatch_score
|
|
88
|
+
|
|
89
|
+
# Sync
|
|
90
|
+
scores = batch_score(requests, scorer.score, max_workers=8)
|
|
91
|
+
|
|
92
|
+
# Async
|
|
93
|
+
scores = await abatch_score(requests, scorer.score, max_concurrency=8)
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Advanced Features
|
|
97
|
+
|
|
98
|
+
### Pipeline
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from returnguard import FraudPipeline
|
|
102
|
+
|
|
103
|
+
pipeline = (
|
|
104
|
+
FraudPipeline()
|
|
105
|
+
.filter(lambda s: s.score > 0.5, name="high_risk_only")
|
|
106
|
+
.map(lambda scores: sorted(scores, key=lambda s: -s.score), name="sort_by_risk")
|
|
107
|
+
.with_retry(count=2)
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
high_risk = pipeline.run(all_scores)
|
|
111
|
+
print(pipeline.audit_log())
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Caching
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
from returnguard import FraudCache
|
|
118
|
+
|
|
119
|
+
cache = FraudCache(max_size=1000, ttl_seconds=600)
|
|
120
|
+
|
|
121
|
+
@cache.memoize
|
|
122
|
+
def score_with_cache(request):
|
|
123
|
+
return scorer.score(request)
|
|
124
|
+
|
|
125
|
+
print(cache.stats())
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Validation
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from returnguard import ReturnValidator, ReturnRule
|
|
132
|
+
|
|
133
|
+
validator = ReturnValidator()
|
|
134
|
+
validator.add_rule(ReturnRule("max_days", 60, "Return window expired"))
|
|
135
|
+
validator.add_rule(ReturnRule("max_order_value", 1000, "High-value item requires manual review"))
|
|
136
|
+
|
|
137
|
+
valid, errors = validator.validate(request)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Diff & Trend
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from returnguard import diff_scores, RiskTrend
|
|
144
|
+
|
|
145
|
+
diff = diff_scores(previous_scores, current_scores)
|
|
146
|
+
print(diff.summary()) # {'added': 3, 'removed': 1, 'modified': 2}
|
|
147
|
+
|
|
148
|
+
trend = RiskTrend(window=20)
|
|
149
|
+
for score in historical_scores:
|
|
150
|
+
trend.record(score.score)
|
|
151
|
+
print(trend.trend()) # "increasing"
|
|
152
|
+
print(trend.volatility()) # 0.12
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Streaming & NDJSON
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from returnguard import stream_scores, scores_to_ndjson
|
|
159
|
+
|
|
160
|
+
for score in stream_scores(results):
|
|
161
|
+
process(score)
|
|
162
|
+
|
|
163
|
+
for line in scores_to_ndjson(results):
|
|
164
|
+
file.write(line)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Audit Log
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from returnguard import AuditLog
|
|
171
|
+
|
|
172
|
+
log = AuditLog()
|
|
173
|
+
log.record("scored", "RET-001", detail="risk=high")
|
|
174
|
+
log.record("blocked", "RET-001")
|
|
175
|
+
entries = log.export()
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## License
|
|
179
|
+
|
|
180
|
+
MIT
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""returnguard — AI-powered returns fraud detection for retail and eCommerce."""
|
|
2
|
+
from returnguard.models import (
|
|
3
|
+
CustomerProfile,
|
|
4
|
+
FraudScore,
|
|
5
|
+
FraudSignal,
|
|
6
|
+
ReturnRequest,
|
|
7
|
+
ReturnReason,
|
|
8
|
+
RiskLevel,
|
|
9
|
+
)
|
|
10
|
+
from returnguard.scorer import FraudScorer
|
|
11
|
+
from returnguard.exceptions import (
|
|
12
|
+
ProfileError,
|
|
13
|
+
ReturnGuardError,
|
|
14
|
+
ScoringError,
|
|
15
|
+
ValidationError,
|
|
16
|
+
)
|
|
17
|
+
from returnguard.advanced import (
|
|
18
|
+
AuditLog,
|
|
19
|
+
CancellationToken,
|
|
20
|
+
FraudCache,
|
|
21
|
+
FraudDiff,
|
|
22
|
+
FraudPipeline,
|
|
23
|
+
FraudProfiler,
|
|
24
|
+
PIIScrubber,
|
|
25
|
+
RateLimiter,
|
|
26
|
+
ReturnRule,
|
|
27
|
+
ReturnValidator,
|
|
28
|
+
RiskTrend,
|
|
29
|
+
abatch_score,
|
|
30
|
+
batch_score,
|
|
31
|
+
diff_scores,
|
|
32
|
+
scores_to_ndjson,
|
|
33
|
+
stream_scores,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
__version__ = "1.0.0"
|
|
37
|
+
__all__ = [
|
|
38
|
+
# Core
|
|
39
|
+
"FraudScorer",
|
|
40
|
+
"FraudScore",
|
|
41
|
+
"FraudSignal",
|
|
42
|
+
"ReturnRequest",
|
|
43
|
+
"ReturnReason",
|
|
44
|
+
"RiskLevel",
|
|
45
|
+
"CustomerProfile",
|
|
46
|
+
# Exceptions
|
|
47
|
+
"ReturnGuardError",
|
|
48
|
+
"ScoringError",
|
|
49
|
+
"ProfileError",
|
|
50
|
+
"ValidationError",
|
|
51
|
+
# Advanced
|
|
52
|
+
"FraudCache",
|
|
53
|
+
"FraudPipeline",
|
|
54
|
+
"ReturnValidator",
|
|
55
|
+
"ReturnRule",
|
|
56
|
+
"RateLimiter",
|
|
57
|
+
"CancellationToken",
|
|
58
|
+
"batch_score",
|
|
59
|
+
"abatch_score",
|
|
60
|
+
"FraudProfiler",
|
|
61
|
+
"RiskTrend",
|
|
62
|
+
"stream_scores",
|
|
63
|
+
"scores_to_ndjson",
|
|
64
|
+
"FraudDiff",
|
|
65
|
+
"diff_scores",
|
|
66
|
+
"AuditLog",
|
|
67
|
+
"PIIScrubber",
|
|
68
|
+
]
|