Add startup.jobs extractor support (#279)
* Add startup.jobs extractor support * Harden startup.jobs extractor inputs * Wire startupjobs into Docker and CI * Tighten startupjobs review follow-ups * fix: publish ghcr during release workflow * feat: add startupjobs max jobs configuration and update related tests
This commit is contained in:
@@ -753,6 +753,7 @@ describe("OrchestratorPage", () => {
|
||||
gradcrackerMaxJobsPerTerm: 150,
|
||||
ukvisajobsMaxJobs: 150,
|
||||
adzunaMaxJobsPerTerm: 150,
|
||||
startupjobsMaxJobsPerTerm: 150,
|
||||
jobspyCountryIndeed: "united kingdom",
|
||||
searchCities: "United Kingdom",
|
||||
});
|
||||
|
||||
@@ -182,6 +182,7 @@ export const AutomaticRunTab: React.FC<AutomaticRunTabProps> = ({
|
||||
|
||||
const rememberedRunBudget =
|
||||
settings?.jobspyResultsWanted?.value ??
|
||||
settings?.startupjobsMaxJobsPerTerm?.value ??
|
||||
settings?.adzunaMaxJobsPerTerm?.value ??
|
||||
settings?.gradcrackerMaxJobsPerTerm?.value ??
|
||||
settings?.ukvisajobsMaxJobs?.value ??
|
||||
|
||||
@@ -52,6 +52,17 @@ describe("automatic-run utilities", () => {
|
||||
expect(cap).toBeLessThanOrEqual(750);
|
||||
});
|
||||
|
||||
it("assigns a dedicated startupjobs max-jobs limit", () => {
|
||||
const limits = deriveExtractorLimits({
|
||||
budget: 120,
|
||||
searchTerms: ["backend", "platform"],
|
||||
sources: ["startupjobs"],
|
||||
});
|
||||
|
||||
expect(limits.startupjobsMaxJobsPerTerm).toBeGreaterThan(0);
|
||||
expect(limits.startupjobsMaxJobsPerTerm).toBeLessThanOrEqual(120);
|
||||
});
|
||||
|
||||
it("returns zero estimate when no search terms are provided", () => {
|
||||
const estimate = calculateAutomaticEstimate({
|
||||
values: {
|
||||
@@ -112,4 +123,21 @@ describe("automatic-run utilities", () => {
|
||||
expect(estimate.discovered.cap).toBeGreaterThan(0);
|
||||
expect(estimate.discovered.cap).toBeLessThanOrEqual(120);
|
||||
});
|
||||
|
||||
it("includes startupjobs in estimate caps using the shared term budget", () => {
|
||||
const estimate = calculateAutomaticEstimate({
|
||||
values: {
|
||||
topN: 10,
|
||||
minSuitabilityScore: 50,
|
||||
searchTerms: ["backend", "platform"],
|
||||
runBudget: 120,
|
||||
country: "united kingdom",
|
||||
cityLocations: [],
|
||||
},
|
||||
sources: ["startupjobs"],
|
||||
});
|
||||
|
||||
expect(estimate.discovered.cap).toBeGreaterThan(0);
|
||||
expect(estimate.discovered.cap).toBeLessThanOrEqual(120);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,6 +66,7 @@ export interface ExtractorLimits {
|
||||
gradcrackerMaxJobsPerTerm: number;
|
||||
ukvisajobsMaxJobs: number;
|
||||
adzunaMaxJobsPerTerm: number;
|
||||
startupjobsMaxJobsPerTerm: number;
|
||||
}
|
||||
|
||||
export function deriveExtractorLimits(args: {
|
||||
@@ -82,6 +83,7 @@ export function deriveExtractorLimits(args: {
|
||||
const includesUkVisaJobs = args.sources.includes("ukvisajobs");
|
||||
const includesAdzuna = args.sources.includes("adzuna");
|
||||
const includesHiringCafe = args.sources.includes("hiringcafe");
|
||||
const includesStartupJobs = args.sources.includes("startupjobs");
|
||||
|
||||
const weightedContributors =
|
||||
(includesIndeed ? termCount : 0) +
|
||||
@@ -90,7 +92,8 @@ export function deriveExtractorLimits(args: {
|
||||
(includesGradcracker ? termCount : 0) +
|
||||
(includesUkVisaJobs ? 1 : 0) +
|
||||
(includesAdzuna ? termCount : 0) +
|
||||
(includesHiringCafe ? termCount : 0);
|
||||
(includesHiringCafe ? termCount : 0) +
|
||||
(includesStartupJobs ? termCount : 0);
|
||||
|
||||
if (weightedContributors <= 0) {
|
||||
return {
|
||||
@@ -98,6 +101,7 @@ export function deriveExtractorLimits(args: {
|
||||
gradcrackerMaxJobsPerTerm: budget,
|
||||
ukvisajobsMaxJobs: budget,
|
||||
adzunaMaxJobsPerTerm: budget,
|
||||
startupjobsMaxJobsPerTerm: budget,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -109,6 +113,7 @@ export function deriveExtractorLimits(args: {
|
||||
gradcrackerMaxJobsPerTerm: perUnit,
|
||||
ukvisajobsMaxJobs: Math.min(budget, perUnit + remainder),
|
||||
adzunaMaxJobsPerTerm: perUnit,
|
||||
startupjobsMaxJobsPerTerm: perUnit,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -173,6 +178,7 @@ export function calculateAutomaticEstimate(args: {
|
||||
const hasGlassdoor = sources.includes("glassdoor");
|
||||
const hasAdzuna = sources.includes("adzuna");
|
||||
const hasHiringCafe = sources.includes("hiringcafe");
|
||||
const hasStartupJobs = sources.includes("startupjobs");
|
||||
const limits = deriveExtractorLimits({
|
||||
budget: values.runBudget,
|
||||
searchTerms: values.searchTerms,
|
||||
@@ -191,9 +197,17 @@ export function calculateAutomaticEstimate(args: {
|
||||
const hiringCafeCap = hasHiringCafe
|
||||
? limits.jobspyResultsWanted * termCount
|
||||
: 0;
|
||||
const startupJobsCap = hasStartupJobs
|
||||
? limits.startupjobsMaxJobsPerTerm * termCount
|
||||
: 0;
|
||||
|
||||
const discoveredCap =
|
||||
jobspyCap + gradcrackerCap + ukvisaCap + adzunaCap + hiringCafeCap;
|
||||
jobspyCap +
|
||||
gradcrackerCap +
|
||||
ukvisaCap +
|
||||
adzunaCap +
|
||||
hiringCafeCap +
|
||||
startupJobsCap;
|
||||
const discoveredMin = Math.round(discoveredCap * 0.35);
|
||||
const discoveredMax = Math.round(discoveredCap * 0.75);
|
||||
const processedMin = Math.min(values.topN, discoveredMin);
|
||||
|
||||
@@ -181,11 +181,13 @@ export function usePipelineControls(
|
||||
);
|
||||
const hasAdzuna = compatibleSources.includes("adzuna");
|
||||
const hasHiringCafe = compatibleSources.includes("hiringcafe");
|
||||
const hasStartupJobs = compatibleSources.includes("startupjobs");
|
||||
const serializedCities = serializeCityLocationsSetting(
|
||||
values.cityLocations,
|
||||
);
|
||||
const searchCities =
|
||||
(hasJobSpySite || hasAdzuna || hasHiringCafe) && serializedCities
|
||||
(hasJobSpySite || hasAdzuna || hasHiringCafe || hasStartupJobs) &&
|
||||
serializedCities
|
||||
? serializedCities
|
||||
: formatCountryLabel(values.country);
|
||||
await api.updateSettings({
|
||||
@@ -194,6 +196,7 @@ export function usePipelineControls(
|
||||
gradcrackerMaxJobsPerTerm: limits.gradcrackerMaxJobsPerTerm,
|
||||
ukvisajobsMaxJobs: limits.ukvisajobsMaxJobs,
|
||||
adzunaMaxJobsPerTerm: limits.adzunaMaxJobsPerTerm,
|
||||
startupjobsMaxJobsPerTerm: limits.startupjobsMaxJobsPerTerm,
|
||||
jobspyCountryIndeed: values.country,
|
||||
searchCities,
|
||||
});
|
||||
|
||||
@@ -17,6 +17,10 @@ describe("orchestrator utils", () => {
|
||||
expect(getEnabledSources(withoutKey)).not.toContain("adzuna");
|
||||
});
|
||||
|
||||
it("enables startupjobs without credentials", () => {
|
||||
expect(getEnabledSources(createAppSettings())).toContain("startupjobs");
|
||||
});
|
||||
|
||||
it("counts processing jobs in ready and discovered tabs", () => {
|
||||
const jobs = [
|
||||
createJob({ id: "ready", status: "ready", closedAt: null }),
|
||||
|
||||
@@ -195,6 +195,10 @@ export const getEnabledSources = (
|
||||
enabled.push(source);
|
||||
continue;
|
||||
}
|
||||
if (source === "startupjobs") {
|
||||
enabled.push(source);
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
source === "indeed" ||
|
||||
source === "linkedin" ||
|
||||
|
||||
@@ -254,6 +254,7 @@ export const DEMO_SOURCE_BASE_URLS: Record<JobSource, string> = {
|
||||
ukvisajobs: "https://www.ukvisajobs.com",
|
||||
adzuna: "https://www.adzuna.com",
|
||||
hiringcafe: "https://hiring.cafe",
|
||||
startupjobs: "https://startup.jobs",
|
||||
manual: "https://example.com",
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user