@baravak/risloo-profile-cli 4.84.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@baravak/risloo-profile-cli",
3
- "version": "4.84.0",
3
+ "version": "4.85.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,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,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;
@@ -0,0 +1,110 @@
1
+ {{#> layout}}
2
+
3
+ <defs>
4
+ {{! Component bar: grows left → right, so the gradient runs from the 0 guide (light)
5
+ to the tip (dark) in each bar's own bounding box. One hue for all seven — a
6
+ per-component colour would read as a class the scoring does not define. }}
7
+ <linearGradient id="psqiBar" x1="0" y1="0" x2="1" y2="0">
8
+ <stop stop-color="#A5B4FC"/>
9
+ <stop offset="1" stop-color="#4338CA"/>
10
+ </linearGradient>
11
+
12
+ {{! Total track, fully rounded; the fill is clipped to it so any width keeps the ends. }}
13
+ <clipPath id="psqiTotalTrack">
14
+ <rect x="713" y="260" width="130" height="10" rx="5"/>
15
+ </clipPath>
16
+ </defs>
17
+
18
+ <g transform="translate({{spec.profile.padding.x}}, {{spec.profile.padding.y}})">
19
+
20
+ {{#if valid}}
21
+
22
+ {{! ===================== Components: heading (over the chart, right-aligned to it) ===================== }}
23
+ <text x="660" y="24" text-anchor="start" font-size="16" font-weight="600" fill="#1E293B">مولفه‌های کیفیت خواب</text>
24
+ <text x="660" y="46" text-anchor="start" font-size="12" fill="#64748B">نمره هر مولفه از 0 تا 3؛ نمره بالاتر یعنی مشکل بیشتر</text>
25
+
26
+ {{! Axis values over the 0–3 guides, and the raw-score column header }}
27
+ {{#each ticks as |tick|}}
28
+ <text x="{{tick.x}}" y="72" text-anchor="middle" font-size="12" fill="#94A3B8">{{tick.v}}</text>
29
+ {{/each}}
30
+ <text x="644" y="72" text-anchor="middle" font-size="12" fill="#94A3B8">نمره</text>
31
+
32
+ {{! ===================== Gridlines (1, 2, 3) ===================== }}
33
+ <line x1="312.5" y1="78" x2="312.5" y2="366" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
34
+ <line x1="462.5" y1="78" x2="462.5" y2="366" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
35
+ <line x1="612.5" y1="78" x2="612.5" y2="366" stroke="#E2E8F0" stroke-linecap="round" stroke-dasharray="6 6"/>
36
+
37
+ {{! ===================== Tracks ===================== }}
38
+ {{#each items as |item|}}
39
+ <g transform="translate(162, {{item.top}})">
40
+ {{bar 450 18 (object tl=0 bl=0 tr=3 br=3) (toRad 0) fill="black" fill-opacity="0.04"}}
41
+ </g>
42
+ {{/each}}
43
+
44
+ {{! ===================== Bars ===================== }}
45
+ {{#each items as |item|}}
46
+ {{#if item.width}}
47
+ <g transform="translate({{item.x}}, {{item.top}})">
48
+ {{bar item.width 18 (object tl=0 bl=0 tr=3 br=3) (toRad 0) fill="url(#psqiBar)"}}
49
+ </g>
50
+ {{/if}}
51
+ {{/each}}
52
+
53
+ {{! 0 guide }}
54
+ <line x1="161.5" y1="78" x2="161.5" y2="366" stroke="#CBD5E1" stroke-linecap="round"/>
55
+
56
+ {{! ===================== Component names (left of the 0 guide) and raw scores (after 3) ===================== }}
57
+ {{#each items as |item|}}
58
+ <text x="150" y="{{item.baseline}}" text-anchor="start" font-size="14" fill="#334155">{{item.title}}</text>
59
+ <rect x="628.5" y="{{math item.top '+' 0.5}}" width="31" height="17" rx="4" fill="white" stroke="#6366F1"/>
60
+ <text x="644" y="{{item.baseline}}" text-anchor="middle" font-size="13" font-weight="600" fill="#3730A3">{{item.mark}}</text>
61
+ {{/each}}
62
+
63
+ {{! ===================== Total card (right of the chart) ===================== }}
64
+ <rect x="693.5" y="78.5" width="169" height="287" rx="8" fill="#F8FAFC" stroke="#E2E8F0"/>
65
+ <text x="778" y="136" text-anchor="middle" font-size="14" font-weight="600" fill="#475569">نمره کل</text>
66
+ <text x="778" y="206" text-anchor="middle" font-size="48" font-weight="700" fill="#1E293B">{{total.mark}}</text>
67
+ <text x="778" y="232" text-anchor="middle" font-size="13" fill="#64748B">از {{total.max}}</text>
68
+
69
+ {{! Neutral 0–21 bar, filling left → right; deliberately no cut mark or colour change }}
70
+ <rect x="713" y="260" width="130" height="10" rx="5" fill="#E2E8F0"/>
71
+ {{#if total.width}}
72
+ <rect x="{{total.x}}" y="260" width="{{total.width}}" height="10" fill="#475569" clip-path="url(#psqiTotalTrack)"/>
73
+ {{/if}}
74
+ <text x="713" y="288" text-anchor="middle" font-size="11" fill="#94A3B8">0</text>
75
+ <text x="843" y="288" text-anchor="middle" font-size="11" fill="#94A3B8">21</text>
76
+ <text x="778" y="322" text-anchor="middle" font-size="12" fill="#94A3B8">جمع هفت مولفه</text>
77
+
78
+ {{! ===================== Interpretation panel ===================== }}
79
+ {{! Every sentence here is approved fixed text; the screening result appears nowhere else. }}
80
+ <rect x="0.5" y="410.5" width="862" height="263" rx="8" fill="#F8FAFC" stroke="#E2E8F0"/>
81
+ <text x="843" y="452" text-anchor="start" font-size="15" font-weight="600" fill="#1E293B">تفسیر نتیجه</text>
82
+
83
+ <text x="843" y="{{lines.direction}}" text-anchor="start" font-size="13.5" fill="#334155"><tspan font-weight="600">جهت نمره</tspan>: نمره بالاتر نشان‌دهنده مشکلات بیشتر در ابعاد سنجیده‌شده است.</text>
84
+
85
+ <text x="843" y="{{lines.screen}}" text-anchor="start" font-size="13.5" fill="#334155"><tspan font-weight="600">نتیجه غربال (غربال، نه تشخیص)</tspan>: نمره کل {{total.mark}} از {{total.max}} — <tspan font-weight="600">{{screen.text}}</tspan>.</text>
86
+
87
+ <text x="843" y="{{lines.cutoff}}" text-anchor="start" font-size="13.5" fill="#334155"><tspan font-weight="600">برش غربال</tspan>: نمره کل 6 یا بیشتر؛ برش فقط بر نمره کل اعمال می‌شود و مولفه‌ها طبقه‌بندی نمی‌شوند.</text>
88
+
89
+ {{#if screen.positive}}
90
+ <text x="843" y="{{lines.falsePositive}}" text-anchor="start" font-size="13.5" fill="#334155"><tspan font-weight="600">مثبت کاذب</tspan>: ویژگی این برش 72.2 درصد است؛ بخشی از افراد بالای برش در واقع مشکل ندارند و نمره زیر برش اطلاع‌بخش‌تر است.</text>
91
+ {{/if}}
92
+
93
+ <text x="843" y="{{lines.limits1}}" text-anchor="start" font-size="13.5" fill="#334155"><tspan font-weight="600">محدودیت</tspan>: این نتیجه بر پایه گزارش خود فرد درباره ماه گذشته است و تشخیص نیست؛ ابزار تصمیم‌یار است</text>
94
+ <text x="843" y="{{lines.limits2}}" text-anchor="start" font-size="13.5" fill="#334155">و جای مصاحبه بالینی و اندازه‌گیری عینی خواب را نمی‌گیرد.</text>
95
+
96
+ {{else}}
97
+
98
+ {{! ===================== No result (all-or-nothing) ===================== }}
99
+ <rect x="181.5" y="237.5" width="500" height="199" rx="12" fill="#F8FAFC" stroke="#E2E8F0"/>
100
+ <circle cx="431.5" cy="288" r="17" fill="none" stroke="#64748B" stroke-width="2"/>
101
+ <line x1="431.5" y1="279" x2="431.5" y2="291" stroke="#64748B" stroke-width="2.5" stroke-linecap="round"/>
102
+ <circle cx="431.5" cy="297.5" r="1.6" fill="#64748B"/>
103
+ <text x="431.5" y="344" text-anchor="middle" font-size="17" font-weight="600" fill="#1E293B">نتیجه تولید نشد؛ پاسخ ناقص یا نامعتبر است</text>
104
+ <text x="431.5" y="376" text-anchor="middle" font-size="13" fill="#64748B">برای این پاسخ‌نامه هیچ نمره‌ای، حتی نمره جزئی، گزارش نمی‌شود.</text>
105
+
106
+ {{/if}}
107
+
108
+ </g>
109
+
110
+ {{/layout}}