5454 "name" : "Intelligence Platform Knowledge Graph - Events" ,
5555 "description" : "Dated events the on-device knowledge graph inferred, such as "
5656 "location visits and calendar events, with their imputed start "
57- "and end times." ,
57+ "and end times, and for a location visit the referenced place "
58+ "resolved to a name, address and coordinates." ,
5859 "author" : "@AlexisBrignoni, Claude" ,
5960 "creation_date" : "2026-09-23" ,
6061 "last_update_date" : "2026-09-23" ,
7172 "not reported. Predicate and class codes "
7273 "are resolved from the ontology.db shipped in the same folder. Events are "
7374 "inferred by the system, so an event is not evidence the user was present "
74- "or confirmed it. The location reference is reported as stored and is not "
75- "resolved to a place name here. Confidence is the value the graph "
76- "recorded, not a measurement made here, and can hold one value across "
77- "every event on a device that stored few of them. Retired events are "
78- "read from the expired_event_graph table alongside the live event_graph "
79- "and flagged in the Expired column. The IntelligencePlatform graph "
75+ "or confirmed it. The location reference is reported as stored (an "
76+ "'md:<id>' pointer whose number is the modeled place node's id) and is "
77+ "also resolved here to that place's name, composed address, latitude and "
78+ "longitude by reading the referenced node from the entity tables "
79+ "(stable_graph, then expired_stable_graph). A location visit therefore "
80+ "carries coordinates and is written to KML; calendar and other events "
81+ "with no location leave the location columns blank. Confidence is the "
82+ "value the graph recorded, not a measurement made here, and can hold one "
83+ "value across every event on a device that stored few of them. Retired "
84+ "events are read from the expired_event_graph table alongside the live "
85+ "event_graph and flagged in the Expired column. The IntelligencePlatform "
86+ "graph "
8087 "databases were described by 0x11 Forensics and Consulting, 'That is one "
8188 "smart Apple', 0x11forensicssc.com, 2026-07-21." ,
8289 "paths" : (
8390 '*/mobile/Library/IntelligencePlatform/graph.db*' ,
8491 '*/mobile/Library/IntelligencePlatform/ontology.db*' ),
85- "output_types" : "standard " ,
92+ "output_types" : "all " ,
8693 "artifact_icon" : "map-pin" ,
8794 "sample_data" : {
8895 "dexter_ios18" : "iOS 18.3.2 | 1032 rows" ,
@@ -244,6 +251,55 @@ def _cocoa(value):
244251 return None
245252
246253
254+ def _first (values ):
255+ """Return the first non-empty value as text, else ''."""
256+ for value in values :
257+ text = '' if value is None else str (value )
258+ if text :
259+ return text
260+ return ''
261+
262+
263+ def _place_lookup (files_found , predicates ):
264+ """Map each modeled node's subject id to its resolved (name, composed address,
265+ latitude, longitude), gathered from the live and expired entity tables. An event
266+ references its location by node id (object 'md:<id>'), so this turns that pointer
267+ into the place the graph modeled. The live stable_graph wins over the expired
268+ table when a node is in both."""
269+ lookup = {}
270+ for table_name in ("stable_graph" , "expired_stable_graph" ):
271+ rows , _ = _read_triples (files_found , table_name )
272+ for subject , ent in _group_by_subject (rows , predicates ).items ():
273+ number = _first (_part (ent , 'has address' , 'sub thoroughfare' ))
274+ street = _first (_part (ent , 'has address' , 'full street address' ))
275+ city = _first (_part (ent , 'has address' , 'city' ))
276+ region = _first (_part (ent , 'has address' , 'administrative district' ))
277+ postal = _first (_part (ent , 'has address' , 'postal code' ))
278+ name = _first (_part (ent , 'has address' , 'name' ))
279+ lat , lon = _coords (ent )
280+ line1 = ' ' .join (p for p in (number , street ) if p )
281+ region_zip = ' ' .join (p for p in (region , postal ) if p )
282+ address = ', ' .join (p for p in (line1 , city , region_zip ) if p )
283+ if subject not in lookup and (name or address or lat or lon ):
284+ lookup [subject ] = (name , address , lat , lon )
285+ return lookup
286+
287+
288+ def _resolve_location (ent , places ):
289+ """Resolve an event's location reference ('md:<id>') to the modeled place's
290+ (name, address, latitude, longitude). Returns blanks when the event has no
291+ location or the reference does not resolve to a modeled node."""
292+ for ref in _part (ent , 'has location relationship' , 'has location' ):
293+ if isinstance (ref , str ) and ref .startswith ('md:' ):
294+ try :
295+ node = int (ref [3 :])
296+ except ValueError :
297+ continue
298+ if node in places :
299+ return places [node ]
300+ return '' , '' , '' , ''
301+
302+
247303@artifact_processor
248304def intelligencePlatformEntities (context ):
249305 files_found = context .get_files_found ()
@@ -306,6 +362,7 @@ def intelligencePlatformEntities(context):
306362def intelligencePlatformEvents (context ):
307363 files_found = context .get_files_found ()
308364 predicates , classes = _load_ontology (files_found )
365+ places = _place_lookup (files_found , predicates )
309366
310367 data_list = []
311368 source_path = ''
@@ -321,12 +378,17 @@ def intelligencePlatformEvents(context):
321378 ends = [_cocoa (v ) for v in _part (ent , 'has date' , 'imputed end time' )]
322379 starts = [s for s in starts if s ]
323380 ends = [e for e in ends if e ]
381+ loc_name , loc_addr , loc_lat , loc_lon = _resolve_location (ent , places )
324382 data_list .append ((
325383 min (starts ) if starts else '' ,
326384 max (ends ) if ends else '' ,
327385 _join (classes .get (v , v ) for v in _base (ent , 'is a' )),
328386 _join (_base (ent , 'name' )),
329387 _join (_part (ent , 'has location relationship' , 'has location' )),
388+ loc_name ,
389+ loc_addr ,
390+ loc_lat ,
391+ loc_lon ,
330392 round (ent ['confidence' ], 4 ),
331393 expired ,
332394 str (subject )))
@@ -337,6 +399,10 @@ def intelligencePlatformEvents(context):
337399 'Event Type' ,
338400 'Name' ,
339401 'Location (as stored)' ,
402+ 'Location Name' ,
403+ 'Location Address' ,
404+ 'Latitude' ,
405+ 'Longitude' ,
340406 'Confidence' ,
341407 'Expired' ,
342408 'Event ID' )
0 commit comments