destinyebuka commited on
Commit
315d362
·
1 Parent(s): 4010f30
app/__pycache__/config.cpython-313.pyc CHANGED
Binary files a/app/__pycache__/config.cpython-313.pyc and b/app/__pycache__/config.cpython-313.pyc differ
 
app/__pycache__/database.cpython-313.pyc CHANGED
Binary files a/app/__pycache__/database.cpython-313.pyc and b/app/__pycache__/database.cpython-313.pyc differ
 
app/ai/agent/__pycache__/schemas.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/__pycache__/schemas.cpython-313.pyc and b/app/ai/agent/__pycache__/schemas.cpython-313.pyc differ
 
app/ai/agent/__pycache__/state.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/__pycache__/state.cpython-313.pyc and b/app/ai/agent/__pycache__/state.cpython-313.pyc differ
 
app/ai/agent/nodes/__pycache__/classify_intent.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/nodes/__pycache__/classify_intent.cpython-313.pyc and b/app/ai/agent/nodes/__pycache__/classify_intent.cpython-313.pyc differ
 
app/ai/agent/nodes/__pycache__/greeting.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/nodes/__pycache__/greeting.cpython-313.pyc and b/app/ai/agent/nodes/__pycache__/greeting.cpython-313.pyc differ
 
app/ai/agent/nodes/__pycache__/listing_collect.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/nodes/__pycache__/listing_collect.cpython-313.pyc and b/app/ai/agent/nodes/__pycache__/listing_collect.cpython-313.pyc differ
 
app/ai/agent/nodes/__pycache__/listing_validate.cpython-313.pyc CHANGED
Binary files a/app/ai/agent/nodes/__pycache__/listing_validate.cpython-313.pyc and b/app/ai/agent/nodes/__pycache__/listing_validate.cpython-313.pyc differ
 
app/ai/services/__pycache__/__init__.cpython-313.pyc CHANGED
Binary files a/app/ai/services/__pycache__/__init__.cpython-313.pyc and b/app/ai/services/__pycache__/__init__.cpython-313.pyc differ
 
app/ai/tools/__pycache__/listing_tool.cpython-313.pyc CHANGED
Binary files a/app/ai/tools/__pycache__/listing_tool.cpython-313.pyc and b/app/ai/tools/__pycache__/listing_tool.cpython-313.pyc differ
 
app/schemas/auth.py CHANGED
@@ -41,7 +41,7 @@ class SignupDto(BaseModel):
41
  class VerifySignupOtpDto(BaseModel):
42
  """Verify signup OTP DTO"""
43
  identifier: str = Field(..., min_length=1)
44
- code: str = Field(..., min_length=4, max_length=4)
45
 
46
  class LoginDto(BaseModel):
47
  """Login request DTO"""
@@ -56,7 +56,7 @@ class SendPasswordResetOtpDto(BaseModel):
56
  class VerifyPasswordResetOtpDto(BaseModel):
57
  """Verify password reset OTP DTO"""
58
  identifier: str = Field(..., min_length=1)
59
- code: str = Field(..., min_length=4, max_length=4)
60
 
61
  class ResetPasswordDto(BaseModel):
62
  """Reset password DTO"""
 
41
  class VerifySignupOtpDto(BaseModel):
42
  """Verify signup OTP DTO"""
43
  identifier: str = Field(..., min_length=1)
44
+ code: str = Field(..., min_length=6, max_length=6)
45
 
46
  class LoginDto(BaseModel):
47
  """Login request DTO"""
 
56
  class VerifyPasswordResetOtpDto(BaseModel):
57
  """Verify password reset OTP DTO"""
58
  identifier: str = Field(..., min_length=1)
59
+ code: str = Field(..., min_length=6, max_length=6)
60
 
61
  class ResetPasswordDto(BaseModel):
62
  """Reset password DTO"""
test_intelligent_replies.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Test script for intelligent reply functionality
3
+ """
4
+
5
+ import asyncio
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ # Add project root to path
10
+ sys.path.insert(0, str(Path(__file__).parent))
11
+
12
+ from app.ai.agent.reply_intelligence import (
13
+ determine_reply_to_message,
14
+ build_reply_context_for_response
15
+ )
16
+
17
+
18
+ def test_determine_reply_to_message():
19
+ """Test the reply message determination logic"""
20
+ print("\n" + "="*60)
21
+ print("TEST 1: Simple conversation - should quote latest user message")
22
+ print("="*60)
23
+
24
+ conversation_history = [
25
+ {
26
+ "id": "msg-003",
27
+ "sender_id": "user-123",
28
+ "sender_name": "John Doe",
29
+ "content": "I need 2 bedrooms",
30
+ "is_ai": False,
31
+ "timestamp": "2026-02-15T10:03:00Z"
32
+ },
33
+ {
34
+ "id": "msg-002",
35
+ "sender_id": "aida",
36
+ "sender_name": "AIDA",
37
+ "content": "Hello! How can I help?",
38
+ "is_ai": True,
39
+ "timestamp": "2026-02-15T10:02:00Z"
40
+ },
41
+ {
42
+ "id": "msg-001",
43
+ "sender_id": "user-123",
44
+ "sender_name": "John Doe",
45
+ "content": "Hi",
46
+ "is_ai": False,
47
+ "timestamp": "2026-02-15T10:01:00Z"
48
+ }
49
+ ]
50
+
51
+ reply_to_id = determine_reply_to_message(conversation_history)
52
+
53
+ print(f"\n✅ Result: reply_to_id = {reply_to_id}")
54
+ print(f"Expected: msg-003 (latest user message)")
55
+ assert reply_to_id == "msg-003", f"Expected msg-003, got {reply_to_id}"
56
+ print("✅ TEST PASSED")
57
+
58
+
59
+ def test_build_reply_context():
60
+ """Test the reply context building logic"""
61
+ print("\n" + "="*60)
62
+ print("TEST 2: Build reply context for response")
63
+ print("="*60)
64
+
65
+ conversation_history = [
66
+ {
67
+ "id": "msg-003",
68
+ "sender_id": "user-123",
69
+ "sender_name": "John Doe",
70
+ "content": "I need 2 bedrooms in Cotonou",
71
+ "is_ai": False,
72
+ "timestamp": "2026-02-15T10:03:00Z"
73
+ },
74
+ {
75
+ "id": "msg-002",
76
+ "sender_id": "aida",
77
+ "sender_name": "AIDA",
78
+ "content": "Hello! How can I help?",
79
+ "is_ai": True,
80
+ "timestamp": "2026-02-15T10:02:00Z"
81
+ }
82
+ ]
83
+
84
+ reply_context = build_reply_context_for_response(
85
+ conversation_history=conversation_history,
86
+ reply_to_id="msg-003"
87
+ )
88
+
89
+ print(f"\n✅ Result:")
90
+ print(f" - id: {reply_context['id']}")
91
+ print(f" - sender_name: {reply_context['sender_name']}")
92
+ print(f" - content: {reply_context['content']}")
93
+
94
+ assert reply_context['id'] == "msg-003"
95
+ assert reply_context['sender_name'] == "John Doe"
96
+ assert reply_context['content'] == "I need 2 bedrooms in Cotonou"
97
+ print("\n✅ TEST PASSED")
98
+
99
+
100
+ def test_empty_conversation():
101
+ """Test with empty conversation history"""
102
+ print("\n" + "="*60)
103
+ print("TEST 3: Empty conversation - should return None")
104
+ print("="*60)
105
+
106
+ reply_to_id = determine_reply_to_message([])
107
+
108
+ print(f"\n✅ Result: reply_to_id = {reply_to_id}")
109
+ print(f"Expected: None")
110
+ assert reply_to_id is None
111
+ print("✅ TEST PASSED")
112
+
113
+
114
+ def test_only_ai_messages():
115
+ """Test with only AI messages - should return None"""
116
+ print("\n" + "="*60)
117
+ print("TEST 4: Only AI messages - should return None")
118
+ print("="*60)
119
+
120
+ conversation_history = [
121
+ {
122
+ "id": "msg-002",
123
+ "sender_id": "aida",
124
+ "sender_name": "AIDA",
125
+ "content": "Hello!",
126
+ "is_ai": True,
127
+ "timestamp": "2026-02-15T10:02:00Z"
128
+ },
129
+ {
130
+ "id": "msg-001",
131
+ "sender_id": "aida",
132
+ "sender_name": "AIDA",
133
+ "content": "How can I help?",
134
+ "is_ai": True,
135
+ "timestamp": "2026-02-15T10:01:00Z"
136
+ }
137
+ ]
138
+
139
+ reply_to_id = determine_reply_to_message(conversation_history)
140
+
141
+ print(f"\n✅ Result: reply_to_id = {reply_to_id}")
142
+ print(f"Expected: None")
143
+ assert reply_to_id is None
144
+ print("✅ TEST PASSED")
145
+
146
+
147
+ def test_with_reply_context():
148
+ """Test when user swipes to reply to a specific message"""
149
+ print("\n" + "="*60)
150
+ print("TEST 5: User swipes to reply - should still quote latest user message")
151
+ print("="*60)
152
+
153
+ conversation_history = [
154
+ {
155
+ "id": "msg-005",
156
+ "sender_id": "user-123",
157
+ "sender_name": "John Doe",
158
+ "content": "I need 2 bedrooms",
159
+ "is_ai": False,
160
+ "timestamp": "2026-02-15T10:05:00Z"
161
+ },
162
+ {
163
+ "id": "msg-004",
164
+ "sender_id": "aida",
165
+ "sender_name": "AIDA",
166
+ "content": "I can help with that!",
167
+ "is_ai": True,
168
+ "timestamp": "2026-02-15T10:04:00Z"
169
+ },
170
+ {
171
+ "id": "msg-003",
172
+ "sender_id": "user-123",
173
+ "sender_name": "John Doe",
174
+ "content": "Show me apartments",
175
+ "is_ai": False,
176
+ "timestamp": "2026-02-15T10:03:00Z"
177
+ },
178
+ {
179
+ "id": "msg-002",
180
+ "sender_id": "aida",
181
+ "sender_name": "AIDA",
182
+ "content": "Hello! How can I help?",
183
+ "is_ai": True,
184
+ "timestamp": "2026-02-15T10:02:00Z"
185
+ },
186
+ {
187
+ "id": "msg-001",
188
+ "sender_id": "user-123",
189
+ "sender_name": "John Doe",
190
+ "content": "Hi",
191
+ "is_ai": False,
192
+ "timestamp": "2026-02-15T10:01:00Z"
193
+ }
194
+ ]
195
+
196
+ # User swiped "Hi" but the latest user message is "I need 2 bedrooms"
197
+ reply_context = {"message_id": "msg-001", "content": "Hi"}
198
+
199
+ reply_to_id = determine_reply_to_message(
200
+ conversation_history=conversation_history,
201
+ reply_context=reply_context
202
+ )
203
+
204
+ print(f"\n✅ Result: reply_to_id = {reply_to_id}")
205
+ print(f"Expected: msg-005 (latest user message, NOT msg-001 which was swiped)")
206
+ assert reply_to_id == "msg-005", f"Expected msg-005, got {reply_to_id}"
207
+ print("✅ TEST PASSED")
208
+
209
+
210
+ def main():
211
+ """Run all tests"""
212
+ print("\n" + "="*60)
213
+ print("INTELLIGENT REPLY TESTS")
214
+ print("="*60)
215
+
216
+ try:
217
+ test_determine_reply_to_message()
218
+ test_build_reply_context()
219
+ test_empty_conversation()
220
+ test_only_ai_messages()
221
+ test_with_reply_context()
222
+
223
+ print("\n" + "="*60)
224
+ print("✅ ALL TESTS PASSED!")
225
+ print("="*60 + "\n")
226
+
227
+ except AssertionError as e:
228
+ print(f"\n❌ TEST FAILED: {e}\n")
229
+ sys.exit(1)
230
+ except Exception as e:
231
+ print(f"\n❌ ERROR: {e}\n")
232
+ import traceback
233
+ traceback.print_exc()
234
+ sys.exit(1)
235
+
236
+
237
+ if __name__ == "__main__":
238
+ main()
test_reply_logic_simple.py ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Simple standalone test for intelligent reply functionality
3
+ """
4
+
5
+ from typing import Optional, Dict, Any, List
6
+
7
+
8
+ def determine_reply_to_message(
9
+ conversation_history: List[Dict[str, Any]],
10
+ reply_context: Optional[Dict[str, Any]] = None
11
+ ) -> Optional[str]:
12
+ """
13
+ Analyze conversation and determine which message AIDA should quote.
14
+
15
+ Strategy:
16
+ 1. If user just replied to a specific message (reply_context exists),
17
+ quote the LATEST USER MESSAGE, not the message they swiped
18
+ 2. Otherwise, quote the most recent user message
19
+
20
+ Args:
21
+ conversation_history: List of messages with structure:
22
+ [{"id": str, "sender_id": str, "content": str, "is_ai": bool, ...}, ...]
23
+ reply_context: Optional context of message user replied to
24
+
25
+ Returns:
26
+ Message ID to quote, or None if no appropriate message found
27
+ """
28
+
29
+ if not conversation_history:
30
+ return None
31
+
32
+ # Find the most recent user message (not AI)
33
+ latest_user_message = None
34
+ for msg in conversation_history:
35
+ if not msg.get("is_ai", False):
36
+ latest_user_message = msg
37
+ break # conversation_history is sorted newest first
38
+
39
+ if latest_user_message:
40
+ return latest_user_message.get("id")
41
+
42
+ return None
43
+
44
+
45
+ def build_reply_context_for_response(
46
+ conversation_history: List[Dict[str, Any]],
47
+ reply_to_id: str
48
+ ) -> Optional[Dict[str, Any]]:
49
+ """
50
+ Build reply context for AIDA's response.
51
+
52
+ Returns:
53
+ {
54
+ "id": message_id,
55
+ "sender_name": sender name,
56
+ "content": message content
57
+ }
58
+ """
59
+
60
+ if not reply_to_id:
61
+ return None
62
+
63
+ # Find the message to quote
64
+ for msg in conversation_history:
65
+ if msg.get("id") == reply_to_id:
66
+ return {
67
+ "id": msg.get("id"),
68
+ "sender_name": msg.get("sender_name", "User"),
69
+ "content": msg.get("content", ""),
70
+ }
71
+
72
+ return None
73
+
74
+
75
+ def test_determine_reply_to_message():
76
+ """Test the reply message determination logic"""
77
+ print("\n" + "="*60)
78
+ print("TEST 1: Simple conversation - should quote latest user message")
79
+ print("="*60)
80
+
81
+ conversation_history = [
82
+ {
83
+ "id": "msg-003",
84
+ "sender_id": "user-123",
85
+ "sender_name": "John Doe",
86
+ "content": "I need 2 bedrooms",
87
+ "is_ai": False,
88
+ "timestamp": "2026-02-15T10:03:00Z"
89
+ },
90
+ {
91
+ "id": "msg-002",
92
+ "sender_id": "aida",
93
+ "sender_name": "AIDA",
94
+ "content": "Hello! How can I help?",
95
+ "is_ai": True,
96
+ "timestamp": "2026-02-15T10:02:00Z"
97
+ },
98
+ {
99
+ "id": "msg-001",
100
+ "sender_id": "user-123",
101
+ "sender_name": "John Doe",
102
+ "content": "Hi",
103
+ "is_ai": False,
104
+ "timestamp": "2026-02-15T10:01:00Z"
105
+ }
106
+ ]
107
+
108
+ reply_to_id = determine_reply_to_message(conversation_history)
109
+
110
+ print(f"\nResult: reply_to_id = {reply_to_id}")
111
+ print(f"Expected: msg-003 (latest user message)")
112
+ assert reply_to_id == "msg-003", f"Expected msg-003, got {reply_to_id}"
113
+ print("PASS: TEST PASSED")
114
+
115
+
116
+ def test_build_reply_context():
117
+ """Test the reply context building logic"""
118
+ print("\n" + "="*60)
119
+ print("TEST 2: Build reply context for response")
120
+ print("="*60)
121
+
122
+ conversation_history = [
123
+ {
124
+ "id": "msg-003",
125
+ "sender_id": "user-123",
126
+ "sender_name": "John Doe",
127
+ "content": "I need 2 bedrooms in Cotonou",
128
+ "is_ai": False,
129
+ "timestamp": "2026-02-15T10:03:00Z"
130
+ },
131
+ {
132
+ "id": "msg-002",
133
+ "sender_id": "aida",
134
+ "sender_name": "AIDA",
135
+ "content": "Hello! How can I help?",
136
+ "is_ai": True,
137
+ "timestamp": "2026-02-15T10:02:00Z"
138
+ }
139
+ ]
140
+
141
+ reply_context = build_reply_context_for_response(
142
+ conversation_history=conversation_history,
143
+ reply_to_id="msg-003"
144
+ )
145
+
146
+ print(f"\nResult:")
147
+ print(f" - id: {reply_context['id']}")
148
+ print(f" - sender_name: {reply_context['sender_name']}")
149
+ print(f" - content: {reply_context['content']}")
150
+
151
+ assert reply_context['id'] == "msg-003"
152
+ assert reply_context['sender_name'] == "John Doe"
153
+ assert reply_context['content'] == "I need 2 bedrooms in Cotonou"
154
+ print("\nPASS: TEST PASSED")
155
+
156
+
157
+ def test_empty_conversation():
158
+ """Test with empty conversation history"""
159
+ print("\n" + "="*60)
160
+ print("TEST 3: Empty conversation - should return None")
161
+ print("="*60)
162
+
163
+ reply_to_id = determine_reply_to_message([])
164
+
165
+ print(f"\nResult: reply_to_id = {reply_to_id}")
166
+ print(f"Expected: None")
167
+ assert reply_to_id is None
168
+ print("PASS: TEST PASSED")
169
+
170
+
171
+ def test_only_ai_messages():
172
+ """Test with only AI messages - should return None"""
173
+ print("\n" + "="*60)
174
+ print("TEST 4: Only AI messages - should return None")
175
+ print("="*60)
176
+
177
+ conversation_history = [
178
+ {
179
+ "id": "msg-002",
180
+ "sender_id": "aida",
181
+ "sender_name": "AIDA",
182
+ "content": "Hello!",
183
+ "is_ai": True,
184
+ "timestamp": "2026-02-15T10:02:00Z"
185
+ },
186
+ {
187
+ "id": "msg-001",
188
+ "sender_id": "aida",
189
+ "sender_name": "AIDA",
190
+ "content": "How can I help?",
191
+ "is_ai": True,
192
+ "timestamp": "2026-02-15T10:01:00Z"
193
+ }
194
+ ]
195
+
196
+ reply_to_id = determine_reply_to_message(conversation_history)
197
+
198
+ print(f"\nResult: reply_to_id = {reply_to_id}")
199
+ print(f"Expected: None")
200
+ assert reply_to_id is None
201
+ print("PASS: TEST PASSED")
202
+
203
+
204
+ def test_with_reply_context():
205
+ """Test when user swipes to reply to a specific message"""
206
+ print("\n" + "="*60)
207
+ print("TEST 5: User swipes to reply - should still quote latest user message")
208
+ print("="*60)
209
+
210
+ conversation_history = [
211
+ {
212
+ "id": "msg-005",
213
+ "sender_id": "user-123",
214
+ "sender_name": "John Doe",
215
+ "content": "I need 2 bedrooms",
216
+ "is_ai": False,
217
+ "timestamp": "2026-02-15T10:05:00Z"
218
+ },
219
+ {
220
+ "id": "msg-004",
221
+ "sender_id": "aida",
222
+ "sender_name": "AIDA",
223
+ "content": "I can help with that!",
224
+ "is_ai": True,
225
+ "timestamp": "2026-02-15T10:04:00Z"
226
+ },
227
+ {
228
+ "id": "msg-003",
229
+ "sender_id": "user-123",
230
+ "sender_name": "John Doe",
231
+ "content": "Show me apartments",
232
+ "is_ai": False,
233
+ "timestamp": "2026-02-15T10:03:00Z"
234
+ },
235
+ {
236
+ "id": "msg-002",
237
+ "sender_id": "aida",
238
+ "sender_name": "AIDA",
239
+ "content": "Hello! How can I help?",
240
+ "is_ai": True,
241
+ "timestamp": "2026-02-15T10:02:00Z"
242
+ },
243
+ {
244
+ "id": "msg-001",
245
+ "sender_id": "user-123",
246
+ "sender_name": "John Doe",
247
+ "content": "Hi",
248
+ "is_ai": False,
249
+ "timestamp": "2026-02-15T10:01:00Z"
250
+ }
251
+ ]
252
+
253
+ # User swiped "Hi" but the latest user message is "I need 2 bedrooms"
254
+ reply_context = {"message_id": "msg-001", "content": "Hi"}
255
+
256
+ reply_to_id = determine_reply_to_message(
257
+ conversation_history=conversation_history,
258
+ reply_context=reply_context
259
+ )
260
+
261
+ print(f"\nResult: reply_to_id = {reply_to_id}")
262
+ print(f"Expected: msg-005 (latest user message, NOT msg-001 which was swiped)")
263
+ assert reply_to_id == "msg-005", f"Expected msg-005, got {reply_to_id}"
264
+ print("PASS: TEST PASSED")
265
+
266
+
267
+ def main():
268
+ """Run all tests"""
269
+ print("\n" + "="*60)
270
+ print("INTELLIGENT REPLY TESTS")
271
+ print("="*60)
272
+
273
+ try:
274
+ test_determine_reply_to_message()
275
+ test_build_reply_context()
276
+ test_empty_conversation()
277
+ test_only_ai_messages()
278
+ test_with_reply_context()
279
+
280
+ print("\n" + "="*60)
281
+ print("ALL TESTS PASSED!")
282
+ print("="*60 + "\n")
283
+ return 0
284
+
285
+ except AssertionError as e:
286
+ print(f"\nTEST FAILED: {e}\n")
287
+ return 1
288
+ except Exception as e:
289
+ print(f"\nERROR: {e}\n")
290
+ import traceback
291
+ traceback.print_exc()
292
+ return 1
293
+
294
+
295
+ if __name__ == "__main__":
296
+ import sys
297
+ sys.exit(main())