Date : 23/12/2025
Status : IMPLEMENTEE
Fichier concerné : /app/src/analyzers/need-extractor.ts
Problème : Le scoring est basé uniquement sur la confidence Ollama (souvent 1.0 → score 100)
opportunity_score = Math.round((result.confidence || 0.5) * 100)
Résultat : 90% des besoins ont un score de 100, aucune différenciation.
| Facteur | Points max | Description |
|---|---|---|
| Mentions | 25 pts | +5 pts par mention (cap 25) |
| Score posts | 20 pts | Moyenne upvotes normalisée |
| Engagement | 15 pts | Total commentaires normalisé |
| Sources | 15 pts | +5 pts par source unique (cap 15) |
| Type besoin | 15 pts | wish=15, frustration=12, feature=10, question=5 |
| Willingness to pay | 10 pts | Bonus si détecté |
Total max : 100 pts
calculateOpportunityScore// /app/src/analyzers/scoring.ts
interface ScoringInput {
mentionCount: number;
uniqueSources: number;
avgPostScore: number;
totalComments: number;
needType: string;
willingToPay: boolean;
}
export function calculateOpportunityScore(input: ScoringInput): number {
let score = 0;
// Mentions (0-25 pts) - +5 par mention, cap 25
score += Math.min(25, input.mentionCount * 5);
// Score posts (0-20 pts) - normalisé sur 500 upvotes max
score += Math.min(20, Math.round((input.avgPostScore / 500) * 20));
// Engagement (0-15 pts) - normalisé sur 100 commentaires
score += Math.min(15, Math.round((input.totalComments / 100) * 15));
// Sources uniques (0-15 pts) - +5 par source
score += Math.min(15, input.uniqueSources * 5);
// Type de besoin (0-15 pts)
const typeScores: Record<string, number> = {
wish: 15,
frustration: 12,
feature_request: 10,
question: 5,
};
score += typeScores[input.needType] || 5;
// Willingness to pay (0-10 pts)
if (input.willingToPay) score += 10;
return Math.min(100, score);
}
extractNeed pour détecter willingness to pay// Dans le prompt Ollama, ajouter :
{
// ... existant ...
"willingToPay": true/false // "would pay", "shut up and take my money", etc.
}
// Lors de l'insertion d'un nouveau besoin
opportunity_score = calculateOpportunityScore({
mentionCount: 1,
uniqueSources: 1,
avgPostScore: post.score || 0,
totalComments: post.comment_count || 0,
needType: result.needType || "question",
willingToPay: result.willingToPay || false,
});
// Recalcule les scores tous les jours pour tenir compte des nouvelles mentions
export async function recalculateAllScores(): Promise<void> {
await sql`SET search_path TO needfinder`;
await sql`
UPDATE needs n SET
opportunity_score = (
-- Mentions (0-25)
LEAST(25, n.mention_count * 5) +
-- Score posts moyen (0-20)
LEAST(20, ROUND(COALESCE(
(SELECT AVG(p.score) FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 0
) / 500 * 20)) +
-- Engagement (0-15)
LEAST(15, ROUND(COALESCE(
(SELECT SUM(p.comment_count) FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 0
) / 100 * 15)) +
-- Sources uniques (0-15)
LEAST(15, COALESCE(
(SELECT COUNT(DISTINCT p.source_id) FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 1
) * 5) +
-- Type (0-15)
CASE n.need_type
WHEN 'wish' THEN 15
WHEN 'frustration' THEN 12
WHEN 'feature_request' THEN 10
ELSE 5
END +
-- Willing to pay (0-10)
CASE WHEN n.willing_to_pay THEN 10 ELSE 0 END
)
`;
}
| Avant | Après |
|---|---|
| 90% des besoins à 100 | Distribution 5-100 |
| Aucune différenciation | Top besoins = vrais signaux forts |
| Score statique | Score évolutif avec mentions |
willing_to_pay BOOLEAN DEFAULT false si absentescoring.ts avec la nouvelle fonctionneed-extractor.ts pour utiliser le nouveau scoringSET search_path TO needfinder;
-- Recalcule tous les scores
UPDATE needs n SET
opportunity_score = (
LEAST(25, n.mention_count * 5) +
LEAST(20, ROUND(COALESCE(
(SELECT AVG(p.score)::numeric FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 0
) / 500 * 20)) +
LEAST(15, ROUND(COALESCE(
(SELECT SUM(p.comment_count)::numeric FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 0
) / 100 * 15)) +
LEAST(15, COALESCE(
(SELECT COUNT(DISTINCT p.source_id) FROM mentions m
JOIN posts p ON p.id = m.post_id
WHERE m.need_id = n.id), 1
) * 5) +
CASE n.need_type
WHEN 'wish' THEN 15
WHEN 'frustration' THEN 12
WHEN 'feature_request' THEN 10
ELSE 5
END +
CASE WHEN n.willing_to_pay THEN 10 ELSE 0 END
);
Attente de ton GO pour implémenter.