Checkpoint: Simplification de la classification IA dans l'onglet Veille Stratégique : suppression de la catégorie médico-sociale (Handicap, PA, Enfance, etc.), remplacement de "Générale" par "Informationnelle", classification IA réduite au seul type de veille (réglementaire/concurrentielle/technologique/informationnelle). Migration BDD des données generale → informationnelle. 0 erreur TypeScript, 21 tests passés.
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
* Ces tests vérifient le comportement du module de classification IA :
|
||||
* - Fallback sur les règles en cas d'erreur LLM
|
||||
* - Résultat "hors périmètre" quand pertinent = false
|
||||
* - Classification correcte quand les deux étapes réussissent
|
||||
* - Classification correcte en une seule étape (pertinence + type de veille)
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
@@ -37,8 +37,7 @@ function makeLLMResponse(content: string) {
|
||||
}
|
||||
|
||||
const fallbackFn = () => ({
|
||||
categorie: "Autre" as const,
|
||||
typeVeille: "generale" as const,
|
||||
typeVeille: "informationnelle" as const,
|
||||
});
|
||||
|
||||
// ─── Tests ────────────────────────────────────────────────────────────────────
|
||||
@@ -48,28 +47,33 @@ describe("classifyArticle", () => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it("retourne le fallback (rules) si le LLM échoue à l'étape 1 (pertinence)", async () => {
|
||||
it("retourne le fallback (rules) si le LLM échoue", async () => {
|
||||
mockInvokeLLM.mockRejectedValueOnce(new Error("LLM timeout"));
|
||||
|
||||
const result = await classifyArticle("Titre test", "Résumé test", fallbackFn);
|
||||
|
||||
expect(result.classifiedBy).toBe("rules");
|
||||
expect(result.categorie).toBe("Autre");
|
||||
expect(result.typeVeille).toBe("informationnelle");
|
||||
expect(result.relevant).toBe(true); // on suppose pertinent par défaut
|
||||
expect(result.reason).toContain("règles");
|
||||
});
|
||||
|
||||
it("retourne le fallback (rules) si le LLM retourne un JSON malformé à l'étape 1", async () => {
|
||||
it("retourne le fallback (rules) si le LLM retourne un JSON malformé", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(makeLLMResponse("pas du JSON valide ici"));
|
||||
|
||||
const result = await classifyArticle("Titre test", "Résumé test", fallbackFn);
|
||||
|
||||
expect(result.classifiedBy).toBe("rules");
|
||||
expect(result.categorie).toBe("Autre");
|
||||
expect(result.typeVeille).toBe("informationnelle");
|
||||
});
|
||||
|
||||
it("retourne relevant=false et categorie=Autre si l'article est hors périmètre", async () => {
|
||||
it("retourne relevant=false et typeVeille=null si l'article est hors périmètre", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({ pertinent: false, raison: "Actualité politique générale sans lien médico-social" }))
|
||||
makeLLMResponse(JSON.stringify({
|
||||
pertinent: false,
|
||||
typeVeille: null,
|
||||
raison: "Actualité politique générale sans lien médico-social",
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await classifyArticle(
|
||||
@@ -79,25 +83,18 @@ describe("classifyArticle", () => {
|
||||
);
|
||||
|
||||
expect(result.relevant).toBe(false);
|
||||
expect(result.categorie).toBe("Autre");
|
||||
expect(result.typeVeille).toBeNull();
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
expect(result.reason).toContain("politique");
|
||||
});
|
||||
|
||||
it("classifie correctement un article pertinent en deux étapes", async () => {
|
||||
// Étape 1 : pertinent = true
|
||||
it("classifie correctement un article pertinent réglementaire en une seule étape", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({ pertinent: true, raison: "Concerne les EHPAD et la perte d'autonomie" }))
|
||||
);
|
||||
// Étape 2 : classification
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(
|
||||
JSON.stringify({
|
||||
categorie: "PA",
|
||||
typeVeille: "reglementaire",
|
||||
raison: "Décret relatif au financement des EHPAD",
|
||||
})
|
||||
)
|
||||
makeLLMResponse(JSON.stringify({
|
||||
pertinent: true,
|
||||
typeVeille: "reglementaire",
|
||||
raison: "Décret relatif au financement des EHPAD",
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await classifyArticle(
|
||||
@@ -107,50 +104,33 @@ describe("classifyArticle", () => {
|
||||
);
|
||||
|
||||
expect(result.relevant).toBe(true);
|
||||
expect(result.categorie).toBe("PA");
|
||||
expect(result.typeVeille).toBe("reglementaire");
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
});
|
||||
|
||||
it("retourne le fallback (rules) si l'étape 2 (classification) échoue", async () => {
|
||||
// Étape 1 : pertinent = true
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({ pertinent: true, raison: "Article pertinent" }))
|
||||
);
|
||||
// Étape 2 : erreur LLM
|
||||
mockInvokeLLM.mockRejectedValueOnce(new Error("LLM quota exceeded"));
|
||||
|
||||
const result = await classifyArticle("Titre pertinent", "Résumé pertinent", fallbackFn);
|
||||
|
||||
expect(result.classifiedBy).toBe("rules");
|
||||
expect(result.relevant).toBe(true);
|
||||
expect(result.reason).toContain("Pertinent");
|
||||
});
|
||||
|
||||
it("accepte les réponses LLM avec balises markdown ```json```", async () => {
|
||||
// Étape 1 avec balises markdown
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse("```json\n" + JSON.stringify({ pertinent: false, raison: "Hors périmètre" }) + "\n```")
|
||||
makeLLMResponse("```json\n" + JSON.stringify({
|
||||
pertinent: false,
|
||||
typeVeille: null,
|
||||
raison: "Hors périmètre",
|
||||
}) + "\n```")
|
||||
);
|
||||
|
||||
const result = await classifyArticle("Titre sport", "Résultats du championnat de football", fallbackFn);
|
||||
|
||||
expect(result.relevant).toBe(false);
|
||||
expect(result.typeVeille).toBeNull();
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
});
|
||||
|
||||
it("classe un article handicap correctement", async () => {
|
||||
it("classe un article handicap comme réglementaire", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({ pertinent: true, raison: "Concerne les IME et l'inclusion scolaire" }))
|
||||
);
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(
|
||||
JSON.stringify({
|
||||
categorie: "Handicap",
|
||||
typeVeille: "reglementaire",
|
||||
raison: "Circulaire sur l'inclusion scolaire des enfants en situation de handicap",
|
||||
})
|
||||
)
|
||||
makeLLMResponse(JSON.stringify({
|
||||
pertinent: true,
|
||||
typeVeille: "reglementaire",
|
||||
raison: "Circulaire sur l'inclusion scolaire des enfants en situation de handicap",
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await classifyArticle(
|
||||
@@ -159,8 +139,48 @@ describe("classifyArticle", () => {
|
||||
fallbackFn
|
||||
);
|
||||
|
||||
expect(result.categorie).toBe("Handicap");
|
||||
expect(result.relevant).toBe(true);
|
||||
expect(result.typeVeille).toBe("reglementaire");
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
});
|
||||
|
||||
it("classe un article technologique correctement", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({
|
||||
pertinent: true,
|
||||
typeVeille: "technologique",
|
||||
raison: "Déploiement d'un nouveau logiciel de gestion pour les ESMS",
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await classifyArticle(
|
||||
"Nouveau logiciel de gestion pour les établissements médico-sociaux",
|
||||
"Un éditeur lance une solution numérique dédiée à la gestion des ESMS.",
|
||||
fallbackFn
|
||||
);
|
||||
|
||||
expect(result.relevant).toBe(true);
|
||||
expect(result.typeVeille).toBe("technologique");
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
});
|
||||
|
||||
it("utilise informationnelle comme typeVeille par défaut si pertinent mais typeVeille null", async () => {
|
||||
mockInvokeLLM.mockResolvedValueOnce(
|
||||
makeLLMResponse(JSON.stringify({
|
||||
pertinent: true,
|
||||
typeVeille: null,
|
||||
raison: "Article pertinent mais type non déterminé",
|
||||
}))
|
||||
);
|
||||
|
||||
const result = await classifyArticle(
|
||||
"Rapport annuel sur le secteur médico-social",
|
||||
"Publication du rapport annuel de la CNSA sur l'état du secteur.",
|
||||
fallbackFn
|
||||
);
|
||||
|
||||
expect(result.relevant).toBe(true);
|
||||
expect(result.typeVeille).toBe("informationnelle"); // fallback sur informationnelle
|
||||
expect(result.classifiedBy).toBe("ia");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user