Career Signal Engine is a deterministic, explainable career matching engine. It compares a professional profile with job postings, ranks the jobs, explains the match, decides radar visibility, processes user feedback, and shows how personalization changes the result while preserving the original base score.
The project is written as a portfolio-ready technical case study for a future Job Radar product. It focuses on architecture, product judgment, explainability, and controlled automation.
Job matching tools often fail in two ways:
- They hide or rank jobs without explaining why.
- They jump directly to opaque AI or automation before the product has clear rules, controls, or user trust.
This project takes the opposite approach. It starts with deterministic scoring, makes every decision inspectable, keeps human review in the loop, and turns user behavior into explicit preference signals before any automation is allowed to act on it.
This is not a scraper. It does not fetch jobs from websites or external services.
This is not a black-box AI agent. It does not call AI APIs, browse, apply to jobs, or make autonomous decisions.
This is not a job board. It has no accounts, persistence, frontend, search index, or live job marketplace.
It is a modular scoring and signal engine designed to power a future dashboard or API.
- Models professional profiles and job postings with dataclasses.
- Scores jobs across role, seniority, location, skills, and work mode.
- Produces structured strengths, gaps, risks, and recommendations.
- Separates recommendation from radar visibility.
- Explains why a job is
top_match,review, orhidden_by_radar. - Converts user actions into transparent
PreferencePackageobjects. - Applies preference packages to produce adaptive scores while preserving the original base score.
- Provides a scenario CLI that demonstrates the full product loop.
flowchart LR
Profile["CareerProfile"] --> Analyzer["Analyzer"]
Job["JobPosting"] --> Analyzer
Analyzer --> Scoring["Core Scoring"]
Analyzer --> Gaps["Gap Detection"]
Analyzer --> Rec["Recommendation"]
Analyzer --> Discard["Explainable Discarding"]
Scoring --> Result["AnalysisResult"]
Gaps --> Result
Rec --> Result
Discard --> Result
Result --> Actions["User Actions"]
Actions --> Signals["User Signal Engine"]
Signals --> Package["PreferencePackage"]
Result --> Adaptive["Adaptive Scoring"]
Package --> Adaptive
Adaptive --> AdaptiveResult["AdaptiveAnalysisResult"]
Result --> Scenario["Scenario Demo CLI"]
Package --> Scenario
AdaptiveResult --> Scenario
The engine is intentionally split into small modules:
scoring.py: deterministic category scoringgaps.py: missing skill, location, work mode, and seniority issuesrecommendations.py: product action policydiscard_policy.py: radar visibility and discard reasonsuser_signal_engine.py: user actions to preference packagesadaptive_scoring.py: personalized scoring with base score preservedscenario_demo.py: recruiter-friendly end-to-end demo
The project was built incrementally:
- CP1 Core Engine: deterministic scoring, models, demo data, CLI, tests, docs
- CP2 Explainable Discarding: visibility states, structured discard reasons, recovery actions
- CP3 User Signal Engine: user actions converted into preference packages
- CP4 Adaptive Scoring: preference packages applied to analysis results with capped score adjustments
- CP5 Scenario Demo CLI: narrative product loop showing initial ranking, feedback, preferences, and before/after scoring
- CP6 Portfolio Polish: documentation, architecture narrative, recruiter summary, roadmap, and quality checklist
- Deterministic before autonomous
- Explainable before magical
- Human review before automation
- Visibility is separate from recommendation
- User feedback becomes explicit preference packages
- Base score is preserved even when adaptive scoring is applied
Create a virtual environment:
cd /root/career-signal-engine
python -m venv .venv
source .venv/bin/activateInstall the project:
pip install -e ".[dev]"Run the ranked demo:
career-signal-engineRun the full scenario demo:
career-signal-engine scenarioRun tests:
pytestRanked demo:
1. Senior QA Automation Engineer at Northstar Payments (98.67) [apply] [top_match]
scores: role=100, seniority=100, location=100, skills=93, work_mode=100
5. Senior Quality Assurance Engineer at LedgerLine Analytics (71.67) [save_for_review] [review]
radar reason: location_mismatch (soft) - Job location 'Chicago, IL' is outside preferred locations.
8. Manual QA Analyst at Blue Harbor Insurance (32.57) [hide] [hidden_by_radar]
radar reason: seniority_mismatch (hard) - Profile seniority is senior; job seniority is junior.
Scenario demo excerpt:
=== Generated Preference Package ===
- allow_long_commute_for_strong_matches
- prefer_qa_automation_roles
- reduce_location_mismatch_weight
- reject_junior_roles
=== Adaptive Scoring Comparison ===
Senior Quality Assurance Engineer at LedgerLine Analytics
base_score=71.67
adaptive_score=86.67
original_visibility=review
adaptive_visibility=top_match
Career matching affects real decisions: where a person spends time, which opportunities they consider, and which roles they ignore. A system that hides jobs silently or changes recommendations without traceability is hard to trust.
This project keeps every decision inspectable:
- Base score and adaptive score are both visible.
- Hidden jobs include discard reasons and recovery actions.
- User feedback becomes named signals, not invisible model state.
- Hard rules can prevent over-personalization.
- Adaptive adjustments are capped and explained.
- Dashboard/API integration
- Job Radar frontend
- Importing pasted job descriptions
- Optional AI-assisted parsing
- Multi-profile support
- Deployment as demo SaaS
- Agent Ops / Safety Layer for controlled automation
- Tests passing
- No credentials
- No scraping
- Deterministic logic
- Explainable outputs
- Portfolio-ready docs