ファイルレイアウト定義書の位置づけ

バッチ連携・レガシーシステム連携では、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サンプルデータ(正常・異常)が記載されているか