Objective
Today, a sequence's azimuth (Sequence.sequence_azimuth) is computed once, at sequence creation, from the center of the first detection's bounding box — see resolve_cone (src/app/services/cones.py) called in src/app/api/api_v1/endpoints/detections.py (~L582-590). It never changes afterward.
We want to be able to refine that azimuth after the fact. Two future sources will need this (handled in separate issues ):
- an operator clicking the fire origin point in the frontend, and/or
- a CV algorithm estimating the fire origin automatically.
This issue covers the backend workflow only: exposing a clean API entry point that updates the azimuth and keeps the triangulation state consistent.
Note:The source of the new azimuth is not tracked or persisted in this issue.
Current behavior
sequence_azimuth (and cone_angle) feed the whole triangulation pipeline:
_attach_sequence_to_alert (src/app/api/api_v1/endpoints/detections.py, ~L392) — builds detection cones, finds overlapping sequences, and creates/merges/links alerts.
refresh_alert_state (src/app/services/alerts.py, ~L21) — recomputes an alert's bounds and lat/lon from its member sequences via compute_overlap (src/app/services/overlap.py).
There is no route to change a sequence's azimuth. The only sequence mutations exposed are PATCH /sequences/{id}/label and DELETE /sequences/{id} (src/app/api/api_v1/endpoints/sequences.py). Both, notably, already re-run the alert/triangulation reconciliation after mutating — that's the pattern to follow.
Expected behavior
Expose a new endpoint (suggested: PATCH /sequences/{sequence_id}/azimuth) that, when the azimuth of a sequence is updated:
- Persists the new
sequence_azimuth.
- Recomputes all triangulations involving this sequence (its cone changes, so overlaps with other sequences change).
- Reconciles the linked alerts — create / update / remove
AlertSequence links and alert lat-lon as needed, exactly as sequence creation and labeling already do.
- Leaves the triangulation state consistent across the affected alerts (no orphaned links, no stale alert locations).
The endpoint must be usable both by the frontend (operator refinement) and by an automated CV pipeline (service/admin token).
A suggested approach (can be challenged)
- Add a
SequenceAzimuth request schema in src/app/schemas/sequences.py validating sequence_azimuth in [0, 360) (mirror the model constraint on camera_azimuth).
- In the endpoint: fetch the sequence, persist the new azimuth, then re-run the same attach/reconcile flow the creation path uses. Note for the implementer: the triangulation entry point
_attach_sequence_to_alert currently lives inside the detections endpoint module and is already imported cross-module by src/app/services/validation.py (~L371). Reusing it here (rather than duplicating the logic) likely warrants extracting it into a service module (e.g. app/services/triangulation.py or extending app/services/alerts.py). ?
- After re-attaching, refresh every previously- and newly-linked alert via
refresh_alert_state so alert bounds and location stay correct — including deleting an alert that ends up empty (already handled inside refresh_alert_state).
Auth / roles
Allowed: ADMIN and USER (the acknowledging role).
AGENT must NOT have access. (This intentionally differs from /label, which is ADMIN + AGENT.)
Non-admins are scoped to their own organization -> you might use verify_org_rights (src/app/api/api_v1/endpoints/sequences.py).
Open question to resolve during implementation -> cone angle
Refining the azimuth by clicking a fire origin point conceptually collapses the detection to (near) a point, whereas cone_angle today encodes the bbox width (src/app/services/cones.py) and directly drives cone overlap. So:
- Is updating
cone_angle relevant when the azimuth is refined?
- If yes, what value —> should the endpoint accept a
cone_angle, or should a refined azimuth always snap to a fixed, narrow opening angle (a constant in src/app/core/config.py)?
Acceptance criteria
Happy to discuss it 🖌️
Objective
Today, a sequence's azimuth (
Sequence.sequence_azimuth) is computed once, at sequence creation, from the center of the first detection's bounding box — seeresolve_cone(src/app/services/cones.py) called insrc/app/api/api_v1/endpoints/detections.py(~L582-590). It never changes afterward.We want to be able to refine that azimuth after the fact. Two future sources will need this (handled in separate issues ):
This issue covers the backend workflow only: exposing a clean API entry point that updates the azimuth and keeps the triangulation state consistent.
Note:The source of the new azimuth is not tracked or persisted in this issue.
Current behavior
sequence_azimuth(andcone_angle) feed the whole triangulation pipeline:_attach_sequence_to_alert(src/app/api/api_v1/endpoints/detections.py, ~L392) — builds detection cones, finds overlapping sequences, and creates/merges/links alerts.refresh_alert_state(src/app/services/alerts.py, ~L21) — recomputes an alert's bounds and lat/lon from its member sequences viacompute_overlap(src/app/services/overlap.py).There is no route to change a sequence's azimuth. The only sequence mutations exposed are
PATCH /sequences/{id}/labelandDELETE /sequences/{id}(src/app/api/api_v1/endpoints/sequences.py). Both, notably, already re-run the alert/triangulation reconciliation after mutating — that's the pattern to follow.Expected behavior
Expose a new endpoint (suggested:
PATCH /sequences/{sequence_id}/azimuth) that, when the azimuth of a sequence is updated:sequence_azimuth.AlertSequencelinks and alert lat-lon as needed, exactly as sequence creation and labeling already do.The endpoint must be usable both by the frontend (operator refinement) and by an automated CV pipeline (service/admin token).
A suggested approach (can be challenged)
SequenceAzimuthrequest schema insrc/app/schemas/sequences.pyvalidatingsequence_azimuthin[0, 360)(mirror the model constraint oncamera_azimuth)._attach_sequence_to_alertcurrently lives inside the detections endpoint module and is already imported cross-module bysrc/app/services/validation.py(~L371). Reusing it here (rather than duplicating the logic) likely warrants extracting it into a service module (e.g.app/services/triangulation.pyor extendingapp/services/alerts.py). ?refresh_alert_stateso alert bounds and location stay correct — including deleting an alert that ends up empty (already handled insiderefresh_alert_state).Auth / roles
Allowed:
ADMINandUSER(the acknowledging role).AGENTmust NOT have access. (This intentionally differs from/label, which isADMIN+AGENT.)Non-admins are scoped to their own organization -> you might use
verify_org_rights(src/app/api/api_v1/endpoints/sequences.py).Open question to resolve during implementation -> cone angle
Refining the azimuth by clicking a fire origin point conceptually collapses the detection to (near) a point, whereas
cone_angletoday encodes the bbox width (src/app/services/cones.py) and directly drives cone overlap. So:cone_anglerelevant when the azimuth is refined?cone_angle, or should a refined azimuth always snap to a fixed, narrow opening angle (a constant insrc/app/core/config.py)?Acceptance criteria
sequence_azimuthon a sequence and persists it.ADMIN+USERallowed,AGENTforbidden (403), non-admins limited to their org.[0, 360).cone_anglebehavior decided and implemented per the open question above.src/tests/endpoints/.Happy to discuss it 🖌️