L'assistant pouvait conclure « prenez plutot un conteneur complet » avec l'autorite de la marque, et repondre sur du transport interieur. Il ne pouvait pas non plus combler un trou de documentation qu'il venait de rencontrer. Perimetre (openai-trade.adapter) - SCOPE_RULES : LCL uniquement. Le FCL reste explicable — c'est du vocabulaire metier — mais jamais recommande, chiffre, ni presente comme la meilleure option. Un cas hors LCL part vers support@xpeditis.com. - SCOPE_RULES : transport international uniquement. Le transport interieur, le demenagement et le transport de personnes sont refuses, pas traites « un peu ». - La page wiki « LCL vs FCL » ne conseille plus le FCL : elle le decrit, et liste les cas hors perimetre a signaler au support. Corpus reconstruit. - L'amorce « LCL ou FCL ? » devient une question sur le calcul du fret LCL. Documentation interne d'abord (KNOWLEDGE_RULES) - Les extraits du wiki sont dits source de reference, avant les connaissances generales du modele. Entretien du wiki global - Nouvelle capacite `contribute_wiki_page` (scope write) : quand le wiki ne couvre pas un sujet d'information generale sur le transport international, l'assistant ecrit la page. Un titre deja pris est mis a jour, pas duplique. - `wiki-contribution-policy` (domaine) decide ce qui entre : refus du contenu qui conseille le FCL, du cas client (dossier, tarif, coordonnees) et du hors perimetre. Le prompt oriente, cette fonction empeche. - Un sujet deja couvert par le wiki publie (score >= 0,62) est refuse. - `WikiRetriever` indexe les contributions a cote du corpus fige, avec un index en memoire invalide par la revision du jeu ; une panne de ce cote ne coute pas la reponse. - Page `/dashboard/wiki/complements`, etiquetee comme ecrite par l'assistant, et carte sur l'index du wiki. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
146 lines
4.1 KiB
TypeScript
146 lines
4.1 KiB
TypeScript
import {
|
|
WikiContributionDraft,
|
|
refuseWikiContribution,
|
|
} from '../services/wiki-contribution-policy';
|
|
|
|
/**
|
|
* Page ajoutee au wiki global par l'assistant.
|
|
*
|
|
* Le wiki publie vit dans les fichiers de traduction du frontend : il est fige
|
|
* au build et ne peut pas grandir pendant qu'un client pose une question. Cette
|
|
* entite est l'autre moitie du wiki — celle qui s'ecrit a l'execution, quand
|
|
* l'assistant rencontre un sujet d'information generale que la documentation ne
|
|
* couvre pas encore.
|
|
*
|
|
* Elle n'est pas un brouillon : une fois creee, elle est lue par la recherche
|
|
* documentaire et citee sous les reponses, comme n'importe quelle page. C'est
|
|
* pour cela que sa validation (`refuseWikiContribution`) est faite ici, a la
|
|
* construction, et pas laissee au bon vouloir de l'appelant.
|
|
*
|
|
* Elle porte son auteur : la page est globale, mais on sait toujours quelle
|
|
* question l'a fait naitre.
|
|
*/
|
|
export interface WikiContributionProps {
|
|
id: string;
|
|
locale: string;
|
|
/** Sujet du wiki auquel la page se rattache, ex. `douanes`. */
|
|
topic: string;
|
|
title: string;
|
|
/** Intitule de la section, affiche sous le titre et indexe avec lui. */
|
|
section: string;
|
|
body: string;
|
|
/** Compte dont la question a declenche la contribution. */
|
|
authorUserId: string;
|
|
authorOrganizationId: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
export class WikiContributionRejected extends Error {}
|
|
|
|
export class WikiContribution {
|
|
private constructor(private readonly props: WikiContributionProps) {}
|
|
|
|
/**
|
|
* Cree une page, ou refuse le brouillon.
|
|
*
|
|
* Le refus est une exception et non un `null` : l'appelant est une capacite
|
|
* invoquee par un modele, et le motif doit lui revenir en toutes lettres pour
|
|
* qu'il l'explique a l'utilisateur au lieu de reessayer.
|
|
*/
|
|
static create(
|
|
props: Omit<WikiContributionProps, 'id' | 'createdAt' | 'updatedAt'> & { id: string }
|
|
): WikiContribution {
|
|
const draft: WikiContributionDraft = {
|
|
topic: props.topic,
|
|
title: props.title,
|
|
section: props.section,
|
|
body: props.body,
|
|
};
|
|
|
|
const refusal = refuseWikiContribution(draft);
|
|
if (refusal) throw new WikiContributionRejected(refusal);
|
|
|
|
const now = new Date();
|
|
return new WikiContribution({ ...props, createdAt: now, updatedAt: now });
|
|
}
|
|
|
|
static fromPersistence(props: WikiContributionProps): WikiContribution {
|
|
return new WikiContribution(props);
|
|
}
|
|
|
|
/**
|
|
* Remplace le corps d'une page existante.
|
|
*
|
|
* Un sujet deja couvert ne donne pas une seconde page : le wiki grandirait en
|
|
* doublons, et la recherche citerait deux fois la meme chose. Le meme titre
|
|
* dans la meme langue est donc mis a jour, pas duplique.
|
|
*/
|
|
revise(section: string, body: string): WikiContribution {
|
|
const refusal = refuseWikiContribution({
|
|
topic: this.props.topic,
|
|
title: this.props.title,
|
|
section,
|
|
body,
|
|
});
|
|
if (refusal) throw new WikiContributionRejected(refusal);
|
|
|
|
return new WikiContribution({ ...this.props, section, body, updatedAt: new Date() });
|
|
}
|
|
|
|
get id(): string {
|
|
return this.props.id;
|
|
}
|
|
|
|
get locale(): string {
|
|
return this.props.locale;
|
|
}
|
|
|
|
get topic(): string {
|
|
return this.props.topic;
|
|
}
|
|
|
|
get title(): string {
|
|
return this.props.title;
|
|
}
|
|
|
|
get section(): string {
|
|
return this.props.section;
|
|
}
|
|
|
|
get body(): string {
|
|
return this.props.body;
|
|
}
|
|
|
|
get authorUserId(): string {
|
|
return this.props.authorUserId;
|
|
}
|
|
|
|
get authorOrganizationId(): string {
|
|
return this.props.authorOrganizationId;
|
|
}
|
|
|
|
get createdAt(): Date {
|
|
return this.props.createdAt;
|
|
}
|
|
|
|
get updatedAt(): Date {
|
|
return this.props.updatedAt;
|
|
}
|
|
|
|
/**
|
|
* Lien vers la page, dans le wiki.
|
|
*
|
|
* Les complements tiennent sur une seule page, groupee par sujet : l'ancre
|
|
* amene le lecteur au bon paragraphe, sans creer une route par contribution
|
|
* ni un sujet vide pour chaque page qui n'en a pas encore.
|
|
*/
|
|
get href(): string {
|
|
return `/dashboard/wiki/complements#${this.props.id}`;
|
|
}
|
|
|
|
toObject(): WikiContributionProps {
|
|
return { ...this.props };
|
|
}
|
|
}
|