Skip to content
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
28c8705
DTS-50661: Refactor batch job common constant.
shunaray Dec 1, 2025
70c9ed3
DTS-50661:Refactor job execution trace logging: replace ProcessorExec…
shunaray Dec 3, 2025
3358c58
DTS-50661: Created AiProcessorConstant for centralised constants.
shunaray Dec 4, 2025
4d3ea6e
DTS-50661: Add recommendation calculation job and related components
shunaray Dec 4, 2025
0d4059c
Merge branch 'develop' of https://github.com/PublicisSapient/knowhow-…
shunaray Dec 7, 2025
22ee198
DTS-50661: Enhance recommendation processing: add job execution trace…
shunaray Dec 8, 2025
838f0cf
DTS-50661:Add RecommendationValidator for AI-generated recommendation…
shunaray Dec 8, 2025
5e29029
DTS-50661:Update scheduling cron expression and clean up code comments
shunaray Dec 8, 2025
6fe903c
DTS-50661:Refactor recommendation parsing and validation logic for im…
shunaray Dec 8, 2025
dfd802b
DTS-50661:Enhance logging in recommendation processing with consisten…
shunaray Dec 8, 2025
6404333
DTS-50661:Refactor project-related classes and update variable names …
shunaray Dec 9, 2025
1f13e4d
DTS-50661: Refactor ProjectItemWriter to projectWiseTraceLog future s…
shunaray Dec 10, 2025
b9789f6
DTS-50661: Refactor configuration and update project ID references in…
shunaray Dec 10, 2025
195ebe2
DTS-50661: Refactor JobOrchestratorTest to use JobExecutionTraceLog a…
shunaray Dec 10, 2025
8440440
DTS-50661: Refactor code and added unit test cases.
shunaray Dec 10, 2025
d7e87a7
DTS-50661: Update application-local.yml with new API and authenticati…
shunaray Dec 10, 2025
d2ba174
DTS-50661: Remove the throws in javadoc causing failure.
shunaray Dec 10, 2025
812c5b4
DTS-50661: Refactor job execution trace log to support processor job-…
shunaray Dec 11, 2025
7b5cb98
DTS-50661: Renamed AiDataProcessorConstants to JobConstants.
shunaray Dec 11, 2025
5b2f1dd
DTS-50661: Review comment fixes, removed redundant ex try,catch and r…
shunaray Dec 11, 2025
da8e011
DTS-50661: Review comment unit test fixes
shunaray Dec 11, 2025
e3001ba
DTS-50661: Review comment added null validation
shunaray Dec 11, 2025
ca172bf
DTS-50661: Recommendation batch only for scrum projects and not on hold
shunaray Dec 11, 2025
451e242
Merge branch 'develop' of https://github.com/PublicisSapient/knowhow-…
shunaray Dec 11, 2025
0936187
Merge branch 'develop' of https://github.com/PublicisSapient/knowhow-…
shunaray Dec 12, 2025
b7d704f
DTS-50661: Added fast fail check in beforeJob for ai gateway availabi…
shunaray Dec 12, 2025
10d5eea
DTS-50661: Filtering on-hold project.
shunaray Dec 12, 2025
628cc8f
DTS-50661: Adding basicProjectConfig id in ProjectInputDTO
shunaray Dec 12, 2025
7a1f70b
DTS-50661: Added kpi data casting check.
shunaray Dec 12, 2025
47c79c8
DTS-50661: Test case fix.
shunaray Dec 12, 2025
8fd4020
DTS-50661: Test case fix.
shunaray Dec 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions ai-data-processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.knowhow.retro</groupId>
<artifactId>ai-gateway-client</artifactId>
<version>1.0.0</version>
</dependency>

<!--Logging dependencies-->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@ComponentScan(basePackages = {"com.publicissapient", "com.knowhow.retro.notifications"})
@EnableMongoRepositories(basePackages = {"com.publicissapient.**.repository"})
@ComponentScan(basePackages = { "com.publicissapient", "com.knowhow.retro.notifications",
"com.knowhow.retro.aigatewayclient" })
@EnableMongoRepositories(basePackages = { "com.publicissapient.**.repository" })
@EnableBatchProcessing
@EnableAsync
@EnableScheduling
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@

package com.publicissapient.kpidashboard.job.aiusagestatisticscollector.listener;

import com.publicissapient.kpidashboard.common.model.ProcessorExecutionTraceLog;
import com.publicissapient.kpidashboard.common.model.tracelog.JobExecutionTraceLog;
import com.publicissapient.kpidashboard.common.model.application.ErrorDetail;
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogServiceImpl;
import com.publicissapient.kpidashboard.common.service.JobExecutionTraceLogService;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.service.AccountBatchService;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -37,7 +37,7 @@
@AllArgsConstructor
public class AIUsageStatisticsJobCompletionListener implements JobExecutionListener {
private final AccountBatchService accountBatchService;
private final ProcessorExecutionTraceLogServiceImpl processorExecutionTraceLogServiceImpl;
private final JobExecutionTraceLogService jobExecutionTraceLogService;

@Override
public void afterJob(@NonNull JobExecution jobExecution) {
Expand All @@ -50,20 +50,20 @@ private void storeJobExecutionStatus(JobExecution jobExecution) {
String jobName = jobParameters.getString("jobName");
ObjectId executionId = (ObjectId) Objects.requireNonNull(jobParameters.getParameter("executionId")).getValue();

Optional<ProcessorExecutionTraceLog> processorExecutionTraceLogOptional = this.processorExecutionTraceLogServiceImpl
Optional<JobExecutionTraceLog> executionTraceLogOptional = this.jobExecutionTraceLogService
.findById(executionId);
if (processorExecutionTraceLogOptional.isPresent()) {
ProcessorExecutionTraceLog executionTraceLog = processorExecutionTraceLogOptional.get();
if (executionTraceLogOptional.isPresent()) {
JobExecutionTraceLog executionTraceLog = executionTraceLogOptional.get();
executionTraceLog.setExecutionOngoing(false);
executionTraceLog.setExecutionEndedAt(Instant.now().toEpochMilli());
executionTraceLog.setExecutionEndedAt(Instant.now());
executionTraceLog.setExecutionSuccess(jobExecution.getStatus() == BatchStatus.COMPLETED);
executionTraceLog
.setErrorDetailList(jobExecution.getAllFailureExceptions().stream().map(failureException -> {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setError(failureException.getMessage());
return errorDetail;
}).toList());
this.processorExecutionTraceLogServiceImpl.saveAiDataProcessorExecutions(executionTraceLog);
this.jobExecutionTraceLogService.updateJobExecution(executionTraceLog);
} else {
log.error("Could not store job execution ending status for job with name {} and execution id {}. Job "
+ "execution could not be found", jobName, executionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.dto.PagedAIUsagePerOrgLevel;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.model.AIUsageStatistics;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.service.AIUsageStatisticsService;
import com.publicissapient.kpidashboard.job.constant.JobConstants;

import jakarta.annotation.Nonnull;
import lombok.AllArgsConstructor;
Expand All @@ -33,7 +34,7 @@ public class AccountItemProcessor implements ItemProcessor<PagedAIUsagePerOrgLev

@Override
public AIUsageStatistics process(@Nonnull PagedAIUsagePerOrgLevel item) {
log.debug("[ai-usage-statistics-collector job] Fetching AI usage statistics for level name: {}", item.levelName());
log.debug("{} Fetching AI usage statistics for level name: {}", JobConstants.LOG_PREFIX_AI_USAGE_STATISTICS, item.levelName());
return aiUsageStatisticsService.fetchAIUsageStatistics(item.levelName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.dto.PagedAIUsagePerOrgLevel;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.service.AccountBatchService;
import com.publicissapient.kpidashboard.job.constant.JobConstants;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -33,7 +34,7 @@ public class AccountItemReader implements ItemReader<PagedAIUsagePerOrgLevel> {
@Override
public PagedAIUsagePerOrgLevel read() {
PagedAIUsagePerOrgLevel aiUsageStatistics = accountBatchService.getNextAccountPage();
log.info("[ai-usage-statistics-collector job] Reader fetched level name: {}", aiUsageStatistics.levelName());
log.info("{} Reader fetched level name: {}", JobConstants.LOG_PREFIX_AI_USAGE_STATISTICS, aiUsageStatistics.levelName());
return aiUsageStatistics;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
import org.springframework.stereotype.Component;
import org.springframework.transaction.PlatformTransactionManager;

import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogServiceImpl;
import com.publicissapient.kpidashboard.common.service.JobExecutionTraceLogService;
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogService;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.config.AIUsageStatisticsCollectorJobConfig;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.dto.PagedAIUsagePerOrgLevel;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.listener.AIUsageStatisticsJobCompletionListener;
Expand Down Expand Up @@ -61,7 +62,7 @@ public class AIUsageStatisticsJobStrategy implements JobStrategy {

private final AccountBatchService accountBatchService;
private final AIUsageStatisticsService aiUsageStatisticsService;
private final ProcessorExecutionTraceLogServiceImpl processorExecutionTraceLogServiceImpl;
private final JobExecutionTraceLogService jobExecutionTraceLogService;

@Override
public String getJobName() {
Expand All @@ -72,7 +73,7 @@ public String getJobName() {
public Job getJob() {
Step startStep = chunkProcessAIUsageStatisticsForAccounts();
AIUsageStatisticsJobCompletionListener jobListener = new AIUsageStatisticsJobCompletionListener(
this.accountBatchService, this.processorExecutionTraceLogServiceImpl);
this.accountBatchService, this.jobExecutionTraceLogService);
return new JobBuilder(aiUsageStatisticsCollectorJobConfig.getName(), jobRepository)
.start(startStep)
.listener(jobListener)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.model.AIUsageStatistics;
import com.publicissapient.kpidashboard.job.aiusagestatisticscollector.service.AIUsageStatisticsService;
import com.publicissapient.kpidashboard.job.constant.JobConstants;

import lombok.AllArgsConstructor;
import lombok.NonNull;
Expand All @@ -35,7 +36,7 @@ public class AccountItemWriter implements ItemWriter<AIUsageStatistics> {

@Override
public void write(@NonNull Chunk<? extends AIUsageStatistics> chunk) {
log.info("[ai-usage-statistics-collector job] Received chunk items for inserting into database with size: {}", chunk.size());
log.info("{} Received chunk items for inserting into database with size: {}", JobConstants.LOG_PREFIX_AI_USAGE_STATISTICS, chunk.size());
aiUsageStatisticsService.saveAll((List.copyOf(chunk.getItems())));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2024 Sapient Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and limitations under the
* License.
*/

package com.publicissapient.kpidashboard.job.constant;

import lombok.experimental.UtilityClass;

/**
* Constants used across AI Data Processor jobs.
*/
@UtilityClass
public final class JobConstants {

public static final String JOB_PRODUCTIVITY_CALCULATION = "productivity-calculation";
public static final String JOB_KPI_MATURITY_CALCULATION = "kpi-maturity-calculation";
public static final String JOB_AI_USAGE_STATISTICS_COLLECTOR = "ai-usage-statistics-collector";
public static final String JOB_RECOMMENDATION_CALCULATION = "recommendation-calculation";

public static final String LOG_PREFIX_RECOMMENDATION = "[recommendation-calculation job]";
public static final String LOG_PREFIX_PRODUCTIVITY = "[productivity-calculation job]";
public static final String LOG_PREFIX_KPI_MATURITY = "[kpi-maturity-calculation job]";
public static final String LOG_PREFIX_AI_USAGE_STATISTICS = "[ai-usage-statistics-collector job]";

}
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
import org.springframework.batch.core.JobParameters;
import org.springframework.lang.NonNull;

import com.publicissapient.kpidashboard.common.model.ProcessorExecutionTraceLog;
import com.publicissapient.kpidashboard.common.model.tracelog.JobExecutionTraceLog;
import com.publicissapient.kpidashboard.common.model.application.ErrorDetail;
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogServiceImpl;
import com.publicissapient.kpidashboard.common.service.JobExecutionTraceLogService;
import com.publicissapient.kpidashboard.job.productivitycalculation.service.ProjectBatchService;

import lombok.RequiredArgsConstructor;
Expand All @@ -39,7 +39,7 @@
@RequiredArgsConstructor
public class KpiMaturityCalculationJobExecutionListener implements JobExecutionListener {
private final ProjectBatchService projectBatchService;
private final ProcessorExecutionTraceLogServiceImpl processorExecutionTraceLogServiceImpl;
private final JobExecutionTraceLogService jobExecutionTraceLogService;

@Override
public void afterJob(@NonNull JobExecution jobExecution) {
Expand All @@ -52,20 +52,20 @@ private void storeJobExecutionStatus(JobExecution jobExecution) {
String jobName = jobParameters.getString("jobName");
ObjectId executionId = (ObjectId) Objects.requireNonNull(jobParameters.getParameter("executionId")).getValue();

Optional<ProcessorExecutionTraceLog> processorExecutionTraceLogOptional = this.processorExecutionTraceLogServiceImpl
Optional<JobExecutionTraceLog> executionTraceLogOptional = this.jobExecutionTraceLogService
.findById(executionId);
if (processorExecutionTraceLogOptional.isPresent()) {
ProcessorExecutionTraceLog executionTraceLog = processorExecutionTraceLogOptional.get();
if (executionTraceLogOptional.isPresent()) {
JobExecutionTraceLog executionTraceLog = executionTraceLogOptional.get();
executionTraceLog.setExecutionOngoing(false);
executionTraceLog.setExecutionEndedAt(Instant.now().toEpochMilli());
executionTraceLog.setExecutionEndedAt(Instant.now());
executionTraceLog.setExecutionSuccess(jobExecution.getStatus() == BatchStatus.COMPLETED);
executionTraceLog
.setErrorDetailList(jobExecution.getAllFailureExceptions().stream().map(failureException -> {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setError(failureException.getMessage());
return errorDetail;
}).toList());
this.processorExecutionTraceLogServiceImpl.saveAiDataProcessorExecutions(executionTraceLog);
this.jobExecutionTraceLogService.updateJobExecution(executionTraceLog);
} else {
log.error("Could not store job execution ending status for job with name {} and execution id {}. Job "
+ "execution could not be found", jobName, executionId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.springframework.batch.item.ItemProcessor;

import com.publicissapient.kpidashboard.common.model.kpimaturity.organization.KpiMaturity;
import com.publicissapient.kpidashboard.job.constant.JobConstants;
import com.publicissapient.kpidashboard.job.kpimaturitycalculation.service.KpiMaturityCalculationService;
import com.publicissapient.kpidashboard.job.shared.dto.ProjectInputDTO;

Expand All @@ -34,7 +35,7 @@ public class ProjectItemProcessor implements ItemProcessor<ProjectInputDTO, KpiM

@Override
public KpiMaturity process(@Nonnull ProjectInputDTO item) {
log.info("[kpi-maturity-calculation job] Starting kpi metrics calculation for project with nodeId: {}", item.nodeId());
log.info("{} Starting kpi metrics calculation for project with nodeId: {}", JobConstants.LOG_PREFIX_KPI_MATURITY,item.nodeId());

return kpiMaturityCalculationService.calculateKpiMaturityForProject(item);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.springframework.batch.item.ItemReader;

import com.publicissapient.kpidashboard.job.constant.JobConstants;
import com.publicissapient.kpidashboard.job.productivitycalculation.service.ProjectBatchService;
import com.publicissapient.kpidashboard.job.shared.dto.ProjectInputDTO;

Expand All @@ -34,7 +35,7 @@ public class ProjectItemReader implements ItemReader<ProjectInputDTO> {
public ProjectInputDTO read() {
ProjectInputDTO projectInputDTO = projectBatchService.getNextProjectInputData();

log.info("[kpi-maturity-calculation job] Received project input dto {}", projectInputDTO);
log.info("{} Received project input dto {}", JobConstants.LOG_PREFIX_KPI_MATURITY, projectInputDTO);

return projectInputDTO;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
import org.springframework.transaction.PlatformTransactionManager;

import com.publicissapient.kpidashboard.common.model.kpimaturity.organization.KpiMaturity;
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogServiceImpl;
import com.publicissapient.kpidashboard.common.service.JobExecutionTraceLogService;
import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogService;
import com.publicissapient.kpidashboard.job.config.base.SchedulingConfig;
import com.publicissapient.kpidashboard.job.kpimaturitycalculation.config.KpiMaturityCalculationConfig;
import com.publicissapient.kpidashboard.job.kpimaturitycalculation.listener.KpiMaturityCalculationJobExecutionListener;
Expand Down Expand Up @@ -59,7 +60,8 @@ public class KpiMaturityCalculationJobStrategy implements JobStrategy {

private final ProjectBatchService projectBatchService;
private final KpiMaturityCalculationService kpiMaturityCalculationService;
private final ProcessorExecutionTraceLogServiceImpl processorExecutionTraceLogServiceImpl;
private final JobExecutionTraceLogService jobExecutionTraceLogService;
private final ProcessorExecutionTraceLogService processorExecutionTraceLogService;

@Override
public String getJobName() {
Expand All @@ -71,7 +73,7 @@ public Job getJob() {
return new JobBuilder(this.kpiMaturityCalculationConfig.getName(), this.jobRepository)
.start(chunkProcessProjects())
.listener(new KpiMaturityCalculationJobExecutionListener(this.projectBatchService,
this.processorExecutionTraceLogServiceImpl))
this.jobExecutionTraceLogService))
.build();
}

Expand All @@ -98,7 +100,7 @@ private AsyncItemProcessor<ProjectInputDTO, KpiMaturity> asyncProjectProcessor()

private AsyncItemWriter<KpiMaturity> asyncItemWriter() {
AsyncItemWriter<KpiMaturity> writer = new AsyncItemWriter<>();
writer.setDelegate(new ProjectItemWriter(this.kpiMaturityCalculationService));
writer.setDelegate(new ProjectItemWriter(this.kpiMaturityCalculationService, this.processorExecutionTraceLogService));
return writer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.util.List;

import com.publicissapient.kpidashboard.common.service.ProcessorExecutionTraceLogService;
import com.publicissapient.kpidashboard.job.constant.JobConstants;
import org.springframework.batch.item.Chunk;
import org.springframework.batch.item.ItemWriter;
import org.springframework.lang.NonNull;
Expand All @@ -32,11 +34,13 @@
@RequiredArgsConstructor
public class ProjectItemWriter implements ItemWriter<KpiMaturity> {

private final KpiMaturityCalculationService kpiMaturityCalculationService;
private final KpiMaturityCalculationService kpiMaturityCalculationService;
private final ProcessorExecutionTraceLogService processorExecutionTraceLogService;

@Override
public void write(@NonNull Chunk<? extends KpiMaturity> chunk) {
log.info("[kpi-maturity-calculation job] Received chunk items for inserting into database with size: {}", chunk.size());
kpiMaturityCalculationService.saveAll((List<KpiMaturity>) chunk.getItems());
}
@Override
public void write(@NonNull Chunk<? extends KpiMaturity> chunk) {
log.info("{} Received chunk items for inserting into database with size: {}",
JobConstants.LOG_PREFIX_KPI_MATURITY, chunk.size());
kpiMaturityCalculationService.saveAll((List<KpiMaturity>) chunk.getItems());
}
}
Loading