@baravak/risloo-profile-cli 4.83.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.
package/AGENTS.md CHANGED
@@ -183,6 +183,27 @@ get dimensions() {
183
183
 
184
184
  Padding is layout metadata. Do not apply the same padding again inside the HBS when the layout already owns it.
185
185
 
186
+ #### Subtract the 20 px the layout already owns — every page must resolve to 903 × 714
187
+
188
+ The designs are drawn on a **943 × 754** page, but `layout.hbs` insets the profile `<svg>` by `canvas.profile.padding` (**20 px**) on every side, so the real with-sidebar drawing area is **903 × 714**. Padding is therefore the Chart's inset inside the design page **minus that 20 px**:
189
+
190
+ ```js
191
+ padding: {
192
+ x: (943 - CHART_WIDTH) / 2 - 20,
193
+ y: (754 - CHART_HEIGHT) / 2 - 20,
194
+ },
195
+ ```
196
+
197
+ so `dimensions` comes out at exactly `903 × 714`. Use the Chart's *measured* inset instead of `/2` whenever the Chart is not centred on the design page — the `− 20` still applies.
198
+
199
+ This is not cosmetic. The inner `<svg>` uses the default `preserveAspectRatio="xMidYMid meet"`, so a `943 × 754` viewBox is **shrunk to fit at 0.9469** — the whole page, text and geometry included — while `903 × 714` renders at **scale 1**. Check it after every render:
200
+
201
+ ```bash
202
+ grep -A4 '<svg x=' temp/<NAME>.svg # width/height must equal the viewBox in the with-sidebar variant
203
+ ```
204
+
205
+ The raw variant is a different box and legitimately renders at ~1.056; only the with-sidebar variant is the scale-1 target.
206
+
186
207
  Never introduce a conventional `translate(20,20)`, arbitrary centering transform, or scale. Add an inner translation only when that offset visibly belongs to the Chart layer and is measured from an authorized source.
187
208
 
188
209
  Fix geometry at the layer that owns the error:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baravak/risloo-profile-cli",
3
- "version": "4.83.0",
3
+ "version": "4.84.0",
4
4
  "description": "**Risloo Profile CLI** is a library for creating profiles, reports and sheets for *psychological* samples.",
5
5
  "main": "bin/risloo.js",
6
6
  "publishConfig": {
@@ -0,0 +1,684 @@
1
+ {
2
+ "id": "$X1HXXXXX",
3
+ "scale": {
4
+ "id": "$JSS-93",
5
+ "title": "پرسشنامه رضایت شغلی اسپکتور"
6
+ },
7
+ "chain": null,
8
+ "version": "1",
9
+ "edition": "",
10
+ "edition_version": null,
11
+ "client": {
12
+ "id": "GH966XXXX",
13
+ "name": "مراجع"
14
+ },
15
+ "room": {
16
+ "id": "RS96XXXX",
17
+ "manager": {
18
+ "id": "GH9XXXXX",
19
+ "name": "درمانگر",
20
+ "user_id": "IR9XXXX"
21
+ },
22
+ "center": {
23
+ "id": "RS966XXXX",
24
+ "acceptation": null,
25
+ "status": "active",
26
+ "type": "personal_clinic",
27
+ "detail": {
28
+ "title": "درمانگر"
29
+ }
30
+ },
31
+ "acceptation": null,
32
+ "status": "active",
33
+ "type": "personal_clinic"
34
+ },
35
+ "case": null,
36
+ "code": null,
37
+ "created_at": 1694157079,
38
+ "started_at": 1694157198,
39
+ "scored_at": 1694242467,
40
+ "closed_at": 1694160574,
41
+ "total_amount": 100000,
42
+ "prepayment": true,
43
+ "purchased": true,
44
+ "amount": 100000,
45
+ "description": "لطفاً هر عبارت را با توجه به تجربه و احساس واقعی خود دربارهٔ شغل فعلی‌تان بخوانید.\n\nبرای هر عبارت، گزینه‌ای را انتخاب کنید که بیشترین انطباق را با نظر شما دارد.\n\nپاسخ درست یا غلطی وجود ندارد؛ لطفاً به همهٔ عبارت‌ها پاسخ دهید.",
46
+ "items": [
47
+ {
48
+ "type": "text",
49
+ "text": "احساس می‌کنم در قبال کاری که انجام می‌دهم حقوق منصفانه‌ای به من پرداخت می‌شود.",
50
+ "answer": {
51
+ "type": "optional",
52
+ "options": [
53
+ "کاملاً مخالفم",
54
+ "مخالفم",
55
+ "نه موافقم نه مخالفم",
56
+ "موافقم",
57
+ "کاملاً موافقم"
58
+ ]
59
+ },
60
+ "user_answered": "5"
61
+ },
62
+ {
63
+ "type": "text",
64
+ "text": "افزایش حقوق بسیار کم و با فاصله زیاد است.",
65
+ "answer": {
66
+ "type": "optional",
67
+ "options": [
68
+ "کاملاً مخالفم",
69
+ "مخالفم",
70
+ "نه موافقم نه مخالفم",
71
+ "موافقم",
72
+ "کاملاً موافقم"
73
+ ]
74
+ },
75
+ "user_answered": "2"
76
+ },
77
+ {
78
+ "type": "text",
79
+ "text": "وقتی که به حقوقی که به من می‌دهند فکر می‌کنم، احساس می‌کنم سازمان از من قدردانی نمی‌کند.",
80
+ "answer": {
81
+ "type": "optional",
82
+ "options": [
83
+ "کاملاً مخالفم",
84
+ "مخالفم",
85
+ "نه موافقم نه مخالفم",
86
+ "موافقم",
87
+ "کاملاً موافقم"
88
+ ]
89
+ },
90
+ "user_answered": "2"
91
+ },
92
+ {
93
+ "type": "text",
94
+ "text": "از فرصت‌هایی که برای افزایش حقوق دارم، احساس رضایت می‌کنم.",
95
+ "answer": {
96
+ "type": "optional",
97
+ "options": [
98
+ "کاملاً مخالفم",
99
+ "مخالفم",
100
+ "نه موافقم نه مخالفم",
101
+ "موافقم",
102
+ "کاملاً موافقم"
103
+ ]
104
+ },
105
+ "user_answered": "4"
106
+ },
107
+ {
108
+ "type": "text",
109
+ "text": "بسیاری از مقررات و دستورالعمل‌ها، انجام کار خوب را با مشکل مواجه می‌کنند.",
110
+ "answer": {
111
+ "type": "optional",
112
+ "options": [
113
+ "کاملاً مخالفم",
114
+ "مخالفم",
115
+ "نه موافقم نه مخالفم",
116
+ "موافقم",
117
+ "کاملاً موافقم"
118
+ ]
119
+ },
120
+ "user_answered": "2"
121
+ },
122
+ {
123
+ "type": "text",
124
+ "text": "تلاش‌هایی که برای انجام یک کار خوب به کار می‌گیرم به‌ندرت با مانع مواجه می‌شود.",
125
+ "answer": {
126
+ "type": "optional",
127
+ "options": [
128
+ "کاملاً مخالفم",
129
+ "مخالفم",
130
+ "نه موافقم نه مخالفم",
131
+ "موافقم",
132
+ "کاملاً موافقم"
133
+ ]
134
+ },
135
+ "user_answered": "4"
136
+ },
137
+ {
138
+ "type": "text",
139
+ "text": "حجم کار من خیلی زیاد است.",
140
+ "answer": {
141
+ "type": "optional",
142
+ "options": [
143
+ "کاملاً مخالفم",
144
+ "مخالفم",
145
+ "نه موافقم نه مخالفم",
146
+ "موافقم",
147
+ "کاملاً موافقم"
148
+ ]
149
+ },
150
+ "user_answered": "3"
151
+ },
152
+ {
153
+ "type": "text",
154
+ "text": "کارهای نوشتنی زیادی را باید انجام دهم.",
155
+ "answer": {
156
+ "type": "optional",
157
+ "options": [
158
+ "کاملاً مخالفم",
159
+ "مخالفم",
160
+ "نه موافقم نه مخالفم",
161
+ "موافقم",
162
+ "کاملاً موافقم"
163
+ ]
164
+ },
165
+ "user_answered": "3"
166
+ },
167
+ {
168
+ "type": "text",
169
+ "text": "کسانی را که با آن‌ها کار می‌کنم، دوست دارم.",
170
+ "answer": {
171
+ "type": "optional",
172
+ "options": [
173
+ "کاملاً مخالفم",
174
+ "مخالفم",
175
+ "نه موافقم نه مخالفم",
176
+ "موافقم",
177
+ "کاملاً موافقم"
178
+ ]
179
+ },
180
+ "user_answered": "5"
181
+ },
182
+ {
183
+ "type": "text",
184
+ "text": "فکر می‌کنم مجبورم بیشتر کار کنم زیرا کسانی که با من کار می‌کنند توانایی لازم را ندارند.",
185
+ "answer": {
186
+ "type": "optional",
187
+ "options": [
188
+ "کاملاً مخالفم",
189
+ "مخالفم",
190
+ "نه موافقم نه مخالفم",
191
+ "موافقم",
192
+ "کاملاً موافقم"
193
+ ]
194
+ },
195
+ "user_answered": "1"
196
+ },
197
+ {
198
+ "type": "text",
199
+ "text": "از کار کردن با همکارانم لذت می‌برم.",
200
+ "answer": {
201
+ "type": "optional",
202
+ "options": [
203
+ "کاملاً مخالفم",
204
+ "مخالفم",
205
+ "نه موافقم نه مخالفم",
206
+ "موافقم",
207
+ "کاملاً موافقم"
208
+ ]
209
+ },
210
+ "user_answered": "5"
211
+ },
212
+ {
213
+ "type": "text",
214
+ "text": "پرخاش، بحث و مشاجره در محیط کار من بسیار زیاد است.",
215
+ "answer": {
216
+ "type": "optional",
217
+ "options": [
218
+ "کاملاً مخالفم",
219
+ "مخالفم",
220
+ "نه موافقم نه مخالفم",
221
+ "موافقم",
222
+ "کاملاً موافقم"
223
+ ]
224
+ },
225
+ "user_answered": "2"
226
+ },
227
+ {
228
+ "type": "text",
229
+ "text": "در شغل من شانس بسیار کمی برای ارتقا وجود دارد.",
230
+ "answer": {
231
+ "type": "optional",
232
+ "options": [
233
+ "کاملاً مخالفم",
234
+ "مخالفم",
235
+ "نه موافقم نه مخالفم",
236
+ "موافقم",
237
+ "کاملاً موافقم"
238
+ ]
239
+ },
240
+ "user_answered": "3"
241
+ },
242
+ {
243
+ "type": "text",
244
+ "text": "افرادی که کار خود را به‌خوبی انجام می‌دهند، شانس خوبی برای ارتقا دارند.",
245
+ "answer": {
246
+ "type": "optional",
247
+ "options": [
248
+ "کاملاً مخالفم",
249
+ "مخالفم",
250
+ "نه موافقم نه مخالفم",
251
+ "موافقم",
252
+ "کاملاً موافقم"
253
+ ]
254
+ },
255
+ "user_answered": "3"
256
+ },
257
+ {
258
+ "type": "text",
259
+ "text": "پیشرفت افراد در این سازمان به همان نسبتی است که می‌توانستند در سایر سازمان‌ها داشته باشند.",
260
+ "answer": {
261
+ "type": "optional",
262
+ "options": [
263
+ "کاملاً مخالفم",
264
+ "مخالفم",
265
+ "نه موافقم نه مخالفم",
266
+ "موافقم",
267
+ "کاملاً موافقم"
268
+ ]
269
+ },
270
+ "user_answered": "3"
271
+ },
272
+ {
273
+ "type": "text",
274
+ "text": "از فرصتی که برای ارتقا دارم راضی هستم.",
275
+ "answer": {
276
+ "type": "optional",
277
+ "options": [
278
+ "کاملاً مخالفم",
279
+ "مخالفم",
280
+ "نه موافقم نه مخالفم",
281
+ "موافقم",
282
+ "کاملاً موافقم"
283
+ ]
284
+ },
285
+ "user_answered": "2"
286
+ },
287
+ {
288
+ "type": "text",
289
+ "text": "سرپرستم صلاحیت لازم را برای انجام کار دارد.",
290
+ "answer": {
291
+ "type": "optional",
292
+ "options": [
293
+ "کاملاً مخالفم",
294
+ "مخالفم",
295
+ "نه موافقم نه مخالفم",
296
+ "موافقم",
297
+ "کاملاً موافقم"
298
+ ]
299
+ },
300
+ "user_answered": "1"
301
+ },
302
+ {
303
+ "type": "text",
304
+ "text": "سرپرستم با من منصفانه رفتار نمی‌کند.",
305
+ "answer": {
306
+ "type": "optional",
307
+ "options": [
308
+ "کاملاً مخالفم",
309
+ "مخالفم",
310
+ "نه موافقم نه مخالفم",
311
+ "موافقم",
312
+ "کاملاً موافقم"
313
+ ]
314
+ },
315
+ "user_answered": "5"
316
+ },
317
+ {
318
+ "type": "text",
319
+ "text": "سرپرستم توجه کمی نسبت به احساسات زیردستانش نشان می‌دهد.",
320
+ "answer": {
321
+ "type": "optional",
322
+ "options": [
323
+ "کاملاً مخالفم",
324
+ "مخالفم",
325
+ "نه موافقم نه مخالفم",
326
+ "موافقم",
327
+ "کاملاً موافقم"
328
+ ]
329
+ },
330
+ "user_answered": "5"
331
+ },
332
+ {
333
+ "type": "text",
334
+ "text": "سرپرستم را دوست دارم.",
335
+ "answer": {
336
+ "type": "optional",
337
+ "options": [
338
+ "کاملاً مخالفم",
339
+ "مخالفم",
340
+ "نه موافقم نه مخالفم",
341
+ "موافقم",
342
+ "کاملاً موافقم"
343
+ ]
344
+ },
345
+ "user_answered": "1"
346
+ },
347
+ {
348
+ "type": "text",
349
+ "text": "از مزایای دریافتی خود راضی نیستم.",
350
+ "answer": {
351
+ "type": "optional",
352
+ "options": [
353
+ "کاملاً مخالفم",
354
+ "مخالفم",
355
+ "نه موافقم نه مخالفم",
356
+ "موافقم",
357
+ "کاملاً موافقم"
358
+ ]
359
+ },
360
+ "user_answered": "4"
361
+ },
362
+ {
363
+ "type": "text",
364
+ "text": "مزایایی که دریافت می‌کنیم به‌خوبی سایر سازمان‌های مشابه است.",
365
+ "answer": {
366
+ "type": "optional",
367
+ "options": [
368
+ "کاملاً مخالفم",
369
+ "مخالفم",
370
+ "نه موافقم نه مخالفم",
371
+ "موافقم",
372
+ "کاملاً موافقم"
373
+ ]
374
+ },
375
+ "user_answered": "2"
376
+ },
377
+ {
378
+ "type": "text",
379
+ "text": "مزایایی که ما دریافت می‌کنیم عادلانه است.",
380
+ "answer": {
381
+ "type": "optional",
382
+ "options": [
383
+ "کاملاً مخالفم",
384
+ "مخالفم",
385
+ "نه موافقم نه مخالفم",
386
+ "موافقم",
387
+ "کاملاً موافقم"
388
+ ]
389
+ },
390
+ "user_answered": "2"
391
+ },
392
+ {
393
+ "type": "text",
394
+ "text": "مزایایی وجود دارد که ما باید از آن بهره‌مند گردیم در حالی که از آن بی‌بهره‌ایم.",
395
+ "answer": {
396
+ "type": "optional",
397
+ "options": [
398
+ "کاملاً مخالفم",
399
+ "مخالفم",
400
+ "نه موافقم نه مخالفم",
401
+ "موافقم",
402
+ "کاملاً موافقم"
403
+ ]
404
+ },
405
+ "user_answered": "4"
406
+ },
407
+ {
408
+ "type": "text",
409
+ "text": "وقتی کار خوبی انجام می‌دهم در قبال آن به اندازه کافی از من قدردانی می‌شود.",
410
+ "answer": {
411
+ "type": "optional",
412
+ "options": [
413
+ "کاملاً مخالفم",
414
+ "مخالفم",
415
+ "نه موافقم نه مخالفم",
416
+ "موافقم",
417
+ "کاملاً موافقم"
418
+ ]
419
+ },
420
+ "user_answered": "4"
421
+ },
422
+ {
423
+ "type": "text",
424
+ "text": "احساس می‌کنم از کاری که انجام می‌دهم قدردانی نمی‌شود.",
425
+ "answer": {
426
+ "type": "optional",
427
+ "options": [
428
+ "کاملاً مخالفم",
429
+ "مخالفم",
430
+ "نه موافقم نه مخالفم",
431
+ "موافقم",
432
+ "کاملاً موافقم"
433
+ ]
434
+ },
435
+ "user_answered": "3"
436
+ },
437
+ {
438
+ "type": "text",
439
+ "text": "برای آن‌هایی که در اینجا کار می‌کنند پاداش کمی وجود دارد.",
440
+ "answer": {
441
+ "type": "optional",
442
+ "options": [
443
+ "کاملاً مخالفم",
444
+ "مخالفم",
445
+ "نه موافقم نه مخالفم",
446
+ "موافقم",
447
+ "کاملاً موافقم"
448
+ ]
449
+ },
450
+ "user_answered": "3"
451
+ },
452
+ {
453
+ "type": "text",
454
+ "text": "احساس می‌کنم تلاش من آن‌طور که شایسته است مورد تشویق قرار نمی‌گیرد.",
455
+ "answer": {
456
+ "type": "optional",
457
+ "options": [
458
+ "کاملاً مخالفم",
459
+ "مخالفم",
460
+ "نه موافقم نه مخالفم",
461
+ "موافقم",
462
+ "کاملاً موافقم"
463
+ ]
464
+ },
465
+ "user_answered": "3"
466
+ },
467
+ {
468
+ "type": "text",
469
+ "text": "گاهی اوقات فکر می‌کنم که شغلم بی‌معنی است.",
470
+ "answer": {
471
+ "type": "optional",
472
+ "options": [
473
+ "کاملاً مخالفم",
474
+ "مخالفم",
475
+ "نه موافقم نه مخالفم",
476
+ "موافقم",
477
+ "کاملاً موافقم"
478
+ ]
479
+ },
480
+ "user_answered": "5"
481
+ },
482
+ {
483
+ "type": "text",
484
+ "text": "کارهایی که در محیط کار انجام می‌دهم را دوست دارم.",
485
+ "answer": {
486
+ "type": "optional",
487
+ "options": [
488
+ "کاملاً مخالفم",
489
+ "مخالفم",
490
+ "نه موافقم نه مخالفم",
491
+ "موافقم",
492
+ "کاملاً موافقم"
493
+ ]
494
+ },
495
+ "user_answered": "1"
496
+ },
497
+ {
498
+ "type": "text",
499
+ "text": "از کاری که انجام می‌دهم احساس غرور می‌کنم.",
500
+ "answer": {
501
+ "type": "optional",
502
+ "options": [
503
+ "کاملاً مخالفم",
504
+ "مخالفم",
505
+ "نه موافقم نه مخالفم",
506
+ "موافقم",
507
+ "کاملاً موافقم"
508
+ ]
509
+ },
510
+ "user_answered": "1"
511
+ },
512
+ {
513
+ "type": "text",
514
+ "text": "کار من لذت‌بخش است.",
515
+ "answer": {
516
+ "type": "optional",
517
+ "options": [
518
+ "کاملاً مخالفم",
519
+ "مخالفم",
520
+ "نه موافقم نه مخالفم",
521
+ "موافقم",
522
+ "کاملاً موافقم"
523
+ ]
524
+ },
525
+ "user_answered": "1"
526
+ },
527
+ {
528
+ "type": "text",
529
+ "text": "به نظر می‌رسد در این سازمان ارتباطات مناسب است.",
530
+ "answer": {
531
+ "type": "optional",
532
+ "options": [
533
+ "کاملاً مخالفم",
534
+ "مخالفم",
535
+ "نه موافقم نه مخالفم",
536
+ "موافقم",
537
+ "کاملاً موافقم"
538
+ ]
539
+ },
540
+ "user_answered": "3"
541
+ },
542
+ {
543
+ "type": "text",
544
+ "text": "اهداف سازمان برایم کاملاً واضح و شفاف نمی‌باشند.",
545
+ "answer": {
546
+ "type": "optional",
547
+ "options": [
548
+ "کاملاً مخالفم",
549
+ "مخالفم",
550
+ "نه موافقم نه مخالفم",
551
+ "موافقم",
552
+ "کاملاً موافقم"
553
+ ]
554
+ },
555
+ "user_answered": "3"
556
+ },
557
+ {
558
+ "type": "text",
559
+ "text": "اغلب احساس می‌کنم از اتفاقاتی که در سازمان می‌گذرد بی‌اطلاعم.",
560
+ "answer": {
561
+ "type": "optional",
562
+ "options": [
563
+ "کاملاً مخالفم",
564
+ "مخالفم",
565
+ "نه موافقم نه مخالفم",
566
+ "موافقم",
567
+ "کاملاً موافقم"
568
+ ]
569
+ },
570
+ "user_answered": "3"
571
+ },
572
+ {
573
+ "type": "text",
574
+ "text": "در مورد شرح وظایف شغلی توضیح کامل داده نمی‌شود.",
575
+ "answer": {
576
+ "type": "optional",
577
+ "options": [
578
+ "کاملاً مخالفم",
579
+ "مخالفم",
580
+ "نه موافقم نه مخالفم",
581
+ "موافقم",
582
+ "کاملاً موافقم"
583
+ ]
584
+ },
585
+ "user_answered": "3"
586
+ }
587
+ ],
588
+ "prerequisites": [
589
+ {
590
+ "type": "text",
591
+ "label": "gender",
592
+ "text": "جنسیت",
593
+ "answer": {
594
+ "type": "select",
595
+ "options": [
596
+ "زن",
597
+ "مرد"
598
+ ]
599
+ },
600
+ "alias": "gender",
601
+ "force": true,
602
+ "user_answered": "2"
603
+ },
604
+ {
605
+ "type": "text",
606
+ "label": "age",
607
+ "text": "سن",
608
+ "answer": {
609
+ "type": "number"
610
+ },
611
+ "alias": "age",
612
+ "force": true,
613
+ "user_answered": "34"
614
+ },
615
+ {
616
+ "type": "text",
617
+ "label": "education",
618
+ "text": "تحصیلات",
619
+ "answer": {
620
+ "type": "select",
621
+ "options": [
622
+ "ابتدایی اول",
623
+ "ابتدایی دوم",
624
+ "متوسطه اول",
625
+ "متوسطه دوم",
626
+ "دیپلم",
627
+ "کارشناسی",
628
+ "کارشناسی ارشد",
629
+ "پزشکی عمومی",
630
+ "متخصص",
631
+ "دکتری تخصصی",
632
+ "حوزوی مقدمات",
633
+ "حوزوی سطح۱",
634
+ "حوزوی سطح۲",
635
+ "حوزوی سطح۳",
636
+ "حوزوی سطح۴"
637
+ ]
638
+ },
639
+ "alias": "education",
640
+ "force": true,
641
+ "user_answered": "6"
642
+ },
643
+ {
644
+ "type": "text",
645
+ "label": "marital_status",
646
+ "text": "وضعیت تأهل",
647
+ "answer": {
648
+ "type": "select",
649
+ "options": [
650
+ "مجرد",
651
+ "متأهل"
652
+ ]
653
+ },
654
+ "alias": "marital_status",
655
+ "force": true,
656
+ "user_answered": "2"
657
+ }
658
+ ],
659
+ "cornometer": 743,
660
+ "score": {
661
+ "pay_raw": 17,
662
+ "pay_percentage": 0.85,
663
+ "operating_procedures_raw": 14,
664
+ "operating_procedures_percentage": 0.7,
665
+ "coworkers_raw": 19,
666
+ "coworkers_percentage": 0.95,
667
+ "promotion_raw": 11,
668
+ "promotion_percentage": 0.55,
669
+ "supervision_raw": 4,
670
+ "supervision_percentage": 0.2,
671
+ "fringe_benefits_raw": 8,
672
+ "fringe_benefits_percentage": 0.4,
673
+ "contingent_rewards_raw": 13,
674
+ "contingent_rewards_percentage": 0.65,
675
+ "nature_of_work_raw": 4,
676
+ "nature_of_work_percentage": 0.2,
677
+ "communication_raw": 12,
678
+ "communication_percentage": 0.6,
679
+ "total_raw": 102,
680
+ "total_percentage": 0.567
681
+ },
682
+ "score_current_version": "1.0.0",
683
+ "score_last_version": "1.0.0"
684
+ }
@@ -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,131 @@
1
+ {{#> layout}}
2
+
3
+ <defs>
4
+ {{! Teal wash behind the plot area (Figma "Bar Back Total") }}
5
+ <linearGradient id="jssWash" x1="0" y1="0" x2="1" y2="0">
6
+ <stop stop-color="#F0FDFA"/>
7
+ <stop offset="1" stop-color="#CCFBF1"/>
8
+ </linearGradient>
9
+
10
+ {{! Factor bar: Figma anchors the gradient to each bar's own width, so bounding-box
11
+ units reproduce all nine bars from one definition. }}
12
+ <linearGradient id="jssBar" x1="0" y1="0" x2="1" y2="0">
13
+ <stop stop-color="#99F6E4"/>
14
+ <stop offset="1" stop-color="#0F766E"/>
15
+ </linearGradient>
16
+
17
+ {{! Total bar: Figma runs the gradient 194 % of the fill height up from the foot of
18
+ the bar, i.e. from bounding-box y = 1 to y = 1 − 1.94, so the visible tip keeps
19
+ the same tone at every score. }}
20
+ <linearGradient id="jssTotal" x1="0" y1="1" x2="0" y2="-0.94">
21
+ <stop stop-color="#94A3B8"/>
22
+ <stop offset="1" stop-color="#334155"/>
23
+ </linearGradient>
24
+
25
+ {{! Shadow of the white 0 % baseline (offset right, soft blur) }}
26
+ <filter id="jssBaseShadow" x="171" y="27" width="7" height="390" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
27
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
28
+ <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
29
+ <feOffset dx="1"/>
30
+ <feGaussianBlur stdDeviation="1.5"/>
31
+ <feComposite in2="hardAlpha" operator="out"/>
32
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
33
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1"/>
34
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1" result="shape"/>
35
+ </filter>
36
+
37
+ {{! Same treatment under the foot of the total bar }}
38
+ <filter id="jssTotalBaseShadow" x="711" y="390" width="34" height="14" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
39
+ <feFlood flood-opacity="0" result="BackgroundImageFix"/>
40
+ <feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
41
+ <feOffset dx="2"/>
42
+ <feGaussianBlur stdDeviation="3"/>
43
+ <feComposite in2="hardAlpha" operator="out"/>
44
+ <feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"/>
45
+ <feBlend mode="normal" in2="BackgroundImageFix" result="effect1"/>
46
+ <feBlend mode="normal" in="SourceGraphic" in2="effect1" result="shape"/>
47
+ </filter>
48
+ </defs>
49
+
50
+ <g transform="translate({{spec.profile.padding.x}}, {{spec.profile.padding.y}})">
51
+
52
+ {{! ===================== Plot area wash ===================== }}
53
+ <rect x="175" y="30" width="399" height="384" fill="url(#jssWash)" fill-opacity="0.25"/>
54
+
55
+ {{! ===================== Raw-score boxes ===================== }}
56
+ {{#each items as |item|}}
57
+ <rect x="587.5" y="{{math item.top '+' 0.5}}" width="31" height="15" rx="3.5" fill="none" stroke="#059669"/>
58
+ <text x="603" y="{{item.baseline}}" text-anchor="middle" font-size="13" font-weight="500" fill="#047857">{{item.mark}}</text>
59
+ {{/each}}
60
+
61
+ {{! ===================== Gridlines (10 % … 100 %) ===================== }}
62
+ <line x1="213.5" y1="30.5" x2="213.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
63
+ <line x1="253.5" y1="30.5" x2="253.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
64
+ <line x1="293.5" y1="30.5" x2="293.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
65
+ <line x1="333.5" y1="30.5" x2="333.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
66
+ <line x1="373.5" y1="30.5" x2="373.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
67
+ <line x1="413.5" y1="30.5" x2="413.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
68
+ <line x1="453.5" y1="30.5" x2="453.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
69
+ <line x1="493.5" y1="30.5" x2="493.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
70
+ <line x1="533.5" y1="30.5" x2="533.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
71
+ <line x1="573.5" y1="30.5" x2="573.5" y2="413.5" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
72
+
73
+ {{! ===================== Bar tracks ===================== }}
74
+ {{#each items as |item|}}
75
+ <g transform="translate(174, {{item.top}})">
76
+ {{bar 400 16 (object tl=0 bl=0 tr=2 br=2) (toRad 0) fill="black" fill-opacity="0.04"}}
77
+ </g>
78
+ {{/each}}
79
+
80
+ {{! ===================== Bars + percentage inside the tip ===================== }}
81
+ {{#each items as |item|}}
82
+ <g transform="translate(174, {{item.top}})">
83
+ {{#if item.width}}{{bar item.width 16 (object tl=0 bl=0 tr=2 br=2) (toRad 0) fill="url(#jssBar)"}}{{/if}}
84
+ </g>
85
+ <text x="{{item.labelX}}" y="{{item.baseline}}" text-anchor="end" direction="ltr" font-size="13" font-weight="500" fill="white">٪ {{item.pct}}</text>
86
+ {{/each}}
87
+
88
+ {{! ===================== Factor names ===================== }}
89
+ {{#each items as |item|}}
90
+ <text x="165" y="{{item.baseline}}" text-anchor="start" font-size="13" fill="#475569">{{item.title}}</text>
91
+ {{/each}}
92
+
93
+ {{! ===================== White shadowed 0 % baseline ===================== }}
94
+ <g opacity="0.5" filter="url(#jssBaseShadow)">
95
+ <line x1="173.5" y1="30.5" x2="173.5" y2="413.5" stroke="white" stroke-linecap="round"/>
96
+ </g>
97
+
98
+ {{! ===================== Axis labels ===================== }}
99
+ <text x="173.5" y="18.06" text-anchor="middle" font-size="12" fill="#94A3B8">0</text>
100
+ <text x="373.5" y="18.06" text-anchor="middle" direction="ltr" font-size="12" fill="#94A3B8">٪ 50</text>
101
+ <text x="563" y="18.06" text-anchor="middle" direction="ltr" font-size="12" fill="#94A3B8">٪ 100</text>
102
+ <text x="603" y="10.06" text-anchor="middle" font-size="12" fill="#94A3B8">نمره خام</text>
103
+ <text x="603" y="24.06" text-anchor="middle" font-size="12" fill="#94A3B8">از 20</text>
104
+
105
+ {{! ===================== Total score column ===================== }}
106
+ <g transform="translate(717, 46)">
107
+ {{bar 18 350 (object tl=2 tr=2 bl=0 br=0) (toRad 0) fill="#F1F5F9"}}
108
+ </g>
109
+ {{#if total.height}}
110
+ <g transform="translate(717, {{total.tip}})">
111
+ {{bar 18 total.height (object tl=2 tr=2 bl=0 br=0) (toRad 0) fill="url(#jssTotal)"}}
112
+ </g>
113
+ {{/if}}
114
+
115
+ {{! Readout travelling with the tip of the bar }}
116
+ <text x="743" y="{{total.pctBaseline}}" text-anchor="start" direction="ltr" font-size="18" font-weight="600" fill="#334155">٪ {{total.pct}}</text>
117
+ <text x="743" y="{{total.fractionBaseline}}" text-anchor="end" font-size="12" fill="#64748B">{{total.mark}} از {{total.max}}</text>
118
+
119
+ {{! Column title, reading bottom-up along the bar }}
120
+ <text x="0" y="0" transform="translate(707.02, 296.2) rotate(-90)" text-anchor="start" font-size="12" fill="#475569">نمره کل رضایت شغلی</text>
121
+
122
+ {{! Foot of the total bar }}
123
+ <g opacity="0.5" filter="url(#jssTotalBaseShadow)">
124
+ <path d="M716 397H736" stroke="white" stroke-width="2" stroke-linecap="round"/>
125
+ </g>
126
+
127
+ <text x="711" y="49.06" text-anchor="end" direction="ltr" font-size="12" fill="#475569">٪ 100</text>
128
+
129
+ </g>
130
+
131
+ {{/layout}}