Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions k8s/welearn-datastack/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ common:
RCLONE_CONFIG_DEST_ENV_AUTH: true
RCLONE_CONFIG_DEST_TYPE: azureblob
TEAM_EMAIL: welearn@learningplanetinstitute.org
TIKA_ADDRESS: http://tika:9998
embeddingModelFr: sentence-camembert-base
embeddingModelEn: all-minilm-l6-v2
modelsPathRoot: /models
Expand Down
640 changes: 381 additions & 259 deletions poetry.lock

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,8 @@ requests = "^2.32.4"
wikipedia-api = "^0.8.1"
sentence-transformers = "^4.1.0"
spacy = "^3.8.7"
pypdf = "^5.6.0"
refinedoc = "^0.0.3"
qdrant-client = "^1.14.2"
refinedoc = "^1.0.0"
qdrant-client = "1.12.2"
python-dotenv = "^1.1.0"
beautifulsoup4 = "^4.13.4"
pyphen = "^0.17.2"
Expand All @@ -46,6 +45,7 @@ psycopg2-binary = "^2.9.10"
brotli = "^1.1.0"
scikit-learn = "~=1.6.1"
optimum = {extras = ["onnxruntime"], version = "^1.26.1"}
azure-storage-blob = "^12.26.0"

[tool.poetry.group.metrics.dependencies]
alembic = "^1.16.1"
Expand Down
2 changes: 1 addition & 1 deletion sql/89920abb7ff8_populate_corpus_category.sql
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ INSERT
INTO
corpus_related.category(title)
VALUES
('Academic scientific publications')
('academic scientific publications')
RETURNING id AS catid
)
UPDATE
Expand Down
11 changes: 0 additions & 11 deletions sql/qty_document_in_qdrant

This file was deleted.

8 changes: 7 additions & 1 deletion tests/document_collector_hub/plugins_test/test_hal.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,12 +125,15 @@ def test__convert_json_dict_to_welearndoc(self):
doc0["abstract_s"][0].split(".")[0] + "...",
)

@patch("welearn_datastack.modules.pdf_extractor._send_pdf_to_tika")
@patch(
"welearn_datastack.plugins.rest_requesters.hal.HAL_URL_BASE",
"https://example.org/",
)
@patch("requests.Session.get")
def test__convert_json_dict_to_welearndoc_mode_pdf(self, mock_get):
def test__convert_json_dict_to_welearndoc_mode_pdf(
self, mock_get, mock_send_pdf_to_tika
):
class MockResponse:
def __init__(self, status_code):
self.content = (
Expand All @@ -144,6 +147,9 @@ def __init__(self, status_code):
def raise_for_status(self):
pass

mock_send_pdf_to_tika.return_value = {
"X-TIKA:content": "<div class='page'>For primary vpiRNAs that are produced from the abundant</div>"
}
mock_get.side_effect = [MockResponse(200)]
os.environ["PDF_SIZE_PAGE_LIMIT"] = "100000"
doc0 = self.content_json["response"]["docs"][0]
Expand Down
6 changes: 5 additions & 1 deletion tests/document_collector_hub/plugins_test/test_oapen.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ def test_get_oapen_url_from_handle_id(self):
"https://library.oapen.org/handle/20.500.12657/12345",
)

@patch("welearn_datastack.modules.pdf_extractor._send_pdf_to_tika")
@patch("welearn_datastack.plugins.rest_requesters.oapen.get_new_https_session")
def test_get_pdf_content(self, mock_get_new_https_session):
def test_get_pdf_content(self, mock_get_new_https_session, mock_send_pdf_to_tika):
class MockResponse:
def __init__(self, status_code):
self.content = (
Expand All @@ -96,6 +97,9 @@ def raise_for_status(self):
pass

os.environ["PDF_SIZE_PAGE_LIMIT"] = "100000"
mock_send_pdf_to_tika.return_value = {
"X-TIKA:content": "<div class='page'>For primary vpiRNAs that are produced from the abundant</div>"
}

mock_response = MockResponse(200)
mock_get_new_https_session.return_value.get.return_value = mock_response
Expand Down
7 changes: 5 additions & 2 deletions tests/document_collector_hub/plugins_test/test_open_alex.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,19 @@ def test__generate_api_query_params(self):
tested_query,
)

@patch("welearn_datastack.modules.pdf_extractor._send_pdf_to_tika")
@patch("welearn_datastack.plugins.rest_requesters.open_alex.get_new_https_session")
def test__get_pdf_content(self, http_session_mock):
def test__get_pdf_content(self, http_session_mock, mock_send_pdf_to_tika):

mock_session = Mock()
http_session_mock.return_value = mock_session

mock_session.get.return_value = MockResponse(
status_code=200, content=self.pdf.read_bytes()
)

mock_send_pdf_to_tika.return_value = {
"X-TIKA:content": "<div class='page'>2.2. Measurements of Fiber Parameters Small pieces were extracted from various positions on the strip. and mechanical properties to provide additional information regarding these areas of study and growth conditions.</div>"
}
tested_result = self.openalexColector._get_pdf_content("https://example.org/1")
self.assertTrue(
tested_result.startswith(
Expand Down
93 changes: 70 additions & 23 deletions tests/document_collector_hub/test_pdf_extractor.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import io
import unittest
from pathlib import Path
from unittest.mock import MagicMock, patch

from pypdf import PdfReader

from welearn_datastack.data.enumerations import DeletePart
from welearn_datastack.modules import pdf_extractor
from welearn_datastack.modules.pdf_extractor import (
_parse_tika_content,
_send_pdf_to_tika,
extract_txt_from_pdf_with_tika,
)


class TestPDFExtractor(unittest.TestCase):
def setUp(self):
resources_fp = (
Path(__file__).parent / "resources" / "file_plugin_input" / "hal_pdf.pdf"
)

self.reader = PdfReader(resources_fp)

def test_extract_txt_from_pdf(self):
pdf_content = pdf_extractor.extract_txt_from_pdf(self.reader)
self.assertEqual(len(pdf_content), len(self.reader.pages))

def test_replace_ligatures(self):
text = "first flight"
cleaned_text = pdf_extractor.replace_ligatures(text)
Expand All @@ -34,12 +26,67 @@ def test_remove_hyphens(self):
cleaned_text = pdf_extractor.remove_hyphens(text)
self.assertEqual(cleaned_text, "wellknown\n")

def test_check_page_size_positive(self):
ret = pdf_extractor.large_pages_size_flag(self.reader, 10000)
self.assertEqual(len(ret[0]), len(self.reader.pages))
self.assertTrue(ret[1])
@patch("welearn_datastack.modules.pdf_extractor.get_new_https_session")
def test_send_pdf_to_tika(self, mock_get_session):
# Mock de la session HTTP
mock_session = MagicMock()
mock_response = MagicMock()
mock_response.json.return_value = {
"X-TIKA:content": "<html>Mock Content</html>"
}
mock_response.raise_for_status.return_value = None
mock_session.put.return_value = mock_response
mock_get_session.return_value.__enter__.return_value = mock_session

# Appel de la méthode
pdf_content = io.BytesIO(b"Mock PDF content")
tika_base_url = "http://mock-tika-url"
result = _send_pdf_to_tika(pdf_content, tika_base_url)

# Assertions
mock_session.put.assert_called_once_with(
url=f"{tika_base_url}/tika",
files={"file": pdf_content},
headers={
"Accept": "application/json",
"Content-type": "application/octet-stream",
"X-Tika-PDFOcrStrategy": "no_ocr",
},
)
self.assertEqual(result, {"X-TIKA:content": "<html>Mock Content</html>"})

def test_check_page_size_negative(self):
ret = pdf_extractor.large_pages_size_flag(self.reader, 100000)
self.assertEqual(len(ret[0]), len(self.reader.pages))
self.assertFalse(ret[1])
def test_parse_tika_content(self):
tika_content = {
"X-TIKA:content": """
<html>
<div class="page">Page 1 content</div>
<div class="page">Page 2 content</div>
</html>
"""
}

result = _parse_tika_content(tika_content)

expected_result = [["Page 1 content"], ["Page 2 content"]]
self.assertEqual(result, expected_result)

@patch("welearn_datastack.modules.pdf_extractor._send_pdf_to_tika")
@patch("welearn_datastack.modules.pdf_extractor._parse_tika_content")
def test_extract_txt_from_pdf_with_tika(
self, mock_parse_tika_content, mock_send_pdf_to_tika
):
pdf_content = io.BytesIO(b"%PDF-1.4 simulated content")
tika_base_url = "http://localhost:9998"

mock_send_pdf_to_tika.return_value = {
"X-TIKA:content": "<div class='page'>Page 1 content</div>"
}
mock_parse_tika_content.return_value = [["Page 1 content"]]

result = extract_txt_from_pdf_with_tika(pdf_content, tika_base_url)

self.assertEqual(result, [["Page 1 content"]])
mock_send_pdf_to_tika.assert_called_once_with(pdf_content, tika_base_url)
mock_parse_tika_content.assert_called_once_with(
mock_send_pdf_to_tika.return_value
)
78 changes: 49 additions & 29 deletions welearn_datastack/modules/pdf_extractor.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,69 @@
import io
import logging
from typing import List, Tuple
import re
from typing import List

from pypdf import PdfReader
from bs4 import BeautifulSoup
from refinedoc.refined_document import RefinedDocument

from welearn_datastack.utils_.http_client_utils import get_new_https_session

logger = logging.getLogger(__name__)


def large_pages_size_flag(reader: PdfReader, limit: int) -> Tuple[List[int], bool]:
def _send_pdf_to_tika(pdf_content: io.BytesIO, tika_base_url: str) -> dict:
"""
Send a PDF document to Tika micro service and return the content as a dictionary
:param pdf_content: the PDF document content as a byte stream
:param tika_base_url: the base URL of the Tika micro service
:return: the content returned by Tika micro service as a dictionary (JSON)
"""
tika_base_url = re.sub(r"\/$", "", tika_base_url)
pdf_process_addr = f"{tika_base_url}/tika"
local_headers = {
"Accept": "application/json",
"Content-type": "application/octet-stream",
"X-Tika-PDFOcrStrategy": "no_ocr",
}

with get_new_https_session() as http_session:
resp = http_session.put(
url=pdf_process_addr,
files={"file": pdf_content},
headers=local_headers,
)
resp.raise_for_status()
tika_content = resp.json()
return tika_content


def _parse_tika_content(tika_content: dict) -> list[list[str]]:
"""
Check the size of a PDF document page
:param limit: In byte, limit the pdf need to not exceed
:param reader: The PDF document already opened. Each page gonna be processed.
:return: List of sizes for each page (index in the list equal index in pdf) and if one of this exceed limit
Parse the content returned by Tika micro service
:param tika_content: the content returned by Tika micro service
:return: the parsed content as a list of list of strings (one list per page
"""
logger.info(f"Size limit for each page : {limit}")
ret = []
number_of_pages = len(reader.pages)
logger.info(f"Test size of {number_of_pages} pages")
flag = False
for i in range(number_of_pages):
page = reader.pages[i]
page_size = len(page.get_contents().get_data()) # type: ignore
ret.append(page_size)
if page_size > limit:
flag = True
htmlx = tika_content.get("X-TIKA:content")
soup = BeautifulSoup(htmlx, features="html.parser")
pages = soup.find_all("div", {"class": "page"})
res = [p.split("\n") for p in [page.get_text() for page in pages]]

return ret, flag
return res


def extract_txt_from_pdf(
reader: PdfReader, remove_headers: bool = True, remove_footers: bool = True
def extract_txt_from_pdf_with_tika(
pdf_content: io.BytesIO, tika_base_url: str
) -> List[List[str]]:
"""
Extract the text from a PDF document and return it as a list of strings for each page of the document and a list of
strings for each page for a filtered document and the reference document (extracted with PyPDF)
strings for each page for a filtered document and the reference document (extracted with tika micro service)

:param reader: the PDF reader object
:return: a tuple containing the extracted & filtered text
:param pdf_content: the PDF document content as a byte stream
:param tika_base_url: the base URL of the Tika micro service
:return: Matrix of strings (list of list of strings) for each page of the document
"""
pdf_content: List[List[str]] = []
for page in reader.pages:
text = page.extract_text().split("\n")
page_content = [t.strip() for t in text if t.strip()]
pdf_content.append(page_content)
tika_content = _send_pdf_to_tika(pdf_content, tika_base_url)
pdf_content = _parse_tika_content(tika_content)

refined_pdf_content = RefinedDocument(content=pdf_content)

Expand Down
Loading