Career Search & Interview Workflow System
CareerNeed
A full-stack product that models a job search as one connected workflow — sources, jobs, career directions, resumes, applications, and interviews — with AI analysis that has to cite its evidence before it is shown.
- Status
- In development
- Stack
- Next.js
- React
- TypeScript
- Python
- FastAPI
- SQLAlchemy
- PostgreSQL
- Alembic
- Docker
- GitHub Actions
- LLM integration
- Structured outputs
The problem
A job search produces a surprising amount of state. Roles are found on different job boards. Each one becomes a saved link, then maybe a row in a spreadsheet. Resumes multiply into versions. Applications live in other companies' portals. Interview notes go in a document, follow-ups in a calendar, and analysis in an AI chat that knows nothing about any of the rest.
None of those artifacts is hard to manage alone. The cost is in the gaps between them: which resume went to which role, what was promised in the last conversation, what is due this week.
CareerNeed treats the search as a single structured workflow, with real records and relationships underneath, instead of a set of bookmarks with a chat box attached.
Product model
The domain separates what is shared from what belongs to one person, and what is a fact from what is derived.
Sources and catalog
Company source
A tracked company and its job-board configuration, owned by one account.
Job
A normalized posting. Shared and read-only to normal clients.
Job classification
Derived role metadata with its own run history. Can abstain.
a person acts on a job through
Owned by one account
Career direction
A search track: role, seniority, optional resume, lifecycle.
Resume
Uploaded PDF with extracted text, label, default, archive.
Application
The person's own pipeline record for one job.
an application accumulates
Around an application
Contacts
People connected to the opportunity.
Follow-ups
Typed next actions with dates.
Interviews
Rounds with participants and questions.
analysis is recorded as
AI runs
Match brief run
An immutable snapshot of one resume and one job, plus the validated result.
Interview preparation runIn progress
Snapshot foundation for preparation briefs.
Two choices in this model do most of the work. A Job is a shared reference that normal clients cannot edit or delete, while an Application is private to its owner. And anything inferred — a role classification, an AI analysis — is stored as derived data with its own history, next to the source records rather than on top of them.
Architecture
Client
Next.js web app
React and TypeScript. Server-rendered pages and client views.
same-origin /api/*
API
FastAPI routers
Authenticated, owner-scoped endpoints.
Domain services
Source sync, classification, match briefs, interview preparation.
SQLAlchemy
Data
PostgreSQL
Schema managed by Alembic migrations, with database-level invariants.
outbound only, from the API
External services
Job boards
Ashby, Greenhouse, Lever.
LLM providers
OpenAI and Google Gemini, using the user's own key.
Identity provider
Verifies sign-in; never owns business data.
The frontend is the public origin and proxies /api/* to the API. That keeps authentication first-party and lets server-rendered pages make authenticated requests without cross-site cookie behavior.
Job ingestion
The interesting part of ingestion is not fetching postings. It is turning three different providers into one model, behind one safe entry point.
- Three importers, one job model. Ashby, Greenhouse, and Lever each have their own importer. All of them write the same normalized
Jobfields — company, title, location, workplace type, description, application link, and first-seen and last-seen timestamps — and deduplicate on the provider's external id. - Manual jobs are first-class. Adding a job by hand creates a shared catalog entry together with the creator's private application, in one step.
- One write path. Synchronization happens through a single authenticated, owner-scoped endpoint for a single stored source. The browser sends only the source id; provider type and configuration come from the server's stored record.
- A retired shortcut. An earlier set of provider-wide sync routes accepted caller-supplied configuration without authentication. They were removed, and regression tests assert they now return 404.
- Classification runs after ingestion, in isolation. A deterministic, title-only classifier runs in its own transaction after an import commits, so a classification failure cannot fail the import.
Synchronization is an explicit, per-source action. Nothing scans or applies automatically.
Career directions
People rarely run one job search. A frontend-leaning search and a platform-leaning search want different titles, a different seniority, and often a different resume.
A career direction makes that a record instead of a habit. It carries a role chosen from a versioned reference taxonomy (or a custom one), a seniority, an optional resume, and a lifecycle of active, paused, or archived. At most one active direction is the default, and that rule is enforced by the database rather than by the interface.
Its integration with job search is deliberately narrow: a direction can seed the editable search filters once, as a snapshot. It is not a live sync, and it is kept independent of job classification.
From job to application
Workflow
- then
Source
Tracked company or manual entry.
- then
Job
Searched, filtered, saved filters.
- then
Match brief
Optional, against a chosen resume.
Application
Private pipeline record.
An application is a workflow record, not an "applied" flag on a job. It has its own status, notes, an associated resume, contacts, and typed follow-ups — a thank-you, a status check, a recruiter reply, preparation, or a custom action. The same applications can be viewed as a list, a pipeline board, or a detail page, and a separate view gathers what is due today.
Because the job is shared and the application is private, two people can track the same posting without seeing anything of each other's process.
Interviews
Interview support is the area where the line between built and in progress matters most.
Implemented. Interviews are records attached to an application, with a type, a status, participants, and questions. They feed the "today" and follow-up views, and the system can produce preparation and outcome suggestions.
In progress. The interview preparation brief has a structured backend: bounded context, a typed output schema, validation that references point to allowed sources, safe error mapping, and audit records. It is covered by mock-based tests, and its quality against live providers has not yet been validated.
Foundation only. Preparation runs now store immutable snapshots of their inputs, and a resolver maps an interview to a reference interview type. Nothing generates from those snapshots yet.
AI as a workflow layer
AI in CareerNeed is not a chat surface. It is a set of runs attached to specific records, and the central feature is the match brief: an analysis of one resume against one job.
Match brief
- then
Run snapshot
Frozen copy of the resume and job, fingerprinted.
- then
Evidence segments
Sources split into referenceable pieces.
- then
Provider adapter
Structured generation with the user's key.
Validated result
Schema and citations checked, or the run fails.
- Runs are immutable. Each run snapshots its inputs, so a later edit to a resume cannot change what an old analysis was based on. The interface shows when a source has since gone stale.
- Claims must cite. Sources are divided into a versioned registry of segments. The model's output has to reference those segments, and every reference is validated against the exact run it belongs to. The result page resolves each citation back to the excerpt.
- Invalid output is a failed run. Output that breaks the schema or cites something that does not exist is rejected. There is no partial result and no silent downgrade.
- Role-aware analysis is opt-in by eligibility. A second version adds competency alignment, but only when the job has a current, non-stale role classification. Otherwise the run explicitly uses the first version.
- Bring your own key. Users supply their own provider credentials, which are encrypted at rest. A model router sits in front of provider adapters — OpenAI and Google Gemini today — and maps provider failures to a fixed set of safe error codes. Usage is logged per run.
- Development has a fake provider. A deterministic, credential-free adapter exercises the full production path — prompt, schema, validation, persistence — and is unavailable outside development and test.
Evaluation is treated the same way as the feature. There is a frozen synthetic benchmark with a recorded digest and tooling to run and review it, but the real-provider baseline and human review have not been done. So this page makes no claim about how accurate the analysis is.
Data isolation and auth
Every private record — resumes, applications, directions, interviews, contacts, match briefs, saved filters, AI credentials — carries its owner, and every route filters by the authenticated account. Cross-user tests cover the major private resources.
Authentication is kept separate from product-owned account data. Identity is verified at the application boundary, and what comes out of that check is mapped to a CareerNeed account through a dedicated table. Business records only ever reference the account, never an external identity, and an existing account is never linked to a new sign-in on the strength of a matching email alone.
Sign-in through external identity providers is being integrated on top of this boundary. Because ownership is expressed in terms of the account, adding a provider does not change how any record is owned or filtered.
Engineering decisions
A shared job, a private application
- Why
- A posting and one person's process around it are different things with different owners and lifecycles.
- Tradeoff
- Two records to keep coherent, and no ordinary endpoint for editing a shared job.
Career direction as a first-class record
- Why
- Different searches need their own role, seniority, and resume. The single-default rule is enforced in the database.
- Tradeoff
- More relationships and interface state than one global profile, and its coupling to job search is intentionally limited.
One owner-scoped sync path behind normalized importers
- Why
- Providers differ, and letting a caller choose provider configuration was an unsafe parallel path. It was removed.
- Tradeoff
- Each provider needs its own importer to maintain, and there is no bulk or automatic synchronization.
AI output must validate and cite, or the run fails
- Why
- An analysis is only useful in a workflow if each statement can be traced to the resume or the job it came from.
- Tradeoff
- Versioned schemas, a segment registry, and more failed runs than a free-text answer would produce.
Bring-your-own-key behind a provider router
- Why
- Product logic should not depend on one model vendor, and users keep control of their own usage.
- Tradeoff
- Encrypted credential storage, provider-specific adapters, and error handling for each provider's quirks.
Delivery and reliability
- Continuous integration runs against a real database. The backend job starts PostgreSQL, applies every migration, runs the test suite, lints and format-checks changed Python files, and then checks for drift between the models and the migrations.
- The frontend job is a full gate. Tests, formatting, lint, generated route types, a TypeScript check, and a production build.
- Both services are containerized. The API and the web app each have a provider-neutral Dockerfile with no secrets baked in.
- Staging is specified as a contract. The required environment, the same-origin
/apirouting, secure-cookie settings, and the migration step are documented, with secrets supplied by the host. - Startup fails closed. The reference taxonomy is verified by digest when the API starts; a mismatch stops the service instead of serving inconsistent data.
CareerNeed is an evolving application. Its tests and CI establish that the implementation behaves as designed. They are not a claim of production readiness, and the project's own status document says so.