ドキュメント / 詳細設計 / インターフェース系
•
PART 02 / 02
•
2026.06.18
•
20 min read
PART 02 — ファイルレイアウト定義書
CSV・固定長・XML ファイル連携の完全仕様
ファイル連携はAPIと異なりエラー検出が遅れやすい。レイアウト定義書で仕様を明確化し、受信側・送信側双方のバリデーションを実装することが品質確保の鍵となる。
ファイルレイアウト定義書の位置づけ
バッチ連携・レガシーシステム連携では、REST API でなくファイル(CSV・固定長・XML)でデータをやり取りするケースが多い。ファイルレイアウト定義書は、ファイルの「設計図」として送受信双方が同じ形式でデータを作成・解釈するための合意文書だ。
💡 固定長ファイルの注意点
固定長ファイルは古いシステムとの連携で今でも使われる。全角文字はバイト数(3バイト)を考慮した桁数定義が必要だ。また左詰め・右詰め・ゼロパディングのルールを明記しないと、取り込み時に値がずれる。
ファイル一覧
| ファイルID | ファイル名 | 形式 | 方向 | タイミング |
| FL-001 | 売上データ送信ファイル | CSV(ヘッダーあり) | 送信(基幹システムへ) | 日次 02:00 |
| FL-002 | 商品マスタ受信ファイル | 固定長 | 受信(基幹システムから) | 週次 月曜 01:00 |
| FL-003 | 在庫調整ファイル | CSV(ヘッダーなし) | 双方向 | 都度 |
定義すべき項目一覧
| 分類 | 定義項目 | 必須度 |
| ファイル基本 | ファイル名規則(固定名 or 日付含む) | ◎ |
| ファイル基本 | 文字コード・改行コード・区切り文字 | ◎ |
| ファイル基本 | ヘッダーレコードの有無・内容 | ◎ |
| ファイル基本 | トレーラーレコードの有無・内容 | ○ |
| レコード | 各カラムの順序・論理名・物理名 | ◎ |
| レコード | データ型・桁数・必須/任意 | ◎ |
| レコード | フォーマット(日付形式・数値形式) | ◎ |
| 転送 | 転送プロトコル(SFTP/FTP/S3) | ◎ |
| 転送 | 格納ディレクトリ・ファイル完了通知方法 | ◎ |
| エラー | 不正データの扱い(スキップ/全件エラー) | ◎ |
CSVレイアウト定義
FL-001 売上データ送信ファイル仕様
■ ファイル基本仕様
ファイル名 : SALES_YYYYMMDD.csv(例: SALES_20260618.csv)
文字コード : UTF-8(BOMなし)
改行コード : LF
区切り文字 : カンマ(,)
囲み文字 : ダブルクォーテーション(")フィールドに , や改行を含む場合のみ
ヘッダー : あり(1行目)
トレーラー : あり(最終行、"TOTAL",件数,合計金額)
■ レコード定義(データ行)
No | ヘッダー名 | 論理名 | 型 | 桁数 | 必須 | フォーマット | 備考
---|--------------|-----------|--------|------|------|-----------------|------------------
1 | order_id | 注文ID | 数値 | 19 | ○ | 整数 |
2 | order_date | 注文日 | 文字列 | 10 | ○ | YYYY-MM-DD | UTCではなくJST日付
3 | product_code | 商品コード | 文字列 | 20 | ○ | 英数字+ハイフン |
4 | quantity | 数量 | 数値 | 5 | ○ | 整数 1以上 |
5 | unit_price | 単価 | 数値 | 12 | ○ | 整数(円)税抜き |
6 | total_amount | 合計金額 | 数値 | 12 | ○ | 整数(円)税込み |
7 | customer_name| 顧客名 | 文字列 | 100 | ○ | 自由文字列 | 全角可
■ サンプルデータ
order_id,order_date,product_code,quantity,unit_price,total_amount,customer_name
12345,2026-06-18,PROD-001,2,5000,11000,山田太郎
12346,2026-06-18,PROD-002,1,3000,3300,田中花子
TOTAL,2,14300
固定長レイアウト定義
FL-002 商品マスタ受信ファイル(固定長)仕様
■ ファイル基本仕様
ファイル名 : PRODUCT_MASTER.dat
文字コード : Shift_JIS
改行コード : CRLF
レコード長 : 200バイト固定
■ レコード定義
開始バイト | バイト数 | 論理名 | 型 | 詰め | 説明
----------|---------|-----------|--------|------|----------------------------------
1 | 10 | 商品コード | 英数字 | 右空白 | 半角英数字+ハイフン
11 | 60 | 商品名 | 文字列 | 右空白 | 全角文字は2バイトカウント
71 | 10 | 単価 | 数値 | 左ゼロ | 整数(円)
81 | 5 | 在庫数 | 数値 | 左ゼロ | 整数
86 | 1 | 販売フラグ | コード | — | "1"=販売中 "0"=販売停止
87 | 8 | 更新日 | 日付 | — | YYYYMMDD 形式
95 | 106 | 予備 | 空白 | 空白 | 将来拡張用
■ サンプルデータ(16進で確認のこと)
PROD-001 山田商事特製 高品質ノート 0000050000010020260618
ファイル転送仕様
| 項目 | 内容 |
| 転送プロトコル | SFTP |
| 接続先ホスト | sftp.legacy.example.com:22 |
| 認証方式 | 公開鍵認証(RSA 4096bit) |
| 送信ディレクトリ | /upload/sales/ |
| 受信ディレクトリ | /download/master/ |
| ファイル完了通知 | 同名の .done ファイルを配置後、本体ファイルを処理する |
| 転送後の処理 | 受信側は処理完了後に /processed/ ディレクトリに移動 |
Python Tips — ファイルバリデーションの自動化
Python — CSV ファイルバリデーションスクリプト
"""
連携 CSV ファイルのレイアウト・データバリデーション
"""
import csv, re
from pathlib import Path
from datetime import datetime
LAYOUT = [
{"name": "order_id", "required": True, "type": "int", "max_len": 19},
{"name": "order_date", "required": True, "type": "date", "format": "%Y-%m-%d"},
{"name": "product_code", "required": True, "type": "str", "pattern": r"^[A-Z0-9\-]{1,20}$"},
{"name": "quantity", "required": True, "type": "int", "min": 1},
{"name": "unit_price", "required": True, "type": "int", "min": 0},
{"name": "total_amount", "required": True, "type": "int", "min": 0},
{"name": "customer_name","required": True, "type": "str", "max_len": 100},
]
def validate_value(val: str, rule: dict) -> str | None:
if rule["required"] and not val.strip():
return f"必須項目が空白"
if not val.strip():
return None
if rule["type"] == "int":
if not val.isdigit():
return "整数でない"
v = int(val)
if "min" in rule and v < rule["min"]:
return f"{rule['min']}以上が必要"
if "max_len" in rule and len(val) > rule["max_len"]:
return f"{rule['max_len']}桁超過"
elif rule["type"] == "date":
try:
datetime.strptime(val, rule["format"])
except ValueError:
return f"日付フォーマット {rule['format']} 不正"
elif rule["type"] == "str":
if "max_len" in rule and len(val.encode("utf-8")) > rule["max_len"] * 3:
return f"文字列長超過"
if "pattern" in rule and not re.match(rule["pattern"], val):
return f"フォーマット不正: {rule['pattern']}"
return None
def validate_csv(filepath: str) -> list[dict]:
errors = []
with open(filepath, encoding="utf-8") as f:
reader = csv.DictReader(f)
for row_num, row in enumerate(reader, start=2):
if list(row.keys())[0] == "TOTAL":
continue
for rule in LAYOUT:
val = row.get(rule["name"], "")
err = validate_value(val, rule)
if err:
errors.append({"row": row_num, "field": rule["name"], "error": err, "value": val})
return errors
errors = validate_csv("SALES_20260618.csv")
if errors:
print(f"❌ バリデーションエラー {len(errors)} 件:")
for e in errors:
print(f" 行{e['row']:4d} [{e['field']}] {e['error']} (値: '{e['value']}')")
else:
print("✅ バリデーション OK")
レビューチェックリスト
| # | チェック項目 |
| 1 | 文字コード・改行コードが送受信双方で合意されているか |
| 2 | 固定長の場合、全角文字のバイト数カウントが定義されているか |
| 3 | ファイル名規則(日付フォーマット等)が定義されているか |
| 4 | ファイル完了通知方法が定義されているか |
| 5 | 不正データの扱い方針が定義されているか |
| 6 | サンプルデータ(正常・異常)が記載されているか |