← retour aux projets

CS.04, CASE STUDY · CÔTÉ SYSTÈME

JobDeck

Freelance

|←DASHBOARD · PIPELINE & CONVERSION · DONNÉES DE DÉMO→|
|←PARCOURS ATOMIQUE · UN VÉCU, PLUSIEURS ANGLES→|

CONTEXTE

Piloter une recherche d'emploi comme un pipeline commercial : sourcing, suivi, relances, statistiques, en temps réel.

MON RÔLE

Produit personnel conçu et développé intégralement, de l'architecture Convex à l'extension navigateur.

RÉALISATIONS

  • Modèle atomique des réalisations : un fait vérifié, plusieurs formulations par angle, recomposé par offre
  • Backend Convex réactif : 12 tables, kanban temps réel à 7 statuts
  • Sourcing par APIs officielles (France Travail, Adzuna) + extension de capture couvrant 7 jobboards
  • Worker LLM local : génération CV, lettre 3 tons, scoring ATS et audit d'entreprise

CHIFFRES

42 fonctions · 11 tables · temps réel

OUTILS

  • React 19
  • TypeScript
  • Convex
  • TanStack

CS.04.M, LES MODULES, PIÈCE PAR PIÈCE

Sous le capot

01 · Le modèle atomique

Pas de CV figés par métier. Chaque expérience se décompose en atomes (un fait vérifié) portant plusieurs formulations selon l'angle (management, dev, commercial…) et une pondération par casquette. Une offre entrante sélectionne les bons atomes, le bon angle, et recompose un CV taillé. Les chiffres vivent une seule fois, comme source de vérité : jamais contredits d'un CV à l'autre, jamais inventés.

convex/schema.tstypescript
// ATOMS. Each achievement = one fact, told through several angle-specific lenses.
achievements: defineTable({
  experienceId: v.id("experiences"),
  label: v.string(), // internal name, e.g. "Exosphère, direction d'équipe"
  // Multiple phrasings; the offer picks the matching angle.
  textVariants: v.array(
    v.object({
      angle: v.string(), // "management" | "dev" | "commercial" | "marketing"…
      text: v.string(),
    }),
  ),
  tags: v.array(v.string()),
  // Verified numbers: SINGLE SOURCE OF TRUTH. Never contradicted across CVs.
  metrics: v.optional(v.array(v.object({ key: v.string(), value: v.string() }))),
  keywords: v.array(v.string()), // ATS keywords this atom can satisfy
  // Per-profile priority weighting (key = profiles.key, value = weight 0-10).
  priorityByProfile: v.optional(v.array(v.object({ profileKey: v.string(), weight: v.number() }))),
  order: v.number(),
}).index("by_experience", ["experienceId"]),
L'atome : un fait, plusieurs lentilles. La table au cœur de tout le pipeline de génération.

02 · Le worker LLM et ses garde-fous

Un worker local polle la file de jobs Convex et pilote un LLM en ligne de commande, flux JSON parsé en direct pour afficher la progression dans l'interface. Verrou mono-instance, plafond de processus concurrents, timeout dur : les leçons d'un fork-bomb accidentel, gravées en code. Et comme un modèle hallucine parfois un identifiant, chaque composition est assainie avant d'entrer en base.

scripts/jobdeck-worker.mjsjavascript
// The LLM occasionally hallucinates or mistypes an achievementId in cvComposition;
// Convex's v.id("achievements") then rejects the whole saveCv mutation. Keep only
// entries whose id is a real achievement so one bad id can't kill the CV step.
function sanitizeCvComposition(comp, ctx) {
  if (!Array.isArray(comp)) return [];
  const validIds = new Set((ctx.achievements ?? []).map((a) => a._id));
  const clean = comp.filter((c) => c && validIds.has(c.achievementId));
  const dropped = comp.length - clean.length;
  if (dropped > 0) log(`  ⚠ ${dropped} achievementId(s) hallucinés écartés de cvComposition`);
  return clean;
}
Un id halluciné ne doit pas tuer l'étape CV : on filtre contre les atomes réels et on journalise l'écart.

03 · Huit types de jobs de génération

La même file traite huit natures de travaux : structuration d'annonce, audit d'entreprise sur le web, scoring d'adéquation ATS avec mots-clés manquants, composition du CV, lettre en trois tons avec recommandation, préparation d'entretien, négociation salariale et comparaison d'offres. Chaque étape écrit son état dans un pipeline visible en temps réel sur la fiche candidature.

LA FACE VISIBLE, CÔTÉ STUDIO