CognxSafeTrack commited on
Commit
9ca5873
·
1 Parent(s): 4967196

fix(core): remove AI jus-de-fruits hallucination, expand onboarding sectors to 8, streamline pedagogy menu and lock REPRISE badge

Browse files
apps/api/src/services/ai/index.ts CHANGED
@@ -117,8 +117,9 @@ ${criteriaContext}
117
 
118
  1. RÈGLE D'OR : ANTI-HALLUCINATION
119
  Agnosticisme Sectoriel : Tu ne dois JAMAIS utiliser d'exemples liés aux forages, à l'irrigation, aux agriculteurs ou aux fourneaux, SAUF si l'activité déclarée de l'étudiant (${activityLabel}) y est explicitement liée.
120
- Zéro-Shot Contextuel : Si l'étudiant vend des jus de fruits, tous tes exemples de coûts, de clients et de marketing doivent porter UNIQUEMENT sur les jus de fruits.
121
- Interdiction de généralisation paresseuse : Ne remplis pas les réponses avec du contenu générique pour atteindre une longueur de texte. Reste concis, précis et ancré dans la réalité du métier de l'utilisateur.
 
122
 
123
  2. LOGIQUE D'ÉVALUATION
124
  Pour chaque feedback, base-toi strictement sur les CRITÈRES D'ÉVALUATION fournis :
@@ -190,10 +191,11 @@ ${businessContext}
190
  ${prevContext}
191
 
192
  1. RÈGLE D'OR : ANTI-HALLUCINATION
193
- Adapte TOUS les exemples de la leçon pour qu'ils soient directement liés à la véritable activité de l'étudiant (${activityLabel}). N'invente RIEN en dehors de son domaine réel. Si l'activité est inconnue ou trop courte, pose une question de clarification au lieu d'halluciner un secteur.
194
  Agnosticisme Sectoriel : Tu ne dois JAMAIS utiliser d'exemples liés aux forages, à l'irrigation, aux agriculteurs ou aux fourneaux, SAUF si l'activité déclarée de l'étudiant (${activityLabel}) y est explicitement liée.
195
- Zéro-Shot Contextuel : Si l'étudiant vend des jus de fruits, tous tes exemples doivent porter UNIQUEMENT sur les jus de fruits.
196
- Interdiction de généralisation paresseuse : Ne remplis pas les réponses avec du contenu générique. Reste direct et ancré dans le métier de l'utilisateur. Garde la VALEUR PÉDAGOGIQUE EXACTEMENT la même.
 
197
 
198
  2. NORMALISATION WOLOF & TONE
199
  STT Cleanup : Applique les règles de normalisation Wolof (ex: 'damae' -> 'damay', 'jendi' -> 'jënd') dans ton texte.
 
117
 
118
  1. RÈGLE D'OR : ANTI-HALLUCINATION
119
  Agnosticisme Sectoriel : Tu ne dois JAMAIS utiliser d'exemples liés aux forages, à l'irrigation, aux agriculteurs ou aux fourneaux, SAUF si l'activité déclarée de l'étudiant (${activityLabel}) y est explicitement liée.
120
+ Zéro-Shot Contextuel : Adapte tous tes exemples UNIQUEMENT au métier réel de l'étudiant (${activityLabel}). Interdiction formelle d'inventer un secteur d'activité.
121
+ Règle de Sécurité : Si l'utilisateur n'a pas défini son projet ou si l'activité est inconnue, utilise le terme générique "ton business" et reste sur des principes théoriques abstraits.
122
+ Interdiction de généralisation paresseuse : Reste concis, précis et ancré dans la réalité de l'utilisateur.
123
 
124
  2. LOGIQUE D'ÉVALUATION
125
  Pour chaque feedback, base-toi strictement sur les CRITÈRES D'ÉVALUATION fournis :
 
191
  ${prevContext}
192
 
193
  1. RÈGLE D'OR : ANTI-HALLUCINATION
194
+ Adapte TOUS les exemples de la leçon pour qu'ils soient directement liés à la véritable activité de l'étudiant (${activityLabel}).
195
  Agnosticisme Sectoriel : Tu ne dois JAMAIS utiliser d'exemples liés aux forages, à l'irrigation, aux agriculteurs ou aux fourneaux, SAUF si l'activité déclarée de l'étudiant (${activityLabel}) y est explicitement liée.
196
+ Zéro-Shot Contextuel : Adapte tous tes exemples UNIQUEMENT au métier réel de l'étudiant (${activityLabel}). Interdiction formelle d'inventer un secteur d'activité.
197
+ Règle de Sécurité : Si l'utilisateur n'a pas défini son projet ou si l'activité est inconnue, utilise le terme générique "ton business" et reste sur des principes théoriques abstraits.
198
+ Interdiction de généralisation paresseuse : Reste direct et ancré dans le métier de l'utilisateur. Garde la VALEUR PÉDAGOGIQUE EXACTEMENT la même.
199
 
200
  2. NORMALISATION WOLOF & TONE
201
  STT Cleanup : Applique les règles de normalisation Wolof (ex: 'damae' -> 'damay', 'jendi' -> 'jënd') dans ton texte.
apps/api/src/services/whatsapp.ts CHANGED
@@ -1,5 +1,5 @@
1
  import { prisma } from './prisma';
2
- import { scheduleMessage, enrollUser, whatsappQueue, scheduleInteractiveButtons } from './queue';
3
 
4
  export class WhatsAppService {
5
  private static normalizeCommand(text: string): string {
@@ -223,11 +223,25 @@ export class WhatsAppService {
223
  ? "Parfait, nous allons continuer en Français ! 🇫🇷\nDans quel domaine d'activité te trouves-tu ?"
224
  : "Baax na, dinanu wéy ci Wolof ! 🇸🇳\nCi ban mbir ngay yëngu ?";
225
 
226
- await scheduleInteractiveButtons(user.id, promptText, [
227
- { id: 'SEC_COMMERCE', title: newLang === 'FR' ? 'Commerce / Vente' : 'Njaay / Commerce' },
228
- { id: 'SEC_AGRI', title: newLang === 'FR' ? 'Agri / Élevage' : 'Mbay / Samm' },
229
- { id: 'SEC_AUTRE', title: newLang === 'FR' ? 'Autre secteur' : 'Beneen mbir' }
230
- ]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  return;
232
  }
233
 
 
1
  import { prisma } from './prisma';
2
+ import { scheduleMessage, enrollUser, whatsappQueue, scheduleInteractiveButtons, scheduleInteractiveList } from './queue';
3
 
4
  export class WhatsAppService {
5
  private static normalizeCommand(text: string): string {
 
223
  ? "Parfait, nous allons continuer en Français ! 🇫🇷\nDans quel domaine d'activité te trouves-tu ?"
224
  : "Baax na, dinanu wéy ci Wolof ! 🇸🇳\nCi ban mbir ngay yëngu ?";
225
 
226
+ await scheduleInteractiveList(
227
+ user.id,
228
+ newLang === 'FR' ? "Ton secteur" : "Sa Mbir",
229
+ promptText,
230
+ newLang === 'FR' ? "Secteurs" : "Tànn",
231
+ [{
232
+ title: newLang === 'FR' ? 'Liste' : 'Mbir',
233
+ rows: [
234
+ { id: 'SEC_COMMERCE', title: newLang === 'FR' ? 'Commerce / Vente' : 'Njaay' },
235
+ { id: 'SEC_AGRI', title: newLang === 'FR' ? 'Agri / Élevage' : 'Mbay / Samm' },
236
+ { id: 'SEC_FOOD', title: newLang === 'FR' ? 'Alimentation / Rest.' : 'Lekk / Restauration' },
237
+ { id: 'SEC_COUTURE', title: newLang === 'FR' ? 'Couture / Mode' : 'Couture' },
238
+ { id: 'SEC_BEAUTE', title: newLang === 'FR' ? 'Beauté / Bien-être' : 'Rafet' },
239
+ { id: 'SEC_TRANSPORT', title: newLang === 'FR' ? 'Transport / Livr.' : 'Transport / Yëgël' },
240
+ { id: 'SEC_TECH', title: newLang === 'FR' ? 'Tech / Digital' : 'Tech / Digital' },
241
+ { id: 'SEC_AUTRE', title: newLang === 'FR' ? 'Autre secteur' : 'Beneen mbir' }
242
+ ]
243
+ }]
244
+ );
245
  return;
246
  }
247
 
apps/whatsapp-worker/src/pedagogy.ts CHANGED
@@ -126,8 +126,18 @@ export async function sendLessonDay(userId: string, trackId: string, dayNumber:
126
  let badgeText = '';
127
  const badges = (userProgress?.badges as string[]) || [];
128
  if (badges.length > 0) {
129
- const lastBadge = badges[badges.length - 1];
130
- badgeText = `\nBadge : ${lastBadge} ${BADGE_EMOJIS[lastBadge] || '🏅'}`;
 
 
 
 
 
 
 
 
 
 
131
  }
132
 
133
  const dayDisplay = dayNumber === 1.5 ? '1bis' : Math.floor(dayNumber).toString();
@@ -273,39 +283,39 @@ export async function sendLessonDay(userId: string, trackId: string, dayNumber:
273
 
274
  // 🌟 3. Send Action LIST menu ────────────────────────────────────────────────
275
  // Shown after every lesson so the user knows their options
276
- await sendInteractiveListMessage(
277
- user.phone,
278
- isWolof ? `Jour ${dayNumber}` : `Leçon ${dayNumber}`,
279
- isWolof
280
- ? "Seetee ci suuf ban jëf ngay def :"
281
- : "Que veux-tu faire maintenant ?",
282
- isWolof ? "Tànnal" : "Choisir",
283
- [{
284
- title: isWolof ? "Jëfandikoo" : "Actions",
285
- rows: [
286
- {
287
- id: `DAY${dayNumber}_REPLAY`,
288
- title: isWolof ? "🎧 Dégg ci kanam" : "🎧 Réécouter",
289
- description: isWolof ? "Waxtu bi ci kaw" : "Réécouter la leçon"
290
- },
291
- {
292
- id: `DAY${dayNumber}_PROMPT`,
293
- title: isWolof ? "📝 Lëjj bi" : "📝 Faire l'exercice",
294
- description: isWolof ? "Jàngaat laaj bi" : "Relire la question"
295
- },
296
- {
297
- id: `DAY${dayNumber}_EXERCISE`,
298
- title: isWolof ? "🎙️ Yónni tontu" : "🎙️ Répondre",
299
- description: isWolof ? "Yónnee sa tontu ji" : "Envoyer ta réponse"
300
- },
301
- {
302
- id: `DAY${dayNumber}_CONTINUE`,
303
- title: isWolof ? "⏭️ Ci kanam" : "⏭️ Passer",
304
- description: isWolof ? "Dem ci Jour " + (dayNumber + 1) : "Passer au Jour " + (dayNumber + 1)
305
- }
306
- ]
307
- }]
308
- );
309
 
310
  // 🌟 4. Update User Progress to PENDING 🌟
311
  await prisma.userProgress.upsert({
 
126
  let badgeText = '';
127
  const badges = (userProgress?.badges as string[]) || [];
128
  if (badges.length > 0) {
129
+ let lastBadge = badges[badges.length - 1];
130
+
131
+ // 🚨 REMEDIATION BADGE GUARD: Hide 'REPRISE' on normal integer days
132
+ if (lastBadge === 'REPRISE' && dayNumber % 1 === 0) {
133
+ const nonRepriseBadges = badges.filter(b => b !== 'REPRISE');
134
+ if (nonRepriseBadges.length > 0) {
135
+ lastBadge = nonRepriseBadges[nonRepriseBadges.length - 1];
136
+ badgeText = `\nBadge : ${lastBadge} ${BADGE_EMOJIS[lastBadge] || '🏅'}`;
137
+ }
138
+ } else {
139
+ badgeText = `\nBadge : ${lastBadge} ${BADGE_EMOJIS[lastBadge] || '🏅'}`;
140
+ }
141
  }
142
 
143
  const dayDisplay = dayNumber === 1.5 ? '1bis' : Math.floor(dayNumber).toString();
 
283
 
284
  // 🌟 3. Send Action LIST menu ────────────────────────────────────────────────
285
  // Shown after every lesson so the user knows their options
286
+ if (dayNumber === 1) {
287
+ // Direct invitation to respond for Day 1 to reduce friction
288
+ await sendTextMessage(
289
+ user.phone,
290
+ isWolof
291
+ ? "🎙️ Lëjj bi: Tontul kàddu gi ci dëbb (vocal) walla mbind (texte)."
292
+ : "🎙️ À toi de jouer ! Réponds à l'exercice ci-dessus par message vocal ou texte."
293
+ );
294
+ } else {
295
+ await sendInteractiveListMessage(
296
+ user.phone,
297
+ isWolof ? `Jour ${dayNumber}` : `Leçon ${dayNumber}`,
298
+ isWolof
299
+ ? "Seetee ci suuf ban jëf ngay def :"
300
+ : "Que veux-tu faire maintenant ?",
301
+ isWolof ? "Tànnal" : "Choisir",
302
+ [{
303
+ title: isWolof ? "Jëfandikoo" : "Actions",
304
+ rows: [
305
+ {
306
+ id: `DAY${dayNumber}_REPLAY`,
307
+ title: isWolof ? "🎧 Dégg ci kanam" : "🎧 Réécouter",
308
+ description: isWolof ? "Waxtu bi ci kaw" : "Réécouter la leçon"
309
+ },
310
+ {
311
+ id: `DAY${dayNumber}_EXERCISE`,
312
+ title: isWolof ? "🎙️ Yónni tontu" : "🎙️ Répondre",
313
+ description: isWolof ? "Dëbb (vocal) walla mbind" : "Message vocal ou texte"
314
+ }
315
+ ]
316
+ }]
317
+ );
318
+ }
319
 
320
  // 🌟 4. Update User Progress to PENDING 🌟
321
  await prisma.userProgress.upsert({