Skip to content

Commit

Permalink
feat: improve Slack & nutripatrol notifier (#1343)
Browse files Browse the repository at this point in the history
* feat: improve Slack & nutripatrol notifier

- don't publish anymore on Slack public channel
- specify the reason when pushing to nutripatrol
- remove the likelihood key

* fix: fix mypy issue
  • Loading branch information
raphael0202 committed May 15, 2024
1 parent beef9a4 commit 076fb11
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 66 deletions.
3 changes: 2 additions & 1 deletion robotoff/app/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -895,7 +895,8 @@ def on_get(self, req: falcon.Request, resp: falcon.Response):
result = model.detect_from_image(image, output_image=output_image)

if output_image:
image_response(result.boxed_image, resp)
boxed_image = cast(Image.Image, result.boxed_image)
image_response(boxed_image, resp)
return
else:
predictions[model_name] = result.to_json()
Expand Down
125 changes: 72 additions & 53 deletions robotoff/notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,27 +73,30 @@ def get_notifier() -> NotifierInterface:
return MultiNotifier(notifiers)


HUMAN_FLAG_LABELS = {
"face",
"head",
"selfie",
"hair",
"forehead",
"chin",
"cheek",
"tooth",
"eyebrow",
"ear",
"neck",
"nose",
"facial expression",
"child",
"baby",
"human",
}


def _sensitive_image(flag_type: str, flagged_label: str) -> bool:
"""Determines whether the given flagged image should be considered as
sensitive."""
is_human: bool = flagged_label in {
"face",
"head",
"selfie",
"hair",
"forehead",
"chin",
"cheek",
"tooth",
"eyebrow",
"ear",
"neck",
"nose",
"facial expression",
"child",
"baby",
"human",
}
is_human = flagged_label in HUMAN_FLAG_LABELS
return (
is_human and flag_type == "label_annotation"
) or flag_type == "safe_search_annotation"
Expand Down Expand Up @@ -174,34 +177,50 @@ def notify_image_flag(
if not predictions:
return

prediction = predictions[0]
image_url = settings.BaseURLProvider.image_url(
product_id.server_type, source_image
)
image_id = source_image.rsplit("/", 1)[-1].split(".", 1)[0]
data = {
"barcode": product_id.barcode,
"type": "image",
"url": image_url,
"user_id": "roboto-app",
"source": "robotoff",
"confidence": prediction.confidence,
"image_id": image_id,
"flavor": product_id.server_type.value,
"comment": json.dumps(prediction.data),
}
try:
logger.info("Notifying image %s to moderation service", image_url)
http_session.post(self.service_url, json=data)
except Exception:
logger.exception(
"Error while notifying image to moderation service",
extra={
"params": data,
"url": image_url,
"barcode": product_id.barcode,
},
)
for prediction in predictions:
reason = "other"
prediction_subtype = prediction.data.get("type")
prediction_label = prediction.data.get("label")
if prediction_subtype == "safe_search_annotation":
reason = "inappropriate"
elif (
prediction_subtype == "label_annotation"
and prediction_label in HUMAN_FLAG_LABELS
):
reason = "human"
elif prediction_subtype == "text" and prediction_label == "beauty":
reason = "beauty"

data = {
"barcode": product_id.barcode,
"type": "image",
"url": image_url,
"user_id": "roboto-app",
"source": "robotoff",
"confidence": prediction.confidence,
"image_id": image_id,
"flavor": product_id.server_type.value,
"reason": reason,
"comment": json.dumps(
{k: v for k, v in prediction.data.items() if k != "likelihood"}
),
}
try:
logger.info("Notifying image %s to moderation service", image_url)
http_session.post(self.service_url, json=data)
except Exception:
logger.exception(
"Error while notifying image to moderation service",
extra={
"params": data,
"url": image_url,
"barcode": product_id.barcode,
},
)


class SlackNotifier(NotifierInterface):
Expand All @@ -210,7 +229,6 @@ class SlackNotifier(NotifierInterface):
# Slack channel IDs.
ROBOTOFF_ALERT_CHANNEL = "CGKPALRCG" # robotoff-alerts-annotations
ROBOTOFF_PRIVATE_IMAGE_ALERT_CHANNEL = "GGMRWLEF2" # moderation-off-alerts-private
ROBOTOFF_PUBLIC_IMAGE_ALERT_CHANNEL = "CT2N423PA" # moderation-off-alerts

BASE_URL = "https://slack.com/api"
POST_MESSAGE_URL = BASE_URL + "/chat.postMessage"
Expand All @@ -234,14 +252,14 @@ def notify_image_flag(
return

text = ""
slack_channel: str = self.ROBOTOFF_PUBLIC_IMAGE_ALERT_CHANNEL
slack_channel = self.ROBOTOFF_PRIVATE_IMAGE_ALERT_CHANNEL

for flagged in predictions:
flag_type = flagged.data["type"]
label = flagged.data["label"]

if _sensitive_image(flag_type, label):
slack_channel = self.ROBOTOFF_PRIVATE_IMAGE_ALERT_CHANNEL
if not _sensitive_image(flag_type, label):
continue

if flag_type in ("safe_search_annotation", "label_annotation"):
likelihood = flagged.data["likelihood"]
Expand All @@ -250,15 +268,16 @@ def notify_image_flag(
match_text = flagged.data["text"]
text += f"type: {flag_type}\nlabel: *{label}*, match: {match_text}\n"

edit_url = f"{settings.BaseURLProvider.world(product_id.server_type)}/cgi/product.pl?type=edit&code={product_id.barcode}"
image_url = settings.BaseURLProvider.image_url(
product_id.server_type, source_image
)
if text:
edit_url = f"{settings.BaseURLProvider.world(product_id.server_type)}/cgi/product.pl?type=edit&code={product_id.barcode}"
image_url = settings.BaseURLProvider.image_url(
product_id.server_type, source_image
)

full_text = f"{text}\n <{image_url}|Image> -- <{edit_url}|*Edit*>"
message = _slack_message_block(full_text, with_image=image_url)
full_text = f"{text}\n <{image_url}|Image> -- <{edit_url}|*Edit*>"
message = _slack_message_block(full_text, with_image=image_url)

self._post_message(message, slack_channel, **self.COLLAPSE_LINKS_PARAMS)
self._post_message(message, slack_channel, **self.COLLAPSE_LINKS_PARAMS)

def notify_automatic_processing(self, insight: ProductInsight):
server_type = ServerType[insight.server_type]
Expand Down
16 changes: 4 additions & 12 deletions tests/unit/test_notifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,17 +103,7 @@ def test_notify_image_flag_public(mocker, monkeypatch):
DEFAULT_PRODUCT_ID,
)

assert len(mock_http.mock_calls) == 2
mock_http.assert_any_call(
slack_notifier.POST_MESSAGE_URL,
data=PartialRequestMatcher(
f"type: SENSITIVE\nlabel: *flagged*, match: bad_word\n\n <{settings.BaseURLProvider.image_url(DEFAULT_SERVER_TYPE, '/source_image/2.jpg')}|Image> -- <{settings.BaseURLProvider.world(DEFAULT_SERVER_TYPE)}/cgi/product.pl?type=edit&code=123|*Edit*>",
slack_notifier.ROBOTOFF_PUBLIC_IMAGE_ALERT_CHANNEL,
settings.BaseURLProvider.image_url(
DEFAULT_SERVER_TYPE, "/source_image/2.jpg"
),
),
)
assert len(mock_http.mock_calls) == 1
mock_http.assert_any_call(
"https://images.org",
json={
Expand All @@ -125,6 +115,7 @@ def test_notify_image_flag_public(mocker, monkeypatch):
"confidence": None,
"image_id": "2",
"flavor": "off",
"reason": "other",
"comment": '{"text": "bad_word", "type": "SENSITIVE", "label": "flagged"}',
},
)
Expand Down Expand Up @@ -173,7 +164,8 @@ def test_notify_image_flag_private(mocker, monkeypatch):
"source": "robotoff",
"image_id": "2",
"flavor": "off",
"comment": '{"type": "label_annotation", "label": "face", "likelihood": 0.8}',
"reason": "human",
"comment": '{"type": "label_annotation", "label": "face"}',
"confidence": 0.8,
},
)
Expand Down

0 comments on commit 076fb11

Please sign in to comment.