@@ -17,70 +17,152 @@ func NewEvaluator(backend *client.BackendClient) *Evaluator {
1717 return & Evaluator {backend : backend }
1818}
1919
20- func (e * Evaluator ) Evaluate (ctx context.Context , cfg models.ReportConfig ) (models.Evaluation , error ) {
20+ func (e * Evaluator ) Evaluate (ctx context.Context , cfg models.ControlConfig ) (models.ControlEvaluation , error ) {
2121 // 1. Obtener index patterns activos
22- _ , err := e .backend .GetActiveIndexPatterns (ctx )
22+ patterns , err := e .backend .GetActiveIndexPatterns (ctx )
2323 if err != nil {
24- return models.Evaluation {}, fmt .Errorf ("failed to get index patterns: %w" , err )
24+ return models.ControlEvaluation {}, fmt .Errorf ("failed to get index patterns: %w" , err )
2525 }
2626
27- // 2. Evaluar cada QuerySpec
28- /*var results []models.QueryResult*/
29- for _ , q := range cfg . Queries {
30- /*qr := e.evaluateQuery(ctx, q, patterns)
31- results = append(results, qr)*/
27+ results := make ([]models. QueryEvaluation , 0 )
28+ applicable := make ( []models.QueryEvaluation , 0 ) // solo queries con indexPattern activo
29+
30+ // 2. Evaluar cada QueryConfig
31+ for _ , q := range cfg . QueriesConfigs {
3232 catcher .Info ("Evaluating query" , map [string ]any {
3333 "query_id" : q .ID ,
3434 })
35- }
3635
37- /*final := combineResults(cfg, results)
36+ // 2.1 Si el index pattern NO está activo → NOT_APPLICABLE
37+ if ! patternExists (int (q .IndexPatternID ), patterns ) {
38+ reason := "Index pattern not active"
39+ qr := models.QueryEvaluation {
40+ QueryConfigID : q .ID ,
41+ QueryName : q .QueryName ,
42+ Hits : 0 ,
43+ Status : models .QueryStatusNotApplicable ,
44+ ErrorMessage : & reason ,
45+ }
46+ results = append (results , qr )
47+ continue
48+ }
3849
39- return final, nil*/
50+ // 2.2 Evaluar query normalmente
51+ qr := e .evaluateQuery (ctx , q )
52+ results = append (results , qr )
4053
41- return models.Evaluation {}, nil
42- }
54+ // 2.3 Solo las queries aplicables participan en la estrategia ALL/ANY
55+ if qr .Status != models .QueryStatusNotApplicable {
56+ applicable = append (applicable , qr )
57+ }
58+ }
59+
60+ // 3. Combinar resultados según la estrategia del control
61+ finalStatus := computeControlStatus (cfg .ControlStrategy , applicable )
4362
44- func (e * Evaluator ) evaluateQuery (ctx context.Context , q models.QuerySpec , patterns []models.IndexPattern ) models.QueryResult {
63+ // 4. Construir evaluación final
64+ return models.ControlEvaluation {
65+ ControlConfigID : cfg .ID ,
66+ ControlName : cfg .ControlName ,
67+ Status : finalStatus ,
68+ QueryEvaluations : results ,
69+ }, nil
70+ }
4571
46- /*if !patternExists(q.IndexPatternID, patterns) {
47- return models.QueryResult{
48- QueryID: int(q.ID),
49- Status: models.StatusNotApplicable,
50- Reason: "Index pattern not active",
72+ func (e * Evaluator ) evaluateQuery (ctx context.Context , q models.QueryConfig ) models.QueryEvaluation {
73+ // Ejecutar la query SQL real contra OpenSearch
74+ hits , err := e .backend .ExecuteSQLQuery (ctx , q .SQLQuery )
75+ if err != nil {
76+ msg := fmt .Sprintf ("query execution failed: %v" , err )
77+ return models.QueryEvaluation {
78+ QueryConfigID : q .ID ,
79+ QueryName : q .QueryName ,
80+ Hits : 0 ,
81+ Status : models .QueryStatusError ,
82+ ErrorMessage : & msg ,
5183 }
52- }*/
84+ }
85+
86+ // Evaluar la regla con los hits obtenidos
87+ status , errMsg := evaluateQueryRule (q , hits )
5388
54- return models.QueryResult {
55- QueryID : int (q .ID ),
56- Status : models .StatusCompliant ,
57- Reason : "Query executed successfully (placeholder)" ,
89+ return models.QueryEvaluation {
90+ QueryConfigID : q .ID ,
91+ QueryName : q .QueryName ,
92+ Hits : int64 (hits ),
93+ Status : status ,
94+ ErrorMessage : errMsg ,
5895 }
5996}
6097
61- func patternExists (pattern int , active []models.IndexPattern ) bool {
62- for _ , p := range active {
63- if p .ID == pattern && p .Active {
64- return true
98+ func evaluateQueryRule (q models.QueryConfig , hits int ) (models.QueryEvaluationStatus , * string ) {
99+ switch q .EvaluationRule {
100+
101+ case models .NoHitsAllowed :
102+ if hits == 0 {
103+ return models .QueryStatusCompliant , nil
104+ }
105+ return models .QueryStatusNonCompliant , nil
106+
107+ case models .MinHitsRequired :
108+ if q .RuleValue == nil {
109+ msg := "ruleValue is required for MIN_HITS_REQUIRED"
110+ return models .QueryStatusError , & msg
65111 }
112+ if hits >= * q .RuleValue {
113+ return models .QueryStatusCompliant , nil
114+ }
115+ return models .QueryStatusNonCompliant , nil
116+
117+ case models .ThresholdMax :
118+ if q .RuleValue == nil {
119+ msg := "ruleValue is required for THRESHOLD_MAX"
120+ return models .QueryStatusError , & msg
121+ }
122+ if hits <= * q .RuleValue {
123+ return models .QueryStatusCompliant , nil
124+ }
125+ return models .QueryStatusNonCompliant , nil
126+
127+ default :
128+ msg := "unknown evaluation rule"
129+ return models .QueryStatusError , & msg
66130 }
67- return false
68131}
69132
70- func combineResults (cfg models.ReportConfig , results []models.QueryResult ) models.Evaluation {
71- final := models.Evaluation {
72- ReportID : int (cfg .ID ),
73- Results : results ,
74- }
133+ func computeControlStatus (strategy models.ComplianceStrategy , results []models.QueryEvaluation ) models.ControlEvaluationStatus {
75134
76- // Estrategia simple: si alguna query es NON_COMPLIANT → NON_COMPLIANT
77- for _ , r := range results {
78- if r .Status == models .StatusNonCompliant {
79- final .Status = models .StatusNonCompliant
80- return final
135+ switch strategy {
136+
137+ case models .StrategyAll :
138+ // ALL → todas deben ser COMPLIANT
139+ for _ , r := range results {
140+ if r .Status != models .QueryStatusCompliant {
141+ return models .ControlStatusNonCompliant
142+ }
143+ }
144+ return models .ControlStatusCompliant
145+
146+ case models .StrategyAny :
147+ // ANY → basta con que una sea COMPLIANT
148+ for _ , r := range results {
149+ if r .Status == models .QueryStatusCompliant {
150+ return models .ControlStatusCompliant
151+ }
81152 }
153+ return models .ControlStatusNonCompliant
154+
155+ default :
156+ // fallback seguro
157+ return models .ControlStatusNonCompliant
82158 }
159+ }
83160
84- final .Status = models .StatusCompliant
85- return final
161+ func patternExists (pattern int , active []models.IndexPattern ) bool {
162+ for _ , p := range active {
163+ if int (p .ID ) == pattern && p .Active {
164+ return true
165+ }
166+ }
167+ return false
86168}
0 commit comments