feat(inference): extract geospatial workloads from backend - #673
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #673 +/- ##
==========================================
+ Coverage 93.74% 94.20% +0.46%
==========================================
Files 59 62 +3
Lines 3147 3262 +115
==========================================
+ Hits 2950 3073 +123
+ Misses 197 189 -8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Adversarial review completed across OpenCode, Claude, Codex, and Cursor.\n\nApplied:\n- deploy workflow uploads the updated Compose file and provisions the shared inference token\n- CPU triangulation runs off the event loop and remains serialized per process\n- backend exposes a stable 503 response without leaking upstream details\n- zero-angle cone compatibility and concurrency/health regression coverage\n\nKept intentionally:\n- notification timezone lookup failures skip delivery, matching the agreed best-effort policy\n- no Redis, local fallback, or shared geospatial abstraction without evidence they are needed |
Summary
Extract geospatial triangulation and timezone lookup from the database-facing backend into an independently deployed
inferenceservice.Architecture
flowchart LR U["Clients / cameras"] -->|"published API :5050"| B["Backend\nDB + storage + JWT credentials\ndata and compute networks"] B -->|"data network"| DB[("PostgreSQL")] B -->|"data network"| S3["S3 / LocalStack"] B -->|"compute network\nBearer token"| I["Inference\ntriangulation + timezone\n1 worker by default"] I -. "no route" .-> X[("Database blocked")] I -. "no credentials" .-> Y["S3 / JWT / backend secrets"] classDef isolated fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px; classDef blocked fill:#ffebee,stroke:#c62828,stroke-dasharray:5 5; class I isolated; class X,Y blocked;The backend remains the sole authority for persistence and orchestration. Inference is a stateless compute boundary with three endpoints:
POST /v1/triangulatePOST /v1/timezoneGET /status(health only; unauthenticated)The two compute endpoints validate bearer authentication, coordinate bounds, finite numeric values, unique sequence IDs, timestamps, classifications, and canonical response shape.
Dependency and image impact
flowchart LR BEFORE["Backend before\n26 direct\n35 transitive-only\n61 total\n581.6 MB"] AFTER["Backend after\n19 direct\n29 transitive-only\n48 total\n299.3 MB"] INF["Inference after\n7 direct\n17 transitive-only\n24 total\n385.7 MB"] BEFORE -->|"remove 7 direct / 6 transitive-only\n-282.2 MB (-48.5%)"| AFTER BEFORE -->|"extract native geospatial closure"| INFDependency closures are counted per image from
uv export --only-group <group> --no-dev --no-hashes --no-emit-project; “transitive-only” is the closure minus direct declarations. Packages shared by both groups are counted in both images. Raw image sizes therefore must not be summed as host disk usage because shared Docker layers may be deduplicated.Image measurements use
docker image inspect .Sizeon Linux/arm64 images built from the same checkout andpython:3.11-slimDockerfile:581,552,675bytes299,322,028bytes385,675,379bytesPackage decisions
geopypyproj.GeodpandasnumpynetworkxpyprojshapelytimezonefinderFailure semantics
sequenceDiagram participant W as Validation worker participant B as Backend participant I as Inference participant D as Database W->>B: validate sequence B->>I: authenticated triangulation alt inference succeeds I-->>B: canonical groups + location B->>D: persist alert state else timeout / 4xx / 5xx / malformed response I--xB: unavailable B-->>W: raise W->>D: retain due marker / retry lease endInteractive mutations use the same ordering: fetch state → call inference → mutate only after success. There is no local compute fallback that could silently diverge.
Verification
637backend tests passed in the Docker Compose stack.12inference tests passed, covering authentication, validation, deterministic output, timezone fallback, empty/singleton/relaxed-time/same-pose/same-mast/mixed/dateline behavior, and responsive health checks during serialized compute.ty, lock check, and dependency-sync verification passed.geopy,networkx,numpy,pandas,pyproj,shapely, ortimezonefinder.pyronear_compute, no sensitive environment variables, and could not resolvedb.200; timezone200(Europe/Paris).Synthetic in-process triangulation benchmark (median of 3 runs, Linux/arm64 dependency environment):
Deployment and rollback
Release automation builds and publishes both images, uploads the current Compose definition, provisions
INFERENCE_API_TOKENfrom GitHub Secrets, starts healthy inference first, then switches backend. CPU work runs off the event loop behind a per-process lock, so triangulations remain serialized while/statusand timezone lookup stay responsive.INFERENCE_WORKERSdefaults to1and can scale independently later.Rollback remains backend-only: restore the previous backend image; the stateless inference container may remain running.
Production acceptance still required
INFERENCE_API_TOKENfor VPS rollout.