@myrobotaxi/contracts 0.4.0 → 0.6.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.
- package/CONTRIBUTING.md +4 -1
- package/README.md +26 -2
- package/dist/index.cjs +364 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +329 -1
- package/dist/index.d.ts +329 -1
- package/dist/index.js +364 -0
- package/dist/index.js.map +1 -1
- package/dist/types.d.cts +300 -1
- package/dist/types.d.ts +300 -1
- package/package.json +3 -1
- package/schemas/drive-detail.schema.json +174 -0
- package/schemas/drives-list.schema.json +184 -0
package/CONTRIBUTING.md
CHANGED
|
@@ -48,7 +48,10 @@ If you add a new `x-*` keyword to a schema, add it to the `ANNOTATIONS` array in
|
|
|
48
48
|
|
|
49
49
|
The repo doubles as a Swift package (`Package.swift` at the root, product `MyRobotaxiContracts`). Swift types are generated from the same `schemas/*.json` source of truth:
|
|
50
50
|
|
|
51
|
-
- **Generator**: `scripts/codegen-swift.mjs` — a small custom Node script (zero npm dependencies; runs without `npm ci`). One `Codable`/`Equatable`/`Sendable` struct per root schema object and per `$defs` entry (websocket-protocol.md §9.2); string enums become Swift `enum
|
|
51
|
+
- **Generator**: `scripts/codegen-swift.mjs` — a small custom Node script (zero npm dependencies; runs without `npm ci`). One `Codable`/`Equatable`/`Sendable` struct per root schema object and per `$defs` entry (websocket-protocol.md §9.2); string enums become **decode-tolerant** Swift enums — a `RawRepresentable` enum with the known cases plus a catch-all `case unrecognized(String)` (MYR-195). Unknown wire values decode to `.unrecognized(raw)` instead of throwing (so a newer contracts version adding an enum member never hard-fails an older installed client's whole-frame decode) and re-encode byte-identically. The generator emits a small suite of unit tests (`src/codegen-swift.test.ts`) alongside the Swift `swift test` round-trip tests.
|
|
52
|
+
- **Loud codegen errors**: to prevent silent wire drift, `scripts/codegen-swift.mjs` hard-errors (naming the schema path) rather than degrading when it hits a construct it can't faithfully map:
|
|
53
|
+
- a property enum whose members match a `$defs` enum but in a **different order** (would otherwise silently mint a near-duplicate nested type) — reorder the property enum to match the `$defs` order, or replace it with a `$ref`;
|
|
54
|
+
- schema composition (`oneOf` / `anyOf` / `allOf`) and **non-string enums** (would otherwise silently degrade to `JSONValue` / `Int`) — model the position as a concrete typed object, a string enum, or an explicit open `JSONValue`.
|
|
52
55
|
- **Output**: `Sources/MyRobotaxiContracts/Generated/*.swift` — **committed**, never hand-edited (same rule as `src/generated/`). Regeneration is deterministic: emission order follows the schema JSON, so re-running on an unchanged schema is a no-op diff.
|
|
53
56
|
- **Hand-written support code**: `Sources/MyRobotaxiContracts/JSONValue.swift` (arbitrary-JSON enum used for `WebSocketEnvelope.payload` and `VehicleUpdatePayload.fields`) is NOT generated and may be edited.
|
|
54
57
|
- **Regenerate**: `npm run codegen:swift` after any schema change, and commit the result alongside the regenerated TypeScript.
|
package/README.md
CHANGED
|
@@ -25,13 +25,17 @@ Runtime schemas for Ajv / runtime validation:
|
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
27
|
import { schemas } from '@myrobotaxi/contracts';
|
|
28
|
-
// schemas.vehicleState, schemas.
|
|
28
|
+
// schemas.vehicleState, schemas.vehicleSummary, schemas.drivesList,
|
|
29
|
+
// schemas.driveDetail, schemas.wsMessages, schemas.wsEnvelope
|
|
29
30
|
```
|
|
30
31
|
|
|
31
32
|
Raw JSON for non-JS consumers (Go):
|
|
32
33
|
|
|
33
34
|
```
|
|
34
35
|
./node_modules/@myrobotaxi/contracts/schemas/vehicle-state.schema.json
|
|
36
|
+
./node_modules/@myrobotaxi/contracts/schemas/vehicle-summary.schema.json
|
|
37
|
+
./node_modules/@myrobotaxi/contracts/schemas/drives-list.schema.json
|
|
38
|
+
./node_modules/@myrobotaxi/contracts/schemas/drive-detail.schema.json
|
|
35
39
|
./node_modules/@myrobotaxi/contracts/schemas/ws-messages.schema.json
|
|
36
40
|
./node_modules/@myrobotaxi/contracts/schemas/ws-envelope.schema.json
|
|
37
41
|
```
|
|
@@ -64,7 +68,27 @@ let envelope = try JSONDecoder().decode(WebSocketEnvelope.self, from: frameData)
|
|
|
64
68
|
if envelope.type == .vehicleUpdate { /* decode VehicleUpdatePayload from envelope.payload */ }
|
|
65
69
|
```
|
|
66
70
|
|
|
67
|
-
The same custom annotations are folded into the generated SwiftDoc comments, so `grep '@classification "P1"'` works inside the checked-out package too. Wire enums (`status`, `chargeState`, `MessageType`, error codes, …) generate as Swift
|
|
71
|
+
The same custom annotations are folded into the generated SwiftDoc comments, so `grep '@classification "P1"'` works inside the checked-out package too. Wire enums (`status`, `chargeState`, `MessageType`, error codes, …) generate as **decode-tolerant** Swift enums: a `RawRepresentable` enum with the known cases plus a catch-all `case unrecognized(String)` (MYR-195). Nullable / non-required schema fields are Swift optionals. The intentionally-open wire positions — `WebSocketEnvelope.payload` and `VehicleUpdatePayload.fields` — are typed via the hand-written `JSONValue` enum, which preserves explicit JSON `null`s (required for atomic-group clears per NFR-3.9).
|
|
72
|
+
|
|
73
|
+
### Decode-tolerant wire enums (forward-compat for installed clients)
|
|
74
|
+
|
|
75
|
+
A plain `enum: String` (Swift's synthesized `Codable`) throws on the first unrecognized value, which would make **one** new enum member shipped in a newer contracts version hard-fail the *entire* frame/snapshot decode on a not-yet-updated iOS app. To keep installed clients forward-compatible, each string wire enum is emitted as:
|
|
76
|
+
|
|
77
|
+
```swift
|
|
78
|
+
public enum Status: RawRepresentable, Codable, Hashable, Sendable {
|
|
79
|
+
case driving
|
|
80
|
+
case parked
|
|
81
|
+
// …known cases…
|
|
82
|
+
/// A wire value not known to this build of the contracts package.
|
|
83
|
+
case unrecognized(String)
|
|
84
|
+
// init(rawValue:) / rawValue / init(from:) / encode(to:) map every string,
|
|
85
|
+
// falling back to .unrecognized(raw) instead of throwing.
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
- **Unknown values never throw** — they decode to `.unrecognized(raw)`, so the surrounding `VehicleState` / `WebSocketEnvelope` still decodes.
|
|
90
|
+
- **Round-trips byte-identically** — the raw string is preserved and re-encoded verbatim, so a relaying client never corrupts a value it doesn't understand.
|
|
91
|
+
- **Ergonomic for consumers** — `switch status { case .driving: …; case .unrecognized(let raw): … }` stays exhaustive and compiler-checked; `== .parked` and `.rawValue` work as before. The catch-all is named `unrecognized` (not `unknown`) because `ChargeState` already has a known member whose wire value is `"Unknown"`.
|
|
68
92
|
|
|
69
93
|
## Tool choice (Swift): custom generator
|
|
70
94
|
|
package/dist/index.cjs
CHANGED
|
@@ -455,6 +455,368 @@ var vehicle_summary_schema_default = {
|
|
|
455
455
|
}
|
|
456
456
|
};
|
|
457
457
|
|
|
458
|
+
// schemas/drives-list.schema.json
|
|
459
|
+
var drives_list_schema_default = {
|
|
460
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
461
|
+
$id: "https://myrobotaxi.com/schemas/drives-list.schema.json",
|
|
462
|
+
title: "DrivesListResponse",
|
|
463
|
+
description: "Cursor-paginated envelope returned by GET /api/vehicles/{vehicleId}/drives (rest-api.md \xA77.2). Wraps an array of DriveSummary rows under `items`, newest first (ORDER BY startTime DESC, id DESC). Mirrors the Go `drivesPageResponse` struct in telemetry/internal/telemetry/vehicle_drives_types.go and the `PaginatedDrives` component in telemetry/docs/contracts/specs/rest.openapi.yaml. Pairs with the WebSocket `drive_ended` subscription for FR-9.1/FR-9.2: the SDK hydrates the first page here and prepends live `drive_ended` frames without re-fetching. The envelope is part of the wire contract: consumers MUST NOT strip it silently.",
|
|
464
|
+
type: "object",
|
|
465
|
+
additionalProperties: false,
|
|
466
|
+
required: ["items", "nextCursor", "hasMore"],
|
|
467
|
+
properties: {
|
|
468
|
+
items: {
|
|
469
|
+
type: "array",
|
|
470
|
+
description: "Drive summary rows for this page. Always present; an empty array (never null) means no more drives \u2014 e.g. the final page or a vehicle with no completed drives. Ordered startTime DESC, id DESC (rest-api.md \xA77.2 Ordering).",
|
|
471
|
+
"x-classification": "P0",
|
|
472
|
+
items: { $ref: "#/$defs/DriveSummary" }
|
|
473
|
+
},
|
|
474
|
+
nextCursor: {
|
|
475
|
+
type: ["string", "null"],
|
|
476
|
+
description: "Opaque base64 cursor for the next page, or null on the final page. Pass it back as the `cursor` query param to fetch the next page. Encodes (startTime, id) so pagination is stable across concurrent writes. Nullable on the wire \u2014 the Go server emits JSON `null` (not an omitted key) when there is no further page (drivesPageResponse.NextCursor is a `*string` with no omitempty, telemetry/internal/telemetry/vehicle_drives_types.go). Redundant with `hasMore == false`.",
|
|
477
|
+
"x-classification": "P0"
|
|
478
|
+
},
|
|
479
|
+
hasMore: {
|
|
480
|
+
type: "boolean",
|
|
481
|
+
description: "True iff more pages exist after this one. Redundant with `nextCursor != null`, provided for caller convenience.",
|
|
482
|
+
"x-classification": "P0"
|
|
483
|
+
}
|
|
484
|
+
},
|
|
485
|
+
$defs: {
|
|
486
|
+
DriveSummary: {
|
|
487
|
+
title: "DriveSummary",
|
|
488
|
+
description: "Lightweight per-drive row returned by GET /api/vehicles/{vehicleId}/drives. A list-view projection of a completed drive containing the FR-3.4 headline stats plus origin/destination labels, so the history list renders without a per-row DriveDetail fetch. Includes the four P1 location/address labels (MYR-145) and the P0 fsdMiles/fsdPercentage stats (MYR-152). Deliberately omits energyUsedKwh, interventions, and routePoints \u2014 those live on Drive (drive detail, \xA77.3) and DriveRoute (\xA77.4). Mirrors the Go `driveSummary` struct + `toMaskMap` in telemetry/internal/telemetry/vehicle_drives_types.go and the `DriveSummary` component in telemetry/docs/contracts/specs/rest.openapi.yaml.",
|
|
489
|
+
type: "object",
|
|
490
|
+
additionalProperties: false,
|
|
491
|
+
required: [
|
|
492
|
+
"id",
|
|
493
|
+
"vehicleId",
|
|
494
|
+
"startTime",
|
|
495
|
+
"endTime",
|
|
496
|
+
"date",
|
|
497
|
+
"distanceMiles",
|
|
498
|
+
"durationSeconds",
|
|
499
|
+
"avgSpeedMph",
|
|
500
|
+
"maxSpeedMph",
|
|
501
|
+
"startChargeLevel",
|
|
502
|
+
"endChargeLevel",
|
|
503
|
+
"fsdMiles",
|
|
504
|
+
"fsdPercentage",
|
|
505
|
+
"createdAt"
|
|
506
|
+
],
|
|
507
|
+
properties: {
|
|
508
|
+
id: {
|
|
509
|
+
type: "string",
|
|
510
|
+
description: "Opaque database identifier (cuid) for this drive. Matches the `driveId` carried by `drive_started` / `drive_ended` WebSocket frames and the path param of GET /api/drives/{driveId}.",
|
|
511
|
+
"x-classification": "P0",
|
|
512
|
+
examples: ["clmno9876543210zyxw0001"]
|
|
513
|
+
},
|
|
514
|
+
vehicleId: {
|
|
515
|
+
type: "string",
|
|
516
|
+
description: "Opaque database identifier (cuid) for the drive's parent vehicle. Matches VehicleState.vehicleId / VehicleSummary.vehicleId.",
|
|
517
|
+
"x-classification": "P0",
|
|
518
|
+
examples: ["clxyz1234567890abcdef"]
|
|
519
|
+
},
|
|
520
|
+
startTime: {
|
|
521
|
+
type: "string",
|
|
522
|
+
format: "date-time",
|
|
523
|
+
description: "ISO 8601 / RFC 3339 UTC timestamp when the drive began (e.g., '2026-04-13T18:22:00Z').",
|
|
524
|
+
"x-classification": "P0",
|
|
525
|
+
examples: ["2026-04-13T18:22:00Z"]
|
|
526
|
+
},
|
|
527
|
+
endTime: {
|
|
528
|
+
type: "string",
|
|
529
|
+
format: "date-time",
|
|
530
|
+
description: "ISO 8601 / RFC 3339 UTC timestamp when the drive ended (e.g., '2026-04-13T18:46:18Z').",
|
|
531
|
+
"x-classification": "P0",
|
|
532
|
+
examples: ["2026-04-13T18:46:18Z"]
|
|
533
|
+
},
|
|
534
|
+
date: {
|
|
535
|
+
type: "string",
|
|
536
|
+
format: "date",
|
|
537
|
+
description: "Local calendar date of the drive as a 'YYYY-MM-DD' string (e.g., '2026-04-13'), for day-grouping the history list. Not a full timestamp \u2014 use startTime/endTime for instants.",
|
|
538
|
+
"x-classification": "P0",
|
|
539
|
+
examples: ["2026-04-13"]
|
|
540
|
+
},
|
|
541
|
+
startLocation: {
|
|
542
|
+
type: "string",
|
|
543
|
+
description: "Reverse-geocoded place name at the drive's start (e.g., 'Home', 'Whole Foods Market'). OPTIONAL on the wire: the Go handler OMITS this key entirely (never emits '' or null) when the underlying Drive.startLocation column is empty \u2014 zero-GPS at drive start or reverse-geocode failed (toMaskMap, telemetry/internal/telemetry/vehicle_drives_types.go). Consumers branch on key presence.",
|
|
544
|
+
"x-classification": "P1",
|
|
545
|
+
examples: ["Home"]
|
|
546
|
+
},
|
|
547
|
+
startAddress: {
|
|
548
|
+
type: "string",
|
|
549
|
+
description: "Reverse-geocoded street address at the drive's start. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.startAddress column is empty (same omit-when-empty convention as startLocation).",
|
|
550
|
+
"x-classification": "P1",
|
|
551
|
+
examples: ["742 Evergreen Terrace, San Francisco, CA 94107"]
|
|
552
|
+
},
|
|
553
|
+
endLocation: {
|
|
554
|
+
type: "string",
|
|
555
|
+
description: "Reverse-geocoded place name at the drive's end. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.endLocation column is empty \u2014 drive in progress, zero-GPS at drive end, or reverse-geocode failed.",
|
|
556
|
+
"x-classification": "P1",
|
|
557
|
+
examples: ["Whole Foods Market"]
|
|
558
|
+
},
|
|
559
|
+
endAddress: {
|
|
560
|
+
type: "string",
|
|
561
|
+
description: "Reverse-geocoded street address at the drive's end. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.endAddress column is empty.",
|
|
562
|
+
"x-classification": "P1",
|
|
563
|
+
examples: ["399 4th Street, San Francisco, CA 94107"]
|
|
564
|
+
},
|
|
565
|
+
distanceMiles: {
|
|
566
|
+
type: "number",
|
|
567
|
+
description: "Total distance driven, in miles (FR-3.4).",
|
|
568
|
+
"x-classification": "P0",
|
|
569
|
+
"x-unit": "miles",
|
|
570
|
+
minimum: 0,
|
|
571
|
+
examples: [12.4]
|
|
572
|
+
},
|
|
573
|
+
durationSeconds: {
|
|
574
|
+
type: "integer",
|
|
575
|
+
description: "Total drive duration, in seconds (FR-3.4). The Go server converts the stored DurationMinutes to seconds (DurationMinutes * 60) before emitting. Matches the WebSocket drive_ended.payload.durationSeconds shape (DV-12).",
|
|
576
|
+
"x-classification": "P0",
|
|
577
|
+
"x-unit": "seconds",
|
|
578
|
+
minimum: 0,
|
|
579
|
+
examples: [1458]
|
|
580
|
+
},
|
|
581
|
+
avgSpeedMph: {
|
|
582
|
+
type: "number",
|
|
583
|
+
description: "Average speed over the drive, in miles per hour (FR-3.4).",
|
|
584
|
+
"x-classification": "P0",
|
|
585
|
+
"x-unit": "mph",
|
|
586
|
+
minimum: 0,
|
|
587
|
+
examples: [30.5]
|
|
588
|
+
},
|
|
589
|
+
maxSpeedMph: {
|
|
590
|
+
type: "number",
|
|
591
|
+
description: "Maximum speed reached during the drive, in miles per hour (FR-3.4).",
|
|
592
|
+
"x-classification": "P0",
|
|
593
|
+
"x-unit": "mph",
|
|
594
|
+
minimum: 0,
|
|
595
|
+
examples: [65.2]
|
|
596
|
+
},
|
|
597
|
+
startChargeLevel: {
|
|
598
|
+
type: "integer",
|
|
599
|
+
description: "Battery state of charge at drive start (FR-3.4).",
|
|
600
|
+
"x-classification": "P0",
|
|
601
|
+
"x-unit": "percent",
|
|
602
|
+
minimum: 0,
|
|
603
|
+
maximum: 100,
|
|
604
|
+
examples: [82]
|
|
605
|
+
},
|
|
606
|
+
endChargeLevel: {
|
|
607
|
+
type: "integer",
|
|
608
|
+
description: "Battery state of charge at drive end (FR-3.4).",
|
|
609
|
+
"x-classification": "P0",
|
|
610
|
+
"x-unit": "percent",
|
|
611
|
+
minimum: 0,
|
|
612
|
+
maximum: 100,
|
|
613
|
+
examples: [76]
|
|
614
|
+
},
|
|
615
|
+
fsdMiles: {
|
|
616
|
+
type: "number",
|
|
617
|
+
description: "Miles driven under Full Self-Driving during this drive (FR-3.4, MYR-152). Always present; defaults to 0 for a drive with no FSD usage.",
|
|
618
|
+
"x-classification": "P0",
|
|
619
|
+
"x-unit": "miles",
|
|
620
|
+
minimum: 0,
|
|
621
|
+
examples: [8.1]
|
|
622
|
+
},
|
|
623
|
+
fsdPercentage: {
|
|
624
|
+
type: "number",
|
|
625
|
+
description: "Percent of the drive covered by FSD (FR-3.4, MYR-152). Always present; defaults to 0 for a drive with no FSD usage.",
|
|
626
|
+
"x-classification": "P0",
|
|
627
|
+
"x-unit": "percent",
|
|
628
|
+
minimum: 0,
|
|
629
|
+
maximum: 100,
|
|
630
|
+
examples: [65.3]
|
|
631
|
+
},
|
|
632
|
+
createdAt: {
|
|
633
|
+
type: "string",
|
|
634
|
+
format: "date-time",
|
|
635
|
+
description: "ISO 8601 / RFC 3339 UTC row-creation timestamp. The Go server renders it as `CreatedAt.UTC().Format(time.RFC3339)` (telemetry/internal/telemetry/vehicle_drives_types.go).",
|
|
636
|
+
"x-classification": "P0",
|
|
637
|
+
examples: ["2026-04-13T18:46:19Z"]
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
// schemas/drive-detail.schema.json
|
|
645
|
+
var drive_detail_schema_default = {
|
|
646
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
647
|
+
$id: "https://myrobotaxi.com/schemas/drive-detail.schema.json",
|
|
648
|
+
title: "Drive",
|
|
649
|
+
description: "Full FR-3.4 record for a single completed drive, returned as a bare object (NOT an envelope) by GET /api/drives/{driveId} (rest-api.md \xA77.3). Contains every FR-3.4 stat EXCEPT routePoints, which is served separately by GET /api/drives/{driveId}/route (DriveRoute, \xA77.4). This is the tap-through detail behind a DriveSummary row / a `drive_ended` frame: the SDK's fetchDrive(driveId) helper. Mirrors the Go `driveDetail` struct + `toMaskMap` in telemetry/internal/telemetry/drive_detail_types.go and the `DriveDetail` component in telemetry/docs/contracts/specs/rest.openapi.yaml. Owners and viewers see the same field set including start/end location and address (viewer consent via accepted invite, \xA75.2.3).",
|
|
650
|
+
type: "object",
|
|
651
|
+
additionalProperties: false,
|
|
652
|
+
required: [
|
|
653
|
+
"id",
|
|
654
|
+
"vehicleId",
|
|
655
|
+
"startTime",
|
|
656
|
+
"endTime",
|
|
657
|
+
"date",
|
|
658
|
+
"distanceMiles",
|
|
659
|
+
"durationSeconds",
|
|
660
|
+
"avgSpeedMph",
|
|
661
|
+
"maxSpeedMph",
|
|
662
|
+
"energyUsedKwh",
|
|
663
|
+
"startChargeLevel",
|
|
664
|
+
"endChargeLevel",
|
|
665
|
+
"fsdMiles",
|
|
666
|
+
"fsdPercentage",
|
|
667
|
+
"interventions",
|
|
668
|
+
"createdAt"
|
|
669
|
+
],
|
|
670
|
+
properties: {
|
|
671
|
+
id: {
|
|
672
|
+
type: "string",
|
|
673
|
+
description: "Opaque database identifier (cuid) for this drive. Matches DriveSummary.id and the `driveId` carried by `drive_started` / `drive_ended` WebSocket frames.",
|
|
674
|
+
"x-classification": "P0",
|
|
675
|
+
examples: ["clmno9876543210zyxw0001"]
|
|
676
|
+
},
|
|
677
|
+
vehicleId: {
|
|
678
|
+
type: "string",
|
|
679
|
+
description: "Opaque database identifier (cuid) for the drive's parent vehicle. Matches VehicleState.vehicleId / VehicleSummary.vehicleId / DriveSummary.vehicleId.",
|
|
680
|
+
"x-classification": "P0",
|
|
681
|
+
examples: ["clxyz1234567890abcdef"]
|
|
682
|
+
},
|
|
683
|
+
startTime: {
|
|
684
|
+
type: "string",
|
|
685
|
+
format: "date-time",
|
|
686
|
+
description: "ISO 8601 / RFC 3339 UTC timestamp when the drive began (e.g., '2026-04-13T18:22:00Z').",
|
|
687
|
+
"x-classification": "P0",
|
|
688
|
+
examples: ["2026-04-13T18:22:00Z"]
|
|
689
|
+
},
|
|
690
|
+
endTime: {
|
|
691
|
+
type: "string",
|
|
692
|
+
format: "date-time",
|
|
693
|
+
description: "ISO 8601 / RFC 3339 UTC timestamp when the drive ended (e.g., '2026-04-13T18:46:18Z').",
|
|
694
|
+
"x-classification": "P0",
|
|
695
|
+
examples: ["2026-04-13T18:46:18Z"]
|
|
696
|
+
},
|
|
697
|
+
date: {
|
|
698
|
+
type: "string",
|
|
699
|
+
format: "date",
|
|
700
|
+
description: "Local calendar date of the drive as a 'YYYY-MM-DD' string (e.g., '2026-04-13'). Same field as DriveSummary.date.",
|
|
701
|
+
"x-classification": "P0",
|
|
702
|
+
examples: ["2026-04-13"]
|
|
703
|
+
},
|
|
704
|
+
distanceMiles: {
|
|
705
|
+
type: "number",
|
|
706
|
+
description: "Total distance driven, in miles (FR-3.4).",
|
|
707
|
+
"x-classification": "P0",
|
|
708
|
+
"x-unit": "miles",
|
|
709
|
+
minimum: 0,
|
|
710
|
+
examples: [12.4]
|
|
711
|
+
},
|
|
712
|
+
durationSeconds: {
|
|
713
|
+
type: "integer",
|
|
714
|
+
description: "Total drive duration, in seconds (FR-3.4). The Go server converts the stored DurationMinutes to seconds (DurationMinutes * 60) before emitting.",
|
|
715
|
+
"x-classification": "P0",
|
|
716
|
+
"x-unit": "seconds",
|
|
717
|
+
minimum: 0,
|
|
718
|
+
examples: [1458]
|
|
719
|
+
},
|
|
720
|
+
avgSpeedMph: {
|
|
721
|
+
type: "number",
|
|
722
|
+
description: "Average speed over the drive, in miles per hour (FR-3.4).",
|
|
723
|
+
"x-classification": "P0",
|
|
724
|
+
"x-unit": "mph",
|
|
725
|
+
minimum: 0,
|
|
726
|
+
examples: [30.5]
|
|
727
|
+
},
|
|
728
|
+
maxSpeedMph: {
|
|
729
|
+
type: "number",
|
|
730
|
+
description: "Maximum speed reached during the drive, in miles per hour (FR-3.4).",
|
|
731
|
+
"x-classification": "P0",
|
|
732
|
+
"x-unit": "mph",
|
|
733
|
+
minimum: 0,
|
|
734
|
+
examples: [65.2]
|
|
735
|
+
},
|
|
736
|
+
energyUsedKwh: {
|
|
737
|
+
type: "number",
|
|
738
|
+
description: "Energy consumed during the drive, in kilowatt-hours (FR-3.4). Detail-only stat \u2014 deliberately absent from DriveSummary.",
|
|
739
|
+
"x-classification": "P0",
|
|
740
|
+
"x-unit": "kWh",
|
|
741
|
+
minimum: 0,
|
|
742
|
+
examples: [4.2]
|
|
743
|
+
},
|
|
744
|
+
startChargeLevel: {
|
|
745
|
+
type: "integer",
|
|
746
|
+
description: "Battery state of charge at drive start (FR-3.4).",
|
|
747
|
+
"x-classification": "P0",
|
|
748
|
+
"x-unit": "percent",
|
|
749
|
+
minimum: 0,
|
|
750
|
+
maximum: 100,
|
|
751
|
+
examples: [82]
|
|
752
|
+
},
|
|
753
|
+
endChargeLevel: {
|
|
754
|
+
type: "integer",
|
|
755
|
+
description: "Battery state of charge at drive end (FR-3.4).",
|
|
756
|
+
"x-classification": "P0",
|
|
757
|
+
"x-unit": "percent",
|
|
758
|
+
minimum: 0,
|
|
759
|
+
maximum: 100,
|
|
760
|
+
examples: [76]
|
|
761
|
+
},
|
|
762
|
+
fsdMiles: {
|
|
763
|
+
type: "number",
|
|
764
|
+
description: "Miles driven under Full Self-Driving during this drive (FR-3.4). Always present; defaults to 0 for a drive with no FSD usage.",
|
|
765
|
+
"x-classification": "P0",
|
|
766
|
+
"x-unit": "miles",
|
|
767
|
+
minimum: 0,
|
|
768
|
+
examples: [8.1]
|
|
769
|
+
},
|
|
770
|
+
fsdPercentage: {
|
|
771
|
+
type: "number",
|
|
772
|
+
description: "Percent of the drive covered by FSD (FR-3.4). Always present; defaults to 0 for a drive with no FSD usage.",
|
|
773
|
+
"x-classification": "P0",
|
|
774
|
+
"x-unit": "percent",
|
|
775
|
+
minimum: 0,
|
|
776
|
+
maximum: 100,
|
|
777
|
+
examples: [65.3]
|
|
778
|
+
},
|
|
779
|
+
interventions: {
|
|
780
|
+
type: "integer",
|
|
781
|
+
description: "Count of FSD interventions recorded during the drive (FR-3.4). Detail-only stat \u2014 deliberately absent from DriveSummary.",
|
|
782
|
+
"x-classification": "P0",
|
|
783
|
+
minimum: 0,
|
|
784
|
+
examples: [1]
|
|
785
|
+
},
|
|
786
|
+
startLocation: {
|
|
787
|
+
type: "string",
|
|
788
|
+
description: "Reverse-geocoded place name at the drive's start (e.g., 'Home'). OPTIONAL on the wire: the Go handler OMITS this key entirely (never emits '' or null) when the underlying Drive.startLocation column is empty (toMaskMap, telemetry/internal/telemetry/drive_detail_types.go). Consumers branch on key presence. NOTE: the OpenAPI DriveDetail component types this `[string, null]`, but the Go handler omits the key rather than emitting null \u2014 the code is ground truth (see MYR-202 PR body doc-drift note).",
|
|
789
|
+
"x-classification": "P1",
|
|
790
|
+
examples: ["Home"]
|
|
791
|
+
},
|
|
792
|
+
startAddress: {
|
|
793
|
+
type: "string",
|
|
794
|
+
description: "Reverse-geocoded street address at the drive's start. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.startAddress column is empty (same omit-when-empty convention as startLocation).",
|
|
795
|
+
"x-classification": "P1",
|
|
796
|
+
examples: ["742 Evergreen Terrace, San Francisco, CA 94107"]
|
|
797
|
+
},
|
|
798
|
+
endLocation: {
|
|
799
|
+
type: "string",
|
|
800
|
+
description: "Reverse-geocoded place name at the drive's end. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.endLocation column is empty.",
|
|
801
|
+
"x-classification": "P1",
|
|
802
|
+
examples: ["Whole Foods Market"]
|
|
803
|
+
},
|
|
804
|
+
endAddress: {
|
|
805
|
+
type: "string",
|
|
806
|
+
description: "Reverse-geocoded street address at the drive's end. OPTIONAL on the wire: the key is omitted entirely when the underlying Drive.endAddress column is empty.",
|
|
807
|
+
"x-classification": "P1",
|
|
808
|
+
examples: ["399 4th Street, San Francisco, CA 94107"]
|
|
809
|
+
},
|
|
810
|
+
createdAt: {
|
|
811
|
+
type: "string",
|
|
812
|
+
format: "date-time",
|
|
813
|
+
description: "ISO 8601 / RFC 3339 UTC row-creation timestamp. The Go server renders it as `CreatedAt.UTC().Format(time.RFC3339)` (telemetry/internal/telemetry/drive_detail_types.go).",
|
|
814
|
+
"x-classification": "P0",
|
|
815
|
+
examples: ["2026-04-13T18:46:19Z"]
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
};
|
|
819
|
+
|
|
458
820
|
// schemas/ws-messages.schema.json
|
|
459
821
|
var ws_messages_schema_default = {
|
|
460
822
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -825,6 +1187,8 @@ var ws_envelope_schema_default = {
|
|
|
825
1187
|
var schemas = {
|
|
826
1188
|
vehicleState: vehicle_state_schema_default,
|
|
827
1189
|
vehicleSummary: vehicle_summary_schema_default,
|
|
1190
|
+
drivesList: drives_list_schema_default,
|
|
1191
|
+
driveDetail: drive_detail_schema_default,
|
|
828
1192
|
wsMessages: ws_messages_schema_default,
|
|
829
1193
|
wsEnvelope: ws_envelope_schema_default
|
|
830
1194
|
};
|