Aller au contenu
← ProjetsDevIAInfraEn production

Un CV et une lettre taillés pour chaque offre, optimisés ATS, en quelques secondes.

Rôle
Fondateur — produit, dev full-stack, infra
Période
2025 — aujourd’hui
Contexte
Produit personnel
Jobspark

3

apps Next.js + 1 API

FR/EN

langues

1

manifest de déploiement

24/7

staging auto à chaque push

Contexte

Les candidats envoient le même CV partout et se font filtrer par les ATS avant qu’un humain ne le lise.

Le défi

Produire un document vraiment adapté à chaque offre, fiable et rapide, sans que les coûts IA n’explosent, avec un paiement simple et une mise en page irréprochable.

  1. 01IA

    Génération IA cadrée

    Prompts structurés via OpenRouter (Mistral), sorties validées et rendues par un moteur de documents dédié. Entretien vocal en temps réel via OpenAI Realtime (WebRTC).

  2. 02Dev

    Score ATS maison

    Un package `ats-score` mesure l’adéquation CV ↔ offre et guide l’utilisateur avant l’envoi.

  3. 03Dev

    Monorepo Turborepo

    Trois apps Next.js 16 (landing, app, web) + API NestJS 11 / Drizzle, packages partagés (ui, rendu, score), tests Playwright de bout en bout.

  4. 04Infra

    Plateforme as code

    Déployé sur ma PaaS Dokploy via un manifest unique : staging à chaque push, production à la demande, sauvegardes R2 quotidiennes.

Architecture

Stack

  • Next.js
  • React
  • NestJS
  • PostgreSQL
  • Drizzle ORM
  • Redis
  • MinIO / S3
  • Stripe
  • OpenRouter / Mistral
  • Turborepo
  • PPlaywright
  • DDokploy
  • Terraform / OpenTofu

Résultats

  • ✦Crédits et paiements Stripe en production
  • ✦Interface bilingue FR/EN et PWA installable
  • ✦Outils SEO gratuits pour l’acquisition

Le récit

Comment ça marche

Les captures viennent d’une instance locale, avec un compte de démo et une candidate fictive, Léa Moreau. Le parcours suit ce que fait un candidat, de l’offre repérée à l’entretien répété.

1. Les offres viennent au candidat

Page « Offres du jour » : six cartes d’offres avec un pourcentage de correspondance et les compétences reconnues
Les offres du jour, triées par correspondance avec la recherche et le CV du candidat, avant tout appel à un modèle.

Le candidat décrit sa recherche une fois : métier, lieu, contrat. L’API lit le flux national de France Travail par tranches de temps et les pages carrières des entreprises qu’il suit toutes les 30 minutes. Chaque nouvelle offre est comparée aux recherches au moment où elle arrive. Les offres fermées sont anonymisées, puis purgées après 30 jours.

2. Un score qu’on peut lire

Panneau de détail d’une offre : score de 81 %, barème par critère et bouton « Postuler »
Le détail d’une offre montre le barème du score, critère par critère : le candidat voit pourquoi elle lui est proposée.

Le pourcentage n’est pas une note d’IA. C’est un barème déterministe sur 100 points : intitulé du poste, compétences, lieu, fraîcheur, expérience et salaire. Les compétences sont aussi lues à travers le référentiel ROME 4.0 de France Travail, ce qui rattrape un CV qui dit la même chose avec d’autres mots. Une analyse IA de l’offre existe, mais elle est en option et payante.

3. Une candidature, ses documents, son score

Liste des candidatures avec statut, documents générés, score ATS et date
Chaque candidature garde son statut, ses documents et son score ATS, recalculé à chaque enregistrement du CV.

« Postuler » crée une candidature à partir de l’offre. Une offre collée ou importée depuis une URL suit le même chemin : un appel au modèle la structure en intitulé, missions, exigences et mots-clés. Le CV et la lettre sont ensuite générés à partir du profil du candidat. Le score ATS affiché dans la liste est recalculé gratuitement à chaque enregistrement. Le détail de ce calcul fait l’objet d’un article à part.

4. Des documents qui restent à la main du candidat

Éditeur de lettre de motivation : formulaire à gauche, aperçu du document final à droite
La lettre générée reste éditable champ par champ, avec l’aperçu exact du PDF, l’historique des versions et l’export DOCX.

Rien n’est figé après la génération. Chaque champ se modifie, l’aperçu à droite est le HTML qui sera imprimé en PDF, et chaque génération ou enregistrement crée une version qu’on peut restaurer. Le nom, l’e-mail et le téléphone ne partent jamais au modèle : ils sont réinjectés côté serveur après la génération.

5. Répéter l’entretien à voix haute

Entretien vocal en cours : orbe animé « Je vous écoute », minuteur et transcription des échanges
L’entretien d’entraînement se fait à la voix, avec un recruteur qu’on peut interrompre et une transcription qui s’écrit au fil de l’échange.

L’entretien est une vraie conversation vocale. Le recruteur virtuel s’appuie sur l’offre et le profil, suit un agenda de questions et peut être coupé en pleine phrase. La séance est facturée à la minute, parce que son coût réel l’est aussi.

Rapport d’entretien : nombre de réponses, durée moyenne, hésitations, mots-clés de l’offre repris et note sur 10
Le rapport mesure ce qui est mesurable (durée, hésitations, mots-clés de l’offre repris) et donne deux points précis à travailler.

À la fin, un rapport compte les prises de parole, la durée moyenne, les hésitations et la part des mots-clés de l’offre repris à l’oral. La note par dimension se suit d’une séance à l’autre.

Sous le capot

Un entretien en temps réel sans exposer la clé

La première version de l’entretien enchaînait transcription, modèle et synthèse vocale à chaque tour. Le recruteur répondait trop lentement et ne s’arrêtait pas quand on le coupait. L’ADR-026 a déplacé la voix vers OpenAI Realtime en WebRTC, avec le modèle mini, le seul qui tienne dans le prix d’une séance.

La négociation passe par l’API, l’audio va directement du navigateur à OpenAI

Le navigateur n’a jamais la clé ni le prompt. Il envoie son offre SDP à l’API, qui ouvre l’appel chez OpenAI avec le modèle, la voix et le brief de l’entretien, puis lui renvoie la réponse SDP. L’audio circule ensuite en direct. En parallèle, l’API rejoint le même appel par un WebSocket « sideband ». Elle enregistre la conversation dans l’ordre pour le rapport, met à jour l’agenda après chaque réponse et raccroche quand la durée payée, plus 90 secondes, est écoulée.

Le barème des offres du jour

Le score de correspondance du panneau d’offre vient de cette fonction. Chaque critère vaut une part fixe des 100 points, et pour les compétences et l’intitulé, on garde la meilleure des deux lectures : mots-clés du CV ou référentiel ROME.

apps/api/src/job-search/matching/job-matching.ts
const breakdown: ScoreBreakdown = {
  experience: SCORE_WEIGHTS.experience * experienceScore(job, project),
  freshness: SCORE_WEIGHTS.freshness * freshnessScore(job, now),
  location: SCORE_WEIGHTS.location * locationScore(job, project),
  salary: SCORE_WEIGHTS.salary * salaryScore(job, project),
  // The best of the two readings: ROME only ever adds to the keywords.
  skills:
    SCORE_WEIGHTS.skills *
    Math.max(
      input.skills.length > 0
        ? Math.min(1, keywordSkills.length / Math.min(5, input.skills.length))
        : 0,
      romeSkills.ratio,
    ),
  title:
    SCORE_WEIGHTS.title *
    Math.max(
      titleScore(job, project),
      romeTitleScore(job, rome?.projectCodes ?? []),
    ),
};
// SCORE_WEIGHTS = { title: 30, skills: 25, location: 15,
//                   freshness: 15, experience: 10, salary: 5 }

C’est volontairement simple. Le score est calculé pour chaque nouvelle offre et pour chaque recherche, sans appel à un modèle, donc sans coût. Le reclassement par IA du matin est une option à 1 crédit par jour, débitée seulement si l’appel réussit.

Ce que j’en retiens

  • Le modèle rédige, le code décide. Les faits d’un CV (entreprises, dates, chiffres, compétences) sont recalés sur le profil après la génération. Tout ce que le profil n’étaye pas est retiré. Le prompt le demande aussi, mais je ne compte pas sur le prompt seul.
  • Facturer comme on paie. Une génération coûte 3 crédits, débités seulement une fois la sortie validée. L’entretien est facturé à la minute depuis qu’il est devenu la partie la plus chère d’une candidature. Les ventes de crédits sont refusées si le solde OpenRouter passe sous un seuil critique.
  • Écrire les décisions. Le repo compte plus de vingt-cinq ADR. Quand un choix de modèle ou un barème change, la raison et les mesures sont dans le repo, pas dans ma mémoire.

Galerie

Tableau de bord Jobspark d’un compte de démo : candidatures suivies, envoyées, taux de réponse, crédits et score d’entretien
Liste des candidatures avec statut, documents générés, score ATS et date
Rapport d’entretien : nombre de réponses, durée moyenne, hésitations, mots-clés de l’offre repris et note sur 10
Shipyard
Projet suivant →ShipyardD’un cahier des charges à un repo livré en autonomie par des agents.

Parlons-en. Décrivez votre besoin en quelques lignes, je reviens vers vous sous 48 h.