@baravak/risloo-profile-cli 4.46.3 → 4.75.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 (101) hide show
  1. package/.agents/skills/create-profile/SKILL.md +14 -0
  2. package/.claude/settings.local.json +1 -3
  3. package/.claude/skills/create-profile/SKILL.md +203 -0
  4. package/AGENTS.md +413 -0
  5. package/CLAUDE.md +2 -185
  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/BSSI93.json +392 -0
  13. package/src/publish/json/profiles/FRHPT93.json +2 -2
  14. package/src/publish/json/profiles/ISKS93.json +288 -0
  15. package/src/publish/json/profiles/MCMI9A.json +1540 -0
  16. package/src/publish/json/profiles/NEO93.json +3858 -0
  17. package/src/publish/json/profiles/NEO9A.json +1159 -1
  18. package/src/publish/json/profiles/NEO9Q.json +3856 -0
  19. package/src/publish/json/profiles/NEO9V.json +1157 -0
  20. package/src/publish/json/profiles/RIASEC9A.json +2775 -0
  21. package/src/publish/json/profiles/RSI93.json +359 -0
  22. package/src/samples/AEQ93.js +1 -1
  23. package/src/samples/AMS93.js +1 -1
  24. package/src/samples/AMS9A.js +3 -1
  25. package/src/samples/BAOMEIS93.js +1 -1
  26. package/src/samples/BEQI93.js +1 -1
  27. package/src/samples/BSCT93.js +5 -0
  28. package/src/samples/BSSI93.js +93 -0
  29. package/src/samples/CAATS93.js +1 -1
  30. package/src/samples/CERQ93.js +1 -1
  31. package/src/samples/CRAAS93.js +1 -1
  32. package/src/samples/DSWLS93.js +1 -1
  33. package/src/samples/EMSS93.js +1 -1
  34. package/src/samples/EPQ9A.js +6 -1
  35. package/src/samples/FACES93.js +6 -0
  36. package/src/samples/FRHPT93.js +41 -13
  37. package/src/samples/GMIT93.js +1 -1
  38. package/src/samples/HPL93.js +1 -1
  39. package/src/samples/ISKS93.js +62 -0
  40. package/src/samples/IUS93.js +1 -1
  41. package/src/samples/JCSI93.js +1 -1
  42. package/src/samples/JPFQ93.js +5 -0
  43. package/src/samples/KJGI93.js +1 -1
  44. package/src/samples/LMIQ93.js +1 -1
  45. package/src/samples/MCMI9A.js +530 -0
  46. package/src/samples/MMFAD93.js +1 -1
  47. package/src/samples/MOCI93.js +1 -1
  48. package/src/samples/NEO93.js +279 -0
  49. package/src/samples/NEO9A.js +93 -60
  50. package/src/samples/NEO9Q.js +281 -0
  51. package/src/samples/NEO9V.js +218 -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/MCMI9A_facets_partial.hbs +96 -0
  67. package/views/profiles/MCMI9A_main_partial.hbs +262 -0
  68. package/views/profiles/MCMI9A_noteworthy_partial.hbs +66 -0
  69. package/views/profiles/MCMI9A_vertical_label_partial.hbs +8 -0
  70. package/views/profiles/NEO_long_facets.hbs +125 -0
  71. package/views/profiles/NEO_main.hbs +152 -0
  72. package/views/profiles/NEO_short_facets.hbs +82 -0
  73. package/views/profiles/samples/BSCT93.hbs +17 -17
  74. package/views/profiles/samples/BSSI93.hbs +102 -0
  75. package/views/profiles/samples/FRHPT93_1.hbs +568 -87
  76. package/views/profiles/samples/FRHPT93_2.hbs +90 -76
  77. package/views/profiles/samples/FRHPT93_3.hbs +2 -2
  78. package/views/profiles/samples/FRHPT93_4.hbs +2 -2
  79. package/views/profiles/samples/FRHPT93_5.hbs +103 -0
  80. package/views/profiles/samples/FRHPT9A.hbs +7 -7
  81. package/views/profiles/samples/ISKS93.hbs +38 -0
  82. package/views/profiles/samples/MCMI9A_1.hbs +3 -0
  83. package/views/profiles/samples/MCMI9A_2.hbs +3 -0
  84. package/views/profiles/samples/MCMI9A_3.hbs +3 -0
  85. package/views/profiles/samples/MCMI9A_4.hbs +3 -0
  86. package/views/profiles/samples/NEO93_1.hbs +14 -0
  87. package/views/profiles/samples/NEO93_2.hbs +15 -0
  88. package/views/profiles/samples/NEO93_3.hbs +15 -0
  89. package/views/profiles/samples/NEO9A.hbs +14 -29
  90. package/views/profiles/samples/NEO9Q_1.hbs +13 -0
  91. package/views/profiles/samples/NEO9Q_2.hbs +14 -0
  92. package/views/profiles/samples/NEO9Q_3.hbs +14 -0
  93. package/views/profiles/samples/NEO9V_1.hbs +13 -0
  94. package/views/profiles/samples/NEO9V_2.hbs +3 -0
  95. package/views/profiles/samples/RIASEC9A_1.hbs +38 -0
  96. package/views/profiles/samples/RIASEC9A_2.hbs +67 -0
  97. package/views/profiles/samples/RIASEC9A_3.hbs +93 -0
  98. package/views/profiles/samples/RSI93.hbs +101 -0
  99. package/a.json +0 -4805
  100. package/src/publish/json/profiles/BSCT93_t1.json +0 -512
  101. package/src/publish/json/profiles/BSCT93_t2.json +0 -512
@@ -0,0 +1,279 @@
1
+ const { Profile } = require("../Profile");
2
+
3
+ const DOMAIN_MAX = 192;
4
+ const FACET_MAX = 32;
5
+ const INSIDE_THRESHOLD = 25;
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 NEO93 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: 662 + 2 * page2.y,
111
+ },
112
+ {
113
+ width: 811 + 2 * page3.x,
114
+ height: 458 + 2 * page3.y,
115
+ },
116
+ ];
117
+ },
118
+ padding: [
119
+ { x: 71.5, y: 40 },
120
+ { x: 66, y: 46 },
121
+ { x: 66, y: 148 },
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
+ const ratio = (eng) => clamp(number(eng), 0, 1);
144
+
145
+ const genderField = (dataset.info.fields ?? []).find((field) => field?.eng === "gender");
146
+ const isFemale = Number(genderField?.user_answered) === 1;
147
+ const gender = isFemale ? "female" : "male";
148
+ const colors = COLOR_THEMES[gender];
149
+ const versionTitle = isFemale ? "نسخه بانوان" : "نسخه آقایان";
150
+ const titleAppend = (page) => ` - ${versionTitle} - صفحه ${page}`;
151
+ const sharedContext = {
152
+ gender,
153
+ isFemale,
154
+ theme: gender,
155
+ colors,
156
+ };
157
+
158
+ const items = DOMAINS.map(({ key, fa, letter }) => {
159
+ const raw = number(`${key}_raw`);
160
+ const score = ratio(`${key}_score`);
161
+ const level = number(`${key}_level`);
162
+ const percentage = Math.round(score * 100);
163
+
164
+ return {
165
+ key,
166
+ fa,
167
+ letter,
168
+ raw,
169
+ mark: raw,
170
+ max: DOMAIN_MAX,
171
+ score,
172
+ p: score,
173
+ percentage,
174
+ barW: (500 * percentage) / 100,
175
+ inside: percentage >= INSIDE_THRESHOLD,
176
+ level,
177
+ levelText: LEVELS[level] ?? "-",
178
+ };
179
+ });
180
+
181
+ const redErrors = [];
182
+ if (number("validity") === 0) {
183
+ redErrors.push({ text: "پاسخ سؤالات اعتبار", length: 108 });
184
+ }
185
+ if (number("options_3_count") >= 41) {
186
+ redErrors.push({ text: "تعداد پاسخ‌های «نظری ندارم»", length: 170 });
187
+ }
188
+
189
+ const yellowErrors = [];
190
+ if (number("acquiescence") === 1) {
191
+ yellowErrors.push({ text: "احتمال تمایل به پاسخ مثبت", length: 155 });
192
+ }
193
+ if (number("nay_saying") === 1) {
194
+ yellowErrors.push({ text: "احتمال تمایل به پاسخ منفی", length: 143 });
195
+ }
196
+ if (number("random_responding") === 1) {
197
+ yellowErrors.push({ text: "احتمال پاسخ تصادفی", length: 105 });
198
+ }
199
+
200
+ const blocks = DOMAINS.map(({ key, fa, letter }, domainIndex) => {
201
+ const raw = number(`${key}_raw`);
202
+ const score = ratio(`${key}_score`);
203
+ const level = number(`${key}_level`);
204
+ const percentage = Math.round(score * 100);
205
+
206
+ const facets = FACET_NAMES[key].map((facetName, facetIndex) => {
207
+ const factorKey = `${key}${facetIndex + 1}`;
208
+ const facetRaw = number(`${factorKey}_raw`);
209
+ const facetScore = ratio(`${factorKey}_score`);
210
+ const facetLevel = number(`${factorKey}_level`);
211
+ const facetPercentage = Math.round(facetScore * 100);
212
+
213
+ return {
214
+ key: factorKey,
215
+ fa: facetName,
216
+ raw: facetRaw,
217
+ mark: facetRaw,
218
+ max: FACET_MAX,
219
+ score: facetScore,
220
+ p: facetScore,
221
+ percentage: facetPercentage,
222
+ barW: (300 * facetPercentage) / 100,
223
+ inside: facetPercentage >= INSIDE_THRESHOLD,
224
+ level: facetLevel,
225
+ levelText: LEVELS[facetLevel] ?? "-",
226
+ };
227
+ });
228
+
229
+ return {
230
+ key,
231
+ fa,
232
+ letter,
233
+ raw,
234
+ mark: raw,
235
+ max: DOMAIN_MAX,
236
+ score,
237
+ p: score,
238
+ percentage,
239
+ barW: (500 * percentage) / 100,
240
+ inside: percentage >= INSIDE_THRESHOLD,
241
+ level,
242
+ levelText: LEVELS[level] ?? "-",
243
+ isPrimary: domainIndex % 2 === 0,
244
+ isBlue: domainIndex % 2 === 0,
245
+ facetOffsetY: 0,
246
+ facets,
247
+ };
248
+ });
249
+
250
+ return [
251
+ {
252
+ ...sharedContext,
253
+ page: 1,
254
+ titleAppend: titleAppend(1),
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: titleAppend(2),
265
+ blocks: blocks.slice(0, 3),
266
+ gridBottom: 658.5,
267
+ },
268
+ {
269
+ ...sharedContext,
270
+ page: 3,
271
+ titleAppend: titleAppend(3),
272
+ blocks: blocks.slice(3, 5),
273
+ gridBottom: 454.5,
274
+ },
275
+ ];
276
+ }
277
+ }
278
+
279
+ module.exports = NEO93;
@@ -1,54 +1,76 @@
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 INSIDE_THRESHOLD = 25;
6
+ const LEVELS = {
7
+ 1: "خیلی پایین",
8
+ 2: "پایین",
9
+ 3: "متوسط",
10
+ 4: "بالا",
11
+ 5: "خیلی بالا",
12
+ };
13
+ const COLORS = {
14
+ light: "#BAE6FD",
15
+ dark: "#0369A1",
16
+ accent: "#0284C7",
17
+ stroke: "#0284C7",
18
+ };
19
+ const percentageFromRatio = (ratio) => Math.round(Number(ratio) * 100);
20
+ const DOMAINS = [
21
+ { d: "n", fa: "روان‌آزرده‌گرایی", letter: "N" },
22
+ { d: "e", fa: "برون‌گرایی", letter: "E" },
23
+ { d: "o", fa: "گشودگی", letter: "O" },
24
+ { d: "a", fa: "موافق بودن", letter: "A" },
25
+ { d: "c", fa: "با وجدان بودن", letter: "C" },
26
+ ];
3
27
 
4
28
  class NEO9A extends Profile {
5
- // Number of pages
6
29
  static pages = 1;
7
30
 
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"},
31
+ static partials = {
32
+ NEO_main: "NEO_main.hbs",
33
+ };
21
34
 
22
- L8: { eng: "c", fr: "با وجدان‌بودن", startColor:"#C4B5FD", endColor: "#7C3AED", textColor:"#6D28D9", bgColor: "#F5F3FF", steps: [27, 32, 35, 39, 48]},
23
- L9: { eng: "c_raw"},
35
+ labels = {
36
+ N_raw: { eng: "n_raw", fa: "روان‌آزرده‌گرایی", letter: "N", max: DOMAIN_MAX },
37
+ N_score: { eng: "n_score" },
38
+ N_lvl: { eng: "n_level" },
39
+ E_raw: { eng: "e_raw", fa: "برون‌گرایی", letter: "E", max: DOMAIN_MAX },
40
+ E_score: { eng: "e_score" },
41
+ E_lvl: { eng: "e_level" },
42
+ O_raw: { eng: "o_raw", fa: "گشودگی", letter: "O", max: DOMAIN_MAX },
43
+ O_score: { eng: "o_score" },
44
+ O_lvl: { eng: "o_level" },
45
+ A_raw: { eng: "a_raw", fa: "موافق بودن", letter: "A", max: DOMAIN_MAX },
46
+ A_score: { eng: "a_score" },
47
+ A_lvl: { eng: "a_level" },
48
+ C_raw: { eng: "c_raw", fa: "با وجدان بودن", letter: "C", max: DOMAIN_MAX },
49
+ C_score: { eng: "c_score" },
50
+ C_lvl: { eng: "c_level" },
51
+ validity: { eng: "validity" },
24
52
  };
25
53
 
26
54
  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
55
  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 */,
56
+ name: "پرسشنامه شخصیت نئو ۶۰ سوالی (NEO-FFI-60)",
57
+ multiProfile: false,
58
+ questions: true,
59
+ defaultFields: true,
60
+ fields: [],
36
61
  },
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
62
  profile: {
40
63
  get dimensions() {
41
64
  return {
42
- width: 866 + 2 * this.padding.x,
43
- height: 649 + 2 * this.padding.y,
65
+ width: 800 + 2 * this.padding.x,
66
+ height: 674 + 2 * this.padding.y,
44
67
  };
45
68
  },
46
69
  padding: {
47
- x: 18,
48
- y: 32,
70
+ x: 71.5,
71
+ y: 40,
49
72
  },
50
73
  },
51
- /* "labels" part which has to be provided for each profile */
52
74
  labels: Object.values(this.labels),
53
75
  };
54
76
 
@@ -56,36 +78,47 @@ class NEO9A extends Profile {
56
78
  super();
57
79
  this._init(dataset, options, config);
58
80
  }
81
+
59
82
  _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
- }
83
+ const { dataset } = this;
84
+ const mark = (eng) => dataset.score.find((item) => item.label.eng === eng)?.mark ?? 0;
85
+
86
+ const items = DOMAINS.map(({ d, fa, letter }) => {
87
+ const raw = mark(`${d}_raw`);
88
+ const p = Number(mark(`${d}_score`));
89
+ const level = mark(`${d}_level`);
90
+ const percentage = percentageFromRatio(p);
91
+ return {
92
+ fa,
93
+ letter,
94
+ max: DOMAIN_MAX,
95
+ raw,
96
+ mark: raw,
97
+ p,
98
+ barW: (BAR_WIDTH * percentage) / 100,
99
+ percentage,
100
+ inside: percentage >= INSIDE_THRESHOLD,
101
+ levelText: LEVELS[level] ?? "-",
102
+ };
103
+ });
88
104
 
105
+ const invalidValidity = mark("validity") === 0;
106
+ const redErrors = invalidValidity
107
+ ? [{ text: "پاسخ سؤالات اعتبار", length: 105, start: 0, last: true }]
108
+ : [];
89
109
 
110
+ return [
111
+ {
112
+ page: 1,
113
+ titleAppend: " - صفحه 1",
114
+ theme: "sky",
115
+ colors: COLORS,
116
+ items,
117
+ redErrors,
118
+ showRed: redErrors.length > 0,
119
+ },
120
+ ];
121
+ }
122
+ }
90
123
 
91
124
  module.exports = NEO9A;