Geocoding 82,400 Patient Addresses for Mobile Clinic Planning
A regional health network needs to map 82,400 patient addresses from a CSV to plan mobile clinic routes. The developer's prototype runs in the browser: for each row it calls the World Geocoding Service findAddressCandidates endpoint, then draws a point. In a 30-minute test the browser completed about 2,900 addresses, then calls began returning HTTP 429, a few returned 503, and the tab crashed, losing all results. Some addresses matched a street centroid rather than a house number, and 4 percent came back with a score below 70 but were still plotted on the map. The network has an ArcGIS Developer subscription with a monthly credit allowance and no on-premises locator. Patient addresses are sensitive, and the team is unsure whether they may be sent to a cloud service. The final output must be a hosted point layer plus a CSV of failures for manual correction, and the geocodes will be stored permanently.
1. What is wrong with per-address browser calls, and what architecture should replace them?
Read the worked answer
Three failures compound. First, the browser holds the credential and sends patient addresses directly from the client, which is a data-protection and key-exposure problem, not only a performance one. Second, one HTTP request per address ignores the batch endpoint and has no backoff; the service throttles with 429 when request rates exceed limits, and 503 signals transient overload, and both require retry with exponential backoff and jitter rather than immediate re-fire. Third, results exist only in the tab, so a crash discards completed work and leaves no audit trail.
Move the work server-side as a resumable job. A worker reads the CSV, sends up to 1,000 addresses per request to geocodeAddresses, records each raw response against the source row ID, and checkpoints. Limit concurrency to a level the service tolerates, retry 429 and 503, and make the job idempotent so a restart skips completed rows. Hold the API key or app-login token on the server and never ship it to the client.
At 82,400 records that is roughly 83 batch requests plus retries rather than 82,400 single calls, and a crash costs one batch instead of the run.
2. How do you set match-quality thresholds so bad geocodes do not reach the map?
Read the worked answer
Quality control here is a matching-threshold design, not a single cutoff. The geocoder returns a score and an address type, and a high score on a street-name match is still a poor result for routing.
Tier it:
- Score at or above 95 with a house-number-level address type such as PointAddress or Subaddress: auto-accept.
- Score 80 to 94, or a coarser type such as StreetName or locality: manual review, showing the matched address next to the input.
- Score below 80, or no candidates: reject and export to the correction CSV.
Set maxLocations to 1 to avoid ambiguity, use a composite locator if a local fallback exists, and store the raw candidate JSON per row so a reviewer can see alternatives. Never discard the original address string.
The visible symptom in the prototype is that nothing gates the output at all; the review queue is what prevents a sub-70 match from appearing as a confident clinic stop. Calibrate the thresholds on a stratified human-scored sample of 300 to 500 rows before applying them to all 82,400.
3. How do you handle the privacy question and control credit consumption?
Read the worked answer
Privacy is a legal and contractual question before it is a technical one. Strip direct identifiers before transmission: send only the address fields, never name, medical record number, or date of birth. Confirm with counsel and with the Esri agreement whether the remaining data may leave the network. If it may not, the alternative is an on-premises locator with StreetMap Premium or a locally built locator, which keeps addresses inside the infrastructure.
Cost: batching reduces requests, but credits are generally consumed per geocode rather than per HTTP call, so 82,400 addresses costs roughly the same whether sent singly or 1,000 at a time. The batch buys reliability and time, not free geocodes. Set forStorage=true when results will be persisted, since storage rights are a separate term. Cache by a hash of the normalized address so re-runs and duplicate roster addresses are not billed twice; duplicates are common.
Monitor credit consumption weekly with an alert threshold, and scope any later re-geocode to the failure list only. Store coordinates with a stable source_row_id plus score, type, and locator version so corrections are traceable.