Skip to contents

spdgt.telem 0.4.10

  • The package now declares a telemetry address for each region, so it works in Canada again. .onLoad() registered a single unnamed URL, which spdgt.auth 2.0.0 accepts only in the "us" region — and only with a warning. Anywhere else it is an error, because the address for that region is genuinely unknown: api_url("telemetry") under SPDGT_REGION=ca aborted with “has no address for region”, taking every read and write with it. us resolves to marks.spdgt.com and ca to marks.ca.spdgt.com, and spdgt.auth picks between them when a URL is read rather than when it is registered, so setting the region before or after library(spdgt.telem) works either way.

  • Requires spdgt.auth 2.0.0. The named-address form does not exist before it.

  • Worth knowing for anyone auditing the rest of the estate: a package registering "counts" with the old single-URL form is unaffected, because spdgt.auth registers that API itself with both regions and declines the stale re-registration. Only the APIs spdgt.auth does not own — telemetry here, and the Cloud Run services elsewhere — actually break.

spdgt.telem 0.4.9

  • No user-facing change. The documentation site was left a version behind: 0.4.6, 0.4.7 and 0.4.8 were merged within half a minute of each other, their builds raced to force-push the same branch, and the site that survived was 0.4.7’s. Nexus held all three throughout. This release exists so the site rebuilds, and records the trap in CLAUDE.md — re-running a build cannot fix it, because the Nexus upload runs first and refuses an already-published version before the documentation step is reached.

  • CLAUDE.md no longer lists which versions the public Nexus holds. It went stale four times in three weeks, two lines above the query that answers the question properly.

spdgt.telem 0.4.8

  • Every telem_update_*() now validates before sending and refuses to write from a session with no project, as the create functions already did. An update previously went out unchecked: a misspelled enum or an over-long string came back as a 422 from the server rather than failing here, and four of the eleven did not even confirm an id was present.

  • telem_validate() gains an action argument. action = "update" relaxes the required fields to optional, because an update sends only what it is changing — the API says as much itself, marking every field sometimes on its update requests where the create request marks it required. A present field stays present, so state is still expected on a device update. Types, limits and enumerations are enforced identically either way, and id is required on an update and never reported as an unknown column.

  • Two invalid values in the package’s own tests were caught by this and corrected: a capture method of "Darting", which is not one, and an event type of "Drop-off", which is the right event in the wrong case.

spdgt.telem 0.4.7

  • telem_create_positions() now checks that the payload actually yields a fix before sending. Positions are the one resource whose required values live inside a JSON payload rather than in columns, so the schema could only insist the column was there. A payload carrying temperature and voltage but no coordinates passed validation and was accepted by the API, which then marked every event Failed — stored, counted, and left for someone to find.

  • The check asks exactly what the server asks. Coordinates are resolved case-insensitively through the same alias list ProcessGPS uses (latitude_dd, latitude, lat_dd, lat, y, and the longitude equivalents), so a raw vendor record passes untouched and only a payload the server could not read is refused. A pair of exactly 0, 0 is refused for the same reason it is: the server treats it as no fix at all.

  • Coordinates outside their range are refused too. The server does not reject one, it stores it, so a transposed latitude and longitude becomes a position in the wrong hemisphere rather than an error.

spdgt.telem 0.4.6

  • A create with no rows is now refused. An empty frame reached the API as an empty batch, which some endpoints answered with an error and positions reported as a success having sent nothing — the silent success this package exists to prevent, arriving through a filter that matched nothing.

  • Date columns are now rendered for the wire like datetimes already were. A Date or POSIXct in birth_date, death_date, purchased_at, retired_at or date_of_capture went out in whatever shape the JSON encoder chose, while the same value in start_at was normalised. Both are normalised now, dates to YYYY-MM-DD.

  • Supplying project_id from the session now fails loudly when there is no project, instead of returning the data unchanged. Every caller runs check_project() first, so this cannot be reached today — but returning unchanged would have sent a record the API stores where nothing can read, update or delete it, and that is not a failure worth making quietly.

  • The confirmation prompt for incomplete deployments is now covered by tests. Reaching it needs an interactive session, so the check uses rlang::is_interactive() rather than base::interactive(), which a test can declare and a base binding cannot.

spdgt.telem 0.4.5

  • alberta_assign_units.R now names the country it works on, like the two load scripts already did. It was the only one of the three without that guard, and the only one that touches both APIs – it reads unit polygons from counts and writes the ids onto telemetry records – so it checks both resolve to the same country before doing anything. Run in a session pointed somewhere else, it would have written one country’s unit assignments onto another’s records, which deleting rows afterwards does not undo.

  • The same script no longer says you will be asked to sign in twice. One sign-in serves both APIs: telemetry is served from the counts credential.

spdgt.telem 0.4.4

  • Bug reports now go to https://support.spdgt.com, and DESCRIPTION carries a real BugReports field for the first time. The field was previously absent because the address was an email, which R CMD check rejects as a mailto:, and a link to the private source repository would have sent readers somewhere they cannot reach — so the documentation site’s sidebar link was written by hand instead. A support URL removes both objections, so the workaround is gone and the link is generated from the package metadata like any other.

spdgt.telem 0.4.3

  • DESCRIPTION now requires spdgt.auth (>= 1.2.1). Every telem_update_*() passes its single-row frame straight to api_patch(), and before 1.2.1 a base data frame was sent as a one-element array rather than an object — which the API accepts with a success response while writing nothing. Installed against the old floor of 1.1.0.19, the update functions returned success and changed nothing. The floor is 1.2.1 because that is the version carrying the fix, not the newest available.

spdgt.telem 0.4.2

  • No user-facing change. The note recording which versions the public Nexus holds is brought up to date, and the release exists so the documentation site publishes: the pkgdown output has been building correctly for some time but nothing was serving it, and the Vercel project that now does deploys from the gh-pages branch this build refreshes.

spdgt.telem 0.4.1

  • The reference index lists every exported function again. The biological records added since 0.3.1 – blueprints, sensor deployments, groups, group individuals, diagnoses, procedures and samples – were exported without being added to _pkgdown.yml, and pkgdown treats an unlisted topic as an error, so the documentation site had been building only partially.

  • The Alberta load scripts in inst/scripts/ now select the country’s database explicitly with SPDGT_REGION and refuse to run if the telemetry API does not resolve to the expected regional host. Alberta data is Canadian; writing it to the United States instance is the one mistake that deleting rows afterwards does not undo, so a region that did not take effect is an error rather than a warning. The scripts also require spdgt.auth (>= 1.1.0.20), the version that introduced regions, because an older one would resolve silently to the wrong country.

spdgt.telem 0.4.0

  • Every telem_create_*() now validates its data against the fields the API actually accepts before sending anything, and reports every problem at once rather than stopping at the first. telem_fields() is that list, and telem_validate() runs the check on its own if you want it earlier. The fields come from the API’s own validation rules; spdgt.auth::api_discover() reports some of them wrongly, because it serves an OpenAPI annotation that has drifted from the rules beneath it.

  • The vendor collar layer is removed: read_collar(), default_col_map(), prep_individuals(), prep_devices(), prep_deployments(), prep_positions() and telem_ingest(). It targeted one export shape and degraded silently on anything else — a column mapping it could not resolve was dropped rather than raised, yielding all-NA columns that passed every check downstream. Reshaping arbitrary vendor files was never a job this package could finish; guaranteeing what reaches the API is. spdgt.telem is pre-1.0, so this is a clean break with no deprecated aliases.

  • New telem_add_payload() builds the payload column a position upload needs, keeping every column the collar recorded rather than the four the old prep_positions() kept. This matches what the server itself stores. telem_create_positions() takes that column directly.

  • Writes are refused when the session carries no project. Such a write is accepted by the API and reported as a success, but the resulting row cannot be read, updated or deleted by anyone — a previous load left 21 device rows that had to be removed in SQL.

  • telem_create_deployments() stops and asks when a deployment has no individual_id or no start_at. The API accepts these and records them as Incomplete, which cannot anchor a position and is never reported back. Pass on_incomplete = "allow" for a collar not yet on an animal.

  • telem_create_positions() now warns when the data carries a deployment_id, as telem_create_events() already did. Positions post to the same endpoint, so the API discards the column the same way while reporting success; it was previously removed before the request was built, without a word. The column is sent regardless, so this starts working with no change here once the API accepts it.

  • telem_validate() reports an unrecognised enumerated value readably, and in the same words as the rest of the package. It previously printed the deparsed vector ("\"Bogus\""), and a value containing a brace aborted with a cli error instead of the validation problem.

  • An unfiltered read now warns when it is about to return more records than max_records (50,000 by default). Reads still return every record by default; the event tables are simply large enough that the whole of one is rarely what anybody meant.

  • telem_event_sources() gains "CSV", which is in wide use. Note that "API" means the record was fetched from the manufacturer’s API by the server, not that a client wrote it.

  • vignette("loading-data") is rewritten around the schema: the order records have to be created in and the two traps that order alone does not reveal, what telem_fields() says and how to read it, how to check a batch before sending it, and how to upload positions you hold locally. Its hand-maintained tables of required columns are gone — they had already drifted from the API, and telem_fields() cannot.

  • DESCRIPTION no longer pins dplyr (>= 1.2.0). The pin existed for recode_values(), used by the deleted map_sex(); the only dplyr function left in the package is bind_rows(), so requiring a 2026 dplyr asked consumers to upgrade for nothing.

spdgt.telem 0.3.1

  • Every read function’s help now links to [spdgt.auth::api_discover()] as a real cross-reference rather than naming it in backticks, and DESCRIPTION requires spdgt.auth (>= 1.1.0.19) — the version that added it. Both were deferred in 0.2.0 for a reason that has since gone away: a cross-package Rd link only resolves once that version is installable, and api_discover() was merged but unpublished. It is on Nexus now.

spdgt.telem 0.3.0

  • The vendor collar-file layer moved here from spdgt.telem.admin: read_collar(), default_col_map(), prep_devices(), prep_individuals(), prep_deployments(), prep_positions() and telem_ingest(). Nothing in them is privileged — parsing a vendor CSV needs no role — so an admin-tier package was the wrong home, and with the CRUD already here they had been left calling across a package boundary for no reason. spdgt.telem.admin keeps its repo for genuinely admin-only work later, and exports nothing for now.

  • vignette("loading-data") gained the collar-file section it previously only pointed at: what read_collar() normalises, the col_map fallback that yields all-NA columns rather than an error when a mapping cannot be resolved, and the prep_*() sequence with the lookups that bridge it.

  • inst/scripts/quinault_elk_load.R is a worked per-project loading script, readable start to finish. It existed only on an unmerged branch of the admin package and would have been lost.

spdgt.telem 0.2.0

  • The package now writes as well as reads. Create, update and delete functions for individuals, devices, deployments, captures, events and positions moved here from spdgt.telem.admin, which is where they were written and the wrong place for them: nothing in them is admin-only, and end users need programmatic access to the same operations. What a caller may actually do is decided by the API from their role, not by which R package they installed — creates and updates need a project relationship, and deletes need the Admin role, so a User-role caller gets a 403 rather than a deletion.

  • The four telem_read_*_id() functions are removed, replaced by telem_read_individuals(), telem_read_devices(), telem_read_deployments() and telem_read_positions(). The _id suffix was a misnomer — none of them read a single record by id; they read a filtered list, which is what the replacements do. The replacements also drop the project_id argument, which the API fills in from the caller’s token, and add pages. spdgt.telem is pre-1.0, so this is a clean break with no deprecated aliases.

  • Reads now return every matching record by default. The API paginates at 100, so the previous behaviour silently returned the first page — on the Quinault device list that was 100 of 179 records, with nothing to indicate the rest existed. Pass pages = list(size = 100) to page deliberately.

  • New telem_read_captures() and telem_read_events().

  • build_lookup() moved here alongside the create functions, because they are barely usable without it. A batch endpoint answers with one row per submitted record and the created record inside a data list-column, so the assigned ids are not where a caller reaches for them; build_lookup() flattens that to the successful records and returns the named vector that turns a serial number or nickname into the device_id or individual_id the next call needs.

  • New enumeration functions expose the values the API accepts, so a payload can be built and checked before it is sent rather than coming back as a 422: telem_event_types(), telem_event_statuses(), telem_event_sources(), telem_device_states(), telem_capture_methods(), telem_deployment_statuses(), telem_fates(), telem_sexes() and telem_device_manufacturers(). They mirror app/Enums/*.php in telemetry-api, which stays authoritative. The create functions validate against them locally.

  • Read functions now name the filters people actually reach for, so nobody has to know a filter name to get started: species_id, sex, fate and nickname on individuals; serial_number and state on devices; method on captures; deployment_id and status on events; and from/to on deployments, captures, events and positions. Anything else the endpoint accepts still goes through ..., and every read function’s help now points at spdgt.auth::api_discover() for finding out what that is rather than guessing.

  • Bug fix: telem_read_positions(date_range = ) never worked and is replaced by from and to. The API’s filter vocabulary has no two-sided comparison: a comma-separated value is a whereIn list, so date_range = "2026-01-01,2026-06-30" asked for positions at exactly those two instants and returned nothing rather than the span between them. Prefixing does not rescue it either — ">a,<b" strips the prefix from the first element only and then applies that one operator to every element, comparing against the literal string "<b". So from/to send one bound to the server and apply the other locally, which is correct at the cost of more rows travelling than were asked for. Both bounds are inclusive, and a plain date as to means the end of that day.

  • Filter coverage is uneven across endpoints in ways worth stating, since guessing wastes time: species exists only on individuals, spatial filtering only on positions (bounds), and time on every endpoint but under a different column each — which is why from/to map to whichever column that endpoint means.

  • New telem_mortality_causes() gives a shared vocabulary for cause of death. The API has no field for it, and none for predator species either, so cause and predator columns passed to telem_create_events() are lower-cased and folded into notes with a message saying so. cause is checked against the vocabulary and warns on anything outside it; predator is free text, since a predator list is a species list and this package is not the authority on it. Source data commonly conflates the two — a cause recorded literally as "cougar" — and splitting it into cause = "predation", predator = "cougar" keeps both facts without a cause vocabulary per predator. A predator left in cause still reaches notes, after a warning: recording it imprecisely beats losing it. This is a labelled workaround, not a schema.

  • telem_create_events() now warns when deployment_id is supplied. The column is in the Event model’s fillable but absent from the create request’s validation rules, so the API discards it and still reports success — which is how 130 mortality events ended up linked to no animal and needed repairing in SQL. The value is sent anyway, so this starts working with no change here once the API accepts it.

  • Workarounds for known API gaps are collected in R/api_gaps.R, each recording what to delete when the API side lands, so they can be removed together rather than found one at a time.

  • Two vignettes: vignette("spdgt-telem") covers reading, discovery and what your role allows, and vignette("loading-data") is the write reference — the order records have to be created in, what each endpoint requires, and how to get the assigned ids back out of a batch result.

  • Live debug scripts in inst/debug/, for exercising the endpoints against the real API where the mocked test suite cannot. write.R is a dry run unless TELEM_DEBUG_LIVE=yes, and its live section sits inside an if (live) { } block rather than behind an early return: return() at file scope does not stop a sourced script, so a return-guard would print “dry run only” and then write anyway.

  • Repo hygiene: .vscode/ is now gitignored. The JS worm spread through a tasks.json folderOpen task executing a payload disguised as a font — the vendored guard catches the payload, and ignoring the folder removes the vector.

  • The package’s documentation URL is now https://docs.spdgt.com/telem, matching spdgt.core and spdgt.sight; it had pointed at a github.io address that is not where the estate serves docs. DESCRIPTION gained the matching URL field it was missing.

  • Bug reports go to , shown in the README and in the pkgdown sidebar. There is deliberately no BugReports field: this package is installable by anyone from the SpeedGoat repository while its source repo is private, so a GitHub issues URL points somewhere the people installing it cannot reach, and R CMD check rejects a mailto: there as “not a suitable URL”. The sidebar link is therefore configured by hand rather than derived from DESCRIPTION.

spdgt.telem 0.1.0

  • Initial release with ID-based read functions.
  • telem_read_individuals_id() to query individuals by project or species.
  • telem_read_deployments_id() to query deployments by project, device, or individual.
  • telem_read_devices_id() to query devices by project.
  • telem_read_positions_id() to query GPS positions with date range and spatial filters.