# Weak Signal Finder

**Level:** 1 hour · your first real analysis

**APIs used:** GET /api/v2/net_devices/, GET /api/v2/net_device_signal_samples/

---
Read https://cradlepoint.ericsson.com/llms.txt and the OpenAPI document at
https://cradlepoint.ericsson.com/developer/api/openapi.json so you know the
Ericsson NetCloud Manager (NCM) API.

Build me a Python script (standard library only) that finds the routers with
the worst cellular signal over the past 7 days. It covers every router that
has at least one cellular modem.

Approach:
- List cellular modems with
  GET /api/v2/net_devices/?mode=wan&is_asset=true&expand=router&limit=500.
  `mode` only accepts wan or lan (mode=cellular silently returns nothing);
  is_asset=true is what selects modems. expand=router puts the router (id and
  name) inline. Skip modems whose router is null; they aren't attached to a
  router.
- Pull samples from GET /api/v2/net_device_signal_samples/ with
  net_device__in=<comma-separated modem IDs>,
  created_at__gt=<UTC timestamp 7 days ago, ending in Z, e.g.
  2026-10-19T17:00:00Z>, limit=500 and fields=net_device,created_at,rsrp,sinr.
  The API reference caps a single query at 100 devices, so batch the modem IDs
  in groups of 100.
- Paging: this endpoint has no offset. Follow meta.next exactly as returned
  until it is null. If a next URL is missing any of your parameters
  (net_device__in, created_at__gt, fields, limit), add them back before
  requesting it. If you set order_by, use only created_at_timeuuid or
  -created_at_timeuuid; ordering by created_at or by a metric makes the API
  return an error.
- Each sample's net_device is a URL; take the modem ID from its last path
  segment. Pool the samples of all a router's modems together.
- For each router, compute the sample count, plus average and 5th-percentile
  RSRP and SINR, using linear interpolation for the percentile. Skip null
  readings in the maths, but report how many there were. Rank by
  5th-percentile RSRP, lowest first; routers whose RSRP readings are all null
  go last. Print the 20 worst as a table.

Requirements:
- List routers that reported zero samples separately, below the table, so
  "no data" is never mistaken for "good signal".
- v2 paths keep the trailing slash. Auth is four headers (X-CP-API-ID,
  X-CP-API-KEY, X-ECM-API-ID, X-ECM-API-KEY) read from the environment
  variables X_CP_API_ID, X_CP_API_KEY, X_ECM_API_ID and X_ECM_API_KEY. Send a
  descriptive User-Agent header.
- meta.next may point at a regional host such as www.us0.cradlepointecm.com.
  Send the same headers there, but only ever send credentials to https:// URLs
  on cradlepointecm.com or its subdomains.
- Don't follow redirects automatically; treat any 3xx response as an error (a
  301 usually means a missing trailing slash). HTTP clients re-send custom
  headers to wherever a redirect points, which would leak your keys.
- This is the starter project most likely to hit the rate limit on a large
  fleet. On HTTP 429, wait until X-RateLimit-Reset (a Unix epoch) if that
  header is present, otherwise back off exponentially (1s, 2s, 4s, 8s). Make at
  most 5 attempts per request in total, and never wait more than 60s at a time.
  On any other HTTP error, stop with a clear message. Use a 30s request timeout.

Then tell me how to run it, including which environment variables to set.
