Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ public class DeterministicRequirementExtractor {
+ "){0,3}Go" + GO_LIST_TAIL
+ ")";

/**
* Explicit Go notation in the job <em>title</em>. A title is short, curated and names the
* stack directly, so two notations that are too weak to trust in prose are unambiguous
* there: a parenthesised language tag ("Juju Software Engineer (Go)") and a slash-separated
* language pair ("Software Engineer, Python / Go", "Software Engineer - Go / Python").
* Production preview showed {@link #QUALIFIED_GO} alone dropping exactly those rows.
*
* <p>Deliberately limited to those two forms, and deliberately applied to the title only:
* bare Go in a title is still the English verb ("Ready to Go Platform Engineer"), and the
* body keeps the unchanged conservative rule. The unambiguous spellings — Golang, "Go
* developer", "Go engineer" — already come through {@link #QUALIFIED_GO} wherever they sit.
*/
private static final Pattern TITLE_GO = Pattern.compile("(?i)(?:"
+ "\\(\\s*Go\\s*\\)"
+ "|(?<![\\p{L}\\p{N}])" + LANGUAGE_TOKEN + "\\s*/\\s*Go(?![\\p{L}\\p{N}])"
+ "|(?<![\\p{L}\\p{N}])Go\\s*/\\s*" + LANGUAGE_TOKEN + "(?![\\p{L}\\p{N}])"
+ ")");

private static final Map<String, Pattern> TECHNOLOGIES = technologies();
private static final List<String> PROGRAMMING = List.of("Java", "TypeScript", "JavaScript", "Python", "Kotlin", "C#", "C++", "Go");
// Whole words only: "internal" or "international" must not count as an internship.
Expand Down Expand Up @@ -149,17 +167,17 @@ public ExtractedRequirements extract(Job job) {
boolean trainee = TRAINEE_SIGNALS.matcher(text).find();
boolean finalYear = Pattern.compile("(?i)(must|only|required|currently)\\s+(?:be\\s+)?(?:a\\s+)?final[- ]year|final[- ]year\\s+(student\\s+)?required")
.matcher(text).find();
List<String> technologies = extractTechnologies(text);
// Seniority reads the title and the body separately, and Go additionally reads the
// title on its own; everything else keeps using the combined text exactly as before.
String titleText = String.valueOf(job.getTitle()).replace('\u00a0', ' ');
String bodyText = String.valueOf(job.getDescription()).replace('\u00a0', ' ');
List<String> technologies = extractTechnologies(text, titleText);
List<String> programming = technologies.stream().filter(PROGRAMMING::contains).toList();
List<String> spoken = spokenLanguages(text);
List<String> mentoring = signals(lower, Map.of(
"mentorship", "mentorship", "mentor", "mentor", "structured learning", "structured learning",
"structured mentorship", "structured mentorship", "training program", "training program",
"academy", "academy", "pair programming", "pair programming"));
// Seniority reads the title and the body separately; everything else keeps using
// the combined text exactly as before.
String titleText = String.valueOf(job.getTitle()).replace('\u00a0', ' ');
String bodyText = String.valueOf(job.getDescription()).replace('\u00a0', ' ');
return new ExtractedRequirements(seniority(titleText, bodyText), trainee, experience(text),
education(text), finalYear, technologies, programming, spoken,
job.getLocation(), remoteEligibility(lower), mentoring, workAuthorization(text),
Expand Down Expand Up @@ -202,8 +220,15 @@ private static String token(String tech) {
};
}

private List<String> extractTechnologies(String text) {
return TECHNOLOGIES.entrySet().stream().filter(e -> e.getValue().matcher(text).find())
/**
* Combined title+description evidence, plus the title-only Go recovery. The scan still walks
* {@link #TECHNOLOGIES} in declaration order, so ordering stays stable and each term — Go
* included, however many rules matched it — is emitted at most once.
*/
private List<String> extractTechnologies(String text, String title) {
return TECHNOLOGIES.entrySet().stream()
.filter(e -> e.getValue().matcher(text).find()
|| ("Go".equals(e.getKey()) && TITLE_GO.matcher(title).find()))
.map(Map.Entry::getKey).toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,11 @@ private List<String> technologiesOf(String description) {
.technologies();
}

private List<String> technologiesOfTitle(String title) {
return extractor.extract(jobWithDescription(title, "Build and ship backend services."))
.technologies();
}

@ParameterizedTest
@ValueSource(strings = {
"We build REST API services",
Expand Down Expand Up @@ -444,6 +449,82 @@ void rejectsOrdinaryEnglishGo(String description) {
assertThat(technologiesOf(description)).doesNotContain("Go");
}

// ------------------------------------------------- explicit Go notation in the job title

/** Production preview rows where the conservative body rule dropped a title-declared Go. */
@ParameterizedTest
@ValueSource(strings = {
"Juju Software Engineer (Go)",
"Distributed Systems Testing Software Engineer, Python / Go",
"Software Engineer - Go / Python",
"Software Engineer (Go), Platform",
"Backend Engineer ( Go )",
"Senior Engineer, Go/Rust",
"Backend Engineer - TypeScript / Go"
})
void extractsExplicitGoNotationFromTheTitle(String title) {
assertThat(technologiesOfTitle(title)).contains("Go");
}

@Test
void titleDeclaredGoIsAlsoAProgrammingLanguageAndListedOnce() {
var requirements = extractor.extract(jobWithDescription("Juju Software Engineer (Go)",
"Work on SQL storage, Docker images and Kubernetes operators."));

assertThat(requirements.technologies())
.containsExactly("SQL", "Docker", "Kubernetes", "Go");
assertThat(requirements.programmingLanguages()).containsExactly("Go");
}

/** Both a title and a body rule can fire for one posting; Go must still appear once. */
@Test
void goIsNotDuplicatedWhenTitleAndBodyEvidenceBothMatch() {
var requirements = extractor.extract(jobWithDescription("Software Engineer - Go / Python",
"Golang experience preferred; our services are written in Go, mostly."));

assertThat(requirements.technologies()).containsOnlyOnce("Go");
assertThat(requirements.programmingLanguages()).containsOnlyOnce("Go");
}

/** The unambiguous title spellings already handled by the body rule keep working. */
@ParameterizedTest
@ValueSource(strings = {
"Golang Software Engineer, Commercial Systems",
"Go (Golang) Software Engineer, Developer Tooling and Containers",
"Software Engineer - Python/Golang - Kubernetes",
"Go Developer",
"Go Engineer, Payments"
})
void keepsUnambiguousGoTitlesWorking(String title) {
assertThat(technologiesOfTitle(title)).contains("Go");
}

/** Ordinary English "go" in a title is still the verb, not the language. */
@ParameterizedTest
@ValueSource(strings = {
"Ready to Go Platform Engineer",
"Go To Market Engineer",
"Engineer, Go-Live Support",
"Software Engineer, Ready to Go Team"
})
void rejectsOrdinaryEnglishGoInTheTitle(String title) {
assertThat(technologiesOfTitle(title)).doesNotContain("Go");
}

/**
* The recovery is title evidence only. A parenthesised or slashed Go buried in prose stays
* unqualified, so the body rule keeps the semantics production is already running.
*/
@ParameterizedTest
@ValueSource(strings = {
"The toolchain team ships (Go) tooling for internal customers",
"Our internal wiki lists Python / Go under legacy owners",
"Teams may pick Go / Rust for greenfield work"
})
void doesNotTurnBodyOnlyGoNotationIntoANewSignal(String description) {
assertThat(technologiesOf(description)).doesNotContain("Go");
}

@ParameterizedTest
@ValueSource(strings = {
"Experience with Spring Boot 3",
Expand Down
Loading