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
8 changes: 8 additions & 0 deletions apps/admin_dashboard/src/dashboard-utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
formatDate,
githubUrl,
isTerminalJobStatus,
jobLeadClassificationMethodLabel,
labelForOnboardingState,
linkedinUrl,
onboardingStateValue,
Expand Down Expand Up @@ -61,4 +62,11 @@ describe("dashboard utility helpers", () => {
expect(isTerminalJobStatus("running")).toBe(false)
expect(isTerminalJobStatus(undefined)).toBe(false)
})

it("labels job lead classification provenance without guessing", () => {
expect(jobLeadClassificationMethodLabel("llm")).toBe("LLM")
expect(jobLeadClassificationMethodLabel("heuristic")).toBe("Keyword fallback")
expect(jobLeadClassificationMethodLabel("unknown")).toBe("Unknown")
expect(jobLeadClassificationMethodLabel()).toBe("Unknown")
})
})
6 changes: 6 additions & 0 deletions apps/admin_dashboard/src/dashboard-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ export function isTerminalJobStatus(value?: string | null) {
)
}

export function jobLeadClassificationMethodLabel(value?: string | null) {
if (value === "llm") return "LLM"
if (value === "heuristic") return "Keyword fallback"
return "Unknown"
}

export function onboardingStateValue(person: OnboardingStateCarrier) {
return person.onboarding_state || person.onboardingState || person.cOnboardingState || ""
}
Expand Down
35 changes: 25 additions & 10 deletions apps/admin_dashboard/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
formatDate,
githubUrl,
isTerminalJobStatus,
jobLeadClassificationMethodLabel,
jsonPreview,
labelForOnboardingState,
linkedinUrl,
Expand Down Expand Up @@ -255,6 +256,7 @@ type JobLead = {
confidence_label?: string
rationale?: string
method?: string
contact_email?: string | null
}
review_summary?: string
reviewed_by_discord_user_id?: string
Expand Down Expand Up @@ -5595,12 +5597,19 @@ function defaultJobPostTagNames(lead: JobLead, channel?: JobPostChannel) {

function jobLeadClassificationLabel(lead: JobLead) {
const method = lead.contractor_classification?.method
const confidenceLabel = lead.contractor_classification?.confidence_label
if (method && confidenceLabel) {
const methodLabel = method === "llm" ? "LLM" : "Keyword fallback"
return `${methodLabel}: ${confidenceLabel} fit`
}
return lead.review_summary || "Classification unavailable"
const postingType = lead.contractor_classification?.posting_type || lead.posting_type
const postingTypeLabel =
{
part_time: "Part-time / contract",
full_time: "Full-time",
part_time_or_full_time: "Full-time or part-time / contract",
unknown: "Employment type unknown",
}[String(postingType || "")] || "Employment type unknown"
if (method) {
const methodLabel = jobLeadClassificationMethodLabel(method)
return `${postingTypeLabel} · ${methodLabel}`
Comment thread
michaelmwu marked this conversation as resolved.
}
return postingType ? postingTypeLabel : lead.review_summary || "Classification unavailable"
}

function JobLeadListItem({
Expand Down Expand Up @@ -5659,6 +5668,7 @@ function JobLeadListItem({
lead.discord_guild_id,
)}/${encodeURIComponent(lead.discord_thread_id)}`
: ""
const contactEmail = lead.contractor_classification?.contact_email
return (
<article className="grid gap-4 rounded-md border bg-background p-4 lg:grid-cols-[minmax(0,1fr)_220px_190px] lg:items-start">
<div className="min-w-0">
Expand All @@ -5670,9 +5680,6 @@ function JobLeadListItem({
</div>
<div className="mt-2 flex flex-wrap gap-1.5">
{lead.organization ? <Badge variant="neutral">{lead.organization}</Badge> : null}
{lead.posting_type ? (
<Badge variant="neutral">{titleCase(lead.posting_type)}</Badge>
) : null}
{lead.location ? <Badge variant="neutral">{lead.location}</Badge> : null}
{(lead.tags || []).slice(0, 6).map((tag) => (
<Badge key={tag} variant="queued">
Expand Down Expand Up @@ -5707,10 +5714,18 @@ function JobLeadListItem({
target="_blank"
rel="noreferrer"
>
Apply
Apply website
<ExternalLink className="size-3.5" />
</a>
) : null}
{contactEmail ? (
<a
className="inline-flex items-center gap-1 font-extrabold text-primary"
href={`mailto:${contactEmail}`}
>
Email {contactEmail}
</a>
) : null}
{discordUrl ? (
<a
className="inline-flex items-center gap-1 font-extrabold text-primary"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"index.html": {
"file": "assets/index-DCDV0WK0.js",
"file": "assets/index-LL0oAOC_.js",
"name": "index",
"src": "index.html",
"isEntry": true,
Expand Down

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/api/src/five08/backend/static/dashboard/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>508 Operations Dashboard</title>
<script type="module" crossorigin src="/dashboard/assets/index-DCDV0WK0.js"></script>
<script type="module" crossorigin src="/dashboard/assets/index-LL0oAOC_.js"></script>
<link rel="stylesheet" crossorigin href="/dashboard/assets/index-2UypYUrJ.css">
</head>
<body>
Expand Down
15 changes: 15 additions & 0 deletions docs/discord-gig-dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ log in and see only gigs they originally posted, based on
Dashboard gig mutations also require `lifecycle_stage = 'pending_gig'`, so the
gig controls cannot mutate future non-gig project engagement rows by id.

## Hacker News Job Lead Review

The Gigs page can scrape employer posts from Hacker News “Who is hiring?”
threads into a review queue. Each lead shows an explicit employment type such
as `Full-time`, `Part-time / contract`, or `Full-time or part-time / contract`,
plus whether the result came from the LLM classifier or keyword fallback.

When a post contains them, the review card exposes the role-specific
application page and a direct contact email as separate actions. Model-proposed
links and email addresses are accepted only when they appear verbatim in the
source post; deterministic scoring prefers application pages over company
homepages. Re-scraping can correct pending or rejected leads that were
previously admitted by a keyword false positive, while approved or posted leads
remain unchanged.

## Candidate Sources

`engagement_applications.source` records how a candidate entered the gig flow:
Expand Down
Loading