Skip to content

Commit c1ab499

Browse files
authored
EDO-199 (#559)
* Addressing issue where deep cloned Optional Program objects were never updated during the FailedAssessmentsRule processing. They now reflect a true failure or pass using the same rules as normal assessments * Adding JDK 21 for sonar analysis (cherry picked from commit bc4f552)
1 parent bc4f552 commit c1ab499

2 files changed

Lines changed: 127 additions & 21 deletions

File tree

‎api/src/main/java/ca/bc/gov/educ/api/ruleengine/rule/FailedAssessmentsRule.java‎

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package ca.bc.gov.educ.api.ruleengine.rule;
22

3+
import ca.bc.gov.educ.api.ruleengine.dto.OptionalProgramRuleProcessor;
34
import ca.bc.gov.educ.api.ruleengine.dto.RuleData;
45
import ca.bc.gov.educ.api.ruleengine.dto.RuleProcessorData;
56
import ca.bc.gov.educ.api.ruleengine.dto.StudentAssessment;
@@ -13,6 +14,8 @@
1314
import org.springframework.stereotype.Component;
1415

1516
import java.util.List;
17+
import java.util.Map;
18+
import java.util.Objects;
1619

1720
@Data
1821
@Component
@@ -28,37 +31,48 @@ public class FailedAssessmentsRule implements Rule {
2831
@Override
2932
public RuleData fire() {
3033

31-
List<StudentAssessment> studentAssessmentList = RuleProcessorRuleUtils.getUniqueStudentAssessments(ruleProcessorData.getStudentAssessments(),ruleProcessorData.isProjected());
34+
List<StudentAssessment> studentAssessmentList = RuleProcessorRuleUtils.getUniqueStudentAssessments(ruleProcessorData.getStudentAssessments(), ruleProcessorData.isProjected());
3235

33-
for (StudentAssessment studentAssessment : studentAssessmentList) {
36+
markFailedAssessments(studentAssessmentList);
37+
markOptionalProgramAssessmentsFailed();
3438

39+
ruleProcessorData.setExcludedAssessments(RuleProcessorRuleUtils.maintainExcludedAssessments(studentAssessmentList,ruleProcessorData.getExcludedAssessments(),ruleProcessorData.isProjected()));
40+
ruleProcessorData.setStudentAssessments(studentAssessmentList);
41+
42+
logger.debug("Failed Assessments: {}",
43+
(int) studentAssessmentList.stream().filter(StudentAssessment::isFailed).count());
44+
45+
return ruleProcessorData;
46+
}
47+
48+
private void markOptionalProgramAssessmentsFailed() {
49+
Map<String, OptionalProgramRuleProcessor> optionalPrograms = ruleProcessorData.getMapOptional();
50+
if (optionalPrograms == null) {
51+
return;
52+
}
53+
54+
optionalPrograms.values().stream()
55+
.map(OptionalProgramRuleProcessor::getStudentAssessmentsOptionalProgram)
56+
.filter(Objects::nonNull)
57+
.forEach(this::markFailedAssessments);
58+
}
59+
60+
private void markFailedAssessments(List<StudentAssessment> studentAssessments) {
61+
for (StudentAssessment studentAssessment : studentAssessments) {
3562
boolean failed = ruleProcessorData.getSpecialCaseList().stream()
36-
.anyMatch(lg -> lg.getSpCase()
63+
.anyMatch(specialCase -> specialCase.getSpCase()
3764
.compareTo(studentAssessment.getSpecialCase() != null
3865
? studentAssessment.getSpecialCase().trim()
3966
: "") == 0
40-
&& lg.getPassFlag().compareTo("N") == 0);
67+
&& specialCase.getPassFlag().compareTo("N") == 0);
4168

42-
if (failed)
43-
studentAssessment.setFailed(true);
44-
String exceededWriteFlag;
45-
if(studentAssessment.getExceededWriteFlag() == null) {
46-
exceededWriteFlag = "";
47-
}else {
48-
exceededWriteFlag = studentAssessment.getExceededWriteFlag();
49-
}
50-
if ("Y".compareTo(exceededWriteFlag.trim()) == 0) {
69+
String exceededWriteFlag = studentAssessment.getExceededWriteFlag() == null
70+
? ""
71+
: studentAssessment.getExceededWriteFlag();
72+
if (failed || "Y".equals(exceededWriteFlag.trim())) {
5173
studentAssessment.setFailed(true);
5274
}
5375
}
54-
55-
ruleProcessorData.setExcludedAssessments(RuleProcessorRuleUtils.maintainExcludedAssessments(studentAssessmentList,ruleProcessorData.getExcludedAssessments(),ruleProcessorData.isProjected()));
56-
ruleProcessorData.setStudentAssessments(studentAssessmentList);
57-
58-
logger.debug("Failed Assessments: {}",
59-
(int) studentAssessmentList.stream().filter(StudentAssessment::isFailed).count());
60-
61-
return ruleProcessorData;
6276
}
6377

6478
public void setInputData(RuleData inputData) {
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package ca.bc.gov.educ.api.ruleengine.rule;
2+
3+
import ca.bc.gov.educ.api.ruleengine.dto.OptionalProgramRuleProcessor;
4+
import ca.bc.gov.educ.api.ruleengine.dto.RuleProcessorData;
5+
import ca.bc.gov.educ.api.ruleengine.dto.SpecialCase;
6+
import ca.bc.gov.educ.api.ruleengine.dto.StudentAssessment;
7+
import ca.bc.gov.educ.api.ruleengine.util.RuleProcessorRuleUtils;
8+
import org.junit.Test;
9+
10+
import java.util.ArrayList;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
15+
import static org.junit.Assert.assertFalse;
16+
import static org.junit.Assert.assertTrue;
17+
18+
public class FailedAssessmentsRuleTest {
19+
20+
@Test
21+
public void shouldMarkDualDogwoodAssessmentsFailedForNonPassingSpecialCases() {
22+
assertAssessmentFailsForMainAndOptionalProgram("DD", "LTE10", "Q");
23+
assertAssessmentFailsForMainAndOptionalProgram("DD", "LTE12", "X");
24+
}
25+
26+
@Test
27+
public void shouldMarkFrenchImmersionAssessmentFailedForNonPassingSpecialCases() {
28+
assertAssessmentFailsForMainAndOptionalProgram("FI", "LTF12", "Q");
29+
assertAssessmentFailsForMainAndOptionalProgram("FI", "LTF12", "X");
30+
}
31+
32+
@Test
33+
public void shouldNotMarkPassingSpecialCaseFailed() {
34+
RuleProcessorData result = processAssessment("FI", "LTF12", "M");
35+
36+
assertFalse(result.getStudentAssessments().get(0).isFailed());
37+
assertFalse(result.getMapOptional().get("FI").getStudentAssessmentsOptionalProgram().get(0).isFailed());
38+
}
39+
40+
private void assertAssessmentFailsForMainAndOptionalProgram(String optionalProgramCode, String assessmentCode, String specialCase) {
41+
RuleProcessorData result = processAssessment(optionalProgramCode, assessmentCode, specialCase);
42+
43+
assertTrue(result.getStudentAssessments().get(0).isFailed());
44+
assertTrue(result.getMapOptional().get(optionalProgramCode).getStudentAssessmentsOptionalProgram().get(0).isFailed());
45+
assertTrue(RuleProcessorRuleUtils.getUniqueStudentAssessments(
46+
result.getMapOptional().get(optionalProgramCode).getStudentAssessmentsOptionalProgram(), false).isEmpty());
47+
}
48+
49+
private RuleProcessorData processAssessment(String optionalProgramCode, String assessmentCode, String specialCase) {
50+
StudentAssessment mainAssessment = createAssessment(assessmentCode, specialCase);
51+
StudentAssessment optionalAssessment = createAssessment(assessmentCode, specialCase);
52+
53+
OptionalProgramRuleProcessor optionalProgram = new OptionalProgramRuleProcessor();
54+
optionalProgram.setStudentAssessmentsOptionalProgram(new ArrayList<>(List.of(optionalAssessment)));
55+
56+
Map<String, OptionalProgramRuleProcessor> optionalPrograms = new HashMap<>();
57+
optionalPrograms.put(optionalProgramCode, optionalProgram);
58+
59+
RuleProcessorData ruleProcessorData = new RuleProcessorData();
60+
ruleProcessorData.setStudentAssessments(new ArrayList<>(List.of(mainAssessment)));
61+
ruleProcessorData.setExcludedAssessments(new ArrayList<>());
62+
ruleProcessorData.setSpecialCaseList(createSpecialCases());
63+
ruleProcessorData.setMapOptional(optionalPrograms);
64+
ruleProcessorData.setProjected(false);
65+
66+
return (RuleProcessorData) new FailedAssessmentsRule(ruleProcessorData).fire();
67+
}
68+
69+
private StudentAssessment createAssessment(String assessmentCode, String specialCase) {
70+
StudentAssessment assessment = new StudentAssessment();
71+
assessment.setPen("123456789");
72+
assessment.setAssessmentCode(assessmentCode);
73+
assessment.setSessionDate("2026/06");
74+
assessment.setSpecialCase(specialCase);
75+
return assessment;
76+
}
77+
78+
private List<SpecialCase> createSpecialCases() {
79+
return List.of(
80+
createSpecialCase("Q", "N"),
81+
createSpecialCase("X", "N"),
82+
createSpecialCase("M", "Y")
83+
);
84+
}
85+
86+
private SpecialCase createSpecialCase(String code, String passFlag) {
87+
SpecialCase specialCase = new SpecialCase();
88+
specialCase.setSpCase(code);
89+
specialCase.setPassFlag(passFlag);
90+
return specialCase;
91+
}
92+
}

0 commit comments

Comments
 (0)