feat(workspace): add application intelligence
Phase 5.3. Three read-only reads that answer "how suitable is this job", "how
does my experience match", "what am I missing", "what happened previously".
Timeline (GET /{id}/timeline) is an interpretation layer over JobEvent, which
stays the source of historical truth. Each row gains a readable summary, a
category and a milestone flag; events group by day. Milestones are returned
unfiltered, because narrowing the detail must not hide what actually happened.
Job analysis (GET /{id}/analysis) extracts role, company, location, employment
type, seniority, salary, technologies, skills, responsibilities and keywords
from the advert, reusing the existing SkillTagger so the vocabulary matches the
job importer. It also reports what the advert does NOT say, which is usually the
more useful half.
Career matching (GET /{id}/match) feeds the master CareerProfile into the same
JobCvMatchService the CV builder uses, so one application scores identically
whichever surface asks. It returns the score, matched and missing skills, and
which experience and project entries are the evidence for each match.
All three are deterministic and own no data — no new table, no new column, and
nothing writes to the CareerProfile, a CvVariant, or the JobApplication. The AI
narrative stays where it already was, in AiWorkspaceService's job-analysis and
career-match modules, generated only when the user asks and versioned by the
append-only AiInteraction history. Opening a section costs nothing and changes
nothing.
Frontend adds Timeline, Analysis and Match sections to the workspace, sharing
one loader so loading, empty and error states are consistent. The deterministic
answer renders first, with the AI panel below it.
345 backend tests, 104 frontend tests, type check, production build all pass
locally.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -18,6 +18,8 @@ The workspace **owns no data and duplicates none**. It is an aggregate read plus
|
||||
|
||||
| Section | Backed by (existing system) |
|
||||
|---|---|
|
||||
| Timeline | `JobEvent` — interpreted, never replaced |
|
||||
| Analysis / Match | the advert and the master `CareerProfile`, read deterministically |
|
||||
| Checklist | `ApplicationChecklistItem` — completion state only, seeded from the readiness signals |
|
||||
| CV | Phase 4 `CvVariant` — a lens over the master `CareerProfile` |
|
||||
| Analysis / Match / Interview | Phase 5 `AiWorkspacePanel` + `AiInteraction` history |
|
||||
@@ -158,6 +160,68 @@ its own parallel checklist. It **projects** the persisted checklist:
|
||||
So the division is: **the checklist is the workflow the user drives, readiness is the calculation and
|
||||
health indicator derived from it.** One system, two projections.
|
||||
|
||||
## Application intelligence (Phase 5.3)
|
||||
|
||||
Three read-only reads that answer "how suitable is this job", "how does my experience match", "what am
|
||||
I missing", "what happened previously". All deterministic, all owned by nothing:
|
||||
|
||||
| Endpoint | Reads | Owns |
|
||||
|---|---|---|
|
||||
| `GET /{id}/timeline` | `JobEvent` | nothing |
|
||||
| `GET /{id}/analysis` | `JobApplication.Description` | nothing |
|
||||
| `GET /{id}/match` | `CareerProfile` + the advert | nothing |
|
||||
|
||||
No new table, no new column. `ApplicationTimelineService` and `ApplicationIntelligenceService` write
|
||||
nothing at all.
|
||||
|
||||
### The AI boundary
|
||||
|
||||
**The deterministic answer and the AI narrative are separate on purpose.**
|
||||
|
||||
- The three endpoints above never call the AI. Opening the Analysis or Match section costs nothing
|
||||
and cannot change anything — the page renders a computed answer.
|
||||
- The narrative lives where it already did: `AiWorkspaceService`'s `job-analysis` and `career-match`
|
||||
modules, reached from `AiWorkspacePanel`, generated only when the user asks.
|
||||
- Every generation is appended as an `AiInteraction` — that append-only history *is* the versioning,
|
||||
and the user restores, compares or deletes from it.
|
||||
- AI output is a suggestion. Nothing in this phase writes to the `CareerProfile`, a `CvVariant`, a
|
||||
cover letter, or the `JobApplication`. `Match_never_writes_to_the_career_profile` pins that.
|
||||
|
||||
So a user gets a trustworthy number for free, and pays for prose only when they want it.
|
||||
|
||||
### Timeline
|
||||
|
||||
`JobEvent` stays the source of historical truth; the service is an interpretation layer over it. Each
|
||||
row gains a readable summary (`("StatusChanged", "Applied", "Interview")` → "Moved from Applied to
|
||||
Interview"), a category (`lifecycle`, `stage`, `follow-up`, `communication`, `ai`) and a milestone
|
||||
flag. Events group by day with relative labels.
|
||||
|
||||
Milestones are the stages that mean something happened — applied, interview, offer, rejected,
|
||||
accepted, declined — plus creation and replies received. They are returned **unfiltered**: narrowing
|
||||
the detail below must not hide what actually happened.
|
||||
|
||||
An unrecognised future `Type` degrades to a humanised sentence rather than disappearing.
|
||||
|
||||
### Job analysis
|
||||
|
||||
Structured extraction from the advert, reusing the existing `SkillTagger` so the vocabulary matches
|
||||
the job importer and the CV match. Facts (role, company, location, employment type, seniority,
|
||||
salary), lists (technologies, skills, responsibilities, requirements, keywords, interview topics),
|
||||
and — deliberately — **what the advert does not say**, which is usually the more useful half.
|
||||
|
||||
With no advert saved it degrades to the fields on the application and says so.
|
||||
|
||||
### Career matching
|
||||
|
||||
Feeds the master `CareerProfile` into the same `JobCvMatchService` the CV builder uses, so one
|
||||
application scores identically whichever surface asks. Returns the score and band, matched and
|
||||
missing skills, and — the part that makes it actionable — **which experience and project entries are
|
||||
the evidence** for each matched keyword, ranked by hit count.
|
||||
|
||||
Suggestions describe what the user could change. They never change it.
|
||||
|
||||
With no profile it returns score 0 and asks the user to build one, rather than implying a bad match.
|
||||
|
||||
## Extension points
|
||||
|
||||
- **New section**: add to `WORKSPACE_SECTIONS` and render it; nav is data-driven.
|
||||
@@ -170,5 +234,7 @@ health indicator derived from it.** One system, two projections.
|
||||
1. ✅ Workspace foundation — route, nav shell, aggregate overview, next recommended action.
|
||||
2. ✅ Checklist and progress tracking — persisted items, auto-completion from the readiness signals,
|
||||
custom items, reordering, dismissal; readiness refactored into a projection of it.
|
||||
3. Timeline and activity history. 4. Job analysis. 5. Career matching. 6. CV integration.
|
||||
3. ✅ Application intelligence — timeline interpretation, structured job analysis, career matching
|
||||
(Phase 5.3, all three deterministic and read-only).
|
||||
4. CV integration.
|
||||
7. Cover letter workflow. 8. Documents. 9. Interview preparation. 10. Dashboard improvements.
|
||||
|
||||
Reference in New Issue
Block a user