volleyballsimtypes 0.0.482 → 0.0.485

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 (119) hide show
  1. package/dist/cjs/src/api/index.d.ts +3 -17
  2. package/dist/cjs/src/data/models/tactics.d.ts +7 -17
  3. package/dist/cjs/src/data/transformers/match-preset-override.test.js +10 -1
  4. package/dist/cjs/src/data/transformers/rally-event.d.ts +2 -1
  5. package/dist/cjs/src/data/transformers/rally-event.js +12 -0
  6. package/dist/cjs/src/data/transformers/rally.js +2 -0
  7. package/dist/cjs/src/data/transformers/rally.test.d.ts +1 -0
  8. package/dist/cjs/src/data/transformers/rally.test.js +62 -0
  9. package/dist/cjs/src/data/transformers/tactics.js +26 -54
  10. package/dist/cjs/src/data/transformers/tactics.test.js +16 -11
  11. package/dist/cjs/src/service/competition/season.d.ts +2 -3
  12. package/dist/cjs/src/service/competition/season.js +0 -11
  13. package/dist/cjs/src/service/event/block.d.ts +0 -1
  14. package/dist/cjs/src/service/event/block.js +0 -6
  15. package/dist/cjs/src/service/event/free-ball.d.ts +0 -1
  16. package/dist/cjs/src/service/event/free-ball.js +0 -5
  17. package/dist/cjs/src/service/event/in-play-event.d.ts +0 -1
  18. package/dist/cjs/src/service/event/in-play-event.js +0 -8
  19. package/dist/cjs/src/service/event/libero-replacement.d.ts +0 -1
  20. package/dist/cjs/src/service/event/libero-replacement.js +0 -7
  21. package/dist/cjs/src/service/event/rally-event.d.ts +0 -1
  22. package/dist/cjs/src/service/event/reception.d.ts +0 -1
  23. package/dist/cjs/src/service/event/reception.js +0 -5
  24. package/dist/cjs/src/service/event/serve.d.ts +0 -1
  25. package/dist/cjs/src/service/event/serve.js +0 -5
  26. package/dist/cjs/src/service/event/set.d.ts +0 -1
  27. package/dist/cjs/src/service/event/set.js +0 -6
  28. package/dist/cjs/src/service/event/spike.d.ts +0 -1
  29. package/dist/cjs/src/service/event/spike.js +0 -5
  30. package/dist/cjs/src/service/event/substitution.d.ts +0 -1
  31. package/dist/cjs/src/service/event/substitution.js +0 -7
  32. package/dist/cjs/src/service/player/improvement.d.ts +0 -1
  33. package/dist/cjs/src/service/player/improvement.js +0 -9
  34. package/dist/cjs/src/service/player/improvement.test.js +0 -15
  35. package/dist/cjs/src/service/team/designated-sub.d.ts +2 -4
  36. package/dist/cjs/src/service/team/energy-band.d.ts +0 -1
  37. package/dist/cjs/src/service/team/energy-band.js +1 -2
  38. package/dist/cjs/src/service/team/formation.d.ts +0 -1
  39. package/dist/cjs/src/service/team/formation.js +0 -3
  40. package/dist/cjs/src/service/team/index.d.ts +1 -0
  41. package/dist/cjs/src/service/team/index.js +1 -0
  42. package/dist/cjs/src/service/team/libero-sub.d.ts +0 -4
  43. package/dist/cjs/src/service/team/pinch-condition.d.ts +11 -3
  44. package/dist/cjs/src/service/team/pinch-condition.js +9 -4
  45. package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +30 -32
  46. package/dist/cjs/src/service/team/schemas/designated-sub.z.js +27 -23
  47. package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +4 -2
  48. package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +15 -34
  49. package/dist/cjs/src/service/team/schemas/libero-sub.z.js +10 -26
  50. package/dist/cjs/src/service/team/schemas/libero-sub.z.test.js +74 -35
  51. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +28 -36
  52. package/dist/cjs/src/service/team/schemas/tactics.z.js +4 -3
  53. package/dist/cjs/src/service/team/schemas/team.z.d.ts +28 -36
  54. package/dist/cjs/src/service/team/sub-config-convert.d.ts +13 -0
  55. package/dist/cjs/src/service/team/sub-config-convert.js +91 -0
  56. package/dist/cjs/src/service/team/sub-config-convert.test.d.ts +1 -0
  57. package/dist/cjs/src/service/team/sub-config-convert.test.js +84 -0
  58. package/dist/cjs/src/service/utils/object-utils.d.ts +0 -1
  59. package/dist/cjs/src/service/utils/object-utils.js +0 -2
  60. package/dist/esm/src/api/index.d.ts +3 -17
  61. package/dist/esm/src/data/models/tactics.d.ts +7 -17
  62. package/dist/esm/src/data/transformers/match-preset-override.test.js +10 -1
  63. package/dist/esm/src/data/transformers/rally-event.d.ts +2 -1
  64. package/dist/esm/src/data/transformers/rally-event.js +12 -1
  65. package/dist/esm/src/data/transformers/rally.js +4 -2
  66. package/dist/esm/src/data/transformers/rally.test.d.ts +1 -0
  67. package/dist/esm/src/data/transformers/rally.test.js +60 -0
  68. package/dist/esm/src/data/transformers/tactics.js +27 -55
  69. package/dist/esm/src/data/transformers/tactics.test.js +16 -11
  70. package/dist/esm/src/service/competition/season.d.ts +2 -3
  71. package/dist/esm/src/service/competition/season.js +0 -11
  72. package/dist/esm/src/service/event/block.d.ts +0 -1
  73. package/dist/esm/src/service/event/block.js +0 -6
  74. package/dist/esm/src/service/event/free-ball.d.ts +0 -1
  75. package/dist/esm/src/service/event/free-ball.js +0 -5
  76. package/dist/esm/src/service/event/in-play-event.d.ts +0 -1
  77. package/dist/esm/src/service/event/in-play-event.js +0 -8
  78. package/dist/esm/src/service/event/libero-replacement.d.ts +0 -1
  79. package/dist/esm/src/service/event/libero-replacement.js +0 -7
  80. package/dist/esm/src/service/event/rally-event.d.ts +0 -1
  81. package/dist/esm/src/service/event/reception.d.ts +0 -1
  82. package/dist/esm/src/service/event/reception.js +0 -5
  83. package/dist/esm/src/service/event/serve.d.ts +0 -1
  84. package/dist/esm/src/service/event/serve.js +0 -5
  85. package/dist/esm/src/service/event/set.d.ts +0 -1
  86. package/dist/esm/src/service/event/set.js +0 -6
  87. package/dist/esm/src/service/event/spike.d.ts +0 -1
  88. package/dist/esm/src/service/event/spike.js +0 -5
  89. package/dist/esm/src/service/event/substitution.d.ts +0 -1
  90. package/dist/esm/src/service/event/substitution.js +0 -7
  91. package/dist/esm/src/service/player/improvement.d.ts +0 -1
  92. package/dist/esm/src/service/player/improvement.js +0 -8
  93. package/dist/esm/src/service/player/improvement.test.js +1 -16
  94. package/dist/esm/src/service/team/designated-sub.d.ts +2 -4
  95. package/dist/esm/src/service/team/energy-band.d.ts +0 -1
  96. package/dist/esm/src/service/team/energy-band.js +0 -1
  97. package/dist/esm/src/service/team/formation.d.ts +0 -1
  98. package/dist/esm/src/service/team/formation.js +0 -3
  99. package/dist/esm/src/service/team/index.d.ts +1 -0
  100. package/dist/esm/src/service/team/index.js +1 -0
  101. package/dist/esm/src/service/team/libero-sub.d.ts +0 -4
  102. package/dist/esm/src/service/team/pinch-condition.d.ts +11 -3
  103. package/dist/esm/src/service/team/pinch-condition.js +9 -4
  104. package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +30 -32
  105. package/dist/esm/src/service/team/schemas/designated-sub.z.js +26 -22
  106. package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +4 -2
  107. package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +15 -34
  108. package/dist/esm/src/service/team/schemas/libero-sub.z.js +9 -25
  109. package/dist/esm/src/service/team/schemas/libero-sub.z.test.js +75 -36
  110. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +28 -36
  111. package/dist/esm/src/service/team/schemas/tactics.z.js +4 -3
  112. package/dist/esm/src/service/team/schemas/team.z.d.ts +28 -36
  113. package/dist/esm/src/service/team/sub-config-convert.d.ts +13 -0
  114. package/dist/esm/src/service/team/sub-config-convert.js +85 -0
  115. package/dist/esm/src/service/team/sub-config-convert.test.d.ts +1 -0
  116. package/dist/esm/src/service/team/sub-config-convert.test.js +82 -0
  117. package/dist/esm/src/service/utils/object-utils.d.ts +0 -1
  118. package/dist/esm/src/service/utils/object-utils.js +1 -1
  119. package/package.json +1 -1
@@ -1,86 +1,125 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
- import { LiberoSetSubConfigSchema, LiberoSubConfigSchema, LiberoSubModeSchema } from './libero-sub.z';
2
+ import { LiberoSetSubConfigSchema, LiberoSubConfigSchema } from './libero-sub.z';
3
3
  import { EnergyBand } from '../energy-band';
4
4
  import { MatchPhase, PinchConditionType } from '../pinch-condition';
5
- describe('LiberoSubModeSchema', () => {
6
- it('accepts NEVER, FATIGUE, ALWAYS and CONDITIONS', () => {
7
- for (const mode of ['NEVER', 'FATIGUE', 'ALWAYS', 'CONDITIONS']) {
8
- expect(LiberoSubModeSchema.safeParse(mode).success).toBe(true);
9
- }
10
- });
11
- it('rejects an unknown mode', () => {
12
- expect(LiberoSubModeSchema.safeParse('SOMETIMES').success).toBe(false);
5
+ // Unified condition engine (owner 2026-07-11): the libero rule is a pure condition list; legacy mode-shaped
6
+ // configs (NEVER/FATIGUE/ALWAYS/CONDITIONS) auto-convert in the schema preprocess.
7
+ describe('LiberoSetSubConfigSchema (unified)', () => {
8
+ it('accepts an empty config (no conditions = the starting libero always plays)', () => {
9
+ const res = LiberoSetSubConfigSchema.safeParse({});
10
+ expect(res.success).toBe(true);
11
+ if (res.success)
12
+ expect(res.data.conditions ?? []).toHaveLength(0);
13
13
  });
14
- });
15
- describe('LiberoSetSubConfigSchema', () => {
16
- it('accepts a mode with no band', () => {
17
- expect(LiberoSetSubConfigSchema.safeParse({ mode: 'ALWAYS' }).success).toBe(true);
14
+ it('accepts a FATIGUE condition with a band', () => {
15
+ const res = LiberoSetSubConfigSchema.safeParse({
16
+ conditions: [{ type: PinchConditionType.FATIGUE, band: EnergyBand.TIRED }]
17
+ });
18
+ expect(res.success).toBe(true);
18
19
  });
19
- it('accepts a fatigue mode with a band', () => {
20
- expect(LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: EnergyBand.TIRED }).success).toBe(true);
20
+ it('accepts a FATIGUE condition without a band (team default)', () => {
21
+ expect(LiberoSetSubConfigSchema.safeParse({ conditions: [{ type: PinchConditionType.FATIGUE }] }).success).toBe(true);
21
22
  });
22
- it('rejects an unknown band', () => {
23
- expect(LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: 'SLEEPY' }).success).toBe(false);
23
+ it('rejects a FATIGUE condition with an unknown band', () => {
24
+ expect(LiberoSetSubConfigSchema.safeParse({ conditions: [{ type: PinchConditionType.FATIGUE, band: 'SLEEPY' }] }).success).toBe(false);
24
25
  });
25
- it('accepts CONDITIONS with a PHASE condition (serve/receive split)', () => {
26
+ it('accepts a PHASE condition (serve/receive split)', () => {
26
27
  const res = LiberoSetSubConfigSchema.safeParse({
27
- mode: 'CONDITIONS',
28
28
  conditionLogic: 'ALL',
29
29
  conditions: [{ type: PinchConditionType.PHASE, phase: MatchPhase.RECEIVING }]
30
30
  });
31
31
  expect(res.success).toBe(true);
32
32
  });
33
- it('accepts CONDITIONS mixing a pinch condition and a PHASE condition', () => {
33
+ it('accepts mixed conditions (score + phase + fatigue) under ANY logic', () => {
34
34
  const res = LiberoSetSubConfigSchema.safeParse({
35
- mode: 'CONDITIONS',
36
35
  conditionLogic: 'ANY',
37
36
  conditions: [
38
37
  { type: PinchConditionType.MIN_OWN_SCORE, score: 20 },
39
- { type: PinchConditionType.PHASE, phase: MatchPhase.SERVING }
38
+ { type: PinchConditionType.PHASE, phase: MatchPhase.SERVING },
39
+ { type: PinchConditionType.FATIGUE, band: EnergyBand.EXHAUSTED }
40
40
  ]
41
41
  });
42
42
  expect(res.success).toBe(true);
43
43
  });
44
- it('rejects CONDITIONS mode with no conditions', () => {
45
- expect(LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS' }).success).toBe(false);
46
- expect(LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS', conditions: [] }).success).toBe(false);
47
- });
48
44
  it('rejects an invalid PHASE value', () => {
49
45
  const res = LiberoSetSubConfigSchema.safeParse({
50
- mode: 'CONDITIONS',
51
46
  conditions: [{ type: PinchConditionType.PHASE, phase: 'ATTACKING' }]
52
47
  });
53
48
  expect(res.success).toBe(false);
54
49
  });
55
50
  });
51
+ describe('LiberoSetSubConfigSchema (legacy conversion)', () => {
52
+ it('converts legacy NEVER to an empty condition list', () => {
53
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'NEVER' });
54
+ expect(res.success).toBe(true);
55
+ if (res.success)
56
+ expect(res.data.conditions).toEqual([]);
57
+ });
58
+ it('converts legacy ALWAYS to a single ASAP condition', () => {
59
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'ALWAYS' });
60
+ expect(res.success).toBe(true);
61
+ if (res.success)
62
+ expect(res.data.conditions).toEqual([{ type: PinchConditionType.ASAP }]);
63
+ });
64
+ it('converts legacy FATIGUE + band to a FATIGUE condition carrying the band', () => {
65
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: EnergyBand.WORN });
66
+ expect(res.success).toBe(true);
67
+ if (res.success)
68
+ expect(res.data.conditions).toEqual([{ type: PinchConditionType.FATIGUE, band: EnergyBand.WORN }]);
69
+ });
70
+ it('converts legacy FATIGUE without a band to a bandless FATIGUE condition (team default)', () => {
71
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'FATIGUE' });
72
+ expect(res.success).toBe(true);
73
+ if (res.success)
74
+ expect(res.data.conditions).toEqual([{ type: PinchConditionType.FATIGUE }]);
75
+ });
76
+ it('converts legacy CONDITIONS by stripping the mode and keeping the conditions', () => {
77
+ const conditions = [{ type: PinchConditionType.PHASE, phase: MatchPhase.RECEIVING }];
78
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS', conditionLogic: 'ALL', conditions });
79
+ expect(res.success).toBe(true);
80
+ if (res.success) {
81
+ expect(res.data.conditions).toEqual(conditions);
82
+ expect(res.data.mode).toBeUndefined();
83
+ }
84
+ });
85
+ it('heals a legacy CONDITIONS config with no conditions to never (empty list) instead of rejecting', () => {
86
+ const res = LiberoSetSubConfigSchema.safeParse({ mode: 'CONDITIONS' });
87
+ expect(res.success).toBe(true);
88
+ if (res.success)
89
+ expect(res.data.conditions ?? []).toHaveLength(0);
90
+ });
91
+ });
56
92
  describe('LiberoSubConfigSchema', () => {
57
- it('accepts a flat config with no per-set overrides', () => {
93
+ it('accepts a flat config with no per-set overrides (legacy shape converts)', () => {
58
94
  expect(LiberoSubConfigSchema.safeParse({ mode: 'FATIGUE', fatigueBand: EnergyBand.WORN }).success).toBe(true);
59
95
  });
60
- it('accepts up to five per-set configs', () => {
96
+ it('accepts up to five per-set configs (legacy per-set entries convert)', () => {
61
97
  const res = LiberoSubConfigSchema.safeParse({
62
98
  mode: 'NEVER',
63
99
  sets: [
64
100
  { mode: 'NEVER' },
65
101
  { mode: 'ALWAYS' },
66
102
  { mode: 'FATIGUE', fatigueBand: EnergyBand.TIRED },
67
- { mode: 'NEVER' },
68
- { mode: 'ALWAYS' }
103
+ { conditions: [{ type: PinchConditionType.FATIGUE }] },
104
+ {}
69
105
  ]
70
106
  });
71
107
  expect(res.success).toBe(true);
108
+ if (res.success) {
109
+ expect(res.data.sets?.[0].conditions).toEqual([]);
110
+ expect(res.data.sets?.[1].conditions).toEqual([{ type: PinchConditionType.ASAP }]);
111
+ expect(res.data.sets?.[2].conditions).toEqual([{ type: PinchConditionType.FATIGUE, band: EnergyBand.TIRED }]);
112
+ }
72
113
  });
73
114
  it('rejects more than five per-set configs', () => {
74
115
  const res = LiberoSubConfigSchema.safeParse({
75
- mode: 'NEVER',
76
- sets: Array.from({ length: 6 }, () => ({ mode: 'NEVER' }))
116
+ sets: Array.from({ length: 6 }, () => ({}))
77
117
  });
78
118
  expect(res.success).toBe(false);
79
119
  });
80
- it('rejects an unknown mode in a per-set config', () => {
120
+ it('rejects an unknown condition type in a per-set config', () => {
81
121
  const res = LiberoSubConfigSchema.safeParse({
82
- mode: 'NEVER',
83
- sets: [{ mode: 'WHENEVER' }]
122
+ sets: [{ conditions: [{ type: 'WHENEVER' }] }]
84
123
  });
85
124
  expect(res.success).toBe(false);
86
125
  });
@@ -15,16 +15,9 @@ export declare const TacticsInputSchema: z.ZodObject<{
15
15
  }, z.core.$strip>;
16
16
  liberoReplacements: z.ZodArray<z.ZodCustom<Player, Player>>;
17
17
  receiveRotationOffset: z.ZodDefault<z.ZodBoolean>;
18
- designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
19
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
20
- mode: z.ZodEnum<{
21
- NEVER: "NEVER";
22
- FATIGUE: "FATIGUE";
23
- PINCH: "PINCH";
24
- }>;
18
+ designatedSubs: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
19
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
25
20
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
26
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
27
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
28
21
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
29
22
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
30
23
  }, z.core.$strip>, z.ZodObject<{
@@ -56,9 +49,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
56
49
  }, z.core.$strip>, z.ZodObject<{
57
50
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
58
51
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
52
+ }, z.core.$strip>, z.ZodObject<{
53
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
54
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
59
55
  }, z.core.$strip>], "type">>>;
60
56
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
61
57
  pinchServer: z.ZodOptional<z.ZodBoolean>;
58
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
62
59
  subBackMode: z.ZodOptional<z.ZodEnum<{
63
60
  FATIGUE: "FATIGUE";
64
61
  CONDITIONS: "CONDITIONS";
@@ -94,17 +91,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
94
91
  }, z.core.$strip>, z.ZodObject<{
95
92
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
96
93
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
94
+ }, z.core.$strip>, z.ZodObject<{
95
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
96
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
97
97
  }, z.core.$strip>], "type">>>;
98
98
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
99
- }, z.core.$strip>>>;
100
- mode: z.ZodEnum<{
101
- NEVER: "NEVER";
102
- FATIGUE: "FATIGUE";
103
- PINCH: "PINCH";
104
- }>;
99
+ }, z.core.$strip>>>>;
105
100
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
106
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
107
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
108
101
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
109
102
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
110
103
  }, z.core.$strip>, z.ZodObject<{
@@ -136,9 +129,13 @@ export declare const TacticsInputSchema: z.ZodObject<{
136
129
  }, z.core.$strip>, z.ZodObject<{
137
130
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
138
131
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
132
+ }, z.core.$strip>, z.ZodObject<{
133
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
134
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
139
135
  }, z.core.$strip>], "type">>>;
140
136
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
141
137
  pinchServer: z.ZodOptional<z.ZodBoolean>;
138
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
142
139
  subBackMode: z.ZodOptional<z.ZodEnum<{
143
140
  FATIGUE: "FATIGUE";
144
141
  CONDITIONS: "CONDITIONS";
@@ -174,21 +171,17 @@ export declare const TacticsInputSchema: z.ZodObject<{
174
171
  }, z.core.$strip>, z.ZodObject<{
175
172
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
176
173
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
174
+ }, z.core.$strip>, z.ZodObject<{
175
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
176
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
177
177
  }, z.core.$strip>], "type">>>;
178
178
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
179
179
  starter: z.ZodCustom<Player, Player>;
180
- }, z.core.$strip>>>;
180
+ }, z.core.$strip>>>>;
181
181
  substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>>;
182
182
  secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
183
- liberoSub: z.ZodOptional<z.ZodObject<{
184
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
185
- mode: z.ZodEnum<{
186
- NEVER: "NEVER";
187
- FATIGUE: "FATIGUE";
188
- CONDITIONS: "CONDITIONS";
189
- ALWAYS: "ALWAYS";
190
- }>;
191
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
183
+ liberoSub: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
184
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
192
185
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
193
186
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
194
187
  }, z.core.$strip>, z.ZodObject<{
@@ -220,16 +213,12 @@ export declare const TacticsInputSchema: z.ZodObject<{
220
213
  }, z.core.$strip>, z.ZodObject<{
221
214
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
222
215
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
216
+ }, z.core.$strip>, z.ZodObject<{
217
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
218
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
223
219
  }, z.core.$strip>], "type">>>;
224
220
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
225
- }, z.core.$strip>>>;
226
- mode: z.ZodEnum<{
227
- NEVER: "NEVER";
228
- FATIGUE: "FATIGUE";
229
- CONDITIONS: "CONDITIONS";
230
- ALWAYS: "ALWAYS";
231
- }>;
232
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
221
+ }, z.core.$strip>>>>;
233
222
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
234
223
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
235
224
  }, z.core.$strip>, z.ZodObject<{
@@ -261,9 +250,12 @@ export declare const TacticsInputSchema: z.ZodObject<{
261
250
  }, z.core.$strip>, z.ZodObject<{
262
251
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
263
252
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
253
+ }, z.core.$strip>, z.ZodObject<{
254
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
255
+ band: z.ZodOptional<z.ZodEnum<typeof EnergyBand>>;
264
256
  }, z.core.$strip>], "type">>>;
265
257
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
266
- }, z.core.$strip>>;
258
+ }, z.core.$strip>>>;
267
259
  liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
268
260
  rotationSystem: z.ZodDefault<z.ZodEnum<typeof RotationSystemEnum>>;
269
261
  designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
@@ -218,10 +218,11 @@ export const TacticsInputSchema = z.object({
218
218
  ...(libero != null ? [libero.id] : []),
219
219
  ...data.lineup.bench.map(p => p.id)
220
220
  ]);
221
- const liberoSubModes = data.liberoSub != null
222
- ? [data.liberoSub.mode, ...(data.liberoSub.sets ?? []).map(s => s.mode)]
221
+ // Unified condition engine: the rule wants a swap when ANY config (flat or per-set) has conditions.
222
+ const liberoConditionSets = data.liberoSub != null
223
+ ? [data.liberoSub.conditions ?? [], ...(data.liberoSub.sets ?? []).map(s => s.conditions ?? [])]
223
224
  : [];
224
- const wantsLiberoSwap = liberoSubModes.some(mode => mode !== 'NEVER');
225
+ const wantsLiberoSwap = liberoConditionSets.some(conditions => conditions.length > 0);
225
226
  if (wantsLiberoSwap && data.secondLibero == null) {
226
227
  ctx.addIssue({ code: 'custom', message: 'LIBERO_SWAP_REQUIRES_SECOND_LIBERO', path: ['secondLibero'] });
227
228
  }
@@ -23,16 +23,9 @@ export declare const TeamInputSchema: z.ZodObject<{
23
23
  }, z.core.$strip>;
24
24
  liberoReplacements: z.ZodArray<z.ZodCustom<Player, Player>>;
25
25
  receiveRotationOffset: z.ZodDefault<z.ZodBoolean>;
26
- designatedSubs: z.ZodDefault<z.ZodArray<z.ZodObject<{
27
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
28
- mode: z.ZodEnum<{
29
- NEVER: "NEVER";
30
- FATIGUE: "FATIGUE";
31
- PINCH: "PINCH";
32
- }>;
26
+ designatedSubs: z.ZodDefault<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
27
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
33
28
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
34
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
35
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
36
29
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
37
30
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
38
31
  }, z.core.$strip>, z.ZodObject<{
@@ -64,9 +57,13 @@ export declare const TeamInputSchema: z.ZodObject<{
64
57
  }, z.core.$strip>, z.ZodObject<{
65
58
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
66
59
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
60
+ }, z.core.$strip>, z.ZodObject<{
61
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
62
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
67
63
  }, z.core.$strip>], "type">>>;
68
64
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
69
65
  pinchServer: z.ZodOptional<z.ZodBoolean>;
66
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
70
67
  subBackMode: z.ZodOptional<z.ZodEnum<{
71
68
  FATIGUE: "FATIGUE";
72
69
  CONDITIONS: "CONDITIONS";
@@ -102,17 +99,13 @@ export declare const TeamInputSchema: z.ZodObject<{
102
99
  }, z.core.$strip>, z.ZodObject<{
103
100
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
104
101
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
102
+ }, z.core.$strip>, z.ZodObject<{
103
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
104
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
105
105
  }, z.core.$strip>], "type">>>;
106
106
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
107
- }, z.core.$strip>>>;
108
- mode: z.ZodEnum<{
109
- NEVER: "NEVER";
110
- FATIGUE: "FATIGUE";
111
- PINCH: "PINCH";
112
- }>;
107
+ }, z.core.$strip>>>>;
113
108
  bench: z.ZodOptional<z.ZodCustom<Player, Player>>;
114
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
115
- benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
116
109
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
117
110
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
118
111
  }, z.core.$strip>, z.ZodObject<{
@@ -144,9 +137,13 @@ export declare const TeamInputSchema: z.ZodObject<{
144
137
  }, z.core.$strip>, z.ZodObject<{
145
138
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
146
139
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
140
+ }, z.core.$strip>, z.ZodObject<{
141
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
142
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
147
143
  }, z.core.$strip>], "type">>>;
148
144
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
149
145
  pinchServer: z.ZodOptional<z.ZodBoolean>;
146
+ benchFatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
150
147
  subBackMode: z.ZodOptional<z.ZodEnum<{
151
148
  FATIGUE: "FATIGUE";
152
149
  CONDITIONS: "CONDITIONS";
@@ -182,21 +179,17 @@ export declare const TeamInputSchema: z.ZodObject<{
182
179
  }, z.core.$strip>, z.ZodObject<{
183
180
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
184
181
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
182
+ }, z.core.$strip>, z.ZodObject<{
183
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
184
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
185
185
  }, z.core.$strip>], "type">>>;
186
186
  subBackConditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
187
187
  starter: z.ZodCustom<Player, Player>;
188
- }, z.core.$strip>>>;
188
+ }, z.core.$strip>>>>;
189
189
  substitutionBand: z.ZodDefault<z.ZodUnion<readonly [z.ZodEnum<typeof import("..").EnergyBand>, z.ZodLiteral<"NEVER">]>>;
190
190
  secondLibero: z.ZodOptional<z.ZodCustom<Player, Player>>;
191
- liberoSub: z.ZodOptional<z.ZodObject<{
192
- sets: z.ZodOptional<z.ZodArray<z.ZodObject<{
193
- mode: z.ZodEnum<{
194
- NEVER: "NEVER";
195
- FATIGUE: "FATIGUE";
196
- CONDITIONS: "CONDITIONS";
197
- ALWAYS: "ALWAYS";
198
- }>;
199
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
191
+ liberoSub: z.ZodOptional<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
192
+ sets: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodTransform<unknown, unknown>, z.ZodObject<{
200
193
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
201
194
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
202
195
  }, z.core.$strip>, z.ZodObject<{
@@ -228,16 +221,12 @@ export declare const TeamInputSchema: z.ZodObject<{
228
221
  }, z.core.$strip>, z.ZodObject<{
229
222
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
230
223
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
224
+ }, z.core.$strip>, z.ZodObject<{
225
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
226
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
231
227
  }, z.core.$strip>], "type">>>;
232
228
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
233
- }, z.core.$strip>>>;
234
- mode: z.ZodEnum<{
235
- NEVER: "NEVER";
236
- FATIGUE: "FATIGUE";
237
- CONDITIONS: "CONDITIONS";
238
- ALWAYS: "ALWAYS";
239
- }>;
240
- fatigueBand: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
229
+ }, z.core.$strip>>>>;
241
230
  conditions: z.ZodOptional<z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
242
231
  type: z.ZodLiteral<import("..").PinchConditionType.ASAP>;
243
232
  }, z.core.$strip>, z.ZodObject<{
@@ -269,9 +258,12 @@ export declare const TeamInputSchema: z.ZodObject<{
269
258
  }, z.core.$strip>, z.ZodObject<{
270
259
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
271
260
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
261
+ }, z.core.$strip>, z.ZodObject<{
262
+ type: z.ZodLiteral<import("..").PinchConditionType.FATIGUE>;
263
+ band: z.ZodOptional<z.ZodEnum<typeof import("..").EnergyBand>>;
272
264
  }, z.core.$strip>], "type">>>;
273
265
  conditionLogic: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<"ALL">, z.ZodLiteral<"ANY">]>>;
274
- }, z.core.$strip>>;
266
+ }, z.core.$strip>>>;
275
267
  liberoReplacementSets: z.ZodOptional<z.ZodArray<z.ZodArray<z.ZodCustom<Player, Player>>>>;
276
268
  rotationSystem: z.ZodDefault<z.ZodEnum<typeof import("..").RotationSystemEnum>>;
277
269
  designatedSetters: z.ZodDefault<z.ZodArray<z.ZodCustom<Player, Player>>>;
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Convert ONE legacy set-sub config (flat or per-set entry). Handles BOTH legacy generations:
3
+ * mode-era rows ({mode: NEVER|FATIGUE|PINCH}) and the oldest isPinchServer-era rows (no mode;
4
+ * isPinchServer flag + a 'NEVER' fatigueBand for the old never-sub state). Non-records and
5
+ * already-unified shapes (neither mode nor isPinchServer nor fatigueBand keys) pass through.
6
+ */
7
+ export declare function convertLegacySetSubConfig(raw: unknown): unknown;
8
+ /** Convert a legacy DesignatedSub (flat fields + per-set entries); unified shapes pass through. */
9
+ export declare function convertLegacyDesignatedSub(raw: unknown): unknown;
10
+ /** Convert ONE legacy libero set-sub config; unified shapes pass through. */
11
+ export declare function convertLegacyLiberoSetSubConfig(raw: unknown): unknown;
12
+ /** Convert a legacy LiberoSubConfig (flat fields + per-set entries); unified shapes pass through. */
13
+ export declare function convertLegacyLiberoSubConfig(raw: unknown): unknown;
@@ -0,0 +1,85 @@
1
+ import { EnergyBand } from './energy-band';
2
+ import { PinchConditionType } from './pinch-condition';
3
+ function isRecord(v) {
4
+ return typeof v === 'object' && v != null && !Array.isArray(v);
5
+ }
6
+ function fatigueCondition(band) {
7
+ return Object.values(EnergyBand).includes(band)
8
+ ? { type: PinchConditionType.FATIGUE, band }
9
+ : { type: PinchConditionType.FATIGUE };
10
+ }
11
+ /**
12
+ * Convert ONE legacy set-sub config (flat or per-set entry). Handles BOTH legacy generations:
13
+ * mode-era rows ({mode: NEVER|FATIGUE|PINCH}) and the oldest isPinchServer-era rows (no mode;
14
+ * isPinchServer flag + a 'NEVER' fatigueBand for the old never-sub state). Non-records and
15
+ * already-unified shapes (neither mode nor isPinchServer nor fatigueBand keys) pass through.
16
+ */
17
+ export function convertLegacySetSubConfig(raw) {
18
+ if (!isRecord(raw))
19
+ return raw;
20
+ // Derive the mode for the oldest (pre-mode) generation; a unified shape has none of these keys.
21
+ const mode = raw.mode ?? (raw.isPinchServer === true
22
+ ? 'PINCH'
23
+ : raw.fatigueBand === 'NEVER'
24
+ ? 'NEVER'
25
+ : raw.fatigueBand != null || 'isPinchServer' in raw ? 'FATIGUE' : null);
26
+ if (mode == null)
27
+ return raw;
28
+ const { mode: _m, fatigueBand, isPinchServer: _p, ...rest } = raw;
29
+ if (mode === 'NEVER') {
30
+ // Old NEVER dropped every other field; an explicit empty condition list = never substituted.
31
+ return { conditions: [] };
32
+ }
33
+ if (mode === 'FATIGUE') {
34
+ return {
35
+ ...rest,
36
+ conditions: [fatigueCondition(fatigueBand)],
37
+ conditionLogic: 'ALL',
38
+ pinchServer: false,
39
+ subBackMode: rest.subBackMode ?? 'FATIGUE'
40
+ };
41
+ }
42
+ if (mode === 'PINCH') {
43
+ // A legacy PINCH row without the flag is a serve-and-return pinch server (the documented default).
44
+ return { ...rest, pinchServer: rest.pinchServer ?? true };
45
+ }
46
+ // Unknown legacy mode: strip it and let validation judge the rest.
47
+ return rest;
48
+ }
49
+ /** Convert a legacy DesignatedSub (flat fields + per-set entries); unified shapes pass through. */
50
+ export function convertLegacyDesignatedSub(raw) {
51
+ if (!isRecord(raw))
52
+ return raw;
53
+ const { starter, sets, ...cfg } = raw;
54
+ const flat = convertLegacySetSubConfig(cfg);
55
+ const out = { ...(isRecord(flat) ? flat : {}), starter };
56
+ if (Array.isArray(sets))
57
+ out.sets = sets.map(convertLegacySetSubConfig);
58
+ return out;
59
+ }
60
+ /** Convert ONE legacy libero set-sub config; unified shapes pass through. */
61
+ export function convertLegacyLiberoSetSubConfig(raw) {
62
+ if (!isRecord(raw) || raw.mode == null)
63
+ return raw;
64
+ const { mode, fatigueBand, ...rest } = raw;
65
+ if (mode === 'NEVER')
66
+ return { conditions: [] };
67
+ if (mode === 'ALWAYS')
68
+ return { ...rest, conditions: [{ type: PinchConditionType.ASAP }], conditionLogic: 'ALL' };
69
+ if (mode === 'FATIGUE')
70
+ return { ...rest, conditions: [fatigueCondition(fatigueBand)], conditionLogic: 'ALL' };
71
+ if (mode === 'CONDITIONS')
72
+ return rest;
73
+ return rest;
74
+ }
75
+ /** Convert a legacy LiberoSubConfig (flat fields + per-set entries); unified shapes pass through. */
76
+ export function convertLegacyLiberoSubConfig(raw) {
77
+ if (!isRecord(raw))
78
+ return raw;
79
+ const { sets, ...cfg } = raw;
80
+ const flat = convertLegacyLiberoSetSubConfig(cfg);
81
+ const out = { ...(isRecord(flat) ? flat : {}) };
82
+ if (Array.isArray(sets))
83
+ out.sets = sets.map(convertLegacyLiberoSetSubConfig);
84
+ return out;
85
+ }
@@ -0,0 +1,82 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { EnergyBand } from './energy-band';
3
+ import { PinchConditionType } from './pinch-condition';
4
+ import { convertLegacyDesignatedSub, convertLegacyLiberoSetSubConfig, convertLegacySetSubConfig } from './sub-config-convert';
5
+ // The conversion matrix for the unified condition engine: every legacy generation must land on the exact
6
+ // unified shape (these functions are the single conversion authority used by the schema preprocess, the
7
+ // transformers, and the API repo's read path).
8
+ describe('convertLegacySetSubConfig', () => {
9
+ it('passes an already-unified config through untouched', () => {
10
+ const unified = { bench: 'X', conditions: [{ type: PinchConditionType.ASAP }], pinchServer: true };
11
+ expect(convertLegacySetSubConfig(unified)).toBe(unified);
12
+ });
13
+ it('converts mode NEVER to an explicit empty condition list, dropping every other field', () => {
14
+ expect(convertLegacySetSubConfig({ mode: 'NEVER', bench: 'X' })).toEqual({ conditions: [] });
15
+ });
16
+ it('converts mode FATIGUE with a band to a FATIGUE condition + FATIGUE sub-back', () => {
17
+ expect(convertLegacySetSubConfig({ mode: 'FATIGUE', fatigueBand: EnergyBand.WORN, bench: 'X', benchFatigueBand: EnergyBand.TIRED })).toEqual({
18
+ bench: 'X',
19
+ benchFatigueBand: EnergyBand.TIRED,
20
+ conditions: [{ type: PinchConditionType.FATIGUE, band: EnergyBand.WORN }],
21
+ conditionLogic: 'ALL',
22
+ pinchServer: false,
23
+ subBackMode: 'FATIGUE'
24
+ });
25
+ });
26
+ it('converts mode FATIGUE without a band to a bandless FATIGUE condition (team default)', () => {
27
+ const out = convertLegacySetSubConfig({ mode: 'FATIGUE' });
28
+ expect(out.conditions).toEqual([{ type: PinchConditionType.FATIGUE }]);
29
+ });
30
+ it('converts mode PINCH keeping conditions and defaulting a missing pinchServer flag to true', () => {
31
+ expect(convertLegacySetSubConfig({ mode: 'PINCH', bench: 'X', conditions: [{ type: PinchConditionType.ASAP }] })).toEqual({
32
+ bench: 'X',
33
+ conditions: [{ type: PinchConditionType.ASAP }],
34
+ pinchServer: true
35
+ });
36
+ });
37
+ it('keeps an explicit pinchServer false on a legacy PINCH regular conditional sub', () => {
38
+ const out = convertLegacySetSubConfig({ mode: 'PINCH', bench: 'X', conditions: [{ type: PinchConditionType.ASAP }], pinchServer: false, subBackMode: 'CONDITIONS', subBackConditions: [{ type: PinchConditionType.ASAP }] });
39
+ expect(out.pinchServer).toBe(false);
40
+ expect(out.subBackMode).toBe('CONDITIONS');
41
+ });
42
+ it('converts the oldest isPinchServer-era row to a pinch server', () => {
43
+ expect(convertLegacySetSubConfig({ isPinchServer: true, bench: 'X', conditions: [{ type: PinchConditionType.ASAP }] })).toEqual({
44
+ bench: 'X',
45
+ conditions: [{ type: PinchConditionType.ASAP }],
46
+ pinchServer: true
47
+ });
48
+ });
49
+ it("converts the oldest 'NEVER' fatigueBand row to never (empty conditions)", () => {
50
+ expect(convertLegacySetSubConfig({ fatigueBand: 'NEVER' })).toEqual({ conditions: [] });
51
+ });
52
+ it('converts the oldest band-only row to a FATIGUE condition', () => {
53
+ const out = convertLegacySetSubConfig({ fatigueBand: EnergyBand.EXHAUSTED });
54
+ expect(out.conditions).toEqual([{ type: PinchConditionType.FATIGUE, band: EnergyBand.EXHAUSTED }]);
55
+ });
56
+ it('strips an unknown legacy mode and keeps the rest (lenient heal)', () => {
57
+ expect(convertLegacySetSubConfig({ mode: 'SOMETIMES', bench: 'X' })).toEqual({ bench: 'X' });
58
+ });
59
+ });
60
+ describe('convertLegacyDesignatedSub', () => {
61
+ it('converts the flat fields and every per-set entry, keeping the starter', () => {
62
+ const out = convertLegacyDesignatedSub({
63
+ starter: 'S',
64
+ mode: 'FATIGUE',
65
+ fatigueBand: EnergyBand.TIRED,
66
+ sets: [{ mode: 'NEVER' }, { mode: 'PINCH', bench: 'X', conditions: [{ type: PinchConditionType.ASAP }] }]
67
+ });
68
+ expect(out.starter).toBe('S');
69
+ expect(out.conditions).toEqual([{ type: PinchConditionType.FATIGUE, band: EnergyBand.TIRED }]);
70
+ expect(out.sets[0]).toEqual({ conditions: [] });
71
+ expect(out.sets[1].pinchServer).toBe(true);
72
+ });
73
+ });
74
+ describe('convertLegacyLiberoSetSubConfig', () => {
75
+ it('maps each legacy libero mode to its unified condition list', () => {
76
+ expect(convertLegacyLiberoSetSubConfig({ mode: 'NEVER' })).toEqual({ conditions: [] });
77
+ expect(convertLegacyLiberoSetSubConfig({ mode: 'ALWAYS' })).toEqual({ conditions: [{ type: PinchConditionType.ASAP }], conditionLogic: 'ALL' });
78
+ expect(convertLegacyLiberoSetSubConfig({ mode: 'FATIGUE', fatigueBand: EnergyBand.WORN })).toEqual({ conditions: [{ type: PinchConditionType.FATIGUE, band: EnergyBand.WORN }], conditionLogic: 'ALL' });
79
+ const conditions = [{ type: PinchConditionType.PHASE, phase: 'RECEIVING' }];
80
+ expect(convertLegacyLiberoSetSubConfig({ mode: 'CONDITIONS', conditions, conditionLogic: 'ANY' })).toEqual({ conditions, conditionLogic: 'ANY' });
81
+ });
82
+ });
@@ -1,4 +1,3 @@
1
- export declare const getKeys: <T extends object>(obj: T) => Array<keyof T>;
2
1
  export type DataProps<T> = {
3
2
  [K in keyof T as T[K] extends (...args: any[]) => any ? never : K]: T[K];
4
3
  };
@@ -1 +1 @@
1
- export const getKeys = Object.keys;
1
+ export {};