Checkpoint: isSubscription appliqué lors des imports. Seuil de confiance persisté en base (champ learningConfidenceThreshold dans userSettings). LearningSettings.tsx charge et sauvegarde le seuil via settings.get/upsert.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import DashboardLayout from "@/components/DashboardLayout";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
@@ -34,14 +34,31 @@ const FIELD_COLORS: Record<string, string> = {
|
||||
isSubscription: "bg-orange-100 text-orange-800",
|
||||
};
|
||||
|
||||
const DEFAULT_CONFIDENCE_THRESHOLD = 2;
|
||||
const DEFAULT_THRESHOLD = 2;
|
||||
|
||||
export default function LearningSettings() {
|
||||
const utils = trpc.useUtils();
|
||||
const { data: learnings, isLoading } = trpc.learnings.list.useQuery();
|
||||
const [confidenceThreshold, setConfidenceThreshold] = useState(DEFAULT_CONFIDENCE_THRESHOLD);
|
||||
const { data: settings, isLoading: settingsLoading } = trpc.settings.get.useQuery();
|
||||
|
||||
const [confidenceThreshold, setConfidenceThreshold] = useState(DEFAULT_THRESHOLD);
|
||||
const [editingThreshold, setEditingThreshold] = useState(false);
|
||||
const [thresholdInput, setThresholdInput] = useState(String(DEFAULT_CONFIDENCE_THRESHOLD));
|
||||
const [thresholdInput, setThresholdInput] = useState(String(DEFAULT_THRESHOLD));
|
||||
|
||||
// Sync threshold from DB when settings load
|
||||
useEffect(() => {
|
||||
if (settings?.learningConfidenceThreshold != null) {
|
||||
setConfidenceThreshold(settings.learningConfidenceThreshold);
|
||||
setThresholdInput(String(settings.learningConfidenceThreshold));
|
||||
}
|
||||
}, [settings?.learningConfidenceThreshold]);
|
||||
|
||||
const upsertSettingsMutation = trpc.settings.upsert.useMutation({
|
||||
onSuccess: () => {
|
||||
utils.settings.get.invalidate();
|
||||
},
|
||||
onError: () => toast.error("Erreur lors de la sauvegarde du seuil"),
|
||||
});
|
||||
|
||||
const deleteMutation = trpc.learnings.delete.useMutation({
|
||||
onSuccess: () => {
|
||||
@@ -97,6 +114,7 @@ export default function LearningSettings() {
|
||||
}
|
||||
setConfidenceThreshold(val);
|
||||
setEditingThreshold(false);
|
||||
upsertSettingsMutation.mutate({ learningConfidenceThreshold: val });
|
||||
toast.success(`Seuil de confiance mis à jour : ${val} application(s)`);
|
||||
};
|
||||
|
||||
@@ -184,10 +202,15 @@ export default function LearningSettings() {
|
||||
<CardDescription>
|
||||
Un apprentissage est marqué <strong>Confirmé</strong> quand il a été appliqué au moins{" "}
|
||||
<strong>{confidenceThreshold} fois</strong>. En dessous, il est <strong>En observation</strong>.
|
||||
Ce réglage est sauvegardé et partagé entre toutes vos sessions.
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
{editingThreshold ? (
|
||||
{settingsLoading ? (
|
||||
<div className="flex items-center gap-2 text-sm text-muted-foreground">
|
||||
<RefreshCw className="w-4 h-4 animate-spin" /> Chargement...
|
||||
</div>
|
||||
) : editingThreshold ? (
|
||||
<div className="flex items-center gap-3">
|
||||
<Label htmlFor="threshold" className="text-sm whitespace-nowrap">
|
||||
Nombre minimum d'applications :
|
||||
@@ -201,7 +224,9 @@ export default function LearningSettings() {
|
||||
onChange={(e) => setThresholdInput(e.target.value)}
|
||||
className="w-24"
|
||||
/>
|
||||
<Button size="sm" onClick={handleSaveThreshold}>Appliquer</Button>
|
||||
<Button size="sm" onClick={handleSaveThreshold} disabled={upsertSettingsMutation.isPending}>
|
||||
{upsertSettingsMutation.isPending ? <RefreshCw className="w-4 h-4 animate-spin" /> : "Appliquer"}
|
||||
</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => { setEditingThreshold(false); setThresholdInput(String(confidenceThreshold)); }}>
|
||||
Annuler
|
||||
</Button>
|
||||
@@ -229,8 +254,8 @@ export default function LearningSettings() {
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground space-y-1">
|
||||
<p>
|
||||
Quand vous modifiez manuellement le <strong>type d'achat</strong>, le <strong>service concerné</strong> ou
|
||||
la <strong>ventilation comptable</strong> d'une facture (dans le détail ou dans la liste BAP), le système mémorise cette
|
||||
Quand vous modifiez manuellement le <strong>type d'achat</strong>, le <strong>service concerné</strong>,
|
||||
la <strong>ventilation comptable</strong> ou le champ <strong>Abonnement</strong> d'une facture, le système mémorise cette
|
||||
correction pour ce fournisseur.
|
||||
</p>
|
||||
<p>
|
||||
@@ -256,7 +281,7 @@ export default function LearningSettings() {
|
||||
<Brain className="w-12 h-12 text-gray-300 mb-4" />
|
||||
<h3 className="text-lg font-semibold text-gray-500">Aucun apprentissage enregistré</h3>
|
||||
<p className="text-sm text-muted-foreground mt-2 max-w-sm">
|
||||
Modifiez manuellement les champs d'une facture (type d'achat, service, ventilation) pour que le système
|
||||
Modifiez manuellement les champs d'une facture (type d'achat, service, ventilation, abonnement) pour que le système
|
||||
commence à apprendre.
|
||||
</p>
|
||||
</CardContent>
|
||||
|
||||
1
drizzle/0019_smooth_lord_hawal.sql
Normal file
1
drizzle/0019_smooth_lord_hawal.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `userSettings` ADD `learningConfidenceThreshold` int DEFAULT 2 NOT NULL;
|
||||
1702
drizzle/meta/0019_snapshot.json
Normal file
1702
drizzle/meta/0019_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -134,6 +134,13 @@
|
||||
"when": 1776019747088,
|
||||
"tag": "0018_warm_changeling",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 19,
|
||||
"version": "5",
|
||||
"when": 1776021930426,
|
||||
"tag": "0019_smooth_lord_hawal",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -146,6 +146,8 @@ export const userSettings = mysqlTable("userSettings", {
|
||||
sftpAutoExport: int("sftpAutoExport").default(0).notNull(), // 0 = manual, 1 = automatic
|
||||
// LLM Logs retention
|
||||
llmLogsRetentionMonths: int("llmLogsRetentionMonths").default(3).notNull(), // Durée de conservation des logs LLM en mois (défaut: 3 mois)
|
||||
// Seuil de confiance pour les apprentissages IA
|
||||
learningConfidenceThreshold: int("learningConfidenceThreshold").default(2).notNull(), // Nombre minimum d'applications pour marquer un apprentissage comme Confirmé
|
||||
createdAt: timestamp("createdAt").defaultNow().notNull(),
|
||||
updatedAt: timestamp("updatedAt").defaultNow().onUpdateNow().notNull(),
|
||||
});
|
||||
|
||||
@@ -299,11 +299,16 @@ export const appRouter = router({
|
||||
const learnings = await getLearningsBySupplier(userId, newInvoice.supplierName);
|
||||
if (learnings.length > 0) {
|
||||
const learningUpdates: Record<string, string> = {};
|
||||
const isSubscriptionLearning = learnings.find(l => l.fieldName === 'isSubscription');
|
||||
for (const learning of learnings) {
|
||||
if (learning.fieldName === 'typeAchat' || learning.fieldName === 'serviceConcerne' || learning.fieldName === 'ventilationComptable') {
|
||||
learningUpdates[learning.fieldName] = learning.correctedValue;
|
||||
}
|
||||
}
|
||||
// Appliquer l'apprentissage isSubscription
|
||||
if (isSubscriptionLearning) {
|
||||
(learningUpdates as any)['isSubscription'] = isSubscriptionLearning.correctedValue === 'OUI' ? 1 : 0;
|
||||
}
|
||||
if (Object.keys(learningUpdates).length > 0) {
|
||||
await updateInvoice(newInvoice.id, learningUpdates as any);
|
||||
console.log(`[Learning] Applied ${Object.keys(learningUpdates).length} learning(s) to invoice ${newInvoice.id} (${newInvoice.supplierName})`);
|
||||
@@ -990,6 +995,7 @@ export const appRouter = router({
|
||||
sftpRemotePath: z.string().optional(),
|
||||
sftpAutoExport: z.number().optional(),
|
||||
llmLogsRetentionMonths: z.number().optional(),
|
||||
learningConfidenceThreshold: z.number().min(1).optional(),
|
||||
}))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
await upsertUserSettings({
|
||||
|
||||
Reference in New Issue
Block a user