+
+
+
+
+
Erreurs de classification
+
+
+ Fallbacks IA détectés pendant la lecture des flux RSS. Les articles concernés ont été importés avec les règles de repli.
+
+
+
+
+
+
+
+
+
+
+
Fallbacks enregistrés
+
+ {total}
+
+
+
+
+
+
+ Comportement de sécurité
+
+ Import maintenu via règles de repli
+
+
+
+
+
+
+ {errorsQuery.isLoading ? (
+
+ ) : errorsQuery.isError ? (
+
+
+
Le rapport ne peut pas être chargé
+
{errorsQuery.error.message}
+
+ ) : errors.length === 0 ? (
+
+
+
Aucune erreur de classification
+
Les prochains fallbacks IA apparaîtront ici.
+
+ ) : (
+
+
+
+
+ | Date |
+ Flux |
+ Article |
+ Cause technique |
+
+
+
+ {errors.map((error) => {
+ const type = typeConfig[error.feedType];
+ return (
+
+ | {formatDate(error.occurredAt)} |
+
+
+ {type.label}
+ {error.feedName}
+
+ |
+
+ {error.articleUrl ? (
+
+ {error.articleTitle}
+
+ ) : {error.articleTitle}}
+ |
+ {error.errorMessage} |
+
+ );
+ })}
+
+
+
+ )}
+
+
+
+ {totalPages > 1 && (
+
+
+ Page {page} / {totalPages}
+
+
+ )}
+
+ );
+}
diff --git a/drizzle/0013_odd_grim_reaper.sql b/drizzle/0013_odd_grim_reaper.sql
new file mode 100644
index 0000000..388140f
--- /dev/null
+++ b/drizzle/0013_odd_grim_reaper.sql
@@ -0,0 +1,11 @@
+CREATE TABLE `classification_errors` (
+ `id` int AUTO_INCREMENT NOT NULL,
+ `feedId` int,
+ `feedName` varchar(255) NOT NULL,
+ `feedType` enum('veille','aap') NOT NULL,
+ `articleTitle` text NOT NULL,
+ `articleUrl` text,
+ `errorMessage` text NOT NULL,
+ `occurredAt` timestamp NOT NULL DEFAULT (now()),
+ CONSTRAINT `classification_errors_id` PRIMARY KEY(`id`)
+);
diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json
new file mode 100644
index 0000000..5d25532
--- /dev/null
+++ b/drizzle/meta/0013_snapshot.json
@@ -0,0 +1,1135 @@
+{
+ "version": "5",
+ "dialect": "mysql",
+ "id": "16abfc91-2357-4963-a504-f4e544317178",
+ "prevId": "af8c04b5-9744-46e0-a2f3-12394683c8b4",
+ "tables": {
+ "aap_items": {
+ "name": "aap_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "dedupKey": {
+ "name": "dedupKey",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "titre": {
+ "name": "titre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "categorie": {
+ "name": "categorie",
+ "type": "enum('Handicap','PA','Enfance','Précarité','Sanitaire','Autre')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "region": {
+ "name": "region",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "departement": {
+ "name": "departement",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "departements": {
+ "name": "departements",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "dateCloture": {
+ "name": "dateCloture",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "datePublication": {
+ "name": "datePublication",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lien": {
+ "name": "lien",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "importedAt": {
+ "name": "importedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "iaRelevant": {
+ "name": "iaRelevant",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaCategorie": {
+ "name": "iaCategorie",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaClassifiedBy": {
+ "name": "iaClassifiedBy",
+ "type": "enum('ia','rules')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaReason": {
+ "name": "iaReason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaResume": {
+ "name": "iaResume",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "aap_items_id": {
+ "name": "aap_items_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "aap_items_dedupKey_unique": {
+ "name": "aap_items_dedupKey_unique",
+ "columns": [
+ "dedupKey"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "app_settings": {
+ "name": "app_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "key": {
+ "name": "key",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "app_settings_id": {
+ "name": "app_settings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "app_settings_key_unique": {
+ "name": "app_settings_key_unique",
+ "columns": [
+ "key"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "article_reads": {
+ "name": "article_reads",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "articleType": {
+ "name": "articleType",
+ "type": "enum('veille','aap')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "articleId": {
+ "name": "articleId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "readAt": {
+ "name": "readAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {
+ "article_reads_user_type_article_unique": {
+ "name": "article_reads_user_type_article_unique",
+ "columns": [
+ "userId",
+ "articleType",
+ "articleId"
+ ],
+ "isUnique": true
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "article_reads_id": {
+ "name": "article_reads_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "classification_errors": {
+ "name": "classification_errors",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "feedId": {
+ "name": "feedId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "feedName": {
+ "name": "feedName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "feedType": {
+ "name": "feedType",
+ "type": "enum('veille','aap')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "articleTitle": {
+ "name": "articleTitle",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "articleUrl": {
+ "name": "articleUrl",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "occurredAt": {
+ "name": "occurredAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "classification_errors_id": {
+ "name": "classification_errors_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "ideas": {
+ "name": "ideas",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "userId": {
+ "name": "userId",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "userName": {
+ "name": "userName",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "titre": {
+ "name": "titre",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "message": {
+ "name": "message",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "statut": {
+ "name": "statut",
+ "type": "enum('ouvert','en_cours','resolu','ferme')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'ouvert'"
+ },
+ "reponseAdmin": {
+ "name": "reponseAdmin",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reponduPar": {
+ "name": "reponduPar",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "reponduAt": {
+ "name": "reponduAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "ideas_id": {
+ "name": "ideas_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "import_logs": {
+ "name": "import_logs",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "fileType": {
+ "name": "fileType",
+ "type": "enum('veille','aap')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "status": {
+ "name": "status",
+ "type": "enum('success','partial','error')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "totalRows": {
+ "name": "totalRows",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "newRows": {
+ "name": "newRows",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "skippedRows": {
+ "name": "skippedRows",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": 0
+ },
+ "errorMessage": {
+ "name": "errorMessage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "details": {
+ "name": "details",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "startedAt": {
+ "name": "startedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "completedAt": {
+ "name": "completedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "import_logs_id": {
+ "name": "import_logs_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "local_users": {
+ "name": "local_users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "passwordHash": {
+ "name": "passwordHash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('admin','user','readonly')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "azureAdId": {
+ "name": "azureAdId",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ },
+ "lastSignedIn": {
+ "name": "lastSignedIn",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "local_users_id": {
+ "name": "local_users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "local_users_username_unique": {
+ "name": "local_users_username_unique",
+ "columns": [
+ "username"
+ ]
+ },
+ "local_users_azureAdId_unique": {
+ "name": "local_users_azureAdId_unique",
+ "columns": [
+ "azureAdId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "processed_dedup_keys": {
+ "name": "processed_dedup_keys",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "dedupKey": {
+ "name": "dedupKey",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "feedType": {
+ "name": "feedType",
+ "type": "enum('veille','aap')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "processedAt": {
+ "name": "processedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "processed_dedup_keys_id": {
+ "name": "processed_dedup_keys_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "processed_dedup_keys_dedupKey_unique": {
+ "name": "processed_dedup_keys_dedupKey_unique",
+ "columns": [
+ "dedupKey"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "rss_feeds": {
+ "name": "rss_feeds",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "feedType": {
+ "name": "feedType",
+ "type": "enum('veille','aap')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "defaultTypeVeille": {
+ "name": "defaultTypeVeille",
+ "type": "enum('reglementaire','concurrentielle','technologique','informationnelle')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "defaultCategorieAap": {
+ "name": "defaultCategorieAap",
+ "type": "enum('Handicap','PA','Enfance','Précarité','Sanitaire','Autre')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "autoRules": {
+ "name": "autoRules",
+ "type": "json",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "isActive": {
+ "name": "isActive",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "lastFetchedAt": {
+ "name": "lastFetchedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lastFetchStatus": {
+ "name": "lastFetchStatus",
+ "type": "enum('ok','error','pending')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'pending'"
+ },
+ "lastFetchError": {
+ "name": "lastFetchError",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "rss_feeds_id": {
+ "name": "rss_feeds_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "rss_settings": {
+ "name": "rss_settings",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "fetchIntervalMinutes": {
+ "name": "fetchIntervalMinutes",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": 360
+ },
+ "scheduledTime": {
+ "name": "scheduledTime",
+ "type": "varchar(5)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false,
+ "default": "'06:00'"
+ },
+ "fetchMode": {
+ "name": "fetchMode",
+ "type": "enum('interval','scheduled')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'scheduled'"
+ },
+ "autoFetchEnabled": {
+ "name": "autoFetchEnabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": true
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "rss_settings_id": {
+ "name": "rss_settings_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "checkConstraint": {}
+ },
+ "users": {
+ "name": "users",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "openId": {
+ "name": "openId",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "email": {
+ "name": "email",
+ "type": "varchar(320)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "loginMethod": {
+ "name": "loginMethod",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "role": {
+ "name": "role",
+ "type": "enum('user','admin')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "'user'"
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "onUpdate": true,
+ "default": "(now())"
+ },
+ "lastSignedIn": {
+ "name": "lastSignedIn",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "users_id": {
+ "name": "users_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "users_openId_unique": {
+ "name": "users_openId_unique",
+ "columns": [
+ "openId"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ },
+ "veille_items": {
+ "name": "veille_items",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "int",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "dedupKey": {
+ "name": "dedupKey",
+ "type": "varchar(64)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "titre": {
+ "name": "titre",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "categorie": {
+ "name": "categorie",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "niveau": {
+ "name": "niveau",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "territoire": {
+ "name": "territoire",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "territoires": {
+ "name": "territoires",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "resume": {
+ "name": "resume",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "source": {
+ "name": "source",
+ "type": "varchar(512)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "passage": {
+ "name": "passage",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "lien": {
+ "name": "lien",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "typeVeille": {
+ "name": "typeVeille",
+ "type": "enum('reglementaire','concurrentielle','technologique','informationnelle')",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "datePublication": {
+ "name": "datePublication",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "importedAt": {
+ "name": "importedAt",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false,
+ "default": "(now())"
+ },
+ "iaRelevant": {
+ "name": "iaRelevant",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaCategorie": {
+ "name": "iaCategorie",
+ "type": "varchar(128)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaClassifiedBy": {
+ "name": "iaClassifiedBy",
+ "type": "enum('ia','rules')",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaReason": {
+ "name": "iaReason",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "iaResume": {
+ "name": "iaResume",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "veille_items_id": {
+ "name": "veille_items_id",
+ "columns": [
+ "id"
+ ]
+ }
+ },
+ "uniqueConstraints": {
+ "veille_items_dedupKey_unique": {
+ "name": "veille_items_dedupKey_unique",
+ "columns": [
+ "dedupKey"
+ ]
+ }
+ },
+ "checkConstraint": {}
+ }
+ },
+ "views": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ },
+ "internal": {
+ "tables": {},
+ "indexes": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index d15b4bc..71d5ec9 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -92,6 +92,13 @@
"when": 1786997741455,
"tag": "0012_fluffy_boomer",
"breakpoints": true
+ },
+ {
+ "idx": 13,
+ "version": "5",
+ "when": 1787039231200,
+ "tag": "0013_odd_grim_reaper",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/drizzle/schema.ts b/drizzle/schema.ts
index 22e7b77..e2be5a2 100644
--- a/drizzle/schema.ts
+++ b/drizzle/schema.ts
@@ -137,6 +137,25 @@ export const importLogs = mysqlTable("import_logs", {
export type ImportLog = typeof importLogs.$inferSelect;
export type InsertImportLog = typeof importLogs.$inferInsert;
+// ─── Erreurs de classification RSS ───────────────────────────────────────────
+
+/**
+ * Journal technique destiné à l'administration. Il conserve les fallbacks IA
+ * article par article, sans bloquer l'import ni exposer l'erreur aux utilisateurs.
+ */
+export const classificationErrors = mysqlTable("classification_errors", {
+ id: int("id").autoincrement().primaryKey(),
+ feedId: int("feedId"),
+ feedName: varchar("feedName", { length: 255 }).notNull(),
+ feedType: mysqlEnum("feedType", ["veille", "aap"]).notNull(),
+ articleTitle: text("articleTitle").notNull(),
+ articleUrl: text("articleUrl"),
+ errorMessage: text("errorMessage").notNull(),
+ occurredAt: timestamp("occurredAt").defaultNow().notNull(),
+});
+
+export type ClassificationError = typeof classificationErrors.$inferSelect;
+
// ─── Boîte à idées ───────────────────────────────────────────────────────────
export const ideas = mysqlTable("ideas", {
diff --git a/server/aiClassifier.test.ts b/server/aiClassifier.test.ts
index 34cad47..5f60ea3 100644
--- a/server/aiClassifier.test.ts
+++ b/server/aiClassifier.test.ts
@@ -56,6 +56,7 @@ describe("classifyArticle", () => {
expect(result.typeVeille).toBe("informationnelle");
expect(result.relevant).toBe(true); // on suppose pertinent par défaut
expect(result.reason).toContain("règles");
+ expect(result.technicalError).toBe("LLM timeout");
});
it("retourne le fallback (rules) si le LLM retourne un JSON malformé", async () => {
diff --git a/server/aiClassifier.ts b/server/aiClassifier.ts
index b83f791..e810eb7 100644
--- a/server/aiClassifier.ts
+++ b/server/aiClassifier.ts
@@ -38,6 +38,13 @@ export interface AiClassificationResult {
reason: string;
/** Indique si la classification a été faite par l'IA ou par les règles (fallback) */
classifiedBy: "ia" | "rules";
+ /** Cause technique du fallback, réservée au journal d'administration. */
+ technicalError: string | null;
+}
+
+function getTechnicalErrorMessage(error: unknown): string {
+ const message = error instanceof Error ? error.message : String(error);
+ return message.slice(0, 1000);
}
// ─── Prompt veille stratégique : pertinence + type de veille ─────────────────
@@ -194,9 +201,11 @@ export async function classifyArticle(
categorieAap: null,
reason: parsed.raison,
classifiedBy: "ia",
+ technicalError: null,
};
} catch (e) {
- console.error("[AI Classifier] Erreur classification veille:", (e as Error).message);
+ const technicalError = getTechnicalErrorMessage(e);
+ console.error("[AI Classifier] Erreur classification veille:", technicalError);
const fb = fallbackFn();
return {
relevant: true,
@@ -204,6 +213,7 @@ export async function classifyArticle(
categorieAap: null,
reason: "Classification par règles (erreur LLM)",
classifiedBy: "rules",
+ technicalError,
};
}
}
@@ -265,15 +275,18 @@ export async function classifyAap(
categorieAap: parsed.pertinent ? (parsed.categorie ?? "Autre") : null,
reason: parsed.raison,
classifiedBy: "ia",
+ technicalError: null,
};
} catch (e) {
- console.error("[AI Classifier] Erreur classification AAP:", (e as Error).message);
+ const technicalError = getTechnicalErrorMessage(e);
+ console.error("[AI Classifier] Erreur classification AAP:", technicalError);
return {
relevant: true,
typeVeille: null,
categorieAap: fallbackCategorie,
reason: "Classification par règles (erreur LLM)",
classifiedBy: "rules",
+ technicalError,
};
}
}
diff --git a/server/db.ts b/server/db.ts
index d7105c6..6d7f5e0 100644
--- a/server/db.ts
+++ b/server/db.ts
@@ -16,9 +16,11 @@ import {
rssSettings,
processedDedupKeys,
articleReads,
+ classificationErrors,
type InsertRssFeed,
type InsertRssSettings,
type ImportLog,
+ type ClassificationError,
type RssFeed,
type RssSettings,
} from "../drizzle/schema";
@@ -489,6 +491,49 @@ export async function getImportStats() {
};
}
+// ─── Rapport d'erreurs de classification RSS ──────────────────────────────────
+
+export interface ClassificationErrorInput {
+ feedId: number | null;
+ feedName: string;
+ feedType: "veille" | "aap";
+ articleTitle: string;
+ articleUrl: string | null;
+ errorMessage: string;
+}
+
+/**
+ * Enregistre un fallback IA sans interrompre le traitement du flux RSS.
+ * La collecte reste opérationnelle : l'erreur est visible dans l'administration
+ * tandis que l'article est classé par la règle de repli prévue.
+ */
+export async function recordClassificationError(input: ClassificationErrorInput): Promise