こんにちは!前の章、第5章:
社内勤務表リーダー
(InternalTimesheetReader)では、社内用のCSVファイルを読み込んで、プログラムが理解できる共通のデータ形式
(InternalTimesheet)
に変換する専門家について学びましたね。
しかし、比較のためにはもう一方、お客様から受け取る勤務表も読み込む必要があります。ここで少し困った問題があります。お客様によって、勤務表の形式がバラバラなのです!あるお客様はPDFで、別のお客様はExcelで、さらにそのExcelの中でもレイアウトが違ったり…と、多種多様です。
これら全ての形式に個別に対応するのは大変ですよね。そこで登場するのが、今回学ぶ客先勤務表リーダー (ClientTimesheetReader) です!
ClientTimesheetReader
は、一言でいうと、様々な形式(PDF、Excelなど)で送られてくる客先勤務表ファイルを読み込み、プログラムが統一的に扱える共通の形式
(ClientTimesheet データモデル)
に変換するための機能群 のことです。
ちょうど、英語の文書もフランス語の文書も、日本語という共通言語に翻訳してくれる多言語対応の翻訳機のような役割を果たします。この「翻訳機」があるおかげで、プログラムの他の部分(特に第1章: 勤務表比較
(TimesheetComparator))は、元のファイルがPDFであろうとExcelであろうと、常に「日本語」(=
ClientTimesheet
形式)で書かれた情報を扱えるようになります。
具体的には、ClientTimesheetReader
は以下のような仕事を担当します:
PDFReader を、Excelファイルなら ExcelReader
を選びます。ClientTimesheet という共通の形式にまとめます。重要なのは、ClientTimesheetReader
自体が一つの具体的なクラスというよりは、「客先の様々な勤務表を読み込む」という役割全体を指す概念であり、その役割を果たすための窓口(インターフェース)と、実際の処理を行う専門家(PDFReader,
ExcelReader
など)の集まりである、という点です。
例えば、A社からは下のようなレイアウトのPDF勤務表が、B社からは全く違うレイアウトのExcel勤務表が送られてくるとします。
A社 PDF (例)
+-------------------------------------+
| 勤務時間報告書 |
| 氏名: 田中 太郎 期間: 2024/07 |
+-------------------------------------+
| 日付 | 開始 | 終了 | 休憩 | 実働 |
|------|------|------|------|------|
| 7/1 | 9:00 | 18:00| 1.0 | 8.0 |
| 7/2 | 9:05 | 18:00| 1.0 | 7.92 |
| ... | ... | ... | ... | ... |
+-------------------------------------+
B社 Excel (例)
+------------------------------------------------------+
| 月次作業報告書 (2024年7月) |
| 作業者: 佐藤 次郎 |
+------------------------------------------------------+
| 日 | 曜日 | 業務開始 | 業務終了 | 休憩(分) | 稼働時間 |
|----|------|----------|----------|----------|----------|
| 1 | 月 | 09:00 | 17:30 | 60 | 7.5 |
| 2 | 火 | 09:00 | 18:00 | 60 | 8.0 |
| ...| ... | ... | ... | ... | ... |
+------------------------------------------------------+
これらは見た目も形式も全く違いますが、どちらも「誰が」「いつ」「どれだけ働いたか」という情報を含んでいます。ClientTimesheetReader
のおかげで、プログラムはこれらの違いを意識することなく、最終的に同じ形の
ClientTimesheet データとして情報を取得できるのです。
graph LR
A[A社.pdf] -- ClientTimesheetReader --> C{ClientTimesheet};
B[B社.xlsx] -- ClientTimesheetReader --> C;
subgraph ClientTimesheetReader [客先勤務表リーダー]
direction LR
D(ファイル形式判定) --> E{PDF?};
E -- Yes --> F[PDFReader];
E -- No --> G{Excel?};
G -- Yes --> H[ExcelReader];
F -- 解析 --> I(データ抽出);
H -- 解析 --> I;
I -- 変換 --> J([共通形式 ClientTimesheet]);
end
C -- データ利用 --> K([勤務表比較]);
style C fill:#f9f,stroke:#333,stroke-width:2px
この図のように、ClientTimesheetReader
が入り口となり、ファイルの種類に応じて適切な専門リーダー
(PDFReader や ExcelReader)
に処理を振り分け、最終的に共通の ClientTimesheet
形式に「翻訳」している様子がわかります。
第4章: ファイル選択
(FileSelector) が客先勤務表のファイルパスを受け取った後、内部では
ClientTimesheetReader
を使ってデータを読み込みます。その際のイメージは以下のようになります。
# FileSelector の get_client_timesheet メソッド内で... (イメージ)
# ユーザーが選択したファイルのパスを取得
client_file_path = self._client_file_path # 例: "C:/path/to/client_timesheet.pdf" or "D:/data/customer_report.xlsx"
# 適切なリーダーを取得する (ファイル拡張子などから判断)
# ここでは ClientTimesheetReader が内部的に PDFReader か ExcelReader を選択するイメージ
reader: ClientTimesheetReader # 型ヒントで Reader であることを示す
file_extension = client_file_path.suffix.lower()
if file_extension == ".pdf":
reader = PDFReader() # PDFリーダーを選択
elif file_extension in [".xlsx", ".xls", ".xlsm"]:
reader = ExcelReader() # Excelリーダーを選択
else:
raise ValueError(f"未対応のファイル形式です: {file_extension}")
# リーダーの read メソッドを呼び出してファイルを読み込む
try:
# どのリーダーでも同じ read メソッドを呼べば良い!
client_timesheet_list = reader.read(client_file_path)
# 成功! client_timesheet_list には ClientTimesheet オブジェクトのリストが入る
print("客先勤務表の読み込みに成功しました!")
# この後、このデータが比較処理に使われる
except FileNotFoundError:
print("エラー: ファイルが見つかりません。")
except ValueError as e:
print(f"エラー: ファイル形式が正しくないか、未対応の可能性があります: {e}")
except Exception as e:
print(f"エラー: 予期せぬ問題が発生しました: {e}")このコードのポイントは、ファイルがPDFでもExcelでも、最終的に
reader.read(client_file_path)
という同じ形で読み込み処理を呼び出せている点です。これは、PDFReader
も ExcelReader も、ClientTimesheetReader
という共通の「お約束」(インターフェース)に従って作られているためです。
ClientTimesheetReader (具体的には PDFReader
や ExcelReader) が read
メソッドで呼び出されたとき、内部では何が起こっているのでしょうか?
PDFReader):
camelot
などのライブラリを使って、PDF内の表(テーブル)を探し出し、その中の文字や数値を読み取ります。PDFはレイアウトが固定されているため、表を見つけるのが少し難しい場合があります。ExcelReader):
pandas
などのライブラリを使って、Excelシート内のデータを読み取ります。Excelはセル単位でデータが構造化されているため、比較的データは読み取りやすいですが、どのセルに何の情報があるかはファイルによって異なります。PDFReader
や ExcelReader
は、読み込んだファイルのレイアウトがどのテンプレートに合致するかを判定し、そのテンプレートの指示に従って必要な情報(日付、開始時刻、終了時刻など)を正確に抽出します。このテンプレートの詳細は第7章:
ファイル形式テンプレート (File Format
Templates)で詳しく学びます。ClientDailyRecord
オブジェクトを日ごと作成し、それらをまとめて
ClientTimesheet オブジェクトを作成します。ClientTimesheet
オブジェクト(のリスト)を呼び出し元に返します。大まかな流れをシーケンス図で見てみましょう。(PDFReader
の例)
sequenceDiagram
participant FS as FileSelector
participant PR as PDFReader (ClientTimesheetReader の具体例)
participant Lib as 外部ライブラリ (Camelot)
participant Tmpl as テンプレート (例: KagaPDFTemplate)
participant PDF as PDFファイル
participant CTS as ClientTimesheet
FS->>PR: read(ファイルパス) を呼び出す
PR->>Lib: PDF解析を依頼 (camelot.read_pdf)
Lib->>PDF: ファイル内容を読み込む
PDF-->>Lib: PDFデータ
Lib-->>PR: 解析結果 (テーブルデータなど)
PR->>PR: どのテンプレートを使うか判定 (detect_format)
Note right of PR: レイアウトを分析
PR->>Tmpl: テンプレートの指示でデータ抽出を依頼 (extract_data)
Tmpl->>PR: 解析結果から必要な情報を抽出
PR-->>Tmpl: 抽出データ
Tmpl->>Tmpl: データを整形
Tmpl->>CTS: ClientTimesheet オブジェクトを作成
CTS-->>Tmpl: 作成された ClientTimesheet
Tmpl-->>PR: ClientTimesheet (リスト)
PR-->>FS: ClientTimesheet (リスト) を返す
この図から、PDFReader
が外部ライブラリやテンプレートと連携しながら、PDFファイルという「外国語」を
ClientTimesheet
という「共通言語」に翻訳している様子がわかりますね。ExcelReader
も同様に、Excel用のライブラリやテンプレートを使って処理を行います。
ClientTimesheetReader
の考え方を支える重要な要素が、共通のインターフェース(お約束)です。
src/models/client_timesheet_reader.py
には、客先勤務表リーダーが最低限満たすべき「お約束」が定義されています。
# src/models/client_timesheet_reader.py より (抜粋)
from abc import abstractmethod
from pathlib import Path
from typing import Protocol # Protocolを使って「お約束」を定義
from .client_timesheet import ClientTimesheet
class ClientTimesheetReader(Protocol):
"""クライアント勤務表読み取りインターフェース"""
@abstractmethod # このメソッドは必ず実装する必要があるという印
def read(self, file_path: Path | str) -> ClientTimesheet:
"""
勤務表ファイルを読み取り、ClientTimesheetオブジェクトを返します。
"""
raise NotImplementedError # 中身はここでは定義しない
# detect_format メソッドも同様に定義されている場合があります
# @abstractmethod
# def detect_format(self, ...) -> str:
# """ファイルの形式を検出します。"""
# raise NotImplementedErrorこれは Protocol という Python
の機能を使って、「ClientTimesheetReader を名乗るなら、必ず
read
という名前のメソッドを持っていて、それはファイルパスを受け取って
ClientTimesheet
を返すものである」というルールを定めています。PDFReader も
ExcelReader も、このルールに従って作られています。
src/pdf_handler/pdf_reader.py にある
PDFReader は、この ClientTimesheetReader
のルールに従った具体例の一つです。
# src/pdf_handler/pdf_reader.py より (簡略化)
from pathlib import Path
import camelot # PDF解析ライブラリ
from ..models.client_timesheet import ClientTimesheet
# ClientTimesheetReader のルールに従うことを示す (暗黙的に)
from ..models.client_timesheet_reader import ClientTimesheetReader
from .pdf_template_detector import PDFTemplateDetector # テンプレート判定役
from .templates import PDFTemplate # 各テンプレートの基底クラス
from .templates.client_kaga_template import ClientKagaPDFTemplate # 具体的なテンプレート例
class PDFReader: # ClientTimesheetReader Protocol を実装
"""PDFリーダークラス"""
def __init__(self):
self.template_detector = PDFTemplateDetector()
# 対応しているテンプレートを登録
self.templates: Dict[str, PDFTemplate] = {
"client_kaga": ClientKagaPDFTemplate(),
# 他のテンプレートもここに追加 ...
}
def read(self, file_path: Path | str): # インターフェースで定義されたメソッド
file_path = Path(file_path)
if not file_path.exists():
raise FileNotFoundError(f"ファイルが見つかりません: {file_path}")
try:
# 1. camelot で PDF からテーブルデータを抽出
pdf_tables = camelot.read_pdf(str(file_path), pages="all", ...)
# 2. どのテンプレートを使うか判定
template_id = self.template_detector.detect(pdf_tables)
if template_id not in self.templates:
raise ValueError(f"未対応のテンプレート形式です: {template_id}")
# 3. 対応するテンプレートを取得
template = self.templates[template_id]
# 4. テンプレートを使ってデータを抽出し、ClientTimesheet を作成
client_timesheet_list = template.extract_data(pdf_tables)
# 5. (オプション) データの妥当性チェック
if not template.validate(client_timesheet_list):
raise ValueError("抽出されたデータが不正です。")
return client_timesheet_list # 結果を返す
except Exception as e:
# エラー処理
raise IOError(f"PDFの読み取りまたは解析に失敗しました: {e}")PDFReader の read メソッドは、ライブラリ
(camelot)
を使ってPDFを解析し、template_detector
で適切なテンプレートを特定し、そのテンプレート (template)
に実際のデータ抽出 (extract_data) と検証
(validate) を任せていることがわかります。
同様に src/excel_handler/excel_reader.py にある
ExcelReader も ClientTimesheetReader
のルールに従っています。
# src/excel_handler/excel_reader.py より (簡略化)
from pathlib import Path
import pandas as pd # Excel操作ライブラリ
from ..models.client_timesheet import ClientTimesheet
# ClientTimesheetReader のルールに従う
from ..models.client_timesheet_reader import ClientTimesheetReader
from .excel_template_detector import ExcelTemplateDetector
from .templates import ExcelTemplate
from .templates.client_cw_template import ClientCWExcelTemplate # 具体的なテンプレート例
class ExcelReader: # ClientTimesheetReader Protocol を実装
"""Excelリーダークラス"""
def __init__(self):
self.template_detector = ExcelTemplateDetector()
# 対応しているテンプレートを登録
self.templates: Dict[str, ExcelTemplate] = {
"client_cw": ClientCWExcelTemplate(),
# 他のテンプレートもここに追加 ...
}
def read(self, file_path: Path | str): # インターフェースで定義されたメソッド
file_path = Path(file_path)
if not file_path.exists():
raise FileNotFoundError(f"ファイルが見つかりません: {file_path}")
try:
# 1. pandas で Excel ファイルを読み込む
df = pd.read_excel(file_path, engine='openpyxl') # または 'xlrd'
# 2. どのテンプレートを使うか判定
template_id = self.template_detector.detect(df)
if template_id not in self.templates:
raise ValueError(f"未対応のテンプレート形式です: {template_id}")
# 3. 対応するテンプレートを取得
template = self.templates[template_id]
# 4. テンプレートを使ってデータを抽出し、ClientTimesheet を作成
client_timesheet = template.extract_data(df)
# 5. (オプション) データの妥当性チェック
# if not template.validate(client_timesheet):
# raise ValueError("抽出されたデータが不正です。")
return client_timesheet # 結果を返す (Excelは通常1ファイル1シート)
except Exception as e:
# エラー処理
raise IOError(f"Excelの読み取りまたは解析に失敗しました: {e}")ExcelReader も PDFReader
と非常によく似た構造で、Excel用ライブラリ (pandas)
とExcel用テンプレートを使って、共通の ClientTimesheet
形式にデータを変換しています。
このように、共通のインターフェース
(ClientTimesheetReader)
を設けることで、ファイル形式ごとに特化したリーダー
(PDFReader, ExcelReader)
を用意しつつ、それらを呼び出す側はリーダーの種類を意識せずに済む、という柔軟な設計が実現されています。
この章では、多種多様な形式の客先勤務表を読み込むための「翻訳機」である客先勤務表リーダー (ClientTimesheetReader) について学びました。
ClientTimesheetReader は、様々な形式(PDF,
Excelなど)の客先勤務表ファイルを読み込み、共通のデータ形式
ClientTimesheet に変換する機能群(概念)です。PDFReader,
ExcelReader など)に処理を委任します。camelot,
pandas
など)や、レイアウトの違いを吸収するための「テンプレート」を利用してデータを抽出・整形します。ClientTimesheetReader
は、全てのリーダーが従うべき共通のインターフェース(お約束)を定義しており、これによりプログラム全体の見通しが良くなっています。これで、形式の異なる客先勤務表が、どのようにしてプログラム内部で統一的に扱えるデータになるのか、その仕組みが理解できたはずです。
しかし、PDFReader や ExcelReader
が、どうやって様々なレイアウトのファイルから正確に情報を見つけ出しているのでしょうか?その鍵を握るのが「テンプレート」です。次の章では、このテンプレートの仕組みについて詳しく見ていきましょう。
次の章: 第7章: ファイル形式テンプレート (File Format Templates)
Generated by AI Codebase Knowledge Builder