Une candidature reçue n'est pas une candidature traitée.
Track : onPrem
Notre contribution au #HackAVP, le hackathon de l'OPT-NC, de Station N et de la commission Data & IA d'OPEN NC, réalisée dans le cours Interopérabilité des SI du M2 MIAGE de l'IAE de Nouvelle-Calédonie. Dans JobMatch NC, le projet de notre promo, nous nous occupons de la brique recruteur (MS6).
CÎté candidat, Adopte un Job permet de swiper les vrais postes de l'OPT-NC et de comprendre pourquoi ils nous correspondent (l'article de Bastian). Mais un swipe à droite, ça crée une candidature⊠et quelqu'un, de l'autre cÎté, doit la lire, la comprendre et y répondre.
C'est ce qu'on a construit : AVPRO, l'espace recruteur d'Adopte un Job.
- đ Code source : github.com/DamienJmt/avpro
- đŹ DĂ©mo vidĂ©o : 3 min 38 (plus bas)
- đ„ Ăquipe : Damien & Luca, module MS6 â AVPRO-NC du #HackAVP
đŻ Le contexte : recruter, c'est encore souvent laborieux
Aujourd'hui, une candidature Ă un avis de vacance de poste (AVP), c'est trĂšs souvent :
- đ§ un mail, parfois un « RE: RE: Candidature », parfois une relance ;
- đ des piĂšces jointes Ă ouvrir une par une :
CV_final_v3.pdf,CV(1).docx,scan_cv.jpg⊠; - đ un tableau Excel pour savoir qui a Ă©tĂ© vu, qui a Ă©tĂ© relancĂ©, qui est un doublon ;
- âł et au bout, des candidats qui attendent des semaines sans savoir oĂč ils en sont.
Le recruteur passe son temps à trier au lieu d'évaluer. Et le candidat, lui, n'a aucune visibilité.
đĄ La rĂ©ponse : AVPRO
AVPRO remplace tout ça par un seul outil :
| Avant | Avec AVPRO | |
|---|---|---|
| đ„ RĂ©ception | des mails Ă©parpillĂ©s | toutes les candidatures au mĂȘme endroit |
| đ Ăvaluation | ouvrir chaque CV, comparer Ă la fiche de poste | un score de compatibilitĂ© expliquĂ©, compĂ©tence par compĂ©tence |
| â DĂ©cision | un mail Ă rĂ©diger | un geste : Ă droite on prĂ©sĂ©lectionne, Ă gauche on Ă©carte |
| đ RĂ©ponse | des semaines d'attente | le candidat voit la dĂ©cision aussitĂŽt dans son app |
| đ Entretiens | un fil de mails | suivis dans l'outil, sans un seul mail |
L'idée est volontairement symétrique à Adopte un Job : le candidat swipe des postes, le recruteur swipe des candidatures.
đŹ La dĂ©mo
Les données de la démo (candidats, AVP, scores) sont fictives.
đ§© Notre place dans le projet commun du Master MIAGE
Le #HackAVP s'inscrit dans JobMatch NC, le projet commun de la promo :
Le #HackAVP réunit sept groupes, chacun sur un maillon de la chaßne :
| Module | Projet | Ăquipe | Article |
|---|---|---|---|
| MS1 | CV Forge | Florian | lire |
| MS2 | HACKAVPI | Dan & Thimotée | lire |
| MS3 | Adopte un Job | Bastian | lire |
| MS4 | CV Generator | Anthony & Kim | lire |
| MS5 | CV-Matching | Jo-Michel | lire |
| MS6 | AVPRO-NC | Damien & Luca | vous y ĂȘtes đ |
| MS7 | Insight Squad | Ălodie & Matthieu | lire |
Nous, c'est MS6, le cÎté recruteur. AVPRO n'a ni base de données ni serveur à lui : il s'appuie sur trois autres modules.
- MS2 â HACKAVPI : l'API commune (comptes, AVP, candidatures, profils, dĂ©cisions, entretiens).
- MS3 â Adopte un Job : les candidats, leur CV au format JSON Resume et les critĂšres qu'ils ont saisis (mĂ©tier visĂ©, contrat, zone, disponibilitĂ©).
- MS5 â CV-Matching : le score de compatibilitĂ© entre un CV et un AVP.
Pour tout savoir sur l'API commune, l'article de l'équipe HACKAVPI :
ConcrĂštement : un swipe Ă droite dans Adopte un Job crĂ©e une candidature dans HACKAVPI, AVPRO la rĂ©cupĂšre, demande son score Ă CV-Matching, et la dĂ©cision du recruteur repart dans HACKAVPI, oĂč le candidat la voit.
đ§ Le parcours recruteur, Ă©tape par Ă©tape
1ïžâŁ Je me connecte
Un Ă©cran simple : adresse et mot de passe. Pas d'inscription libre, un compte recruteur ne peut ĂȘtre créé que par un recruteur ou un admin.
à la connexion, l'API renvoie un jeton JWT valable 1 h. AVPRO lit le rÎle dans le jeton : si c'est un compte candidat, il est poliment renvoyé vers Adopte un Job. Quand le jeton expire, on revient à la connexion, sans écran cassé.
2ïžâŁ Je swipe les candidatures
Une carte = une candidature Ă traiter. On y voit d'un coup d'Ćil :
- le score de compatibilité (« 88 % · trÚs bonne adéquation ») et le nombre de compétences du poste couvertes ;
- l'expérience, le niveau de formation, la disponibilité ;
- des repÚres verts quand le candidat vise ce métier ou cherche dans cette zone ;
- ses compétences, avec en vert celles que le matching a reconnues dans le poste.
à droite pour présélectionner, à gauche pour écarter : à la souris, au doigt, ou avec les boutons sous la carte.
3ïžâŁ Je comprends le score
à cÎté de la carte (ou dans une feuille sur téléphone), le panneau de détail répond à la vraie question : pourquoi ce score ?
- Points forts : chaque compĂ©tence attendue par l'AVP, avec la phrase du CV qui la prouve (« DĂ©veloppement web React / TypeScript â âDĂ©veloppeuse React et Node depuis 4 ansâ »).
- Ă vĂ©rifier : ce qui est seulement approchant (« Bases de donnĂ©es SQL â au plus proche : âPostgreSQLâ »).
- Savoir-ĂȘtre, Ă vĂ©rifier en entretien : ce qu'un CV ne peut pas prouver.
- Prérequis : permis, diplÎme⊠marqués rempli, non rempli ou à vérifier.
Et à part, ce que le candidat recherche : métier, contrat, lieu, disponibilité, affichés en oui / non / inconnu.
đĄ Choix assumĂ© : les critĂšres du candidat ne font pas de score. S'il ne vise pas ce contrat ou cette zone, la carte l'indique (« hors de ses critĂšres ») mais le recruteur peut le retenir quand mĂȘme. L'outil Ă©claire, le recruteur dĂ©cide.
4ïžâŁ Je filtre
Une recherche (nom, métier, intitulé de poste) et des filtres Poste · Direction · Lieu du poste · Options (profil renseigné, CV déposé).
Plusieurs choix dans une mĂȘme catĂ©gorie, c'est l'un ou l'autre ; d'une catĂ©gorie Ă l'autre, c'est les deux. Et chaque case annonce combien de candidats elle garderait avant mĂȘme qu'on la coche : plus de filtre qui vide l'Ă©cran par surprise.
5ïžâŁ Je dĂ©cide⊠et j'ai 30 secondes pour changer d'avis
Dans l'API, une décision est définitive et le candidat la voit immédiatement. Un swipe trop rapide ne doit pas devenir un refus envoyé à quelqu'un.
Alors une dĂ©cision ne part qu'aprĂšs 30 secondes. Pendant ce dĂ©lai, une barre en bas de l'Ă©cran affiche « envoi dans 28 s » avec Annuler et Envoyer maintenant ; le bouton Retour du swipe et l'onglet Candidats permettent aussi d'annuler. Et si on ferme la page, les dĂ©cisions en attente partent quand mĂȘme.
6ïžâŁ Je suis mes candidatures
L'onglet Candidats reprend tout, par statut : à traiter, Présélectionné, Toutes. Chaque ligne garde sa case de compatibilité, et une candidature retirée par le candidat apparaßt grisée.
| Statut dans l'API | Dans AVPRO |
|---|---|
EN_ATTENTE |
Ă traiter |
VALIDEE |
Présélectionné |
REJETEE |
Rejetée |
ANNULEE |
Retirée par le candidat |
VALIDEE + entretienAProg
|
Entretien |
7ïžâŁ Je valide un entretien
Un profil présélectionné peut passer en entretien, aprÚs une confirmation. Il rejoint l'onglet Entretiens, avec un récapitulatif du poste (intitulé, référence, direction, lieu, contrat, début, date limite) et de la candidature (profil, contact, expérience, formation, disponibilité, compatibilité).
Choix de périmÚtre : AVPRO marque l'entretien à programmer, il ne gÚre pas l'agenda. La date et le lieu restent l'affaire du recruteur.
8ïžâŁ Et aussi
- AVP : les avis de vacance de poste, les ouverts d'abord, triés par date limite, avec le nombre de candidatures à traiter.
- Profil : l'adresse du compte et le changement de mot de passe par un code reçu par mail.
đ± Responsive : ordinateur et tĂ©lĂ©phone
Un recruteur ne trie pas toujours à son bureau. Sur grand écran, AVPRO affiche un menu latéral et le détail du score en panneau à cÎté de la carte. Sur téléphone, les onglets passent en bas de l'écran, et le détail s'ouvre d'un geste, en feuille, depuis le score de la carte.
đ€ïž Track choisie : onPrem
AVPRO est un front 100 % statique : pas de base de données, pas de serveur à nous. Le dossier produit par vite build se dépose sur n'importe quel serveur web, y compris les serveurs de l'OPT-NC, sans cloud tiers.
Les données ne quittent pas la chaßne du projet : elles restent dans l'API commune (MS2), et le CV n'est envoyé au matching (MS5) que sans identité. La seule clé sensible, celle du matching, reste cÎté serveur et n'est jamais livrée au navigateur.
đ ïž Sous le capot (pour les devs)
La stack
- React 19 + TypeScript strict + Vite 7, sortie 100 % statique.
-
MĂȘme socle et mĂȘme design que l'app candidat : les CSS d'Adopte un Job sont repris tels quels, pour une fusion simple dans
beta/. - Pas de base, pas de serveur Ă nous : tout passe par l'API commune (MS2) et par CV-Matching (MS5).
Un seul fichier parle Ă l'API
src/api.ts est le seul fichier qui appelle HACKAVPI. Il traduit leurs réponses dans les formes de src/types.ts (Avp, Candidat, Candidature) : les écrans ne connaissent jamais le format de l'API. Quand l'API change, un seul fichier s'adapte.
| Ăcran | Route |
|---|---|
| Connexion | POST /auth/login |
| AVP | GET /avp |
| Swipe, Candidats |
GET /candidatures · GET /profils · GET /profils/{id}/cv
|
| Décision |
POST /swipes/recruteurs/candidature/{id} { estLike }
|
| Entretien |
PATCH /candidatures/{id}/entretien-a-prog { entretienAProg }
|
| Compatibilité |
POST /v1/score { reference, cv } (CV-Matching) |
| Profil |
POST /auth/forgot-password · POST /auth/reset-password
|
Les trois listes (AVP, candidatures, profils) sont chargées en parallÚle, toutes pages lues (100 par page), au démarrage et à chaque « Actualiser ».
Les données : plusieurs formats de CV, une seule forme
Dans l'API, plusieurs formats de CV circulent :
- le JSON Resume écrit par l'app candidat, avec les critÚres dans
meta.criteres; - le JSON Resume produit par la lecture du CV déposé (
cvJsonGenere) ; - les formats courts des comptes de démonstration (
titre,competences: string[],experiences: {poste, annees}[]âŠ).
api.ts les ramĂšne tous Ă une seule forme Candidat : annĂ©es d'expĂ©rience cumulĂ©es, niveau de formation (Bac â Bac+5), compĂ©tences dĂ©doublonnĂ©es, critĂšres. Les zones sont recalculĂ©es comme dans l'app candidat (Grand NoumĂ©a, Sud, Nord, Ăles) pour comparer un AVP aux zones visĂ©es.
La décision différée
Le cĆur du « droit Ă l'erreur » tient en quelques lignes dans App.tsx :
/* Une décision du recruteur est définitive dans l'API et le candidat la voit
aussitÎt : elle n'est envoyée qu'aprÚs ce délai, annulable jusque-là . */
const DELAI = 30_000;
const decider = (id: number, ok: boolean) => {
const avant = minuteurs.current.get(id);
if (avant) window.clearTimeout(avant.t);
maj(id, ok ? "preselectionne" : "rejete"); // l'écran change tout de suite
const t = window.setTimeout(() => void envoie(id, ok), DELAI);
minuteurs.current.set(id, { t, ok });
setAttente((l) => [...l.filter((x) => x.id !== id), { id, ok, echeance: Date.now() + DELAI }]);
};
Et pour ne rien perdre à la fermeture de l'onglet, les décisions en attente partent sur pagehide avec un fetch(..., { keepalive: true }), que le navigateur termine seul.
Le matching, Ă la demande
src/matching.ts appelle CV-Matching uniquement quand un écran affiche un score, trois appels à la fois au plus, et garde les résultats pour la session (un candidat ne change pas de CV pendant qu'on le regarde). Les composants s'abonnent au cache avec useSyncExternalStore ; « Actualiser » le vide.
CORS et clé d'API
Le serveur commun n'envoie pas d'en-tĂȘtes CORS, et CV-Matching exige une clĂ© X-API-Key. En dĂ©veloppement, le proxy Vite rĂšgle les deux :
// vite.config.ts
"/api-matching": {
target: env.MATCHING_URL || "https://myz0-cv-matching.hf.space",
changeOrigin: true,
rewrite: (p) => p.replace(/^\/api-matching/, ""),
// MATCHING_API_KEY vient de .env.local, sans préfixe VITE_ :
// elle reste cÎté serveur et n'est jamais dans le code envoyé au navigateur.
headers: env.MATCHING_API_KEY ? { "X-API-Key": env.MATCHING_API_KEY } : {},
},
En production, il faudra un relais du mĂȘme genre (comme le relais.php d'Adopte un Job), dĂ©clarĂ© dans VITE_API_URL.
đ DonnĂ©es personnelles
- Le CV envoyĂ© Ă CV-Matching part sans identitĂ© : ni nom, ni adresse, ni tĂ©lĂ©phone, ni profils en ligne. Le service n'en a pas besoin, elles n'ont pas Ă quitter l'API commune. On retire mĂȘme le nom quand il apparaĂźt dans le rĂ©sumĂ© (« LĂ©a Wamytan, 3 ans d'expĂ©rience⊠»).
// api.ts â extrait de cvPourMatching()
const { meta: _meta, $schema: _schema, references: _refs, ...reste } = doc;
return {
...reste,
basics: {
label: b.label,
summary: sansNom(b.summary), // le nom du candidat est retiré du résumé
location: objet(b.location).city ? { city: objet(b.location).city } : undefined,
},
};
- La clé du matching n'est jamais dans le navigateur (voir le proxy plus haut).
-
Seule la session vit dans le navigateur : le jeton, en
sessionStorage, effacé à la fermeture de l'onglet. - Les données de la démo sont fictives.
âïž Nos arbitrages
Quelques décisions qu'on a dû trancher, et pourquoi :
- Un score qui s'explique, plutÎt qu'un classement opaque. Un pourcentage seul ne dit rien ; les preuves tirées du CV, si.
- Les critÚres du candidat informent, ils ne filtrent pas. Exclure automatiquement quelqu'un parce qu'il a coché « CDI » sur un CDD, c'est passer à cÎté de profils.
- 30 secondes de dĂ©lai plutĂŽt qu'une confirmation Ă chaque swipe. Une popup « Ătes-vous sĂ»r ? » casse le rythme du tri ; un dĂ©lai annulable protĂšge sans gĂȘner.
- Pas d'agenda d'entretien. On marque l'entretien à programmer, on ne réinvente pas le calendrier du recruteur.
- Aucune base à nous. Une seule source de vérité, l'API commune : ce que voit le recruteur est exactement ce que voit le candidat.
đ Et ensuite ?
-
Fusionner AVPRO dans l'app Adopte un Job (
beta/) : mĂȘme socle, mĂȘmes CSS, il reste Ă importer les styles communs et Ă ajouter quelques icĂŽnes au sprite. - Un relais de production pour le CORS et la clĂ© du matching, comme cĂŽtĂ© candidat.
- Le renouvellement de session : le jeton dure 1 h sans renouvellement aujourd'hui.
- Le changement d'adresse du compte, que l'API ne permet pas encore.
đ Pour finir
Adopte un Job aide le candidat à trouver le poste qui lui correspond. AVPRO aide le recruteur à trouver les candidats qui correspondent à son poste, et à leur répondre vite.
Fini la boĂźte mail qui dĂ©borde : chaque candidature arrive au mĂȘme endroit, avec un score qui s'explique, une dĂ©cision en un geste et un candidat qui sait oĂč il en est.
Et vous, cĂŽtĂ© recruteur ou cĂŽtĂ© candidat : qu'est-ce qui vous a le plus fait perdre de temps dans un recrutement ? Dites-le-nous en commentaire đ
Damien & Luca â AVPRO-NC (MS6), #HackAVP, Master MIAGE · IAE Nouvelle-CalĂ©donie Ă OPT-NC
đ Code source · JobMatch NC, le projet de la promo · HACKAVPI (MS2) · Adopte un Job (MS3) · CV-Matching (MS5)
























