Skip to content

Commit 9cba4af

Browse files
authored
Modify is wildfire column (#497)
* small refactor of the slack notification * add url of the S3 in slack notification * fix migration files * run quality * fix client test * change enum * make style
1 parent 3cf6e39 commit 9cba4af

12 files changed

Lines changed: 165 additions & 21 deletions

client/pyroclient/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,12 @@ def fetch_detections(self) -> Response:
218218
timeout=self.timeout,
219219
)
220220

221-
def label_sequence(self, sequence_id: int, is_wildfire: bool) -> Response:
221+
def label_sequence(self, sequence_id: int, is_wildfire: str) -> Response:
222222
"""Update the label of a sequence made by a camera
223223
224224
>>> from pyroclient import client
225225
>>> api_client = Client("MY_USER_TOKEN")
226-
>>> response = api_client.label_sequence(1, is_wildfire=True)
226+
>>> response = api_client.label_sequence(1, is_wildfire="wildfire_smoke")
227227
228228
Args:
229229
sequence_id: ID of the associated sequence entry

client/tests/test_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_agent_workflow(test_cam_workflow, agent_token):
5454
agent_client = Client(agent_token, "http://localhost:5050", timeout=10)
5555
response = agent_client.fetch_latest_sequences().json()
5656
assert len(response) == 1
57-
response = agent_client.label_sequence(response[0]["id"], True)
57+
response = agent_client.label_sequence(response[0]["id"], "wildfire_smoke")
5858
assert response.status_code == 200, response.__dict__
5959

6060

scripts/dbdiagram.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Table "Sequence" as S {
3737
"id" int [not null]
3838
"camera_id" int [ref: > C.id, not null]
3939
"azimuth" float [not null]
40-
"is_wildfire" bool
40+
"is_wildfire" AnnotationType
4141
"started_at" timestamp [not null]
4242
"last_seen_at" timestamp [not null]
4343
Indexes {

scripts/test_e2e.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,9 @@ def main(args):
152152
== 1
153153
)
154154
# Label the sequence
155-
api_request("patch", f"{args.endpoint}/sequences/{sequence['id']}/label", agent_auth, {"is_wildfire": True})
155+
api_request(
156+
"patch", f"{args.endpoint}/sequences/{sequence['id']}/label", agent_auth, {"is_wildfire": "wildfire_smoke"}
157+
)
156158
# Check the sequence's detections
157159
dets = api_request("get", f"{args.endpoint}/sequences/{sequence['id']}/detections", agent_auth)
158160
assert len(dets) == 3

src/app/models.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class Role(str, Enum):
2727
USER = "user"
2828

2929

30+
class AnnotationType(str, Enum):
31+
WILDFIRE_SMOKE = "wildfire_smoke"
32+
OTHER_SMOKE = "other_smoke"
33+
OTHER = "other"
34+
35+
3036
class User(SQLModel, table=True):
3137
__tablename__ = "users"
3238
id: int = Field(None, primary_key=True)
@@ -69,7 +75,7 @@ class Sequence(SQLModel, table=True):
6975
id: int = Field(None, primary_key=True)
7076
camera_id: int = Field(..., foreign_key="cameras.id", nullable=False)
7177
azimuth: float = Field(..., ge=0, lt=360)
72-
is_wildfire: Union[bool, None] = None
78+
is_wildfire: Union[AnnotationType, None] = None
7379
started_at: datetime = Field(..., nullable=False)
7480
last_seen_at: datetime = Field(..., nullable=False)
7581

src/app/schemas/detections.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@
99
from pydantic import BaseModel, Field
1010

1111
from app.core.config import settings
12-
from app.models import Detection
12+
from app.models import AnnotationType, Detection
1313

1414
__all__ = ["Azimuth", "DetectionCreate", "DetectionLabel", "DetectionUrl"]
1515

1616

1717
class DetectionLabel(BaseModel):
18-
is_wildfire: bool
18+
is_wildfire: AnnotationType
1919

2020

2121
class Azimuth(BaseModel):

src/app/schemas/sequences.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from pydantic import BaseModel
99

10-
from app.models import Sequence
10+
from app.models import AnnotationType, Sequence
1111

1212
__all__ = ["SequenceUpdate", "SequenceWithCone"]
1313

@@ -18,7 +18,7 @@ class SequenceUpdate(BaseModel):
1818

1919

2020
class SequenceLabel(BaseModel):
21-
is_wildfire: bool
21+
is_wildfire: AnnotationType
2222

2323

2424
class SequenceWithCone(Sequence):
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import sqlalchemy as sa
2+
from alembic import op
3+
4+
# Ajoute ton identifiant de révision et dépendance si besoin
5+
revision = "42dzeg392dhu"
6+
down_revision = "4265426f8438"
7+
branch_labels = None
8+
depends_on = None
9+
10+
11+
def upgrade() -> None:
12+
# 1. Créer la table sequences (doit précéder la FK)
13+
op.create_table(
14+
"sequences",
15+
sa.Column("id", sa.Integer(), primary_key=True),
16+
sa.Column("camera_id", sa.Integer(), sa.ForeignKey("camera.id"), nullable=False),
17+
sa.Column("azimuth", sa.Float(), nullable=False),
18+
sa.Column("is_wildfire", sa.Boolean(), nullable=True), # sera modifié par la 4e migration
19+
sa.Column("started_at", sa.DateTime(), nullable=False),
20+
sa.Column("last_seen_at", sa.DateTime(), nullable=False),
21+
)
22+
23+
# 2. Ajouter les colonnes manquantes
24+
op.add_column("camera", sa.Column("last_image", sa.String(), nullable=True))
25+
op.add_column("organization", sa.Column("telegram_id", sa.String(), nullable=True))
26+
op.add_column("detection", sa.Column("sequence_id", sa.Integer(), nullable=True))
27+
op.add_column("detection", sa.Column("bboxes", sa.String(length=5000), nullable=False)) # adapter à settings
28+
29+
# 3. Ajouter la contrainte FK après la création de la table sequences
30+
op.create_foreign_key(
31+
"fk_detection_sequence",
32+
"detection",
33+
"sequences",
34+
["sequence_id"],
35+
["id"],
36+
)
37+
38+
# 4. Créer la table webhooks
39+
op.create_table(
40+
"webhooks",
41+
sa.Column("id", sa.Integer(), primary_key=True),
42+
sa.Column("url", sa.String(), nullable=False, unique=True),
43+
)
44+
45+
46+
def downgrade() -> None:
47+
op.drop_table("webhooks")
48+
op.drop_constraint("fk_detection_sequence", "detection", type_="foreignkey")
49+
op.drop_column("detection", "bboxes")
50+
op.drop_column("detection", "sequence_id")
51+
op.drop_column("organization", "telegram_id")
52+
op.drop_column("camera", "last_image")
53+
op.drop_table("sequences")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Add Slack Hook
2+
Revision ID: 2853acd1fc32
3+
Revises: 4265426f8438
4+
Create Date: 2025-06-25 17:20:14.959429
5+
"""
6+
7+
from typing import Sequence, Union
8+
9+
import sqlalchemy as sa
10+
import sqlmodel
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision: str = "2853acd1fc32"
15+
down_revision: Union[str, None] = "42dzeg392dhu"
16+
branch_labels: Union[str, Sequence[str], None] = None
17+
depends_on: Union[str, Sequence[str], None] = None
18+
19+
20+
def upgrade() -> None:
21+
op.add_column("organization", sa.Column("slack_hook", sqlmodel.sql.sqltypes.AutoString(), nullable=True))
22+
23+
24+
def downgrade() -> None:
25+
op.drop_column("organization", "slack_hook")
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"""modify is_wilfire column
2+
3+
Revision ID: 307a1d6d490d
4+
Revises: 2853acd1fc32
5+
Create Date: 2025-08-20 16:47:05.346210
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "307a1d6d490d"
16+
down_revision: Union[str, None] = "2853acd1fc32"
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
# Define the new ENUM type
21+
annotation_type_enum = sa.Enum(
22+
"WILDFIRE_SMOKE",
23+
"OTHER_SMOKE",
24+
"OTHER",
25+
name="annotationtype",
26+
)
27+
28+
29+
def upgrade():
30+
# Create the enum type in the database
31+
annotation_type_enum.create(op.get_bind(), checkfirst=True)
32+
33+
# Use raw SQL with a CASE expression for the conversion
34+
op.execute("""
35+
ALTER TABLE sequences
36+
ALTER COLUMN is_wildfire
37+
TYPE annotationtype
38+
USING CASE
39+
WHEN is_wildfire = TRUE THEN 'WILDFIRE_SMOKE'::annotationtype
40+
ELSE 'OTHER'::annotationtype
41+
END
42+
""")
43+
44+
45+
def downgrade():
46+
# Revert the column back to a boolean (or previous enum if applicable)
47+
op.execute("""
48+
ALTER TABLE sequences
49+
ALTER COLUMN is_wildfire
50+
TYPE boolean
51+
USING CASE
52+
WHEN is_wildfire = 'WILDFIRE_SMOKE' THEN TRUE
53+
ELSE FALSE
54+
END
55+
""")
56+
57+
# Drop the enum type from the DB
58+
annotation_type_enum.drop(op.get_bind(), checkfirst=True)

0 commit comments

Comments
 (0)