Inspiration

What it doesWhat Inspired Us

Every hiring platform has the same blind spot: self-reported data with no verification layer. Anyone can claim "10 years of experience" or paste in a polished bio. LinkedIn endorsements are easy to game. Star ratings are meaningless without context.

We wanted to answer one question: what if credibility was measurable?

What We Built HireCred is a professional trust platform that assigns every candidate a HireCred Score (0–100) — a composite, AI-computed metric based on real evidence, not self-reporting.

The score is driven by:

16-point authenticity heuristics — catches fake names, disposable emails, sci-fi locations (Mars Colony, anyone?), impossible numeric claims ("999 years of experience"), far-future dates, and fictional company names Dead-link detection — every portfolio URL and proof signal is checked via HTTP + HTML title extraction; parked pages and "domain for sale" URLs are flagged even when they return HTTP 200 LLM-evaluated profile quality — Ollama (running a local qwen2.5:3b model) reads the full profile and scores it across 6 weighted criteria: $$\text{Score} = \text{Completeness}{18} + \text{Skill Alignment}{22} + \text{Portfolio Quality}{22} + \text{Writing Clarity}{8} + \text{Proof Signals}{18} + \text{CV Quality}{12}$$

Post-LLM adjustments — authenticity penalty (up to −60 pts), dead URL penalty (−6 per link, max −18), admin-approved report penalty (−12 pts) Hard score ceilings — risk_level = high → score capped at 15; risk_level = medium with significant penalty → capped at 35 Beyond scoring, HireCred includes intent-based natural language search ("find a reliable React developer with startup experience"), a Trust Leaderboard, AI-structured appreciations from clients, a report system, and a full admin panel for moderation.

How we built it

The backend is a FastAPI + SQLAlchemy (async) service backed by Neon PostgreSQL. All score computation runs entirely asynchronously — every API response returns in under 1 second, with Ollama processing in the background.

Search is SQL-first and scales to 1M+ profiles: we store a maintained tsvector column on the profiles table (kept fresh by a PostgreSQL trigger), index it with a plain GIN index for full-text search, and layer pg_trgm for typo-tolerant fuzzy fallback — all filtering happens in the database before any Python ranking logic runs.

The frontend is React 18 + TypeScript + TailwindCSS v4, using Zustand for auth state and TanStack Query for server state with automatic polling.

The AI layer is fully local — no API keys, no rate limits, no cost per request. Ollama runs qwen2.5:3b on-device. Every LLM-dependent feature has a non-LLM fallback so the platform stays functional even when the model is unavailable.

Challenges we ran into

The immutability trap: PostgreSQL rejects to_tsvector() in a functional GIN index expression because the text→regconfig cast is STABLE, not IMMUTABLE. We solved this by switching to a stored tsvector column maintained by a trigger — cleaner, faster, and no volatility constraint.

LLM timeout tuning: qwen2.5:3b on CPU takes 65–200 seconds for a 200-token response. We set the Ollama connection timeout to 120s (enough for the model) but cap the scoring call itself at 10s with an asyncio.wait_for — so slow hardware gracefully falls back to rule-based scoring without hanging the pipeline.

Race conditions at scale: Concurrent profile saves could create duplicate credibility_scores rows. We replaced the check-then-insert pattern with a single atomic PostgreSQL upsert (ON CONFLICT DO UPDATE), and added an asyncio.Lock to the leaderboard cache to prevent stampede on cache expiry.

Fake detection edge cases: The authenticity heuristics needed careful tuning — "wizard" in "Prompt Wizard" (real job title) versus "Full-Stack Ninja Wizard Rockstar" (boilerplate). We tuned penalties to be cumulative rather than binary, so borderline cases get warned, not blocked.

Accomplishments that we're proud of

Getting fake detection to actually work. The 16-point authenticity heuristic system went through many iterations. The hard part wasn't writing checks — it was calibrating when to penalize. "Ninja" in a bio is a red flag; "Ninja Framework" as a skill is legitimate. We built a cumulative penalty system with a hard cap so edge cases get docked points without being unfairly buried, and we're genuinely proud of how well it catches synthetic profiles without false-positiving real ones.

Zero-cost, zero-latency AI. Running qwen2.5:3b fully locally via Ollama means no API keys, no per-request billing, no rate limits, and no data leaving the machine. The entire AI pipeline — profile scoring, fraud detection, appreciation extraction, search intent parsing — runs on commodity hardware. Every LLM call has a non-LLM fallback, so the platform degrades gracefully instead of breaking.

Search that actually scales. We're proud that search isn't just a LIKE '%query%' scan. The stored tsvector column maintained by a PostgreSQL trigger, combined with a three-stage fallback chain (FTS → pg_trgm fuzzy → ILIKE), means the search architecture could handle a million profiles without a rewrite. Most hobby projects skip this entirely.

Fully non-blocking UX. Every write operation — profile save, CV upload, proof signal add — returns in under one second. Score computation, URL checking, and LLM inference all happen in the background. Users never stare at a loading spinner waiting for AI. This required careful use of asyncio.create_task, atomic upserts, and a semaphore to cap concurrent URL checks — and it all works cleanly.

A trust system that's hard to game. Score ceilings tied to risk level (high → max 15 pts), dead-link penalties that survive HTTP 200 responses by reading page titles, and admin-approved reports that permanently dock scores — together these make the score meaningful rather than just a vanity metric.

What we learned

LLMs are great at qualitative judgment, terrible at low-latency tasks. Structured scoring (does this bio sound professional? do these skills match this title?) is exactly where LLMs shine — they caught things we never would have hardcoded. But they're too slow for synchronous user-facing responses. The lesson was architectural: use the LLM for evaluation, use the database for retrieval, and never put them on the same code path as an HTTP response.

PostgreSQL is far more powerful than most people use it for. We went from full-table Python scans to stored tsvector columns, triggers, GIN indexes, pg_trgm similarity queries, and atomic ON CONFLICT DO UPDATE upserts — all in a single project. PostgreSQL can do in one query what most tutorials suggest solving with a caching layer.

Non-blocking validation changes everything UX-wise. Early versions blocked saves on validation errors. Users hated it. Switching to warnings-only (issues become score deductions, not hard errors) made the product feel trusting and fast — and actually surfaced more honest data because users didn't game inputs to pass validation.

Fake detection is a calibration problem, not a rules problem. Writing a rule that fires on "ninja" is easy. Writing a rule that fires on "ninja" in the right context, with the right weight, that compounds correctly with other signals — that's the real work. We learned that fraud heuristics need to be tuned empirically, not written theoretically.

Async Python has sharp edges. asyncio.create_task fires and forgets — which is exactly what we wanted for background scoring. But it also means exceptions silently disappear unless you wrap tasks in try/except and log them. We learned this the hard way when scores silently stopped updating and the reason was buried in an unhandled coroutine exception.

What's next for Hirecred

GitHub OAuth ownership verification. Right now anyone can paste a GitHub link as a proof signal. The next step is actual OAuth verification — confirm the user owns the GitHub account they're linking. Same pattern for portfolio domains (DNS TXT record or file-based proof of possession). This turns proof signals from "I claim this" into "I proved this."

Cross-profile duplicate detection. The current authenticity system checks for copy-paste within a single profile. The natural extension is cross-profile detection — flagging bios that are suspiciously similar to other profiles using text embeddings or fingerprinting. This catches account farms and wholesale identity theft.

Score breakdown UI. The score is currently a single number. We want to show the per-criterion breakdown (completeness / skill alignment / portfolio quality / etc.) so candidates know exactly where to improve, and hirers can weight criteria that matter to them.

Hirer-side features. Saved candidate lists, post-hire ratings from the hirer's perspective, and advanced search filters (score range, location, experience level). Right now HireCred is candidate-centric; we want to make it equally useful for the people doing the hiring.

Embeddings-based semantic search. The current search is keyword-based (with fuzzy fallback). The next evolution is replacing the Ollama intent parser with a local embedding model (e.g. nomic-embed-text via Ollama) — so "someone who has shipped products under tight deadlines" finds candidates with relevant experience even if none of those exact words appear in their profile.

Email/in-app notifications. New appreciation received, score changed significantly, report status updated — none of these are surfaced proactively right now. A lightweight notification system would close the feedback loop and bring users back to the platform.

Built With

Share this project:

Updates