Skip to content

Commit af5fb41

Browse files
use 3 labels (#263)
1 parent 5f5ba1c commit af5fb41

5 files changed

Lines changed: 85 additions & 40 deletions

File tree

app/callbacks/display_callbacks.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -628,12 +628,21 @@ def update_location_and_parcel_info(opacity, smoke_location, lang):
628628
return children, {"display": "block", "marginTop": "0px", "padding": "6px 0"}
629629

630630

631+
# Button id → enum string mapping
632+
BUTTON_TO_ENUM = {
633+
"confirm-fire": "wildfire_smoke",
634+
"confirm-other-smoke": "other_smoke",
635+
"confirm-false": "other",
636+
}
637+
638+
631639
@app.callback(
632640
[Output("confirmation-modal", "style"), Output("to_acknowledge", "data")],
633641
[
634642
Input("acknowledge-button", "n_clicks"),
635-
Input("confirm-wildfire", "n_clicks"),
636-
Input("confirm-non-wildfire", "n_clicks"),
643+
Input("confirm-fire", "n_clicks"),
644+
Input("confirm-other-smoke", "n_clicks"),
645+
Input("confirm-false", "n_clicks"),
637646
Input("cancel-confirmation", "n_clicks"),
638647
],
639648
[
@@ -643,7 +652,7 @@ def update_location_and_parcel_info(opacity, smoke_location, lang):
643652
prevent_initial_call=True,
644653
)
645654
def acknowledge_event(
646-
acknowledge_clicks, confirm_wildfire, confirm_non_wildfire, cancel, sequence_id_on_display, user_token
655+
acknowledge_clicks, confirm_fire, confirm_other_smoke, confirm_false, cancel, sequence_id_on_display, user_token
647656
):
648657
ctx = dash.callback_context
649658

@@ -669,24 +678,26 @@ def acknowledge_event(
669678
client.token = user_token
670679

671680
if triggered_id == "acknowledge-button":
672-
if acknowledge_clicks is None or acknowledge_clicks == 0:
681+
if not acknowledge_clicks:
673682
raise PreventUpdate
674683
return modal_visible_style, dash.no_update
675684

676-
elif triggered_id == "confirm-wildfire":
677-
if confirm_wildfire is None or confirm_wildfire == 0:
685+
elif triggered_id in BUTTON_TO_ENUM:
686+
# guard for specific button click
687+
if triggered_id == "confirm-fire" and not confirm_fire:
678688
raise PreventUpdate
679-
client.label_sequence(sequence_id_on_display, True)
680-
return modal_hidden_style, sequence_id_on_display
681-
682-
elif triggered_id == "confirm-non-wildfire":
683-
if confirm_non_wildfire is None or confirm_non_wildfire == 0:
689+
if triggered_id == "confirm-other-smoke" and not confirm_other_smoke:
684690
raise PreventUpdate
685-
client.label_sequence(sequence_id_on_display, False)
691+
if triggered_id == "confirm-false" and not confirm_false:
692+
raise PreventUpdate
693+
694+
enum_value = BUTTON_TO_ENUM[triggered_id]
695+
client.label_sequence(sequence_id_on_display, enum_value)
696+
686697
return modal_hidden_style, sequence_id_on_display
687698

688699
elif triggered_id == "cancel-confirmation":
689-
if cancel is None or cancel == 0:
700+
if not cancel:
690701
raise PreventUpdate
691702
return modal_hidden_style, dash.no_update
692703

app/pages/homepage.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -233,27 +233,41 @@ def homepage_layout(user_token, api_cameras, lang="fr", descending_order=True):
233233
html.Div(
234234
[
235235
html.Button(
236-
translate("confirmation_modal_yes", lang),
237-
id="confirm-wildfire",
236+
translate("confirmation_modal_fire", lang),
237+
id="confirm-fire",
238238
n_clicks=0,
239239
style={
240240
"margin-right": "10px",
241241
"padding": "10px 20px",
242-
"background-color": "#4CAF50",
242+
"background-color": "#f44336", # red
243243
"color": "white",
244244
"border": "none",
245245
"border-radius": "5px",
246246
"cursor": "pointer",
247247
},
248248
),
249249
html.Button(
250-
translate("confirmation_modal_no", lang),
251-
id="confirm-non-wildfire",
250+
translate("confirmation_modal_other_smoke", lang),
251+
id="confirm-other-smoke",
252252
n_clicks=0,
253253
style={
254254
"margin-right": "10px",
255255
"padding": "10px 20px",
256-
"background-color": "#f44336",
256+
"background-color": "#ff9800", # orange
257+
"color": "white",
258+
"border": "none",
259+
"border-radius": "5px",
260+
"cursor": "pointer",
261+
},
262+
),
263+
html.Button(
264+
translate("confirmation_modal_false", lang),
265+
id="confirm-false",
266+
n_clicks=0,
267+
style={
268+
"margin-right": "10px",
269+
"padding": "10px 20px",
270+
"background-color": "#4CAF50", # green
257271
"color": "white",
258272
"border": "none",
259273
"border-radius": "5px",

app/translations.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,10 @@
2323
"show_hide_prediction": "Afficher / Cacher la prédiction",
2424
"download_image": "Télécharger l'image",
2525
"acknowledge_alert": "Acquitter l'alerte",
26-
"confirmation_modal_title": "Est-ce une fumée suspecte ?",
27-
"confirmation_modal_yes": "Oui, c'est une fumée",
28-
"confirmation_modal_no": "Non, c'est un faux positif",
26+
"confirmation_modal_title": "Quel type de fumée est-ce ?",
27+
"confirmation_modal_fire": "C'est un feu",
28+
"confirmation_modal_other_smoke": "C'est une autre fumée",
29+
"confirmation_modal_false": "C'est un faux positif",
2930
"confirmation_modal_cancel": "Annuler",
3031
"enlarge_map": "Agrandir la carte",
3132
"alert_information": "Information Alerte",
@@ -100,9 +101,10 @@
100101
"show_hide_prediction": "Mostrar / Ocultar la predicción",
101102
"download_image": "Descargar la imagen",
102103
"acknowledge_alert": "Reconocer la alerta",
103-
"confirmation_modal_title": "¿Es un humo sospechoso?",
104-
"confirmation_modal_yes": "Sí, es un humo",
105-
"confirmation_modal_no": "No, es un falso positivo",
104+
"confirmation_modal_title": "¿Qué tipo de humo es?",
105+
"confirmation_modal_fire": "Es un incendio",
106+
"confirmation_modal_other_smoke": "Es otro humo",
107+
"confirmation_modal_false": "Es un falso positivo",
106108
"confirmation_modal_cancel": "Cancelar",
107109
"enlarge_map": "Ampliar el mapa",
108110
"alert_information": "Información sobre alerta",
@@ -179,9 +181,10 @@
179181
"show_hide_prediction": "Show / Hide Prediction",
180182
"download_image": "Download Image",
181183
"acknowledge_alert": "Acknowledge Alert",
182-
"confirmation_modal_title": "Is this suspicious smoke?",
183-
"confirmation_modal_yes": "Yes, it's smoke",
184-
"confirmation_modal_no": "No, it's a false positive",
184+
"confirmation_modal_title": "What type of smoke is it?",
185+
"confirmation_modal_fire": "It is a fire",
186+
"confirmation_modal_other_smoke": "It is another smoke",
187+
"confirmation_modal_false": "It is a false positive",
185188
"confirmation_modal_cancel": "Cancel",
186189
"enlarge_map": "Enlarge Map",
187190
"alert_information": "Alert Information",

app/utils/data.py

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,28 +195,43 @@ def project_polygon(polygon):
195195

196196

197197
def sequences_have_changed(df1, df2, cols_to_check=None):
198+
import pandas as pd
199+
200+
# Default to new schema
198201
if cols_to_check is None:
199-
cols_to_check = ["last_seen_at_local", "is_wildfire"]
202+
cols_to_check = ["last_seen_at_local", "label"]
200203

204+
# Shape change means changed
201205
if df1.shape != df2.shape:
202206
return True
203207

208+
# Required columns missing means changed
204209
if not all(col in df1.columns and col in df2.columns for col in cols_to_check):
205210
return True
206211

207212
df1_checked = df1[cols_to_check].copy()
208213
df2_checked = df2[cols_to_check].copy()
209214

210215
for col in cols_to_check:
211-
if "datetime" in str(df1_checked[col].dtype) or "date" in col:
216+
# Normalize datetimes to seconds
217+
if "datetime" in str(df1_checked[col].dtype) or "date" in col or "time" in col:
212218
df1_checked[col] = pd.to_datetime(df1_checked[col], errors="coerce").dt.round("s")
213219
df2_checked[col] = pd.to_datetime(df2_checked[col], errors="coerce").dt.round("s")
214-
elif col == "is_wildfire":
215-
# Do NOT fillna; keep None/NA to detect changes
216-
df1_checked[col] = df1_checked[col].astype("boolean")
217-
df2_checked[col] = df2_checked[col].astype("boolean")
218220

219-
# Sort by id if available
221+
# Legacy support when comparing a boolean column
222+
elif col == "is_wildfire":
223+
# Keep NA to detect changes, cast only if boolean like
224+
if df1_checked[col].dtype == bool or df2_checked[col].dtype == bool:
225+
df1_checked[col] = df1_checked[col].astype("boolean")
226+
df2_checked[col] = df2_checked[col].astype("boolean")
227+
228+
# New schema label, keep strings and NA as is
229+
elif col == "label":
230+
# Ensure object dtype, do not fillna so NA stays comparable
231+
df1_checked[col] = df1_checked[col].astype("object")
232+
df2_checked[col] = df2_checked[col].astype("object")
233+
234+
# Stable index for comparison
220235
if "id" in df1.columns and "id" in df2.columns:
221236
df1_checked.index = df1["id"]
222237
df2_checked.index = df2["id"]
@@ -227,7 +242,7 @@ def sequences_have_changed(df1, df2, cols_to_check=None):
227242
df1_checked = df1_checked.sort_index()
228243
df2_checked = df2_checked.sort_index()
229244

230-
# Compare while preserving NA values
245+
# pandas.DataFrame.equals treats NaN as equal, which is desired here
231246
return not df1_checked.equals(df2_checked)
232247

233248

@@ -388,7 +403,7 @@ def compute_overlap(api_sequences, R_km=35, r_min_km=0.5, max_dist_km=2.0, unmat
388403
df["started_at"] = pd.to_datetime(df["started_at"])
389404
df["last_seen_at"] = pd.to_datetime(df["last_seen_at"])
390405

391-
df_valid = df[df["is_wildfire"] != 0.0]
406+
df_valid = df[df["is_wildfire"].isin([None, "wildfire_smoke"])]
392407

393408
projected_cones = {row["id"]: get_projected_cone(row, R_km, r_min_km) for _, row in df_valid.iterrows()}
394409

app/utils/display.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -237,10 +237,12 @@ def create_sequence_list(api_sequences, cameras, event_id_table):
237237
# Sort events by most recent first
238238
event_id_table = event_id_table.sort_values("time", ascending=False)
239239

240-
def get_annotation_emoji(value):
241-
if value == 1.0:
240+
def get_annotation_emoji(value: str) -> str:
241+
if value == "wildfire_smoke":
242242
return "🔥"
243-
elif value == 0.0:
243+
elif value == "other_smoke":
244+
return "🏭" # chimney / smoke stack
245+
elif value == "other":
244246
return "🚫"
245247
return ""
246248

0 commit comments

Comments
 (0)