@baravak/risloo-profile-cli 4.83.0 → 4.85.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,365 @@
1
+ {
2
+ "id": "$X1HXXXXX",
3
+ "scale": {
4
+ "id": "$PSQI-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": "سؤالات زیر مربوط به عادات معمول خواب شما **فقط** در ماه گذشته می‌باشد. جواب‌های شما باید مشخص‌کننده صحیح‌ترین پاسخ در **اکثر** ایام ماه گذشته باشد، لطفاً به تمام سؤالات پاسخ دهید.",
46
+ "items": [
47
+ {
48
+ "type": "text",
49
+ "text": "در طی ماه گذشته معمولاً شب‌ها ساعت چند به رختخواب می‌رفتید؟ (ساعت را به صورت عددی بین ۰ تا ۲۳ بنویسید)",
50
+ "description": "ساعت رفتن به رختخواب",
51
+ "answer": {
52
+ "type": "number"
53
+ },
54
+ "user_answered": "23"
55
+ },
56
+ {
57
+ "type": "text",
58
+ "text": "در طی ماه گذشته معمولاً هر شب چقدر طول می‌کشید تا به خواب بروید (به دقیقه)؟",
59
+ "description": "تعداد دقایق",
60
+ "answer": {
61
+ "type": "number"
62
+ },
63
+ "user_answered": "40"
64
+ },
65
+ {
66
+ "type": "text",
67
+ "text": "در طی ماه گذشته معمولاً صبح‌ها چه ساعتی از رختخواب بلند شده‌اید؟ (ساعت را به صورت عددی بین ۰ تا ۲۳ بنویسید)",
68
+ "description": "ساعت بلند شدن",
69
+ "answer": {
70
+ "type": "number"
71
+ },
72
+ "user_answered": "7"
73
+ },
74
+ {
75
+ "type": "text",
76
+ "text": "در طی ماه گذشته هر شب عملاً چند ساعت خوابیده‌اید؟ (ممکن است با تعداد ساعت‌هایی که در رختخواب گذرانده‌اید متفاوت باشد)",
77
+ "description": "تعداد ساعات خواب در هر شب — به ساعت؛ کسر ساعت را اعشاری وارد کنید، مثلاً 6.5 برای شش ساعت و نیم",
78
+ "answer": {
79
+ "type": "number"
80
+ },
81
+ "user_answered": "5.5"
82
+ },
83
+ {
84
+ "type": "text",
85
+ "text": "به خواب نرفتن در عرض 30 دقیقه",
86
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
87
+ "answer": {
88
+ "type": "optional",
89
+ "options": [
90
+ "هیچ مورد در طی ماه گذشته",
91
+ "کمتر از یک مورد در هفته",
92
+ "یک یا دو بار در هفته",
93
+ "سه بار یا بیشتر در هفته"
94
+ ]
95
+ },
96
+ "user_answered": "3"
97
+ },
98
+ {
99
+ "type": "text",
100
+ "text": "بیدار شدن در نیمه‌شب یا صبح زود",
101
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
102
+ "answer": {
103
+ "type": "optional",
104
+ "options": [
105
+ "هیچ مورد در طی ماه گذشته",
106
+ "کمتر از یک مورد در هفته",
107
+ "یک یا دو بار در هفته",
108
+ "سه بار یا بیشتر در هفته"
109
+ ]
110
+ },
111
+ "user_answered": "4"
112
+ },
113
+ {
114
+ "type": "text",
115
+ "text": "بلند شدن از رختخواب برای رفتن به دستشویی",
116
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
117
+ "answer": {
118
+ "type": "optional",
119
+ "options": [
120
+ "هیچ مورد در طی ماه گذشته",
121
+ "کمتر از یک مورد در هفته",
122
+ "یک یا دو بار در هفته",
123
+ "سه بار یا بیشتر در هفته"
124
+ ]
125
+ },
126
+ "user_answered": "2"
127
+ },
128
+ {
129
+ "type": "text",
130
+ "text": "مشکل در نفس کشیدن",
131
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
132
+ "answer": {
133
+ "type": "optional",
134
+ "options": [
135
+ "هیچ مورد در طی ماه گذشته",
136
+ "کمتر از یک مورد در هفته",
137
+ "یک یا دو بار در هفته",
138
+ "سه بار یا بیشتر در هفته"
139
+ ]
140
+ },
141
+ "user_answered": "1"
142
+ },
143
+ {
144
+ "type": "text",
145
+ "text": "سرفه و خروپف با صدای بلند",
146
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
147
+ "answer": {
148
+ "type": "optional",
149
+ "options": [
150
+ "هیچ مورد در طی ماه گذشته",
151
+ "کمتر از یک مورد در هفته",
152
+ "یک یا دو بار در هفته",
153
+ "سه بار یا بیشتر در هفته"
154
+ ]
155
+ },
156
+ "user_answered": "1"
157
+ },
158
+ {
159
+ "type": "text",
160
+ "text": "احساس سرمای شدید",
161
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
162
+ "answer": {
163
+ "type": "optional",
164
+ "options": [
165
+ "هیچ مورد در طی ماه گذشته",
166
+ "کمتر از یک مورد در هفته",
167
+ "یک یا دو بار در هفته",
168
+ "سه بار یا بیشتر در هفته"
169
+ ]
170
+ },
171
+ "user_answered": "1"
172
+ },
173
+ {
174
+ "type": "text",
175
+ "text": "احساس گرمای شدید",
176
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
177
+ "answer": {
178
+ "type": "optional",
179
+ "options": [
180
+ "هیچ مورد در طی ماه گذشته",
181
+ "کمتر از یک مورد در هفته",
182
+ "یک یا دو بار در هفته",
183
+ "سه بار یا بیشتر در هفته"
184
+ ]
185
+ },
186
+ "user_answered": "1"
187
+ },
188
+ {
189
+ "type": "text",
190
+ "text": "خواب بد دیدن",
191
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
192
+ "answer": {
193
+ "type": "optional",
194
+ "options": [
195
+ "هیچ مورد در طی ماه گذشته",
196
+ "کمتر از یک مورد در هفته",
197
+ "یک یا دو بار در هفته",
198
+ "سه بار یا بیشتر در هفته"
199
+ ]
200
+ },
201
+ "user_answered": "2"
202
+ },
203
+ {
204
+ "type": "text",
205
+ "text": "درد داشتن",
206
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
207
+ "answer": {
208
+ "type": "optional",
209
+ "options": [
210
+ "هیچ مورد در طی ماه گذشته",
211
+ "کمتر از یک مورد در هفته",
212
+ "یک یا دو بار در هفته",
213
+ "سه بار یا بیشتر در هفته"
214
+ ]
215
+ },
216
+ "user_answered": "3"
217
+ },
218
+ {
219
+ "type": "text",
220
+ "text": "اگر موارد دیگری وجود داشته است، لطفاً توضیح دهید (تعداد دفعات آن را نیز مشخص بفرمایید):",
221
+ "category": "برای سؤالات باقی‌مانده بهترین پاسخ را علامت بزنید. لطفاً به تمام سؤالات پاسخ دهید.\n\nدر طی ماه گذشته چند بار به دلایل زیر در خوابیدن مشکل داشته‌اید (تعداد دفعات را مشخص بفرمایید):",
222
+ "answer": {
223
+ "type": "optional",
224
+ "description": {
225
+ "text": "موارد دیگر را اینجا توضیح دهید"
226
+ },
227
+ "options": [
228
+ "هیچ مورد در طی ماه گذشته",
229
+ "کمتر از یک مورد در هفته",
230
+ "یک یا دو بار در هفته",
231
+ "سه بار یا بیشتر در هفته"
232
+ ]
233
+ },
234
+ "user_answered": "2",
235
+ "user_description": "پای بی‌قرار"
236
+ },
237
+ {
238
+ "type": "text",
239
+ "text": "به‌طور کلی کیفیت خواب خود را در طی ماه گذشته چگونه ارزیابی می‌کنید؟",
240
+ "answer": {
241
+ "type": "optional",
242
+ "options": [
243
+ "خیلی خوب",
244
+ "نسبتاً خوب",
245
+ "نسبتاً بد",
246
+ "خیلی بد"
247
+ ]
248
+ },
249
+ "user_answered": "3"
250
+ },
251
+ {
252
+ "type": "text",
253
+ "text": "در طی ماه گذشته چند بار جهت خواب رفتن از دارو استفاده کرده‌اید؟ (چه با تجویز پزشک و چه بدون نسخه)",
254
+ "answer": {
255
+ "type": "optional",
256
+ "options": [
257
+ "هیچ مورد در طی ماه گذشته",
258
+ "کمتر از یک مورد در هفته",
259
+ "یک یا دو بار در هفته",
260
+ "سه بار یا بیشتر در هفته"
261
+ ]
262
+ },
263
+ "user_answered": "1"
264
+ },
265
+ {
266
+ "type": "text",
267
+ "text": "در طی ماه گذشته چند بار جهت بیدار ماندن حین رانندگی، غذا خوردن یا شرکت در فعالیت اجتماعی دچار مشکل شده‌اید؟",
268
+ "answer": {
269
+ "type": "optional",
270
+ "options": [
271
+ "هیچ مورد در طی ماه گذشته",
272
+ "کمتر از یک مورد در هفته",
273
+ "یک یا دو بار در هفته",
274
+ "سه بار یا بیشتر در هفته"
275
+ ]
276
+ },
277
+ "user_answered": "2"
278
+ },
279
+ {
280
+ "type": "text",
281
+ "text": "در طی ماه گذشته برای اینکه اشتیاق کافی جهت انجام دادن کار را در خود حفظ کنید، چقدر مشکل داشته‌اید؟",
282
+ "answer": {
283
+ "type": "optional",
284
+ "options": [
285
+ "هیچ مشکلی نداشتم",
286
+ "خیلی کم مشکل داشتم",
287
+ "تا حدی مشکل داشتم",
288
+ "مشکل زیادی داشتم"
289
+ ]
290
+ },
291
+ "user_answered": "3"
292
+ }
293
+ ],
294
+ "prerequisites": [
295
+ {
296
+ "type": "text",
297
+ "label": "gender",
298
+ "text": "جنسیت",
299
+ "answer": {
300
+ "type": "select",
301
+ "options": [
302
+ "زن",
303
+ "مرد"
304
+ ]
305
+ },
306
+ "alias": "gender",
307
+ "force": true,
308
+ "user_answered": "1"
309
+ },
310
+ {
311
+ "type": "text",
312
+ "label": "age",
313
+ "text": "سن",
314
+ "answer": {
315
+ "type": "number"
316
+ },
317
+ "alias": "age",
318
+ "force": true,
319
+ "user_answered": "35"
320
+ },
321
+ {
322
+ "type": "text",
323
+ "label": "education",
324
+ "text": "تحصیلات",
325
+ "answer": {
326
+ "type": "select",
327
+ "options": [
328
+ "ابتدایی اول",
329
+ "ابتدایی دوم",
330
+ "متوسطه اول",
331
+ "متوسطه دوم",
332
+ "دیپلم",
333
+ "کارشناسی",
334
+ "کارشناسی ارشد",
335
+ "پزشکی عمومی",
336
+ "متخصص",
337
+ "دکتری تخصصی",
338
+ "حوزوی مقدمات",
339
+ "حوزوی سطح۱",
340
+ "حوزوی سطح۲",
341
+ "حوزوی سطح۳",
342
+ "حوزوی سطح۴"
343
+ ]
344
+ },
345
+ "alias": "education",
346
+ "force": true,
347
+ "user_answered": "6"
348
+ }
349
+ ],
350
+ "cornometer": 743,
351
+ "score": {
352
+ "status": "valid",
353
+ "subjective_quality": 2,
354
+ "latency": 2,
355
+ "duration": 2,
356
+ "efficiency": 2,
357
+ "disturbances": 1,
358
+ "medication": 0,
359
+ "daytime_dysfunction": 2,
360
+ "total": 11,
361
+ "screening": "positive"
362
+ },
363
+ "score_current_version": "1.0.0",
364
+ "score_last_version": "1.0.0"
365
+ }
@@ -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,135 @@
1
+ const { Profile } = require("../Profile");
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // PSQI93 — شاخص کیفیت خواب پیتزبورگ. No Figma source: the chart was designed
5
+ // directly from the approved result specification of the science record
6
+ // (catalog/assessments/psqi, «مشخصات نتیجه فردی»), whose rules this layout
7
+ // enforces rather than decorates:
8
+ // - seven components on one shared 0–3 axis, raw only — no per-component
9
+ // class, colour or label (no evidence for any);
10
+ // - the total 0–21, drawn neutral: the screening result lives ONLY in the
11
+ // approved interpretation text — no badge, no colour, no cut line;
12
+ // - all-or-nothing: when scoring/PSQI93.py returns status "invalid" nothing
13
+ // but the no-result message is drawn — never a partial or zero score.
14
+ // Chart 863 × 674, centred on the 943 × 754 design page.
15
+ // ---------------------------------------------------------------------------
16
+ const CHART = { width: 863, height: 674 };
17
+
18
+ // Component rows: bars grow left → right from the 0 guide; the total card sits
19
+ // to the right of the chart.
20
+ const ROW = { top: 84, pitch: 44, height: 18 };
21
+ const AXIS = { zero: 162, unit: 150, max: 3 }; // 0 at x=162, 3 at x=612
22
+ const BASELINE = 13.2; // 14 px text on an 18 px row
23
+
24
+ // Total card bar, also left → right: 0 at the card's left inset, 21 at its right.
25
+ const TOTAL = { left: 713, length: 130, max: 21, cutoff: 6 };
26
+
27
+ // Interpretation panel: one fixed line pitch; the conditional false-positive
28
+ // line collapses its space when the screen is negative.
29
+ const TEXT = { top: 494, pitch: 27 };
30
+
31
+ class PSQI93 extends Profile {
32
+ static pages = 1;
33
+
34
+ labels = {
35
+ L1: { eng: "subjective_quality", title: "کیفیت ذهنی خواب" },
36
+ L2: { eng: "latency", title: "تأخیر خواب" },
37
+ L3: { eng: "duration", title: "مدت خواب" },
38
+ L4: { eng: "efficiency", title: "کارایی معمول خواب" },
39
+ L5: { eng: "disturbances", title: "اختلال‌های خواب" },
40
+ L6: { eng: "medication", title: "مصرف داروی خواب" },
41
+ L7: { eng: "daytime_dysfunction", title: "اختلال عملکرد روزانه" },
42
+ L8: { eng: "total", title: "نمره کل", max: 21 },
43
+ L9: { eng: "status" }, // valid | invalid
44
+ L10: { eng: "screening" }, // positive | negative — read, never drawn as a label
45
+ };
46
+
47
+ profileSpec = {
48
+ sample: {
49
+ name: "شاخص کیفیت خواب پیتزبورگ",
50
+ multiProfile: false,
51
+ questions: false,
52
+ defaultFields: true,
53
+ fields: [],
54
+ },
55
+ profile: {
56
+ get dimensions() {
57
+ return {
58
+ width: CHART.width + 2 * this.padding.x, // 863 → 903
59
+ height: CHART.height + 2 * this.padding.y, // 674 → 714
60
+ };
61
+ },
62
+ // Chart inset inside the 943 × 754 design page minus the 20 px the layout
63
+ // owns on every side, so the with-sidebar page renders at scale 1.
64
+ padding: {
65
+ x: (943 - CHART.width) / 2 - 20, // 20
66
+ y: (754 - CHART.height) / 2 - 20, // 20
67
+ },
68
+ },
69
+ labels: Object.values(this.labels),
70
+ };
71
+
72
+ constructor(dataset, options, config = {}) {
73
+ super();
74
+ this._init(dataset, options, config);
75
+ }
76
+
77
+ _calcContext() {
78
+ const s = this.dataset.score;
79
+ const status = s[8].mark;
80
+
81
+ // Anything but an explicit "valid" is treated as no result: a missing
82
+ // status must not let seven `?? 0` zeros read as "no sleep problem".
83
+ if (status !== "valid") return [{ valid: false }];
84
+
85
+ const items = s.slice(0, 7).map((data, i) => {
86
+ const mark = data.mark ?? 0;
87
+ const top = ROW.top + i * ROW.pitch;
88
+ const width = mark * AXIS.unit;
89
+ return {
90
+ title: data.label.title,
91
+ mark,
92
+ top,
93
+ width,
94
+ x: AXIS.zero,
95
+ baseline: top + BASELINE,
96
+ };
97
+ });
98
+
99
+ const totalMark = s[7].mark ?? 0;
100
+ const positive = totalMark >= TOTAL.cutoff;
101
+ const totalWidth = (TOTAL.length * totalMark) / TOTAL.max;
102
+
103
+ const line = (n) => TEXT.top + n * TEXT.pitch;
104
+ const falsePositive = positive ? line(3) : null;
105
+ const next = positive ? 4 : 3;
106
+
107
+ return [
108
+ {
109
+ valid: true,
110
+ items,
111
+ ticks: [0, 1, 2, 3].map((v) => ({ v, x: AXIS.zero + v * AXIS.unit })),
112
+ total: {
113
+ mark: totalMark,
114
+ max: TOTAL.max,
115
+ width: totalWidth,
116
+ x: TOTAL.left,
117
+ },
118
+ screen: {
119
+ positive,
120
+ text: positive ? "نیازمند بررسی بیشتر" : "نشانه‌ای از مشکل قابل‌توجه دیده نشد",
121
+ },
122
+ lines: {
123
+ direction: line(0),
124
+ screen: line(1),
125
+ cutoff: line(2),
126
+ falsePositive,
127
+ limits1: line(next),
128
+ limits2: line(next + 1),
129
+ },
130
+ },
131
+ ];
132
+ }
133
+ }
134
+
135
+ module.exports = PSQI93;