Checkpoint: Classification IA en deux étapes (filtre pertinence + classification catégorie) intégrée dans le moteur RSS. Inclut : module aiClassifier.ts avec les deux prompts, intégration dans rssEngine.ts avec fallback sur règles, procédures tRPC reclassifyWithAI pour veille et AAP, badge IA violet dans VeilleDashboard, bouton "Classer avec l'IA" (admin), section "Analyse par l'IA" dans la boîte de dialogue détail, 7 nouveaux tests Vitest (20 tests au total). Migration BDD appliquée (4 nouveaux champs IA dans veille_items et aap_items).
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
* et insère les nouveaux articles dans veille_items ou aap_items.
|
||||
*
|
||||
* Enrichissement automatique :
|
||||
* - Classification IA en deux étapes (pertinence + catégorie) avec fallback sur mots-clés
|
||||
* - AAP : région (toujours Auvergne-Rhône-Alpes) + département extrait du titre/description
|
||||
* - Veille : territoire, catégorie (Handicap/PA/Enfance/Précarité/Sanitaire/Autre), niveau
|
||||
*
|
||||
@@ -21,6 +22,7 @@ import {
|
||||
type RssFeed,
|
||||
} from "../drizzle/schema";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { classifyArticle } from "./aiClassifier";
|
||||
|
||||
// ─── Types internes ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -349,13 +351,26 @@ async function processFeed(feed: RssFeed): Promise<FetchResult> {
|
||||
const dedupKey = dedupHash(normalizedTitle + "|" + (feed.feedType ?? ""));
|
||||
|
||||
if (feed.feedType === "veille") {
|
||||
const matchedRule = applyAutoRules(title, description, rules);
|
||||
const typeVeille = (matchedRule?.typeVeille ?? feed.defaultTypeVeille ?? "generale") as
|
||||
"reglementaire" | "concurrentielle" | "technologique" | "generale";
|
||||
|
||||
const categorie = detectVeilleCategorie(fullText);
|
||||
// ── Classification IA (avec fallback sur mots-clés) ──────────────────
|
||||
const { niveau, territoire } = detectVeilleNiveauTerritoire(fullText);
|
||||
|
||||
const aiResult = await classifyArticle(
|
||||
title,
|
||||
description,
|
||||
() => {
|
||||
const matchedRule = applyAutoRules(title, description, rules);
|
||||
return {
|
||||
categorie: detectVeilleCategorie(fullText),
|
||||
typeVeille: (matchedRule?.typeVeille ?? feed.defaultTypeVeille ?? "generale") as
|
||||
"reglementaire" | "concurrentielle" | "technologique" | "generale",
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const categorie = aiResult.categorie;
|
||||
const typeVeille = (aiResult.typeVeille ?? feed.defaultTypeVeille ?? "generale") as
|
||||
"reglementaire" | "concurrentielle" | "technologique" | "generale";
|
||||
|
||||
try {
|
||||
// Essayer d'insérer
|
||||
await db.insert(veilleItems).values({
|
||||
@@ -370,6 +385,10 @@ async function processFeed(feed: RssFeed): Promise<FetchResult> {
|
||||
lien: link || null,
|
||||
typeVeille,
|
||||
datePublication: pubDate,
|
||||
iaRelevant: aiResult.relevant,
|
||||
iaCategorie: aiResult.categorie,
|
||||
iaClassifiedBy: aiResult.classifiedBy,
|
||||
iaReason: aiResult.reason,
|
||||
});
|
||||
result.newItems++;
|
||||
} catch (e: any) {
|
||||
@@ -396,12 +415,25 @@ async function processFeed(feed: RssFeed): Promise<FetchResult> {
|
||||
}
|
||||
}
|
||||
} else if (feed.feedType === "aap") {
|
||||
const matchedRule = applyAutoRules(title, description, rules);
|
||||
const categorie = (matchedRule?.categorieAap ?? feed.defaultCategorieAap ?? "Autre") as
|
||||
"Handicap" | "PA" | "Enfance" | "Précarité" | "Sanitaire" | "Autre";
|
||||
|
||||
// ── Classification IA pour les AAP ───────────────────────────────────
|
||||
const { region, departement } = detectAapGeo(fullText);
|
||||
|
||||
const aiResult = await classifyArticle(
|
||||
title,
|
||||
description,
|
||||
() => {
|
||||
const matchedRule = applyAutoRules(title, description, rules);
|
||||
return {
|
||||
categorie: (matchedRule?.categorieAap ?? feed.defaultCategorieAap ?? "Autre") as
|
||||
"Handicap" | "PA" | "Enfance" | "Précarité" | "Sanitaire" | "Autre",
|
||||
typeVeille: "generale" as const,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
const categorie = aiResult.categorie as
|
||||
"Handicap" | "PA" | "Enfance" | "Précarité" | "Sanitaire" | "Autre";
|
||||
|
||||
try {
|
||||
await db.insert(aapItems).values({
|
||||
dedupKey,
|
||||
@@ -412,6 +444,10 @@ async function processFeed(feed: RssFeed): Promise<FetchResult> {
|
||||
departements: JSON.stringify(departement ? [departement] : []),
|
||||
lien: link || null,
|
||||
datePublication: pubDate,
|
||||
iaRelevant: aiResult.relevant,
|
||||
iaCategorie: aiResult.categorie,
|
||||
iaClassifiedBy: aiResult.classifiedBy,
|
||||
iaReason: aiResult.reason,
|
||||
});
|
||||
result.newItems++;
|
||||
} catch (e: any) {
|
||||
|
||||
Reference in New Issue
Block a user