
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.
- 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).
- 02Dev
Score ATS maison
Un package `ats-score` mesure l’adéquation CV ↔ offre et guide l’utilisateur avant l’envoi.
- 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.
- 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

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

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

« 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

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

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.

À 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.
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.
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




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