mirror of
https://github.com/prdlk/jobyssey.git
synced 2026-11-02 13:12:49 +00:00
feat: add skills
This commit is contained in:
1 parent
6984212b17
commit
c50aa92862
150 files changed
+12702
No files matched your search
+116
@@ -0,0 +1,116 @@
|
||||
import { Database } from "bun:sqlite"
|
||||
import { existsSync } from "node:fs"
|
||||
|
||||
export const DB_PATH = process.env.JOBSDB_PATH ?? "documents/postings/jobs.db"
|
||||
|
||||
export const REMOTE_CLASS: Record<string, string> = { remote: "R1", "remote-global": "R1", "remote-regional": "R2", hybrid: "H", onsite: "O" }
|
||||
export const CLASS_LABEL: Record<string, string> = { R1: "Remote (global)", R2: "Remote (regional)", H: "Hybrid", O: "Onsite" }
|
||||
|
||||
export interface Row {
|
||||
posting_id: string
|
||||
company_id: string | null
|
||||
company_name: string | null
|
||||
title: string | null
|
||||
seniority: string | null
|
||||
posted_date: string | null
|
||||
location_text: string | null
|
||||
location_class: string | null
|
||||
location_country: string | null
|
||||
salary_min: string | null
|
||||
salary_max: string | null
|
||||
salary_currency: string | null
|
||||
url: string | null
|
||||
source_ats: string | null
|
||||
scrape_timestamp: string | null
|
||||
target_sector: string | null
|
||||
crypto_native: string | null
|
||||
priority_tier: number | null
|
||||
}
|
||||
|
||||
export interface Job {
|
||||
id: string
|
||||
title: string | null
|
||||
company: string | null
|
||||
location: string | null
|
||||
date: string | null
|
||||
date_approximate: boolean
|
||||
url: string | null
|
||||
description: null
|
||||
remote: string | null
|
||||
seniority: string | null
|
||||
salary: string | null
|
||||
sector: string | null
|
||||
crypto_native: boolean
|
||||
priority_tier: number | null
|
||||
ats: string | null
|
||||
snapshot: string | null
|
||||
}
|
||||
|
||||
export function openDb(): Database {
|
||||
if (!existsSync(DB_PATH)) {
|
||||
writeError(`database not found at ${DB_PATH} (set JOBSDB_PATH)`, "NO_DB")
|
||||
process.exit(1)
|
||||
}
|
||||
return new Database(DB_PATH, { readonly: true })
|
||||
}
|
||||
|
||||
/** "Posted 8 Days Ago" + scrape_timestamp -> ISO date. "30+" is a floor, flagged approximate. */
|
||||
export function resolveDate(posted: string | null, scraped: string | null): { date: string | null; approximate: boolean } {
|
||||
if (!posted) return { date: null, approximate: false }
|
||||
// greenhouse/ashby/lever store ISO timestamps; workday stores relative text.
|
||||
if (/^\d{4}-\d{2}-\d{2}/.test(posted)) return { date: posted.slice(0, 10), approximate: false }
|
||||
if (!scraped) return { date: null, approximate: false }
|
||||
const base = new Date(scraped)
|
||||
if (isNaN(base.getTime())) return { date: null, approximate: false }
|
||||
const m = /(\d+)(\+?)\s*Days?/i.exec(posted)
|
||||
let days: number
|
||||
let approximate = false
|
||||
if (/Today/i.test(posted)) days = 0
|
||||
else if (/Yesterday/i.test(posted)) days = 1
|
||||
else if (m) { days = Number(m[1]); approximate = m[2] === "+" }
|
||||
else return { date: null, approximate: false }
|
||||
base.setUTCDate(base.getUTCDate() - days)
|
||||
return { date: base.toISOString().slice(0, 10), approximate }
|
||||
}
|
||||
|
||||
export function toJob(r: Row): Job {
|
||||
const { date, approximate } = resolveDate(r.posted_date, r.scrape_timestamp)
|
||||
const salary = r.salary_min && r.salary_max ? `${r.salary_min}-${r.salary_max} ${r.salary_currency ?? ""}`.trim() : null
|
||||
return {
|
||||
id: r.posting_id,
|
||||
title: r.title || null,
|
||||
company: r.company_name || null,
|
||||
location: r.location_text || null,
|
||||
date,
|
||||
date_approximate: approximate,
|
||||
url: r.url || null,
|
||||
description: null,
|
||||
remote: r.location_class ? CLASS_LABEL[r.location_class] ?? null : null,
|
||||
seniority: r.seniority || null,
|
||||
salary,
|
||||
sector: r.target_sector || null,
|
||||
crypto_native: r.crypto_native === "True",
|
||||
priority_tier: r.priority_tier ?? null,
|
||||
ats: r.source_ats || null,
|
||||
snapshot: r.scrape_timestamp ? r.scrape_timestamp.slice(0, 10) : null,
|
||||
}
|
||||
}
|
||||
|
||||
export function writeError(error: string, code: string): void {
|
||||
process.stderr.write(JSON.stringify({ error, code }) + "\n")
|
||||
}
|
||||
|
||||
export function renderTable(jobs: Job[]): string {
|
||||
if (jobs.length === 0) return "No results."
|
||||
const rows = jobs.map((j) =>
|
||||
[
|
||||
(j.title ?? "").slice(0, 44).padEnd(44),
|
||||
(j.company ?? "-").slice(0, 22).padEnd(22),
|
||||
(j.location ?? "-").slice(0, 22).padEnd(22),
|
||||
(j.remote ?? "-").slice(0, 16).padEnd(16),
|
||||
j.date ? j.date + (j.date_approximate ? "~" : " ") : "-".padEnd(11),
|
||||
j.url ?? "",
|
||||
].join(" "),
|
||||
)
|
||||
return rows.join("\n")
|
||||
}
|
||||
Reference in new issue
Block a user