Event + Context - rotalabs-cascade
Description: Domain-agnostic orchestration engine for multi-stage decision routing

Skip to content

# 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:

    @dataclass
    class EventWithContext:
        event: UniversalEvent
        context: EventContext

### 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