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,217 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/device.py
|
|
2
|
+
"""GeoTab Device response model (JSON-RPC ``Get`` on ``typeName: Device``).
|
|
3
|
+
|
|
4
|
+
Written from captured live responses (2026-07-09 probe session), never
|
|
5
|
+
from docs. The captured schema is polymorphic -- one entity type spans
|
|
6
|
+
at least three shapes: GO7-era hardware, GO9-era hardware (a superset
|
|
7
|
+
with ``vinInfo*`` and continuous-connect fields), and trailer entries
|
|
8
|
+
(``deviceType: "None"``, ``productId: -1``, ``tmpTrailerId``, no
|
|
9
|
+
telematics-parameter fields at all). Two tracked GO9 records also
|
|
10
|
+
arrived without ``deviceFlags``/``devicePlans`` entirely, so shape
|
|
11
|
+
poverty is itself a shape. The model is therefore the union of observed
|
|
12
|
+
fields with every field optional; ``models_to_dataframe`` lands absent
|
|
13
|
+
fields as nulls, one typed schema across all shapes.
|
|
14
|
+
|
|
15
|
+
Excluded fields (``extra='ignore'`` makes exclusion exactly "don't
|
|
16
|
+
model it"):
|
|
17
|
+
|
|
18
|
+
- ``ignoreDownloadsUntil`` -- observed live at ``0001-01-01``, which
|
|
19
|
+
overflows nanosecond-precision timestamp columns (captured
|
|
20
|
+
2026-07-09). No other captured field carries a year-one datetime.
|
|
21
|
+
- ``wifiHotspotLimits``, ``customProperties``, ``mediaFiles`` --
|
|
22
|
+
observed only as empty lists, so their element shape is uncaptured
|
|
23
|
+
and cannot be honestly typed.
|
|
24
|
+
- ``groups``, ``devicePlanBillingInfo``, ``autoGroups``,
|
|
25
|
+
``customParameters`` -- lists of objects; the records layer's schema
|
|
26
|
+
derivation (DESIGN section 9) supports scalars, enums,
|
|
27
|
+
``list[scalar]``, and nested models only, so these shapes have no
|
|
28
|
+
honest column until the ``list[nested model]`` derivation vertical
|
|
29
|
+
lands. Model them there, or when a consumer forces typed access.
|
|
30
|
+
|
|
31
|
+
Sentinels are stored as-is, never transformed (interpretation is the
|
|
32
|
+
consumer's concern; the boundary model's job is shape): ``activeTo``
|
|
33
|
+
of ``2050-01-01`` means "still active" (and is ns-safe); the VIN fields
|
|
34
|
+
carry ``""`` and a literal ``"?"`` for unknown VINs; ``productId: -1``
|
|
35
|
+
marks non-telematics (trailer) entries.
|
|
36
|
+
|
|
37
|
+
Wire keys are camelCase; fields are snake_case via the ``to_camel``
|
|
38
|
+
alias generator rather than 74 hand-written ``Field(alias=...)`` lines
|
|
39
|
+
-- a typo'd hand alias would land silently as ``None`` under
|
|
40
|
+
``extra='ignore'``, while the generator is mechanically exact (the
|
|
41
|
+
model tests validate every captured shape against it). The three
|
|
42
|
+
``DeviceFlags`` acronym keys the generator cannot produce
|
|
43
|
+
(``isHOSAllowed`` / ``isUIAllowed`` / ``isVINAllowed``) carry explicit
|
|
44
|
+
alias overrides, proven non-``None`` against a captured block.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
from datetime import datetime
|
|
48
|
+
|
|
49
|
+
from pydantic import ConfigDict, Field
|
|
50
|
+
from pydantic.alias_generators import to_camel
|
|
51
|
+
|
|
52
|
+
from fleetpull.model_contract import ResponseModel
|
|
53
|
+
|
|
54
|
+
__all__: list[str] = ['CustomFeatures', 'Device', 'DeviceFlags']
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DeviceFlags(ResponseModel):
|
|
58
|
+
"""The ``deviceFlags`` block: account-level capability flags.
|
|
59
|
+
|
|
60
|
+
A pure mirror of the captured block -- one ``activeFeatures`` string
|
|
61
|
+
list plus ten capability booleans; absent from two tracked GO9
|
|
62
|
+
captures and every trailer entry, so the whole block is optional on
|
|
63
|
+
``Device`` and its columns land as nulls there. ``ratePlans`` is
|
|
64
|
+
excluded from this model's interior: observed only as an empty
|
|
65
|
+
list, its element shape is uncapturable (the same rule as the
|
|
66
|
+
top-level empty lists).
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
70
|
+
|
|
71
|
+
active_features: list[str] | None = None
|
|
72
|
+
is_active_tracking_allowed: bool | None = None
|
|
73
|
+
is_continuous_connect_allowed: bool | None = None
|
|
74
|
+
is_engine_allowed: bool | None = None
|
|
75
|
+
is_garmin_allowed: bool | None = None
|
|
76
|
+
# The acronym-alias trap: to_camel would emit isHosAllowed /
|
|
77
|
+
# isUiAllowed / isVinAllowed, silently landing these as None under
|
|
78
|
+
# extra='ignore'; the wire capitalizes the acronyms, so the three
|
|
79
|
+
# carry explicit aliases (an explicit alias beats the generator).
|
|
80
|
+
is_hos_allowed: bool | None = Field(default=None, alias='isHOSAllowed')
|
|
81
|
+
is_iridium_allowed: bool | None = None
|
|
82
|
+
is_odometer_allowed: bool | None = None
|
|
83
|
+
is_trip_detail_allowed: bool | None = None
|
|
84
|
+
is_ui_allowed: bool | None = Field(default=None, alias='isUIAllowed')
|
|
85
|
+
is_vin_allowed: bool | None = Field(default=None, alias='isVINAllowed')
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class CustomFeatures(ResponseModel):
|
|
89
|
+
"""The ``customFeatures`` block: per-account feature toggles.
|
|
90
|
+
|
|
91
|
+
Capture-limited: ``autoHos`` is the one key every tracked capture
|
|
92
|
+
carries, and the name suggests the block holds per-account keys --
|
|
93
|
+
any unknown key is absorbed by ``extra='ignore'`` today and gets
|
|
94
|
+
modeled if a capture ever shows it.
|
|
95
|
+
"""
|
|
96
|
+
|
|
97
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
98
|
+
|
|
99
|
+
auto_hos: bool | None = None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class Device(ResponseModel):
|
|
103
|
+
"""One GeoTab Device entity: vehicle-installed GO hardware or a trailer.
|
|
104
|
+
|
|
105
|
+
A pure mirror of the union of captured fields, everything optional
|
|
106
|
+
(the shape polymorphism above). Field semantics are GeoTab's; no
|
|
107
|
+
value is derived or interpreted here. Groups below follow the
|
|
108
|
+
captured record layout: identity and lifecycle, vehicle identity,
|
|
109
|
+
fleet bookkeeping, firmware/parameter state, telematics
|
|
110
|
+
configuration scalars, and the aux-channel arrays.
|
|
111
|
+
|
|
112
|
+
Attributes:
|
|
113
|
+
id: GeoTab's device id -- the seek-paging sort key (hex-suffixed
|
|
114
|
+
string, ascending).
|
|
115
|
+
serial_number: Hardware serial; ``""`` on trailer entries.
|
|
116
|
+
name: The unit's display name.
|
|
117
|
+
device_type: Hardware generation (``GO7``, ``GO9``; trailers
|
|
118
|
+
carry the literal string ``"None"``).
|
|
119
|
+
product_id: Numeric hardware product code; ``-1`` on trailers.
|
|
120
|
+
active_from: Start of the device's active window (UTC).
|
|
121
|
+
active_to: End of the active window; ``2050-01-01`` is GeoTab's
|
|
122
|
+
still-active sentinel, stored as-is.
|
|
123
|
+
vehicle_identification_number: Registered VIN; ``""`` and a
|
|
124
|
+
literal ``"?"`` are captured unknown-VIN sentinels.
|
|
125
|
+
engine_vehicle_identification_number: ECM-reported VIN; same
|
|
126
|
+
sentinels.
|
|
127
|
+
tmp_trailer_id: Trailer-entry identifier; absent on tracked
|
|
128
|
+
vehicles.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
132
|
+
|
|
133
|
+
# Identity and lifecycle.
|
|
134
|
+
id: str | None = None
|
|
135
|
+
serial_number: str | None = None
|
|
136
|
+
name: str | None = None
|
|
137
|
+
device_type: str | None = None
|
|
138
|
+
product_id: int | None = None
|
|
139
|
+
active_from: datetime | None = None
|
|
140
|
+
active_to: datetime | None = None
|
|
141
|
+
comment: str | None = None
|
|
142
|
+
tmp_trailer_id: str | None = None
|
|
143
|
+
|
|
144
|
+
# Vehicle identity (VIN sentinels: "" and a literal "?").
|
|
145
|
+
vehicle_identification_number: str | None = None
|
|
146
|
+
engine_vehicle_identification_number: str | None = None
|
|
147
|
+
vin_info_make: str | None = None
|
|
148
|
+
vin_info_model: str | None = None
|
|
149
|
+
vin_info_vehicle_type: int | None = None
|
|
150
|
+
vin_info_year: str | None = None
|
|
151
|
+
license_plate: str | None = None
|
|
152
|
+
license_state: str | None = None
|
|
153
|
+
|
|
154
|
+
# Fleet bookkeeping.
|
|
155
|
+
device_plans: list[str] | None = None
|
|
156
|
+
device_flags: DeviceFlags | None = None
|
|
157
|
+
custom_features: CustomFeatures | None = None
|
|
158
|
+
time_zone_id: str | None = None
|
|
159
|
+
work_time: str | None = None
|
|
160
|
+
auto_hos: str | None = None
|
|
161
|
+
go_talk_language: str | None = None
|
|
162
|
+
pin_device: bool | None = None
|
|
163
|
+
|
|
164
|
+
# Firmware / parameter state.
|
|
165
|
+
major: int | None = None
|
|
166
|
+
minor: int | None = None
|
|
167
|
+
parameter_version: int | None = None
|
|
168
|
+
parameter_version_on_device: int | None = None
|
|
169
|
+
enable_must_reprogram: bool | None = None
|
|
170
|
+
time_to_download: str | None = None
|
|
171
|
+
|
|
172
|
+
# Telematics configuration scalars.
|
|
173
|
+
acceleration_warning_threshold: int | None = None
|
|
174
|
+
accelerometer_threshold_warning_factor: int | None = None
|
|
175
|
+
braking_warning_threshold: int | None = None
|
|
176
|
+
cornering_warning_threshold: int | None = None
|
|
177
|
+
communication_threshold_interval_moving: int | None = None
|
|
178
|
+
communication_threshold_interval_stationary: int | None = None
|
|
179
|
+
disable_buzzer: bool | None = None
|
|
180
|
+
disable_sleeper_berth: bool | None = None
|
|
181
|
+
enable_beep_on_dangerous_driving: bool | None = None
|
|
182
|
+
enable_beep_on_idle: bool | None = None
|
|
183
|
+
enable_beep_on_rpm: bool | None = None
|
|
184
|
+
enable_control_external_relay: bool | None = None
|
|
185
|
+
enable_speed_warning: bool | None = None
|
|
186
|
+
engine_hour_offset: int | None = None
|
|
187
|
+
engine_type: str | None = None
|
|
188
|
+
ensure_hot_start: bool | None = None
|
|
189
|
+
external_device_shut_down_delay: int | None = None
|
|
190
|
+
force_active_tracking: bool | None = None
|
|
191
|
+
fuel_tank_capacity: int | None = None
|
|
192
|
+
gps_off_delay: int | None = None
|
|
193
|
+
idle_minutes: int | None = None
|
|
194
|
+
immobilize_arming: int | None = None
|
|
195
|
+
immobilize_unit: bool | None = None
|
|
196
|
+
is_active_tracking_enabled: bool | None = None
|
|
197
|
+
is_continuous_connect_enabled: bool | None = None
|
|
198
|
+
is_driver_seatbelt_warning_on: bool | None = None
|
|
199
|
+
is_iox_connection_enabled: bool | None = None
|
|
200
|
+
is_passenger_seatbelt_warning_on: bool | None = None
|
|
201
|
+
is_reverse_detect_on: bool | None = None
|
|
202
|
+
is_speed_indicator: bool | None = None
|
|
203
|
+
max_seconds_between_logs: int | None = None
|
|
204
|
+
min_accident_speed: int | None = None
|
|
205
|
+
obd_alert_enabled: bool | None = None
|
|
206
|
+
odometer_factor: float | None = None
|
|
207
|
+
odometer_offset: float | None = None
|
|
208
|
+
rpm_value: int | None = None
|
|
209
|
+
seatbelt_warning_speed: int | None = None
|
|
210
|
+
speeding_off: int | None = None
|
|
211
|
+
speeding_on: int | None = None
|
|
212
|
+
|
|
213
|
+
# Aux-channel arrays (fixed-width per hardware generation).
|
|
214
|
+
aux_warning_speed: list[int] | None = None
|
|
215
|
+
enable_aux_warning: list[bool] | None = None
|
|
216
|
+
is_aux_ign_trigger: list[bool] | None = None
|
|
217
|
+
is_aux_inverted: list[bool] | None = None
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/driver_change.py
|
|
2
|
+
"""GeoTab DriverChange response model (``GetFeed`` on ``typeName: DriverChange``).
|
|
3
|
+
|
|
4
|
+
Written from the 2026-07-21 feed wave two census (30-day seeded pulls at
|
|
5
|
+
the probed tenant), never from docs. A DriverChange is one
|
|
6
|
+
driver-to-device assignment event. The type carries a per-record
|
|
7
|
+
``version``, and DriverChange records are user-editable through the
|
|
8
|
+
provider, so re-emission under newer versions is expected and the
|
|
9
|
+
consumer reconciles by ``(id, max version)`` (DESIGN §4).
|
|
10
|
+
|
|
11
|
+
Requiredness posture (the wave-two conservative stance, DESIGN §8): the
|
|
12
|
+
census is a TENANT-SCOPED observation (1,114 records), so structural
|
|
13
|
+
requiredness is limited to the record identity — ``id``, ``dateTime``
|
|
14
|
+
(the event time), ``version``, and the primary entity ref (``driver``,
|
|
15
|
+
the entity the type names) — and every other field is optional EVEN
|
|
16
|
+
where the census was total. The observed arms:
|
|
17
|
+
|
|
18
|
+
- ``driver`` is PROVEN mixed object-or-string (1,114/1,114, both arms
|
|
19
|
+
observed); its object arm carries ``isDriver`` beside the id, so the
|
|
20
|
+
ref model mirrors it (null exactly on string-arm rows). ``device``
|
|
21
|
+
was object-only on this census; both refs ride the shared
|
|
22
|
+
``bare_id_to_reference`` lift (the census-scope lesson: a tenant
|
|
23
|
+
census cannot prove the string arm absent).
|
|
24
|
+
|
|
25
|
+
``dateTime`` is recovered tz-aware by validation, the GeoTab sibling
|
|
26
|
+
idiom. ``type`` is a census-open vocabulary — a plain str, never an
|
|
27
|
+
enum.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from datetime import datetime
|
|
31
|
+
from typing import Annotated
|
|
32
|
+
|
|
33
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
34
|
+
from pydantic.alias_generators import to_camel
|
|
35
|
+
|
|
36
|
+
from fleetpull.model_contract import ResponseModel
|
|
37
|
+
from fleetpull.models.geotab.shared import bare_id_to_reference
|
|
38
|
+
|
|
39
|
+
__all__: list[str] = [
|
|
40
|
+
'DriverChange',
|
|
41
|
+
'DriverChangeDeviceRef',
|
|
42
|
+
'DriverChangeDriverRef',
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DriverChangeDeviceRef(ResponseModel):
|
|
47
|
+
"""The assignment's device reference.
|
|
48
|
+
|
|
49
|
+
Census-observed as an ``{id}`` object on every record; the shared
|
|
50
|
+
coercion lifts a bare string defensively (the census-scope lesson).
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
54
|
+
|
|
55
|
+
id: str
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class DriverChangeDriverRef(ResponseModel):
|
|
59
|
+
"""The assignment's driver reference.
|
|
60
|
+
|
|
61
|
+
PROVEN mixed on this census: an ``{id, isDriver}`` object or a bare
|
|
62
|
+
id string (e.g. the ``"UnknownDriverId"`` sentinel); the shared
|
|
63
|
+
coercion lifts the bare form to ``{"id": <string>}``, so
|
|
64
|
+
``is_driver`` is null exactly on string-arm rows.
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
68
|
+
|
|
69
|
+
id: str
|
|
70
|
+
is_driver: bool | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class DriverChange(ResponseModel):
|
|
74
|
+
"""One GeoTab driver-to-device assignment event.
|
|
75
|
+
|
|
76
|
+
The wave-two conservative mirror (the module docstring's posture):
|
|
77
|
+
``id`` / ``date_time`` / ``version`` / ``driver`` required,
|
|
78
|
+
everything else optional even where census-total.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
date_time: The change's UTC instant — the endpoint's event time.
|
|
82
|
+
device: The assignment's device reference (object-only on this
|
|
83
|
+
census; defensively lifted).
|
|
84
|
+
driver: The assignment's driver reference — proven
|
|
85
|
+
object-or-string, ``is_driver`` null exactly on string-arm
|
|
86
|
+
rows.
|
|
87
|
+
id: GeoTab's record id.
|
|
88
|
+
type: The change-type token (census-open plain str).
|
|
89
|
+
version: The record's version token — the reconcile key beside
|
|
90
|
+
``id``.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
94
|
+
|
|
95
|
+
date_time: datetime
|
|
96
|
+
device: Annotated[
|
|
97
|
+
DriverChangeDeviceRef | None, BeforeValidator(bare_id_to_reference)
|
|
98
|
+
] = None
|
|
99
|
+
driver: Annotated[DriverChangeDriverRef, BeforeValidator(bare_id_to_reference)]
|
|
100
|
+
id: str
|
|
101
|
+
type: str | None = None
|
|
102
|
+
version: str
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/duty_status_log.py
|
|
2
|
+
"""GeoTab DutyStatusLog response model (``GetFeed`` on ``typeName: DutyStatusLog``).
|
|
3
|
+
|
|
4
|
+
Written from the 2026-07-21 feed wave two census (30-day seeded pulls at
|
|
5
|
+
the probed tenant), never from docs. A DutyStatusLog is one HOS
|
|
6
|
+
duty-status event — an EDITABLE log: ``editDateTime`` is the edit
|
|
7
|
+
trail, re-emission under newer ``version`` tokens is expected, and the
|
|
8
|
+
consumer reconciles by ``(id, max version)`` (DESIGN §4).
|
|
9
|
+
|
|
10
|
+
Requiredness posture (the wave-two conservative stance, DESIGN §8): the
|
|
11
|
+
census is a TENANT-SCOPED observation, so structural requiredness is
|
|
12
|
+
limited to the record identity — ``id``, ``dateTime`` (the event
|
|
13
|
+
time), ``version``, and the primary entity ref (``driver``, the log's
|
|
14
|
+
subject) — and every other field is optional EVEN where the census was
|
|
15
|
+
total (2,000/2,000). The observed arms:
|
|
16
|
+
|
|
17
|
+
- ``device`` and ``driver`` are PROVEN mixed object-or-string
|
|
18
|
+
(2,000/2,000 each, both arms observed); every reference field rides
|
|
19
|
+
the shared ``bare_id_to_reference`` lift.
|
|
20
|
+
- ``annotations`` (126/2,000) carried elements that are EXACTLY
|
|
21
|
+
``{"id": <str>}`` on every sampled record; the records layer supports
|
|
22
|
+
list-of-scalar only, so the field is an ID-LIST — ``list[str]`` via a
|
|
23
|
+
STRICT element lift (``_annotation_ids``): a shape change fails
|
|
24
|
+
loudly, never silently drops sibling keys. The ids join to the
|
|
25
|
+
``annotation_logs`` vertical (feed wave three) for the full
|
|
26
|
+
annotation records.
|
|
27
|
+
- ``location`` (1,859/2,000) is the shared ``GeotabAddressedLocation``
|
|
28
|
+
wrapper (DVIRLog is the second consumer; DESIGN §8): it carries a
|
|
29
|
+
double-nested ``{location: {x, y}}`` COORDINATE arm or an
|
|
30
|
+
``{address: {formattedAddress}}`` arm. The 200-sample census saw only
|
|
31
|
+
coordinates; a 24,860-block live-proof walk found 14 address-arm
|
|
32
|
+
blocks (the census-scope lesson), so both arms are modeled optional.
|
|
33
|
+
- Mixed int-or-float numerics (``distanceSinceValidCoordinates``,
|
|
34
|
+
``engineHours``, ``odometer``) model ``float``.
|
|
35
|
+
|
|
36
|
+
``dateTime``, ``editDateTime``, and ``verifyDateTime`` are recovered
|
|
37
|
+
tz-aware by validation, the GeoTab sibling idiom. ``deferralStatus``,
|
|
38
|
+
``malfunction``, ``origin``, ``state``, and ``status`` are census-open
|
|
39
|
+
vocabularies — plain strs, never enums.
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
from datetime import datetime
|
|
43
|
+
from typing import Annotated
|
|
44
|
+
|
|
45
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
46
|
+
from pydantic.alias_generators import to_camel
|
|
47
|
+
|
|
48
|
+
from fleetpull.model_contract import ResponseModel
|
|
49
|
+
from fleetpull.models.geotab.shared import GeotabAddressedLocation, bare_id_to_reference
|
|
50
|
+
from fleetpull.vocabulary import JsonValue
|
|
51
|
+
|
|
52
|
+
__all__: list[str] = [
|
|
53
|
+
'DutyStatusLog',
|
|
54
|
+
'DutyStatusLogDeviceRef',
|
|
55
|
+
'DutyStatusLogDriverRef',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _annotation_ids(value: JsonValue) -> JsonValue:
|
|
60
|
+
"""Strictly lift the ``annotations`` element list to its id list.
|
|
61
|
+
|
|
62
|
+
The census shows elements carrying ONLY ``{"id": <str>}``; the lift
|
|
63
|
+
is deliberately STRICT so a wire-shape change fails validation
|
|
64
|
+
loudly instead of silently dropping sibling keys the model never
|
|
65
|
+
saw (the loud-failure doctrine).
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
value: The raw ``annotations`` wire value.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
``None`` passthrough (the field is optional), or the element
|
|
72
|
+
ids: a bare-string element passes verbatim, an EXACTLY
|
|
73
|
+
``{'id': <str>}`` element becomes its id.
|
|
74
|
+
|
|
75
|
+
Raises:
|
|
76
|
+
ValueError: The value is not a list, or an element is neither a
|
|
77
|
+
bare string nor exactly ``{'id': <str>}`` (extra keys, other
|
|
78
|
+
shapes); the message names the offending element.
|
|
79
|
+
"""
|
|
80
|
+
if value is None:
|
|
81
|
+
return None
|
|
82
|
+
if not isinstance(value, list):
|
|
83
|
+
raise ValueError(f'annotations must be a list, got {type(value).__name__}')
|
|
84
|
+
lifted: list[JsonValue] = []
|
|
85
|
+
for element in value:
|
|
86
|
+
if isinstance(element, str):
|
|
87
|
+
lifted.append(element)
|
|
88
|
+
continue
|
|
89
|
+
if (
|
|
90
|
+
isinstance(element, dict)
|
|
91
|
+
and set(element) == {'id'}
|
|
92
|
+
and isinstance(element['id'], str)
|
|
93
|
+
):
|
|
94
|
+
lifted.append(element['id'])
|
|
95
|
+
continue
|
|
96
|
+
raise ValueError(
|
|
97
|
+
f'annotations element must be a bare id string or exactly '
|
|
98
|
+
f"{{'id': <str>}}, got {element!r}"
|
|
99
|
+
)
|
|
100
|
+
return lifted
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DutyStatusLogDeviceRef(ResponseModel):
|
|
104
|
+
"""The recording device's reference.
|
|
105
|
+
|
|
106
|
+
PROVEN mixed on this census: an ``{id}`` object or a bare id
|
|
107
|
+
string, the bare form lifted by the shared coercion so both arms
|
|
108
|
+
land as ``device__id``.
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
112
|
+
|
|
113
|
+
id: str
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class DutyStatusLogDriverRef(ResponseModel):
|
|
117
|
+
"""The log's driver reference.
|
|
118
|
+
|
|
119
|
+
PROVEN mixed on this census: an ``{id}`` object or a bare id
|
|
120
|
+
string (e.g. the ``"UnknownDriverId"`` sentinel), the bare form
|
|
121
|
+
lifted by the shared coercion so both arms land as ``driver__id``.
|
|
122
|
+
"""
|
|
123
|
+
|
|
124
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
125
|
+
|
|
126
|
+
id: str
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class DutyStatusLog(ResponseModel):
|
|
130
|
+
"""One GeoTab HOS duty-status event from the DutyStatusLog feed.
|
|
131
|
+
|
|
132
|
+
The wave-two conservative mirror (the module docstring's posture):
|
|
133
|
+
``id`` / ``date_time`` / ``version`` / ``driver`` required,
|
|
134
|
+
everything else optional even where census-total.
|
|
135
|
+
|
|
136
|
+
Attributes:
|
|
137
|
+
annotations: The annotation-log ids attached to the event — the
|
|
138
|
+
strict id-list reduction (126/2,000 carriers); join to the
|
|
139
|
+
``annotation_logs`` vertical for the records.
|
|
140
|
+
date_time: The event's UTC instant — the endpoint's event time.
|
|
141
|
+
deferral_minutes: Deferred off-duty minutes.
|
|
142
|
+
deferral_status: The deferral-status token (census-open).
|
|
143
|
+
device: The recording device's reference — proven
|
|
144
|
+
object-or-string.
|
|
145
|
+
distance_since_valid_coordinates: Distance since the last valid
|
|
146
|
+
GPS fix (308/2,000; mixed int-or-float, modeled float).
|
|
147
|
+
driver: The log's driver reference — proven object-or-string.
|
|
148
|
+
edit_date_time: The last edit's UTC instant — the edit trail.
|
|
149
|
+
engine_hours: The engine-hours reading (1,844/2,000; mixed
|
|
150
|
+
int-or-float, modeled float).
|
|
151
|
+
event_code: The ELD event code (1,623/2,000).
|
|
152
|
+
event_record_status: The ELD event-record status.
|
|
153
|
+
event_type: The ELD event type (1,753/2,000).
|
|
154
|
+
id: GeoTab's record id.
|
|
155
|
+
is_ignored: Whether the log is ignored.
|
|
156
|
+
is_transitioning: Whether the log is mid-transition.
|
|
157
|
+
location: The event's nested location (1,859/2,000; the shared
|
|
158
|
+
wrapper — a ``{x, y}`` coordinate arm, x longitude / y
|
|
159
|
+
latitude, or a ``formattedAddress`` arm).
|
|
160
|
+
malfunction: The malfunction token (census-open plain str).
|
|
161
|
+
odometer: The odometer reading (1,863/2,000; mixed
|
|
162
|
+
int-or-float, modeled float).
|
|
163
|
+
origin: The origin token (census-open plain str).
|
|
164
|
+
sequence: The ELD sequence token (1,753/2,000).
|
|
165
|
+
state: The state token (census-open plain str).
|
|
166
|
+
status: The duty-status token (census-open plain str).
|
|
167
|
+
verify_date_time: The driver-verification UTC instant
|
|
168
|
+
(765/2,000).
|
|
169
|
+
version: The record's version token — the editable-log
|
|
170
|
+
reconcile key beside ``id``.
|
|
171
|
+
"""
|
|
172
|
+
|
|
173
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
174
|
+
|
|
175
|
+
annotations: Annotated[list[str] | None, BeforeValidator(_annotation_ids)] = None
|
|
176
|
+
date_time: datetime
|
|
177
|
+
deferral_minutes: int | None = None
|
|
178
|
+
deferral_status: str | None = None
|
|
179
|
+
device: Annotated[
|
|
180
|
+
DutyStatusLogDeviceRef | None, BeforeValidator(bare_id_to_reference)
|
|
181
|
+
] = None
|
|
182
|
+
distance_since_valid_coordinates: float | None = None
|
|
183
|
+
driver: Annotated[DutyStatusLogDriverRef, BeforeValidator(bare_id_to_reference)]
|
|
184
|
+
edit_date_time: datetime | None = None
|
|
185
|
+
engine_hours: float | None = None
|
|
186
|
+
event_code: int | None = None
|
|
187
|
+
event_record_status: int | None = None
|
|
188
|
+
event_type: int | None = None
|
|
189
|
+
id: str
|
|
190
|
+
is_ignored: bool | None = None
|
|
191
|
+
is_transitioning: bool | None = None
|
|
192
|
+
location: GeotabAddressedLocation | None = None
|
|
193
|
+
malfunction: str | None = None
|
|
194
|
+
odometer: float | None = None
|
|
195
|
+
origin: str | None = None
|
|
196
|
+
sequence: str | None = None
|
|
197
|
+
state: str | None = None
|
|
198
|
+
status: str | None = None
|
|
199
|
+
verify_date_time: datetime | None = None
|
|
200
|
+
version: str
|