mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
feat(r5.2): assistant au contrat + suggestion de codes de bilan
apps/ai : /internal/ask — seuil de pertinence, extraits sourcés ou refus honnête portant la taille du corpus cherché (D2), rédaction via le Generateur opt-in ; /internal/suggest — similarité sémantique entre la description libre et les libellés ACTIFS des référentiels, un code par champ, confiance FORTE/MOYENNE, « N bilans similaires sur ce parc ». Sans LLM : déterministe, explicable. 23 pytest. Contrat (74 opérations) : POST /assistant/ask → AssistantAnswer (EXTRACTIVE/GENERATED/REFUSAL, extraits cités, corpus cherché) et POST /assistant/suggest-bilan (codes existants seulement) ; clients web/mobile régénérés. API NestJS : module assistant — proxy vers siop2-ai (AI_SERVICE_URL/ AI_SERVICE_TOKEN, ADR-004 §4), permissions matrice (ask=view, suggest=edit), traduction interne→contrat, 503 propre si service éteint. 6 e2e sur stub HTTP (76 tests API). Bug débusqué par la vraie chaîne : fastembed ne norme pas ses vecteurs — la similarité des suggestions dépassait 1 (pgvector normalisait dans son opérateur, masquant l'écart). Normalisation à l'encodage + réindexation : bilans en tête (0.41), refus hors corpus, scores cosinus ≤ 1. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
62
packages/shared/src/schemas/assistant.ts
Normal file
62
packages/shared/src/schemas/assistant.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { z } from 'zod';
|
||||
import { BILAN_FIELDS } from '../exploitation';
|
||||
|
||||
/** Assistant R5 (D1/D2 validées) : sourcé ou silencieux, l'humain valide.
|
||||
* Le web/mobile parlent à l'API NestJS ; le service `siop2-ai` reste
|
||||
* interne (ADR-004 §4). */
|
||||
|
||||
export const ASSISTANT_MIN_CHARS = 3;
|
||||
|
||||
export const AssistantAskSchema = z.object({
|
||||
question: z.string().min(ASSISTANT_MIN_CHARS).max(500),
|
||||
});
|
||||
export type AssistantAsk = z.infer<typeof AssistantAskSchema>;
|
||||
|
||||
export const AssistantExcerptSchema = z.object({
|
||||
sourceType: z.enum(['DOCUMENT', 'WORK_ORDER']),
|
||||
documentId: z.uuid().nullable(),
|
||||
workOrderId: z.uuid().nullable(),
|
||||
title: z.string(), // nom de fichier ou référence d'OT
|
||||
locator: z.string(), // « p. 42 », « bilan du 17/07/2026 »
|
||||
content: z.string(), // l'extrait EXACT — la citation montre sa source
|
||||
score: z.number(),
|
||||
});
|
||||
export type AssistantExcerpt = z.infer<typeof AssistantExcerptSchema>;
|
||||
|
||||
export const AssistantAnswerSchema = z.object({
|
||||
/** REFUSAL = le corpus ne porte pas la réponse — l'assistant le dit (D2). */
|
||||
mode: z.enum(['EXTRACTIVE', 'GENERATED', 'REFUSAL']),
|
||||
/** Rédaction (mode génératif opt-in, ADR-004 §3) — null en extractif/refus. */
|
||||
answer: z.string().nullable(),
|
||||
excerpts: z.array(AssistantExcerptSchema),
|
||||
/** Ce qui a été cherché — le refus honnête l'affiche (« 6 documents, 214 bilans »). */
|
||||
corpus: z.object({
|
||||
documents: z.number().int(),
|
||||
reports: z.number().int(),
|
||||
}),
|
||||
});
|
||||
export type AssistantAnswer = z.infer<typeof AssistantAnswerSchema>;
|
||||
|
||||
// ————— Suggestion de codes de bilan (sans LLM — ADR-004 §3) —————
|
||||
|
||||
export const SuggestBilanSchema = z.object({
|
||||
description: z.string().min(10).max(2000),
|
||||
});
|
||||
export type SuggestBilan = z.infer<typeof SuggestBilanSchema>;
|
||||
|
||||
export const BilanSuggestionSchema = z.object({
|
||||
field: z.enum(BILAN_FIELDS),
|
||||
/** Toujours un code EXISTANT du référentiel — l'IA n'invente pas de valeur. */
|
||||
valueId: z.uuid(),
|
||||
label: z.string(),
|
||||
confidence: z.enum(['HIGH', 'MEDIUM']),
|
||||
/** « N bilans similaires sur ce parc » — la justification vérifiable. */
|
||||
similarReports: z.number().int(),
|
||||
score: z.number(),
|
||||
});
|
||||
export type BilanSuggestion = z.infer<typeof BilanSuggestionSchema>;
|
||||
|
||||
export const BilanSuggestionsResponseSchema = z.object({
|
||||
suggestions: z.array(BilanSuggestionSchema),
|
||||
});
|
||||
export type BilanSuggestionsResponse = z.infer<typeof BilanSuggestionsResponseSchema>;
|
||||
Reference in New Issue
Block a user