diff --git a/README.ja.md b/README.ja.md index 6ec860e..4cbdafc 100644 --- a/README.ja.md +++ b/README.ja.md @@ -8,8 +8,8 @@ ExStruct は Excel ワークブックを読み取り、構造化データ(セ ## 主な特徴 -- **Excel → 構造化 JSON**: セル、図形、チャート、SmartArt、テーブル候補、印刷範囲/自動改ページ範囲(PrintArea/PrintAreaView)をシート単位・範囲単位で出力。 -- **出力モード**: `light`(セル+テーブル候補のみ)、`standard`(テキスト付き図形+矢印、チャート、SmartArt)、`verbose`(全図形を幅高さ付きで出力、セルのハイパーリンクも出力)。 +- **Excel → 構造化 JSON**: セル、図形、チャート、SmartArt、テーブル候補、セル結合範囲、印刷範囲/自動改ページ範囲(PrintArea/PrintAreaView)をシート単位・範囲単位で出力。 +- **出力モード**: `light`(セル+テーブル候補のみ)、`standard`(テキスト付き図形+矢印、チャート、SmartArt、セル結合範囲)、`verbose`(全図形を幅高さ付きで出力、セルのハイパーリンクも出力)。 - **フォーマット**: JSON(デフォルトはコンパクト、`--pretty` で整形)、YAML、TOON(任意依存)。 - **テーブル検出のチューニング**: API でヒューリスティックを動的に変更可能。 - **ハイパーリンク抽出**: `verbose` モード(または `include_cell_links=True` 指定)でセルのリンクを `links` に出力。 @@ -149,11 +149,11 @@ exstruct input.xlsx --pdf --image --dpi 144 `.pdf` と `_images/` 配下に PNG を生成します。 -## ベンチマーク: Excel 構造化デモ +## 例 ①: Excel 構造化デモ 本ライブラリ exstruct がどの程度 Excel を構造化できるのかを示すため、 以下の 3 要素を 1 シートにまとめた Excel を解析し、 -その JSON 出力を用いた AI 推論精度ベンチマーク を掲載します。 +その JSON 出力を用いた LLM 推論例 を掲載します。 - 表(売上データ) - 折れ線グラフ @@ -335,9 +335,169 @@ flowchart TD ``` ```` -このことから、 +## 例 ②: 一般的な申請書 -**exstruct の JSON は AI にとって "そのまま意味として理解できる形式" である** +### Excel データ + +![一般的な申請書Excel](/docs/assets/demo_form.ja.png) + +### ExStruct JSON + +※長いので一部省略 + +```json +{ + "book_name": "ja_form.xlsx", + "sheets": { + "Sheet1": { + "rows": [ + { "r": 1, "c": { "0": "介護保険負担限度額認定申請書" } }, + { + "r": 3, + "c": { "0": "(申請先)", "7": "     年    月    日" } + }, + { "r": 4, "c": { "1": "X市長 " } }, + ... + ], + "table_candidates": ["B25:C26", "C37:D50"], + "merged_cells": [ + { + "r1": 55, + "c1": 5, + "r2": 55, + "c2": 10, + "v": "申請者が被保険者本人の場合には、下記について記載は不要です。" + }, + { "r1": 54, "c1": 8, "r2": 54, "c2": 10 }, + { "r1": 51, "c1": 5, "r2": 52, "c2": 6, "v": "有価証券" }, + ... + ] + } + } +} + +``` + +### LLM 推論による ExStruct JSON → Markdown 変換結果 + +```md +# 介護保険負担限度額認定申請書 + +(申請先)              年  月  日 +X 市長 + +次のとおり関係書類を添えて、食費・居住費(滞在費)に係る負担限度額認定を申請します。 + +--- + +## 被保険者情報 + +| 項目 | 内容 | +| ------------ | ---------------------------- | +| フリガナ | | +| 被保険者氏名 | | +| 被保険者番号 | | +| 個人番号 | | +| 生年月日 | 明・大・昭  年  月  日 | +| 住所 | | +| 連絡先 | | + +--- + +## 入所(院)した介護保険施設 + +| 項目 | 内容 | +| --------------------- | ---------- | +| 施設所在地・名称(※) | | +| 連絡先 | | +| 入所(院)年月日 | 年 月 日 | + +**(※)介護保険施設に入所(院)していない場合、およびショートステイ利用時は記入不要** + +--- + +## 配偶者の有無 + +| 項目 | 内容 | +| ------------ | -------- | +| 配偶者の有無 | 有 ・ 無 | + +※「無」の場合、以下の「配偶者に関する事項」は記入不要 + +--- + +## 配偶者に関する事項 + +| 項目 | 内容 | +| ---------------------------------------------- | -------------------------- | +| フリガナ | | +| 氏名 | | +| 生年月日 | 明・大・昭 年 月 日 | +| 個人番号 | | +| 住所 | 〒 | +| 連絡先 | | +| 本年 1 月 1 日現在の住所(現住所と異なる場合) | 〒 | +| 課税状況 | 市町村民税:課税 ・ 非課税 | + +--- + +## 収入等に関する申告 + +以下の該当する項目にチェックしてください。 + +- □ ① 生活保護受給者 +- □ ② 市町村民税世帯非課税である老齢福祉年金受給者 +- □ ③ 市町村民税世帯非課税者で、課税年金収入額+遺族年金・障害年金+その他所得の合計が **年額 80 万円以下** +- □ ④ 同上で **80 万円超〜120 万円以下** +- □ ⑤ 同上で **120 万円超** + +※遺族年金には寡婦年金、寡夫年金、母子年金、準母子年金、遺児年金を含む + +--- + +## 預貯金等に関する申告 + +- □ 預貯金・有価証券等の合計額が以下の基準以下である + - ② の方:1000 万円(夫婦 2000 万円) + - ③ の方:650 万円(夫婦 1650 万円) + - ④ の方:550 万円(夫婦 1550 万円) + - ⑤ の方:500 万円(夫婦 1500 万円) + - ※第 2 号被保険者(40〜64 歳)は ③〜⑤ の方:1000 万円(夫婦 2000 万円)以下 + +### 預貯金等の内訳 + +| 項目 | 金額 | +| -------------------------- | ---------------- | +| 預貯金額 | 円 | +| 有価証券(評価概算額) | 円 | +| その他(現金・負債を含む) | 円(内容を記入) | + +--- + +## 申請者情報(※被保険者本人の場合は不要) + +| 項目 | 内容 | +| ---------------------- | ---- | +| 申請者氏名 | | +| 連絡先(自宅・勤務先) | | +| 申請者住所 | | +| 本人との関係 | | + +--- + +## 注意事項 + +1. この申請書における「配偶者」には、世帯分離している配偶者および内縁関係の者を含む。 +2. 預貯金等は、同じ種類を複数所有している場合すべて記入し、通帳等の写しを添付すること。 +3. 書き切れない場合は余白または別紙に記入して添付すること。 +4. 虚偽の申告により不正に特定入所者介護サービス費等の支給を受けた場合、介護保険法第 22 条第 1 項に基づき、支給額および最大 2 倍の加算金を返還する必要がある。 +``` + +## 考察 + +上記の実験結果から、 + +**ExStruct の JSON は AI にとって "そのまま意味として理解できる形式" である** ということが明確に示されています。 @@ -346,6 +506,7 @@ flowchart TD - [Basic Excel](sample/basic/) - [Flowchart](sample/flowchart/) - [Gantt Chart](sample/gantt_chart/) +- [Application forms with many merged cells](sample/forms_with_many_merged_cells/) ### 4. Summary diff --git a/README.md b/README.md index f235e30..9e08dc5 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ ![ExStruct Image](/docs/assets/icon.webp) -ExStruct reads Excel workbooks and outputs structured data (cells, table candidates, shapes, charts, smartart, print areas/views, auto page-break areas, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates + print areas), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines. +ExStruct reads Excel workbooks and outputs structured data (cells, table candidates, shapes, charts, smartart, merged cell ranges, print areas/views, auto page-break areas, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates + print areas), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines. [日本版 README](README.ja.md) ## Features - **Excel → Structured JSON**: cells, shapes, charts, smartart, table candidates, print areas/views, and auto page-break areas per sheet. -- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, print areas), `verbose` (all shapes with width/height, charts with size, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled. +- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled. - **Auto page-break export (COM only)**: capture Excel-computed auto page breaks and write per-area JSON/YAML/TOON when requested (CLI option appears only when COM is available). - **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies). - **Table detection tuning**: adjust heuristics at runtime via API. @@ -133,8 +133,8 @@ Use higher thresholds to reduce false positives; lower them if true tables are m ## Output Modes - **light**: cells + table candidates (no COM needed). -- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`. -- **verbose**: all shapes (with width/height), charts, table candidates, cell hyperlinks, and `colors_map`. +- **standard**: texted shapes + arrows, charts (COM if available), merged cell ranges, table candidates. Hyperlinks are off unless `include_cell_links=True`. +- **verbose**: all shapes (with width/height), charts, merged cell ranges, table candidates, cell hyperlinks, and `colors_map`. ## Error Handling / Fallbacks @@ -152,7 +152,7 @@ exstruct input.xlsx --pdf --image --dpi 144 Creates `.pdf` and `_images/` PNGs per sheet. -## Benchmark: Excel Structuring Demo +## Example 1: Excel Structuring Demo To show how well exstruct can structure Excel, we parse a workbook that combines three elements on one sheet and share an AI reasoning benchmark that uses the JSON output. @@ -336,6 +336,87 @@ flowchart TD ``` ```` +## Example 2: General Application Form + +### Excel Sheet + +![General Application Form Excel](/docs/assets/demo_form.en.png) + +### ExStruct JSON + +(Truncated for brevity) + +```json +{ + "book_name": "ja_form.xlsx", + "sheets": { + "Sheet1": { + "rows": [ + { "r": 1, "c": { "0": "??????????????" } }, + { + "r": 3, + "c": { "0": "???", "7": " ???????????????" } + }, + { "r": 4, "c": { "1": "X???" } }, + ... + ], + "table_candidates": ["B25:C26", "C37:D50"], + "merged_cells": [ + { + "r1": 55, + "c1": 5, + "r2": 55, + "c2": 10, + "v": "?????????????????????????????" + }, + { "r1": 54, "c1": 8, "r2": 54, "c2": 10 }, + { "r1": 51, "c1": 5, "r2": 52, "c2": 6, "v": "????" }, + ... + ] + } + } +} +``` + +### LLM reconstruction example + +```md +# ?????????????? + +???????????????????????? +X ?? + +????????????????????????????????????????? + +--- + +## ?????? + +| ?? | ?? | +| ------ | -------------- | +| ???? | | +| ????? | | +| ?????? | | +| ???? | | +| ???? | ?????????????? | +| ?? | | +| ??? | | + +--- + +## ????????????? + +| ?? | ?? | +| ----------- | ----- | +| ??????????? | | +| ??? | | +| ???????? | ????? | + +**???????????????????????????????????????** + +... +``` + From this we can see: **exstruct's JSON is already in a format that AI can read and reason over directly.** @@ -345,6 +426,7 @@ Other LLM inference samples using this library can be found in the following dir - [Basic Excel](sample/basic/) - [Flowchart](sample/flowchart/) - [Gantt Chart](sample/gantt_chart/) +- [Application forms with many merged cells](sample/forms_with_many_merged_cells/) ### 4. Summary diff --git a/docs/README.en.md b/docs/README.en.md index e43314b..24c9702 100644 --- a/docs/README.en.md +++ b/docs/README.en.md @@ -4,14 +4,14 @@ ![ExStruct Image](assets/icon.webp) -ExStruct reads Excel workbooks and outputs structured data (cells, table candidates, shapes, charts, smartart, print areas/views, auto page-break areas, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates + print areas), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines. +ExStruct reads Excel workbooks and outputs structured data (cells, table candidates, shapes, charts, smartart, merged cell ranges, print areas/views, auto page-break areas, hyperlinks) as JSON by default, with optional YAML/TOON formats. It targets both COM/Excel environments (rich extraction) and non-COM environments (cells + table candidates + print areas), with tunable detection heuristics and multiple output modes to fit LLM/RAG pipelines. [日本版 README](README.ja.md) ## Features - **Excel → Structured JSON**: cells, shapes, charts, smartart, table candidates, print areas/views, and auto page-break areas per sheet. -- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, print areas), `verbose` (all shapes with width/height, charts with size, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled. +- **Output modes**: `light` (cells + table candidates + print areas; no COM, shapes/charts empty), `standard` (texted shapes + arrows, charts, smartart, merged cell ranges, print areas), `verbose` (all shapes with width/height, charts with size, merged cell ranges, print areas). Verbose also emits cell hyperlinks and `colors_map`. Size output is flag-controlled. - **Auto page-break export (COM only)**: capture Excel-computed auto page breaks and write per-area JSON/YAML/TOON when requested (CLI option appears only when COM is available). - **Formats**: JSON (compact by default, `--pretty` available), YAML, TOON (optional dependencies). - **Table detection tuning**: adjust heuristics at runtime via API. @@ -133,8 +133,8 @@ Use higher thresholds to reduce false positives; lower them if true tables are m ## Output Modes - **light**: cells + table candidates (no COM needed). -- **standard**: texted shapes + arrows, charts (COM if available), table candidates. Hyperlinks are off unless `include_cell_links=True`. -- **verbose**: all shapes (with width/height), charts, table candidates, cell hyperlinks, and `colors_map`. +- **standard**: texted shapes + arrows, charts (COM if available), merged cell ranges, table candidates. Hyperlinks are off unless `include_cell_links=True`. +- **verbose**: all shapes (with width/height), charts, merged cell ranges, table candidates, cell hyperlinks, and `colors_map`. ## Error Handling / Fallbacks @@ -152,7 +152,7 @@ exstruct input.xlsx --pdf --image --dpi 144 Creates `.pdf` and `_images/` PNGs per sheet. -## Benchmark: Excel Structuring Demo +## Example 1: Excel Structuring Demo To show how well exstruct can structure Excel, we parse a workbook that combines three elements on one sheet and share an AI reasoning benchmark that uses the JSON output. @@ -336,6 +336,220 @@ flowchart TD ``` ```` + +## Example 2: General Application Form + +### Excel Sheet + +![General Application Form Excel](/assets/demo_form_en.png) + +### ExStruct JSON + +(Truncated for brevity) + +```json +{ + "book_name": "en_sf425.xlsx", + "sheets": { + "FFR": { + "rows": [ + { "r": 1, "c": { "0": "FEDERAL FINANCIAL REPORT" } }, + { "r": 3, "c": { "0": "(Follow form instructions)" } }, + { + "r": 4, + "c": { + "0": "1. Federal Agency and Organizational Element", + "5": "2. Federal Grant or Other Identifying Number Assigned by Federal Agency", + "20": "Page", + "23": " of" + } + }, + ... + ], + "shapes": [ + { + "id": 1, + "text": "Not Required by EPA", + "l": 575, + "t": 396, + "kind": "shape", + "type": "AutoShape-Rectangle" + } + ], + "print_areas": [{ "r1": 1, "c1": 0, "r2": 66, "c2": 23 }], + "merged_cells": [ + { "r1": 34, "c1": 15, "r2": 34, "c2": 23 }, + { + "r1": 56, + "c1": 10, + "r2": 57, + "c2": 17, + "v": "Federal Share Calculation" + }, + { "r1": 18, "c1": 10, "r2": 18, "c2": 23 }, + { "r1": 15, "c1": 0, "r2": 15, "c2": 1 }, + ... + ] + } + } +} + +``` + +### LLM reconstruction example + +```md + + +# **Federal Financial Report (FFR)** + +_(Follow form instructions)_ + +--- + +## **1. Federal Agency and Organizational Element** + +**United States Environmental Protection Agency** + +## **2. Federal Grant or Other Identifying Number Assigned by Federal Agency** + +**Page 1 of pages** + +--- + +## **3. Recipient Organization** + +_(Name and complete address including Zip code)_ + +--- + +## **4. Recipient Identifiers** + +- **4a. DUNS Number** +- **4b. EIN** +- **5. Recipient Account Number or Identifying Number** + _(To report multiple grants, use FFR Attachment)_ +- **6. Report Type** + - □ Quarterly + - □ Semi-Annual + - □ Annual + - □ Final +- **7. Basis of Accounting** + - □ Cash + - □ Accrual + +--- + +## **8. Project/Grant Period** + +- **From:** (Month, Day, Year) +- **To:** (Month, Day, Year) + +## **9. Reporting Period End Date** + +(Month, Day, Year) + +--- + +# **10. Transactions** + +_(Use lines a–c for single or multiple grant reporting)_ + +### **Federal Cash** + +_(To report multiple grants, also use FFR Attachment)_ + +- **a. Cash Receipts** +- **b. Cash Disbursements** +- **c. Cash on Hand** (line a minus b) + +_(Use lines d–o for single grant reporting)_ + +### **Federal Expenditures and Unobligated Balance** + +- **d. Total Federal funds authorized** +- **e. Federal share of expenditures** +- **f. Federal share of unliquidated obligations** — 0 +- **g. Total Federal share** (sum of lines e and f) — 0 +- **h. Unobligated balance of Federal funds** (line d minus g) — 0 + +### **Recipient Share** + +- **i. Total recipient share required** +- **j. Recipient share of expenditures** +- **k. Remaining recipient share to be provided** (line i minus j) — 0 + +### **Program Income** + +- **l. Total Federal program income earned** +- **m. Program income expended (deduction alternative)** +- **n. Program income expended (addition alternative)** +- **o. Unexpended program income** (line l minus line m or line n) + +--- + +# **11. Indirect Expense** + +| Type | Rate | Period From | Period To | Base | Amount Charged | Federal Share | +| -------------- | ---- | ----------- | --------- | ---- | -------------- | ------------- | +| | | | | | | | +| **g. Totals:** | | | | 0 | 0 | 0 | + +--- + +# **12. Remarks** + +Attach any explanations deemed necessary or information required by the Federal sponsoring agency in compliance with governing legislation. + +--- + +# **13. Certification** + +By signing this report, I certify that it is true, complete, and accurate to the best of my knowledge. +I am aware that any false, fictitious, or fraudulent information may subject me to criminal, civil, or administrative penalties. +_(U.S. Code, Title 18, Section 1001)_ + +- **a. Typed or Printed Name and Title of Authorized Certifying Official** +- **b. Signature of Authorized Certifying Official** +- **c. Telephone** (Area code, number, extension) +- **d. Email address** +- **e. Date Report Submitted** (Month, Day, Year) + +--- + +# **14. Agency Use Only** + +**Federal Share Calculation** + +--- + +### **Standard Form 425** + +**OMB Approval Number:** 0348-0061 +**Expiration Date:** 02/28/2015 + +--- + +# **Paperwork Burden Statement** + +According to the Paperwork Reduction Act, as amended, no persons are required to respond to a collection of information unless it displays a valid OMB Control Number. +The valid OMB control number for this information collection is **0348-0061**. + +Public reporting burden for this collection of information is estimated to average **1.5 hours per response**, including time for reviewing instructions, searching existing data sources, gathering and maintaining the data needed, and completing and reviewing the collection of information. + +Send comments regarding the burden estimate or any other aspect of this collection of information, including suggestions for reducing this burden, to: + +**Office of Management and Budget** +Paperwork Reduction Project (0348-0060) +Washington, DC 20503 + +--- + +### **Shape in Original Document** + +- _Not Required by EPA_ +``` + From this we can see: **exstruct's JSON is already in a format that AI can read and reason over directly.** @@ -345,6 +559,7 @@ Other LLM inference samples using this library can be found in the following dir - [Basic Excel](sample/basic/) - [Flowchart](sample/flowchart/) - [Gantt Chart](sample/gantt_chart/) +- [Application forms with many merged cells](sample/forms_with_many_merged_cells/) ### 4. Summary diff --git a/docs/README.ja.md b/docs/README.ja.md index 7091001..ba289fe 100644 --- a/docs/README.ja.md +++ b/docs/README.ja.md @@ -2,14 +2,14 @@ [![PyPI version](https://badge.fury.io/py/exstruct.svg)](https://pypi.org/project/exstruct/) [![PyPI Downloads](https://static.pepy.tech/personalized-badge/exstruct?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/exstruct) ![Licence: BSD-3-Clause](https://img.shields.io/badge/license-BSD--3--Clause-blue?style=flat-square) [![pytest](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml/badge.svg)](https://github.com/harumiWeb/exstruct/actions/workflows/pytest.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e081cb4f634e4175b259eb7c34f54f60)](https://app.codacy.com/gh/harumiWeb/exstruct/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![codecov](https://codecov.io/gh/harumiWeb/exstruct/graph/badge.svg?token=2XI1O8TTA9)](https://codecov.io/gh/harumiWeb/exstruct) -![ExStruct Image](assets/icon.webp) +![ExStruct Image](/assets/icon.webp) ExStruct は Excel ワークブックを読み取り、構造化データ(セル・テーブル候補・図形・チャート・SmartArt・印刷範囲ビュー)をデフォルトで JSON に出力します。必要に応じて YAML/TOON も選択でき、COM/Excel 環境ではリッチ抽出、非 COM 環境ではセル+テーブル候補+印刷範囲へのフォールバックで安全に動作します。LLM/RAG 向けに検出ヒューリスティックや出力モードを調整可能です。 ## 主な特徴 -- **Excel → 構造化 JSON**: セル、図形、チャート、SmartArt、テーブル候補、印刷範囲/自動改ページ範囲(PrintArea/PrintAreaView)をシート単位・範囲単位で出力。 -- **出力モード**: `light`(セル+テーブル候補のみ)、`standard`(テキスト付き図形+矢印、チャート、SmartArt)、`verbose`(全図形を幅高さ付きで出力、セルのハイパーリンクも出力)。 +- **Excel → 構造化 JSON**: セル、図形、チャート、SmartArt、テーブル候補、セル結合範囲、印刷範囲/自動改ページ範囲(PrintArea/PrintAreaView)をシート単位・範囲単位で出力。 +- **出力モード**: `light`(セル+テーブル候補のみ)、`standard`(テキスト付き図形+矢印、チャート、SmartArt、セル結合範囲)、`verbose`(全図形を幅高さ付きで出力、セルのハイパーリンクも出力)。 - **フォーマット**: JSON(デフォルトはコンパクト、`--pretty` で整形)、YAML、TOON(任意依存)。 - **テーブル検出のチューニング**: API でヒューリスティックを動的に変更可能。 - **ハイパーリンク抽出**: `verbose` モード(または `include_cell_links=True` 指定)でセルのリンクを `links` に出力。 @@ -149,18 +149,18 @@ exstruct input.xlsx --pdf --image --dpi 144 `.pdf` と `_images/` 配下に PNG を生成します。 -## ベンチマーク: Excel 構造化デモ +## 例 ①: Excel 構造化デモ 本ライブラリ exstruct がどの程度 Excel を構造化できるのかを示すため、 以下の 3 要素を 1 シートにまとめた Excel を解析し、 -その JSON 出力を用いた AI 推論精度ベンチマーク を掲載します。 +その JSON 出力を用いた LLM 推論例 を掲載します。 - 表(売上データ) - 折れ線グラフ - 図形のみで作成したフローチャート (下画像が実際のサンプル Excel シート) -![Sample Excel](assets/demo_sheet.png) +![Sample Excel](/assets/demo_sheet.png) サンプル Excel: `sample/sample.xlsx` ### 1. Input: Excel Sheet Overview @@ -335,9 +335,169 @@ flowchart TD ``` ```` -このことから、 +## 例 ②: 一般的な申請書 -**exstruct の JSON は AI にとって "そのまま意味として理解できる形式" である** +### Excel データ + +![一般的な申請書Excel](/assets/demo_form.ja.png) + +### ExStruct JSON + +※長いので一部省略 + +```json +{ + "book_name": "ja_form.xlsx", + "sheets": { + "Sheet1": { + "rows": [ + { "r": 1, "c": { "0": "介護保険負担限度額認定申請書" } }, + { + "r": 3, + "c": { "0": "(申請先)", "7": "     年    月    日" } + }, + { "r": 4, "c": { "1": "X市長 " } }, + ... + ], + "table_candidates": ["B25:C26", "C37:D50"], + "merged_cells": [ + { + "r1": 55, + "c1": 5, + "r2": 55, + "c2": 10, + "v": "申請者が被保険者本人の場合には、下記について記載は不要です。" + }, + { "r1": 54, "c1": 8, "r2": 54, "c2": 10 }, + { "r1": 51, "c1": 5, "r2": 52, "c2": 6, "v": "有価証券" }, + ... + ] + } + } +} + +``` + +### LLM 推論による ExStruct JSON → Markdown 変換結果 + +```md +# 介護保険負担限度額認定申請書 + +(申請先)              年  月  日 +X 市長 + +次のとおり関係書類を添えて、食費・居住費(滞在費)に係る負担限度額認定を申請します。 + +--- + +## 被保険者情報 + +| 項目 | 内容 | +| ------------ | ---------------------------- | +| フリガナ | | +| 被保険者氏名 | | +| 被保険者番号 | | +| 個人番号 | | +| 生年月日 | 明・大・昭  年  月  日 | +| 住所 | | +| 連絡先 | | + +--- + +## 入所(院)した介護保険施設 + +| 項目 | 内容 | +| --------------------- | ---------- | +| 施設所在地・名称(※) | | +| 連絡先 | | +| 入所(院)年月日 | 年 月 日 | + +**(※)介護保険施設に入所(院)していない場合、およびショートステイ利用時は記入不要** + +--- + +## 配偶者の有無 + +| 項目 | 内容 | +| ------------ | -------- | +| 配偶者の有無 | 有 ・ 無 | + +※「無」の場合、以下の「配偶者に関する事項」は記入不要 + +--- + +## 配偶者に関する事項 + +| 項目 | 内容 | +| ---------------------------------------------- | -------------------------- | +| フリガナ | | +| 氏名 | | +| 生年月日 | 明・大・昭 年 月 日 | +| 個人番号 | | +| 住所 | 〒 | +| 連絡先 | | +| 本年 1 月 1 日現在の住所(現住所と異なる場合) | 〒 | +| 課税状況 | 市町村民税:課税 ・ 非課税 | + +--- + +## 収入等に関する申告 + +以下の該当する項目にチェックしてください。 + +- □ ① 生活保護受給者 +- □ ② 市町村民税世帯非課税である老齢福祉年金受給者 +- □ ③ 市町村民税世帯非課税者で、課税年金収入額+遺族年金・障害年金+その他所得の合計が **年額 80 万円以下** +- □ ④ 同上で **80 万円超〜120 万円以下** +- □ ⑤ 同上で **120 万円超** + +※遺族年金には寡婦年金、寡夫年金、母子年金、準母子年金、遺児年金を含む + +--- + +## 預貯金等に関する申告 + +- □ 預貯金・有価証券等の合計額が以下の基準以下である + - ② の方:1000 万円(夫婦 2000 万円) + - ③ の方:650 万円(夫婦 1650 万円) + - ④ の方:550 万円(夫婦 1550 万円) + - ⑤ の方:500 万円(夫婦 1500 万円) + - ※第 2 号被保険者(40〜64 歳)は ③〜⑤ の方:1000 万円(夫婦 2000 万円)以下 + +### 預貯金等の内訳 + +| 項目 | 金額 | +| -------------------------- | ---------------- | +| 預貯金額 | 円 | +| 有価証券(評価概算額) | 円 | +| その他(現金・負債を含む) | 円(内容を記入) | + +--- + +## 申請者情報(※被保険者本人の場合は不要) + +| 項目 | 内容 | +| ---------------------- | ---- | +| 申請者氏名 | | +| 連絡先(自宅・勤務先) | | +| 申請者住所 | | +| 本人との関係 | | + +--- + +## 注意事項 + +1. この申請書における「配偶者」には、世帯分離している配偶者および内縁関係の者を含む。 +2. 預貯金等は、同じ種類を複数所有している場合すべて記入し、通帳等の写しを添付すること。 +3. 書き切れない場合は余白または別紙に記入して添付すること。 +4. 虚偽の申告により不正に特定入所者介護サービス費等の支給を受けた場合、介護保険法第 22 条第 1 項に基づき、支給額および最大 2 倍の加算金を返還する必要がある。 +``` + +## 考察 + +上記の実験結果から、 + +**ExStruct の JSON は AI にとって "そのまま意味として理解できる形式" である** ということが明確に示されています。 @@ -346,6 +506,7 @@ flowchart TD - [Basic Excel](sample/basic/) - [Flowchart](sample/flowchart/) - [Gantt Chart](sample/gantt_chart/) +- [Application forms with many merged cells](sample/forms_with_many_merged_cells/) ### 4. Summary @@ -383,11 +544,6 @@ ExStruct は主に **ライブラリ** として利用される想定で、サ - CLI `--auto-page-breaks-dir`(COM 限定)、`DestinationOptions.auto_page_breaks_dir`(推奨)、または `export_auto_page_breaks(...)` で自動改ページ範囲ごとにファイルを出力できます。自動改ページが存在しない場合、`export_auto_page_breaks(...)` は `ValueError` を送出します。 - `PrintAreaView` には範囲内の行・テーブル候補に加え、範囲と交差する図形/チャートを含みます(サイズ不明の図形は座標のみで判定)。`normalize=True` で行/列を範囲起点に再基準化できます。 -## ドキュメントビルド - -- サイトビルド前にモデル断片を再生成してください: `python scripts/gen_model_docs.py` -- mkdocs + mkdocstrings でローカルビルド(開発用依存が必要): `uv run mkdocs serve` または `uv run mkdocs build` - ## アーキテクチャ ExStruct はパイプライン型のアーキテクチャを採用し、 diff --git a/docs/agents/DATA_MODEL.md b/docs/agents/DATA_MODEL.md index 3ed8c7f..ff2284b 100644 --- a/docs/agents/DATA_MODEL.md +++ b/docs/agents/DATA_MODEL.md @@ -1,6 +1,6 @@ # ExStruct データモデル仕様 -**Version**: 0.13 +**Version**: 0.14 **Status**: Authoritative — 本ドキュメントは ExStruct が返す全モデルの唯一の正準ソースです。 core / io / integrate は必ずこの仕様に従うこと。モデルは **pydantic v2** で実装します。 @@ -148,7 +148,25 @@ PrintAreaView { --- -# 8. SheetData Model +# 8. MergedCell Model + +```jsonc +MergedCell { + r1: int // 開始行 (1-based) + c1: int // 開始列 (0-based) + r2: int // 終了行 (1-based, inclusive) + c2: int // 終了列 (0-based, inclusive) + v: str // 結合セルの代表値 (空文字の可能性あり) +} +``` + +補足: + +- `standard` / `verbose` で取得される + +--- + +# 9. SheetData Model ```jsonc SheetData { @@ -159,6 +177,7 @@ SheetData { print_areas: [PrintArea] auto_print_areas: [PrintArea] // 自動改ページ矩形(COM 前提、デフォルト無効) colors_map: {[colorHex: str]: [[int, int]]} // (row=1-based, col=0-based) のセル座標を列挙 + merged_cells: [MergedCell] // ?????? (standard/verbose) } ``` @@ -169,7 +188,7 @@ SheetData { --- -# 9. WorkbookData Model (トップレベル) +# 10. WorkbookData Model (トップレベル) ```jsonc WorkbookData { @@ -184,7 +203,7 @@ WorkbookData { --- -# 10. Export Helpers (SheetData / WorkbookData) +# 11. Export Helpers (SheetData / WorkbookData) 共通: @@ -205,7 +224,7 @@ WorkbookData { --- -# 11. Versioning Principles(エージェント向け) +# 12. Versioning Principles(エージェント向け) - モデル変更時は必ず本ファイルを先に更新する。 - モデルは純粋なデータコンテナとし、副作用を持たせない。 @@ -213,7 +232,7 @@ WorkbookData { --- -# 12. Changelog +# 13. Changelog - 0.3: serialize/save ヘルパーを追加、`WorkbookData` に `__iter__` / `__getitem__` を定義。 - 0.4: `CellRow.links` を追加(ハイパーリンクは opt-in、verbose でデフォルト有効)。 @@ -226,3 +245,4 @@ WorkbookData { - 0.11: コネクタのフィールド名を `begin_id` / `end_id` にリネーム。 - 0.12: SheetData に背景色情報を格納する`colors_map`を追加。 - 0.13: Shape を `Shape` / `Arrow` / `SmartArt` に分離し、`SmartArtNode` のネスト構造を追加。 +- 0.14: セル結合範囲データを持つ`MergedCell`,`SheetData.merged_cells` を追加 diff --git a/docs/agents/FEATURE_SPEC.md b/docs/agents/FEATURE_SPEC.md index f512aa7..32ae620 100644 --- a/docs/agents/FEATURE_SPEC.md +++ b/docs/agents/FEATURE_SPEC.md @@ -4,12 +4,11 @@ --- -## SmartArt解析機能追加 +## セル結合範囲取得機能の追加 -- shapeがSmartArtの場合に専用解析をするように条件分岐 -- Shapeの振る舞いが増えてきたので、ここでShape, Arrow, SmartArtにクラスを分ける -- SmartArtは基本はShapeのフィールドを持ちつつ、Nodeの情報を再帰的に持つようにする -- rootノードとそれ以外のノードでクラスを分ける +- 新たに`MergedCell`モデルを作成、座標情報とセル値を持つ +- `SheetData`に`merged_ranges`フィールドを追加し、list[MergedRange]を持つ +- デフォルトモード以上(standard/verbose)で取得するようにする。engineには無効化オプションをつけておく(outputoptionsで出力時に削除する方向で) ## 今後のオプション(検討メモ) diff --git a/docs/agents/ROADMAP.md b/docs/agents/ROADMAP.md index f13ad1d..55aaefa 100644 --- a/docs/agents/ROADMAP.md +++ b/docs/agents/ROADMAP.md @@ -36,6 +36,14 @@ - Shapes と Arrows の分離(後の SmartArt 追加のため) - SmartArt 解析 +## v0.3.2 + +- セル結合範囲取得機能追加 + +## v0.3.3 + +- 数式取得オプション追加 + ## v0.4.0 - Excel Form Controls 解析 diff --git a/docs/agents/TASKS.md b/docs/agents/TASKS.md index 767d666..fbccecc 100644 --- a/docs/agents/TASKS.md +++ b/docs/agents/TASKS.md @@ -1,34 +1,17 @@ # Task List -## 1. 既存実装の修正(モデル分離の影響対応) +## 結合セル(MergedCell)テスト -- [x] `src/exstruct/io/__init__.py` の `_filter_shapes_to_area` が `list[Shape | Arrow | SmartArt]` を受け取れるように型と処理を調整する -- [x] `src/exstruct/core/shapes.py` のコネクタ判定を `Arrow` 前提に変更する(`begin_arrow_style` / `end_arrow_style` などは `Arrow` のみ参照) -- [x] `src/exstruct/core/shapes.py` の接続 ID 参照を `Arrow` に限定し、`Shape` からの誤参照を除去する -- [x] `PrintAreaView` 側の `shapes` フィルタで `SmartArt` を落とさないことを確認する +- [ ] フィクスチャ作成:結合セルあり/なし、複数範囲、値あり/空文字の Excel を用意 +- [ ] OpenpyxlBackend.extract_merged_cells の正常系(座標・代表値)をユニットテスト +- [ ] OpenpyxlBackend.extract_merged_cells の例外時フォールバック(空マップ)をテスト +- [ ] ComBackend.extract_merged_cells が NotImplementedError を送出することをテスト +- [ ] Pipeline: standard/verbose で merged_cells を含み、light では空になることをテスト +- [ ] Pipeline: include_merged_cells=False で抽出ステップが無効化されることをテスト +- [ ] Modeling: SheetRawData→SheetData で merged_cells が保持されることをテスト +- [ ] Engine: OutputOptions.filters.include_merged_cells=False で出力から除外されることをテスト +- [ ] Export: dict_without_empty_values により merged_cells 空リストが出力されないことをテスト -## 2. SmartArt 取得機能の実装方針 +## カバレッジ対応 -- [x] `shape.HasSmartArt` を条件に SmartArt を抽出する -- [x] `SmartArt.Layout.Name` を `SmartArt.layout` に格納する -- [x] `SmartArt.AllNodes` を走査し、`level` と `text` を収集する -- [x] ノード配列から `SmartArtNode` のツリー(`nodes`)を構築する(`level` を使ったスタック組み立て) -- [x] `SmartArt` は `BaseShape` 相当の位置/サイズ/回転/テキストを併せて格納する - -## 3. 実装箇所の整理 - -- [x] `src/exstruct/core/shapes.py` に SmartArt 抽出用の関数を追加する(1 関数=1 責務を遵守) -- [x] `src/exstruct/core/shapes.py` のメイン抽出処理で `Shape` / `Arrow` / `SmartArt` に振り分ける -- [x] `src/exstruct/io/__init__.py` で `Shape | Arrow | SmartArt` のシリアライズ挙動が崩れないことを確認する - -## 4. 動作確認 - -- [x] 既存の shape / connector 抽出が壊れていないことを確認する -- [ ] SmartArt が含まれるブックで `SmartArt.nodes` が期待どおりに出力されることを確認する - -## 5. テストケース(カバレッジ維持) - -- [x] `SmartArt` の `nodes` がネスト構造でシリアライズされることを確認する -- [x] `Arrow` のみが `begin_id` / `end_id` を持ち、`Shape` では参照されないことを確認する -- [x] `_filter_shapes_to_area` が `Shape | Arrow | SmartArt` を受け取り、SmartArt も対象に含めることを確認する -- [x] `kind` による判別が想定どおり動くことを確認する +- [ ] 追加テストで 78% 以上の全体カバレッジを満たすことを確認 \ No newline at end of file diff --git a/docs/agents/TEST_REQUIREMENTS.md b/docs/agents/TEST_REQUIREMENTS.md index 05dd053..7d47622 100644 --- a/docs/agents/TEST_REQUIREMENTS.md +++ b/docs/agents/TEST_REQUIREMENTS.md @@ -1,9 +1,10 @@ # ExStruct テスト要件仕様書 -Version: 0.4 +Version: 0.5 Status: Required for Release ExStruct の全機能について、正式なテスト要件をまとめたドキュメントです。AI エージェント/人間開発者が自動テスト・手動テストを設計するための基盤とします。 +全体のコードカバレッジは **78% 以上** を満たすこと。 --- @@ -47,6 +48,13 @@ ExStruct の全機能について、正式なテスト要件をまとめたド - [COL-04] COM 利用時は `DisplayFormat.Interior` を参照し条件付き書式を含めて取得 - [COL-05] `_normalize_color_key` / `_normalize_rgb` は ARGB/#/auto/theme/indexed を正規化 +## 2.1.2 結合セル + +- [MRG-01] standard/verbose のみ `merged_cells` を抽出する(light は空) +- [MRG-02] 1-based row / 0-based column の座標で出力する +- [MRG-03] `v` は結合範囲の左上セル値(None は空文字) +- [MRG-04] 複数範囲がある場合も全件を保持する + ## 2.2 図形抽出 - [SHP-01] AutoShape の type を正規化 @@ -143,6 +151,7 @@ ExStruct の全機能について、正式なテスト要件をまとめたド - [EXP-22] serialize_workbook は未対応フォーマットで SerializationError - [EXP-23] export/process API は output_path/sheets_dir/print_areas_dir/auto_page_breaks_dir に str/Path を渡しても正しく出力できる - [EXP-24] fmt="yml" は yaml として扱い、拡張子は .yaml になる +- [EXP-25] OutputOptions の include_merged_cells=False で `merged_cells` を除外 --- @@ -213,6 +222,7 @@ ExStruct の全機能について、正式なテスト要件をまとめたド - [PIPE-06] print_areas は openpyxl の結果を保持し、COM は不足分のみ補完する - [PIPE-07] PipelineState は com_attempted/com_succeeded/fallback_reason を保持する - [PIPE-08] include_auto_page_breaks=False の場合は auto_page_breaks の COM ステップを含めない +- [PIPE-09] include_merged_cells=False の場合は merged_cells の抽出ステップを含めない - [PIPE-MOD-01] build_workbook_data は raw コンテナから WorkbookData/SheetData を構築する - [PIPE-MOD-02] collect_sheet_raw_data は抽出済みデータを raw コンテナにまとめる @@ -223,6 +233,8 @@ ExStruct の全機能について、正式なテスト要件をまとめたド - [BE-03] ComBackend は colors_map 抽出失敗時に None を返す - [BE-04] OpenpyxlBackend は colors_map 抽出失敗時に None を返す - [BE-05] ComBackend は print_areas 抽出失敗時に空マップで継続する +- [BE-06] OpenpyxlBackend は merged_cells 抽出失敗時に空マップで継続する +- [BE-07] ComBackend の merged_cells 未実装は NotImplementedError とする ## 2.8 Ranges diff --git a/docs/assets/demo_form.ja.png b/docs/assets/demo_form.ja.png new file mode 100644 index 0000000..a21c096 Binary files /dev/null and b/docs/assets/demo_form.ja.png differ diff --git a/docs/assets/demo_form_en.png b/docs/assets/demo_form_en.png new file mode 100644 index 0000000..7614617 Binary files /dev/null and b/docs/assets/demo_form_en.png differ diff --git a/docs/release-notes/v0.3.2.md b/docs/release-notes/v0.3.2.md new file mode 100644 index 0000000..d353016 --- /dev/null +++ b/docs/release-notes/v0.3.2.md @@ -0,0 +1,17 @@ +# v0.3.2 Release Notes + +This release adds merged cell range extraction and output controls, with +pipeline integration and tests to cover the new feature. + +## Highlights + +- `MergedCell` model and `SheetData.merged_cells` added to output. +- Merged cell ranges extracted via openpyxl in standard/verbose modes. +- Output can exclude merged cells via `OutputOptions.filters.include_merged_cells`. +- Pipeline/Backend/Modeling integrations updated with coverage for the new flow. + +## Compatibility Notes + +- Standard/verbose outputs now include `merged_cells` by default. +- Set `StructOptions.include_merged_cells=False` or + `OutputOptions.filters.include_merged_cells=False` to suppress the field. diff --git a/mkdocs.yml b/mkdocs.yml index 528976a..d143ed9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ nav: - CLI Guide: cli.md - Concept / Why ExStruct?: concept.md - Release Notes: + - v0.3.2: release-notes/v0.3.2.md - v0.3.1: release-notes/v0.3.1.md - v0.3.0: release-notes/v0.3.0.md - v0.2.90: release-notes/v0.2.90.md diff --git a/pyproject.toml b/pyproject.toml index e9fb6ca..68b391f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "exstruct" -version = "0.3.1" +version = "0.3.2" description = "Excel to structured JSON (tables, shapes, charts) for LLM/RAG pipelines" readme = "README.md" license = { file = "LICENSE" } diff --git a/sample/basic/sample_json_for_llm.md b/sample/basic/sample.json_for_llm.md similarity index 100% rename from sample/basic/sample_json_for_llm.md rename to sample/basic/sample.json_for_llm.md diff --git a/sample/forms_with_many_merged_cells/en_form_sf425/README.md b/sample/forms_with_many_merged_cells/en_form_sf425/README.md new file mode 100644 index 0000000..03a6da7 --- /dev/null +++ b/sample/forms_with_many_merged_cells/en_form_sf425/README.md @@ -0,0 +1,10 @@ +### Sample Form + +This project uses the U.S. Federal Financial Report (SF-425) +as a demonstration example. + +SF-425 is a U.S. Government form and is in the public domain +under 17 U.S.C. §105. + +Source: +https://www.epa.gov/financial/forms diff --git a/sample/forms_with_many_merged_cells/en_form_sf425/sample.json b/sample/forms_with_many_merged_cells/en_form_sf425/sample.json new file mode 100644 index 0000000..bb22a4f --- /dev/null +++ b/sample/forms_with_many_merged_cells/en_form_sf425/sample.json @@ -0,0 +1,359 @@ +{ + "book_name": "en_sf425.xlsx", + "sheets": { + "FFR": { + "rows": [ + { "r": 1, "c": { "0": "FEDERAL FINANCIAL REPORT" } }, + { "r": 3, "c": { "0": "(Follow form instructions)" } }, + { + "r": 4, + "c": { + "0": "1. Federal Agency and Organizational Element", + "5": "2. Federal Grant or Other Identifying Number Assigned by Federal Agency", + "20": "Page", + "23": " of" + } + }, + { + "r": 5, + "c": { + "0": " to Which Report is Submitted", + "5": " (To report multiple grants, use FFR Attachment)" + } + }, + { + "r": 7, + "c": { + "0": "United States Environmental Protection Agency", + "21": 1, + "23": "pages" + } + }, + { + "r": 8, + "c": { + "0": "3. Recipient Organization (Name and complete address including Zip code)" + } + }, + { + "r": 13, + "c": { + "0": "4a. DUNS Number ", + "2": "4b. EIN", + "5": "5. Recipient Account Number or Identifying Number", + "12": "6. Report Type", + "18": "7. Basis of Accounting" + } + }, + { + "r": 14, + "c": { + "5": " (To report multiple grants, use FFR Attachment)", + "12": "□ Quarterly \n□ Semi-Annual \n□ Annual \n□ Final", + "18": "□ Cash □ Accrual" + } + }, + { + "r": 16, + "c": { + "0": "8. Project/Grant Period", + "10": "9. Reporting Period End Date" + } + }, + { + "r": 17, + "c": { + "0": " From: (Month, Day, Year)", + "5": "To: (Month, Day, Year)", + "10": "(Month, Day, Year)" + } + }, + { + "r": 19, + "c": { "0": "10. Transactions ", "15": " Cumulative " } + }, + { + "r": 20, + "c": { "0": "(Use lines a-c for single or multiple grant reporting)" } + }, + { + "r": 21, + "c": { + "0": " Federal Cash (To report multiple grants, also use FFR Attachment):" + } + }, + { "r": 22, "c": { "0": " a. Cash Receipts " } }, + { "r": 23, "c": { "0": " b. Cash Disbursements" } }, + { "r": 24, "c": { "0": " c. Cash on Hand (line a minus b)" } }, + { "r": 25, "c": { "0": "(Use lines d-o for single grant reporting)" } }, + { + "r": 26, + "c": { "0": " Federal Expenditures and Unobligated Balance: " } + }, + { "r": 27, "c": { "0": " d. Total Federal funds authorized " } }, + { "r": 28, "c": { "0": " e. Federal share of expenditures " } }, + { + "r": 29, + "c": { + "0": " f. Federal share of unliquidated obligations", + "15": 0 + } + }, + { + "r": 30, + "c": { + "0": " g. Total Federal share (sum of lines e and f)", + "15": 0 + } + }, + { + "r": 31, + "c": { + "0": " h. Unobligated balance of Federal funds (line d minus g)", + "15": 0 + } + }, + { "r": 32, "c": { "0": " Recipient Share:" } }, + { "r": 33, "c": { "0": " i. Total recipient share required " } }, + { "r": 34, "c": { "0": " j. Recipient share of expenditures" } }, + { + "r": 35, + "c": { + "0": " k. Remaining recipient share to be provided (line i minus j)", + "15": 0 + } + }, + { "r": 36, "c": { "0": " Program Income:" } }, + { + "r": 37, + "c": { "0": " l. Total Federal program income earned" } + }, + { + "r": 38, + "c": { + "0": " m. Program income expended in accordance with the deduction alternative" + } + }, + { + "r": 39, + "c": { + "0": " n. Program income expended in accordance with the addition alternative" + } + }, + { + "r": 40, + "c": { + "0": " o. Unexpended program income (line l minus line m or line n)" + } + }, + { + "r": 41, + "c": { + "0": "11. Indirect Expense", + "1": " a. Type ", + "3": "b. Rate", + "5": "c. Period From", + "6": "Period To", + "7": "d. Base", + "10": "e. Amount Charged", + "18": "f. Federal Share" + } + }, + { "r": 44, "c": { "6": " g. Totals:", "7": 0, "10": 0, "18": 0 } }, + { + "r": 45, + "c": { + "0": "12. Remarks: Attach any explanations deemed necessary or information required by Federal sponsoring agency in compliance with governing legislation:" + } + }, + { + "r": 47, + "c": { + "0": "13. Certification: By signing this report, I certify that it is true, complete, and accurate to the best of my knowledge. I am aware that" + } + }, + { + "r": 48, + "c": { + "0": " any false, fictitious, or fraudulent information may subject me to criminal, civil, or administrative penalties. (U.S. Code, Title 18, Section 1001)" + } + }, + { + "r": 49, + "c": { + "0": "a. Typed or Printed Name and Title of Authorized Certifying Official", + "10": " c. Telephone (Area code, number and extension)" + } + }, + { "r": 51, "c": { "10": " d. Email address" } }, + { + "r": 53, + "c": { + "0": "b. Signature of Authorized Certifying Official ", + "10": " e. Date Report Submitted (Month, Day, Year)" + } + }, + { "r": 55, "c": { "10": "14. Agency use only:" } }, + { "r": 56, "c": { "10": "Federal Share Calculation" } }, + { "r": 58, "c": { "12": "Standard Form 425" } }, + { "r": 59, "c": { "12": "OMB Approval Number: 0348-0061 " } }, + { "r": 60, "c": { "12": "Expiration Date: 02/28/2015" } }, + { + "r": 61, + "c": { + "0": "Paperwork Burden Statement\nAccording to the Paperwork Reduction Act, as amended, no persons are required to respond to a collection of information unless it displays a valid OMB Control Number. The valid OMB control number for this information collection is 0348-0061. Public reporting burden for this collection of information is estimated to average 1.5 hours per response, including time for reviewing instructions, searching existing data sources, gathering and maintaining the data needed, and completing and reviewing the collection of information. Send comments regarding the burden estimate or any other aspect of this collection of information, including suggestions for reducing this burden, to the Office of Management and Budget, Paperwork Reduction Project ( 0348-0060), Washington, DC 20503." + } + } + ], + "shapes": [ + { + "id": 1, + "text": "Not Required by EPA", + "l": 575, + "t": 396, + "kind": "shape", + "type": "AutoShape-Rectangle" + } + ], + "print_areas": [{ "r1": 1, "c1": 0, "r2": 66, "c2": 23 }], + "merged_cells": [ + { "r1": 34, "c1": 15, "r2": 34, "c2": 23 }, + { + "r1": 56, + "c1": 10, + "r2": 57, + "c2": 17, + "v": "Federal Share Calculation" + }, + { "r1": 18, "c1": 10, "r2": 18, "c2": 23 }, + { "r1": 15, "c1": 0, "r2": 15, "c2": 1 }, + { + "r1": 16, + "c1": 10, + "r2": 16, + "c2": 23, + "v": "9. Reporting Period End Date" + }, + { "r1": 13, "c1": 2, "r2": 14, "c2": 4, "v": "4b. EIN" }, + { "r1": 27, "c1": 15, "r2": 27, "c2": 23 }, + { "r1": 13, "c1": 12, "r2": 13, "c2": 17, "v": "6. Report Type" }, + { + "r1": 13, + "c1": 18, + "r2": 13, + "c2": 23, + "v": "7. Basis of Accounting" + }, + { "r1": 46, "c1": 0, "r2": 46, "c2": 23 }, + { "r1": 39, "c1": 15, "r2": 39, "c2": 23 }, + { "r1": 23, "c1": 15, "r2": 23, "c2": 23 }, + { "r1": 7, "c1": 5, "r2": 7, "c2": 19 }, + { "r1": 43, "c1": 1, "r2": 43, "c2": 2 }, + { + "r1": 3, + "c1": 0, + "r2": 3, + "c2": 23, + "v": "(Follow form instructions)" + }, + { "r1": 54, "c1": 10, "r2": 54, "c2": 23 }, + { + "r1": 13, + "c1": 0, + "r2": 14, + "c2": 1, + "v": "4a. DUNS Number " + }, + { "r1": 12, "c1": 0, "r2": 12, "c2": 23 }, + { + "r1": 16, + "c1": 0, + "r2": 16, + "c2": 4, + "v": "8. Project/Grant Period" + }, + { + "r1": 7, + "c1": 0, + "r2": 7, + "c2": 4, + "v": "United States Environmental Protection Agency" + }, + { "r1": 28, "c1": 15, "r2": 28, "c2": 23 }, + { "r1": 42, "c1": 1, "r2": 42, "c2": 2 }, + { "r1": 19, "c1": 15, "r2": 19, "c2": 23, "v": " Cumulative " }, + { "r1": 37, "c1": 15, "r2": 37, "c2": 23 }, + { "r1": 18, "c1": 5, "r2": 18, "c2": 9 }, + { "r1": 50, "c1": 10, "r2": 50, "c2": 23 }, + { "r1": 44, "c1": 10, "r2": 44, "c2": 17, "v": "0" }, + { + "r1": 1, + "c1": 0, + "r2": 2, + "c2": 23, + "v": "FEDERAL FINANCIAL REPORT" + }, + { "r1": 55, "c1": 19, "r2": 57, "c2": 23, "v": " " }, + { "r1": 14, "c1": 18, "r2": 15, "c2": 23, "v": "□ Cash □ Accrual" }, + { "r1": 18, "c1": 0, "r2": 18, "c2": 4 }, + { + "r1": 17, + "c1": 5, + "r2": 17, + "c2": 9, + "v": "To: (Month, Day, Year)" + }, + { "r1": 30, "c1": 15, "r2": 30, "c2": 23, "v": "0" }, + { + "r1": 61, + "c1": 0, + "r2": 64, + "c2": 23, + "v": "Paperwork Burden Statement\nAccording to the Paperwork Reduction Act, as amended, no persons are required to respond to a collection of information unless it displays a valid OMB Control Number. The valid OMB control number for this information collection is 0348-0061. Public reporting burden for this collection of information is estimated to average 1.5 hours per response, including time for reviewing instructions, searching existing data sources, gathering and maintaining the data needed, and completing and reviewing the collection of information. Send comments regarding the burden estimate or any other aspect of this collection of information, including suggestions for reducing this burden, to the Office of Management and Budget, Paperwork Reduction Project ( 0348-0060), Washington, DC 20503." + }, + { "r1": 42, "c1": 7, "r2": 42, "c2": 9 }, + { "r1": 43, "c1": 10, "r2": 43, "c2": 17 }, + { "r1": 24, "c1": 15, "r2": 24, "c2": 23 }, + { "r1": 43, "c1": 3, "r2": 43, "c2": 4 }, + { "r1": 52, "c1": 10, "r2": 52, "c2": 23 }, + { "r1": 33, "c1": 15, "r2": 33, "c2": 23 }, + { "r1": 10, "c1": 0, "r2": 10, "c2": 23 }, + { "r1": 4, "c1": 20, "r2": 4, "c2": 22, "v": "Page" }, + { "r1": 44, "c1": 18, "r2": 44, "c2": 23, "v": "0" }, + { "r1": 41, "c1": 0, "r2": 43, "c2": 0, "v": "11. Indirect Expense" }, + { "r1": 42, "c1": 10, "r2": 42, "c2": 17 }, + { "r1": 15, "c1": 5, "r2": 15, "c2": 11 }, + { "r1": 15, "c1": 2, "r2": 15, "c2": 4 }, + { + "r1": 17, + "c1": 0, + "r2": 17, + "c2": 4, + "v": " From: (Month, Day, Year)" + }, + { "r1": 9, "c1": 0, "r2": 9, "c2": 23 }, + { "r1": 43, "c1": 7, "r2": 43, "c2": 9 }, + { "r1": 50, "c1": 0, "r2": 52, "c2": 9 }, + { "r1": 43, "c1": 18, "r2": 43, "c2": 23 }, + { "r1": 29, "c1": 15, "r2": 29, "c2": 23, "v": "0" }, + { "r1": 35, "c1": 15, "r2": 35, "c2": 23, "v": "0" }, + { "r1": 38, "c1": 15, "r2": 38, "c2": 23 }, + { "r1": 42, "c1": 3, "r2": 42, "c2": 4 }, + { "r1": 17, "c1": 10, "r2": 17, "c2": 23, "v": "(Month, Day, Year)" }, + { "r1": 44, "c1": 7, "r2": 44, "c2": 9, "v": "0" }, + { "r1": 42, "c1": 18, "r2": 42, "c2": 23 }, + { "r1": 11, "c1": 0, "r2": 11, "c2": 23 }, + { "r1": 31, "c1": 15, "r2": 31, "c2": 23, "v": "0" }, + { + "r1": 14, + "c1": 12, + "r2": 15, + "c2": 17, + "v": "□ Quarterly \n□ Semi-Annual \n□ Annual \n□ Final" + }, + { "r1": 22, "c1": 15, "r2": 22, "c2": 23 }, + { "r1": 40, "c1": 15, "r2": 40, "c2": 23 } + ] + } + } +} diff --git a/sample/forms_with_many_merged_cells/en_form_sf425/sample.json_for_llm.md b/sample/forms_with_many_merged_cells/en_form_sf425/sample.json_for_llm.md new file mode 100644 index 0000000..27c640d --- /dev/null +++ b/sample/forms_with_many_merged_cells/en_form_sf425/sample.json_for_llm.md @@ -0,0 +1,149 @@ + + +# **Federal Financial Report (FFR)** + +_(Follow form instructions)_ + +--- + +## **1. Federal Agency and Organizational Element** + +**United States Environmental Protection Agency** + +## **2. Federal Grant or Other Identifying Number Assigned by Federal Agency** + +**Page 1 of pages** + +--- + +## **3. Recipient Organization** + +_(Name and complete address including Zip code)_ + +--- + +## **4. Recipient Identifiers** + +- **4a. DUNS Number** +- **4b. EIN** +- **5. Recipient Account Number or Identifying Number** + _(To report multiple grants, use FFR Attachment)_ +- **6. Report Type** + - □ Quarterly + - □ Semi-Annual + - □ Annual + - □ Final +- **7. Basis of Accounting** + - □ Cash + - □ Accrual + +--- + +## **8. Project/Grant Period** + +- **From:** (Month, Day, Year) +- **To:** (Month, Day, Year) + +## **9. Reporting Period End Date** + +(Month, Day, Year) + +--- + +# **10. Transactions** + +_(Use lines a–c for single or multiple grant reporting)_ + +### **Federal Cash** + +_(To report multiple grants, also use FFR Attachment)_ + +- **a. Cash Receipts** +- **b. Cash Disbursements** +- **c. Cash on Hand** (line a minus b) + +_(Use lines d–o for single grant reporting)_ + +### **Federal Expenditures and Unobligated Balance** + +- **d. Total Federal funds authorized** +- **e. Federal share of expenditures** +- **f. Federal share of unliquidated obligations** — 0 +- **g. Total Federal share** (sum of lines e and f) — 0 +- **h. Unobligated balance of Federal funds** (line d minus g) — 0 + +### **Recipient Share** + +- **i. Total recipient share required** +- **j. Recipient share of expenditures** +- **k. Remaining recipient share to be provided** (line i minus j) — 0 + +### **Program Income** + +- **l. Total Federal program income earned** +- **m. Program income expended (deduction alternative)** +- **n. Program income expended (addition alternative)** +- **o. Unexpended program income** (line l minus line m or line n) + +--- + +# **11. Indirect Expense** + +| Type | Rate | Period From | Period To | Base | Amount Charged | Federal Share | +| -------------- | ---- | ----------- | --------- | ---- | -------------- | ------------- | +| | | | | | | | +| **g. Totals:** | | | | 0 | 0 | 0 | + +--- + +# **12. Remarks** + +Attach any explanations deemed necessary or information required by the Federal sponsoring agency in compliance with governing legislation. + +--- + +# **13. Certification** + +By signing this report, I certify that it is true, complete, and accurate to the best of my knowledge. +I am aware that any false, fictitious, or fraudulent information may subject me to criminal, civil, or administrative penalties. +_(U.S. Code, Title 18, Section 1001)_ + +- **a. Typed or Printed Name and Title of Authorized Certifying Official** +- **b. Signature of Authorized Certifying Official** +- **c. Telephone** (Area code, number, extension) +- **d. Email address** +- **e. Date Report Submitted** (Month, Day, Year) + +--- + +# **14. Agency Use Only** + +**Federal Share Calculation** + +--- + +### **Standard Form 425** + +**OMB Approval Number:** 0348-0061 +**Expiration Date:** 02/28/2015 + +--- + +# **Paperwork Burden Statement** + +According to the Paperwork Reduction Act, as amended, no persons are required to respond to a collection of information unless it displays a valid OMB Control Number. +The valid OMB control number for this information collection is **0348-0061**. + +Public reporting burden for this collection of information is estimated to average **1.5 hours per response**, including time for reviewing instructions, searching existing data sources, gathering and maintaining the data needed, and completing and reviewing the collection of information. + +Send comments regarding the burden estimate or any other aspect of this collection of information, including suggestions for reducing this burden, to: + +**Office of Management and Budget** +Paperwork Reduction Project (0348-0060) +Washington, DC 20503 + +--- + +### **Shape in Original Document** + +- _Not Required by EPA_ diff --git a/sample/forms_with_many_merged_cells/en_form_sf425/sample.xlsx b/sample/forms_with_many_merged_cells/en_form_sf425/sample.xlsx new file mode 100644 index 0000000..210133c Binary files /dev/null and b/sample/forms_with_many_merged_cells/en_form_sf425/sample.xlsx differ diff --git a/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json new file mode 100644 index 0000000..e759226 --- /dev/null +++ b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json @@ -0,0 +1,366 @@ +{ + "book_name": "ja_form.xlsx", + "sheets": { + "Sheet1": { + "rows": [ + { "r": 1, "c": { "0": "介護保険負担限度額認定申請書" } }, + { + "r": 3, + "c": { "0": "(申請先)", "7": "     年    月    日" } + }, + { "r": 4, "c": { "1": "X市長 " } }, + { + "r": 5, + "c": { + "0": "次のとおり関係書類を添えて、食費・居住費(滞在費)に係る負担限度額認定を申請します。" + } + }, + { "r": 6, "c": { "0": "フ リ ガ ナ", "7": " 被保険者番号" } }, + { "r": 7, "c": { "0": "被保険者氏名" } }, + { "r": 8, "c": { "7": "個人番号" } }, + { + "r": 9, + "c": { + "0": "生年月日", + "2": "明・大・昭      年     月     日" + } + }, + { "r": 10, "c": { "0": "住   所" } }, + { "r": 12, "c": { "7": "連絡先" } }, + { + "r": 13, + "c": { + "0": "入所(院)し       た介護保険施        設の所在地及     び名称(※)" + } + }, + { "r": 14, "c": { "7": "連絡先" } }, + { + "r": 17, + "c": { + "0": "入所(院)", + "2": "     年   月   日", + "5": "(※)介護保険施設に入所(院)していない場合及び                        ショートスティを利用している場合は、記入不要です。" + } + }, + { "r": 18, "c": { "0": "年月日(*)" } }, + { + "r": 20, + "c": { + "0": "配偶者の有無", + "2": "有     ・     無", + "5": "左記において「無」の場合は、以下の「配偶者に関する事項」につい                   ては、記入不要です。" + } + }, + { "r": 22, "c": { "0": "配偶者に関する事項", "1": "フ リ ガ ナ" } }, + { "r": 23, "c": { "1": "氏  名" } }, + { + "r": 25, + "c": { + "1": "生年月日", + "2": "  明  ・  大  ・  昭       年       月       日", + "8": "個人番号" + } + }, + { "r": 26, "c": { "1": "住  所", "2": "〒", "7": "連絡先" } }, + { + "r": 29, + "c": { + "1": "本年1月1日現在の住所   (現住所と     異なる場合)", + "2": "〒" + } + }, + { + "r": 33, + "c": { + "1": "課税状況", + "2": "市町村民税", + "4": "  課税       ・       非課税" + } + }, + { + "r": 35, + "c": { + "0": "収入等に関    する申告", + "2": "□", + "3": "①生活保護受給者/②市町村民税世帯非課税である老齢福祉年金受給者" + } + }, + { + "r": 37, + "c": { "2": "□", "3": "③市町村民税世帯非課税者であって、" } + }, + { + "r": 38, + "c": { + "3": "課税年金収入額と 【遺族年金※・障害年金】の収入額、その他の合計所得金額の合計額が" + } + }, + { + "r": 39, + "c": { + "3": "年額80万円以下です。(受給している年金に〇して下さい。以下同じ。)" + } + }, + { + "r": 40, + "c": { + "3": "  ※ 寡婦年金、かん夫年金、母子年金、準母子年金、遺児年金を含みます。以下同じ。" + } + }, + { + "r": 41, + "c": { "2": "□", "3": "④市町村民税世帯非課税者であって、" } + }, + { + "r": 42, + "c": { + "3": "課税年金収入額と 【遺族年金※・障害年金】の収入額、その他の合計所得金額の合計額が" + } + }, + { "r": 43, "c": { "3": "年額80万円を超え、120万円以下です。" } }, + { + "r": 44, + "c": { "2": "□", "3": "⑤市町村民税世帯非課税者であって、" } + }, + { + "r": 45, + "c": { + "3": "課税年金収入額と 【遺族年金※・障害年金】の収入額、その他の合計所得金額の合計額が" + } + }, + { "r": 46, "c": { "3": "年額120万円を超えます。" } }, + { + "r": 47, + "c": { + "0": "預貯金等に     関する申告", + "2": "□", + "3": "預貯金、有価証券等の金額の合計が②の方は1000万円(夫婦は2000万円)、" + } + }, + { + "r": 48, + "c": { + "3": "③の方は650万円(同1650万円)、④の方は550万円(同1550万円)、" + } + }, + { + "r": 49, + "c": { "3": "⑤の方は500万円(同1500万円)以下です。" } + }, + { + "r": 50, + "c": { + "3": "※第2号被保険者(40歳以上64歳以下)の場合、③~⑤の方は1000万円(夫婦は2000万円)以下です。" + } + }, + { + "r": 51, + "c": { + "2": "預貯金額", + "4": "円", + "5": "有価証券", + "7": "円", + "8": "その他", + "10": "(        )※" + } + }, + { + "r": 52, + "c": { "8": "(現金・負債      を含む)", "10": "円" } + }, + { + "r": 53, + "c": { "5": "(評価概算額)", "10": "※内容を記入してください" } + }, + { + "r": 55, + "c": { + "5": "申請者が被保険者本人の場合には、下記について記載は不要です。" + } + }, + { + "r": 56, + "c": { "0": " 申請者氏名", "8": " 連絡先(自宅・勤務先)" } + }, + { "r": 58, "c": { "0": " 申請者住所", "10": "本人との関係" } }, + { "r": 63, "c": { "0": "注意事項" } }, + { + "r": 64, + "c": { + "0": -1, + "1": " この申請書における「配偶者」については、世帯分離している配偶者又は内縁関係の者を含みます。" + } + }, + { + "r": 65, + "c": { + "0": -2, + "1": " 預貯金等に関しては、同じ種類の預貯金等を複数所有している場合は、そのすべてを記入し、通帳等の写し                  を添付してください。" + } + }, + { + "r": 67, + "c": { + "0": -3, + "1": " 書き切れない場合は、余白に記入するか又は別紙に記入の上添付してください。" + } + }, + { + "r": 68, + "c": { + "0": -4, + "1": " 虚偽の申告により不正に特定入所者介護サービス費等の支給を受けた場合には、介護保険法第22条第1項              の規定に基づき、支給された額及び最大2倍の加算金を返還していただくことがあります。" + } + } + ], + "table_candidates": ["B25:C26", "C37:D50"], + "merged_cells": [ + { + "r1": 55, + "c1": 5, + "r2": 55, + "c2": 10, + "v": "申請者が被保険者本人の場合には、下記について記載は不要です。" + }, + { "r1": 54, "c1": 8, "r2": 54, "c2": 10 }, + { "r1": 51, "c1": 5, "r2": 52, "c2": 6, "v": "有価証券" }, + { + "r1": 52, + "c1": 8, + "r2": 53, + "c2": 9, + "v": "(現金・負債      を含む)" + }, + { + "r1": 47, + "c1": 0, + "r2": 53, + "c2": 1, + "v": "預貯金等に     関する申告" + }, + { "r1": 10, "c1": 0, "r2": 12, "c2": 1, "v": "住   所" }, + { "r1": 10, "c1": 2, "r2": 10, "c2": 10 }, + { "r1": 7, "c1": 3, "r2": 8, "c2": 4 }, + { + "r1": 3, + "c1": 7, + "r2": 3, + "c2": 10, + "v": "     年    月    日" + }, + { "r1": 6, "c1": 7, "r2": 7, "c2": 7, "v": " 被保険者番号" }, + { "r1": 7, "c1": 6, "r2": 8, "c2": 6 }, + { "r1": 22, "c1": 0, "r2": 33, "c2": 0, "v": "配偶者に関する事項" }, + { "r1": 11, "c1": 3, "r2": 12, "c2": 6 }, + { + "r1": 1, + "c1": 0, + "r2": 1, + "c2": 10, + "v": "介護保険負担限度額認定申請書" + }, + { "r1": 6, "c1": 0, "r2": 6, "c2": 1, "v": "フ リ ガ ナ" }, + { "r1": 35, "c1": 2, "r2": 36, "c2": 2, "v": "□" }, + { "r1": 6, "c1": 3, "r2": 6, "c2": 4 }, + { "r1": 23, "c1": 1, "r2": 24, "c2": 1, "v": "氏  名" }, + { "r1": 6, "c1": 8, "r2": 7, "c2": 10 }, + { "r1": 20, "c1": 0, "r2": 21, "c2": 1, "v": "配偶者の有無" }, + { "r1": 26, "c1": 1, "r2": 28, "c2": 1, "v": "住  所" }, + { "r1": 25, "c1": 8, "r2": 25, "c2": 9, "v": "個人番号" }, + { "r1": 47, "c1": 2, "r2": 50, "c2": 2, "v": "□" }, + { "r1": 8, "c1": 8, "r2": 8, "c2": 10 }, + { + "r1": 29, + "c1": 1, + "r2": 32, + "c2": 1, + "v": "本年1月1日現在の住所   (現住所と     異なる場合)" + }, + { "r1": 18, "c1": 0, "r2": 18, "c2": 1, "v": "年月日(*)" }, + { + "r1": 13, + "c1": 0, + "r2": 16, + "c2": 1, + "v": "入所(院)し       た介護保険施        設の所在地及     び名称(※)" + }, + { "r1": 3, "c1": 0, "r2": 3, "c2": 1, "v": "(申請先)" }, + { + "r1": 9, + "c1": 2, + "r2": 9, + "c2": 6, + "v": "明・大・昭      年     月     日" + }, + { + "r1": 20, + "c1": 2, + "r2": 21, + "c2": 4, + "v": "有     ・     無" + }, + { + "r1": 68, + "c1": 1, + "r2": 69, + "c2": 10, + "v": " 虚偽の申告により不正に特定入所者介護サービス費等の支給を受けた場合には、介護保険法第22条第1項              の規定に基づき、支給された額及び最大2倍の加算金を返還していただくことがあります。" + }, + { + "r1": 20, + "c1": 5, + "r2": 21, + "c2": 10, + "v": "左記において「無」の場合は、以下の「配偶者に関する事項」につい                   ては、記入不要です。" + }, + { "r1": 9, "c1": 8, "r2": 9, "c2": 10 }, + { "r1": 51, "c1": 4, "r2": 53, "c2": 4, "v": "円" }, + { + "r1": 65, + "c1": 1, + "r2": 66, + "c2": 10, + "v": " 預貯金等に関しては、同じ種類の預貯金等を複数所有している場合は、そのすべてを記入し、通帳等の写し                  を添付してください。" + }, + { + "r1": 17, + "c1": 5, + "r2": 18, + "c2": 10, + "v": "(※)介護保険施設に入所(院)していない場合及び                        ショートスティを利用している場合は、記入不要です。" + }, + { "r1": 17, "c1": 0, "r2": 17, "c2": 1, "v": "入所(院)" }, + { "r1": 41, "c1": 2, "r2": 43, "c2": 2, "v": "□" }, + { + "r1": 35, + "c1": 3, + "r2": 36, + "c2": 10, + "v": "①生活保護受給者/②市町村民税世帯非課税である老齢福祉年金受給者" + }, + { "r1": 44, "c1": 2, "r2": 46, "c2": 2, "v": "□" }, + { "r1": 51, "c1": 2, "r2": 53, "c2": 3, "v": "預貯金額" }, + { "r1": 51, "c1": 8, "r2": 51, "c2": 9, "v": "その他" }, + { "r1": 7, "c1": 0, "r2": 8, "c2": 1, "v": "被保険者氏名" }, + { "r1": 51, "c1": 7, "r2": 53, "c2": 7, "v": "円" }, + { + "r1": 35, + "c1": 0, + "r2": 46, + "c2": 1, + "v": "収入等に関    する申告" + }, + { + "r1": 17, + "c1": 2, + "r2": 18, + "c2": 4, + "v": "     年   月   日" + }, + { "r1": 37, "c1": 2, "r2": 40, "c2": 2, "v": "□" }, + { "r1": 9, "c1": 0, "r2": 9, "c2": 1, "v": "生年月日" }, + { "r1": 53, "c1": 5, "r2": 53, "c2": 6, "v": "(評価概算額)" } + ] + } + } +} diff --git a/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json_for_llm.md b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json_for_llm.md new file mode 100644 index 0000000..49441b1 --- /dev/null +++ b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.json_for_llm.md @@ -0,0 +1,110 @@ +# 介護保険負担限度額認定申請書 + +(申請先)              年  月  日 +X 市長 + +次のとおり関係書類を添えて、食費・居住費(滞在費)に係る負担限度額認定を申請します。 + +--- + +## 被保険者情報 + +| 項目 | 内容 | +| ------------ | ---------------------------- | +| フリガナ | | +| 被保険者氏名 | | +| 被保険者番号 | | +| 個人番号 | | +| 生年月日 | 明・大・昭  年  月  日 | +| 住所 | | +| 連絡先 | | + +--- + +## 入所(院)した介護保険施設 + +| 項目 | 内容 | +| --------------------- | ---------- | +| 施設所在地・名称(※) | | +| 連絡先 | | +| 入所(院)年月日 | 年 月 日 | + +**(※)介護保険施設に入所(院)していない場合、およびショートステイ利用時は記入不要** + +--- + +## 配偶者の有無 + +| 項目 | 内容 | +| ------------ | -------- | +| 配偶者の有無 | 有 ・ 無 | + +※「無」の場合、以下の「配偶者に関する事項」は記入不要 + +--- + +## 配偶者に関する事項 + +| 項目 | 内容 | +| ---------------------------------------------- | -------------------------- | +| フリガナ | | +| 氏名 | | +| 生年月日 | 明・大・昭 年 月 日 | +| 個人番号 | | +| 住所 | 〒 | +| 連絡先 | | +| 本年 1 月 1 日現在の住所(現住所と異なる場合) | 〒 | +| 課税状況 | 市町村民税:課税 ・ 非課税 | + +--- + +## 収入等に関する申告 + +以下の該当する項目にチェックしてください。 + +- □ ① 生活保護受給者 +- □ ② 市町村民税世帯非課税である老齢福祉年金受給者 +- □ ③ 市町村民税世帯非課税者で、課税年金収入額+遺族年金・障害年金+その他所得の合計が **年額 80 万円以下** +- □ ④ 同上で **80 万円超〜120 万円以下** +- □ ⑤ 同上で **120 万円超** + +※遺族年金には寡婦年金、寡夫年金、母子年金、準母子年金、遺児年金を含む + +--- + +## 預貯金等に関する申告 + +- □ 預貯金・有価証券等の合計額が以下の基準以下である + - ② の方:1000 万円(夫婦 2000 万円) + - ③ の方:650 万円(夫婦 1650 万円) + - ④ の方:550 万円(夫婦 1550 万円) + - ⑤ の方:500 万円(夫婦 1500 万円) + - ※第 2 号被保険者(40〜64 歳)は ③〜⑤ の方:1000 万円(夫婦 2000 万円)以下 + +### 預貯金等の内訳 + +| 項目 | 金額 | +| -------------------------- | ---------------- | +| 預貯金額 | 円 | +| 有価証券(評価概算額) | 円 | +| その他(現金・負債を含む) | 円(内容を記入) | + +--- + +## 申請者情報(※被保険者本人の場合は不要) + +| 項目 | 内容 | +| ---------------------- | ---- | +| 申請者氏名 | | +| 連絡先(自宅・勤務先) | | +| 申請者住所 | | +| 本人との関係 | | + +--- + +## 注意事項 + +1. この申請書における「配偶者」には、世帯分離している配偶者および内縁関係の者を含む。 +2. 預貯金等は、同じ種類を複数所有している場合すべて記入し、通帳等の写しを添付すること。 +3. 書き切れない場合は余白または別紙に記入して添付すること。 +4. 虚偽の申告により不正に特定入所者介護サービス費等の支給を受けた場合、介護保険法第 22 条第 1 項に基づき、支給額および最大 2 倍の加算金を返還する必要がある。 diff --git a/sample/forms_with_many_merged_cells/ja_general_form/ja_form.xlsx b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.xlsx new file mode 100644 index 0000000..20c66fa Binary files /dev/null and b/sample/forms_with_many_merged_cells/ja_general_form/ja_form.xlsx differ diff --git a/sample/forms_with_many_merged_cells/ja_general_form/notebooklm.chat.md b/sample/forms_with_many_merged_cells/ja_general_form/notebooklm.chat.md new file mode 100644 index 0000000..b50ab54 --- /dev/null +++ b/sample/forms_with_many_merged_cells/ja_general_form/notebooklm.chat.md @@ -0,0 +1,167 @@ +> 以xStruct JSON をそのままテキストとして NotebookLM に投入して中身についての質問を行った結果です。 + +# 📝 Japanese Nursing Care Benefit Limit Application Form + +```md +提供された資料は、介護施設等における**食費や居住費の負担軽減**を自治体へ申請するための「**介護保険負担限度額認定申請書**」の構成案です。この書類には、**被保険者本人**の氏名、住所、マイナンバーといった基本情報のほか、**配偶者の有無**やその資産状況を記載する欄が設けられています。具体的には、預貯金や有価証券などの**資産合計額**、および年金受給額を含む**所得状況**を正確に申告することが求められます。末尾の注意事項では、通帳の写しの添付義務や、**虚偽の申告**を行った場合の罰則および給付金の返還義務についても明記されています。全体として、経済的な補助を受ける資格があるかを判定するための**行政手続き用フォーマット**となっています。 +``` + +--- + +**質問: 私はこの申請書のどこを記入すればよいですか?** + +```md +ご提示いただいた「**介護保険負担限度額認定申請書**」の主な記入項目について、出典(ソース)に基づきご案内します。 + +この申請書は、食費や居住費の負担軽減を申請するためのもので、主に以下の項目を記入する必要があります。 + +### 1. 被保険者(あなた)に関する基本情報 + +まず、申請書の冒頭にあるあなた自身の情報を記入します。 + +- **申請日と申請先**: 申請する年月日と、市長名(例:X 市長)を記入します。 +- **氏名・住所等**: あなたのフリガナ、氏名、被保険者番号、個人番号(マイナンバー)、生年月日、住所、および連絡先を記入してください。 + +### 2. 介護保険施設の利用状況 + +- **施設名と所在地**: すでに入所(入院)している場合は、その施設の所在地と名称を記入します。 +- **入所年月日**: 施設に入った日付を記入します。 +- **※記入不要な場合**: 施設に入所していない場合や、**ショートステイ**を利用している場合は、この欄の記入は不要です。 + +### 3. 配偶者に関する事項 + +- **配偶者の有無**: 「有」か「無」に丸をつけます。 +- **配偶者の詳細**: 「有」の場合は、配偶者の氏名、生年月日、個人番号、住所、連絡先、および課税状況を記入します。 +- **※記入不要な場合**: 配偶者が「無」の場合は、この項目全体の記入は不要です。なお、ここでの配偶者には、世帯分離している場合や内縁関係の方も含まれます。 + +### 4. 収入および預貯金等に関する申告(重要) + +負担限度額を決定するための重要な判断材料となります。 + +- **収入等**: 自分の状況に該当する項目(①〜⑤)のチェックボックスに印をつけます。これには生活保護の受給状況や、年金収入額(遺族年金・障害年金を含む合計所得)などが関わります。 +- **預貯金等**: 預貯金、有価証券、現金、負債などの合計額が、規定の金額(例:単身 1,000 万円、夫婦 2,000 万円など、区分により異なる)以下であることを確認し、チェックを入れます。 +- **具体的な金額**: 預貯金額、有価証券の評価概算額、その他の金額をそれぞれ記入します。 + +### 5. 申請者が本人以外の場合 + +- **代理人情報**: 申請者が被保険者本人ではなく、家族などが代筆・申請する場合は、申請者の氏名、住所、連絡先、本人との関係を記入します。 +- **※記入不要な場合**: あなた(被保険者)本人が申請する場合は、この欄の記載は不要です。 + +--- + +**補足事項** + +- 預貯金等に関しては、通帳の写しなどの添付資料が必要です。また、複数の口座がある場合はすべて記入する必要があります。 +- 虚偽の申告をした場合、給付額の返還だけでなく、最大 2 倍の加算金を課される可能性があるため注意が必要です。 + +この申請書は、いわば**「家計の健康診断書」**のようなものです。現在の収入や蓄えの状態を正確に伝えることで、適切なサポート(費用の減免)を受けるための手続きとなります。 +``` + +--- + +**質問: 収入等の項目はどれにチェックを付けたらいい?** + +```md +「収入等に関する申告」の欄には、あなたの現在の**住民税の課税状況**、**受給している年金の種類**、および**年間の合計収入額**に応じて、以下の**① から ⑤ のいずれか 1 つ**にチェックを付けてください。 + +### 1. 特別な受給状況に該当する場合 + +- **① にチェック**: **生活保護**を受給している場合。 +- **② にチェック**: 世帯全員が市町村民税非課税で、かつ**「老齢福祉年金」**を受給している場合。 + +### 2. 世帯全員が市町村民税非課税の場合(③〜⑤) + +世帯全員が住民税非課税である方は、**「課税年金収入額 + 遺族年金・障害年金 + その他の合計所得金額」**の合計(年額)によって選ぶ項目が変わります。 + +- **③ にチェック**: 合計が**年額 80 万円以下**の場合。 +- **④ にチェック**: 合計が**年額 80 万円を超え、120 万円以下**の場合。 +- **⑤ にチェック**: 合計が**年額 120 万円を超える**場合。 + +--- + +### 記入時の注意点 + +- **年金の種類の確認**: 遺族年金には、寡婦年金、かん夫年金、母子年金、準母子年金、遺児年金も含まれます。 +- **丸印をつける**: ③〜⑤ にチェックを入れる場合は、あわせて項目内にある【遺族年金・障害年金】のうち、**実際に受給している年金の種類に〇**をつけてください。 +- **預貯金等との連動**: ここで選んだ番号(②〜⑤)によって、後の項目である「預貯金等に関する申告」で認められる資産の限度額が異なります(例:② の方は単身 1,000 万円、⑤ の方は 500 万円以下など)。 + +**「収入等のチェック」は、いわば「サポートの階層(ステージ)を選ぶための階段」のようなものです。** 収入の階段を一段ずつ確認し、自分がどの高さに該当するかを特定することで、受けることができる減免の幅が決まります。 +``` + +## 比較用資料: PDFをNotebookLMに渡した場合の回答 + +```md +この文書は、介護保険サービスを利用する際の**食費や居住費の負担軽減**を申請するための**公式な申請様式**です。申請者は、自身の**世帯の課税状況**や、障害年金を含む**年金収入の合計額**を正確に報告する必要があります。また、預貯金や有価証券といった**資産の総保有額**が一定基準以下であることを証明しなければなりません。これには**配偶者の所得や資産**も含まれ、基準を満たすことで介護施設等の利用料が減額される仕組みです。各項目は**所得段階別の判定基準**に基づいて構成されており、生活保護受給者から非課税世帯まで幅広く対応しています。 +``` + +--- + +**質問: 私はこの申請書のどこを記入すればよいですか?** + +```md +ご提示いただいた「介護保険負担限度額認定申請書」の主な記入項目について、出典に基づいてご説明します。この申請書は、介護保険施設における食費・居住費(滞在費)の負担軽減を受けるためのものです。 + +主に以下の項目を記入する必要があります。 + +### 1. 被保険者(本人)に関する基本情報 +* **被保険者番号、氏名(フリガナ)、住所、生年月日、個人番号(マイナンバー)**を記入します。 +* 連絡先(電話番号)や、現在の住所が今年の1月1日時点の住所と異なる場合は、その当時の住所も記入します。 + +### 2. 世帯の課税状況と収入の申告 +ご自身の世帯の状況に合わせて、以下のいずれかを選択します。 +* **①生活保護受給者**、または**②老齢福祉年金受給者**。 +* **③〜⑤市町村民税世帯非課税者**:年金収入(遺族年金・障害年金を含む)やその他の合計所得金額の合計額に応じて区分が分かれています(80万円以下、80万円超120万円以下、120万円超など)。 + +### 3. 預貯金等に関する申告 +資産の合計額が、お選びの区分(②〜⑤)に応じた基準額以下であることを確認し、具体的な金額を記入します。 +* **預貯金額、有価証券(評価概算額)、その他(現金・負債を含む)**の金額を記入してください。 +* 同じ種類の預貯金等を複数持っている場合は、そのすべてを記入し、通帳等の写しを添える必要があります。 + +### 4. 配偶者に関する事項 +* まず**配偶者の「有・無」**に印をつけます。 +* 「無」の場合は、この欄の記入は不要です。 +* 「有」の場合は、氏名、生年月日、住所、個人番号を記入します。なお、この「配偶者」には、世帯分離している場合や内縁関係にある方も含まれます。 + +### 5. 介護保険施設に関する情報 +* **現在入所(院)している施設がある場合**、その所在地と名称を記入します。 +* ただし、施設に入所していない場合や、ショートステイ(短期入所)のみを利用している場合は、記入不要です。 + +### 6. 申請者(窓口に来る方・手続きをする方)の情報 +* **被保険者本人が申請する場合は、この欄の記入は不要です**。 +* 家族などが代理で申請する場合は、申請者の氏名、住所、本人との関係、連絡先を記入してください。 + +最後に、**申請年月日**を忘れずに記入してください。 + +この申請は、いわば「介護費用の家計診断」のようなものです。ご自身やご家族の家計状況を正確に伝えることで、所得に応じた適切な負担軽減を受けるための準備となります。 +``` + +--- + +**質問: 収入等の項目はどれにチェックを付けたらいい?** + +```md +「収入等に関する申告」の欄では、ご自身の世帯の課税状況や年金などの年間収入額に応じて、**以下の5つの項目のうち当てはまるもの1つ**にチェックを入れます。 + +### 1. 生活保護または老齢福祉年金を受給している場合 +* **①生活保護受給者**、または**②市町村民税世帯非課税である老齢福祉年金受給者**の項目にチェックを入れます。 + +### 2. 世帯全員が市町村民税非課税で、年金等の収入額に応じる場合 +世帯全員が住民税非課税(市町村民税世帯非課税)である場合は、以下の合計金額を確認し、該当する区分を選んでください。 + +**【合計金額の計算に含まれるもの】** +* 課税年金(老齢年金など)の収入額 +* 非課税年金(遺族年金・障害年金)の収入額 +* その他の合計所得金額 + +**【チェックを入れる基準】** +* **③合計額が年額80万円以下の方** +* **④合計額が年額80万円を超え、120万円以下の方** +* **⑤合計額が年額120万円を超える方** + +### 注意事項 +* ③〜⑤のいずれかにチェックを入れる場合、遺族年金や障害年金を受給している方は、**【遺族年金・障害年金】の文字を〇で囲む**必要があります。 +* 遺族年金には、寡婦年金、かん夫年金、母子年金、準母子年金、遺児年金も含まれます。 +* この区分(②〜⑤)によって、預貯金等の資産額の基準(上限)も異なりますので、併せて「預貯金等に関する申告」も確認してください。 + +ご自身の昨年の年金振込通知書や確定申告の控えなどを参考に、年間の合計収入額を計算して選んでください。 +``` \ No newline at end of file diff --git a/sample/gantt_chart/en/example.py b/sample/gantt_chart/en/example.py new file mode 100644 index 0000000..34f0498 --- /dev/null +++ b/sample/gantt_chart/en/example.py @@ -0,0 +1,16 @@ +from exstruct import ( + ColorsOptions, + ExStructEngine, + StructOptions, +) + +file_path = "sample.xlsx" + +engine = ExStructEngine( + options=StructOptions( + include_colors_map=True, + colors=ColorsOptions(include_default_background=False), + ), +) +wb = engine.extract(file_path) +engine.export(wb, "sample.json", pretty=True) diff --git a/sample/gantt_chart/en/sample.json b/sample/gantt_chart/en/sample.json new file mode 100644 index 0000000..ba39650 --- /dev/null +++ b/sample/gantt_chart/en/sample.json @@ -0,0 +1,488 @@ +{ + "book_name": "sample.xlsx", + "sheets": { + "Project Schedule": { + "rows": [ + { + "r": 1, + "c": { + "0": "Project Gantt Schedule (Original, Redistributable Template)" + } + }, + { + "r": 3, + "c": { + "0": "Project Name", + "2": "Project Owner", + "4": "Project Start Date", + "5": "2026-01-05 00:00:00", + "6": "View Start Week", + "7": 1 + } + }, + { + "r": 6, + "c": { + "0": "Task / Phase", + "1": "Assignee", + "2": "Progress", + "3": "Start Date", + "4": "End Date", + "5": "Days", + "6": "2026-01-05 00:00:00", + "7": "2026-01-06 00:00:00", + "8": "2026-01-07 00:00:00", + "9": "2026-01-08 00:00:00", + "10": "2026-01-09 00:00:00", + "11": "2026-01-10 00:00:00", + "12": "2026-01-11 00:00:00", + "13": "2026-01-12 00:00:00", + "14": "2026-01-13 00:00:00", + "15": "2026-01-14 00:00:00", + "16": "2026-01-15 00:00:00", + "17": "2026-01-16 00:00:00", + "18": "2026-01-17 00:00:00", + "19": "2026-01-18 00:00:00", + "20": "2026-01-19 00:00:00", + "21": "2026-01-20 00:00:00", + "22": "2026-01-21 00:00:00", + "23": "2026-01-22 00:00:00", + "24": "2026-01-23 00:00:00", + "25": "2026-01-24 00:00:00", + "26": "2026-01-25 00:00:00", + "27": "2026-01-26 00:00:00", + "28": "2026-01-27 00:00:00", + "29": "2026-01-28 00:00:00", + "30": "2026-01-29 00:00:00", + "31": "2026-01-30 00:00:00", + "32": "2026-01-31 00:00:00", + "33": "2026-02-01 00:00:00", + "34": "2026-02-02 00:00:00", + "35": "2026-02-03 00:00:00", + "36": "2026-02-04 00:00:00", + "37": "2026-02-05 00:00:00", + "38": "2026-02-06 00:00:00", + "39": "2026-02-07 00:00:00", + "40": "2026-02-08 00:00:00", + "41": "2026-02-09 00:00:00", + "42": "2026-02-10 00:00:00", + "43": "2026-02-11 00:00:00", + "44": "2026-02-12 00:00:00", + "45": "2026-02-13 00:00:00", + "46": "2026-02-14 00:00:00", + "47": "2026-02-15 00:00:00", + "48": "2026-02-16 00:00:00", + "49": "2026-02-17 00:00:00", + "50": "2026-02-18 00:00:00", + "51": "2026-02-19 00:00:00" + } + }, + { "r": 7, "c": { "0": "Phase 1: Planning" } }, + { + "r": 8, + "c": { + "0": "Requirements Definition", + "1": "Alice", + "2": 0.8, + "3": "2026-01-05 00:00:00", + "4": "2026-01-10 00:00:00", + "5": 5 + } + }, + { + "r": 9, + "c": { + "0": "Stakeholder Interviews", + "1": "Bob", + "2": 0.6, + "3": "2026-01-08 00:00:00", + "4": "2026-01-14 00:00:00", + "5": 6 + } + }, + { + "r": 10, + "c": { + "0": "Scope Agreement", + "1": "Carol", + "2": 0.5, + "3": "2026-01-11 00:00:00", + "4": "2026-01-15 00:00:00", + "5": 4 + } + }, + { + "r": 11, + "c": { + "0": "Planning Review", + "1": "Dave", + "2": 0, + "3": "2026-01-15 00:00:00", + "4": "2026-01-17 00:00:00", + "5": 2 + } + }, + { "r": 12, "c": { "0": "Phase 2: Design" } }, + { + "r": 13, + "c": { + "0": "UI / UX Design", + "1": "Alice", + "2": 0.7, + "3": "2026-01-17 00:00:00", + "4": "2026-01-23 00:00:00", + "5": 6 + } + }, + { + "r": 14, + "c": { + "0": "Data Model Design", + "1": "Bob", + "2": 0.5, + "3": "2026-01-18 00:00:00", + "4": "2026-01-25 00:00:00", + "5": 7 + } + }, + { + "r": 15, + "c": { + "0": "API Design", + "1": "Carol", + "2": 0.4, + "3": "2026-01-20 00:00:00", + "4": "2026-01-26 00:00:00", + "5": 6 + } + }, + { + "r": 16, + "c": { + "0": "Design Approval", + "1": "Dave", + "2": 0, + "3": "2026-01-24 00:00:00", + "4": "2026-01-26 00:00:00", + "5": 2 + } + }, + { "r": 17, "c": { "0": "Phase 3: Implementation" } }, + { + "r": 18, + "c": { + "0": "Core Feature Dev", + "1": "Eve", + "2": 0.3, + "3": "2026-01-26 00:00:00", + "4": "2026-02-03 00:00:00", + "5": 8 + } + }, + { + "r": 19, + "c": { + "0": "Edge Case Handling", + "1": "Frank", + "2": 0.2, + "3": "2026-01-27 00:00:00", + "4": "2026-02-03 00:00:00", + "5": 7 + } + }, + { + "r": 20, + "c": { + "0": "Integration Work", + "1": "Grace", + "2": 0.1, + "3": "2026-01-29 00:00:00", + "4": "2026-02-04 00:00:00", + "5": 6 + } + }, + { + "r": 21, + "c": { + "0": "Internal Review", + "1": "Heidi", + "2": 0, + "3": "2026-02-01 00:00:00", + "4": "2026-02-04 00:00:00", + "5": 3 + } + }, + { "r": 22, "c": { "0": "Phase 4: Testing" } }, + { + "r": 23, + "c": { + "0": "Unit Tests", + "1": "Eve", + "2": 0.4, + "3": "2026-02-02 00:00:00", + "4": "2026-02-07 00:00:00", + "5": 5 + } + }, + { + "r": 24, + "c": { + "0": "Integration Tests", + "1": "Frank", + "2": 0.2, + "3": "2026-02-04 00:00:00", + "4": "2026-02-09 00:00:00", + "5": 5 + } + }, + { + "r": 25, + "c": { + "0": "User Acceptance Test", + "1": "Grace", + "2": 0, + "3": "2026-02-06 00:00:00", + "4": "2026-02-10 00:00:00", + "5": 4 + } + }, + { + "r": 26, + "c": { + "0": "Test Report", + "1": "Heidi", + "2": 0, + "3": "2026-02-09 00:00:00", + "4": "2026-02-11 00:00:00", + "5": 2 + } + }, + { "r": 27, "c": { "0": "Phase 5: Release & Operation" } }, + { + "r": 28, + "c": { + "0": "Release Preparation", + "1": "Alice", + "2": 0, + "3": "2026-02-11 00:00:00", + "4": "2026-02-14 00:00:00", + "5": 3 + } + }, + { + "r": 29, + "c": { + "0": "Production Deployment", + "1": "Bob", + "2": 0, + "3": "2026-02-14 00:00:00", + "4": "2026-02-16 00:00:00", + "5": 2 + } + }, + { + "r": 30, + "c": { + "0": "Monitoring Setup", + "1": "Carol", + "2": 0, + "3": "2026-02-15 00:00:00", + "4": "2026-02-18 00:00:00", + "5": 3 + } + }, + { + "r": 31, + "c": { + "0": "Post-release Review", + "1": "Dave", + "2": 0, + "3": "2026-02-18 00:00:00", + "4": "2026-02-20 00:00:00", + "5": 2 + } + } + ], + "table_candidates": ["A6:AZ31"], + "colors_map": { + "D9E1F2": [ + [6, 0], + [6, 1], + [6, 2], + [6, 3], + [6, 4], + [6, 5], + [6, 6], + [6, 7], + [6, 8], + [6, 9], + [6, 10], + [6, 11], + [6, 12], + [6, 13], + [6, 14], + [6, 15], + [6, 16], + [6, 17], + [6, 18], + [6, 19], + [6, 20], + [6, 21], + [6, 22], + [6, 23], + [6, 24], + [6, 25], + [6, 26], + [6, 27], + [6, 28], + [6, 29], + [6, 30], + [6, 31], + [6, 32], + [6, 33], + [6, 34], + [6, 35], + [6, 36], + [6, 37], + [6, 38], + [6, 39], + [6, 40], + [6, 41], + [6, 42], + [6, 43], + [6, 44], + [6, 45], + [6, 46], + [6, 47], + [6, 48], + [6, 49], + [6, 50], + [6, 51] + ], + "E2EFDA": [ + [7, 0], + [12, 0], + [17, 0], + [22, 0], + [27, 0] + ], + "FCE4D6": [ + [8, 6], + [8, 7], + [8, 8], + [8, 9], + [8, 10], + [9, 9], + [9, 10], + [9, 11], + [9, 12], + [9, 13], + [9, 14], + [10, 12], + [10, 13], + [10, 14], + [10, 15], + [11, 16], + [11, 17], + [13, 18], + [13, 19], + [13, 20], + [13, 21], + [13, 22], + [13, 23], + [14, 19], + [14, 20], + [14, 21], + [14, 22], + [14, 23], + [14, 24], + [14, 25], + [15, 21], + [15, 22], + [15, 23], + [15, 24], + [15, 25], + [15, 26], + [16, 25], + [16, 26], + [18, 27], + [18, 28], + [18, 29], + [18, 30], + [18, 31], + [18, 32], + [18, 33], + [18, 34], + [19, 28], + [19, 29], + [19, 30], + [19, 31], + [19, 32], + [19, 33], + [19, 34], + [20, 30], + [20, 31], + [20, 32], + [20, 33], + [20, 34], + [20, 35], + [21, 33], + [21, 34], + [21, 35], + [23, 34], + [23, 35], + [23, 36], + [23, 37], + [23, 38], + [24, 36], + [24, 37], + [24, 38], + [24, 39], + [24, 40], + [25, 38], + [25, 39], + [25, 40], + [25, 41], + [26, 41], + [26, 42], + [28, 43], + [28, 44], + [28, 45], + [29, 46], + [29, 47], + [30, 47], + [30, 48], + [30, 49], + [31, 50], + [31, 51] + ] + }, + "merged_cells": [ + { + "r1": 1, + "c1": 0, + "r2": 1, + "c2": 25, + "v": "Project Gantt Schedule (Original, Redistributable Template)" + }, + { + "r1": 27, + "c1": 0, + "r2": 27, + "c2": 5, + "v": "Phase 5: Release & Operation" + }, + { "r1": 22, "c1": 0, "r2": 22, "c2": 5, "v": "Phase 4: Testing" }, + { + "r1": 17, + "c1": 0, + "r2": 17, + "c2": 5, + "v": "Phase 3: Implementation" + }, + { "r1": 12, "c1": 0, "r2": 12, "c2": 5, "v": "Phase 2: Design" }, + { "r1": 7, "c1": 0, "r2": 7, "c2": 5, "v": "Phase 1: Planning" } + ] + } + } +} diff --git a/sample/gantt_chart/en/sample.json_for_llm.md b/sample/gantt_chart/en/sample.json_for_llm.md new file mode 100644 index 0000000..061baeb --- /dev/null +++ b/sample/gantt_chart/en/sample.json_for_llm.md @@ -0,0 +1,73 @@ +# **Project Gantt Schedule** + +_(Original, Redistributable Template)_ + +--- + +## **Project Overview** + +| Field | Value | +| ---------------------- | ---------- | +| **Project Name** | — | +| **Project Owner** | — | +| **Project Start Date** | 2026-01-05 | +| **View Start Week** | 1 | + +--- + +# **Project Schedule** + +--- + +## **Phase 1: Planning** + +| Task | Assignee | Progress | Start Date | End Date | Days | +| ----------------------- | -------- | -------- | ---------- | ---------- | ---- | +| Requirements Definition | Alice | 80% | 2026-01-05 | 2026-01-10 | 5 | +| Stakeholder Interviews | Bob | 60% | 2026-01-08 | 2026-01-14 | 6 | +| Scope Agreement | Carol | 50% | 2026-01-11 | 2026-01-15 | 4 | +| Planning Review | Dave | 0% | 2026-01-15 | 2026-01-17 | 2 | + +--- + +## **Phase 2: Design** + +| Task | Assignee | Progress | Start Date | End Date | Days | +| ----------------- | -------- | -------- | ---------- | ---------- | ---- | +| UI / UX Design | Alice | 70% | 2026-01-17 | 2026-01-23 | 6 | +| Data Model Design | Bob | 50% | 2026-01-18 | 2026-01-25 | 7 | +| API Design | Carol | 40% | 2026-01-20 | 2026-01-26 | 6 | +| Design Approval | Dave | 0% | 2026-01-24 | 2026-01-26 | 2 | + +--- + +## **Phase 3: Implementation** + +| Task | Assignee | Progress | Start Date | End Date | Days | +| ------------------ | -------- | -------- | ---------- | ---------- | ---- | +| Core Feature Dev | Eve | 30% | 2026-01-26 | 2026-02-03 | 8 | +| Edge Case Handling | Frank | 20% | 2026-01-27 | 2026-02-03 | 7 | +| Integration Work | Grace | 10% | 2026-01-29 | 2026-02-04 | 6 | +| Internal Review | Heidi | 0% | 2026-02-01 | 2026-02-04 | 3 | + +--- + +## **Phase 4: Testing** + +| Task | Assignee | Progress | Start Date | End Date | Days | +| -------------------- | -------- | -------- | ---------- | ---------- | ---- | +| Unit Tests | Eve | 40% | 2026-02-02 | 2026-02-07 | 5 | +| Integration Tests | Frank | 20% | 2026-02-04 | 2026-02-09 | 5 | +| User Acceptance Test | Grace | 0% | 2026-02-06 | 2026-02-10 | 4 | +| Test Report | Heidi | 0% | 2026-02-09 | 2026-02-11 | 2 | + +--- + +## **Phase 5: Release & Operation** + +| Task | Assignee | Progress | Start Date | End Date | Days | +| --------------------- | -------- | -------- | ---------- | ---------- | ---- | +| Release Preparation | Alice | 0% | 2026-02-11 | 2026-02-14 | 3 | +| Production Deployment | Bob | 0% | 2026-02-14 | 2026-02-16 | 2 | +| Monitoring Setup | Carol | 0% | 2026-02-15 | 2026-02-18 | 3 | +| Post-release Review | Dave | 0% | 2026-02-18 | 2026-02-20 | 2 | diff --git a/sample/gantt_chart/en/sample.xlsx b/sample/gantt_chart/en/sample.xlsx new file mode 100644 index 0000000..5bc3a23 Binary files /dev/null and b/sample/gantt_chart/en/sample.xlsx differ diff --git a/sample/gantt_chart/sample_gantt.json b/sample/gantt_chart/sample_gantt.json deleted file mode 100644 index b07aba8..0000000 --- a/sample/gantt_chart/sample_gantt.json +++ /dev/null @@ -1 +0,0 @@ -{"book_name": "Book.xlsx", "sheets": {"プロジェクトのスケジュール": {"rows": [{"r": 1, "c": {"0": "このワークシートでプロジェクトのスケジュール​​を作成します。\nセル B1 には、このプロジェクトのタイトルを入力します。\nこのワークシートの使用方法に関する情報 (スクリーン リーダーの説明やブックの作成者など) は、\"詳細情報\" ワークシートに入ります。\n列 A を下方に移動し続けると、さらに多くの情報を確認できます。", "1": "プロジェクト タイトル", "8": "シンプル ガント チャート (Vertex42.com)"}, "links": {"8": "https://www.vertex42.com/ExcelTemplates/simple-gantt-chart.html?utm_source=ms&utm_medium=file&utm_campaign=office&utm_content=text"}}, {"r": 2, "c": {"0": "セル B2 には会社の名前を入力します。", "1": "会社名", "8": "https://www.vertex42.com/ExcelTemplates/simple-gantt-chart.html"}, "links": {"8": "https://www.vertex42.com/ExcelTemplates/simple-gantt-chart.html?utm_source=ms&utm_medium=file&utm_campaign=office&utm_content=url"}}, {"r": 3, "c": {"0": "セル B3 に、プロジェクト主任の名前を入力します。セル E3 には、プロジェクトの開始日を入力します。プロジェクトの開始: ラベルはセル C3 にあります。", "1": "プロジェクト主任", "2": "プロジェクトの開始:", "4": "2025-12-24 00:00:00"}}, {"r": 4, "c": {"0": "セル E4 の週表示は、セル I4 のプロジェクトのスケジュールに表示する開始週を表します。プロジェクトの開始日は、第 1 週と見なされます。週表示を変更するには、セル E4 に新しい週番号を入力します。\nセル E4 の週表示から始まる各週の開始日は、セル I4 から始まり、自動計算されます。このビューには、セル I4 からセル BF4 までの 8 週間が表示されます。\nこれらのセルは変更しないでください。\n週表示: ラベルはセル C4 にあります。", "2": "週表示:", "4": 1, "8": "2025-12-22 00:00:00", "15": "2025-12-29 00:00:00", "22": "2026-01-05 00:00:00", "29": "2026-01-12 00:00:00", "36": "2026-01-19 00:00:00", "43": "2026-01-26 00:00:00", "50": "2026-02-02 00:00:00", "57": "2026-02-09 00:00:00"}}, {"r": 5, "c": {"0": "セル I5 から BL5 には、各日付のセルの上にあるセル ブロック内に表示される週の日付の数字が含まれており、自動的に計算が行われます。\nこれらのセルは変更しないでください。\n今日の日付は、行 5 の今日の日付からプロジェクト スケジュールの最後まで日付列全体が赤 (16 進値 #AD3815) で囲まれます。", "8": "2025-12-22 00:00:00", "9": "2025-12-23 00:00:00", "10": "2025-12-24 00:00:00", "11": "2025-12-25 00:00:00", "12": "2025-12-26 00:00:00", "13": "2025-12-27 00:00:00", "14": "2025-12-28 00:00:00", "15": "2025-12-29 00:00:00", "16": "2025-12-30 00:00:00", "17": "2025-12-31 00:00:00", "18": "2026-01-01 00:00:00", "19": "2026-01-02 00:00:00", "20": "2026-01-03 00:00:00", "21": "2026-01-04 00:00:00", "22": "2026-01-05 00:00:00", "23": "2026-01-06 00:00:00", "24": "2026-01-07 00:00:00", "25": "2026-01-08 00:00:00", "26": "2026-01-09 00:00:00", "27": "2026-01-10 00:00:00", "28": "2026-01-11 00:00:00", "29": "2026-01-12 00:00:00", "30": "2026-01-13 00:00:00", "31": "2026-01-14 00:00:00", "32": "2026-01-15 00:00:00", "33": "2026-01-16 00:00:00", "34": "2026-01-17 00:00:00", "35": "2026-01-18 00:00:00", "36": "2026-01-19 00:00:00", "37": "2026-01-20 00:00:00", "38": "2026-01-21 00:00:00", "39": "2026-01-22 00:00:00", "40": "2026-01-23 00:00:00", "41": "2026-01-24 00:00:00", "42": "2026-01-25 00:00:00", "43": "2026-01-26 00:00:00", "44": "2026-01-27 00:00:00", "45": "2026-01-28 00:00:00", "46": "2026-01-29 00:00:00", "47": "2026-01-30 00:00:00", "48": "2026-01-31 00:00:00", "49": "2026-02-01 00:00:00", "50": "2026-02-02 00:00:00", "51": "2026-02-03 00:00:00", "52": "2026-02-04 00:00:00", "53": "2026-02-05 00:00:00", "54": "2026-02-06 00:00:00", "55": "2026-02-07 00:00:00", "56": "2026-02-08 00:00:00", "57": "2026-02-09 00:00:00", "58": "2026-02-10 00:00:00", "59": "2026-02-11 00:00:00", "60": "2026-02-12 00:00:00", "61": "2026-02-13 00:00:00", "62": "2026-02-14 00:00:00", "63": "2026-02-15 00:00:00"}}, {"r": 6, "c": {"0": "この行には、その下に続くプロジェクトのスケジュールの見出しが含まれます。\nコンテンツを読み上げるには、B6 から BL 6 に移動します。その見出しの上にある日付の各曜日の最初の文字が、セル I6 から始まってセル BL6 まで続きます。\nすべてのプロジェクトのタイムライン グラフは、条件付き書式を使用して、入力された開始日と終了日に基づいて自動生成されます。\nセル I7 から始まる列 I 以降の列のセルの内容を変更しないでください。", "1": "タスク", "2": "担当者", "3": "進捗状況", "4": "開始", "5": "終了", "7": "日数", "8": "月", "9": "火", "10": "水", "11": "木", "12": "金", "13": "土", "14": "日", "15": "月", "16": "火", "17": "水", "18": "木", "19": "金", "20": "土", "21": "日", "22": "月", "23": "火", "24": "水", "25": "木", "26": "金", "27": "土", "28": "日", "29": "月", "30": "火", "31": "水", "32": "木", "33": "金", "34": "土", "35": "日", "36": "月", "37": "火", "38": "水", "39": "木", "40": "金", "41": "土", "42": "日", "43": "月", "44": "火", "45": "水", "46": "木", "47": "金", "48": "土", "49": "日", "50": "月", "51": "火", "52": "水", "53": "木", "54": "金", "55": "土", "56": "日", "57": "月", "58": "火", "59": "水", "60": "木", "61": "金", "62": "土", "63": "日"}}, {"r": 7, "c": {"0": "この行は削除しないでください。この行は、プロジェクトのスケジュール内にある現在の日付を強調表示するために使用されている数式を保持するために非表示になっています。 "}}, {"r": 8, "c": {"0": "セル B8 には、フェーズ 1 のサンプルのタイトルが含まれます。\nセル B8 に新しいタイトルを入力します。\nプロジェクトに該当する場合は、セル C8 にフェーズを割り当てる名前を入力します。\nプロジェクトに該当する場合は、セル D8 にフェーズ全体の進捗状況を入力します。\nプロジェクトに該当する場合は、フェーズ全体の開始日と終了日をセル E8 と F8 に入力します。\nガント チャートは自動的に適切な日付を入力し、入力した進捗状況に応じて網かけを設定します。\nフェーズを削除してタスクからのみ作業をするには、この行を削除するだけです。", "1": "フェーズ 1 企画・要件"}}, {"r": 9, "c": {"0": "セル B9 には、サンプル タスク \"タスク 1\" が含まれます。\nセル B9 に、新しいタスク名を入力します。\nセル C9 にタスクを割り当てるユーザーを入力します。\nセル D9 のタスクの進捗状況を入力します。進行状況バーは、セル内に表示され、セルの数に従って網かけを適用します。たとえば、50% の進捗状況はセルの半分に網かけを設定します。\nセル E9 にタスクの開始日を入力します。\nセル F9 にタスクの終了日を入力します。\n入力した日付の網かけのステータス バーは、セル I9 から始まる BL9 までのブロックに表示されます。 ", "1": "目的と成功指標の整理", "2": "名前", "3": 0.5, "4": "2025-12-24 00:00:00", "5": "2025-12-27 00:00:00", "7": 4}}, {"r": 10, "c": {"0": "行 10 から 13 は、行 9 のパターンを繰り返します。\nこのワークシート内のすべてのタスク行に対し、セル A9 の手順を繰り返します。サンプル データはすべて上書きします。\n別のフェーズのサンプルは、セル A14 から始まります。\nセル A10 から A13 までにタスクの入力を続けるか、セル A14 に移動して詳細を確認します。", "1": "ステークホルダー要件ヒアリング", "3": 0.6, "4": "2025-12-27 00:00:00", "5": "2025-12-29 00:00:00", "7": 3}}, {"r": 11, "c": {"1": "現場業務フロー整理", "3": 0.5, "4": "2025-12-29 00:00:00", "5": "2026-01-02 00:00:00", "7": 5}}, {"r": 12, "c": {"1": "スコープ定義と除外事項確定", "3": 0.25, "4": "2026-01-02 00:00:00", "5": "2026-01-07 00:00:00", "7": 6}}, {"r": 13, "c": {"1": "要件レビューと合意形成", "4": "2025-12-28 00:00:00", "5": "2025-12-30 00:00:00", "7": 3}}, {"r": 14, "c": {"0": "右のセルには、フェーズ 2 のサンプルのタイトルが含まれます。\nB 列に、いつでも新しいフェーズを作成できます。このプロジェクトのスケジュールには、フェーズは不要です。フェーズを削除するには、行を削除するだけです。\nこの行に新しいフェーズ ブロックを作成するには、右のセルに新しいタイトルを入力します。\n上記のフェーズにタスクの追加を続けるには、この行の 1 つ上に新しい行を入力し、セル A9 の説明のようにタスク データを入力します。\nセル A8 の説明に基づいて右側のセルのフェーズの詳細情報を更新します。\n詳細については、A 列のセルを下に移動します。\nこのワークシートに新しい行を追加していない場合は、セル B20 と B26 に 2 つの追加のサンプル フェーズ ブロックが作成され表示されます。それ以外の場合、A 列のセルを移動して追加のブロックを探します。\n必要に応じて、セル A8 と A9 の手順を繰り返します。", "1": "フェーズ 2 設計・準備"}}, {"r": 15, "c": {"1": "画面/帳票の基本設計", "3": 0.5, "4": "2025-12-29 00:00:00", "5": "2026-01-02 00:00:00", "7": 5}}, {"r": 16, "c": {"1": "データ項目・マスタ設計", "3": 0.5, "4": "2025-12-31 00:00:00", "5": "2026-01-05 00:00:00", "7": 6}}, {"r": 17, "c": {"1": "インタフェース仕様策定", "4": "2026-01-05 00:00:00", "5": "2026-01-08 00:00:00", "7": 4}}, {"r": 18, "c": {"1": "リスク洗い出しと対策案", "4": "2026-01-05 00:00:00", "5": "2026-01-07 00:00:00", "7": 3}}, {"r": 19, "c": {"1": "実装計画・見積確定", "4": "2026-01-05 00:00:00", "5": "2026-01-08 00:00:00", "7": 4}}, {"r": 20, "c": {"0": "サンプル フェーズ タイトル ブロック", "1": "フェーズ 3 実装・検証"}}, {"r": 21, "c": {"1": "コア機能実装", "4": "2026-01-08 00:00:00", "5": "2026-01-13 00:00:00", "7": 6}}, {"r": 22, "c": {"1": "例外/エッジケース対応", "4": "2026-01-14 00:00:00", "5": "2026-01-18 00:00:00", "7": 5}}, {"r": 23, "c": {"1": "単体テスト作成", "4": "2026-01-19 00:00:00", "5": "2026-01-24 00:00:00", "7": 6}}, {"r": 24, "c": {"1": "結合テスト準備", "4": "2026-01-25 00:00:00", "5": "2026-01-29 00:00:00", "7": 5}}, {"r": 25, "c": {"1": "ユーザー受け入れテスト", "4": "2026-01-19 00:00:00", "5": "2026-01-23 00:00:00", "7": 5}}, {"r": 26, "c": {"0": "サンプル フェーズ タイトル ブロック", "1": "フェーズ 4 リリース・運用"}}, {"r": 27, "c": {"1": "リリース手順書作成", "4": "日付", "5": "日付"}}, {"r": 28, "c": {"1": "本番データ移行計画", "4": "日付", "5": "日付"}}, {"r": 29, "c": {"1": "監視・ログ設計", "4": "日付", "5": "日付"}}, {"r": 30, "c": {"1": "運用手順/教育", "4": "日付", "5": "日付"}}, {"r": 31, "c": {"1": "リリース後レビュー", "4": "日付", "5": "日付"}}, {"r": 32, "c": {"0": "これは空の行です"}}, {"r": 33, "c": {"0": "この行は、プロジェクトのスケジュールの終了を表します。この行には何も入力しないでください。\nこの行の上に新しい行を挿入して、プロジェクトのスケジュールの作成を続けます。", "1": "この行の上に新しい行を挿入する"}}], "table_candidates": ["H4:BL6", "B8:F31", "D15:F19", "E21:F25", "E27:F31"], "colors_map": {"D9D9D9": [[4, 8], [4, 9], [4, 10], [4, 11], [4, 12], [4, 13], [4, 14], [4, 15], [4, 16], [4, 17], [4, 18], [4, 19], [4, 20], [4, 21], [4, 22], [4, 23], [4, 24], [4, 25], [4, 26], [4, 27], [4, 28], [4, 29], [4, 30], [4, 31], [4, 32], [4, 33], [4, 34], [4, 35], [4, 36], [4, 37], [4, 38], [4, 39], [4, 40], [4, 41], [4, 42], [4, 43], [4, 44], [4, 45], [4, 46], [4, 47], [4, 48], [4, 49], [4, 50], [4, 51], [4, 52], [4, 53], [4, 54], [4, 55], [4, 56], [4, 57], [4, 58], [4, 59], [4, 60], [4, 61], [4, 62], [4, 63], [5, 8], [5, 9], [5, 10], [5, 11], [5, 12], [5, 13], [5, 14], [5, 15], [5, 16], [5, 17], [5, 18], [5, 19], [5, 20], [5, 21], [5, 22], [5, 23], [5, 24], [5, 25], [5, 26], [5, 27], [5, 28], [5, 29], [5, 30], [5, 31], [5, 32], [5, 33], [5, 34], [5, 35], [5, 36], [5, 37], [5, 38], [5, 39], [5, 40], [5, 41], [5, 42], [5, 43], [5, 44], [5, 45], [5, 46], [5, 47], [5, 48], [5, 49], [5, 50], [5, 51], [5, 52], [5, 53], [5, 54], [5, 55], [5, 56], [5, 57], [5, 58], [5, 59], [5, 60], [5, 61], [5, 62], [5, 63]], "595959": [[6, 1], [6, 2], [6, 3], [6, 4], [6, 5], [6, 6], [6, 7], [6, 8], [6, 9], [6, 10], [6, 11], [6, 12], [6, 13], [6, 14], [6, 15], [6, 16], [6, 17], [6, 18], [6, 19], [6, 20], [6, 21], [6, 22], [6, 23], [6, 24], [6, 25], [6, 26], [6, 27], [6, 28], [6, 29], [6, 30], [6, 31], [6, 32], [6, 33], [6, 34], [6, 35], [6, 36], [6, 37], [6, 38], [6, 39], [6, 40], [6, 41], [6, 42], [6, 43], [6, 44], [6, 45], [6, 46], [6, 47], [6, 48], [6, 49], [6, 50], [6, 51], [6, 52], [6, 53], [6, 54], [6, 55], [6, 56], [6, 57], [6, 58], [6, 59], [6, 60], [6, 61], [6, 62], [6, 63]], "B8CCE4": [[8, 1], [8, 2], [8, 3], [8, 4], [8, 5]], "DCE6F1": [[9, 1], [9, 2], [9, 3], [9, 4], [9, 5], [10, 1], [10, 2], [10, 3], [10, 4], [10, 5], [11, 1], [11, 2], [11, 3], [11, 4], [11, 5], [12, 1], [12, 2], [12, 3], [12, 4], [12, 5], [13, 1], [13, 2], [13, 3], [13, 4], [13, 5]], "A6A6A6": [[9, 10], [9, 11], [10, 13], [11, 15], [11, 16], [12, 19], [15, 15], [15, 16], [16, 17], [16, 18], [16, 19]], "8064A2": [[9, 12], [9, 13], [10, 14], [10, 15], [11, 17], [11, 18], [11, 19], [12, 20], [12, 21], [12, 22], [12, 23], [12, 24], [13, 14], [13, 15], [13, 16], [15, 17], [15, 18], [15, 19], [16, 20], [16, 21], [16, 22], [17, 22], [17, 23], [17, 24], [17, 25], [18, 22], [18, 23], [18, 24], [19, 22], [19, 23], [19, 24], [19, 25], [21, 25], [21, 26], [21, 27], [21, 28], [21, 29], [21, 30], [22, 31], [22, 32], [22, 33], [22, 34], [22, 35], [23, 36], [23, 37], [23, 38], [23, 39], [23, 40], [23, 41], [24, 42], [24, 43], [24, 44], [24, 45], [24, 46], [25, 36], [25, 37], [25, 38], [25, 39], [25, 40]], "E6B8B7": [[14, 1], [14, 2], [14, 3], [14, 4], [14, 5]], "F2DCDB": [[15, 1], [15, 2], [15, 3], [15, 4], [15, 5], [16, 1], [16, 2], [16, 3], [16, 4], [16, 5], [17, 1], [17, 2], [17, 3], [17, 4], [17, 5], [18, 1], [18, 2], [18, 3], [18, 4], [18, 5], [19, 1], [19, 2], [19, 3], [19, 4], [19, 5]], "D8E4BC": [[20, 1], [20, 2], [20, 3], [20, 4], [20, 5]], "EBF1DE": [[21, 1], [21, 2], [21, 3], [21, 4], [21, 5], [22, 1], [22, 2], [22, 3], [22, 4], [22, 5], [23, 1], [23, 2], [23, 3], [23, 4], [23, 5], [24, 1], [24, 2], [24, 3], [24, 4], [24, 5], [25, 1], [25, 2], [25, 3], [25, 4], [25, 5]], "CCC0DA": [[26, 1], [26, 2], [26, 3], [26, 4], [26, 5]], "E4DFEC": [[27, 1], [27, 2], [27, 3], [27, 4], [27, 5], [28, 1], [28, 2], [28, 3], [28, 4], [28, 5], [29, 1], [29, 2], [29, 3], [29, 4], [29, 5], [30, 1], [30, 2], [30, 3], [30, 4], [30, 5], [31, 1], [31, 2], [31, 3], [31, 4], [31, 5]], "F2F2F2": [[33, 1], [33, 2], [33, 3], [33, 4], [33, 5], [33, 6], [33, 7], [33, 8], [33, 9], [33, 10], [33, 11], [33, 12], [33, 13], [33, 14], [33, 15], [33, 16], [33, 17], [33, 18], [33, 19], [33, 20], [33, 21], [33, 22], [33, 23], [33, 24], [33, 25], [33, 26], [33, 27], [33, 28], [33, 29], [33, 30], [33, 31], [33, 32], [33, 33], [33, 34], [33, 35], [33, 36], [33, 37], [33, 38], [33, 39], [33, 40], [33, 41], [33, 42], [33, 43], [33, 44], [33, 45], [33, 46], [33, 47], [33, 48], [33, 49], [33, 50], [33, 51], [33, 52], [33, 53], [33, 54], [33, 55], [33, 56], [33, 57], [33, 58], [33, 59], [33, 60], [33, 61], [33, 62], [33, 63]]}}}} \ No newline at end of file diff --git a/sample/gantt_chart/sample_gantt.xlsx b/sample/gantt_chart/sample_gantt.xlsx deleted file mode 100644 index 606d070..0000000 Binary files a/sample/gantt_chart/sample_gantt.xlsx and /dev/null differ diff --git a/sample/gantt_chart/sample_gantt_json_for_llm.md b/sample/gantt_chart/sample_gantt_json_for_llm.md deleted file mode 100644 index 8b0129a..0000000 --- a/sample/gantt_chart/sample_gantt_json_for_llm.md +++ /dev/null @@ -1,103 +0,0 @@ -# プロジェクトスケジュール - -## 基本情報 - -| 項目 | 内容 | -| -------------------- | ------------------------------------------------------------------------------------------------------ | -| プロジェクトタイトル | (未入力) | -| 会社名 | (未入力) | -| プロジェクト主任 | (未入力) | -| プロジェクト開始日 | 2025-12-24 | -| テンプレート | [シンプル ガント チャート (Vertex42)](https://www.vertex42.com/ExcelTemplates/simple-gantt-chart.html) | - ---- - -## スケジュール概要 - -- 表示期間:**2025-12-22 〜 2026-02-15** -- 週単位で 8 週間表示 -- 各タスクは開始日・終了日に基づき自動でガント表示 -- 進捗率はセル内バーおよび網掛けで表現 - ---- - -## フェーズ別タスク一覧 - ---- - -### フェーズ 1:企画・要件 - -| タスク | 担当 | 進捗 | 開始 | 終了 | 日数 | -| ------------------------------ | ---- | ---: | ---------- | ---------- | ---: | -| 目的と成功指標の整理 | 名前 | 50% | 2025-12-24 | 2025-12-27 | 4 | -| ステークホルダー要件ヒアリング | | 60% | 2025-12-27 | 2025-12-29 | 3 | -| 現場業務フロー整理 | | 50% | 2025-12-29 | 2026-01-02 | 5 | -| スコープ定義と除外事項確定 | | 25% | 2026-01-02 | 2026-01-07 | 6 | -| 要件レビューと合意形成 | | | 2025-12-28 | 2025-12-30 | 3 | - ---- - -### フェーズ 2:設計・準備 - -| タスク | 進捗 | 開始 | 終了 | 日数 | -| ---------------------- | ---: | ---------- | ---------- | ---: | -| 画面/帳票の基本設計 | 50% | 2025-12-29 | 2026-01-02 | 5 | -| データ項目・マスタ設計 | 50% | 2025-12-31 | 2026-01-05 | 6 | -| インタフェース仕様策定 | | 2026-01-05 | 2026-01-08 | 4 | -| リスク洗い出しと対策案 | | 2026-01-05 | 2026-01-07 | 3 | -| 実装計画・見積確定 | | 2026-01-05 | 2026-01-08 | 4 | - ---- - -### フェーズ 3:実装・検証 - -| タスク | 開始 | 終了 | 日数 | -| ---------------------- | ---------- | ---------- | ---: | -| コア機能実装 | 2026-01-08 | 2026-01-13 | 6 | -| 例外/エッジケース対応 | 2026-01-14 | 2026-01-18 | 5 | -| 単体テスト作成 | 2026-01-19 | 2026-01-24 | 6 | -| 結合テスト準備 | 2026-01-25 | 2026-01-29 | 5 | -| ユーザー受け入れテスト | 2026-01-19 | 2026-01-23 | 5 | - ---- - -### フェーズ 4:リリース・運用 - -| タスク | 開始 | 終了 | -| ------------------ | ---- | ---- | -| リリース手順書作成 | 未定 | 未定 | -| 本番データ移行計画 | 未定 | 未定 | -| 監視・ログ設計 | 未定 | 未定 | -| 運用手順 / 教育 | 未定 | 未定 | -| リリース後レビュー | 未定 | 未定 | - ---- - -## ガントチャート(Mermaid) - -```mermaid -gantt - title プロジェクト ガントチャート - dateFormat YYYY-MM-DD - - section フェーズ1 企画・要件 - 目的と成功指標の整理 :2025-12-24, 4d - ステークホルダー要件ヒアリング :2025-12-27, 3d - 現場業務フロー整理 :2025-12-29, 5d - スコープ定義 :2026-01-02, 6d - 要件レビュー :2025-12-28, 3d - - section フェーズ2 設計・準備 - 画面/帳票設計 :2025-12-29, 5d - データ・マスタ設計 :2025-12-31, 6d - IF仕様策定 :2026-01-05, 4d - リスク対策 :2026-01-05, 3d - 実装計画・見積 :2026-01-05, 4d - - section フェーズ3 実装・検証 - コア機能実装 :2026-01-08, 6d - 例外対応 :2026-01-14, 5d - 単体テスト :2026-01-19, 6d - 結合テスト準備 :2026-01-25, 5d - UAT :2026-01-19, 5d -``` diff --git a/src/exstruct/core/backends/base.py b/src/exstruct/core/backends/base.py index 8cf68fd..27d8bdc 100644 --- a/src/exstruct/core/backends/base.py +++ b/src/exstruct/core/backends/base.py @@ -3,11 +3,12 @@ from dataclasses import dataclass from typing import Protocol -from ...models import CellRow, PrintArea +from ...models import CellRow, MergedCell, PrintArea from ..cells import WorkbookColorsMap CellData = dict[str, list[CellRow]] PrintAreaData = dict[str, list[PrintArea]] +MergedCellData = dict[str, list[MergedCell]] @dataclass(frozen=True) @@ -36,3 +37,6 @@ def extract_colors_map( self, *, include_default_background: bool, ignore_colors: set[str] | None ) -> WorkbookColorsMap | None: """Extract colors map from the workbook.""" + + def extract_merged_cells(self) -> MergedCellData: + """Extract merged cell ranges from the workbook.""" diff --git a/src/exstruct/core/backends/com_backend.py b/src/exstruct/core/backends/com_backend.py index 94ac8cf..c6bdaf3 100644 --- a/src/exstruct/core/backends/com_backend.py +++ b/src/exstruct/core/backends/com_backend.py @@ -11,7 +11,7 @@ from ...models import PrintArea from ..cells import WorkbookColorsMap, extract_sheet_colors_map_com from ..ranges import parse_range_zero_based -from .base import PrintAreaData +from .base import MergedCellData, PrintAreaData logger = logging.getLogger(__name__) @@ -154,6 +154,10 @@ def extract_auto_page_breaks(self) -> PrintAreaData: continue return results + def extract_merged_cells(self) -> MergedCellData: + """Extract merged cell ranges via COM (not implemented).""" + raise NotImplementedError("COM merged cell extraction is not implemented.") + def _parse_print_area_range(range_str: str) -> tuple[int, int, int, int] | None: """Parse an Excel range string into zero-based coordinates. diff --git a/src/exstruct/core/backends/openpyxl_backend.py b/src/exstruct/core/backends/openpyxl_backend.py index 1619bd8..f7593d3 100644 --- a/src/exstruct/core/backends/openpyxl_backend.py +++ b/src/exstruct/core/backends/openpyxl_backend.py @@ -13,10 +13,11 @@ extract_sheet_cells, extract_sheet_cells_with_links, extract_sheet_colors_map, + extract_sheet_merged_cells, ) from ..ranges import parse_range_zero_based from ..workbook import openpyxl_workbook -from .base import CellData, PrintAreaData +from .base import CellData, MergedCellData, PrintAreaData logger = logging.getLogger(__name__) @@ -87,6 +88,17 @@ def extract_colors_map( ) return None + def extract_merged_cells(self) -> MergedCellData: + """Extract merged cell ranges per sheet. + + Returns: + Mapping of sheet name to merged cell ranges. + """ + try: + return extract_sheet_merged_cells(self.file_path) + except Exception: + return {} + def detect_tables(self, sheet_name: str) -> list[str]: """Detect table candidates for a single sheet. diff --git a/src/exstruct/core/cells.py b/src/exstruct/core/cells.py index a855a3e..5aaead9 100644 --- a/src/exstruct/core/cells.py +++ b/src/exstruct/core/cells.py @@ -15,7 +15,7 @@ import pandas as pd import xlwings as xw -from ..models import CellRow +from ..models import CellRow, MergedCell from .workbook import openpyxl_workbook logger = logging.getLogger(__name__) @@ -526,6 +526,41 @@ def extract_sheet_cells_with_links(file_path: Path) -> dict[str, list[CellRow]]: return merged +def extract_sheet_merged_cells(file_path: Path) -> dict[str, list[MergedCell]]: + """Extract merged cell ranges per sheet via openpyxl. + + Args: + file_path: Excel workbook path. + + Returns: + Mapping of sheet name to merged cell ranges. + """ + merged_by_sheet: dict[str, list[MergedCell]] = {} + with openpyxl_workbook(file_path, data_only=True, read_only=False) as wb: + for ws in wb.worksheets: + merged_ranges = getattr(ws, "merged_cells", None) + if merged_ranges is None: + merged_by_sheet[ws.title] = [] + continue + results: list[MergedCell] = [] + for merged_range in getattr(merged_ranges, "ranges", []): + bounds = range_boundaries(str(merged_range)) + min_col, min_row, max_col, max_row = bounds + cell_value = ws.cell(row=min_row, column=min_col).value + value_str = "" if cell_value is None else str(cell_value) + results.append( + MergedCell( + r1=min_row, + c1=min_col - 1, + r2=max_row, + c2=max_col - 1, + v=value_str, + ) + ) + merged_by_sheet[ws.title] = results + return merged_by_sheet + + def shrink_to_content( # noqa: C901 sheet: xw.Sheet, top: int, diff --git a/src/exstruct/core/integrate.py b/src/exstruct/core/integrate.py index afb41dc..77815aa 100644 --- a/src/exstruct/core/integrate.py +++ b/src/exstruct/core/integrate.py @@ -17,6 +17,7 @@ def extract_workbook( # noqa: C901 include_colors_map: bool | None = None, include_default_background: bool = False, ignore_colors: set[str] | None = None, + include_merged_cells: bool | None = None, ) -> WorkbookData: """Extract workbook and return WorkbookData. @@ -31,6 +32,7 @@ def extract_workbook( # noqa: C901 include_colors_map: Whether to include colors map; None uses mode defaults. include_default_background: Whether to include default background color. ignore_colors: Optional set of color keys to ignore. + include_merged_cells: Whether to include merged cell ranges; None uses mode defaults. Returns: Extracted WorkbookData. @@ -47,6 +49,7 @@ def extract_workbook( # noqa: C901 include_colors_map=include_colors_map, include_default_background=include_default_background, ignore_colors=ignore_colors, + include_merged_cells=include_merged_cells, ) result = run_extraction_pipeline(inputs) return result.workbook diff --git a/src/exstruct/core/modeling.py b/src/exstruct/core/modeling.py index 2b312e8..fb3e665 100644 --- a/src/exstruct/core/modeling.py +++ b/src/exstruct/core/modeling.py @@ -6,6 +6,7 @@ Arrow, CellRow, Chart, + MergedCell, PrintArea, Shape, SheetData, @@ -26,6 +27,7 @@ class SheetRawData: print_areas: Extracted print areas. auto_print_areas: Extracted auto page-break areas. colors_map: Mapping of color keys to (row, column) positions. + merged_cells: Extracted merged cell ranges. """ rows: list[CellRow] @@ -35,6 +37,7 @@ class SheetRawData: print_areas: list[PrintArea] auto_print_areas: list[PrintArea] colors_map: dict[str, list[tuple[int, int]]] + merged_cells: list[MergedCell] @dataclass(frozen=True) @@ -67,6 +70,7 @@ def build_sheet_data(raw: SheetRawData) -> SheetData: print_areas=raw.print_areas, auto_print_areas=raw.auto_print_areas, colors_map=raw.colors_map, + merged_cells=raw.merged_cells, ) diff --git a/src/exstruct/core/pipeline.py b/src/exstruct/core/pipeline.py index 9dfcc04..5199b30 100644 --- a/src/exstruct/core/pipeline.py +++ b/src/exstruct/core/pipeline.py @@ -10,7 +10,16 @@ import xlwings as xw from ..errors import FallbackReason -from ..models import Arrow, CellRow, Chart, PrintArea, Shape, SmartArt, WorkbookData +from ..models import ( + Arrow, + CellRow, + Chart, + MergedCell, + PrintArea, + Shape, + SmartArt, + WorkbookData, +) from .backends.com_backend import ComBackend from .backends.openpyxl_backend import OpenpyxlBackend from .cells import WorkbookColorsMap, detect_tables @@ -23,6 +32,7 @@ ExtractionMode = Literal["light", "standard", "verbose"] CellData = dict[str, list[CellRow]] PrintAreaData = dict[str, list[PrintArea]] +MergedCellData = dict[str, list[MergedCell]] ShapeData = dict[str, list[Shape | Arrow | SmartArt]] ChartData = dict[str, list[Chart]] @@ -42,6 +52,7 @@ class ExtractionInputs: include_colors_map: Whether to include background colors map. include_default_background: Whether to include default background color. ignore_colors: Optional set of color keys to ignore. + include_merged_cells: Whether to include merged cell ranges. """ file_path: Path @@ -52,6 +63,7 @@ class ExtractionInputs: include_colors_map: bool include_default_background: bool ignore_colors: set[str] | None + include_merged_cells: bool @dataclass @@ -65,6 +77,7 @@ class ExtractionArtifacts: colors_map_data: Extracted colors map for workbook sheets. shape_data: Extracted shapes per sheet. chart_data: Extracted charts per sheet. + merged_cell_data: Extracted merged cell ranges per sheet. """ cell_data: CellData = field(default_factory=dict) @@ -73,6 +86,7 @@ class ExtractionArtifacts: colors_map_data: WorkbookColorsMap | None = None shape_data: ShapeData = field(default_factory=dict) chart_data: ChartData = field(default_factory=dict) + merged_cell_data: MergedCellData = field(default_factory=dict) ExtractionStep = Callable[[ExtractionInputs, ExtractionArtifacts], None] @@ -164,6 +178,7 @@ def resolve_extraction_inputs( include_colors_map: bool | None, include_default_background: bool, ignore_colors: set[str] | None, + include_merged_cells: bool | None, ) -> ExtractionInputs: """Resolve include flags and normalize inputs for the pipeline. @@ -176,6 +191,7 @@ def resolve_extraction_inputs( include_colors_map: Whether to include background colors; None uses mode defaults. include_default_background: Include default background colors when colors_map is enabled. ignore_colors: Optional set of colors to ignore when colors_map is enabled. + include_merged_cells: Whether to include merged cell ranges; None uses mode defaults. Returns: Resolved ExtractionInputs. @@ -203,6 +219,9 @@ def resolve_extraction_inputs( resolved_ignore_colors = ignore_colors if resolved_colors_map else None if resolved_colors_map and resolved_ignore_colors is None: resolved_ignore_colors = set() + resolved_merged_cells = ( + include_merged_cells if include_merged_cells is not None else mode != "light" + ) return ExtractionInputs( file_path=normalized_file_path, @@ -213,6 +232,7 @@ def resolve_extraction_inputs( include_colors_map=resolved_colors_map, include_default_background=resolved_default_background, ignore_colors=resolved_ignore_colors, + include_merged_cells=resolved_merged_cells, ) @@ -258,6 +278,11 @@ def build_pre_com_pipeline(inputs: ExtractionInputs) -> list[ExtractionStep]: step=step_extract_colors_map_openpyxl, enabled=lambda _inputs: _inputs.include_colors_map, ), + StepConfig( + name="merged_cells_openpyxl", + step=step_extract_merged_cells_openpyxl, + enabled=lambda _inputs: _inputs.include_merged_cells, + ), ), "standard": ( StepConfig( @@ -276,6 +301,11 @@ def build_pre_com_pipeline(inputs: ExtractionInputs) -> list[ExtractionStep]: enabled=lambda _inputs: _inputs.include_colors_map and bool(os.getenv("SKIP_COM_TESTS")), ), + StepConfig( + name="merged_cells_openpyxl", + step=step_extract_merged_cells_openpyxl, + enabled=lambda _inputs: _inputs.include_merged_cells, + ), ), "verbose": ( StepConfig( @@ -294,6 +324,11 @@ def build_pre_com_pipeline(inputs: ExtractionInputs) -> list[ExtractionStep]: enabled=lambda _inputs: _inputs.include_colors_map and bool(os.getenv("SKIP_COM_TESTS")), ), + StepConfig( + name="merged_cells_openpyxl", + step=step_extract_merged_cells_openpyxl, + enabled=lambda _inputs: _inputs.include_merged_cells, + ), ), } steps: list[ExtractionStep] = [] @@ -432,6 +467,19 @@ def step_extract_colors_map_openpyxl( ) +def step_extract_merged_cells_openpyxl( + inputs: ExtractionInputs, artifacts: ExtractionArtifacts +) -> None: + """Extract merged cell ranges via openpyxl. + + Args: + inputs: Pipeline inputs. + artifacts: Artifact container to update. + """ + backend = OpenpyxlBackend(inputs.file_path) + artifacts.merged_cell_data = backend.extract_merged_cells() + + def step_extract_shapes_com( inputs: ExtractionInputs, artifacts: ExtractionArtifacts, workbook: xw.Book ) -> None: @@ -540,6 +588,7 @@ def collect_sheet_raw_data( cell_data: CellData, shape_data: ShapeData, chart_data: ChartData, + merged_cell_data: MergedCellData, workbook: xw.Book, mode: ExtractionMode = "standard", print_area_data: PrintAreaData | None = None, @@ -552,6 +601,7 @@ def collect_sheet_raw_data( cell_data: Extracted cell rows per sheet. shape_data: Extracted shapes per sheet. chart_data: Extracted charts per sheet. + merged_cell_data: Extracted merged cells per sheet. workbook: xlwings workbook instance. mode: Extraction mode. print_area_data: Optional print area data per sheet. @@ -574,6 +624,7 @@ def collect_sheet_raw_data( if auto_page_break_data else [], colors_map=_resolve_sheet_colors_map(colors_map_data, sheet_name), + merged_cells=merged_cell_data.get(sheet_name, []), ) result[sheet_name] = sheet_raw return result @@ -620,6 +671,7 @@ def _fallback(message: str, reason: FallbackReason) -> PipelineResult: cell_data=artifacts.cell_data, shape_data=artifacts.shape_data, chart_data=artifacts.chart_data, + merged_cell_data=artifacts.merged_cell_data, workbook=workbook, mode=inputs.mode, print_area_data=artifacts.print_area_data @@ -691,6 +743,7 @@ def build_cells_tables_workbook( else [], auto_print_areas=[], colors_map=sheet_colors.colors_map if sheet_colors else {}, + merged_cells=artifacts.merged_cell_data.get(sheet_name, []), ) raw = WorkbookRawData(book_name=inputs.file_path.name, sheets=sheets) return build_workbook_data(raw) diff --git a/src/exstruct/engine.py b/src/exstruct/engine.py index c23ada6..86a2e11 100644 --- a/src/exstruct/engine.py +++ b/src/exstruct/engine.py @@ -70,6 +70,7 @@ class StructOptions: before extraction. Use this to tweak table detection heuristics per engine instance without touching global state. include_colors_map: Whether to extract background color maps. + include_merged_cells: Whether to extract merged cell ranges. colors: Color extraction options. """ @@ -79,6 +80,7 @@ class StructOptions: ) include_cell_links: bool | None = None # None -> auto: verbose=True, others=False include_colors_map: bool | None = None # None -> auto: verbose=True, others=False + include_merged_cells: bool | None = None # None -> auto: light=False, others=True colors: ColorsOptions = field(default_factory=ColorsOptions) @@ -121,6 +123,9 @@ class FilterOptions(BaseModel): include_auto_print_areas: bool = Field( default=False, description="Include COM-computed auto page-break areas." ) + include_merged_cells: bool = Field( + default=True, description="Include merged cell ranges." + ) class DestinationOptions(BaseModel): @@ -279,6 +284,9 @@ def _filter_sheet( colors_map=sheet.colors_map, print_areas=sheet.print_areas if include_print_areas else [], auto_print_areas=sheet.auto_print_areas if include_auto_print_areas else [], + merged_cells=sheet.merged_cells + if self.output.filters.include_merged_cells + else [], ) def _filter_workbook( @@ -348,6 +356,7 @@ def extract( include_colors_map=self.options.include_colors_map, include_default_background=self.options.colors.include_default_background, ignore_colors=self.options.colors.ignore_colors_set(), + include_merged_cells=self.options.include_merged_cells, ) def serialize( diff --git a/src/exstruct/models/__init__.py b/src/exstruct/models/__init__.py index bd40d0b..9578ea9 100644 --- a/src/exstruct/models/__init__.py +++ b/src/exstruct/models/__init__.py @@ -76,6 +76,16 @@ class SmartArt(BaseShape): ) +class MergedCell(BaseModel): + """Metadata for a merged cell range.""" + + r1: int = Field(description="Start row (1-based).") + c1: int = Field(description="Start column (0-based).") + r2: int = Field(description="End row (1-based, inclusive).") + c2: int = Field(description="End column (0-based, inclusive).") + v: str = "" + + class CellRow(BaseModel): """A single row of cells with optional hyperlinks.""" @@ -160,6 +170,9 @@ class SheetData(BaseModel): "where row is 1-based and column is 0-based." ), ) + merged_cells: list[MergedCell] = Field( + default_factory=list, description="Merged cell ranges on the sheet." + ) def _as_payload(self) -> dict[str, object]: from ..io import dict_without_empty_values diff --git a/tests/backends/test_auto_page_breaks.py b/tests/backends/test_auto_page_breaks.py index efa9bb7..6c3adda 100644 --- a/tests/backends/test_auto_page_breaks.py +++ b/tests/backends/test_auto_page_breaks.py @@ -27,6 +27,7 @@ def fake_extract( include_colors_map: bool = False, include_default_background: bool = False, ignore_colors: set[str] | None = None, + include_merged_cells: bool | None = None, ) -> WorkbookData: called["include_auto_page_breaks"] = include_auto_page_breaks return WorkbookData(book_name=path.name, sheets={}) diff --git a/tests/backends/test_merged_cells.py b/tests/backends/test_merged_cells.py new file mode 100644 index 0000000..fbafca4 --- /dev/null +++ b/tests/backends/test_merged_cells.py @@ -0,0 +1,55 @@ +from pathlib import Path +from typing import cast + +from _pytest.monkeypatch import MonkeyPatch +from openpyxl import Workbook +import pytest +import xlwings as xw + +from exstruct.core.backends.com_backend import ComBackend +from exstruct.core.backends.openpyxl_backend import OpenpyxlBackend +from exstruct.models import MergedCell + + +def _make_merged_book(path: Path) -> None: + """Create a test workbook with a merged cell range. + + Args: + path: Path where the workbook will be saved. + """ + wb = Workbook() + ws = wb.active + ws.title = "Sheet1" + ws["A1"] = "Header" + ws.merge_cells("A1:C1") + wb.save(path) + wb.close() + + +def test_openpyxl_backend_extract_merged_cells(tmp_path: Path) -> None: + path = tmp_path / "merged.xlsx" + _make_merged_book(path) + + backend = OpenpyxlBackend(path) + merged = backend.extract_merged_cells() + assert merged["Sheet1"] == [MergedCell(r1=1, c1=0, r2=1, c2=2, v="Header")] + + +def test_openpyxl_backend_extract_merged_cells_handles_failure( + monkeypatch: MonkeyPatch, tmp_path: Path +) -> None: + def _boom(_path: Path) -> dict[str, list[MergedCell]]: + raise RuntimeError("boom") + + monkeypatch.setattr( + "exstruct.core.backends.openpyxl_backend.extract_sheet_merged_cells", + _boom, + ) + backend = OpenpyxlBackend(tmp_path / "missing.xlsx") + assert backend.extract_merged_cells() == {} + + +def test_com_backend_extract_merged_cells_not_implemented() -> None: + backend = ComBackend(cast(xw.Book, object())) + with pytest.raises(NotImplementedError): + backend.extract_merged_cells() diff --git a/tests/core/test_merged_cells_core.py b/tests/core/test_merged_cells_core.py new file mode 100644 index 0000000..4129bd6 --- /dev/null +++ b/tests/core/test_merged_cells_core.py @@ -0,0 +1,36 @@ +from pathlib import Path + +from openpyxl import Workbook + +from exstruct.core.cells import extract_sheet_merged_cells + + +def _make_merged_book(path: Path) -> None: + wb = Workbook() + ws = wb.active + ws.title = "Sheet1" + ws["A1"] = "Title" + ws.merge_cells("A1:B2") + ws.merge_cells("D4:E4") + wb.save(path) + + +def test_extract_sheet_merged_cells_basic(tmp_path: Path) -> None: + path = tmp_path / "merged.xlsx" + _make_merged_book(path) + + merged = extract_sheet_merged_cells(path) + ranges = merged["Sheet1"] + assert len(ranges) == 2 + tuples = {(r.r1, r.c1, r.r2, r.c2, r.v) for r in ranges} + assert (1, 0, 2, 1, "Title") in tuples + assert (4, 3, 4, 4, "") in tuples + + +def test_extract_sheet_merged_cells_empty(tmp_path: Path) -> None: + path = tmp_path / "plain.xlsx" + wb = Workbook() + wb.save(path) + + merged = extract_sheet_merged_cells(path) + assert merged["Sheet"] == [] diff --git a/tests/core/test_pipeline.py b/tests/core/test_pipeline.py index dc6e383..e45c5b1 100644 --- a/tests/core/test_pipeline.py +++ b/tests/core/test_pipeline.py @@ -25,6 +25,7 @@ def test_build_pre_com_pipeline_respects_flags( include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=False, ) steps = build_pre_com_pipeline(inputs) step_names = [step.__name__ for step in steps] @@ -43,6 +44,7 @@ def test_build_pre_com_pipeline_includes_colors_map_for_light( include_colors_map=True, include_default_background=False, ignore_colors=None, + include_merged_cells=True, ) steps = build_pre_com_pipeline(inputs) step_names = [step.__name__ for step in steps] @@ -50,9 +52,29 @@ def test_build_pre_com_pipeline_includes_colors_map_for_light( "step_extract_cells", "step_extract_print_areas_openpyxl", "step_extract_colors_map_openpyxl", + "step_extract_merged_cells_openpyxl", ] +def test_build_pre_com_pipeline_skips_merged_cells_when_disabled( + tmp_path: Path, +) -> None: + inputs = ExtractionInputs( + file_path=tmp_path / "book.xlsx", + mode="standard", + include_cell_links=False, + include_print_areas=True, + include_auto_page_breaks=False, + include_colors_map=True, + include_default_background=False, + ignore_colors=None, + include_merged_cells=False, + ) + steps = build_pre_com_pipeline(inputs) + step_names = [step.__name__ for step in steps] + assert "step_extract_merged_cells_openpyxl" not in step_names + + def test_build_com_pipeline_respects_flags(tmp_path: Path) -> None: inputs = ExtractionInputs( file_path=tmp_path / "book.xlsx", @@ -63,6 +85,7 @@ def test_build_com_pipeline_respects_flags(tmp_path: Path) -> None: include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=False, ) steps = build_com_pipeline(inputs) step_names = [step.__name__ for step in steps] @@ -85,6 +108,7 @@ def test_build_com_pipeline_excludes_auto_page_breaks_when_disabled( include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=False, ) steps = build_com_pipeline(inputs) step_names = [step.__name__ for step in steps] @@ -101,6 +125,7 @@ def test_build_com_pipeline_empty_for_light(tmp_path: Path) -> None: include_colors_map=True, include_default_background=False, ignore_colors=None, + include_merged_cells=False, ) steps = build_com_pipeline(inputs) assert steps == [] @@ -116,11 +141,13 @@ def test_resolve_extraction_inputs_defaults(tmp_path: Path) -> None: include_colors_map=None, include_default_background=True, ignore_colors=None, + include_merged_cells=None, ) assert inputs.include_cell_links is False assert inputs.include_print_areas is True assert inputs.include_colors_map is False assert inputs.include_default_background is False + assert inputs.include_merged_cells is True def test_build_cells_tables_workbook_uses_print_areas( @@ -143,10 +170,12 @@ def fake_detect_tables(_: Path, __: str) -> list[str]: include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=True, ) artifacts = ExtractionArtifacts( cell_data={"Sheet1": [CellRow(r=1, c={"0": "v"})]}, print_area_data={"Sheet1": [PrintArea(r1=1, c1=0, r2=1, c2=0)]}, + merged_cell_data={"Sheet1": []}, ) wb = build_cells_tables_workbook( inputs=inputs, @@ -156,3 +185,4 @@ def fake_detect_tables(_: Path, __: str) -> list[str]: sheet = wb.sheets["Sheet1"] assert sheet.print_areas assert sheet.table_candidates == ["A1:B2"] + assert sheet.merged_cells == [] diff --git a/tests/core/test_pipeline_fallbacks.py b/tests/core/test_pipeline_fallbacks.py index 5a214ce..4eea4c7 100644 --- a/tests/core/test_pipeline_fallbacks.py +++ b/tests/core/test_pipeline_fallbacks.py @@ -34,6 +34,7 @@ def test_pipeline_fallback_skip_com_tests( include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=None, ) result = run_extraction_pipeline(inputs) @@ -66,6 +67,7 @@ def _raise(*_args: object, **_kwargs: object) -> None: include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=None, ) result = run_extraction_pipeline(inputs) @@ -106,6 +108,7 @@ def _raise( include_colors_map=False, include_default_background=False, ignore_colors=None, + include_merged_cells=None, ) result = run_extraction_pipeline(inputs) diff --git a/tests/engine/test_engine.py b/tests/engine/test_engine.py index 93c2529..d3c66c7 100644 --- a/tests/engine/test_engine.py +++ b/tests/engine/test_engine.py @@ -14,6 +14,7 @@ CellRow, Chart, ChartSeries, + MergedCell, PrintArea, Shape, SheetData, @@ -33,6 +34,7 @@ def fake_extract( include_colors_map: bool = False, include_default_background: bool = False, ignore_colors: set[str] | None = None, + include_merged_cells: bool | None = None, ) -> WorkbookData: called["mode"] = mode called["include_print_areas"] = include_print_areas @@ -63,6 +65,7 @@ def _sample_workbook() -> WorkbookData: charts=[chart], table_candidates=["A1:B2"], print_areas=[PrintArea(r1=1, c1=0, r2=3, c2=2)], + merged_cells=[MergedCell(r1=1, c1=0, r2=1, c2=1, v="merged")], ) return WorkbookData(book_name="book.xlsx", sheets={"Sheet1": sheet}) @@ -85,6 +88,15 @@ def test_engine_serialize_filters_tables(tmp_path: Path) -> None: assert "table_candidates" not in text +def test_engine_serialize_filters_merged_cells() -> None: + wb = _sample_workbook() + engine = ExStructEngine( + output=OutputOptions(filters=FilterOptions(include_merged_cells=False)) + ) + text = engine.serialize(wb, fmt="json") + assert "merged_cells" not in text + + def test_engine_include_cell_links_toggle() -> None: wb = _sample_workbook() # By default links remain (already present) diff --git a/tests/export/test_export_requirements.py b/tests/export/test_export_requirements.py index a85baab..dae98d4 100644 --- a/tests/export/test_export_requirements.py +++ b/tests/export/test_export_requirements.py @@ -112,3 +112,9 @@ def test_export_sheets_yamlとtoonが出力される(tmp_path: Path) -> None: toon_paths = export_sheets_as(wb, toon_dir, fmt="toon") assert toon_paths["SheetA"].suffix == ".toon" assert toon_paths["SheetA"].exists() + + +def test_merged_cells_empty_is_omitted_in_sheet_json() -> None: + sheet = SheetData(rows=[CellRow(r=1, c={"0": "v"})], merged_cells=[]) + payload = sheet.to_json() + assert "merged_cells" not in payload diff --git a/tests/integration/test_integrate_raw_data.py b/tests/integration/test_integrate_raw_data.py index f24e8db..68be78c 100644 --- a/tests/integration/test_integrate_raw_data.py +++ b/tests/integration/test_integrate_raw_data.py @@ -56,6 +56,7 @@ def test_collect_sheet_raw_data_includes_extracted_fields( cell_data={"Sheet1": [CellRow(r=1, c={"0": "A"}, links=None)]}, shape_data={"Sheet1": [Shape(text="S", l=0, t=0)]}, chart_data={"Sheet1": [_make_chart()]}, + merged_cell_data={"Sheet1": []}, workbook=workbook, mode="standard", print_area_data={"Sheet1": [PrintArea(r1=1, c1=0, r2=1, c2=0)]}, @@ -90,6 +91,7 @@ def test_collect_sheet_raw_data_skips_charts_in_light_mode( cell_data={"Sheet1": []}, shape_data={"Sheet1": []}, chart_data={"Sheet1": []}, + merged_cell_data={"Sheet1": []}, workbook=workbook, mode="light", print_area_data=None, diff --git a/tests/models/test_modeling.py b/tests/models/test_modeling.py index 4106408..60a274a 100644 --- a/tests/models/test_modeling.py +++ b/tests/models/test_modeling.py @@ -25,6 +25,7 @@ def test_build_workbook_data_from_raw() -> None: print_areas=[PrintArea(r1=1, c1=0, r2=1, c2=0)], auto_print_areas=[], colors_map={"#FFFFFF": [(1, 0)]}, + merged_cells=[], ) raw_workbook = WorkbookRawData(book_name="book.xlsx", sheets={"Sheet1": raw_sheet}) diff --git a/uv.lock b/uv.lock index 771cff4..4ee6c6d 100644 --- a/uv.lock +++ b/uv.lock @@ -298,7 +298,7 @@ wheels = [ [[package]] name = "exstruct" -version = "0.3.0" +version = "0.3.1" source = { editable = "." } dependencies = [ { name = "numpy" },