volleyballsimtypes 0.0.477 → 0.0.479

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 (63) hide show
  1. package/dist/cjs/src/service/index.d.ts +1 -0
  2. package/dist/cjs/src/service/index.js +1 -0
  3. package/dist/cjs/src/service/notification/catalog/en.json +41 -0
  4. package/dist/cjs/src/service/notification/catalog/es.json +41 -0
  5. package/dist/cjs/src/service/notification/catalog/fr.json +41 -0
  6. package/dist/cjs/src/service/notification/catalog/index.d.ts +3 -0
  7. package/dist/cjs/src/service/notification/catalog/index.js +22 -0
  8. package/dist/cjs/src/service/notification/catalog/it.json +41 -0
  9. package/dist/cjs/src/service/notification/catalog/pl.json +41 -0
  10. package/dist/cjs/src/service/notification/catalog/pt-BR.json +41 -0
  11. package/dist/cjs/src/service/notification/catalog/tr.json +41 -0
  12. package/dist/cjs/src/service/notification/index.d.ts +5 -0
  13. package/dist/cjs/src/service/notification/index.js +23 -0
  14. package/dist/cjs/src/service/notification/notification-type.d.ts +6 -0
  15. package/dist/cjs/src/service/notification/notification-type.js +30 -0
  16. package/dist/cjs/src/service/notification/notification.test.d.ts +1 -0
  17. package/dist/cjs/src/service/notification/notification.test.js +128 -0
  18. package/dist/cjs/src/service/notification/payloads.d.ts +34 -0
  19. package/dist/cjs/src/service/notification/payloads.js +5 -0
  20. package/dist/cjs/src/service/notification/registry.d.ts +13 -0
  21. package/dist/cjs/src/service/notification/registry.js +112 -0
  22. package/dist/cjs/src/service/notification/render.d.ts +10 -0
  23. package/dist/cjs/src/service/notification/render.js +64 -0
  24. package/dist/cjs/src/service/team/pinch-condition.d.ts +7 -0
  25. package/dist/cjs/src/service/team/pinch-condition.js +10 -1
  26. package/dist/cjs/src/service/team/schemas/designated-sub.z.d.ts +22 -1
  27. package/dist/cjs/src/service/team/schemas/designated-sub.z.js +5 -3
  28. package/dist/cjs/src/service/team/schemas/designated-sub.z.test.js +10 -0
  29. package/dist/cjs/src/service/team/schemas/libero-sub.z.d.ts +9 -0
  30. package/dist/cjs/src/service/team/schemas/tactics.z.d.ts +18 -0
  31. package/dist/cjs/src/service/team/schemas/team.z.d.ts +18 -0
  32. package/dist/esm/src/service/index.d.ts +1 -0
  33. package/dist/esm/src/service/index.js +1 -0
  34. package/dist/esm/src/service/notification/catalog/en.json +41 -0
  35. package/dist/esm/src/service/notification/catalog/es.json +41 -0
  36. package/dist/esm/src/service/notification/catalog/fr.json +41 -0
  37. package/dist/esm/src/service/notification/catalog/index.d.ts +3 -0
  38. package/dist/esm/src/service/notification/catalog/index.js +16 -0
  39. package/dist/esm/src/service/notification/catalog/it.json +41 -0
  40. package/dist/esm/src/service/notification/catalog/pl.json +41 -0
  41. package/dist/esm/src/service/notification/catalog/pt-BR.json +41 -0
  42. package/dist/esm/src/service/notification/catalog/tr.json +41 -0
  43. package/dist/esm/src/service/notification/index.d.ts +5 -0
  44. package/dist/esm/src/service/notification/index.js +5 -0
  45. package/dist/esm/src/service/notification/notification-type.d.ts +6 -0
  46. package/dist/esm/src/service/notification/notification-type.js +26 -0
  47. package/dist/esm/src/service/notification/notification.test.d.ts +1 -0
  48. package/dist/esm/src/service/notification/notification.test.js +126 -0
  49. package/dist/esm/src/service/notification/payloads.d.ts +34 -0
  50. package/dist/esm/src/service/notification/payloads.js +4 -0
  51. package/dist/esm/src/service/notification/registry.d.ts +13 -0
  52. package/dist/esm/src/service/notification/registry.js +109 -0
  53. package/dist/esm/src/service/notification/render.d.ts +10 -0
  54. package/dist/esm/src/service/notification/render.js +58 -0
  55. package/dist/esm/src/service/team/pinch-condition.d.ts +7 -0
  56. package/dist/esm/src/service/team/pinch-condition.js +9 -0
  57. package/dist/esm/src/service/team/schemas/designated-sub.z.d.ts +22 -1
  58. package/dist/esm/src/service/team/schemas/designated-sub.z.js +6 -4
  59. package/dist/esm/src/service/team/schemas/designated-sub.z.test.js +11 -1
  60. package/dist/esm/src/service/team/schemas/libero-sub.z.d.ts +9 -0
  61. package/dist/esm/src/service/team/schemas/tactics.z.d.ts +18 -0
  62. package/dist/esm/src/service/team/schemas/team.z.d.ts +18 -0
  63. package/package.json +6 -1
@@ -0,0 +1,34 @@
1
+ export interface MatchResultTeam {
2
+ id?: string;
3
+ name?: string;
4
+ }
5
+ export interface MatchResultPayload {
6
+ matchId?: string;
7
+ homeTeam?: MatchResultTeam;
8
+ awayTeam?: MatchResultTeam;
9
+ result?: 'WIN' | 'LOSS';
10
+ side?: 'HOME' | 'AWAY';
11
+ }
12
+ export interface InjuryPayload {
13
+ playerId?: string;
14
+ playerName?: string;
15
+ playerNickname?: string;
16
+ teamId?: string;
17
+ matchId?: string;
18
+ }
19
+ export interface InjuryRecoveredPayload {
20
+ playerId?: string;
21
+ playerName?: string;
22
+ playerNickname?: string;
23
+ teamId?: string;
24
+ }
25
+ export interface SeasonEndPayload {
26
+ competitionId?: string;
27
+ leagueId?: string;
28
+ position?: number;
29
+ }
30
+ export interface PromotionPayload {
31
+ teamId?: string;
32
+ amount?: number;
33
+ }
34
+ export type NotificationPayload = MatchResultPayload | InjuryPayload | InjuryRecoveredPayload | SeasonEndPayload | PromotionPayload | Record<string, unknown>;
@@ -0,0 +1,4 @@
1
+ // Typed payload shapes per notification type, for call-site safety in the Sim/API. The DB `Notification.payload`
2
+ // column is jsonb (free-form), so the registry reads payloads defensively (Record<string, unknown>); these
3
+ // interfaces document the intended shape and can be used by emitters when constructing the payload.
4
+ export {};
@@ -0,0 +1,13 @@
1
+ import { type NotificationType } from './notification-type';
2
+ export type CatalogKeyResolver = string | ((payload: Record<string, unknown>) => string);
3
+ export interface NotificationDescriptor {
4
+ emitsPush: boolean;
5
+ deepLink?: (payload: Record<string, unknown>) => string;
6
+ pushTitleKey?: CatalogKeyResolver;
7
+ pushBodyKey?: CatalogKeyResolver;
8
+ inAppMessageKey?: CatalogKeyResolver;
9
+ richRender?: NotificationType;
10
+ params?: (payload: Record<string, unknown>) => Record<string, string>;
11
+ catalogKeys: string[];
12
+ }
13
+ export declare const NOTIFICATION_REGISTRY: Record<NotificationType, NotificationDescriptor>;
@@ -0,0 +1,109 @@
1
+ function readString(value) {
2
+ return typeof value === 'string' && value !== '' ? value : null;
3
+ }
4
+ function teamPath(payload) {
5
+ const teamId = readString(payload.teamId);
6
+ return teamId != null ? `/team/${encodeURIComponent(teamId)}` : '/';
7
+ }
8
+ // MATCH_RESULT copy is recipient-relative: `result` is WIN/LOSS from the recipient's view and `side` marks
9
+ // which team is theirs, so the OTHER team is the opponent. When side/names are missing, do not name a winner.
10
+ function matchOpponent(payload) {
11
+ const homeTeam = typeof payload.homeTeam === 'object' && payload.homeTeam != null ? payload.homeTeam : {};
12
+ const awayTeam = typeof payload.awayTeam === 'object' && payload.awayTeam != null ? payload.awayTeam : {};
13
+ const homeName = readString(homeTeam.name);
14
+ const awayName = readString(awayTeam.name);
15
+ if (payload.side === 'HOME')
16
+ return awayName;
17
+ if (payload.side === 'AWAY')
18
+ return homeName;
19
+ return null;
20
+ }
21
+ function injuredName(payload) {
22
+ // Matches the old Sim injuredPlayerName fallback so a name-less payload never renders a subjectless sentence.
23
+ return readString(payload.playerNickname) ?? readString(payload.playerName) ?? 'A player';
24
+ }
25
+ export const NOTIFICATION_REGISTRY = {
26
+ MATCH_RESULT: {
27
+ emitsPush: true,
28
+ deepLink: (p) => {
29
+ const matchId = readString(p.matchId);
30
+ return matchId != null ? `/match/${encodeURIComponent(matchId)}` : '/';
31
+ },
32
+ pushTitleKey: 'matchResult.title',
33
+ pushBodyKey: (p) => {
34
+ const outcome = p.result === 'WIN' ? 'won' : 'lost';
35
+ const named = matchOpponent(p) != null ? 'Named' : 'Unnamed';
36
+ return `matchResult.${outcome}${named}`;
37
+ },
38
+ params: (p) => ({ opponent: matchOpponent(p) ?? '' }),
39
+ richRender: 'MATCH_RESULT',
40
+ catalogKeys: ['matchResult.title', 'matchResult.wonNamed', 'matchResult.wonUnnamed', 'matchResult.lostNamed', 'matchResult.lostUnnamed']
41
+ },
42
+ INJURY: {
43
+ emitsPush: true,
44
+ deepLink: () => '/infirmary',
45
+ pushTitleKey: 'injury.title',
46
+ pushBodyKey: 'injury.body',
47
+ inAppMessageKey: 'injury.body',
48
+ params: (p) => ({ name: injuredName(p) }),
49
+ catalogKeys: ['injury.title', 'injury.body']
50
+ },
51
+ INJURY_RECOVERED: {
52
+ emitsPush: true,
53
+ deepLink: teamPath,
54
+ pushTitleKey: 'injuryRecovered.title',
55
+ pushBodyKey: 'injuryRecovered.body',
56
+ inAppMessageKey: 'injuryRecovered.body',
57
+ params: (p) => ({ name: injuredName(p) }),
58
+ catalogKeys: ['injuryRecovered.title', 'injuryRecovered.body']
59
+ },
60
+ SEASON_END: {
61
+ emitsPush: true,
62
+ deepLink: (p) => {
63
+ const leagueId = readString(p.leagueId);
64
+ return leagueId != null ? `/league/${encodeURIComponent(leagueId)}` : '/league';
65
+ },
66
+ pushTitleKey: 'seasonEnd.title',
67
+ pushBodyKey: 'seasonEnd.body',
68
+ inAppMessageKey: 'seasonEnd.body',
69
+ catalogKeys: ['seasonEnd.title', 'seasonEnd.body']
70
+ },
71
+ GRANT_RECEIVED: {
72
+ emitsPush: true,
73
+ deepLink: () => '/recruit',
74
+ pushTitleKey: 'grant.title',
75
+ pushBodyKey: 'grant.body',
76
+ inAppMessageKey: 'grant.body',
77
+ catalogKeys: ['grant.title', 'grant.body']
78
+ },
79
+ PROMOTION: {
80
+ emitsPush: true,
81
+ deepLink: teamPath,
82
+ pushTitleKey: 'promotion.title',
83
+ pushBodyKey: 'promotion.body',
84
+ inAppMessageKey: 'promotion.body',
85
+ catalogKeys: ['promotion.title', 'promotion.body']
86
+ },
87
+ DAILY_LOGIN: {
88
+ emitsPush: true,
89
+ deepLink: () => '/recruit',
90
+ pushTitleKey: 'dailyLogin.title',
91
+ pushBodyKey: 'dailyLogin.body',
92
+ inAppMessageKey: 'dailyLogin.body',
93
+ catalogKeys: ['dailyLogin.title', 'dailyLogin.body']
94
+ },
95
+ WELCOME: {
96
+ emitsPush: true,
97
+ deepLink: () => '/recruit',
98
+ pushTitleKey: 'welcome.title',
99
+ pushBodyKey: 'welcome.body',
100
+ richRender: 'WELCOME',
101
+ catalogKeys: ['welcome.title', 'welcome.body']
102
+ },
103
+ // In-app only (no push); the UI keeps a bespoke renderer (amount formatting) and its own `compensation` label.
104
+ COMPENSATION: {
105
+ emitsPush: false,
106
+ richRender: 'COMPENSATION',
107
+ catalogKeys: []
108
+ }
109
+ };
@@ -0,0 +1,10 @@
1
+ import { type NotificationLocale } from './notification-type';
2
+ export interface RenderedPush {
3
+ title: string;
4
+ body: string;
5
+ data: Record<string, string>;
6
+ }
7
+ export declare function interpolate(template: string, params: Record<string, string>): string;
8
+ export declare function translateNotification(locale: NotificationLocale, key: string, params?: Record<string, string>): string;
9
+ export declare function renderPush(type: string, payload: Record<string, unknown>, locale: NotificationLocale): RenderedPush | undefined;
10
+ export declare function notificationPath(type: string, payload: Record<string, unknown>): string;
@@ -0,0 +1,58 @@
1
+ import { DEFAULT_NOTIFICATION_LOCALE } from './notification-type';
2
+ import { NOTIFICATION_REGISTRY } from './registry';
3
+ import { NOTIFICATION_CATALOG } from './catalog';
4
+ function readNested(node, dotKey) {
5
+ let current = node;
6
+ for (const part of dotKey.split('.')) {
7
+ if (typeof current !== 'object' || current == null)
8
+ return undefined;
9
+ current = current[part];
10
+ }
11
+ return typeof current === 'string' ? current : undefined;
12
+ }
13
+ function lookupCatalog(locale, dotKey) {
14
+ return readNested(NOTIFICATION_CATALOG[locale], dotKey) ??
15
+ readNested(NOTIFICATION_CATALOG[DEFAULT_NOTIFICATION_LOCALE], dotKey) ??
16
+ dotKey;
17
+ }
18
+ function resolveKey(resolver, payload) {
19
+ if (resolver == null)
20
+ return undefined;
21
+ return typeof resolver === 'function' ? resolver(payload) : resolver;
22
+ }
23
+ export function interpolate(template, params) {
24
+ return template.replace(/\{\{(\w+)\}\}/g, (_match, key) => params[key] ?? '');
25
+ }
26
+ // Resolve one catalog key to a localized, interpolated string. Missing key/locale falls back to English,
27
+ // then to the raw key. Exposed so consumers can render notification copy off the shared catalog too.
28
+ export function translateNotification(locale, key, params = {}) {
29
+ return interpolate(lookupCatalog(locale, key), params);
30
+ }
31
+ // Build the localized push payload for a notification. Returns undefined for in-app-only types (emitsPush
32
+ // false, e.g. COMPENSATION). Unknown/future types fall back to the generic copy and the root path, matching
33
+ // the previous hardcoded fallback. Callers pass the recipient's locale (normalizeNotificationLocale first).
34
+ export function renderPush(type, payload, locale) {
35
+ const buildData = (path) => ({ type, payload: JSON.stringify(payload), path });
36
+ const descriptor = NOTIFICATION_REGISTRY[type];
37
+ if (descriptor == null) {
38
+ return {
39
+ title: translateNotification(locale, 'generic.title'),
40
+ body: translateNotification(locale, 'generic.body'),
41
+ data: buildData('/')
42
+ };
43
+ }
44
+ if (!descriptor.emitsPush)
45
+ return undefined;
46
+ const params = descriptor.params?.(payload) ?? {};
47
+ const titleKey = resolveKey(descriptor.pushTitleKey, payload) ?? 'generic.title';
48
+ const bodyKey = resolveKey(descriptor.pushBodyKey, payload) ?? 'generic.body';
49
+ return {
50
+ title: translateNotification(locale, titleKey, params),
51
+ body: translateNotification(locale, bodyKey, params),
52
+ data: buildData(descriptor.deepLink?.(payload) ?? '/')
53
+ };
54
+ }
55
+ // Deep-link path for a type/payload, used for both the push data.path and the in-app click target.
56
+ export function notificationPath(type, payload) {
57
+ return NOTIFICATION_REGISTRY[type]?.deepLink?.(payload) ?? '/';
58
+ }
@@ -22,6 +22,10 @@ export declare enum ScoreDirection {
22
22
  TRAILING = "TRAILING",
23
23
  LEADING = "LEADING"
24
24
  }
25
+ export declare enum Comparison {
26
+ AT_LEAST = "AT_LEAST",
27
+ AT_MOST = "AT_MOST"
28
+ }
25
29
  export interface AsapCondition {
26
30
  readonly type: PinchConditionType.ASAP;
27
31
  }
@@ -38,6 +42,7 @@ export interface ScoreDiffCondition {
38
42
  readonly type: PinchConditionType.SCORE_DIFF;
39
43
  readonly direction: ScoreDirection;
40
44
  readonly points: number;
45
+ readonly comparison?: Comparison;
41
46
  }
42
47
  export interface AfterRotationsCondition {
43
48
  readonly type: PinchConditionType.AFTER_ROTATIONS;
@@ -50,10 +55,12 @@ export interface FromSetCondition {
50
55
  export interface MinOwnScoreCondition {
51
56
  readonly type: PinchConditionType.MIN_OWN_SCORE;
52
57
  readonly score: number;
58
+ readonly comparison?: Comparison;
53
59
  }
54
60
  export interface MinOpponentScoreCondition {
55
61
  readonly type: PinchConditionType.MIN_OPPONENT_SCORE;
56
62
  readonly score: number;
63
+ readonly comparison?: Comparison;
57
64
  }
58
65
  export interface PhaseCondition {
59
66
  readonly type: PinchConditionType.PHASE;
@@ -31,3 +31,12 @@ export var ScoreDirection;
31
31
  ScoreDirection["TRAILING"] = "TRAILING";
32
32
  ScoreDirection["LEADING"] = "LEADING";
33
33
  })(ScoreDirection || (ScoreDirection = {}));
34
+ // Comparison direction for a numeric-threshold condition (SCORE_DIFF / MIN_OWN_SCORE / MIN_OPPONENT_SCORE).
35
+ // AT_LEAST ("Or more", value >= threshold) is the default when absent, so every existing stored condition keeps
36
+ // its `>=` behavior. AT_MOST ("Or less", value <= threshold) lets a rule fire as a value SHRINKS (e.g. a sub-back
37
+ // once "Leading by 2 or less" holds, instead of the always-implied "Leading by 2 or more").
38
+ export var Comparison;
39
+ (function (Comparison) {
40
+ Comparison["AT_LEAST"] = "AT_LEAST";
41
+ Comparison["AT_MOST"] = "AT_MOST";
42
+ })(Comparison || (Comparison = {}));
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { Player } from '../../player';
3
3
  import { EnergyBand } from '../energy-band';
4
- import { MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
4
+ import { Comparison, MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
5
5
  export declare const SubBandSchema: z.ZodUnion<readonly [z.ZodEnum<typeof EnergyBand>, z.ZodLiteral<"NEVER">]>;
6
6
  export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
7
7
  type: z.ZodLiteral<PinchConditionType.ASAP>;
@@ -16,6 +16,7 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
16
16
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
17
17
  direction: z.ZodEnum<typeof ScoreDirection>;
18
18
  points: z.ZodNumber;
19
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
19
20
  }, z.core.$strip>, z.ZodObject<{
20
21
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
21
22
  rotations: z.ZodNumber;
@@ -25,9 +26,11 @@ export declare const PinchConditionSchema: z.ZodDiscriminatedUnion<[z.ZodObject<
25
26
  }, z.core.$strip>, z.ZodObject<{
26
27
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
27
28
  score: z.ZodNumber;
29
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
28
30
  }, z.core.$strip>, z.ZodObject<{
29
31
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
30
32
  score: z.ZodNumber;
33
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
31
34
  }, z.core.$strip>, z.ZodObject<{
32
35
  type: z.ZodLiteral<PinchConditionType.PHASE>;
33
36
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -64,6 +67,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
64
67
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
65
68
  direction: z.ZodEnum<typeof ScoreDirection>;
66
69
  points: z.ZodNumber;
70
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
67
71
  }, z.core.$strip>, z.ZodObject<{
68
72
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
69
73
  rotations: z.ZodNumber;
@@ -73,9 +77,11 @@ export declare const SetSubConfigSchema: z.ZodObject<{
73
77
  }, z.core.$strip>, z.ZodObject<{
74
78
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
75
79
  score: z.ZodNumber;
80
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
76
81
  }, z.core.$strip>, z.ZodObject<{
77
82
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
78
83
  score: z.ZodNumber;
84
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
79
85
  }, z.core.$strip>, z.ZodObject<{
80
86
  type: z.ZodLiteral<PinchConditionType.PHASE>;
81
87
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -99,6 +105,7 @@ export declare const SetSubConfigSchema: z.ZodObject<{
99
105
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
100
106
  direction: z.ZodEnum<typeof ScoreDirection>;
101
107
  points: z.ZodNumber;
108
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
102
109
  }, z.core.$strip>, z.ZodObject<{
103
110
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
104
111
  rotations: z.ZodNumber;
@@ -108,9 +115,11 @@ export declare const SetSubConfigSchema: z.ZodObject<{
108
115
  }, z.core.$strip>, z.ZodObject<{
109
116
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
110
117
  score: z.ZodNumber;
118
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
111
119
  }, z.core.$strip>, z.ZodObject<{
112
120
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
113
121
  score: z.ZodNumber;
122
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
114
123
  }, z.core.$strip>, z.ZodObject<{
115
124
  type: z.ZodLiteral<PinchConditionType.PHASE>;
116
125
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -140,6 +149,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
140
149
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
141
150
  direction: z.ZodEnum<typeof ScoreDirection>;
142
151
  points: z.ZodNumber;
152
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
143
153
  }, z.core.$strip>, z.ZodObject<{
144
154
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
145
155
  rotations: z.ZodNumber;
@@ -149,9 +159,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
149
159
  }, z.core.$strip>, z.ZodObject<{
150
160
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
151
161
  score: z.ZodNumber;
162
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
152
163
  }, z.core.$strip>, z.ZodObject<{
153
164
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
154
165
  score: z.ZodNumber;
166
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
155
167
  }, z.core.$strip>, z.ZodObject<{
156
168
  type: z.ZodLiteral<PinchConditionType.PHASE>;
157
169
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -175,6 +187,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
175
187
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
176
188
  direction: z.ZodEnum<typeof ScoreDirection>;
177
189
  points: z.ZodNumber;
190
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
178
191
  }, z.core.$strip>, z.ZodObject<{
179
192
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
180
193
  rotations: z.ZodNumber;
@@ -184,9 +197,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
184
197
  }, z.core.$strip>, z.ZodObject<{
185
198
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
186
199
  score: z.ZodNumber;
200
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
187
201
  }, z.core.$strip>, z.ZodObject<{
188
202
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
189
203
  score: z.ZodNumber;
204
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
190
205
  }, z.core.$strip>, z.ZodObject<{
191
206
  type: z.ZodLiteral<PinchConditionType.PHASE>;
192
207
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -214,6 +229,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
214
229
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
215
230
  direction: z.ZodEnum<typeof ScoreDirection>;
216
231
  points: z.ZodNumber;
232
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
217
233
  }, z.core.$strip>, z.ZodObject<{
218
234
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
219
235
  rotations: z.ZodNumber;
@@ -223,9 +239,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
223
239
  }, z.core.$strip>, z.ZodObject<{
224
240
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
225
241
  score: z.ZodNumber;
242
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
226
243
  }, z.core.$strip>, z.ZodObject<{
227
244
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
228
245
  score: z.ZodNumber;
246
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
229
247
  }, z.core.$strip>, z.ZodObject<{
230
248
  type: z.ZodLiteral<PinchConditionType.PHASE>;
231
249
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -249,6 +267,7 @@ export declare const DesignatedSubSchema: z.ZodObject<{
249
267
  type: z.ZodLiteral<PinchConditionType.SCORE_DIFF>;
250
268
  direction: z.ZodEnum<typeof ScoreDirection>;
251
269
  points: z.ZodNumber;
270
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
252
271
  }, z.core.$strip>, z.ZodObject<{
253
272
  type: z.ZodLiteral<PinchConditionType.AFTER_ROTATIONS>;
254
273
  rotations: z.ZodNumber;
@@ -258,9 +277,11 @@ export declare const DesignatedSubSchema: z.ZodObject<{
258
277
  }, z.core.$strip>, z.ZodObject<{
259
278
  type: z.ZodLiteral<PinchConditionType.MIN_OWN_SCORE>;
260
279
  score: z.ZodNumber;
280
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
261
281
  }, z.core.$strip>, z.ZodObject<{
262
282
  type: z.ZodLiteral<PinchConditionType.MIN_OPPONENT_SCORE>;
263
283
  score: z.ZodNumber;
284
+ comparison: z.ZodOptional<z.ZodEnum<typeof Comparison>>;
264
285
  }, z.core.$strip>, z.ZodObject<{
265
286
  type: z.ZodLiteral<PinchConditionType.PHASE>;
266
287
  phase: z.ZodEnum<typeof MatchPhase>;
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { Player } from '../../player';
3
3
  import { EnergyBand } from '../energy-band';
4
- import { MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
4
+ import { Comparison, MatchPhase, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
5
5
  const playerInstanceSchema = z.custom((v) => v instanceof Player, {
6
6
  message: 'INVALID_PLAYER_INSTANCE'
7
7
  });
@@ -10,11 +10,13 @@ export const PinchConditionSchema = z.discriminatedUnion('type', [
10
10
  z.object({ type: z.literal(PinchConditionType.ASAP) }),
11
11
  z.object({ type: z.literal(PinchConditionType.TEAM_SET_POINT), margin: z.number().int().min(1).max(25), opponent: z.nativeEnum(OpponentRelation) }),
12
12
  z.object({ type: z.literal(PinchConditionType.OPPONENT_SET_POINT), margin: z.number().int().min(1).max(25) }),
13
- z.object({ type: z.literal(PinchConditionType.SCORE_DIFF), direction: z.nativeEnum(ScoreDirection), points: z.number().int().min(1).max(25) }),
13
+ // SCORE_DIFF / MIN_OWN_SCORE / MIN_OPPONENT_SCORE carry an optional comparison direction ("Or more" AT_LEAST,
14
+ // the default; "Or less" AT_MOST). Absent = AT_LEAST so existing conditions keep their `>=` behavior.
15
+ z.object({ type: z.literal(PinchConditionType.SCORE_DIFF), direction: z.nativeEnum(ScoreDirection), points: z.number().int().min(1).max(25), comparison: z.nativeEnum(Comparison).optional() }),
14
16
  z.object({ type: z.literal(PinchConditionType.AFTER_ROTATIONS), rotations: z.number().int().min(1).max(6) }),
15
17
  z.object({ type: z.literal(PinchConditionType.FROM_SET), set: z.number().int().min(1).max(5) }),
16
- z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40) }),
17
- z.object({ type: z.literal(PinchConditionType.MIN_OPPONENT_SCORE), score: z.number().int().min(0).max(40) }),
18
+ z.object({ type: z.literal(PinchConditionType.MIN_OWN_SCORE), score: z.number().int().min(0).max(40), comparison: z.nativeEnum(Comparison).optional() }),
19
+ z.object({ type: z.literal(PinchConditionType.MIN_OPPONENT_SCORE), score: z.number().int().min(0).max(40), comparison: z.nativeEnum(Comparison).optional() }),
18
20
  // PHASE is used by the libero deployment rule (which reuses this schema); pinch servers never offer it.
19
21
  z.object({ type: z.literal(PinchConditionType.PHASE), phase: z.nativeEnum(MatchPhase) })
20
22
  ]);
@@ -1,7 +1,7 @@
1
1
  import { describe, it, expect } from '@jest/globals';
2
2
  import { ConditionLogicSchema, DesignatedSubSchema, PinchConditionSchema, SubBandSchema } from './designated-sub.z';
3
3
  import { EnergyBand } from '../energy-band';
4
- import { OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
4
+ import { Comparison, OpponentRelation, PinchConditionType, ScoreDirection } from '../pinch-condition';
5
5
  import { makeCountry, makePlayer } from '../../test-helpers';
6
6
  describe('SubBandSchema', () => {
7
7
  it('accepts every energy band and NEVER', () => {
@@ -57,6 +57,16 @@ describe('PinchConditionSchema', () => {
57
57
  it('rejects a non-integer numeric field', () => {
58
58
  expect(PinchConditionSchema.safeParse({ type: PinchConditionType.FROM_SET, set: 2.5 }).success).toBe(false);
59
59
  });
60
+ it('accepts an optional comparison (Or more / Or less) on the numeric-threshold conditions', () => {
61
+ expect(PinchConditionSchema.safeParse({ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.LEADING, points: 2, comparison: Comparison.AT_MOST }).success).toBe(true);
62
+ expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OWN_SCORE, score: 20, comparison: Comparison.AT_MOST }).success).toBe(true);
63
+ expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OPPONENT_SCORE, score: 20, comparison: Comparison.AT_LEAST }).success).toBe(true);
64
+ // absent comparison stays valid (defaults to AT_LEAST at eval time)
65
+ expect(PinchConditionSchema.safeParse({ type: PinchConditionType.SCORE_DIFF, direction: ScoreDirection.LEADING, points: 2 }).success).toBe(true);
66
+ });
67
+ it('rejects an invalid comparison value', () => {
68
+ expect(PinchConditionSchema.safeParse({ type: PinchConditionType.MIN_OWN_SCORE, score: 20, comparison: 'EXACTLY' }).success).toBe(false);
69
+ });
60
70
  });
61
71
  describe('DesignatedSubSchema', () => {
62
72
  const country = makeCountry();
@@ -27,6 +27,7 @@ export declare const LiberoSetSubConfigSchema: z.ZodObject<{
27
27
  type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
28
28
  direction: z.ZodEnum<typeof import("..").ScoreDirection>;
29
29
  points: z.ZodNumber;
30
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
30
31
  }, z.core.$strip>, z.ZodObject<{
31
32
  type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
32
33
  rotations: z.ZodNumber;
@@ -36,9 +37,11 @@ export declare const LiberoSetSubConfigSchema: z.ZodObject<{
36
37
  }, z.core.$strip>, z.ZodObject<{
37
38
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
38
39
  score: z.ZodNumber;
40
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
39
41
  }, z.core.$strip>, z.ZodObject<{
40
42
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
41
43
  score: z.ZodNumber;
44
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
42
45
  }, z.core.$strip>, z.ZodObject<{
43
46
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
44
47
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
@@ -67,6 +70,7 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
67
70
  type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
68
71
  direction: z.ZodEnum<typeof import("..").ScoreDirection>;
69
72
  points: z.ZodNumber;
73
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
70
74
  }, z.core.$strip>, z.ZodObject<{
71
75
  type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
72
76
  rotations: z.ZodNumber;
@@ -76,9 +80,11 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
76
80
  }, z.core.$strip>, z.ZodObject<{
77
81
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
78
82
  score: z.ZodNumber;
83
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
79
84
  }, z.core.$strip>, z.ZodObject<{
80
85
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
81
86
  score: z.ZodNumber;
87
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
82
88
  }, z.core.$strip>, z.ZodObject<{
83
89
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
84
90
  phase: z.ZodEnum<typeof import("..").MatchPhase>;
@@ -105,6 +111,7 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
105
111
  type: z.ZodLiteral<import("..").PinchConditionType.SCORE_DIFF>;
106
112
  direction: z.ZodEnum<typeof import("..").ScoreDirection>;
107
113
  points: z.ZodNumber;
114
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
108
115
  }, z.core.$strip>, z.ZodObject<{
109
116
  type: z.ZodLiteral<import("..").PinchConditionType.AFTER_ROTATIONS>;
110
117
  rotations: z.ZodNumber;
@@ -114,9 +121,11 @@ export declare const LiberoSubConfigSchema: z.ZodObject<{
114
121
  }, z.core.$strip>, z.ZodObject<{
115
122
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OWN_SCORE>;
116
123
  score: z.ZodNumber;
124
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
117
125
  }, z.core.$strip>, z.ZodObject<{
118
126
  type: z.ZodLiteral<import("..").PinchConditionType.MIN_OPPONENT_SCORE>;
119
127
  score: z.ZodNumber;
128
+ comparison: z.ZodOptional<z.ZodEnum<typeof import("..").Comparison>>;
120
129
  }, z.core.$strip>, z.ZodObject<{
121
130
  type: z.ZodLiteral<import("..").PinchConditionType.PHASE>;
122
131
  phase: z.ZodEnum<typeof import("..").MatchPhase>;