CognxSafeTrack commited on
Commit ·
74d7942
1
Parent(s): d9879cf
feat(ai): refactor coach feedback to 3-pillar format with mandatory browsing (Sprint 39)
Browse files
apps/api/src/routes/ai.ts
CHANGED
|
@@ -223,13 +223,13 @@ export async function aiRoutes(fastify: FastifyInstance) {
|
|
| 223 |
);
|
| 224 |
|
| 225 |
// 🌟 Standard Feedback UX: 3 lines 🌟
|
| 226 |
-
// 1. Encouragement/Validation (
|
| 227 |
-
// 2. Diagnostic (
|
| 228 |
-
// 3. Action / Help (
|
| 229 |
const formattedFeedback = `✨ *Coach XAMLÉ* :\n\n` +
|
| 230 |
-
`🌟 ${feedback.
|
| 231 |
-
`
|
| 232 |
-
`💡 ${feedback.
|
| 233 |
|
| 234 |
return {
|
| 235 |
success: true,
|
|
|
|
| 223 |
);
|
| 224 |
|
| 225 |
// 🌟 Standard Feedback UX: 3 lines 🌟
|
| 226 |
+
// 1. Encouragement/Validation (VALIDATION)
|
| 227 |
+
// 2. Diagnostic (ENRICHED VERSION)
|
| 228 |
+
// 3. Action / Help (ACTIONABLE ADVICE)
|
| 229 |
const formattedFeedback = `✨ *Coach XAMLÉ* :\n\n` +
|
| 230 |
+
`🌟 ${feedback.validation}\n\n` +
|
| 231 |
+
`🚀 ${feedback.enrichedVersion}\n\n` +
|
| 232 |
+
`💡 ${feedback.actionableAdvice}`;
|
| 233 |
|
| 234 |
return {
|
| 235 |
success: true,
|
apps/api/src/services/ai/__fixtures__/mock-data.ts
CHANGED
|
@@ -86,9 +86,9 @@ export const MOCK_DECK_COUTURE = {
|
|
| 86 |
|
| 87 |
export const MOCK_FEEDBACK = {
|
| 88 |
isQualified: true,
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
confidence: 95,
|
| 93 |
notes: "Réponse solide.",
|
| 94 |
missingElements: []
|
|
|
|
| 86 |
|
| 87 |
export const MOCK_FEEDBACK = {
|
| 88 |
isQualified: true,
|
| 89 |
+
validation: "Excellent travail ! Ta vision est claire et ambitieuse (🌟 Validation).",
|
| 90 |
+
enrichedVersion: "Tu proposes donc une solution innovante pour le marché local qui répond aux 15% de déficit observé par l'ANSD (🚀 Enrichissement).",
|
| 91 |
+
actionableAdvice: "Continue ainsi pour l'étape suivante, et focalise-toi sur ta supply chain B2B (💡 Conseil).",
|
| 92 |
confidence: 95,
|
| 93 |
notes: "Réponse solide.",
|
| 94 |
missingElements: []
|
apps/api/src/services/ai/index.ts
CHANGED
|
@@ -138,19 +138,18 @@ class AIService {
|
|
| 138 |
|
| 139 |
let searchContext = '';
|
| 140 |
let searchResults: any[] | undefined = undefined;
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
}
|
| 151 |
-
} catch (err) {
|
| 152 |
-
console.error('[AI_SERVICE] Search enrichment failed:', err);
|
| 153 |
}
|
|
|
|
|
|
|
| 154 |
}
|
| 155 |
|
| 156 |
const criteriaContext = exerciseCriteria
|
|
@@ -174,22 +173,22 @@ class AIService {
|
|
| 174 |
RÉPONSE DE L'ÉTUDIANT :
|
| 175 |
"${userInput}"
|
| 176 |
|
| 177 |
-
MISSIONS STRATÉGIQUES "
|
| 178 |
-
|
| 179 |
-
2. Extraction Métriques : Concurrence (J10), Projections (J11), The Ask (J12).
|
| 180 |
-
3. Feedback "Mini-Cours" : Rédige une Reformulation stratégique, un Praise enthousiaste et une Action (prochaine étape).
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
|
| 185 |
-
PROTOCOLE DATA STEWARD (
|
| 186 |
-
-
|
| 187 |
-
- TRANSPARENCE : Cite toujours la source (ex:
|
| 188 |
-
- HONNÊTETÉ : Si aucune donnée fiable n'est trouvée, propose l'estimation de l'utilisateur.
|
| 189 |
-
|
| 190 |
-
TON INSTITUTIONNEL (Browsing) :
|
| 191 |
-
Utilise les données de marché pour impressionner l'élève :
|
| 192 |
-
"Félicitations ! Ton approche est validée par les chiffres : selon [Source], le marché de [Secteur] est évalué à [Valeur]. J'ai intégré cette preuve de marché dans ta Slide 5."
|
| 193 |
|
| 194 |
DÉTANCHÉITÉ LINGUISTIQUE : 100% ${userLanguage === 'WOLOF' ? 'WOLOF (ñ, ë, é)' : 'Français institutionnel'}. Zéro mélange.
|
| 195 |
`;
|
|
|
|
| 138 |
|
| 139 |
let searchContext = '';
|
| 140 |
let searchResults: any[] | undefined = undefined;
|
| 141 |
+
// 🚀 Brique 1: Activation du Browsing (Obligatoire)
|
| 142 |
+
console.log(`[AI_SERVICE] 🔍 Triggering Market Search for Day ${dayNumber} (Always ON)...`);
|
| 143 |
+
const query = `${activityLabel} ${region} Sénégal marché chiffres statistiques data`;
|
| 144 |
+
try {
|
| 145 |
+
const results = await searchService.search(query);
|
| 146 |
+
if (results && results.length > 0) {
|
| 147 |
+
searchResults = results;
|
| 148 |
+
searchContext = `\n🌐 DONNÉES DE MARCHÉ RÉELLES (Google Search) :\n${results.map(r => `- ${r.title}: ${r.snippet}`).join('\n')}\n`;
|
| 149 |
+
console.log(`[AI_SERVICE] ✅ Search enrichment added (${results.length} results).`);
|
|
|
|
|
|
|
|
|
|
| 150 |
}
|
| 151 |
+
} catch (err) {
|
| 152 |
+
console.error('[AI_SERVICE] Search enrichment failed:', err);
|
| 153 |
}
|
| 154 |
|
| 155 |
const criteriaContext = exerciseCriteria
|
|
|
|
| 173 |
RÉPONSE DE L'ÉTUDIANT :
|
| 174 |
"${userInput}"
|
| 175 |
|
| 176 |
+
MISSIONS STRATÉGIQUES "SENIOR BUSINESS COACH" (CORRECTION RADICALE) :
|
| 177 |
+
Tu es un consultant pragmatique, pas un professeur pointilleux. Rédige un feedback d'au minimum 15 lignes de haute densité.
|
|
|
|
|
|
|
| 178 |
|
| 179 |
+
LES 3 PILIERS OBLIGATOIRES DU FEEDBACK :
|
| 180 |
+
1. 🌟 Validation (Pilier 1) : Félicite et valide l'idée de l'utilisateur avec l'enthousiasme d'un investisseur.
|
| 181 |
+
2. 🚀 Version Enrichie (Pilier 2) : Réécris sa phrase de manière exécutive en y intégrant OBLIGATOIREMENT des données chiffrées réelles trouvées dans ta recherche (ex: taille du marché ANSD, nombre de boutiques) et des termes stratégiques (supply chain, B2B, rétention).
|
| 182 |
+
3. 💡 Conseil Actionnable (Pilier 3) : Donne un conseil de terrain hyper concret basé là aussi sur la recherche web (ex: "Conseil : Le créneau 6h-8h est critique dans les boutiques de Dakar...").
|
| 183 |
+
|
| 184 |
+
⚠️ INTERDICTION ABSOLUE (Anti-Remediation Loop) :
|
| 185 |
+
- Tu NE DOIS PLUS JAMAIS demander à l'utilisateur de s'expliquer davantage (ex: "Quel est l'âge exact ?", "Combien gagnes-tu ?").
|
| 186 |
+
- S'il manque un détail mais que l'idée est claire, c'est TOI qui apportes le savoir (donne les tranches d'âges classiques du secteur, donne les revenus moyens du pays).
|
| 187 |
+
- Tu es là pour ENRICHIR sa vision, pas pour lui faire passer un interrogatoire. Ne pose AUCUNE question bloquante à la fin.
|
| 188 |
|
| 189 |
+
PROTOCOLE DATA STEWARD (Intégrité Géographique) :
|
| 190 |
+
- Si l'utilisateur est à ${region}, donne lui les vrais chiffres de ${region}. S'ils n'existent pas, back-up sur les chiffres du Sénégal.
|
| 191 |
+
- TRANSPARENCE : Cite toujours la source formelle (ex: Agence Nationale de la Statistique et de la Démographie, Banque Mondiale, Direction du commerce, etc.).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
DÉTANCHÉITÉ LINGUISTIQUE : 100% ${userLanguage === 'WOLOF' ? 'WOLOF (ñ, ë, é)' : 'Français institutionnel'}. Zéro mélange.
|
| 194 |
`;
|
apps/api/src/services/ai/types.ts
CHANGED
|
@@ -50,9 +50,9 @@ export type PitchDeckData = z.infer<typeof PitchDeckSchema>;
|
|
| 50 |
|
| 51 |
// Schema for AI Feedback (Coach)
|
| 52 |
export const FeedbackSchema = z.object({
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
isQualified: z.boolean().describe("Whether the answer meets the lesson's criteria"),
|
| 57 |
missingElements: z.array(z.string()).describe("List of IDs from criteria that were not met"),
|
| 58 |
confidence: z.number().min(0).max(100).describe("AI confidence in the evaluation"),
|
|
|
|
| 50 |
|
| 51 |
// Schema for AI Feedback (Coach)
|
| 52 |
export const FeedbackSchema = z.object({
|
| 53 |
+
validation: z.string().describe("Félicitations et validation claire de la réponse de l'utilisateur (Pilier 1)"),
|
| 54 |
+
enrichedVersion: z.string().describe("Réécriture de la phrase y intégrant des données chiffrées réelles et stratégiques issues de la recherche (Pilier 2)"),
|
| 55 |
+
actionableAdvice: z.string().describe("Conseil terrain concret, actionnable et documenté pour cette étape (Pilier 3)"),
|
| 56 |
isQualified: z.boolean().describe("Whether the answer meets the lesson's criteria"),
|
| 57 |
missingElements: z.array(z.string()).describe("List of IDs from criteria that were not met"),
|
| 58 |
confidence: z.number().min(0).max(100).describe("AI confidence in the evaluation"),
|
apps/whatsapp-worker/src/index.ts
CHANGED
|
@@ -89,8 +89,8 @@ const worker = new Worker('whatsapp-queue', async (job: Job) => {
|
|
| 89 |
|
| 90 |
if (feedbackRes.ok) {
|
| 91 |
feedbackData = await feedbackRes.json();
|
| 92 |
-
if (feedbackData.
|
| 93 |
-
feedbackMsg = `${feedbackData.
|
| 94 |
} else if (feedbackData.text) {
|
| 95 |
feedbackMsg = feedbackData.text;
|
| 96 |
} else {
|
|
|
|
| 89 |
|
| 90 |
if (feedbackRes.ok) {
|
| 91 |
feedbackData = await feedbackRes.json();
|
| 92 |
+
if (feedbackData.validation && feedbackData.enrichedVersion && feedbackData.actionableAdvice) {
|
| 93 |
+
feedbackMsg = `🌟 ${feedbackData.validation}\n\n🚀 ${feedbackData.enrichedVersion}\n\n💡 Conseil de Terrain :\n${feedbackData.actionableAdvice}`;
|
| 94 |
} else if (feedbackData.text) {
|
| 95 |
feedbackMsg = feedbackData.text;
|
| 96 |
} else {
|