Skip to content

feat(trackpoints): add per-trip GPS trackpoints endpoint#207

Merged
DasBasti merged 1 commit into
DasBasti:mainfrom
jusii:feat/trip-trackpoints
Jun 13, 2026
Merged

feat(trackpoints): add per-trip GPS trackpoints endpoint#207
DasBasti merged 1 commit into
DasBasti:mainfrom
jusii:feat/trip-trackpoints

Conversation

@jusii

@jusii jusii commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds account.get_trip_trackpoints(vin, start_time_ms, end_time_ms, page_size=500) against /vehicle-history-service/journal-service/vehicle/status/history/{VIN} on apiv2.ecloudeu.com. A trip is identified by its (start_time_ms, end_time_ms) window (the endpoint has no trip-id). Returns TripTrackpoints(points=[Trackpoint(lat, lon), ...], total_size) in chronological order, with lat/lon as WGS84 decimal degrees.

Complements get_trip_journal from #194: that endpoint gives per-trip metrics plus the start/end positions embedded in journalLogV4; this one gives the GPS trail between them.

What's added

  • pysmarthashtag/vehicle/trackpoints.pyTrackpoint and TripTrackpoints frozen dataclasses, plus parse_trackpoints_response.
  • pysmarthashtag/account.pyget_trip_trackpoints, module-level TRIP_TRACKPOINTS_PATH_PREFIX, TRIP_TRACKPOINTS_PAGE_SIZE, and a private _is_benign_empty helper.
  • pysmarthashtag/tests/test_trackpoints.py (11 tests) and pysmarthashtag/tests/replys/trackpoints_response.json (4-point newest-first fixture).
  • pysmarthashtag/tests/common.pySmartMockRouter registers the trackpoints path prefix for both test VINs on each base URL.

Design choices worth flagging

No grant_journal_authorization ceremony. Unlike journalLogV4 from #194, this endpoint is not gated behind the per-session authorization/insert handshake. Calling grant defensively per fetch would add a cloud round-trip per trip and risks transient 7065 on back-to-back grants. We observed across sessions that the minimum flow is auth at startup (already done by get_vehicles) plus the single signed GET. test_get_trip_trackpoints_does_not_call_grant registers authorization/insert on both base URLs and asserts zero hits.

pageSize=500, pageIndex=0, no page loop. Page size is fixed at TRIP_TRACKPOINTS_PAGE_SIZE = 500, matching the cap the cloud advertises in its own pagination block. In observed responses totleSize never exceeded this. Rather than ship dead pagination code, the wrapper emits a WARNING ("page-size cap exceeded ... Wrapper does not loop pageIndex; returned points are the head only. Add pagination if this fires for real trips.") and still returns the truncated head. Locked by test_get_trip_trackpoints_warns_on_oversize_total. Caller can override page_size. Note: this endpoint is 0-indexed; journalLogV4 is 1-indexed — empirical, not a typo.

desc reversed to chronological at the wrapper boundary. The cloud reports pagination.direction = "desc" (newest-first). parse_trackpoints_response reverses once via reversed(items) so every consumer sees points[0] = trip start, points[-1] = trip end. Tests test_parse_reverses_to_chronological_order and test_get_trip_trackpoints_returns_chronological_points lock this.

Coordinate scaling — wire format, not Position-equivalence. The cloud transmits lat/lon as integer milliarcseconds; Trackpoint exposes WGS84 decimal degrees (mas / 3,600,000 via _MAS_PER_DEGREE). The existing pysmarthashtag.vehicle.position.Position stores raw mas integers without conversion — Trackpoint's decimal-degree convention is the more consumer-friendly representation for downstream consumers (e.g. HA trip-log integrations needing plottable WGS84). Happy to add a Position.lat_degrees property in a follow-up if preferred; docstring will be reworded to make the divergence explicit rather than claim equivalence.

Defensive parsing. Every nesting level (response_data, data, data.list, data.pagination, each item, basicVehicleStatus, position) is isinstance-guarded. Degraded entries yield Trackpoint(lat=None, lon=None) placeholders rather than dropping out — sequence integrity over per-point completeness. total_size falls back to len(items) when totleSize is missing or non-numeric.

Three benign-empty paths collapse to TripTrackpoints(points=[], total_size=0): code "1000" + empty list, code "1000" + data: null, and code "8153" "data unavailable". The 8153 case arrives as httpx.HTTPStatusError from the lower layer; _is_benign_empty JSON-decodes the response body and matches on code == "8153". All other HTTPStatusErrors re-raise — locked by test_get_trip_trackpoints_normalises_8153 and test_get_trip_trackpoints_propagates_other_http_errors (uses code: "5000").

No refresh-path coupling. get_trip_trackpoints is caller opt-in; it is not invoked from _refresh_vehicle_data. Adding this endpoint cannot make periodic refresh more fragile.

Tests

Parser-only: test_parse_handles_none_and_non_dict, test_parse_handles_empty_data_block, test_parse_handles_empty_list_with_zero_total, test_parse_reverses_to_chronological_order, test_parse_milliarcsecond_to_degree_scaling, test_parse_handles_degraded_per_point_payload, test_parse_non_list_payload_coerces_to_empty.

End-to-end (respx): test_get_trip_trackpoints_returns_chronological_points, test_get_trip_trackpoints_does_not_call_grant, test_get_trip_trackpoints_normalises_8153, test_get_trip_trackpoints_propagates_other_http_errors, test_get_trip_trackpoints_warns_on_oversize_total.

Signing reuses the existing utils.generate_default_header HMAC path — no changes to auth or request signing.

Adds SmartAccount.get_trip_trackpoints(vin, start_time_ms, end_time_ms),
backed by /vehicle-history-service/journal-service/vehicle/status/history/{VIN}
on the same apiv2.ecloudeu.com host as journalLogV4. Returns a
TripTrackpoints dataclass with chronologically-ordered Trackpoints
(cloud sends desc; wrapper reverses at the boundary). Coordinates are
converted from cloud milliarcseconds to WGS84 decimal degrees, matching
the scaling already used by pysmarthashtag.vehicle.position.Position.

Unlike journalLogV4, this endpoint does NOT require the
grant_journal_authorization handshake — calling it defensively per
fetch would burn an extra cloud round-trip and risks transient 7065
errors on back-to-back grants.

Three benign-empty cloud responses (1000+empty list, 1000+null data,
8153 "data unavailable") all normalise to TripTrackpoints(points=[],
total_size=0). Other HTTPStatusError exceptions propagate so callers
can decide whether to retry or surface the error.
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@jusii, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 36 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b267c80a-c77f-477a-ad8f-1e790e393a1f

📥 Commits

Reviewing files that changed from the base of the PR and between a49f0a6 and 1101fbf.

📒 Files selected for processing (5)
  • pysmarthashtag/account.py
  • pysmarthashtag/tests/common.py
  • pysmarthashtag/tests/replys/trackpoints_response.json
  • pysmarthashtag/tests/test_trackpoints.py
  • pysmarthashtag/vehicle/trackpoints.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@DasBasti DasBasti merged commit 0c9f3b7 into DasBasti:main Jun 13, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants