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,105 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/shipment_log.py
|
|
2
|
+
"""GeoTab ShipmentLog response model (``GetFeed`` on ``typeName: ShipmentLog``).
|
|
3
|
+
|
|
4
|
+
Written from the 2026-07-21 feed wave three SCALE census (walked at
|
|
5
|
+
scale at the probed tenant), never from docs. A ShipmentLog is one
|
|
6
|
+
shipment-manifest record attached to a driver and device over an active
|
|
7
|
+
window — a versioned feed, so re-emission under newer ``version``
|
|
8
|
+
tokens is expected and the consumer reconciles by ``(id, max version)``
|
|
9
|
+
(DESIGN §4).
|
|
10
|
+
|
|
11
|
+
Requiredness posture (the wave-two conservative stance, DESIGN §8): the
|
|
12
|
+
census is a TENANT-SCOPED observation (2,771 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
|
+
consistent with the log family) — and every other field is optional
|
|
16
|
+
EVEN where the census was total. The observed arms:
|
|
17
|
+
|
|
18
|
+
- ``device`` and ``driver`` were object-only (``{id}``) at scale; both
|
|
19
|
+
ride the shared ``bare_id_to_reference`` lift regardless (the
|
|
20
|
+
census-scope lesson, DESIGN §8: a tenant census cannot prove the
|
|
21
|
+
string arm absent).
|
|
22
|
+
|
|
23
|
+
``activeFrom``/``activeTo`` (the shipment's active window) and
|
|
24
|
+
``dateTime`` are recovered tz-aware by validation, the GeoTab sibling
|
|
25
|
+
idiom. ``commodity``, ``documentNumber``, and ``shipperName`` are
|
|
26
|
+
census-open free-text strings.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from datetime import datetime
|
|
30
|
+
from typing import Annotated
|
|
31
|
+
|
|
32
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
33
|
+
from pydantic.alias_generators import to_camel
|
|
34
|
+
|
|
35
|
+
from fleetpull.model_contract import ResponseModel
|
|
36
|
+
from fleetpull.models.geotab.shared import bare_id_to_reference
|
|
37
|
+
|
|
38
|
+
__all__: list[str] = [
|
|
39
|
+
'ShipmentLog',
|
|
40
|
+
'ShipmentLogDeviceRef',
|
|
41
|
+
'ShipmentLogDriverRef',
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ShipmentLogDeviceRef(ResponseModel):
|
|
46
|
+
"""The shipment's device reference.
|
|
47
|
+
|
|
48
|
+
Census-observed as an ``{id}`` object at scale; the shared coercion
|
|
49
|
+
lifts a bare string defensively (the census-scope lesson).
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
53
|
+
|
|
54
|
+
id: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ShipmentLogDriverRef(ResponseModel):
|
|
58
|
+
"""The shipment's driver reference.
|
|
59
|
+
|
|
60
|
+
Census-observed as an ``{id}`` object at scale; the shared coercion
|
|
61
|
+
lifts a bare string defensively (the census-scope lesson).
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
65
|
+
|
|
66
|
+
id: str
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class ShipmentLog(ResponseModel):
|
|
70
|
+
"""One GeoTab shipment-manifest record from the ShipmentLog feed.
|
|
71
|
+
|
|
72
|
+
The wave-two conservative mirror (the module docstring's posture):
|
|
73
|
+
``id`` / ``date_time`` / ``version`` / ``driver`` required,
|
|
74
|
+
everything else optional even where census-total.
|
|
75
|
+
|
|
76
|
+
Attributes:
|
|
77
|
+
active_from: The shipment window's UTC start.
|
|
78
|
+
active_to: The shipment window's UTC end.
|
|
79
|
+
commodity: The shipped commodity (census-open str).
|
|
80
|
+
date_time: The record's UTC instant — the endpoint's event time.
|
|
81
|
+
device: The shipment's device reference (object-only at scale;
|
|
82
|
+
defensively lifted).
|
|
83
|
+
document_number: The manifest document number (census-open str).
|
|
84
|
+
driver: The shipment's driver reference (object-only at scale;
|
|
85
|
+
defensively lifted).
|
|
86
|
+
id: GeoTab's record id.
|
|
87
|
+
shipper_name: The shipper company name (census-open str).
|
|
88
|
+
version: The record's version token — the reconcile key beside
|
|
89
|
+
``id``.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
93
|
+
|
|
94
|
+
active_from: datetime | None = None
|
|
95
|
+
active_to: datetime | None = None
|
|
96
|
+
commodity: str | None = None
|
|
97
|
+
date_time: datetime
|
|
98
|
+
device: Annotated[
|
|
99
|
+
ShipmentLogDeviceRef | None, BeforeValidator(bare_id_to_reference)
|
|
100
|
+
] = None
|
|
101
|
+
document_number: str | None = None
|
|
102
|
+
driver: Annotated[ShipmentLogDriverRef, BeforeValidator(bare_id_to_reference)]
|
|
103
|
+
id: str
|
|
104
|
+
shipper_name: str | None = None
|
|
105
|
+
version: str
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/status_data.py
|
|
2
|
+
"""GeoTab StatusData response model (``GetFeed`` on ``typeName: StatusData``).
|
|
3
|
+
|
|
4
|
+
Written from the 2026-07-21 live probe session, never from docs. A
|
|
5
|
+
StatusData record is one engine-diagnostic reading — the LogRecord
|
|
6
|
+
stream's sibling ACTIVE feed, with one deliberate asymmetry: StatusData
|
|
7
|
+
DOES carry a per-record ``version`` (LogRecord carries none), observed on
|
|
8
|
+
every census record and mirrored — the consumer still reconciles this
|
|
9
|
+
active feed by ``id``, the version riding along as wire truth
|
|
10
|
+
(DESIGN §4).
|
|
11
|
+
|
|
12
|
+
Requiredness posture: the census is a large uniform whole-page total —
|
|
13
|
+
2,000/2,000 records carried every key — so every field is required with
|
|
14
|
+
no nullable arm (none was observed). The census is a TENANT-SCOPED
|
|
15
|
+
observation (DESIGN §8). Volume on the probed tenant is ~24,500
|
|
16
|
+
records/hour, which is why the leaf declares the 50,000
|
|
17
|
+
protocol-maximum ``resultsLimit``.
|
|
18
|
+
|
|
19
|
+
``dateTime`` (the event time) is recovered tz-aware by validation, the
|
|
20
|
+
GeoTab sibling idiom. ``data`` — the diagnostic's value — is MIXED
|
|
21
|
+
int-or-float on the wire and modeled ``float`` (the one dtype that
|
|
22
|
+
carries both arms losslessly). ``controller`` is STRING-OR-OBJECT:
|
|
23
|
+
the ``"ControllerNoneId"`` sentinel string on 49,745 of a 50,000-record
|
|
24
|
+
live full-page census (2026-07-21) and an ``{id}`` reference on the
|
|
25
|
+
other 255 — the Trip ``UnknownDriverId`` mechanism verbatim (the
|
|
26
|
+
initial one-hour census saw only the sentinel arm; the live proof's
|
|
27
|
+
full walk surfaced the object arm — a census-scope lesson recorded in
|
|
28
|
+
the section 8 block). The shared ``bare_id_to_reference`` coercion
|
|
29
|
+
lifts the sentinel to ``{"id": <string>}``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from datetime import datetime
|
|
33
|
+
from typing import Annotated
|
|
34
|
+
|
|
35
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
36
|
+
from pydantic.alias_generators import to_camel
|
|
37
|
+
|
|
38
|
+
from fleetpull.model_contract import ResponseModel
|
|
39
|
+
from fleetpull.models.geotab.shared import bare_id_to_reference
|
|
40
|
+
|
|
41
|
+
__all__: list[str] = [
|
|
42
|
+
'StatusData',
|
|
43
|
+
'StatusDataControllerRef',
|
|
44
|
+
'StatusDataDeviceRef',
|
|
45
|
+
'StatusDataDiagnosticRef',
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class StatusDataControllerRef(ResponseModel):
|
|
50
|
+
"""The source controller reference.
|
|
51
|
+
|
|
52
|
+
Arrives as an ``{id}`` object (255 of the 50,000-record live
|
|
53
|
+
census) or the bare ``"ControllerNoneId"`` sentinel string
|
|
54
|
+
(49,745), which the shared coercion lifts to ``{"id": <string>}``.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
id: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class StatusDataDeviceRef(ResponseModel):
|
|
61
|
+
"""The reading's device reference: the id alone, on every record."""
|
|
62
|
+
|
|
63
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
64
|
+
|
|
65
|
+
id: str
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class StatusDataDiagnosticRef(ResponseModel):
|
|
69
|
+
"""The reading's diagnostic reference: the id alone, on every record."""
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
72
|
+
|
|
73
|
+
id: str
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class StatusData(ResponseModel):
|
|
77
|
+
"""One GeoTab diagnostic reading from the StatusData feed.
|
|
78
|
+
|
|
79
|
+
A pure mirror of the 2,000/2,000 whole-page census: seven keys, all
|
|
80
|
+
present and non-null on every record, so all seven are required.
|
|
81
|
+
|
|
82
|
+
Attributes:
|
|
83
|
+
controller: The source controller reference — an ``{id}``
|
|
84
|
+
object or the bare ``"ControllerNoneId"`` sentinel string,
|
|
85
|
+
lifted by the shared coercion so the sentinel lands as
|
|
86
|
+
``controller__id`` (census-open
|
|
87
|
+
plain str).
|
|
88
|
+
data: The diagnostic's value — mixed int-or-float on the wire,
|
|
89
|
+
modeled float.
|
|
90
|
+
date_time: The reading's UTC instant — the endpoint's event time.
|
|
91
|
+
device: The emitting vehicle unit's reference.
|
|
92
|
+
diagnostic: The diagnostic definition's reference.
|
|
93
|
+
id: GeoTab's record id.
|
|
94
|
+
version: The record's version token — present on this active
|
|
95
|
+
feed (unlike LogRecord), mirrored as wire truth.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
99
|
+
|
|
100
|
+
controller: Annotated[
|
|
101
|
+
StatusDataControllerRef, BeforeValidator(bare_id_to_reference)
|
|
102
|
+
]
|
|
103
|
+
data: float
|
|
104
|
+
date_time: datetime
|
|
105
|
+
device: StatusDataDeviceRef
|
|
106
|
+
diagnostic: StatusDataDiagnosticRef
|
|
107
|
+
id: str
|
|
108
|
+
version: str
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/text_message.py
|
|
2
|
+
"""GeoTab TextMessage response model (``GetFeed`` on ``typeName: TextMessage``).
|
|
3
|
+
|
|
4
|
+
Written from the 2026-07-21 feed wave three SCALE census (walked at
|
|
5
|
+
scale at the probed tenant), never from docs. A TextMessage is one
|
|
6
|
+
dispatch message between the office and a vehicle.
|
|
7
|
+
|
|
8
|
+
NO per-record ``version`` key AND NO ``dateTime`` key — the append-only
|
|
9
|
+
asymmetry (FaultData/LogRecord): append-only storage is trivially
|
|
10
|
+
complete and the consumer reconciles by ``id`` alone (DESIGN §4). The
|
|
11
|
+
event-time identity is ``sent`` (the send instant, 25,000/25,000), so
|
|
12
|
+
the binding anchors ``event_time_column='sent'`` and ``sent`` is a
|
|
13
|
+
REQUIRED datetime — storage partitions on it. The feed's own
|
|
14
|
+
``toVersion`` still advances (delivered/read receipts re-emit a message
|
|
15
|
+
under a newer FEED version); those re-emissions are stored-as-emitted,
|
|
16
|
+
the feed's versioning rather than a per-record ``version`` key.
|
|
17
|
+
|
|
18
|
+
Requiredness posture (the wave-two conservative stance, DESIGN §8): the
|
|
19
|
+
census is a TENANT-SCOPED observation (25,000 records), so structural
|
|
20
|
+
requiredness is limited to the record identity — ``id`` and ``sent``
|
|
21
|
+
(the event time) — and every other field is optional EVEN where the
|
|
22
|
+
census was total. The observed arms:
|
|
23
|
+
|
|
24
|
+
- ``device`` was object-only (``{id}``) at scale; it rides the shared
|
|
25
|
+
``bare_id_to_reference`` lift (the census-scope lesson). It is
|
|
26
|
+
OPTIONAL — a text message has no required primary entity ref.
|
|
27
|
+
- ``messageContent`` is a NESTED block ``{contentType, ids}``; both
|
|
28
|
+
keys are required WITHIN the block on the nested-block-required
|
|
29
|
+
convention (200/200 nested, 25,000/25,000 present). ``ids`` is a
|
|
30
|
+
PLAIN ``list[str]`` — the elements ARE strings on the wire, so this
|
|
31
|
+
is a §9 list-of-scalar direct field, NOT the DutyStatusLog
|
|
32
|
+
``annotations`` id-object reduction (there the elements were
|
|
33
|
+
``{id}`` objects needing a strict lift).
|
|
34
|
+
- ``delivered`` and ``read`` are receipt datetimes present on
|
|
35
|
+
24,995/25,000 — optional.
|
|
36
|
+
|
|
37
|
+
``activeFrom``/``activeTo``, ``delivered``, ``read``, and ``sent`` are
|
|
38
|
+
recovered tz-aware by validation, the GeoTab sibling idiom.
|
|
39
|
+
``contentType`` and ``recipient`` are census-open strings;
|
|
40
|
+
``isDirectionToVehicle`` is a bool, ``messageSize`` an int.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
from datetime import datetime
|
|
44
|
+
from typing import Annotated
|
|
45
|
+
|
|
46
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
47
|
+
from pydantic.alias_generators import to_camel
|
|
48
|
+
|
|
49
|
+
from fleetpull.model_contract import ResponseModel
|
|
50
|
+
from fleetpull.models.geotab.shared import bare_id_to_reference
|
|
51
|
+
|
|
52
|
+
__all__: list[str] = [
|
|
53
|
+
'TextMessage',
|
|
54
|
+
'TextMessageContent',
|
|
55
|
+
'TextMessageDeviceRef',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class TextMessageDeviceRef(ResponseModel):
|
|
60
|
+
"""The message's device reference (the vehicle end of the exchange).
|
|
61
|
+
|
|
62
|
+
Census-observed as an ``{id}`` object at scale; the shared coercion
|
|
63
|
+
lifts a bare string defensively (the census-scope lesson).
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
67
|
+
|
|
68
|
+
id: str
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class TextMessageContent(ResponseModel):
|
|
72
|
+
"""The ``messageContent`` block: the message payload descriptor.
|
|
73
|
+
|
|
74
|
+
Both keys are required WITHIN the block (the nested-block-required
|
|
75
|
+
convention): a present ``messageContent`` block missing either is a
|
|
76
|
+
shape change and must fail loudly. ``ids`` is a direct
|
|
77
|
+
``list[str]`` (the elements are strings on the wire, a §9
|
|
78
|
+
list-of-scalar), NOT the ``annotations`` id-object reduction.
|
|
79
|
+
"""
|
|
80
|
+
|
|
81
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
82
|
+
|
|
83
|
+
content_type: str
|
|
84
|
+
ids: list[str]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class TextMessage(ResponseModel):
|
|
88
|
+
"""One GeoTab dispatch message from the TextMessage feed.
|
|
89
|
+
|
|
90
|
+
The wave-two conservative mirror with the append-only asymmetry (the
|
|
91
|
+
module docstring's posture): ``id`` / ``sent`` required, NO
|
|
92
|
+
per-record ``version``, everything else optional even where
|
|
93
|
+
census-total.
|
|
94
|
+
|
|
95
|
+
Attributes:
|
|
96
|
+
active_from: The message window's UTC start.
|
|
97
|
+
active_to: The message window's UTC end.
|
|
98
|
+
delivered: The delivery-receipt UTC instant (24,995/25,000).
|
|
99
|
+
device: The message's device reference (object-only at scale;
|
|
100
|
+
defensively lifted). Optional — no required primary ref.
|
|
101
|
+
id: GeoTab's record id.
|
|
102
|
+
is_direction_to_vehicle: Whether the message is office→vehicle.
|
|
103
|
+
message_content: The message payload descriptor block.
|
|
104
|
+
message_size: The message payload size.
|
|
105
|
+
read: The read-receipt UTC instant (24,995/25,000).
|
|
106
|
+
recipient: The message recipient address (census-open str).
|
|
107
|
+
sent: The send UTC instant — the endpoint's event time (the
|
|
108
|
+
event-time identity, in place of the absent ``dateTime``).
|
|
109
|
+
"""
|
|
110
|
+
|
|
111
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
112
|
+
|
|
113
|
+
active_from: datetime | None = None
|
|
114
|
+
active_to: datetime | None = None
|
|
115
|
+
delivered: datetime | None = None
|
|
116
|
+
device: Annotated[
|
|
117
|
+
TextMessageDeviceRef | None, BeforeValidator(bare_id_to_reference)
|
|
118
|
+
] = None
|
|
119
|
+
id: str
|
|
120
|
+
is_direction_to_vehicle: bool | None = None
|
|
121
|
+
message_content: TextMessageContent | None = None
|
|
122
|
+
message_size: int | None = None
|
|
123
|
+
read: datetime | None = None
|
|
124
|
+
recipient: str | None = None
|
|
125
|
+
sent: datetime
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/trip.py
|
|
2
|
+
"""GeoTab Trip response model (JSON-RPC ``Get`` on ``typeName: Trip``).
|
|
3
|
+
|
|
4
|
+
Written from captured live responses (2026-07-13 probe session), never
|
|
5
|
+
from docs. A Trip is one engine-on movement interval plus its trailing
|
|
6
|
+
stop window; the model is the union of observed fields with every field
|
|
7
|
+
optional, a pure mirror -- no value is derived or interpreted here.
|
|
8
|
+
|
|
9
|
+
Interval semantics (12 of 12 captured records):
|
|
10
|
+
``driving_duration = stop - start``; ``stop_duration = next_trip_start
|
|
11
|
+
- stop``; ``idling_duration`` measures engine-on time WITHIN the
|
|
12
|
+
post-trip stop window, never within the drive. The zero-distance
|
|
13
|
+
degenerate shape has ``start == stop``, ``driving_duration`` of zero,
|
|
14
|
+
and NO ``averageSpeed`` key at all -- absence is a shape, landing as a
|
|
15
|
+
null.
|
|
16
|
+
|
|
17
|
+
Units (delta-arithmetic confirmed against the captures):
|
|
18
|
+
|
|
19
|
+
- distances (``distance``, ``after_hours_distance``, ``work_distance``)
|
|
20
|
+
are kilometers;
|
|
21
|
+
- speeds (``average_speed``, ``maximum_speed``) are km/h;
|
|
22
|
+
- ``odometer`` is METERS (confirmed by delta arithmetic against a
|
|
23
|
+
trip's own km distance);
|
|
24
|
+
- ``engine_hours`` is SECONDS despite the name -- a captured 26.1M
|
|
25
|
+
"hours" is 7,251 real engine-hours. The value is stored verbatim;
|
|
26
|
+
renaming or converting it would break the pure-mirror rule, so the
|
|
27
|
+
trap is documented here and at every mention instead.
|
|
28
|
+
|
|
29
|
+
Durations arrive as .NET TimeSpan strings, parsed at the boundary
|
|
30
|
+
through ``GeotabTimeSpan`` (``models/geotab/shared.py``); day-prefixed
|
|
31
|
+
spans (``"4.16:41:16"``) occur whenever a stop window crosses days.
|
|
32
|
+
|
|
33
|
+
The ``driver`` reference arrives as either an object
|
|
34
|
+
(``{"id": ..., "isDriver": true}``) or the bare known-id sentinel
|
|
35
|
+
string ``"UnknownDriverId"``; the shared ``bare_id_to_reference``
|
|
36
|
+
coercion lifts any bare string to ``{"id": <string>}`` verbatim, so the
|
|
37
|
+
sentinel lands as ``driver__id`` and ``driver__is_driver`` stays null
|
|
38
|
+
on sentinel rows. ``maximum_speed`` and the ``speed_range1/2/3`` trio
|
|
39
|
+
are modeled ``float`` although every captured value is integral: they
|
|
40
|
+
are physical measurements like ``average_speed``, and JSON numbers do
|
|
41
|
+
not distinguish ``94`` from ``94.0`` (``speed_range*`` semantics are
|
|
42
|
+
unconfirmed -- captured all-zero).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from datetime import datetime
|
|
46
|
+
from typing import Annotated
|
|
47
|
+
|
|
48
|
+
from pydantic import BeforeValidator, ConfigDict
|
|
49
|
+
from pydantic.alias_generators import to_camel
|
|
50
|
+
|
|
51
|
+
from fleetpull.model_contract import ResponseModel
|
|
52
|
+
from fleetpull.models.geotab.shared import GeotabTimeSpan, bare_id_to_reference
|
|
53
|
+
|
|
54
|
+
__all__: list[str] = ['Trip', 'TripDeviceRef', 'TripDriverRef', 'TripStopPoint']
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class TripDeviceRef(ResponseModel):
|
|
58
|
+
"""The trip's device reference: the id alone."""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
61
|
+
|
|
62
|
+
id: str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TripDriverRef(ResponseModel):
|
|
66
|
+
"""The trip's driver reference.
|
|
67
|
+
|
|
68
|
+
Arrives as an object or the bare ``"UnknownDriverId"`` sentinel
|
|
69
|
+
string; the ``Trip.driver`` field's coercion lifts the bare form to
|
|
70
|
+
``{"id": <string>}``, so ``is_driver`` is null exactly on sentinel
|
|
71
|
+
rows.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
75
|
+
|
|
76
|
+
id: str | None = None
|
|
77
|
+
is_driver: bool | None = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TripStopPoint(ResponseModel):
|
|
81
|
+
"""The trip's stop coordinate: ``x`` longitude, ``y`` latitude."""
|
|
82
|
+
|
|
83
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
84
|
+
|
|
85
|
+
x: float | None = None
|
|
86
|
+
y: float | None = None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class Trip(ResponseModel):
|
|
90
|
+
"""One GeoTab Trip: a movement interval and its trailing stop window.
|
|
91
|
+
|
|
92
|
+
A pure mirror of the union of captured fields, everything optional.
|
|
93
|
+
Groups below follow the captured record layout: identity, the
|
|
94
|
+
interval, durations, distances and speeds, counters, and flags. The
|
|
95
|
+
unit traps (``engine_hours`` is seconds, ``odometer`` meters) are
|
|
96
|
+
documented in the module docstring.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
id: GeoTab's trip id -- the seek-paging sort key.
|
|
100
|
+
version: The record's version token (ids and versions share one
|
|
101
|
+
counter space).
|
|
102
|
+
device: The vehicle reference.
|
|
103
|
+
driver: The driver reference; the bare ``"UnknownDriverId"``
|
|
104
|
+
sentinel lands as ``driver.id`` verbatim.
|
|
105
|
+
start: Trip start (UTC) -- the endpoint's event time.
|
|
106
|
+
stop: Trip end (UTC); equals ``start`` on the zero-distance
|
|
107
|
+
degenerate shape.
|
|
108
|
+
next_trip_start: The following trip's start; bounds this trip's
|
|
109
|
+
stop window.
|
|
110
|
+
engine_hours: Cumulative engine SECONDS despite the name (the
|
|
111
|
+
module-docstring trap), stored verbatim.
|
|
112
|
+
odometer: Cumulative odometer in METERS, stored verbatim.
|
|
113
|
+
average_speed: Mean speed in km/h; the key is absent on
|
|
114
|
+
zero-distance trips (lands null).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
118
|
+
|
|
119
|
+
# Identity.
|
|
120
|
+
id: str | None = None
|
|
121
|
+
version: str | None = None
|
|
122
|
+
device: TripDeviceRef | None = None
|
|
123
|
+
driver: Annotated[TripDriverRef | None, BeforeValidator(bare_id_to_reference)] = (
|
|
124
|
+
None
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
# The interval.
|
|
128
|
+
start: datetime | None = None
|
|
129
|
+
stop: datetime | None = None
|
|
130
|
+
next_trip_start: datetime | None = None
|
|
131
|
+
|
|
132
|
+
# Durations (.NET TimeSpan strings on the wire).
|
|
133
|
+
driving_duration: GeotabTimeSpan = None
|
|
134
|
+
stop_duration: GeotabTimeSpan = None
|
|
135
|
+
idling_duration: GeotabTimeSpan = None
|
|
136
|
+
after_hours_driving_duration: GeotabTimeSpan = None
|
|
137
|
+
after_hours_stop_duration: GeotabTimeSpan = None
|
|
138
|
+
work_driving_duration: GeotabTimeSpan = None
|
|
139
|
+
work_stop_duration: GeotabTimeSpan = None
|
|
140
|
+
speed_range1_duration: GeotabTimeSpan = None
|
|
141
|
+
speed_range2_duration: GeotabTimeSpan = None
|
|
142
|
+
speed_range3_duration: GeotabTimeSpan = None
|
|
143
|
+
|
|
144
|
+
# Distances (km) and speeds (km/h).
|
|
145
|
+
distance: float | None = None
|
|
146
|
+
after_hours_distance: float | None = None
|
|
147
|
+
work_distance: float | None = None
|
|
148
|
+
average_speed: float | None = None
|
|
149
|
+
maximum_speed: float | None = None
|
|
150
|
+
speed_range1: float | None = None
|
|
151
|
+
speed_range2: float | None = None
|
|
152
|
+
speed_range3: float | None = None
|
|
153
|
+
|
|
154
|
+
# Cumulative counters (the unit traps: seconds and meters).
|
|
155
|
+
engine_hours: float | None = None
|
|
156
|
+
odometer: float | None = None
|
|
157
|
+
|
|
158
|
+
# Flags and the stop coordinate.
|
|
159
|
+
after_hours_start: bool | None = None
|
|
160
|
+
after_hours_end: bool | None = None
|
|
161
|
+
is_seat_belt_off: bool | None = None
|
|
162
|
+
stop_point: TripStopPoint | None = None
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# src/fleetpull/models/geotab/user.py
|
|
2
|
+
"""GeoTab User response model (JSON-RPC ``Get`` on ``typeName: User``).
|
|
3
|
+
|
|
4
|
+
Written from captured live responses (2026-07-16 probe session: a
|
|
5
|
+
full-population sweep of all 157 accounts plus seven captured record
|
|
6
|
+
variants), never from docs. Drivers ARE users in GeoTab -- ``isDriver``
|
|
7
|
+
splits the population (129/157 captured) and the driver-only key block
|
|
8
|
+
(``licenseNumber``, ``licenseProvince``, ``viewDriversOwnDataOnly``)
|
|
9
|
+
is ABSENT, not null, on non-driver accounts; the sweep observed no null
|
|
10
|
+
value and no type variance on any key, so optionality here is
|
|
11
|
+
absence-shaped. Fields the sweep proved on every record are required;
|
|
12
|
+
the partial-presence fields are optional (presence counts in the
|
|
13
|
+
attribute docs).
|
|
14
|
+
|
|
15
|
+
Excluded fields (``extra='ignore'`` makes exclusion exactly "don't
|
|
16
|
+
model it"):
|
|
17
|
+
|
|
18
|
+
- ``activeDashboardReports``, ``activeDefaultDashboards``,
|
|
19
|
+
``availableDashboardReports``, ``bookmarks``, ``cannedResponseOptions``,
|
|
20
|
+
``companyGroups``, ``driverGroups``, ``jobPriorities``, ``keys``,
|
|
21
|
+
``mapViews``, ``mediaFiles``, ``privateUserGroups``, ``reportGroups``,
|
|
22
|
+
``securityGroups`` -- lists; the records layer's schema derivation
|
|
23
|
+
(DESIGN section 9) supports scalars, enums, ``list[scalar]``, and
|
|
24
|
+
nested models only, and these are UI/grouping plumbing whose struct
|
|
25
|
+
shapes have no honest column until the ``list[nested model]``
|
|
26
|
+
derivation vertical lands (the Device exclusion precedent).
|
|
27
|
+
- ``iAMMetadata`` -- identity-provider plumbing (an IAM GUID, a
|
|
28
|
+
connection name, two provisioning flags), present on only 42/157
|
|
29
|
+
swept records.
|
|
30
|
+
|
|
31
|
+
Every value mirrors verbatim, empty strings included -- the model
|
|
32
|
+
preserves ``""`` faithfully from the wire, and empty strings normalize
|
|
33
|
+
to null once, at the DataFrame boundary (DESIGN section 9). Sentinels
|
|
34
|
+
likewise: ``activeTo`` of ``2050-01-01`` is GeoTab's still-active
|
|
35
|
+
sentinel, and ``hosRuleSet`` carries the literal string ``"None"`` on
|
|
36
|
+
non-driving accounts -- the provider's vocabulary, never interpreted.
|
|
37
|
+
|
|
38
|
+
Wire keys are camelCase; fields are snake_case via the ``to_camel``
|
|
39
|
+
alias generator. The five acronym keys the generator cannot produce
|
|
40
|
+
(``acceptedEULA``, ``isEULAAccepted``, ``isExemptHOSEnabled``,
|
|
41
|
+
``maxPCDistancePerDay``, ``wifiEULA``) carry explicit alias overrides,
|
|
42
|
+
each pinned against a captured value in the model tests (the Device
|
|
43
|
+
acronym-trap precedent).
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
from datetime import datetime
|
|
47
|
+
|
|
48
|
+
from pydantic import ConfigDict, Field
|
|
49
|
+
from pydantic.alias_generators import to_camel
|
|
50
|
+
|
|
51
|
+
from fleetpull.model_contract import ResponseModel
|
|
52
|
+
|
|
53
|
+
__all__: list[str] = ['User', 'UserAccessGroupFilterRef']
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class UserAccessGroupFilterRef(ResponseModel):
|
|
57
|
+
"""The ``accessGroupFilter`` reference: a data-scope filter by id.
|
|
58
|
+
|
|
59
|
+
Observed on exactly one of the 157 swept records (2026-07-16) -- an
|
|
60
|
+
account whose visibility is restricted to an access group. The id is
|
|
61
|
+
the ``a``-prefixed GUID-like form (the ExceptionEvent id shape), a
|
|
62
|
+
pure reference mirrored as-is.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
66
|
+
|
|
67
|
+
id: str
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class User(ResponseModel):
|
|
71
|
+
"""One GeoTab User entity: a console account, driver or otherwise.
|
|
72
|
+
|
|
73
|
+
A pure mirror of the captured scalar fields. Field semantics are
|
|
74
|
+
GeoTab's; no value is derived or interpreted here. Groups below
|
|
75
|
+
follow the captured record layout: identity and lifecycle, person
|
|
76
|
+
and contact, the driver-only block, company/authority identity,
|
|
77
|
+
locale and display preferences, HOS and driving flags, and the
|
|
78
|
+
notification/UI flags.
|
|
79
|
+
|
|
80
|
+
Attributes:
|
|
81
|
+
id: GeoTab's user id -- the seek-paging sort key (hex-suffixed
|
|
82
|
+
string, ascending; the id space trip ``driver`` refs point
|
|
83
|
+
into).
|
|
84
|
+
name: The login identifier -- an email address on most captured
|
|
85
|
+
accounts, a bare username on one.
|
|
86
|
+
active_from: Start of the account's active window (UTC).
|
|
87
|
+
active_to: End of the active window; ``2050-01-01`` is GeoTab's
|
|
88
|
+
still-active sentinel, stored as-is.
|
|
89
|
+
last_access_date: Last console/app access (UTC); absent on one
|
|
90
|
+
never-accessed swept account (156/157).
|
|
91
|
+
is_driver: Whether the account is a driver -- the key whose
|
|
92
|
+
value predicts the driver-only block's presence.
|
|
93
|
+
license_number: Driver's license number; driver-only (129/157).
|
|
94
|
+
license_province: Driver's license state/province; driver-only
|
|
95
|
+
(129/157).
|
|
96
|
+
view_drivers_own_data_only: Driver data-visibility restriction;
|
|
97
|
+
driver-only (129/157).
|
|
98
|
+
access_group_filter: Data-scope filter reference; observed on
|
|
99
|
+
one swept record (1/157).
|
|
100
|
+
max_pc_distance_per_day: Personal-conveyance distance cap;
|
|
101
|
+
``0`` captured everywhere it appears (126/157, not aligned
|
|
102
|
+
with the driver split).
|
|
103
|
+
hos_rule_set: HOS ruleset name; the literal string ``"None"``
|
|
104
|
+
on non-driving accounts, mirrored verbatim.
|
|
105
|
+
carrier_number: Motor-carrier number; captured populated on
|
|
106
|
+
driver accounts, ``""`` elsewhere, mirrored verbatim.
|
|
107
|
+
"""
|
|
108
|
+
|
|
109
|
+
model_config = ConfigDict(alias_generator=to_camel)
|
|
110
|
+
|
|
111
|
+
# Identity and lifecycle.
|
|
112
|
+
id: str
|
|
113
|
+
name: str
|
|
114
|
+
active_from: datetime
|
|
115
|
+
active_to: datetime
|
|
116
|
+
last_access_date: datetime | None = None
|
|
117
|
+
is_auto_added: bool
|
|
118
|
+
user_authentication_type: str
|
|
119
|
+
change_password: bool
|
|
120
|
+
access_group_filter: UserAccessGroupFilterRef | None = None
|
|
121
|
+
|
|
122
|
+
# Person and contact.
|
|
123
|
+
first_name: str
|
|
124
|
+
last_name: str
|
|
125
|
+
designation: str
|
|
126
|
+
employee_no: str
|
|
127
|
+
phone_number: str
|
|
128
|
+
phone_number_extension: str
|
|
129
|
+
comment: str
|
|
130
|
+
|
|
131
|
+
# Driver-only block (absent, not null, on non-driver accounts).
|
|
132
|
+
is_driver: bool
|
|
133
|
+
license_number: str | None = None
|
|
134
|
+
license_province: str | None = None
|
|
135
|
+
view_drivers_own_data_only: bool | None = None
|
|
136
|
+
|
|
137
|
+
# Company / authority identity.
|
|
138
|
+
company_name: str
|
|
139
|
+
company_address: str
|
|
140
|
+
authority_name: str
|
|
141
|
+
authority_address: str
|
|
142
|
+
carrier_number: str
|
|
143
|
+
|
|
144
|
+
# Locale and display preferences.
|
|
145
|
+
language: str
|
|
146
|
+
country_code: str
|
|
147
|
+
time_zone_id: str
|
|
148
|
+
date_format: str
|
|
149
|
+
first_day_of_week: str
|
|
150
|
+
display_currency: str
|
|
151
|
+
is_metric: bool
|
|
152
|
+
fuel_economy_unit: str
|
|
153
|
+
electric_energy_economy_unit: str
|
|
154
|
+
default_page: str
|
|
155
|
+
default_map_engine: str
|
|
156
|
+
default_google_map_style: str
|
|
157
|
+
default_here_map_style: str
|
|
158
|
+
default_open_street_map_style: str
|
|
159
|
+
zone_display_mode: str
|
|
160
|
+
feature_preview: str
|
|
161
|
+
|
|
162
|
+
# HOS and driving flags.
|
|
163
|
+
hos_rule_set: str
|
|
164
|
+
is_exempt_hos_enabled: bool = Field(alias='isExemptHOSEnabled')
|
|
165
|
+
is_yard_move_enabled: bool
|
|
166
|
+
is_personal_conveyance_enabled: bool
|
|
167
|
+
is_adverse_driving_enabled: bool
|
|
168
|
+
max_pc_distance_per_day: int | None = Field(
|
|
169
|
+
default=None, alias='maxPCDistancePerDay'
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# EULA state.
|
|
173
|
+
is_eula_accepted: bool = Field(alias='isEULAAccepted')
|
|
174
|
+
accepted_eula: int = Field(alias='acceptedEULA')
|
|
175
|
+
wifi_eula: int = Field(alias='wifiEULA')
|
|
176
|
+
|
|
177
|
+
# Notification and UI flags.
|
|
178
|
+
is_email_report_enabled: bool
|
|
179
|
+
is_maintenance_notification_enabled: bool
|
|
180
|
+
is_service_disruption_notifications_enabled: bool
|
|
181
|
+
sms_notifications_opt_in: bool
|
|
182
|
+
whats_app_notifications_opt_in: bool
|
|
183
|
+
is_news_enabled: bool
|
|
184
|
+
is_labs_enabled: bool
|
|
185
|
+
is_ace_disclaimer_disabled: bool
|
|
186
|
+
show_click_once_warning: bool
|
|
187
|
+
show_rate_this_app: bool
|