| import numpy as np |
|
|
| class FraudGuard: |
| """ |
| Hyperlocal Fraud Shield & SLA Penalty Engine - Upgraded. |
| Features: |
| 1. Dynamic COD Risk Gatekeeper. |
| 2. Rider Theft Sentinel with GPS movement verification. |
| 3. Merchant SLA Penalty Engine with Cold Food Escrow & Search rank demotion. |
| 4. Semantic Plausibility Checker to block copy-paste template refund scams. |
| 5. User Auto-Refund Cap (prevents escrow draining by limiting users to 1 auto-refund/30d). |
| 6. Cloud-Kitchen Tenure Guard (prevents false positives in multi-tenant kitchen hubs). |
| """ |
| def __init__(self, cod_threshold=0.15, fraud_threshold_high=0.60, fraud_threshold_low=0.20): |
| self.cod_threshold = cod_threshold |
| self.fraud_threshold_high = fraud_threshold_high |
| self.fraud_threshold_low = fraud_threshold_low |
| self.merchant_registry = {} |
|
|
| def get_merchant_metrics(self, merchant_id): |
| if merchant_id not in self.merchant_registry: |
| self.merchant_registry[merchant_id] = { |
| "merchant_id": merchant_id, |
| "order_count": 0, |
| "cold_food_complaints": 0, |
| "escrow_balance": 0.0, |
| "search_visibility_factor": 1.0, |
| "high_cold_food_alert": False |
| } |
| return self.merchant_registry[merchant_id] |
|
|
| |
| def predict_cod_rejection_risk(self, user_cancellation_rate, user_rating, order_value, hour_of_day): |
| score = -2.5 + (5.0 * user_cancellation_rate) - (0.8 * (user_rating - 4.5)) + (0.001 * order_value) |
| if hour_of_day >= 22 or hour_of_day <= 4: |
| score += 0.5 |
| |
| prob = 1.0 / (1.0 + np.exp(-score)) |
| is_cod_allowed = prob <= self.cod_threshold |
| return float(prob), is_cod_allowed |
|
|
| |
| def validate_breakdown_claim(self, rider_id, historical_claims_30d, current_velocity_mps): |
| is_suspicious_frequency = historical_claims_30d > 3 |
| is_gps_moving = current_velocity_mps > 2.0 |
| is_valid_breakdown = not (is_suspicious_frequency or is_gps_moving) |
| |
| reasons = [] |
| if is_suspicious_frequency: reasons.append("HIGH_FREQUENCY_CLAIMS") |
| if is_gps_moving: reasons.append("GPS_MOVEMENT_DETECTED") |
| |
| return is_valid_breakdown, reasons |
|
|
| |
| def record_complaint(self, merchant_id, complaint_type, order_value): |
| m = self.get_merchant_metrics(merchant_id) |
| m["order_count"] += 1 |
| |
| if complaint_type == "cold_food": |
| m["cold_food_complaints"] += 1 |
| penalty = order_value * 1.10 |
| m["escrow_balance"] += penalty |
| |
| complaints = m["cold_food_complaints"] |
| if complaints >= 10: |
| m["search_visibility_factor"] = 0.20 |
| m["high_cold_food_alert"] = True |
| elif complaints >= 5: |
| m["search_visibility_factor"] = 0.70 |
| m["high_cold_food_alert"] = True |
| else: |
| m["search_visibility_factor"] = 1.00 |
| m["high_cold_food_alert"] = False |
|
|
| def auto_resolve_refund(self, merchant_id, complaint_type, user_auto_refunds_30d=0): |
| """ |
| Decides if a customer complaint for cold food should be automatically resolved |
| from merchant escrow pool. Enforces a user-level cap to prevent abuse. |
| """ |
| m = self.get_merchant_metrics(merchant_id) |
| |
| if complaint_type == "cold_food" and m["high_cold_food_alert"]: |
| |
| if user_auto_refunds_30d >= 1: |
| return False, "EXCEEDED_USER_AUTO_REFUND_LIMIT" |
| return True, "AUTO_REFUND_APPROVED_PEER_SIGNAL" |
| |
| return False, "STANDARD_PROOF_REQUIRED" |
|
|
| |
| def check_semantic_plausibility(self, complaint_text, items_list): |
| text_lower = complaint_text.lower() |
| items_lower = [i.lower() for i in items_list] |
| |
| |
| is_cold_complaint = any(word in text_lower for word in ["cold", "soggy", "ice cold", "not hot", "reheat"]) |
| if is_cold_complaint: |
| hot_items = ["fries", "burger", "pizza", "biryani", "chicken", "curry", "roti", "samosa", "momo"] |
| has_hot_item = any(hot_item in item for hot_item in hot_items for item in items_lower) |
| if not has_hot_item: |
| return False, "COLD_COMPLAINT_ON_DEFAULT_COLD_ITEMS" |
|
|
| |
| is_spill_complaint = any(word in text_lower for word in ["spill", "spilled", "leak", "leaked", "gravy out", "mess"]) |
| if is_spill_complaint: |
| liquid_items = ["dal", "curry", "soup", "gravy", "coke", "pepsi", "shake", "smoothie", "lassi"] |
| has_liquid_item = any(liquid_item in item for liquid_item in liquid_items for item in items_lower) |
| if not has_liquid_item: |
| return False, "SPILL_COMPLAINT_ON_DRY_ITEMS" |
|
|
| return True, "PLAUSIBLE_COMPLAINT" |
|
|
| |
| def detect_astroturfing_risk(self, distance_m, user_tenure_days, user_historical_orders, shared_ip=False): |
| """ |
| Detects astroturfing (fake ordering) while protecting multi-tenant cloud-kitchen hubs. |
| Bypasses fraud alert if user has high tenure and transaction history. |
| """ |
| is_colocated = distance_m < 50.0 |
| |
| |
| is_established_customer = (user_tenure_days > 90) and (user_historical_orders > 10) |
| |
| if is_colocated and not is_established_customer: |
| return True, "PROXIMITY_COLLISION_UNESTABLISHED_ACCOUNT" |
| if shared_ip and not is_established_customer: |
| return True, "SHARED_IP_UNESTABLISHED_ACCOUNT" |
| |
| return False, "NO_ASTROTURFING_RISK" |
|
|
| |
| def triage_refund_request(self, merchant_id, user_refund_ratio, user_tenure_days, user_historical_orders, user_auto_refunds_30d, delivery_duration_min, refund_amount_ratio, has_duplicate_hash, complaint_type, complaint_text, items_list): |
| """ |
| Triages customer refund requests. |
| """ |
| |
| is_plausible, plausibility_reason = self.check_semantic_plausibility(complaint_text, items_list) |
| if not is_plausible: |
| return "HUMAN_TAKEOVER", 0.99, f"SEMANTIC_FRAUD_DETECTED: {plausibility_reason}" |
|
|
| |
| auto_approved, reason = self.auto_resolve_refund(merchant_id, complaint_type, user_auto_refunds_30d) |
| if auto_approved: |
| return "AUTO_REFUND", 0.0, reason |
| elif reason == "EXCEEDED_USER_AUTO_REFUND_LIMIT": |
| return "VERIFICATION_REQUIRED", 0.35, reason |
| |
| |
| score = -3.0 + (6.0 * user_refund_ratio) + (4.0 * float(has_duplicate_hash)) + (2.0 * refund_amount_ratio) |
| |
| |
| if user_tenure_days < 10: |
| score += 1.0 |
| |
| if complaint_type == "cold_food": |
| if delivery_duration_min < 10.0: |
| score += 2.0 |
| elif delivery_duration_min > 25.0: |
| score -= 1.5 |
| |
| prob_fraud = 1.0 / (1.0 + np.exp(-score)) |
| |
| if prob_fraud >= self.fraud_threshold_high: |
| outcome = "HUMAN_TAKEOVER" |
| elif prob_fraud >= self.fraud_threshold_low: |
| outcome = "VERIFICATION_REQUIRED" |
| else: |
| outcome = "AUTO_REFUND" |
| |
| return outcome, float(prob_fraud), "CONTEXTUAL_FRAUD_CLASSIFICATION" |
|
|