48 Commits
Author SHA1 Message Date
ClaudeandJames Woglom ddeaa79ded Fix #156: handle LidMalfunctionActivated in ProcessAlarm; add typed guardrail
ProcessAlarm.skip_event() read event.alarmId on every EventClass.ALARM event,
but LidMalfunctionActivated (a sibling of LidAlarmActivated in that class) has
no alarmId, so a malfunction alarm crashed the sync with AttributeError.

- Narrow with isinstance before reading alarmId; malfunction events now upload
  as "Malfunction" as intended, and sync continues.
- Type the alarm handlers against an explicit AlarmEvent union and add an
  assert_never exhaustiveness guard, so a type checker rejects unguarded
  subtype attribute access and flags any newly added ALARM event type.
- Fix a latent None-leak: alarm_to_nsentry now always returns a dict.
- Add mypy as a gradual-typing beachhead (setup.cfg [mypy], CI step, Pipfile
  typecheck script), scoped to process_alarm.py. This configuration fails on
  exactly the #156 class of bug.
- Add regression tests: malfunction processing, mixed alarm batches, the event
  shape, and an AlarmEvent/EventClass.ALARM sync guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LpMTd5LzkqFLZTUKd3H8em
2026-07-20 20:49:43 -04:00
James WoglomandClaude Opus 4.8 c95fe424ab Fix flaky autoupdate tests: use constant mocked clock
Patching autoupdate.time.time patches the global time.time, which
logging calls internally per record; a finite side_effect list gets
exhausted and raises StopIteration on Python 3.11. Use return_value.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 20:46:23 -04:00
James Woglom 4bae860212 tests/sync/tandemsource/test_autoupdate.py 2026-07-20 20:40:02 -04:00
xannasavinandJames Woglom 823bb785ba Harden autoupdate against transient errors instead of exiting
get() retries only HTTP 401 and 500, so any other API error propagates out of
the autoupdate loop and exits the process. When Tandem retired the
reportsfacade endpoints and pumpeventmetadata began returning 404 (#146), a
container with a restart policy would crash-loop. That is the worst possible
response to an API outage: the credentials cache dies with the process, so
every restart performs a full login against sso.tandemdiabetes.com. In my EU
deployment that was a fresh login roughly every two minutes for hours from a
single IP, which seems a good way to earn a WAF ban while already broken.

Transient network errors (DNS failures, timeouts, mid-stream disconnects,
urllib3 retry-budget exhaustion) have the same problem.

This keeps both failure families inside the loop and backs off exponentially:
30s doubling to a cap of AUTOUPDATE_DEFAULT_SLEEP_SECONDS (300s default),
reset on any successful poll. The cap reuses the existing poll interval, so a
failing API is never contacted more often than a healthy one. After three
consecutive failures the log escalates from WARNING to ERROR.

Staying alive forever would make a real outage silent on deployments whose
only alarm is the container dying, so after AUTOUPDATE_API_FAILURE_MINUTES
(default 45) of unbroken failure the process gives up and exits non-zero.
That is roughly one restart per hour during a genuine outage instead of one
every two minutes, while short blips stay silent. Set 0 to disable.

This is deliberately not gated on AUTOUPDATE_RESTART_ON_FAILURE, which covers
the pump-not-uploading watchdog where restarting achieves nothing (as the
existing TODO notes) and which many users therefore disable. An unreachable
API is a different failure and gets its own knob.

ApiLoginException stays fatal: bad credentials are not transient, and
retrying them in-process would hammer the login endpoint with attempts that
cannot succeed.

Also included:

- A defensive clamp so a negative rolling-average entry can never reach
  time.sleep() and crash with ValueError.
- Tests covering the backoff sequence, reset-on-success, the sustained-failure
  exit, the opt-out, and that login failures and programming errors still
  propagate.
- README documentation for all nine AUTOUPDATE_* variables, none of which were
  documented outside secret.py.
2026-07-20 19:12:24 -04:00
James Woglom 6f987989b8 Add real-data unit tests for cartridge, basal, basal suspend/resume, and CGM alert handlers
One test file per handler, built from real captured pump-log events (verbatim,
deviceAssignmentId redacted) embedded inline. Assert exact Nightscout output:
- cartridge: site-change reason strings (insulinVolume fill, -1 tubing sentinel, %.1f cannula)
- basal: rate scaling, commandedRateSource reason, inter-event + capped durations (locks total_seconds), zero-rate suspend
- basal suspension/resume: exact treatments + dedup
- cgm alert: Dexcom-prefixed dalertId names, out-of-range + unmapped skips, cleared/ack not synced
2026-07-01 07:15:58 +00:00
James Woglom 1500438c1b Add end-to-end integration tests for the Tandem Source -> Nightscout flow
Drive the real ProcessTimeRange + process_* handlers and the real
TandemSourceApi / NightscoutApi clients, mocking only the HTTP transport
(base_session / requests). Tandem responses are a small representative slice of
real captured pump-log events (verbatim, deviceAssignmentId redacted); tests
assert the exact Nightscout operations produced: full multi-type sync, api-secret
header, resume-alarm skip, dedup, pretend mode, and empty window.
2026-07-01 07:09:43 +00:00
James Woglom ee7384cc31 Sync events.json to upstream schema; preserve camelCase attribute names
Copy the exact eventSchema.json to events.json (camelCase keys) and fix
build_events.py's fieldNameFormat to preserve camelCase instead of collapsing
it via .title() (which turned schema keys like commandedRate into commandedrate).
Regenerate events.py so attributes are clean camelCase (commandedRate,
currentGlucoseDisplayValue, egvTimeStamp, bolusId, ...); acronyms follow the
schema's own casing (bg, iob, rssi). Update the battery transform and all
attribute references in the process handlers and tests to match.
2026-07-01 06:50:30 +00:00
James Woglom e5304dc605 Add G6 JSON-path tests to ProcessCGMReading from real early-2023 data
Real Dexcom G6 (LidCgmDataGxb, eventCode 256) pump-logs JSON readings captured
from a live t:slim X2 account, exercising the production Events -> ProcessCGMReading
path (the existing G6 coverage used only the binary decoder). Covers steady/rising/
falling/high readings plus a SpecialLow (raw display 0 -> LOW sentinel 39).
2026-07-01 06:27:57 +00:00
James Woglom cb045796e5 Add ProcessBolus tests from real captured pump-log data
Covers regular, extended (combo), and canceled boluses using complete event
groups captured verbatim from the live Tandem Source API (all messages of each
bolusId; deviceAssignmentId redacted), embedded inline as test class variables.
2026-07-01 06:22:06 +00:00
James Woglom ddca912eea Remove PumpMetadata transform; callers use raw BffPump, normalize at call sites
Delete the PumpMetadata TypedDict, _bff_pump_to_metadata and pump_metadata
transform layer. Callers now consume the raw BffPump dicts from get_pumper()
directly. The pump-local -> UTC date conversion is kept as a shared
naive_local_to_utc() helper, applied only at the call sites that compare a
pump date against real UTC (choose_device staleness/selection, autoupdate
timing).

Also expand pump_events JSON parse coverage: drive bolus (20), basal (279),
CGM (399) and alarm (5) events through pump_events(), asserting decoded
fields and enum members (previously only eventCode 16 was covered).
2026-07-01 04:22:57 +00:00
James Woglom a977bd65f9 Report CGM out-of-range readings as LOW/HIGH sentinels
The bff pump-logs endpoint gives glucoseValueStatus + a raw display value; a
below/above-range reading (e.g. status SpecialLow with displayValue 38) is a
boundary indicator, not a measurement. Mirror the Tandem Source frontend
(CgmBuilder.determineGlucoseValue): map SpecialLow/precise<40 -> 39 and
SpecialHigh/precise>400 -> 401. Each sensor (G7/G6/FSL2/FSL3) is resolved
against its own glucoseValueStatus enum members rather than assuming the enums
are consistent across sensor types.

Also widen the real-JSON CGM tests to span glucose 38..361 (incl. the LOW
sentinel) and add a ProcessTimeRange basal JSON integration test.
2026-07-01 03:03:26 +00:00
James Woglom 69637015e2 Fold pump-logs JSON handling into Event()/Events()
Event(x) and Events(x) now accept either a raw binary event/stream or a
pump-logs JSON event dict / iterable of dicts, dispatching on input type,
replacing the separate Event_from_json/Events_from_json functions. Point
pump_events() and all tests at the unified entry points, and add real-JSON
sync tests for CGM readings, user-mode sleep/exercise, and alarms alongside
the existing binary-fixture tests.
2026-07-01 02:52:16 +00:00
James Woglom d1d67b5042 Add real-JSON CGM reading tests via the production path
Exercise Events_from_json -> ProcessCGMReading with real LID_CGM_DATA_G7
pump-logs events captured from a live account (device id redacted), asserting
sgv, egv-derived dateString, pump_event_id, and last-upload skip behavior.
The existing binary-fixture tests are kept alongside.
2026-07-01 02:45:18 +00:00
James Woglom 7516226bd3 Verify UpdateProfiles builds Nightscout profiles from the new settings
UpdateProfiles already sources settings.details via pump_metadata(); add
end-to-end compare_profiles tests over a real-shape PumpSettings confirming
the per-segment basal/carbratio/sens schedule, flat-cgm target_low/high, and
defaultProfile are translated correctly, and that a matching Nightscout
profile yields no change.
2026-07-01 02:40:04 +00:00
James Woglom c5854ac17c Degrade DEVICE_STATUS gracefully when event 81 is missing or partial
Event 81 (LidDailyBasal, battery) is not in Tandem's default id list; the
pump-logs endpoint may not return it. DEVICE_STATUS already fetches all
event types, and no-daily-basal-event already returns nothing — add a test
pinning that, plus a guard so an event 81 that arrives without battery
fields is skipped with a warning instead of raising on the percent math.
2026-07-01 02:29:57 +00:00
James Woglom b60bfb94ee Add Phase-2 backfill tests (update_profiles, adapter, event ids)
- tests/sync/tandemsource/test_update_profiles.py: settings sourcing from
  pump_metadata() — matching deviceId with settings reaches
  PumpSettings.from_dict (proven via sentinel, using the real BFF
  settings.details shape); settings=None / no-match / empty return False
  without parsing.
- test_tandemsource.py: adapter edge cases (availableDataRange/settings
  keys absent, missing required key raises KeyError, Mobi Control-IQ+
  passthrough) and DEFAULT_EVENT_IDS regression (55 ids, no dupes,
  477/480/486 present).

Fixtures are inline and trimmed from the real captured responses.
2026-07-01 01:26:22 +00:00
James Woglom 4c548203a5 Migrate metadata consumers to BFF pump_metadata()
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.
2026-07-01 01:18:52 +00:00
Beshoy GirgisandJames Woglom 594ee19450 Add Freestyle Libre 3 (FSL3) CGM sensor support
Implement FSL3 sensor integration alongside G6, G7, and FSL2 with expanded
CGM alert codes, improved datetime handling, and test coverage.

**FSL3 Event Integration:**
- Add FSL3 events to CGM_READING, _CGM_JOIN, and _CGM_STOP event classes
- Enable unified processing of FSL3 alongside existing sensor variants:
  - Event 480: LID_CGM_DATA_FSL3
  - Event 477: LID_CGM_JOIN_SESSION_FSL3
  - Event 486: LID_CGM_STOP_SESSION_FSL3

**CGM Alert Enumeration:**
- Expand CGM alert codes from 8 to 18 mapped codes
- Add verified alert codes: 1, 2, 3, 8, 12, 22, 25, 45, 46, 48
- Rename alert 51 to CONTROL_IQ_LOW (was DEFAULT_ALERT_51)
- Add alert descriptions based on pump history verification

**Code Quality Improvements:**
- Fix duplicate enum keys in events.json (Sensor Type codes 12, 13)
- Add logging infrastructure to generic.py for event diagnostics
- Add conftest.py test configuration

**DateTime and API Improvements:**
- Fix format_datetime() for proper UTC conversion with Z suffix
- Simplify Nightscout API methods by removing redundant retry logic
- Improve error reporting in last_uploaded_entry() and last_uploaded_bg_entry()

**Test Coverage:**
- Add 3 FSL3 test cases with real pump data
- Test single reading processing, multiple readings, and JOIN event parsing
- All 105 tests passing (102 existing + 3 new FSL3 tests)
2026-03-27 11:08:23 -07:00
James Woglom 795361c6ad timezone tests 2025-12-14 23:58:35 -05:00
James Woglom 15160537f5 add process_cgm_reading tests 2025-12-14 23:52:55 -05:00
James Woglom e848a8482d add new process test for tz change 2025-11-18 14:00:54 -05:00
James Woglom d51080f6d0 Fix profile test 2025-06-07 23:24:41 -04:00
James Woglom b32415aaac Delete dead non-tandem source tests 2025-06-07 23:21:38 -04:00
James Woglom 07aaea9e1c fix tests 2024-12-30 22:05:11 -05:00
James Woglom c7ade49eea devicestatus: tune nightscout output to look more like Loops uploader 2024-12-13 02:09:36 -05:00
James Woglom d1341e09c0 devicestatus: set pump.battery.percent 2024-12-12 22:20:13 -05:00
James Woglom 9493b6546f process_alarm exclude resumepumpalarm and test 2024-12-12 00:23:17 -05:00
James Woglom 24a635ca21 add process_device_status and process_user_mode tests 2024-12-12 00:09:28 -05:00
James Woglom bb3094869d fix old tests (will be removed/updated) 2024-09-30 00:14:16 -04:00
James Woglom 2bfc5a7860 test pump events 2024-05-23 00:44:06 -04:00
James Woglom 4409b78890 add completed extended bolus test 2023-02-04 00:09:35 -05:00
James Woglom ee85226c2f fix test failure 2023-01-16 19:54:16 -05:00
James Woglom b9fae36dda ensure permutations of the same number in different types are considered the same NS profile 2023-01-16 17:35:31 -05:00
James Woglom dd9a9ea72c tests for profile synchronization 2023-01-16 16:00:50 -05:00
James Woglom a18c2f3d17 move Bolus internal state from dictionary to domain object 2022-08-09 22:54:51 -04:00
James Woglom 118ee236ba test_process: add WS2 activity event process tests 2021-12-04 21:37:34 -05:00
James Woglom 0a9dd8732f bug: use deepcopy to avoid mutating the base dictionary 2021-12-04 21:20:13 -05:00
James Woglom 9635b0bd0f sync/basal: process manual suspension events as manual 0u/hr basal, fixes #17 2021-10-28 01:38:11 -04:00
James Woglom f5ef34174f in-progress: support BOLUS_BG and CGM features.
BOLUS_BG will add BG readings which are associated with boluses
on the pump into the Nightscout treatment object. It will determine
whether the BG reading was automatically filled via the Dexcom
connection on the pump or was manually entered by seeing if the
BG reading matches the current CGM reading as known to the pump
at that time. Support for this is nearly complete.

CGM will add Dexcom CGM readings from the pump to Nightscout
as SGV (sensor glucose value) entries. This should only be
used in a situation where xDrip is not used and the pump
connection to the CGM will be the only source of CGM
data to Nightscout. This requires additional testing before
it should be considered ready.

Both options are hidden behind the ENABLE_TESTING_MODES=true
environment variable. Run tconnectsync with this environment
variable and BOLUS_BG and CGM will be usable with the
--features argument.
2021-10-24 00:36:48 -04:00
James Woglom c33a674bad iob: add e2e tests for IOB behavior 2021-10-14 02:02:57 -04:00
James Woglom 3be0289177 bolus: add tests for partial entries, propagate requested_insulin 2021-10-14 02:02:57 -04:00
James Woglom 87f531cbb3 tests: add iob test, fix sleep increment in ws2 tests 2021-10-14 02:02:57 -04:00
James Woglom a3c7aa69a9 tests: add bolus parsing tests 2021-04-21 01:38:53 -04:00
James Woglom 093a7f0627 tests: add bolus tests 2021-04-21 01:27:13 -04:00
James Woglom 4c6e05b2c1 tests: add Nightscout fake, tests for basal processing 2021-04-21 00:44:50 -04:00
James Woglom 209292dd21 tests/sync: complete basal ciq process test 2021-03-18 02:07:39 -04:00
James Woglom 1a9fb36b88 refactor: move tconnect and nightscout API parsers into subpackage 2021-03-18 01:53:03 -04:00
James Woglom 2e0120fdfc tests: add first test for basal processing 2021-03-18 01:42:44 -04:00