diff --git a/.github/workflows/unit-test.yaml b/.github/workflows/unit-test.yaml index ff1f2c8..82b9154 100644 --- a/.github/workflows/unit-test.yaml +++ b/.github/workflows/unit-test.yaml @@ -88,6 +88,7 @@ jobs: steps.check-cache-hit.outputs.check != 'true' run: | python -m pip install --upgrade pip + cd libs/kotaemon pip install -U --upgrade-strategy eager -e .[dev] - name: New dependencies cache for key ${{ steps.restore-dependencies.outputs.cache-primary-key }} diff --git a/README.md b/README.md index 5843cc5..fcc3477 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,6 @@ pip install kotaemon@git+ssh://git@github.com/Cinnamon/kotaemon.git ```shell git clone git@github.com:Cinnamon/kotaemon.git - cd kotaemon - ``` - -- Install all - - ```shell - pip install -e ".[dev]" ``` - Pre-commit @@ -41,6 +34,13 @@ pip install kotaemon@git+ssh://git@github.com/Cinnamon/kotaemon.git pre-commit install ``` +- Install all + + ```shell + cd kotaemon/libs/kotaemon + pip install -e ".[dev]" + ``` + - Test ```shell diff --git a/libs/kotaemon/kotaemon/loaders/__init__.py b/libs/kotaemon/kotaemon/loaders/__init__.py index e564c5e..369242e 100644 --- a/libs/kotaemon/kotaemon/loaders/__init__.py +++ b/libs/kotaemon/kotaemon/loaders/__init__.py @@ -1,5 +1,7 @@ from .base import AutoReader, DirectoryReader +from .docx_loader import DocxReader from .excel_loader import PandasExcelReader +from .html_loader import HtmlReader from .mathpix_loader import MathpixPDFReader from .ocr_loader import OCRReader from .unstructured_loader import UnstructuredReader @@ -11,4 +13,6 @@ __all__ = [ "OCRReader", "DirectoryReader", "UnstructuredReader", + "DocxReader", + "HtmlReader", ] diff --git a/libs/kotaemon/kotaemon/loaders/docx_loader.py b/libs/kotaemon/kotaemon/loaders/docx_loader.py new file mode 100644 index 0000000..ed3df60 --- /dev/null +++ b/libs/kotaemon/kotaemon/loaders/docx_loader.py @@ -0,0 +1,91 @@ +import unicodedata +from pathlib import Path +from typing import List, Optional + +import pandas as pd +from llama_index.readers.base import BaseReader + +from kotaemon.base import Document + + +class DocxReader(BaseReader): + """Read Docx files that respect table, using python-docx library + + Reader behavior: + - All paragraphs are extracted as a Document + - Each table is extracted as a Document, rendered as a CSV string + - The output is a list of Documents, concatenating the above + (tables + paragraphs) + """ + + def __init__(self, *args, **kwargs): + try: + import docx + except ImportError: + raise ImportError( + "docx is not installed. " + "Please install it using `pip install python-docx`" + ) + self._module = docx + + def load_data( + self, file_path: Path, extra_info: Optional[dict] = None, **kwargs + ) -> List[Document]: + """Load data using Docx reader + + Args: + file_path (Path): Path to PDF file + + Returns: + List[Document]: list of documents extracted from the HTML file + """ + file_path = Path(file_path).resolve() + + doc = self._module.Document(str(file_path)) + all_text = "\n".join( + [unicodedata.normalize("NFKC", p.text) for p in doc.paragraphs] + ) + pages = [all_text] # 1 page only + + tables = [] + for t in doc.tables: + arrays = [ + [ + unicodedata.normalize("NFKC", t.cell(i, j).text) + for i in range(len(t.rows)) + ] + for j in range(len(t.columns)) + ] + tables.append(pd.DataFrame({a[0]: a[1:] for a in arrays})) + + extra_info = extra_info or {} + + # create output Document with metadata from table + documents = [ + Document( + text=table.to_csv( + index=False + ).strip(), # strip_special_chars_markdown() + metadata={ + "table_origin": table.to_csv(index=False), + "type": "table", + **extra_info, + }, + metadata_template="", + metadata_seperator="", + ) + for table in tables # page_id + ] + + # create Document from non-table text + documents.extend( + [ + Document( + text=non_table_text.strip(), + metadata={"page_label": 1, **extra_info}, + ) + for _, non_table_text in enumerate(pages) + ] + ) + + return documents diff --git a/libs/kotaemon/kotaemon/loaders/html_loader.py b/libs/kotaemon/kotaemon/loaders/html_loader.py new file mode 100644 index 0000000..c4b1c25 --- /dev/null +++ b/libs/kotaemon/kotaemon/loaders/html_loader.py @@ -0,0 +1,77 @@ +import unicodedata +from pathlib import Path +from typing import List, Optional + +from llama_index.readers.base import BaseReader + +from kotaemon.base import Document + + +class HtmlReader(BaseReader): + """Reader HTML usimg html2text + + Reader behavior: + - HTML is read with html2text. + - All of the texts will be split by `page_break_pattern` + - Each page is extracted as a Document + - The output is a list of Documents + + Args: + page_break_pattern (str): Pattern to split the HTML into pages + """ + + def __init__(self, page_break_pattern: Optional[str] = None, *args, **kwargs): + try: + import html2text + except ImportError: + raise ImportError( + "html2text is not installed. " + "Please install it using `pip install html2text`" + ) + + self._module = html2text + self._page_break_pattern: Optional[str] = page_break_pattern + super().__init__() + + def load_data( + self, file_path: Path, extra_info: Optional[dict] = None, **kwargs + ) -> List[Document]: + """Load data using Html reader + + Args: + file_path (Path): Path to PDF file + debug_path (Path): Path to store debug image output + artifact_path (Path): Path to OCR endpoints artifacts directory + Returns: + List[Document]: list of documents extracted from the HTML file + """ + file_path = Path(file_path).resolve() + + with file_path.open("r") as content: + html_text = "".join( + [ + unicodedata.normalize("NFKC", line[:-1]) + for line in content.readlines() + ] + ) + + # read HTML + all_text = self._module.html2text(html_text) + pages = ( + all_text.split(self._page_break_pattern) + if self._page_break_pattern + else [all_text] + ) + + extra_info = extra_info or {} + + # create Document from non-table text + documents = [ + Document( + text=page.strip(), + metadata={"page_label": page_id + 1, **extra_info}, + ) + for page_id, page in enumerate(pages) + ] + + return documents diff --git a/libs/kotaemon/pyproject.toml b/libs/kotaemon/pyproject.toml index f16e2e5..944e7e2 100644 --- a/libs/kotaemon/pyproject.toml +++ b/libs/kotaemon/pyproject.toml @@ -11,7 +11,7 @@ packages.find.exclude = ["tests*", "env*"] # metadata and dependencies [project] name = "kotaemon" -version = "0.3.5" +version = "0.3.6" requires-python = ">= 3.10" description = "Kotaemon core library for AI development." dependencies = [ @@ -55,6 +55,7 @@ dev = [ "wikipedia", "duckduckgo-search", "googlesearch-python", + "python-docx", "python-dotenv", "pytest-mock", "unstructured[pdf]", @@ -62,6 +63,7 @@ dev = [ "cohere", "elasticsearch", "pypdf", + "html2text", ] [project.scripts] diff --git a/libs/kotaemon/tests/resources/dummy.docx b/libs/kotaemon/tests/resources/dummy.docx new file mode 100644 index 0000000..51eb960 Binary files /dev/null and b/libs/kotaemon/tests/resources/dummy.docx differ diff --git a/libs/kotaemon/tests/resources/html/dummy.html b/libs/kotaemon/tests/resources/html/dummy.html new file mode 100644 index 0000000..ae824a1 --- /dev/null +++ b/libs/kotaemon/tests/resources/html/dummy.html @@ -0,0 +1 @@ +

細則 本社編(情報システム部)              分類番号 157300

2020.2

 

 

 

 

1. スパットくん紛失・盗難時の取扱

スパットくんの紛失・盗難の際は、速やかに停止依頼処理を入力するとともに、報告書を起票します。

NO

項目

内容

対象のスパット

くん確認

紛失・盗難に気づいた時には、対象のスパットくんの端末識別番号を確認します。

※盗難の場合は警察への届出も必要です。

※紛失・盗難の場合は盗難・紛失事故報告も必要です。

あいリクエスト(総務室(大阪))

『盗難・紛失事故兼個人情報等事故報告』

報告書の起票

あいリクエスト(システム業務室)-『モバイル決済端末(スパットくん)紛失・盗難報告書』を起票します。

停止依頼入力

モバイル端末管理ウェブより停止依頼処理を入力します。

※停止依頼入力により該当スパットくんは使用不可となります。

報告書の承認と

担当室への報告

所属長はあいリクエストにて申請・送付された報告書を確認・承認します。承認後、報告書をあいリクエスト(総務室(大阪))-『紛失・盗難事故 兼 個人情報等事故報告』に添付し報告します。(関連細則:本支社編11110/210060「紛失・盗難事故等の被害報告」を参照)

※日計処理の実施が「無」の場合本社担当室(システム業務室、

契約審査室、収納サービス室、損保サービス室)へ連絡する。

【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル

 

<スパットくん再発見時の対応>

NO

項目

内容

端末設置組織の

確認

スパットくんの設置状況照会を行い、紛失・盗難となったスパットくんであるか確認します。

端末停止解除

入力

モバイル端末管理ウェブより停止解除処理を入力します。

※スパットくんの利用再開は、支社にて停止解除入力から2営業日以上経過してから利用下さい。

        【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル

 

 

 

2. スパットくん故障時の取扱

スパットくんが故障した場合には、代替機への交換と故障機の返却を行います。

NO

項目

内容

故障内容の確認と報告書の起票

スパットくんが故障した場合は、あいリクエスト(システム業務室)-『モバイル決済端末(スパットくん)故障報告書』を起票します。

修理依頼入力

 

『モバイル端末管理ウェブ』より修理依頼処理を入力します。修理

依頼処理の入力により、代替機が送付されます。
その際、「モバイル決済端末修理依頼書」を印刷し、「モバイル決済端末(スパットくん)故障報告書」を参照して必要事項を記入します。

報告書の承認と担当室への報告

所属長はあいリクエストにて申請・送付された報告書を確認・承認し、システム業務室に報告します。

梱包

以下をセットで梱包します。

・故障したスパットくん本体

・付属品(充電アダプタ、コード、タッチペン、ストラップ)

・モバイル決済端末修理依頼書

※各所属にて保管のスパットくん送付箱に梱包します

 

代替機の受取と

故障機の送付

 

入力後数日でスパットくんの代替機が到着します。

受け取ると同時に、配送業者(日本通運)に故障したスパットくん、および「モバイル決済端末修理依頼書」を渡します。

※「モバイル決済端末修理依頼書」を忘れずに同梱下さい。

 

代替機の端末受取入力

モバイル端末管理ウェブより代替機の端末受取処理を入力します。

          【関連マニュアル】 スパットくん・モバイル管理ウェブ操作マニュアル

 

- 1 -

diff --git a/libs/kotaemon/tests/resources/html/dummy_image.png b/libs/kotaemon/tests/resources/html/dummy_image.png new file mode 100644 index 0000000..6f0aa4f Binary files /dev/null and b/libs/kotaemon/tests/resources/html/dummy_image.png differ diff --git a/libs/kotaemon/tests/test_reader.py b/libs/kotaemon/tests/test_reader.py index 4231e18..1ead0d8 100644 --- a/libs/kotaemon/tests/test_reader.py +++ b/libs/kotaemon/tests/test_reader.py @@ -4,13 +4,29 @@ from langchain.schema import Document as LangchainDocument from llama_index.node_parser import SimpleNodeParser from kotaemon.base import Document -from kotaemon.loaders import AutoReader, UnstructuredReader +from kotaemon.loaders import AutoReader, DocxReader, HtmlReader, UnstructuredReader + + +def test_docx_reader(): + reader = DocxReader() + documents = reader.load_data(Path(__file__).parent / "resources" / "dummy.docx") + + assert len(documents) + + +def test_html_reader(): + reader = HtmlReader() + documents = reader.load_data( + Path(__file__).parent / "resources" / "html" / "dummy.html" + ) + + assert len(documents) def test_pdf_reader(): reader = AutoReader("PDFReader") dirpath = Path(__file__).parent - documents = reader.load_data(dirpath / "resources/dummy.pdf") + documents = reader.load_data(dirpath / "resources" / "dummy.pdf") # check document reader output assert len(documents) == 1