Skip to content

patrol-datamodel

Creates or updates, from a smart-able migration bundle, everything an EarthRanger site needs before SMART patrols can be imported:

Step What it writes Idempotency
Patrol types one per SMART transport, value = patrol_<transport key>, display in the chosen language upsert on value; inactive transports are created inactive and never deactivated by the tool
Universal patrol fields the default segment schema: objective, mandate, station, patrol_class, armed, comment, plus smart_<key> per custom patrol attribute merged with what is on the site (admin-added fields survive); --replace-segment-schema overwrites; skipped with a checklist item while the patrol_schemas preview feature is off
Choices patrol_mandate, patrol_station, patrol_class, patrol_<attribute> upsert on value; only options the bundle names are changed. These fields are site-wide and a choice records no owner, so an option from another conservation area's bundle or added by an admin is left as it is and counted in the summary
Subjects one ranger subject per employee with a leg membership or an active employment, additional.smart_member_id = SMART employee UUID; SMART logins that never went on a patrol (such as the smart admin every install creates) are left out matched on smart_member_id, then, for a subject without one that records this conservation area, on smart_employee_id when that id is unique; patched when name, active flag or identity metadata differ; a subject an earlier run made for this conservation area that is no longer planned is deactivated, never deleted
Roster subjects added to the patrol configuration's first member group, or to --roster-group NAME only missing members are added

It then writes checklist.md next to the bundle directory with the steps an admin has to do by hand: enabling the preview feature, creating teams, granting permissions, choosing the roster group, and picking an icon for each patrol type (the API cannot set icons).

Station choices are keyed by the station's SMART UUID, since SMART stations have no key of their own; the display is the label in the chosen language. SMART lets a station exist with no name in any language. Such a station is created as Unnamed station <first 8 characters of its UUID>. If no patrol uses it, the choice is inactive, so the form does not offer it; if a patrol does, it is active and the checklist asks for a real name.

A SMART login is left out only when it never went on a patrol. Sites often record shared admin logins as patrol members, and the patrol import needs those as subjects. Bundles written before smart-able flagged logins (has_smart_login) leave nobody out. In a dry run no subject gets a real id, so the summary always reports 0 subjects added to the roster group.

Usage

er-smart-sync patrol-datamodel --bundle work/export/050500/bundle \
    --er-endpoint site.pamdas.org --er-token TOKEN
er-smart-sync --dry-run patrol-datamodel --config sync.yaml   # bundle.path and earthranger from the file

Options

Option Meaning
--bundle DIR the bundle directory (holds manifest.json); or bundle.path in --config
--smart-language CODE language for every display string written to ER; must be one of the bundle's languages (smart-able's Data Model tab lists them with coverage)
--timezone NAME assert the bundle's timezone; a mismatch stops the run and says how to re-export
--replace-segment-schema overwrite the universal patrol fields instead of merging
--roster-group NAME subject group to add the subjects to
--checklist PATH where to write the checklist (default: beside the bundle directory)
--dry-run (before the subcommand) log every write instead of sending it

Exit status is 0 even when the checklist has items; it is 1 when a step failed (a 403, for example), when the bundle is invalid, or when the timezone assertion fails.

What the bundle timezone means

Every timestamp in the bundle carries the offset of the zone smart-able wrote it for (manifest.timezone). patrol-datamodel never reinterprets them. smart.timezone in sync.yaml (or --timezone) is an assertion: set it to the zone you know the site uses, and the run stops if the bundle was written for another one, telling you to re-export with SMART_ABLE_TIMEZONE set. Without it, a bundle whose zone was inferred from waypoint positions produces a warning.

Undoing a run

Nothing the command creates is deleted by the command. Instead, every real run that changes anything writes patrol-datamodel-run-<time>.json next to the checklist. It lists each write in order, with the values it replaced. A dry run writes no record.

Undo only what the records list. Work from the newest record to the oldest, and inside a record from the last change to the first. Don't undo by searching the site. The patrol fields, patrol types and choices are site-wide, so another conservation area's run or an admin may own objects that look the same. A subject a run only matched or updated existed before that run.

Change in the record How to undo it
roster, added For each id in subject_ids: DELETE /api/v1.0/subjectgroup/<group_id>/subjects/ with [{"id": "<id>"}]. Members that were in the group before the run are not listed.
subject, created PATCH /api/v1.0/subject/<id>/ with {"is_active": false}, so history stays intact. DELETE /api/v1.0/subject/<id>/ removes it outright, which needs an admin permission.
subject, updated PATCH /api/v1.0/subject/<id>/ with the before object.
choice, created PATCH /api/v1.0/choices/<id>/ with {"is_active": false}, or delete it in Django admin > Choices if nothing uses it. If id is null, look it up with GET /api/v1.0/choices/?model=activity.event&field=<field> and match on value.
choice, updated PATCH /api/v1.0/choices/<id>/ with the before object.
universal_patrol_fields, written PUT /api/v1.0/activity/patrols/segments/types/default/ with {"schema": <the document in before_file>}. A null before_file means the site had no form, so PUT the empty envelope: {"schema": {"json": {"$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": {}, "required": [], "unevaluatedProperties": false}, "ui": {"fields": {}, "headers": {}, "order": [], "sections": {}}}}
patrol_type, created DELETE /api/v1.0/activity/patrols/types/<value>/ if no patrol uses the type. Otherwise PATCH it with {"is_active": false}.
patrol_type, updated PATCH /api/v1.0/activity/patrols/types/<value>/ with the before object.

Teams and the roster subject group are created by an admin, not by the command, so they are removed where they were made. A second run after a clean-up recreates everything.