@@ -69,6 +69,13 @@ def _require_string_list(value: Any, name: str, *, allow_empty: bool = False) ->
6969 return result
7070
7171
72+ def _require_number_0_1 (value : Any , name : str ) -> None :
73+ if not isinstance (value , (int , float )) or isinstance (value , bool ):
74+ raise SignalValidationError (f"{ name } must be numeric" )
75+ if value < 0 or value > 1 :
76+ raise SignalValidationError (f"{ name } must be between 0 and 1" )
77+
78+
7279def validate_signal (payload : Mapping [str , Any ]) -> None :
7380 missing = [key for key in REQUIRED_TOP_LEVEL_KEYS if key not in payload ]
7481 if missing :
@@ -94,17 +101,15 @@ def validate_signal(payload: Mapping[str, Any]) -> None:
94101
95102 if "theme_bias" in payload :
96103 _validate_bias_mapping (_require_mapping (payload ["theme_bias" ], "theme_bias" ), "theme_bias" )
104+ if "symbol_bias" in payload :
105+ _validate_bias_mapping (_require_mapping (payload ["symbol_bias" ], "symbol_bias" ), "symbol_bias" )
97106 if "symbol_theme_exposure" in payload :
98107 symbol_theme_exposure = _require_mapping (payload ["symbol_theme_exposure" ], "symbol_theme_exposure" )
99108 for symbol , theme_ids in symbol_theme_exposure .items ():
100109 _require_string (symbol , "symbol_theme_exposure key" )
101110 _require_string_list (theme_ids , f"symbol_theme_exposure[{ symbol !r} ]" )
102111
103- confidence = payload ["confidence" ]
104- if not isinstance (confidence , (int , float )) or isinstance (confidence , bool ):
105- raise SignalValidationError ("confidence must be numeric" )
106- if confidence < 0 or confidence > 1 :
107- raise SignalValidationError ("confidence must be between 0 and 1" )
112+ _require_number_0_1 (payload ["confidence" ], "confidence" )
108113
109114 evidence = _require_mapping (payload ["evidence" ], "evidence" )
110115 _require_string_list (evidence .get ("sources" ), "evidence.sources" )
@@ -121,7 +126,22 @@ def validate_signal(payload: Mapping[str, Any]) -> None:
121126def _validate_bias_mapping (mapping : Mapping [str , Any ], name : str ) -> None :
122127 for key , bias in mapping .items ():
123128 _require_string (key , f"{ name } key" )
124- if bias not in ALLOWED_BIAS_VALUES :
125- raise SignalValidationError (
126- f"{ name } [{ key !r} ] must be one of: { ', ' .join (sorted (ALLOWED_BIAS_VALUES ))} "
127- )
129+ _validate_bias_value (bias , f"{ name } [{ key !r} ]" )
130+
131+
132+ def _validate_bias_value (value : Any , name : str ) -> None :
133+ if isinstance (value , str ):
134+ bias = value
135+ else :
136+ raw = _require_mapping (value , name )
137+ bias = _require_string (raw .get ("bias" ), f"{ name } .bias" )
138+ if "confidence" in raw :
139+ _require_number_0_1 (raw ["confidence" ], f"{ name } .confidence" )
140+ for optional_key in ("rationale" , "horizon" ):
141+ if optional_key in raw :
142+ _require_string (raw [optional_key ], f"{ name } .{ optional_key } " )
143+ for optional_list_key in ("risk_flags" , "linked_themes" ):
144+ if optional_list_key in raw :
145+ _require_string_list (raw [optional_list_key ], f"{ name } .{ optional_list_key } " , allow_empty = True )
146+ if bias not in ALLOWED_BIAS_VALUES :
147+ raise SignalValidationError (f"{ name } must be one of: { ', ' .join (sorted (ALLOWED_BIAS_VALUES ))} " )
0 commit comments