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.
- fleetpull/__init__.py +36 -0
- fleetpull/api/__init__.py +32 -0
- fleetpull/api/auth_ingress.py +216 -0
- fleetpull/api/catalog.py +139 -0
- fleetpull/api/fetch.py +229 -0
- fleetpull/api/identity.py +90 -0
- fleetpull/api/sync.py +735 -0
- fleetpull/cli.py +146 -0
- fleetpull/config/__init__.py +47 -0
- fleetpull/config/base.py +18 -0
- fleetpull/config/example.py +90 -0
- fleetpull/config/geotab.py +78 -0
- fleetpull/config/http.py +32 -0
- fleetpull/config/loading.py +195 -0
- fleetpull/config/logger.py +139 -0
- fleetpull/config/providers.py +520 -0
- fleetpull/config/rate_limit.py +50 -0
- fleetpull/config/resolution.py +156 -0
- fleetpull/config/retry.py +69 -0
- fleetpull/config/root.py +157 -0
- fleetpull/config/sections.py +146 -0
- fleetpull/endpoints/__init__.py +24 -0
- fleetpull/endpoints/geotab/__init__.py +12 -0
- fleetpull/endpoints/geotab/_requests.py +427 -0
- fleetpull/endpoints/geotab/annotation_logs.py +73 -0
- fleetpull/endpoints/geotab/audits.py +70 -0
- fleetpull/endpoints/geotab/devices.py +79 -0
- fleetpull/endpoints/geotab/driver_changes.py +73 -0
- fleetpull/endpoints/geotab/duty_status_logs.py +75 -0
- fleetpull/endpoints/geotab/dvir_logs.py +76 -0
- fleetpull/endpoints/geotab/exception_events.py +119 -0
- fleetpull/endpoints/geotab/fault_data.py +72 -0
- fleetpull/endpoints/geotab/fill_ups.py +79 -0
- fleetpull/endpoints/geotab/fuel_and_energy_used.py +74 -0
- fleetpull/endpoints/geotab/fuel_tax_details.py +75 -0
- fleetpull/endpoints/geotab/log_records.py +74 -0
- fleetpull/endpoints/geotab/media_files.py +74 -0
- fleetpull/endpoints/geotab/shipment_logs.py +71 -0
- fleetpull/endpoints/geotab/status_data.py +71 -0
- fleetpull/endpoints/geotab/text_messages.py +77 -0
- fleetpull/endpoints/geotab/trips.py +102 -0
- fleetpull/endpoints/geotab/users.py +81 -0
- fleetpull/endpoints/motive/__init__.py +12 -0
- fleetpull/endpoints/motive/_spec_builders.py +94 -0
- fleetpull/endpoints/motive/driver_idle_rollups.py +113 -0
- fleetpull/endpoints/motive/driving_periods.py +85 -0
- fleetpull/endpoints/motive/groups.py +61 -0
- fleetpull/endpoints/motive/idle_events.py +94 -0
- fleetpull/endpoints/motive/users.py +64 -0
- fleetpull/endpoints/motive/vehicle_locations.py +170 -0
- fleetpull/endpoints/motive/vehicle_utilizations.py +122 -0
- fleetpull/endpoints/motive/vehicles.py +106 -0
- fleetpull/endpoints/registry.py +280 -0
- fleetpull/endpoints/samsara/__init__.py +12 -0
- fleetpull/endpoints/samsara/_spec_builders.py +202 -0
- fleetpull/endpoints/samsara/addresses.py +77 -0
- fleetpull/endpoints/samsara/asset_locations.py +217 -0
- fleetpull/endpoints/samsara/driver_fuel_energy_reports.py +124 -0
- fleetpull/endpoints/samsara/driver_vehicle_assignments.py +211 -0
- fleetpull/endpoints/samsara/drivers.py +169 -0
- fleetpull/endpoints/samsara/engine_states.py +114 -0
- fleetpull/endpoints/samsara/gps_readings.py +113 -0
- fleetpull/endpoints/samsara/idling_events.py +195 -0
- fleetpull/endpoints/samsara/odometer_readings.py +116 -0
- fleetpull/endpoints/samsara/trips.py +217 -0
- fleetpull/endpoints/samsara/vehicle_fuel_energy_reports.py +139 -0
- fleetpull/endpoints/samsara/vehicles.py +121 -0
- fleetpull/endpoints/shared/__init__.py +57 -0
- fleetpull/endpoints/shared/base.py +529 -0
- fleetpull/endpoints/shared/request_shape.py +227 -0
- fleetpull/endpoints/shared/resume.py +74 -0
- fleetpull/endpoints/shared/spec_builders.py +59 -0
- fleetpull/endpoints/shared/sync_mode.py +159 -0
- fleetpull/endpoints/shared/url_paths.py +149 -0
- fleetpull/exceptions.py +320 -0
- fleetpull/incremental/__init__.py +27 -0
- fleetpull/incremental/cursor.py +66 -0
- fleetpull/incremental/resolution.py +152 -0
- fleetpull/incremental/seed.py +53 -0
- fleetpull/incremental/window.py +94 -0
- fleetpull/logger/__init__.py +5 -0
- fleetpull/logger/setup.py +145 -0
- fleetpull/model_contract/__init__.py +6 -0
- fleetpull/model_contract/coercions.py +33 -0
- fleetpull/model_contract/response.py +45 -0
- fleetpull/models/__init__.py +7 -0
- fleetpull/models/geotab/__init__.py +166 -0
- fleetpull/models/geotab/annotation_log.py +109 -0
- fleetpull/models/geotab/audit.py +56 -0
- fleetpull/models/geotab/device.py +217 -0
- fleetpull/models/geotab/driver_change.py +102 -0
- fleetpull/models/geotab/duty_status_log.py +200 -0
- fleetpull/models/geotab/dvir_log.py +184 -0
- fleetpull/models/geotab/exception_event.py +135 -0
- fleetpull/models/geotab/fault_data.py +168 -0
- fleetpull/models/geotab/fill_up.py +189 -0
- fleetpull/models/geotab/fuel_and_energy_used.py +81 -0
- fleetpull/models/geotab/fuel_tax_detail.py +150 -0
- fleetpull/models/geotab/log_record.py +68 -0
- fleetpull/models/geotab/media_file.py +133 -0
- fleetpull/models/geotab/shared.py +221 -0
- fleetpull/models/geotab/shipment_log.py +105 -0
- fleetpull/models/geotab/status_data.py +108 -0
- fleetpull/models/geotab/text_message.py +125 -0
- fleetpull/models/geotab/trip.py +162 -0
- fleetpull/models/geotab/user.py +187 -0
- fleetpull/models/motive/__init__.py +43 -0
- fleetpull/models/motive/driver_idle_rollup.py +109 -0
- fleetpull/models/motive/driving_period.py +82 -0
- fleetpull/models/motive/group.py +49 -0
- fleetpull/models/motive/idle_event.py +77 -0
- fleetpull/models/motive/shared.py +192 -0
- fleetpull/models/motive/user.py +217 -0
- fleetpull/models/motive/vehicle.py +162 -0
- fleetpull/models/motive/vehicle_location.py +127 -0
- fleetpull/models/motive/vehicle_utilization.py +128 -0
- fleetpull/models/samsara/__init__.py +108 -0
- fleetpull/models/samsara/address.py +149 -0
- fleetpull/models/samsara/asset_location.py +131 -0
- fleetpull/models/samsara/driver.py +232 -0
- fleetpull/models/samsara/driver_fuel_energy_report.py +153 -0
- fleetpull/models/samsara/driver_vehicle_assignment.py +168 -0
- fleetpull/models/samsara/engine_state.py +92 -0
- fleetpull/models/samsara/gps_reading.py +146 -0
- fleetpull/models/samsara/idling_event.py +174 -0
- fleetpull/models/samsara/odometer_reading.py +90 -0
- fleetpull/models/samsara/trip.py +199 -0
- fleetpull/models/samsara/vehicle.py +167 -0
- fleetpull/models/samsara/vehicle_fuel_energy_report.py +191 -0
- fleetpull/network/__init__.py +9 -0
- fleetpull/network/auth/__init__.py +15 -0
- fleetpull/network/auth/authenticate.py +271 -0
- fleetpull/network/auth/manager.py +223 -0
- fleetpull/network/auth/models.py +57 -0
- fleetpull/network/auth/strategies.py +169 -0
- fleetpull/network/classifiers/__init__.py +11 -0
- fleetpull/network/classifiers/geotab.py +145 -0
- fleetpull/network/classifiers/motive.py +79 -0
- fleetpull/network/classifiers/samsara.py +80 -0
- fleetpull/network/client/__init__.py +25 -0
- fleetpull/network/client/page.py +32 -0
- fleetpull/network/client/profile.py +27 -0
- fleetpull/network/client/registry.py +103 -0
- fleetpull/network/client/registry_base.py +150 -0
- fleetpull/network/client/runtime.py +44 -0
- fleetpull/network/client/transport.py +381 -0
- fleetpull/network/contract/__init__.py +50 -0
- fleetpull/network/contract/auth.py +48 -0
- fleetpull/network/contract/classifier.py +189 -0
- fleetpull/network/contract/envelope_fetcher.py +33 -0
- fleetpull/network/contract/envelopes.py +189 -0
- fleetpull/network/contract/outcome.py +43 -0
- fleetpull/network/contract/page_decoder.py +101 -0
- fleetpull/network/contract/request.py +97 -0
- fleetpull/network/decoders/__init__.py +40 -0
- fleetpull/network/decoders/_window_stamp.py +78 -0
- fleetpull/network/decoders/geotab.py +309 -0
- fleetpull/network/decoders/motive.py +204 -0
- fleetpull/network/decoders/motive_reports.py +114 -0
- fleetpull/network/decoders/samsara.py +343 -0
- fleetpull/network/decoders/samsara_reports.py +119 -0
- fleetpull/network/decoders/single_page.py +55 -0
- fleetpull/network/limits/__init__.py +13 -0
- fleetpull/network/limits/bucket_math.py +58 -0
- fleetpull/network/limits/limiter.py +153 -0
- fleetpull/network/limits/registry.py +96 -0
- fleetpull/network/posture/__init__.py +6 -0
- fleetpull/network/posture/client_options.py +96 -0
- fleetpull/network/retry/__init__.py +13 -0
- fleetpull/network/retry/decision.py +155 -0
- fleetpull/network/tls/__init__.py +5 -0
- fleetpull/network/tls/truststore_context.py +35 -0
- fleetpull/orchestrator/__init__.py +30 -0
- fleetpull/orchestrator/backfill.py +127 -0
- fleetpull/orchestrator/batch.py +155 -0
- fleetpull/orchestrator/bisection.py +271 -0
- fleetpull/orchestrator/drivers.py +350 -0
- fleetpull/orchestrator/entry.py +347 -0
- fleetpull/orchestrator/executors.py +128 -0
- fleetpull/orchestrator/fanout.py +163 -0
- fleetpull/orchestrator/feed_drive.py +266 -0
- fleetpull/orchestrator/metadata_projection.py +195 -0
- fleetpull/orchestrator/outcome.py +53 -0
- fleetpull/orchestrator/recording.py +85 -0
- fleetpull/orchestrator/resume.py +143 -0
- fleetpull/orchestrator/roster_harvest.py +108 -0
- fleetpull/orchestrator/roster_refresh.py +363 -0
- fleetpull/orchestrator/runner.py +262 -0
- fleetpull/orchestrator/shape_resolution.py +221 -0
- fleetpull/orchestrator/spine.py +166 -0
- fleetpull/orchestrator/streaming.py +153 -0
- fleetpull/orchestrator/unit_loop.py +273 -0
- fleetpull/orchestrator/watermark_drive.py +455 -0
- fleetpull/paths/__init__.py +13 -0
- fleetpull/paths/datasets.py +38 -0
- fleetpull/paths/partitions.py +41 -0
- fleetpull/paths/resolution.py +98 -0
- fleetpull/polars_typing/__init__.py +20 -0
- fleetpull/py.typed +0 -0
- fleetpull/records/__init__.py +19 -0
- fleetpull/records/convert.py +53 -0
- fleetpull/records/dataframe.py +63 -0
- fleetpull/records/event_time.py +70 -0
- fleetpull/records/fields.py +190 -0
- fleetpull/records/flatten.py +54 -0
- fleetpull/records/roster_members.py +74 -0
- fleetpull/records/schema.py +73 -0
- fleetpull/records/validation.py +62 -0
- fleetpull/resources/__init__.py +10 -0
- fleetpull/resources/config.example.yaml +222 -0
- fleetpull/roster/__init__.py +16 -0
- fleetpull/roster/definition.py +46 -0
- fleetpull/roster/key.py +40 -0
- fleetpull/roster/registry.py +93 -0
- fleetpull/state/__init__.py +36 -0
- fleetpull/state/cursors.py +417 -0
- fleetpull/state/database.py +463 -0
- fleetpull/state/migrations.py +430 -0
- fleetpull/state/reconcile.py +127 -0
- fleetpull/state/rosters.py +159 -0
- fleetpull/state/run_ledger.py +572 -0
- fleetpull/state/work_units.py +648 -0
- fleetpull/storage/__init__.py +30 -0
- fleetpull/storage/append.py +187 -0
- fleetpull/storage/atomic.py +161 -0
- fleetpull/storage/files.py +176 -0
- fleetpull/storage/frames.py +98 -0
- fleetpull/storage/metadata.py +161 -0
- fleetpull/storage/partitioned.py +205 -0
- fleetpull/storage/pruning.py +169 -0
- fleetpull/storage/read.py +35 -0
- fleetpull/storage/result.py +34 -0
- fleetpull/storage/single_file.py +130 -0
- fleetpull/storage/splitting.py +88 -0
- fleetpull/storage/staging.py +178 -0
- fleetpull/storage/writers.py +145 -0
- fleetpull/timing/__init__.py +21 -0
- fleetpull/timing/canon.py +92 -0
- fleetpull/timing/clock.py +192 -0
- fleetpull/timing/codec.py +122 -0
- fleetpull/timing/sleeper.py +65 -0
- fleetpull/vocabulary/__init__.py +16 -0
- fleetpull/vocabulary/json_types.py +20 -0
- fleetpull/vocabulary/provider.py +29 -0
- fleetpull/vocabulary/quota_scope.py +47 -0
- fleetpull/vocabulary/response_category.py +32 -0
- fleetpull-0.1.0.dist-info/METADATA +248 -0
- fleetpull-0.1.0.dist-info/RECORD +252 -0
- fleetpull-0.1.0.dist-info/WHEEL +5 -0
- fleetpull-0.1.0.dist-info/entry_points.txt +2 -0
- fleetpull-0.1.0.dist-info/licenses/LICENSE +202 -0
- 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
|
+
)
|