Returns fraud detection for retail and eCommerce — wardrobing, serial returner, refund anomaly detection, behavioral fingerprinting, policy simulation
Project description
returnguard
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.
The Problem
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.
Installation
pip install returnguard
Quick Start
from returnguard import FraudScorer, ReturnRequest, ReturnReason
from datetime import datetime, timedelta
scorer = FraudScorer(
return_rate_threshold=0.30,
high_value_threshold=150.0,
velocity_limit=3,
)
request = ReturnRequest(
return_id="RET-001",
order_id="ORD-5521",
customer_id="CUST-42",
sku="SKU-JACKET-XL",
quantity=1,
reason=ReturnReason.CHANGED_MIND,
order_date=datetime.utcnow() - timedelta(days=3),
order_value=220.00,
channel="shopify",
)
result = scorer.score(request)
print(result.risk_level) # RiskLevel.HIGH
print(result.score) # 0.6
print(result.signals) # [FraudSignal.WARDROBING]
print(result.recommended_action) # "Require photo evidence + manual review"
Fraud Signals Detected
| Signal | Description |
|---|---|
HIGH_RETURN_RATE |
Customer's return rate exceeds threshold |
WARDROBING |
Use-and-return: changed_mind + < 7 days + high-value item |
VELOCITY |
Too many returns in a short window |
SERIAL_RETURNER |
Customer has been flagged multiple times |
POLICY_ABUSE |
Return submitted after policy window |
Customer Profile Tracking
from returnguard import CustomerProfile
profile = CustomerProfile(
customer_id="CUST-42",
total_orders=20,
total_returns=8,
flagged_count=2,
)
result = scorer.score(request, profile=profile)
# Score accounts for historical return behaviour
Risk Levels & Actions
| Risk Level | Score | Recommended Action |
|---|---|---|
| LOW | 0.0–0.30 | Auto-approve |
| MEDIUM | 0.30–0.55 | Flag for manual review |
| HIGH | 0.55–0.75 | Require photo evidence |
| CRITICAL | 0.75–1.0 | Block + escalate to fraud team |
Batch Scoring
from returnguard import batch_score, abatch_score
# Sync
scores = batch_score(requests, scorer.score, max_workers=8)
# Async
scores = await abatch_score(requests, scorer.score, max_concurrency=8)
Advanced Features
Pipeline
from returnguard import FraudPipeline
pipeline = (
FraudPipeline()
.filter(lambda s: s.score > 0.5, name="high_risk_only")
.map(lambda scores: sorted(scores, key=lambda s: -s.score), name="sort_by_risk")
.with_retry(count=2)
)
high_risk = pipeline.run(all_scores)
print(pipeline.audit_log())
Caching
from returnguard import FraudCache
cache = FraudCache(max_size=1000, ttl_seconds=600)
@cache.memoize
def score_with_cache(request):
return scorer.score(request)
print(cache.stats())
Validation
from returnguard import ReturnValidator, ReturnRule
validator = ReturnValidator()
validator.add_rule(ReturnRule("max_days", 60, "Return window expired"))
validator.add_rule(ReturnRule("max_order_value", 1000, "High-value item requires manual review"))
valid, errors = validator.validate(request)
Diff & Trend
from returnguard import diff_scores, RiskTrend
diff = diff_scores(previous_scores, current_scores)
print(diff.summary()) # {'added': 3, 'removed': 1, 'modified': 2}
trend = RiskTrend(window=20)
for score in historical_scores:
trend.record(score.score)
print(trend.trend()) # "increasing"
print(trend.volatility()) # 0.12
Streaming & NDJSON
from returnguard import stream_scores, scores_to_ndjson
for score in stream_scores(results):
process(score)
for line in scores_to_ndjson(results):
file.write(line)
Audit Log
from returnguard import AuditLog
log = AuditLog()
log.record("scored", "RET-001", detail="risk=high")
log.record("blocked", "RET-001")
entries = log.export()
Changelog
v1.2.2 (2026-04-10)
- Added Contributing and Author sections to README
v1.2.1 (2026-04-10)
- Fixed type error in
SimulationResult.to_dict()—max()key now uses explicit lambda for reliableSupportsRichComparisonresolution - Fixed Pylance
reportMissingImportson optionalopentelemetryimports inReturnSpanEmitter— guarded with# type: ignore[import-untyped](runtime behaviour unchanged; opentelemetry remains optional)
v1.2.0 (2026-03-xx)
- Added
RefundAnomalyDetector— statistical anomaly detection on refund amounts - Added
BehaviorFingerprinter— fingerprint customer return behaviour patterns - Expanded SEO keywords for PyPI discoverability
v1.0.1
- Advanced features update: pipeline, caching, validation, diff/trend, streaming, audit log
v1.0.0
- Initial release: core fraud scoring, wardrobing, serial returner, velocity, policy abuse detection
License
MIT
Contributing
Contributions are welcome! Here's how to get started:
- Fork the repository on GitHub
- Create a feature branch:
git checkout -b feature/your-feature - Make your changes and add tests
- Run the test suite:
pytest tests/ -v - Submit a pull request
Please open an issue first for major changes to discuss the approach.
Author
Mahesh Makvana — GitHub · PyPI
MIT License
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file returnguard-1.2.2.tar.gz.
File metadata
- Download URL: returnguard-1.2.2.tar.gz
- Upload date:
- Size: 25.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1ce91e891a9e18b153ee51767ff262e6266e2767e3a9df7981b1db09357ef752
|
|
| MD5 |
77ff0815ac6174c0cb6923458c2ddc28
|
|
| BLAKE2b-256 |
63dab5158c7d1e8f7cb16bc1015f77d14586c4bf8c1d9b475bbf4a0f9413b1ca
|
File details
Details for the file returnguard-1.2.2-py3-none-any.whl.
File metadata
- Download URL: returnguard-1.2.2-py3-none-any.whl
- Upload date:
- Size: 22.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
17a3e909e820b74b6b527a6157be57dac817268aa4c9b7be157f8153186fe9bc
|
|
| MD5 |
48c1f106f4f4f041a549f316c8bf0397
|
|
| BLAKE2b-256 |
a6c12388540ff2c471b3352f218b976ea68aae1e01e21af9cdf7078e2e6f11d3
|