Skip to content

Conversation

@MateoLostanlen
Copy link
Member

@MateoLostanlen MateoLostanlen commented Dec 23, 2025

This PR introduces an alerts feature on the API side, including the data model, schemas, CRUD layer, and endpoints. It also integrates alerts into the existing detections and sequences flows.

What changed

Alerts API

  • New alerts endpoint

    • src/app/api/api_v1/endpoints/alerts.py
  • New alert domain layer

    • CRUD: src/app/crud/crud_alert.py
    • Schemas: src/app/schemas/alerts.py
    • Model updates in src/app/models.py
  • Router and dependency wiring updated accordingly

Sequences and detections

  • Updates to sequences.py and detections.py
  • Cone computation performed at sequence creation time
  • Field naming aligned (started_at, last_seen_at)
  • Alert related hooks integrated into the sequence lifecycle

Overlap logic

Scope clarification, intentional follow up

  • A new detection splitting strategy based on bbox and pose_id is planned
  • This change is intentionally not included in this PR to keep the scope manageable
  • The bbox plus pose_id based split will be introduced in a separate follow up PR

How to test

Using the dev environment

Notebook example

@socket-security
Copy link

socket-security bot commented Dec 23, 2025

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednumpy@​1.26.47510010010070
Addednumpy@​2.4.07510010010070
Addedpandas@​2.3.37610010010080
Addednetworkx@​3.697100100100100
Addednetworkx@​3.6.197100100100100
Updatedbotocore@​1.42.13 ⏵ 1.42.1498 -210010010070 -30
Updatednodeenv@​1.9.1 ⏵ 1.10.098 +1100100100100
Addedpyproj@​3.7.298100100100100
Updatedboto3@​1.42.13 ⏵ 1.42.1499 -110010010080 -19
Addedgeopy@​2.4.1100100100100100
Addedgeographiclib@​2.1100100100100100

View full report

@codecov
Copy link

codecov bot commented Dec 23, 2025

Codecov Report

❌ Patch coverage is 76.56613% with 101 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.51%. Comparing base (ac3e175) to head (662300e).

Files with missing lines Patch % Lines
src/app/api/api_v1/endpoints/sequences.py 41.81% 32 Missing ⚠️
src/app/services/overlap.py 84.61% 24 Missing ⚠️
src/app/api/api_v1/endpoints/detections.py 75.34% 18 Missing ⚠️
src/app/api/api_v1/endpoints/alerts.py 79.31% 12 Missing ⚠️
src/app/api/api_v1/endpoints/organizations.py 41.66% 7 Missing ⚠️
src/app/db.py 33.33% 6 Missing ⚠️
src/app/crud/base.py 60.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #538      +/-   ##
==========================================
- Coverage   83.99%   82.51%   -1.48%     
==========================================
  Files          42       47       +5     
  Lines        1318     1687     +369     
==========================================
+ Hits         1107     1392     +285     
- Misses        211      295      +84     
Flag Coverage Δ
backend 82.44% <76.56%> (-1.58%) ⬇️
client 83.67% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@MateoLostanlen MateoLostanlen requested review from fe51 and frgfm and removed request for fe51 December 23, 2025 14:54
@MateoLostanlen MateoLostanlen mentioned this pull request Dec 29, 2025
Copy link
Member

@fe51 fe51 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi there, thanks a lot for the PR.

I have started to review it, here are few comments and questions before going deeper.

pose_id: Union[int, None] = Field(None, foreign_key="poses.id", nullable=True)
azimuth: float = Field(..., ge=0, lt=360)
is_wildfire: Union[AnnotationType, None] = None
cone_azimuth: Union[float, None] = Field(None, nullable=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here cone_azimuth is what is expected to be the azimuth of the sequence right ? Does sequence_azimuth sounds good to you ? We must find something as clear as possible to avoid consfusion

azimuth: float = Field(..., ge=0, lt=360)
is_wildfire: Union[AnnotationType, None] = None
cone_azimuth: Union[float, None] = Field(None, nullable=True)
cone_angle: Union[float, None] = Field(None, nullable=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not we go for angular_width? Also, this one must be in degree, hence a suggestion below

Suggested change
cone_angle: Union[float, None] = Field(None, nullable=True)
angular_width: Union[float, None] = Field(None, ge=0, lt=360, nullable=True)

(modification must be done elsewhere to adapt the naming)


async def get_session() -> AsyncSession: # type: ignore[misc]
async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
async_session = async_sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naive question: why do we need to switch to asynchronous approach here?



class AlertUpdate(BaseModel):
organization_id: Optional[int] = Field(None, gt=0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As AlertCreate and Alertupdate have properties in common, you might create a AlertBase schemas to put shared properties (org id, lat lon, started_at, last_seen_at might be deferent if optinnal in one case but not the other)

class Alert(SQLModel, table=True):
__tablename__ = "alerts"
id: int = Field(None, primary_key=True)
organization_id: int = Field(..., foreign_key="organizations.id", nullable=False)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
organization_id: int = Field(..., foreign_key="organizations.id", nullable=False)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why organization_id is needed here ? Shouldn't we handle it like we do for sequences, and cross-check via the user's scope?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need it to filter alerts, when you fetch alerts using fetch_latest_unlabeled_alerts for exemple you want alerts from your organisation only

telemetry_client.capture(
token_payload.sub, event="organizations-deletion", properties={"organization_id": organization_id}
)
# Remove alerts and their associations for this organization to satisfy FK constraints
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned in the model comments -> if the organisationid is not required in alerts, this part of the code must therefore be deleted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as previous comment



class AlertCreate(BaseModel):
organization_id: int = Field(..., gt=0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

org_id might not be needed in alert db model, hence not useful in schema

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same

)

# Ensure the newly created sequence is present
if all(seq.id != sequence_.id for seq in recent_sequences):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naive question, in which case the sequence could be missing ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when you create a new sequence

"lat": float(cam.lat),
"lon": float(cam.lon),
"cone_azimuth": float(seq.cone_azimuth),
"cone_angle": float(seq.cone_angle),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(maybe to) Rename according to what is chosen as the attribute name in the database model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants