1717
1818import re
1919from typing import Any
20+ from typing import Optional
2021
2122
2223TIMESTAMP_PRECISION = 3
2526class Normalizer :
2627 """Normalizes backend snapshots for fair cross-backend comparison."""
2728
28- def normalize_timestamp (self , ts : float ) -> float :
29- """Round timestamp to uniform precision."""
29+ def normalize_timestamp (self , ts : Optional [float ]) -> float :
30+ """Round timestamp to uniform precision.
31+
32+ Returns 0.0 for None to handle backends that may store NULL timestamps.
33+ """
34+ if ts is None :
35+ return 0.0
3036 return round (ts , TIMESTAMP_PRECISION )
3137
3238 def normalize_id (self , value : str ) -> str :
@@ -81,6 +87,7 @@ def normalize_event(self, event_dict: dict[str, Any]) -> dict[str, Any]:
8187 normalized ["parent_invocation_id" ] = self .normalize_id (
8288 normalized ["parent_invocation_id" ]
8389 )
90+ self ._normalize_scalar_fields (normalized )
8491 return normalized
8592
8693 def normalize_session (self , session_dict : dict [str , Any ]) -> dict [str , Any ]:
@@ -94,6 +101,7 @@ def normalize_session(self, session_dict: dict[str, Any]) -> dict[str, Any]:
94101 normalized ["events" ] = [
95102 self .normalize_event (e ) for e in normalized ["events" ]
96103 ]
104+ self ._normalize_scalar_fields (normalized )
97105 return normalized
98106
99107 def normalize_summary (self , summary_dict : dict [str , Any ]) -> dict [str , Any ]:
@@ -107,4 +115,16 @@ def normalize_summary(self, summary_dict: dict[str, Any]) -> dict[str, Any]:
107115 normalized ["summary_timestamp" ] = self .normalize_timestamp (
108116 normalized ["summary_timestamp" ]
109117 )
110- return normalized
118+ self ._normalize_scalar_fields (normalized )
119+ return normalized
120+
121+ def _normalize_scalar_fields (self , d : dict [str , Any ]) -> None :
122+ """Normalize null/empty representations for all scalar fields.
123+
124+ Skips lists and dicts to avoid destroying structured data like events
125+ or state, but normalizes None, "", [], {} for all other types so that
126+ different backends' empty representations are treated as equal.
127+ """
128+ for key , value in d .items ():
129+ if not isinstance (value , (list , dict )):
130+ d [key ] = self .normalize_null (value )
0 commit comments