Personal Project
Job Market Intelligence Agent
Python · Claude API · Telegram · Apify · Serper
Job Market Intelligence Agent
A Telegram-based AI agent that turns a job market scan into a ranked, CV-scored briefing in under 90 seconds
Personal Project · Live in Production
90 sec
Per Full Run
From trigger to ranked briefing
7
Python Modules
Scrape, filter, rank, score, synthesize, format, deliver
3
Data Sources
LinkedIn, Bayt.com via Apify, web search
~$0.11
Cost Per Run
Claude API and Serper combined
The Problem
The Problem
Tracking senior marketing roles across job boards manually takes 2 to 3 hours per week. Results are inconsistent, employer quality is mixed, salary data is rarely disclosed, and there is no way to score role fit against a specific profile without reading every job description individually.
The question a senior marketing leader actually needs answered, "which of these roles should I apply for, and why?", had no fast, structured answer. This agent provides one on demand.
What It Does
What It Does
One Telegram command triggers the full pipeline. The agent handles scraping, filtering, ranking, CV scoring, and synthesis automatically.
Trigger
A single Telegram command starts the pipeline. The agent runs entirely in the background on a remote server, no local machine required.
Scrape and Filter
Three data sources run in sequence: LinkedIn job listings via Apify, regional MENA listings from Bayt.com scraped via a dedicated Apify actor, and web search signals via Serper. Only confirmed senior-marketing roles from verified employers are passed to the next stage. Aggregators, job boards, and non-target titles are filtered out.
Rank and Score
Confirmed employers are ranked by opening count and company size (SME, Scaleup, Enterprise). A consulting opportunity score (1 to 10) is calculated based on SME and scaleup density and fractional role signals. Week-on-week diff logic identifies new employers since the last run.
CV Fit Scoring
Each confirmed role is scored against a loaded CV using Claude with forced tool use and a JSON schema, guaranteeing structured output every time. Each role receives a fit score (1 to 10), a recommendation (APPLY, MAYBE, or SKIP), up to three match reasons, and up to two gap flags.
Synthesize and Deliver
All data is synthesized by Claude into a structured Telegram briefing. The top 5 CV-matched roles are appended as a ranked shortlist. The full briefing is split to respect Telegram's 4096-character message limit and delivered within 90 seconds of the trigger command.
Sample Briefing Output
Sample Briefing Output
This is what a typical run delivers directly in Telegram.
System Architecture
System Architecture
Telegram Bot
/report command triggers the pipeline
Pipeline
job_scraper.py
salary_signals.py
company_tracker.py
consulting_score.py
cv_scorer.py
synthesizer.py
formatter.py
Apify
LinkedIn job scraper
curious_coder/linkedin-jobs-scraper
Serper.dev
Web search signals
News · Google · Job listings
Bayt.com
Regional MENA job listings
Scraped via Apify actor · MENA-focused
Claude API
Synthesis and CV scoring
claude-opus-4-8 · Forced tool use
Hetzner VPS
Production deployment
Ubuntu · systemd · Auto-restart · Zero Docker
One Telegram command. Seven Python modules. Briefing delivered in under 90 seconds.
Technical Depth
Technical Depth
Built across 8 sessions from scaffold to live production deployment. Key engineering decisions below.
Forced Tool Use for CV Scoring
Claude scores each role using a strict JSON schema enforced via forced tool use, guaranteeing structured output (fit_score, recommendation, match_reasons, gap_flags) every time, with no free-form text that could break downstream formatting.
Confirmed-Only Data Quality
Only roles from verified employers (real company name, known size, LinkedIn source) reach the briefing. Aggregators, job boards, hashtag bios, and unknown fragments are filtered at the company_tracker stage, keeping signal-to-noise high even when raw scrape volume is large.
Mock and Live Mode Separation
Mock runs save to a dedicated outputs/mock/ namespace and diff only against mock history. Live runs never see mock data. This prevents test runs from corrupting the week-on-week baseline, a real production issue caught and fixed during development.
Week-on-Week Diff Logic
Every run saves a timestamped JSON payload. The next run loads the most recent payload and diffs confirmed employer names, surfacing new entrants since the last run without requiring a database.
Systemd Deployment with Auto-Restart
The agent runs as a native systemd service on a Hetzner VPS with Restart=always, start-limit backoff, and crash-loop protection. Survives reboots. Coexists with a separate Dockerised marketing intelligence bot on the same server with zero interference.
One-Command Deploy
A single deploy.ps1 (Windows) or deploy.sh (Linux/macOS) script stages the project with rsync excludes, repairs the Python venv if broken, installs dependencies, installs the systemd unit, and restarts the service, with no manual SSH steps required.
What I Built
What I Built
Every part of this system was designed, built, debugged, and deployed by me using Claude Code as the development environment. Built from scaffold to live production across 8 sessions.
Designed the full pipeline architecture across 7 Python modules
Built and wired three data sources with graceful degradation per source
Engineered CV fit scoring using forced tool use and JSON schema validation
Built the week-on-week diff system using local JSON run history
Designed the consulting opportunity score with data-driven ceilings and calibrated thresholds
Deployed as a production systemd service on Hetzner with one-command deploy scripts
Debugged three live production issues: Python 3.14 event loop regression, Apify client API change, and mock/live namespace collision
Manual job market research used to take hours. Now it takes 90 seconds and delivers a ranked shortlist with a recommendation for every role.