* wave 1, jobspy only * combine usa/ca to united states * strict city location filter * hide and show based on focus * UI changes * allow clicking cross! * pill animate in * animate out, uggo fix * animate out * framer motion * animate component height * adzuna * hiring cafe implementation * refactor: centralize shared search-city parsing and matching * feat: migrate city setting to searchCities with legacy fallback * docs: update pipeline and extractor city-search wording * fix(orchestrator): normalize tokenized paste behavior * fix(shared): tighten city matching semantics * docs(extractors): document city-location knobs and geocoding note
1119 lines
26 KiB
TypeScript
1119 lines
26 KiB
TypeScript
/**
|
|
* Shared types for the job-ops orchestrator.
|
|
*/
|
|
|
|
export type JobStatus =
|
|
| "discovered" // Crawled but not processed
|
|
| "processing" // Currently generating resume
|
|
| "ready" // PDF generated, waiting for user to apply
|
|
| "applied" // Application sent
|
|
| "in_progress" // In process beyond initial application
|
|
| "skipped" // User skipped this job
|
|
| "expired"; // Deadline passed
|
|
|
|
export const APPLICATION_STAGES = [
|
|
"applied",
|
|
"recruiter_screen",
|
|
"assessment",
|
|
"hiring_manager_screen",
|
|
"technical_interview",
|
|
"onsite",
|
|
"offer",
|
|
"closed",
|
|
] as const;
|
|
|
|
export type ApplicationStage = (typeof APPLICATION_STAGES)[number];
|
|
|
|
export const STAGE_LABELS: Record<ApplicationStage, string> = {
|
|
applied: "Applied",
|
|
recruiter_screen: "Recruiter Screen",
|
|
assessment: "Assessment",
|
|
hiring_manager_screen: "Team Match",
|
|
technical_interview: "Technical Interview",
|
|
onsite: "Final Round",
|
|
offer: "Offer",
|
|
closed: "Closed",
|
|
};
|
|
|
|
export type StageTransitionTarget = ApplicationStage | "no_change";
|
|
|
|
export const APPLICATION_OUTCOMES = [
|
|
"offer_accepted",
|
|
"offer_declined",
|
|
"rejected",
|
|
"withdrawn",
|
|
"no_response",
|
|
"ghosted",
|
|
] as const;
|
|
|
|
export type JobOutcome = (typeof APPLICATION_OUTCOMES)[number];
|
|
|
|
export const APPLICATION_TASK_TYPES = [
|
|
"prep",
|
|
"todo",
|
|
"follow_up",
|
|
"check_status",
|
|
] as const;
|
|
|
|
export type ApplicationTaskType = (typeof APPLICATION_TASK_TYPES)[number];
|
|
|
|
export const INTERVIEW_TYPES = [
|
|
"recruiter_screen",
|
|
"technical",
|
|
"onsite",
|
|
"panel",
|
|
"behavioral",
|
|
"final",
|
|
] as const;
|
|
|
|
export type InterviewType = (typeof INTERVIEW_TYPES)[number];
|
|
|
|
export const INTERVIEW_OUTCOMES = [
|
|
"pass",
|
|
"fail",
|
|
"pending",
|
|
"cancelled",
|
|
] as const;
|
|
|
|
export type InterviewOutcome = (typeof INTERVIEW_OUTCOMES)[number];
|
|
|
|
export interface StageEventMetadata {
|
|
note?: string | null;
|
|
actor?: "system" | "user";
|
|
groupId?: string | null;
|
|
groupLabel?: string | null;
|
|
eventLabel?: string | null;
|
|
externalUrl?: string | null;
|
|
reasonCode?: string | null;
|
|
eventType?: "interview_log" | "status_update" | "note" | null;
|
|
}
|
|
|
|
export interface StageEvent {
|
|
id: string;
|
|
applicationId: string;
|
|
title: string;
|
|
groupId: string | null;
|
|
fromStage: ApplicationStage | null;
|
|
toStage: ApplicationStage;
|
|
occurredAt: number;
|
|
metadata: StageEventMetadata | null;
|
|
outcome: JobOutcome | null;
|
|
}
|
|
|
|
export interface ApplicationTask {
|
|
id: string;
|
|
applicationId: string;
|
|
type: ApplicationTaskType;
|
|
title: string;
|
|
dueDate: number | null;
|
|
isCompleted: boolean;
|
|
notes: string | null;
|
|
}
|
|
|
|
export interface Interview {
|
|
id: string;
|
|
applicationId: string;
|
|
scheduledAt: number;
|
|
durationMins: number | null;
|
|
type: InterviewType;
|
|
outcome: InterviewOutcome | null;
|
|
}
|
|
|
|
export type JobSource =
|
|
| "gradcracker"
|
|
| "indeed"
|
|
| "linkedin"
|
|
| "glassdoor"
|
|
| "ukvisajobs"
|
|
| "adzuna"
|
|
| "hiringcafe"
|
|
| "manual";
|
|
|
|
export interface Job {
|
|
id: string;
|
|
|
|
// Source / provenance
|
|
source: JobSource;
|
|
sourceJobId: string | null; // External ID (if provided)
|
|
jobUrlDirect: string | null; // Source-provided direct URL (if provided)
|
|
datePosted: string | null; // Source-provided posting date (if provided)
|
|
|
|
// From crawler (normalized)
|
|
title: string;
|
|
employer: string;
|
|
employerUrl: string | null;
|
|
jobUrl: string; // Gradcracker listing URL
|
|
applicationLink: string | null; // Actual application URL
|
|
disciplines: string | null;
|
|
deadline: string | null;
|
|
salary: string | null;
|
|
location: string | null;
|
|
degreeRequired: string | null;
|
|
starting: string | null;
|
|
jobDescription: string | null;
|
|
|
|
// Orchestrator enrichments
|
|
status: JobStatus;
|
|
outcome: JobOutcome | null;
|
|
closedAt: number | null;
|
|
suitabilityScore: number | null; // 0-100 AI-generated score
|
|
suitabilityReason: string | null; // AI explanation
|
|
tailoredSummary: string | null; // Generated resume summary
|
|
tailoredHeadline: string | null; // Generated resume headline
|
|
tailoredSkills: string | null; // Generated resume skills (JSON)
|
|
selectedProjectIds: string | null; // Comma-separated IDs of selected projects
|
|
pdfPath: string | null; // Path to generated PDF
|
|
tracerLinksEnabled: boolean; // Rewrite outbound resume links to tracer links on next PDF generation
|
|
sponsorMatchScore: number | null; // 0-100 fuzzy match score with visa sponsors
|
|
sponsorMatchNames: string | null; // JSON array of matched sponsor names (when 100% matches or top match)
|
|
|
|
// JobSpy fields (nullable for non-JobSpy sources)
|
|
jobType: string | null;
|
|
salarySource: string | null;
|
|
salaryInterval: string | null;
|
|
salaryMinAmount: number | null;
|
|
salaryMaxAmount: number | null;
|
|
salaryCurrency: string | null;
|
|
isRemote: boolean | null;
|
|
jobLevel: string | null;
|
|
jobFunction: string | null;
|
|
listingType: string | null;
|
|
emails: string | null;
|
|
companyIndustry: string | null;
|
|
companyLogo: string | null;
|
|
companyUrlDirect: string | null;
|
|
companyAddresses: string | null;
|
|
companyNumEmployees: string | null;
|
|
companyRevenue: string | null;
|
|
companyDescription: string | null;
|
|
skills: string | null;
|
|
experienceRange: string | null;
|
|
companyRating: number | null;
|
|
companyReviewsCount: number | null;
|
|
vacancyCount: number | null;
|
|
workFromHomeType: string | null;
|
|
|
|
// Timestamps
|
|
discoveredAt: string;
|
|
processedAt: string | null;
|
|
appliedAt: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export type JobListItem = Pick<
|
|
Job,
|
|
| "id"
|
|
| "source"
|
|
| "title"
|
|
| "employer"
|
|
| "jobUrl"
|
|
| "applicationLink"
|
|
| "datePosted"
|
|
| "deadline"
|
|
| "salary"
|
|
| "location"
|
|
| "status"
|
|
| "outcome"
|
|
| "closedAt"
|
|
| "suitabilityScore"
|
|
| "sponsorMatchScore"
|
|
| "jobType"
|
|
| "jobFunction"
|
|
| "salaryMinAmount"
|
|
| "salaryMaxAmount"
|
|
| "salaryCurrency"
|
|
| "discoveredAt"
|
|
| "appliedAt"
|
|
| "updatedAt"
|
|
>;
|
|
|
|
export interface CreateJobInput {
|
|
source: JobSource;
|
|
title: string;
|
|
employer: string;
|
|
employerUrl?: string;
|
|
jobUrl: string;
|
|
applicationLink?: string;
|
|
disciplines?: string;
|
|
deadline?: string;
|
|
salary?: string;
|
|
location?: string;
|
|
degreeRequired?: string;
|
|
starting?: string;
|
|
jobDescription?: string;
|
|
|
|
// JobSpy fields (optional)
|
|
sourceJobId?: string;
|
|
jobUrlDirect?: string;
|
|
datePosted?: string;
|
|
jobType?: string;
|
|
salarySource?: string;
|
|
salaryInterval?: string;
|
|
salaryMinAmount?: number;
|
|
salaryMaxAmount?: number;
|
|
salaryCurrency?: string;
|
|
isRemote?: boolean;
|
|
jobLevel?: string;
|
|
jobFunction?: string;
|
|
listingType?: string;
|
|
emails?: string;
|
|
companyIndustry?: string;
|
|
companyLogo?: string;
|
|
companyUrlDirect?: string;
|
|
companyAddresses?: string;
|
|
companyNumEmployees?: string;
|
|
companyRevenue?: string;
|
|
companyDescription?: string;
|
|
skills?: string;
|
|
experienceRange?: string;
|
|
companyRating?: number;
|
|
companyReviewsCount?: number;
|
|
vacancyCount?: number;
|
|
workFromHomeType?: string;
|
|
}
|
|
|
|
export interface ManualJobDraft {
|
|
title?: string;
|
|
employer?: string;
|
|
jobUrl?: string;
|
|
applicationLink?: string;
|
|
location?: string;
|
|
salary?: string;
|
|
deadline?: string;
|
|
jobDescription?: string;
|
|
jobType?: string;
|
|
jobLevel?: string;
|
|
jobFunction?: string;
|
|
disciplines?: string;
|
|
degreeRequired?: string;
|
|
starting?: string;
|
|
}
|
|
|
|
export interface ManualJobInferenceResponse {
|
|
job: ManualJobDraft;
|
|
warning?: string | null;
|
|
}
|
|
|
|
export interface ManualJobFetchResponse {
|
|
content: string;
|
|
url: string;
|
|
}
|
|
|
|
export interface UpdateJobInput {
|
|
title?: string;
|
|
employer?: string;
|
|
jobUrl?: string;
|
|
applicationLink?: string | null;
|
|
location?: string | null;
|
|
salary?: string | null;
|
|
deadline?: string | null;
|
|
status?: JobStatus;
|
|
outcome?: JobOutcome | null;
|
|
closedAt?: number | null;
|
|
jobDescription?: string | null;
|
|
suitabilityScore?: number;
|
|
suitabilityReason?: string;
|
|
tailoredSummary?: string;
|
|
tailoredHeadline?: string;
|
|
tailoredSkills?: string;
|
|
selectedProjectIds?: string;
|
|
pdfPath?: string;
|
|
tracerLinksEnabled?: boolean;
|
|
appliedAt?: string;
|
|
sponsorMatchScore?: number;
|
|
sponsorMatchNames?: string;
|
|
}
|
|
|
|
export interface PipelineConfig {
|
|
topN: number; // Number of top jobs to process
|
|
minSuitabilityScore: number; // Minimum score to auto-process
|
|
sources: JobSource[]; // Job sources to crawl
|
|
outputDir: string; // Directory for generated PDFs
|
|
enableCrawling?: boolean;
|
|
enableScoring?: boolean;
|
|
enableImporting?: boolean;
|
|
enableAutoTailoring?: boolean;
|
|
}
|
|
|
|
export interface PipelineRun {
|
|
id: string;
|
|
startedAt: string;
|
|
completedAt: string | null;
|
|
status: "running" | "completed" | "failed" | "cancelled";
|
|
jobsDiscovered: number;
|
|
jobsProcessed: number;
|
|
errorMessage: string | null;
|
|
}
|
|
|
|
// API Response types
|
|
export interface ApiMeta {
|
|
requestId: string;
|
|
simulated?: boolean;
|
|
blockedReason?: string;
|
|
}
|
|
|
|
export interface ApiErrorPayload {
|
|
code: string;
|
|
message: string;
|
|
details?: unknown;
|
|
}
|
|
|
|
export type ApiResponse<T> =
|
|
| {
|
|
ok: true;
|
|
data: T;
|
|
meta?: ApiMeta;
|
|
}
|
|
| {
|
|
ok: false;
|
|
error: ApiErrorPayload;
|
|
meta: ApiMeta;
|
|
};
|
|
|
|
export interface TracerAnalyticsTimeseriesPoint {
|
|
day: string; // YYYY-MM-DD
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
}
|
|
|
|
export interface TracerAnalyticsTopJob {
|
|
jobId: string;
|
|
title: string;
|
|
employer: string;
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
lastClickedAt: number | null;
|
|
}
|
|
|
|
export interface TracerAnalyticsTopLink {
|
|
tracerLinkId: string;
|
|
token: string;
|
|
jobId: string;
|
|
title: string;
|
|
employer: string;
|
|
sourcePath: string;
|
|
sourceLabel: string;
|
|
destinationUrl: string;
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
lastClickedAt: number | null;
|
|
}
|
|
|
|
export interface TracerAnalyticsResponse {
|
|
filters: {
|
|
jobId: string | null;
|
|
from: number | null;
|
|
to: number | null;
|
|
includeBots: boolean;
|
|
limit: number;
|
|
};
|
|
totals: {
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
};
|
|
timeSeries: TracerAnalyticsTimeseriesPoint[];
|
|
topJobs: TracerAnalyticsTopJob[];
|
|
topLinks: TracerAnalyticsTopLink[];
|
|
}
|
|
|
|
export interface JobTracerLinkAnalyticsItem {
|
|
tracerLinkId: string;
|
|
token: string;
|
|
sourcePath: string;
|
|
sourceLabel: string;
|
|
destinationUrl: string;
|
|
isActive: boolean;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
lastClickedAt: number | null;
|
|
}
|
|
|
|
export interface JobTracerLinksResponse {
|
|
job: {
|
|
id: string;
|
|
title: string;
|
|
employer: string;
|
|
tracerLinksEnabled: boolean;
|
|
};
|
|
totals: {
|
|
links: number;
|
|
clicks: number;
|
|
uniqueOpens: number;
|
|
botClicks: number;
|
|
humanClicks: number;
|
|
};
|
|
links: JobTracerLinkAnalyticsItem[];
|
|
}
|
|
|
|
export type TracerReadinessStatus = "ready" | "unconfigured" | "unavailable";
|
|
|
|
export interface TracerReadinessResponse {
|
|
status: TracerReadinessStatus;
|
|
canEnable: boolean;
|
|
publicBaseUrl: string | null;
|
|
healthUrl: string | null;
|
|
checkedAt: number;
|
|
lastSuccessAt: number | null;
|
|
reason: string | null;
|
|
}
|
|
|
|
export const POST_APPLICATION_PROVIDERS = ["gmail", "imap"] as const;
|
|
export type PostApplicationProvider =
|
|
(typeof POST_APPLICATION_PROVIDERS)[number];
|
|
|
|
export const POST_APPLICATION_PROVIDER_ACTIONS = [
|
|
"connect",
|
|
"status",
|
|
"sync",
|
|
"disconnect",
|
|
] as const;
|
|
export type PostApplicationProviderAction =
|
|
(typeof POST_APPLICATION_PROVIDER_ACTIONS)[number];
|
|
|
|
export const POST_APPLICATION_INTEGRATION_STATUSES = [
|
|
"disconnected",
|
|
"connected",
|
|
"error",
|
|
] as const;
|
|
export type PostApplicationIntegrationStatus =
|
|
(typeof POST_APPLICATION_INTEGRATION_STATUSES)[number];
|
|
|
|
export const POST_APPLICATION_SYNC_RUN_STATUSES = [
|
|
"running",
|
|
"completed",
|
|
"failed",
|
|
"cancelled",
|
|
] as const;
|
|
export type PostApplicationSyncRunStatus =
|
|
(typeof POST_APPLICATION_SYNC_RUN_STATUSES)[number];
|
|
|
|
export const POST_APPLICATION_RELEVANCE_DECISIONS = [
|
|
"relevant",
|
|
"not_relevant",
|
|
"needs_llm",
|
|
] as const;
|
|
export type PostApplicationRelevanceDecision =
|
|
(typeof POST_APPLICATION_RELEVANCE_DECISIONS)[number];
|
|
|
|
export const POST_APPLICATION_MESSAGE_TYPES = [
|
|
"interview",
|
|
"rejection",
|
|
"offer",
|
|
"update",
|
|
"other",
|
|
] as const;
|
|
export type PostApplicationMessageType =
|
|
(typeof POST_APPLICATION_MESSAGE_TYPES)[number];
|
|
|
|
export const POST_APPLICATION_ROUTER_STAGE_TARGETS = [
|
|
"no_change",
|
|
"applied",
|
|
"recruiter_screen",
|
|
"assessment",
|
|
"hiring_manager_screen",
|
|
"technical_interview",
|
|
"onsite",
|
|
"offer",
|
|
"rejected",
|
|
"withdrawn",
|
|
"closed",
|
|
] as const;
|
|
export type PostApplicationRouterStageTarget =
|
|
(typeof POST_APPLICATION_ROUTER_STAGE_TARGETS)[number];
|
|
|
|
export const POST_APPLICATION_PROCESSING_STATUSES = [
|
|
"auto_linked",
|
|
"pending_user",
|
|
"manual_linked",
|
|
"ignored",
|
|
] as const;
|
|
export type PostApplicationProcessingStatus =
|
|
(typeof POST_APPLICATION_PROCESSING_STATUSES)[number];
|
|
|
|
export interface PostApplicationIntegration {
|
|
id: string;
|
|
provider: PostApplicationProvider;
|
|
accountKey: string;
|
|
displayName: string | null;
|
|
status: PostApplicationIntegrationStatus;
|
|
credentials: Record<string, unknown> | null;
|
|
lastConnectedAt: number | null;
|
|
lastSyncedAt: number | null;
|
|
lastError: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PostApplicationSyncRun {
|
|
id: string;
|
|
provider: PostApplicationProvider;
|
|
accountKey: string;
|
|
integrationId: string | null;
|
|
status: PostApplicationSyncRunStatus;
|
|
startedAt: number;
|
|
completedAt: number | null;
|
|
messagesDiscovered: number;
|
|
messagesRelevant: number;
|
|
messagesClassified: number;
|
|
messagesMatched: number;
|
|
messagesApproved: number;
|
|
messagesDenied: number;
|
|
messagesErrored: number;
|
|
errorCode: string | null;
|
|
errorMessage: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PostApplicationMessage {
|
|
id: string;
|
|
provider: PostApplicationProvider;
|
|
accountKey: string;
|
|
integrationId: string | null;
|
|
syncRunId: string | null;
|
|
externalMessageId: string;
|
|
externalThreadId: string | null;
|
|
fromAddress: string;
|
|
fromDomain: string | null;
|
|
senderName: string | null;
|
|
subject: string;
|
|
receivedAt: number;
|
|
snippet: string;
|
|
classificationLabel: string | null;
|
|
classificationConfidence: number | null;
|
|
classificationPayload: Record<string, unknown> | null;
|
|
relevanceLlmScore: number | null;
|
|
relevanceDecision: PostApplicationRelevanceDecision;
|
|
matchedJobId: string | null;
|
|
matchConfidence: number | null;
|
|
stageTarget: PostApplicationRouterStageTarget | null;
|
|
messageType: PostApplicationMessageType;
|
|
stageEventPayload: Record<string, unknown> | null;
|
|
processingStatus: PostApplicationProcessingStatus;
|
|
decidedAt: number | null;
|
|
decidedBy: string | null;
|
|
errorCode: string | null;
|
|
errorMessage: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface PostApplicationProviderActionConnectRequest {
|
|
accountKey?: string;
|
|
payload?: Record<string, unknown>;
|
|
}
|
|
|
|
export interface PostApplicationProviderActionSyncRequest {
|
|
accountKey?: string;
|
|
maxMessages?: number;
|
|
searchDays?: number;
|
|
}
|
|
|
|
export interface PostApplicationProviderStatus {
|
|
provider: PostApplicationProvider;
|
|
accountKey: string;
|
|
connected: boolean;
|
|
integration: PostApplicationIntegration | null;
|
|
}
|
|
|
|
export interface PostApplicationProviderActionResponse {
|
|
provider: PostApplicationProvider;
|
|
action: PostApplicationProviderAction;
|
|
accountKey: string;
|
|
status: PostApplicationProviderStatus;
|
|
message?: string;
|
|
}
|
|
|
|
export interface PostApplicationInboxItem {
|
|
message: PostApplicationMessage;
|
|
matchedJob?: {
|
|
id: string;
|
|
title: string;
|
|
employer: string;
|
|
} | null;
|
|
}
|
|
|
|
export type PostApplicationAction = "approve" | "deny";
|
|
|
|
export interface PostApplicationActionRequest {
|
|
action: PostApplicationAction;
|
|
provider: PostApplicationProvider;
|
|
accountKey: string;
|
|
}
|
|
|
|
export type PostApplicationActionResult =
|
|
| {
|
|
messageId: string;
|
|
ok: true;
|
|
message: PostApplicationMessage;
|
|
stageEventId?: string | null;
|
|
}
|
|
| {
|
|
messageId: string;
|
|
ok: false;
|
|
error: {
|
|
code: string;
|
|
message: string;
|
|
};
|
|
};
|
|
|
|
export interface PostApplicationActionResponse {
|
|
action: PostApplicationAction;
|
|
requested: number;
|
|
succeeded: number;
|
|
failed: number;
|
|
skipped: number;
|
|
results: PostApplicationActionResult[];
|
|
}
|
|
|
|
export interface JobsListResponse<TJob = Job> {
|
|
jobs: TJob[];
|
|
total: number;
|
|
byStatus: Record<JobStatus, number>;
|
|
revision: string;
|
|
}
|
|
|
|
export interface JobsRevisionResponse {
|
|
revision: string;
|
|
latestUpdatedAt: string | null;
|
|
total: number;
|
|
statusFilter: string | null;
|
|
}
|
|
|
|
export type JobAction = "skip" | "move_to_ready" | "rescore";
|
|
|
|
export type JobActionRequest =
|
|
| {
|
|
action: "skip" | "rescore";
|
|
jobIds: string[];
|
|
}
|
|
| {
|
|
action: "move_to_ready";
|
|
jobIds: string[];
|
|
options?: {
|
|
force?: boolean;
|
|
};
|
|
};
|
|
|
|
export type JobActionResult =
|
|
| {
|
|
jobId: string;
|
|
ok: true;
|
|
job: Job;
|
|
}
|
|
| {
|
|
jobId: string;
|
|
ok: false;
|
|
error: {
|
|
code: string;
|
|
message: string;
|
|
};
|
|
};
|
|
|
|
export interface JobActionResponse {
|
|
action: JobAction;
|
|
requested: number;
|
|
succeeded: number;
|
|
failed: number;
|
|
results: JobActionResult[];
|
|
}
|
|
|
|
export type JobActionStreamEvent =
|
|
| {
|
|
type: "started";
|
|
action: JobAction;
|
|
requested: number;
|
|
completed: number;
|
|
succeeded: number;
|
|
failed: number;
|
|
requestId: string;
|
|
}
|
|
| {
|
|
type: "progress";
|
|
action: JobAction;
|
|
requested: number;
|
|
completed: number;
|
|
succeeded: number;
|
|
failed: number;
|
|
result: JobActionResult;
|
|
requestId: string;
|
|
}
|
|
| {
|
|
type: "completed";
|
|
action: JobAction;
|
|
requested: number;
|
|
completed: number;
|
|
succeeded: number;
|
|
failed: number;
|
|
results: JobActionResult[];
|
|
requestId: string;
|
|
}
|
|
| {
|
|
type: "error";
|
|
code: string;
|
|
message: string;
|
|
requestId: string;
|
|
};
|
|
|
|
export const JOB_CHAT_MESSAGE_ROLES = [
|
|
"system",
|
|
"user",
|
|
"assistant",
|
|
"tool",
|
|
] as const;
|
|
export type JobChatMessageRole = (typeof JOB_CHAT_MESSAGE_ROLES)[number];
|
|
|
|
export const JOB_CHAT_MESSAGE_STATUSES = [
|
|
"complete",
|
|
"partial",
|
|
"cancelled",
|
|
"failed",
|
|
] as const;
|
|
export type JobChatMessageStatus = (typeof JOB_CHAT_MESSAGE_STATUSES)[number];
|
|
|
|
export const JOB_CHAT_RUN_STATUSES = [
|
|
"running",
|
|
"completed",
|
|
"cancelled",
|
|
"failed",
|
|
] as const;
|
|
export type JobChatRunStatus = (typeof JOB_CHAT_RUN_STATUSES)[number];
|
|
|
|
export interface JobChatThread {
|
|
id: string;
|
|
jobId: string;
|
|
title: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
lastMessageAt: string | null;
|
|
}
|
|
|
|
export interface JobChatMessage {
|
|
id: string;
|
|
threadId: string;
|
|
jobId: string;
|
|
role: JobChatMessageRole;
|
|
content: string;
|
|
status: JobChatMessageStatus;
|
|
tokensIn: number | null;
|
|
tokensOut: number | null;
|
|
version: number;
|
|
replacesMessageId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface JobChatRun {
|
|
id: string;
|
|
threadId: string;
|
|
jobId: string;
|
|
status: JobChatRunStatus;
|
|
model: string | null;
|
|
provider: string | null;
|
|
errorCode: string | null;
|
|
errorMessage: string | null;
|
|
startedAt: number;
|
|
completedAt: number | null;
|
|
requestId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export type JobChatStreamEvent =
|
|
| {
|
|
type: "ready";
|
|
runId: string;
|
|
threadId: string;
|
|
messageId: string;
|
|
requestId: string;
|
|
}
|
|
| {
|
|
type: "delta";
|
|
runId: string;
|
|
messageId: string;
|
|
delta: string;
|
|
}
|
|
| {
|
|
type: "completed";
|
|
runId: string;
|
|
message: JobChatMessage;
|
|
}
|
|
| {
|
|
type: "cancelled";
|
|
runId: string;
|
|
message: JobChatMessage;
|
|
}
|
|
| {
|
|
type: "error";
|
|
runId: string;
|
|
code: string;
|
|
message: string;
|
|
requestId: string;
|
|
};
|
|
|
|
// Visa Sponsors types
|
|
export interface VisaSponsor {
|
|
organisationName: string;
|
|
townCity: string;
|
|
county: string;
|
|
typeRating: string;
|
|
route: string;
|
|
}
|
|
|
|
export interface VisaSponsorSearchResult {
|
|
sponsor: VisaSponsor;
|
|
score: number;
|
|
matchedName: string;
|
|
}
|
|
|
|
export interface VisaSponsorSearchResponse {
|
|
results: VisaSponsorSearchResult[];
|
|
query: string;
|
|
total: number;
|
|
}
|
|
|
|
export interface VisaSponsorStatusResponse {
|
|
lastUpdated: string | null;
|
|
csvPath: string | null;
|
|
totalSponsors: number;
|
|
isUpdating: boolean;
|
|
nextScheduledUpdate: string | null;
|
|
error: string | null;
|
|
}
|
|
|
|
export interface PipelineStatusResponse {
|
|
isRunning: boolean;
|
|
lastRun: PipelineRun | null;
|
|
nextScheduledRun: string | null;
|
|
}
|
|
|
|
export interface ResumeProjectCatalogItem {
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
date: string;
|
|
isVisibleInBase: boolean;
|
|
}
|
|
|
|
export interface ResumeProjectsSettings {
|
|
maxProjects: number;
|
|
lockedProjectIds: string[];
|
|
aiSelectableProjectIds: string[];
|
|
}
|
|
|
|
export interface ResumeProfile {
|
|
basics?: {
|
|
name?: string;
|
|
label?: string;
|
|
image?: string;
|
|
email?: string;
|
|
phone?: string;
|
|
url?: string;
|
|
summary?: string;
|
|
headline?: string;
|
|
location?: {
|
|
address?: string;
|
|
postalCode?: string;
|
|
city?: string;
|
|
countryCode?: string;
|
|
region?: string;
|
|
};
|
|
profiles?: Array<{
|
|
network?: string;
|
|
username?: string;
|
|
url?: string;
|
|
}>;
|
|
};
|
|
sections?: {
|
|
summary?: {
|
|
id?: string;
|
|
visible?: boolean;
|
|
name?: string;
|
|
content?: string;
|
|
};
|
|
skills?: {
|
|
id?: string;
|
|
visible?: boolean;
|
|
name?: string;
|
|
items?: Array<{
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
level: number;
|
|
keywords: string[];
|
|
visible: boolean;
|
|
}>;
|
|
};
|
|
projects?: {
|
|
id?: string;
|
|
visible?: boolean;
|
|
name?: string;
|
|
items?: Array<{
|
|
id: string;
|
|
name: string;
|
|
description: string;
|
|
date: string;
|
|
summary: string;
|
|
visible: boolean;
|
|
keywords?: string[];
|
|
url?: string;
|
|
}>;
|
|
};
|
|
experience?: {
|
|
id?: string;
|
|
visible?: boolean;
|
|
name?: string;
|
|
items?: Array<{
|
|
id: string;
|
|
company: string;
|
|
position: string;
|
|
location: string;
|
|
date: string;
|
|
summary: string;
|
|
visible: boolean;
|
|
}>;
|
|
};
|
|
[key: string]: unknown;
|
|
};
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
export interface ProfileStatusResponse {
|
|
exists: boolean;
|
|
error: string | null;
|
|
}
|
|
|
|
export interface ValidationResult {
|
|
valid: boolean;
|
|
message: string | null;
|
|
}
|
|
|
|
export interface DemoInfoResponse {
|
|
demoMode: boolean;
|
|
resetCadenceHours: number;
|
|
lastResetAt: string | null;
|
|
nextResetAt: string | null;
|
|
baselineVersion: string | null;
|
|
baselineName: string | null;
|
|
}
|
|
|
|
export interface AppSettings {
|
|
model: string;
|
|
defaultModel: string;
|
|
overrideModel: string | null;
|
|
// Specific model overrides
|
|
modelScorer: string; // resolved
|
|
overrideModelScorer: string | null;
|
|
modelTailoring: string; // resolved
|
|
overrideModelTailoring: string | null;
|
|
modelProjectSelection: string; // resolved
|
|
overrideModelProjectSelection: string | null;
|
|
|
|
llmProvider: string;
|
|
defaultLlmProvider: string;
|
|
overrideLlmProvider: string | null;
|
|
llmBaseUrl: string;
|
|
defaultLlmBaseUrl: string;
|
|
overrideLlmBaseUrl: string | null;
|
|
|
|
pipelineWebhookUrl: string;
|
|
defaultPipelineWebhookUrl: string;
|
|
overridePipelineWebhookUrl: string | null;
|
|
jobCompleteWebhookUrl: string;
|
|
defaultJobCompleteWebhookUrl: string;
|
|
overrideJobCompleteWebhookUrl: string | null;
|
|
profileProjects: ResumeProjectCatalogItem[];
|
|
resumeProjects: ResumeProjectsSettings;
|
|
defaultResumeProjects: ResumeProjectsSettings;
|
|
overrideResumeProjects: ResumeProjectsSettings | null;
|
|
rxresumeBaseResumeId: string | null;
|
|
ukvisajobsMaxJobs: number;
|
|
defaultUkvisajobsMaxJobs: number;
|
|
overrideUkvisajobsMaxJobs: number | null;
|
|
adzunaMaxJobsPerTerm: number;
|
|
defaultAdzunaMaxJobsPerTerm: number;
|
|
overrideAdzunaMaxJobsPerTerm: number | null;
|
|
gradcrackerMaxJobsPerTerm: number;
|
|
defaultGradcrackerMaxJobsPerTerm: number;
|
|
overrideGradcrackerMaxJobsPerTerm: number | null;
|
|
searchTerms: string[];
|
|
defaultSearchTerms: string[];
|
|
overrideSearchTerms: string[] | null;
|
|
searchCities: string;
|
|
defaultSearchCities: string;
|
|
overrideSearchCities: string | null;
|
|
jobspyResultsWanted: number;
|
|
defaultJobspyResultsWanted: number;
|
|
overrideJobspyResultsWanted: number | null;
|
|
jobspyCountryIndeed: string;
|
|
defaultJobspyCountryIndeed: string;
|
|
overrideJobspyCountryIndeed: string | null;
|
|
showSponsorInfo: boolean;
|
|
defaultShowSponsorInfo: boolean;
|
|
overrideShowSponsorInfo: boolean | null;
|
|
chatStyleTone: string;
|
|
defaultChatStyleTone: string;
|
|
overrideChatStyleTone: string | null;
|
|
chatStyleFormality: string;
|
|
defaultChatStyleFormality: string;
|
|
overrideChatStyleFormality: string | null;
|
|
chatStyleConstraints: string;
|
|
defaultChatStyleConstraints: string;
|
|
overrideChatStyleConstraints: string | null;
|
|
chatStyleDoNotUse: string;
|
|
defaultChatStyleDoNotUse: string;
|
|
overrideChatStyleDoNotUse: string | null;
|
|
llmApiKeyHint: string | null;
|
|
rxresumeEmail: string | null;
|
|
rxresumePasswordHint: string | null;
|
|
basicAuthUser: string | null;
|
|
basicAuthPasswordHint: string | null;
|
|
ukvisajobsEmail: string | null;
|
|
ukvisajobsPasswordHint: string | null;
|
|
adzunaAppId: string | null;
|
|
adzunaAppKeyHint: string | null;
|
|
webhookSecretHint: string | null;
|
|
basicAuthActive: boolean;
|
|
// Backup settings
|
|
backupEnabled: boolean;
|
|
defaultBackupEnabled: boolean;
|
|
overrideBackupEnabled: boolean | null;
|
|
backupHour: number;
|
|
defaultBackupHour: number;
|
|
overrideBackupHour: number | null;
|
|
backupMaxCount: number;
|
|
defaultBackupMaxCount: number;
|
|
overrideBackupMaxCount: number | null;
|
|
// Scoring settings
|
|
penalizeMissingSalary: boolean;
|
|
defaultPenalizeMissingSalary: boolean;
|
|
overridePenalizeMissingSalary: boolean | null;
|
|
missingSalaryPenalty: number;
|
|
defaultMissingSalaryPenalty: number;
|
|
overrideMissingSalaryPenalty: number | null;
|
|
// Auto-skip settings
|
|
autoSkipScoreThreshold: number | null;
|
|
defaultAutoSkipScoreThreshold: number | null;
|
|
overrideAutoSkipScoreThreshold: number | null;
|
|
}
|
|
|
|
export interface BackupInfo {
|
|
filename: string;
|
|
type: "auto" | "manual";
|
|
size: number;
|
|
createdAt: string;
|
|
}
|