We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[model:accessibility] Add the model to `http_service` and `data_pipeline`
Depends on merging: #3775

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `http_service/bugbug_http/models.py`
Content:
```
1 # -*- coding: utf-8 -*-
2 # This Source Code Form is subject to the terms of the Mozilla Public
3 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 # You can obtain one at http://mozilla.org/MPL/2.0/.
5 
6 import logging
7 import os
8 from datetime import timedelta
9 from functools import lru_cache
10 from typing import Sequence
11 
12 import orjson
13 import requests
14 import zstandard
15 from redis import Redis
16 
17 from bugbug import bugzilla, repository, test_scheduling
18 from bugbug.github import Github
19 from bugbug.model import Model
20 from bugbug.models import testselect
21 from bugbug.utils import get_hgmo_stack
22 from bugbug_http.readthrough_cache import ReadthroughTTLCache
23 
24 logging.basicConfig(level=logging.INFO)
25 LOGGER = logging.getLogger()
26 
27 MODELS_NAMES = [
28     "defectenhancementtask",
29     "component",
30     "invalidcompatibilityreport",
31     "needsdiagnosis",
32     "regression",
33     "stepstoreproduce",
34     "spambug",
35     "testlabelselect",
36     "testgroupselect",
37 ]
38 
39 DEFAULT_EXPIRATION_TTL = 7 * 24 * 3600  # A week
40 redis = Redis.from_url(os.environ.get("REDIS_URL", "redis://localhost/0"))
41 
42 MODEL_CACHE: ReadthroughTTLCache[str, Model] = ReadthroughTTLCache(
43     timedelta(hours=1), lambda m: Model.load(f"{m}model")
44 )
45 MODEL_CACHE.start_ttl_thread()
46 
47 cctx = zstandard.ZstdCompressor(level=10)
48 
49 
50 def setkey(key: str, value: bytes, compress: bool = False) -> None:
51     LOGGER.debug(f"Storing data at {key}: {value!r}")
52     if compress:
53         value = cctx.compress(value)
54     redis.set(key, value)
55     redis.expire(key, DEFAULT_EXPIRATION_TTL)
56 
57 
58 def classify_bug(model_name: str, bug_ids: Sequence[int], bugzilla_token: str) -> str:
59     from bugbug_http.app import JobInfo
60 
61     # This should be called in a process worker so it should be safe to set
62     # the token here
63     bug_ids_set = set(map(int, bug_ids))
64     bugzilla.set_token(bugzilla_token)
65 
66     bugs = bugzilla.get(bug_ids)
67 
68     missing_bugs = bug_ids_set.difference(bugs.keys())
69 
70     for bug_id in missing_bugs:
71         job = JobInfo(classify_bug, model_name, bug_id)
72 
73         # TODO: Find a better error format
74         setkey(job.result_key, orjson.dumps({"available": False}))
75 
76     if not bugs:
77         return "NOK"
78 
79     model = MODEL_CACHE.get(model_name)
80 
81     if not model:
82         LOGGER.info("Missing model %r, aborting" % model_name)
83         return "NOK"
84 
85     model_extra_data = model.get_extra_data()
86 
87     # TODO: Classify could choke on a single bug which could make the whole
88     # job to fails. What should we do here?
89     probs = model.classify(list(bugs.values()), True)
90     indexes = probs.argmax(axis=-1)
91     suggestions = model.le.inverse_transform(indexes)
92 
93     probs_list = probs.tolist()
94     indexes_list = indexes.tolist()
95     suggestions_list = suggestions.tolist()
96 
97     for i, bug_id in enumerate(bugs.keys()):
98         data = {
99             "prob": probs_list[i],
100             "index": indexes_list[i],
101             "class": suggestions_list[i],
102             "extra_data": model_extra_data,
103         }
104 
105         job = JobInfo(classify_bug, model_name, bug_id)
106         setkey(job.result_key, orjson.dumps(data), compress=True)
107 
108         # Save the bug last change
109         setkey(job.change_time_key, bugs[bug_id]["last_change_time"].encode())
110 
111     return "OK"
112 
113 
114 def classify_issue(
115     model_name: str, owner: str, repo: str, issue_nums: Sequence[int]
116 ) -> str:
117     from bugbug_http.app import JobInfo
118 
119     github = Github(owner=owner, repo=repo)
120 
121     issue_ids_set = set(map(int, issue_nums))
122 
123     issues = {
124         issue_num: github.fetch_issue_by_number(owner, repo, issue_num, True)
125         for issue_num in issue_nums
126     }
127 
128     missing_issues = issue_ids_set.difference(issues.keys())
129 
130     for issue_id in missing_issues:
131         job = JobInfo(classify_issue, model_name, owner, repo, issue_id)
132 
133         # TODO: Find a better error format
134         setkey(job.result_key, orjson.dumps({"available": False}))
135 
136     if not issues:
137         return "NOK"
138 
139     model = MODEL_CACHE.get(model_name)
140 
141     if not model:
142         LOGGER.info("Missing model %r, aborting" % model_name)
143         return "NOK"
144 
145     model_extra_data = model.get_extra_data()
146 
147     # TODO: Classify could choke on a single bug which could make the whole
148     # job to fail. What should we do here?
149     probs = model.classify(list(issues.values()), True)
150     indexes = probs.argmax(axis=-1)
151     suggestions = model.le.inverse_transform(indexes)
152 
153     probs_list = probs.tolist()
154     indexes_list = indexes.tolist()
155     suggestions_list = suggestions.tolist()
156 
157     for i, issue_id in enumerate(issues.keys()):
158         data = {
159             "prob": probs_list[i],
160             "index": indexes_list[i],
161             "class": suggestions_list[i],
162             "extra_data": model_extra_data,
163         }
164 
165         job = JobInfo(classify_issue, model_name, owner, repo, issue_id)
166         setkey(job.result_key, orjson.dumps(data), compress=True)
167 
168         # Save the bug last change
169         setkey(job.change_time_key, issues[issue_id]["updated_at"].encode())
170 
171     return "OK"
172 
173 
174 def classify_broken_site_report(model_name: str, reports_data: list[dict]) -> str:
175     from bugbug_http.app import JobInfo
176 
177     reports = {
178         report["uuid"]: {"title": report["title"], "body": report["body"]}
179         for report in reports_data
180     }
181 
182     if not reports:
183         return "NOK"
184 
185     model = MODEL_CACHE.get(model_name)
186 
187     if not model:
188         LOGGER.info("Missing model %r, aborting" % model_name)
189         return "NOK"
190 
191     model_extra_data = model.get_extra_data()
192     probs = model.classify(list(reports.values()), True)
193     indexes = probs.argmax(axis=-1)
194     suggestions = model.le.inverse_transform(indexes)
195 
196     probs_list = probs.tolist()
197     indexes_list = indexes.tolist()
198     suggestions_list = suggestions.tolist()
199 
200     for i, report_uuid in enumerate(reports.keys()):
201         data = {
202             "prob": probs_list[i],
203             "index": indexes_list[i],
204             "class": suggestions_list[i],
205             "extra_data": model_extra_data,
206         }
207 
208         job = JobInfo(classify_broken_site_report, model_name, report_uuid)
209         setkey(job.result_key, orjson.dumps(data), compress=True)
210 
211     return "OK"
212 
213 
214 @lru_cache(maxsize=None)
215 def get_known_tasks() -> tuple[str, ...]:
216     with open("known_tasks", "r") as f:
217         return tuple(line.strip() for line in f)
218 
219 
220 def schedule_tests(branch: str, rev: str) -> str:
221     from bugbug_http import REPO_DIR
222     from bugbug_http.app import JobInfo
223 
224     job = JobInfo(schedule_tests, branch, rev)
225     LOGGER.info("Processing %s...", job)
226 
227     # Pull the revision to the local repository
228     LOGGER.info("Pulling commits from the remote repository...")
229     repository.pull(REPO_DIR, branch, rev)
230 
231     # Load the full stack of patches leading to that revision
232     LOGGER.info("Loading commits to analyze using automationrelevance...")
233     try:
234         revs = get_hgmo_stack(branch, rev)
235     except requests.exceptions.RequestException:
236         LOGGER.warning(f"Push not found for {branch} @ {rev}!")
237         return "NOK"
238 
239     test_selection_threshold = float(
240         os.environ.get("TEST_SELECTION_CONFIDENCE_THRESHOLD", 0.5)
241     )
242 
243     # On "try", consider commits from other branches too (see https://bugzilla.mozilla.org/show_bug.cgi?id=1790493).
244     # On other repos, only consider "tip" commits (to exclude commits such as https://hg.mozilla.org/integration/autoland/rev/961f253985a4388008700a6a6fde80f4e17c0b4b).
245     if branch == "try":
246         repo_branch = None
247     else:
248         repo_branch = "tip"
249 
250     # Analyze patches.
251     commits = repository.download_commits(
252         REPO_DIR,
253         revs=revs,
254         branch=repo_branch,
255         save=False,
256         use_single_process=True,
257         include_no_bug=True,
258     )
259 
260     if len(commits) > 0:
261         testlabelselect_model = MODEL_CACHE.get("testlabelselect")
262         testgroupselect_model = MODEL_CACHE.get("testgroupselect")
263 
264         tasks = testlabelselect_model.select_tests(commits, test_selection_threshold)
265 
266         reduced = testselect.reduce_configs(
267             set(t for t, c in tasks.items() if c >= 0.8), 1.0
268         )
269 
270         reduced_higher = testselect.reduce_configs(
271             set(t for t, c in tasks.items() if c >= 0.9), 1.0
272         )
273 
274         groups = testgroupselect_model.select_tests(commits, test_selection_threshold)
275 
276         config_groups = testselect.select_configs(groups.keys(), 0.9)
277     else:
278         tasks = {}
279         reduced = set()
280         groups = {}
281         config_groups = {}
282 
283     data = {
284         "tasks": tasks,
285         "groups": groups,
286         "config_groups": config_groups,
287         "reduced_tasks": {t: c for t, c in tasks.items() if t in reduced},
288         "reduced_tasks_higher": {t: c for t, c in tasks.items() if t in reduced_higher},
289         "known_tasks": get_known_tasks(),
290     }
291     setkey(job.result_key, orjson.dumps(data), compress=True)
292 
293     return "OK"
294 
295 
296 def get_config_specific_groups(config: str) -> str:
297     from bugbug_http.app import JobInfo
298 
299     job = JobInfo(get_config_specific_groups, config)
300     LOGGER.info("Processing %s...", job)
301 
302     equivalence_sets = testselect._get_equivalence_sets(0.9)
303 
304     past_failures_data = test_scheduling.PastFailures("group", True)
305 
306     setkey(
307         job.result_key,
308         orjson.dumps(
309             [
310                 {"name": group}
311                 for group in past_failures_data.all_runnables
312                 if any(
313                     equivalence_set == {config}
314                     for equivalence_set in equivalence_sets[group]
315                 )
316             ]
317         ),
318         compress=True,
319     )
320 
321     return "OK"
322
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/http_service/bugbug_http/models.py b/http_service/bugbug_http/models.py
--- a/http_service/bugbug_http/models.py
+++ b/http_service/bugbug_http/models.py
@@ -34,6 +34,7 @@
     "spambug",
     "testlabelselect",
     "testgroupselect",
+    "accessibility",
 ]
 
 DEFAULT_EXPIRATION_TTL = 7 * 24 * 3600  # A week

{"golden_diff": "diff --git a/http_service/bugbug_http/models.py b/http_service/bugbug_http/models.py\n--- a/http_service/bugbug_http/models.py\n+++ b/http_service/bugbug_http/models.py\n@@ -34,6 +34,7 @@\n     \"spambug\",\n     \"testlabelselect\",\n     \"testgroupselect\",\n+    \"accessibility\",\n ]\n \n DEFAULT_EXPIRATION_TTL = 7 * 24 * 3600  # A week\n", "issue": "[model:accessibility] Add the model to `http_service` and `data_pipeline`\nDepends on merging: #3775\n", "before_files": [{"content": "# -*- coding: utf-8 -*-\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not distributed with this file,\n# You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport logging\nimport os\nfrom datetime import timedelta\nfrom functools import lru_cache\nfrom typing import Sequence\n\nimport orjson\nimport requests\nimport zstandard\nfrom redis import Redis\n\nfrom bugbug import bugzilla, repository, test_scheduling\nfrom bugbug.github import Github\nfrom bugbug.model import Model\nfrom bugbug.models import testselect\nfrom bugbug.utils import get_hgmo_stack\nfrom bugbug_http.readthrough_cache import ReadthroughTTLCache\n\nlogging.basicConfig(level=logging.INFO)\nLOGGER = logging.getLogger()\n\nMODELS_NAMES = [\n    \"defectenhancementtask\",\n    \"component\",\n    \"invalidcompatibilityreport\",\n    \"needsdiagnosis\",\n    \"regression\",\n    \"stepstoreproduce\",\n    \"spambug\",\n    \"testlabelselect\",\n    \"testgroupselect\",\n]\n\nDEFAULT_EXPIRATION_TTL = 7 * 24 * 3600  # A week\nredis = Redis.from_url(os.environ.get(\"REDIS_URL\", \"redis://localhost/0\"))\n\nMODEL_CACHE: ReadthroughTTLCache[str, Model] = ReadthroughTTLCache(\n    timedelta(hours=1), lambda m: Model.load(f\"{m}model\")\n)\nMODEL_CACHE.start_ttl_thread()\n\ncctx = zstandard.ZstdCompressor(level=10)\n\n\ndef setkey(key: str, value: bytes, compress: bool = False) -> None:\n    LOGGER.debug(f\"Storing data at {key}: {value!r}\")\n    if compress:\n        value = cctx.compress(value)\n    redis.set(key, value)\n    redis.expire(key, DEFAULT_EXPIRATION_TTL)\n\n\ndef classify_bug(model_name: str, bug_ids: Sequence[int], bugzilla_token: str) -> str:\n    from bugbug_http.app import JobInfo\n\n    # This should be called in a process worker so it should be safe to set\n    # the token here\n    bug_ids_set = set(map(int, bug_ids))\n    bugzilla.set_token(bugzilla_token)\n\n    bugs = bugzilla.get(bug_ids)\n\n    missing_bugs = bug_ids_set.difference(bugs.keys())\n\n    for bug_id in missing_bugs:\n        job = JobInfo(classify_bug, model_name, bug_id)\n\n        # TODO: Find a better error format\n        setkey(job.result_key, orjson.dumps({\"available\": False}))\n\n    if not bugs:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n\n    # TODO: Classify could choke on a single bug which could make the whole\n    # job to fails. What should we do here?\n    probs = model.classify(list(bugs.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, bug_id in enumerate(bugs.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_bug, model_name, bug_id)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n        # Save the bug last change\n        setkey(job.change_time_key, bugs[bug_id][\"last_change_time\"].encode())\n\n    return \"OK\"\n\n\ndef classify_issue(\n    model_name: str, owner: str, repo: str, issue_nums: Sequence[int]\n) -> str:\n    from bugbug_http.app import JobInfo\n\n    github = Github(owner=owner, repo=repo)\n\n    issue_ids_set = set(map(int, issue_nums))\n\n    issues = {\n        issue_num: github.fetch_issue_by_number(owner, repo, issue_num, True)\n        for issue_num in issue_nums\n    }\n\n    missing_issues = issue_ids_set.difference(issues.keys())\n\n    for issue_id in missing_issues:\n        job = JobInfo(classify_issue, model_name, owner, repo, issue_id)\n\n        # TODO: Find a better error format\n        setkey(job.result_key, orjson.dumps({\"available\": False}))\n\n    if not issues:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n\n    # TODO: Classify could choke on a single bug which could make the whole\n    # job to fail. What should we do here?\n    probs = model.classify(list(issues.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, issue_id in enumerate(issues.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_issue, model_name, owner, repo, issue_id)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n        # Save the bug last change\n        setkey(job.change_time_key, issues[issue_id][\"updated_at\"].encode())\n\n    return \"OK\"\n\n\ndef classify_broken_site_report(model_name: str, reports_data: list[dict]) -> str:\n    from bugbug_http.app import JobInfo\n\n    reports = {\n        report[\"uuid\"]: {\"title\": report[\"title\"], \"body\": report[\"body\"]}\n        for report in reports_data\n    }\n\n    if not reports:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n    probs = model.classify(list(reports.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, report_uuid in enumerate(reports.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_broken_site_report, model_name, report_uuid)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n    return \"OK\"\n\n\n@lru_cache(maxsize=None)\ndef get_known_tasks() -> tuple[str, ...]:\n    with open(\"known_tasks\", \"r\") as f:\n        return tuple(line.strip() for line in f)\n\n\ndef schedule_tests(branch: str, rev: str) -> str:\n    from bugbug_http import REPO_DIR\n    from bugbug_http.app import JobInfo\n\n    job = JobInfo(schedule_tests, branch, rev)\n    LOGGER.info(\"Processing %s...\", job)\n\n    # Pull the revision to the local repository\n    LOGGER.info(\"Pulling commits from the remote repository...\")\n    repository.pull(REPO_DIR, branch, rev)\n\n    # Load the full stack of patches leading to that revision\n    LOGGER.info(\"Loading commits to analyze using automationrelevance...\")\n    try:\n        revs = get_hgmo_stack(branch, rev)\n    except requests.exceptions.RequestException:\n        LOGGER.warning(f\"Push not found for {branch} @ {rev}!\")\n        return \"NOK\"\n\n    test_selection_threshold = float(\n        os.environ.get(\"TEST_SELECTION_CONFIDENCE_THRESHOLD\", 0.5)\n    )\n\n    # On \"try\", consider commits from other branches too (see https://bugzilla.mozilla.org/show_bug.cgi?id=1790493).\n    # On other repos, only consider \"tip\" commits (to exclude commits such as https://hg.mozilla.org/integration/autoland/rev/961f253985a4388008700a6a6fde80f4e17c0b4b).\n    if branch == \"try\":\n        repo_branch = None\n    else:\n        repo_branch = \"tip\"\n\n    # Analyze patches.\n    commits = repository.download_commits(\n        REPO_DIR,\n        revs=revs,\n        branch=repo_branch,\n        save=False,\n        use_single_process=True,\n        include_no_bug=True,\n    )\n\n    if len(commits) > 0:\n        testlabelselect_model = MODEL_CACHE.get(\"testlabelselect\")\n        testgroupselect_model = MODEL_CACHE.get(\"testgroupselect\")\n\n        tasks = testlabelselect_model.select_tests(commits, test_selection_threshold)\n\n        reduced = testselect.reduce_configs(\n            set(t for t, c in tasks.items() if c >= 0.8), 1.0\n        )\n\n        reduced_higher = testselect.reduce_configs(\n            set(t for t, c in tasks.items() if c >= 0.9), 1.0\n        )\n\n        groups = testgroupselect_model.select_tests(commits, test_selection_threshold)\n\n        config_groups = testselect.select_configs(groups.keys(), 0.9)\n    else:\n        tasks = {}\n        reduced = set()\n        groups = {}\n        config_groups = {}\n\n    data = {\n        \"tasks\": tasks,\n        \"groups\": groups,\n        \"config_groups\": config_groups,\n        \"reduced_tasks\": {t: c for t, c in tasks.items() if t in reduced},\n        \"reduced_tasks_higher\": {t: c for t, c in tasks.items() if t in reduced_higher},\n        \"known_tasks\": get_known_tasks(),\n    }\n    setkey(job.result_key, orjson.dumps(data), compress=True)\n\n    return \"OK\"\n\n\ndef get_config_specific_groups(config: str) -> str:\n    from bugbug_http.app import JobInfo\n\n    job = JobInfo(get_config_specific_groups, config)\n    LOGGER.info(\"Processing %s...\", job)\n\n    equivalence_sets = testselect._get_equivalence_sets(0.9)\n\n    past_failures_data = test_scheduling.PastFailures(\"group\", True)\n\n    setkey(\n        job.result_key,\n        orjson.dumps(\n            [\n                {\"name\": group}\n                for group in past_failures_data.all_runnables\n                if any(\n                    equivalence_set == {config}\n                    for equivalence_set in equivalence_sets[group]\n                )\n            ]\n        ),\n        compress=True,\n    )\n\n    return \"OK\"\n", "path": "http_service/bugbug_http/models.py"}], "after_files": [{"content": "# -*- coding: utf-8 -*-\n# This Source Code Form is subject to the terms of the Mozilla Public\n# License, v. 2.0. If a copy of the MPL was not distributed with this file,\n# You can obtain one at http://mozilla.org/MPL/2.0/.\n\nimport logging\nimport os\nfrom datetime import timedelta\nfrom functools import lru_cache\nfrom typing import Sequence\n\nimport orjson\nimport requests\nimport zstandard\nfrom redis import Redis\n\nfrom bugbug import bugzilla, repository, test_scheduling\nfrom bugbug.github import Github\nfrom bugbug.model import Model\nfrom bugbug.models import testselect\nfrom bugbug.utils import get_hgmo_stack\nfrom bugbug_http.readthrough_cache import ReadthroughTTLCache\n\nlogging.basicConfig(level=logging.INFO)\nLOGGER = logging.getLogger()\n\nMODELS_NAMES = [\n    \"defectenhancementtask\",\n    \"component\",\n    \"invalidcompatibilityreport\",\n    \"needsdiagnosis\",\n    \"regression\",\n    \"stepstoreproduce\",\n    \"spambug\",\n    \"testlabelselect\",\n    \"testgroupselect\",\n    \"accessibility\",\n]\n\nDEFAULT_EXPIRATION_TTL = 7 * 24 * 3600  # A week\nredis = Redis.from_url(os.environ.get(\"REDIS_URL\", \"redis://localhost/0\"))\n\nMODEL_CACHE: ReadthroughTTLCache[str, Model] = ReadthroughTTLCache(\n    timedelta(hours=1), lambda m: Model.load(f\"{m}model\")\n)\nMODEL_CACHE.start_ttl_thread()\n\ncctx = zstandard.ZstdCompressor(level=10)\n\n\ndef setkey(key: str, value: bytes, compress: bool = False) -> None:\n    LOGGER.debug(f\"Storing data at {key}: {value!r}\")\n    if compress:\n        value = cctx.compress(value)\n    redis.set(key, value)\n    redis.expire(key, DEFAULT_EXPIRATION_TTL)\n\n\ndef classify_bug(model_name: str, bug_ids: Sequence[int], bugzilla_token: str) -> str:\n    from bugbug_http.app import JobInfo\n\n    # This should be called in a process worker so it should be safe to set\n    # the token here\n    bug_ids_set = set(map(int, bug_ids))\n    bugzilla.set_token(bugzilla_token)\n\n    bugs = bugzilla.get(bug_ids)\n\n    missing_bugs = bug_ids_set.difference(bugs.keys())\n\n    for bug_id in missing_bugs:\n        job = JobInfo(classify_bug, model_name, bug_id)\n\n        # TODO: Find a better error format\n        setkey(job.result_key, orjson.dumps({\"available\": False}))\n\n    if not bugs:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n\n    # TODO: Classify could choke on a single bug which could make the whole\n    # job to fails. What should we do here?\n    probs = model.classify(list(bugs.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, bug_id in enumerate(bugs.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_bug, model_name, bug_id)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n        # Save the bug last change\n        setkey(job.change_time_key, bugs[bug_id][\"last_change_time\"].encode())\n\n    return \"OK\"\n\n\ndef classify_issue(\n    model_name: str, owner: str, repo: str, issue_nums: Sequence[int]\n) -> str:\n    from bugbug_http.app import JobInfo\n\n    github = Github(owner=owner, repo=repo)\n\n    issue_ids_set = set(map(int, issue_nums))\n\n    issues = {\n        issue_num: github.fetch_issue_by_number(owner, repo, issue_num, True)\n        for issue_num in issue_nums\n    }\n\n    missing_issues = issue_ids_set.difference(issues.keys())\n\n    for issue_id in missing_issues:\n        job = JobInfo(classify_issue, model_name, owner, repo, issue_id)\n\n        # TODO: Find a better error format\n        setkey(job.result_key, orjson.dumps({\"available\": False}))\n\n    if not issues:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n\n    # TODO: Classify could choke on a single bug which could make the whole\n    # job to fail. What should we do here?\n    probs = model.classify(list(issues.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, issue_id in enumerate(issues.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_issue, model_name, owner, repo, issue_id)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n        # Save the bug last change\n        setkey(job.change_time_key, issues[issue_id][\"updated_at\"].encode())\n\n    return \"OK\"\n\n\ndef classify_broken_site_report(model_name: str, reports_data: list[dict]) -> str:\n    from bugbug_http.app import JobInfo\n\n    reports = {\n        report[\"uuid\"]: {\"title\": report[\"title\"], \"body\": report[\"body\"]}\n        for report in reports_data\n    }\n\n    if not reports:\n        return \"NOK\"\n\n    model = MODEL_CACHE.get(model_name)\n\n    if not model:\n        LOGGER.info(\"Missing model %r, aborting\" % model_name)\n        return \"NOK\"\n\n    model_extra_data = model.get_extra_data()\n    probs = model.classify(list(reports.values()), True)\n    indexes = probs.argmax(axis=-1)\n    suggestions = model.le.inverse_transform(indexes)\n\n    probs_list = probs.tolist()\n    indexes_list = indexes.tolist()\n    suggestions_list = suggestions.tolist()\n\n    for i, report_uuid in enumerate(reports.keys()):\n        data = {\n            \"prob\": probs_list[i],\n            \"index\": indexes_list[i],\n            \"class\": suggestions_list[i],\n            \"extra_data\": model_extra_data,\n        }\n\n        job = JobInfo(classify_broken_site_report, model_name, report_uuid)\n        setkey(job.result_key, orjson.dumps(data), compress=True)\n\n    return \"OK\"\n\n\n@lru_cache(maxsize=None)\ndef get_known_tasks() -> tuple[str, ...]:\n    with open(\"known_tasks\", \"r\") as f:\n        return tuple(line.strip() for line in f)\n\n\ndef schedule_tests(branch: str, rev: str) -> str:\n    from bugbug_http import REPO_DIR\n    from bugbug_http.app import JobInfo\n\n    job = JobInfo(schedule_tests, branch, rev)\n    LOGGER.info(\"Processing %s...\", job)\n\n    # Pull the revision to the local repository\n    LOGGER.info(\"Pulling commits from the remote repository...\")\n    repository.pull(REPO_DIR, branch, rev)\n\n    # Load the full stack of patches leading to that revision\n    LOGGER.info(\"Loading commits to analyze using automationrelevance...\")\n    try:\n        revs = get_hgmo_stack(branch, rev)\n    except requests.exceptions.RequestException:\n        LOGGER.warning(f\"Push not found for {branch} @ {rev}!\")\n        return \"NOK\"\n\n    test_selection_threshold = float(\n        os.environ.get(\"TEST_SELECTION_CONFIDENCE_THRESHOLD\", 0.5)\n    )\n\n    # On \"try\", consider commits from other branches too (see https://bugzilla.mozilla.org/show_bug.cgi?id=1790493).\n    # On other repos, only consider \"tip\" commits (to exclude commits such as https://hg.mozilla.org/integration/autoland/rev/961f253985a4388008700a6a6fde80f4e17c0b4b).\n    if branch == \"try\":\n        repo_branch = None\n    else:\n        repo_branch = \"tip\"\n\n    # Analyze patches.\n    commits = repository.download_commits(\n        REPO_DIR,\n        revs=revs,\n        branch=repo_branch,\n        save=False,\n        use_single_process=True,\n        include_no_bug=True,\n    )\n\n    if len(commits) > 0:\n        testlabelselect_model = MODEL_CACHE.get(\"testlabelselect\")\n        testgroupselect_model = MODEL_CACHE.get(\"testgroupselect\")\n\n        tasks = testlabelselect_model.select_tests(commits, test_selection_threshold)\n\n        reduced = testselect.reduce_configs(\n            set(t for t, c in tasks.items() if c >= 0.8), 1.0\n        )\n\n        reduced_higher = testselect.reduce_configs(\n            set(t for t, c in tasks.items() if c >= 0.9), 1.0\n        )\n\n        groups = testgroupselect_model.select_tests(commits, test_selection_threshold)\n\n        config_groups = testselect.select_configs(groups.keys(), 0.9)\n    else:\n        tasks = {}\n        reduced = set()\n        groups = {}\n        config_groups = {}\n\n    data = {\n        \"tasks\": tasks,\n        \"groups\": groups,\n        \"config_groups\": config_groups,\n        \"reduced_tasks\": {t: c for t, c in tasks.items() if t in reduced},\n        \"reduced_tasks_higher\": {t: c for t, c in tasks.items() if t in reduced_higher},\n        \"known_tasks\": get_known_tasks(),\n    }\n    setkey(job.result_key, orjson.dumps(data), compress=True)\n\n    return \"OK\"\n\n\ndef get_config_specific_groups(config: str) -> str:\n    from bugbug_http.app import JobInfo\n\n    job = JobInfo(get_config_specific_groups, config)\n    LOGGER.info(\"Processing %s...\", job)\n\n    equivalence_sets = testselect._get_equivalence_sets(0.9)\n\n    past_failures_data = test_scheduling.PastFailures(\"group\", True)\n\n    setkey(\n        job.result_key,\n        orjson.dumps(\n            [\n                {\"name\": group}\n                for group in past_failures_data.all_runnables\n                if any(\n                    equivalence_set == {config}\n                    for equivalence_set in equivalence_sets[group]\n                )\n            ]\n        ),\n        compress=True,\n    )\n\n    return \"OK\"\n", "path": "http_service/bugbug_http/models.py"}]}