UiPathとDX Suiteを使ってPDFからCSVファイルに変換する処理を作った時の話

# 前置き
[RPAを使った仕事をした感想をまとめた記事](https://qiita.com/Shiho_anyplus/items/6444d58678901d6388bb) をちょっと前に書いたのですが、会社関係者以外からの反応も頂けてRPAへの関心の高さを伺いしれました。
(リアクションくださった方、感謝です！)
せっかくなのでまたナレッジを共有したいと思います。

今回は、紙からPDF化した請求書をDX Suite[^DXSuite]のAPIを使ってデータ化し、CSVファイルとしてダウンロードするという処理を作った際に悩んだところがあったのでその内容と解決方法を記事にしてみます。

なお、UiPathのワークフローの公開やDX SuiteのAPI説明等はしておりません。
あくまで、ロジックの考え方の参考になれば幸い程度の内容となっております。ご了承ください。

# 目次

1. やったこと 
1. 悩んだ箇所とその対処法
    1. 悩み1：明細表の読み取り方法によって変わるCSVのフォーマット
    2. 悩み2：CSV出力時、１つの列に別の項目の値が含まれることがある
1. あとがき

# やったこと
請求書PDFファイルをDX Suiteへアップロードし、帳票の自動仕分け(Elastic Sorter機能)とOCR処理(Intelligent OCR機能)を実行させて帳票のデジタルデータ化。
DX Suiteからデータ化されたファイルをダウンロードする処理を作りました。

## アプリケーション
UiPath[19.10.4]
DX Suite[2020/04/03]

## UiPathライブラリ
UiPath.UIAutomation.Activities[19.11.1]
UiPath.System.Activities[19.10.1]
UiPath.WebApi[1.4.4]

> // コメント
> UiPathのマーケットプレースには公式でDX Suiteを利用するためのワークフローが提供されています。参考：[AI-OCR workflow for AI inside - DX Suite](https://connect.uipath.com/ja/marketplace/components/ai-ocr-workflow-for-ai-inside-dx-suite)
> 今回の開発では参考にした位で利用していません。

# 悩んだ箇所とその対処法
DX Suiteの仕様に悩まされました。
この話をするには簡単にDX Suiteの機能を説明する必要があります。

## DX Suiteの仕様
DX Suiteは簡単に説明すると、予め帳票の種類ごとに読み取り位置を設定しておき、OCR実行時は設定した位置だけ解析を行ってデータ化を行います。
この帳票の種類を特定して分類する機能が`Elastic Sorter機能`になり、帳票の種類特定後に行うOCR処理が`Intelligent OCR機能`になります。
これら機能を実行すると、読み取り位置ごとカンマ区切りでデータ化されたCSVをダウンロードすることができます。

## 悩み1：明細表の読み取り方法によって変わるCSVのフォーマット
DX Suiteは表形式の画像データならば表全体を範囲指定して読み取ることが可能で、CSV出力した際も行ごとにデータが出力されます。
しかし、表形式でもセルの中に改行されて複数の項目が記入されているデータの場合、範囲指定は使えず、１つずつ読み取り範囲を指定するしかありませんでした。

▼イメージ：範囲指定でOKパターン
項目ごとのCSV出力される。

| 明細内容 | 数量 | 単価 | 金額(税込み) |
|:-:|:-:|:-:|:-:|
| 明細１ | 1 | 1000 | 1100 |
| 明細２ | 1 | 2000 | 2200 |

▼イメージ：範囲指定NGパターン
数量、単価が明細内容に含まれた状態でCSV出力されてしまう。

| 明細内容 | 金額(税込み) |
|:--|:-:|
| 明細１<br>　　　数量：1<br>　　　単価：1000 | 1100 |
| 明細２<br>　　　数量：1<br>　　　単価：2000 | 2200 |

なので、範囲指定NGパターンの場合、１項目ずつ読み取り指定を行う対応をしました。
その結果、CSV出力すると１行目に全データが出力されてしまう事に。。。

▼出力イメージ

```
明細内容_1, 数量_1, 単価_1, 金額(税込み)_1, 明細内容_2, 数量_2, 単価_2, 金額(税込み)_2
明細１, 1, 1000, 1100, 明細２, 1, 2000, 2200,
```

### 対処法
範囲指定パターンと１項目ずつ指定パターンの両方に対応するため、フォーマットの統一化処理を入れました。
１項目ずつ指定パターンの場合は、前述の出力イメージのように項目名の末尾に「_[行番号]」を設定することで、データの持替え処理を作りました。

処理の大まかな流れは以下の通りです。

1. CSVを読み込みDataTable[^DataTable]として保持
2. 項目名の末尾に"_1"が存在する場合持ち替え処理必要と判定する
3. 項目名の末尾が"_1"の項目のみ抽出して持ち替え後の列イメージを作成
4. 元データの項目ごとに繰り返し処理
    1. 末尾の行番号ごとに新規行を作成
    2. 持ち替え後DataTableに追加


## 悩み2：CSV出力時、１つの列に別の項目の値が含まれることがある
PDFをDX SuiteのElastic Sorter機能へアップロードすると仕分けユニットIDが発番されます。
この仕分けユニットIDを使ってCSVダウンロードした場合、１つの列に別の項目の値が含まれることがあります。
なぜ、そんなことが起きてしまうのか！？
それはPDFが複数ページ(帳票の種類が複数)あった時に出力項目が異なる帳票データをまとめて出力すると、項目名を見ずに左から順に出力しているためのようです。
（ど忘れしましたが、確かヘッダーの項目名は最後に読み取った項目名で上書きされた）


▼PDF1枚目の出力イメージ

```
H_請求書番号, H_会社名, H_請求日, H_支払日, H_請求金額
A-001-0001, 株式会社A, 2020/4/1, 2020/4/30, 3300
```

▼PDF2枚目の出力イメージ

```
H_請求書番号, D_明細内容, D_数量, D_単価, D_金額(税込み)
A-001-0001, 明細１, 1, 1000, 1100
A-001-0001, 明細２, 1, 2000, 2200
```

▼仕分けユニットIDを使ってCSVダウンロードした場合の出力イメージ

```
H_請求書番号, D_明細内容, D_数量, D_単価, D_金額(税込み)　　// 最後に読み取った項目に上書きされている
A-001-0001, 株式会社A, 2020/4/1, 2020/4/30, 3300　　// 項目名と内容が一致しない！
A-001-0001, 明細１, 1, 1000, 1100
A-001-0001, 明細２, 1, 2000, 2200
```

> // コメント
> 上記イメージでは分かりやすくするため、項目名の頭文字「H_」は１項目ずつ指定パターン、頭文字「D_」は範囲指定パターンとしています。
> 直接触っていないので分から無いのですが、１項目ずつ指定パターンで読み取った文字列でも、全行に繰り返し出力させる設定することが出来るようです。

### 対処法
アップロード時に発番される仕分けユニットIDとは別に仕分けした帳票種類ごとに発番される読取ユニットIDがあり、この読取ユニットIDごとにダウンロードすることで項目が混ざっていないCSVファイルを出力することができました。
仕分けユニットIDから読取ユニットIDリストを取得するのは、APIの仕分けユニット検索で仕分けユニットIDを渡してあげると、返却される情報から取ることができます。
ただ、この方法だと帳票種類ごとの出力となるため、ページごとにバラバラになったCSVファイルを統合する処理を入れました。

処理の大まかな流れは以下のとおりです。

1. DX SuiteAPI:仕分けユニットID検索を呼び出す
2. 返却値から読取りユニットIDのリストを取得
3. 読取りユニットIDリストごとに繰り返し
    1. DX SuiteAPI:読取りユニットCSVダウンロードを呼び出す
    2. ダウンロードしたファイルのパスをリストで保持 
4. 統合用の新規DataTable[^DataTable]作成
5. ダウンロードファイルリストごとに繰り返し
    1. ファイルを読み込みDataTableに保持
    2. 統合用Tableに列追加。すでにある場合はしない。
    3. 統合用Tableに読み込んだデータをマージ

以下は読取りユニットIDごとの出力例と統合処理後のイメージです。

▼読取ユニットID:001のCSVファイル（PDF1枚目）

```
H_請求書番号, H_会社名, H_請求日, H_支払日, H_請求金額
A-001-0001, 株式会社A, 2020/4/1, 2020/4/30, 3300
```

▼読取ユニットID:002のCSVファイル（PDF2枚目）

```
H_請求書番号, D_明細内容, D_数量, D_単価, D_金額(税込み)
A-001-0001, 明細１, 1, 1000, 1100
A-001-0001, 明細２, 1, 2000, 2200
```

▼統合処理後のCSVファイル

```
H_請求書番号, H_会社名, H_請求日, H_支払日, H_請求金額, D_明細内容, D_数量, D_単価, D_金額(税込み)
A-001-0001, 株式会社A, 2020/4/1, 2020/4/30, 3300,,,,　　// 読取ユニットID:001のデータ
A-001-0001,,,,, 明細１, 1, 1000, 1100　　// 読取ユニットID:002のデータ
A-001-0001,,,,, 明細２, 1, 2000, 2200
```
> // コメント
> 上記処理はPDFの内容を1つのCSVファイルに統合する方法になります。
> 今回の開発では1つのPDFに複数の請求書番号を含んでいる場合も考慮する必要があったので、請求書番号ごとに分割する処理も入れました。

# あとがき

業務効率化において今注目のRPAとOCR。しかもUiPathが公式でDX Suiteをサポートしているので今回の私の経験が他の方にとって参考になる可能性が高いと思いましたので記事化してました。
とはいえ、詳細を割愛している箇所が多くて分かりづらかったかと思います。
すみません。

今回対処方法として行ったデータ操作は、通常のプログラミングでも結構面倒ですが、
まだRPA開発に慣れていない中で基本のアクティビティだけで実現させるという縛りがあったので尚の事苦労しました。
お陰でかなりの経験値を積むことができた気がします。

最後まで読んでくださりありがとうございました。

[^DXSuite]: DX SuiteとはAI inside株式会社が提供するAI-OCRソリューション。リンク：[DX Suite](https://dx-suite.com)
[^DataTable]: UiPath標準言語VB.NETのクラス名。