From 75a85a36ba8cbf74bded145b5ab7b67366ca939a Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Fri, 4 Jun 2021 19:05:58 +0200 Subject: [PATCH 01/12] Assessment confidence endpoint and tests --- .github/scripts/check_api.sh | 9 ++ .../controllers/AssessmentsResource.java | 8 ++ .../pathfinder/dto/AdoptionCandidateDto.java | 19 ++++ .../pathfinder/services/AssessmentSvc.java | 87 +++++++++++++++++++ src/main/resources/application.properties | 16 ++++ .../controllers/AssessmentsResourceTest.java | 46 +++++++++- .../services/AssessmentSvcTest.java | 40 +++++++-- 7 files changed, 218 insertions(+), 7 deletions(-) create mode 100644 src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java diff --git a/.github/scripts/check_api.sh b/.github/scripts/check_api.sh index 4b9eafc7..286bc67c 100755 --- a/.github/scripts/check_api.sh +++ b/.github/scripts/check_api.sh @@ -209,4 +209,13 @@ req_not_existing_assessment=$(curl -X POST "http://$api_ip/pathfinder/assessment -w "%{http_code}") test "404" = "$req_not_existing_assessment" + +echo +echo +echo "15 >>> Checking the confidence of assessments" +confidence=$(curl -X PATCH "http://$api_ip/pathfinder/assessments/confidence?applicationId=100&applicationId=$applicationTarget" -H 'Accept: application/json' \ + -H "Authorization: Bearer $access_token" -w "%{http_code}" \ + -H 'Content-Type: application/json' ) +echo $confidence | grep "\"assessmentId\": $assessmentSourceId, \"confidence\"" + echo " +++++ API CHECK SUCCESSFUL ++++++" diff --git a/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java b/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java index d8449a32..e453c2ac 100644 --- a/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java +++ b/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java @@ -1,5 +1,6 @@ package io.tackle.pathfinder.controllers; +import io.tackle.pathfinder.dto.AdoptionCandidateDto; import io.tackle.pathfinder.dto.ApplicationDto; import io.tackle.pathfinder.dto.AssessmentDto; import io.tackle.pathfinder.dto.AssessmentHeaderDto; @@ -77,4 +78,11 @@ public Response deleteAssessment(@NotNull @PathParam("assessmentId") Long assess return Response.ok().status(Response.Status.NO_CONTENT).build(); } + @GET + @Path("/confidence") + @Produces("application/json") + public List adoptionCandidate(@QueryParam("applicationId") List applicationId) { + return service.getAdoptionCandidate(applicationId); + } + } diff --git a/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java b/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java new file mode 100644 index 00000000..550ea075 --- /dev/null +++ b/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java @@ -0,0 +1,19 @@ +package io.tackle.pathfinder.dto; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.quarkus.runtime.annotations.RegisterForReflection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@RegisterForReflection +public class AdoptionCandidateDto { + public Long assessmentId; + public Integer confidence; +} diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index f91e1427..766a0391 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -1,9 +1,13 @@ package io.tackle.pathfinder.services; +import com.google.common.util.concurrent.AtomicDouble; +import io.quarkus.arc.config.ConfigProperties; +import io.tackle.pathfinder.dto.AdoptionCandidateDto; import io.tackle.pathfinder.dto.AssessmentDto; import io.tackle.pathfinder.dto.AssessmentHeaderDto; import io.tackle.pathfinder.dto.AssessmentStatus; import io.tackle.pathfinder.mapper.AssessmentMapper; +import io.tackle.pathfinder.model.Risk; import io.tackle.pathfinder.model.assessment.Assessment; import io.tackle.pathfinder.model.assessment.AssessmentCategory; import io.tackle.pathfinder.model.assessment.AssessmentQuestion; @@ -16,6 +20,7 @@ import io.tackle.pathfinder.model.questionnaire.Questionnaire; import io.tackle.pathfinder.model.questionnaire.SingleOption; import lombok.extern.java.Log; +import org.eclipse.microprofile.config.inject.ConfigProperty; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; @@ -25,7 +30,11 @@ import javax.ws.rs.BadRequestException; import javax.ws.rs.NotFoundException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.logging.Level; import java.util.stream.Collectors; @@ -36,6 +45,29 @@ public class AssessmentSvc { @Inject AssessmentMapper mapper; + @ConfigProperty(name = "confidence.risk.RED.weight") + Integer redWeight; + @ConfigProperty(name = "confidence.risk.GREEN.weight") + Integer greenWeight; + @ConfigProperty(name = "confidence.risk.AMBER.weight") + Integer amberWeight; + @ConfigProperty(name = "confidence.risk.UNKNOWN.weight") + Integer unknownWeight; + + @ConfigProperty(name = "confidence.risk.AMBER.multiplier") + Double amberMultiplier; + @ConfigProperty(name = "confidence.risk.RED.multiplier") + Double redMultiplier; + + @ConfigProperty(name = "confidence.risk.RED.adjuster") + Double redAdjuster; + @ConfigProperty(name = "confidence.risk.AMBER.adjuster") + Double amberAdjuster; + @ConfigProperty(name = "confidence.risk.GREEN.adjuster") + Double greenAdjuster; + @ConfigProperty(name = "confidence.risk.UNKNOWN.adjuster") + Double unknownAdjuster; + public Optional getAssessmentHeaderDtoByApplicationId(@NotNull Long applicationId) { List assessmentQuery = Assessment.list("application_id", applicationId); return assessmentQuery.stream().findFirst().map(e -> mapper.assessmentToAssessmentHeaderDto(e)); @@ -298,4 +330,59 @@ private AssessmentQuestionnaire copyQuestionnaireBetweenAssessments(Assessment s return questionnaire; } + @Transactional + public List getAdoptionCandidate(List applicationId) { + return applicationId.stream() + .map(a-> Assessment.find("applicationId", a).firstResultOptional()) + .filter(b -> b.isPresent()) + .map(c -> new AdoptionCandidateDto(((Assessment) c.get()).id, calculateConfidence((Assessment) c.get()))) + .collect(Collectors.toList()); + } + + private Integer calculateConfidence(Assessment assessment) { + Map weightMap = Map.of(Risk.RED, redWeight, + Risk.UNKNOWN, unknownWeight, + Risk.AMBER, amberWeight, + Risk.GREEN, greenWeight); + Map confidenceMultiplier = Map.of(Risk.RED, redMultiplier, Risk.AMBER, amberMultiplier); + Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); + + Map answersCountByRisk = assessment.assessmentQuestionnaire.categories.stream() + .flatMap(cat -> cat.questions.stream()) + .flatMap(que -> que.singleOptions.stream()) + .filter(opt -> opt.selected) + .collect(Collectors.groupingBy(a -> a.risk, Collectors.counting())); + + long totalAnswered = answersCountByRisk.values().stream().mapToLong(Long::longValue).sum(); + + // Adjuster calculation + AtomicDouble adjuster = new AtomicDouble(1); + answersCountByRisk.entrySet().stream() + .filter(a -> a.getValue() > 0 ) + .forEach(b -> updateAdjuster(adjusterBase, adjuster, b)); + + // Temp confidence iteration calculation + AtomicDouble confidence = new AtomicDouble(0.0); + assessment.assessmentQuestionnaire.categories.stream() + .flatMap(cat -> cat.questions.stream()) + .flatMap(que -> que.singleOptions.stream()) + .filter(opt -> opt.selected) + .forEach(opt -> { + confidence.set(confidence.get() * confidenceMultiplier.getOrDefault(opt.risk, 1.0)); + confidence.getAndAdd(weightMap.get(opt.risk) * adjuster.get()); + }); + + double maxConfidence = weightMap.get(Risk.GREEN) * totalAnswered; + + BigDecimal result = new BigDecimal((confidence.get() / maxConfidence) * 100); + result.setScale(0, RoundingMode.DOWN); + + return result.intValue(); + } + + // if (redCount > 0) adjuster = adjuster * Math.pow(0.5, redCount); + // if (amberCount > 0) adjuster = adjuster * Math.pow(0.98, amberCount); + private void updateAdjuster(Map adjusterBase, AtomicDouble adjuster, Map.Entry b) { + adjuster.set(adjuster.get() * Math.pow(adjusterBase.get(b.getKey()), b.getValue())); + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index ee1812f4..0a229646 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -39,6 +39,20 @@ quarkus.openshift.part-of=${quarkus.kubernetes.part-of} quarkus.kubernetes.labels."app.kubernetes.io/component"=rest quarkus.openshift.labels."app.kubernetes.io/component"=rest +# Service +confidence.risk.RED.weight=1 +confidence.risk.AMBER.weight=800 +confidence.risk.GREEN.weight=1000 +confidence.risk.UNKNOWN.weight=700 + +confidence.risk.RED.multiplier=0.6 +confidence.risk.AMBER.multiplier=0.95 + +confidence.risk.AMBER.adjuster=0.98 +confidence.risk.UNKNOWN.adjuster=1.0 +confidence.risk.RED.adjuster=0.5 +confidence.risk.GREEN.adjuster=1.0 + # ----- PROD %prod.quarkus.hibernate-orm.log.sql=false @@ -63,6 +77,8 @@ quarkus.openshift.labels."app.kubernetes.io/component"=rest %dev.quarkus.hibernate-orm.database.generation=none + + # -------- Extra profiles %local.quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/pathfinder_db %local.quarkus.oidc.auth-server-url=https://localhost:8543/auth/realms/quarkus diff --git a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java index 40f1343b..dda173b4 100644 --- a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java +++ b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java @@ -16,6 +16,7 @@ import io.tackle.pathfinder.dto.AssessmentQuestionOptionDto; import io.tackle.pathfinder.dto.AssessmentQuestionnaireDto; import io.tackle.pathfinder.dto.AssessmentStatus; +import io.tackle.pathfinder.model.Risk; import io.tackle.pathfinder.model.assessment.Assessment; import io.tackle.pathfinder.model.assessment.AssessmentCategory; import io.tackle.pathfinder.model.assessment.AssessmentSingleOption; @@ -29,7 +30,7 @@ import org.junit.jupiter.api.Test; import javax.inject.Inject; -import javax.transaction.Transactional; +import javax.transaction.*; import java.time.Duration; import java.time.LocalTime; @@ -64,6 +65,9 @@ public class AssessmentsResourceTest extends SecuredResourceTest { @Inject ManagedExecutor managedExecutor; + @Inject + UserTransaction userTransaction; + @BeforeEach @Transactional public void init() { @@ -739,4 +743,44 @@ public void given_ApplicationNotAssessed_When_CopyAssessmentToAnotherNotAssessed .log().all() .statusCode(404); } + + @Test + public void given_ApplicationsAssessed_When_Confidence_Then_ResultIsTheExpected() throws SystemException, NotSupportedException, HeuristicRollbackException, HeuristicMixedException, RollbackException { + userTransaction.begin(); + // create assessment + AssessmentHeaderDto assessmentREDHeader = assessmentSvc.createAssessment( 20008L); + AssessmentHeaderDto assessmentGREENHeader = assessmentSvc.createAssessment( 20009L); + AssessmentHeaderDto assessmentAMBERHeader = assessmentSvc.createAssessment( 20010L); + AssessmentHeaderDto assessmentUNKNOWNHeader = assessmentSvc.createAssessment( 20011L); + Assessment assessmentRED = Assessment.findById(assessmentREDHeader.getId()); + Assessment assessmentGREEN = Assessment.findById(assessmentGREENHeader.getId()); + Assessment assessmentAMBER = Assessment.findById(assessmentAMBERHeader.getId()); + Assessment assessmentUNKNOWN = Assessment.findById(assessmentUNKNOWNHeader.getId()); + + // answer questions + assessmentRED.status = AssessmentStatus.COMPLETE; + assessmentRED.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.RED).findFirst().ifPresent(b -> b.selected = true))); + assessmentGREEN.status = AssessmentStatus.COMPLETE; + assessmentGREEN.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.GREEN).findFirst().ifPresent(b -> b.selected = true))); + assessmentAMBER.status = AssessmentStatus.COMPLETE; + assessmentAMBER.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.AMBER).findFirst().ifPresent(b -> b.selected = true))); + assessmentUNKNOWN.status = AssessmentStatus.COMPLETE; + assessmentUNKNOWN.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.UNKNOWN).findFirst().ifPresent(b -> b.selected = true))); + + userTransaction.commit(); + + given() + .contentType(ContentType.JSON) + .accept(ContentType.JSON) + .when() + .get("/assessments/confidence?applicationId=20008&applicationId=20009&applicationId=20010&applicationId=20011") + .then() + .log().all() + .statusCode(200) + .body("find{it.assessmentId=="+ assessmentRED.id + "}.confidence", is(0)) + .body("find{it.assessmentId=="+ assessmentGREEN.id + "}.confidence", is(100)) + .body("find{it.assessmentId=="+ assessmentAMBER.id + "}.confidence", is(25)) + .body("find{it.assessmentId=="+ assessmentUNKNOWN.id + "}.confidence", is(70)); + + } } diff --git a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java index f008beee..4578ed46 100644 --- a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java +++ b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java @@ -6,12 +6,7 @@ import io.quarkus.test.common.ResourceArg; import io.quarkus.test.junit.QuarkusTest; import io.tackle.commons.testcontainers.PostgreSQLDatabaseTestResource; -import io.tackle.pathfinder.dto.AssessmentCategoryDto; -import io.tackle.pathfinder.dto.AssessmentDto; -import io.tackle.pathfinder.dto.AssessmentHeaderDto; -import io.tackle.pathfinder.dto.AssessmentQuestionDto; -import io.tackle.pathfinder.dto.AssessmentQuestionOptionDto; -import io.tackle.pathfinder.dto.AssessmentStatus; +import io.tackle.pathfinder.dto.*; import io.tackle.pathfinder.mapper.AssessmentMapper; import io.tackle.pathfinder.model.QuestionType; import io.tackle.pathfinder.model.Risk; @@ -370,6 +365,39 @@ public void given_AlreadyAssessedApplication_When_CopyAssessment_ShouldCopyAllUs assertThatThrownBy(() -> assessmentSvc.deleteAssessment(assessment.id)).isInstanceOf(BadRequestException.class); } + @Test + @Transactional + public void given_ApplicationsAssessed_when_AdoptionCandidate_then_ResuletIsTheExpected() { + // create assessment + AssessmentHeaderDto assessmentREDHeader = assessmentSvc.createAssessment( 10008L); + AssessmentHeaderDto assessmentGREENHeader = assessmentSvc.createAssessment( 10009L); + AssessmentHeaderDto assessmentAMBERHeader = assessmentSvc.createAssessment( 10010L); + AssessmentHeaderDto assessmentUNKNOWNHeader = assessmentSvc.createAssessment( 10011L); + Assessment assessmentRED = Assessment.findById(assessmentREDHeader.getId()); + Assessment assessmentGREEN = Assessment.findById(assessmentGREENHeader.getId()); + Assessment assessmentAMBER = Assessment.findById(assessmentAMBERHeader.getId()); + Assessment assessmentUNKNOWN = Assessment.findById(assessmentUNKNOWNHeader.getId()); + + // answer questions + assessmentRED.status = AssessmentStatus.COMPLETE; + assessmentRED.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.RED).findFirst().ifPresent(b -> b.selected = true))); + assessmentGREEN.status = AssessmentStatus.COMPLETE; + assessmentGREEN.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.GREEN).findFirst().ifPresent(b -> b.selected = true))); + assessmentAMBER.status = AssessmentStatus.COMPLETE; + assessmentAMBER.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.AMBER).findFirst().ifPresent(b -> b.selected = true))); + assessmentUNKNOWN.status = AssessmentStatus.COMPLETE; + assessmentUNKNOWN.assessmentQuestionnaire.categories.forEach(e -> e.questions.forEach(f -> f.singleOptions.stream().filter(a -> a.risk == Risk.UNKNOWN).findFirst().ifPresent(b -> b.selected = true))); + + // get confidence + List adoptionCandidate = assessmentSvc.getAdoptionCandidate(List.of(10008L, 10009L, 10010L, 10011L, 99999955L)); + + // assert + assertThat(adoptionCandidate).containsExactlyInAnyOrder(new AdoptionCandidateDto(assessmentREDHeader.getId(), 0), + new AdoptionCandidateDto(assessmentGREENHeader.getId(), 100), + new AdoptionCandidateDto(assessmentAMBERHeader.getId(), 25), + new AdoptionCandidateDto(assessmentUNKNOWNHeader.getId(), 70)); + } + @Transactional public Assessment createAssessment(Questionnaire questionnaire, long applicationId) { log.info("Creating an assessment "); From 1227bc4b3f7abc23a8633a8f4e78aca79b4e7673 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Fri, 4 Jun 2021 19:18:11 +0200 Subject: [PATCH 02/12] Added new version of the openapi.json --- src/main/resources/META-INF/openapi.json | 425 +++++++++++++++++++++-- 1 file changed, 400 insertions(+), 25 deletions(-) diff --git a/src/main/resources/META-INF/openapi.json b/src/main/resources/META-INF/openapi.json index 1c146804..4803b828 100644 --- a/src/main/resources/META-INF/openapi.json +++ b/src/main/resources/META-INF/openapi.json @@ -2,8 +2,8 @@ "openapi": "3.0.2", "info": { "title": "tackle-pathfinder", - "version": "0.0.3", - "description": "Tackle PathFinder 0.0.3\n\nChanges : \n* added delete assessment function\n* added create assessment copying responses from another assessment" + "version": "0.0.4", + "description": "Tackle PathFinder 0.0.4\n\nChanges : \n* added delete assessment function\n* added create assessment copying responses from another assessment\n* added bulk creation/copy of assessments\n* added identified risks function\n* added assessment confidence function" }, "paths": { "/assessments": { @@ -180,15 +180,13 @@ "id": 85, "order": 77, "option": "some text", - "checked": true, - "risk": "GREEN" + "checked": true }, { "id": 16, "order": 88, "option": "some text", - "checked": true, - "risk": "AMBER" + "checked": true } ], "description": "some text" @@ -202,15 +200,13 @@ "id": 45, "order": 36, "option": "some text", - "checked": true, - "risk": "UNKNOWN" + "checked": true }, { "id": 37, "order": 57, "option": "some text", - "checked": true, - "risk": "RED" + "checked": true } ], "description": "some text" @@ -232,15 +228,13 @@ "id": 79, "order": 3, "option": "some text", - "checked": true, - "risk": "AMBER" + "checked": true }, { "id": 7, "order": 65, "option": "some text", - "checked": false, - "risk": "AMBER" + "checked": false } ], "description": "some text" @@ -254,15 +248,13 @@ "id": 62, "order": 32, "option": "some text", - "checked": true, - "risk": "AMBER" + "checked": true }, { "id": 66, "order": 98, "option": "some text", - "checked": true, - "risk": "AMBER" + "checked": true } ], "description": "some text" @@ -438,6 +430,230 @@ "required": true } ] + }, + "/assessments/bulk": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "examples": { + "bulk_request": { + "value": [ + 74, + 54 + ] + } + } + } + }, + "required": true + }, + "parameters": [ + { + "name": "fromAssessmentId", + "description": "", + "schema": { + "type": "integer" + }, + "in": "query", + "required": false + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssessmentBulk" + }, + "examples": { + "assessment_bulk_post": { + "value": { + "bulkId": 58, + "applications": [ + 9, + 45 + ], + "fromAssessmentId": 74, + "createdAssessments": [ + { + "status": "COMPLETE", + "applicationId": 57, + "id": 50, + "error": "some text" + }, + { + "status": "STARTED", + "applicationId": 6, + "id": 13, + "error": "some text" + } + ] + } + } + } + } + }, + "description": "When the operation finishes successfully." + } + }, + "operationId": "bulkCreateAssessment", + "summary": "Async call to create a list of assessments for the given applications.", + "description": "It will allow to copy the assessment answers from a given assessment ( in query params )" + } + }, + "/assessments/bulk/{bulkId}": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssessmentBulk" + }, + "examples": { + "bulk_response_ok": { + "value": { + "bulkId": 31, + "applications": [ + 92, + 0 + ], + "fromAssessmentId": 72, + "createdAssessments": [ + { + "status": "STARTED", + "applicationId": 90, + "id": 52, + "error": "some text" + }, + { + "status": "COMPLETE", + "applicationId": 28, + "id": 18, + "error": "some text" + } + ] + } + } + } + } + }, + "description": "If bulk process is found" + }, + "404": { + "description": "If bulk process is not found" + } + } + }, + "parameters": [ + { + "name": "bulkId", + "schema": { + "type": "integer" + }, + "in": "path", + "required": true + } + ] + }, + "/assessments/risks": { + "summary": "Will retrieve the risks for filtered assessments", + "description": "The response will contain only those applications assessed. This can end on having 0 elements in the response.", + "get": { + "parameters": [ + { + "name": "applications", + "description": "", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RiskLine" + } + }, + "examples": { + "risks_ok": { + "value": [ + { + "category": "some text", + "question": "some text", + "answer": "some text", + "applications": [ + 89, + 32 + ] + }, + { + "category": "some text", + "question": "some text", + "answer": "some text", + "applications": [ + 74, + 5 + ] + } + ] + } + } + } + }, + "description": "On successfull response" + } + } + } + }, + "/assessments/confidence": { + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AssessmentConfidence" + } + } + } + }, + "description": "List of confidences for the assessments" + } + } + }, + "parameters": [ + { + "name": "applicationId", + "description": "", + "schema": { + "type": "array", + "items": { + "type": "integer" + } + }, + "in": "query", + "required": true + } + ] } }, "components": { @@ -867,21 +1083,21 @@ "type": "boolean" }, "risk": { - "description": "Risk associated to this option", + "description": "", "enum": [ "GREEN", "AMBER", - "RED", - "UNKNOWN" + "RED" ], "type": "string" } }, "example": { - "id": 1, - "order": 0, - "option": "I'm a radioButton/checkbox option", - "checked": true + "id": 77, + "order": 80, + "option": "some text", + "checked": true, + "risk": "AMBER" } }, "Pach_Assessment": { @@ -1026,6 +1242,165 @@ "COMPLETE" ], "type": "string" + }, + "Post_AssessmentBulk": { + "description": "", + "required": [ + "applications" + ], + "type": "object", + "properties": { + "fromAssessmentId": { + "description": "", + "type": "integer" + }, + "applications": { + "description": "", + "type": "array", + "items": { + "$ref": "#/components/schemas/Application" + } + } + } + }, + "AssessmentBulk": { + "description": "", + "required": [ + "bulkId", + "applications", + "assessments", + "completed" + ], + "type": "object", + "properties": { + "bulkId": { + "description": "", + "type": "integer" + }, + "applications": { + "description": "", + "type": "array", + "items": { + "type": "integer" + } + }, + "fromAssessmentId": { + "description": "", + "type": "integer" + }, + "completed": { + "description": "", + "type": "boolean" + }, + "assessments": { + "description": "", + "type": "array", + "items": { + "$ref": "#/components/schemas/AssessmentHeaderBulk" + } + } + } + }, + "AssessmentHeaderBulk": { + "description": "", + "required": [ + "id", + "applicationId", + "status" + ], + "type": "object", + "properties": { + "status": { + "$ref": "#/components/schemas/Status", + "description": "" + }, + "applicationId": { + "description": "", + "type": "integer" + }, + "id": { + "description": "", + "type": "integer" + }, + "error": { + "description": "", + "type": "string" + } + }, + "example": { + "status": "STARTED", + "id": 24, + "applicationId": 14 + } + }, + "AssessmentBulkPost": { + "description": "", + "required": [ + "applications" + ], + "type": "object", + "properties": { + "fromAssessmentId": { + "description": "", + "type": "integer" + }, + "applications": { + "description": "", + "type": "array", + "items": { + "$ref": "#/components/schemas/Application" + } + } + } + }, + "RiskLine": { + "description": "", + "required": [ + "category", + "question", + "answer", + "applications" + ], + "type": "object", + "properties": { + "category": { + "description": "", + "type": "string" + }, + "question": { + "description": "", + "type": "string" + }, + "answer": { + "description": "", + "type": "string" + }, + "applications": { + "description": "", + "type": "array", + "items": { + "type": "integer" + } + } + } + }, + "AssessmentConfidence": { + "description": "", + "required": [ + "assessmentId", + "confidence" + ], + "type": "object", + "properties": { + "assessmentId": { + "description": "", + "type": "integer" + }, + "confidence": { + "description": "", + "type": "integer" + } + } } }, "responses": { From 46e1ae7003fae91226a9e2279edaf73d6ae9d7e1 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Mon, 7 Jun 2021 09:50:15 +0200 Subject: [PATCH 03/12] Changed the verb of "confidence" endpoint to POST + body params Also changed the tests affected --- .github/scripts/check_api.sh | 10 ++++++---- .../pathfinder/controllers/AssessmentsResource.java | 7 ++++--- .../controllers/AssessmentsResourceTest.java | 3 ++- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/scripts/check_api.sh b/.github/scripts/check_api.sh index 286bc67c..9b0d6ca5 100755 --- a/.github/scripts/check_api.sh +++ b/.github/scripts/check_api.sh @@ -1,5 +1,6 @@ #!/bin/sh -set -e +set -x # print commands executed +set -e # exit immediatly if any command fails # Usage : check_api.sh api_ip keycloak_api , both arguments optional defaulted to $(minikube ip) # ./check_api.sh localhost:8085 localhost:8180 @@ -213,9 +214,10 @@ test "404" = "$req_not_existing_assessment" echo echo echo "15 >>> Checking the confidence of assessments" -confidence=$(curl -X PATCH "http://$api_ip/pathfinder/assessments/confidence?applicationId=100&applicationId=$applicationTarget" -H 'Accept: application/json' \ - -H "Authorization: Bearer $access_token" -w "%{http_code}" \ +confidence=$(curl -X POST "http://$api_ip/pathfinder/assessments/confidence" -H 'Accept: application/json' \ + -H "Authorization: Bearer $access_token" \ + -d "[{\"applicationId\":100} , {\"applicationId\": $applicationTarget}]" \ -H 'Content-Type: application/json' ) -echo $confidence | grep "\"assessmentId\": $assessmentSourceId, \"confidence\"" +echo $confidence | grep "{\"assessmentId\":$assessmentCopiedId,\"confidence\"" echo " +++++ API CHECK SUCCESSFUL ++++++" diff --git a/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java b/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java index e453c2ac..79d60c25 100644 --- a/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java +++ b/src/main/java/io/tackle/pathfinder/controllers/AssessmentsResource.java @@ -78,11 +78,12 @@ public Response deleteAssessment(@NotNull @PathParam("assessmentId") Long assess return Response.ok().status(Response.Status.NO_CONTENT).build(); } - @GET + @POST @Path("/confidence") @Produces("application/json") - public List adoptionCandidate(@QueryParam("applicationId") List applicationId) { - return service.getAdoptionCandidate(applicationId); + @Consumes("application/json") + public List adoptionCandidate(@NotNull @Valid List applicationId) { + return service.getAdoptionCandidate(applicationId.stream().map(a -> a.getApplicationId()).collect(Collectors.toList())); } } diff --git a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java index dda173b4..3cb1510a 100644 --- a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java +++ b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java @@ -772,8 +772,9 @@ public void given_ApplicationsAssessed_When_Confidence_Then_ResultIsTheExpected( given() .contentType(ContentType.JSON) .accept(ContentType.JSON) + .body(List.of(new ApplicationDto(20008L),new ApplicationDto(20009L),new ApplicationDto(20010L),new ApplicationDto(20011L))) .when() - .get("/assessments/confidence?applicationId=20008&applicationId=20009&applicationId=20010&applicationId=20011") + .post("/assessments/confidence") .then() .log().all() .statusCode(200) From fb596966fd5ce73e1a60ac4d5134dd83e51d43bf Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Mon, 7 Jun 2021 15:01:31 +0200 Subject: [PATCH 04/12] Updated the openapi.json --- src/main/resources/META-INF/openapi.json | 59 +++++++++++++++++------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/src/main/resources/META-INF/openapi.json b/src/main/resources/META-INF/openapi.json index 4803b828..9a55564d 100644 --- a/src/main/resources/META-INF/openapi.json +++ b/src/main/resources/META-INF/openapi.json @@ -623,7 +623,32 @@ } }, "/assessments/confidence": { - "get": { + "post": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Application" + } + }, + "examples": { + "post_confidence_request": { + "value": [ + { + "applicationId": 5 + }, + { + "applicationId": 51 + } + ] + } + } + } + }, + "required": true + }, "responses": { "200": { "content": { @@ -633,27 +658,27 @@ "items": { "$ref": "#/components/schemas/AssessmentConfidence" } + }, + "examples": { + "confidence_response_ok": { + "value": [ + { + "assessmentId": 48, + "confidence": 83 + }, + { + "assessmentId": 80, + "confidence": 10 + } + ] + } } } }, - "description": "List of confidences for the assessments" + "description": "On success" } } - }, - "parameters": [ - { - "name": "applicationId", - "description": "", - "schema": { - "type": "array", - "items": { - "type": "integer" - } - }, - "in": "query", - "required": true - } - ] + } } }, "components": { From 9072b684a91158c45f07b14f21ec134dd32b5f44 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Mon, 7 Jun 2021 15:10:46 +0200 Subject: [PATCH 05/12] Added comment to make clear the formula seems wrong --- src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 766a0391..4dcc1e2e 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -362,6 +362,7 @@ private Integer calculateConfidence(Assessment assessment) { .forEach(b -> updateAdjuster(adjusterBase, adjuster, b)); // Temp confidence iteration calculation + // TODO Apparently this formula seems wrong, as the first execution in the forEach is multiplying by 0 AtomicDouble confidence = new AtomicDouble(0.0); assessment.assessmentQuestionnaire.categories.stream() .flatMap(cat -> cat.questions.stream()) From 4d06ad65ef6be67199ed000bea73f9d51cd1aea5 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Mon, 14 Jun 2021 11:44:30 +0200 Subject: [PATCH 06/12] Added sorting by Risk on old pathfinder formula Added new formula with only 1 distribution adjustment --- .../pathfinder/services/AssessmentSvc.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 4dcc1e2e..4bd9f7f6 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -32,12 +32,10 @@ import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; +import java.util.stream.Stream; @ApplicationScoped @Log @@ -344,16 +342,26 @@ private Integer calculateConfidence(Assessment assessment) { Risk.UNKNOWN, unknownWeight, Risk.AMBER, amberWeight, Risk.GREEN, greenWeight); - Map confidenceMultiplier = Map.of(Risk.RED, redMultiplier, Risk.AMBER, amberMultiplier); - Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); - Map answersCountByRisk = assessment.assessmentQuestionnaire.categories.stream() + Stream answeredOptions = assessment.assessmentQuestionnaire.categories.stream() .flatMap(cat -> cat.questions.stream()) .flatMap(que -> que.singleOptions.stream()) - .filter(opt -> opt.selected) + .filter(opt -> opt.selected); + long totalAnswered = answeredOptions.count(); + + // Grouping to know how many answers per Risk + Map answersCountByRisk = answeredOptions .collect(Collectors.groupingBy(a -> a.risk, Collectors.counting())); - long totalAnswered = answersCountByRisk.values().stream().mapToLong(Long::longValue).sum(); + + BigDecimal result = getConfidenceOldPathfinder(weightMap, answeredOptions, totalAnswered, answersCountByRisk); + + return result.intValue(); + } + + private BigDecimal getConfidenceOldPathfinder(Map weightMap, Stream answeredOptions, long totalAnswered, Map answersCountByRisk) { + Map confidenceMultiplier = Map.of(Risk.RED, redMultiplier, Risk.AMBER, amberMultiplier); + Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); // Adjuster calculation AtomicDouble adjuster = new AtomicDouble(1); @@ -364,10 +372,9 @@ private Integer calculateConfidence(Assessment assessment) { // Temp confidence iteration calculation // TODO Apparently this formula seems wrong, as the first execution in the forEach is multiplying by 0 AtomicDouble confidence = new AtomicDouble(0.0); - assessment.assessmentQuestionnaire.categories.stream() - .flatMap(cat -> cat.questions.stream()) - .flatMap(que -> que.singleOptions.stream()) - .filter(opt -> opt.selected) + + answeredOptions + .sorted(Comparator.comparing(a -> weightMap.get(a.risk))) // sorting by weight to put REDs first .forEach(opt -> { confidence.set(confidence.get() * confidenceMultiplier.getOrDefault(opt.risk, 1.0)); confidence.getAndAdd(weightMap.get(opt.risk) * adjuster.get()); @@ -377,8 +384,19 @@ private Integer calculateConfidence(Assessment assessment) { BigDecimal result = new BigDecimal((confidence.get() / maxConfidence) * 100); result.setScale(0, RoundingMode.DOWN); + return result; + } - return result.intValue(); + private BigDecimal getConfidenceTacklePathfinder(Map weightMap, Stream answeredOptions, long totalAnswered, Map answersCountByRisk) { + Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); + + double answeredWeight = answeredOptions.mapToDouble(a -> weightMap.get(a.risk) * adjusterBase.getOrDefault(a.risk, 1d)).sum(); + + long maxWeight = weightMap.get(Risk.GREEN) * totalAnswered; + + BigDecimal result = new BigDecimal(answeredWeight / maxWeight * 100); + result.setScale(0, RoundingMode.DOWN); + return result; } // if (redCount > 0) adjuster = adjuster * Math.pow(0.5, redCount); From 0d5737b79167747d69b1902fe4c1c7e2ae30c5cf Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 11:55:38 +0200 Subject: [PATCH 07/12] Added applicationId to the output of the Adoption Plan resource --- .../java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java | 1 + src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java b/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java index 550ea075..87dd830b 100644 --- a/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java +++ b/src/main/java/io/tackle/pathfinder/dto/AdoptionCandidateDto.java @@ -14,6 +14,7 @@ @NoArgsConstructor @RegisterForReflection public class AdoptionCandidateDto { + public Long applicationId; public Long assessmentId; public Integer confidence; } diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 4bd9f7f6..1a740960 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -333,7 +333,7 @@ public List getAdoptionCandidate(List applicationId) return applicationId.stream() .map(a-> Assessment.find("applicationId", a).firstResultOptional()) .filter(b -> b.isPresent()) - .map(c -> new AdoptionCandidateDto(((Assessment) c.get()).id, calculateConfidence((Assessment) c.get()))) + .map(c -> new AdoptionCandidateDto(((Assessment) c.get()).applicationId, ((Assessment) c.get()).id, calculateConfidence((Assessment) c.get()))) .collect(Collectors.toList()); } From 4ad60f71c3a0e5be925083148300b556b56733be Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 12:06:55 +0200 Subject: [PATCH 08/12] Fixed issues with refactoring --- .../tackle/pathfinder/services/AssessmentSvc.java | 13 +++++++------ .../pathfinder/services/AssessmentSvcTest.java | 8 ++++---- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 1a740960..c3e3416a 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -343,14 +343,15 @@ private Integer calculateConfidence(Assessment assessment) { Risk.AMBER, amberWeight, Risk.GREEN, greenWeight); - Stream answeredOptions = assessment.assessmentQuestionnaire.categories.stream() + List answeredOptions = assessment.assessmentQuestionnaire.categories.stream() .flatMap(cat -> cat.questions.stream()) .flatMap(que -> que.singleOptions.stream()) - .filter(opt -> opt.selected); - long totalAnswered = answeredOptions.count(); + .filter(opt -> opt.selected) + .collect(Collectors.toList()); + long totalAnswered = answeredOptions.stream().count(); // Grouping to know how many answers per Risk - Map answersCountByRisk = answeredOptions + Map answersCountByRisk = answeredOptions.stream() .collect(Collectors.groupingBy(a -> a.risk, Collectors.counting())); @@ -359,7 +360,7 @@ private Integer calculateConfidence(Assessment assessment) { return result.intValue(); } - private BigDecimal getConfidenceOldPathfinder(Map weightMap, Stream answeredOptions, long totalAnswered, Map answersCountByRisk) { + private BigDecimal getConfidenceOldPathfinder(Map weightMap, List answeredOptions, long totalAnswered, Map answersCountByRisk) { Map confidenceMultiplier = Map.of(Risk.RED, redMultiplier, Risk.AMBER, amberMultiplier); Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); @@ -373,7 +374,7 @@ private BigDecimal getConfidenceOldPathfinder(Map weightMap, Stre // TODO Apparently this formula seems wrong, as the first execution in the forEach is multiplying by 0 AtomicDouble confidence = new AtomicDouble(0.0); - answeredOptions + answeredOptions.stream() .sorted(Comparator.comparing(a -> weightMap.get(a.risk))) // sorting by weight to put REDs first .forEach(opt -> { confidence.set(confidence.get() * confidenceMultiplier.getOrDefault(opt.risk, 1.0)); diff --git a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java index 4578ed46..9b59dfe2 100644 --- a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java +++ b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java @@ -392,10 +392,10 @@ public void given_ApplicationsAssessed_when_AdoptionCandidate_then_ResuletIsTheE List adoptionCandidate = assessmentSvc.getAdoptionCandidate(List.of(10008L, 10009L, 10010L, 10011L, 99999955L)); // assert - assertThat(adoptionCandidate).containsExactlyInAnyOrder(new AdoptionCandidateDto(assessmentREDHeader.getId(), 0), - new AdoptionCandidateDto(assessmentGREENHeader.getId(), 100), - new AdoptionCandidateDto(assessmentAMBERHeader.getId(), 25), - new AdoptionCandidateDto(assessmentUNKNOWNHeader.getId(), 70)); + assertThat(adoptionCandidate).containsExactlyInAnyOrder(new AdoptionCandidateDto(assessmentREDHeader.getApplicationId(), assessmentREDHeader.getId(), 0), + new AdoptionCandidateDto(assessmentGREENHeader.getApplicationId(), assessmentGREENHeader.getId(), 100), + new AdoptionCandidateDto(assessmentAMBERHeader.getApplicationId(), assessmentAMBERHeader.getId(), 25), + new AdoptionCandidateDto(assessmentUNKNOWNHeader.getApplicationId(), assessmentUNKNOWNHeader.getId(), 70)); } @Transactional From 67ba86517416794e8aa05de607bd50e3e8640e88 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 13:27:38 +0200 Subject: [PATCH 09/12] Merged main and updated openapi.json --- .../pathfinder/services/AssessmentSvc.java | 7 +- src/main/resources/META-INF/openapi.json | 316 +----------------- 2 files changed, 8 insertions(+), 315 deletions(-) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 853d5358..b31b55da 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -1,11 +1,7 @@ package io.tackle.pathfinder.services; import com.google.common.util.concurrent.AtomicDouble; -import io.quarkus.arc.config.ConfigProperties; -import io.tackle.pathfinder.dto.AdoptionCandidateDto; -import io.tackle.pathfinder.dto.AssessmentDto; -import io.tackle.pathfinder.dto.AssessmentHeaderDto; -import io.tackle.pathfinder.dto.AssessmentStatus; +import io.tackle.pathfinder.dto.*; import io.tackle.pathfinder.mapper.AssessmentMapper; import io.tackle.pathfinder.model.Risk; import io.tackle.pathfinder.model.assessment.Assessment; @@ -39,7 +35,6 @@ import java.util.function.Function; import java.math.BigDecimal; import java.math.RoundingMode; -import java.util.*; import java.util.logging.Level; import java.util.stream.Collectors; import java.util.stream.Stream; diff --git a/src/main/resources/META-INF/openapi.json b/src/main/resources/META-INF/openapi.json index f31f8226..9bbec08d 100644 --- a/src/main/resources/META-INF/openapi.json +++ b/src/main/resources/META-INF/openapi.json @@ -563,313 +563,6 @@ } ] }, - "/assessments/risks": { - "summary": "Will retrieve the risks for filtered assessments", - "description": "The response will contain only those applications assessed. This can end on having 0 elements in the response.", - "get": { - "parameters": [ - { - "name": "applications", - "description": "", - "schema": { - "type": "array", - "items": { - "type": "integer" - } - }, - "in": "query", - "required": true - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RiskLine" - } - }, - "examples": { - "risks_ok": { - "value": [ - { - "category": "some text", - "question": "some text", - "answer": "some text", - "applications": [ - 89, - 32 - ] - }, - { - "category": "some text", - "question": "some text", - "answer": "some text", - "applications": [ - 74, - 5 - ] - } - ] - } - } - } - }, - "description": "On successfull response" - } - } - } - }, - "/assessments/confidence": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Application" - } - }, - "examples": { - "post_confidence_request": { - "value": [ - { - "applicationId": 5 - }, - { - "applicationId": 51 - } - ] - } - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AssessmentConfidence" - } - }, - "examples": { - "confidence_response_ok": { - "value": [ - { - "assessmentId": 48, - "confidence": 83 - }, - { - "assessmentId": 80, - "confidence": 10 - } - ] - } - } - } - }, - "description": "On success" - } - } - } - }, - "/assessments/assessment-risk": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Application" - } - }, - "examples": { - "assessment-risk-ok": { - "value": [ - { - "applicationId": 5 - }, - { - "applicationId": 37 - } - ] - } - } - } - }, - "required": true - }, - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/AssessmentRisk" - } - }, - "examples": { - "assessment-risk-ok-resp": { - "value": [ - { - "assessmentId": 34, - "risk": "RED" - }, - { - "assessmentId": 69, - "risk": "UNKNOWN" - } - ] - } - } - } - }, - "description": "Success response" - } - } - } - }, - "/assessments/bulk": { - "post": { - "requestBody": { - "content": { - "application/json": { - "schema": { - "type": "array", - "items": { - "type": "integer" - } - }, - "examples": { - "bulk_request": { - "value": [ - 74, - 54 - ] - } - } - } - }, - "required": true - }, - "parameters": [ - { - "name": "fromAssessmentId", - "description": "", - "schema": { - "type": "integer" - }, - "in": "query", - "required": false - } - ], - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AssessmentBulk" - }, - "examples": { - "assessment_bulk_post": { - "value": { - "bulkId": 58, - "applications": [ - 9, - 45 - ], - "fromAssessmentId": 74, - "createdAssessments": [ - { - "status": "COMPLETE", - "applicationId": 57, - "id": 50, - "error": "some text" - }, - { - "status": "STARTED", - "applicationId": 6, - "id": 13, - "error": "some text" - } - ] - } - } - } - } - }, - "description": "When the operation finishes successfully." - } - }, - "operationId": "bulkCreateAssessment", - "summary": "Async call to create a list of assessments for the given applications.", - "description": "It will allow to copy the assessment answers from a given assessment ( in query params )" - } - }, - "/assessments/bulk/{bulkId}": { - "get": { - "responses": { - "200": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/AssessmentBulk" - }, - "examples": { - "bulk_response_ok": { - "value": { - "bulkId": 31, - "applications": [ - 92, - 0 - ], - "fromAssessmentId": 72, - "createdAssessments": [ - { - "status": "STARTED", - "applicationId": 90, - "id": 52, - "error": "some text" - }, - { - "status": "COMPLETE", - "applicationId": 28, - "id": 18, - "error": "some text" - } - ] - } - } - } - } - }, - "description": "If bulk process is found" - }, - "404": { - "description": "If bulk process is not found" - } - } - }, - "parameters": [ - { - "name": "bulkId", - "schema": { - "type": "integer" - }, - "in": "path", - "required": true - } - ] - }, "/assessments/risks": { "summary": "Will retrieve the risks for filtered assessments", "description": "The response will contain only those applications assessed. This can end on having 0 elements in the response.", @@ -1484,7 +1177,7 @@ "type": "boolean" }, "risk": { - "description": "Risk associated to this option", + "description": "", "enum": [ "GREEN", "AMBER", @@ -1789,7 +1482,8 @@ "description": "", "required": [ "assessmentId", - "confidence" + "confidence", + "applicationId" ], "type": "object", "properties": { @@ -1800,6 +1494,10 @@ "confidence": { "description": "", "type": "integer" + }, + "applicationId": { + "description": "", + "type": "integer" } } }, From b242be9dacdc3dff4d24342a928b554c6c6040fa Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 14:36:21 +0200 Subject: [PATCH 10/12] Fix test on `check_api.sh` according to the confidence --- .github/scripts/check_api.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/scripts/check_api.sh b/.github/scripts/check_api.sh index 3e932b3a..37cbdbab 100755 --- a/.github/scripts/check_api.sh +++ b/.github/scripts/check_api.sh @@ -275,6 +275,9 @@ confidence=$(curl -X POST "http://$api_ip/pathfinder/assessments/confidence" -H -H "Authorization: Bearer $access_token" \ -d "[{\"applicationId\":100} , {\"applicationId\": $applicationTarget}]" \ -H 'Content-Type: application/json' ) -echo $confidence | grep "{\"assessmentId\":$assessmentCopiedId,\"confidence\"" +echo $confidence | grep "\"assessmentId\":$assessmentCopiedId" +echo $confidence | grep "\"confidence\":" +echo $confidence | grep "\"applicationId\":$applicationTarget" + echo " +++++ API CHECK SUCCESSFUL ++++++" From 3c37913617cdd27f1730eb99906dd243e93368ac Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 14:42:52 +0200 Subject: [PATCH 11/12] Cleaning the "new" formula for confidence and move it to another PR --- .../io/tackle/pathfinder/services/AssessmentSvc.java | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index b31b55da..2a2992bf 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -445,17 +445,6 @@ private BigDecimal getConfidenceOldPathfinder(Map weightMap, List return result; } - private BigDecimal getConfidenceTacklePathfinder(Map weightMap, Stream answeredOptions, long totalAnswered, Map answersCountByRisk) { - Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); - - double answeredWeight = answeredOptions.mapToDouble(a -> weightMap.get(a.risk) * adjusterBase.getOrDefault(a.risk, 1d)).sum(); - - long maxWeight = weightMap.get(Risk.GREEN) * totalAnswered; - - BigDecimal result = new BigDecimal(answeredWeight / maxWeight * 100); - result.setScale(0, RoundingMode.DOWN); - return result; - } // if (redCount > 0) adjuster = adjuster * Math.pow(0.5, redCount); // if (amberCount > 0) adjuster = adjuster * Math.pow(0.98, amberCount); From 53f03a2a5cd445b7a4a338df31738b1ceababf60 Mon Sep 17 00:00:00 2001 From: Jonathan Vila Date: Tue, 15 Jun 2021 17:19:38 +0200 Subject: [PATCH 12/12] Added new formula to calculate the Confidence Adapted the test with the new value for AMBERs --- .../pathfinder/services/AssessmentSvc.java | 33 +++---------------- .../controllers/AssessmentsResourceTest.java | 2 +- .../services/AssessmentSvcTest.java | 2 +- 3 files changed, 7 insertions(+), 30 deletions(-) diff --git a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java index 2a2992bf..324cc008 100644 --- a/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java +++ b/src/main/java/io/tackle/pathfinder/services/AssessmentSvc.java @@ -412,46 +412,23 @@ private Integer calculateConfidence(Assessment assessment) { .collect(Collectors.groupingBy(a -> a.risk, Collectors.counting())); - BigDecimal result = getConfidenceOldPathfinder(weightMap, answeredOptions, totalAnswered, answersCountByRisk); + BigDecimal result = getConfidenceTacklePathfinder(weightMap, answeredOptions, totalAnswered, answersCountByRisk); return result.intValue(); } - private BigDecimal getConfidenceOldPathfinder(Map weightMap, List answeredOptions, long totalAnswered, Map answersCountByRisk) { - Map confidenceMultiplier = Map.of(Risk.RED, redMultiplier, Risk.AMBER, amberMultiplier); + private BigDecimal getConfidenceTacklePathfinder(Map weightMap, List answeredOptions, long totalAnswered, Map answersCountByRisk) { Map adjusterBase = Map.of(Risk.RED, redAdjuster, Risk.AMBER, amberAdjuster, Risk.GREEN, greenAdjuster, Risk.UNKNOWN, unknownAdjuster); - // Adjuster calculation - AtomicDouble adjuster = new AtomicDouble(1); - answersCountByRisk.entrySet().stream() - .filter(a -> a.getValue() > 0 ) - .forEach(b -> updateAdjuster(adjusterBase, adjuster, b)); - - // Temp confidence iteration calculation - // TODO Apparently this formula seems wrong, as the first execution in the forEach is multiplying by 0 - AtomicDouble confidence = new AtomicDouble(0.0); - - answeredOptions.stream() - .sorted(Comparator.comparing(a -> weightMap.get(a.risk))) // sorting by weight to put REDs first - .forEach(opt -> { - confidence.set(confidence.get() * confidenceMultiplier.getOrDefault(opt.risk, 1.0)); - confidence.getAndAdd(weightMap.get(opt.risk) * adjuster.get()); - }); + double answeredWeight = answeredOptions.stream().mapToDouble(a -> weightMap.get(a.risk) * adjusterBase.getOrDefault(a.risk, 1d)).sum(); - double maxConfidence = weightMap.get(Risk.GREEN) * totalAnswered; + long maxWeight = weightMap.get(Risk.GREEN) * totalAnswered; - BigDecimal result = new BigDecimal((confidence.get() / maxConfidence) * 100); + BigDecimal result = new BigDecimal(answeredWeight / maxWeight * 100); result.setScale(0, RoundingMode.DOWN); return result; } - - // if (redCount > 0) adjuster = adjuster * Math.pow(0.5, redCount); - // if (amberCount > 0) adjuster = adjuster * Math.pow(0.98, amberCount); - private void updateAdjuster(Map adjusterBase, AtomicDouble adjuster, Map.Entry b) { - adjuster.set(adjuster.get() * Math.pow(adjusterBase.get(b.getKey()), b.getValue())); - } - private AssessmentRiskDto sqlRowToAssessmentRisk(Object row) { return new AssessmentRiskDto((Integer) ((Object[]) row)[0], (String) ((Object[]) row)[1], (Integer) ((Object[]) row)[2]); } diff --git a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java index 8419c088..4da4b455 100644 --- a/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java +++ b/src/test/java/io/tackle/pathfinder/controllers/AssessmentsResourceTest.java @@ -962,7 +962,7 @@ public void given_ApplicationsAssessed_When_Confidence_Then_ResultIsTheExpected( .statusCode(200) .body("find{it.assessmentId=="+ assessmentRED.id + "}.confidence", is(0)) .body("find{it.assessmentId=="+ assessmentGREEN.id + "}.confidence", is(100)) - .body("find{it.assessmentId=="+ assessmentAMBER.id + "}.confidence", is(25)) + .body("find{it.assessmentId=="+ assessmentAMBER.id + "}.confidence", is(78)) // vs old pathfinder formula : 25 .body("find{it.assessmentId=="+ assessmentUNKNOWN.id + "}.confidence", is(70)); } diff --git a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java index 74183511..1d01e496 100644 --- a/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java +++ b/src/test/java/io/tackle/pathfinder/services/AssessmentSvcTest.java @@ -485,7 +485,7 @@ public void given_ApplicationsAssessed_when_AdoptionCandidate_then_ResuletIsTheE // assert assertThat(adoptionCandidate).containsExactlyInAnyOrder(new AdoptionCandidateDto(assessmentREDHeader.getApplicationId(), assessmentREDHeader.getId(), 0), new AdoptionCandidateDto(assessmentGREENHeader.getApplicationId(), assessmentGREENHeader.getId(), 100), - new AdoptionCandidateDto(assessmentAMBERHeader.getApplicationId(), assessmentAMBERHeader.getId(), 25), + new AdoptionCandidateDto(assessmentAMBERHeader.getApplicationId(), assessmentAMBERHeader.getId(), 78), // vs 25 in old pathfinder new AdoptionCandidateDto(assessmentUNKNOWNHeader.getApplicationId(), assessmentUNKNOWNHeader.getId(), 70)); }