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
Metadata
Release files for returnguard 1.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| returnguard-1.2.2.tar.gz | 25.3 kB | Without provenance | Details for returnguard-1.2.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| returnguard-1.2.2-py3-none-any.whl | Python 3 | none | any | Details for returnguard-1.2.2-py3-none-any.whl |
Total release size: 47.4 kB
Release files / returnguard-1.2.2.tar.gz
| Download URL | returnguard-1.2.2.tar.gz |
|---|---|
| Size | 25.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1ce91e891a9e18b153ee51767ff262e6266e2767e3a9df7981b1db09357ef752
|
|
BLAKE2b-256 checksum How to use checksums |
63dab5158c7d1e8f7cb16bc1015f77d14586c4bf8c1d9b475bbf4a0f9413b1ca
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Release files / returnguard-1.2.2-py3-none-any.whl
| Download URL | returnguard-1.2.2-py3-none-any.whl |
|---|---|
| Size | 22.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17a3e909e820b74b6b527a6157be57dac817268aa4c9b7be157f8153186fe9bc
|
|
BLAKE2b-256 checksum How to use checksums |
a6c12388540ff2c471b3352f218b976ea68aae1e01e21af9cdf7078e2e6f11d3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.9
|
Security for returnguard 1.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.