>jose_sosa_
Back to projects
My product

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.

Skillpato mascot

// how it works

The core loop

Everything in the product feeds one habit-forming loop — practice that turns into proof, then into opportunities.

1

Practice

Answer a job-ready question, timed and server-graded.

2

Feedback

Instant right/wrong reveal with an explanation.

3

Learn

Miss it? A concept article is linked right there.

4

Score

Your per-skill ability score and XP update live.

5

Compete

Weekly leagues + a global ladder rank you.

6

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
Next.js · Supabase · Vercel
◀── /api/v1 (HTTP) ──▶no shared DB · keys + SSM
skillpato-generator
Python · AWS Lambda · SAM

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

Next.js 16TypeScriptReact 19Tailwind v4shadcn/uiPWA

Data

SupabasePostgreSQLPrisma 7Zod

AI engine

Python 3.12AWS LambdaAWS SAMEventBridgeOpenAIPydantic

Ops & infra

VercelWeb Push (VAPID)SSM Parameter StoreS3SentryMixpanelGA4

My role

Creator & sole engineer — product, architecture, full-stack, mobile-ready, cloud & AI pipeline