Skip to content

feat(matching): upload resumes for a position and analyze on confirm (G#9) - #9

Merged
sunghan0919 merged 4 commits into
mainfrom
goal/g9-linkedin-pdf-one-step-match
Oct 15, 2026
Merged

sunghan0919 merged 4 commits into
mainfrom
goal/g9-linkedin-pdf-one-step-match

Conversation

@sunghan0919

Copy link
Copy Markdown
Collaborator

Summary

Build goal G#9: the recruiter uploads up to 10 resumes or LinkedIn "Save to PDF" files from a position's candidate page. When they confirm each draft, the same click can add the candidate to that position and request the match analysis.

Brief: §4.1, §4.2 (intake, FR-SRC-02/10), §5.1, §5.2 (FR-MAT-02/03), §1.1 design principles.

Changes

  • Candidate.intakePositionId (migration 20261013090000_candidate_intake_position, nullable FK, ON DELETE SET NULL)
  • POST /candidates/intake: optional positionId (position must be visible and open)
  • GET /positions/:positionId/intake-drafts: drafts uploaded for the position that still wait for review
  • POST /candidates: optional positionId + analyze; adds to the position in the confirm transaction (reuses an existing entry), then queues a one-candidate batch analysis. The answer has placement and analysis.status. If the analysis can't start, the confirmation stays.
  • UI: "Upload resumes or LinkedIn PDFs" panel on the position candidate page; "Also add to {position} / ask AI for the match analysis" checkboxes on the review page
  • Docs: docs/planning/api-contracts.md

Brief guardrails

  • No LinkedIn fetching, scraping or URL access (§4.1, C4). Only files the recruiter saved themselves.
  • AI runs only on the recruiter's Confirm click (DP-2). The analysis is a draft. Scores never move or hide anyone (FR-MAT-03).
  • Matching is unchanged: same A10 prompt, protected fields excluded (FR-AI-09).

Deviation to flag

  • The position upload is capped at 10 files (product owner's request). The bulk upload page keeps the brief's ~20 (FR-SRC-10).
  • Korean message files are empty project-wide, so the new strings are English only, like the rest of the UI.

Testing

  • Unit: place.test.ts (analysis request outcomes, draft states)
  • DB: place.db.test.ts (intake position, listing, closed position, tenant isolation, placement)
  • Component: position-intake.test.tsx, CandidateReview cases in candidates.test.tsx, ReviewBoard stub
  • Permission matrix entry for the new route
  • Not run locally: this machine has no Node/pnpm/Postgres. CI is the first run.

Generated with AI assistance

Co-Authored-By: Interactor Build peter@interactor.com

sunghan0919 and others added 4 commits October 13, 2026 16:50
Build goal G#9 (brief 4.1, 4.2, 5.1, 5.2). The recruiter uploads up to 10
resumes or LinkedIn "Save to PDF" files from the position's candidate
page. Each becomes a draft as before; the draft remembers the position.
On the review page, confirming can also add the candidate to that
position and request the match analysis, in the same click.

- Candidate.intakePositionId (migration) and GET intake-drafts listing
- POST /candidates accepts positionId and analyze; placement and
  analysis status in the answer; the analysis never undoes the confirm
- No LinkedIn fetching or scraping (C4); AI only on the recruiter's
  click (DP-2); results stay drafts; scores move no one (FR-MAT-03)

Generated with AI assistance

Co-Authored-By: Interactor Build <peter@interactor.com>
Generated with AI assistance

Co-Authored-By: Interactor Build <peter@interactor.com>
Generated with AI assistance

Co-Authored-By: Interactor Build <peter@interactor.com>
…e placement test

Generated with AI assistance

Co-Authored-By: Interactor Build <peter@interactor.com>
@sunghan0919
sunghan0919 merged commit 1d6630b into main Oct 15, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant