Event + Context Pattern¶
The Event + Context pattern is the foundational abstraction that makes rotalabs-cascade domain-agnostic. It cleanly separates what happened (the Event) from the circumstances surrounding it (the Context), enabling the same cascade routing logic to work across finance, healthcare, cybersecurity, content moderation, and any other domain.
Overview¶
Traditional detection systems are tightly coupled to their domain. A fraud detection system speaks in "transactions" and "amounts," while a content moderation system speaks in "posts" and "violations." This coupling makes it difficult to share infrastructure, patterns, and learnings across domains.
The Event + Context pattern solves this by providing universal abstractions:
- UniversalEvent: Describes what happened in domain-neutral terms
- EventContext: Captures the circumstances (who, where, when, history)
- EventWithContext: Combines both as input to the cascade engine
UniversalEvent Structure¶
The UniversalEvent dataclass represents any action or occurrence that needs to be evaluated by the cascade:
from dataclasses import dataclass, field
from datetime import datetime
from typing import Any, Dict, Optional
@dataclass
class UniversalEvent:
id: str # Unique event identifier
domain: DomainType # Domain this event belongs to
event_type: str # Type of event (transaction, post, claim, etc.)
timestamp: datetime # When the event occurred
primary_entity: str # Who initiated (user, customer, patient)
secondary_entity: str # Target/recipient (merchant, provider, system)
value: float # Numeric value (amount, size, severity 0-1)
unit: str # Unit of value (USD, bytes, severity_score)
domain_data: Dict[str, Any] # Domain-specific payload
correlation_id: Optional[str] = None
source_system: Optional[str] = None
event_version: str = "1.0"
Field Semantics by Domain¶
| Field | Finance | Content Moderation | Cybersecurity | Healthcare |
|---|---|---|---|---|
primary_entity |
user_id | author_id | user_id | patient_id |
secondary_entity |
merchant_id | forum/channel | resource | provider_id |
value |
amount | content_length | risk_score | claim_amount |
unit |
USD/EUR | characters | risk_score | USD |
domain_data |
card_type, mcc | content, has_media | action, ip | diagnosis, codes |
Supported Domains¶
The DomainType enum defines the supported domains:
class DomainType(Enum):
FINANCE = "FINANCE"
HEALTHCARE = "HEALTHCARE"
SUPPLY_CHAIN = "SUPPLY_CHAIN"
CYBERSECURITY = "CYBERSECURITY"
CONTENT_MODERATION = "CONTENT_MODERATION"
CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT"
INSURANCE = "INSURANCE"
RETAIL = "RETAIL"
HR_RECRUITING = "HR_RECRUITING"
GENERIC = "GENERIC"
EventContext Structure¶
The EventContext dataclass captures contextual information surrounding an event:
@dataclass
class EventContext:
session: SessionContext # Session-related info
device: DeviceContext # Device-related info
location: LocationContext # Location-related info
historical: HistoricalContext # Historical behavior
entity: EntityContext # Entity information
domain_context: Dict[str, Any] # Domain-specific extension
context_timestamp: datetime # When context was captured
context_version: str = "1.0"
SessionContext¶
Captures session and authentication information:
@dataclass
class SessionContext:
ip_address: Optional[str] = None
user_agent: Optional[str] = None
session_id: Optional[str] = None
login_timestamp: Optional[datetime] = None
session_duration_minutes: Optional[int] = None
previous_session_count: Optional[int] = None
is_authenticated: Optional[bool] = None
auth_method: Optional[str] = None # password, oauth, sso, mfa
DeviceContext¶
Captures device fingerprint and characteristics:
@dataclass
class DeviceContext:
device_id: Optional[str] = None
device_type: Optional[str] = None # mobile, desktop, tablet, iot
device_info: Optional[str] = None
operating_system: Optional[str] = None
browser: Optional[str] = None
app_version: Optional[str] = None
is_trusted_device: Optional[bool] = None
device_fingerprint: Optional[str] = None
LocationContext¶
Captures geographic and network location:
@dataclass
class LocationContext:
current_location: Optional[str] = None
registered_location: Optional[str] = None
coordinates: Optional[Dict[str, float]] = None # {lat, lng}
country: Optional[str] = None
region: Optional[str] = None
city: Optional[str] = None
timezone: Optional[str] = None
vpn_detected: Optional[bool] = None
proxy_detected: Optional[bool] = None
location_accuracy: Optional[str] = None # precise, city, region, country
HistoricalContext¶
Captures behavioral history and trust signals:
@dataclass
class HistoricalContext:
account_age_days: Optional[int] = None
previous_events_count: Optional[int] = None
average_event_value: Optional[float] = None
max_event_value: Optional[float] = None
typical_event_times: Optional[List[str]] = None # ["09:00-17:00"]
typical_locations: Optional[List[str]] = None
last_event_timestamp: Optional[datetime] = None
event_frequency_per_day: Optional[float] = None
trust_score: Optional[float] = None # 0-1 historical trust
risk_flags: Optional[List[str]] = None
EntityContext¶
Captures information about the entities involved:
@dataclass
class EntityContext:
primary_entity_id: Optional[str] = None
primary_entity_type: Optional[str] = None # user, customer, patient
primary_entity_verified: Optional[bool] = None
secondary_entity_id: Optional[str] = None
secondary_entity_type: Optional[str] = None # merchant, provider, vendor
secondary_entity_trust: Optional[float] = None
relationship_age_days: Optional[int] = None
EventWithContext¶
The EventWithContext dataclass combines both structures and serves as the primary input to the cascade engine:
Example Usage¶
from rotalabs_cascade import (
UniversalEvent, EventContext, EventWithContext,
DomainType, SessionContext, HistoricalContext, CascadeEngine
)
from datetime import datetime
# Create the event
event = UniversalEvent(
id="evt_abc123",
domain=DomainType.CONTENT_MODERATION,
event_type="post",
timestamp=datetime.now(),
primary_entity="user_456",
secondary_entity="forum_general",
value=0.0,
unit="post",
domain_data={
"content": "Check out this amazing deal!",
"has_media": False,
"mentions": ["@everyone"]
}
)
# Create the context
context = EventContext(
session=SessionContext(
ip_address="192.168.1.100",
is_authenticated=True
),
historical=HistoricalContext(
account_age_days=30,
previous_events_count=100,
trust_score=0.85
)
)
# Combine into EventWithContext
event_with_context = EventWithContext(event=event, context=context)
# Execute through cascade
result = await engine.execute(event_with_context)
Domain Examples¶
Finance: Transaction Fraud Detection¶
from rotalabs_cascade import create_finance_event, EventContext, EventWithContext
# Use the factory function for finance events
event = create_finance_event(
transaction_id="txn_789",
user_id="cust_123",
merchant_id="merch_456",
amount=2500.00,
currency="USD",
transaction_type="purchase",
# Additional domain data
card_type="credit",
mcc_code="5411", # Grocery stores
is_card_present=False
)
context = EventContext(
session=SessionContext(
ip_address="203.0.113.50",
auth_method="mfa"
),
device=DeviceContext(
device_type="mobile",
is_trusted_device=True
),
location=LocationContext(
country="US",
region="CA",
vpn_detected=False
),
historical=HistoricalContext(
account_age_days=730,
average_event_value=150.00,
max_event_value=500.00,
trust_score=0.92
)
)
event_with_context = EventWithContext(event=event, context=context)
Content Moderation: Post Review¶
from rotalabs_cascade import create_content_event, EventContext, EventWithContext
# Use the factory function for content events
event = create_content_event(
content_id="post_abc123",
user_id="author_456",
target="community_gaming",
content_type="post",
content="Join my Discord for free giveaways! Click here: bit.ly/xxx",
# Additional domain data
has_links=True,
link_count=1,
mentions_count=0,
hashtags=["giveaway", "free"]
)
context = EventContext(
session=SessionContext(
ip_address="198.51.100.25",
user_agent="Mozilla/5.0..."
),
historical=HistoricalContext(
account_age_days=3,
previous_events_count=5,
trust_score=0.2,
risk_flags=["new_account", "rapid_posting"]
)
)
event_with_context = EventWithContext(event=event, context=context)
Cybersecurity: Access Attempt¶
from rotalabs_cascade import create_security_event, EventContext, EventWithContext
# Use the factory function for security events
event = create_security_event(
event_id="sec_xyz789",
user_id="employee_123",
resource="database_prod",
action="access_attempt",
risk_score=0.7,
# Additional domain data
requested_permission="read",
time_of_day="02:30",
is_after_hours=True
)
context = EventContext(
session=SessionContext(
ip_address="10.0.0.50",
auth_method="sso"
),
device=DeviceContext(
device_type="desktop",
is_trusted_device=False,
device_fingerprint="unknown"
),
location=LocationContext(
country="CN",
vpn_detected=True
),
historical=HistoricalContext(
typical_event_times=["09:00-17:00"],
typical_locations=["US"],
trust_score=0.6
)
)
event_with_context = EventWithContext(event=event, context=context)
Healthcare: Claim Review¶
from rotalabs_cascade import UniversalEvent, DomainType, EventContext, EventWithContext
from datetime import datetime
event = UniversalEvent(
id="claim_12345",
domain=DomainType.HEALTHCARE,
event_type="claim",
timestamp=datetime.now(),
primary_entity="patient_789",
secondary_entity="provider_456",
value=15000.00,
unit="USD",
domain_data={
"diagnosis_codes": ["J18.9", "R05"],
"procedure_codes": ["99213", "94640"],
"service_date": "2024-01-15",
"facility_type": "outpatient"
}
)
context = EventContext(
entity=EntityContext(
primary_entity_type="patient",
secondary_entity_type="provider",
secondary_entity_trust=0.95,
relationship_age_days=1825 # 5 years
),
historical=HistoricalContext(
average_event_value=500.00,
max_event_value=3000.00,
previous_events_count=45
),
domain_context={
"provider_specialty": "pulmonology",
"patient_insurance_type": "PPO",
"pre_authorization": True
}
)
event_with_context = EventWithContext(event=event, context=context)
Why Domain-Agnostic?¶
The Event + Context pattern enables several powerful capabilities:
1. Shared Infrastructure¶
The same cascade engine, routing rules, and execution infrastructure work across all domains. You build it once and apply it everywhere.
2. Pattern Transfer¶
Patterns learned in one domain can inform detection in another. A velocity check pattern in fraud detection (too many transactions too fast) maps naturally to spam detection (too many posts too fast).
3. Unified Observability¶
Metrics, logging, and monitoring use the same schema regardless of domain. A single dashboard can show detection rates, stage latencies, and cost metrics across your entire organization.
4. Consistent Developer Experience¶
Teams working on different domains use the same APIs, configuration format, and mental model. Knowledge transfers easily between teams.
5. Flexible Extension¶
The domain_data field in UniversalEvent and domain_context field in EventContext provide escape hatches for domain-specific data without breaking the universal schema.
Converting from Flat Dictionaries¶
For backward compatibility, EventWithContext can be converted to a flat dictionary:
event_with_context = EventWithContext(event=event, context=context)
# Get flat dictionary for ExecutionContext
flat_dict = event_with_context.to_flat_dict()
# Contains both nested structure and top-level shortcuts:
# {
# "event": {...},
# "context": {...},
# "event_id": "evt_abc123",
# "domain": "CONTENT_MODERATION",
# "value": 0.0,
# "ip_address": "192.168.1.100",
# "account_age_days": 30,
# ...
# }
Parsing from API Requests¶
When receiving events from external systems, use from_dict:
# Incoming API request
request_data = {
"event": {
"id": "evt_123",
"domain": "FINANCE",
"event_type": "transaction",
"timestamp": "2024-01-15T10:30:00",
"primary_entity": "user_456",
"secondary_entity": "merchant_789",
"value": 100.00,
"unit": "USD",
"domain_data": {"card_type": "debit"}
},
"context": {
"session": {"ip_address": "192.168.1.1"},
"historical": {"account_age_days": 365}
}
}
# Parse into strongly-typed objects
event_with_context = EventWithContext.from_dict(request_data)
Summary¶
The Event + Context pattern is the foundation that makes rotalabs-cascade truly domain-agnostic:
- UniversalEvent captures what happened using generic fields that map to any domain
- EventContext captures the circumstances through structured sub-contexts
- EventWithContext combines both as the primary input to cascade processing
- Factory functions (
create_finance_event,create_content_event, etc.) simplify event creation - The pattern enables shared infrastructure, pattern transfer, and consistent developer experience