fix(codev): algo Solution tokenize direct + seuils releves + fiches demo enrichies
This commit is contained in:
@@ -73,98 +73,93 @@
|
|||||||
import type { CodevFiche, CodevMatch } from '~/types/codev'
|
import type { CodevFiche, CodevMatch } from '~/types/codev'
|
||||||
import { computeMatches } from '~/utils/codev/matching'
|
import { computeMatches } from '~/utils/codev/matching'
|
||||||
|
|
||||||
// 10 fiches factices - hashtags alignes pour demontrer les 3 modes :
|
// 10 fiches sans hashtags — textes enrichis pour que scoreDirect discrimine bien les 3 modes :
|
||||||
//
|
//
|
||||||
// Solution : Lea(besoin coaching) -> Maya(offre coaching)
|
// Solution (scoreDirect besoinA vs offreB) :
|
||||||
// Sami(besoin formation+vente) -> Ines(offre vente+formation)
|
// Sami(besoin vendre formation) -> Ines(offre vente formations) ✓
|
||||||
// Tom(besoin tiers-lieu) -> Zoe(offre facilitation+tiers-lieu)
|
// Nael(besoin site web formation) -> Sami(offre developpement web) ✓
|
||||||
|
// Eva(besoin coaching vente) -> Ines(offre vente formations) ✓
|
||||||
|
// Tom(besoin tiers-lieu) -> Zoe(offre facilitation tiers-lieux) ✓
|
||||||
//
|
//
|
||||||
// Alliance : Lea + Maya (hashtag coaching commun dans besoins)
|
// Alliance (besoins similaires) :
|
||||||
// Sami + Kenji (hashtag formation+vente dans besoins)
|
// Lea + Maya (coaching, lancer, offre) ✓
|
||||||
// Tom + Zoe (hashtag tiers-lieu dans besoins)
|
// Tom + Zoe (tiers-lieu, co-creer) ✓
|
||||||
|
// Sami + Kenji (vendre, formations) ✓
|
||||||
//
|
//
|
||||||
// Surprise : Lea + Zoe (hashtag facilitation dans offres)
|
// Surprise (offres similaires) :
|
||||||
// Tom + Roman (hashtag archi dans offres)
|
// Lea + Zoe (facilitation, groupes) ✓
|
||||||
|
// Tom + Roman (architecture) ✓
|
||||||
|
// Ines + Nael (marketing, formations) ✓
|
||||||
|
|
||||||
const FICHES_DEMO: CodevFiche[] = [
|
const FICHES_DEMO: CodevFiche[] = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1, nom: 'Lea',
|
||||||
nom: 'Lea',
|
besoin: 'Structurer et lancer mon offre de coaching professionnel cet automne',
|
||||||
besoin: 'Structurer mon offre de coaching pour la lancer en septembre',
|
offre: 'Facilitation de groupes et animation de cercles de parole',
|
||||||
offre: 'Animation de groupes, facilitation de cercles de parole',
|
hashtags: [],
|
||||||
hashtags: ['coaching', 'facilitation'],
|
|
||||||
created_at: '2026-05-08T10:00:00Z',
|
created_at: '2026-05-08T10:00:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2, nom: 'Sami',
|
||||||
nom: 'Sami',
|
besoin: 'Vendre ma formation en ligne et attirer mes premiers clients',
|
||||||
besoin: 'Comprendre comment vendre une formation en ligne',
|
offre: 'Developpement web sur mesure, creation de sites et applications',
|
||||||
offre: 'Developpement web, sites Astro et Nuxt',
|
hashtags: [],
|
||||||
hashtags: ['formation', 'vente'],
|
|
||||||
created_at: '2026-05-08T10:01:00Z',
|
created_at: '2026-05-08T10:01:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 3,
|
id: 3, nom: 'Ines',
|
||||||
nom: 'Ines',
|
besoin: 'Ameliorer la facilitation de mes ateliers collaboratifs',
|
||||||
besoin: 'Aide pour la facilitation de mes ateliers ecriture',
|
offre: 'Vente de formations en ligne et marketing pour formateurs',
|
||||||
offre: 'Vente de formations en ligne, marketing direct',
|
hashtags: [],
|
||||||
hashtags: ['vente', 'formation'],
|
|
||||||
created_at: '2026-05-08T10:02:00Z',
|
created_at: '2026-05-08T10:02:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 4,
|
id: 4, nom: 'Tom',
|
||||||
nom: 'Tom',
|
besoin: 'Trouver des associes pour co-creer un tiers-lieu rural',
|
||||||
besoin: 'Trouver un associe pour un projet de tiers-lieu',
|
offre: 'Architecture bioclimatique et eco-construction pour tiers-lieux',
|
||||||
offre: 'Architecture eco-responsable, conception bioclimatique',
|
hashtags: [],
|
||||||
hashtags: ['tiers-lieu', 'archi'],
|
|
||||||
created_at: '2026-05-08T10:03:00Z',
|
created_at: '2026-05-08T10:03:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5, nom: 'Maya',
|
||||||
nom: 'Maya',
|
besoin: 'Creer et lancer mon offre de coaching en transition professionnelle',
|
||||||
besoin: 'Structurer mon offre de coaching freelance',
|
offre: 'Accompagnement coaching de carriere et transitions professionnelles',
|
||||||
offre: 'Coaching de carriere, accompagnement transition pro',
|
hashtags: [],
|
||||||
hashtags: ['coaching', 'carriere'],
|
|
||||||
created_at: '2026-05-08T10:04:00Z',
|
created_at: '2026-05-08T10:04:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6, nom: 'Kenji',
|
||||||
nom: 'Kenji',
|
besoin: 'Apprendre a vendre mes formations sans pression commerciale',
|
||||||
besoin: 'Apprendre a vendre mes formations sans me sentir vendeur',
|
offre: 'Photographie professionnelle et direction artistique editoriale',
|
||||||
offre: 'Photographie, direction artistique de projets editoriaux',
|
hashtags: [],
|
||||||
hashtags: ['formation', 'vente'],
|
|
||||||
created_at: '2026-05-08T10:05:00Z',
|
created_at: '2026-05-08T10:05:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 7,
|
id: 7, nom: 'Zoe',
|
||||||
nom: 'Zoe',
|
besoin: 'Co-creer un tiers-lieu avec des porteurs de projet alignes',
|
||||||
besoin: 'Trouver des associes pour mon projet de tiers-lieu rural',
|
offre: 'Facilitation de collectifs et animation en intelligence collective',
|
||||||
offre: 'Animation et facilitation de collectifs, intelligence collective',
|
hashtags: [],
|
||||||
hashtags: ['tiers-lieu', 'facilitation'],
|
|
||||||
created_at: '2026-05-08T10:06:00Z',
|
created_at: '2026-05-08T10:06:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 8,
|
id: 8, nom: 'Nael',
|
||||||
nom: 'Nael',
|
besoin: 'Creer un site web pour presenter et vendre ma formation',
|
||||||
besoin: 'Construire un site web pour ma formation',
|
offre: 'Strategie marketing digital et lancement de produits en ligne',
|
||||||
offre: 'Strategie marketing, lancement de produits digitaux',
|
hashtags: [],
|
||||||
hashtags: ['web', 'strategie'],
|
|
||||||
created_at: '2026-05-08T10:07:00Z',
|
created_at: '2026-05-08T10:07:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 9,
|
id: 9, nom: 'Eva',
|
||||||
nom: 'Eva',
|
besoin: 'Lancer mon coaching avec une page de vente qui convertit',
|
||||||
besoin: 'Lancer mon offre de coaching avec une page de vente',
|
offre: 'Ecriture longue forme, articles de fond et tribunes editoriales',
|
||||||
offre: 'Ecriture longue forme, articles essais et tribunes',
|
hashtags: [],
|
||||||
hashtags: ['coaching', 'ecriture'],
|
|
||||||
created_at: '2026-05-08T10:08:00Z',
|
created_at: '2026-05-08T10:08:00Z',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 10,
|
id: 10, nom: 'Roman',
|
||||||
nom: 'Roman',
|
besoin: 'Ecrire de meilleurs articles pour mon blog et ma newsletter',
|
||||||
besoin: 'Ameliorer mes articles de blog sur la renovation',
|
offre: 'Architecture technique et plans pour renovation energetique',
|
||||||
offre: 'Architecture, plans techniques pour renovation energetique',
|
hashtags: [],
|
||||||
hashtags: ['archi', 'reno'],
|
|
||||||
created_at: '2026-05-08T10:09:00Z',
|
created_at: '2026-05-08T10:09:00Z',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@@ -186,7 +181,7 @@ function setMode(newMode: typeof mode.value) {
|
|||||||
if (newMode === 'none') {
|
if (newMode === 'none') {
|
||||||
matches.value = []
|
matches.value = []
|
||||||
} else {
|
} else {
|
||||||
matches.value = computeMatches(fiches.value, newMode)
|
matches.value = computeMatches(fiches.value, newMode, 0.12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -41,15 +41,21 @@ function score(textA: string, hashtagsA: string[], textB: string, hashtagsB: str
|
|||||||
return jaccard(tokenize(textA), tokenize(textB))
|
return jaccard(tokenize(textA), tokenize(textB))
|
||||||
}
|
}
|
||||||
|
|
||||||
const THRESHOLD = 0.15
|
// scoreDirect tokenise TOUJOURS les textes, ignore les hashtags
|
||||||
|
// Utilise pour matchSolution : besoin vs offre doivent etre compares par leur contenu reel
|
||||||
|
function scoreDirect(textA: string, textB: string): number {
|
||||||
|
return jaccard(tokenize(textA), tokenize(textB))
|
||||||
|
}
|
||||||
|
|
||||||
export function matchSolution(fiches: CodevFiche[]): CodevMatch[] {
|
export function matchSolution(fiches: CodevFiche[], threshold = 0.18): CodevMatch[] {
|
||||||
const matches: CodevMatch[] = []
|
const matches: CodevMatch[] = []
|
||||||
for (const a of fiches) {
|
for (const a of fiches) {
|
||||||
for (const b of fiches) {
|
for (const b of fiches) {
|
||||||
if (a.id === b.id) continue
|
if (a.id === b.id) continue
|
||||||
const s = score(a.besoin, a.hashtags, b.offre, b.hashtags)
|
// Solution : on compare le TEXTE besoin de A avec le TEXTE offre de B
|
||||||
if (s >= THRESHOLD) {
|
// On ignore les hashtags pour differencier besoin et offre
|
||||||
|
const s = scoreDirect(a.besoin, b.offre)
|
||||||
|
if (s >= threshold) {
|
||||||
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'solution' })
|
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'solution' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,13 +63,14 @@ export function matchSolution(fiches: CodevFiche[]): CodevMatch[] {
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchAlliance(fiches: CodevFiche[]): CodevMatch[] {
|
export function matchAlliance(fiches: CodevFiche[], threshold = 0.25): CodevMatch[] {
|
||||||
const matches: CodevMatch[] = []
|
const matches: CodevMatch[] = []
|
||||||
for (let i = 0; i < fiches.length; i++) {
|
for (let i = 0; i < fiches.length; i++) {
|
||||||
for (let j = i + 1; j < fiches.length; j++) {
|
for (let j = i + 1; j < fiches.length; j++) {
|
||||||
const a = fiches[i], b = fiches[j]
|
const a = fiches[i], b = fiches[j]
|
||||||
|
// Alliance : besoins similaires — on compare hashtags si presents, sinon textes
|
||||||
const s = score(a.besoin, a.hashtags, b.besoin, b.hashtags)
|
const s = score(a.besoin, a.hashtags, b.besoin, b.hashtags)
|
||||||
if (s >= THRESHOLD) {
|
if (s >= threshold) {
|
||||||
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'alliance' })
|
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'alliance' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,13 +78,14 @@ export function matchAlliance(fiches: CodevFiche[]): CodevMatch[] {
|
|||||||
return matches
|
return matches
|
||||||
}
|
}
|
||||||
|
|
||||||
export function matchSurprise(fiches: CodevFiche[]): CodevMatch[] {
|
export function matchSurprise(fiches: CodevFiche[], threshold = 0.25): CodevMatch[] {
|
||||||
const matches: CodevMatch[] = []
|
const matches: CodevMatch[] = []
|
||||||
for (let i = 0; i < fiches.length; i++) {
|
for (let i = 0; i < fiches.length; i++) {
|
||||||
for (let j = i + 1; j < fiches.length; j++) {
|
for (let j = i + 1; j < fiches.length; j++) {
|
||||||
const a = fiches[i], b = fiches[j]
|
const a = fiches[i], b = fiches[j]
|
||||||
|
// Surprise : offres similaires
|
||||||
const s = score(a.offre, a.hashtags, b.offre, b.hashtags)
|
const s = score(a.offre, a.hashtags, b.offre, b.hashtags)
|
||||||
if (s >= THRESHOLD) {
|
if (s >= threshold) {
|
||||||
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'surprise' })
|
matches.push({ fromId: a.id, toId: b.id, score: s, mode: 'surprise' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,10 +96,11 @@ export function matchSurprise(fiches: CodevFiche[]): CodevMatch[] {
|
|||||||
export function computeMatches(
|
export function computeMatches(
|
||||||
fiches: CodevFiche[],
|
fiches: CodevFiche[],
|
||||||
mode: 'solution' | 'alliance' | 'surprise',
|
mode: 'solution' | 'alliance' | 'surprise',
|
||||||
|
threshold?: number,
|
||||||
): CodevMatch[] {
|
): CodevMatch[] {
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case 'solution': return matchSolution(fiches)
|
case 'solution': return matchSolution(fiches, threshold)
|
||||||
case 'alliance': return matchAlliance(fiches)
|
case 'alliance': return matchAlliance(fiches, threshold)
|
||||||
case 'surprise': return matchSurprise(fiches)
|
case 'surprise': return matchSurprise(fiches, threshold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user