File size: 3,657 Bytes
7f9f4b4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
"""
Custom exception classes for the Booking Allocation Engine.

This module defines the exception hierarchy used throughout the allocation system
to distinguish between transient errors (retryable) and permanent errors (non-retryable).
"""


class AllocationError(Exception):
    """Base exception for all allocation-related errors."""
    
    def __init__(self, message: str, booking_id: str = None, partner_id: str = None):
        self.message = message
        self.booking_id = booking_id
        self.partner_id = partner_id
        super().__init__(self.message)


class TransientError(AllocationError):
    """
    Transient errors that may succeed on retry.
    
    These errors indicate temporary failures such as network issues,
    database connection problems, or service unavailability.
    Operations that raise TransientError should be retried with backoff.
    """
    pass


class PermanentError(AllocationError):
    """
    Permanent errors that will not succeed on retry.
    
    These errors indicate business logic violations, validation failures,
    or data integrity issues that cannot be resolved by retrying.
    Operations that raise PermanentError should not be retried.
    """
    pass


class DatabaseConnectionError(TransientError):
    """
    Database connection or query execution failure.
    
    Raised when PostgreSQL connection fails or times out.
    Should be retried with exponential backoff.
    """
    
    def __init__(self, message: str, operation: str = None, booking_id: str = None):
        self.operation = operation
        super().__init__(message, booking_id=booking_id)


class RedisError(TransientError):
    """
    Redis connection or operation failure.
    
    Raised when Redis connection fails or times out.
    Should trigger fallback to PostgreSQL for data retrieval.
    """
    
    def __init__(self, message: str, key: str = None, operation: str = None):
        self.key = key
        self.operation = operation
        super().__init__(message)


class NoEligiblePartnersError(PermanentError):
    """
    No eligible partners found for booking.
    
    Raised when partner filtering returns empty result set.
    Should set allocation_status to 'failed'.
    """
    pass


class BookingAlreadyAssignedError(PermanentError):
    """
    Booking already assigned to a partner.
    
    Raised when attempting to create offer for already-assigned booking.
    Should abort allocation attempt.
    """
    pass


class InvalidEventStructureError(PermanentError):
    """
    Event structure validation failed.
    
    Raised when required fields are missing or have invalid types.
    Event should be discarded and logged.
    """
    
    def __init__(self, message: str, event_data: dict = None):
        self.event_data = event_data
        super().__init__(message)


class OfferExpiredError(PermanentError):
    """
    Offer has already expired.
    
    Raised when partner responds after offer expiry timeout.
    Response should be rejected.
    """
    
    def __init__(self, message: str, offer_id: str = None, expiry_time: str = None):
        self.offer_id = offer_id
        self.expiry_time = expiry_time
        super().__init__(message)


class DuplicateResponseError(PermanentError):
    """
    Partner already responded to offer.
    
    Raised when duplicate accept/decline response received.
    Duplicate response should be ignored.
    """
    
    def __init__(self, message: str, offer_id: str = None, previous_response: str = None):
        self.offer_id = offer_id
        self.previous_response = previous_response
        super().__init__(message)