riasec-co 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/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/items.d.ts +11 -0
- package/dist/items.d.ts.map +1 -0
- package/dist/items.js +45 -0
- package/dist/items.js.map +1 -0
- package/dist/programs.d.ts +15 -0
- package/dist/programs.d.ts.map +1 -0
- package/dist/programs.js +127 -0
- package/dist/programs.js.map +1 -0
- package/dist/quiz.d.ts +29 -0
- package/dist/quiz.d.ts.map +1 -0
- package/dist/quiz.js +130 -0
- package/dist/quiz.js.map +1 -0
- package/dist/recommender.d.ts +21 -0
- package/dist/recommender.d.ts.map +1 -0
- package/dist/recommender.js +150 -0
- package/dist/recommender.js.map +1 -0
- package/dist/scoring.d.ts +60 -0
- package/dist/scoring.d.ts.map +1 -0
- package/dist/scoring.js +116 -0
- package/dist/scoring.js.map +1 -0
- package/dist/types.d.ts +159 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +47 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createQuiz } from "./quiz.js";
|
|
2
|
+
export type { Quiz } from "./quiz.js";
|
|
3
|
+
export { uniformPrior, updateAlpha, posteriorMean, entropy, confidence, cosineSimilarity, klDivergence, expectedInfoGain, MAX_ENTROPY, } from "./scoring.js";
|
|
4
|
+
export { loadItems, getItemsByType } from "./items.js";
|
|
5
|
+
export { loadPrograms, loadMapping, getCINEFields, getDepartments, } from "./programs.js";
|
|
6
|
+
export { recommend } from "./recommender.js";
|
|
7
|
+
export { RIASEC_TYPES, type RIASECType, type RIASECProfile, type DirichletAlpha, type LikertResponse, type Item, type ItemBank, type Answer, type QuizConfig, type QuizProgress, type Program, type Cobertura, type Convenio, type FieldMapping, type TypeMapping, type RecommendConfig, type ProgramFilters, type PriorWeights, type Recommendation, } from "./types.js";
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,YAAY,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,YAAY,EACZ,KAAK,UAAU,EACf,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,IAAI,EACT,KAAK,QAAQ,EACb,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,OAAO,EACZ,KAAK,SAAS,EACd,KAAK,QAAQ,EACb,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,cAAc,GACpB,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { createQuiz } from "./quiz.js";
|
|
2
|
+
export { uniformPrior, updateAlpha, posteriorMean, entropy, confidence, cosineSimilarity, klDivergence, expectedInfoGain, MAX_ENTROPY, } from "./scoring.js";
|
|
3
|
+
export { loadItems, getItemsByType } from "./items.js";
|
|
4
|
+
export { loadPrograms, loadMapping, getCINEFields, getDepartments, } from "./programs.js";
|
|
5
|
+
export { recommend } from "./recommender.js";
|
|
6
|
+
export { RIASEC_TYPES, } from "./types.js";
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGvC,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,OAAO,EACP,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,EAChB,WAAW,GACZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACvD,OAAO,EACL,YAAY,EACZ,WAAW,EACX,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAE7C,OAAO,EACL,YAAY,GAmBb,MAAM,YAAY,CAAC"}
|
package/dist/items.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* items.ts — IPIP RIASEC item bank loader
|
|
3
|
+
*
|
|
4
|
+
* Loads the 48-item IPIP Basic Interest Markers from bundled JSON.
|
|
5
|
+
*/
|
|
6
|
+
import type { Item, ItemBank } from "./types.js";
|
|
7
|
+
/** Load the IPIP RIASEC item bank */
|
|
8
|
+
export declare function loadItems(language?: "en" | "es"): ItemBank;
|
|
9
|
+
/** Get all items for a specific RIASEC type */
|
|
10
|
+
export declare function getItemsByType(items: Item[], type: string): Item[];
|
|
11
|
+
//# sourceMappingURL=items.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.d.ts","sourceRoot":"","sources":["../src/items.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAsBjD,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,QAAQ,GAAE,IAAI,GAAG,IAAW,GAAG,QAAQ,CAahE;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,CAElE"}
|
package/dist/items.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* items.ts — IPIP RIASEC item bank loader
|
|
3
|
+
*
|
|
4
|
+
* Loads the 48-item IPIP Basic Interest Markers from bundled JSON.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
/** Path to the canonical data directory */
|
|
11
|
+
const DATA_DIR = resolve(__dirname, "../../data/canonical");
|
|
12
|
+
// Fallback: when running from packages/core/src, data is at repo root
|
|
13
|
+
const DATA_DIR_ALT = resolve(__dirname, "../../../data/canonical");
|
|
14
|
+
function findDataDir() {
|
|
15
|
+
try {
|
|
16
|
+
readFileSync(resolve(DATA_DIR, "items_en.json"));
|
|
17
|
+
return DATA_DIR;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return DATA_DIR_ALT;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
let cachedEn = null;
|
|
24
|
+
let cachedEs = null;
|
|
25
|
+
/** Load the IPIP RIASEC item bank */
|
|
26
|
+
export function loadItems(language = "en") {
|
|
27
|
+
if (language === "en" && cachedEn)
|
|
28
|
+
return cachedEn;
|
|
29
|
+
if (language === "es" && cachedEs)
|
|
30
|
+
return cachedEs;
|
|
31
|
+
const dataDir = findDataDir();
|
|
32
|
+
const path = resolve(dataDir, `items_${language}.json`);
|
|
33
|
+
const raw = readFileSync(path, "utf-8");
|
|
34
|
+
const bank = JSON.parse(raw);
|
|
35
|
+
if (language === "en")
|
|
36
|
+
cachedEn = bank;
|
|
37
|
+
else
|
|
38
|
+
cachedEs = bank;
|
|
39
|
+
return bank;
|
|
40
|
+
}
|
|
41
|
+
/** Get all items for a specific RIASEC type */
|
|
42
|
+
export function getItemsByType(items, type) {
|
|
43
|
+
return items.filter((i) => i.type === type);
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=items.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"items.js","sourceRoot":"","sources":["../src/items.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE1D,2CAA2C;AAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;AAE5D,sEAAsE;AACtE,MAAM,YAAY,GAAG,OAAO,CAAC,SAAS,EAAE,yBAAyB,CAAC,CAAC;AAEnE,SAAS,WAAW;IAClB,IAAI,CAAC;QACH,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC;QACjD,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,YAAY,CAAC;IACtB,CAAC;AACH,CAAC;AAED,IAAI,QAAQ,GAAoB,IAAI,CAAC;AACrC,IAAI,QAAQ,GAAoB,IAAI,CAAC;AAErC,qCAAqC;AACrC,MAAM,UAAU,SAAS,CAAC,WAAwB,IAAI;IACpD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IACnD,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAEnD,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,SAAS,QAAQ,OAAO,CAAC,CAAC;IACxD,MAAM,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAa,CAAC;IAEzC,IAAI,QAAQ,KAAK,IAAI;QAAE,QAAQ,GAAG,IAAI,CAAC;;QAClC,QAAQ,GAAG,IAAI,CAAC;IAErB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,cAAc,CAAC,KAAa,EAAE,IAAY;IACxD,OAAO,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;AAC9C,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* programs.ts — SNIES program data loader
|
|
3
|
+
*
|
|
4
|
+
* Loads the canonical programs.csv into typed Program objects.
|
|
5
|
+
*/
|
|
6
|
+
import type { Program, TypeMapping, RIASECType } from "./types.js";
|
|
7
|
+
/** Load all programs from canonical CSV */
|
|
8
|
+
export declare function loadPrograms(): Program[];
|
|
9
|
+
/** Load the RIASEC → CINE field mapping */
|
|
10
|
+
export declare function loadMapping(): Record<RIASECType, TypeMapping>;
|
|
11
|
+
/** Get unique CINE broad fields from programs */
|
|
12
|
+
export declare function getCINEFields(programs: Program[]): string[];
|
|
13
|
+
/** Get unique departments from programs */
|
|
14
|
+
export declare function getDepartments(programs: Program[]): string[];
|
|
15
|
+
//# sourceMappingURL=programs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"programs.d.ts","sourceRoot":"","sources":["../src/programs.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAKH,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmDnE,2CAA2C;AAC3C,wBAAgB,YAAY,IAAI,OAAO,EAAE,CAoDxC;AAED,2CAA2C;AAC3C,wBAAgB,WAAW,IAAI,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAQ7D;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE3D;AAED,2CAA2C;AAC3C,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,MAAM,EAAE,CAE5D"}
|
package/dist/programs.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* programs.ts — SNIES program data loader
|
|
3
|
+
*
|
|
4
|
+
* Loads the canonical programs.csv into typed Program objects.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync } from "node:fs";
|
|
7
|
+
import { resolve, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const DATA_DIR = resolve(__dirname, "../../data/canonical");
|
|
11
|
+
const DATA_DIR_ALT = resolve(__dirname, "../../../data/canonical");
|
|
12
|
+
function findDataDir() {
|
|
13
|
+
try {
|
|
14
|
+
readFileSync(resolve(DATA_DIR, "programs.csv"), { encoding: "utf-8", flag: "r" });
|
|
15
|
+
return DATA_DIR;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return DATA_DIR_ALT;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
let cachedPrograms = null;
|
|
22
|
+
let cachedMapping = null;
|
|
23
|
+
/** Parse a CSV line handling quoted fields */
|
|
24
|
+
function parseCSVLine(line) {
|
|
25
|
+
const result = [];
|
|
26
|
+
let current = "";
|
|
27
|
+
let inQuotes = false;
|
|
28
|
+
for (let i = 0; i < line.length; i++) {
|
|
29
|
+
const ch = line[i];
|
|
30
|
+
if (inQuotes) {
|
|
31
|
+
if (ch === '"') {
|
|
32
|
+
if (i + 1 < line.length && line[i + 1] === '"') {
|
|
33
|
+
current += '"';
|
|
34
|
+
i++;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
inQuotes = false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
current += ch;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
else if (ch === '"') {
|
|
45
|
+
inQuotes = true;
|
|
46
|
+
}
|
|
47
|
+
else if (ch === ",") {
|
|
48
|
+
result.push(current);
|
|
49
|
+
current = "";
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
current += ch;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
result.push(current);
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
/** Load all programs from canonical CSV */
|
|
59
|
+
export function loadPrograms() {
|
|
60
|
+
if (cachedPrograms)
|
|
61
|
+
return cachedPrograms;
|
|
62
|
+
const dataDir = findDataDir();
|
|
63
|
+
const csv = readFileSync(resolve(dataDir, "programs.csv"), "utf-8");
|
|
64
|
+
const lines = csv.trim().split("\n");
|
|
65
|
+
const headers = parseCSVLine(lines[0]);
|
|
66
|
+
const programs = [];
|
|
67
|
+
for (let i = 1; i < lines.length; i++) {
|
|
68
|
+
const values = parseCSVLine(lines[i]);
|
|
69
|
+
const row = {};
|
|
70
|
+
for (let j = 0; j < headers.length; j++) {
|
|
71
|
+
row[headers[j]] = values[j] ?? "";
|
|
72
|
+
}
|
|
73
|
+
programs.push({
|
|
74
|
+
codigo_institucion_padre: row.codigo_institucion_padre,
|
|
75
|
+
codigo_snies: parseInt(row.codigo_snies, 10),
|
|
76
|
+
nombre_programa: row.nombre_programa,
|
|
77
|
+
codigo_institucion: row.codigo_institucion,
|
|
78
|
+
nombre_institucion: row.nombre_institucion,
|
|
79
|
+
estado: row.estado,
|
|
80
|
+
estado_institucion: row.estado_institucion,
|
|
81
|
+
caracter_academico: row.caracter_academico,
|
|
82
|
+
sector: row.sector,
|
|
83
|
+
nivel_academico: row.nivel_academico,
|
|
84
|
+
nivel_formacion: row.nivel_formacion,
|
|
85
|
+
modalidad: row.modalidad,
|
|
86
|
+
titulo_otorgado: row.titulo_otorgado,
|
|
87
|
+
reconocimiento: row.reconocimiento || null,
|
|
88
|
+
cine_amplio: row.cine_amplio,
|
|
89
|
+
cine_especifico: row.cine_especifico,
|
|
90
|
+
cine_detallado: row.cine_detallado,
|
|
91
|
+
area_conocimiento: row.area_conocimiento,
|
|
92
|
+
nucleo_conocimiento: row.nucleo_conocimiento,
|
|
93
|
+
departamento: row.departamento,
|
|
94
|
+
municipio: row.municipio,
|
|
95
|
+
creditos: row.creditos ? parseInt(row.creditos, 10) : null,
|
|
96
|
+
periodos_duracion: row.periodos_duracion ? parseInt(row.periodos_duracion, 10) : null,
|
|
97
|
+
periodicidad: row.periodicidad || null,
|
|
98
|
+
periodicidad_admisiones: row.periodicidad_admisiones || null,
|
|
99
|
+
costo_matricula: row.costo_matricula ? parseFloat(row.costo_matricula) : null,
|
|
100
|
+
en_convenio: row.en_convenio || null,
|
|
101
|
+
vigencia_anos: row.vigencia_anos || null,
|
|
102
|
+
ciclos_propedeuticos: row.ciclos_propedeuticos || null,
|
|
103
|
+
fecha_registro_snies: row.fecha_registro_snies || null,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
cachedPrograms = programs;
|
|
107
|
+
return programs;
|
|
108
|
+
}
|
|
109
|
+
/** Load the RIASEC → CINE field mapping */
|
|
110
|
+
export function loadMapping() {
|
|
111
|
+
if (cachedMapping)
|
|
112
|
+
return cachedMapping;
|
|
113
|
+
const dataDir = findDataDir();
|
|
114
|
+
const raw = readFileSync(resolve(dataDir, "mapping.json"), "utf-8");
|
|
115
|
+
const data = JSON.parse(raw);
|
|
116
|
+
cachedMapping = data.mapping;
|
|
117
|
+
return cachedMapping;
|
|
118
|
+
}
|
|
119
|
+
/** Get unique CINE broad fields from programs */
|
|
120
|
+
export function getCINEFields(programs) {
|
|
121
|
+
return [...new Set(programs.map((p) => p.cine_amplio))].filter(Boolean).sort();
|
|
122
|
+
}
|
|
123
|
+
/** Get unique departments from programs */
|
|
124
|
+
export function getDepartments(programs) {
|
|
125
|
+
return [...new Set(programs.map((p) => p.departamento))].filter(Boolean).sort();
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=programs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"programs.js","sourceRoot":"","sources":["../src/programs.ts"],"names":[],"mappings":"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"}
|
package/dist/quiz.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quiz.ts — Bayesian adaptive RIASEC quiz engine
|
|
3
|
+
*
|
|
4
|
+
* Implements an adaptive testing engine that selects the most informative
|
|
5
|
+
* question at each step using expected information gain (KL divergence).
|
|
6
|
+
* Stops when posterior entropy drops below a configurable threshold.
|
|
7
|
+
*/
|
|
8
|
+
import type { Item, Answer, QuizConfig, QuizProgress, RIASECProfile, DirichletAlpha, LikertResponse, RIASECType } from "./types.js";
|
|
9
|
+
export interface Quiz {
|
|
10
|
+
/** Get the next question to ask. Returns null if quiz is complete. */
|
|
11
|
+
nextQuestion(): Item | null;
|
|
12
|
+
/** Record an answer */
|
|
13
|
+
answer(itemId: string, response: LikertResponse): void;
|
|
14
|
+
/** Check if the quiz should stop */
|
|
15
|
+
isComplete(): boolean;
|
|
16
|
+
/** Get current progress */
|
|
17
|
+
progress(): QuizProgress;
|
|
18
|
+
/** Get current RIASEC profile (posterior mean) */
|
|
19
|
+
profile(): RIASECProfile;
|
|
20
|
+
/** Get current Dirichlet alpha parameters */
|
|
21
|
+
alpha(): DirichletAlpha;
|
|
22
|
+
/** Get all recorded answers */
|
|
23
|
+
answers(): Answer[];
|
|
24
|
+
/** Get the top N RIASEC types */
|
|
25
|
+
topTypes(n?: number): RIASECType[];
|
|
26
|
+
}
|
|
27
|
+
/** Create a new quiz instance */
|
|
28
|
+
export declare function createQuiz(config?: Partial<QuizConfig>): Quiz;
|
|
29
|
+
//# sourceMappingURL=quiz.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quiz.d.ts","sourceRoot":"","sources":["../src/quiz.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAWH,OAAO,KAAK,EACV,IAAI,EACJ,MAAM,EACN,UAAU,EACV,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,UAAU,EACX,MAAM,YAAY,CAAC;AAUpB,MAAM,WAAW,IAAI;IACnB,sEAAsE;IACtE,YAAY,IAAI,IAAI,GAAG,IAAI,CAAC;IAC5B,uBAAuB;IACvB,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACvD,oCAAoC;IACpC,UAAU,IAAI,OAAO,CAAC;IACtB,2BAA2B;IAC3B,QAAQ,IAAI,YAAY,CAAC;IACzB,kDAAkD;IAClD,OAAO,IAAI,aAAa,CAAC;IACzB,6CAA6C;IAC7C,KAAK,IAAI,cAAc,CAAC;IACxB,+BAA+B;IAC/B,OAAO,IAAI,MAAM,EAAE,CAAC;IACpB,iCAAiC;IACjC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,GAAG,UAAU,EAAE,CAAC;CACpC;AAED,iCAAiC;AACjC,wBAAgB,UAAU,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,IAAI,CA6H7D"}
|
package/dist/quiz.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* quiz.ts — Bayesian adaptive RIASEC quiz engine
|
|
3
|
+
*
|
|
4
|
+
* Implements an adaptive testing engine that selects the most informative
|
|
5
|
+
* question at each step using expected information gain (KL divergence).
|
|
6
|
+
* Stops when posterior entropy drops below a configurable threshold.
|
|
7
|
+
*/
|
|
8
|
+
import { uniformPrior, updateAlpha, posteriorMean, entropy, confidence, expectedInfoGain, } from "./scoring.js";
|
|
9
|
+
import { loadItems } from "./items.js";
|
|
10
|
+
const DEFAULT_CONFIG = {
|
|
11
|
+
language: "es",
|
|
12
|
+
mode: "adaptive",
|
|
13
|
+
entropyThreshold: 1.5,
|
|
14
|
+
maxQuestions: 24,
|
|
15
|
+
minQuestions: 12,
|
|
16
|
+
};
|
|
17
|
+
/** Create a new quiz instance */
|
|
18
|
+
export function createQuiz(config) {
|
|
19
|
+
const cfg = { ...DEFAULT_CONFIG, ...config };
|
|
20
|
+
const bank = loadItems(cfg.language);
|
|
21
|
+
const allItems = [...bank.items];
|
|
22
|
+
let alpha = uniformPrior();
|
|
23
|
+
const answeredItems = new Set();
|
|
24
|
+
const recordedAnswers = [];
|
|
25
|
+
function nextQuestion() {
|
|
26
|
+
if (isComplete())
|
|
27
|
+
return null;
|
|
28
|
+
const remaining = allItems.filter((i) => !answeredItems.has(i.id));
|
|
29
|
+
if (remaining.length === 0)
|
|
30
|
+
return null;
|
|
31
|
+
if (cfg.mode === "full") {
|
|
32
|
+
// In full mode, return items in order
|
|
33
|
+
return remaining[0];
|
|
34
|
+
}
|
|
35
|
+
// Adaptive mode: pick the item with highest expected information gain
|
|
36
|
+
let bestItem = remaining[0];
|
|
37
|
+
let bestGain = -Infinity;
|
|
38
|
+
for (const item of remaining) {
|
|
39
|
+
const gain = expectedInfoGain(alpha, item.type);
|
|
40
|
+
if (gain > bestGain) {
|
|
41
|
+
bestGain = gain;
|
|
42
|
+
bestItem = item;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return bestItem;
|
|
46
|
+
}
|
|
47
|
+
function answer(itemId, response) {
|
|
48
|
+
const item = allItems.find((i) => i.id === itemId);
|
|
49
|
+
if (!item)
|
|
50
|
+
throw new Error(`Unknown item: ${itemId}`);
|
|
51
|
+
if (answeredItems.has(itemId))
|
|
52
|
+
throw new Error(`Item already answered: ${itemId}`);
|
|
53
|
+
if (response < 1 || response > 5)
|
|
54
|
+
throw new Error(`Invalid response: ${response}`);
|
|
55
|
+
answeredItems.add(itemId);
|
|
56
|
+
alpha = updateAlpha(alpha, item.type, response, item.keyed);
|
|
57
|
+
recordedAnswers.push({
|
|
58
|
+
itemId,
|
|
59
|
+
type: item.type,
|
|
60
|
+
response,
|
|
61
|
+
keyed: item.keyed,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function isComplete() {
|
|
65
|
+
const answered = answeredItems.size;
|
|
66
|
+
if (cfg.mode === "full") {
|
|
67
|
+
return answered >= allItems.length;
|
|
68
|
+
}
|
|
69
|
+
// Adaptive: stop when we have enough data and entropy is low enough
|
|
70
|
+
if (answered >= cfg.maxQuestions)
|
|
71
|
+
return true;
|
|
72
|
+
if (answered < cfg.minQuestions)
|
|
73
|
+
return false;
|
|
74
|
+
return entropy(alpha) < cfg.entropyThreshold;
|
|
75
|
+
}
|
|
76
|
+
function progress() {
|
|
77
|
+
const answered = answeredItems.size;
|
|
78
|
+
const total = cfg.mode === "full" ? allItems.length : cfg.maxQuestions;
|
|
79
|
+
const currentEntropy = entropy(alpha);
|
|
80
|
+
const conf = confidence(alpha);
|
|
81
|
+
let estimatedRemaining;
|
|
82
|
+
if (cfg.mode === "full") {
|
|
83
|
+
estimatedRemaining = allItems.length - answered;
|
|
84
|
+
}
|
|
85
|
+
else if (answered < cfg.minQuestions) {
|
|
86
|
+
estimatedRemaining = cfg.minQuestions - answered;
|
|
87
|
+
}
|
|
88
|
+
else if (currentEntropy < cfg.entropyThreshold) {
|
|
89
|
+
estimatedRemaining = 0;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Rough estimate based on entropy decay rate
|
|
93
|
+
estimatedRemaining = Math.min(cfg.maxQuestions - answered, Math.ceil((currentEntropy - cfg.entropyThreshold) * 8));
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
answered,
|
|
97
|
+
total,
|
|
98
|
+
estimatedRemaining,
|
|
99
|
+
confidence: conf,
|
|
100
|
+
entropy: currentEntropy,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function getProfile() {
|
|
104
|
+
return posteriorMean(alpha);
|
|
105
|
+
}
|
|
106
|
+
function getAlpha() {
|
|
107
|
+
return { ...alpha };
|
|
108
|
+
}
|
|
109
|
+
function getAnswers() {
|
|
110
|
+
return [...recordedAnswers];
|
|
111
|
+
}
|
|
112
|
+
function topTypes(n = 3) {
|
|
113
|
+
const profile = getProfile();
|
|
114
|
+
return Object.entries(profile)
|
|
115
|
+
.sort((a, b) => b[1] - a[1])
|
|
116
|
+
.slice(0, n)
|
|
117
|
+
.map(([type]) => type);
|
|
118
|
+
}
|
|
119
|
+
return {
|
|
120
|
+
nextQuestion,
|
|
121
|
+
answer,
|
|
122
|
+
isComplete,
|
|
123
|
+
progress,
|
|
124
|
+
profile: getProfile,
|
|
125
|
+
alpha: getAlpha,
|
|
126
|
+
answers: getAnswers,
|
|
127
|
+
topTypes,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=quiz.js.map
|
package/dist/quiz.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quiz.js","sourceRoot":"","sources":["../src/quiz.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recommender.ts — Program recommendation engine
|
|
3
|
+
*
|
|
4
|
+
* Matches a student's RIASEC profile to SNIES programs using
|
|
5
|
+
* the RIASEC → CINE field mapping. Supports enrollment-weighted
|
|
6
|
+
* priors that surface hidden-gem programs.
|
|
7
|
+
*/
|
|
8
|
+
import type { RecommendConfig, Recommendation } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Recommend programs based on RIASEC profile.
|
|
11
|
+
*
|
|
12
|
+
* score(program) = similarity(student_profile, program_cine_vector)
|
|
13
|
+
* × prior(program)
|
|
14
|
+
*
|
|
15
|
+
* prior(program) = base
|
|
16
|
+
* × (1 + enrollmentWeight × log(national / field_count))
|
|
17
|
+
* × (1 + regionalBoost × isInRegion)
|
|
18
|
+
* × (1 + virtualBoost × isVirtual)
|
|
19
|
+
*/
|
|
20
|
+
export declare function recommend(config: RecommendConfig): Recommendation[];
|
|
21
|
+
//# sourceMappingURL=recommender.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.d.ts","sourceRoot":"","sources":["../src/recommender.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAKV,eAAe,EAEf,cAAc,EACf,MAAM,YAAY,CAAC;AAgFpB;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,cAAc,EAAE,CAsFnE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* recommender.ts — Program recommendation engine
|
|
3
|
+
*
|
|
4
|
+
* Matches a student's RIASEC profile to SNIES programs using
|
|
5
|
+
* the RIASEC → CINE field mapping. Supports enrollment-weighted
|
|
6
|
+
* priors that surface hidden-gem programs.
|
|
7
|
+
*/
|
|
8
|
+
import { cosineSimilarity } from "./scoring.js";
|
|
9
|
+
import { loadMapping } from "./programs.js";
|
|
10
|
+
import { RIASEC_TYPES } from "./types.js";
|
|
11
|
+
const VIRTUAL_MODALITIES = new Set([
|
|
12
|
+
"Virtual",
|
|
13
|
+
"A distancia",
|
|
14
|
+
"Virtual-A distancia",
|
|
15
|
+
]);
|
|
16
|
+
/**
|
|
17
|
+
* Build a RIASEC profile vector for a given CINE broad field,
|
|
18
|
+
* based on the mapping weights.
|
|
19
|
+
*/
|
|
20
|
+
function fieldToProfile(cineAmplio, mapping) {
|
|
21
|
+
const profile = { R: 0, I: 0, A: 0, S: 0, E: 0, C: 0 };
|
|
22
|
+
for (const type of RIASEC_TYPES) {
|
|
23
|
+
const entry = mapping[type];
|
|
24
|
+
for (const field of entry.fields) {
|
|
25
|
+
if (field.cine_amplio === cineAmplio) {
|
|
26
|
+
profile[type] = field.weight;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return profile;
|
|
31
|
+
}
|
|
32
|
+
/** Apply filters to a program list */
|
|
33
|
+
function applyFilters(programs, filters) {
|
|
34
|
+
if (!filters)
|
|
35
|
+
return programs;
|
|
36
|
+
let result = programs;
|
|
37
|
+
if (filters.active !== undefined) {
|
|
38
|
+
const status = filters.active ? "Activo" : "Inactivo";
|
|
39
|
+
result = result.filter((p) => p.estado === status);
|
|
40
|
+
}
|
|
41
|
+
if (filters.departments?.length) {
|
|
42
|
+
const depts = new Set(filters.departments);
|
|
43
|
+
result = result.filter((p) => depts.has(p.departamento));
|
|
44
|
+
}
|
|
45
|
+
if (filters.nivel_formacion?.length) {
|
|
46
|
+
const niveles = new Set(filters.nivel_formacion);
|
|
47
|
+
result = result.filter((p) => niveles.has(p.nivel_formacion));
|
|
48
|
+
}
|
|
49
|
+
if (filters.modalidad?.length) {
|
|
50
|
+
const mods = new Set(filters.modalidad);
|
|
51
|
+
result = result.filter((p) => mods.has(p.modalidad));
|
|
52
|
+
}
|
|
53
|
+
if (filters.sector?.length) {
|
|
54
|
+
const sectors = new Set(filters.sector);
|
|
55
|
+
result = result.filter((p) => sectors.has(p.sector));
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Compute program counts per CINE broad field (for enrollment-based priors).
|
|
61
|
+
* Uses program count as a proxy for enrollment when actual enrollment data
|
|
62
|
+
* is not available.
|
|
63
|
+
*/
|
|
64
|
+
function computeFieldCounts(programs) {
|
|
65
|
+
const counts = new Map();
|
|
66
|
+
for (const p of programs) {
|
|
67
|
+
const field = p.cine_amplio;
|
|
68
|
+
if (field) {
|
|
69
|
+
counts.set(field, (counts.get(field) ?? 0) + 1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return counts;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Recommend programs based on RIASEC profile.
|
|
76
|
+
*
|
|
77
|
+
* score(program) = similarity(student_profile, program_cine_vector)
|
|
78
|
+
* × prior(program)
|
|
79
|
+
*
|
|
80
|
+
* prior(program) = base
|
|
81
|
+
* × (1 + enrollmentWeight × log(national / field_count))
|
|
82
|
+
* × (1 + regionalBoost × isInRegion)
|
|
83
|
+
* × (1 + virtualBoost × isVirtual)
|
|
84
|
+
*/
|
|
85
|
+
export function recommend(config) {
|
|
86
|
+
const { profile, programs, filters, limit = 20, } = config;
|
|
87
|
+
const mapping = config.mapping ?? loadMapping();
|
|
88
|
+
const priors = {
|
|
89
|
+
enrollmentWeight: config.priors?.enrollmentWeight ?? -0.3,
|
|
90
|
+
regionalBoost: config.priors?.regionalBoost ?? 1.0,
|
|
91
|
+
virtualBoost: config.priors?.virtualBoost ?? 1.0,
|
|
92
|
+
};
|
|
93
|
+
const filtered = applyFilters(programs, filters);
|
|
94
|
+
const fieldCounts = computeFieldCounts(programs); // use full dataset for counts
|
|
95
|
+
const totalPrograms = programs.filter((p) => p.estado === "Activo").length;
|
|
96
|
+
// Pre-compute field profiles
|
|
97
|
+
const fieldProfileCache = new Map();
|
|
98
|
+
for (const field of fieldCounts.keys()) {
|
|
99
|
+
fieldProfileCache.set(field, fieldToProfile(field, mapping));
|
|
100
|
+
}
|
|
101
|
+
const regionalDepts = filters?.departments
|
|
102
|
+
? new Set(filters.departments)
|
|
103
|
+
: null;
|
|
104
|
+
const results = [];
|
|
105
|
+
for (const program of filtered) {
|
|
106
|
+
const fieldProfile = fieldProfileCache.get(program.cine_amplio);
|
|
107
|
+
if (!fieldProfile)
|
|
108
|
+
continue;
|
|
109
|
+
// Profile similarity (cosine)
|
|
110
|
+
const similarity = cosineSimilarity(profile, fieldProfile);
|
|
111
|
+
if (similarity === 0)
|
|
112
|
+
continue;
|
|
113
|
+
// Enrollment-based prior
|
|
114
|
+
const fieldCount = fieldCounts.get(program.cine_amplio) ?? 1;
|
|
115
|
+
const enrollmentFactor = 1 + priors.enrollmentWeight * Math.log(totalPrograms / fieldCount);
|
|
116
|
+
// Regional boost
|
|
117
|
+
const isRegional = regionalDepts?.has(program.departamento) ? 1 : 0;
|
|
118
|
+
const regionalFactor = 1 + priors.regionalBoost * isRegional;
|
|
119
|
+
// Virtual boost
|
|
120
|
+
const isVirtual = VIRTUAL_MODALITIES.has(program.modalidad) ? 1 : 0;
|
|
121
|
+
const virtualFactor = 1 + priors.virtualBoost * isVirtual;
|
|
122
|
+
const priorAdjustment = enrollmentFactor * regionalFactor * virtualFactor;
|
|
123
|
+
const score = similarity * priorAdjustment;
|
|
124
|
+
// Find which RIASEC types match this field
|
|
125
|
+
const matchingTypes = RIASEC_TYPES.filter((t) => fieldProfile[t] > 0);
|
|
126
|
+
results.push({
|
|
127
|
+
program,
|
|
128
|
+
score,
|
|
129
|
+
matchDetails: {
|
|
130
|
+
profileSimilarity: similarity,
|
|
131
|
+
priorAdjustment,
|
|
132
|
+
matchingTypes,
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
// Sort by score descending, deduplicate by program name + institution
|
|
137
|
+
results.sort((a, b) => b.score - a.score);
|
|
138
|
+
// Deduplicate: keep highest-scoring entry per unique program
|
|
139
|
+
const seen = new Set();
|
|
140
|
+
const deduped = [];
|
|
141
|
+
for (const r of results) {
|
|
142
|
+
const key = `${r.program.codigo_snies}`;
|
|
143
|
+
if (!seen.has(key)) {
|
|
144
|
+
seen.add(key);
|
|
145
|
+
deduped.push(r);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return deduped.slice(0, limit);
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=recommender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommender.js","sourceRoot":"","sources":["../src/recommender.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scoring.ts — Dirichlet-based Bayesian scoring for RIASEC profiles
|
|
3
|
+
*
|
|
4
|
+
* Uses Dirichlet distribution as conjugate prior for the categorical
|
|
5
|
+
* distribution over 6 RIASEC types. Each answer updates the alpha
|
|
6
|
+
* (concentration) parameters.
|
|
7
|
+
*/
|
|
8
|
+
import { type RIASECType, type RIASECProfile, type DirichletAlpha, type LikertResponse } from "./types.js";
|
|
9
|
+
/** Create uniform Dirichlet prior: α = (1, 1, 1, 1, 1, 1) */
|
|
10
|
+
export declare function uniformPrior(): DirichletAlpha;
|
|
11
|
+
/**
|
|
12
|
+
* Update Dirichlet alpha after observing a response.
|
|
13
|
+
*
|
|
14
|
+
* For a positively-keyed item of type T with response r (1-5):
|
|
15
|
+
* α_T += (r - 1) / 4 (normalized to [0, 1])
|
|
16
|
+
*
|
|
17
|
+
* For negatively-keyed items, the response is reversed:
|
|
18
|
+
* α_T += (5 - r) / 4
|
|
19
|
+
*/
|
|
20
|
+
export declare function updateAlpha(alpha: DirichletAlpha, type: RIASECType, response: LikertResponse, keyed: "+" | "-"): DirichletAlpha;
|
|
21
|
+
/**
|
|
22
|
+
* Compute the posterior mean (expected profile) from Dirichlet alpha.
|
|
23
|
+
* E[θ_k] = α_k / Σα
|
|
24
|
+
*/
|
|
25
|
+
export declare function posteriorMean(alpha: DirichletAlpha): RIASECProfile;
|
|
26
|
+
/** Sum of all alpha parameters */
|
|
27
|
+
export declare function alphaSum(alpha: DirichletAlpha): number;
|
|
28
|
+
/**
|
|
29
|
+
* Shannon entropy of the posterior mean.
|
|
30
|
+
* H = -Σ p_k log2(p_k)
|
|
31
|
+
*
|
|
32
|
+
* Maximum entropy (uniform) = log2(6) ≈ 2.585
|
|
33
|
+
* Minimum entropy = 0 (all mass on one type)
|
|
34
|
+
*/
|
|
35
|
+
export declare function entropy(alpha: DirichletAlpha): number;
|
|
36
|
+
/** Maximum possible entropy for 6 categories */
|
|
37
|
+
export declare const MAX_ENTROPY: number;
|
|
38
|
+
/**
|
|
39
|
+
* Confidence: 1 - (entropy / max_entropy)
|
|
40
|
+
* 0 = uniform (no info), 1 = certain (all mass on one type)
|
|
41
|
+
*/
|
|
42
|
+
export declare function confidence(alpha: DirichletAlpha): number;
|
|
43
|
+
/**
|
|
44
|
+
* Expected information gain from asking an item of a given type.
|
|
45
|
+
*
|
|
46
|
+
* Approximation: items of the type with lowest alpha (most uncertain)
|
|
47
|
+
* yield the most information gain. We compute the expected KL divergence
|
|
48
|
+
* between the current posterior and the expected posterior after answering.
|
|
49
|
+
*/
|
|
50
|
+
export declare function expectedInfoGain(alpha: DirichletAlpha, type: RIASECType): number;
|
|
51
|
+
/**
|
|
52
|
+
* KL divergence: D_KL(P || Q) = Σ P(x) log2(P(x) / Q(x))
|
|
53
|
+
*/
|
|
54
|
+
export declare function klDivergence(p: RIASECProfile, q: RIASECProfile): number;
|
|
55
|
+
/**
|
|
56
|
+
* Cosine similarity between two RIASEC profiles.
|
|
57
|
+
* Returns value in [0, 1].
|
|
58
|
+
*/
|
|
59
|
+
export declare function cosineSimilarity(a: RIASECProfile, b: RIASECProfile): number;
|
|
60
|
+
//# sourceMappingURL=scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.d.ts","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAEzH,6DAA6D;AAC7D,wBAAgB,YAAY,IAAI,cAAc,CAE7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,cAAc,EACrB,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,cAAc,EACxB,KAAK,EAAE,GAAG,GAAG,GAAG,GACf,cAAc,CAShB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,aAAa,CAKlE;AAED,kCAAkC;AAClC,wBAAgB,QAAQ,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAEtD;AAED;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAUrD;AAED,gDAAgD;AAChD,eAAO,MAAM,WAAW,QAAe,CAAC;AAExC;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,CAehF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,MAAM,CAQvE;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,aAAa,GAAG,MAAM,CAS3E"}
|
package/dist/scoring.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* scoring.ts — Dirichlet-based Bayesian scoring for RIASEC profiles
|
|
3
|
+
*
|
|
4
|
+
* Uses Dirichlet distribution as conjugate prior for the categorical
|
|
5
|
+
* distribution over 6 RIASEC types. Each answer updates the alpha
|
|
6
|
+
* (concentration) parameters.
|
|
7
|
+
*/
|
|
8
|
+
import { RIASEC_TYPES } from "./types.js";
|
|
9
|
+
/** Create uniform Dirichlet prior: α = (1, 1, 1, 1, 1, 1) */
|
|
10
|
+
export function uniformPrior() {
|
|
11
|
+
return Object.fromEntries(RIASEC_TYPES.map((t) => [t, 1]));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Update Dirichlet alpha after observing a response.
|
|
15
|
+
*
|
|
16
|
+
* For a positively-keyed item of type T with response r (1-5):
|
|
17
|
+
* α_T += (r - 1) / 4 (normalized to [0, 1])
|
|
18
|
+
*
|
|
19
|
+
* For negatively-keyed items, the response is reversed:
|
|
20
|
+
* α_T += (5 - r) / 4
|
|
21
|
+
*/
|
|
22
|
+
export function updateAlpha(alpha, type, response, keyed) {
|
|
23
|
+
const normalized = keyed === "+"
|
|
24
|
+
? (response - 1) / 4
|
|
25
|
+
: (5 - response) / 4;
|
|
26
|
+
return {
|
|
27
|
+
...alpha,
|
|
28
|
+
[type]: alpha[type] + normalized,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Compute the posterior mean (expected profile) from Dirichlet alpha.
|
|
33
|
+
* E[θ_k] = α_k / Σα
|
|
34
|
+
*/
|
|
35
|
+
export function posteriorMean(alpha) {
|
|
36
|
+
const sum = alphaSum(alpha);
|
|
37
|
+
return Object.fromEntries(RIASEC_TYPES.map((t) => [t, alpha[t] / sum]));
|
|
38
|
+
}
|
|
39
|
+
/** Sum of all alpha parameters */
|
|
40
|
+
export function alphaSum(alpha) {
|
|
41
|
+
return RIASEC_TYPES.reduce((s, t) => s + alpha[t], 0);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Shannon entropy of the posterior mean.
|
|
45
|
+
* H = -Σ p_k log2(p_k)
|
|
46
|
+
*
|
|
47
|
+
* Maximum entropy (uniform) = log2(6) ≈ 2.585
|
|
48
|
+
* Minimum entropy = 0 (all mass on one type)
|
|
49
|
+
*/
|
|
50
|
+
export function entropy(alpha) {
|
|
51
|
+
const profile = posteriorMean(alpha);
|
|
52
|
+
let h = 0;
|
|
53
|
+
for (const t of RIASEC_TYPES) {
|
|
54
|
+
const p = profile[t];
|
|
55
|
+
if (p > 0) {
|
|
56
|
+
h -= p * Math.log2(p);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return h;
|
|
60
|
+
}
|
|
61
|
+
/** Maximum possible entropy for 6 categories */
|
|
62
|
+
export const MAX_ENTROPY = Math.log2(6);
|
|
63
|
+
/**
|
|
64
|
+
* Confidence: 1 - (entropy / max_entropy)
|
|
65
|
+
* 0 = uniform (no info), 1 = certain (all mass on one type)
|
|
66
|
+
*/
|
|
67
|
+
export function confidence(alpha) {
|
|
68
|
+
return 1 - entropy(alpha) / MAX_ENTROPY;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Expected information gain from asking an item of a given type.
|
|
72
|
+
*
|
|
73
|
+
* Approximation: items of the type with lowest alpha (most uncertain)
|
|
74
|
+
* yield the most information gain. We compute the expected KL divergence
|
|
75
|
+
* between the current posterior and the expected posterior after answering.
|
|
76
|
+
*/
|
|
77
|
+
export function expectedInfoGain(alpha, type) {
|
|
78
|
+
const sum = alphaSum(alpha);
|
|
79
|
+
// Simulate average response (response=3, neutral contribution)
|
|
80
|
+
// and high response (response=5, full contribution)
|
|
81
|
+
// Weight: 50% neutral, 50% positive (approximation of response distribution)
|
|
82
|
+
let totalKL = 0;
|
|
83
|
+
for (const response of [1, 3, 5]) {
|
|
84
|
+
const newAlpha = updateAlpha(alpha, type, response, "+");
|
|
85
|
+
const kl = klDivergence(posteriorMean(newAlpha), posteriorMean(alpha));
|
|
86
|
+
totalKL += kl / 3; // uniform weight over simulated responses
|
|
87
|
+
}
|
|
88
|
+
return totalKL;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* KL divergence: D_KL(P || Q) = Σ P(x) log2(P(x) / Q(x))
|
|
92
|
+
*/
|
|
93
|
+
export function klDivergence(p, q) {
|
|
94
|
+
let kl = 0;
|
|
95
|
+
for (const t of RIASEC_TYPES) {
|
|
96
|
+
if (p[t] > 0 && q[t] > 0) {
|
|
97
|
+
kl += p[t] * Math.log2(p[t] / q[t]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return kl;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Cosine similarity between two RIASEC profiles.
|
|
104
|
+
* Returns value in [0, 1].
|
|
105
|
+
*/
|
|
106
|
+
export function cosineSimilarity(a, b) {
|
|
107
|
+
let dot = 0, normA = 0, normB = 0;
|
|
108
|
+
for (const t of RIASEC_TYPES) {
|
|
109
|
+
dot += a[t] * b[t];
|
|
110
|
+
normA += a[t] * a[t];
|
|
111
|
+
normB += b[t] * b[t];
|
|
112
|
+
}
|
|
113
|
+
const denom = Math.sqrt(normA) * Math.sqrt(normB);
|
|
114
|
+
return denom === 0 ? 0 : dot / denom;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scoring.js","sourceRoot":"","sources":["../src/scoring.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/** The six Holland RIASEC types */
|
|
2
|
+
export declare const RIASEC_TYPES: readonly ["R", "I", "A", "S", "E", "C"];
|
|
3
|
+
export type RIASECType = (typeof RIASEC_TYPES)[number];
|
|
4
|
+
/** A RIASEC profile: probability distribution over 6 types */
|
|
5
|
+
export type RIASECProfile = Record<RIASECType, number>;
|
|
6
|
+
/** Dirichlet concentration parameters (alpha vector) */
|
|
7
|
+
export type DirichletAlpha = Record<RIASECType, number>;
|
|
8
|
+
/** Response scale: 1-5 Likert */
|
|
9
|
+
export type LikertResponse = 1 | 2 | 3 | 4 | 5;
|
|
10
|
+
/** A single IPIP RIASEC item */
|
|
11
|
+
export interface Item {
|
|
12
|
+
id: string;
|
|
13
|
+
type: RIASECType;
|
|
14
|
+
text: string;
|
|
15
|
+
keyed: "+" | "-";
|
|
16
|
+
}
|
|
17
|
+
/** Item bank with metadata */
|
|
18
|
+
export interface ItemBank {
|
|
19
|
+
meta: {
|
|
20
|
+
source: string;
|
|
21
|
+
scale: string;
|
|
22
|
+
reference: string;
|
|
23
|
+
license: string;
|
|
24
|
+
response_scale: Record<string, string>;
|
|
25
|
+
types: Record<RIASECType, string>;
|
|
26
|
+
};
|
|
27
|
+
items: Item[];
|
|
28
|
+
}
|
|
29
|
+
/** A recorded answer */
|
|
30
|
+
export interface Answer {
|
|
31
|
+
itemId: string;
|
|
32
|
+
type: RIASECType;
|
|
33
|
+
response: LikertResponse;
|
|
34
|
+
keyed: "+" | "-";
|
|
35
|
+
}
|
|
36
|
+
/** Quiz configuration */
|
|
37
|
+
export interface QuizConfig {
|
|
38
|
+
/** Language for item text */
|
|
39
|
+
language: "en" | "es";
|
|
40
|
+
/** Quiz mode: adaptive (entropy-based stopping) or full (all 48 items) */
|
|
41
|
+
mode: "adaptive" | "full";
|
|
42
|
+
/** Entropy threshold for adaptive stopping (lower = more questions). Default: 1.5 */
|
|
43
|
+
entropyThreshold?: number;
|
|
44
|
+
/** Maximum questions in adaptive mode. Default: 24 */
|
|
45
|
+
maxQuestions?: number;
|
|
46
|
+
/** Minimum questions in adaptive mode. Default: 12 */
|
|
47
|
+
minQuestions?: number;
|
|
48
|
+
}
|
|
49
|
+
/** Quiz progress info */
|
|
50
|
+
export interface QuizProgress {
|
|
51
|
+
answered: number;
|
|
52
|
+
total: number;
|
|
53
|
+
estimatedRemaining: number;
|
|
54
|
+
confidence: number;
|
|
55
|
+
entropy: number;
|
|
56
|
+
}
|
|
57
|
+
/** A SNIES program record (canonical CSV row) */
|
|
58
|
+
export interface Program {
|
|
59
|
+
codigo_institucion_padre: string;
|
|
60
|
+
codigo_snies: number;
|
|
61
|
+
nombre_programa: string;
|
|
62
|
+
codigo_institucion: string;
|
|
63
|
+
nombre_institucion: string;
|
|
64
|
+
estado: "Activo" | "Inactivo";
|
|
65
|
+
estado_institucion: string;
|
|
66
|
+
caracter_academico: string;
|
|
67
|
+
sector: string;
|
|
68
|
+
nivel_academico: string;
|
|
69
|
+
nivel_formacion: string;
|
|
70
|
+
modalidad: string;
|
|
71
|
+
titulo_otorgado: string;
|
|
72
|
+
reconocimiento: string | null;
|
|
73
|
+
cine_amplio: string;
|
|
74
|
+
cine_especifico: string;
|
|
75
|
+
cine_detallado: string;
|
|
76
|
+
area_conocimiento: string;
|
|
77
|
+
nucleo_conocimiento: string;
|
|
78
|
+
departamento: string;
|
|
79
|
+
municipio: string;
|
|
80
|
+
creditos: number | null;
|
|
81
|
+
periodos_duracion: number | null;
|
|
82
|
+
periodicidad: string | null;
|
|
83
|
+
periodicidad_admisiones: string | null;
|
|
84
|
+
costo_matricula: number | null;
|
|
85
|
+
en_convenio: string | null;
|
|
86
|
+
vigencia_anos: string | null;
|
|
87
|
+
ciclos_propedeuticos: string | null;
|
|
88
|
+
fecha_registro_snies: string | null;
|
|
89
|
+
}
|
|
90
|
+
/** A coverage record (Cobertura sheet — per-municipality offering) */
|
|
91
|
+
export interface Cobertura {
|
|
92
|
+
codigo_snies: number;
|
|
93
|
+
nombre_programa: string;
|
|
94
|
+
tipo_cubrimiento: string;
|
|
95
|
+
departamento: string;
|
|
96
|
+
municipio: string;
|
|
97
|
+
nombre_institucion: string;
|
|
98
|
+
codigo_institucion: string;
|
|
99
|
+
costo_matricula: number | null;
|
|
100
|
+
}
|
|
101
|
+
/** A convenio record (inter-institutional agreement) */
|
|
102
|
+
export interface Convenio {
|
|
103
|
+
codigo_snies: number;
|
|
104
|
+
nombre_programa: string;
|
|
105
|
+
tipo_cubrimiento: string;
|
|
106
|
+
departamento: string;
|
|
107
|
+
municipio: string;
|
|
108
|
+
nombre_institucion: string;
|
|
109
|
+
codigo_institucion: string;
|
|
110
|
+
}
|
|
111
|
+
/** RIASEC → CINE field mapping entry */
|
|
112
|
+
export interface FieldMapping {
|
|
113
|
+
cine_amplio: string;
|
|
114
|
+
weight: number;
|
|
115
|
+
}
|
|
116
|
+
/** Full mapping for one RIASEC type */
|
|
117
|
+
export interface TypeMapping {
|
|
118
|
+
name: string;
|
|
119
|
+
name_es: string;
|
|
120
|
+
description: string;
|
|
121
|
+
fields: FieldMapping[];
|
|
122
|
+
}
|
|
123
|
+
/** Recommendation config */
|
|
124
|
+
export interface RecommendConfig {
|
|
125
|
+
profile: RIASECProfile;
|
|
126
|
+
programs: Program[];
|
|
127
|
+
mapping?: Record<RIASECType, TypeMapping>;
|
|
128
|
+
filters?: ProgramFilters;
|
|
129
|
+
priors?: PriorWeights;
|
|
130
|
+
limit?: number;
|
|
131
|
+
}
|
|
132
|
+
/** Filters for program recommendations */
|
|
133
|
+
export interface ProgramFilters {
|
|
134
|
+
departments?: string[];
|
|
135
|
+
active?: boolean;
|
|
136
|
+
nivel_formacion?: string[];
|
|
137
|
+
modalidad?: string[];
|
|
138
|
+
sector?: string[];
|
|
139
|
+
}
|
|
140
|
+
/** Prior weights for recommendation scoring */
|
|
141
|
+
export interface PriorWeights {
|
|
142
|
+
/** Weight for enrollment-based adjustment. Negative = downweight popular fields. Default: -0.3 */
|
|
143
|
+
enrollmentWeight?: number;
|
|
144
|
+
/** Boost for programs in specified departments. Default: 1.0 */
|
|
145
|
+
regionalBoost?: number;
|
|
146
|
+
/** Boost for virtual/distance programs. Default: 1.0 */
|
|
147
|
+
virtualBoost?: number;
|
|
148
|
+
}
|
|
149
|
+
/** A single program recommendation with score */
|
|
150
|
+
export interface Recommendation {
|
|
151
|
+
program: Program;
|
|
152
|
+
score: number;
|
|
153
|
+
matchDetails: {
|
|
154
|
+
profileSimilarity: number;
|
|
155
|
+
priorAdjustment: number;
|
|
156
|
+
matchingTypes: RIASECType[];
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,eAAO,MAAM,YAAY,yCAA0C,CAAC;AACpE,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAEvD,wDAAwD;AACxD,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;AAExD,iCAAiC;AACjC,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAE/C,gCAAgC;AAChC,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB;AAED,8BAA8B;AAC9B,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE;QACJ,MAAM,EAAE,MAAM,CAAC;QACf,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;QAChB,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;KACnC,CAAC;IACF,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,wBAAwB;AACxB,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,cAAc,CAAC;IACzB,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;CAClB;AAED,yBAAyB;AACzB,MAAM,WAAW,UAAU;IACzB,6BAA6B;IAC7B,QAAQ,EAAE,IAAI,GAAG,IAAI,CAAC;IACtB,0EAA0E;IAC1E,IAAI,EAAE,UAAU,GAAG,MAAM,CAAC;IAC1B,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,yBAAyB;AACzB,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,iDAAiD;AACjD,MAAM,WAAW,OAAO;IACtB,wBAAwB,EAAE,MAAM,CAAC;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC9B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAC;CACrC;AAED,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wDAAwD;AACxD,MAAM,WAAW,QAAQ;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,uCAAuC;AACvC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,4BAA4B;AAC5B,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;IAC1C,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,kGAAkG;IAClG,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,iDAAiD;AACjD,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE;QACZ,iBAAiB,EAAE,MAAM,CAAC;QAC1B,eAAe,EAAE,MAAM,CAAC;QACxB,aAAa,EAAE,UAAU,EAAE,CAAC;KAC7B,CAAC;CACH"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAU,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "riasec-co",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Bayesian adaptive RIASEC quiz engine with Colombian SNIES program data",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/",
|
|
16
|
+
"../../data/canonical/"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"ci": "tsc --noEmit && vitest run"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"riasec",
|
|
26
|
+
"holland",
|
|
27
|
+
"vocational",
|
|
28
|
+
"career",
|
|
29
|
+
"guidance",
|
|
30
|
+
"bayesian",
|
|
31
|
+
"snies",
|
|
32
|
+
"colombia",
|
|
33
|
+
"education"
|
|
34
|
+
],
|
|
35
|
+
"author": "Andres Romero <me@afromero.co>",
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/afromero/riasec-co.git",
|
|
40
|
+
"directory": "packages/core"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/node": "^25.5.0",
|
|
44
|
+
"typescript": "^5.6.0",
|
|
45
|
+
"vitest": "^3.0.0"
|
|
46
|
+
}
|
|
47
|
+
}
|