こんにちは!前の章、第4章: ファイル選択 (FileSelector)では、比較したい勤務表ファイルをアプリケーションに指定する方法を学びましたね。ユーザーがファイルを選んでくれる「受付係」の役割でした。
さて、ファイルが選ばれただけでは、まだ中身はコンピューターにとって意味不明な文字列の集まりです。比較処理を行うためには、ファイルの中身を読み取り、プログラムが理解できる形、つまり第2章: 勤務表データモデル (Timesheet Data Models)で学んだ「設計図」に沿った形に変換する必要があります。
今回は、その中でも社内で使われている特定のCSV形式の勤務表ファイルを読み込む専門家、社内勤務表リーダー (InternalTimesheetReader) について学んでいきましょう。
InternalTimesheetReader
は、その名の通り、社内で標準的に使われているCSV形式の勤務表ファイルを読み込むことに特化した部品です。
社内には様々な書類がありますが、その中でも特定のフォーマット(ここではCSV形式の勤務表)だけを専門に扱い、その情報を決まった形式(このプロジェクトの共通言語である
InternalTimesheet
データモデル)に整理してくれる、社内文書専門のファイリング係のような存在だと考えてください。
他の形式のファイル(例えば客先から来るPDFやExcel)は扱えませんが、社内CSVファイルに関しては、その構造を熟知しており、正確に情報を抜き出してくれます。
主な仕事は以下の通りです:
InternalTimesheet と InternalDailyRecord
の形にまとめ上げます。これにより、CSVファイルという特定の形式が、プログラム全体で統一的に扱える
InternalTimesheet
データオブジェクトへと変換されるのです。
InternalTimesheetReader は、主にメインウィンドウ
(MainWindow) から(間接的にファイル選択
(FileSelector) を通じて)呼び出されます。
ユーザーがファイルを選び、「実行(比較)」ボタンを押すと、内部的に次のような処理が行われます。(これは概念的なコードの流れです)
# FileSelector の get_internal_timesheet メソッド内で... (イメージ)
# 1. 社内勤務表リーダーのインスタンスを作成
internal_reader = InternalTimesheetReader()
# 2. ユーザーが選択したファイルのパスを取得 (これは FileSelector が覚えている)
internal_file_path = self._internal_file_path # 例: "C:/path/to/internal_timesheet.csv"
# 3. リーダーの read メソッドを呼び出してファイルを読み込む
try:
# read メソッドにファイルパスを渡す
internal_timesheet_list = internal_reader.read(internal_file_path)
# 成功! internal_timesheet_list には InternalTimesheet オブジェクトのリストが入る
print("社内勤務表の読み込みに成功しました!")
# この後、このデータが比較処理に使われる
except FileNotFoundError:
print("エラー: ファイルが見つかりません。")
except ValueError:
print("エラー: CSVファイルの形式が正しくない可能性があります。")
except Exception as e:
print(f"エラー: 予期せぬ問題が発生しました: {e}")このコードでは、まず InternalTimesheetReader
の「実体」(インスタンス)を作ります。次に、ファイル選択
(FileSelector)
が記憶している社内勤務表のファイルパスを使って、internal_reader
の read メソッドを呼び出します。
read
メソッドは、指定されたCSVファイルを読み込み、解析し、その結果を
InternalTimesheet
オブジェクト(またはそのリスト)として返します。もしファイルが見つからなかったり、CSVの内容が期待通りでなかったりした場合は、エラーが発生することもあります。
InternalTimesheetReader
が読み込むCSVファイルは、例えば以下のような形式を想定しています。(実際のファイルはもっと複雑な場合があります)
"社員番号","社員名","処理年月","日付","勤務区分","出勤時刻","退勤時刻","出勤打刻時刻","退勤打刻時刻","コメント","プロジェクト名称","プロジェクト時間"
"99001","山田 太郎","2024/07","2024/07/01","通常","09:00","18:00","08:58","18:05","","プロジェクトA","08:00"
"99001","山田 太郎","2024/07","2024/07/02","通常","09:00","18:30","09:02","18:35","残業対応","プロジェクトA","08:30"
"99001","山田 太郎","2024/07","2024/07/03","午前半休","13:00","18:00","12:55","18:02","午前半休取得","プロジェクトA","04:00"
# ... 以下、一ヶ月分の日次データが続く ...
,)で区切られ、多くの場合ダブルクォーテーション(")で囲まれています。上記のCSVファイルを InternalTimesheetReader
で読み込むと、内部的に以下のような構造のデータ(InternalTimesheet
オブジェクト)が作成されます。
# 生成される InternalTimesheet オブジェクトのイメージ
InternalTimesheet(
employee_number="99001", # 社員番号
employee_name="山田 太郎", # 社員名
target_period="2024年7月1日~2024年7月31日", # 対象期間 (CSVから計算される)
daily_records=[ # 日々の記録のリスト
InternalDailyRecord(
date=date(2024, 7, 1), # 日付
registered_start_time=time(9, 0), # 登録された出勤時刻
registered_end_time=time(18, 0), # 登録された退勤時刻
start_time=time(8, 58), # 打刻された出勤時刻
end_time=time(18, 5), # 打刻された退勤時刻
main_pj_work_time=timedelta(hours=8), # 主要プロジェクトの稼働時間
break_time=timedelta(hours=1), # 休憩時間 (計算される場合も)
work_time=timedelta(hours=8), # 実働時間 (CSVの値)
comment="" # コメント
),
InternalDailyRecord(
date=date(2024, 7, 2),
registered_start_time=time(9, 0),
registered_end_time=time(18, 30),
start_time=time(9, 2),
end_time=time(18, 35),
main_pj_work_time=timedelta(hours=8, minutes=30),
break_time=timedelta(hours=1),
work_time=timedelta(hours=8, minutes=30),
comment="残業対応"
),
# ... 他の日付の InternalDailyRecord が続く ...
]
)
# 実際には、ファイルごとに複数の InternalTimesheet オブジェクトがリストとして返されるCSVファイルの各行の情報が、プログラムで扱いやすい date,
time, timedelta といった型を持つ
InternalDailyRecord に変換され、それらが
InternalTimesheet にまとめられているのが分かりますね。
InternalTimesheetReader の read
メソッドが呼び出されると、内部では以下のようなステップで処理が進みます。
chardetというライブラリを使います)pandasというデータ分析ライブラリを使って、CSVデータを効率的に扱える表形式(DataFrame)に変換します。"日付",
"出勤時刻"
など)のデータを取り出します。この際、文字列データを日付型
(date) や時刻型 (time)、時間間隔型
(timedelta) に変換します。InternalDailyRecord
オブジェクトを一日分ずつ作成します。InternalDailyRecord
のリストと、社員名や対象期間などの情報を合わせて、最終的な
InternalTimesheet
オブジェクトを作成します。ファイルによっては複数の社員が含まれる可能性があるため、通常は
InternalTimesheet のリストを返します。InternalTimesheet
オブジェクト(のリスト)を呼び出し元(通常は
FileSelector)に返します。この流れをシーケンス図で見てみましょう。
sequenceDiagram
participant FS as ファイル選択 (FileSelector)
participant ITR as 社内勤務表リーダー (InternalTimesheetReader)
participant Lib as 外部ライブラリ (Pandas, Chardet)
participant File as CSVファイル
participant ITS as InternalTimesheet
FS->>ITR: read(ファイルパス) を呼び出す
ITR->>File: ファイル存在確認
File-->>ITR: 存在する
ITR->>Lib: 文字コード検出を依頼 (chardet)
Lib-->>ITR: 文字コード (例: cp932)
ITR->>Lib: CSV読み込みを依頼 (pandas.read_csv)
Lib->>File: ファイル内容を読み込む
File-->>Lib: CSVデータ
Lib-->>ITR: DataFrame (表データ)
ITR->>ITR: 必要なデータを抽出・整形
loop 各行のデータごと
ITR->>ITR: InternalDailyRecord を作成
end
ITR->>ITS: InternalTimesheet オブジェクトを作成
ITS-->>ITR: 作成された InternalTimesheet (リスト)
ITR-->>FS: InternalTimesheet (リスト) を返す
このように、InternalTimesheetReader
はライブラリ(PandasやChardet)の助けを借りながら、CSVファイルを解析し、最終的に
InternalTimesheet
という標準化された形式に変換しているのです。
InternalTimesheetReader の実装は
src/core/internal_timesheet_reader.py
にあります。主要な部分を見てみましょう。
__init__)リーダーが作成されるときに、必要な設定を行います。ここでは、CSVファイルに最低限含まれていてほしい列の名前を定義しています。
# src/core/internal_timesheet_reader.py より (簡略化)
class InternalTimesheetReader:
"""内部タイムシートリーダークラス"""
def __init__(self):
"""内部タイムシートリーダーを初期化します。"""
# CSVファイルに必須の列名をリストで定義
self.required_columns = [
'"社員番号"',
'"社員名"',
# ... 他の必須列名 ...
'"プロジェクト時間"',
]
# 他にも初期設定があればここで行うこれにより、後でCSVファイルを読み込んだ際に、これらの必須列が存在するかどうかをチェックできます。
read メソッド)これがメインの処理を行うメソッドです。
# src/core/internal_timesheet_reader.py より (簡略化)
import pandas as pd # データ操作ライブラリ
import chardet # 文字コード検出ライブラリ
from pathlib import Path
from ..models.internal_timesheet import InternalTimesheet
class InternalTimesheetReader:
# ... (__init__ は省略) ...
def read(self, file_path: Path | str) -> List[InternalTimesheet]:
"""CSVファイルを読み取り、InternalTimesheetオブジェクトのリストを返します。"""
file_path = Path(file_path) # ファイルパスを確実にPathオブジェクトにする
if not file_path.exists():
raise FileNotFoundError(f"ファイルが見つかりません: {file_path}")
try:
# 1. 文字コードを検出
encoding = self._detect_encoding(file_path)
if encoding is None:
encoding = 'cp932' # デフォルトとしてcp932 (Shift_JIS) を試すことも
# 2. Pandas を使って CSV を読み込む
# (実際のコードでは、コメント行などを考慮してより複雑な読み込み方をしています)
df = pd.read_csv(file_path, encoding=encoding)
# 3. 必須列の存在チェック
self._validate_columns(df)
# 4. DataFrame から InternalTimesheet のリストを作成
# (この処理は _create_internal_data_sheets メソッドに分離されている)
internal_sheets = self._create_internal_data_sheets(df)
return internal_sheets
except Exception as e:
# エラーが発生したら、ログに記録して再発生させる
logger.error(f"CSVの読み取りまたは解析に失敗しました: {e}")
raise IOError(f"CSVの読み取りに失敗しました: {str(e)}") from e
def _detect_encoding(self, file_path):
"""ファイルの文字コードを検出します。"""
with open(file_path, "rb") as f:
result = chardet.detect(f.read(10000)) # 先頭10000バイトで判断
return result["encoding"]
def _validate_columns(self, df):
"""DataFrameに必要な列が存在するかチェックします。"""
for col in self.required_columns:
if col not in df.columns:
raise ValueError(f"必須列が不足しています: {col}")
def _create_internal_data_sheets(self, df: pd.DataFrame) -> List[InternalTimesheet]:
"""DataFrameからInternalTimesheetオブジェクトのリストを作成します。"""
# このメソッドの中で、DataFrameの各行をループし、
# 日付、時刻などを抽出し、InternalDailyRecordを作成し、
# 社員ごとに InternalTimesheet にまとめます。
# (詳細な実装は省略)
sheets = []
# ... DataFrame を処理して sheets に InternalTimesheet を追加 ...
logger.info(f"{len(sheets)} 件の社内勤務表データを読み込みました。")
return sheetsread メソッドの流れは以下の通りです。
_detect_encoding
でファイルの文字コードを調べます。pandas の
read_csv 関数を使って、CSVファイルの内容を表形式のデータ
(DataFrame)
として読み込みます。このとき、検出した文字コードを指定します。_validate_columns
で、__init__ で定義した必須列が DataFrame
にちゃんと存在するかを確認します。なければエラーになります。_create_internal_data_sheets
という別のメソッド(実際のデータ変換ロジックはこの中にあります)を呼び出して、DataFrame
から InternalTimesheet
オブジェクトのリストを作成します。try...except)、エラーを記録して処理を中断します。_create_internal_data_sheets
の中では、DataFrame
の一行一行を処理し、日付や時刻の文字列を date 型や
time
型に変換したり、必要な計算(例えば休憩時間の算出など)を行ったりしながら、InternalDailyRecord
を作成し、最終的に社員ごとに InternalTimesheet
としてまとめていきます。この部分が、まさにCSVの生データをプログラムが理解できる形に変換する核心部となります。
この章では、社内用のCSV形式の勤務表ファイルを読み込む専門家、社内勤務表リーダー (InternalTimesheetReader) について学びました。
InternalTimesheetReader
は、特定の社内CSV勤務表フォーマットを読み込むための部品です。InternalTimesheet
オブジェクト(のリスト)に変換することです。pandas
ライブラリを使った効率的なCSV読み込み、データ型の変換などが行われています。これで、ファイル選択 (FileSelector) で選ばれた社内勤務表ファイルが、どのようにしてプログラム内部で扱えるデータ形式になるのかが理解できたはずです。
しかし、比較するためにはもう一つ、客先の勤務表データも必要ですよね。客先の勤務表はPDFやExcelなど、様々な形式がありえます。次の章では、これらの客先勤務表ファイルを読み込むためのリーダーについて見ていきましょう。
次の章: 第6章: 客先勤務表リーダー (ClientTimesheetReader)
Generated by AI Codebase Knowledge Builder