@baravak/risloo-profile-cli 4.82.0 → 4.84.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.
@@ -0,0 +1,132 @@
1
+ const { Profile } = require("../Profile");
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Chart geometry, measured from the Figma "Chart" layer (789 × 414). Only the
5
+ // values the controller has to compute with live here; the static geometry is
6
+ // transcribed in the template.
7
+ // ---------------------------------------------------------------------------
8
+ const ROW = { top: 54, pitch: 40 }; // first bar row, row-to-row pitch
9
+ const BAR = { x: 174, coefficient: 4, gap: 4 }; // 100 % = 400 px (designer note); label 4 px inside the tip
10
+ const BASELINE = 12.06; // 13 px text baseline, measured from the row top
11
+
12
+ // Total column: vertical bar filling bottom → up, 100 % = 350 px (designer note).
13
+ const TOTAL = { bottom: 396, coefficient: 3.5 };
14
+
15
+ class JSS93 extends Profile {
16
+ static pages = 1;
17
+
18
+ // JSS93 — پرسشنامه رضایت شغلی اسپکتور. Nine 4-item factors (raw 4–20) plus the
19
+ // total (raw 36–180). `percentage` is the share of the possible maximum, the
20
+ // definition the design uses (scoring/JSS93.py → round(raw / max, 3)); it drives
21
+ // both the bar length and the "٪ NN" readout so the two can never disagree.
22
+ labels = {
23
+ L1_1: { eng: "pay_raw", title: "رضایت از پرداخت‌ها", max: 20 },
24
+ L1_2: { eng: "pay_percentage" },
25
+
26
+ L2_1: { eng: "operating_procedures_raw", title: "فرآیندهای اجرایی کار (شرایط کار)", max: 20 },
27
+ L2_2: { eng: "operating_procedures_percentage" },
28
+
29
+ L3_1: { eng: "coworkers_raw", title: "همکاران", max: 20 },
30
+ L3_2: { eng: "coworkers_percentage" },
31
+
32
+ L4_1: { eng: "promotion_raw", title: "ارتقای شغلی", max: 20 },
33
+ L4_2: { eng: "promotion_percentage" },
34
+
35
+ L5_1: { eng: "supervision_raw", title: "نظارت", max: 20 },
36
+ L5_2: { eng: "supervision_percentage" },
37
+
38
+ L6_1: { eng: "fringe_benefits_raw", title: "مزایای جانبی شغل", max: 20 },
39
+ L6_2: { eng: "fringe_benefits_percentage" },
40
+
41
+ L7_1: { eng: "contingent_rewards_raw", title: "پاداش‌های احتمالی", max: 20 },
42
+ L7_2: { eng: "contingent_rewards_percentage" },
43
+
44
+ L8_1: { eng: "nature_of_work_raw", title: "ماهیت شغل", max: 20 },
45
+ L8_2: { eng: "nature_of_work_percentage" },
46
+
47
+ L9_1: { eng: "communication_raw", title: "ارتباطات و اطلاع‌رسانی", max: 20 },
48
+ L9_2: { eng: "communication_percentage" },
49
+
50
+ L10_1: { eng: "total_raw", title: "نمره کل رضایت شغلی", max: 180 },
51
+ L10_2: { eng: "total_percentage" },
52
+ };
53
+
54
+ profileSpec = {
55
+ sample: {
56
+ name: "پرسشنامه رضایت شغلی اسپکتور",
57
+ multiProfile: false,
58
+ questions: false,
59
+ defaultFields: true,
60
+ fields: [],
61
+ },
62
+ profile: {
63
+ get dimensions() {
64
+ return {
65
+ width: 789 + 2 * this.padding.x, // Chart layer 789 wide → 903
66
+ height: 414 + 2 * this.padding.y, // Chart layer 414 tall → 714
67
+ };
68
+ },
69
+ // Padding is the Chart's inset inside the 943 × 754 design page *minus* the
70
+ // 20 px the layout already owns on every side, so the page resolves to
71
+ // 903 × 714 — the exact with-sidebar drawing area — and renders at scale 1
72
+ // instead of being shrunk to fit.
73
+ padding: {
74
+ x: 57, // (943 − 789) / 2 − 20
75
+ y: 150, // (754 − 414) / 2 − 20
76
+ },
77
+ },
78
+ labels: Object.values(this.labels),
79
+ };
80
+
81
+ constructor(dataset, options, config = {}) {
82
+ super();
83
+ this._init(dataset, options, config);
84
+ }
85
+
86
+ _calcContext() {
87
+ const { dataset } = this;
88
+ const s = dataset.score; // [raw, percentage] × 9 factors, then the total
89
+
90
+ const items = [];
91
+ for (let i = 0; i < 9; i++) {
92
+ const raw = s[2 * i];
93
+ const top = ROW.top + i * ROW.pitch;
94
+ // The design rounds the percentage before drawing, so the tip of the bar
95
+ // always lands exactly on the number printed inside it.
96
+ const pct = Math.round((s[2 * i + 1].mark ?? 0) * 100);
97
+ const width = BAR.coefficient * pct;
98
+
99
+ items.push({
100
+ title: raw.label.title,
101
+ mark: raw.mark ?? 0,
102
+ pct,
103
+ width,
104
+ top,
105
+ // Designer note: the percentage never leaves the bar — this test cannot
106
+ // score below 20 %, so there is always room for the label inside it.
107
+ labelX: BAR.x + width - BAR.gap,
108
+ baseline: top + BASELINE,
109
+ });
110
+ }
111
+
112
+ const totalRaw = s[18];
113
+ const totalPct = Math.round((s[19].mark ?? 0) * 100);
114
+ const height = TOTAL.coefficient * totalPct;
115
+ const tip = TOTAL.bottom - height;
116
+
117
+ const total = {
118
+ mark: totalRaw.mark ?? 0,
119
+ max: totalRaw.label.max,
120
+ pct: totalPct,
121
+ height,
122
+ tip,
123
+ // Designer note: the readout travels with the bar, centred on its tip.
124
+ pctBaseline: tip - 0.98,
125
+ fractionBaseline: tip + 13.06,
126
+ };
127
+
128
+ return [{ items, total }];
129
+ }
130
+ }
131
+
132
+ module.exports = JSS93;
@@ -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;