Building Skill Align - Part 6 - Project Staffing Assistant(Backend)

Published: (February 26, 2026 at 05:20 AM EST)
5 min read
Source: Dev.to

Source: Dev.to

Project Staffing Assistant – SkillEvaluatorService

Project Staffing Assistant helps managers decide which candidates are suitable for a project based on actual project requirements.
Below is a cleaned‑up view of the backend implementation (the intelligence layer) written in Apex.


The Core Service – SkillEvaluatorService

public with sharing class SkillEvaluatorService {
    // … implementation …
}

Two important design decisions

DecisionReason
publicRequired because LWC will call this Apex class.
with sharingEnsures record‑level security is respected (queries and DML respect the current user’s sharing rules).

I had previously configured roles, OWD, and sharing rules (see the related blog post here). Using with sharing guarantees that this evaluation logic follows those configurations.

Apex Sharing Behavior

  • Apex runs in system context by default; object‑level and field‑level permissions are not automatically enforced.
  • with sharing enforces record‑level sharing rules only.
  • with sharing does not enforce object or field permissions—you must handle CRUD/FLS explicitly (e.g., WITH SECURITY_ENFORCED or Security.stripInaccessible()).
  • If no sharing keyword is defined, the class inherits sharing from its caller, so behavior may vary depending on how it is invoked.
  • Triggers run in system context. Even if a helper class is marked with sharing, the trigger itself executes in system mode.

Designing Data Transfer Objects (DTOs)

Instead of returning raw Employee__c or Employee_Skill__c records, I created DTOs that expose only the fields required by the UI. This keeps the response clean, prevents unnecessary data exposure, and isolates business logic in the backend.

Note: @AuraEnabled is required for LWC to access Apex properties and methods.

Skill‑Level DTO

public class SkillGapDetail {
    @AuraEnabled public String  skillName;
    @AuraEnabled public Integer requiredLevel;
    @AuraEnabled public Integer impact;
}

Represents a single skill gap for a candidate.

Advantages

  • All evaluation logic runs in Apex; the UI performs no calculations.
  • Business logic stays in the backend.
  • UI remains lightweight.
  • Future logic changes don’t affect frontend code.

Candidate‑Level DTO

public class CandidateResult {
    @AuraEnabled public String  employeeName;
    @AuraEnabled public Decimal gapScore;
    @AuraEnabled public Boolean isProjectReady;
    @AuraEnabled public SkillGapDetail detail;
}

For each evaluated employee, the UI receives:

  • Employee name
  • Final gap score
  • Ready / Not Ready flag
  • Skill‑gap detail (the DTO above)

Entry Point – evaluateProject()

@AuraEnabled
public static List evaluateProject(Id projectId, Integer topN) {
    // implementation …
}

Responsibilities

  1. Accept a Project Id.
  2. Evaluate unallocated employees.
  3. Rank them.
  4. Return the top N candidates.
  5. Persist evaluation results.

Guard Clause

Guard clauses stop processing early when required inputs are missing, preventing unnecessary governor‑limit consumption and UI errors.

if (projectId == null) return new List();

Prevents

  • Null‑pointer exceptions.
  • Unexpected UI errors.
  • Wasted governor limits.

Load Project Requirements

List reqs = [
    SELECT Skill__c, Required_Level__c, Importance__c, Weight__c
    FROM Project_Skill_Requirement__c
    WHERE Project__c = :projectId
];

Each requirement contains:

  • Skill
  • Required Level
  • Importance (Required / Nice‑to‑have)
  • Weight (configurable per skill)

After fetching, I convert the list into Maps for fast, constant‑time lookups (O(1)). This avoids SOQL inside loops and keeps the code bulk‑safe.


Weighted Impact Formula

The core of the evaluation engine calculates a deficit and then applies a weighted impact.

Integer deficit = requiredLevel - employeeLevel;
Integer impact  = deficit * importanceMultiplier * weight;
FactorValue
Required skillmultiplier = 2
Nice‑to‑havemultiplier = 1
Weightconfigurable per skill

Result:

  • Missing a critical skill has a higher impact.
  • Minor skills don’t disproportionately penalize a candidate.
  • The system is realistic and flexible rather than rigid.

Effective Level – Making It Smarter

Raw skill levels aren’t always reliable. I introduced two adjustments to improve accuracy:

  1. Confidence Adjustment – accounts for who supplied the rating.
  2. Staleness Adjustment – penalizes outdated assessments.

1. Confidence Adjustment

Boolean isTrusted = (src == 'Manager-assessed');
Integer confidenceAdjust = isTrusted ? 0 : 1;
Integer afterConfidence = rawLevel - confidenceAdjust;
  • Self‑assessed → reduce slightly.
  • Manager‑assessed → keep unchanged.

2. Staleness Adjustment

Date staleCutoff = Date.today().addMonths(-12);

if (lastVerifiedDate  12 months ago** → slight reduction  

---

## Effective Level  

```java
Integer effectiveLevel = afterConfidence - stalenessAdjust;
if (effectiveLevel  {
    public Integer compare(CandidateResult x, CandidateResult y) {
        if (x.gapScore != y.gapScore) {
            return (x.gapScore  **Note:** `Project_Employee_Key__c` is a **Text** field marked **Unique** and **Required**.

Records are saved with an upsert operation:

```java
upsert candidates Project_Employee_Key__c;

The upsert ensures:

  • Insert if the record doesn’t exist
  • Update the record if it already exists
  • Prevent duplicate records

This allows re‑evaluation to update previous scores without creating duplicates.

0 views
Back to Blog

Related posts

Read more »