@media-quest/builder 0.0.22 → 0.0.24

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.
Files changed (56) hide show
  1. package/package.json +1 -1
  2. package/src/Builder-option.ts +66 -66
  3. package/src/Builder-page.spec.ts +320 -320
  4. package/src/Builder-page.ts +257 -257
  5. package/src/Builder-question.spec.ts +68 -68
  6. package/src/Builder-question.ts +101 -101
  7. package/src/Builder-schema.spec.ts +51 -0
  8. package/src/Builder-schema.ts +47 -15
  9. package/src/Builder-text.spec.ts +24 -24
  10. package/src/Builder-text.ts +57 -57
  11. package/src/BuilderMainImageDto.ts +7 -7
  12. package/src/BuilderMainText.ts +81 -81
  13. package/src/BuilderMainVideoDto.ts +10 -10
  14. package/src/BuilderObject.ts +61 -61
  15. package/src/BuilderTag.ts +97 -97
  16. package/src/builder-compiler.ts +14 -0
  17. package/src/codebook.ts +72 -72
  18. package/src/media-files.ts +28 -28
  19. package/src/primitives/page-prefix.ts +58 -58
  20. package/src/primitives/prefix.spec.ts +5 -5
  21. package/src/primitives/schema-prefix.ts +52 -52
  22. package/src/primitives/varID.ts +11 -11
  23. package/src/public-api.ts +3 -1
  24. package/src/rulebuilder/Builder-rule.spec.ts +322 -322
  25. package/src/rulebuilder/Builder-rule.ts +190 -190
  26. package/src/rulebuilder/RuleAction.ts +106 -106
  27. package/src/rulebuilder/RuleBuilder-test-utils.ts +316 -316
  28. package/src/rulebuilder/RuleInput.ts +44 -44
  29. package/src/rulebuilder/RuleVariable.ts +49 -49
  30. package/src/rulebuilder/SingleSelectItem.ts +135 -135
  31. package/src/rulebuilder/condition/Builder-condition-group.spec.ts +47 -47
  32. package/src/rulebuilder/condition/Builder-condition-group.ts +118 -118
  33. package/src/rulebuilder/condition/Builder-condition.spec.ts +195 -195
  34. package/src/rulebuilder/condition/Builder-condition.ts +208 -208
  35. package/src/rulebuilder/condition/Builder-operator.spec.ts +9 -9
  36. package/src/rulebuilder/condition/Builder-operator.ts +31 -31
  37. package/src/rulebuilder/index.ts +22 -22
  38. package/src/rulebuilder/jump-to-action-manager.ts +33 -33
  39. package/src/rulebuilder/multi-select-item.ts +73 -73
  40. package/src/rulebuilder/page-action-manager.ts +31 -31
  41. package/src/rulebuilder/rule2/Rule2.ts +211 -211
  42. package/src/rulebuilder/tag-action-manager.spec.ts +44 -44
  43. package/src/rulebuilder/tag-action-manager.ts +28 -28
  44. package/src/schema-config.ts +25 -25
  45. package/src/theme/AbstractThemeCompiler.ts +7 -7
  46. package/src/theme/IDefaultTheme.ts +226 -226
  47. package/src/theme/css-theme.ts +7 -7
  48. package/src/theme/default-theme-compiler.ts +358 -358
  49. package/src/theme/icon-urls.ts +29 -29
  50. package/src/theme/theme-utils.ts +57 -57
  51. package/src/theme/theme1.spec.ts +52 -52
  52. package/src/variable/mq-variable.spec.ts +146 -0
  53. package/src/{mq-variable.ts → variable/mq-variable.ts} +8 -1
  54. package/src/variable/sum-score.ts +138 -0
  55. package/tsconfig.json +15 -15
  56. package/tsconfig.tsbuildinfo +1 -1
@@ -1,29 +1,29 @@
1
- export const IconUrls = {
2
- ispeWhitePng:
3
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fispe-hvit.png?alt=media&token=f6fe628b-2d99-472d-a2ea-e062873e2e22',
4
- pauseSvg:
5
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause-24px.svg?alt=media&token=47337491-bb0d-4c56-9c89-a073ce19fad4',
6
- pauseCircleOutlineSvg:
7
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause_circle_outline-24px.svg?alt=media&token=133aabc8-ab9a-4eaa-9914-4f0f82c22ee2',
8
-
9
- pauseCircleFilledSvg:
10
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause_circle_filled-24px.svg?alt=media&token=af3f12c3-3199-4096-8ed2-76347b9a0a0a',
11
- playCircleRegular:
12
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay-circle-regular.svg?alt=media&token=0867b690-d7fd-475c-8e91-b2d7aeca54d1',
13
- playArrowSvg:
14
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay_arrow-24px.svg?alt=media&token=2fa95e1f-61f7-4a18-afb3-210eabae8227',
15
- playCircleOutline:
16
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay_circle_outline-24px.svg?alt=media&token=3a9f62c5-dfa2-40ef-a50e-cf1bdd2d47f2',
17
-
18
- stopCircleSvg:
19
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fstop_circle-24px.svg?alt=media&token=8fbc8b89-29bb-49ad-ae11-b8882ba3ca77',
20
- stopSvg:
21
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fstop-24px.svg?alt=media&token=d2cd41f1-1331-4243-9b7a-dc0f0ccd255d',
22
- replayCircleSvg:
23
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Freplay-24px.svg?alt=media&token=3c35ccf4-b467-4e81-85d6-b36ac64738ad',
24
-
25
- volumeOffSvg:
26
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fvolume_off-24px.svg?alt=media&token=4e41cc10-9f4b-4967-b4df-ed0682e657a9',
27
- volumeUpSvg:
28
- 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fvolume_up-24px.svg?alt=media&token=551bd0a6-a515-4f87-a245-da433f4833f9'
29
- };
1
+ export const IconUrls = {
2
+ ispeWhitePng:
3
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fispe-hvit.png?alt=media&token=f6fe628b-2d99-472d-a2ea-e062873e2e22',
4
+ pauseSvg:
5
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause-24px.svg?alt=media&token=47337491-bb0d-4c56-9c89-a073ce19fad4',
6
+ pauseCircleOutlineSvg:
7
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause_circle_outline-24px.svg?alt=media&token=133aabc8-ab9a-4eaa-9914-4f0f82c22ee2',
8
+
9
+ pauseCircleFilledSvg:
10
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fpause_circle_filled-24px.svg?alt=media&token=af3f12c3-3199-4096-8ed2-76347b9a0a0a',
11
+ playCircleRegular:
12
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay-circle-regular.svg?alt=media&token=0867b690-d7fd-475c-8e91-b2d7aeca54d1',
13
+ playArrowSvg:
14
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay_arrow-24px.svg?alt=media&token=2fa95e1f-61f7-4a18-afb3-210eabae8227',
15
+ playCircleOutline:
16
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fplay_circle_outline-24px.svg?alt=media&token=3a9f62c5-dfa2-40ef-a50e-cf1bdd2d47f2',
17
+
18
+ stopCircleSvg:
19
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fstop_circle-24px.svg?alt=media&token=8fbc8b89-29bb-49ad-ae11-b8882ba3ca77',
20
+ stopSvg:
21
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fstop-24px.svg?alt=media&token=d2cd41f1-1331-4243-9b7a-dc0f0ccd255d',
22
+ replayCircleSvg:
23
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Freplay-24px.svg?alt=media&token=3c35ccf4-b467-4e81-85d6-b36ac64738ad',
24
+
25
+ volumeOffSvg:
26
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fvolume_off-24px.svg?alt=media&token=4e41cc10-9f4b-4967-b4df-ed0682e657a9',
27
+ volumeUpSvg:
28
+ 'https://firebasestorage.googleapis.com/v0/b/ispe-backend-dev.appspot.com/o/public-assets%2Fvolume_up-24px.svg?alt=media&token=551bd0a6-a515-4f87-a245-da433f4833f9'
29
+ };
@@ -1,57 +1,57 @@
1
- import { DStyle } from "@media-quest/engine";
2
- import { DElementDto } from "@media-quest/engine";
3
-
4
- export namespace ThemeUtils {
5
- export const spaceEvenlyX = <T extends Pick<DElementDto, "style">>(
6
- items: ReadonlyArray<T>,
7
- options: { startAt: number; endAt: number; defaultItemWidth: number } = {
8
- startAt: 0,
9
- endAt: 100,
10
- defaultItemWidth: 5,
11
- },
12
- ): ReadonlyArray<T> => {
13
- const startAt = options?.startAt ?? 0;
14
- const endAt = options?.endAt ?? 100;
15
- const range = Math.abs(endAt - startAt);
16
- if (items.length === 0) {
17
- return [];
18
- }
19
- const marginCount = items.length + 1;
20
- const defaultWidth = options.defaultItemWidth ?? 150 / marginCount;
21
-
22
- let totalWidthOfElements = items.reduce((prev, curr) => {
23
- const w = curr.style.w ?? defaultWidth;
24
- return prev + w;
25
- }, 0);
26
-
27
- let cursor = startAt;
28
- const rest = Math.max(range - totalWidthOfElements, 0);
29
- const margin = rest / marginCount;
30
-
31
- items.forEach((item) => {
32
- cursor = cursor + margin;
33
- const w = item.style.w ?? defaultWidth;
34
- const x = cursor;
35
- cursor = cursor + w;
36
- item.style.w = w;
37
- item.style.x = x;
38
- });
39
-
40
- return items;
41
- };
42
-
43
- export const centerY = (): Pick<DStyle, "y" | "transform"> => ({
44
- y: 50,
45
- transform: "translate(0%, 50%)",
46
- });
47
- export const centerX = (): Pick<DStyle, "x" | "transform"> => ({
48
- x: 50,
49
- transform: "translate(-50%, 0%)",
50
- });
51
-
52
- export const centerXY = (): Pick<DStyle, "x" | "y" | "transform"> => ({
53
- x: 50,
54
- y: 50,
55
- transform: "translate(-50%, 50%)",
56
- });
57
- }
1
+ import { DStyle } from "@media-quest/engine";
2
+ import { DElementDto } from "@media-quest/engine";
3
+
4
+ export namespace ThemeUtils {
5
+ export const spaceEvenlyX = <T extends Pick<DElementDto, "style">>(
6
+ items: ReadonlyArray<T>,
7
+ options: { startAt: number; endAt: number; defaultItemWidth: number } = {
8
+ startAt: 0,
9
+ endAt: 100,
10
+ defaultItemWidth: 5,
11
+ },
12
+ ): ReadonlyArray<T> => {
13
+ const startAt = options?.startAt ?? 0;
14
+ const endAt = options?.endAt ?? 100;
15
+ const range = Math.abs(endAt - startAt);
16
+ if (items.length === 0) {
17
+ return [];
18
+ }
19
+ const marginCount = items.length + 1;
20
+ const defaultWidth = options.defaultItemWidth ?? 150 / marginCount;
21
+
22
+ let totalWidthOfElements = items.reduce((prev, curr) => {
23
+ const w = curr.style.w ?? defaultWidth;
24
+ return prev + w;
25
+ }, 0);
26
+
27
+ let cursor = startAt;
28
+ const rest = Math.max(range - totalWidthOfElements, 0);
29
+ const margin = rest / marginCount;
30
+
31
+ items.forEach((item) => {
32
+ cursor = cursor + margin;
33
+ const w = item.style.w ?? defaultWidth;
34
+ const x = cursor;
35
+ cursor = cursor + w;
36
+ item.style.w = w;
37
+ item.style.x = x;
38
+ });
39
+
40
+ return items;
41
+ };
42
+
43
+ export const centerY = (): Pick<DStyle, "y" | "transform"> => ({
44
+ y: 50,
45
+ transform: "translate(0%, 50%)",
46
+ });
47
+ export const centerX = (): Pick<DStyle, "x" | "transform"> => ({
48
+ x: 50,
49
+ transform: "translate(-50%, 0%)",
50
+ });
51
+
52
+ export const centerXY = (): Pick<DStyle, "x" | "y" | "transform"> => ({
53
+ x: 50,
54
+ y: 50,
55
+ transform: "translate(-50%, 50%)",
56
+ });
57
+ }
@@ -1,52 +1,52 @@
1
- import { ThemeUtils } from "./theme-utils";
2
- import { DStyle } from "@media-quest/engine";
3
-
4
- describe("Theme1 works", () => {
5
- test("Can space evenly when all is even", () => {
6
- const btn1 = {};
7
- const els = ThemeUtils.spaceEvenlyX<{ style: Partial<DStyle> }>([
8
- { style: { w: 20 } },
9
- { style: { w: 20 } },
10
- { style: { w: 20 } },
11
- ]);
12
-
13
- const first = els[0];
14
- const second = els[1];
15
- const third = els[2];
16
- expect(first.style.x).toBe(10);
17
- expect(second.style.x).toBe(40);
18
- expect(third.style.x).toBe(70);
19
- // responseButtons.forEach((el) => {
20
- // expect(el._tag === "div").toBe(true);
21
- // expect(el.children.length).toBe(1);
22
- // });
23
- });
24
-
25
- test("Can space evenly even if uneven lengths", () => {
26
- const btn1 = {};
27
- const els = ThemeUtils.spaceEvenlyX([
28
- { style: { w: 10, x: 0 } },
29
- { style: { w: 30, x: 10 } },
30
- { style: { w: 20, x: 30 } },
31
- ]);
32
-
33
- const first = els[0];
34
- const second = els[1];
35
- const third = els[2];
36
- expect(first.style.x).toBe(10);
37
- expect(second.style.x).toBe(30);
38
- expect(third.style.x).toBe(70);
39
- });
40
-
41
- test("Can space evenly on a shorter scale 10-90", () => {
42
- const els = ThemeUtils.spaceEvenlyX([{ style: {} }, { style: {} }, { style: {} }], {
43
- startAt: 10,
44
- endAt: 90,
45
- defaultItemWidth: 20,
46
- }) as ReadonlyArray<{ style: { x: number; w: number } }>;
47
-
48
- expect(els[0].style.x).toBe(15);
49
- // expect(els[1].style.x).toBe(10);
50
- // expect(els[2].style.x).toBe(10);
51
- });
52
- });
1
+ import { ThemeUtils } from "./theme-utils";
2
+ import { DStyle } from "@media-quest/engine";
3
+
4
+ describe("Theme1 works", () => {
5
+ test("Can space evenly when all is even", () => {
6
+ const btn1 = {};
7
+ const els = ThemeUtils.spaceEvenlyX<{ style: Partial<DStyle> }>([
8
+ { style: { w: 20 } },
9
+ { style: { w: 20 } },
10
+ { style: { w: 20 } },
11
+ ]);
12
+
13
+ const first = els[0];
14
+ const second = els[1];
15
+ const third = els[2];
16
+ expect(first.style.x).toBe(10);
17
+ expect(second.style.x).toBe(40);
18
+ expect(third.style.x).toBe(70);
19
+ // responseButtons.forEach((el) => {
20
+ // expect(el._tag === "div").toBe(true);
21
+ // expect(el.children.length).toBe(1);
22
+ // });
23
+ });
24
+
25
+ test("Can space evenly even if uneven lengths", () => {
26
+ const btn1 = {};
27
+ const els = ThemeUtils.spaceEvenlyX([
28
+ { style: { w: 10, x: 0 } },
29
+ { style: { w: 30, x: 10 } },
30
+ { style: { w: 20, x: 30 } },
31
+ ]);
32
+
33
+ const first = els[0];
34
+ const second = els[1];
35
+ const third = els[2];
36
+ expect(first.style.x).toBe(10);
37
+ expect(second.style.x).toBe(30);
38
+ expect(third.style.x).toBe(70);
39
+ });
40
+
41
+ test("Can space evenly on a shorter scale 10-90", () => {
42
+ const els = ThemeUtils.spaceEvenlyX([{ style: {} }, { style: {} }, { style: {} }], {
43
+ startAt: 10,
44
+ endAt: 90,
45
+ defaultItemWidth: 20,
46
+ }) as ReadonlyArray<{ style: { x: number; w: number } }>;
47
+
48
+ expect(els[0].style.x).toBe(15);
49
+ // expect(els[1].style.x).toBe(10);
50
+ // expect(els[2].style.x).toBe(10);
51
+ });
52
+ });
@@ -0,0 +1,146 @@
1
+ import { MqVariable } from "./mq-variable";
2
+ import { SumScoreVariable, SumScore } from "./sum-score";
3
+ const createVariable = (value: number): MqVariable => {
4
+ return {
5
+ varId: "v" + value,
6
+ label: "label for v" + value,
7
+ numericValue: value,
8
+ origin: "question",
9
+ kind: "numeric-variable",
10
+ };
11
+ };
12
+
13
+ const createSumScore = (basedOn: Array<{ variable: MqVariable; weight?: number }>) => {
14
+ const sumVar1: SumScoreVariable = {
15
+ kind: "numeric-variable",
16
+ initialValue: 0,
17
+ min: 0,
18
+ origin: "sum-score",
19
+ label: "Sum 123",
20
+ varId: "sum-score-variable-id",
21
+ basedOn: basedOn.map((v) => ({ varId: v.variable.varId, weight: v.weight })),
22
+ };
23
+ return sumVar1;
24
+ };
25
+
26
+ const v0 = createVariable(0);
27
+ const v1 = createVariable(1);
28
+ const v2 = createVariable(2);
29
+ const v3 = createVariable(3);
30
+ const v4 = createVariable(4);
31
+ const v5 = createVariable(5);
32
+ const v6 = createVariable(6);
33
+ const v7 = createVariable(7);
34
+ const v8 = createVariable(8);
35
+ const v9 = createVariable(9);
36
+
37
+ const all = [v0, v1, v2, v3, v4, v5, v6, v7, v8, v9];
38
+ describe("Sum score variable.", () => {
39
+ test("Is allowed SumScoreValue", () => {
40
+ expect(SumScore.isAllowedValue(-1)).toBeFalsy();
41
+ expect(SumScore.isAllowedValue(0)).toBeTruthy();
42
+ expect(SumScore.isAllowedValue(1)).toBeTruthy();
43
+ expect(SumScore.isAllowedValue(2)).toBeTruthy();
44
+ expect(SumScore.isAllowedValue(3)).toBeTruthy();
45
+ expect(SumScore.isAllowedValue(4)).toBeTruthy();
46
+ expect(SumScore.isAllowedValue(5)).toBeTruthy();
47
+ expect(SumScore.isAllowedValue(6)).toBeTruthy();
48
+ expect(SumScore.isAllowedValue(7)).toBeTruthy();
49
+ expect(SumScore.isAllowedValue(8)).toBeTruthy();
50
+ expect(SumScore.isAllowedValue(9)).toBeFalsy();
51
+ expect(SumScore.isAllowedValue(999)).toBeFalsy();
52
+ });
53
+
54
+ test("Will calculate 2", () => {
55
+ const ss1 = createSumScore([
56
+ { variable: v1, weight: 1 },
57
+ { variable: v2, weight: 1 },
58
+ { variable: v9, weight: 1 },
59
+ ]);
60
+ const score = SumScore.calculate(ss1, all);
61
+ expect(score.sumScore).toBe(3);
62
+ expect(score.basedOn.length).toBe(3);
63
+ expect(score.avg).toBe(1.5);
64
+ expect(score.skippedBy9Count).toBe(1);
65
+ });
66
+ test("Will calculate 5 values", () => {
67
+ const ss1 = createSumScore([
68
+ { variable: v1, weight: 1 },
69
+ { variable: v2, weight: 1 },
70
+ { variable: v4, weight: 1 },
71
+ { variable: v5, weight: 1 },
72
+ { variable: v6, weight: 1 },
73
+ { variable: v9, weight: 0.5 },
74
+ ]);
75
+ const score = SumScore.calculate(ss1, all);
76
+ expect(score.sumScore).toBe(18);
77
+ expect(score.avg).toBe(3.6);
78
+ const basedOn1 = score.basedOn[0];
79
+ expect(basedOn1.value).toBe(v1.numericValue);
80
+ expect(basedOn1.weight).toBe(1);
81
+ expect(basedOn1.varId).toBe(v1.varId);
82
+ expect(basedOn1.varLabel).toBe(v1.label);
83
+ });
84
+ test("Will not include 9 in includedAnswersCount", () => {
85
+ const ss1 = createSumScore([
86
+ { variable: v0, weight: 1 },
87
+ { variable: v1, weight: 1 },
88
+ { variable: v5, weight: 1 },
89
+ { variable: v9, weight: 1 },
90
+ ]);
91
+ const score = SumScore.calculate(ss1, all);
92
+ expect(score.sumScore).toBe(6);
93
+ expect(score.avg).toBe(2);
94
+ expect(score.includedAnswerCount).toBe(3);
95
+ });
96
+ test("Will calculate weight", () => {
97
+ const ss1 = createSumScore([
98
+ { variable: v1, weight: 1 },
99
+ { variable: v8, weight: 0.5 },
100
+ ]);
101
+ const score = SumScore.calculate(ss1, all);
102
+ expect(score.sumScore).toBe(5);
103
+ });
104
+ test("Will calculate many weighted variables.", () => {
105
+ const ss1 = createSumScore([
106
+ { variable: v1, weight: 1 },
107
+ { variable: v2, weight: 0.5 },
108
+ { variable: v5, weight: 1.2 },
109
+ { variable: v8, weight: 2 },
110
+ ]);
111
+ const score = SumScore.calculate(ss1, all);
112
+ expect(score.sumScore).toBe(24);
113
+ });
114
+ test("Will count missing answers (missingAnswersCount)", () => {
115
+ const ss1 = createSumScore([
116
+ { variable: v1 },
117
+ { variable: v2 },
118
+ { variable: v3 },
119
+ { variable: v5 },
120
+ { variable: v9 },
121
+ ]);
122
+ const missing5InDataSet = [v1, v2, v8];
123
+ const score = SumScore.calculate(ss1, missing5InDataSet);
124
+ expect(score.sumScore).toBe(3);
125
+ expect(score.includedAnswerCount).toBe(2);
126
+ expect(score.missingAnswerCount).toBe(3);
127
+ expect(score.avg).toBe(1.5);
128
+ expect(score.basedOn.length).toBe(2);
129
+ expect(score.avg).toBe(1.5);
130
+ expect(score.errorMessages.length).toBe(0);
131
+ });
132
+ test("includedAnswerCount + missingAnswerCount + skippedBy9Count = SumScoreVariable.basedOn.length", () => {
133
+ const ss1 = createSumScore([
134
+ { variable: v0 },
135
+ { variable: v1 },
136
+ { variable: v2 },
137
+ { variable: v9 },
138
+ ]);
139
+ const answers = [v1, v7, v8, v9];
140
+ const score = SumScore.calculate(ss1, answers);
141
+
142
+ expect(score.includedAnswerCount + score.missingAnswerCount + score.skippedBy9Count).toBe(
143
+ ss1.basedOn.length,
144
+ );
145
+ });
146
+ });
@@ -3,6 +3,7 @@ export interface MqVariable {
3
3
 
4
4
  label: string;
5
5
  varId: string;
6
+ description?: string;
6
7
 
7
8
  // Values - The actual value of the variable.
8
9
  stringValue?: string;
@@ -40,9 +41,16 @@ export interface MqVariable {
40
41
  rangeFloor?: number;
41
42
  rangeCeiling?: number;
42
43
 
44
+ // Analytics
43
45
  options?: Array<{ label: string; value: number }>;
46
+ basedOn?: Array<{ varId: string; weight?: number }>;
44
47
  }
45
48
 
49
+ const MISSING = 999;
50
+ export const MqVariable = {
51
+ MISSING,
52
+ } as const;
53
+
46
54
  export interface PageVariable extends MqVariable {
47
55
  readonly pageId: string;
48
56
  readonly pagePrefix: string;
@@ -56,6 +64,5 @@ export interface PredefinedVariable extends MqVariable {
56
64
  readonly modulePrefix: string;
57
65
  readonly moduleID: string;
58
66
  defaultValue: number;
59
- requireManualSelection: boolean;
60
67
  options: Array<{ label: string; value: number }>;
61
68
  }
@@ -0,0 +1,138 @@
1
+ import { MqVariable } from "./mq-variable";
2
+
3
+ export interface SumScoreVariable extends MqVariable {
4
+ readonly origin: "sum-score";
5
+ useAvg?: boolean;
6
+ /**
7
+ * All variables that the sum-score should be based on.
8
+ */
9
+ basedOn: Array<{ varId: string; weight?: number }>;
10
+ }
11
+
12
+ /**
13
+ *
14
+ * A constant Array that contains all legal
15
+ */
16
+ const ALLOWED_VALUES = [0, 1, 2, 3, 4, 5, 6, 7, 8];
17
+ const isAllowedValue = (num: number) => {
18
+ const notNegative = num >= 0;
19
+ const lessThanNine = num < 9;
20
+ return notNegative && lessThanNine;
21
+ };
22
+
23
+ type BasedOnEntry =
24
+ | { kind: "missing"; varId: string }
25
+ | { kind: "invalid-variable"; varId: string; message: string }
26
+ | { kind: "has-value"; varId: string; value: number; weight: number; varLabel: string };
27
+
28
+ export interface SumScore {
29
+ sumScore: number;
30
+ avg: number; // Alle besvarte spørsmål som ikke er 9.
31
+ useAvg: boolean;
32
+ includedAnswerCount: number;
33
+ missingAnswerCount: number;
34
+ skippedBy9Count: number; // Antall vetikke / hopp over
35
+ basedOn: Array<{ varId: string; value: number; weight: number; varLabel: string }>;
36
+ errorMessages: string[];
37
+ }
38
+
39
+ const calculate = (
40
+ sumScoreVariable: SumScoreVariable,
41
+ allVariables: Array<Pick<MqVariable, "varId" | "numericValue" | "kind" | "label">>,
42
+ ): SumScore => {
43
+ const legalValues: Array<number> = [...ALLOWED_VALUES];
44
+
45
+ // CALCULATE THESE!!
46
+ let includedAnswerCount = 0;
47
+ let skippedBy9Count = 0;
48
+ let missingAnswerCount = 0;
49
+ let sumScore = 0;
50
+ const errorMessages: string[] = [];
51
+ const basedOn: SumScore["basedOn"] = [];
52
+ const useAvg = !!sumScoreVariable.useAvg;
53
+
54
+ const basedOnEntries: BasedOnEntry[] = sumScoreVariable.basedOn.map((scv) => {
55
+ const maybeVariable = allVariables.find((v) => v.varId === scv.varId);
56
+ let result: BasedOnEntry = { kind: "missing", varId: scv.varId };
57
+ if (!maybeVariable) {
58
+ return result;
59
+ }
60
+
61
+ if (maybeVariable.kind !== "numeric-variable") {
62
+ result = {
63
+ kind: "invalid-variable",
64
+ varId: scv.varId,
65
+ message: "The actual variable is not a numeric-variable",
66
+ };
67
+ return result;
68
+ }
69
+ const value = maybeVariable.numericValue ?? MqVariable.MISSING;
70
+ const varLabel = maybeVariable.label;
71
+ const weight = scv.weight ?? 1;
72
+ const varId = maybeVariable.varId;
73
+
74
+ result = {
75
+ kind: "has-value",
76
+ varId,
77
+ weight,
78
+ value,
79
+ varLabel,
80
+ };
81
+
82
+ return result;
83
+ });
84
+
85
+ basedOnEntries.forEach((entry) => {
86
+ if (entry.kind === "missing") {
87
+ missingAnswerCount++;
88
+ }
89
+ if (entry.kind === "has-value") {
90
+ const { varId, varLabel, weight, value } = { ...entry };
91
+ const isAllowed = isAllowedValue(value);
92
+ if (isAllowed) {
93
+ basedOn.push({ varId, weight, value, varLabel });
94
+ sumScore += entry.value * entry.weight;
95
+ includedAnswerCount++;
96
+ }
97
+ if (value === 9) {
98
+ skippedBy9Count++;
99
+ basedOn.push({ varId, weight, value, varLabel });
100
+ }
101
+ }
102
+ if (entry.kind === "invalid-variable") {
103
+ errorMessages.push(entry.message);
104
+ }
105
+ });
106
+
107
+ const avg = sumScore / includedAnswerCount;
108
+ const result: SumScore = {
109
+ avg,
110
+ useAvg,
111
+ includedAnswerCount,
112
+ skippedBy9Count,
113
+ sumScore,
114
+ basedOn,
115
+ missingAnswerCount,
116
+ errorMessages,
117
+ };
118
+ // Calculate avg
119
+
120
+ return result;
121
+ };
122
+ const createVariable = (varId: string): SumScoreVariable => {
123
+ return {
124
+ origin: "sum-score",
125
+ kind: "numeric-variable",
126
+ initialValue: 0,
127
+ min: 0,
128
+ label: "Sum 123",
129
+ varId: "sum-score-variable-id",
130
+ basedOn: [],
131
+ };
132
+ };
133
+ export const SumScore = {
134
+ ALLOWED_VALUES,
135
+ calculate,
136
+ isAllowedValue,
137
+ createVariable,
138
+ };
package/tsconfig.json CHANGED
@@ -1,15 +1,15 @@
1
- {
2
- "compilerOptions": {
3
- "target": "es2022",
4
- "module": "commonjs",
5
- "moduleResolution": "node",
6
- "declaration": true,
7
- "strict": true,
8
- "esModuleInterop": true,
9
- "skipLibCheck": true,
10
- "forceConsistentCasingInFileNames": true,
11
- "rootDir": "./src",
12
- "composite": true,
13
- "outDir": "./dist"
14
- },
15
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2022",
4
+ "module": "commonjs",
5
+ "moduleResolution": "node",
6
+ "declaration": true,
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "rootDir": "./src",
12
+ "composite": true,
13
+ "outDir": "./dist"
14
+ },
15
+ }