Sort devices into fleets
Your inventory system already knows which store, vehicle or team each device belongs to. This program reads that as a CSV and moves each device into the matching fleet, so it gets that fleet’s settings at once. Run it after each delivery, or on a schedule.
Needs: a service account with ViewDevices and MoveDevices, and .NET 10.
serial,fleetA0000A00042,Downtown storeA0000A00043,Airport kioskOHR_TOKEN=ohrs_… dotnet run sort-into-fleets.cs -- inventory.csv # what would moveOHR_TOKEN=ohrs_… dotnet run sort-into-fleets.cs -- inventory.csv --apply # move themA0000A00042: (no fleet) → Downtown storeA0000A00043: Warehouse → Airport kioskWould move 2, already in place 0, problems 0. Run again with --apply to move them.#:package OhrTech.Management@*// Puts devices into fleets from your own inventory: a CSV of serial number and fleet name.// OHR_TOKEN=ohrs_… dotnet run sort-into-fleets.cs -- inventory.csv show what would move// OHR_TOKEN=ohrs_… dotnet run sort-into-fleets.cs -- inventory.csv --apply move them// Needs ViewDevices and MoveDevices.using OhrTech.Management;
var file = args.FirstOrDefault(a => !a.StartsWith("--")) ?? "inventory.csv";var apply = args.Contains("--apply");
using var api = OhrManagementClient.ForServiceAccount( Environment.GetEnvironmentVariable("OHR_TOKEN") ?? throw new InvalidOperationException("Set OHR_TOKEN to a service account token."));
var fleets = (await api.ListFleetsAsync()).ToLookup(f => f.FleetName, StringComparer.OrdinalIgnoreCase);var devices = (await api.ListDevicesAsync()) .Where(d => d.Serial != null) .ToDictionary(d => d.Serial!, StringComparer.OrdinalIgnoreCase);
int moved = 0, unchanged = 0, problems = 0;foreach (var line in File.ReadLines(file).Skip(1)) // header: serial,fleet{ var columns = line.Split(',', 2, StringSplitOptions.TrimEntries); if (columns.Length < 2 || columns[0].Length == 0) continue; var (serial, fleetName) = (columns[0], columns[1]);
if (!devices.TryGetValue(serial, out var device)) { Console.WriteLine($"{serial}: no such device in your organisation"); problems++; continue; }
var matches = fleets[fleetName].ToList(); if (matches.Count != 1) { Console.WriteLine($"{serial}: {(matches.Count == 0 ? "no fleet" : "more than one fleet")} named \"{fleetName}\""); problems++; continue; }
var fleet = matches[0]; if (device.FleetId == fleet.FleetId) { unchanged++; continue; }
Console.WriteLine($"{serial}: {device.FleetName ?? "(no fleet)"} → {fleet.FleetName}"); if (apply) await api.MoveDeviceAsync(device.DeviceId, new MoveDeviceRequest { FleetId = fleet.FleetId }); moved++;}
Console.WriteLine($"{(apply ? "Moved" : "Would move")} {moved}, already in place {unchanged}, problems {problems}." + (apply || moved == 0 ? "" : " Run again with --apply to move them."));How it works
Section titled “How it works”- Fleets are matched by name (ignoring case), devices by serial number. A name that matches no fleet, or more than one, is reported and skipped.
- Devices already in the right fleet are left alone, so running it again changes nothing.
- A service account scoped to some fleets can only move devices among them.
Make it yours
Section titled “Make it yours”Read from your inventory system’s API instead of a CSV, or take the fleet from a field you already have, such as a store number.