# vesaias/jobnavigator architecture

> Architecture diagram of the GitHub repository vesaias/jobnavigator, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-06-24). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/vesaias/jobnavigator
- Repository: https://github.com/vesaias/jobnavigator

## Overview

1. JobNavigator is a self-hosted, three-part system: a Python backend API, a React/Tailwind frontend, and a Chrome extension for passive LinkedIn/job capture. PostgreSQL is the persistent store, and Docker Compose/Caddy/nginx are used to run and expose the stack locally or self-hosted.

2. The backend is the orchestration center. `backend/main.py` and the FastAPI routes under `backend/api/` expose the domain surface for jobs, companies, searches, resumes, applications, cover letters, persona, and settings. These routes are the main boundary between the UI/extension and the core job automation logic.

3. Job discovery is split across multiple ingestion sources. The scraper subsystem handles career pages via Playwright, plus dedicated ATS adapters in `backend/scraper/ats/` for Workday, Greenhouse, Lever, Ashby, Oracle HCM, Phenom, TalentBrew, Rippling, SmartRecruiters, Google, Meta, and generic/custom pages. Separate source adapters cover aggregators and special inputs like JobSpy, Levels.fyi, Jobright, LinkedIn personal collections, and the Chrome extension.

4. Scraping is not just fetching pages; it has a processing pipeline in `backend/scraper/_shared/` and `backend/scraper/orchestrator.py`. Shared concerns include browser control, URL normalization, URL safety, deduplication, and filtering, which turn many heterogeneous sources into one normalized job record model before it reaches the database.

5. After ingestion, the backend enriches and filters jobs. The analyzer layer computes salary extraction, H-1B/company-labor lookup, CV scoring, cover-letter generation, and LLM-backed analysis. This is where job data becomes actionable: matching against resumes/persona, generating reports, and preparing tailored documents.

6. The LLM boundary is abstracted so multiple providers can be swapped without changing the rest of the system. `backend/analyzer/llm_client.py` plus cost/logging helpers support Anthropic/Claude, OpenAI, Ollama, and OpenAI-compatible providers, while tests indicate prompt caching and call logging are first-class concerns.

7. Resume and cover-letter generation are template-driven. The `backend/resume_templates/` and `backend/cover_letter_templates/` directories contain multiple auto-discovered themes, each with metadata and Jinja templates. The backend renders these into exportable documents, and the README indicates PDF generation is done through Playwright.

8. Tracking and follow-up are separate async subsystems. `backend/email_monitor/` connects to Gmail via OAuth and parses replies to update application state, while `backend/notifier/telegram.py` sends alerts, daily digests, and scrape-health notifications. `backend/job_monitor.py` and `backend/scheduler.py` coordinate periodic jobs such as scraping, email checks, cleanup, and other maintenance tasks.

9. The frontend is a single-page app built with React 18, Vite, Tailwind, and Recharts. Its components map closely to backend domains: job feed, application board, company manager, resume builder, cover-letter builder, persona, search manager, settings, and stats. The frontend consumes the backend API and is responsible for interactive review, filtering, drag-and-drop status changes, and editing content.

10. The Chrome extension is a separate capture surface, not just a UI add-on. Under `extension/`, the content script and background service worker support passive LinkedIn capture and “send job from any page” behavior, giving the system a browser-side ingestion path that bypasses the normal scraper when needed.

11. The data model is centered on jobs, companies, searches, resumes, applications, and personas, with status transitions and scoring artifacts persisted in PostgreSQL. The tests show important behaviors like deduplication, caching, best-score selection, job-run targeting, company alias filtering, and transition history, which suggests the app treats these as durable workflow entities rather than ephemeral search results.

12. External service boundaries matter to the architecture: job boards/ATS pages, MyVisaJobs for H-1B data, Gmail for inbox monitoring, Telegram for notifications, and LLM providers for scoring/tailoring. The system is designed so each of these can fail independently without collapsing the core app, which is why scraping, AI, tracking, and messaging are separated into distinct backend modules.

Main entry points: [`backend/models`](https://github.com/vesaias/jobnavigator/tree/main/backend/models) (Data model), [`backend/api`](https://github.com/vesaias/jobnavigator/tree/main/backend/api) (Domain routes), [`backend/scraper/orchestrator.py`](https://github.com/vesaias/jobnavigator/blob/main/backend/scraper/orchestrator.py) (Ingest orchestrator), [`backend/analyzer`](https://github.com/vesaias/jobnavigator/tree/main/backend/analyzer) (Analyzer)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_repo_core["Core backend"]
  node_backend_main["API entry<br/>FastAPI app<br/>[main.py]"]
  node_backend_routes["Domain routes<br/>HTTP routes"]
  node_backend_models[("Data model<br/>DB models")]
  node_scraper_orchestrator{{"Ingest orchestrator<br/>scrape pipeline<br/>[orchestrator.py]"}}
  node_scraper_shared["Shared scrape<br/>scrape utils"]
  node_scraper_ats["ATS adapters<br/>ATS parsers"]
  node_scraper_sources["Source adapters<br/>source inputs"]
  node_analyzer{{"Analyzer<br/>job enrichment"}}
  node_llm_client["LLM boundary<br/>provider adapter<br/>[llm_client.py]"]
  node_doc_templates["Doc templates<br/>Jinja templates"]
  node_email_monitor["Email monitor<br/>gmail watcher"]
  node_notifier["Notifier<br/>alerts"]
  node_scheduler{{"Scheduler<br/>periodic jobs<br/>[scheduler.py]"}}
end

subgraph group_repo_ui["Frontend"]
  node_frontend_app["SPA shell<br/>React app<br/>[App.jsx]"]
  node_frontend_views["Workflows UI<br/>feature views"]
end

subgraph group_repo_capture["Browser capture"]
  node_extension_bg["Extension core<br/>Chrome extension<br/>[background.js]"]
  node_extension_ui["Extension UI<br/>popup UI<br/>[popup.html]"]
end

subgraph group_repo_runtime["Runtime and deploy"]
  node_docker_stack["Compose stack<br/>deployment<br/>[docker-compose.yml]"]
  node_edge_proxy["Edge proxy<br/>reverse proxy"]
end

node_backend_main -->|"mounts"| node_backend_routes
node_backend_routes -->|"reads/writes"| node_backend_models
node_backend_routes -->|"triggers"| node_scraper_orchestrator
node_scraper_orchestrator -->|"uses"| node_scraper_shared
node_scraper_orchestrator -->|"dispatches"| node_scraper_ats
node_scraper_orchestrator -->|"ingests"| node_scraper_sources
node_scraper_shared -->|"produces"| node_backend_models
node_scraper_ats -->|"produces"| node_backend_models
node_scraper_sources -->|"produces"| node_backend_models
node_backend_routes -->|"invokes"| node_analyzer
node_analyzer -->|"calls"| node_llm_client
node_analyzer -->|"renders"| node_doc_templates
node_analyzer -->|"updates"| node_backend_models
node_email_monitor -->|"updates"| node_backend_models
node_notifier -->|"reads"| node_backend_models
node_scheduler -->|"runs"| node_scraper_orchestrator
node_scheduler -->|"runs"| node_email_monitor
node_scheduler -->|"runs"| node_notifier
node_frontend_app -->|"consumes"| node_backend_routes
node_frontend_views -->|"composes"| node_frontend_app
node_extension_bg -->|"feeds"| node_scraper_sources
node_extension_ui -->|"sends jobs"| node_extension_bg
node_docker_stack -->|"runs"| node_backend_main
node_docker_stack -->|"runs"| node_frontend_app
node_docker_stack -->|"exposes"| node_edge_proxy

click node_backend_main "https://github.com/vesaias/jobnavigator/blob/main/backend/main.py"
click node_backend_routes "https://github.com/vesaias/jobnavigator/tree/main/backend/api"
click node_backend_models "https://github.com/vesaias/jobnavigator/tree/main/backend/models"
click node_scraper_orchestrator "https://github.com/vesaias/jobnavigator/blob/main/backend/scraper/orchestrator.py"
click node_scraper_shared "https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/_shared"
click node_scraper_ats "https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/ats"
click node_scraper_sources "https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/sources"
click node_analyzer "https://github.com/vesaias/jobnavigator/tree/main/backend/analyzer"
click node_llm_client "https://github.com/vesaias/jobnavigator/blob/main/backend/analyzer/llm_client.py"
click node_doc_templates "https://github.com/vesaias/jobnavigator/tree/main/backend/resume_templates"
click node_email_monitor "https://github.com/vesaias/jobnavigator/tree/main/backend/email_monitor"
click node_notifier "https://github.com/vesaias/jobnavigator/tree/main/backend/notifier"
click node_scheduler "https://github.com/vesaias/jobnavigator/blob/main/backend/scheduler.py"
click node_frontend_app "https://github.com/vesaias/jobnavigator/blob/main/frontend/src/App.jsx"
click node_frontend_views "https://github.com/vesaias/jobnavigator/tree/main/frontend/src/components"
click node_extension_bg "https://github.com/vesaias/jobnavigator/blob/main/extension/background.js"
click node_extension_ui "https://github.com/vesaias/jobnavigator/blob/main/extension/popup.html"
click node_docker_stack "https://github.com/vesaias/jobnavigator/blob/main/docker-compose.yml"
click node_edge_proxy "https://github.com/vesaias/jobnavigator/tree/main/Caddyfile"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_backend_main,node_backend_routes,node_backend_models,node_scraper_orchestrator,node_scraper_shared,node_scraper_ats,node_scraper_sources,node_analyzer,node_llm_client,node_doc_templates,node_email_monitor,node_notifier,node_scheduler toneBlue
class node_frontend_app,node_frontend_views toneAmber
class node_extension_bg,node_extension_ui toneMint
class node_docker_stack,node_edge_proxy toneRose
```

## Components

### Core backend

Python API, ingestion, analysis, persistence, and background automation

- [API entry](https://github.com/vesaias/jobnavigator/blob/main/backend/main.py): FastAPI app · Application startup and server wiring · `backend/main.py`
- [Domain routes](https://github.com/vesaias/jobnavigator/tree/main/backend/api): HTTP routes · Jobs, companies, searches, resumes, applications, cover letters, persona, settings · `backend/api`
- [Data model](https://github.com/vesaias/jobnavigator/tree/main/backend/models): DB models · Persistent entities and database access · `backend/models`
- [Ingest orchestrator](https://github.com/vesaias/jobnavigator/blob/main/backend/scraper/orchestrator.py): scrape pipeline · Coordinates source capture, normalization, deduplication, and filtering · `backend/scraper/orchestrator.py`
- [Shared scrape](https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/_shared): scrape utils · Browser control, URL safety, normalization, deduplication, and analysis helpers · `backend/scraper/_shared`
- [ATS adapters](https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/ats): ATS parsers · Workday, Greenhouse, Lever, Ashby, Oracle HCM, Phenom, TalentBrew, Rippling, SmartRecruiters, Google, Meta, and generic pages · `backend/scraper/ats`
- [Source adapters](https://github.com/vesaias/jobnavigator/tree/main/backend/scraper/sources): source inputs · Company pages, JobSpy, Levels.fyi, Jobright, LinkedIn personal collections, and extension feeds · `backend/scraper/sources`
- [Analyzer](https://github.com/vesaias/jobnavigator/tree/main/backend/analyzer): job enrichment · Salary extraction, H-1B lookup, CV scoring, cover-letter generation, and LLM-backed analysis · `backend/analyzer`
- [LLM boundary](https://github.com/vesaias/jobnavigator/blob/main/backend/analyzer/llm_client.py): provider adapter · Anthropic, OpenAI, Ollama, and OpenAI-compatible calls with logging and cost tracking · `backend/analyzer/llm_client.py`
- [Doc templates](https://github.com/vesaias/jobnavigator/tree/main/backend/resume_templates): Jinja templates · Resume and cover-letter theme sets rendered into exportable documents · `backend/resume_templates`
- [Email monitor](https://github.com/vesaias/jobnavigator/tree/main/backend/email_monitor): gmail watcher · Gmail OAuth, reply parsing, and application-state updates · `backend/email_monitor`
- [Notifier](https://github.com/vesaias/jobnavigator/tree/main/backend/notifier): alerts · Telegram alerts, digests, and scrape-health notifications · `backend/notifier`
- [Scheduler](https://github.com/vesaias/jobnavigator/blob/main/backend/scheduler.py): periodic jobs · Runs scraping, email checks, cleanup, and maintenance · `backend/scheduler.py`

### Frontend

React/Vite user interface

- [SPA shell](https://github.com/vesaias/jobnavigator/blob/main/frontend/src/App.jsx): React app · Vite/React/Tailwind front end and shared API client · `frontend/src/App.jsx`
- [Workflows UI](https://github.com/vesaias/jobnavigator/tree/main/frontend/src/components): feature views · Job feed, application board, company manager, resume builder, cover-letter builder, persona, search manager, settings, and stats · `frontend/src/components`

### Browser capture

Chrome extension for LinkedIn and page-based job capture

- [Extension core](https://github.com/vesaias/jobnavigator/blob/main/extension/background.js): Chrome extension · Background service worker and content script for passive LinkedIn capture · `extension/background.js`
- [Extension UI](https://github.com/vesaias/jobnavigator/blob/main/extension/popup.html): popup UI · Popup surface for sending jobs from any page · `extension/popup.html`

### Runtime and deploy

Local/self-hosted packaging and edge exposure

- [Compose stack](https://github.com/vesaias/jobnavigator/blob/main/docker-compose.yml): deployment · Docker Compose, backend/frontend images, and local self-hosted runtime · `docker-compose.yml`
- [Edge proxy](https://github.com/vesaias/jobnavigator/tree/main/Caddyfile): reverse proxy · Caddy/nginx exposure for the hosted stack · `Caddyfile`

## Connections

- API entry → Domain routes: mounts · Registers API surface on startup
- Domain routes → Data model: reads/writes · Persists jobs, companies, searches, resumes, applications, and persona state
- Domain routes → Ingest orchestrator: triggers · API actions can start or manage ingestion workflows
- Ingest orchestrator → Shared scrape: uses · Normalizes URLs, controls browser sessions, deduplicates, and filters results
- Ingest orchestrator → ATS adapters: dispatches · Routes career-page scraping to the appropriate ATS adapter
- Ingest orchestrator → Source adapters: ingests · Pulls jobs from aggregators, personal collections, and extension feeds
- Shared scrape → Data model: produces · Converts heterogeneous captures into normalized persisted records
- ATS adapters → Data model: produces · Stores normalized job listings
- Source adapters → Data model: produces · Stores normalized job listings from external sources
- Domain routes → Analyzer: invokes · Runs scoring, tailoring, and job enrichment on demand
- Analyzer → LLM boundary: calls · Uses provider-agnostic LLM operations for analysis and document generation
- Analyzer → Doc templates: renders · Builds resumes and cover letters from theme templates
- Analyzer → Data model: updates · Writes scores, extracted data, and generated artifacts
- Email monitor → Data model: updates · Applies reply parsing to application status
- Notifier → Data model: reads · Builds alerts and digests from persisted workflow state
- Scheduler → Ingest orchestrator: runs · Schedules recurring ingestion jobs
- Scheduler → Email monitor: runs · Schedules mailbox polling and reply processing
- Scheduler → Notifier: runs · Schedules digests and health notifications
- SPA shell → Domain routes: consumes · Interactive client for the API boundary
- Workflows UI → SPA shell: composes · Feature screens inside the SPA
- Extension core → Source adapters: feeds · Browser-side LinkedIn capture becomes an ingestion source
- Extension UI → Extension core: sends jobs · Popup forwards pages/jobs to the background worker
- Compose stack → API entry: runs · Containerizes the API service
- Compose stack → SPA shell: runs · Containerizes the UI service
- Compose stack → Edge proxy: exposes · Routes local/self-hosted traffic through the proxy layer
