Switch choose_device, check, update_profiles, process.py and cli_helpers
from the old pump_event_metadata() (reportsfacade) to the normalized
pump_metadata() (BFF): tconnectDeviceId -> deviceId (UUID), and
lastUpload.settings -> settings.
Handle the BFF returning never-uploaded pumps (maxDateWithEvents=None):
skip them in the most-recent auto-select and fall back to the first
pump; raise a clear NoDevicesFound on an empty account instead of an
opaque TypeError. Clean stale comments/docstrings in tandemsource.py.
Tests: add tests/sync/tandemsource/test_choose_device.py (11 cases:
explicit/auto/never-uploaded/empty/InvalidSerialNumber/stale-warning)
and tests/api/test_tandemsource.py (pump_metadata adapter mapping),
and update the test_process fixture to the new keys.
Introduce a typed PumpMetadata TypedDict and pump_metadata()/
_bff_pump_to_metadata() that adapt get_pumper().pumps[] into the stable
shape the sync code needs. Maps the new BFF fields to normalized names:
assignmentId -> deviceId (UUID), maxDateOfEvents -> maxDateWithEvents,
availableDataRange.start -> minDateWithEvents, settings.details ->
settings. Verified against the captured account response (7 pumps).
Additive only; consumers are migrated off pump_event_metadata() in a
follow-up commit.
Add TandemSourceApi.get_pumper() for GET api/reports/bff/pumper/{pumperId},
the new device-list endpoint that replaces pumpeventmetadata. Adds
strictly-typed TypedDicts (BffPumper, BffPump, AvailableDataRange,
PumpSettingsEnvelope) derived from the captured account response; verified
they exactly cover the real JSON keys. pumps[].assignmentId is the UUID
device id for the pump-logs endpoint; settings.details (typed as dict for
now) will be modeled by PumpSettings in a later step. Nullable/absent
fields use total=False + Optional.
After the client_id change, extract_jwt still validates the id_token
audience against TDC_OIDC_CLIENT_ID (the OIDC-standard case). But since
the token exchange wasn't captured, we can't be certain Tandem sets
aud=client_id on the id_token. If it doesn't, fall back to decoding with
verify_aud disabled (signature + issuer still verified) and log a
warning, rather than failing login outright.
The Tandem Source web app now authenticates with client_id
0oa4wnbvtladeyVZX4h7 (US); the old 0oa27ho9tpZE9Arjy4h7 no longer
appears in the current build. This value is used both for the OIDC
authorize/token requests and the id_token audience check, which stay
consistent. EU client_id left unchanged (no EU capture to verify).
Match the Tandem Source web app's getLogIDList() as observed in the live
GET api/reports/bff/pump-logs request (from the captured HAR). Adds the
FSL3 event ids 477 (join), 480 (data), 486 (stop); reorders to match the
frontend. No removals (was 52 ids, now 55).
Annotate the 11 event processors, the ProcessTimeRange orchestrator, and
UpdateProfiles. The shared processor interface is now typed:
__init__(tconnect, nightscout, tconnect_device_id, pretend, features),
enabled() -> bool, process(events, time_start, time_end) -> List[dict],
write(ns_entries) -> int. Converter helpers return Optional[dict] since
they fall through to None on type mismatch.
ProcessTimeRange.process() returns Tuple[int, Optional[int]] and its
tconnectDevice param reuses the PumpEventMetadata TypedDict. Client
params (TConnectApi/NightscoutApi/BaseEvent) use TYPE_CHECKING-guarded
imports with string forward refs to avoid import cycles at runtime.
Annotate NightscoutApi methods and the module-level date helpers. Adds a
DateLike alias (str | datetime | arrow.Arrow) for the timestamp/filter
params. Writers return None; the last_uploaded_* getters return
Optional[dict]; api_status/current_profile return dict.
Response shapes are kept as loose dict/Optional[dict] rather than
TypedDicts since the Nightscout API shape varies across versions; only
inputs (which we control) are tightly typed.
Annotate method signatures and dynamic JSON response types. Adds
TypedDicts for the responses whose shapes are confirmed from call sites
and logs:
- PumpEventMetadata (+ nested LastUpload) for pump_event_metadata()
- JwtClaims for the decoded id_token stored on jwtData; pumperId and
accountId are UUID strings, not ints
pump_events_raw() returns a base64 str (not Any). pumper_info() stays
Any since it has no callers and its shape is never logged. TypedDict is
imported with a typing_extensions fallback for Python 3.7.
api_headers() hardcoded Origin/Referer to tconnect.tandemdiabetes.com,
but requests target SOURCE_URL (source.tandemdiabetes.com /
source.eu.tandemdiabetes.com). The WAF enforces same-origin and
returned HTTP 403 ("The request is blocked"). Derive Origin/Referer
from SOURCE_URL so both US and EU regions match.