Spaces:
Running on Zero
Running on Zero
| """ | |
| campaign.py | |
| ----------- | |
| Merchant promotional campaign registration and risk signal adjustment. | |
| During a registered promotional campaign (e.g., FLASH_SALE): | |
| - Volume velocity expectations are adjusted by expected_volume_multiplier. | |
| - Fraud-excess evidence REMAINS ACTIVE. | |
| - High transaction fraud probabilities and elevated fraud-excess ratios STILL trigger VERIFY / ALERT. | |
| """ | |
| from __future__ import annotations | |
| from datetime import datetime | |
| from typing import Any | |
| from src.risk_engine.schemas import CampaignRegistration | |
| class CampaignManager: | |
| """Manages active merchant promotional campaign registrations.""" | |
| def __init__(self): | |
| self.campaigns: dict[str, list[CampaignRegistration]] = {} | |
| def register_campaign(self, campaign: CampaignRegistration): | |
| """Registers a new promotional campaign for a merchant.""" | |
| if campaign.merchant_id not in self.campaigns: | |
| self.campaigns[campaign.merchant_id] = [] | |
| self.campaigns[campaign.merchant_id].append(campaign) | |
| def is_campaign_active(self, merchant_id: str, event_time: datetime) -> tuple[bool, float]: | |
| """ | |
| Checks if a campaign is active for the merchant at event_time. | |
| Returns (is_active, expected_volume_multiplier). | |
| """ | |
| if merchant_id not in self.campaigns: | |
| return False, 1.0 | |
| for cmp in self.campaigns[merchant_id]: | |
| if cmp.start_time <= event_time <= cmp.end_time: | |
| return True, cmp.expected_volume_multiplier | |
| return False, 1.0 | |
| def adjust_features_for_campaign( | |
| self, | |
| features: dict[str, float], | |
| is_campaign_active: bool, | |
| volume_multiplier: float, | |
| ) -> dict[str, float]: | |
| """ | |
| Adjusts velocity expectations during active campaigns while preserving fraud excess signals. | |
| """ | |
| adj_features = features.copy() | |
| if is_campaign_active: | |
| # Dampen velocity ratio by expected campaign volume multiplier | |
| raw_velocity = adj_features.get("velocity_ratio", 1.0) | |
| adj_velocity = max(1.0, raw_velocity / max(1.0, volume_multiplier)) | |
| adj_features["velocity_ratio"] = adj_velocity | |
| adj_features["volume_deviation"] = adj_velocity | |
| adj_features["fraud_excess_minus_velocity"] = ( | |
| adj_features.get("fraud_excess_ratio", 1.0) - adj_velocity | |
| ) | |
| return adj_features | |
| def clear(self): | |
| self.campaigns.clear() | |