Files
krow_backend/go-api/internal/tools/hiring_test.go
2026-08-28 12:21:44 +05:30

247 lines
7.9 KiB
Go

package tools_test
import (
"context"
"encoding/json"
"fmt"
"testing"
"github.com/krow/krow-backend/go-api/internal/authctx"
"github.com/krow/krow-backend/go-api/internal/testutil"
"github.com/krow/krow-backend/go-api/internal/tools"
)
// An ai_score of 0 is the absence of a score, not a score of zero. The product
// states this rule in candidateIntelligence.js: "null rather than zeros ... so
// an unscreened candidate shows '—' instead of a confident-looking 0".
//
// candidates_quality shipped without a test and broke the rule three ways: it
// counted every unscored candidate as weak, dragged the average down with their
// zeros, and offered no way to tell how many the average was over. On the real
// corpus that reported "16 weak, averaging 28" where the truth was "1 weak, 9
// scored, averaging 76" — a number that would push a manager to reject a pool
// that is in fact strong.
func TestCandidateQualityTreatsAnAbsentScoreAsAbsentNotAsZero(t *testing.T) {
h := testutil.New(t)
f := seedFunnel(t, h, "quality-zero") // Dana: ai_screened, scored 88
reg := liveRegistry(t, h)
ctx := context.Background()
// Three more, sharing Dana's org and posting.
for _, c := range []struct {
name, email, status string
score int
}{
{"Unscored Applicant", "u1-quality@example.test", "applied", 0},
{"Advanced Unscored", "u2-quality@example.test", "interview", 0},
{"Genuinely Weak", "u3-quality@example.test", "ai_screened", 30},
} {
if _, err := h.Pool.Exec(ctx, `
INSERT INTO job_applications (org_id, job_posting_id, applicant_name, email, status, ai_score)
SELECT org_id, job_posting_id, $2, $3, $4::application_status, $5
FROM job_applications WHERE id = $1::uuid`,
f.appID, c.name, c.email, c.status, c.score); err != nil {
t.Fatalf("seed %s: %v", c.name, err)
}
}
res := reg.Dispatch(ctx,
tools.Context{Principal: f.admin, RunID: "run_q1"},
"candidates_quality", json.RawMessage(`{}`))
if res.Error != nil {
t.Fatalf("candidates_quality failed: %+v", res.Error)
}
var got struct {
Applications int `json:"applications"`
Strong int `json:"strong"`
Weak int `json:"weak"`
Unscreened int `json:"unscreened"`
Scored int `json:"scored"`
Average int `json:"averageMatchScore"`
Basis int `json:"averageMatchScoreBasis"`
}
body, _ := json.Marshal(res.Data)
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("decode %s: %v", body, err)
}
// Only the 30 is weak. The two zeros are unscored, not bad.
for _, c := range []struct {
field string
got int
want int
}{
{"applications", got.Applications, 4},
{"strong", got.Strong, 1}, // 88
{"weak", got.Weak, 1}, // 30 only — not the two zeros
{"unscreened", got.Unscreened, 1}, // the one at 'applied'
{"scored", got.Scored, 2}, // 88 and 30
{"averageMatchScore", got.Average, 59}, // (88+30)/2, not (88+30+0+0)/4
{"averageMatchScoreBasis", got.Basis, 2},
} {
if c.got != c.want {
t.Errorf("%s = %d, want %d — full payload: %s", c.field, c.got, c.want, body)
}
}
}
// seedRatedWorkers creates an org with workers at the given krow scores. A score
// of 0 is how the product records "not yet rated" — worker_profiles.krow_score is
// NOT NULL, so there is no null to distinguish it, which is exactly the trap.
func seedRatedWorkers(t *testing.T, h *testutil.Harness, slug string,
workers []struct {
name string
krow int
rated float64
}) (string, authctx.Identity) {
t.Helper()
ctx := context.Background()
org := freshOrg(t, h, slug)
boss := fmt.Sprintf("boss-%s@example.test", slug)
admin := authctx.Identity{
UserID: seedUser(t, h, org, boss, "admin"),
OrgID: org, Role: "admin", Email: boss,
}
for i, w := range workers {
if _, err := h.Pool.Exec(ctx, `
INSERT INTO worker_profiles
(org_id, full_name, email, krow_score, reliability_score,
attendance_score, performance_score, client_rating)
VALUES ($1::uuid, $2, $3, $4, $4, $4, $4, $5)`,
org, w.name, fmt.Sprintf("w%d-%s@example.test", i, slug), w.krow, w.rated); err != nil {
t.Fatalf("seed worker %s: %v", w.name, err)
}
}
return org, admin
}
// A krow_score of 0 means "Not yet scored" — the product says so in as many
// words (dataResolver.js) and its lowest band starts above 0 (TalentPool.jsx).
// Reading it as a score of zero made hires_performance report the four unrated
// workers as the four *weakest* performers by name, and dragged every average
// down with them: a workforce rated 4.7 of 5 was reported at 1.6.
func TestPerformanceNeverNamesAnUnratedWorkerAsWeakest(t *testing.T) {
h := testutil.New(t)
org, admin := seedRatedWorkers(t, h, "perf-unrated", []struct {
name string
krow int
rated float64
}{
{"Scored High", 90, 5},
{"Scored Low", 50, 4},
{"Never Rated", 0, 0},
{"Also Never Rated", 0, 0},
})
_ = org
reg := liveRegistry(t, h)
res := reg.Dispatch(context.Background(),
tools.Context{Principal: admin, RunID: "run_p1"},
"hires_performance", json.RawMessage(`{}`))
if res.Error != nil {
t.Fatalf("hires_performance failed: %+v", res.Error)
}
var got struct {
Workers int `json:"workers"`
Scored int `json:"scored"`
Unscored int `json:"unscored"`
AvgKrow int `json:"averageKrowScore"`
AvgRated float64 `json:"averageClientRating"`
Weakest []struct {
Name string `json:"name"`
} `json:"weakest"`
Strongest []struct {
Name string `json:"name"`
} `json:"strongest"`
}
body, _ := json.Marshal(res.Data)
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("decode %s: %v", body, err)
}
// The headline the operator acts on.
for _, c := range []struct {
field string
got int
want int
}{
{"workers", got.Workers, 4},
{"scored", got.Scored, 2},
{"unscored", got.Unscored, 2},
{"averageKrowScore", got.AvgKrow, 70}, // (90+50)/2, not (90+50+0+0)/4 = 35
} {
if c.got != c.want {
t.Errorf("%s = %d, want %d — payload: %s", c.field, c.got, c.want, body)
}
}
if got.AvgRated != 4.5 { // (5+4)/2, not (5+4+0+0)/4 = 2.25
t.Errorf("averageClientRating = %v, want 4.5 — payload: %s", got.AvgRated, body)
}
// The part that names real people.
for _, w := range append(append([]struct {
Name string `json:"name"`
}{}, got.Weakest...), got.Strongest...) {
if w.Name == "Never Rated" || w.Name == "Also Never Rated" {
t.Errorf("%q has no rating but was ranked by score — payload: %s", w.Name, body)
}
}
if len(got.Weakest) == 0 || got.Weakest[0].Name != "Scored Low" {
t.Errorf("weakest should start at the lowest *rated* worker — payload: %s", body)
}
}
// TalentPool.jsx puts the lowest band at (krow_score || 0) > 0 && < 60, so an
// unrated worker is counted separately rather than as the least ready.
func TestTalentPoolCountsUnratedSeparatelyFromLowScoring(t *testing.T) {
h := testutil.New(t)
_, admin := seedRatedWorkers(t, h, "pool-bands", []struct {
name string
krow int
rated float64
}{
{"Ready", 90, 5},
{"Developing", 70, 4},
{"Early", 30, 3},
{"Unrated", 0, 0},
})
reg := liveRegistry(t, h)
res := reg.Dispatch(context.Background(),
tools.Context{Principal: admin, RunID: "run_p2"},
"talent_pool", json.RawMessage(`{}`))
if res.Error != nil {
t.Fatalf("talent_pool failed: %+v", res.Error)
}
var got struct {
Workers int `json:"workers"`
JobReady int `json:"jobReady"`
Developing int `json:"developing"`
Early int `json:"early"`
Unscored int `json:"unscored"`
}
body, _ := json.Marshal(res.Data)
if err := json.Unmarshal(body, &got); err != nil {
t.Fatalf("decode %s: %v", body, err)
}
for _, c := range []struct {
field string
got int
want int
}{
{"workers", got.Workers, 4},
{"jobReady", got.JobReady, 1},
{"developing", got.Developing, 1},
{"early", got.Early, 1}, // the 30 only — not the unrated worker
{"unscored", got.Unscored, 1},
} {
if c.got != c.want {
t.Errorf("%s = %d, want %d — payload: %s", c.field, c.got, c.want, body)
}
}
}