You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Extract geospatial triangulation and timezone lookup from the database-facing backend into an independently deployed inference service.
Backend calls inference over authenticated internal HTTP; no Redis, shared cache, or local fallback.
Inference receives no PostgreSQL, S3, JWT, temporal-model, risk-service, or superadmin credentials.
Inference is attached only to an internal compute network; it has no database network route and no published port.
Validation-worker failures continue through the existing lease/retry/dead-letter path.
Delete, label, and unmatch operations precompute the resulting alert state before any mutation; inference failure returns HTTP 503 with database state unchanged.
Notification timezone lookup stays best-effort: lookup failure is logged and notification delivery is skipped.
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;
Loading
The backend remains the sole authority for persistence and orchestration. Inference is a stateless compute boundary with three endpoints:
POST /v1/triangulate
POST /v1/timezone
GET /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"| INF
Loading
Image / state
Direct
Transitive-only
Locked closure
Docker image
Delta
Backend before
26
35
61
581.6 MB
—
Backend after
19
29
48
299.3 MB
-282.2 MB / -48.5%
Inference after
7
17
24
385.7 MB
new independently scaled image
Dependency 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 .Size on Linux/arm64 images built from the same checkout and python:3.11-slim Dockerfile:
removed from backend and direct code; remains inference-only transitively
networkx
inference-only for tested maximal-clique enumeration
pyproj
inference-only for CRS/geodesic projection
shapely
inference-only for polygon repair/intersection/centroid
timezonefinder
inference-only; notification timezone lookup is remote
Failure 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
end
Loading
Interactive mutations use the same ordering: fetch state → call inference → mutate only after success. There is no local compute fallback that could silently diverge.
Verification
637 backend tests passed in the Docker Compose stack.
12 inference 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.
Ruff, ty, lock check, and dependency-sync verification passed.
Release automation builds and publishes both images, uploads the current Compose definition, provisions INFERENCE_API_TOKEN from 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 /status and timezone lookup stay responsive. INFERENCE_WORKERS defaults to 1 and can scale independently later.
Rollback remains backend-only: restore the previous backend image; the stateless inference container may remain running.
Production acceptance still required
Configure a strong repository secret named INFERENCE_API_TOKEN for VPS rollout.
Run authenticated triangulation and timezone notification smoke tests.
Confirm inference has no database credentials or connectivity in production.
❌ Patch coverage is 94.42897% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.20%. Comparing base (729a870) to head (6d997b7).
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
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
Dependency 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
Interactive 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.