@baravak/risloo-profile-cli 4.46.2 → 4.70.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.
Files changed (90) hide show
  1. package/.agents/skills/create-profile/SKILL.md +14 -0
  2. package/.claude/settings.local.json +7 -0
  3. package/.claude/skills/create-profile/SKILL.md +80 -0
  4. package/AGENTS.md +239 -0
  5. package/CLAUDE.md +5 -0
  6. package/package.json +1 -1
  7. package/src/cli-commands/Executor.js +8 -2
  8. package/src/cli-commands/ExtractExecutor.js +55 -21
  9. package/src/cli-commands/GiftExecutor.js +1 -0
  10. package/src/handlebars/importPartials.js +10 -44
  11. package/src/handlebars/init.js +17 -11
  12. package/src/publish/json/profiles/BSCT93.json +495 -0
  13. package/src/publish/json/profiles/BSSI93.json +392 -0
  14. package/src/publish/json/profiles/FRHPT93.json +2 -2
  15. package/src/publish/json/profiles/ISKS93.json +288 -0
  16. package/src/publish/json/profiles/MCMI9A.json +1540 -0
  17. package/src/publish/json/profiles/NEO93.json +3858 -0
  18. package/src/publish/json/profiles/NEO9A.json +1159 -1
  19. package/src/publish/json/profiles/NEO9Q.json +3856 -0
  20. package/src/publish/json/profiles/NEO9V.json +1157 -0
  21. package/src/publish/json/profiles/RIASEC9A.json +2775 -0
  22. package/src/publish/json/profiles/RSI93.json +359 -0
  23. package/src/samples/AEQ93.js +1 -1
  24. package/src/samples/AMS93.js +1 -1
  25. package/src/samples/AMS9A.js +3 -1
  26. package/src/samples/BAOMEIS93.js +1 -1
  27. package/src/samples/BEQI93.js +1 -1
  28. package/src/samples/BSCT93.js +90 -0
  29. package/src/samples/BSSI93.js +93 -0
  30. package/src/samples/CAATS93.js +1 -1
  31. package/src/samples/CERQ93.js +1 -1
  32. package/src/samples/CRAAS93.js +1 -1
  33. package/src/samples/DSWLS93.js +1 -1
  34. package/src/samples/EMSS93.js +1 -1
  35. package/src/samples/EPQ9A.js +6 -1
  36. package/src/samples/FACES93.js +6 -0
  37. package/src/samples/FRHPT93.js +41 -13
  38. package/src/samples/GMIT93.js +1 -1
  39. package/src/samples/HPL93.js +1 -1
  40. package/src/samples/ISKS93.js +62 -0
  41. package/src/samples/IUS93.js +1 -1
  42. package/src/samples/JCSI93.js +1 -1
  43. package/src/samples/JPFQ93.js +5 -0
  44. package/src/samples/KJGI93.js +1 -1
  45. package/src/samples/LMIQ93.js +1 -1
  46. package/src/samples/MMFAD93.js +1 -1
  47. package/src/samples/MOCI93.js +1 -1
  48. package/src/samples/NEO93.js +277 -0
  49. package/src/samples/NEO9A.js +91 -60
  50. package/src/samples/NEO9Q.js +279 -0
  51. package/src/samples/NEO9V.js +215 -0
  52. package/src/samples/OBQ4493.js +1 -1
  53. package/src/samples/PIES93.js +1 -1
  54. package/src/samples/PIES9A.js +4 -0
  55. package/src/samples/PMCIEF93.js +7 -1
  56. package/src/samples/PSWQ93.js +1 -1
  57. package/src/samples/RIASEC93.js +1 -1
  58. package/src/samples/RIASEC9A.js +256 -0
  59. package/src/samples/RSI93.js +89 -0
  60. package/src/samples/SASQ93.js +1 -1
  61. package/src/samples/SDCAQ93.js +1 -1
  62. package/src/samples/STAIY93.js +1 -1
  63. package/src/samples/WAQ93.js +1 -1
  64. package/src/samples/YBOCS93.js +1 -1
  65. package/src/samples/YPI93.js +5 -1
  66. package/views/profiles/NEO_long_facets.hbs +125 -0
  67. package/views/profiles/NEO_main.hbs +150 -0
  68. package/views/profiles/NEO_short_facets.hbs +82 -0
  69. package/views/profiles/samples/BSCT93.hbs +99 -0
  70. package/views/profiles/samples/BSSI93.hbs +102 -0
  71. package/views/profiles/samples/FRHPT93_1.hbs +570 -87
  72. package/views/profiles/samples/FRHPT93_2.hbs +90 -76
  73. package/views/profiles/samples/FRHPT93_3.hbs +2 -2
  74. package/views/profiles/samples/FRHPT93_4.hbs +2 -2
  75. package/views/profiles/samples/FRHPT93_5.hbs +103 -0
  76. package/views/profiles/samples/FRHPT9A.hbs +7 -7
  77. package/views/profiles/samples/ISKS93.hbs +38 -0
  78. package/views/profiles/samples/NEO93_1.hbs +14 -0
  79. package/views/profiles/samples/NEO93_2.hbs +15 -0
  80. package/views/profiles/samples/NEO93_3.hbs +15 -0
  81. package/views/profiles/samples/NEO9A.hbs +14 -29
  82. package/views/profiles/samples/NEO9Q_1.hbs +13 -0
  83. package/views/profiles/samples/NEO9Q_2.hbs +14 -0
  84. package/views/profiles/samples/NEO9Q_3.hbs +14 -0
  85. package/views/profiles/samples/NEO9V_1.hbs +13 -0
  86. package/views/profiles/samples/NEO9V_2.hbs +3 -0
  87. package/views/profiles/samples/RIASEC9A_1.hbs +38 -0
  88. package/views/profiles/samples/RIASEC9A_2.hbs +67 -0
  89. package/views/profiles/samples/RIASEC9A_3.hbs +93 -0
  90. package/views/profiles/samples/RSI93.hbs +101 -0
@@ -1,54 +1,75 @@
1
- const { Profile, FS } = require("../Profile");
1
+ const { Profile } = require("../Profile");
2
2
 
3
+ const DOMAIN_MAX = 48;
4
+ const BAR_WIDTH = 500;
5
+ const LEVELS = {
6
+ 1: "خیلی پایین",
7
+ 2: "پایین",
8
+ 3: "متوسط",
9
+ 4: "بالا",
10
+ 5: "خیلی بالا",
11
+ };
12
+ const COLORS = {
13
+ light: "#BAE6FD",
14
+ dark: "#0369A1",
15
+ accent: "#0284C7",
16
+ stroke: "#0284C7",
17
+ };
18
+ const percentageFromRatio = (ratio) => Math.round(Number(ratio) * 100);
19
+ const DOMAINS = [
20
+ { d: "n", fa: "روان‌آزرده‌گرایی", letter: "N" },
21
+ { d: "e", fa: "برون‌گرایی", letter: "E" },
22
+ { d: "o", fa: "گشودگی", letter: "O" },
23
+ { d: "a", fa: "موافق بودن", letter: "A" },
24
+ { d: "c", fa: "با وجدان بودن", letter: "C" },
25
+ ];
3
26
 
4
27
  class NEO9A extends Profile {
5
- // Number of pages
6
28
  static pages = 1;
7
29
 
8
- // Labels of the sample
9
- labels = {
10
- L0: { eng: "n", fr: "روان‌آزده‌گرایی", startColor:"#F9A8D4", endColor: "#DB2777", textColor:"#BE185D", bgColor: "#FDF2F8", steps: [15, 20, 25, 31, 48]},
11
- L1: { eng: "n_raw"},
12
-
13
- L2: { eng: "e", fr: "برون‌گرایی", startColor:"#67E8F9", endColor: "#0891B2", textColor:"#0E7490", bgColor: "#ECFEFF", steps: [22, 26, 29, 32, 48]},
14
- L3: { eng: "e_raw"},
15
-
16
- L4: { eng: "o", fr: "گشودگی", startColor:"#7DD3FC", endColor: "#0284C7", textColor:"#0369A1", bgColor: "#F0F9FF", steps: [23, 27, 29, 32, 48]},
17
- L5: { eng: "o_raw"},
18
-
19
- L6: { eng: "a", fr: "موافق‌بودن", startColor:"#A5B4FC", endColor: "#4F46E5", textColor:"#4338CA", bgColor: "#EEF2FF", steps: [27, 31, 33, 37, 48]},
20
- L7: { eng: "a_raw"},
30
+ static partials = {
31
+ NEO_main: "NEO_main.hbs",
32
+ };
21
33
 
22
- L8: { eng: "c", fr: "با وجدان‌بودن", startColor:"#C4B5FD", endColor: "#7C3AED", textColor:"#6D28D9", bgColor: "#F5F3FF", steps: [27, 32, 35, 39, 48]},
23
- L9: { eng: "c_raw"},
34
+ labels = {
35
+ N_raw: { eng: "n_raw", fa: "روان‌آزرده‌گرایی", letter: "N", max: DOMAIN_MAX },
36
+ N_score: { eng: "n_score" },
37
+ N_lvl: { eng: "n_level" },
38
+ E_raw: { eng: "e_raw", fa: "برون‌گرایی", letter: "E", max: DOMAIN_MAX },
39
+ E_score: { eng: "e_score" },
40
+ E_lvl: { eng: "e_level" },
41
+ O_raw: { eng: "o_raw", fa: "گشودگی", letter: "O", max: DOMAIN_MAX },
42
+ O_score: { eng: "o_score" },
43
+ O_lvl: { eng: "o_level" },
44
+ A_raw: { eng: "a_raw", fa: "موافق بودن", letter: "A", max: DOMAIN_MAX },
45
+ A_score: { eng: "a_score" },
46
+ A_lvl: { eng: "a_level" },
47
+ C_raw: { eng: "c_raw", fa: "با وجدان بودن", letter: "C", max: DOMAIN_MAX },
48
+ C_score: { eng: "c_score" },
49
+ C_lvl: { eng: "c_level" },
50
+ validity: { eng: "validity" },
24
51
  };
25
52
 
26
53
  profileSpec = {
27
- /* "sample" determines some important info about the sample and profile */
28
- /* Default prerequisites: 1. gender, 2. age, 3. education */
29
- /* "prerequisites" is synonym to "fields" in our program */
30
54
  sample: {
31
- name: "پرسشنامه شخصیت نئو ۶۰ سوالی (NEO-FFI-60)" /* Name of the sample */,
32
- multiProfile: false /* Whether the sample has multiple profiles or not */,
33
- questions: true /* Determines whether to get questions from inital dataset or not */,
34
- defaultFields: true /* Determines whether to have default prerequisites in the profile or not */,
35
- fields: [] /* In case you want to get some additional fields and show in the profile */,
55
+ name: "پرسشنامه شخصیت نئو ۶۰ سوالی (NEO-FFI-60)",
56
+ multiProfile: false,
57
+ questions: true,
58
+ defaultFields: true,
59
+ fields: [],
36
60
  },
37
- /* "profile" determines the dimensions of the drawn profile (to be used in svg tag viewbox) */
38
- /* calculating its dimensions carefully is of great importance */
39
61
  profile: {
40
62
  get dimensions() {
41
63
  return {
42
- width: 866 + 2 * this.padding.x,
43
- height: 649 + 2 * this.padding.y,
64
+ width: 800 + 2 * this.padding.x,
65
+ height: 674 + 2 * this.padding.y,
44
66
  };
45
67
  },
46
68
  padding: {
47
- x: 18,
48
- y: 32,
69
+ x: 71.5,
70
+ y: 40,
49
71
  },
50
72
  },
51
- /* "labels" part which has to be provided for each profile */
52
73
  labels: Object.values(this.labels),
53
74
  };
54
75
 
@@ -56,36 +77,46 @@ class NEO9A extends Profile {
56
77
  super();
57
78
  this._init(dataset, options, config);
58
79
  }
80
+
59
81
  _calcContext() {
60
- const {
61
- dataset,
62
- } = this;
63
- const factors = []
64
- for(let i = 0; i < 10; i+=2){
65
- const factor = {
66
- ...dataset.score[i],
67
- mark: dataset.score[i+1].mark,
68
- raw: dataset.score[i].mark - 1,
69
- label: {
70
- ...dataset.score[i].label,
71
- eng: dataset.score[i].label.eng.toUpperCase(),
72
- stepTitle: [],
73
- fill: `url(#bar-${dataset.score[i].label.eng.toUpperCase()})`
74
- }
75
- }
76
- for(let j = 0; j < factor.label.steps.length; j++){
77
- const start = j === 0 ? 0 : factor.label.steps[j-1]
78
- const step = factor.label.steps[j]
79
- const middle = (((step - start) * 14) / 2) + (start * 14)
80
- factor.label.stepTitle.push(middle)
81
- }
82
- factors.push(factor)
83
- }
84
- const steps = ['خیلی پایین', 'پایین', 'وسط', 'بالا', 'خیلی بالا']
85
- return [{factors, steps}];
86
- }
87
- }
82
+ const { dataset } = this;
83
+ const mark = (eng) => dataset.score.find((item) => item.label.eng === eng)?.mark ?? 0;
84
+
85
+ const items = DOMAINS.map(({ d, fa, letter }) => {
86
+ const raw = mark(`${d}_raw`);
87
+ const p = Number(mark(`${d}_score`));
88
+ const level = mark(`${d}_level`);
89
+ const percentage = percentageFromRatio(p);
90
+ return {
91
+ fa,
92
+ letter,
93
+ max: DOMAIN_MAX,
94
+ raw,
95
+ mark: raw,
96
+ p,
97
+ barW: (BAR_WIDTH * percentage) / 100,
98
+ percentage,
99
+ levelText: LEVELS[level] ?? "-",
100
+ };
101
+ });
88
102
 
103
+ const invalidValidity = mark("validity") === 0;
104
+ const redErrors = invalidValidity
105
+ ? [{ text: "پاسخ سؤالات اعتبار", length: 105, start: 0, last: true }]
106
+ : [];
89
107
 
108
+ return [
109
+ {
110
+ page: 1,
111
+ titleAppend: "",
112
+ theme: "sky",
113
+ colors: COLORS,
114
+ items,
115
+ redErrors,
116
+ showRed: redErrors.length > 0,
117
+ },
118
+ ];
119
+ }
120
+ }
90
121
 
91
122
  module.exports = NEO9A;
@@ -0,0 +1,279 @@
1
+ const { Profile } = require("../Profile");
2
+
3
+ const DOMAIN_MAX = 192;
4
+ const FACET_MAX = 32;
5
+ const INSIDE_THRESHOLD = 24;
6
+
7
+ const LEVELS = ["", "بسیار پایین", "پایین", "طبیعی", "بالا", "بسیار بالا"];
8
+
9
+ const DOMAINS = [
10
+ { key: "n", fa: "بی‌ثباتی هیجانی", letter: "N" },
11
+ { key: "e", fa: "برون‌گرایی", letter: "E" },
12
+ { key: "o", fa: "باز بودن به تجربه", letter: "O" },
13
+ { key: "a", fa: "توافق با دیگران", letter: "A" },
14
+ { key: "c", fa: "وجدانی بودن", letter: "C" },
15
+ ];
16
+
17
+ const FACET_NAMES = {
18
+ n: ["اضطراب", "خشم و کینه‌ورزی", "افسردگی", "شرم", "تکانش‌وری", "آسیب‌پذیری از استرس"],
19
+ e: ["صمیمیت", "جمع‌گرایی", "قاطعیت", "جنب‌وجوش", "هیجان‌خواهی", "هیجان‌‌های مثبت"],
20
+ o: ["تخیل", "زیبایی شناسی", "عواطف", "کنش‌ها", "دیدگاه‌ها", "ارزش‌ها"],
21
+ a: ["اعتماد", "سادگی", "نوع‌دوستی", "تبعیت", "تواضع", "نرمش در برابر دیگران"],
22
+ c: ["شایستگی", "نظم", "وظیفه‌شناسی", "تلاش برای موفقیت", "نظم درونی", "انعطاف‌ناپذیری"],
23
+ };
24
+
25
+ const COLOR_THEMES = {
26
+ female: {
27
+ light: "#E9D5FF",
28
+ dark: "#7E22CE",
29
+ accent: "#9333EA",
30
+ stroke: "#C084FC",
31
+ },
32
+ male: {
33
+ light: "#BFDBFE",
34
+ dark: "#1D4ED8",
35
+ accent: "#2563EB",
36
+ stroke: "#60A5FA",
37
+ },
38
+ };
39
+
40
+ const FACTOR_LABELS = {};
41
+ for (const { key } of DOMAINS) {
42
+ for (const suffix of ["raw", "score", "level"]) {
43
+ FACTOR_LABELS[`${key}_${suffix}`] = { eng: `${key}_${suffix}` };
44
+ }
45
+
46
+ for (let index = 1; index <= 6; index += 1) {
47
+ for (const suffix of ["raw", "score", "level"]) {
48
+ FACTOR_LABELS[`${key}${index}_${suffix}`] = {
49
+ eng: `${key}${index}_${suffix}`,
50
+ };
51
+ }
52
+ }
53
+ }
54
+
55
+ function clamp(value, min, max) {
56
+ const number = Number(value);
57
+ if (!Number.isFinite(number)) return min;
58
+ return Math.min(max, Math.max(min, number));
59
+ }
60
+
61
+ function layoutIndicators(indicators) {
62
+ let start = 0;
63
+
64
+ return indicators.map((indicator, index) => {
65
+ const positioned = {
66
+ ...indicator,
67
+ start,
68
+ last: index === indicators.length - 1,
69
+ };
70
+ start += indicator.length + 54;
71
+ return positioned;
72
+ });
73
+ }
74
+
75
+ class NEO9Q extends Profile {
76
+ static pages = 3;
77
+
78
+ static partials = {
79
+ NEO_main: "NEO_main.hbs",
80
+ NEO_long_facets: "NEO_long_facets.hbs",
81
+ };
82
+
83
+ labels = {
84
+ ...FACTOR_LABELS,
85
+ validity: { eng: "validity" },
86
+ options_3_count: { eng: "options_3_count" },
87
+ acquiescence: { eng: "acquiescence" },
88
+ nay_saying: { eng: "nay_saying" },
89
+ random_responding: { eng: "random_responding" },
90
+ };
91
+
92
+ profileSpec = {
93
+ sample: {
94
+ name: "پرسشنامه شخصیت نئو فرم بلند (حق‌شناس)",
95
+ multiProfile: false,
96
+ questions: false,
97
+ defaultFields: true,
98
+ fields: [],
99
+ },
100
+ profile: {
101
+ get dimensions() {
102
+ const [page1, page2, page3] = this.padding;
103
+ return [
104
+ {
105
+ width: 800 + 2 * page1.x,
106
+ height: 674 + 2 * page1.y,
107
+ },
108
+ {
109
+ width: 811 + 2 * page2.x,
110
+ height: 668 + 2 * page2.y,
111
+ },
112
+ {
113
+ width: 811 + 2 * page3.x,
114
+ height: 464 + 2 * page3.y,
115
+ },
116
+ ];
117
+ },
118
+ padding: [
119
+ { x: 71.5, y: 40 },
120
+ { x: 66, y: 43 },
121
+ { x: 66, y: 145 },
122
+ ],
123
+ },
124
+ labels: Object.values(this.labels),
125
+ };
126
+
127
+ constructor(dataset, options, config = {}) {
128
+ super();
129
+ this._init(dataset, options, config);
130
+ }
131
+
132
+ _calcContext() {
133
+ const { dataset } = this;
134
+ const scoreByKey = new Map(dataset.score.map((item) => [item.label.eng, item]));
135
+
136
+ // Dataset._extractData drops a numeric zero, so every read deliberately
137
+ // restores it with ?? 0 before converting it to a drawing value.
138
+ const mark = (eng) => scoreByKey.get(eng)?.mark ?? 0;
139
+ const number = (eng) => {
140
+ const value = Number(mark(eng));
141
+ return Number.isFinite(value) ? value : 0;
142
+ };
143
+
144
+ const genderField = (dataset.info.fields ?? []).find((field) => field?.eng === "gender");
145
+ const isFemale = Number(genderField?.user_answered) === 1;
146
+ const gender = isFemale ? "female" : "male";
147
+ const colors = COLOR_THEMES[gender];
148
+ const sharedContext = {
149
+ gender,
150
+ isFemale,
151
+ theme: gender,
152
+ colors,
153
+ };
154
+
155
+ const items = DOMAINS.map(({ key, fa, letter }) => {
156
+ const raw = number(`${key}_raw`);
157
+ const score = number(`${key}_score`);
158
+ const level = number(`${key}_level`);
159
+ const barW = clamp(8 * score - 140, 0, 500);
160
+
161
+ return {
162
+ key,
163
+ fa,
164
+ letter,
165
+ raw,
166
+ mark: raw,
167
+ max: DOMAIN_MAX,
168
+ score,
169
+ tScore: score,
170
+ percentage: score,
171
+ barW,
172
+ p: barW / 500,
173
+ inside: score >= INSIDE_THRESHOLD,
174
+ level,
175
+ levelText: LEVELS[level] ?? "-",
176
+ };
177
+ });
178
+
179
+ const redErrors = [];
180
+ if (number("validity") === 0) {
181
+ redErrors.push({ text: "پاسخ سؤالات اعتبار", length: 108 });
182
+ }
183
+ if (number("options_3_count") >= 41) {
184
+ redErrors.push({ text: "تعداد پاسخ‌های «نظری ندارم»", length: 170 });
185
+ }
186
+
187
+ const yellowErrors = [];
188
+ if (number("acquiescence") === 1) {
189
+ yellowErrors.push({ text: "احتمال تمایل به پاسخ مثبت", length: 155 });
190
+ }
191
+ if (number("nay_saying") === 1) {
192
+ yellowErrors.push({ text: "احتمال تمایل به پاسخ منفی", length: 143 });
193
+ }
194
+ if (number("random_responding") === 1) {
195
+ yellowErrors.push({ text: "احتمال پاسخ تصادفی", length: 105 });
196
+ }
197
+
198
+ const blocks = DOMAINS.map(({ key, fa, letter }, domainIndex) => {
199
+ const raw = number(`${key}_raw`);
200
+ const score = number(`${key}_score`);
201
+ const level = number(`${key}_level`);
202
+ const barW = clamp(8 * score - 140, 0, 500);
203
+
204
+ const facets = FACET_NAMES[key].map((facetName, facetIndex) => {
205
+ const factorKey = `${key}${facetIndex + 1}`;
206
+ const facetRaw = number(`${factorKey}_raw`);
207
+ const facetScore = number(`${factorKey}_score`);
208
+ const facetLevel = number(`${factorKey}_level`);
209
+ const facetBarW = clamp(6 * facetScore - 100, 0, 380);
210
+
211
+ return {
212
+ key: factorKey,
213
+ fa: facetName,
214
+ raw: facetRaw,
215
+ mark: facetRaw,
216
+ max: FACET_MAX,
217
+ score: facetScore,
218
+ tScore: facetScore,
219
+ percentage: facetScore,
220
+ barW: facetBarW,
221
+ p: facetBarW / 380,
222
+ inside: facetScore >= INSIDE_THRESHOLD,
223
+ level: facetLevel,
224
+ levelText: LEVELS[facetLevel] ?? "-",
225
+ };
226
+ });
227
+
228
+ return {
229
+ key,
230
+ fa,
231
+ letter,
232
+ raw,
233
+ mark: raw,
234
+ max: DOMAIN_MAX,
235
+ score,
236
+ tScore: score,
237
+ percentage: score,
238
+ barW,
239
+ p: barW / 500,
240
+ inside: score >= INSIDE_THRESHOLD,
241
+ level,
242
+ levelText: LEVELS[level] ?? "-",
243
+ isPrimary: domainIndex % 2 === 0,
244
+ isBlue: domainIndex % 2 === 0,
245
+ facetOffsetY: key === "c" ? -1 : 0,
246
+ facets,
247
+ };
248
+ });
249
+
250
+ return [
251
+ {
252
+ ...sharedContext,
253
+ page: 1,
254
+ titleAppend: "",
255
+ items,
256
+ redErrors: layoutIndicators(redErrors),
257
+ yellowErrors: layoutIndicators(yellowErrors),
258
+ showRed: redErrors.length > 0,
259
+ showYellow: yellowErrors.length > 0,
260
+ },
261
+ {
262
+ ...sharedContext,
263
+ page: 2,
264
+ titleAppend: " - 2",
265
+ blocks: blocks.slice(0, 3),
266
+ gridBottom: 664.5,
267
+ },
268
+ {
269
+ ...sharedContext,
270
+ page: 3,
271
+ titleAppend: " - 3",
272
+ blocks: blocks.slice(3, 5),
273
+ gridBottom: 460.5,
274
+ },
275
+ ];
276
+ }
277
+ }
278
+
279
+ module.exports = NEO9Q;
@@ -0,0 +1,215 @@
1
+ const { Profile } = require("../Profile");
2
+
3
+ const DOMAIN_MAX = 48;
4
+ const MAIN_BAR_WIDTH = 500;
5
+ const DETAIL_BAR_WIDTH = 300;
6
+ const COLORS = {
7
+ light: "#BFDBFE",
8
+ dark: "#1D4ED8",
9
+ accent: "#2563EB",
10
+ stroke: "#60A5FA",
11
+ };
12
+ const percentageFromRatio = (ratio) => Math.round(Number(ratio) * 100);
13
+
14
+ const DOMAINS = [
15
+ {
16
+ d: "n",
17
+ fa: "بی‌ثباتی هیجانی",
18
+ letter: "N",
19
+ facets: [
20
+ { d: "n1", fa: "عاطفه منفی", max: 20 },
21
+ { d: "n2", fa: "سرزنش خویش", max: 28 },
22
+ ],
23
+ },
24
+ {
25
+ d: "e",
26
+ fa: "برون‌گرایی",
27
+ letter: "E",
28
+ facets: [
29
+ { d: "e1", fa: "عاطفه مثبت", max: 16 },
30
+ { d: "e2", fa: "جامعه‌پذیری", max: 16 },
31
+ { d: "e3", fa: "فعالیت", max: 16 },
32
+ ],
33
+ },
34
+ {
35
+ d: "o",
36
+ fa: "باز بودن به تجربه",
37
+ letter: "O",
38
+ facets: [
39
+ { d: "o1", fa: "علایق زیبایی‌شناختی", max: 12 },
40
+ { d: "o2", fa: "علایق ذهنی", max: 12 },
41
+ { d: "o3", fa: "غیرسنتی", max: 16 },
42
+ ],
43
+ },
44
+ {
45
+ d: "a",
46
+ fa: "توافق با دیگران",
47
+ letter: "A",
48
+ facets: [
49
+ { d: "a1", fa: "گرایش موافق (نامتضاد)", max: 32 },
50
+ { d: "a2", fa: "گرایش عام‌المنفعه", max: 16 },
51
+ ],
52
+ },
53
+ {
54
+ d: "c",
55
+ fa: "وجدانی بودن",
56
+ letter: "C",
57
+ facets: [
58
+ { d: "c1", fa: "نظم‌پذیری", max: 20 },
59
+ { d: "c2", fa: "تلاش هدفمند", max: 12 },
60
+ { d: "c3", fa: "قابل اتکا بودن", max: 16 },
61
+ ],
62
+ },
63
+ ];
64
+
65
+ const SCORE_LABELS = {};
66
+ for (const domain of DOMAINS) {
67
+ SCORE_LABELS[`${domain.d}_raw`] = {
68
+ eng: `${domain.d}_raw`,
69
+ fa: domain.fa,
70
+ letter: domain.letter,
71
+ max: DOMAIN_MAX,
72
+ };
73
+ SCORE_LABELS[`${domain.d}_score`] = { eng: `${domain.d}_score` };
74
+
75
+ for (const facet of domain.facets) {
76
+ SCORE_LABELS[`${facet.d}_raw`] = {
77
+ eng: `${facet.d}_raw`,
78
+ fa: facet.fa,
79
+ max: facet.max,
80
+ };
81
+ SCORE_LABELS[`${facet.d}_score`] = { eng: `${facet.d}_score` };
82
+ }
83
+ }
84
+
85
+ const layoutErrors = (errors) => {
86
+ let start = 0;
87
+ return errors.map((error, index) => {
88
+ const item = {
89
+ ...error,
90
+ start,
91
+ last: index === errors.length - 1,
92
+ };
93
+ start += error.length + 54;
94
+ return item;
95
+ });
96
+ };
97
+
98
+ class NEO9V extends Profile {
99
+ static pages = 2;
100
+
101
+ static partials = {
102
+ NEO_main: "NEO_main.hbs",
103
+ NEO_short_facets: "NEO_short_facets.hbs",
104
+ };
105
+
106
+ labels = {
107
+ ...SCORE_LABELS,
108
+ validity: { eng: "validity" },
109
+ options_3_count: { eng: "options_3_count" },
110
+ };
111
+
112
+ profileSpec = {
113
+ sample: {
114
+ name: "آزمون شخصیت نئو فرم کوتاه (PI-R)",
115
+ multiProfile: false,
116
+ questions: false,
117
+ defaultFields: true,
118
+ fields: [],
119
+ },
120
+ profile: {
121
+ get dimensions() {
122
+ const [page1, page2] = this.padding;
123
+ return [
124
+ {
125
+ width: 801 + 2 * page1.x,
126
+ height: 674 + 2 * page1.y,
127
+ },
128
+ {
129
+ width: 760 + 2 * page2.x,
130
+ height: 662 + 2 * page2.y,
131
+ },
132
+ ];
133
+ },
134
+ padding: [
135
+ { x: 71, y: 40 },
136
+ { x: 91.5, y: 46 },
137
+ ],
138
+ },
139
+ labels: Object.values(this.labels),
140
+ };
141
+
142
+ constructor(dataset, options, config = {}) {
143
+ super();
144
+ this._init(dataset, options, config);
145
+ }
146
+
147
+ _calcContext() {
148
+ const { dataset } = this;
149
+ const mark = (eng) => dataset.score.find((item) => item.label.eng === eng)?.mark ?? 0;
150
+
151
+ const blocks = DOMAINS.map((domain, index) => {
152
+ const raw = mark(`${domain.d}_raw`);
153
+ const p = Number(mark(`${domain.d}_score`));
154
+ const percentage = percentageFromRatio(p);
155
+ const facets = domain.facets.map((facet) => {
156
+ const facetRaw = mark(`${facet.d}_raw`);
157
+ const facetP = Number(mark(`${facet.d}_score`));
158
+ const facetPercentage = percentageFromRatio(facetP);
159
+ return {
160
+ fa: facet.fa,
161
+ max: facet.max,
162
+ raw: facetRaw,
163
+ mark: facetRaw,
164
+ p: facetP,
165
+ barW: (DETAIL_BAR_WIDTH * facetPercentage) / 100,
166
+ percentage: facetPercentage,
167
+ };
168
+ });
169
+
170
+ return {
171
+ fa: domain.fa,
172
+ letter: domain.letter,
173
+ max: DOMAIN_MAX,
174
+ raw,
175
+ mark: raw,
176
+ p,
177
+ barW: (MAIN_BAR_WIDTH * percentage) / 100,
178
+ percentage,
179
+ isBlue: index % 2 === 0,
180
+ facets,
181
+ };
182
+ });
183
+
184
+ const invalidErrors = [];
185
+ if (mark("validity") === 0) {
186
+ invalidErrors.push({ text: "پاسخ سؤالات اعتبار", length: 108 });
187
+ }
188
+ if (Number(mark("options_3_count")) >= 10) {
189
+ invalidErrors.push({ text: "تعداد پاسخ‌های «نظری ندارم»", length: 170 });
190
+ }
191
+ const redErrors = layoutErrors(invalidErrors);
192
+
193
+ return [
194
+ {
195
+ page: 1,
196
+ titleAppend: "",
197
+ theme: "blue",
198
+ colors: COLORS,
199
+ items: blocks.map(({ facets, isBlue, ...item }) => item),
200
+ redErrors,
201
+ showRed: redErrors.length > 0,
202
+ },
203
+ {
204
+ page: 2,
205
+ titleAppend: " - 2",
206
+ theme: "blue",
207
+ colors: COLORS,
208
+ blocks,
209
+ gridBottom: 658.5,
210
+ },
211
+ ];
212
+ }
213
+ }
214
+
215
+ module.exports = NEO9V;
@@ -23,7 +23,7 @@ class OBQ4493 extends Profile {
23
23
  multiProfile: false /* Whether the sample has multiple profiles or not */,
24
24
  questions: false /* Determines whether to get questions from inital dataset or not */,
25
25
  defaultFields: true /* Determines whether to have default prerequisites in the profile or not */,
26
- fields: ["marital_status"] /* In case you want to get some additional fields and show in the profile */,
26
+ fields: [] /* In case you want to get some additional fields and show in the profile */,
27
27
  },
28
28
  /* "profile" determines the dimensions of the drawn profile (to be used in svg tag viewbox) */
29
29
  /* calculating its dimensions carefully is of great importance */
@@ -26,7 +26,7 @@ class PIES93 extends Profile {
26
26
  multiProfile: false /* Whether the sample has multiple profiles or not */,
27
27
  questions: false /* Determines whether to get questions from inital dataset or not */,
28
28
  defaultFields: true /* Determines whether to have default prerequisites in the profile or not */,
29
- fields: ["marital_status"] /* In case you want to get some additional fields and show in the profile */,
29
+ fields: [] /* In case you want to get some additional fields and show in the profile */,
30
30
  },
31
31
  /* "profile" determines the dimensions of the drawn profile (to be used in svg tag viewbox) */
32
32
  /* calculating its dimensions carefully is of great importance */