Skip to content

Commit

Permalink
[Bug] Fix mistake in how status is computed for final assessment step (
Browse files Browse the repository at this point in the history
…#12539)

* Refine computeAssessmentStatus so any disqualified step always disqualifies overallAssessmentStatus

* Add regression test

* Seed more comprehensive assessments, and skip random assessment seeder which wasn't helpful

* Simplify and fix new test

* Make SyncAssessmentStatus cmd dispatch CandidateStatusChanged
just like ComputeCandidateAssessmentStatus listener

(cherry picked from commit 95cdea5)
  • Loading branch information
tristan-orourke authored and brindasasi committed Jan 22, 2025
1 parent c829173 commit 1548d53
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 112 deletions.
4 changes: 4 additions & 0 deletions api/app/Console/Commands/SyncAssessmentStatus.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Console\Commands;

use App\Events\CandidateStatusChanged;
use App\Models\PoolCandidate;
use Illuminate\Console\Command;
use Illuminate\Database\Eloquent\Collection;
Expand Down Expand Up @@ -34,6 +35,9 @@ public function handle()
$candidate->computed_assessment_status = $assessmentStatus;

$candidate->save();

// If assessment status changes, "final decision" may as well.
CandidateStatusChanged::dispatch($candidate);
}
});
}
Expand Down
21 changes: 12 additions & 9 deletions api/app/Models/PoolCandidate.php
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,11 @@ public function setApplicationSnapshot(bool $save = true)
* and if step is Application Assessment then repeat the Essential switch statement education assessment result
* stepStatus is first of UNSUCCESSFUL, TO ASSESS, HOLD, and else QUALIFIED
* no decision for steps that are TO ASSESS but have no results so we can tell when they've been started
*
* overallAssessmentStatus is then:
* if any step is UNSUCCESSFUL, then DISQUALIFIED
* else if all steps are fully assessed, and final step is not HOLD, then QUALIFIED
* else TO ASSESS
*/
public function computeAssessmentStatus()
{
Expand Down Expand Up @@ -1105,24 +1110,22 @@ public function computeAssessmentStatus()
$totalSteps = $this->pool->assessmentSteps->count();
$overallAssessmentStatus = OverallAssessmentStatus::TO_ASSESS->name;

if ($currentStep >= $totalSteps && $totalSteps === count($decisions)) {
$unsuccessfulDecisions = Arr::where($decisions, function ($stepDecision) {
return $stepDecision['decision'] === AssessmentDecision::UNSUCCESSFUL->name;
});
if (! empty($unsuccessfulDecisions)) {
$overallAssessmentStatus = OverallAssessmentStatus::DISQUALIFIED->name;
} elseif ($currentStep >= $totalSteps && $totalSteps === count($decisions)) {
$lastStepDecision = end($decisions);
if ($lastStepDecision && $lastStepDecision['decision'] !== AssessmentDecision::HOLD->name && ! is_null($lastStepDecision['decision'])) {
$overallAssessmentStatus = OverallAssessmentStatus::QUALIFIED->name;
$currentStep = null;
}
} else {
$unsuccessfulDecisions = Arr::where($decisions, function ($stepDecision) {
return $stepDecision['decision'] === AssessmentDecision::UNSUCCESSFUL->name;
});
if (! empty($unsuccessfulDecisions)) {
$overallAssessmentStatus = OverallAssessmentStatus::DISQUALIFIED->name;
}
}

// While unlikely, current step could go over.
// So, set it back to total steps
if ($currentStep > $totalSteps) {
if ($currentStep && $currentStep > $totalSteps) {
$currentStep = $totalSteps;
}

Expand Down
219 changes: 117 additions & 102 deletions api/database/seeders/AssessmentResultTestSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Enums\AssessmentDecisionLevel;
use App\Enums\AssessmentResultJustification;
use App\Enums\AssessmentResultType;
use App\Enums\AssessmentStepType;
use App\Enums\PoolSkillType;
use App\Models\AssessmentResult;
use App\Models\AssessmentStep;
Expand Down Expand Up @@ -74,107 +75,116 @@ public function run()
]);

$publishedPool = Pool::select('id')->where('name->en', 'Published – Complex')->sole();
$user1 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate1 = PoolCandidate::select('id')->where('user_id', $user1->id)->where('pool_id', $publishedPool->id)->sole();
$assessmentStep = AssessmentStep::factory()->create([
'pool_id' => $publishedPool->id,
]);
$this->assessSkillsWithLevelAndJustification(
$poolCandidate1,
$publishedPool->poolSkills()->pluck('id')->toArray(),
$assessmentStep,
AssessmentDecisionLevel::ABOVE_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_ACCEPTED_INFORMATION->name],
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::EDUCATION);

$user2 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate2 = PoolCandidate::select('id')->where('user_id', $user2->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate2,
$publishedPool->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->pluck('id')->toArray(),
$assessmentStep,
AssessmentDecisionLevel::AT_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_ACCEPTED_WORK_EXPERIENCE_EQUIVALENCY->name],
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::SKILL);

$user3 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate3 = PoolCandidate::select('id')->where('user_id', $user3->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate3,
$publishedPool->poolSkills()->pluck('id')->toArray(),
$assessmentStep,
AssessmentDecisionLevel::ABOVE_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_ACCEPTED_WORK_EXPERIENCE_EQUIVALENCY->name],
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::SKILL);

$user4 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate4 = PoolCandidate::select('id')->where('user_id', $user4->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate4,
$publishedPool->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->pluck('id')->toArray(),
$assessmentStep, AssessmentDecisionLevel::AT_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_ACCEPTED_WORK_EXPERIENCE_EQUIVALENCY->name],
AssessmentDecision::HOLD->name,
AssessmentResultType::SKILL);

$user5 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate5 = PoolCandidate::select('id')->where('user_id', $user5->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate5,
$publishedPool->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->pluck('id')->toArray(),
$assessmentStep,
AssessmentDecisionLevel::AT_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_FAILED_NOT_RELEVANT->name,
AssessmentResultJustification::EDUCATION_FAILED_REQUIREMENT_NOT_MET->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::EDUCATION);

$user6 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate6 = PoolCandidate::select('id')->where('user_id', $user6->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate6,
$publishedPool->poolSkills()->pluck('id')->toArray(), $assessmentStep,
null,
[AssessmentResultJustification::EDUCATION_FAILED_REQUIREMENT_NOT_MET->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::EDUCATION);

$user7 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate7 = PoolCandidate::select('id')->where('user_id', $user7->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate7,
null,
$assessmentStep,
null,
[AssessmentResultJustification::EDUCATION_ACCEPTED_WORK_EXPERIENCE_EQUIVALENCY->name],
AssessmentDecision::HOLD->name,
assessmentResultType::EDUCATION);
$this->assessSkillsWithLevelAndJustification(
$poolCandidate7,
$publishedPool->poolSkills()->pluck('id')->toArray(),
$assessmentStep,
null,
[AssessmentResultJustification::SKILL_FAILED_INSUFFICIENTLY_DEMONSTRATED->name],
AssessmentDecision::HOLD->name,
assessmentResultType::SKILL);

$user8 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate8 = PoolCandidate::select('id')->where('user_id', $user8->id)->where('pool_id', $publishedPool->id)->sole();
// select first essential skill from pool skills
$firstEssentialSkill = $publishedPool->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->first();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate8,
[$firstEssentialSkill->id],
$assessmentStep,
null,
[AssessmentResultJustification::SKILL_FAILED_INSUFFICIENTLY_DEMONSTRATED->name,
AssessmentResultJustification::FAILED_NOT_ENOUGH_INFORMATION->name,
AssessmentResultJustification::FAILED_OTHER->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::SKILL);
$publishedPool->load('assessmentSteps.poolSkills');
foreach ($publishedPool->assessmentSteps as $assessmentStep) {
$user1 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate1 = PoolCandidate::select('id')->where('user_id', $user1->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate1,
$assessmentStep->poolSkills,
$assessmentStep,
AssessmentDecisionLevel::ABOVE_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_ACCEPTED_INFORMATION->name],
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::EDUCATION);
$this->assessSkillsWithLevelAndJustification(
$poolCandidate1,
$assessmentStep->poolSkills,
$assessmentStep,
AssessmentDecisionLevel::ABOVE_REQUIRED->name,
null,
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::SKILL);

$user2 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate2 = PoolCandidate::select('id')->where('user_id', $user2->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate2,
$assessmentStep->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->get(),
$assessmentStep,
AssessmentDecisionLevel::AT_REQUIRED->name,
null,
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::SKILL);

$user3 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate3 = PoolCandidate::select('id')->where('user_id', $user3->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate3,
$assessmentStep->poolSkills,
$assessmentStep,
AssessmentDecisionLevel::ABOVE_REQUIRED->name,
null,
AssessmentDecision::SUCCESSFUL->name,
AssessmentResultType::SKILL);

$user4 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate4 = PoolCandidate::select('id')->where('user_id', $user4->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate4,
$assessmentStep->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->get(),
$assessmentStep, AssessmentDecisionLevel::AT_REQUIRED->name,
null,
AssessmentDecision::HOLD->name,
AssessmentResultType::SKILL);

$user5 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate5 = PoolCandidate::select('id')->where('user_id', $user5->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate5,
$assessmentStep->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->get(),
$assessmentStep,
AssessmentDecisionLevel::AT_REQUIRED->name,
[AssessmentResultJustification::EDUCATION_FAILED_NOT_RELEVANT->name,
AssessmentResultJustification::EDUCATION_FAILED_REQUIREMENT_NOT_MET->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::EDUCATION);

$user6 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate6 = PoolCandidate::select('id')->where('user_id', $user6->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate6,
$assessmentStep->poolSkills,
$assessmentStep,
null,
[AssessmentResultJustification::EDUCATION_FAILED_REQUIREMENT_NOT_MET->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::EDUCATION);

$user7 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate7 = PoolCandidate::select('id')->where('user_id', $user7->id)->where('pool_id', $publishedPool->id)->sole();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate7,
null,
$assessmentStep,
null,
[AssessmentResultJustification::EDUCATION_ACCEPTED_WORK_EXPERIENCE_EQUIVALENCY->name],
AssessmentDecision::HOLD->name,
assessmentResultType::EDUCATION);
$this->assessSkillsWithLevelAndJustification(
$poolCandidate7,
$assessmentStep->poolSkills,
$assessmentStep,
null,
null,
AssessmentDecision::HOLD->name,
assessmentResultType::SKILL);

$user8 = User::select('id')->where('email', '[email protected]')->sole();
$poolCandidate8 = PoolCandidate::select('id')->where('user_id', $user8->id)->where('pool_id', $publishedPool->id)->sole();
// select first essential skill from pool skills
$firstEssentialSkill = $assessmentStep->poolSkills()->where('type', PoolSkillType::ESSENTIAL->name)->get()->first();
$this->assessSkillsWithLevelAndJustification(
$poolCandidate8,
[$firstEssentialSkill],
$assessmentStep,
null,
[AssessmentResultJustification::SKILL_FAILED_INSUFFICIENTLY_DEMONSTRATED->name,
AssessmentResultJustification::FAILED_NOT_ENOUGH_INFORMATION->name,
AssessmentResultJustification::FAILED_OTHER->name],
AssessmentDecision::UNSUCCESSFUL->name,
AssessmentResultType::SKILL);
}
}

private function assessSkillsWithLevelAndJustification($poolCandidate,
Expand All @@ -185,6 +195,11 @@ private function assessSkillsWithLevelAndJustification($poolCandidate,
$assessmentDecision,
$assessmentResultType)
{
// Only save education assessment for the first assessment step
if ($assessmentResultType == AssessmentResultType::EDUCATION && $assessmentStep->type != AssessmentStepType::APPLICATION_SCREENING->name) {
return;
}

$nullJustifications = $assessmentDecision === AssessmentDecision::HOLD->name || is_null($assessmentDecision);

if ($assessmentResultType == null) {
Expand All @@ -201,7 +216,7 @@ private function assessSkillsWithLevelAndJustification($poolCandidate,
AssessmentResult::factory()->withResultType($assessmentResultType)->create([
'assessment_step_id' => $assessmentStep->id,
'pool_candidate_id' => $poolCandidate->id,
'pool_skill_id' => $poolSkill,
'pool_skill_id' => $poolSkill->id,
'assessment_decision_level' => $level,
'justifications' => $nullJustifications ? null : $justifications,
'assessment_decision' => $assessmentDecision,
Expand Down
2 changes: 1 addition & 1 deletion api/database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public function run(): void
TeamRandomSeeder::class,
PoolRandomSeeder::class,
UserRandomSeeder::class,
AssessmentResultRandomSeeder::class,
// AssessmentResultRandomSeeder::class,
SearchRequestRandomSeeder::class,
DigitalContractingQuestionnaireRandomSeeder::class,
DepartmentSpecificRecruitmentProcessFormRandomSeeder::class,
Expand Down
59 changes: 59 additions & 0 deletions api/tests/Feature/CandidateAssessmentStatusTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -922,4 +922,63 @@ public function testApplicationScreeningStepEducationResult()
],
]);
}

/**
* This regression test ensures that if all skills are assessed as Successful,
* except for a single skill in the final step which is Unsuccessful,
* then the overall status is Disqualified.
*/
public function testUnsuccessfulEssentialInFinalStepMeansDisqualified(): void
{
$steps = $this->pool->assessmentSteps;

AssessmentResult::factory()
->withResultType(AssessmentResultType::EDUCATION)
->create([
'assessment_step_id' => $steps[0]->id,
'pool_candidate_id' => $this->candidate->id,
'assessment_decision' => AssessmentDecision::SUCCESSFUL->name,
'pool_skill_id' => $this->poolSkill->id,
]);
AssessmentResult::factory()
->withResultType(AssessmentResultType::SKILL)
->create([
'assessment_step_id' => $steps[0]->id,
'pool_candidate_id' => $this->candidate->id,
'assessment_decision' => AssessmentDecision::SUCCESSFUL->name,
'pool_skill_id' => $this->poolSkill->id,
]);

AssessmentResult::factory()
->withResultType(AssessmentResultType::SKILL)
->create([
'assessment_step_id' => $steps[1]->id,
'pool_candidate_id' => $this->candidate->id,
'assessment_decision' => AssessmentDecision::UNSUCCESSFUL->name,
'pool_skill_id' => $this->poolSkill->id,
]);

$this->actingAs($this->adminUser, 'api')
->graphQL($this->query, $this->queryVars)
->assertJson([
'data' => [
'poolCandidate' => [
'assessmentStatus' => [
'assessmentStepStatuses' => [
[
'step' => $steps[0]->id,
'decision' => AssessmentDecision::SUCCESSFUL->name,
],
[
'step' => $steps[1]->id,
'decision' => AssessmentDecision::UNSUCCESSFUL->name,
],
],
'overallAssessmentStatus' => OverallAssessmentStatus::DISQUALIFIED->name,
'currentStep' => 2,
],
],
],
]);
}
}

0 comments on commit 1548d53

Please sign in to comment.