File size: 5,317 Bytes
e01620b
af7959d
e01620b
 
 
 
d008f82
af7959d
98db947
e01620b
 
 
 
 
0c7edbd
e01620b
 
 
 
98db947
af7959d
e01620b
 
 
 
af7959d
e01620b
 
 
af7959d
e01620b
d008f82
 
 
 
 
e01620b
98db947
e01620b
 
0c7edbd
98db947
0c7edbd
 
98db947
af7959d
 
e01620b
 
 
 
 
 
8955797
0c7edbd
e01620b
 
0c7edbd
 
 
e01620b
 
 
 
 
 
 
 
0c7edbd
e01620b
 
 
 
 
 
0c7edbd
 
e01620b
8955797
af7959d
 
 
 
 
0c7edbd
af7959d
0c7edbd
 
 
d008f82
 
 
 
 
 
 
 
 
 
 
161967b
 
d008f82
 
0c7edbd
af7959d
 
 
e01620b
 
 
af7959d
 
e01620b
 
0c7edbd
af7959d
e01620b
0c7edbd
 
 
 
e01620b
 
 
 
 
 
 
0c7edbd
 
 
e01620b
 
 
 
 
 
 
 
161967b
 
e01620b
 
 
0c7edbd
 
 
 
98db947
 
 
 
 
 
e01620b
98db947
 
 
 
 
e01620b
 
 
 
 
 
0c7edbd
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
from typing import Dict, Tuple, Optional, TypeAlias
import requests
import base64
import json
from dataclasses import dataclass

from utils import get_logger, LANGS

from openfoodfacts.api import ProductResource, APIConfig
import gradio as gr


logger = get_logger()

BASE_URL = "https://robotoff.openfoodfacts.org/api/v1"  # Prod
# BASE_URL = "http://localhost:5500/api/v1" # Dev
UPDATE = 1

Annotation: TypeAlias = Tuple[str, str, str, str, str]


@dataclass
class Authentification:
    username: str
    password: str

    def get_credentials(self) -> str:
        credentials = f"{self.username}:{self.password}"
        return base64.b64encode(credentials.encode()).decode()


def next_annotation(lang: Optional[str]) -> Annotation:
    lang_key = LANGS.get(lang)
    if not lang_key:
        logger.warning("The provided Lang was not found. 'All' returned by default.")
    insight = import_random_insight(lang_key=lang_key)
    logger.info("Imported insight: %s", insight)
    return (
        insight["id"],
        insight["data"]["original"],
        insight["data"]["correction"],  # Saved for comparison with annotator changes
        insight["data"]["correction"],
        get_image_url(insight["barcode"]),
        get_product_url(insight["barcode"]),
    )


def submit_correction(
    insight_id: str,
    annotator_correction: str,
    model_correction: str,
    username: str,
    password: str,
    lang: str,
    update: bool = UPDATE,
):
    auth = Authentification(username, password)
    correction = (
        annotator_correction if annotator_correction != model_correction else None
    )
    try:
        submit_to_product_opener(
            insight_id=insight_id,
            update=update,
            annotator_correction=correction,
            auth=auth,
        )
    except gr.Error as e:
        gr.Warning(e.message)  # We use gr.Warning instead of gr.Error to keep the flow
        return (
            gr.update(),
            gr.update(),
            gr.update(),
            gr.update(),
            gr.update(),
            gr.update(),
        )  # Stay unchanged
    gr.Info("Product successfuly updated. Many thanks!")
    return next_annotation(lang=lang)


def import_random_insight(
    insight_type: str = "ingredient_spellcheck",
    predictor: str = "fine-tuned-mistral-7b",
    lang_key: Optional[str] = None,
) -> Dict:
    url = (
        f"{BASE_URL}/insights/random?count=1&type={insight_type}&predictor={predictor}"
    )
    if lang_key:
        url += f"&value_tag={lang_key}"
    try:
        response = requests.get(url)
        response.raise_for_status()  # Raises an HTTPError for bad responses (4xx or 5xx)
        data = response.json()
        if "insights" in data and data["insights"]:
            return data["insights"][0]
        else:
            gr.Warning("No insights found; fetching default insight instead.")
            return import_random_insight(insight_type, predictor)
    except requests.ReadTimeout:
        gr.Error("There's an issue with the server... Wait a couple of minutes and try again.")
    except requests.RequestException as e:
        gr.Error(f"Import product from Product Opener failed: {e}")


def submit_to_product_opener(
    insight_id: str,
    update: bool,
    auth: Authentification,
    annotator_correction: Optional[str] = None,
) -> None:
    url = f"{BASE_URL}/insights/annotate"
    headers = {
        "Authorization": f"Basic {auth.get_credentials()}",
        "Content-Type": "application/x-www-form-urlencoded",
    }
    if annotator_correction:
        logger.info(
            "Change from annotator. New insight sent to Product Opener. New correction: %s",
            annotator_correction,
        )
        payload = {
            "insight_id": insight_id,
            "annotation": 2,
            "update": update,
            "data": json.dumps({"annotation": annotator_correction}),
        }
    else:
        logger.info(
            "No change from annotator. Original insight sent to Product Opener."
        )
        payload = {
            "insight_id": insight_id,
            "annotation": 1,
            "update": update,
        }
    try:
        response = requests.post(url, data=payload, headers=headers)
        response.raise_for_status()
    except requests.ReadTimeout:
        gr.Error("There's an issue with the server... Wait a couple of minutes and try again.")
    except requests.RequestException as e:
        logger.error(e)
        logger.error(response.content)
        raise gr.Error(
            "Failed to submit to Product Opener. Are your username and password correct?"
        )


def get_image_url(
    code: str,
    user_agent: str = "Spellcheck-Annotate",
) -> str:
    fields = ["image_ingredients_url"]
    data = ProductResource(api_config=APIConfig(user_agent=user_agent)).get(
        code=code,
        fields=fields,
    )
    image_url = data.get(fields[0])
    return image_url


def enable_buttons(username, password):
    # Return the updated button states: interactive if both username and password are filled
    state = bool(username) and bool(password)
    return gr.update(interactive=state), gr.update(interactive=state)


def get_product_url(barcode: int) -> str:
    return f"https://world.openfoodfacts.org/product/{barcode}"