@@ -54,9 +54,12 @@ def unique_names(payload):
54
54
def valid_fact_references (payload ):
55
55
fact_references = set ()
56
56
for metric in payload .metrics :
57
- fact_references .add (metric ['numerator' ]['fact_name' ])
57
+ if 'numerator' in metric :
58
+ fact_references .add (metric ['numerator' ]['fact_name' ])
58
59
if 'denominator' in metric :
59
60
fact_references .add (metric ['denominator' ]['fact_name' ])
61
+ if 'percentile' in metric :
62
+ fact_references .add (metric ['percentile' ]['fact_name' ])
60
63
61
64
fact_names = set ()
62
65
for fact_source in payload .fact_sources :
@@ -81,12 +84,22 @@ def valid_experiment_computation(payload):
81
84
82
85
def metric_aggregation_is_valid (payload ):
83
86
for m in payload .metrics :
87
+ if m .get ('type' ) == 'percentile' :
88
+ percentile_error = percentile_metric_is_valid (m )
89
+ if percentile_error :
90
+ payload .validation_errors .append (
91
+ f"{ m ['name' ]} has invalid percentile configuration: { percentile_error } "
92
+ )
93
+ # Skip the rest of the loop iteration for percentile metrics
94
+ # since they don't have numerator/denominator to validate
95
+ continue
84
96
85
- numerator_error = aggregation_is_valid (m ['numerator' ])
86
- if numerator_error :
87
- payload .validation_errors .append (
88
- f"{ m ['name' ]} has invalid numerator: { numerator_error } "
89
- )
97
+ if 'numerator' in m :
98
+ numerator_error = aggregation_is_valid (m ['numerator' ])
99
+ if numerator_error :
100
+ payload .validation_errors .append (
101
+ f"{ m ['name' ]} has invalid numerator: { numerator_error } "
102
+ )
90
103
91
104
if 'denominator' in m :
92
105
denominator_error = aggregation_is_valid (m ['denominator' ])
@@ -103,13 +116,27 @@ def valid_guardrail_cutoff_signs(payload):
103
116
facts [fact ['name' ]] = fact
104
117
105
118
for m in payload .metrics :
106
- numerator_fact_name = m ['numerator' ]['fact_name' ]
107
- if is_guardrail_cutoff_exist (m ) and numerator_fact_name in facts and 'desired_change' in facts [numerator_fact_name ]:
108
- error = is_valid_guardrail_cutoff_sign (m , facts [numerator_fact_name ])
109
- if error :
110
- payload .validation_errors .append (
111
- f"{ m ['name' ]} is having invalid guardrail_cutoff sign: { error } "
112
- )
119
+ if m .get ('type' ) == 'percentile' :
120
+ if is_guardrail_cutoff_exist (m ):
121
+ percentile_fact_name = m ['percentile' ]['fact_name' ]
122
+ if percentile_fact_name in facts and 'desired_change' in facts [percentile_fact_name ]:
123
+ error = is_valid_guardrail_cutoff_sign (m , facts [percentile_fact_name ])
124
+ if error :
125
+ payload .validation_errors .append (
126
+ f"{ m ['name' ]} is having invalid guardrail_cutoff sign: { error } "
127
+ )
128
+ # Skip the rest of the loop iteration for percentile metrics
129
+ # since they don't have numerator/denominator to validate
130
+ continue
131
+
132
+ if 'numerator' in m :
133
+ numerator_fact_name = m ['numerator' ]['fact_name' ]
134
+ if is_guardrail_cutoff_exist (m ) and numerator_fact_name in facts and 'desired_change' in facts [numerator_fact_name ]:
135
+ error = is_valid_guardrail_cutoff_sign (m , facts [numerator_fact_name ])
136
+ if error :
137
+ payload .validation_errors .append (
138
+ f"{ m ['name' ]} is having invalid guardrail_cutoff sign: { error } "
139
+ )
113
140
114
141
115
142
def is_valid_guardrail_cutoff_sign (metric , numerator_fact ):
@@ -218,3 +245,30 @@ def aggregation_is_valid(aggregation):
218
245
return '\n ' .join (error_message )
219
246
else :
220
247
return None
248
+
249
+ def percentile_metric_is_valid (metric ):
250
+ error_message = []
251
+
252
+ # Check for required percentile field
253
+ if 'percentile' not in metric :
254
+ error_message .append ("Missing 'percentile' field for percentile metric" )
255
+ return '\n ' .join (error_message )
256
+
257
+ percentile = metric ['percentile' ]
258
+
259
+ # Check for required fact_name
260
+ if 'fact_name' not in percentile :
261
+ error_message .append ("Missing 'fact_name' in percentile configuration" )
262
+
263
+ # Check for required percentile_value
264
+ if 'percentile_value' not in percentile :
265
+ error_message .append ("Missing 'percentile_value' in percentile configuration" )
266
+ elif not isinstance (percentile ['percentile_value' ], (int , float )):
267
+ error_message .append ("'percentile_value' must be a number" )
268
+ elif percentile ['percentile_value' ] < 0 or percentile ['percentile_value' ] > 1 :
269
+ error_message .append ("'percentile_value' must be between 0 and 1" )
270
+
271
+ if error_message :
272
+ return '\n ' .join (error_message )
273
+ else :
274
+ return None
0 commit comments