We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Load additional fields from SQUAD-format file to meta field for labels
**Is your feature request related to a problem? Please describe.**
Currently `eval_data_from_json` loads additional fields for Documents as meta field but it doesn't do it for Labels.

**Describe the solution you'd like**
`eval_data_from_json` loads additional fields to `Label.meta` dict analogically to how it's done for Documents.

**Describe alternatives you've considered**
Currently I'd would have to rewrite whole loading data from json code to achieve this.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `haystack/document_stores/utils.py`
Content:
```
1 import typing
2 from typing import Dict, List, Optional, Tuple, Union, Generator
3 
4 import json
5 import logging
6 from datetime import datetime
7 
8 from haystack.schema import Document, Label, Answer, Span
9 from haystack.nodes.preprocessor import PreProcessor
10 
11 if typing.TYPE_CHECKING:
12     # This results in a circular import if we don't use typing.TYPE_CHECKING
13     from haystack.document_stores.base import BaseDocumentStore
14 
15 
16 logger = logging.getLogger(__name__)
17 
18 
19 def eval_data_from_json(
20     filename: str,
21     max_docs: Optional[Union[int, bool]] = None,
22     preprocessor: Optional[PreProcessor] = None,
23     open_domain: bool = False,
24 ) -> Tuple[List[Document], List[Label]]:
25     """
26     Read Documents + Labels from a SQuAD-style file.
27     Document and Labels can then be indexed to the DocumentStore and be used for evaluation.
28 
29     :param filename: Path to file in SQuAD format
30     :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.
31     :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.
32     """
33     docs: List[Document] = []
34     labels = []
35     problematic_ids = []
36 
37     with open(filename, "r", encoding="utf-8") as file:
38         data = json.load(file)
39         if "title" not in data["data"][0]:
40             logger.warning("No title information found for documents in QA file: %s", filename)
41 
42         for squad_document in data["data"]:
43             if max_docs and len(docs) > max_docs:
44                 break
45             # Extracting paragraphs and their labels from a SQuAD document dict
46             cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(
47                 squad_document, preprocessor, open_domain
48             )
49             docs.extend(cur_docs)
50             labels.extend(cur_labels)
51             problematic_ids.extend(cur_problematic_ids)
52     if len(problematic_ids) > 0:
53         logger.warning(
54             "Could not convert an answer for %s questions.\nThere were conversion errors for question ids: %s",
55             len(problematic_ids),
56             problematic_ids,
57         )
58     return docs, labels
59 
60 
61 def eval_data_from_jsonl(
62     filename: str,
63     batch_size: Optional[int] = None,
64     max_docs: Optional[Union[int, bool]] = None,
65     preprocessor: Optional[PreProcessor] = None,
66     open_domain: bool = False,
67 ) -> Generator[Tuple[List[Document], List[Label]], None, None]:
68     """
69     Read Documents + Labels from a SQuAD-style file in jsonl format, i.e. one document per line.
70     Document and Labels can then be indexed to the DocumentStore and be used for evaluation.
71 
72     This is a generator which will yield one tuple per iteration containing a list
73     of batch_size documents and a list with the documents' labels.
74     If batch_size is set to None, this method will yield all documents and labels.
75 
76     :param filename: Path to file in SQuAD format
77     :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.
78     :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.
79     """
80     docs: List[Document] = []
81     labels = []
82     problematic_ids = []
83 
84     with open(filename, "r", encoding="utf-8") as file:
85         for document in file:
86             if max_docs and len(docs) > max_docs:
87                 break
88             # Extracting paragraphs and their labels from a SQuAD document dict
89             squad_document = json.loads(document)
90             cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(
91                 squad_document, preprocessor, open_domain
92             )
93             docs.extend(cur_docs)
94             labels.extend(cur_labels)
95             problematic_ids.extend(cur_problematic_ids)
96 
97             if batch_size is not None and len(docs) >= batch_size:
98                 if len(problematic_ids) > 0:
99                     logger.warning(
100                         "Could not convert an answer for %s questions.\n"
101                         "There were conversion errors for question ids: %s",
102                         len(problematic_ids),
103                         problematic_ids,
104                     )
105                 yield docs, labels
106                 docs = []
107                 labels = []
108                 problematic_ids = []
109 
110     yield docs, labels
111 
112 
113 def squad_json_to_jsonl(squad_file: str, output_file: str):
114     """
115     Converts a SQuAD-json-file into jsonl format with one document per line.
116 
117     :param squad_file: SQuAD-file in json format.
118     :param output_file: Name of output file (SQuAD in jsonl format)
119     """
120     with open(squad_file, encoding="utf-8") as json_file, open(output_file, "w", encoding="utf-8") as jsonl_file:
121         squad_json = json.load(json_file)
122 
123         for doc in squad_json["data"]:
124             json.dump(doc, jsonl_file)
125             jsonl_file.write("\n")
126 
127 
128 def _extract_docs_and_labels_from_dict(
129     document_dict: Dict, preprocessor: Optional[PreProcessor] = None, open_domain: bool = False
130 ):
131     """
132     Set open_domain to True if you are trying to load open_domain labels (i.e. labels without doc id or start idx)
133     """
134     docs = []
135     labels = []
136     problematic_ids = []
137 
138     # get all extra fields from document level (e.g. title)
139     meta_doc = {k: v for k, v in document_dict.items() if k not in ("paragraphs", "title")}
140     for paragraph in document_dict["paragraphs"]:
141         ## Create Metadata
142         cur_meta = {"name": document_dict.get("title", None)}
143         # all other fields from paragraph level
144         meta_paragraph = {k: v for k, v in paragraph.items() if k not in ("qas", "context")}
145         cur_meta.update(meta_paragraph)
146         # meta from parent document
147         cur_meta.update(meta_doc)
148 
149         ## Create Document
150         cur_full_doc = Document(content=paragraph["context"], meta=cur_meta)
151         if preprocessor is not None:
152             splits_docs = preprocessor.process(documents=[cur_full_doc])
153             # we need to pull in _split_id into the document id for unique reference in labels
154             splits: List[Document] = []
155             offset = 0
156             for d in splits_docs:
157                 id = f"{d.id}-{d.meta['_split_id']}"
158                 d.meta["_split_offset"] = offset
159                 offset += len(d.content)
160                 # offset correction based on splitting method
161                 if preprocessor.split_by == "word":
162                     offset += 1
163                 elif preprocessor.split_by == "passage":
164                     offset += 2
165                 else:
166                     raise NotImplementedError
167                 mydoc = Document(content=d.content, id=id, meta=d.meta)
168                 splits.append(mydoc)
169         else:
170             splits = [cur_full_doc]
171         docs.extend(splits)
172 
173         ## Assign Labels to corresponding documents
174         for qa in paragraph["qas"]:
175             if not qa.get("is_impossible", False):
176                 for answer in qa["answers"]:
177                     ans = answer["text"]
178                     # TODO The following block of code means that answer_start is never calculated
179                     #  and cur_id is always None for open_domain
180                     #  This can be rewritten so that this function could try to calculate offsets
181                     #  and populate id in open_domain mode
182                     if open_domain:
183                         # TODO check with Branden why we want to treat open_domain here differently.
184                         # Shouldn't this be something configured at eval time only?
185                         cur_ans_start = answer.get("answer_start", 0)
186                         # cur_id = '0'
187                         label = Label(
188                             query=qa["question"],
189                             answer=Answer(answer=ans, type="extractive", score=0.0),
190                             document=None,  # type: ignore
191                             is_correct_answer=True,
192                             is_correct_document=True,
193                             origin="gold-label",
194                         )
195                         labels.append(label)
196                     else:
197                         ans_position = cur_full_doc.content[
198                             answer["answer_start"] : answer["answer_start"] + len(str(ans))
199                         ]
200                         if ans != ans_position:
201                             # do not use answer
202                             problematic_ids.append(qa.get("id", "missing"))
203                             break
204                         # find corresponding document or split
205                         if len(splits) == 1:
206                             # cur_id = splits[0].id
207                             cur_ans_start = answer["answer_start"]
208                             cur_doc = splits[0]
209                         else:
210                             for s in splits:
211                                 # If answer start offset is contained in passage we assign the label to that passage
212                                 if (answer["answer_start"] >= s.meta["_split_offset"]) and (
213                                     answer["answer_start"] < (s.meta["_split_offset"] + len(s.content))
214                                 ):
215                                     cur_doc = s
216                                     cur_ans_start = answer["answer_start"] - s.meta["_split_offset"]
217                                     # If a document is splitting an answer we add the whole answer text to the document
218                                     if s.content[cur_ans_start : cur_ans_start + len(ans)] != ans:
219                                         s.content = s.content[:cur_ans_start] + ans
220                                     break
221                         cur_answer = Answer(
222                             answer=ans,
223                             type="extractive",
224                             score=0.0,
225                             context=cur_doc.content,
226                             offsets_in_document=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],
227                             offsets_in_context=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],
228                             document_ids=[cur_doc.id],
229                         )
230                         label = Label(
231                             query=qa["question"],
232                             answer=cur_answer,
233                             document=cur_doc,
234                             is_correct_answer=True,
235                             is_correct_document=True,
236                             origin="gold-label",
237                         )
238                         labels.append(label)
239             else:
240                 # for no_answer we need to assign each split as not fitting to the question
241                 for s in splits:
242                     label = Label(
243                         query=qa["question"],
244                         answer=Answer(
245                             answer="",
246                             type="extractive",
247                             score=0.0,
248                             offsets_in_document=[Span(start=0, end=0)],
249                             offsets_in_context=[Span(start=0, end=0)],
250                         ),
251                         document=s,
252                         is_correct_answer=True,
253                         is_correct_document=True,
254                         origin="gold-label",
255                     )
256 
257                     labels.append(label)
258 
259     return docs, labels, problematic_ids
260 
261 
262 def convert_date_to_rfc3339(date: str) -> str:
263     """
264     Converts a date to RFC3339 format, as Weaviate requires dates to be in RFC3339 format including the time and
265     timezone.
266 
267     If the provided date string does not contain a time and/or timezone, we use 00:00 as default time
268     and UTC as default time zone.
269 
270     This method cannot be part of WeaviateDocumentStore, as this would result in a circular import between weaviate.py
271     and filter_utils.py.
272     """
273     parsed_datetime = datetime.fromisoformat(date)
274     if parsed_datetime.utcoffset() is None:
275         converted_date = parsed_datetime.isoformat() + "Z"
276     else:
277         converted_date = parsed_datetime.isoformat()
278 
279     return converted_date
280
```
--- 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/haystack/document_stores/utils.py b/haystack/document_stores/utils.py
--- a/haystack/document_stores/utils.py
+++ b/haystack/document_stores/utils.py
@@ -172,6 +172,9 @@
 
         ## Assign Labels to corresponding documents
         for qa in paragraph["qas"]:
+            meta_qa = {
+                k: v for k, v in qa.items() if k not in ("is_impossible", "answers", "question", "id", "missing")
+            }
             if not qa.get("is_impossible", False):
                 for answer in qa["answers"]:
                     ans = answer["text"]
@@ -191,6 +194,7 @@
                             is_correct_answer=True,
                             is_correct_document=True,
                             origin="gold-label",
+                            meta=meta_qa,
                         )
                         labels.append(label)
                     else:
@@ -234,6 +238,7 @@
                             is_correct_answer=True,
                             is_correct_document=True,
                             origin="gold-label",
+                            meta=meta_qa,
                         )
                         labels.append(label)
             else:
@@ -252,6 +257,7 @@
                         is_correct_answer=True,
                         is_correct_document=True,
                         origin="gold-label",
+                        meta=meta_qa,
                     )
 
                     labels.append(label)

{"golden_diff": "diff --git a/haystack/document_stores/utils.py b/haystack/document_stores/utils.py\n--- a/haystack/document_stores/utils.py\n+++ b/haystack/document_stores/utils.py\n@@ -172,6 +172,9 @@\n \n         ## Assign Labels to corresponding documents\n         for qa in paragraph[\"qas\"]:\n+            meta_qa = {\n+                k: v for k, v in qa.items() if k not in (\"is_impossible\", \"answers\", \"question\", \"id\", \"missing\")\n+            }\n             if not qa.get(\"is_impossible\", False):\n                 for answer in qa[\"answers\"]:\n                     ans = answer[\"text\"]\n@@ -191,6 +194,7 @@\n                             is_correct_answer=True,\n                             is_correct_document=True,\n                             origin=\"gold-label\",\n+                            meta=meta_qa,\n                         )\n                         labels.append(label)\n                     else:\n@@ -234,6 +238,7 @@\n                             is_correct_answer=True,\n                             is_correct_document=True,\n                             origin=\"gold-label\",\n+                            meta=meta_qa,\n                         )\n                         labels.append(label)\n             else:\n@@ -252,6 +257,7 @@\n                         is_correct_answer=True,\n                         is_correct_document=True,\n                         origin=\"gold-label\",\n+                        meta=meta_qa,\n                     )\n \n                     labels.append(label)\n", "issue": "Load additional fields from SQUAD-format file to meta field for labels\n**Is your feature request related to a problem? Please describe.**\r\nCurrently `eval_data_from_json` loads additional fields for Documents as meta field but it doesn't do it for Labels.\r\n\r\n**Describe the solution you'd like**\r\n`eval_data_from_json` loads additional fields to `Label.meta` dict analogically to how it's done for Documents.\r\n\r\n**Describe alternatives you've considered**\r\nCurrently I'd would have to rewrite whole loading data from json code to achieve this.\r\n\n", "before_files": [{"content": "import typing\nfrom typing import Dict, List, Optional, Tuple, Union, Generator\n\nimport json\nimport logging\nfrom datetime import datetime\n\nfrom haystack.schema import Document, Label, Answer, Span\nfrom haystack.nodes.preprocessor import PreProcessor\n\nif typing.TYPE_CHECKING:\n    # This results in a circular import if we don't use typing.TYPE_CHECKING\n    from haystack.document_stores.base import BaseDocumentStore\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef eval_data_from_json(\n    filename: str,\n    max_docs: Optional[Union[int, bool]] = None,\n    preprocessor: Optional[PreProcessor] = None,\n    open_domain: bool = False,\n) -> Tuple[List[Document], List[Label]]:\n    \"\"\"\n    Read Documents + Labels from a SQuAD-style file.\n    Document and Labels can then be indexed to the DocumentStore and be used for evaluation.\n\n    :param filename: Path to file in SQuAD format\n    :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.\n    :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.\n    \"\"\"\n    docs: List[Document] = []\n    labels = []\n    problematic_ids = []\n\n    with open(filename, \"r\", encoding=\"utf-8\") as file:\n        data = json.load(file)\n        if \"title\" not in data[\"data\"][0]:\n            logger.warning(\"No title information found for documents in QA file: %s\", filename)\n\n        for squad_document in data[\"data\"]:\n            if max_docs and len(docs) > max_docs:\n                break\n            # Extracting paragraphs and their labels from a SQuAD document dict\n            cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(\n                squad_document, preprocessor, open_domain\n            )\n            docs.extend(cur_docs)\n            labels.extend(cur_labels)\n            problematic_ids.extend(cur_problematic_ids)\n    if len(problematic_ids) > 0:\n        logger.warning(\n            \"Could not convert an answer for %s questions.\\nThere were conversion errors for question ids: %s\",\n            len(problematic_ids),\n            problematic_ids,\n        )\n    return docs, labels\n\n\ndef eval_data_from_jsonl(\n    filename: str,\n    batch_size: Optional[int] = None,\n    max_docs: Optional[Union[int, bool]] = None,\n    preprocessor: Optional[PreProcessor] = None,\n    open_domain: bool = False,\n) -> Generator[Tuple[List[Document], List[Label]], None, None]:\n    \"\"\"\n    Read Documents + Labels from a SQuAD-style file in jsonl format, i.e. one document per line.\n    Document and Labels can then be indexed to the DocumentStore and be used for evaluation.\n\n    This is a generator which will yield one tuple per iteration containing a list\n    of batch_size documents and a list with the documents' labels.\n    If batch_size is set to None, this method will yield all documents and labels.\n\n    :param filename: Path to file in SQuAD format\n    :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.\n    :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.\n    \"\"\"\n    docs: List[Document] = []\n    labels = []\n    problematic_ids = []\n\n    with open(filename, \"r\", encoding=\"utf-8\") as file:\n        for document in file:\n            if max_docs and len(docs) > max_docs:\n                break\n            # Extracting paragraphs and their labels from a SQuAD document dict\n            squad_document = json.loads(document)\n            cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(\n                squad_document, preprocessor, open_domain\n            )\n            docs.extend(cur_docs)\n            labels.extend(cur_labels)\n            problematic_ids.extend(cur_problematic_ids)\n\n            if batch_size is not None and len(docs) >= batch_size:\n                if len(problematic_ids) > 0:\n                    logger.warning(\n                        \"Could not convert an answer for %s questions.\\n\"\n                        \"There were conversion errors for question ids: %s\",\n                        len(problematic_ids),\n                        problematic_ids,\n                    )\n                yield docs, labels\n                docs = []\n                labels = []\n                problematic_ids = []\n\n    yield docs, labels\n\n\ndef squad_json_to_jsonl(squad_file: str, output_file: str):\n    \"\"\"\n    Converts a SQuAD-json-file into jsonl format with one document per line.\n\n    :param squad_file: SQuAD-file in json format.\n    :param output_file: Name of output file (SQuAD in jsonl format)\n    \"\"\"\n    with open(squad_file, encoding=\"utf-8\") as json_file, open(output_file, \"w\", encoding=\"utf-8\") as jsonl_file:\n        squad_json = json.load(json_file)\n\n        for doc in squad_json[\"data\"]:\n            json.dump(doc, jsonl_file)\n            jsonl_file.write(\"\\n\")\n\n\ndef _extract_docs_and_labels_from_dict(\n    document_dict: Dict, preprocessor: Optional[PreProcessor] = None, open_domain: bool = False\n):\n    \"\"\"\n    Set open_domain to True if you are trying to load open_domain labels (i.e. labels without doc id or start idx)\n    \"\"\"\n    docs = []\n    labels = []\n    problematic_ids = []\n\n    # get all extra fields from document level (e.g. title)\n    meta_doc = {k: v for k, v in document_dict.items() if k not in (\"paragraphs\", \"title\")}\n    for paragraph in document_dict[\"paragraphs\"]:\n        ## Create Metadata\n        cur_meta = {\"name\": document_dict.get(\"title\", None)}\n        # all other fields from paragraph level\n        meta_paragraph = {k: v for k, v in paragraph.items() if k not in (\"qas\", \"context\")}\n        cur_meta.update(meta_paragraph)\n        # meta from parent document\n        cur_meta.update(meta_doc)\n\n        ## Create Document\n        cur_full_doc = Document(content=paragraph[\"context\"], meta=cur_meta)\n        if preprocessor is not None:\n            splits_docs = preprocessor.process(documents=[cur_full_doc])\n            # we need to pull in _split_id into the document id for unique reference in labels\n            splits: List[Document] = []\n            offset = 0\n            for d in splits_docs:\n                id = f\"{d.id}-{d.meta['_split_id']}\"\n                d.meta[\"_split_offset\"] = offset\n                offset += len(d.content)\n                # offset correction based on splitting method\n                if preprocessor.split_by == \"word\":\n                    offset += 1\n                elif preprocessor.split_by == \"passage\":\n                    offset += 2\n                else:\n                    raise NotImplementedError\n                mydoc = Document(content=d.content, id=id, meta=d.meta)\n                splits.append(mydoc)\n        else:\n            splits = [cur_full_doc]\n        docs.extend(splits)\n\n        ## Assign Labels to corresponding documents\n        for qa in paragraph[\"qas\"]:\n            if not qa.get(\"is_impossible\", False):\n                for answer in qa[\"answers\"]:\n                    ans = answer[\"text\"]\n                    # TODO The following block of code means that answer_start is never calculated\n                    #  and cur_id is always None for open_domain\n                    #  This can be rewritten so that this function could try to calculate offsets\n                    #  and populate id in open_domain mode\n                    if open_domain:\n                        # TODO check with Branden why we want to treat open_domain here differently.\n                        # Shouldn't this be something configured at eval time only?\n                        cur_ans_start = answer.get(\"answer_start\", 0)\n                        # cur_id = '0'\n                        label = Label(\n                            query=qa[\"question\"],\n                            answer=Answer(answer=ans, type=\"extractive\", score=0.0),\n                            document=None,  # type: ignore\n                            is_correct_answer=True,\n                            is_correct_document=True,\n                            origin=\"gold-label\",\n                        )\n                        labels.append(label)\n                    else:\n                        ans_position = cur_full_doc.content[\n                            answer[\"answer_start\"] : answer[\"answer_start\"] + len(str(ans))\n                        ]\n                        if ans != ans_position:\n                            # do not use answer\n                            problematic_ids.append(qa.get(\"id\", \"missing\"))\n                            break\n                        # find corresponding document or split\n                        if len(splits) == 1:\n                            # cur_id = splits[0].id\n                            cur_ans_start = answer[\"answer_start\"]\n                            cur_doc = splits[0]\n                        else:\n                            for s in splits:\n                                # If answer start offset is contained in passage we assign the label to that passage\n                                if (answer[\"answer_start\"] >= s.meta[\"_split_offset\"]) and (\n                                    answer[\"answer_start\"] < (s.meta[\"_split_offset\"] + len(s.content))\n                                ):\n                                    cur_doc = s\n                                    cur_ans_start = answer[\"answer_start\"] - s.meta[\"_split_offset\"]\n                                    # If a document is splitting an answer we add the whole answer text to the document\n                                    if s.content[cur_ans_start : cur_ans_start + len(ans)] != ans:\n                                        s.content = s.content[:cur_ans_start] + ans\n                                    break\n                        cur_answer = Answer(\n                            answer=ans,\n                            type=\"extractive\",\n                            score=0.0,\n                            context=cur_doc.content,\n                            offsets_in_document=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],\n                            offsets_in_context=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],\n                            document_ids=[cur_doc.id],\n                        )\n                        label = Label(\n                            query=qa[\"question\"],\n                            answer=cur_answer,\n                            document=cur_doc,\n                            is_correct_answer=True,\n                            is_correct_document=True,\n                            origin=\"gold-label\",\n                        )\n                        labels.append(label)\n            else:\n                # for no_answer we need to assign each split as not fitting to the question\n                for s in splits:\n                    label = Label(\n                        query=qa[\"question\"],\n                        answer=Answer(\n                            answer=\"\",\n                            type=\"extractive\",\n                            score=0.0,\n                            offsets_in_document=[Span(start=0, end=0)],\n                            offsets_in_context=[Span(start=0, end=0)],\n                        ),\n                        document=s,\n                        is_correct_answer=True,\n                        is_correct_document=True,\n                        origin=\"gold-label\",\n                    )\n\n                    labels.append(label)\n\n    return docs, labels, problematic_ids\n\n\ndef convert_date_to_rfc3339(date: str) -> str:\n    \"\"\"\n    Converts a date to RFC3339 format, as Weaviate requires dates to be in RFC3339 format including the time and\n    timezone.\n\n    If the provided date string does not contain a time and/or timezone, we use 00:00 as default time\n    and UTC as default time zone.\n\n    This method cannot be part of WeaviateDocumentStore, as this would result in a circular import between weaviate.py\n    and filter_utils.py.\n    \"\"\"\n    parsed_datetime = datetime.fromisoformat(date)\n    if parsed_datetime.utcoffset() is None:\n        converted_date = parsed_datetime.isoformat() + \"Z\"\n    else:\n        converted_date = parsed_datetime.isoformat()\n\n    return converted_date\n", "path": "haystack/document_stores/utils.py"}], "after_files": [{"content": "import typing\nfrom typing import Dict, List, Optional, Tuple, Union, Generator\n\nimport json\nimport logging\nfrom datetime import datetime\n\nfrom haystack.schema import Document, Label, Answer, Span\nfrom haystack.nodes.preprocessor import PreProcessor\n\nif typing.TYPE_CHECKING:\n    # This results in a circular import if we don't use typing.TYPE_CHECKING\n    from haystack.document_stores.base import BaseDocumentStore\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef eval_data_from_json(\n    filename: str,\n    max_docs: Optional[Union[int, bool]] = None,\n    preprocessor: Optional[PreProcessor] = None,\n    open_domain: bool = False,\n) -> Tuple[List[Document], List[Label]]:\n    \"\"\"\n    Read Documents + Labels from a SQuAD-style file.\n    Document and Labels can then be indexed to the DocumentStore and be used for evaluation.\n\n    :param filename: Path to file in SQuAD format\n    :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.\n    :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.\n    \"\"\"\n    docs: List[Document] = []\n    labels = []\n    problematic_ids = []\n\n    with open(filename, \"r\", encoding=\"utf-8\") as file:\n        data = json.load(file)\n        if \"title\" not in data[\"data\"][0]:\n            logger.warning(\"No title information found for documents in QA file: %s\", filename)\n\n        for squad_document in data[\"data\"]:\n            if max_docs and len(docs) > max_docs:\n                break\n            # Extracting paragraphs and their labels from a SQuAD document dict\n            cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(\n                squad_document, preprocessor, open_domain\n            )\n            docs.extend(cur_docs)\n            labels.extend(cur_labels)\n            problematic_ids.extend(cur_problematic_ids)\n    if len(problematic_ids) > 0:\n        logger.warning(\n            \"Could not convert an answer for %s questions.\\nThere were conversion errors for question ids: %s\",\n            len(problematic_ids),\n            problematic_ids,\n        )\n    return docs, labels\n\n\ndef eval_data_from_jsonl(\n    filename: str,\n    batch_size: Optional[int] = None,\n    max_docs: Optional[Union[int, bool]] = None,\n    preprocessor: Optional[PreProcessor] = None,\n    open_domain: bool = False,\n) -> Generator[Tuple[List[Document], List[Label]], None, None]:\n    \"\"\"\n    Read Documents + Labels from a SQuAD-style file in jsonl format, i.e. one document per line.\n    Document and Labels can then be indexed to the DocumentStore and be used for evaluation.\n\n    This is a generator which will yield one tuple per iteration containing a list\n    of batch_size documents and a list with the documents' labels.\n    If batch_size is set to None, this method will yield all documents and labels.\n\n    :param filename: Path to file in SQuAD format\n    :param max_docs: This sets the number of documents that will be loaded. By default, this is set to None, thus reading in all available eval documents.\n    :param open_domain: Set this to True if your file is an open domain dataset where two different answers to the same question might be found in different contexts.\n    \"\"\"\n    docs: List[Document] = []\n    labels = []\n    problematic_ids = []\n\n    with open(filename, \"r\", encoding=\"utf-8\") as file:\n        for document in file:\n            if max_docs and len(docs) > max_docs:\n                break\n            # Extracting paragraphs and their labels from a SQuAD document dict\n            squad_document = json.loads(document)\n            cur_docs, cur_labels, cur_problematic_ids = _extract_docs_and_labels_from_dict(\n                squad_document, preprocessor, open_domain\n            )\n            docs.extend(cur_docs)\n            labels.extend(cur_labels)\n            problematic_ids.extend(cur_problematic_ids)\n\n            if batch_size is not None and len(docs) >= batch_size:\n                if len(problematic_ids) > 0:\n                    logger.warning(\n                        \"Could not convert an answer for %s questions.\\n\"\n                        \"There were conversion errors for question ids: %s\",\n                        len(problematic_ids),\n                        problematic_ids,\n                    )\n                yield docs, labels\n                docs = []\n                labels = []\n                problematic_ids = []\n\n    yield docs, labels\n\n\ndef squad_json_to_jsonl(squad_file: str, output_file: str):\n    \"\"\"\n    Converts a SQuAD-json-file into jsonl format with one document per line.\n\n    :param squad_file: SQuAD-file in json format.\n    :param output_file: Name of output file (SQuAD in jsonl format)\n    \"\"\"\n    with open(squad_file, encoding=\"utf-8\") as json_file, open(output_file, \"w\", encoding=\"utf-8\") as jsonl_file:\n        squad_json = json.load(json_file)\n\n        for doc in squad_json[\"data\"]:\n            json.dump(doc, jsonl_file)\n            jsonl_file.write(\"\\n\")\n\n\ndef _extract_docs_and_labels_from_dict(\n    document_dict: Dict, preprocessor: Optional[PreProcessor] = None, open_domain: bool = False\n):\n    \"\"\"\n    Set open_domain to True if you are trying to load open_domain labels (i.e. labels without doc id or start idx)\n    \"\"\"\n    docs = []\n    labels = []\n    problematic_ids = []\n\n    # get all extra fields from document level (e.g. title)\n    meta_doc = {k: v for k, v in document_dict.items() if k not in (\"paragraphs\", \"title\")}\n    for paragraph in document_dict[\"paragraphs\"]:\n        ## Create Metadata\n        cur_meta = {\"name\": document_dict.get(\"title\", None)}\n        # all other fields from paragraph level\n        meta_paragraph = {k: v for k, v in paragraph.items() if k not in (\"qas\", \"context\")}\n        cur_meta.update(meta_paragraph)\n        # meta from parent document\n        cur_meta.update(meta_doc)\n\n        ## Create Document\n        cur_full_doc = Document(content=paragraph[\"context\"], meta=cur_meta)\n        if preprocessor is not None:\n            splits_docs = preprocessor.process(documents=[cur_full_doc])\n            # we need to pull in _split_id into the document id for unique reference in labels\n            splits: List[Document] = []\n            offset = 0\n            for d in splits_docs:\n                id = f\"{d.id}-{d.meta['_split_id']}\"\n                d.meta[\"_split_offset\"] = offset\n                offset += len(d.content)\n                # offset correction based on splitting method\n                if preprocessor.split_by == \"word\":\n                    offset += 1\n                elif preprocessor.split_by == \"passage\":\n                    offset += 2\n                else:\n                    raise NotImplementedError\n                mydoc = Document(content=d.content, id=id, meta=d.meta)\n                splits.append(mydoc)\n        else:\n            splits = [cur_full_doc]\n        docs.extend(splits)\n\n        ## Assign Labels to corresponding documents\n        for qa in paragraph[\"qas\"]:\n            meta_qa = {\n                k: v for k, v in qa.items() if k not in (\"is_impossible\", \"answers\", \"question\", \"id\", \"missing\")\n            }\n            if not qa.get(\"is_impossible\", False):\n                for answer in qa[\"answers\"]:\n                    ans = answer[\"text\"]\n                    # TODO The following block of code means that answer_start is never calculated\n                    #  and cur_id is always None for open_domain\n                    #  This can be rewritten so that this function could try to calculate offsets\n                    #  and populate id in open_domain mode\n                    if open_domain:\n                        # TODO check with Branden why we want to treat open_domain here differently.\n                        # Shouldn't this be something configured at eval time only?\n                        cur_ans_start = answer.get(\"answer_start\", 0)\n                        # cur_id = '0'\n                        label = Label(\n                            query=qa[\"question\"],\n                            answer=Answer(answer=ans, type=\"extractive\", score=0.0),\n                            document=None,  # type: ignore\n                            is_correct_answer=True,\n                            is_correct_document=True,\n                            origin=\"gold-label\",\n                            meta=meta_qa,\n                        )\n                        labels.append(label)\n                    else:\n                        ans_position = cur_full_doc.content[\n                            answer[\"answer_start\"] : answer[\"answer_start\"] + len(str(ans))\n                        ]\n                        if ans != ans_position:\n                            # do not use answer\n                            problematic_ids.append(qa.get(\"id\", \"missing\"))\n                            break\n                        # find corresponding document or split\n                        if len(splits) == 1:\n                            # cur_id = splits[0].id\n                            cur_ans_start = answer[\"answer_start\"]\n                            cur_doc = splits[0]\n                        else:\n                            for s in splits:\n                                # If answer start offset is contained in passage we assign the label to that passage\n                                if (answer[\"answer_start\"] >= s.meta[\"_split_offset\"]) and (\n                                    answer[\"answer_start\"] < (s.meta[\"_split_offset\"] + len(s.content))\n                                ):\n                                    cur_doc = s\n                                    cur_ans_start = answer[\"answer_start\"] - s.meta[\"_split_offset\"]\n                                    # If a document is splitting an answer we add the whole answer text to the document\n                                    if s.content[cur_ans_start : cur_ans_start + len(ans)] != ans:\n                                        s.content = s.content[:cur_ans_start] + ans\n                                    break\n                        cur_answer = Answer(\n                            answer=ans,\n                            type=\"extractive\",\n                            score=0.0,\n                            context=cur_doc.content,\n                            offsets_in_document=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],\n                            offsets_in_context=[Span(start=cur_ans_start, end=cur_ans_start + len(ans))],\n                            document_ids=[cur_doc.id],\n                        )\n                        label = Label(\n                            query=qa[\"question\"],\n                            answer=cur_answer,\n                            document=cur_doc,\n                            is_correct_answer=True,\n                            is_correct_document=True,\n                            origin=\"gold-label\",\n                            meta=meta_qa,\n                        )\n                        labels.append(label)\n            else:\n                # for no_answer we need to assign each split as not fitting to the question\n                for s in splits:\n                    label = Label(\n                        query=qa[\"question\"],\n                        answer=Answer(\n                            answer=\"\",\n                            type=\"extractive\",\n                            score=0.0,\n                            offsets_in_document=[Span(start=0, end=0)],\n                            offsets_in_context=[Span(start=0, end=0)],\n                        ),\n                        document=s,\n                        is_correct_answer=True,\n                        is_correct_document=True,\n                        origin=\"gold-label\",\n                        meta=meta_qa,\n                    )\n\n                    labels.append(label)\n\n    return docs, labels, problematic_ids\n\n\ndef convert_date_to_rfc3339(date: str) -> str:\n    \"\"\"\n    Converts a date to RFC3339 format, as Weaviate requires dates to be in RFC3339 format including the time and\n    timezone.\n\n    If the provided date string does not contain a time and/or timezone, we use 00:00 as default time\n    and UTC as default time zone.\n\n    This method cannot be part of WeaviateDocumentStore, as this would result in a circular import between weaviate.py\n    and filter_utils.py.\n    \"\"\"\n    parsed_datetime = datetime.fromisoformat(date)\n    if parsed_datetime.utcoffset() is None:\n        converted_date = parsed_datetime.isoformat() + \"Z\"\n    else:\n        converted_date = parsed_datetime.isoformat()\n\n    return converted_date\n", "path": "haystack/document_stores/utils.py"}]}