fleetpull 0.1.0__py3-none-any.whl

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 (252) hide show
  1. fleetpull/__init__.py +36 -0
  2. fleetpull/api/__init__.py +32 -0
  3. fleetpull/api/auth_ingress.py +216 -0
  4. fleetpull/api/catalog.py +139 -0
  5. fleetpull/api/fetch.py +229 -0
  6. fleetpull/api/identity.py +90 -0
  7. fleetpull/api/sync.py +735 -0
  8. fleetpull/cli.py +146 -0
  9. fleetpull/config/__init__.py +47 -0
  10. fleetpull/config/base.py +18 -0
  11. fleetpull/config/example.py +90 -0
  12. fleetpull/config/geotab.py +78 -0
  13. fleetpull/config/http.py +32 -0
  14. fleetpull/config/loading.py +195 -0
  15. fleetpull/config/logger.py +139 -0
  16. fleetpull/config/providers.py +520 -0
  17. fleetpull/config/rate_limit.py +50 -0
  18. fleetpull/config/resolution.py +156 -0
  19. fleetpull/config/retry.py +69 -0
  20. fleetpull/config/root.py +157 -0
  21. fleetpull/config/sections.py +146 -0
  22. fleetpull/endpoints/__init__.py +24 -0
  23. fleetpull/endpoints/geotab/__init__.py +12 -0
  24. fleetpull/endpoints/geotab/_requests.py +427 -0
  25. fleetpull/endpoints/geotab/annotation_logs.py +73 -0
  26. fleetpull/endpoints/geotab/audits.py +70 -0
  27. fleetpull/endpoints/geotab/devices.py +79 -0
  28. fleetpull/endpoints/geotab/driver_changes.py +73 -0
  29. fleetpull/endpoints/geotab/duty_status_logs.py +75 -0
  30. fleetpull/endpoints/geotab/dvir_logs.py +76 -0
  31. fleetpull/endpoints/geotab/exception_events.py +119 -0
  32. fleetpull/endpoints/geotab/fault_data.py +72 -0
  33. fleetpull/endpoints/geotab/fill_ups.py +79 -0
  34. fleetpull/endpoints/geotab/fuel_and_energy_used.py +74 -0
  35. fleetpull/endpoints/geotab/fuel_tax_details.py +75 -0
  36. fleetpull/endpoints/geotab/log_records.py +74 -0
  37. fleetpull/endpoints/geotab/media_files.py +74 -0
  38. fleetpull/endpoints/geotab/shipment_logs.py +71 -0
  39. fleetpull/endpoints/geotab/status_data.py +71 -0
  40. fleetpull/endpoints/geotab/text_messages.py +77 -0
  41. fleetpull/endpoints/geotab/trips.py +102 -0
  42. fleetpull/endpoints/geotab/users.py +81 -0
  43. fleetpull/endpoints/motive/__init__.py +12 -0
  44. fleetpull/endpoints/motive/_spec_builders.py +94 -0
  45. fleetpull/endpoints/motive/driver_idle_rollups.py +113 -0
  46. fleetpull/endpoints/motive/driving_periods.py +85 -0
  47. fleetpull/endpoints/motive/groups.py +61 -0
  48. fleetpull/endpoints/motive/idle_events.py +94 -0
  49. fleetpull/endpoints/motive/users.py +64 -0
  50. fleetpull/endpoints/motive/vehicle_locations.py +170 -0
  51. fleetpull/endpoints/motive/vehicle_utilizations.py +122 -0
  52. fleetpull/endpoints/motive/vehicles.py +106 -0
  53. fleetpull/endpoints/registry.py +280 -0
  54. fleetpull/endpoints/samsara/__init__.py +12 -0
  55. fleetpull/endpoints/samsara/_spec_builders.py +202 -0
  56. fleetpull/endpoints/samsara/addresses.py +77 -0
  57. fleetpull/endpoints/samsara/asset_locations.py +217 -0
  58. fleetpull/endpoints/samsara/driver_fuel_energy_reports.py +124 -0
  59. fleetpull/endpoints/samsara/driver_vehicle_assignments.py +211 -0
  60. fleetpull/endpoints/samsara/drivers.py +169 -0
  61. fleetpull/endpoints/samsara/engine_states.py +114 -0
  62. fleetpull/endpoints/samsara/gps_readings.py +113 -0
  63. fleetpull/endpoints/samsara/idling_events.py +195 -0
  64. fleetpull/endpoints/samsara/odometer_readings.py +116 -0
  65. fleetpull/endpoints/samsara/trips.py +217 -0
  66. fleetpull/endpoints/samsara/vehicle_fuel_energy_reports.py +139 -0
  67. fleetpull/endpoints/samsara/vehicles.py +121 -0
  68. fleetpull/endpoints/shared/__init__.py +57 -0
  69. fleetpull/endpoints/shared/base.py +529 -0
  70. fleetpull/endpoints/shared/request_shape.py +227 -0
  71. fleetpull/endpoints/shared/resume.py +74 -0
  72. fleetpull/endpoints/shared/spec_builders.py +59 -0
  73. fleetpull/endpoints/shared/sync_mode.py +159 -0
  74. fleetpull/endpoints/shared/url_paths.py +149 -0
  75. fleetpull/exceptions.py +320 -0
  76. fleetpull/incremental/__init__.py +27 -0
  77. fleetpull/incremental/cursor.py +66 -0
  78. fleetpull/incremental/resolution.py +152 -0
  79. fleetpull/incremental/seed.py +53 -0
  80. fleetpull/incremental/window.py +94 -0
  81. fleetpull/logger/__init__.py +5 -0
  82. fleetpull/logger/setup.py +145 -0
  83. fleetpull/model_contract/__init__.py +6 -0
  84. fleetpull/model_contract/coercions.py +33 -0
  85. fleetpull/model_contract/response.py +45 -0
  86. fleetpull/models/__init__.py +7 -0
  87. fleetpull/models/geotab/__init__.py +166 -0
  88. fleetpull/models/geotab/annotation_log.py +109 -0
  89. fleetpull/models/geotab/audit.py +56 -0
  90. fleetpull/models/geotab/device.py +217 -0
  91. fleetpull/models/geotab/driver_change.py +102 -0
  92. fleetpull/models/geotab/duty_status_log.py +200 -0
  93. fleetpull/models/geotab/dvir_log.py +184 -0
  94. fleetpull/models/geotab/exception_event.py +135 -0
  95. fleetpull/models/geotab/fault_data.py +168 -0
  96. fleetpull/models/geotab/fill_up.py +189 -0
  97. fleetpull/models/geotab/fuel_and_energy_used.py +81 -0
  98. fleetpull/models/geotab/fuel_tax_detail.py +150 -0
  99. fleetpull/models/geotab/log_record.py +68 -0
  100. fleetpull/models/geotab/media_file.py +133 -0
  101. fleetpull/models/geotab/shared.py +221 -0
  102. fleetpull/models/geotab/shipment_log.py +105 -0
  103. fleetpull/models/geotab/status_data.py +108 -0
  104. fleetpull/models/geotab/text_message.py +125 -0
  105. fleetpull/models/geotab/trip.py +162 -0
  106. fleetpull/models/geotab/user.py +187 -0
  107. fleetpull/models/motive/__init__.py +43 -0
  108. fleetpull/models/motive/driver_idle_rollup.py +109 -0
  109. fleetpull/models/motive/driving_period.py +82 -0
  110. fleetpull/models/motive/group.py +49 -0
  111. fleetpull/models/motive/idle_event.py +77 -0
  112. fleetpull/models/motive/shared.py +192 -0
  113. fleetpull/models/motive/user.py +217 -0
  114. fleetpull/models/motive/vehicle.py +162 -0
  115. fleetpull/models/motive/vehicle_location.py +127 -0
  116. fleetpull/models/motive/vehicle_utilization.py +128 -0
  117. fleetpull/models/samsara/__init__.py +108 -0
  118. fleetpull/models/samsara/address.py +149 -0
  119. fleetpull/models/samsara/asset_location.py +131 -0
  120. fleetpull/models/samsara/driver.py +232 -0
  121. fleetpull/models/samsara/driver_fuel_energy_report.py +153 -0
  122. fleetpull/models/samsara/driver_vehicle_assignment.py +168 -0
  123. fleetpull/models/samsara/engine_state.py +92 -0
  124. fleetpull/models/samsara/gps_reading.py +146 -0
  125. fleetpull/models/samsara/idling_event.py +174 -0
  126. fleetpull/models/samsara/odometer_reading.py +90 -0
  127. fleetpull/models/samsara/trip.py +199 -0
  128. fleetpull/models/samsara/vehicle.py +167 -0
  129. fleetpull/models/samsara/vehicle_fuel_energy_report.py +191 -0
  130. fleetpull/network/__init__.py +9 -0
  131. fleetpull/network/auth/__init__.py +15 -0
  132. fleetpull/network/auth/authenticate.py +271 -0
  133. fleetpull/network/auth/manager.py +223 -0
  134. fleetpull/network/auth/models.py +57 -0
  135. fleetpull/network/auth/strategies.py +169 -0
  136. fleetpull/network/classifiers/__init__.py +11 -0
  137. fleetpull/network/classifiers/geotab.py +145 -0
  138. fleetpull/network/classifiers/motive.py +79 -0
  139. fleetpull/network/classifiers/samsara.py +80 -0
  140. fleetpull/network/client/__init__.py +25 -0
  141. fleetpull/network/client/page.py +32 -0
  142. fleetpull/network/client/profile.py +27 -0
  143. fleetpull/network/client/registry.py +103 -0
  144. fleetpull/network/client/registry_base.py +150 -0
  145. fleetpull/network/client/runtime.py +44 -0
  146. fleetpull/network/client/transport.py +381 -0
  147. fleetpull/network/contract/__init__.py +50 -0
  148. fleetpull/network/contract/auth.py +48 -0
  149. fleetpull/network/contract/classifier.py +189 -0
  150. fleetpull/network/contract/envelope_fetcher.py +33 -0
  151. fleetpull/network/contract/envelopes.py +189 -0
  152. fleetpull/network/contract/outcome.py +43 -0
  153. fleetpull/network/contract/page_decoder.py +101 -0
  154. fleetpull/network/contract/request.py +97 -0
  155. fleetpull/network/decoders/__init__.py +40 -0
  156. fleetpull/network/decoders/_window_stamp.py +78 -0
  157. fleetpull/network/decoders/geotab.py +309 -0
  158. fleetpull/network/decoders/motive.py +204 -0
  159. fleetpull/network/decoders/motive_reports.py +114 -0
  160. fleetpull/network/decoders/samsara.py +343 -0
  161. fleetpull/network/decoders/samsara_reports.py +119 -0
  162. fleetpull/network/decoders/single_page.py +55 -0
  163. fleetpull/network/limits/__init__.py +13 -0
  164. fleetpull/network/limits/bucket_math.py +58 -0
  165. fleetpull/network/limits/limiter.py +153 -0
  166. fleetpull/network/limits/registry.py +96 -0
  167. fleetpull/network/posture/__init__.py +6 -0
  168. fleetpull/network/posture/client_options.py +96 -0
  169. fleetpull/network/retry/__init__.py +13 -0
  170. fleetpull/network/retry/decision.py +155 -0
  171. fleetpull/network/tls/__init__.py +5 -0
  172. fleetpull/network/tls/truststore_context.py +35 -0
  173. fleetpull/orchestrator/__init__.py +30 -0
  174. fleetpull/orchestrator/backfill.py +127 -0
  175. fleetpull/orchestrator/batch.py +155 -0
  176. fleetpull/orchestrator/bisection.py +271 -0
  177. fleetpull/orchestrator/drivers.py +350 -0
  178. fleetpull/orchestrator/entry.py +347 -0
  179. fleetpull/orchestrator/executors.py +128 -0
  180. fleetpull/orchestrator/fanout.py +163 -0
  181. fleetpull/orchestrator/feed_drive.py +266 -0
  182. fleetpull/orchestrator/metadata_projection.py +195 -0
  183. fleetpull/orchestrator/outcome.py +53 -0
  184. fleetpull/orchestrator/recording.py +85 -0
  185. fleetpull/orchestrator/resume.py +143 -0
  186. fleetpull/orchestrator/roster_harvest.py +108 -0
  187. fleetpull/orchestrator/roster_refresh.py +363 -0
  188. fleetpull/orchestrator/runner.py +262 -0
  189. fleetpull/orchestrator/shape_resolution.py +221 -0
  190. fleetpull/orchestrator/spine.py +166 -0
  191. fleetpull/orchestrator/streaming.py +153 -0
  192. fleetpull/orchestrator/unit_loop.py +273 -0
  193. fleetpull/orchestrator/watermark_drive.py +455 -0
  194. fleetpull/paths/__init__.py +13 -0
  195. fleetpull/paths/datasets.py +38 -0
  196. fleetpull/paths/partitions.py +41 -0
  197. fleetpull/paths/resolution.py +98 -0
  198. fleetpull/polars_typing/__init__.py +20 -0
  199. fleetpull/py.typed +0 -0
  200. fleetpull/records/__init__.py +19 -0
  201. fleetpull/records/convert.py +53 -0
  202. fleetpull/records/dataframe.py +63 -0
  203. fleetpull/records/event_time.py +70 -0
  204. fleetpull/records/fields.py +190 -0
  205. fleetpull/records/flatten.py +54 -0
  206. fleetpull/records/roster_members.py +74 -0
  207. fleetpull/records/schema.py +73 -0
  208. fleetpull/records/validation.py +62 -0
  209. fleetpull/resources/__init__.py +10 -0
  210. fleetpull/resources/config.example.yaml +222 -0
  211. fleetpull/roster/__init__.py +16 -0
  212. fleetpull/roster/definition.py +46 -0
  213. fleetpull/roster/key.py +40 -0
  214. fleetpull/roster/registry.py +93 -0
  215. fleetpull/state/__init__.py +36 -0
  216. fleetpull/state/cursors.py +417 -0
  217. fleetpull/state/database.py +463 -0
  218. fleetpull/state/migrations.py +430 -0
  219. fleetpull/state/reconcile.py +127 -0
  220. fleetpull/state/rosters.py +159 -0
  221. fleetpull/state/run_ledger.py +572 -0
  222. fleetpull/state/work_units.py +648 -0
  223. fleetpull/storage/__init__.py +30 -0
  224. fleetpull/storage/append.py +187 -0
  225. fleetpull/storage/atomic.py +161 -0
  226. fleetpull/storage/files.py +176 -0
  227. fleetpull/storage/frames.py +98 -0
  228. fleetpull/storage/metadata.py +161 -0
  229. fleetpull/storage/partitioned.py +205 -0
  230. fleetpull/storage/pruning.py +169 -0
  231. fleetpull/storage/read.py +35 -0
  232. fleetpull/storage/result.py +34 -0
  233. fleetpull/storage/single_file.py +130 -0
  234. fleetpull/storage/splitting.py +88 -0
  235. fleetpull/storage/staging.py +178 -0
  236. fleetpull/storage/writers.py +145 -0
  237. fleetpull/timing/__init__.py +21 -0
  238. fleetpull/timing/canon.py +92 -0
  239. fleetpull/timing/clock.py +192 -0
  240. fleetpull/timing/codec.py +122 -0
  241. fleetpull/timing/sleeper.py +65 -0
  242. fleetpull/vocabulary/__init__.py +16 -0
  243. fleetpull/vocabulary/json_types.py +20 -0
  244. fleetpull/vocabulary/provider.py +29 -0
  245. fleetpull/vocabulary/quota_scope.py +47 -0
  246. fleetpull/vocabulary/response_category.py +32 -0
  247. fleetpull-0.1.0.dist-info/METADATA +248 -0
  248. fleetpull-0.1.0.dist-info/RECORD +252 -0
  249. fleetpull-0.1.0.dist-info/WHEEL +5 -0
  250. fleetpull-0.1.0.dist-info/entry_points.txt +2 -0
  251. fleetpull-0.1.0.dist-info/licenses/LICENSE +202 -0
  252. fleetpull-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,153 @@
1
+ # src/fleetpull/models/samsara/driver_fuel_energy_report.py
2
+ """Samsara DriverFuelEnergyReport response model
3
+ (``GET /fleet/reports/drivers/fuel-energy``, post-decoder
4
+ window-stamped grain).
5
+
6
+ Written from captured live responses (2026-07-20/21 probe session: a
7
+ 47/47 total census on the 1-day walk), never from docs. The vehicle
8
+ arm's shape with the entity swapped: the same metric core plus
9
+ ``estFuelEnergyCost``, attributed to a ``driver {id, name}`` ref
10
+ instead of the vehicle block -- and NO ``externalIds`` anywhere on this
11
+ arm (never observed, so unmodeled as unobserved, never excluded).
12
+
13
+ The model mirrors the record ``SamsaraWindowReportPageDecoder`` emits:
14
+ ``window_start`` / ``window_end`` are DECODER-SYNTHESIZED
15
+ (``windowStartDate``/``windowEndDate``) -- report rows carry NO
16
+ event-time key of any kind, so the decoder stamps each row with the
17
+ window the SENT spec asked for, copied verbatim from its
18
+ ``startDate``/``endDate`` params (the request-sourced contrast with the
19
+ stats triple's record-sourced identity keys); everything else is
20
+ wire-verbatim.
21
+
22
+ **NON-ADDITIVITY -- the rollup grain is the request window
23
+ (2026-07-21).** Proven on this surface family twice: widening a 1-day
24
+ window to 2 days GREW per-entity metrics, and summing two adjacent day
25
+ rollups mismatched the two-day rollup on 89 of 267 vehicle reports.
26
+ Each row is the provider's answer for exactly its window, nothing else:
27
+ day rows MUST NOT be summed to reproduce a wider window's rollup --
28
+ which is why the binding declares ``fixed_unit_days=1``.
29
+
30
+ Requiredness posture: the window stamps, the ``driver`` ref, and its
31
+ ``id`` are required STRUCTURALLY (a rollup row without its window or
32
+ its entity is meaningless); the metric core is required on the
33
+ WHOLE-WALK posture (47/47 total census -- a per-window rollup surface
34
+ has no absence mechanism to be conservative about, so a missing metric
35
+ is a contract change worth a loud failure). The ref's ``name`` stays
36
+ optional per the conservative posture.
37
+
38
+ ``efficiencyMpge``, ``estCarbonEmissionsKg``, and the cost ``amount``
39
+ are MIXED int|float on the wire -- modeled ``float``; ``currencyCode``
40
+ (observed only ``'USD'`` on a 100-report sample) is census-open, a
41
+ plain ``str``.
42
+
43
+ Wire keys are camelCase; fields are snake_case via the ``to_camel``
44
+ alias generator, except the decoder-synthesized window stamps, which
45
+ take explicit aliases.
46
+ """
47
+
48
+ from datetime import datetime
49
+
50
+ from pydantic import ConfigDict, Field
51
+ from pydantic.alias_generators import to_camel
52
+
53
+ from fleetpull.model_contract import ResponseModel
54
+
55
+ __all__: list[str] = [
56
+ 'DriverFuelEnergyCost',
57
+ 'DriverFuelEnergyDriverRef',
58
+ 'DriverFuelEnergyReport',
59
+ ]
60
+
61
+
62
+ class DriverFuelEnergyCost(ResponseModel):
63
+ """The ``estFuelEnergyCost`` block: the window's estimated cost.
64
+
65
+ Both keys were 47/47 in census and required per the whole-walk
66
+ posture (module docstring).
67
+
68
+ Attributes:
69
+ amount: The monetary amount -- MIXED int|float on the wire,
70
+ modeled float.
71
+ currency_code: The currency code -- observed only ``'USD'`` on
72
+ a 100-report sample, census-open, so a plain ``str``.
73
+ """
74
+
75
+ model_config = ConfigDict(alias_generator=to_camel)
76
+
77
+ amount: float
78
+ currency_code: str
79
+
80
+
81
+ class DriverFuelEnergyDriverRef(ResponseModel):
82
+ """The ``driver`` block: the rollup's driver entity.
83
+
84
+ Both keys were 47/47 in census; only ``id`` is required by
85
+ structural judgment (a ref without an id references nothing), while
86
+ ``name`` stays optional per the conservative posture. No
87
+ ``externalIds`` was ever observed on this arm -- unmodeled as
88
+ unobserved.
89
+
90
+ Attributes:
91
+ id: Samsara's driver id -- a string, mirrored as string.
92
+ name: The driver's display name.
93
+ """
94
+
95
+ id: str
96
+ name: str | None = None
97
+
98
+
99
+ class DriverFuelEnergyReport(ResponseModel):
100
+ """One driver's fuel-energy rollup over exactly one request window.
101
+
102
+ A pure mirror of the window-stamped post-decoder record (module
103
+ docstring: the window stamps are decoder-synthesized from the sent
104
+ spec; everything else is wire-verbatim). Field semantics and units
105
+ are Samsara's; no value is derived or interpreted here. Day rows
106
+ MUST NOT be summed to reproduce a wider window's rollup (module
107
+ docstring).
108
+
109
+ Attributes:
110
+ window_start: The request window's start (decoder-synthesized
111
+ ``windowStartDate``, verbatim from the sent ``startDate``)
112
+ -- the event-time column: the row's time identity is the
113
+ window that produced it.
114
+ window_end: The request window's end (decoder-synthesized
115
+ ``windowEndDate``, verbatim from the sent ``endDate``).
116
+ driver: The rollup's driver entity.
117
+ distance_traveled_meters: Distance traveled in the window, in
118
+ meters, a bare int.
119
+ efficiency_mpge: Efficiency in MPGe -- MIXED int|float on the
120
+ wire, modeled float.
121
+ energy_used_kwh: Energy used in the window, in kWh, a bare int.
122
+ engine_idle_time_duration_ms: Engine idle time in the window,
123
+ in milliseconds, a bare int.
124
+ engine_run_time_duration_ms: Engine run time in the window, in
125
+ milliseconds, a bare int.
126
+ est_carbon_emissions_kg: Estimated carbon emissions in the
127
+ window, in kilograms -- MIXED int|float on the wire,
128
+ modeled float.
129
+ fuel_consumed_ml: Fuel consumed in the window, in milliliters,
130
+ a bare int.
131
+ est_fuel_energy_cost: The window's estimated fuel/energy cost
132
+ block.
133
+ """
134
+
135
+ model_config = ConfigDict(alias_generator=to_camel)
136
+
137
+ # Decoder-synthesized window identity (the sent spec's own window).
138
+ window_start: datetime = Field(alias='windowStartDate')
139
+ window_end: datetime = Field(alias='windowEndDate')
140
+
141
+ # The rollup's entity.
142
+ driver: DriverFuelEnergyDriverRef
143
+
144
+ # The wire-verbatim metric core (whole-walk required; provider
145
+ # units mirrored verbatim).
146
+ distance_traveled_meters: int
147
+ efficiency_mpge: float
148
+ energy_used_kwh: int
149
+ engine_idle_time_duration_ms: int
150
+ engine_run_time_duration_ms: int
151
+ est_carbon_emissions_kg: float
152
+ fuel_consumed_ml: int
153
+ est_fuel_energy_cost: DriverFuelEnergyCost
@@ -0,0 +1,168 @@
1
+ # src/fleetpull/models/samsara/driver_vehicle_assignment.py
2
+ """Samsara DriverVehicleAssignment response model
3
+ (``GET /fleet/driver-vehicle-assignments``).
4
+
5
+ Written from captured live responses (2026-07-20 probe session: a full
6
+ 24-hour walk of the fleet under BOTH ``filterBy`` values -- 216 records
7
+ each, proven identical as tuple sets, so the two sweeps are one
8
+ dataset), never from docs.
9
+
10
+ The record census is total: EVERY key was present on 216/216 records --
11
+ ``startTime``/``endTime`` (RFC3339 strs, recovered tz-aware UTC; no
12
+ empty or missing ``endTime`` was observed -- assignments were only
13
+ ever observed complete), ``assignedAtTime`` (present on every record
14
+ but the EMPTY STRING on all of them -- the 2026-07-21 live proof
15
+ failed datetime parsing on record 0, and a 6,921-row week-wide value
16
+ census found '' on every single row: the Samsara empty-string posture,
17
+ mirrored verbatim as ``str``; a populated value's wire format is
18
+ UNOBSERVED, so no datetime recovery is presumed -- revisit on a
19
+ capture that shows one), ``assignmentType``
20
+ (str), ``isPassenger`` (bool), ``driver {id: str, name: str}``, and
21
+ ``vehicle {id: str, name: str, externalIds}``.
22
+
23
+ ``assignmentType``'s 24h census observed ``{'static': 158, 'HOS': 58}``
24
+ and the 2026-07-21 week-wide live proof added ``driverApp`` (25 of
25
+ 8,042 rows) -- an OPEN vocabulary, not API-enforced on output (the
26
+ eldExemptReason lesson; contrast ``filterBy``'s INPUT vocabulary, which
27
+ IS 400-enforced), so the field stays a plain ``str`` with the observed
28
+ values documented here, never an enum -- exactly the posture that let
29
+ the third value land without a failure.
30
+
31
+ ``vehicle.externalIds`` carries the LITERAL DOTTED wire keys
32
+ ``samsara.serial`` and ``samsara.vin`` (both str, 216/216) on a NESTED
33
+ object, mirrored via explicit ``Field`` aliases -- the Samsara
34
+ ``VehicleExternalIds`` precedent. Note the contrast with the stats
35
+ triple's ``vehicleSerial``/``vehicleVin``: those are flat keys the
36
+ series-unnesting DECODER synthesizes; these are the wire's own dotted
37
+ keys on the record, mirrored verbatim.
38
+
39
+ Requiredness posture: 216/216 across one day's two-sweep walk is NOT a
40
+ whole-population-over-time oath (the drivers conservative posture would
41
+ leave everything optional), but the structural core is required anyway
42
+ by structural judgment -- an assignment without its parties (``driver``,
43
+ ``vehicle``, and their ``id``\\s: a party ref without an id references
44
+ nothing) or its bounds (``startTime``/``endTime``) is structurally
45
+ meaningless, so a future record omitting them should fail loudly, never
46
+ land an all-null row. Everything else (``assignedAtTime``,
47
+ ``assignmentType``, ``isPassenger``, the ref ``name``\\s, and
48
+ ``externalIds`` with its keys) stays optional per the conservative
49
+ posture. This is the asset_locations judgment, recorded here and in
50
+ DESIGN section 8.
51
+
52
+ Wire keys are camelCase; fields are snake_case via the ``to_camel``
53
+ alias generator, except the dotted external-id keys, which take
54
+ explicit aliases.
55
+ """
56
+
57
+ from datetime import datetime
58
+
59
+ from pydantic import ConfigDict, Field
60
+ from pydantic.alias_generators import to_camel
61
+
62
+ from fleetpull.model_contract import ResponseModel
63
+
64
+ __all__: list[str] = [
65
+ 'AssignmentDriverRef',
66
+ 'AssignmentVehicleExternalIds',
67
+ 'AssignmentVehicleRef',
68
+ 'DriverVehicleAssignment',
69
+ ]
70
+
71
+
72
+ class AssignmentDriverRef(ResponseModel):
73
+ """The ``driver`` block: the assignment's driver party.
74
+
75
+ Both keys were 216/216 in census; only ``id`` is required by
76
+ structural judgment (module docstring) -- a party ref without an id
77
+ references nothing, while ``name`` stays optional per the
78
+ conservative posture.
79
+
80
+ Attributes:
81
+ id: Samsara's driver id -- a string, mirrored as string.
82
+ name: The driver's display name.
83
+ """
84
+
85
+ id: str
86
+ name: str | None = None
87
+
88
+
89
+ class AssignmentVehicleExternalIds(ResponseModel):
90
+ """The vehicle ref's ``externalIds`` block: namespaced external ids.
91
+
92
+ The wire keys are the LITERAL DOTTED ``samsara.serial`` and
93
+ ``samsara.vin`` (both str, 216/216 in census), mirrored via explicit
94
+ aliases on this NESTED object -- the ``VehicleExternalIds``
95
+ precedent, and the contrast with the stats triple's
96
+ decoder-synthesized flat keys (module docstring). Each key is
97
+ independently optional (the conservative posture; the vehicles
98
+ surface proves ``externalIds`` variance exists in this fleet).
99
+
100
+ Attributes:
101
+ samsara_serial: The gateway serial (wire key ``samsara.serial``).
102
+ samsara_vin: The VIN (wire key ``samsara.vin``).
103
+ """
104
+
105
+ samsara_serial: str | None = Field(default=None, alias='samsara.serial')
106
+ samsara_vin: str | None = Field(default=None, alias='samsara.vin')
107
+
108
+
109
+ class AssignmentVehicleRef(ResponseModel):
110
+ """The ``vehicle`` block: the assignment's vehicle party.
111
+
112
+ All three keys were 216/216 in census; only ``id`` is required by
113
+ structural judgment (module docstring), while ``name`` and
114
+ ``external_ids`` stay optional per the conservative posture.
115
+
116
+ Attributes:
117
+ id: Samsara's vehicle id -- a string, mirrored as string.
118
+ name: The vehicle's display name.
119
+ external_ids: The dotted-key external-id block (wire key
120
+ ``externalIds``).
121
+ """
122
+
123
+ model_config = ConfigDict(alias_generator=to_camel)
124
+
125
+ id: str
126
+ name: str | None = None
127
+ external_ids: AssignmentVehicleExternalIds | None = None
128
+
129
+
130
+ class DriverVehicleAssignment(ResponseModel):
131
+ """One driver-vehicle assignment interval.
132
+
133
+ A pure mirror of the captured record. Field semantics and units are
134
+ Samsara's; no value is derived or interpreted here. The structural
135
+ core (``driver``, ``vehicle``, ``start_time``, ``end_time``) is
136
+ required by structural judgment; the rest is optional per the
137
+ conservative posture (module docstring).
138
+
139
+ Attributes:
140
+ driver: The driver party of the assignment.
141
+ vehicle: The vehicle party of the assignment.
142
+ start_time: The assignment interval's start (RFC3339, recovered
143
+ tz-aware UTC) -- the event-time column: retrieval is
144
+ overlap-anchored, and ownership anchors here via the
145
+ runner's post-fetch window filter.
146
+ end_time: The assignment interval's end (RFC3339, recovered
147
+ tz-aware UTC); never observed empty or missing --
148
+ assignments were only ever observed complete.
149
+ assigned_at_time: The instant the assignment was made --
150
+ observed as the EMPTY STRING on every one of 6,921
151
+ week-censused rows (live-proven 2026-07-21), mirrored
152
+ verbatim; a populated value has never been observed.
153
+ assignment_type: How the assignment arose. Observed values
154
+ ``static``, ``HOS``, and ``driverApp`` (the third surfaced
155
+ only at week scale) -- an open vocabulary, so a plain
156
+ ``str`` (module docstring).
157
+ is_passenger: Whether the driver rode as a passenger.
158
+ """
159
+
160
+ model_config = ConfigDict(alias_generator=to_camel)
161
+
162
+ driver: AssignmentDriverRef
163
+ vehicle: AssignmentVehicleRef
164
+ start_time: datetime
165
+ end_time: datetime
166
+ assigned_at_time: str | None = None
167
+ assignment_type: str | None = None
168
+ is_passenger: bool | None = None
@@ -0,0 +1,92 @@
1
+ # src/fleetpull/models/samsara/engine_state.py
2
+ """Samsara EngineState response model
3
+ (``GET /fleet/vehicles/stats/history``, ``types=engineStates``,
4
+ post-decoder reading grain).
5
+
6
+ Written from captured live responses (2026-07-20 probe session: a
7
+ 1,045-reading census over a 24-hour window, 138 vehicles -- every
8
+ vehicle returned per the requested type carried data; no empty-array
9
+ padding was observed), never from docs. The model mirrors the FLAT
10
+ record ``SamsaraVehicleSeriesPageDecoder`` emits, one row per reading
11
+ -- the grain the records pipeline represents (scalars, not
12
+ list-of-objects; DESIGN section 9) -- and its two field families have
13
+ different provenance:
14
+
15
+ - ``vehicle_id`` / ``vehicle_name`` / ``vehicle_serial`` /
16
+ ``vehicle_vin`` are DECODER-SYNTHESIZED: the unnesting lifts them
17
+ from the per-vehicle ``id``/``name`` keys and the ``externalIds``
18
+ object's literal dotted ``samsara.serial``/``samsara.vin`` wire keys
19
+ onto every reading. ``vehicle_id`` and ``vehicle_name`` were 74/74
20
+ on the censused mixed-type page and are required; ``vehicle_serial``
21
+ and ``vehicle_vin`` were also 74/74 there, but one page is not a
22
+ whole-population oath (an unplugged or serial-less unit could omit
23
+ its ``externalIds`` block -- the vehicles surface shows exactly that
24
+ variance), so both stay OPTIONAL -- the drivers conservative posture.
25
+ - ``time`` / ``value`` are WIRE-VERBATIM reading keys: the series
26
+ census observed exactly ``{time, value}`` on every one of the 1,045
27
+ readings, so both are required.
28
+
29
+ ``value`` is a plain ``str``, NOT an enum: the observed vocabulary is
30
+ exactly ``{'On': 475, 'Off': 301, 'Idle': 269}``, but that closure is
31
+ census-only -- the API does not 400-enforce output values (unlike
32
+ ``driverActivationStatus``'s proven closure; the ``eldExemptReason``
33
+ lesson), so an unobserved state lands as data, not a crash.
34
+
35
+ ``time`` is an RFC3339 string recovered as a tz-aware UTC datetime by
36
+ Pydantic's standard parse. Readings fall strictly inside the requested
37
+ ``[startTime, endTime)`` window (probe-proven), so ``time`` is the
38
+ endpoint's event-time column with retrieval and routing coinciding
39
+ natively.
40
+
41
+ Wire keys are camelCase; fields are snake_case via the ``to_camel``
42
+ alias generator.
43
+ """
44
+
45
+ from datetime import datetime
46
+
47
+ from pydantic import ConfigDict
48
+ from pydantic.alias_generators import to_camel
49
+
50
+ from fleetpull.model_contract import ResponseModel
51
+
52
+ __all__: list[str] = ['EngineState']
53
+
54
+
55
+ class EngineState(ResponseModel):
56
+ """One engine-state reading of one vehicle, at the reading grain.
57
+
58
+ A pure mirror of the flat post-decoder record (module docstring:
59
+ the identity fields are decoder-synthesized, the reading fields
60
+ wire-verbatim). Field semantics are Samsara's; no value is derived
61
+ or interpreted here.
62
+
63
+ Attributes:
64
+ vehicle_id: The vehicle's Samsara id -- a numeric string,
65
+ decoder-synthesized from the vehicle record's ``id``.
66
+ vehicle_name: The vehicle's display name, decoder-synthesized
67
+ from the vehicle record's ``name``.
68
+ vehicle_serial: The gateway serial, decoder-synthesized from
69
+ ``externalIds['samsara.serial']`` (74/74 on the censused
70
+ page; optional -- module docstring).
71
+ vehicle_vin: The VIN, decoder-synthesized from
72
+ ``externalIds['samsara.vin']`` (74/74 on the censused page;
73
+ optional -- module docstring).
74
+ time: The reading instant (RFC3339, recovered tz-aware UTC) --
75
+ the event-time column; readings fall strictly inside the
76
+ requested window.
77
+ value: The engine state -- a plain string; observed vocabulary
78
+ exactly ``On``/``Off``/``Idle``, census-closed only, so
79
+ membership is not enforced (module docstring).
80
+ """
81
+
82
+ model_config = ConfigDict(alias_generator=to_camel)
83
+
84
+ # Decoder-synthesized vehicle identity.
85
+ vehicle_id: str
86
+ vehicle_name: str
87
+ vehicle_serial: str | None = None
88
+ vehicle_vin: str | None = None
89
+
90
+ # Wire-verbatim reading payload.
91
+ time: datetime
92
+ value: str
@@ -0,0 +1,146 @@
1
+ # src/fleetpull/models/samsara/gps_reading.py
2
+ """Samsara GpsReading response model
3
+ (``GET /fleet/vehicles/stats/history``, ``types=gps``, post-decoder
4
+ reading grain).
5
+
6
+ Written from captured live responses (2026-07-20 probe session: a
7
+ 2,512-reading sample over 8 cursor pages of a 24-hour window whose
8
+ full walk spanned 569 vehicles -- every vehicle returned per the
9
+ requested type carried data; no empty-array padding was observed),
10
+ never from docs. The model mirrors the FLAT record
11
+ ``SamsaraVehicleSeriesPageDecoder`` emits, one row per reading -- the
12
+ grain the records pipeline represents (scalars, not list-of-objects;
13
+ DESIGN section 9) -- and its two field families have different
14
+ provenance:
15
+
16
+ - ``vehicle_id`` / ``vehicle_name`` / ``vehicle_serial`` /
17
+ ``vehicle_vin`` are DECODER-SYNTHESIZED: the unnesting lifts them
18
+ from the per-vehicle ``id``/``name`` keys and the ``externalIds``
19
+ object's literal dotted ``samsara.serial``/``samsara.vin`` wire keys
20
+ onto every reading. ``vehicle_id`` and ``vehicle_name`` were 74/74
21
+ on the censused mixed-type page and are required; ``vehicle_serial``
22
+ and ``vehicle_vin`` were also 74/74 there, but one page is not a
23
+ whole-population oath (an unplugged or serial-less unit could omit
24
+ its ``externalIds`` block -- the vehicles surface shows exactly that
25
+ variance), so both stay OPTIONAL -- the drivers conservative posture.
26
+ - The reading keys are WIRE-VERBATIM. Sampled presence out of 2,512:
27
+ ``time``, ``latitude``, ``longitude``, ``headingDegrees``,
28
+ ``speedMilesPerHour``, ``isEcuSpeed``, and ``reverseGeo``
29
+ (``{formattedLocation}``, the key always present in every carrying
30
+ block) rode every reading and are required; ``address``
31
+ (``{id, name}``, the defined-address-book reference) rode 401/2,512
32
+ and is optional.
33
+
34
+ ``speedMilesPerHour`` is MIXED int|float on the wire -- modeled
35
+ ``float``, lax coercion lifting the int shape (the idling_events
36
+ ``fuelConsumedMilliliters`` precedent). ``headingDegrees`` is a bare
37
+ int on every sampled reading. ``time`` is an RFC3339 string recovered
38
+ as a tz-aware UTC datetime by Pydantic's standard parse. Readings fall
39
+ strictly inside the requested ``[startTime, endTime)`` window
40
+ (probe-proven), so ``time`` is the endpoint's event-time column with
41
+ retrieval and routing coinciding natively.
42
+
43
+ The reverse-geocoded ``formattedLocation`` string is PII-adjacent --
44
+ capture fixtures are fully synthetic (the trips precedent).
45
+
46
+ Wire keys are camelCase; fields are snake_case via the ``to_camel``
47
+ alias generator.
48
+ """
49
+
50
+ from datetime import datetime
51
+
52
+ from pydantic import ConfigDict
53
+ from pydantic.alias_generators import to_camel
54
+
55
+ from fleetpull.model_contract import ResponseModel
56
+
57
+ __all__: list[str] = [
58
+ 'GpsReading',
59
+ 'GpsReadingAddressRef',
60
+ 'GpsReadingReverseGeo',
61
+ ]
62
+
63
+
64
+ class GpsReadingReverseGeo(ResponseModel):
65
+ """The ``reverseGeo`` block: the reading's reverse-geocoded location.
66
+
67
+ Present on every sampled reading (2,512/2,512), with
68
+ ``formattedLocation`` present in every carrying block.
69
+
70
+ Attributes:
71
+ formatted_location: The reverse-geocoded location string,
72
+ mirrored verbatim (PII-adjacent; fixtures are synthetic).
73
+ """
74
+
75
+ model_config = ConfigDict(alias_generator=to_camel)
76
+
77
+ formatted_location: str
78
+
79
+
80
+ class GpsReadingAddressRef(ResponseModel):
81
+ """The ``address`` block: a matched address-book reference (401/2,512).
82
+
83
+ Present when the reading fell inside a defined address/geofence --
84
+ the addresses surface is the book it references.
85
+
86
+ Attributes:
87
+ id: The defined address's id -- a string on the wire.
88
+ name: The defined address's display name.
89
+ """
90
+
91
+ id: str
92
+ name: str
93
+
94
+
95
+ class GpsReading(ResponseModel):
96
+ """One GPS reading of one vehicle, at the reading grain.
97
+
98
+ A pure mirror of the flat post-decoder record (module docstring:
99
+ the identity fields are decoder-synthesized, the reading fields
100
+ wire-verbatim). Field semantics and units are Samsara's; no value
101
+ is derived or interpreted here.
102
+
103
+ Attributes:
104
+ vehicle_id: The vehicle's Samsara id -- a numeric string,
105
+ decoder-synthesized from the vehicle record's ``id``.
106
+ vehicle_name: The vehicle's display name, decoder-synthesized
107
+ from the vehicle record's ``name``.
108
+ vehicle_serial: The gateway serial, decoder-synthesized from
109
+ ``externalIds['samsara.serial']`` (74/74 on the censused
110
+ page; optional -- module docstring).
111
+ vehicle_vin: The VIN, decoder-synthesized from
112
+ ``externalIds['samsara.vin']`` (74/74 on the censused page;
113
+ optional -- module docstring).
114
+ time: The reading instant (RFC3339, recovered tz-aware UTC) --
115
+ the event-time column; readings fall strictly inside the
116
+ requested window.
117
+ latitude: Reading latitude, decimal degrees.
118
+ longitude: Reading longitude, decimal degrees.
119
+ heading_degrees: Heading in degrees, a bare int on every
120
+ sampled reading.
121
+ speed_miles_per_hour: Speed in miles per hour -- MIXED
122
+ int|float on the wire, modeled float (module docstring).
123
+ is_ecu_speed: Whether the speed came from the ECU.
124
+ reverse_geo: The reverse-geocoded location block (2,512/2,512).
125
+ address: The matched address-book reference (401/2,512).
126
+ """
127
+
128
+ model_config = ConfigDict(alias_generator=to_camel)
129
+
130
+ # Decoder-synthesized vehicle identity.
131
+ vehicle_id: str
132
+ vehicle_name: str
133
+ vehicle_serial: str | None = None
134
+ vehicle_vin: str | None = None
135
+
136
+ # Wire-verbatim reading payload.
137
+ time: datetime
138
+ latitude: float
139
+ longitude: float
140
+ heading_degrees: int
141
+ speed_miles_per_hour: float
142
+ is_ecu_speed: bool
143
+ reverse_geo: GpsReadingReverseGeo
144
+
145
+ # The partial block (absence-shaped).
146
+ address: GpsReadingAddressRef | None = None