Skip to content

Commit c0a4fa9

Browse files
authored
Merge pull request #140 from PublicisSapient/feature/DTS-50511-precompute-kpi-maturity
Precompute kpi maturity changes
2 parents f3d3cab + beefaa5 commit c0a4fa9

File tree

32 files changed

+1832
-80
lines changed

32 files changed

+1832
-80
lines changed

ai-data-processor/pom.xml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,20 @@
9696

9797
<dependency>
9898
<groupId>org.apache.commons</groupId>
99-
<artifactId>commons-lang3</artifactId>
99+
<artifactId>commons-math3</artifactId>
100+
<version>3.6.1</version>
100101
</dependency>
101102

102103
<dependency>
103104
<groupId>org.apache.commons</groupId>
104105
<artifactId>commons-collections4</artifactId>
105106
</dependency>
106107

108+
<dependency>
109+
<groupId>org.apache.commons</groupId>
110+
<artifactId>commons-lang3</artifactId>
111+
</dependency>
112+
107113
<!-- Testing dependencies -->
108114
<dependency>
109115
<groupId>org.springframework.batch</groupId>

ai-data-processor/src/main/java/com/publicissapient/kpidashboard/client/customapi/dto/KpiElement.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@
2323
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
2424
import com.publicissapient.kpidashboard.client.customapi.deserializer.TrendValuesListDeserializer;
2525

26+
import lombok.AllArgsConstructor;
27+
import lombok.Builder;
2628
import lombok.Data;
29+
import lombok.NoArgsConstructor;
2730

2831
@Data
32+
@Builder
33+
@AllArgsConstructor
34+
@NoArgsConstructor
2935
@JsonIgnoreProperties(ignoreUnknown = true)
3036
@JsonInclude(JsonInclude.Include.NON_NULL)
3137
public class KpiElement {
3238
private String kpiId;
3339
private String kpiName;
34-
private String sprint;
3540
private String sprintId;
41+
private String overallMaturity;
42+
private String kpiCategory;
43+
3644
private Set<IssueKpiModalValue> issueData;
3745

38-
private Object value;
3946
@JsonDeserialize(using = TrendValuesListDeserializer.class)
4047
private Object trendValueList;
4148
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024 <Sapient Corporation>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the
14+
* License.
15+
*/
16+
17+
package com.publicissapient.kpidashboard.job.kpimaturitycalculation.config;
18+
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Map;
22+
import java.util.Set;
23+
import java.util.stream.Collectors;
24+
25+
import org.apache.commons.collections4.MapUtils;
26+
27+
import com.publicissapient.kpidashboard.job.config.validator.ConfigValidator;
28+
29+
import lombok.Data;
30+
31+
/**
32+
* Configuration class for the calculations performed on the kpi maturity
33+
* calculation job
34+
*/
35+
@Data
36+
public class CalculationConfig implements ConfigValidator {
37+
38+
@Data
39+
public static class DataPoints {
40+
private int count;
41+
}
42+
43+
@Data
44+
public static class Maturity {
45+
private Map<String, Double> weights;
46+
}
47+
48+
private static final int MAXIMUM_DATA_POINTS_ALLOWED = 15;
49+
50+
private final DataPoints dataPoints = new DataPoints();
51+
52+
private final Maturity maturity = new Maturity();
53+
54+
private Set<String> configValidationErrors = new HashSet<>();
55+
56+
@Override
57+
public void validateConfiguration() {
58+
if (MapUtils.isEmpty(this.maturity.weights)) {
59+
configValidationErrors.add("No kpi maturity weight configuration could be found");
60+
}
61+
62+
for (Map.Entry<String, Double> categoryIdWeightEntry : this.maturity.weights.entrySet()) {
63+
if (categoryIdWeightEntry.getValue() < 0.0D) {
64+
configValidationErrors.add(String.format(
65+
"A kpi maturity category weight must be higher or equal "
66+
+ "to zero. Invalid category '%s' was found with a weight of '%s'",
67+
categoryIdWeightEntry.getKey(), categoryIdWeightEntry.getValue()));
68+
}
69+
}
70+
71+
double weightagesSum = this.maturity.getWeights().values().stream().mapToDouble(Double::doubleValue).sum();
72+
if (Double.compare(1.0D, weightagesSum) != 0) {
73+
configValidationErrors.add("The sum of all kpi maturity category weightages must be 1");
74+
}
75+
76+
if (this.dataPoints.count < 1 || this.dataPoints.count > MAXIMUM_DATA_POINTS_ALLOWED) {
77+
configValidationErrors.add("The data points used for kpi maturity calculation must be between 1 and 15");
78+
}
79+
}
80+
81+
@Override
82+
public Set<String> getConfigValidationErrors() {
83+
return Collections.unmodifiableSet(this.configValidationErrors);
84+
}
85+
86+
public Set<String> getAllConfiguredCategories() {
87+
return this.maturity.weights.keySet().stream().filter(
88+
category -> this.maturity.weights.get(category) != null && this.maturity.weights.get(category) > 0.0D)
89+
.collect(Collectors.toSet());
90+
}
91+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/*
2+
* Copyright 2024 <Sapient Corporation>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the
14+
* License.
15+
*/
16+
17+
package com.publicissapient.kpidashboard.job.kpimaturitycalculation.config;
18+
19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Set;
22+
23+
import org.springframework.boot.context.properties.ConfigurationProperties;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.thymeleaf.util.StringUtils;
26+
27+
import com.publicissapient.kpidashboard.job.config.base.BatchConfig;
28+
import com.publicissapient.kpidashboard.job.config.base.SchedulingConfig;
29+
import com.publicissapient.kpidashboard.job.config.validator.ConfigValidator;
30+
31+
import jakarta.annotation.PostConstruct;
32+
import lombok.Data;
33+
34+
@Data
35+
@Configuration
36+
@ConfigurationProperties(prefix = "jobs.kpi-maturity-calculation")
37+
public class KpiMaturityCalculationConfig implements ConfigValidator {
38+
private String name;
39+
40+
private BatchConfig batching;
41+
private SchedulingConfig scheduling;
42+
private CalculationConfig calculationConfig;
43+
44+
private Set<String> configValidationErrors = new HashSet<>();
45+
46+
@PostConstruct
47+
private void retrieveJobConfigValidationErrors() {
48+
this.validateConfiguration();
49+
50+
this.calculationConfig.validateConfiguration();
51+
this.batching.validateConfiguration();
52+
this.scheduling.validateConfiguration();
53+
54+
this.configValidationErrors.addAll(this.calculationConfig.getConfigValidationErrors());
55+
this.configValidationErrors.addAll(this.batching.getConfigValidationErrors());
56+
this.configValidationErrors.addAll(this.scheduling.getConfigValidationErrors());
57+
}
58+
59+
@Override
60+
public void validateConfiguration() {
61+
if(StringUtils.isEmpty(this.name)) {
62+
configValidationErrors.add("The job 'name' parameter is required");
63+
}
64+
}
65+
66+
@Override
67+
public Set<String> getConfigValidationErrors() {
68+
return Collections.unmodifiableSet(this.configValidationErrors);
69+
}
70+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2024 <Sapient Corporation>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the
14+
* License.
15+
*/
16+
17+
package com.publicissapient.kpidashboard.job.kpimaturitycalculation.listener;
18+
19+
import java.time.Instant;
20+
import java.util.Objects;
21+
import java.util.Optional;
22+
23+
import org.bson.types.ObjectId;
24+
import org.springframework.batch.core.BatchStatus;
25+
import org.springframework.batch.core.JobExecution;
26+
import org.springframework.batch.core.JobExecutionListener;
27+
import org.springframework.batch.core.JobParameters;
28+
import org.springframework.lang.NonNull;
29+
30+
import com.publicissapient.kpidashboard.common.model.ProcessorExecutionTraceLog;
31+
import com.publicissapient.kpidashboard.common.model.application.ErrorDetail;
32+
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogServiceImpl;
33+
import com.publicissapient.kpidashboard.job.productivitycalculation.service.ProjectBatchService;
34+
35+
import lombok.RequiredArgsConstructor;
36+
import lombok.extern.slf4j.Slf4j;
37+
38+
@Slf4j
39+
@RequiredArgsConstructor
40+
public class KpiMaturityCalculationJobExecutionListener implements JobExecutionListener {
41+
private final ProjectBatchService projectBatchService;
42+
private final ProcessorExecutionTraceLogServiceImpl processorExecutionTraceLogServiceImpl;
43+
44+
@Override
45+
public void afterJob(@NonNull JobExecution jobExecution) {
46+
projectBatchService.initializeBatchProcessingParametersForTheNextProcess();
47+
storeJobExecutionStatus(jobExecution);
48+
}
49+
50+
private void storeJobExecutionStatus(JobExecution jobExecution) {
51+
JobParameters jobParameters = jobExecution.getJobParameters();
52+
String jobName = jobParameters.getString("jobName");
53+
ObjectId executionId = (ObjectId) Objects.requireNonNull(jobParameters.getParameter("executionId")).getValue();
54+
55+
Optional<ProcessorExecutionTraceLog> processorExecutionTraceLogOptional = this.processorExecutionTraceLogServiceImpl
56+
.findById(executionId);
57+
if (processorExecutionTraceLogOptional.isPresent()) {
58+
ProcessorExecutionTraceLog executionTraceLog = processorExecutionTraceLogOptional.get();
59+
executionTraceLog.setExecutionOngoing(false);
60+
executionTraceLog.setExecutionEndedAt(Instant.now().toEpochMilli());
61+
executionTraceLog.setExecutionSuccess(jobExecution.getStatus() == BatchStatus.COMPLETED);
62+
executionTraceLog
63+
.setErrorDetailList(jobExecution.getAllFailureExceptions().stream().map(failureException -> {
64+
ErrorDetail errorDetail = new ErrorDetail();
65+
errorDetail.setError(failureException.getMessage());
66+
return errorDetail;
67+
}).toList());
68+
this.processorExecutionTraceLogServiceImpl.saveAiDataProcessorExecutions(executionTraceLog);
69+
} else {
70+
log.error("Could not store job execution ending status for job with name {} and execution id {}. Job "
71+
+ "execution could not be found", jobName, executionId);
72+
}
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2024 <Sapient Corporation>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the
14+
* License.
15+
*/
16+
17+
package com.publicissapient.kpidashboard.job.kpimaturitycalculation.processor;
18+
19+
import org.springframework.batch.item.ItemProcessor;
20+
21+
import com.publicissapient.kpidashboard.common.model.kpimaturity.organization.KpiMaturity;
22+
import com.publicissapient.kpidashboard.job.kpimaturitycalculation.service.KpiMaturityCalculationService;
23+
import com.publicissapient.kpidashboard.job.shared.dto.ProjectInputDTO;
24+
25+
import jakarta.annotation.Nonnull;
26+
import lombok.RequiredArgsConstructor;
27+
import lombok.extern.slf4j.Slf4j;
28+
29+
@Slf4j
30+
@RequiredArgsConstructor
31+
public class ProjectItemProcessor implements ItemProcessor<ProjectInputDTO, KpiMaturity> {
32+
33+
private final KpiMaturityCalculationService kpiMaturityCalculationService;
34+
35+
@Override
36+
public KpiMaturity process(@Nonnull ProjectInputDTO item) {
37+
log.info("[kpi-maturity-calculation job] Starting kpi metrics calculation for project with nodeId: {}", item.nodeId());
38+
39+
return kpiMaturityCalculationService.calculateKpiMaturityForProject(item);
40+
}
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2024 <Sapient Corporation>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and limitations under the
14+
* License.
15+
*/
16+
17+
package com.publicissapient.kpidashboard.job.kpimaturitycalculation.reader;
18+
19+
import org.springframework.batch.item.ItemReader;
20+
21+
import com.publicissapient.kpidashboard.job.productivitycalculation.service.ProjectBatchService;
22+
import com.publicissapient.kpidashboard.job.shared.dto.ProjectInputDTO;
23+
24+
import lombok.RequiredArgsConstructor;
25+
import lombok.extern.slf4j.Slf4j;
26+
27+
@Slf4j
28+
@RequiredArgsConstructor
29+
public class ProjectItemReader implements ItemReader<ProjectInputDTO> {
30+
31+
private final ProjectBatchService projectBatchService;
32+
33+
@Override
34+
public ProjectInputDTO read() {
35+
ProjectInputDTO projectInputDTO = projectBatchService.getNextProjectInputData();
36+
37+
log.info("[kpi-maturity-calculation job] Received project input dto {}", projectInputDTO);
38+
39+
return projectInputDTO;
40+
}
41+
}

0 commit comments

Comments
 (0)