@saulwade/swl-ses 1.3.4 → 1.3.5
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/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/bin/swl-mcp-server.js +187 -187
- package/bin/swl-ses.js +4 -62
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/adoptar-proyecto.md +207 -207
- package/comandos/swl/contribuir.md +233 -233
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/extractor-de-aprendizajes/SKILL.md +321 -321
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/swl-claudemd/SKILL.md +220 -220
- package/habilidades/testing-python/SKILL.md +340 -340
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/extraccion-aprendizajes.js +19 -12
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/skills-lock.json +1093 -1093
- package/package.json +1 -1
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +1 -1
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +77 -3
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/instalador.js +38 -1
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +136 -0
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/transformadores/claude.js +200 -200
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +195 -195
- package/scripts/validar-userland-vacio.js +110 -110
- package/scripts/verificar-release.js +5 -1
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* artefactos-python.js — fuente única de verdad para los artefactos
|
|
5
|
-
* Python que NO deben distribuirse en el paquete npm.
|
|
6
|
-
*
|
|
7
|
-
* Por qué existe:
|
|
8
|
-
* Tanto `scripts/limpiar-artefactos-python.js` (prepack) como
|
|
9
|
-
* `scripts/publicar.js` (copiarDir hacia tmpDir del mirror GH)
|
|
10
|
-
* necesitan saber qué directorios y extensiones excluir. Tener la
|
|
11
|
-
* lista hardcodeada en cada archivo es violación DRY de conocimiento
|
|
12
|
-
* — agregar `.pyd` o `.mypy_cache_v2` exigiría editar dos archivos
|
|
13
|
-
* y mantenerlos sincronizados.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const DIRS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
17
|
-
'__pycache__',
|
|
18
|
-
'.pytest_cache',
|
|
19
|
-
'.mypy_cache',
|
|
20
|
-
'.ruff_cache',
|
|
21
|
-
]));
|
|
22
|
-
|
|
23
|
-
const EXTS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
24
|
-
'.pyc',
|
|
25
|
-
'.pyo',
|
|
26
|
-
]));
|
|
27
|
-
|
|
28
|
-
function esDirArtefacto(nombre) {
|
|
29
|
-
return DIRS_ARTEFACTOS_PYTHON.has(nombre);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function esArchivoArtefacto(nombre) {
|
|
33
|
-
const dot = nombre.lastIndexOf('.');
|
|
34
|
-
if (dot < 0) return false;
|
|
35
|
-
return EXTS_ARTEFACTOS_PYTHON.has(nombre.slice(dot).toLowerCase());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
DIRS_ARTEFACTOS_PYTHON,
|
|
40
|
-
EXTS_ARTEFACTOS_PYTHON,
|
|
41
|
-
esDirArtefacto,
|
|
42
|
-
esArchivoArtefacto,
|
|
43
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* artefactos-python.js — fuente única de verdad para los artefactos
|
|
5
|
+
* Python que NO deben distribuirse en el paquete npm.
|
|
6
|
+
*
|
|
7
|
+
* Por qué existe:
|
|
8
|
+
* Tanto `scripts/limpiar-artefactos-python.js` (prepack) como
|
|
9
|
+
* `scripts/publicar.js` (copiarDir hacia tmpDir del mirror GH)
|
|
10
|
+
* necesitan saber qué directorios y extensiones excluir. Tener la
|
|
11
|
+
* lista hardcodeada en cada archivo es violación DRY de conocimiento
|
|
12
|
+
* — agregar `.pyd` o `.mypy_cache_v2` exigiría editar dos archivos
|
|
13
|
+
* y mantenerlos sincronizados.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const DIRS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
17
|
+
'__pycache__',
|
|
18
|
+
'.pytest_cache',
|
|
19
|
+
'.mypy_cache',
|
|
20
|
+
'.ruff_cache',
|
|
21
|
+
]));
|
|
22
|
+
|
|
23
|
+
const EXTS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
24
|
+
'.pyc',
|
|
25
|
+
'.pyo',
|
|
26
|
+
]));
|
|
27
|
+
|
|
28
|
+
function esDirArtefacto(nombre) {
|
|
29
|
+
return DIRS_ARTEFACTOS_PYTHON.has(nombre);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function esArchivoArtefacto(nombre) {
|
|
33
|
+
const dot = nombre.lastIndexOf('.');
|
|
34
|
+
if (dot < 0) return false;
|
|
35
|
+
return EXTS_ARTEFACTOS_PYTHON.has(nombre.slice(dot).toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
DIRS_ARTEFACTOS_PYTHON,
|
|
40
|
+
EXTS_ARTEFACTOS_PYTHON,
|
|
41
|
+
esDirArtefacto,
|
|
42
|
+
esArchivoArtefacto,
|
|
43
|
+
};
|
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* benchmark-metrics.js — Métricas de retrieval para benchmark de memoria SWL.
|
|
5
|
-
*
|
|
6
|
-
* Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
|
|
7
|
-
* Funciones puras zero-deps. Adaptado a IDs SWL (aprendizaje, sesion,
|
|
8
|
-
* instinto) en lugar de session_id de agentmemory.
|
|
9
|
-
*
|
|
10
|
-
* Métricas:
|
|
11
|
-
* - recallAt(k): 1.0 si al menos un gold ID está en los top-k, sino 0.0
|
|
12
|
-
* - precisionAt(k): proporción de top-k que son gold
|
|
13
|
-
* - mrr: Mean Reciprocal Rank (1/rank del primer gold encontrado)
|
|
14
|
-
* - ndcgAt(k): Normalized Discounted Cumulative Gain
|
|
15
|
-
*
|
|
16
|
-
* @module scripts/lib/benchmark-metrics
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
function asSet(arr) {
|
|
22
|
-
return new Set(Array.isArray(arr) ? arr : []);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ── métricas individuales ─────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Recall @ k: 1.0 si ALGÚN id gold está en los primeros k retrieved.
|
|
29
|
-
*
|
|
30
|
-
* @param {string[]} retrievedIds - IDs ordenados (mejor primero).
|
|
31
|
-
* @param {string[]} goldIds - IDs correctos esperados.
|
|
32
|
-
* @param {number} k
|
|
33
|
-
* @returns {number} 0 o 1
|
|
34
|
-
*/
|
|
35
|
-
function recallAt(retrievedIds, goldIds, k) {
|
|
36
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
37
|
-
const topK = new Set(retrievedIds.slice(0, k));
|
|
38
|
-
return goldIds.some(g => topK.has(g)) ? 1.0 : 0.0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Precision @ k: proporción de los primeros k retrieved que son gold.
|
|
43
|
-
*
|
|
44
|
-
* @param {string[]} retrievedIds
|
|
45
|
-
* @param {string[]} goldIds
|
|
46
|
-
* @param {number} k
|
|
47
|
-
* @returns {number} en [0, 1]
|
|
48
|
-
*/
|
|
49
|
-
function precisionAt(retrievedIds, goldIds, k) {
|
|
50
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
51
|
-
const goldSet = asSet(goldIds);
|
|
52
|
-
const topK = retrievedIds.slice(0, k);
|
|
53
|
-
if (topK.length === 0) return 0;
|
|
54
|
-
const hits = topK.filter(id => goldSet.has(id)).length;
|
|
55
|
-
return hits / topK.length;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Mean Reciprocal Rank: 1/rank del primer gold encontrado, o 0 si ninguno.
|
|
60
|
-
*
|
|
61
|
-
* @param {string[]} retrievedIds
|
|
62
|
-
* @param {string[]} goldIds
|
|
63
|
-
* @returns {number} en [0, 1]
|
|
64
|
-
*/
|
|
65
|
-
function mrr(retrievedIds, goldIds) {
|
|
66
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
67
|
-
const goldSet = asSet(goldIds);
|
|
68
|
-
for (let i = 0; i < retrievedIds.length; i++) {
|
|
69
|
-
if (goldSet.has(retrievedIds[i])) {
|
|
70
|
-
return 1 / (i + 1);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function dcg(relevancias, k) {
|
|
77
|
-
let suma = 0;
|
|
78
|
-
for (let i = 0; i < Math.min(k, relevancias.length); i++) {
|
|
79
|
-
suma += (relevancias[i] ? 1 : 0) / Math.log2(i + 2);
|
|
80
|
-
}
|
|
81
|
-
return suma;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Normalized Discounted Cumulative Gain @ k.
|
|
86
|
-
*
|
|
87
|
-
* @param {string[]} retrievedIds
|
|
88
|
-
* @param {string[]} goldIds
|
|
89
|
-
* @param {number} k
|
|
90
|
-
* @returns {number} en [0, 1]
|
|
91
|
-
*/
|
|
92
|
-
function ndcgAt(retrievedIds, goldIds, k) {
|
|
93
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
94
|
-
const goldSet = asSet(goldIds);
|
|
95
|
-
const rels = retrievedIds.slice(0, k).map(id => goldSet.has(id));
|
|
96
|
-
const idealRels = Array.from({ length: Math.min(k, goldSet.size) }, () => true);
|
|
97
|
-
const idealDCG = dcg(idealRels, k);
|
|
98
|
-
if (idealDCG === 0) return 0;
|
|
99
|
-
return dcg(rels, k) / idealDCG;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ── agregados ─────────────────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Calcula el conjunto completo de métricas para una query.
|
|
106
|
-
*
|
|
107
|
-
* @param {string[]} retrievedIds
|
|
108
|
-
* @param {string[]} goldIds
|
|
109
|
-
* @returns {{ recall_at_5, recall_at_10, recall_at_20, mrr, ndcg_at_10, precision_at_5 }}
|
|
110
|
-
*/
|
|
111
|
-
function calcularMetricas(retrievedIds, goldIds) {
|
|
112
|
-
return {
|
|
113
|
-
recall_at_5: recallAt(retrievedIds, goldIds, 5),
|
|
114
|
-
recall_at_10: recallAt(retrievedIds, goldIds, 10),
|
|
115
|
-
recall_at_20: recallAt(retrievedIds, goldIds, 20),
|
|
116
|
-
mrr: mrr(retrievedIds, goldIds),
|
|
117
|
-
ndcg_at_10: ndcgAt(retrievedIds, goldIds, 10),
|
|
118
|
-
precision_at_5: precisionAt(retrievedIds, goldIds, 5),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Promedia métricas sobre múltiples queries.
|
|
124
|
-
*
|
|
125
|
-
* @param {Array<object>} resultados - Array de objetos producidos por calcularMetricas().
|
|
126
|
-
* @returns {object} Promedios con campo `n` (cantidad de queries).
|
|
127
|
-
*/
|
|
128
|
-
function promediar(resultados) {
|
|
129
|
-
if (!Array.isArray(resultados) || resultados.length === 0) {
|
|
130
|
-
return {
|
|
131
|
-
n: 0,
|
|
132
|
-
recall_at_5: 0,
|
|
133
|
-
recall_at_10: 0,
|
|
134
|
-
recall_at_20: 0,
|
|
135
|
-
mrr: 0,
|
|
136
|
-
ndcg_at_10: 0,
|
|
137
|
-
precision_at_5: 0,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const claves = ['recall_at_5', 'recall_at_10', 'recall_at_20', 'mrr', 'ndcg_at_10', 'precision_at_5'];
|
|
142
|
-
const promedio = { n: resultados.length };
|
|
143
|
-
for (const k of claves) {
|
|
144
|
-
const sum = resultados.reduce((a, r) => a + (r[k] || 0), 0);
|
|
145
|
-
promedio[k] = sum / resultados.length;
|
|
146
|
-
}
|
|
147
|
-
return promedio;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── exports ───────────────────────────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
module.exports = {
|
|
153
|
-
recallAt,
|
|
154
|
-
precisionAt,
|
|
155
|
-
mrr,
|
|
156
|
-
ndcgAt,
|
|
157
|
-
dcg,
|
|
158
|
-
calcularMetricas,
|
|
159
|
-
promediar,
|
|
160
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* benchmark-metrics.js — Métricas de retrieval para benchmark de memoria SWL.
|
|
5
|
+
*
|
|
6
|
+
* Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
|
|
7
|
+
* Funciones puras zero-deps. Adaptado a IDs SWL (aprendizaje, sesion,
|
|
8
|
+
* instinto) en lugar de session_id de agentmemory.
|
|
9
|
+
*
|
|
10
|
+
* Métricas:
|
|
11
|
+
* - recallAt(k): 1.0 si al menos un gold ID está en los top-k, sino 0.0
|
|
12
|
+
* - precisionAt(k): proporción de top-k que son gold
|
|
13
|
+
* - mrr: Mean Reciprocal Rank (1/rank del primer gold encontrado)
|
|
14
|
+
* - ndcgAt(k): Normalized Discounted Cumulative Gain
|
|
15
|
+
*
|
|
16
|
+
* @module scripts/lib/benchmark-metrics
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function asSet(arr) {
|
|
22
|
+
return new Set(Array.isArray(arr) ? arr : []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── métricas individuales ─────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Recall @ k: 1.0 si ALGÚN id gold está en los primeros k retrieved.
|
|
29
|
+
*
|
|
30
|
+
* @param {string[]} retrievedIds - IDs ordenados (mejor primero).
|
|
31
|
+
* @param {string[]} goldIds - IDs correctos esperados.
|
|
32
|
+
* @param {number} k
|
|
33
|
+
* @returns {number} 0 o 1
|
|
34
|
+
*/
|
|
35
|
+
function recallAt(retrievedIds, goldIds, k) {
|
|
36
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
37
|
+
const topK = new Set(retrievedIds.slice(0, k));
|
|
38
|
+
return goldIds.some(g => topK.has(g)) ? 1.0 : 0.0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Precision @ k: proporción de los primeros k retrieved que son gold.
|
|
43
|
+
*
|
|
44
|
+
* @param {string[]} retrievedIds
|
|
45
|
+
* @param {string[]} goldIds
|
|
46
|
+
* @param {number} k
|
|
47
|
+
* @returns {number} en [0, 1]
|
|
48
|
+
*/
|
|
49
|
+
function precisionAt(retrievedIds, goldIds, k) {
|
|
50
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
51
|
+
const goldSet = asSet(goldIds);
|
|
52
|
+
const topK = retrievedIds.slice(0, k);
|
|
53
|
+
if (topK.length === 0) return 0;
|
|
54
|
+
const hits = topK.filter(id => goldSet.has(id)).length;
|
|
55
|
+
return hits / topK.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mean Reciprocal Rank: 1/rank del primer gold encontrado, o 0 si ninguno.
|
|
60
|
+
*
|
|
61
|
+
* @param {string[]} retrievedIds
|
|
62
|
+
* @param {string[]} goldIds
|
|
63
|
+
* @returns {number} en [0, 1]
|
|
64
|
+
*/
|
|
65
|
+
function mrr(retrievedIds, goldIds) {
|
|
66
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
67
|
+
const goldSet = asSet(goldIds);
|
|
68
|
+
for (let i = 0; i < retrievedIds.length; i++) {
|
|
69
|
+
if (goldSet.has(retrievedIds[i])) {
|
|
70
|
+
return 1 / (i + 1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function dcg(relevancias, k) {
|
|
77
|
+
let suma = 0;
|
|
78
|
+
for (let i = 0; i < Math.min(k, relevancias.length); i++) {
|
|
79
|
+
suma += (relevancias[i] ? 1 : 0) / Math.log2(i + 2);
|
|
80
|
+
}
|
|
81
|
+
return suma;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Normalized Discounted Cumulative Gain @ k.
|
|
86
|
+
*
|
|
87
|
+
* @param {string[]} retrievedIds
|
|
88
|
+
* @param {string[]} goldIds
|
|
89
|
+
* @param {number} k
|
|
90
|
+
* @returns {number} en [0, 1]
|
|
91
|
+
*/
|
|
92
|
+
function ndcgAt(retrievedIds, goldIds, k) {
|
|
93
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
94
|
+
const goldSet = asSet(goldIds);
|
|
95
|
+
const rels = retrievedIds.slice(0, k).map(id => goldSet.has(id));
|
|
96
|
+
const idealRels = Array.from({ length: Math.min(k, goldSet.size) }, () => true);
|
|
97
|
+
const idealDCG = dcg(idealRels, k);
|
|
98
|
+
if (idealDCG === 0) return 0;
|
|
99
|
+
return dcg(rels, k) / idealDCG;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── agregados ─────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calcula el conjunto completo de métricas para una query.
|
|
106
|
+
*
|
|
107
|
+
* @param {string[]} retrievedIds
|
|
108
|
+
* @param {string[]} goldIds
|
|
109
|
+
* @returns {{ recall_at_5, recall_at_10, recall_at_20, mrr, ndcg_at_10, precision_at_5 }}
|
|
110
|
+
*/
|
|
111
|
+
function calcularMetricas(retrievedIds, goldIds) {
|
|
112
|
+
return {
|
|
113
|
+
recall_at_5: recallAt(retrievedIds, goldIds, 5),
|
|
114
|
+
recall_at_10: recallAt(retrievedIds, goldIds, 10),
|
|
115
|
+
recall_at_20: recallAt(retrievedIds, goldIds, 20),
|
|
116
|
+
mrr: mrr(retrievedIds, goldIds),
|
|
117
|
+
ndcg_at_10: ndcgAt(retrievedIds, goldIds, 10),
|
|
118
|
+
precision_at_5: precisionAt(retrievedIds, goldIds, 5),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Promedia métricas sobre múltiples queries.
|
|
124
|
+
*
|
|
125
|
+
* @param {Array<object>} resultados - Array de objetos producidos por calcularMetricas().
|
|
126
|
+
* @returns {object} Promedios con campo `n` (cantidad de queries).
|
|
127
|
+
*/
|
|
128
|
+
function promediar(resultados) {
|
|
129
|
+
if (!Array.isArray(resultados) || resultados.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
n: 0,
|
|
132
|
+
recall_at_5: 0,
|
|
133
|
+
recall_at_10: 0,
|
|
134
|
+
recall_at_20: 0,
|
|
135
|
+
mrr: 0,
|
|
136
|
+
ndcg_at_10: 0,
|
|
137
|
+
precision_at_5: 0,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const claves = ['recall_at_5', 'recall_at_10', 'recall_at_20', 'mrr', 'ndcg_at_10', 'precision_at_5'];
|
|
142
|
+
const promedio = { n: resultados.length };
|
|
143
|
+
for (const k of claves) {
|
|
144
|
+
const sum = resultados.reduce((a, r) => a + (r[k] || 0), 0);
|
|
145
|
+
promedio[k] = sum / resultados.length;
|
|
146
|
+
}
|
|
147
|
+
return promedio;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── exports ───────────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
recallAt,
|
|
154
|
+
precisionAt,
|
|
155
|
+
mrr,
|
|
156
|
+
ndcgAt,
|
|
157
|
+
dcg,
|
|
158
|
+
calcularMetricas,
|
|
159
|
+
promediar,
|
|
160
|
+
};
|