@mostajs/aop-scoring-ai 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @mostajs/aop-scoring-ai
2
+
3
+ **Auteur** : Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
4
+
5
+ Scoring IA d appels d offres (résumé, score, **go/no-go**, recommandations) — **compose `@mostajs/llm`** (provider injecté). Outil `score_tender` pour `@mostajs/chatbot`. Stack `mosta-aop-stack`.
package/llms.txt ADDED
@@ -0,0 +1,11 @@
1
+ # @mostajs/aop-scoring-ai — fiche LLM
2
+ > Scoring IA d AO (résumé/score/go-no-go/reco) — COMPOSE @mostajs/llm (provider injecté = seam).
3
+
4
+ ## EXPORTS
5
+ createScoring({ provider(=@mostajs/llm getLlmProvider), model?, cache? }) -> { analyze(t), summarize(t), goNoGo(t), makeScoringTool({getTender,permission}) }
6
+ parseAnalysis(text) -> {summary,score,goNoGo,reasons,recommendations} · SYSTEM_PROMPT_AOP
7
+
8
+ ## PIÈGES
9
+ - provider est un SEAM injecté (testable offline). En prod: getLlmProvider("deepseek"|"anthropic") de @mostajs/llm (peer optionnel).
10
+ - analyze() attend du JSON ; parseAnalysis borne score 0-100 et valide goNoGo (go|no-go|watch).
11
+ - makeScoringTool s intègre à @mostajs/chatbot (registerTool). Stack mosta-aop-stack.
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@mostajs/aop-scoring-ai",
3
+ "version": "0.1.0",
4
+ "description": "Scoring IA d'appels d'offres (résumé, score, go/no-go, recommandations) — compose @mostajs/llm (provider injecté). Outil pour @mostajs/chatbot. Extrait d'AgoraScope.",
5
+ "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
+ "license": "AGPL-3.0-or-later",
7
+ "type": "module",
8
+ "main": "src/index.js",
9
+ "exports": {
10
+ ".": "./src/index.js"
11
+ },
12
+ "files": [
13
+ "src",
14
+ "README.md",
15
+ "llms.txt"
16
+ ],
17
+ "peerDependencies": {
18
+ "@mostajs/llm": "*"
19
+ },
20
+ "peerDependenciesMeta": {
21
+ "@mostajs/llm": {
22
+ "optional": true
23
+ }
24
+ },
25
+ "devDependencies": {
26
+ "@mostajs/mjs-unit": "^0.3.0"
27
+ },
28
+ "scripts": {
29
+ "test": "node test-scripts/unit/aop-scoring-ai.test.mjs && node examples/run.mjs"
30
+ }
31
+ }
package/src/index.js ADDED
@@ -0,0 +1,61 @@
1
+ // @mostajs/aop-scoring-ai — résumé / score / go-no-go / recommandations d'un AO par LLM.
2
+ // COMPOSE @mostajs/llm (provider injecté = seam, testable offline). Outil pour @mostajs/chatbot.
3
+ // @author Dr Hamid MADANI <drmdh@msn.com> · AGPL-3.0-or-later
4
+
5
+ export const SYSTEM_PROMPT_AOP = [
6
+ "Tu es un analyste d'appels d'offres publics pour une PME tech (solutions @mostajs/*).",
7
+ "À partir des données d'un avis (AO), produis une analyse STRICTEMENT au format JSON, sans texte autour :",
8
+ '{ "summary": string (2-3 phrases FR), "score": number (0-100, pertinence/faisabilité),',
9
+ ' "goNoGo": "go"|"no-go"|"watch", "reasons": string[], "recommendations": string[] }',
10
+ "Critères : adéquation objet↔compétences, taille marché, échéance tenable, concurrence, éligibilité.",
11
+ ].join('\n');
12
+
13
+ const userPrompt = (t) => `Avis à analyser :\n${JSON.stringify({
14
+ title: t.title, description: t.description, cpvCodes: t.cpvCodes, type: t.type, procedure: t.procedure,
15
+ organization: t.organization?.name, deadline: t.dates?.deadline, value: t.financial?.estimatedValue,
16
+ }, null, 0)}`;
17
+
18
+ /** Parse défensif : extrait le 1er objet JSON du texte du LLM. */
19
+ export function parseAnalysis(text = '') {
20
+ const m = String(text).match(/\{[\s\S]*\}/);
21
+ if (!m) throw new Error('aop-scoring-ai: réponse LLM non parsable');
22
+ const o = JSON.parse(m[0]);
23
+ return {
24
+ summary: String(o.summary || ''), score: Math.max(0, Math.min(100, Number(o.score) || 0)),
25
+ goNoGo: ['go', 'no-go', 'watch'].includes(o.goNoGo) ? o.goNoGo : 'watch',
26
+ reasons: Array.isArray(o.reasons) ? o.reasons : [], recommendations: Array.isArray(o.recommendations) ? o.recommendations : [],
27
+ };
28
+ }
29
+
30
+ /**
31
+ * @param {object} deps
32
+ * @param {{ complete(messages, tools, opts) => Promise<{text}> }} deps.provider LLM (ex. @mostajs/llm getLlmProvider('deepseek'))
33
+ * @param {string} [deps.model]
34
+ * @param {Map} [deps.cache] cache optionnel (clé = tender.id|sourceId)
35
+ */
36
+ export function createScoring({ provider, model, cache = null } = {}) {
37
+ if (!provider?.complete) throw new Error('aop-scoring-ai: provider LLM requis (ex. @mostajs/llm getLlmProvider). Injecté = seam.');
38
+ async function analyze(tender) {
39
+ const key = tender.id || `${tender.sourceType}:${tender.sourceId}`;
40
+ if (cache?.has(key)) return cache.get(key);
41
+ const res = await provider.complete([{ role: 'system', content: SYSTEM_PROMPT_AOP }, { role: 'user', content: userPrompt(tender) }], [], { model });
42
+ const out = parseAnalysis(res.text);
43
+ if (cache) cache.set(key, out);
44
+ return out;
45
+ }
46
+ return {
47
+ analyze,
48
+ summarize: async (t) => (await analyze(t)).summary,
49
+ goNoGo: async (t) => { const a = await analyze(t); return { decision: a.goNoGo, reasons: a.reasons }; },
50
+ /** Outil pour @mostajs/chatbot (function-calling). `getTender(id)` fourni par l'app. */
51
+ makeScoringTool({ getTender, permission = 'aop.score.read' } = {}) {
52
+ return {
53
+ name: 'score_tender', permission,
54
+ description: "Analyse un appel d'offres : résumé, score 0-100, go/no-go, recommandations.",
55
+ schema: { type: 'object', properties: { tenderId: { type: 'string' } }, required: ['tenderId'] },
56
+ run: async ({ tenderId }) => analyze(await getTender(tenderId)),
57
+ };
58
+ },
59
+ };
60
+ }
61
+ export default { createScoring, parseAnalysis, SYSTEM_PROMPT_AOP };