17
17
import java .io .IOException ;
18
18
import java .time .Instant ;
19
19
import java .util .ArrayList ;
20
+ import java .util .Arrays ;
20
21
import java .util .List ;
21
22
import java .util .Optional ;
22
23
@@ -312,6 +313,7 @@ public AnomalyResult(
312
313
* @param threshold Current threshold
313
314
* @param currentData imputed data if any
314
315
* @param featureImputed whether feature is imputed or not
316
+ * @param rules rules we apply on anomaly grade based on condition
315
317
* @return the converted AnomalyResult instance
316
318
*/
317
319
public static AnomalyResult fromRawTRCFResult (
@@ -338,15 +340,20 @@ public static AnomalyResult fromRawTRCFResult(
338
340
double [] likelihoodOfValues ,
339
341
Double threshold ,
340
342
double [] currentData ,
341
- boolean [] featureImputed
343
+ boolean [] featureImputed ,
344
+ List <Rule > rules
342
345
) {
343
346
List <DataByFeatureId > convertedRelevantAttribution = null ;
344
347
List <DataByFeatureId > convertedPastValuesList = null ;
345
348
List <ExpectedValueList > convertedExpectedValues = null ;
349
+ List <String > featureNamesForComparison = null ;
346
350
347
351
int featureSize = featureData == null ? 0 : featureData .size ();
348
352
349
353
if (grade > 0 ) {
354
+ // Get the top feature names based on the relevant attribution criteria
355
+ featureNamesForComparison = getTopFeatureNames (featureData , relevantAttribution );
356
+
350
357
if (relevantAttribution != null ) {
351
358
if (relevantAttribution .length == featureSize ) {
352
359
convertedRelevantAttribution = new ArrayList <>(featureSize );
@@ -425,6 +432,66 @@ public static AnomalyResult fromRawTRCFResult(
425
432
);
426
433
}
427
434
}
435
+ for (String featureName : featureNamesForComparison ) {
436
+ Double valueToCompare = null ;
437
+ if (convertedPastValuesList != null ) {
438
+ Double pastValue = convertedPastValuesList
439
+ .stream ()
440
+ .filter (data -> data .getFeatureId ().equals (featureName ))
441
+ .map (DataByFeatureId ::getData )
442
+ .findFirst ()
443
+ .orElse (null );
444
+ valueToCompare = pastValue != null ? pastValue : 0d ;
445
+ } else {
446
+ int featureIndex = featureData
447
+ .stream ()
448
+ .filter (data -> data .getFeatureId ().equals (featureName ))
449
+ .map (featureData ::indexOf )
450
+ .findFirst ()
451
+ .orElse (-1 );
452
+
453
+ valueToCompare = (featureIndex != -1 && currentData != null ) ? currentData [featureIndex ] : 0d ;
454
+ }
455
+
456
+ Double expectedValue = convertedExpectedValues
457
+ .stream ()
458
+ .flatMap (evList -> evList .getValueList ().stream ())
459
+ .filter (data -> data .getFeatureId ().equals (featureName ))
460
+ .map (DataByFeatureId ::getData )
461
+ .findFirst ()
462
+ .orElse (null );
463
+
464
+ int featureIndex = featureData
465
+ .stream ()
466
+ .filter (data -> data .getFeatureId ().equals (featureName ))
467
+ .map (featureData ::indexOf )
468
+ .findFirst ()
469
+ .orElse (-1 );
470
+
471
+ if (valueToCompare == null || expectedValue == null ) {
472
+ continue ; // Skip if either valueToCompare or expectedValue is missing
473
+ }
474
+
475
+ for (Rule rule : rules ) {
476
+ for (Condition condition : rule .getConditions ()) {
477
+ if (condition .getFeatureName ().equals (featureName )) {
478
+ ThresholdType thresholdType = condition .getThresholdType ();
479
+
480
+ if (thresholdType == ThresholdType .ACTUAL_IS_BELOW_EXPECTED && valueToCompare < expectedValue ) {
481
+ grade = 0d ;
482
+ break ;
483
+ } else if (thresholdType == ThresholdType .ACTUAL_IS_OVER_EXPECTED && valueToCompare > expectedValue ) {
484
+ grade = 0d ;
485
+ break ;
486
+ }
487
+ }
488
+ }
489
+ if (grade == 0 )
490
+ break ;
491
+ }
492
+ if (grade == 0 )
493
+ break ;
494
+ }
428
495
}
429
496
430
497
List <FeatureImputed > featureImputedList = new ArrayList <>();
@@ -468,6 +535,31 @@ public static AnomalyResult fromRawTRCFResult(
468
535
);
469
536
}
470
537
538
+ private static List <String > getTopFeatureNames (List <FeatureData > featureData , double [] relevantAttribution ) {
539
+ List <String > topFeatureNames = new ArrayList <>();
540
+
541
+ if (relevantAttribution == null || relevantAttribution .length == 0 || (relevantAttribution .length != featureData .size ())) {
542
+ featureData .forEach (feature -> topFeatureNames .add (feature .getFeatureId ()));
543
+ return topFeatureNames ;
544
+ }
545
+
546
+ // Find the maximum rounded value in a single pass and add corresponding feature names
547
+ double maxRoundedAttribution = Arrays
548
+ .stream (relevantAttribution )
549
+ .map (value -> Math .round (value * 100.0 ) / 100.0 )
550
+ .max ()
551
+ .orElse (Double .NaN );
552
+
553
+ // Collect feature names with values that match the max rounded value
554
+ for (int i = 0 ; i < relevantAttribution .length ; i ++) {
555
+ if (Math .round (relevantAttribution [i ] * 100.0 ) / 100.0 == maxRoundedAttribution ) {
556
+ topFeatureNames .add (featureData .get (i ).getFeatureId ());
557
+ }
558
+ }
559
+
560
+ return topFeatureNames ;
561
+ }
562
+
471
563
public AnomalyResult (StreamInput input ) throws IOException {
472
564
super (input );
473
565
this .modelId = input .readOptionalString ();
0 commit comments