WorkWingman — Assessment Prep (Technical)¶
Best-effort detection of which coding-assessment platform a job's process may use, plus a curated deep-link map to where the user can practice or subscribe for that platform. This document covers exactly what the detector can and can't know, the practice map, and the hard guardrail that governs both: WorkWingman never subscribes or creates an account on the user's behalf — every resource is an external link the user opens and acts on themselves, the same BYO-credential convention documented for every other external account in connections-and-sso.md.
Why this is best-effort, not a real detection¶
There is no reliable signal in a job posting for which coding-assessment vendor a company uses.
Companies switch vendors, use different vendors per team, or don't reveal the vendor until after
the application is submitted — the assessment-invite email is usually the first place a
candidate actually learns the platform. Any tool claiming to reliably predict the platform from
the job description alone would be guessing. AssessmentPlatformDetector is built around that
honesty constraint rather than around maximizing hit rate.
AssessmentPlatformDetector¶
File: src/WorkWingman.Infrastructure/Automation/AssessmentPlatformDetector.cs
public static AssessmentPlatform DetectFromDescription(string description)
Mirrors the shape of AtsDetector
(a pure, static, no-DI text scan) but with a different default posture:
- Signal used: literal platform-name mentions in the job description text only — "HackerRank
assessment", "CodeSignal", "Codility test", "Karat interview", "CoderByte". Matched
case-insensitively with word-boundary-aware regexes (
hacker\s*rank,coder\s*byte,code\s*signal,codility,\bkarat\b) so both "HackerRank" and "Hacker Rank" match. - No indirect inference. Company name, ATS kind (Workday/Greenhouse/etc.), or role type are never used as evidence — those are exactly the kind of "this company probably uses X" guesses this feature deliberately avoids. Only a literal named-platform mention in the JD text counts.
- Default is
AssessmentPlatformKind.Unknownwith 0 confidence and empty evidence. This is the overwhelmingly common case — most job descriptions don't name their assessment vendor at all. - Confidence is fixed at 65 when a signal is found, never higher. A JD naming a platform is a real but narrow signal: it can be stale (the company switched vendors since the posting was written) or refer to something else (e.g. a candidate's past experience mentioned elsewhere in a boilerplate JD). 65 says "worth telling the user, not worth being certain about" — this detector never returns a confidence that reads as certainty.
- Evidence is captured verbatim — the matched text plus ~40 characters of surrounding context, whitespace-normalized — so the user can judge the signal themselves rather than trust a label.
public class AssessmentPlatform
{
public AssessmentPlatformKind Kind { get; set; } = AssessmentPlatformKind.Unknown;
public int Confidence { get; set; }
public List<string> Evidence { get; set; } = [];
}
public enum AssessmentPlatformKind { Unknown, HackerRank, CoderByte, CodeSignal, Codility, Karat }
Wiring: JobPosting.AssessmentPlatform is
set inside
LinkedInJobsScraper.ScrapeJobDetailAsync,
right alongside Ats = AtsDetector.DetectFromDescriptionPage(...) — the same already-extracted
description text is handed to the detector, so there is no extra request, no extra page load, and
no new network surface.
Future extension (not built here): the real assessment-invite email is a far stronger signal than the job description. A future email-scan feature (mirroring the same read-only, user-already-opened-it philosophy as job-signals.md) could raise confidence meaningfully once the invite arrives. That is explicitly out of scope for this feature — no email scanning exists in this codebase today.
Practice/subscribe map — IAssessmentPracticeMap¶
Files:
src/WorkWingman.Core/Interfaces/IAssessmentPracticeMap.cs,
src/WorkWingman.Infrastructure/Services/AssessmentPracticeMap.cs
A hand-curated, static lookup — not a live API call, because assessment platforms don't expose a "where to practice" API. Every URL below was verified live via WebFetch as of 2026-07.
| Platform | Resource | URL | Free / Paid | What it offers |
|---|---|---|---|---|
| HackerRank | Interview Preparation Kit | https://www.hackerrank.com/interview/interview-preparation-kit | Free | Curated practice problems across 13 topics, each labeled with the share of companies that test it |
| HackerRank | Practice dashboard | https://www.hackerrank.com/dashboard | Free | General free practice problems and skill certifications |
| CoderByte | Main site | https://coderbyte.com | Paid (candidate-facing use is via an employer-sent link; CoderByte's own subscription plans start at $199/mo for the employer side) | Take-home coding challenges / employer assessment product |
| CodeSignal | Learn | https://codesignal.com/learn/ | Free tier + paid | Individual-learner practice tracks and interview-prep courses, separate from the employer assessment product |
| CodeSignal | Main site | https://codesignal.com/ | Free | Background on the assessment format (GCA/GCS) so candidates know what to expect |
| Codility | Lessons | https://app.codility.com/programmers/lessons/ | Free | 17 self-paced lessons (reading + practice tasks) covering arrays, sorting, dynamic programming, etc. — the same task style Codility's employer assessments use |
| Karat | Candidate interview prep | https://karat.com/candidate-experience/ | Free | What to expect in a live, human-proctored Karat interview |
| Karat | Brilliant Black Minds | https://karat.com/bbm-members/ | Free | Free live mock technical interviews, expert feedback, self-guided study plans, and peer community — open to Black software engineers, not limited to Karat-specific interviews |
public interface IAssessmentPracticeMap
{
IReadOnlyList<AssessmentPracticeResource> GetResources(AssessmentPlatformKind kind);
IReadOnlyList<AssessmentPracticeResource> GetCommonPlatforms();
}
public class AssessmentPracticeResource
{
public string Name { get; set; } = string.Empty;
public string Url { get; set; } = string.Empty;
public string WhatItOffers { get; set; } = string.Empty;
public bool IsFree { get; set; }
public bool IsPaid { get; set; }
}
GetResources(Unknown) returns an empty list by design — callers should use
GetCommonPlatforms() instead when the detected kind is Unknown, which returns the flattened
curated set across every known platform, so the user can prep broadly rather than for one guessed
platform.
Hard rule: never auto-subscribe, never auto-create an account¶
This is enforced at every layer, not just documented:
- The map only ever returns URLs. There is no code path anywhere in
AssessmentPracticeMap,StudyPlanService, or the frontendStudycomponent that submits a form, calls a payment API, or creates a session on any of these external platforms. - Every link opens in a new tab (
target="_blank", verified bystudy.spec.ts) — the app hands off to the platform's own site; the user does the clicking, the signing up, and the paying, if any. - This matches the existing
EnrollAsyncstub inStudyPlanService(src/WorkWingman.Infrastructure/Services/StudyPlanService.cs) — even the learning-platform "enroll via linked account" feature is a documented no-op today (see the class doc: "Real impl: Playwright session + learning-platform creds from the vault"). Assessment-platform practice links go further and don't even offer an in-app "enroll" affordance — they are plain external links, full stop. - Consistent with the BYO-credential convention in connections-and-sso.md: every external account WorkWingman touches is either the user's own pre-existing login (resolved from the vault, never created by the app) or, as here, a link the user follows to manage their own account entirely outside WorkWingman.
Surfacing on the Study & prep screen¶
Files:
src/WorkWingman.Core/Models/StudyPlan.cs,
src/WorkWingman.Infrastructure/Services/StudyPlanService.cs,
frontend/src/app/features/study/study.ts,
frontend/src/app/features/study/study.html
StudyPlan carries both the detection and its resolved resources:
public AssessmentPlatform AssessmentPlatform { get; set; } = new();
public List<AssessmentPracticeResource> AssessmentPracticeResources { get; set; } = [];
StudyPlanService.BuildPlan resolves the resource list once, at plan-build time: if
job.AssessmentPlatform.Kind is not Unknown, it calls GetResources(kind); otherwise it calls
GetCommonPlatforms(). This means the resource list a job's study plan shows never has to
re-derive detection logic — it is computed once and persisted with the rest of the plan.
The Study screen renders a card above the existing resource grid:
- When a platform is detected: "Coding assessment — may use {Platform}", with the caption
"detected from a mention in the job description — confirm when the invite arrives" and the
literal evidence snippet(s) quoted underneath, plus that platform's practice/subscribe links
(each tagged
FREEorPAID). - When
Unknown(the common case): "Coding assessment" with the caption "no platform mentioned in this JD yet — the assessment platform is usually only known once the invite email arrives", plus the general common-platforms list so the user can still prep broadly. - Always: a footnote — "WorkWingman only links out — you decide whether to practice or subscribe. It never creates an account or pays on your behalf."
This mirrors the existing pattern for the JD-matched course cards on the same screen (badges + title + matched-to caption), just with an honesty-first framing instead of a match-confidence framing, because the underlying signal is fundamentally weaker than a JD-keyword match.
Tests¶
AssessmentPlatformDetectorTests.cs— every platform's textual signal (including case-insensitivity and "either/or" JDs mentioning two platforms), theUnknowndefault on empty/irrelevant/null-ish description text, confidence bounds (> 0and<= 90— never near-certain), evidence capture, and the "never infers from ATS/company context" guardrail.AssessmentPracticeMapTests.cs— every known platform resolves to at least one well-formedhttps://resource,Unknownresolves to an empty list,GetCommonPlatforms()spans multiple platforms, and every resource across every platform is a plain external HTTPS link (asserting the never-auto-subscribe guardrail at the data layer).StudyPlanServiceTests.cs— a detected platform surfaces its specific resources on the built plan; anUnknownplatform surfaces the common list instead.study.spec.ts—assessmentDetected()reflectsUnknownvs. a named platform,platformBadge()labels every known kind, the assessment card renders its practice links from the plan, and every rendered link openstarget="_blank"with anhttps://href (never an in-app action).- Stryker mutation config:
**/Automation/AssessmentPlatformDetector.csand**/Services/AssessmentPracticeMap.csare added totests/WorkWingman.Tests/stryker-config.json'smutatelist, the same treatment asAtsDetector.cs.
Related docs¶
- Plain-language version: ../plain/assessment-prep.md
- connections-and-sso.md — the BYO-credential convention this feature follows
- job-signals.md — the sibling "honest, best-effort, graceful-degradation" signal design this mirrors
AtsDetector.cs— the detection-pattern this mirrors