feat(r5.3): écrans IA — assistant web, corpus administrable, suggestions OT/mobile

- Contrat (76 opérations) : Document expose inCorpus/indexedAt/chunkCount,
  PATCH /documents/{id}/corpus (ASSETS.edit), POST /assistant/reindex
  (bilan chiffré) ; ci-contract vérifie désormais aussi le client mobile.
- Web : page /assistant (chat sourcé — extraits exacts cités, Ouvrir vers
  PDF authentifié ou fiche OT, avertissement permanent ; refus honnête
  chiffré avec action utile) ; Bibliothèque = corpus (bandeau 09-08,
  statut d'indexation par document, interrupteur d'exclusion PDF,
  Réindexer tout) ; fiche OT : « Décrire pour suggérer » (Appliquer =
  geste humain, liseré « suggéré » retiré au choix manuel).
- Mobile : chips de suggestion dans la clôture (un appui = un champ
  pré-rempli, « réseau requis » hors-ligne — la file R4 n'en dépend pas).
- apps/ai : seuils AI_SEUIL_* configurables par env (CI + calibrage).
- CI e2e : service siop2-ai (embeddeur déterministe, seuils calibrés sur
  mesures : match 0,66 vs bruit 0,11) + parcours R5 Playwright (PDF généré
  xref valide → réindexation → réponse sourcée → refus → suggestion).
- Vérifié : 16/16 Playwright, 78 tests API, 23 pytest, 17 jest-expo ;
  chaîne réelle au vrai modèle ONNX (web 7/7, mobile Expo web 6/6).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-17 17:26:22 +01:00
parent 45ae491827
commit 28eecc1fb9
31 changed files with 1431 additions and 24 deletions

View File

@@ -91,6 +91,15 @@ export function useCocheChecklist(otId: string) {
});
}
/** Suggestion R5 (D1) : l'IA est un service SERVEUR — la suggestion demande le
* réseau, la clôture en file R4 fonctionne sans elle. */
export function useSuggestionBilan() {
return useMutation({
mutationFn: async (description: string) =>
unwrap(await api.POST('/assistant/suggest-bilan', { body: { description } })),
});
}
export function useBilan(otId: string) {
const invalide = useInvalideOT(otId);
return useMutation({

View File

@@ -407,6 +407,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/documents/{id}/corpus": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
/** Inclure/exclure du corpus IA (D3 — réversible, effectif à la prochaine réindexation) */
patch: operations["updateDocumentCorpus"];
trace?: never;
};
"/documents/{id}": {
parameters: {
query?: never;
@@ -475,6 +492,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/assistant/reindex": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
/** Réindexer le corpus (bibliothèque PDF + bilans clôturés, anonymisés à lingestion — D4) */
post: operations["reindexAssistant"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/search": {
parameters: {
query?: never;
@@ -1437,6 +1471,9 @@ export interface components {
uploadedByName: string | null;
/** Format: date-time */
createdAt: string;
inCorpus: boolean;
indexedAt: string | null;
chunkCount: number;
}[];
};
Document: {
@@ -1452,6 +1489,12 @@ export interface components {
uploadedByName: string | null;
/** Format: date-time */
createdAt: string;
inCorpus: boolean;
indexedAt: string | null;
chunkCount: number;
};
DocumentCorpusUpdate: {
inCorpus: boolean;
};
AnalyticsSummary: {
months: number;
@@ -1518,6 +1561,12 @@ export interface components {
SuggestBilan: {
description: string;
};
ReindexResult: {
documentsIndexed: number;
documentsSkipped: number;
reportsIndexed: number;
chunks: number;
};
SearchResponse: {
workOrders: {
/** Format: uuid */
@@ -2990,6 +3039,39 @@ export interface operations {
};
};
};
updateDocumentCorpus: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody: {
content: {
"application/json": components["schemas"]["DocumentCorpusUpdate"];
};
};
responses: {
/** @description Document mis à jour */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Document"];
};
};
/** @description Inconnu */
404: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
deleteDocument: {
parameters: {
query?: never;
@@ -3101,6 +3183,33 @@ export interface operations {
};
};
};
reindexAssistant: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Bilan dindexation */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ReindexResult"];
};
};
/** @description Service IA indisponible */
503: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
globalSearch: {
parameters: {
query: {