@baravak/risloo-profile-cli 4.78.0 → 4.83.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,12 +52,55 @@ for (const { key } of DOMAINS) {
52
52
  }
53
53
  }
54
54
 
55
+ // Page 4 (profile sheet). The Chart layer is 885 × 649; every number below is
56
+ // measured from it, so the shared NEO_sheet partial stays free of profile logic.
57
+ const SHEET_GROUP_X = [54, 175, 317, 459, 601, 743];
58
+ const SHEET_COL_STEP = 21;
59
+ const SHEET_COL_HALF = 6.5;
60
+
61
+ // 0 % sits on the bottom gridline, 100 % on the top one, 500 px apart.
62
+ const SHEET_ZERO_Y = 632;
63
+ const SHEET_PX_PER_PERCENT = 5;
64
+
65
+ const SHEET_DOMAIN_STROKE = "#4338CA";
66
+ const SHEET_FACET_STROKE = "#334155";
67
+
55
68
  function clamp(value, min, max) {
56
69
  const number = Number(value);
57
70
  if (!Number.isFinite(number)) return min;
58
71
  return Math.min(max, Math.max(min, number));
59
72
  }
60
73
 
74
+ function sheetColumns() {
75
+ const columns = DOMAINS.map(({ fa, letter }, index) => ({
76
+ cx: SHEET_GROUP_X[0] + SHEET_COL_HALF + index * SHEET_COL_STEP,
77
+ code: letter,
78
+ fa,
79
+ domain: true,
80
+ }));
81
+
82
+ DOMAINS.forEach(({ key, letter }, groupIndex) => {
83
+ FACET_NAMES[key].forEach((fa, index) => {
84
+ columns.push({
85
+ cx: SHEET_GROUP_X[groupIndex + 1] + SHEET_COL_HALF + index * SHEET_COL_STEP,
86
+ code: `${letter}${index + 1}`,
87
+ fa,
88
+ domain: false,
89
+ });
90
+ });
91
+ });
92
+
93
+ return columns;
94
+ }
95
+
96
+ // One dash per axis unit; every `accentEvery`-th one is the dark, readable tick.
97
+ function sheetDashes(top, pitch, count, accentEvery) {
98
+ return Array.from({ length: count }, (unused, index) => ({
99
+ y: top + index * pitch,
100
+ dark: index % accentEvery === 0,
101
+ }));
102
+ }
103
+
61
104
  function layoutIndicators(indicators) {
62
105
  let start = 0;
63
106
 
@@ -73,11 +116,12 @@ function layoutIndicators(indicators) {
73
116
  }
74
117
 
75
118
  class NEO93 extends Profile {
76
- static pages = 3;
119
+ static pages = 4;
77
120
 
78
121
  static partials = {
79
122
  NEO_main: "NEO_main.hbs",
80
123
  NEO_long_facets: "NEO_long_facets.hbs",
124
+ NEO_sheet: "NEO_sheet.hbs",
81
125
  };
82
126
 
83
127
  labels = {
@@ -99,26 +143,35 @@ class NEO93 extends Profile {
99
143
  },
100
144
  profile: {
101
145
  get dimensions() {
102
- const [page1, page2, page3] = this.padding;
146
+ const [page1, page2, page3, page4] = this.padding;
103
147
  return [
104
148
  {
105
- width: 800 + 2 * page1.x,
106
- height: 674 + 2 * page1.y,
149
+ width: 885 + 2 * page1.x,
150
+ height: 649 + 2 * page1.y,
107
151
  },
108
152
  {
109
- width: 811 + 2 * page2.x,
110
- height: 662 + 2 * page2.y,
153
+ width: 800 + 2 * page2.x,
154
+ height: 674 + 2 * page2.y,
111
155
  },
112
156
  {
113
157
  width: 811 + 2 * page3.x,
114
- height: 458 + 2 * page3.y,
158
+ height: 662 + 2 * page3.y,
159
+ },
160
+ {
161
+ width: 811 + 2 * page4.x,
162
+ height: 458 + 2 * page4.y,
115
163
  },
116
164
  ];
117
165
  },
166
+ // Padding is the Chart's inset inside the 943 × 754 design page *minus*
167
+ // the 20 px the layout already owns on every side. Every page therefore
168
+ // resolves to 903 × 714 — the exact with-sidebar drawing area — and
169
+ // renders at scale 1 instead of being shrunk to fit.
118
170
  padding: [
119
- { x: 71.5, y: 40 },
120
- { x: 66, y: 46 },
121
- { x: 66, y: 148 },
171
+ { x: 9, y: 32.5 },
172
+ { x: 51.5, y: 20 },
173
+ { x: 46, y: 26 },
174
+ { x: 46, y: 128 },
122
175
  ],
123
176
  },
124
177
  labels: Object.values(this.labels),
@@ -247,11 +300,77 @@ class NEO93 extends Profile {
247
300
  };
248
301
  });
249
302
 
303
+ const sheetCols = sheetColumns();
304
+ const sheetY = (percentage) => SHEET_ZERO_Y - SHEET_PX_PER_PERCENT * clamp(percentage, 0, 100);
305
+ const polyline = (cols, values) => values.map((value, index) => `${cols[index].cx},${sheetY(value)}`).join(" ");
306
+
307
+ const series = [
308
+ { stroke: SHEET_DOMAIN_STROKE, points: polyline(sheetCols.slice(0, 5), items.map((item) => item.percentage)) },
309
+ ...blocks.map((block, groupIndex) => ({
310
+ stroke: SHEET_FACET_STROKE,
311
+ points: polyline(
312
+ sheetCols.slice(5 + groupIndex * 6, 11 + groupIndex * 6),
313
+ block.facets.map((facet) => facet.percentage)
314
+ ),
315
+ })),
316
+ ];
317
+
318
+ // The profile sheet opens the report; the bar pages follow it.
250
319
  return [
251
320
  {
252
321
  ...sharedContext,
253
322
  page: 1,
254
- titleAppend: titleAppend(1),
323
+ titleAppend: " - کلاسیک",
324
+ columns: sheetCols,
325
+ series,
326
+ sheet: {
327
+ grid: {
328
+ x: 31,
329
+ w: 854,
330
+ ys: [
331
+ { y: 131, faint: false },
332
+ { y: 231, faint: true },
333
+ { y: 331, faint: true },
334
+ { y: 431, faint: true },
335
+ { y: 531, faint: true },
336
+ { y: 631, faint: false },
337
+ ],
338
+ },
339
+ plot: { top: 132, bottom: 632 },
340
+ dot: { pitch: 5, accentPitch: 20, light: "#F1F5F9", accent: "#94A3B8" },
341
+ tick: { top: 121, h: 10 },
342
+ rail: {
343
+ dashLeftX: 36,
344
+ dashRightX: 873,
345
+ w: 6,
346
+ ruleLeftX: 45.5,
347
+ ruleRightX: 868.5,
348
+ ruleW: 1,
349
+ ruleTop: 131,
350
+ ruleH: 502,
351
+ dark: "#64748B",
352
+ light: "white",
353
+ },
354
+ dashes: sheetDashes(132, 5, 101, 4),
355
+ numberX: 24,
356
+ numbers: [
357
+ { y: 132, text: "100 ٪" },
358
+ { y: 232, text: "80 ٪" },
359
+ { y: 332, text: "60 ٪" },
360
+ { y: 432, text: "40 ٪" },
361
+ { y: 532, text: "20 ٪" },
362
+ { y: 632, text: "0" },
363
+ ],
364
+ levelX: 0,
365
+ levels: [],
366
+ titleBottomY: 115,
367
+ codeY: 648,
368
+ },
369
+ },
370
+ {
371
+ ...sharedContext,
372
+ page: 2,
373
+ titleAppend: titleAppend(2),
255
374
  items,
256
375
  redErrors: layoutIndicators(redErrors),
257
376
  yellowErrors: layoutIndicators(yellowErrors),
@@ -260,15 +379,15 @@ class NEO93 extends Profile {
260
379
  },
261
380
  {
262
381
  ...sharedContext,
263
- page: 2,
264
- titleAppend: titleAppend(2),
382
+ page: 3,
383
+ titleAppend: titleAppend(3),
265
384
  blocks: blocks.slice(0, 3),
266
385
  gridBottom: 658.5,
267
386
  },
268
387
  {
269
388
  ...sharedContext,
270
- page: 3,
271
- titleAppend: titleAppend(3),
389
+ page: 4,
390
+ titleAppend: titleAppend(4),
272
391
  blocks: blocks.slice(3, 5),
273
392
  gridBottom: 454.5,
274
393
  },
@@ -66,9 +66,13 @@ class NEO9A extends Profile {
66
66
  height: 674 + 2 * this.padding.y,
67
67
  };
68
68
  },
69
+ // Padding is the Chart's inset inside the 943 × 754 design page *minus*
70
+ // the 20 px the layout already owns on every side, so the page resolves
71
+ // to 903 × 714 — the exact with-sidebar drawing area — and renders at
72
+ // scale 1 instead of being shrunk to fit.
69
73
  padding: {
70
- x: 71.5,
71
- y: 40,
74
+ x: 51.5,
75
+ y: 20,
72
76
  },
73
77
  },
74
78
  labels: Object.values(this.labels),
@@ -52,12 +52,60 @@ for (const { key } of DOMAINS) {
52
52
  }
53
53
  }
54
54
 
55
+ // Page 4 (profile sheet). The Chart layer is 878 × 704; every number below is
56
+ // measured from it, so the shared NEO_sheet partial stays free of profile logic.
57
+ const SHEET_GROUP_X = [47, 168, 310, 452, 594, 736];
58
+ const SHEET_COL_STEP = 21;
59
+ const SHEET_COL_HALF = 6.5;
60
+
61
+ // The T axis runs 20…80 at a flat 9 px per point: T 20 on the bottom gridline,
62
+ // T 80 on the top one. `_lookup` floors at 20 and the norm tables top out at 80,
63
+ // but the clamp also keeps a missing key (which `?? 0` turns into 0) on the sheet.
64
+ const SHEET_MIN_T = 20;
65
+ const SHEET_MAX_T = 80;
66
+ const SHEET_MIN_T_Y = 687;
67
+ const SHEET_PX_PER_T = 9;
68
+
69
+ const SHEET_DOMAIN_STROKE = "#4338CA";
70
+ const SHEET_FACET_STROKE = "#334155";
71
+
55
72
  function clamp(value, min, max) {
56
73
  const number = Number(value);
57
74
  if (!Number.isFinite(number)) return min;
58
75
  return Math.min(max, Math.max(min, number));
59
76
  }
60
77
 
78
+ function sheetColumns() {
79
+ const columns = DOMAINS.map(({ fa, letter }, index) => ({
80
+ cx: SHEET_GROUP_X[0] + SHEET_COL_HALF + index * SHEET_COL_STEP,
81
+ code: letter,
82
+ fa,
83
+ domain: true,
84
+ }));
85
+
86
+ DOMAINS.forEach(({ key, letter }, groupIndex) => {
87
+ FACET_NAMES[key].forEach((fa, index) => {
88
+ columns.push({
89
+ cx: SHEET_GROUP_X[groupIndex + 1] + SHEET_COL_HALF + index * SHEET_COL_STEP,
90
+ code: `${letter}${index + 1}`,
91
+ fa,
92
+ domain: false,
93
+ });
94
+ });
95
+ });
96
+
97
+ return columns;
98
+ }
99
+
100
+ // One dash per T point; every `accentEvery`-th one is dark — on this form that
101
+ // marks the even T values, which are the only ones the norm tables can produce.
102
+ function sheetDashes(top, pitch, count, accentEvery) {
103
+ return Array.from({ length: count }, (unused, index) => ({
104
+ y: top + index * pitch,
105
+ dark: index % accentEvery === 0,
106
+ }));
107
+ }
108
+
61
109
  function layoutIndicators(indicators) {
62
110
  let start = 0;
63
111
 
@@ -73,11 +121,12 @@ function layoutIndicators(indicators) {
73
121
  }
74
122
 
75
123
  class NEO9Q extends Profile {
76
- static pages = 3;
124
+ static pages = 4;
77
125
 
78
126
  static partials = {
79
127
  NEO_main: "NEO_main.hbs",
80
128
  NEO_long_facets: "NEO_long_facets.hbs",
129
+ NEO_sheet: "NEO_sheet.hbs",
81
130
  };
82
131
 
83
132
  labels = {
@@ -99,26 +148,35 @@ class NEO9Q extends Profile {
99
148
  },
100
149
  profile: {
101
150
  get dimensions() {
102
- const [page1, page2, page3] = this.padding;
151
+ const [page1, page2, page3, page4] = this.padding;
103
152
  return [
104
153
  {
105
- width: 800 + 2 * page1.x,
106
- height: 674 + 2 * page1.y,
154
+ width: 878 + 2 * page1.x,
155
+ height: 704 + 2 * page1.y,
107
156
  },
108
157
  {
109
- width: 811 + 2 * page2.x,
110
- height: 668 + 2 * page2.y,
158
+ width: 800 + 2 * page2.x,
159
+ height: 674 + 2 * page2.y,
111
160
  },
112
161
  {
113
162
  width: 811 + 2 * page3.x,
114
- height: 464 + 2 * page3.y,
163
+ height: 668 + 2 * page3.y,
164
+ },
165
+ {
166
+ width: 811 + 2 * page4.x,
167
+ height: 464 + 2 * page4.y,
115
168
  },
116
169
  ];
117
170
  },
171
+ // Padding is the Chart's inset inside the 943 × 754 design page *minus*
172
+ // the 20 px the layout already owns on every side. Every page therefore
173
+ // resolves to 903 × 714 — the exact with-sidebar drawing area — and
174
+ // renders at scale 1 instead of being shrunk to fit.
118
175
  padding: [
119
- { x: 71.5, y: 40 },
120
- { x: 66, y: 43 },
121
- { x: 66, y: 145 },
176
+ { x: 12.5, y: 5 },
177
+ { x: 51.5, y: 20 },
178
+ { x: 46, y: 23 },
179
+ { x: 46, y: 125 },
122
180
  ],
123
181
  },
124
182
  labels: Object.values(this.labels),
@@ -249,11 +307,84 @@ class NEO9Q extends Profile {
249
307
  };
250
308
  });
251
309
 
310
+ const sheetCols = sheetColumns();
311
+ const sheetY = (t) => SHEET_MIN_T_Y - SHEET_PX_PER_T * (clamp(t, SHEET_MIN_T, SHEET_MAX_T) - SHEET_MIN_T);
312
+ const polyline = (cols, values) => values.map((value, index) => `${cols[index].cx},${sheetY(value)}`).join(" ");
313
+
314
+ const series = [
315
+ { stroke: SHEET_DOMAIN_STROKE, points: polyline(sheetCols.slice(0, 5), items.map((item) => item.tScore)) },
316
+ ...blocks.map((block, groupIndex) => ({
317
+ stroke: SHEET_FACET_STROKE,
318
+ points: polyline(
319
+ sheetCols.slice(5 + groupIndex * 6, 11 + groupIndex * 6),
320
+ block.facets.map((facet) => facet.tScore)
321
+ ),
322
+ })),
323
+ ];
324
+
325
+ // The profile sheet opens the report; the bar pages follow it.
252
326
  return [
253
327
  {
254
328
  ...sharedContext,
255
329
  page: 1,
256
- titleAppend: titleAppend(1),
330
+ titleAppend: " - کلاسیک",
331
+ columns: sheetCols,
332
+ series,
333
+ sheet: {
334
+ grid: {
335
+ x: 24,
336
+ w: 854,
337
+ ys: [
338
+ { y: 146, faint: false },
339
+ { y: 281, faint: true },
340
+ { y: 371, faint: true },
341
+ { y: 461, faint: true },
342
+ { y: 551, faint: true },
343
+ { y: 686, faint: false },
344
+ ],
345
+ },
346
+ plot: { top: 147, bottom: 687 },
347
+ dot: { pitch: 9, accentPitch: 18, light: "#F1F5F9", accent: "#94A3B8" },
348
+ tick: { top: 136, h: 10 },
349
+ rail: {
350
+ dashLeftX: 29,
351
+ dashRightX: 866,
352
+ w: 6,
353
+ ruleLeftX: 38,
354
+ ruleRightX: 861,
355
+ ruleW: 2,
356
+ ruleTop: 146,
357
+ ruleH: 542,
358
+ dark: "#64748B",
359
+ light: "#CBD5E1",
360
+ },
361
+ dashes: sheetDashes(147, 9, 61, 2),
362
+ numberX: 17,
363
+ numbers: [
364
+ { y: 147, text: "80" },
365
+ { y: 282, text: "65" },
366
+ { y: 372, text: "55" },
367
+ { y: 462, text: "45" },
368
+ { y: 552, text: "35" },
369
+ { y: 687, text: "20" },
370
+ ],
371
+ levelX: 9.9,
372
+ // Each band label sits midway between the two gridlines that bound it.
373
+ levels: [
374
+ { y: 214.5, text: LEVELS[5] },
375
+ { y: 327, text: LEVELS[4] },
376
+ { y: 417, text: LEVELS[3] },
377
+ { y: 507, text: LEVELS[2] },
378
+ { y: 619.5, text: LEVELS[1] },
379
+ ],
380
+ titleBottomY: 130,
381
+ codeY: 703,
382
+ },
383
+ },
384
+ {
385
+ ...sharedContext,
386
+ page: 2,
387
+ titleAppend: titleAppend(2),
257
388
  items,
258
389
  redErrors: layoutIndicators(redErrors),
259
390
  yellowErrors: layoutIndicators(yellowErrors),
@@ -262,15 +393,15 @@ class NEO9Q extends Profile {
262
393
  },
263
394
  {
264
395
  ...sharedContext,
265
- page: 2,
266
- titleAppend: titleAppend(2),
396
+ page: 3,
397
+ titleAppend: titleAppend(3),
267
398
  blocks: blocks.slice(0, 3),
268
399
  gridBottom: 664.5,
269
400
  },
270
401
  {
271
402
  ...sharedContext,
272
- page: 3,
273
- titleAppend: titleAppend(3),
403
+ page: 4,
404
+ titleAppend: titleAppend(4),
274
405
  blocks: blocks.slice(3, 5),
275
406
  gridBottom: 460.5,
276
407
  },
@@ -132,9 +132,13 @@ class NEO9V extends Profile {
132
132
  },
133
133
  ];
134
134
  },
135
+ // Padding is the Chart's inset inside the 943 × 754 design page *minus*
136
+ // the 20 px the layout already owns on every side. Both pages therefore
137
+ // resolve to 903 × 714 — the exact with-sidebar drawing area — and
138
+ // render at scale 1 instead of being shrunk to fit.
135
139
  padding: [
136
- { x: 71, y: 40 },
137
- { x: 91.5, y: 46 },
140
+ { x: 51, y: 20 },
141
+ { x: 71.5, y: 26 },
138
142
  ],
139
143
  },
140
144
  labels: Object.values(this.labels),