AidFinder
Back to dashboard

af-map

AidFinder Mapping Service

Go SQS consumer that streams household records to the Mapbox Tilesets API and publishes per-disaster tilesets consumed by af-frontend.

Domain role
Tile-generation worker
Last updated
2026-04-02
Lines of code
4,707
API style
CLI

Go service running as an ECS Fargate task. Long-polls a single SQS queue for DisasterNotification messages, queries Postgres (homeowners_declared for disaster-specific runs, bq_households for state-wide runs) in 50,000-row batches, pipes the rows as line-delimited GeoJSON straight into the Mapbox Tilesets API via io.Pipe, then creates and publishes a tileset named helpishere.<env>.<disaster_id>. Designed to avoid OOM by streaming.

Role in the system: Transforms household records into Mapbox vector tilesets that the af-frontend renders as the disaster-relief map

Surfaces:

  • SQS consumer (af-backend-mapping-service-notifications)
  • Mapbox Tilesets API client
  • Manual operator-triggered SQS messages

User workflows

  • Automatic disaster regen

    Tileset helpishere.<env>.<disaster_id> available; frontend MapboxGL layer renders affected households

  • Manual operator regen

    Tileset re-generated

  • State-based regen (experimental)

    Tileset for entire state — flagged 'use with caution' (not fully validated)

API endpoints

  • SQSDisasterNotificationTrigger tileset regeneration
  • OUT-HTTPPOST https://api.mapbox.com/tilesets/v1/sources/{user}/{sourceID}Upload GeoJSON source
  • OUT-HTTPPOST https://api.mapbox.com/tilesets/v1/{tilesetID}Create tileset from source
  • OUT-HTTPPOST https://api.mapbox.com/tilesets/v1/{tilesetID}/publishPublish tileset

Third-party APIs

  • Mapbox Tilesets API v1

    Source upload + tileset create/publish

Service dependencies

  • AWS SQS

    Inbound trigger queue (af-backend-mapping-service-notifications)

  • AWS RDS Postgres (households)

    Source of households (homeowners_declared, bq_households)

  • AWS SSM Parameter Store

    Secrets retrieval (DB creds, Mapbox API key)

  • AWS IAM

    Task execution + task roles

  • af-backend-go-api (upstream)

    [planned] Publishes SNS events that fan out to this queue

  • af-frontend (downstream)

    Consumes the published tilesets via mapbox-gl

Analysis

overall health3.3 / 5acceptable
4Module overview / clarity of intent
4External dependencies
3API endpoints
3Database schema
4Backend services
3WebSocket / real-time
4Data flow clarity
2Error handling & resilience
4Configuration
3Data refresh patterns
3Performance
4Module interactions
3Troubleshooting / runbooks
3Testing & QA
4Deployment & DevOps
2Security & compliance
4Documentation & maintenance
3Roadmap clarity

af-map — Prop-Build Analysis

Document Type: Critical Review & Analysis (companion to prop-build-template.md) Scope: Per-Repo / Per-Module Subject: af-map (AidFinder Mapping Service) Reviewer(s): Claude (automated code review) Date: 2026-04-09 Version: 0.1 Confidence Level: Medium What would raise confidence: access to CloudWatch metrics and SQS DLQ config in af-infra, a run against real Mapbox, and an interview with Marek Burda on the state-wide path.

Inputs Reviewed:

  • Prop-build doc: data/af-map.yaml
  • Source tree: /Users/andres/src/af/af-map/ (Go 1.26, ~4.7k LoC)
  • Companion files: data/af-map/{api-examples,data-flow,deployment,runbook}.md

Part A — Per-Repo / Per-Module Analysis

A.1 Executive Summary

  • Overall health: A small, focused, well-layered Go worker that does one thing (Postgres -> Mapbox tileset) reasonably well, with a clever streaming design but meaningful gaps in error handling, PII governance, and operational visibility.
  • Top risk: Full household PII (names, addresses, demographics) is streamed to Mapbox and stored in vector tilesets with no application-level access controls or audit trail — see A.4.1 and A.11.
  • Top win / thing worth preserving: io.Pipe-based streaming uploader in service/infrastructure/mapbox/tileset.go:26-69 wired through mapgeneration.go:77-92 — a genuinely memory-efficient design worth propagating.
  • Single recommended next action: Add an explicit DLQ + Mapbox source cleanup-on-failure path, and justify-or-remove every //nolint:gosec (4 instances).
  • Blocking unknowns: SQS DLQ / visibility-timeout settings live in af-infra and were not reviewed; no measured throughput or CloudWatch metrics available; FEMA Privacy Act applicability of source data is unknown.

A.2 Health Scorecard

#DimensionScore (1–5)Justification
1Module overview / clarity of intent4README + hexagonal layout (cmd/, service/application, service/infrastructure, domain/) make intent obvious.
2External dependencies4Small, mainstream Go deps (aws-sdk-go-v2, pgx/v5, scany, validator, tint).
3API endpoints3No HTTP surface; the SQS DisasterNotification contract is validated (service/infrastructure/sqs/sqs.go:131) but undocumented outside YAML.
4Database schema3Schema owned upstream; repo only queries. Two tables (homeowners_declared, bq_households) with reasonable pagination.
5Backend services4Single-purpose worker, cleanly factored into application + infrastructure layers.
6WebSocket / real-time3SQS long-poll, sequential (MaxNumberOfMessages=1), no SNS publishing; simple but serialized.
7Frontend componentsN/ABackend-only.
8Data flow clarity4mapgeneration.go:50-100 -> mapbox.go:43-70 -> tileset.go is linear and readable.
9Error handling & resilience2No DLQ in-app, no circuit breaker, no cleanup of stale Mapbox sources on failure (tileset.go:61-66), errors only logged then the message is left for SQS retry (sqs.go:91-105).
10Configuration4Env-var driven via SSM; env-gated tileset naming is explicit in mapbox.go:46-52.
11Data refresh patterns3Event-driven only; no incremental update (is_updated parsed but unused).
12Performance3Streaming avoids OOM, but serial 1-msg-at-a-time polling and 120-minute HTTP timeout (mapbox.go:16) make tail latency unbounded.
13Module interactions4Clear: SQS in, Postgres in, Mapbox out; boundaries are respected.
14Troubleshooting / runbooks3data/af-map/runbook.md exists; in-repo README documents manual Mapbox cleanup curls.
15Testing & QA3sqs_test.go is 428 LoC with testify; no coverage number, no Mapbox integration test, no load test.
16Deployment & DevOps4GitHub Actions -> ECR -> ECS CodeDeploy blue/green; manual deploy dispatch documented.
17Security & compliance2PII streamed to a third-party tileset store with no app-level audit log; 4 //nolint:gosec without justification.
18Documentation & maintenance4README is clear, YAML prop-build is thorough, changelog tracked.
19Roadmap clarity3section_19_roadmap lists planned items and acknowledged debt, but no dates/owners.

Overall score: 3.28 (average of 18 rated dimensions; dim 7 N/A). Weighted reading: structural dimensions (1, 5, 8, 13, 16, 18) are solidly 4; operational and security dimensions (9, 17) drag the honest average into the low 3s and are where remediation should focus.


A.3 What's Working Well

  • Strength: Streaming io.Pipe uploader — Postgres pagination batches are piped straight into the Mapbox multipart body, so memory is bounded regardless of disaster size.

    • Location: service/infrastructure/mapbox/tileset.go:26-69 + service/application/map-generation/mapgeneration.go:77-92
    • Why it works: avoids the obvious OOM footgun on large disasters; producer/consumer are decoupled by the pipe and errors propagate via pw.CloseWithError.
    • Propagate to: any other worker in the platform that materializes large Postgres result sets to external APIs (af-targeting is the obvious candidate).
  • Strength: Clean hexagonal layering — cmd/map wires concrete infrastructure into application services that depend only on small interfaces (MapGenerationService, HouseholdService).

    • Location: service/application/map-generation/mapgeneration.go:17-26
    • Why it works: substitutable seams for testing; infrastructure is swappable without rewriting the application.
    • Propagate to: af-backend-go-api workers that currently couple handlers to concrete clients.
  • Strength: Explicit count == 0 short-circuit before uploading.

    • Location: service/application/map-generation/mapgeneration.go:54-75
    • Why it works: avoids creating empty Mapbox sources/tilesets and the operational mess of cleaning them up.

A.4 What to Improve

A.4.1 P0 — PII exposure on Mapbox with no app-level audit or cleanup

  • Problem: Full household PII (adult names, street addresses, lat/lon, demographics) is streamed to Mapbox vector tilesets and stored indefinitely. The published tileset properties include household_name, bq_household_id, and lat/lon. There is no application-level audit log of what was published when, and no automated cleanup of stale sources on failure.
  • Evidence: service/infrastructure/mapbox/household.go:33-54 writes householdName (derived from BQAdultsFullNames) and coordinates into every GeoJSON feature; service/infrastructure/mapbox/tileset.go:61-66 returns error on non-200 but does NOT delete the partially-uploaded source; README manual cleanup curls confirm this is operator-driven. section_17_security_compliance.data_protection.pii_handling in data/af-map.yaml flags this as HIGH RISK.
  • Suggested change: (a) emit a structured audit record on every UploadDeclaredHouseholdsToMapbox start/success/failure, including sourceID and row count; (b) add a deferred cleanup step that deletes the Mapbox source if publish fails; (c) confirm Mapbox ToS permits PII storage and minimize properties.
  • Estimated effort: M
  • Risk if ignored: regulatory exposure (FEMA Privacy Act if applicable), breach of Mapbox ToS, and inability to answer "what data was shared, when" during an incident.

A.4.2 P1 — No DLQ handling or poison-message protection in-app

  • Problem: Message processing errors are logged and the message is left in the queue for SQS-default retry. There is no max-retry guard, no DLQ handoff in code, and no metric on failure count per message.
  • Evidence: service/infrastructure/sqs/sqs.go:91-105 continues on both parse and handler errors without deleting; section_9_error_handling.circuit_breakers.applicable: false in YAML.
  • Suggested change: Read ApproximateReceiveCount; after N failures, move to an explicit DLQ or delete with a loud error metric. Emit a CloudWatch metric for failed handles.
  • Estimated effort: S
  • Risk if ignored: a single poison DisasterNotification loops forever, consuming the single-threaded worker and blocking all other disasters.

A.4.3 P1 — Serialized single-message worker is a throughput bottleneck

  • Problem: maxMessages = 1 (sqs.go:19) and a synchronous handler mean one disaster blocks the entire service. Mapbox upload can take up to 120 minutes (mapbox.go:16), so one bad disaster starves the queue.
  • Evidence: service/infrastructure/sqs/sqs.go:19, service/infrastructure/mapbox/mapbox.go:16.
  • Suggested change: Run N worker goroutines with bounded concurrency, or document and alert on SQS ApproximateAgeOfOldestMessage. Reduce the 120m timeout and rely on SQS visibility-timeout extension for long uploads.
  • Estimated effort: M
  • Risk if ignored: backed-up queue during multi-disaster events; no way to hit a reasonable tile-publish SLO.

A.4.4 P2 — Unjustified //nolint:gosec directives

  • Problem: Four //nolint:gosec with no rationale (README §171 lists fixing them as tech debt).
  • Evidence: service/application/map-generation/mapgeneration.go:51, domain/household/postgres/household.go:48,58,80.
  • Suggested change: Add a one-line justification to each (likely int -> uint32 conversion for disaster_id) or refactor to eliminate the cast.
  • Estimated effort: S
  • Risk if ignored: security-review noise; real gosec findings will be drowned out.

A.4.5 P2 — is_updated flag parsed but unimplemented

  • Problem: The DisasterNotification schema carries is_updated, and the README acknowledges incremental updates are not supported; every run is a full rebuild.
  • Evidence: service/infrastructure/sqs/model/model.go:7-12 + section_19_roadmap.tech_debt[0] in YAML.
  • Suggested change: Either delete the field until it's implemented, or implement the update path against Mapbox's tileset-source PATCH.
  • Estimated effort: M
  • Risk if ignored: contract rot; upstream callers will assume the flag does something.

A.5 Things That Don't Make Sense

  1. Observation: The "state-based regen" path is gated on DisasterID == 0 rather than on the presence of StateName, which makes the branch implicit.

    • Location: service/application/map-generation/mapgeneration.go:50-92
    • Hypotheses considered: convenience; validator already enforces exactly-one-of at the boundary.
    • Question for author: can this become an explicit tagged union in the notification model?
  2. Observation: The tileset ID in dev omits the environment prefix (helpishere.<disaster_id>) while stg/prod include it.

    • Location: service/infrastructure/mapbox/mapbox.go:46-52
    • Hypotheses considered: avoid breaking existing dev tileset; historical compatibility.
    • Question for author: why not always prefix (helpishere.dev.<id>) for uniformity?

A.6 Anti-Patterns Detected

A.6.1 Code-level

  • God object / god function
  • Shotgun surgery
  • Feature envy
  • Primitive obsession
  • Dead code — is_updated field parsed and validated but never branched on.
  • Copy-paste / duplication
  • Magic numbers / unexplained constants — maxMessages = 1 (sqs.go:19), mapboxUploadMaxWaitingTime = 120m (mapbox.go:16).
  • Deep nesting (>3 levels)
  • Long parameter lists
  • Boolean-flag parameters

A.6.2 Architectural

  • Big ball of mud
  • Distributed monolith
  • Chatty services
  • Leaky abstraction
  • Golden hammer
  • Vendor lock-in without exit strategy — Mapbox Tilesets API is baked in; no abstraction for a different tile backend.
  • Stovepipe
  • Missing seams for testing

A.6.3 Data

  • God table
  • EAV abuse
  • Missing indexes on hot queries (not verified — schema upstream)
  • N+1 queries
  • Unbounded growth / no retention policy — Mapbox tilesets and sources are only deleted manually.
  • Nullable-everything schemas
  • Implicit coupling via shared database

A.6.4 Async / Ops

  • Poison messages with no dead-letter queue — no in-app DLQ logic.
  • Retry storms / no backoff
  • Missing idempotency keys on non-idempotent ops — re-processing the same disaster_id grows the tileset (documented footgun).
  • Hidden coupling via shared state
  • Work queues without visibility / depth metrics — no CloudWatch custom metrics.

A.6.5 Security

  • Secrets in code, .env committed, or logs
  • Missing authn/z on internal endpoints
  • Overbroad IAM roles
  • Unvalidated input crossing a trust boundary (validator is present)
  • PII/PHI in logs or error messages — PII storage in downstream Mapbox tileset (household.go:33-54); no boundary log/audit.
  • Missing CSRF / XSS / SQLi / SSRF protections (no web surface)

A.6.6 Detected Instances

#Anti-patternLocation (file:line)Severity (P0/P1/P2)Recommendation
1Dead code (is_updated unused)service/infrastructure/sqs/model/model.go:7-12; service/application/map-generation/mapgeneration.go:50-100P2Implement or drop from schema.
2Magic numbersservice/infrastructure/sqs/sqs.go:19; service/infrastructure/mapbox/mapbox.go:16P2Move to config with doc-comments.
3Vendor lock-inservice/infrastructure/mapbox/*.goP2Extract a TileBackend interface at the application boundary.
4Unbounded growthservice/infrastructure/mapbox/mapbox.go:43-70; README cleanup curlsP1Cleanup-on-failure + periodic reaper.
5Poison messages / no DLQservice/infrastructure/sqs/sqs.go:89-106P1Honor ApproximateReceiveCount; wire explicit DLQ.
6Missing idempotency keysservice/application/map-generation/mapgeneration.go:50-100P1Delete-or-replace source/tileset before upload.
7Missing work-queue metricsservice/infrastructure/sqs/sqs.goP2Emit CloudWatch metrics.
8PII governance gapservice/infrastructure/mapbox/household.go:33-54P0Audit log on publish; minimize tileset properties.

A.7 Open Questions

  1. Q: Is the target SQS queue actually configured with a DLQ in af-infra, and with what maxReceiveCount?

    • Blocks: A.4.2, A.6.4 row 1.
    • Who can answer: af-infra owner.
  2. Q: Does Mapbox ToS permit storing full PII in a published tileset? Is there a signed data-processing addendum?

    • Blocks: A.4.1, A.11.
    • Who can answer: legal / data-privacy owner.
  3. Q: Are homeowners_declared rows derived from FEMA IA data?

    • Blocks: FEMA Privacy Act assessment.
    • Who can answer: af-backend-go-api data owner.

A.8 Difficulties Encountered

  • Difficulty: No access to CloudWatch metrics, SQS configuration, or deployed task definitions at review time.

    • Impact on analysis: cannot verify whether a DLQ exists, cannot measure observed throughput or error rate, cannot confirm DATABASE_SSL_MODE=require in prod. All performance and resilience findings are structural, not empirical.
    • Fix that would help next reviewer: commit a read-only link to the CloudWatch dashboard and paste representative aws sqs get-queue-attributes output into data/af-map/runbook.md.
  • Difficulty: The state-wide path is marked "experimental" but has no test coverage asserting it.

    • Impact on analysis: cannot say whether bq_households pagination terminates cleanly for large states.
    • Fix that would help next reviewer: an integration test against LocalStack exercising the DisasterID == 0 branch.
  • Difficulty: Only skimmed the 428-line sqs_test.go; did not enumerate every case.

    • Impact on analysis: test-quality claims are directional, not definitive.

A.9 Risks & Unknowns

A.9.1 Known risks

#RiskLikelihood (L/M/H)Impact (L/M/H)Mitigation
1Poison DisasterNotification blocks single-worker queueMHAdd DLQ handoff (A.4.2).
2Orphan Mapbox source after mid-upload failureMMCleanup-on-failure in mapbox.go (A.4.1).
3PII disclosed via published tileset URLLHAudit, minimize properties (A.4.1).
4Re-run on duplicate disaster_id grows tileset unboundedMMDelete-before-upload idempotency.
5Credentials mis-scoped in SSMLHExternal review of IAM policies in af-infra.

A.9.2 Unknown unknowns

  • Area not reviewed: actual SQS queue + DLQ configuration.
    • Reason: lives in af-infra; out of scope.
    • Best guess at risk level: medium — the repo assumes default retries.
  • Area not reviewed: Postgres query plans and index coverage.
    • Reason: no DB access; schema owned upstream.
    • Best guess at risk level: medium — 50k-row pages over bq_households could be slow on a cold index.
  • Area not reviewed: af-frontend Mapbox consumption.
    • Reason: out of scope for per-repo analysis.
    • Best guess at risk level: low for this repo.
  • Area not reviewed: the 428-line sqs_test.go in detail.
    • Reason: time.
    • Best guess at risk level: low.

A.10 Technical Debt Register

#Debt itemQuadrantEstimated interestRemediation
1No DLQ handling in-app (sqs.go:89-106)Reckless & InadvertentHigh — one poison message stalls the workerHonor ApproximateReceiveCount; explicit DLQ move.
2is_updated parsed but unusedPrudent & DeliberateLow now, Medium laterImplement or drop.
34x //nolint:gosec without justificationReckless & InadvertentLowAdd justification or refactor casts.
4Manual Mapbox source/tileset cleanupReckless & DeliberateMedium — operator toil, audit gapCleanup-on-failure + orphan reaper.
5maxMessages = 1 serial workerPrudent & DeliberateMedium in multi-disaster eventsBounded goroutine pool.
6120-minute HTTP timeoutReckless & InadvertentMedium — hides tail latencyLower + SQS visibility extension.
7DATABASE_RO_HOST missing from .env.commonPrudent & InadvertentLowAdd with default.
8Tileset naming asymmetry dev vs stg/prodPrudent & InadvertentLowNormalize naming.

A.11 Security Posture (lightweight STRIDE)

CategoryThreat present?Mitigated?Gap
Spoofing (identity)Low (IAM-only surface)Yes (IAM task role)
Tampering (integrity)Medium — SQS message is the trust boundaryPartially (validator on DisasterNotification)No signature; anyone with SQS write perms can trigger a regen.
RepudiationHigh — no audit trail of who/when published which PII tilesetNoAdd audit log of publish events.
Information DisclosureHigh — full household PII in published Mapbox tilesetPartially (env-prefixed tileset obscurity)No access control on tiles themselves.
Denial of ServiceMedium — single-worker + 120-min HTTP timeoutNoBounded concurrency, shorter timeouts, DLQ.
Elevation of PrivilegeLowYes (least-privilege IAM per YAML)Not independently verified.

A.12 Operational Readiness

CapabilityPresent / Partial / MissingNotes
Structured logsPresenttint + slog with context.
MetricsMissingNo custom CloudWatch metrics emitted from the app.
Distributed tracingMissingNo OTel / X-Ray instrumentation.
Actionable alertsPartialYAML lists CloudWatch alarms as TBD.
RunbooksPresentdata/af-map/runbook.md + README.
On-call ownership definedPartialCODEOWNERS exists; rotation not in-repo.
SLOs / SLIsMissingNone declared.
Backup & restore testedN/AStateless worker.
Disaster recovery planMissingNot documented in repo.
Chaos / failure testingMissingNone.

A.13 Test & Quality Signals

  • Coverage (line / branch): not reported in repo (section_15_testing.unit.coverage_pct: null); CI uploads to Codecov.
  • Trend: unknown.
  • Flake rate: unknown.
  • Slowest tests: unknown.
  • Untested critical paths: Mapbox 3-step publish (no integration test); state-wide DisasterID == 0 branch.
  • Missing test types: [ ] unit (partial) [x] integration [x] e2e [x] contract [x] load [x] security/fuzz

A.14 Performance & Cost Smells

  • Hot paths: Postgres pagination -> io.Pipe -> Mapbox multipart upload.
  • Suspected bottlenecks: serial 1-message SQS polling (sqs.go:19); Mapbox publish latency; 50k-row page size.
  • Wasteful queries / loops: count query before upload is a small extra round-trip but worth it to skip empty.
  • Oversized infra / idle resources: 2 vCPU / 8 GB Fargate for a mostly I/O-bound worker — worth measuring.
  • Cache hit/miss surprises: N/A — no caches.

A.15 Bus-Factor & Knowledge Risk

  • Who is the only person who understands X? Inferring from meta.authors (Marek Burda, Matúš Bafrnec); not verified.
  • What breaks if they disappear tomorrow? The state-wide experimental path and Mapbox ToS context.
  • What is undocumented tribal knowledge? The manual Mapbox cleanup workflow and the rationale for dev vs stg/prod naming asymmetry.
  • Suggested knowledge-transfer actions: capture the Mapbox cleanup flow as a make clean-orphans script; record a note explaining (or fix) the naming asymmetry.

A.16 Compliance Gaps

RegulationRequirementStatusGapRemediation
Mapbox ToS (PII in tilesets)Permitted data types & retentionUnknownNo documented reviewLegal review + written decision record.
FEMA Privacy Act (if applicable)Downstream disclosure in SORNUnknownSource-of-data lineage unclearConfirm household provenance; update upstream Privacy Act Statement if needed.
NIST 800-53 AU-2/AU-12 (audit events)Audit-log publish operations on PIIMissingNo app-level audit trailEmit structured audit record on every publish.

A.17 Recommendations Summary

PriorityActionOwner (suggested)EffortDepends on
P0Add app-level audit log + cleanup-on-failure for Mapbox publishes; minimize tileset properties (A.4.1, A.6.6 row 8, A.16 row 3)af-map maintainer + data-privacyMLegal input on Mapbox ToS
P0Legal/privacy review of PII in Mapbox tilesets and document the decision (A.11, A.16 row 1)data-privacyS
P1Honor ApproximateReceiveCount and wire explicit DLQ handoff (A.4.2, A.6.6 row 5)af-map maintainerSaf-infra DLQ exists
P1Delete-before-upload idempotency on duplicate disaster_id (A.6.6 row 6)af-map maintainerS
P1Bounded-concurrency worker + reduce 120-minute HTTP timeout (A.4.3, A.10 rows 5-6)af-map maintainerM
P1Emit CloudWatch metrics: handle duration, success/fail count, households/sec (A.6.6 row 7, A.12)SRES
P2Justify or remove 4x //nolint:gosec (A.4.4, A.10 row 3)af-map maintainerS
P2Implement or delete is_updated (A.4.5, A.10 row 2)af-map maintainer + upstreamMUpstream contract
P2Normalize dev vs stg/prod tileset naming (A.5 obs 2, A.10 row 8)af-map maintainerSMigration plan for existing dev tilesets
P2Add DATABASE_RO_HOST to .env.common (A.10 row 7)af-map maintainerS
P2Integration test for state-wide DisasterID == 0 path against LocalStack (A.8, A.13)af-map maintainerM

Environment variables

NamePurpose
ENVIRONMENT*{local|dev|stg|prod} — controls tileset naming
DATABASE_HOST*Writer Postgres host
DATABASE_RO_HOST*Read-only Postgres host
DATABASE_PORT*Postgres port
DATABASE_USERNAME*DB user
DATABASE_PASSWORD*DB password
DATABASE_DB_NAME*DB name
DATABASE_SSL_MODE*{allow|disable|require}
MAPBOX_API_KEY*Mapbox token (create/publish scopes)
MAPBOX_USERNAME*Mapbox account namespace (always 'helpishere')
SQS_QUEUE_NAME*Queue to long-poll
AWS_REGIONAWS region (defaults to us-east-1)
LOG_LEVELslog level