@baravak/risloo-profile-cli 4.78.0 → 4.83.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/.claude/skills/create-profile/SKILL.md +36 -0
- package/.github/workflows/publish.yml +70 -0
- package/.github/workflows/push.yml +18 -9
- package/.mcp.json +8 -0
- package/AGENTS.md +14 -7
- package/package.json +2 -6
- package/src/publish/json/profiles/SII93.json +4858 -0
- package/src/samples/NEO93.js +134 -15
- package/src/samples/NEO9A.js +6 -2
- package/src/samples/NEO9Q.js +146 -15
- package/src/samples/NEO9V.js +6 -2
- package/src/samples/SII93.js +490 -0
- package/views/profiles/NEO_sheet.hbs +70 -0
- package/views/profiles/SII93_bipolar_rows.hbs +100 -0
- package/views/profiles/SII93_summary_card.hbs +46 -0
- package/views/profiles/samples/NEO93_1.hbs +1 -10
- package/views/profiles/samples/NEO93_2.hbs +9 -10
- package/views/profiles/samples/NEO93_3.hbs +1 -1
- package/views/profiles/samples/NEO93_4.hbs +15 -0
- package/views/profiles/samples/NEO9Q_1.hbs +1 -9
- package/views/profiles/samples/NEO9Q_2.hbs +8 -9
- package/views/profiles/samples/NEO9Q_3.hbs +1 -1
- package/views/profiles/samples/NEO9Q_4.hbs +14 -0
- package/views/profiles/samples/SII93_1.hbs +88 -0
- package/views/profiles/samples/SII93_2.hbs +64 -0
- package/views/profiles/samples/SII93_3.hbs +45 -0
- package/views/profiles/samples/SII93_4.hbs +74 -0
- package/.claude/settings.local.json +0 -7
- package/src/publish/bot.js +0 -91
- package/src/publish/new-version.hbs +0 -11
|
@@ -0,0 +1,490 @@
|
|
|
1
|
+
const { Profile } = require("../Profile");
|
|
2
|
+
|
|
3
|
+
const BAR_WIDTH_COEFFICIENT = 125;
|
|
4
|
+
|
|
5
|
+
const FACTOR_SPECS = [
|
|
6
|
+
{
|
|
7
|
+
key: "realistic",
|
|
8
|
+
title: "واقعگرا",
|
|
9
|
+
maxAbs: 114,
|
|
10
|
+
section: "realistic",
|
|
11
|
+
level: "general",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
key: "mechanics_construction",
|
|
15
|
+
title: "مکانیک و ساختوساز",
|
|
16
|
+
maxAbs: 18,
|
|
17
|
+
section: "realistic",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
key: "computer_hardware_electronics",
|
|
21
|
+
title: "سختافزار کامپیوتر و الکترونیک",
|
|
22
|
+
maxAbs: 12,
|
|
23
|
+
section: "realistic",
|
|
24
|
+
},
|
|
25
|
+
{ key: "military", title: "امور نظامی", maxAbs: 12, section: "realistic" },
|
|
26
|
+
{
|
|
27
|
+
key: "protective_services",
|
|
28
|
+
title: "خدمات حفاظتی و ایمنی",
|
|
29
|
+
maxAbs: 10,
|
|
30
|
+
section: "realistic",
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "nature_agriculture",
|
|
34
|
+
title: "طبیعت و کشاورزی",
|
|
35
|
+
maxAbs: 20,
|
|
36
|
+
section: "realistic",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
key: "athletics",
|
|
40
|
+
title: "فعالیتهای ورزشی",
|
|
41
|
+
maxAbs: 18,
|
|
42
|
+
section: "realistic",
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
key: "investigative",
|
|
47
|
+
title: "جستجوگر",
|
|
48
|
+
maxAbs: 82,
|
|
49
|
+
section: "investigative",
|
|
50
|
+
level: "general",
|
|
51
|
+
},
|
|
52
|
+
{ key: "science", title: "علوم", maxAbs: 20, section: "investigative" },
|
|
53
|
+
{ key: "research", title: "پژوهش", maxAbs: 20, section: "investigative" },
|
|
54
|
+
{
|
|
55
|
+
key: "medical_science",
|
|
56
|
+
title: "علوم پزشکی",
|
|
57
|
+
maxAbs: 14,
|
|
58
|
+
section: "investigative",
|
|
59
|
+
},
|
|
60
|
+
{ key: "mathematics", title: "ریاضیات", maxAbs: 12, section: "investigative" },
|
|
61
|
+
|
|
62
|
+
{
|
|
63
|
+
key: "artistic",
|
|
64
|
+
title: "هنری",
|
|
65
|
+
maxAbs: 86,
|
|
66
|
+
section: "artistic",
|
|
67
|
+
level: "general",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
key: "visual_arts_design",
|
|
71
|
+
title: "هنرهای تجسمی و طراحی",
|
|
72
|
+
maxAbs: 16,
|
|
73
|
+
section: "artistic",
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
key: "performing_arts",
|
|
77
|
+
title: "هنرهای نمایشی",
|
|
78
|
+
maxAbs: 20,
|
|
79
|
+
section: "artistic",
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
key: "writing_mass_communication",
|
|
83
|
+
title: "نویسندگی و ارتباطات جمعی",
|
|
84
|
+
maxAbs: 20,
|
|
85
|
+
section: "artistic",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
key: "culinary_arts",
|
|
89
|
+
title: "هنرهای آشپزی",
|
|
90
|
+
maxAbs: 10,
|
|
91
|
+
section: "artistic",
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
{
|
|
95
|
+
key: "social",
|
|
96
|
+
title: "اجتماعی",
|
|
97
|
+
maxAbs: 100,
|
|
98
|
+
section: "social",
|
|
99
|
+
level: "general",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
key: "counseling_helping",
|
|
103
|
+
title: "مشاوره و کمکرسانی",
|
|
104
|
+
maxAbs: 22,
|
|
105
|
+
section: "social",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
key: "teaching_education",
|
|
109
|
+
title: "آموزش و تعلیم",
|
|
110
|
+
maxAbs: 18,
|
|
111
|
+
section: "social",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
key: "human_resources_training",
|
|
115
|
+
title: "منابع انسانی و آموزش",
|
|
116
|
+
maxAbs: 8,
|
|
117
|
+
section: "social",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
key: "social_sciences",
|
|
121
|
+
title: "علوم اجتماعی",
|
|
122
|
+
maxAbs: 8,
|
|
123
|
+
section: "social",
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
key: "religion_spirituality",
|
|
127
|
+
title: "دین و معنویت",
|
|
128
|
+
maxAbs: 14,
|
|
129
|
+
section: "social",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
key: "healthcare_services",
|
|
133
|
+
title: "خدمات مراقبت سلامت",
|
|
134
|
+
maxAbs: 8,
|
|
135
|
+
section: "social",
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
key: "enterprising",
|
|
140
|
+
title: "پیشرو",
|
|
141
|
+
maxAbs: 138,
|
|
142
|
+
section: "enterprising",
|
|
143
|
+
level: "general",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
key: "marketing_advertising",
|
|
147
|
+
title: "بازاریابی و تبلیغات",
|
|
148
|
+
maxAbs: 10,
|
|
149
|
+
section: "enterprising",
|
|
150
|
+
},
|
|
151
|
+
{ key: "sales", title: "فروش", maxAbs: 16, section: "enterprising" },
|
|
152
|
+
{ key: "management", title: "مدیریت", maxAbs: 12, section: "enterprising" },
|
|
153
|
+
{
|
|
154
|
+
key: "entrepreneurship",
|
|
155
|
+
title: "کارآفرینی",
|
|
156
|
+
maxAbs: 10,
|
|
157
|
+
section: "enterprising",
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
key: "politics_public_speaking",
|
|
161
|
+
title: "سیاست و سخنرانی عمومی",
|
|
162
|
+
maxAbs: 18,
|
|
163
|
+
section: "enterprising",
|
|
164
|
+
},
|
|
165
|
+
{ key: "law", title: "حقوق", maxAbs: 14, section: "enterprising" },
|
|
166
|
+
|
|
167
|
+
{
|
|
168
|
+
key: "conventional",
|
|
169
|
+
title: "قراردادی",
|
|
170
|
+
maxAbs: 60,
|
|
171
|
+
section: "conventional",
|
|
172
|
+
level: "general",
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
key: "office_management",
|
|
176
|
+
title: "مدیریت امور اداری",
|
|
177
|
+
maxAbs: 8,
|
|
178
|
+
section: "conventional",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: "taxes_accounting",
|
|
182
|
+
title: "مالیات و حسابداری",
|
|
183
|
+
maxAbs: 10,
|
|
184
|
+
section: "conventional",
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
key: "programming_information_systems",
|
|
188
|
+
title: "برنامهنویسی و سیستمهای اطلاعاتی",
|
|
189
|
+
maxAbs: 8,
|
|
190
|
+
section: "conventional",
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
key: "finance_investing",
|
|
194
|
+
title: "امور مالی و سرمایهگذاری",
|
|
195
|
+
maxAbs: 8,
|
|
196
|
+
section: "conventional",
|
|
197
|
+
},
|
|
198
|
+
|
|
199
|
+
{
|
|
200
|
+
key: "work_style",
|
|
201
|
+
title: "سبک کاری",
|
|
202
|
+
maxAbs: 56,
|
|
203
|
+
section: "personal_style",
|
|
204
|
+
descriptionLeft:
|
|
205
|
+
"ترجیح کار مستقل؛ علاقه بیشتر به کار با دادهها، ایدهها یا اشیا؛ معمولاً محتاطتر و کمتعاملتر در محیط کار.",
|
|
206
|
+
descriptionRight:
|
|
207
|
+
"ترجیح کار با افراد؛ علاقه به کمککردن، تعامل و ارتباط؛ معمولاً اجتماعیتر و برونگراتر در محیط کار.",
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: "learning_environment",
|
|
211
|
+
title: "محیط یادگیری",
|
|
212
|
+
maxAbs: 90,
|
|
213
|
+
section: "personal_style",
|
|
214
|
+
descriptionLeft:
|
|
215
|
+
"ترجیح یادگیری عملی و تجربی؛ یادگیری از راه انجامدادن؛ علاقه بیشتر به آموزشهای کوتاهمدت و مهارتمحور.",
|
|
216
|
+
descriptionRight:
|
|
217
|
+
"ترجیح محیط دانشگاهی و نظری؛ یادگیری از طریق کتاب، سخنرانی و مطالعه؛ آمادگی برای تحصیل طولانیتر و کسب دانش بهخاطر خود دانش.",
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
key: "leadership_style",
|
|
221
|
+
title: "سبک رهبری",
|
|
222
|
+
maxAbs: 32,
|
|
223
|
+
section: "personal_style",
|
|
224
|
+
descriptionLeft:
|
|
225
|
+
"ترجیح انجام شخصی کار؛ عدم تمایل به فرماندهی و هدایت مستقیم؛ ممکن است بیشتر از راه الگو بودن رهبری کند.",
|
|
226
|
+
descriptionRight:
|
|
227
|
+
"راحت در پذیرفتن مسئولیت و هدایت دیگران؛ علاقه به ایجاد انگیزه، آغاز اقدام، بیان نظر و هماهنگکردن فعالیتها.",
|
|
228
|
+
},
|
|
229
|
+
// Figma swaps the next two description cards; keep them with their scoring keys.
|
|
230
|
+
{
|
|
231
|
+
key: "team_orientation",
|
|
232
|
+
title: "گرایش به کار تیمی",
|
|
233
|
+
maxAbs: 20,
|
|
234
|
+
section: "personal_style",
|
|
235
|
+
descriptionLeft: "ترجیح انجام مستقل وظایف؛ علاقه به نقش مشارکتکنندهٔ مستقل و حل مسئله بهتنهایی.",
|
|
236
|
+
descriptionRight: "ترجیح کار تیمی؛ علاقه به همکاری، اهداف مشترک، تبادل نظر و حل مسئله همراه دیگران.",
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
key: "risk_taking",
|
|
240
|
+
title: "ریسکپذیری",
|
|
241
|
+
maxAbs: 22,
|
|
242
|
+
section: "personal_style",
|
|
243
|
+
descriptionLeft:
|
|
244
|
+
"ترجیح اطمینان، ثبات و تصمیمگیری محتاطانه؛ علاقه کمتر به موقعیتهای نامطمئن و فعالیتهای هیجانانگیز.",
|
|
245
|
+
descriptionRight:
|
|
246
|
+
"علاقه بیشتر به تجربههای تازه، ایدههای بدیع، هیجان و پذیرش فرصتهای نامطمئن؛ گرایش به تصمیمگیری سریعتر.",
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
|
|
250
|
+
const GENERAL_FACTOR_KEYS = ["realistic", "investigative", "artistic", "social", "enterprising", "conventional"];
|
|
251
|
+
|
|
252
|
+
const BASIC_INTEREST_PAGE_2 = ["realistic", "investigative", "artistic"];
|
|
253
|
+
const BASIC_INTEREST_PAGE_3 = ["social", "enterprising", "conventional"];
|
|
254
|
+
|
|
255
|
+
const DESCRIPTION_LINES = {
|
|
256
|
+
work_style: {
|
|
257
|
+
left: [
|
|
258
|
+
"ترجیح کار مستقل؛ علاقه بیشتر به کار با دادهها،",
|
|
259
|
+
"ایدهها یا اشیا؛ معمولاً محتاطتر و کمتعاملتر در",
|
|
260
|
+
"محیط کار.",
|
|
261
|
+
],
|
|
262
|
+
right: ["ترجیح کار با افراد؛ علاقه به کمککردن، تعامل و", "ارتباط؛ معمولاً اجتماعیتر و برونگراتر در محیط کار."],
|
|
263
|
+
},
|
|
264
|
+
learning_environment: {
|
|
265
|
+
left: [
|
|
266
|
+
"ترجیح یادگیری عملی و تجربی؛ یادگیری از راه",
|
|
267
|
+
"انجامدادن؛ علاقه بیشتر به آموزشهای کوتاهمدت",
|
|
268
|
+
"و مهارتمحور.",
|
|
269
|
+
],
|
|
270
|
+
right: [
|
|
271
|
+
"ترجیح محیط دانشگاهی و نظری؛ یادگیری از طریق",
|
|
272
|
+
"کتاب، سخنرانی و مطالعه؛ آمادگی برای تحصیل",
|
|
273
|
+
"طولانیتر و کسب دانش بهخاطر خود دانش.",
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
leadership_style: {
|
|
277
|
+
left: [
|
|
278
|
+
"ترجیح انجام شخصی کار؛ عدم تمایل به فرماندهی",
|
|
279
|
+
"و هدایت مستقیم؛ ممکن است بیشتر از راه الگو",
|
|
280
|
+
"بودن رهبری کند.",
|
|
281
|
+
],
|
|
282
|
+
right: [
|
|
283
|
+
"راحت در پذیرفتن مسئولیت و هدایت دیگران؛",
|
|
284
|
+
"علاقه به ایجاد انگیزه، آغاز اقدام، بیان نظر و",
|
|
285
|
+
"هماهنگکردن فعالیتها.",
|
|
286
|
+
],
|
|
287
|
+
},
|
|
288
|
+
team_orientation: {
|
|
289
|
+
left: ["ترجیح انجام مستقل وظایف؛ علاقه به نقش", "مشارکتکنندهٔ مستقل و حل مسئله بهتنهایی."],
|
|
290
|
+
right: ["ترجیح کار تیمی؛ علاقه به همکاری، اهداف مشترک،", "تبادل نظر و حل مسئله همراه دیگران."],
|
|
291
|
+
},
|
|
292
|
+
risk_taking: {
|
|
293
|
+
left: [
|
|
294
|
+
"ترجیح اطمینان، ثبات و تصمیمگیری محتاطانه؛",
|
|
295
|
+
"علاقه کمتر به موقعیتهای نامطمئن و",
|
|
296
|
+
"فعالیتهای هیجانانگیز.",
|
|
297
|
+
],
|
|
298
|
+
right: [
|
|
299
|
+
"علاقه بیشتر به تجربههای تازه، ایدههای بدیع،",
|
|
300
|
+
"هیجان و پذیرش فرصتهای نامطمئن؛ گرایش به",
|
|
301
|
+
"تصمیمگیری سریعتر.",
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
class SII93 extends Profile {
|
|
307
|
+
static pages = 4;
|
|
308
|
+
|
|
309
|
+
static partials = {
|
|
310
|
+
SII93_bipolar_rows: "SII93_bipolar_rows.hbs",
|
|
311
|
+
SII93_summary_card: "SII93_summary_card.hbs",
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
labels = Object.fromEntries(
|
|
315
|
+
FACTOR_SPECS.flatMap(({ key }) => [
|
|
316
|
+
[`${key}_raw`, { eng: `${key}_raw` }],
|
|
317
|
+
[`${key}_percentage`, { eng: `${key}_percentage` }],
|
|
318
|
+
])
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
profileSpec = {
|
|
322
|
+
sample: {
|
|
323
|
+
name: "پرسشنامه رغبتسنج استرانگ",
|
|
324
|
+
multiProfile: false,
|
|
325
|
+
questions: false,
|
|
326
|
+
defaultFields: true,
|
|
327
|
+
fields: [],
|
|
328
|
+
},
|
|
329
|
+
profile: {
|
|
330
|
+
get dimensions() {
|
|
331
|
+
const [page1, page2, page3, page4] = this.padding;
|
|
332
|
+
return [
|
|
333
|
+
{ width: 812 + 2 * page1.x, height: 364 + 2 * page1.y },
|
|
334
|
+
{ width: 895 + 2 * page2.x, height: 664 + 2 * page2.y },
|
|
335
|
+
{ width: 895 + 2 * page3.x, height: 664 + 2 * page3.y },
|
|
336
|
+
{ width: 799 + 2 * page4.x, height: 668 + 2 * page4.y },
|
|
337
|
+
];
|
|
338
|
+
},
|
|
339
|
+
padding: [
|
|
340
|
+
{ x: 45.5, y: 175 },
|
|
341
|
+
{ x: 4, y: 25 },
|
|
342
|
+
{ x: 4, y: 25 },
|
|
343
|
+
{ x: 52, y: 23 },
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
labels: Object.values(this.labels),
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
constructor(dataset, options, config = {}) {
|
|
350
|
+
super();
|
|
351
|
+
this._init(dataset, options, config);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
_calcContext() {
|
|
355
|
+
const { dataset } = this;
|
|
356
|
+
const scoresByKey = new Map(dataset.score.map((item) => [item.label.eng, item]));
|
|
357
|
+
const rowsByKey = Object.fromEntries(FACTOR_SPECS.map((factor) => [factor.key, buildRow(factor, scoresByKey)]));
|
|
358
|
+
|
|
359
|
+
return [
|
|
360
|
+
{
|
|
361
|
+
pageKey: "general-occupational-themes",
|
|
362
|
+
titleAppend: " - ۱",
|
|
363
|
+
items: GENERAL_FACTOR_KEYS.map((key) => rowsByKey[key]),
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
pageKey: "basic-interest-scales-ria",
|
|
367
|
+
titleAppend: " - ۲",
|
|
368
|
+
groups: buildGroups(BASIC_INTEREST_PAGE_2, rowsByKey),
|
|
369
|
+
},
|
|
370
|
+
{
|
|
371
|
+
pageKey: "basic-interest-scales-sec",
|
|
372
|
+
titleAppend: " - ۳",
|
|
373
|
+
groups: buildGroups(BASIC_INTEREST_PAGE_3, rowsByKey),
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
pageKey: "personal-style-scales",
|
|
377
|
+
titleAppend: " - ۴ - مقیاسهای سبک شخصی",
|
|
378
|
+
items: FACTOR_SPECS.filter(({ section }) => section === "personal_style").map(({ key }) => rowsByKey[key]),
|
|
379
|
+
},
|
|
380
|
+
];
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function buildGroups(groupKeys, rowsByKey) {
|
|
385
|
+
return groupKeys.map((key) => ({
|
|
386
|
+
key,
|
|
387
|
+
title: rowsByKey[key].title,
|
|
388
|
+
summary: rowsByKey[key],
|
|
389
|
+
items: FACTOR_SPECS.filter(({ section, level }) => section === key && level !== "general").map(
|
|
390
|
+
(factor) => rowsByKey[factor.key]
|
|
391
|
+
),
|
|
392
|
+
}));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function buildRow(factor, scoresByKey) {
|
|
396
|
+
const raw = getMark(scoresByKey, `${factor.key}_raw`);
|
|
397
|
+
const percentage = getMark(scoresByKey, `${factor.key}_percentage`);
|
|
398
|
+
const scoreNorm = clamp((2 * raw) / factor.maxAbs, -2, 2);
|
|
399
|
+
const score = roundToOne(scoreNorm);
|
|
400
|
+
const zero = raw === 0;
|
|
401
|
+
const isPositive = raw > 0;
|
|
402
|
+
const isNegative = raw < 0;
|
|
403
|
+
const state = zero ? "zero" : isPositive ? "positive" : "negative";
|
|
404
|
+
const rawDisplay = buildRawDisplay(raw, factor.maxAbs, state);
|
|
405
|
+
const descriptionLines = DESCRIPTION_LINES[factor.key] || { left: [], right: [] };
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
key: factor.key,
|
|
409
|
+
title: factor.title,
|
|
410
|
+
raw,
|
|
411
|
+
percentage,
|
|
412
|
+
maxAbs: factor.maxAbs,
|
|
413
|
+
scoreNorm,
|
|
414
|
+
score,
|
|
415
|
+
scoreText: formatScore(score),
|
|
416
|
+
barW: zero ? 0 : Math.abs(scoreNorm) * BAR_WIDTH_COEFFICIENT,
|
|
417
|
+
barRadius: Math.min(4, (Math.abs(scoreNorm) * BAR_WIDTH_COEFFICIENT) / 2),
|
|
418
|
+
inside: !zero && Math.abs(score) >= 1,
|
|
419
|
+
zero,
|
|
420
|
+
isPositive,
|
|
421
|
+
isNegative,
|
|
422
|
+
state,
|
|
423
|
+
negativeRawText: rawDisplay.negative.text,
|
|
424
|
+
positiveRawText: rawDisplay.positive.text,
|
|
425
|
+
summaryRawText:
|
|
426
|
+
state === "positive"
|
|
427
|
+
? `${raw} / ${factor.maxAbs}`
|
|
428
|
+
: state === "negative"
|
|
429
|
+
? `- ${Math.abs(raw)} / - ${factor.maxAbs}`
|
|
430
|
+
: "0",
|
|
431
|
+
rawDisplay,
|
|
432
|
+
descriptionLeft: factor.descriptionLeft || "",
|
|
433
|
+
descriptionRight: factor.descriptionRight || "",
|
|
434
|
+
descriptionLeftLines: descriptionLines.left,
|
|
435
|
+
descriptionRightLines: descriptionLines.right,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function getMark(scoresByKey, key) {
|
|
440
|
+
const mark = scoresByKey.get(key)?.mark ?? 0;
|
|
441
|
+
const number = Number(mark);
|
|
442
|
+
return Number.isFinite(number) ? number : 0;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function buildRawDisplay(raw, maxAbs, state) {
|
|
446
|
+
const negativeBoundText = `- ${maxAbs}`;
|
|
447
|
+
const positiveBoundText = `${maxAbs}`;
|
|
448
|
+
const resultText = raw < 0 ? `- ${Math.abs(raw)}` : `${raw}`;
|
|
449
|
+
const negativeShowsResult = state !== "positive";
|
|
450
|
+
const positiveShowsResult = state !== "negative";
|
|
451
|
+
|
|
452
|
+
return {
|
|
453
|
+
negative: {
|
|
454
|
+
bound: -maxAbs,
|
|
455
|
+
boundText: negativeBoundText,
|
|
456
|
+
result: negativeShowsResult ? raw : null,
|
|
457
|
+
resultText: negativeShowsResult ? resultText : "",
|
|
458
|
+
showResult: negativeShowsResult,
|
|
459
|
+
showSlash: negativeShowsResult,
|
|
460
|
+
active: state === "negative",
|
|
461
|
+
text: negativeShowsResult ? `${negativeBoundText} / ${resultText}` : negativeBoundText,
|
|
462
|
+
},
|
|
463
|
+
positive: {
|
|
464
|
+
bound: maxAbs,
|
|
465
|
+
boundText: positiveBoundText,
|
|
466
|
+
result: positiveShowsResult ? raw : null,
|
|
467
|
+
resultText: positiveShowsResult ? resultText : "",
|
|
468
|
+
showResult: positiveShowsResult,
|
|
469
|
+
showSlash: positiveShowsResult,
|
|
470
|
+
active: state === "positive",
|
|
471
|
+
text: positiveShowsResult ? `${resultText} / ${positiveBoundText}` : positiveBoundText,
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function clamp(value, min, max) {
|
|
477
|
+
return Math.min(Math.max(value, min), max);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
function roundToOne(value) {
|
|
481
|
+
const rounded = (Math.sign(value) * Math.round((Math.abs(value) + Number.EPSILON) * 10)) / 10;
|
|
482
|
+
return Object.is(rounded, -0) ? 0 : rounded;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function formatScore(value) {
|
|
486
|
+
const absolute = `${Math.abs(value)}`.replace(".", "٬");
|
|
487
|
+
return value < 0 ? `- ${absolute}` : absolute;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
module.exports = SII93;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{{!--
|
|
2
|
+
NEO profile sheet (نیمرخ): 35 dotted columns — the 5 domains, then 6 groups of
|
|
3
|
+
6 facets — over a horizontal gridline scale, with rotated titles above, latin
|
|
4
|
+
codes below, and one polyline per group.
|
|
5
|
+
|
|
6
|
+
Every coordinate arrives from the controller; nothing is derived here, so the
|
|
7
|
+
percentage sheet and the T-score sheet share this file without a variant flag.
|
|
8
|
+
|
|
9
|
+
padding {x, y} page padding owned by the layout
|
|
10
|
+
sheet.grid {x, w, ys: [{y, faint}]} gridline rects, y = rect top edge
|
|
11
|
+
sheet.plot {top, bottom} dot-column endpoints (gridline centres)
|
|
12
|
+
sheet.dot {pitch, accentPitch, light, accent}
|
|
13
|
+
sheet.tick {top, h} column ticks above the top gridline
|
|
14
|
+
sheet.rail {dashLeftX, dashRightX, w, ruleLeftX, ruleRightX, ruleW,
|
|
15
|
+
ruleTop, ruleH, dark, light, dashes: [{y, dark}]}
|
|
16
|
+
sheet.numberX right edge the axis numbers align to
|
|
17
|
+
sheet.numbers [{y, text}] axis numbers, y = gridline centre
|
|
18
|
+
sheet.levelX centre guide of the rotated level words
|
|
19
|
+
sheet.levels [{y, text}] rotated level words, may be empty
|
|
20
|
+
sheet.titleBottomY guide the rotated titles grow upward from
|
|
21
|
+
sheet.codeY baseline of the latin codes
|
|
22
|
+
columns [{cx, code, fa, domain}] 35 entries, design draw order
|
|
23
|
+
series [{points, stroke}] one polyline per group
|
|
24
|
+
|
|
25
|
+
Paint order mirrors the design export: codes, titles, dots, rails, numbers,
|
|
26
|
+
ticks, gridlines, result lines.
|
|
27
|
+
--}}
|
|
28
|
+
<g transform="translate({{padding.x}}, {{padding.y}})">
|
|
29
|
+
{{#each columns as |col|}}
|
|
30
|
+
<text class="eng-font" x="{{col.cx}}" y="{{../sheet.codeY}}" direction="ltr" font-size="10" font-weight="400" fill="#94A3B8" text-anchor="middle">{{col.code}}</text>
|
|
31
|
+
{{/each}}
|
|
32
|
+
|
|
33
|
+
{{#each columns as |col|}}
|
|
34
|
+
<text x="{{col.cx}}" y="{{../sheet.titleBottomY}}" transform="rotate(-90 {{col.cx}} {{../sheet.titleBottomY}})" direction="ltr" font-size="12" font-weight="{{ternary col.domain 600 400}}" fill="{{ternary col.domain '#4338CA' '#334155'}}" text-anchor="start" dy=".35em"><tspan class="eng-font">{{col.code}}</tspan> - {{col.fa}}</text>
|
|
35
|
+
{{/each}}
|
|
36
|
+
|
|
37
|
+
{{#each columns as |col|}}
|
|
38
|
+
<line x1="{{col.cx}}" y1="{{../sheet.plot.top}}" x2="{{col.cx}}" y2="{{../sheet.plot.bottom}}" stroke="{{../sheet.dot.light}}" stroke-width="2" stroke-linecap="round" stroke-dasharray="0 {{../sheet.dot.pitch}}"/>
|
|
39
|
+
<line x1="{{col.cx}}" y1="{{../sheet.plot.top}}" x2="{{col.cx}}" y2="{{../sheet.plot.bottom}}" stroke="{{../sheet.dot.accent}}" stroke-width="2" stroke-linecap="round" stroke-dasharray="0 {{../sheet.dot.accentPitch}}"/>
|
|
40
|
+
{{/each}}
|
|
41
|
+
|
|
42
|
+
{{#each sheet.dashes as |dash|}}
|
|
43
|
+
<line x1="{{math ../sheet.rail.dashLeftX '+' 1}}" y1="{{dash.y}}" x2="{{math ../sheet.rail.dashLeftX '+' (math ../sheet.rail.w '-' 1)}}" y2="{{dash.y}}" stroke="{{ternary dash.dark ../sheet.rail.dark ../sheet.rail.light}}" stroke-width="2" stroke-linecap="round"/>
|
|
44
|
+
<line x1="{{math ../sheet.rail.dashRightX '+' 1}}" y1="{{dash.y}}" x2="{{math ../sheet.rail.dashRightX '+' (math ../sheet.rail.w '-' 1)}}" y2="{{dash.y}}" stroke="{{ternary dash.dark ../sheet.rail.dark ../sheet.rail.light}}" stroke-width="2" stroke-linecap="round"/>
|
|
45
|
+
{{/each}}
|
|
46
|
+
<rect x="{{sheet.rail.ruleLeftX}}" y="{{sheet.rail.ruleTop}}" width="{{sheet.rail.ruleW}}" height="{{sheet.rail.ruleH}}" fill="{{sheet.rail.dark}}"/>
|
|
47
|
+
<rect x="{{sheet.rail.ruleRightX}}" y="{{sheet.rail.ruleTop}}" width="{{sheet.rail.ruleW}}" height="{{sheet.rail.ruleH}}" fill="{{sheet.rail.dark}}"/>
|
|
48
|
+
|
|
49
|
+
{{#each sheet.numbers as |number|}}
|
|
50
|
+
<text x="{{../sheet.numberX}}" y="{{number.y}}" dy=".3em" font-size="12" font-weight="400" fill="#64748B" text-anchor="start">{{number.text}}</text>
|
|
51
|
+
{{/each}}
|
|
52
|
+
|
|
53
|
+
{{#each sheet.levels as |level|}}
|
|
54
|
+
<text x="{{../sheet.levelX}}" y="{{level.y}}" transform="rotate(-90 {{../sheet.levelX}} {{level.y}})" font-size="12" font-weight="400" fill="#94A3B8" text-anchor="middle" dy=".35em">{{level.text}}</text>
|
|
55
|
+
{{/each}}
|
|
56
|
+
|
|
57
|
+
{{#each columns as |col|}}
|
|
58
|
+
<g transform="translate({{math col.cx '-' 1}}, {{../sheet.tick.top}})">
|
|
59
|
+
{{bar 2 ../sheet.tick.h (object tl=1 tr=1 bl=0 br=0) (toRad 0) fill=(ternary col.domain '#4338CA' '#64748B')}}
|
|
60
|
+
</g>
|
|
61
|
+
{{/each}}
|
|
62
|
+
|
|
63
|
+
{{#each sheet.grid.ys as |line|}}
|
|
64
|
+
<rect x="{{../sheet.grid.x}}" y="{{line.y}}" width="{{../sheet.grid.w}}" height="2" rx="1" fill="#94A3B8"{{#if line.faint}} fill-opacity="0.25"{{/if}}/>
|
|
65
|
+
{{/each}}
|
|
66
|
+
|
|
67
|
+
{{#each series as |line|}}
|
|
68
|
+
<polyline points="{{line.points}}" fill="none" stroke="{{line.stroke}}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
69
|
+
{{/each}}
|
|
70
|
+
</g>
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{{#each items as |item index|}}
|
|
2
|
+
<g transform="translate(0, {{math ../rowsY '+' (math index '*' ../rowPitch)}})">
|
|
3
|
+
<g transform="translate({{../leftTrackX}}, 0)">
|
|
4
|
+
{{bar 250 24 (object tl=4 bl=4 tr=0 br=0) (toRad 0) fill=../leftTrackFill}}
|
|
5
|
+
</g>
|
|
6
|
+
<g transform="translate({{../rightTrackX}}, 0)">
|
|
7
|
+
{{bar 250 24 (object tl=0 bl=0 tr=4 br=4) (toRad 0) fill=../rightTrackFill}}
|
|
8
|
+
</g>
|
|
9
|
+
|
|
10
|
+
{{#if ../showDescriptions}}
|
|
11
|
+
<g transform="translate({{../leftTrackX}}, 28)">
|
|
12
|
+
{{bar 250 68 (object tl=4 bl=4 tr=0 br=0) (toRad 0) fill=../leftTrackFill}}
|
|
13
|
+
</g>
|
|
14
|
+
<g transform="translate({{../rightTrackX}}, 28)">
|
|
15
|
+
{{bar 250 68 (object tl=0 bl=0 tr=4 br=4) (toRad 0) fill=../rightTrackFill}}
|
|
16
|
+
</g>
|
|
17
|
+
|
|
18
|
+
<text x="{{../leftDescriptionX}}" y="48" font-size="12" font-weight="400" fill="#475569" text-anchor="middle">
|
|
19
|
+
{{#each item.descriptionLeftLines as |line lineIndex|}}
|
|
20
|
+
<tspan x="{{../../leftDescriptionX}}" y="{{math 48 '+' (math lineIndex '*' 17)}}">{{line}}</tspan>
|
|
21
|
+
{{/each}}
|
|
22
|
+
</text>
|
|
23
|
+
<text x="{{../rightDescriptionX}}" y="48" font-size="12" font-weight="400" fill="#475569" text-anchor="middle">
|
|
24
|
+
{{#each item.descriptionRightLines as |line lineIndex|}}
|
|
25
|
+
<tspan x="{{../../rightDescriptionX}}" y="{{math 48 '+' (math lineIndex '*' 17)}}">{{line}}</tspan>
|
|
26
|
+
{{/each}}
|
|
27
|
+
</text>
|
|
28
|
+
{{/if}}
|
|
29
|
+
|
|
30
|
+
{{#if item.isNegative}}
|
|
31
|
+
<g transform="translate({{math ../leftZeroX '-' item.barW}}, 0)">
|
|
32
|
+
{{bar item.barW 24 (object tl=item.barRadius bl=item.barRadius tr=0 br=0) (toRad 0) fill=../leftActiveFill}}
|
|
33
|
+
<text
|
|
34
|
+
x="{{ternary item.inside 7 -7}}"
|
|
35
|
+
y="12"
|
|
36
|
+
dy=".34em"
|
|
37
|
+
font-size="{{ternary item.inside 16 14}}"
|
|
38
|
+
font-weight="{{ternary item.inside 600 500}}"
|
|
39
|
+
fill="{{ternary item.inside 'white' ../leftActiveColor}}"
|
|
40
|
+
text-anchor="{{ternary item.inside 'start' 'end'}}"
|
|
41
|
+
direction="ltr"
|
|
42
|
+
unicode-bidi="bidi-override"
|
|
43
|
+
>{{item.scoreText}}</text>
|
|
44
|
+
</g>
|
|
45
|
+
{{/if}}
|
|
46
|
+
|
|
47
|
+
{{#if item.isPositive}}
|
|
48
|
+
<g transform="translate({{../rightZeroX}}, 0)">
|
|
49
|
+
{{bar item.barW 24 (object tl=0 bl=0 tr=item.barRadius br=item.barRadius) (toRad 0) fill=../rightActiveFill}}
|
|
50
|
+
<text
|
|
51
|
+
x="{{math item.barW '+' (ternary item.inside -7 7)}}"
|
|
52
|
+
y="12"
|
|
53
|
+
dy=".34em"
|
|
54
|
+
font-size="{{ternary item.inside 16 14}}"
|
|
55
|
+
font-weight="{{ternary item.inside 600 500}}"
|
|
56
|
+
fill="{{ternary item.inside 'white' ../rightActiveColor}}"
|
|
57
|
+
text-anchor="{{ternary item.inside 'end' 'start'}}"
|
|
58
|
+
direction="ltr"
|
|
59
|
+
unicode-bidi="bidi-override"
|
|
60
|
+
>{{item.scoreText}}</text>
|
|
61
|
+
</g>
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
64
|
+
<rect x="{{../titleX}}" y="0" width="{{../titleWidth}}" height="24" fill="#E2E8F0" fill-opacity="0.25"/>
|
|
65
|
+
<text
|
|
66
|
+
x="{{math ../titleX '+' (math ../titleWidth '/' 2)}}"
|
|
67
|
+
y="12"
|
|
68
|
+
dy=".32em"
|
|
69
|
+
font-size="{{../titleFontSize}}"
|
|
70
|
+
font-weight="{{../titleFontWeight}}"
|
|
71
|
+
fill="#475569"
|
|
72
|
+
text-anchor="middle"
|
|
73
|
+
>{{item.title}}</text>
|
|
74
|
+
|
|
75
|
+
<text
|
|
76
|
+
x="{{../leftRawX}}"
|
|
77
|
+
y="12"
|
|
78
|
+
dy=".3em"
|
|
79
|
+
font-size="12"
|
|
80
|
+
font-weight="400"
|
|
81
|
+
fill="#64748B"
|
|
82
|
+
text-anchor="end"
|
|
83
|
+
direction="ltr"
|
|
84
|
+
unicode-bidi="bidi-override"
|
|
85
|
+
xml:space="preserve"
|
|
86
|
+
><tspan>{{item.rawDisplay.negative.boundText}}</tspan>{{#if item.rawDisplay.negative.showSlash}}<tspan> / </tspan><tspan font-size="14" font-weight="500">{{item.rawDisplay.negative.resultText}}</tspan>{{/if}}</text>
|
|
87
|
+
<text
|
|
88
|
+
x="{{../rightRawX}}"
|
|
89
|
+
y="12"
|
|
90
|
+
dy=".3em"
|
|
91
|
+
font-size="12"
|
|
92
|
+
font-weight="400"
|
|
93
|
+
fill="#64748B"
|
|
94
|
+
text-anchor="start"
|
|
95
|
+
direction="ltr"
|
|
96
|
+
unicode-bidi="bidi-override"
|
|
97
|
+
xml:space="preserve"
|
|
98
|
+
>{{#if item.rawDisplay.positive.showSlash}}<tspan font-size="14" font-weight="500">{{item.rawDisplay.positive.resultText}}</tspan><tspan> / </tspan>{{/if}}<tspan>{{item.rawDisplay.positive.boundText}}</tspan></text>
|
|
99
|
+
</g>
|
|
100
|
+
{{/each}}
|