Skip to content

Commit

Permalink
feat: add an edit and remove button when nutriscore prediction is pos…
Browse files Browse the repository at this point in the history
…ted on Slack channel (#783)
  • Loading branch information
Jagrutiti authored Jun 11, 2022
1 parent 34f786b commit 0055ba7
Show file tree
Hide file tree
Showing 8 changed files with 68 additions and 22 deletions.
2 changes: 1 addition & 1 deletion i18n/br/LC_MESSAGES/robotoff.po
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.1\n"
"Plural-Forms: nplurals=5; plural=(n%10==1 && (n%100!=11 || n%100!=71 || n%100!=91) ? 0 : n%10==2 && (n%100!=12 || n%100!=72 || n%100!=92) ? 1 : ((n%10>=3 && n%10<=4) || n%10==9) && ((n%100 < 10 || n%100 > 19) || (n%100 < 70 || n%100 > 79) || (n%100 < 90 || n%100 > 99)) ? 2 : (n!=0 && n%1;\n"
"Plural-Forms: nplurals=5; plural=(n%10==1 && (n%100!=11 || n%100!=71 || n%100!=91) ? 0 : n%10==2 && (n%100!=12 || n%100!=72 || n%100!=92) ? 1 : ((n%10>=3 && n%10<=4) || n%10==9) && ((n%100 < 10 || n%100 > 19) || (n%100 < 70 || n%100 > 79) || (n%100 < 90 || n%100 > 99)) ? 2 : (n!=0 && n%1));\n"
"X-Crowdin-Project: openfoodfacts\n"
"X-Crowdin-Project-ID: 243092\n"
"X-Crowdin-Language: br-FR\n"
Expand Down
2 changes: 2 additions & 0 deletions robotoff/cli/insights.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import sys
from typing import Iterable, List, Optional, Set, TextIO, Union

import _io
import click
import dacite
from more_itertools import chunked
Expand All @@ -31,6 +32,7 @@ def run_from_ocr_archive(
output: Optional[pathlib.Path] = None,
):
predictions = generate_from_ocr_archive(input_, prediction_type)
output_f: _io._TextIOBase

if output is not None:
output_f = output.open("w")
Expand Down
3 changes: 3 additions & 0 deletions robotoff/insights/extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def extract_image_ml_predictions(
# detection.
nutriscore_prediction = extract_nutriscore_label(
image,
source_image,
manual_threshold=0.5,
)

Expand Down Expand Up @@ -118,6 +119,7 @@ def extract_ocr_predictions(

def extract_nutriscore_label(
image: Image.Image,
source_image: str,
manual_threshold: float,
automatic_threshold: Optional[float] = None,
) -> Optional[Prediction]:
Expand All @@ -142,6 +144,7 @@ def extract_nutriscore_label(

return Prediction(
type=PredictionType.label,
source_image=source_image,
value_tag=label_tag,
automatic_processing=automatic_processing,
data={
Expand Down
14 changes: 9 additions & 5 deletions robotoff/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ def batch_insert(model_cls, data: Iterable[Dict], batch_size=100) -> int:
return rows


def crop_image_url(source_image, bounding_box) -> str:
base_url = settings.OFF_IMAGE_BASE_URL + source_image
y_min, x_min, y_max, x_max = bounding_box
base_robotoff_url = settings.BaseURLProvider().robotoff().get()
return f"{base_robotoff_url}/api/v1/images/crop?image_url={base_url}&y_min={y_min}&x_min={x_min}&y_max={y_max}&x_max={x_max}"


class BaseModel(peewee.Model):
class Meta:
database = db
Expand Down Expand Up @@ -261,12 +268,9 @@ class Meta:
constraints = [peewee.SQL("UNIQUE(image_prediction_id, index)")]

def get_crop_image_url(self) -> str:
base_url = (
settings.OFF_IMAGE_BASE_URL + self.image_prediction.image.source_image
return crop_image_url(
self.image_prediction.image.source_image, self.bounding_box
)
y_min, x_min, y_max, x_max = self.bounding_box
base_robotoff_url = settings.BaseURLProvider().robotoff().get()
return f"{base_robotoff_url}/api/v1/images/crop?image_url={base_url}&y_min={y_min}&x_min={x_min}&y_max={y_max}&x_max={x_max}"


class LogoConfidenceThreshold(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion robotoff/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def init_sentry(integrations: Optional[List[Integration]] = None):
event_level=logging.WARNING, # Send warning and errors as events
)
)
sentry_sdk.init(
sentry_sdk.init( # type:ignore # mypy say it's abstract
_sentry_dsn,
environment=_robotoff_instance,
integrations=integrations,
Expand Down
20 changes: 16 additions & 4 deletions robotoff/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from robotoff import settings
from robotoff.insights.dataclass import InsightType
from robotoff.logo_label_type import LogoLabelType
from robotoff.models import LogoAnnotation, ProductInsight
from robotoff.models import LogoAnnotation, ProductInsight, crop_image_url
from robotoff.prediction.types import Prediction
from robotoff.utils import get_logger, http_session
from robotoff.utils.types import JSONType
Expand Down Expand Up @@ -161,12 +161,21 @@ def notify_image_flag(

def notify_automatic_processing(self, insight: ProductInsight):
product_url = f"{settings.BaseURLProvider().get()}/product/{insight.barcode}"
edit_url = f"{settings.BaseURLProvider().get()}/cgi/product.pl?type=edit&code={insight.barcode}"

if insight.source_image:
image_url = f"{settings.BaseURLProvider().static().get()}/images/products{insight.source_image}"
if insight.data and "bounding_box" in insight.data:
image_url = crop_image_url(
insight.source_image, insight.data.get("bounding_box")
)
else:
image_url = f"{settings.BaseURLProvider().static().get()}/images/products{insight.source_image}"
metadata_text = f"(<{product_url}|product>, <{image_url}|source image>)"
else:
metadata_text = f"(<{product_url}|product>)"

edit_text = f"(<{edit_url}|edit>)"

value = insight.value or insight.value_tag

if insight.type in {
Expand All @@ -177,11 +186,14 @@ def notify_automatic_processing(self, insight: ProductInsight):
else:
text = f"The `{value}` {insight.type} was automatically added to product {insight.barcode}"

message = _slack_message_block(text + " " + metadata_text)
message = _slack_message_block(f"{text} {metadata_text}")
nutriscore_message = _slack_message_block(f"{text} {metadata_text} {edit_text}")

if insight.value_tag in self.NUTRISCORE_LABELS:
self._post_message(
message, self.NUTRISCORE_ALERT_CHANNEL, **self.COLLAPSE_LINKS_PARAMS
nutriscore_message,
self.NUTRISCORE_ALERT_CHANNEL,
**self.COLLAPSE_LINKS_PARAMS,
)
return

Expand Down
20 changes: 9 additions & 11 deletions tests/unit/insights/test_extraction.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,14 @@ def detect_from_image(


@pytest.mark.parametrize(
"automatic_threshold, processed_automatically",
"automatic_threshold, processed_automatically, source_image",
[
(
None,
False,
),
(
0.7,
True,
),
(None, False, "/image/1"),
(0.7, True, "/image/1"),
],
)
def test_extract_nutriscore_label_automatic(
mocker, automatic_threshold, processed_automatically
mocker, source_image, automatic_threshold, processed_automatically
):
raw_result = ObjectDetectionRawResult(
num_detections=1,
Expand All @@ -49,7 +43,10 @@ def test_extract_nutriscore_label_automatic(
)

insight = extract_nutriscore_label(
Image.Image, manual_threshold=0.5, automatic_threshold=automatic_threshold
Image.Image,
source_image=source_image,
manual_threshold=0.5,
automatic_threshold=automatic_threshold,
)

assert insight == Prediction(
Expand All @@ -60,6 +57,7 @@ def test_extract_nutriscore_label_automatic(
"model": "nutriscore",
"notify": True,
},
source_image=source_image,
value_tag="en:nutriscore-grade-a",
automatic_processing=processed_automatically,
)
27 changes: 27 additions & 0 deletions tests/unit/test_slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,30 @@ def test_noop_slack_notifier_logging(caplog):

(logged,) = caplog.records
assert logged.msg.startswith("Alerting on slack channel")


def test_notify_automatic_processing_nutriscore(mocker, monkeypatch):
mock = mocker.patch(
"robotoff.slack.http_session.post", return_value=MockSlackResponse()
)
monkeypatch.delenv("ROBOTOFF_SCHEME", raising=False) # force defaults to apply

notifier = slack.SlackNotifier("")

notifier.notify_automatic_processing(
ProductInsight(
barcode="123",
source_image="/image/1",
type="label",
value_tag="en:nutriscore",
data={"bounding_box": (2, 2, 4, 4)},
)
)

mock.assert_called_once_with(
notifier.POST_MESSAGE_URL,
data=PartialRequestMatcher(
f"The `en:nutriscore` label was automatically added to product 123 (<https://world.{settings._robotoff_domain}/product/123|product>, <https://robotoff.{settings._robotoff_domain}/api/v1/images/crop?image_url={settings.OFF_IMAGE_BASE_URL}/image/1&y_min=2&x_min=2&y_max=4&x_max=4|source image>) (<https://world.{settings._robotoff_domain}/cgi/product.pl?type=edit&code=123|edit>)",
notifier.NUTRISCORE_ALERT_CHANNEL,
),
)

0 comments on commit 0055ba7

Please sign in to comment.