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:
pr-daaif
2026-07-17 15:05:08 +01:00
parent 76c2ccdfb1
commit 45ae491827
17 changed files with 1215 additions and 2 deletions

View File

@@ -441,6 +441,40 @@ export interface paths {
patch?: never;
trace?: never;
};
"/assistant/ask": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Assistant R5 — sourcé ou silencieux : extraits cités ou refus honnête (D2) */
post: operations["askAssistant"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/assistant/suggest-bilan": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Suggérer des codes de bilan depuis une description libre (D1 — lhumain valide) */
post: operations["suggestBilan"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/search": {
parameters: {
query?: never;
@@ -1446,6 +1480,44 @@ export interface components {
total: number;
}[];
};
AssistantAnswer: {
/** @enum {string} */
mode: "EXTRACTIVE" | "GENERATED" | "REFUSAL";
answer: string | null;
excerpts: {
/** @enum {string} */
sourceType: "DOCUMENT" | "WORK_ORDER";
documentId: string | null;
workOrderId: string | null;
title: string;
locator: string;
content: string;
score: number;
}[];
corpus: {
documents: number;
reports: number;
};
};
AssistantAsk: {
question: string;
};
BilanSuggestions: {
suggestions: {
/** @enum {string} */
field: "DOOR_STATE" | "CABIN_POSITION" | "ANOMALY" | "EXTERNAL_CAUSE" | "ACTION_TAKEN" | "COMPONENT_CONCERNED";
/** Format: uuid */
valueId: string;
label: string;
/** @enum {string} */
confidence: "HIGH" | "MEDIUM";
similarReports: number;
score: number;
}[];
};
SuggestBilan: {
description: string;
};
SearchResponse: {
workOrders: {
/** Format: uuid */
@@ -2967,6 +3039,68 @@ export interface operations {
};
};
};
askAssistant: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["AssistantAsk"];
};
};
responses: {
/** @description Réponse sourcée ou refus */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["AssistantAnswer"];
};
};
/** @description Service IA indisponible */
503: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
suggestBilan: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["SuggestBilan"];
};
};
responses: {
/** @description Suggestions (codes existants seulement) */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["BilanSuggestions"];
};
};
/** @description Service IA indisponible */
503: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
globalSearch: {
parameters: {
query: {