@venulog/phasing-engine-schemas 0.7.6-alpha.0 → 0.8.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,231 @@
1
+ import { z } from './zod.js';
2
+ import { SimulationMode, SimulationStatus } from './enums/simulationMode.js';
3
+ import { createSuccessResponseSchema, createMessageDataResponseSchema } from './common.js';
4
+ // ============================================================================
5
+ // Enums as Zod schemas
6
+ // ============================================================================
7
+ export const simulationModeSchema = z.enum([
8
+ SimulationMode.ENTRY,
9
+ SimulationMode.EXIT,
10
+ SimulationMode.BOTH
11
+ ]);
12
+ export const simulationStatusSchema = z.enum([
13
+ SimulationStatus.RUNNING,
14
+ SimulationStatus.STOPPED,
15
+ SimulationStatus.COMPLETED
16
+ ]);
17
+ // ============================================================================
18
+ // Start Simulation Request
19
+ // ============================================================================
20
+ export const startSimulationBodySchema = z
21
+ .object({
22
+ event_id: z.number().int().positive().openapi({
23
+ description: 'Event ID to simulate',
24
+ example: 1
25
+ }),
26
+ booking_ids: z
27
+ .array(z.number().int().positive())
28
+ .optional()
29
+ .openapi({
30
+ description: 'Specific booking IDs to simulate. If omitted, simulates all active bookings.',
31
+ example: [123, 124, 125]
32
+ }),
33
+ speed_kmh: z.number().min(5).max(50).default(20).openapi({
34
+ description: 'Simulated vehicle speed in km/h',
35
+ example: 20
36
+ }),
37
+ update_interval_ms: z.number().int().min(500).max(5000).default(1000).openapi({
38
+ description: 'Position update interval in milliseconds',
39
+ example: 1000
40
+ }),
41
+ mode: simulationModeSchema.default(SimulationMode.BOTH).openapi({
42
+ description: 'Which movements to simulate',
43
+ example: 'both'
44
+ })
45
+ })
46
+ .openapi('StartSimulationBody');
47
+ // ============================================================================
48
+ // Start Simulation Response
49
+ // ============================================================================
50
+ export const startSimulationDataSchema = z
51
+ .object({
52
+ session_id: z.string().uuid().openapi({
53
+ description: 'Simulation session UUID',
54
+ example: '550e8400-e29b-41d4-a716-446655440000'
55
+ }),
56
+ event_id: z.number().int().positive().openapi({
57
+ description: 'Event ID',
58
+ example: 1
59
+ }),
60
+ booking_ids: z.array(z.number().int().positive()).openapi({
61
+ description: 'Booking IDs being simulated',
62
+ example: [123, 124, 125]
63
+ }),
64
+ speed_kmh: z.number().openapi({
65
+ description: 'Simulation speed',
66
+ example: 20
67
+ }),
68
+ update_interval_ms: z.number().int().openapi({
69
+ description: 'Update interval',
70
+ example: 1000
71
+ }),
72
+ mode: simulationModeSchema.openapi({
73
+ description: 'Simulation mode'
74
+ }),
75
+ started_at: z.string().datetime().openapi({
76
+ description: 'When simulation started',
77
+ example: '2026-01-04T12:34:56Z'
78
+ })
79
+ })
80
+ .openapi('StartSimulationData');
81
+ export const startSimulationResponseSchema = createMessageDataResponseSchema(startSimulationDataSchema, 'StartSimulationResponse', 'Simulation started successfully', 'Simulation session details');
82
+ // ============================================================================
83
+ // Stop Simulation Request
84
+ // ============================================================================
85
+ export const stopSimulationBodySchema = z
86
+ .object({
87
+ session_id: z.string().uuid().optional().openapi({
88
+ description: 'Specific session to stop. If omitted, stops all sessions for event.',
89
+ example: '550e8400-e29b-41d4-a716-446655440000'
90
+ }),
91
+ event_id: z.number().int().positive().optional().openapi({
92
+ description: 'Stop all simulations for this event',
93
+ example: 1
94
+ })
95
+ })
96
+ .refine((data) => data.session_id || data.event_id, {
97
+ message: 'Either session_id or event_id must be provided'
98
+ })
99
+ .openapi('StopSimulationBody');
100
+ // ============================================================================
101
+ // Stop Simulation Response
102
+ // ============================================================================
103
+ export const stopSimulationDataSchema = z
104
+ .object({
105
+ sessions_stopped: z.number().int().min(0).openapi({
106
+ description: 'Number of sessions stopped',
107
+ example: 1
108
+ }),
109
+ session_ids: z.array(z.string().uuid()).openapi({
110
+ description: 'IDs of stopped sessions',
111
+ example: ['550e8400-e29b-41d4-a716-446655440000']
112
+ })
113
+ })
114
+ .openapi('StopSimulationData');
115
+ export const stopSimulationResponseSchema = createMessageDataResponseSchema(stopSimulationDataSchema, 'StopSimulationResponse', 'Simulation stopped successfully', 'Stopped session details');
116
+ // ============================================================================
117
+ // Simulation Session (for status display)
118
+ // ============================================================================
119
+ export const simulationSessionSchema = z
120
+ .object({
121
+ id: z.string().uuid().openapi({
122
+ description: 'Session UUID'
123
+ }),
124
+ event_id: z.number().int().positive().openapi({
125
+ description: 'Event ID'
126
+ }),
127
+ speed_kmh: z.number().openapi({
128
+ description: 'Simulation speed'
129
+ }),
130
+ update_interval_ms: z.number().int().openapi({
131
+ description: 'Update interval'
132
+ }),
133
+ mode: simulationModeSchema.openapi({
134
+ description: 'Simulation mode'
135
+ }),
136
+ booking_ids: z.array(z.number().int().positive()).openapi({
137
+ description: 'Bookings being simulated'
138
+ }),
139
+ status: simulationStatusSchema.openapi({
140
+ description: 'Current status'
141
+ }),
142
+ started_at: z.string().datetime().openapi({
143
+ description: 'Start time'
144
+ }),
145
+ stopped_at: z.string().datetime().nullable().openapi({
146
+ description: 'Stop time (if stopped)'
147
+ }),
148
+ positions_generated: z.number().int().min(0).openapi({
149
+ description: 'Number of position updates generated'
150
+ })
151
+ })
152
+ .openapi('SimulationSession');
153
+ // ============================================================================
154
+ // Get Simulation Status Query
155
+ // ============================================================================
156
+ export const getSimulationStatusQuerySchema = z
157
+ .object({
158
+ event_id: z
159
+ .string()
160
+ .transform((val) => parseInt(val, 10))
161
+ .pipe(z.number().int().positive())
162
+ .optional()
163
+ .openapi({
164
+ description: 'Filter by event ID',
165
+ example: '1'
166
+ }),
167
+ include_completed: z
168
+ .string()
169
+ .optional()
170
+ .default('false')
171
+ .transform((val) => val === 'true')
172
+ .openapi({
173
+ description: 'Include completed sessions',
174
+ example: 'false'
175
+ })
176
+ })
177
+ .openapi('GetSimulationStatusQuery');
178
+ // ============================================================================
179
+ // Get Simulation Status Response
180
+ // ============================================================================
181
+ export const simulationStatusDataSchema = z
182
+ .object({
183
+ active: z.boolean().openapi({
184
+ description: 'Whether any simulations are running',
185
+ example: true
186
+ }),
187
+ sessions: z.array(simulationSessionSchema).openapi({
188
+ description: 'List of simulation sessions'
189
+ }),
190
+ total_active: z.number().int().min(0).openapi({
191
+ description: 'Total number of active sessions',
192
+ example: 2
193
+ })
194
+ })
195
+ .openapi('SimulationStatusData');
196
+ export const getSimulationStatusResponseSchema = createSuccessResponseSchema(simulationStatusDataSchema, 'GetSimulationStatusResponse', 'Simulation status');
197
+ // ============================================================================
198
+ // Path Point (for path calculation)
199
+ // ============================================================================
200
+ export const pathPointSchema = z
201
+ .object({
202
+ x: z.number().min(0).max(100).openapi({
203
+ description: 'X coordinate on site plan (0-100%)'
204
+ }),
205
+ y: z.number().min(0).max(100).openapi({
206
+ description: 'Y coordinate on site plan (0-100%)'
207
+ }),
208
+ distance_from_start: z.number().min(0).openapi({
209
+ description: 'Distance from path start in meters'
210
+ })
211
+ })
212
+ .openapi('PathPoint');
213
+ // ============================================================================
214
+ // Simulated Path
215
+ // ============================================================================
216
+ export const simulatedPathSchema = z
217
+ .object({
218
+ booking_id: z.number().int().positive().openapi({
219
+ description: 'Booking ID'
220
+ }),
221
+ points: z.array(pathPointSchema).openapi({
222
+ description: 'Path waypoints'
223
+ }),
224
+ total_distance: z.number().min(0).openapi({
225
+ description: 'Total path distance in meters'
226
+ }),
227
+ estimated_duration: z.number().min(0).openapi({
228
+ description: 'Estimated duration in seconds at configured speed'
229
+ })
230
+ })
231
+ .openapi('SimulatedPath');
@@ -0,0 +1,216 @@
1
+ import { z } from './zod.js';
2
+ import { PositionSource } from './enums/positionSource.js';
3
+ import { VehicleState } from './enums/vehicleState.js';
4
+ import { VehicleType } from './enums/vehicleType.js';
5
+ export declare const positionSourceSchema: z.ZodEnum<{
6
+ gps: PositionSource.GPS;
7
+ simulated: PositionSource.SIMULATED;
8
+ inferred: PositionSource.INFERRED;
9
+ manual: PositionSource.MANUAL;
10
+ }>;
11
+ export declare const vehicleStateSchema: z.ZodEnum<{
12
+ booked: VehicleState.BOOKED;
13
+ approaching: VehicleState.APPROACHING;
14
+ at_gate: VehicleState.AT_GATE;
15
+ entering: VehicleState.ENTERING;
16
+ parked: VehicleState.PARKED;
17
+ departing: VehicleState.DEPARTING;
18
+ at_exit: VehicleState.AT_EXIT;
19
+ exited: VehicleState.EXITED;
20
+ historical: VehicleState.HISTORICAL;
21
+ }>;
22
+ export declare const sitePlanPositionSchema: z.ZodObject<{
23
+ x: z.ZodNumber;
24
+ y: z.ZodNumber;
25
+ heading: z.ZodOptional<z.ZodNumber>;
26
+ }, z.core.$strip>;
27
+ export type SitePlanPosition = z.infer<typeof sitePlanPositionSchema>;
28
+ export declare const gpsCoordinatesSchema: z.ZodObject<{
29
+ latitude: z.ZodNumber;
30
+ longitude: z.ZodNumber;
31
+ }, z.core.$strip>;
32
+ export type GpsCoordinates = z.infer<typeof gpsCoordinatesSchema>;
33
+ export declare const reportPositionBodySchema: z.ZodObject<{
34
+ booking_id: z.ZodNumber;
35
+ access_token: z.ZodString;
36
+ latitude: z.ZodNumber;
37
+ longitude: z.ZodNumber;
38
+ heading: z.ZodOptional<z.ZodNumber>;
39
+ speed_kmh: z.ZodOptional<z.ZodNumber>;
40
+ accuracy_meters: z.ZodOptional<z.ZodNumber>;
41
+ timestamp: z.ZodOptional<z.ZodString>;
42
+ }, z.core.$strip>;
43
+ export type ReportPositionBody = z.infer<typeof reportPositionBodySchema>;
44
+ export declare const reportPositionDataSchema: z.ZodObject<{
45
+ position_id: z.ZodString;
46
+ received_at: z.ZodString;
47
+ site_plan_position: z.ZodOptional<z.ZodObject<{
48
+ x: z.ZodNumber;
49
+ y: z.ZodNumber;
50
+ heading: z.ZodOptional<z.ZodNumber>;
51
+ }, z.core.$strip>>;
52
+ }, z.core.$strip>;
53
+ export type ReportPositionData = z.infer<typeof reportPositionDataSchema>;
54
+ export declare const reportPositionResponseSchema: z.ZodObject<{
55
+ success: z.ZodBoolean;
56
+ data: z.ZodObject<{
57
+ position_id: z.ZodString;
58
+ received_at: z.ZodString;
59
+ site_plan_position: z.ZodOptional<z.ZodObject<{
60
+ x: z.ZodNumber;
61
+ y: z.ZodNumber;
62
+ heading: z.ZodOptional<z.ZodNumber>;
63
+ }, z.core.$strip>>;
64
+ }, z.core.$strip>;
65
+ }, z.core.$strip>;
66
+ export type ReportPositionResponse = z.infer<typeof reportPositionResponseSchema>;
67
+ export declare const vehiclePositionSchema: z.ZodObject<{
68
+ booking_id: z.ZodNumber;
69
+ license_plate: z.ZodString;
70
+ vehicle_type: z.ZodEnum<{
71
+ PL: VehicleType.PL;
72
+ VUL: VehicleType.VUL;
73
+ VL: VehicleType.VL;
74
+ }>;
75
+ company_name: z.ZodString;
76
+ stand_number: z.ZodOptional<z.ZodString>;
77
+ state: z.ZodEnum<{
78
+ booked: VehicleState.BOOKED;
79
+ approaching: VehicleState.APPROACHING;
80
+ at_gate: VehicleState.AT_GATE;
81
+ entering: VehicleState.ENTERING;
82
+ parked: VehicleState.PARKED;
83
+ departing: VehicleState.DEPARTING;
84
+ at_exit: VehicleState.AT_EXIT;
85
+ exited: VehicleState.EXITED;
86
+ historical: VehicleState.HISTORICAL;
87
+ }>;
88
+ position: z.ZodObject<{
89
+ x: z.ZodNumber;
90
+ y: z.ZodNumber;
91
+ heading: z.ZodOptional<z.ZodNumber>;
92
+ }, z.core.$strip>;
93
+ speed_kmh: z.ZodOptional<z.ZodNumber>;
94
+ parking_area_id: z.ZodOptional<z.ZodNumber>;
95
+ parking_area_name: z.ZodOptional<z.ZodString>;
96
+ entry_time: z.ZodNullable<z.ZodString>;
97
+ last_updated: z.ZodString;
98
+ source: z.ZodEnum<{
99
+ gps: PositionSource.GPS;
100
+ simulated: PositionSource.SIMULATED;
101
+ inferred: PositionSource.INFERRED;
102
+ manual: PositionSource.MANUAL;
103
+ }>;
104
+ }, z.core.$strip>;
105
+ export type VehiclePosition = z.infer<typeof vehiclePositionSchema>;
106
+ export declare const getVehiclePositionsQuerySchema: z.ZodObject<{
107
+ since: z.ZodOptional<z.ZodString>;
108
+ include_historical: z.ZodPipe<z.ZodDefault<z.ZodOptional<z.ZodString>>, z.ZodTransform<boolean, string>>;
109
+ state: z.ZodOptional<z.ZodEnum<{
110
+ booked: VehicleState.BOOKED;
111
+ approaching: VehicleState.APPROACHING;
112
+ at_gate: VehicleState.AT_GATE;
113
+ entering: VehicleState.ENTERING;
114
+ parked: VehicleState.PARKED;
115
+ departing: VehicleState.DEPARTING;
116
+ at_exit: VehicleState.AT_EXIT;
117
+ exited: VehicleState.EXITED;
118
+ historical: VehicleState.HISTORICAL;
119
+ }>>;
120
+ }, z.core.$strip>;
121
+ export type GetVehiclePositionsQuery = z.infer<typeof getVehiclePositionsQuerySchema>;
122
+ export declare const vehiclePositionsDataSchema: z.ZodObject<{
123
+ event_id: z.ZodNumber;
124
+ positions: z.ZodArray<z.ZodObject<{
125
+ booking_id: z.ZodNumber;
126
+ license_plate: z.ZodString;
127
+ vehicle_type: z.ZodEnum<{
128
+ PL: VehicleType.PL;
129
+ VUL: VehicleType.VUL;
130
+ VL: VehicleType.VL;
131
+ }>;
132
+ company_name: z.ZodString;
133
+ stand_number: z.ZodOptional<z.ZodString>;
134
+ state: z.ZodEnum<{
135
+ booked: VehicleState.BOOKED;
136
+ approaching: VehicleState.APPROACHING;
137
+ at_gate: VehicleState.AT_GATE;
138
+ entering: VehicleState.ENTERING;
139
+ parked: VehicleState.PARKED;
140
+ departing: VehicleState.DEPARTING;
141
+ at_exit: VehicleState.AT_EXIT;
142
+ exited: VehicleState.EXITED;
143
+ historical: VehicleState.HISTORICAL;
144
+ }>;
145
+ position: z.ZodObject<{
146
+ x: z.ZodNumber;
147
+ y: z.ZodNumber;
148
+ heading: z.ZodOptional<z.ZodNumber>;
149
+ }, z.core.$strip>;
150
+ speed_kmh: z.ZodOptional<z.ZodNumber>;
151
+ parking_area_id: z.ZodOptional<z.ZodNumber>;
152
+ parking_area_name: z.ZodOptional<z.ZodString>;
153
+ entry_time: z.ZodNullable<z.ZodString>;
154
+ last_updated: z.ZodString;
155
+ source: z.ZodEnum<{
156
+ gps: PositionSource.GPS;
157
+ simulated: PositionSource.SIMULATED;
158
+ inferred: PositionSource.INFERRED;
159
+ manual: PositionSource.MANUAL;
160
+ }>;
161
+ }, z.core.$strip>>;
162
+ total_count: z.ZodNumber;
163
+ timestamp: z.ZodString;
164
+ }, z.core.$strip>;
165
+ export type VehiclePositionsData = z.infer<typeof vehiclePositionsDataSchema>;
166
+ export declare const getVehiclePositionsResponseSchema: z.ZodObject<{
167
+ success: z.ZodBoolean;
168
+ data: z.ZodObject<{
169
+ event_id: z.ZodNumber;
170
+ positions: z.ZodArray<z.ZodObject<{
171
+ booking_id: z.ZodNumber;
172
+ license_plate: z.ZodString;
173
+ vehicle_type: z.ZodEnum<{
174
+ PL: VehicleType.PL;
175
+ VUL: VehicleType.VUL;
176
+ VL: VehicleType.VL;
177
+ }>;
178
+ company_name: z.ZodString;
179
+ stand_number: z.ZodOptional<z.ZodString>;
180
+ state: z.ZodEnum<{
181
+ booked: VehicleState.BOOKED;
182
+ approaching: VehicleState.APPROACHING;
183
+ at_gate: VehicleState.AT_GATE;
184
+ entering: VehicleState.ENTERING;
185
+ parked: VehicleState.PARKED;
186
+ departing: VehicleState.DEPARTING;
187
+ at_exit: VehicleState.AT_EXIT;
188
+ exited: VehicleState.EXITED;
189
+ historical: VehicleState.HISTORICAL;
190
+ }>;
191
+ position: z.ZodObject<{
192
+ x: z.ZodNumber;
193
+ y: z.ZodNumber;
194
+ heading: z.ZodOptional<z.ZodNumber>;
195
+ }, z.core.$strip>;
196
+ speed_kmh: z.ZodOptional<z.ZodNumber>;
197
+ parking_area_id: z.ZodOptional<z.ZodNumber>;
198
+ parking_area_name: z.ZodOptional<z.ZodString>;
199
+ entry_time: z.ZodNullable<z.ZodString>;
200
+ last_updated: z.ZodString;
201
+ source: z.ZodEnum<{
202
+ gps: PositionSource.GPS;
203
+ simulated: PositionSource.SIMULATED;
204
+ inferred: PositionSource.INFERRED;
205
+ manual: PositionSource.MANUAL;
206
+ }>;
207
+ }, z.core.$strip>>;
208
+ total_count: z.ZodNumber;
209
+ timestamp: z.ZodString;
210
+ }, z.core.$strip>;
211
+ }, z.core.$strip>;
212
+ export type GetVehiclePositionsResponse = z.infer<typeof getVehiclePositionsResponseSchema>;
213
+ export declare const eventIdParamsSchema: z.ZodObject<{
214
+ eventId: z.ZodPipe<z.ZodPipe<z.ZodString, z.ZodTransform<number, string>>, z.ZodNumber>;
215
+ }, z.core.$strip>;
216
+ export type EventIdParams = z.infer<typeof eventIdParamsSchema>;
@@ -0,0 +1,237 @@
1
+ import { z } from './zod.js';
2
+ import { PositionSource } from './enums/positionSource.js';
3
+ import { VehicleState } from './enums/vehicleState.js';
4
+ import { VehicleType } from './enums/vehicleType.js';
5
+ import { createSuccessResponseSchema } from './common.js';
6
+ // ============================================================================
7
+ // Enums as Zod schemas
8
+ // ============================================================================
9
+ export const positionSourceSchema = z.enum([
10
+ PositionSource.GPS,
11
+ PositionSource.SIMULATED,
12
+ PositionSource.INFERRED,
13
+ PositionSource.MANUAL
14
+ ]);
15
+ export const vehicleStateSchema = z.enum([
16
+ VehicleState.BOOKED,
17
+ VehicleState.APPROACHING,
18
+ VehicleState.AT_GATE,
19
+ VehicleState.ENTERING,
20
+ VehicleState.PARKED,
21
+ VehicleState.DEPARTING,
22
+ VehicleState.AT_EXIT,
23
+ VehicleState.EXITED,
24
+ VehicleState.HISTORICAL
25
+ ]);
26
+ // ============================================================================
27
+ // Site Plan Position Schema (0-100% coordinates)
28
+ // ============================================================================
29
+ export const sitePlanPositionSchema = z
30
+ .object({
31
+ x: z.number().min(0).max(100).openapi({
32
+ description: 'X coordinate on site plan (0-100%)',
33
+ example: 45.5
34
+ }),
35
+ y: z.number().min(0).max(100).openapi({
36
+ description: 'Y coordinate on site plan (0-100%)',
37
+ example: 32.8
38
+ }),
39
+ heading: z.number().min(0).max(360).optional().openapi({
40
+ description: 'Direction of travel in degrees (0-360)',
41
+ example: 90
42
+ })
43
+ })
44
+ .openapi('SitePlanPosition');
45
+ // ============================================================================
46
+ // GPS Coordinates Schema
47
+ // ============================================================================
48
+ export const gpsCoordinatesSchema = z
49
+ .object({
50
+ latitude: z.number().min(-90).max(90).openapi({
51
+ description: 'Latitude coordinate',
52
+ example: 48.8566
53
+ }),
54
+ longitude: z.number().min(-180).max(180).openapi({
55
+ description: 'Longitude coordinate',
56
+ example: 2.3522
57
+ })
58
+ })
59
+ .openapi('GpsCoordinates');
60
+ // ============================================================================
61
+ // Report Position Request (from Mobile App)
62
+ // ============================================================================
63
+ export const reportPositionBodySchema = z
64
+ .object({
65
+ booking_id: z.number().int().positive().openapi({
66
+ description: 'ID of the parking booking',
67
+ example: 123
68
+ }),
69
+ access_token: z.string().min(1).openapi({
70
+ description: 'JWT access token from booking',
71
+ example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'
72
+ }),
73
+ latitude: z.number().min(-90).max(90).openapi({
74
+ description: 'GPS latitude',
75
+ example: 48.8566
76
+ }),
77
+ longitude: z.number().min(-180).max(180).openapi({
78
+ description: 'GPS longitude',
79
+ example: 2.3522
80
+ }),
81
+ heading: z.number().min(0).max(360).optional().openapi({
82
+ description: 'Direction of travel in degrees',
83
+ example: 90
84
+ }),
85
+ speed_kmh: z.number().min(0).max(200).optional().openapi({
86
+ description: 'Speed in km/h',
87
+ example: 15
88
+ }),
89
+ accuracy_meters: z.number().min(0).optional().openapi({
90
+ description: 'GPS accuracy in meters',
91
+ example: 5.2
92
+ }),
93
+ timestamp: z.string().datetime().optional().openapi({
94
+ description: 'ISO 8601 timestamp of the position reading',
95
+ example: '2026-01-04T12:34:56Z'
96
+ })
97
+ })
98
+ .openapi('ReportPositionBody');
99
+ // ============================================================================
100
+ // Report Position Response
101
+ // ============================================================================
102
+ export const reportPositionDataSchema = z
103
+ .object({
104
+ position_id: z.string().uuid().openapi({
105
+ description: 'UUID of the created position record',
106
+ example: '550e8400-e29b-41d4-a716-446655440000'
107
+ }),
108
+ received_at: z.string().datetime().openapi({
109
+ description: 'Server timestamp when position was received',
110
+ example: '2026-01-04T12:34:56Z'
111
+ }),
112
+ site_plan_position: sitePlanPositionSchema.optional().openapi({
113
+ description: 'Converted position on site plan (if within venue bounds)'
114
+ })
115
+ })
116
+ .openapi('ReportPositionData');
117
+ export const reportPositionResponseSchema = createSuccessResponseSchema(reportPositionDataSchema, 'ReportPositionResponse', 'Position report result');
118
+ // ============================================================================
119
+ // Vehicle Position (for Command Center display)
120
+ // ============================================================================
121
+ export const vehiclePositionSchema = z
122
+ .object({
123
+ booking_id: z.number().int().positive().openapi({
124
+ description: 'ID of the parking booking',
125
+ example: 123
126
+ }),
127
+ license_plate: z.string().openapi({
128
+ description: 'Vehicle license plate',
129
+ example: 'AB-123-CD'
130
+ }),
131
+ vehicle_type: z.enum([VehicleType.PL, VehicleType.VUL, VehicleType.VL]).openapi({
132
+ description: 'Type of vehicle',
133
+ example: 'PL'
134
+ }),
135
+ company_name: z.string().openapi({
136
+ description: 'Company name',
137
+ example: 'ACME Transport'
138
+ }),
139
+ stand_number: z.string().optional().openapi({
140
+ description: 'Exhibition stand number',
141
+ example: 'H4-23'
142
+ }),
143
+ state: vehicleStateSchema.openapi({
144
+ description: 'Current vehicle state',
145
+ example: 'parked'
146
+ }),
147
+ position: sitePlanPositionSchema.openapi({
148
+ description: 'Position on site plan'
149
+ }),
150
+ speed_kmh: z.number().min(0).optional().openapi({
151
+ description: 'Current speed in km/h',
152
+ example: 0
153
+ }),
154
+ parking_area_id: z.number().int().positive().optional().openapi({
155
+ description: 'ID of assigned parking area',
156
+ example: 5
157
+ }),
158
+ parking_area_name: z.string().optional().openapi({
159
+ description: 'Name of assigned parking area',
160
+ example: 'Zone A'
161
+ }),
162
+ entry_time: z.string().datetime().nullable().openapi({
163
+ description: 'Time vehicle entered venue',
164
+ example: '2026-01-04T10:30:00Z'
165
+ }),
166
+ last_updated: z.string().datetime().openapi({
167
+ description: 'Last position update timestamp',
168
+ example: '2026-01-04T12:34:56Z'
169
+ }),
170
+ source: positionSourceSchema.openapi({
171
+ description: 'How the position was obtained',
172
+ example: 'gps'
173
+ })
174
+ })
175
+ .openapi('VehiclePosition');
176
+ // ============================================================================
177
+ // Get Vehicle Positions Query
178
+ // ============================================================================
179
+ export const getVehiclePositionsQuerySchema = z
180
+ .object({
181
+ since: z.string().datetime().optional().openapi({
182
+ description: 'Only return positions updated after this time',
183
+ example: '2026-01-04T12:00:00Z'
184
+ }),
185
+ include_historical: z
186
+ .string()
187
+ .optional()
188
+ .default('false')
189
+ .transform((val) => val === 'true')
190
+ .openapi({
191
+ description: 'Include recently exited vehicles',
192
+ example: 'false'
193
+ }),
194
+ state: vehicleStateSchema.optional().openapi({
195
+ description: 'Filter by vehicle state',
196
+ example: 'parked'
197
+ })
198
+ })
199
+ .openapi('GetVehiclePositionsQuery');
200
+ // ============================================================================
201
+ // Get Vehicle Positions Response
202
+ // ============================================================================
203
+ export const vehiclePositionsDataSchema = z
204
+ .object({
205
+ event_id: z.number().int().positive().openapi({
206
+ description: 'Event ID',
207
+ example: 1
208
+ }),
209
+ positions: z.array(vehiclePositionSchema).openapi({
210
+ description: 'List of vehicle positions'
211
+ }),
212
+ total_count: z.number().int().min(0).openapi({
213
+ description: 'Total number of positions returned',
214
+ example: 15
215
+ }),
216
+ timestamp: z.string().datetime().openapi({
217
+ description: 'Server timestamp',
218
+ example: '2026-01-04T12:34:56Z'
219
+ })
220
+ })
221
+ .openapi('VehiclePositionsData');
222
+ export const getVehiclePositionsResponseSchema = createSuccessResponseSchema(vehiclePositionsDataSchema, 'GetVehiclePositionsResponse', 'Vehicle positions for an event');
223
+ // ============================================================================
224
+ // Path Parameters
225
+ // ============================================================================
226
+ export const eventIdParamsSchema = z
227
+ .object({
228
+ eventId: z
229
+ .string()
230
+ .transform((val) => parseInt(val, 10))
231
+ .pipe(z.number().int().positive())
232
+ .openapi({
233
+ description: 'Event ID',
234
+ example: '1'
235
+ })
236
+ })
237
+ .openapi('EventIdParams');