Skillpato
A job-readiness platform for tech professionals
Practice job-ready questions → get instant feedback → learn from an article on a miss → watch your skill score and XP climb → compete → land better roles.
// how it works
The core loop
Everything in the product feeds one habit-forming loop — practice that turns into proof, then into opportunities.
Practice
Answer a job-ready question, timed and server-graded.
Feedback
Instant right/wrong reveal with an explanation.
Learn
Miss it? A concept article is linked right there.
Score
Your per-skill ability score and XP update live.
Compete
Weekly leagues + a global ladder rank you.
Get hired
A verified skill profile + matched job feed.
// architecture
Two decoupled systems
Skillpato is two independently-deployed systems that only ever talk over a versioned HTTP API — no shared database, no shared code. That boundary keeps the AI content engine from ever touching user data, and lets each side scale and deploy on its own.
skillpato-app
The platform · Next.js on Vercel
- Next.js 16 (App Router), TypeScript, Tailwind v4 + shadcn/ui.
- Supabase (Postgres + Auth) with Prisma 7 as the typed data layer.
- Server-first: pages are thin, logic lives in pure lib modules, DB access is behind a repo interface.
- Bilingual (EN/ES) via next-intl with `/es` routing.
- Observability with Mixpanel + GA4 and Sentry.
skillpato-generator
The AI engine · Python on AWS Lambda
- Python 3.12 packaged with AWS SAM — eleven Lambdas on EventBridge schedules.
- Generates question + article drafts with an LLM (GPT-4o-mini) behind a provider-agnostic client.
- Reads content gaps from the app's API, posts drafts back as PENDING_REVIEW — never publishes.
- An independent verifier LLM re-solves each question; ambiguous ones are dropped before posting.
- Also runs cron jobs: league rollover, season reset, job ingest (JSearch), freshness sweep, billing reconcile, and the hourly push-reminder fan-out.
- Zero database access — its only contract is the HTTP API + secrets in SSM Parameter Store.
// the hard part
Three separate scoring currencies
The whole product rests on one idea: ability, engagement and competition are different things and must never be mixed — otherwise leaderboard grinding would corrupt the skill signal employers rely on.
Skill Score
Ability — the employer moat
A per-topic ability score using difficulty-weighted accuracy with a confidence-faded prior, so a tiny sample can't read as mastery. Credible early, un-inflatable, and a true 100 only comes from a sustained perfect record.
XP & Levels
Engagement
Earned only on correct answers (never docked), driving levels and weekly leagues. Kept strictly separate from ability so leaderboard grinding can't corrupt the skill signal.
Ladder Rating
Competition
A Glicko-1 'question-as-opponent' rating that moves both ways and decays with inactivity, shown conservatively so a fresh account can't fake a top rank. A sharp newcomer can overtake a resting veteran.
// engineering decisions
How I built it to stay trustworthy
A skill score is only worth something if it can't be gamed. These are the invariants that protect it.
Answer-hiding as an invariant
The correct answer never reaches the browser before you commit. Options are mapped to DTOs that strip `isCorrect`/`feedback`; correctness is decided server-side and only returned in the reveal.
Server-authoritative fair play
Each served question carries an HMAC-signed token with the serve timestamp. The 60s limit is enforced on submit — pausing JS to 'stop the clock' or leaving the tab both grade as a miss.
AI drafts, human gate
The generator only ever posts PENDING_REVIEW drafts, each passed through a second verifier LLM. A human approves before anything publishes — which is exactly what lets a cheap model be good enough.
Pure, testable engines
Scoring, ladder rating, streaks and tier resolution are pure functions with no DB — unit-tested in isolation. The data layer sits behind a repo interface (mock ↔ Prisma).
Provider-agnostic AI
Every model call goes through one `LLMClient` interface. Swapping GPT for Gemini or Claude is a single new class — the concrete provider is named in exactly one place.
Privacy by construction
Public profiles are opt-in, pseudonymous and PII-free — the public DTO can never serialize a real name, email or resume. Resumes stream through an owner-checked route, never a public URL.
Retention by web push — at $0
An installable PWA sends timezone-correct reminders only to users who actually need them — a live streak, their local evening, and haven't practiced yet that day. Standard Web Push (VAPID), so the browsers' own services deliver it free; an hourly Lambda just triggers the fan-out. Consent is native, opt-out is per-type, and dead subscriptions self-clean on a 410.
// what's in it
Feature set
- Timed practice with single & multi-select and code questions
- Bilingual learn articles (EN/ES twins), auto-linked on a miss
- Global ladder + weekly promotion/relegation leagues
- Skill-matched remote job feed (ingested from JSearch)
- Public shareable profiles with OG cards & LinkedIn sharing
- Recruiter assessments from a walled, leak-proof question pool
- Badges, daily streaks with freezes, and daily goals
- Free/Pro tiers with entitlement reconciliation
- Installable PWA with opt-in web-push reminders (daily streak & league deadline)
// tech stack
Built with
Platform
Data
AI engine
Ops & infra
My role
Creator & sole engineer — product, architecture, full-stack, mobile-ready, cloud & AI pipeline