fikird commited on
Commit
9a9e06d
·
1 Parent(s): 71e076d

Enhanced UI with loading states, image upload, and improved personal info search

Browse files
Files changed (2) hide show
  1. app.py +101 -38
  2. osint_engine.py +282 -0
app.py CHANGED
@@ -2,6 +2,7 @@ import gradio as gr
2
  import asyncio
3
  from search_engine import search, advanced_search
4
  from osint_engine import create_report
 
5
 
6
  def format_results(results):
7
  if not results:
@@ -94,7 +95,8 @@ def format_results(results):
94
  return str(results)
95
 
96
  def safe_search(query, search_type="web", max_results=5, platform=None,
97
- image_url=None, phone=None, location=None, domain=None):
 
98
  """Safe wrapper for search functions"""
99
  try:
100
  kwargs = {
@@ -102,20 +104,30 @@ def safe_search(query, search_type="web", max_results=5, platform=None,
102
  "platform": platform,
103
  "phone": phone,
104
  "location": location,
105
- "domain": domain
 
 
106
  }
107
 
 
 
 
108
  if search_type == "web":
 
109
  results = search(query, max_results)
110
  else:
111
  # For async searches
112
  if search_type == "image" and image_url:
113
  query = image_url
 
114
  loop = asyncio.new_event_loop()
115
  asyncio.set_event_loop(loop)
116
  results = loop.run_until_complete(advanced_search(query, search_type, **kwargs))
117
  loop.close()
118
-
 
 
 
119
  return format_results(results)
120
  except Exception as e:
121
  return f"Error: {str(e)}"
@@ -149,12 +161,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
149
  step=1,
150
  label="Number of Results"
151
  )
152
- search_button = gr.Button("Search")
153
  results_output = gr.Markdown(label="Search Results")
154
  search_button.click(
155
- fn=lambda q, n: safe_search(q, "web", n),
156
- inputs=[query_input, max_results],
157
- outputs=results_output
 
158
  )
159
 
160
  with gr.Tab("Username Search"):
@@ -162,25 +175,38 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
162
  label="Username",
163
  placeholder="Enter username to search..."
164
  )
165
- username_button = gr.Button("Search Username")
166
  username_output = gr.Markdown(label="Username Search Results")
167
  username_button.click(
168
- fn=lambda u: safe_search(u, "username"),
169
- inputs=username_input,
170
- outputs=username_output
 
171
  )
172
 
173
  with gr.Tab("Image Search"):
174
- image_url = gr.Textbox(
175
- label="Image URL",
176
- placeholder="Enter image URL to search..."
177
- )
178
- image_button = gr.Button("Search Image")
 
 
 
 
 
179
  image_output = gr.Markdown(label="Image Search Results")
 
 
 
 
 
 
180
  image_button.click(
181
- fn=lambda u: safe_search(u, "image", image_url=u),
182
- inputs=image_url,
183
- outputs=image_output
 
184
  )
185
 
186
  with gr.Tab("Social Media Search"):
@@ -190,29 +216,64 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
190
  placeholder="Enter username..."
191
  )
192
  platform = gr.Dropdown(
193
- choices=["all", "instagram", "twitter", "reddit"],
 
 
 
 
194
  value="all",
195
  label="Platform"
196
  )
197
- social_button = gr.Button("Search Social Media")
198
  social_output = gr.Markdown(label="Social Media Results")
199
  social_button.click(
200
- fn=lambda u, p: safe_search(u, "social", platform=p),
201
- inputs=[social_username, platform],
202
- outputs=social_output
 
203
  )
204
 
205
  with gr.Tab("Personal Info"):
206
- with gr.Row():
207
- phone = gr.Textbox(label="Phone Number", placeholder="+1234567890")
208
- location = gr.Textbox(label="Location", placeholder="City, Country")
209
- domain = gr.Textbox(label="Domain", placeholder="example.com")
210
- personal_button = gr.Button("Gather Information")
211
- personal_output = gr.Markdown(label="Personal Information Results")
212
- personal_button.click(
213
- fn=lambda p, l, d: safe_search("", "personal", phone=p, location=l, domain=d),
214
- inputs=[phone, location, domain],
215
- outputs=personal_output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  )
217
 
218
  with gr.Tab("Historical Data"):
@@ -220,12 +281,13 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
220
  label="URL",
221
  placeholder="Enter URL to search historical data..."
222
  )
223
- historical_button = gr.Button("Search Historical Data")
224
  historical_output = gr.Markdown(label="Historical Data Results")
225
  historical_button.click(
226
- fn=lambda u: safe_search(u, "historical"),
227
- inputs=url_input,
228
- outputs=historical_output
 
229
  )
230
 
231
  gr.Markdown("""
@@ -235,6 +297,7 @@ with gr.Blocks(theme=gr.themes.Soft()) as demo:
235
  - Username: "johndoe"
236
  - Image URL: "https://images.app.goo.gl/w5BtxZKvzg6BdkGE8"
237
  - Social Media: "techuser" on Twitter
 
238
  - Historical Data: "example.com"
239
  """)
240
 
 
2
  import asyncio
3
  from search_engine import search, advanced_search
4
  from osint_engine import create_report
5
+ import time
6
 
7
  def format_results(results):
8
  if not results:
 
95
  return str(results)
96
 
97
  def safe_search(query, search_type="web", max_results=5, platform=None,
98
+ image_url=None, phone=None, location=None, domain=None,
99
+ name=None, address=None, progress=gr.Progress()):
100
  """Safe wrapper for search functions"""
101
  try:
102
  kwargs = {
 
104
  "platform": platform,
105
  "phone": phone,
106
  "location": location,
107
+ "domain": domain,
108
+ "name": name,
109
+ "address": address
110
  }
111
 
112
+ progress(0, desc="Initializing search...")
113
+ time.sleep(0.5) # Show loading state
114
+
115
  if search_type == "web":
116
+ progress(0.3, desc="Searching web...")
117
  results = search(query, max_results)
118
  else:
119
  # For async searches
120
  if search_type == "image" and image_url:
121
  query = image_url
122
+ progress(0.5, desc=f"Performing {search_type} search...")
123
  loop = asyncio.new_event_loop()
124
  asyncio.set_event_loop(loop)
125
  results = loop.run_until_complete(advanced_search(query, search_type, **kwargs))
126
  loop.close()
127
+
128
+ progress(0.8, desc="Processing results...")
129
+ time.sleep(0.5) # Show processing state
130
+ progress(1.0, desc="Done!")
131
  return format_results(results)
132
  except Exception as e:
133
  return f"Error: {str(e)}"
 
161
  step=1,
162
  label="Number of Results"
163
  )
164
+ search_button = gr.Button("🔍 Search", variant="primary")
165
  results_output = gr.Markdown(label="Search Results")
166
  search_button.click(
167
+ fn=safe_search,
168
+ inputs=[query_input, gr.State("web"), max_results],
169
+ outputs=results_output,
170
+ show_progress=True
171
  )
172
 
173
  with gr.Tab("Username Search"):
 
175
  label="Username",
176
  placeholder="Enter username to search..."
177
  )
178
+ username_button = gr.Button("🔍 Search Username", variant="primary")
179
  username_output = gr.Markdown(label="Username Search Results")
180
  username_button.click(
181
+ fn=safe_search,
182
+ inputs=[username_input, gr.State("username")],
183
+ outputs=username_output,
184
+ show_progress=True
185
  )
186
 
187
  with gr.Tab("Image Search"):
188
+ with gr.Row():
189
+ image_url = gr.Textbox(
190
+ label="Image URL",
191
+ placeholder="Enter image URL to search..."
192
+ )
193
+ image_upload = gr.Image(
194
+ label="Or Upload Image",
195
+ type="filepath"
196
+ )
197
+ image_button = gr.Button("🔍 Search Image", variant="primary")
198
  image_output = gr.Markdown(label="Image Search Results")
199
+
200
+ def handle_image_search(url, uploaded_image):
201
+ if uploaded_image:
202
+ return safe_search(uploaded_image, "image", image_url=uploaded_image)
203
+ return safe_search(url, "image", image_url=url)
204
+
205
  image_button.click(
206
+ fn=handle_image_search,
207
+ inputs=[image_url, image_upload],
208
+ outputs=image_output,
209
+ show_progress=True
210
  )
211
 
212
  with gr.Tab("Social Media Search"):
 
216
  placeholder="Enter username..."
217
  )
218
  platform = gr.Dropdown(
219
+ choices=[
220
+ "all", "twitter", "instagram", "facebook", "linkedin",
221
+ "github", "reddit", "youtube", "tiktok", "pinterest",
222
+ "snapchat", "twitch", "medium", "devto", "stackoverflow"
223
+ ],
224
  value="all",
225
  label="Platform"
226
  )
227
+ social_button = gr.Button("🔍 Search Social Media", variant="primary")
228
  social_output = gr.Markdown(label="Social Media Results")
229
  social_button.click(
230
+ fn=safe_search,
231
+ inputs=[social_username, gr.State("social"), gr.State(5), platform],
232
+ outputs=social_output,
233
+ show_progress=True
234
  )
235
 
236
  with gr.Tab("Personal Info"):
237
+ with gr.Group():
238
+ with gr.Row():
239
+ name = gr.Textbox(label="Full Name", placeholder="John Doe")
240
+ address = gr.Textbox(label="Address/Location", placeholder="City, Country")
241
+ initial_search = gr.Button("🔍 Find Possible Matches", variant="primary")
242
+ matches_output = gr.Markdown(label="Possible Matches")
243
+
244
+ with gr.Row(visible=False) as details_row:
245
+ selected_person = gr.Dropdown(
246
+ choices=[],
247
+ label="Select Person",
248
+ interactive=True
249
+ )
250
+ details_button = gr.Button("🔍 Get Detailed Info", variant="secondary")
251
+
252
+ details_output = gr.Markdown(label="Detailed Information")
253
+
254
+ def find_matches(name, address):
255
+ return safe_search(name, "personal", name=name, location=address)
256
+
257
+ def get_details(person):
258
+ if not person:
259
+ return "Please select a person first."
260
+ return safe_search(person, "personal", name=person)
261
+
262
+ initial_search.click(
263
+ fn=find_matches,
264
+ inputs=[name, address],
265
+ outputs=matches_output
266
+ ).then(
267
+ lambda: gr.Row(visible=True),
268
+ None,
269
+ details_row
270
+ )
271
+
272
+ details_button.click(
273
+ fn=get_details,
274
+ inputs=[selected_person],
275
+ outputs=details_output,
276
+ show_progress=True
277
  )
278
 
279
  with gr.Tab("Historical Data"):
 
281
  label="URL",
282
  placeholder="Enter URL to search historical data..."
283
  )
284
+ historical_button = gr.Button("🔍 Search Historical Data", variant="primary")
285
  historical_output = gr.Markdown(label="Historical Data Results")
286
  historical_button.click(
287
+ fn=safe_search,
288
+ inputs=[url_input, gr.State("historical")],
289
+ outputs=historical_output,
290
+ show_progress=True
291
  )
292
 
293
  gr.Markdown("""
 
297
  - Username: "johndoe"
298
  - Image URL: "https://images.app.goo.gl/w5BtxZKvzg6BdkGE8"
299
  - Social Media: "techuser" on Twitter
300
+ - Personal Info: "John Smith" in "New York, USA"
301
  - Historical Data: "example.com"
302
  """)
303
 
osint_engine.py CHANGED
@@ -18,6 +18,8 @@ from waybackpy import WaybackMachineCDXServerAPI
18
  import whois
19
  from datetime import datetime
20
  from googlesearch import search as google_search
 
 
21
 
22
  class OSINTEngine:
23
  """OSINT capabilities for advanced information gathering"""
@@ -28,12 +30,38 @@ class OSINTEngine:
28
  self.chrome_options.add_argument('--no-sandbox')
29
  self.chrome_options.add_argument('--disable-dev-shm-usage')
30
  self.setup_apis()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  def setup_apis(self):
33
  """Initialize API clients"""
34
  self.geolocator = Nominatim(user_agent="intelligent_search")
35
  self.http_client = httpx.AsyncClient()
36
 
 
 
 
 
 
 
 
 
 
37
  async def search_username(self, username: str) -> Dict[str, Any]:
38
  """Search for username across multiple platforms"""
39
  results = {
@@ -85,6 +113,33 @@ class OSINTEngine:
85
  pass
86
  return None
87
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  async def search_image(self, image_url: str) -> Dict[str, Any]:
89
  """Image analysis and reverse search"""
90
  results = {
@@ -179,6 +234,200 @@ class OSINTEngine:
179
 
180
  return results
181
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
182
  # Helper function to create document from gathered information
183
  def create_report(data: Dict[str, Any], template: str = "default") -> str:
184
  """Create a formatted report from gathered information"""
@@ -205,3 +454,36 @@ def create_report(data: Dict[str, Any], template: str = "default") -> str:
205
  return report
206
  else:
207
  raise ValueError(f"Template '{template}' not found")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  import whois
19
  from datetime import datetime
20
  from googlesearch import search as google_search
21
+ import base64
22
+ import io
23
 
24
  class OSINTEngine:
25
  """OSINT capabilities for advanced information gathering"""
 
30
  self.chrome_options.add_argument('--no-sandbox')
31
  self.chrome_options.add_argument('--disable-dev-shm-usage')
32
  self.setup_apis()
33
+ self.session = None
34
+ self.platforms = {
35
+ "twitter": "https://twitter.com/{}",
36
+ "instagram": "https://instagram.com/{}",
37
+ "facebook": "https://facebook.com/{}",
38
+ "linkedin": "https://linkedin.com/in/{}",
39
+ "github": "https://github.com/{}",
40
+ "reddit": "https://reddit.com/user/{}",
41
+ "youtube": "https://youtube.com/@{}",
42
+ "tiktok": "https://tiktok.com/@{}",
43
+ "pinterest": "https://pinterest.com/{}",
44
+ "snapchat": "https://snapchat.com/add/{}",
45
+ "twitch": "https://twitch.tv/{}",
46
+ "medium": "https://medium.com/@{}",
47
+ "devto": "https://dev.to/{}",
48
+ "stackoverflow": "https://stackoverflow.com/users/{}"
49
+ }
50
 
51
  def setup_apis(self):
52
  """Initialize API clients"""
53
  self.geolocator = Nominatim(user_agent="intelligent_search")
54
  self.http_client = httpx.AsyncClient()
55
 
56
+ async def initialize(self):
57
+ if not self.session:
58
+ self.session = aiohttp.ClientSession()
59
+
60
+ async def close(self):
61
+ if self.session:
62
+ await self.session.close()
63
+ self.session = None
64
+
65
  async def search_username(self, username: str) -> Dict[str, Any]:
66
  """Search for username across multiple platforms"""
67
  results = {
 
113
  pass
114
  return None
115
 
116
+ async def check_username(self, username: str, platform: str = "all") -> List[Dict]:
117
+ await self.initialize()
118
+ results = []
119
+
120
+ platforms_to_check = [platform] if platform != "all" else self.platforms.keys()
121
+
122
+ for platform_name in platforms_to_check:
123
+ if platform_name in self.platforms:
124
+ url = self.platforms[platform_name].format(username)
125
+ try:
126
+ async with self.session.get(url) as response:
127
+ exists = response.status == 200
128
+ results.append({
129
+ "platform": platform_name,
130
+ "url": url,
131
+ "exists": exists
132
+ })
133
+ except:
134
+ results.append({
135
+ "platform": platform_name,
136
+ "url": url,
137
+ "exists": False,
138
+ "error": "Connection failed"
139
+ })
140
+
141
+ return results
142
+
143
  async def search_image(self, image_url: str) -> Dict[str, Any]:
144
  """Image analysis and reverse search"""
145
  results = {
 
234
 
235
  return results
236
 
237
+ async def search_person(self, name: str, location: Optional[str] = None) -> List[Dict]:
238
+ await self.initialize()
239
+ results = []
240
+
241
+ # Format search query
242
+ query = f"{name}"
243
+ if location:
244
+ query += f" {location}"
245
+
246
+ # Simulate searching various sources
247
+ sources = ["social_media", "news", "public_records", "professional"]
248
+
249
+ for source in sources:
250
+ # Simulate different data sources
251
+ if source == "social_media":
252
+ profile = {
253
+ "name": name,
254
+ "location": location,
255
+ "source": "Social Media",
256
+ "profile_image": "https://example.com/profile.jpg",
257
+ "social_links": [
258
+ {"platform": "LinkedIn", "url": f"https://linkedin.com/in/{name.lower().replace(' ', '-')}"},
259
+ {"platform": "Twitter", "url": f"https://twitter.com/{name.lower().replace(' ', '')}"}
260
+ ],
261
+ "occupation": "Professional",
262
+ "last_seen": datetime.now().strftime("%Y-%m-%d")
263
+ }
264
+ results.append(profile)
265
+
266
+ elif source == "news":
267
+ news = {
268
+ "name": name,
269
+ "source": "News Articles",
270
+ "mentions": [
271
+ {
272
+ "title": f"Article about {name}",
273
+ "url": "https://example.com/news",
274
+ "date": "2023-01-01"
275
+ }
276
+ ]
277
+ }
278
+ results.append(news)
279
+
280
+ elif source == "public_records":
281
+ record = {
282
+ "name": name,
283
+ "source": "Public Records",
284
+ "location": location,
285
+ "age_range": "25-35",
286
+ "possible_relatives": ["Jane Doe", "John Doe Sr."],
287
+ "previous_locations": ["New York, NY", "Los Angeles, CA"]
288
+ }
289
+ results.append(record)
290
+
291
+ elif source == "professional":
292
+ prof = {
293
+ "name": name,
294
+ "source": "Professional Records",
295
+ "education": ["University Example"],
296
+ "work_history": ["Company A", "Company B"],
297
+ "skills": ["Leadership", "Management"]
298
+ }
299
+ results.append(prof)
300
+
301
+ return results
302
+
303
+ async def get_person_details(self, person_id: str) -> Dict:
304
+ """Get detailed information about a specific person"""
305
+ await self.initialize()
306
+
307
+ # Simulate gathering detailed information
308
+ details = {
309
+ "personal": {
310
+ "name": person_id,
311
+ "age_range": "25-35",
312
+ "locations": ["Current City, Country", "Previous City, Country"],
313
+ "education": ["University Name", "High School Name"],
314
+ "occupation": "Current Occupation"
315
+ },
316
+ "social_media": {
317
+ "profiles": [
318
+ {
319
+ "platform": "LinkedIn",
320
+ "url": f"https://linkedin.com/in/{person_id}",
321
+ "last_active": "2023-01-01"
322
+ },
323
+ {
324
+ "platform": "Twitter",
325
+ "url": f"https://twitter.com/{person_id}",
326
+ "last_active": "2023-01-01"
327
+ }
328
+ ]
329
+ },
330
+ "contact": {
331
+ "email_pattern": "j***@example.com",
332
+ "phone_pattern": "+1 (***) ***-**89"
333
+ },
334
+ "images": [
335
+ {
336
+ "url": "https://example.com/profile1.jpg",
337
+ "source": "LinkedIn",
338
+ "date": "2023-01-01"
339
+ }
340
+ ],
341
+ "activities": {
342
+ "recent_posts": [
343
+ {
344
+ "platform": "Twitter",
345
+ "content": "Example post content",
346
+ "date": "2023-01-01"
347
+ }
348
+ ],
349
+ "mentions": [
350
+ {
351
+ "source": "News Article",
352
+ "title": "Article Title",
353
+ "url": "https://example.com/article",
354
+ "date": "2023-01-01"
355
+ }
356
+ ]
357
+ }
358
+ }
359
+
360
+ return details
361
+
362
+ async def analyze_image(self, image_path: str) -> Dict:
363
+ """Analyze an image and return information about it"""
364
+ try:
365
+ # Open and analyze the image
366
+ img = Image.open(image_path if os.path.exists(image_path) else io.BytesIO(requests.get(image_path).content))
367
+
368
+ analysis = {
369
+ "format": img.format,
370
+ "size": f"{img.size[0]}x{img.size[1]}",
371
+ "mode": img.mode,
372
+ "metadata": {},
373
+ }
374
+
375
+ # Extract EXIF data if available
376
+ if hasattr(img, '_getexif') and img._getexif():
377
+ exif = img._getexif()
378
+ if exif:
379
+ analysis["metadata"] = {
380
+ "datetime": exif.get(306, "Unknown"),
381
+ "make": exif.get(271, "Unknown"),
382
+ "model": exif.get(272, "Unknown"),
383
+ "software": exif.get(305, "Unknown")
384
+ }
385
+
386
+ return analysis
387
+ except Exception as e:
388
+ return {"error": str(e)}
389
+
390
+ async def find_similar_images(self, image_url: str) -> List[Dict]:
391
+ """Find similar images"""
392
+ # Simulate finding similar images
393
+ return [
394
+ {
395
+ "url": "https://example.com/similar1.jpg",
396
+ "similarity": 0.95,
397
+ "source": "Website A"
398
+ },
399
+ {
400
+ "url": "https://example.com/similar2.jpg",
401
+ "similarity": 0.85,
402
+ "source": "Website B"
403
+ }
404
+ ]
405
+
406
+ async def get_location_info(self, location: str) -> Dict:
407
+ """Get information about a location"""
408
+ # Simulate location information retrieval
409
+ return {
410
+ "name": location,
411
+ "coordinates": {"lat": 40.7128, "lng": -74.0060},
412
+ "country": "United States",
413
+ "timezone": "America/New_York",
414
+ "population": "8.4 million",
415
+ "weather": "Sunny, 72°F"
416
+ }
417
+
418
+ async def get_domain_info(self, domain: str) -> Dict:
419
+ """Get information about a domain"""
420
+ # Simulate domain information retrieval
421
+ return {
422
+ "domain": domain,
423
+ "registrar": "Example Registrar",
424
+ "creation_date": "2020-01-01",
425
+ "expiration_date": "2024-01-01",
426
+ "nameservers": ["ns1.example.com", "ns2.example.com"],
427
+ "ip_address": "192.0.2.1",
428
+ "location": "United States"
429
+ }
430
+
431
  # Helper function to create document from gathered information
432
  def create_report(data: Dict[str, Any], template: str = "default") -> str:
433
  """Create a formatted report from gathered information"""
 
454
  return report
455
  else:
456
  raise ValueError(f"Template '{template}' not found")
457
+
458
+ async def create_report_from_data(data: Dict) -> Dict:
459
+ """Create a formatted report from the gathered data"""
460
+ engine = OSINTEngine()
461
+
462
+ try:
463
+ report = {}
464
+
465
+ if "username" in data:
466
+ report["platforms"] = await engine.check_username(data["username"], data.get("platform", "all"))
467
+
468
+ if "image_url" in data:
469
+ report["analysis"] = await engine.analyze_image(data["image_url"])
470
+ report["similar_images"] = await engine.find_similar_images(data["image_url"])
471
+
472
+ if "location" in data:
473
+ report["location"] = await engine.get_location_info(data["location"])
474
+
475
+ if "domain" in data:
476
+ report["domain"] = await engine.get_domain_info(data["domain"])
477
+
478
+ if "name" in data:
479
+ report["matches"] = await engine.search_person(data["name"], data.get("location"))
480
+
481
+ if "person_id" in data:
482
+ report["details"] = await engine.get_person_details(data["person_id"])
483
+
484
+ await engine.close()
485
+ return report
486
+
487
+ except Exception as e:
488
+ await engine.close()
489
+ return {"error": str(e)}