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,73 @@
1
+ # src/fleetpull/endpoints/geotab/driver_changes.py
2
+ """The GeoTab driver_changes binding: the driver-assignment feed.
3
+
4
+ A ``GetFeed`` drive of the ``DriverChange`` entity — driver-to-device
5
+ assignment events carrying a per-record ``version``. DriverChange
6
+ records are user-editable through the provider, so re-emission under
7
+ newer versions is expected, every emitted version is stored, and the
8
+ consumer reconciles by ``(id, max version)`` (DESIGN §4). The proven
9
+ object-or-string driver ref (with its ``isDriver`` object-arm sibling)
10
+ rides the model (``models/geotab/driver_change.py``).
11
+
12
+ ``resultsLimit`` is the 50,000 protocol maximum: the docs list no lower
13
+ per-type cap for this type (verified against the GetFeed reference
14
+ 2026-07-21), and the census pulls used small limits, which prove
15
+ nothing about caps (DESIGN §8, the wave two block).
16
+
17
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
18
+ resolved host are the session strategy's injections (the devices-leaf
19
+ convention); the host this module writes is a pre-auth placeholder.
20
+ """
21
+
22
+ from typing import Final
23
+
24
+ from fleetpull.config import GeotabConfig
25
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
26
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
27
+ from fleetpull.models.geotab import DriverChange
28
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
29
+ from fleetpull.vocabulary import Provider, QuotaScope
30
+
31
+ __all__: list[str] = ['build_endpoint']
32
+
33
+ # The GetFeed protocol maximum; no lower documented per-type cap.
34
+ _RESULTS_LIMIT: Final[int] = 50000
35
+
36
+ _DRIVER_CHANGE_TYPE_NAME: Final[str] = 'DriverChange'
37
+
38
+
39
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[DriverChange]:
40
+ """Build the GeoTab driver_changes feed binding.
41
+
42
+ Driver-assignment events fetched incrementally as a version-token
43
+ feed: the run resumes from the stored token (seeded via
44
+ ``search.fromDate`` on the tokenless first run only), each page
45
+ appends durably before its ``toVersion`` commits, and the fetched
46
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
47
+ files — re-emitted versions accumulate for the consumer's
48
+ ``(id, max version)`` reconcile.
49
+
50
+ Args:
51
+ config: The validated GeoTab configuration; supplies the
52
+ authentication host the pre-auth spec URLs are built on.
53
+
54
+ Returns:
55
+ The frozen driver_changes ``EndpointDefinition``. Construction
56
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
57
+ ``event_time_column`` triple against the response model.
58
+ """
59
+ return EndpointDefinition(
60
+ provider=Provider.GEOTAB,
61
+ name='driver_changes',
62
+ spec_builder=GeotabGetFeedSpecBuilder(
63
+ server=server_host(config),
64
+ type_name=_DRIVER_CHANGE_TYPE_NAME,
65
+ results_limit=_RESULTS_LIMIT,
66
+ ),
67
+ page_decoder=GeotabFeedPageDecoder(),
68
+ response_model=DriverChange,
69
+ quota_scope=QuotaScope.GEOTAB_FEED,
70
+ storage_kind=StorageKind.APPEND_LOG,
71
+ sync_mode=FeedMode(),
72
+ event_time_column='date_time',
73
+ )
@@ -0,0 +1,75 @@
1
+ # src/fleetpull/endpoints/geotab/duty_status_logs.py
2
+ """The GeoTab duty_status_logs binding: the HOS duty-status feed.
3
+
4
+ A ``GetFeed`` drive of the ``DutyStatusLog`` entity — an EDITABLE log:
5
+ ``editDateTime`` is the edit trail, past records re-emit under higher
6
+ ``version`` tokens as they are edited and verified, every emitted
7
+ version is stored, and the consumer reconciles by ``(id, max version)``
8
+ (DESIGN §4 — the calculated-feed consumer note from the fill_ups
9
+ binding applies verbatim). The wave-two census facts (the proven
10
+ mixed device/driver refs, the annotations id-list reduction, the
11
+ shared nested-location pair) ride the model
12
+ (``models/geotab/duty_status_log.py``).
13
+
14
+ ``resultsLimit`` is the 50,000 protocol maximum: the docs list no lower
15
+ per-type cap for this type (verified against the GetFeed reference
16
+ 2026-07-21), and the census pulls used small limits, which prove
17
+ nothing about caps (DESIGN §8, the wave two block).
18
+
19
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
20
+ resolved host are the session strategy's injections (the devices-leaf
21
+ convention); the host this module writes is a pre-auth placeholder.
22
+ """
23
+
24
+ from typing import Final
25
+
26
+ from fleetpull.config import GeotabConfig
27
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
28
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
29
+ from fleetpull.models.geotab import DutyStatusLog
30
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
31
+ from fleetpull.vocabulary import Provider, QuotaScope
32
+
33
+ __all__: list[str] = ['build_endpoint']
34
+
35
+ # The GetFeed protocol maximum; no lower documented per-type cap.
36
+ _RESULTS_LIMIT: Final[int] = 50000
37
+
38
+ _DUTY_STATUS_LOG_TYPE_NAME: Final[str] = 'DutyStatusLog'
39
+
40
+
41
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[DutyStatusLog]:
42
+ """Build the GeoTab duty_status_logs feed binding.
43
+
44
+ HOS duty-status events fetched incrementally as a version-token
45
+ feed: the run resumes from the stored token (seeded via
46
+ ``search.fromDate`` on the tokenless first run only), each page
47
+ appends durably before its ``toVersion`` commits, and the fetched
48
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
49
+ files — re-emitted versions accumulate for the consumer's
50
+ ``(id, max version)`` reconcile.
51
+
52
+ Args:
53
+ config: The validated GeoTab configuration; supplies the
54
+ authentication host the pre-auth spec URLs are built on.
55
+
56
+ Returns:
57
+ The frozen duty_status_logs ``EndpointDefinition``. Construction
58
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
59
+ ``event_time_column`` triple against the response model.
60
+ """
61
+ return EndpointDefinition(
62
+ provider=Provider.GEOTAB,
63
+ name='duty_status_logs',
64
+ spec_builder=GeotabGetFeedSpecBuilder(
65
+ server=server_host(config),
66
+ type_name=_DUTY_STATUS_LOG_TYPE_NAME,
67
+ results_limit=_RESULTS_LIMIT,
68
+ ),
69
+ page_decoder=GeotabFeedPageDecoder(),
70
+ response_model=DutyStatusLog,
71
+ quota_scope=QuotaScope.GEOTAB_FEED,
72
+ storage_kind=StorageKind.APPEND_LOG,
73
+ sync_mode=FeedMode(),
74
+ event_time_column='date_time',
75
+ )
@@ -0,0 +1,76 @@
1
+ # src/fleetpull/endpoints/geotab/dvir_logs.py
2
+ """The GeoTab dvir_logs binding: the driver vehicle inspection feed.
3
+
4
+ A ``GetFeed`` drive of the ``DVIRLog`` entity (the wire's own casing —
5
+ the model is the house-cased ``DvirLog``). DVIRs are certified and
6
+ edited after creation, so past records re-emit under higher ``version``
7
+ tokens, every emitted version is stored, and the consumer reconciles by
8
+ ``(id, max version)`` (DESIGN §4). The wave-two census facts (the
9
+ commonly-absent ``device`` trio, the ``defectList`` children exclusion,
10
+ the shared nested-location pair) ride the model
11
+ (``models/geotab/dvir_log.py``).
12
+
13
+ ``resultsLimit`` is the 50,000 protocol maximum: the docs list no lower
14
+ per-type cap for this type (verified against the GetFeed reference
15
+ 2026-07-21), and the census pulls used small limits, which prove
16
+ nothing about caps (DESIGN §8, the wave two block).
17
+
18
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
19
+ resolved host are the session strategy's injections (the devices-leaf
20
+ convention); the host this module writes is a pre-auth placeholder.
21
+ """
22
+
23
+ from typing import Final
24
+
25
+ from fleetpull.config import GeotabConfig
26
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
27
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
28
+ from fleetpull.models.geotab import DvirLog
29
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
30
+ from fleetpull.vocabulary import Provider, QuotaScope
31
+
32
+ __all__: list[str] = ['build_endpoint']
33
+
34
+ # The GetFeed protocol maximum; no lower documented per-type cap.
35
+ _RESULTS_LIMIT: Final[int] = 50000
36
+
37
+ # The wire typeName keeps the provider's own DVIR casing; only the
38
+ # model class is house-cased.
39
+ _DVIR_LOG_TYPE_NAME: Final[str] = 'DVIRLog'
40
+
41
+
42
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[DvirLog]:
43
+ """Build the GeoTab dvir_logs feed binding.
44
+
45
+ Inspection reports fetched incrementally as a version-token feed:
46
+ the run resumes from the stored token (seeded via
47
+ ``search.fromDate`` on the tokenless first run only), each page
48
+ appends durably before its ``toVersion`` commits, and the fetched
49
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
50
+ files — re-emitted versions accumulate for the consumer's
51
+ ``(id, max version)`` reconcile.
52
+
53
+ Args:
54
+ config: The validated GeoTab configuration; supplies the
55
+ authentication host the pre-auth spec URLs are built on.
56
+
57
+ Returns:
58
+ The frozen dvir_logs ``EndpointDefinition``. Construction
59
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
60
+ ``event_time_column`` triple against the response model.
61
+ """
62
+ return EndpointDefinition(
63
+ provider=Provider.GEOTAB,
64
+ name='dvir_logs',
65
+ spec_builder=GeotabGetFeedSpecBuilder(
66
+ server=server_host(config),
67
+ type_name=_DVIR_LOG_TYPE_NAME,
68
+ results_limit=_RESULTS_LIMIT,
69
+ ),
70
+ page_decoder=GeotabFeedPageDecoder(),
71
+ response_model=DvirLog,
72
+ quota_scope=QuotaScope.GEOTAB_FEED,
73
+ storage_kind=StorageKind.APPEND_LOG,
74
+ sync_mode=FeedMode(),
75
+ event_time_column='date_time',
76
+ )
@@ -0,0 +1,119 @@
1
+ # src/fleetpull/endpoints/geotab/exception_events.py
2
+ """The GeoTab exception_events binding: the bisected windowed endpoint.
3
+
4
+ A date-windowed pull of the ``ExceptionEvent`` entity — the UNFILTERED
5
+ stream, every rule (DESIGN §8's 2026-07-15 decision block: no
6
+ server-side rule filter in version one; rule selection is the
7
+ consumer's one-expression job on the delivered stream). The seek walk
8
+ is structurally unavailable here — id-sort is rejected outright for
9
+ this type (captured 2026-07-15: ``ArgumentException``, "Can not sort by
10
+ id"), and any sort composed with a search degrades to the deterministic
11
+ ``-32000 GenericException`` — so the leaf composes the shared
12
+ ``GeotabWindowedGetSpecBuilder`` (``_requests``) with
13
+ ``id_sort=False`` (no ``sort`` member ever written), declares the
14
+ ``BisectedWindowFetch`` request shape, and the orchestrator's bisecting
15
+ driver fetches each unit window whole, halving on the exactly-full
16
+ overflow signal down to the floor.
17
+
18
+ ``ExceptionEventSearch`` window matching is OVERLAP-anchored (captured
19
+ 2026-07-15): retrieval supersets start-anchored ownership, so
20
+ ``active_from`` is the event-time column, the driver's leaf filter and
21
+ the runner's window filter assign every record exactly one owner, and
22
+ no wire-window pad is needed. Events mutate after creation (~1 h
23
+ observed envelope), which the provider-level lookback absorbs.
24
+
25
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
26
+ resolved host are the session strategy's injections (the devices-leaf
27
+ convention); the host this module writes is a pre-auth placeholder.
28
+ """
29
+
30
+ from datetime import timedelta
31
+ from typing import Final
32
+
33
+ from fleetpull.config import GeotabConfig
34
+ from fleetpull.endpoints.geotab._requests import (
35
+ GeotabWindowedGetSpecBuilder,
36
+ server_host,
37
+ )
38
+ from fleetpull.endpoints.shared import (
39
+ BisectedWindowFetch,
40
+ EndpointDefinition,
41
+ StorageKind,
42
+ WatermarkMode,
43
+ )
44
+ from fleetpull.models.geotab import ExceptionEvent
45
+ from fleetpull.network.decoders import SinglePageDecoder
46
+ from fleetpull.vocabulary import Provider, QuotaScope
47
+
48
+ __all__: list[str] = ['build_endpoint']
49
+
50
+ # The per-request record limit AND the bisection overflow threshold.
51
+ # The silent cap is Captured on this type (2026-07-15: GetCountOf
52
+ # 304,716 vs a bare Get returning exactly 5,000); per-type provenance,
53
+ # not a global GeoTab fact. A strong candidate for a user config knob.
54
+ _RESULTS_LIMIT: Final[int] = 5000
55
+
56
+ # The bisection floor: a one-minute window still returning a full page
57
+ # fails loudly (sustained >5,000 events/minute fleet-wide, or >5,000
58
+ # events overlapping one instant -- feed territory either way). A
59
+ # strong candidate for a user config knob.
60
+ _FLOOR: Final[timedelta] = timedelta(minutes=1)
61
+
62
+ # The wire key the bisecting driver anchors leaf ownership by.
63
+ _EVENT_TIME_WIRE_KEY: Final[str] = 'activeFrom'
64
+
65
+ # The JSON-RPC envelope key the single-page decoder reads records from
66
+ # (the constants-scope precedent: module-private, colocated with the
67
+ # decoder composition that consumes it).
68
+ _RESULT_KEY: Final[str] = 'result'
69
+
70
+ _EXCEPTION_EVENT_TYPE_NAME: Final[str] = 'ExceptionEvent'
71
+
72
+
73
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[ExceptionEvent]:
74
+ """Build the GeoTab exception_events bisected watermark binding.
75
+
76
+ The unfiltered ExceptionEvent stream fetched incrementally: the run
77
+ resumes from a ``DateWindow`` (watermark with the provider's
78
+ late-arrival lookback from config, which also absorbs the observed
79
+ post-creation mutation), the bisecting driver fetches each unit
80
+ window whole (halving on overflow per the declared
81
+ ``BisectedWindowFetch`` shape), and the kept records land in
82
+ ``date=YYYY-MM-DD`` partitions on ``active_from``, each refetched
83
+ partition replaced. Responses are single pages under the JSON-RPC
84
+ ``result`` key — the cap that once disqualified single-page decoding
85
+ is the driver's overflow signal now.
86
+
87
+ Args:
88
+ config: The validated GeoTab configuration; supplies the auth
89
+ host and the lookback and cutoff the watermark mode carries.
90
+
91
+ Returns:
92
+ The frozen exception_events ``EndpointDefinition``. Construction
93
+ validates the watermark / partitioned / event-time triple and the
94
+ bisection pairing.
95
+ """
96
+ return EndpointDefinition(
97
+ provider=Provider.GEOTAB,
98
+ name='exception_events',
99
+ spec_builder=GeotabWindowedGetSpecBuilder(
100
+ server=server_host(config),
101
+ type_name=_EXCEPTION_EVENT_TYPE_NAME,
102
+ results_limit=_RESULTS_LIMIT,
103
+ id_sort=False,
104
+ ),
105
+ page_decoder=SinglePageDecoder(records_key=_RESULT_KEY),
106
+ response_model=ExceptionEvent,
107
+ quota_scope=QuotaScope.GEOTAB_GET,
108
+ storage_kind=StorageKind.DATE_PARTITIONED,
109
+ sync_mode=WatermarkMode(
110
+ lookback=timedelta(days=config.lookback_days),
111
+ cutoff=timedelta(days=config.cutoff_days),
112
+ ),
113
+ event_time_column='active_from',
114
+ request_shape=BisectedWindowFetch(
115
+ results_limit=_RESULTS_LIMIT,
116
+ floor=_FLOOR,
117
+ event_time_wire_key=_EVENT_TIME_WIRE_KEY,
118
+ ),
119
+ )
@@ -0,0 +1,72 @@
1
+ # src/fleetpull/endpoints/geotab/fault_data.py
2
+ """The GeoTab fault_data binding: the engine-fault feed.
3
+
4
+ A ``GetFeed`` drive of the ``FaultData`` entity — the log_records
5
+ asymmetry stance verbatim: FaultData carries NO per-record ``version``
6
+ (the 2026-07-21 wave two census, DESIGN §8), so the append-only cell is
7
+ trivially complete and the consumer reconciles by ``id`` alone
8
+ (DESIGN §4). The name is the wire's own uncountable vocabulary
9
+ (``FaultData`` → ``fault_data``, the status_data precedent); there is
10
+ no plural to form.
11
+
12
+ ``resultsLimit`` is the 50,000 protocol maximum: the docs list no lower
13
+ per-type cap for this type (verified against the GetFeed reference
14
+ 2026-07-21), and the census pulls used small limits, which prove
15
+ nothing about caps (DESIGN §8, the wave two block).
16
+
17
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
18
+ resolved host are the session strategy's injections (the devices-leaf
19
+ convention); the host this module writes is a pre-auth placeholder.
20
+ """
21
+
22
+ from typing import Final
23
+
24
+ from fleetpull.config import GeotabConfig
25
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
26
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
27
+ from fleetpull.models.geotab import FaultData
28
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
29
+ from fleetpull.vocabulary import Provider, QuotaScope
30
+
31
+ __all__: list[str] = ['build_endpoint']
32
+
33
+ # The GetFeed protocol maximum; no lower documented per-type cap.
34
+ _RESULTS_LIMIT: Final[int] = 50000
35
+
36
+ _FAULT_DATA_TYPE_NAME: Final[str] = 'FaultData'
37
+
38
+
39
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[FaultData]:
40
+ """Build the GeoTab fault_data feed binding.
41
+
42
+ The engine-fault stream fetched incrementally as a version-token
43
+ feed: the run resumes from the stored token (seeded via
44
+ ``search.fromDate`` on the tokenless first run only), each page
45
+ appends durably before its ``toVersion`` commits, and the fetched
46
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
47
+ files.
48
+
49
+ Args:
50
+ config: The validated GeoTab configuration; supplies the
51
+ authentication host the pre-auth spec URLs are built on.
52
+
53
+ Returns:
54
+ The frozen fault_data ``EndpointDefinition``. Construction
55
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
56
+ ``event_time_column`` triple against the response model.
57
+ """
58
+ return EndpointDefinition(
59
+ provider=Provider.GEOTAB,
60
+ name='fault_data',
61
+ spec_builder=GeotabGetFeedSpecBuilder(
62
+ server=server_host(config),
63
+ type_name=_FAULT_DATA_TYPE_NAME,
64
+ results_limit=_RESULTS_LIMIT,
65
+ ),
66
+ page_decoder=GeotabFeedPageDecoder(),
67
+ response_model=FaultData,
68
+ quota_scope=QuotaScope.GEOTAB_FEED,
69
+ storage_kind=StorageKind.APPEND_LOG,
70
+ sync_mode=FeedMode(),
71
+ event_time_column='date_time',
72
+ )
@@ -0,0 +1,79 @@
1
+ # src/fleetpull/endpoints/geotab/fill_ups.py
2
+ """The GeoTab fill_ups binding: the first calculated feed vertical.
3
+
4
+ A ``GetFeed`` drive of the ``FillUp`` entity — provider-detected
5
+ fuel-stop events on a CALCULATED feed: past records re-emit under
6
+ higher versions on reprocessing, every emitted version is stored, and
7
+ the consumer reconciles by ``(id, max version)`` (DESIGN §4). The
8
+ estimates-only-tenant caveat rides the model
9
+ (``models/geotab/fill_up.py``): the probed tenant has no
10
+ fuel-transaction integration, so every fuel value is provider-derived
11
+ and the census cannot speak for integrated tenants.
12
+
13
+ ``resultsLimit`` is 10,000 with DUAL PROVENANCE (DESIGN §8, the
14
+ 2026-07-21 feed wave block): 10,000 is the DOCUMENTED per-type cap for
15
+ FillUp, and the probe could not falsify it — a 50,000 request was
16
+ ACCEPTED at this tenant's whole 380-record population, which proves
17
+ nothing about the cap (the population never reaches it). Encoding the
18
+ documented figure is the conservative arm of encode-probed-behavior:
19
+ the probe was structurally unable to test the limit, so the documented
20
+ cap stands until a tenant with >10,000 records probes it.
21
+
22
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
23
+ resolved host are the session strategy's injections (the devices-leaf
24
+ convention); the host this module writes is a pre-auth placeholder.
25
+ """
26
+
27
+ from typing import Final
28
+
29
+ from fleetpull.config import GeotabConfig
30
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
31
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
32
+ from fleetpull.models.geotab import FillUp
33
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
34
+ from fleetpull.vocabulary import Provider, QuotaScope
35
+
36
+ __all__: list[str] = ['build_endpoint']
37
+
38
+ # The DOCUMENTED FillUp cap — unprobeable at this tenant's population
39
+ # (module docstring: the dual-provenance record).
40
+ _RESULTS_LIMIT: Final[int] = 10000
41
+
42
+ _FILL_UP_TYPE_NAME: Final[str] = 'FillUp'
43
+
44
+
45
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[FillUp]:
46
+ """Build the GeoTab fill_ups feed binding.
47
+
48
+ Fuel-stop detections fetched incrementally as a version-token feed:
49
+ the run resumes from the stored token (seeded via
50
+ ``search.fromDate`` on the tokenless first run only), each page
51
+ appends durably before its ``toVersion`` commits, and the fetched
52
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
53
+ files — re-emitted versions accumulate for the consumer's
54
+ ``(id, max version)`` reconcile.
55
+
56
+ Args:
57
+ config: The validated GeoTab configuration; supplies the
58
+ authentication host the pre-auth spec URLs are built on.
59
+
60
+ Returns:
61
+ The frozen fill_ups ``EndpointDefinition``. Construction
62
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
63
+ ``event_time_column`` triple against the response model.
64
+ """
65
+ return EndpointDefinition(
66
+ provider=Provider.GEOTAB,
67
+ name='fill_ups',
68
+ spec_builder=GeotabGetFeedSpecBuilder(
69
+ server=server_host(config),
70
+ type_name=_FILL_UP_TYPE_NAME,
71
+ results_limit=_RESULTS_LIMIT,
72
+ ),
73
+ page_decoder=GeotabFeedPageDecoder(),
74
+ response_model=FillUp,
75
+ quota_scope=QuotaScope.GEOTAB_FEED,
76
+ storage_kind=StorageKind.APPEND_LOG,
77
+ sync_mode=FeedMode(),
78
+ event_time_column='date_time',
79
+ )
@@ -0,0 +1,74 @@
1
+ # src/fleetpull/endpoints/geotab/fuel_and_energy_used.py
2
+ """The GeoTab fuel_and_energy_used binding: per-trip fuel/energy totals.
3
+
4
+ A ``GetFeed`` drive of the ``FuelAndEnergyUsed`` entity — a CALCULATED
5
+ feed reconciled by ``(id, max version)`` (DESIGN §4). The name is the
6
+ WIRE'S OWN VOCABULARY, not a plural (the driver_idle_rollups
7
+ precedent): ``FuelAndEnergyUsed`` names a quantity, not a countable
8
+ entity, so no snake-plural exists to form — the endpoint mirrors the
9
+ type name verbatim (DESIGN §8, the 2026-07-21 feed wave block).
10
+
11
+ ``FuelUsed`` is NOT ported: observed identical to this surface on the
12
+ probed tenant (same ids, same values, week-wide) and
13
+ provider-documented as this type's predecessor — the model docstring
14
+ carries the record. The estimates-only-tenant caveat rides the model
15
+ as well (``models/geotab/fuel_and_energy_used.py``).
16
+
17
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
18
+ resolved host are the session strategy's injections (the devices-leaf
19
+ convention); the host this module writes is a pre-auth placeholder.
20
+ """
21
+
22
+ from typing import Final
23
+
24
+ from fleetpull.config import GeotabConfig
25
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
26
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
27
+ from fleetpull.models.geotab import FuelAndEnergyUsed
28
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
29
+ from fleetpull.vocabulary import Provider, QuotaScope
30
+
31
+ __all__: list[str] = ['build_endpoint']
32
+
33
+ # The GetFeed protocol maximum (no lower per-type cap documented or
34
+ # observed for this type).
35
+ _RESULTS_LIMIT: Final[int] = 50000
36
+
37
+ _FUEL_AND_ENERGY_USED_TYPE_NAME: Final[str] = 'FuelAndEnergyUsed'
38
+
39
+
40
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[FuelAndEnergyUsed]:
41
+ """Build the GeoTab fuel_and_energy_used feed binding.
42
+
43
+ Per-trip fuel/energy totals fetched incrementally as a
44
+ version-token feed: the run resumes from the stored token (seeded
45
+ via ``search.fromDate`` on the tokenless first run only), each page
46
+ appends durably before its ``toVersion`` commits, and the fetched
47
+ records land in ``date=YYYY-MM-DD`` partitions as new numbered part
48
+ files — re-emitted versions accumulate for the consumer's
49
+ ``(id, max version)`` reconcile.
50
+
51
+ Args:
52
+ config: The validated GeoTab configuration; supplies the
53
+ authentication host the pre-auth spec URLs are built on.
54
+
55
+ Returns:
56
+ The frozen fuel_and_energy_used ``EndpointDefinition``.
57
+ Construction validates the ``FeedMode`` / ``APPEND_LOG`` /
58
+ ``event_time_column`` triple against the response model.
59
+ """
60
+ return EndpointDefinition(
61
+ provider=Provider.GEOTAB,
62
+ name='fuel_and_energy_used',
63
+ spec_builder=GeotabGetFeedSpecBuilder(
64
+ server=server_host(config),
65
+ type_name=_FUEL_AND_ENERGY_USED_TYPE_NAME,
66
+ results_limit=_RESULTS_LIMIT,
67
+ ),
68
+ page_decoder=GeotabFeedPageDecoder(),
69
+ response_model=FuelAndEnergyUsed,
70
+ quota_scope=QuotaScope.GEOTAB_FEED,
71
+ storage_kind=StorageKind.APPEND_LOG,
72
+ sync_mode=FeedMode(),
73
+ event_time_column='date_time',
74
+ )
@@ -0,0 +1,75 @@
1
+ # src/fleetpull/endpoints/geotab/fuel_tax_details.py
2
+ """The GeoTab fuel_tax_details binding: IFTA jurisdiction segments.
3
+
4
+ A ``GetFeed`` drive of the ``FuelTaxDetail`` entity — a CALCULATED feed
5
+ of per-jurisdiction travel segments, stored as emitted and reconciled
6
+ by ``(id, max version)``; this type's version identity is the
7
+ ``versions`` LIST of component tokens rather than a scalar ``version``
8
+ (the model docstring, DESIGN §8). The estimates-only-tenant caveat
9
+ rides the model (``models/geotab/fuel_tax_detail.py``).
10
+
11
+ The event-time column is ``enter_time``: a segment materializes where
12
+ the vehicle enters the jurisdiction, so its enter instant is the row's
13
+ one event-time identity (``exit_time`` merely closes the interval —
14
+ the routing choice mirrors the interval-endpoint reasoning the trips
15
+ binding records for ``stop``, at the opposite end because THIS
16
+ entity's identity anchors on entry).
17
+
18
+ Every request here is a JSON-RPC POST whose ``params.credentials`` and
19
+ resolved host are the session strategy's injections (the devices-leaf
20
+ convention); the host this module writes is a pre-auth placeholder.
21
+ """
22
+
23
+ from typing import Final
24
+
25
+ from fleetpull.config import GeotabConfig
26
+ from fleetpull.endpoints.geotab._requests import GeotabGetFeedSpecBuilder, server_host
27
+ from fleetpull.endpoints.shared import EndpointDefinition, FeedMode, StorageKind
28
+ from fleetpull.models.geotab import FuelTaxDetail
29
+ from fleetpull.network.decoders import GeotabFeedPageDecoder
30
+ from fleetpull.vocabulary import Provider, QuotaScope
31
+
32
+ __all__: list[str] = ['build_endpoint']
33
+
34
+ # The GetFeed protocol maximum (no lower per-type cap documented or
35
+ # observed for this type).
36
+ _RESULTS_LIMIT: Final[int] = 50000
37
+
38
+ _FUEL_TAX_DETAIL_TYPE_NAME: Final[str] = 'FuelTaxDetail'
39
+
40
+
41
+ def build_endpoint(config: GeotabConfig) -> EndpointDefinition[FuelTaxDetail]:
42
+ """Build the GeoTab fuel_tax_details feed binding.
43
+
44
+ IFTA jurisdiction segments fetched incrementally as a version-token
45
+ feed: the run resumes from the stored token (seeded via
46
+ ``search.fromDate`` on the tokenless first run only), each page
47
+ appends durably before its ``toVersion`` commits, and the fetched
48
+ records land in ``date=YYYY-MM-DD`` partitions (by ``enter_time``)
49
+ as new numbered part files — re-emitted versions accumulate for the
50
+ consumer's ``(id, max version)`` reconcile.
51
+
52
+ Args:
53
+ config: The validated GeoTab configuration; supplies the
54
+ authentication host the pre-auth spec URLs are built on.
55
+
56
+ Returns:
57
+ The frozen fuel_tax_details ``EndpointDefinition``. Construction
58
+ validates the ``FeedMode`` / ``APPEND_LOG`` /
59
+ ``event_time_column`` triple against the response model.
60
+ """
61
+ return EndpointDefinition(
62
+ provider=Provider.GEOTAB,
63
+ name='fuel_tax_details',
64
+ spec_builder=GeotabGetFeedSpecBuilder(
65
+ server=server_host(config),
66
+ type_name=_FUEL_TAX_DETAIL_TYPE_NAME,
67
+ results_limit=_RESULTS_LIMIT,
68
+ ),
69
+ page_decoder=GeotabFeedPageDecoder(),
70
+ response_model=FuelTaxDetail,
71
+ quota_scope=QuotaScope.GEOTAB_FEED,
72
+ storage_kind=StorageKind.APPEND_LOG,
73
+ sync_mode=FeedMode(),
74
+ event_time_column='enter_time',
75
+ )