import pymysql
import pandas as pd
from datetime import datetime 
import re
from conversion_map import conversion_map
from excluded_columns import excluded_columns
from toshima_excluded_columns import toshima_excluded_columns
from schema_mismatch_columns import schema_mismatch_columns
# 疾患コード変換処理
from disease_code_processor import DiseaseCodeProcessor
from pymysql.err import IntegrityError

import unicodedata
import random

def process_decimal_round(value, decimal_places=0):
    """
    小数点データを指定された桁数で四捨五入する
    
    Args:
        value: 変換対象の値
        decimal_places: 小数点以下の桁数
    
    Returns:
        四捨五入された値またはNone（無効な値の場合）
    """
    if value is None or str(value).strip() == '' or str(value).lower() in ['nan', 'none', 'null']:
        return None
    
    try:
        # 文字列から数値に変換
        numeric_value = float(str(value).strip())
        
        # 四捨五入を実行
        rounded_value = round(numeric_value, decimal_places)
        # 整数の場合はintとして返す
        if decimal_places == 0:
            return int(rounded_value)
        else:
            return rounded_value
            
    except (ValueError, TypeError):
        # 数値に変換できない場合はNoneを返す
        return None

def random_mmss() -> str:
    """MMSSだけをランダム生成（00:00〜59:59）。"""
    n = random.randrange(3600)   # 0..3599
    mm, ss = divmod(n, 60)       # 0..59, 0..59
    return f"{mm:02}{ss:02}"

def canon(s):
    """<br>の有無や全角空白・改行などの揺れを吸収して正規化"""
    if s is None:
        return None
    s = str(s)
    s = s.replace('\u3000', ' ')                 # 全角空白→半角
    s = re.sub(r'<br\s*/?>', '', s, flags=re.I)  # <br>, <br/>, <br /> を除去
    s = re.sub(r'\s+', '', s)                    # すべての空白類を削除
    return s

def format_date_value(value, fmt="%Y%m%d"):
    """
    様々な表記の日付や Excel シリアルを pandas でパースし fmt で整形
    失敗時は None を返す
    """
    if value is None:
        return None

    # スカラー化
    try:
        # numpy型/Series/リスト等が来ても最初の要素を試す
        if hasattr(value, "__array__"):
            value = value.item()
        elif isinstance(value, (list, tuple)) and value:
            value = value[0]
    except Exception:
        pass

    # 既に Timestamp の場合
    if isinstance(value, pd.Timestamp):
        return value.strftime(fmt)

    s = str(value).strip()
    if s == "" or s.lower() in ("nan", "none"):
        return None

    # 正規化（全角→半角）
    s = unicodedata.normalize("NFKC", s)

    # 「YYYY年M月D日」→「YYYY/M/D」
    s = s.replace("年", "/").replace("月", "/").replace("日", "")

    # Excel シリアル（整数/小数）を推定
    # 10〜60000日くらいを妥当域として判定（必要に応じて調整）
    try:
        if re.fullmatch(r"-?\d+(\.\d+)?", s):
            f = float(s)
            if 10 <= f <= 60000:
                dt = pd.to_datetime(f, unit="D", origin="1899-12-30", errors="coerce")
                return None if pd.isna(dt) else dt.strftime(fmt)
    except Exception:
        pass

    # 文字列パース（例: 2024/7/1, 2024-07-01 など）
    dt = pd.to_datetime(s, errors="coerce", dayfirst=False, yearfirst=True)
    if pd.isna(dt):
        return None
    return dt.strftime(fmt)

import re
import pandas as pd

# =========================
# 追跡表記の正規化・順位づけ
# =========================

# 正規表現（年の数値 / 3ヶ月の表記ゆらぎ）
_YEAR_RE   = re.compile(r'(\d+)\s*年')           # 例: "10年後" → 10
_MONTH3_RE = re.compile(r'3\s*[ヶヵケか]?\s*月') # 例: "3ヶ月後", "3ケ月後", "3ヵ月後" etc.

def _norm(s) -> str:
    """内部用: 文字列を正規化（None/NaN→空文字、空白除去）"""
    if s is None or (isinstance(s, float) and pd.isna(s)):
        return ""
    return str(s).replace(" ", "").replace("\u3000", "")

def followup_rank(label: str) -> int:
    """
    追跡ラベルを数値ランクに変換。
    - '51年後'→51（上限を 51 にキャップ）
    - '1年後' →1
    - '3ヶ月後'→0（最後尾）
    - 不明     →-1（最後尾）
    """
    t = _norm(label)
    m = _YEAR_RE.search(t)
    if m:
        return min(int(m.group(1)), 51)
    if _MONTH3_RE.search(t):
        return 0
    return -1

# =========================
# DataFrame ベースの関数
# =========================

def sort_df_by_followup_desc(df: pd.DataFrame,
                             followup_col: str) -> pd.DataFrame:
    """
    DF を「51年→…→1年→3ヶ月」の降順に安定ソート。
    followup_col が存在しなければ、わかりやすい KeyError を投げます。
    """
    if followup_col not in df.columns:
        raise KeyError(f"followup_col '{followup_col}' not in columns: {list(df.columns)}")
    tmp = df.assign(__rank=df[followup_col].map(followup_rank))    # 追跡ラベル→ランク
    tmp = tmp.sort_values("__rank", ascending=False, kind="stable") # 降順かつ安定ソート
    return tmp.drop(columns="__rank")

def first_nonempty_value_by_followup(df: pd.DataFrame,
                                     value_col: str,
                                     followup_col: str):
    """
    指定 value_col について、追跡順（51年→…→3ヶ月）で“最初に非空な値”を返す。
    すべて欠損/空なら None。
    """
    if value_col not in df.columns:
        raise KeyError(f"value_col '{value_col}' not in columns: {list(df.columns)}")
    srt = sort_df_by_followup_desc(df, followup_col)
    for v in srt[value_col]:
        if pd.notna(v) and str(v).strip() != "":
            return v
    return None

# =========================
# iterrows() 行リスト（[(idx, Series), ...]）用の関数
# =========================

def sort_rowtuples_by_followup(rows, followup_col: str):
    """
    iterrows() 由来の rows（[(idx, Series), ...]）を
    追跡順（51年→…→3ヶ月）に降順並べ替えして返す。
    """
    return sorted(
        rows,
        key=lambda t: followup_rank(t[1].get(followup_col, "")),
        reverse=True,
    )

def first_nonempty_value_from_rows(rows, value_col: str):
    """
    並び済み rows から、value_col の“最初に非空な値”を返す。なければ None。
    """
    for _idx, row in rows:
        if value_col in row:
            v = row[value_col]
            if pd.notna(v) and str(v).strip() != "":
                return v
    return None

def first_nonempty_value_by_rows(rows, value_col: str, followup_col: str):
    """
    未ソートの rows を追跡順に並べ替えてから、value_col の最初の非空値を返す。
    """
    return first_nonempty_value_from_rows(
        sort_rowtuples_by_followup(rows, followup_col),
        value_col
    )


def to_cycle_str(tsuiseki: str) -> str:
    """
    '3ヶ月後' → '01', '1年後' → '02', '2年後' → '03', ..., '50年後' → '51'
    """
    tsuiseki = tsuiseki.strip()
    #if tsuiseki == "3ヶ月後":
    #    return "01"
    if "年後" in tsuiseki:
        try:
            year = int(tsuiseki.replace("年後", "").strip())
            return f"{year:02}"  # +1 することで3ヶ月後→01, 1年後→02, 2年後→03になる
        except:
            return "99"
    return "99"  # 未知の値

def get_kensa_metadata(column_name):
    mapping = {
        # ドナー用
        'T.Bil[検査値と合併症・再手術・再入院]': ('T.Bil', 'mg/dl', 'float', 1.2),
        'D.Bil[検査値と合併症・再手術・再入院]': ('D.Bil', 'mg/dl', 'float', 0.4),
        'AST[検査値と合併症・再手術・再入院]':   ('AST', 'U/L', 'int', 35),
        'ALT[検査値と合併症・再手術・再入院]':   ('ALT', 'U/L', 'int', 35),
        'PT[検査値と合併症・再手術・再入院]':   ('PT-INR', None, 'int', 1.1),
        'ワーファリンの使用[検査値と合併症・再手術・再入院]': ('ワーファリンの使用', None, 'str', '0'),
        'INR[検査値と合併症・再手術・再入院]':   ('PT-INR', None, 'float', 1.1),
        '疾患の有無[検査値と合併症・再手術・再入院]': ('疾患の有無', None, 'str', '0'),

        # レシピエント用（追跡時）
        'T.Bil[追跡時の検査値]': ('T.Bil', 'mg/dl', 'float', 1.2),
        'D.Bil[追跡時の検査値]': ('D.Bil', 'mg/dl', 'float', 0.4),
        'AST[追跡時の検査値]':   ('AST', 'U/L', 'int', 35),
        'ALT[追跡時の検査値]':   ('ALT', 'U/L', 'int', 35),
        'INR[追跡時の検査値]':   ('PT-INR', None, 'float', 1.1),
        'ワーファリンの使用[追跡時の検査値]': ('ワーファリンの使用', None, 'str', '0'),
    }
    return mapping.get(column_name, (None, None, None, None))

def evaluate_kensa_code(value, dtype, threshold):
    if value in [None, '']:
        return 3
    try:
        if dtype == 'float':
            return 2 if float(value) >= threshold else 1
        elif dtype == 'int':
            return 2 if int(value) >= threshold else 1
        elif dtype == 'str':
            # "無"なら2, "有"なら1, それ以外は3
            if str(value).strip() == '無':
                return 0
            elif str(value).strip() == '有':
                return 1
            else:
                return threshold
    except (ValueError, TypeError):
        return 3



def get_sisetu_cd(facility_name):
    # 文字列化＆空/NaN処理
    if pd.isna(facility_name):
        return "709999"
    name = str(facility_name).strip()
    if name == "":
        return "709999"

    # 完全一致のみ
    match = institution_df.loc[institution_df["Liter-j 施設名"] == name]
    if not match.empty:
        return match.iloc[0]["施設コード"]

    # 見つからなかった場合だけログ
    return "709999"

def reset_tables(connection, cursor):
    # 削除順序（親を最後に）
    ordered_tables = [
        "T_ISHOKU_KIHON_LIVER_LIV",
        "T_GAPPEI_D_LIV",
        "T_KENSA_D_LIV",
        "T_IJI_MENEKI_YOKUSEI_R_LIV",
        "T_REJECTION_R_LIV",
        "T_KENSA_R_LIV",
        "T_GAPPEI_R_LIV",
        "T_LIVING_R_LIV",
        "T_DONOR_LIVER_LIV",
        "T_LIVING_D_LIV",
        "T_DONOR_LIV",
        "T_NYURYOKUJOKYO_LIV",
        "T_ISHOKU_KIHON_LIV",
        "T_TRACER_IKO"  # ← 最後に削除
    ]

    BATCH_SIZE = 1000

    # 削除対象 ID を基点テーブルから取得
    cursor.execute("SELECT SEITAI_ISYOKU_ID FROM T_TRACER_IKO")
    all_target_ids = [row['SEITAI_ISYOKU_ID'] for row in cursor.fetchall()]

    if not all_target_ids:
        print("削除対象なし")
        return

    print("✅ 削除開始")

    for table in ordered_tables:
        print(f"削除中のテーブル：{table}")

        offset = 0
        while offset < len(all_target_ids):
            batch_ids = all_target_ids[offset:offset+BATCH_SIZE]
            if not batch_ids:
                break

            id_placeholders = ",".join(["%s"] * len(batch_ids))

            # 特殊ケース: DONOR_A_ID 経由
            if table in ["T_GAPPEI_D_LIV", "T_KENSA_D_LIV", "T_LIVING_D_LIV"]:
                sql = f"""
                    DELETE FROM {table}
                    WHERE DONOR_A_ID IN (
                        SELECT tmp.DONOR_A_ID
                        FROM (
                            SELECT T_DONOR_LIV.DONOR_A_ID
                            FROM T_ISHOKU_KIHON_LIV
                            INNER JOIN T_DONOR_LIV
                              ON T_ISHOKU_KIHON_LIV.SEITAI_ISYOKU_ID = T_DONOR_LIV.SEITAI_ISYOKU_ID
                            WHERE T_ISHOKU_KIHON_LIV.SEITAI_ISYOKU_ID IN ({id_placeholders})
                            LIMIT %s
                        ) AS tmp
                    )
                """
                params = batch_ids + [BATCH_SIZE]

            # 子テーブル群: SEITAI_ISYOKU_ID あり
            elif table in [
                "T_KENSA_R_LIV", "T_IJI_MENEKI_YOKUSEI_R_LIV",
                "T_REJECTION_R_LIV", "T_GAPPEI_R_LIV",
                "T_LIVING_R_LIV", "T_DONOR_LIVER_LIV",
                "T_DONOR_LIV", "T_NYURYOKUJOKYO_LIV"
            ]:
                sql = f"""
                    DELETE FROM {table}
                    WHERE SEITAI_ISYOKU_ID IN (
                        SELECT tmp.SEITAI_ISYOKU_ID
                        FROM (
                            SELECT SEITAI_ISYOKU_ID
                            FROM T_ISHOKU_KIHON_LIV
                            WHERE SEITAI_ISYOKU_ID IN ({id_placeholders})
                            LIMIT %s
                        ) AS tmp
                    )
                """
                params = batch_ids + [BATCH_SIZE]

            # 親テーブル群
            else:
                sql = f"""
                    DELETE FROM {table}
                    WHERE SEITAI_ISYOKU_ID IN ({id_placeholders})
                """
                params = batch_ids

            cursor.execute(sql, params)
            deleted_count = cursor.rowcount
            connection.commit()

            if deleted_count > 0:
                print(f"{deleted_count} 件削除（{table}）")

            offset += BATCH_SIZE

        # --- 削除確認 ---
        if table in ["T_GAPPEI_D_LIV", "T_KENSA_D_LIV", "T_LIVING_D_LIV"]:
            sql = f"""
                SELECT COUNT(*) AS cnt
                FROM {table} t
                WHERE EXISTS (
                    SELECT 1
                    FROM T_ISHOKU_KIHON_LIV kihon
                    INNER JOIN T_DONOR_LIV donor ON kihon.SEITAI_ISYOKU_ID = donor.SEITAI_ISYOKU_ID
                    INNER JOIN T_TRACER_IKO iko ON iko.SEITAI_ISYOKU_ID = kihon.SEITAI_ISYOKU_ID
                    WHERE donor.DONOR_A_ID = t.DONOR_A_ID
                )
            """
        else:
            sql = f"""
                SELECT COUNT(*) AS cnt
                FROM {table} t
                WHERE EXISTS (
                    SELECT 1
                    FROM T_TRACER_IKO iko
                    WHERE iko.SEITAI_ISYOKU_ID = t.SEITAI_ISYOKU_ID
                )
            """

        cursor.execute(sql)
        remain = cursor.fetchone()['cnt']
        if remain > 0:
            raise Exception(f"❌ {table} に {remain} 件の未削除データあり。処理を中断します。")
        else:
            print(f"✅ {table} の削除確認完了（残件 0 件）")

    print("✅ 外部キー制約を考慮した安全なバッチ削除完了")



def apply_conversion_rules_to_row(table_name, mapping_row, all_row, recipient_rows, donor_rows, conversion_map, toshima_excluded_columns):
    result = []
    for source_col, config in conversion_map.items():
        source_col = source_col.strip()
        # 対象のテーブルでなければスキップ
        if config.get("target_table") != table_name:
            continue
        toshima_excluded_columns = [col.strip() for col in toshima_excluded_columns]
        # 戸嶋さんに相談しないといけないカラムはスキップ
        #if source_col.strip() in toshima_excluded_columns:
            #print(f"{source_col}############################ 戸嶋さんのところとっているよね？")
            #continue
        # 違う箇所で変換処理をしているので、変換処理をする必要がないカラムのため、処理をスキップする
        # 全部完了後に一応、戸嶋さんに確認する
        #if source_col.strip() in [
        #    "倫理的問題(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]", 
        #    "倫理的問題-その他（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]",
        #    "倫理的問題-血縁関係（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]"
        #]:
        #    print(f"{source_col}####### スキップ対象カラム（倫理的問題）")
        #    continue
        
        #mapping_row["アルトマーク　項目名"] = mapping_row["アルトマーク　項目名"].astype(str).str.strip()
        #matching_rows = mapping_row[mapping_row["アルトマーク　項目名"] == source_col.strip()]

        matching_rows = mapping_row[mapping_row["アルトマーク　項目名"] == source_col.strip()]
        if not matching_rows.empty:
            target_column = matching_rows.iloc[0]['項目']
            if target_column == '項目名（移植時）':
                if source_col.strip() not in all_row:
                    if source_col.strip() == '抗体製剤-詳細(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]':
                        print(f"だめよだめだめ {source_col} {raw_value}")
                    if source_col.strip() == '保存液(D)[保存（ドナー）]':
                        print(f"保存液(D)[保存（ドナー）]だめよだめだめ {source_col} {raw_value}")
                    continue
                raw_value = all_row[source_col.strip()]
                print(f"all-data:{source_col.strip()}, {raw_value}")

            elif target_column == '項目名（レシピエントフォロー）' and recipient_rows:
                raw_value = first_nonempty_value_from_rows(
                    recipient_rows,
                    source_col.strip()
                )
                if raw_value is None:
                    continue
                #col_index = [col.strip() for col in recipient_rows[0][1].index]
                #if source_col.strip() in col_index:
                    ## 最初の1件を取得している。空でも空でなくても
                #    raw_value = recipient_rows[0][1][source_col.strip()]
                #else:
                #    continue
            elif target_column == '項目名（ドナーフォロー）' and donor_rows:
                # 追跡順に並べ替え→source_col の最初に非空の値を取得
                raw_value = first_nonempty_value_from_rows(
                    donor_rows,
                    source_col.strip()
                )
                if raw_value is None:
                    continue
                #col_index = [col.strip() for col in donor_rows[0][1].index]
                #if source_col.strip() in col_index:
                #    ## 最初の1件を取得している。空でも空でなくても
                #    raw_value = donor_rows[0][1][source_col.strip()]
                #else:
                #    continue

        conv_type = config.get("type")

        if conv_type == "skip":
            continue

        if 'raw_value' not in locals():
            #print(f"☑ UPDATEの条件に当てはまらないケースがある{target_column} {source_col} ")
            continue
        if conv_type == "multi_flag":
            for keyword, target_col in config["rules"].items():
                value = 1 if keyword in str(raw_value) else 0
                result.append((target_col, value, source_col))


        elif conv_type == "multi_flag_split":
            # 候補を収集（all_row → recipient → donor）
            candidate_values = [str(raw_value).strip()] if raw_value else []

            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    df = row[1] if isinstance(row, tuple) else row
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    if source_col in df.columns:
                        val = str(df[source_col].values[0]).strip()
                        if val:
                            candidate_values.append(val)

            # 最後の非空値を採用し、カンマで分割
            final_value = next((v for v in reversed(candidate_values) if v), "")
            parts = [p.strip() for p in final_value.split(',') if p.strip()]

            # 各キーワードが含まれているか判定
            for keyword, target_col in config["rules"].items():
                value = 1 if keyword in parts else 0
                result.append((target_col, value, source_col))

        elif conv_type == "single_map":
            # 変換ルールを取得（文字列 → 数値のマッピング）
            rules = config["rules"]
            # 最初に all_row（メインのCSV）の値を候補に入れる（存在しない場合は空リスト）
            candidate_values = [str(raw_value).strip()] if raw_value else []
            # recipient_rows（レシピエントの追跡データ）と donor_rows（ドナーの追跡データ）を順番に確認
            # 目的は「CSVと追跡データのすべてから最新の非空データを取得する」ため
            if len(candidate_values) == 0:
                for row_set in [recipient_rows, donor_rows]:
                    for row in row_set:
                        # rowがタプル（(key, DataFrame)）の場合、DataFrameを取り出す
                        df = row[1] if isinstance(row, tuple) else row

                        # Series（1行だけのデータ）の場合はDataFrameに変換
                        if isinstance(df, pd.Series):
                            df = df.to_frame().T

                        # 対象カラムがデータフレーム内に存在するかチェック
                        if source_col in df.columns:
                            # 対象カラムの値を取得し、文字列として扱う（stripで前後空白を除去）
                            val = str(df[source_col].values[0]).strip()
                            # 値が存在する場合、候補リストに追加
                            if val:
                                candidate_values.append(val)

            # 候補リストの中で、最後に見つかった非空データ（最新データ）を取得
            final_value = next((v for v in reversed(candidate_values) if v), "")

            # 変換ルールに完全一致する場合、そのマッピング値を取得
            if final_value in rules:
                mapped_value = rules[final_value]
            else:
                # 一致しない場合、default値を使用（なければNone）
                mapped_value = rules.get("default")

            ### 特別対応：片岡
            if source_col == "悪性腫瘍[レシピエントの状態]":
                print(f"悪性腫瘍[レシピエントの状態] {type(final_value)},{final_value}, {source_col}")

            if source_col == "腫瘍の個数(R)[肝細胞癌歴ありの場合（レシピエント）]":
                if final_value == "それ以上":
                    raw_value = all_row["腫瘍の個数-それ以上（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]"]
                    rules[final_value]
                    if raw_value == "4":
                        mapped_value = 4
                    if raw_value == "5":
                        mapped_value = 5
            if source_col == "single_map":
                result.append(("DONOR_TENKI_SIIN_CMNT", mapped_value, "死因-悪性新生物（テキスト）[現状]"))



            # 最終的な結果を出力リストに追加（カラム名、変換後の値、元のCSVカラム名）
            result.append((config["target_col"], mapped_value, source_col))


        elif conv_type == "date_format":
            formatted = format_date_value(raw_value, config.get("format", "%Y%m%d"))
            result.append((config.get("target_col", source_col.strip()), formatted, source_col))
            """
            # raw_value に加え、recipient_rows・donor_rows も確認
            candidate_values = [str(raw_value).strip()] if raw_value else []

            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    df = row[1] if isinstance(row, tuple) else row
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    if source_col in df.columns:
                        val = str(df[source_col].values[0]).strip()
                        if val:
                            candidate_values.append(val)

            # 最後に見つかった非空の値を使う
            final_value = next((v for v in reversed(candidate_values) if v), None)

            try:
                dt = pd.to_datetime(final_value, errors='coerce')
                formatted = dt.strftime(config.get("format", "%Y%m%d")) if not pd.isna(dt) else None
                result.append((config.get("target_col", source_col), formatted, source_col))
            except Exception as e:
                #print(f"❌ date_format error: {source_col} → {e}")
                result.append((config.get("target_col", source_col), None, source_col))
        """
        elif conv_type == "exist_flag":
            value_if_exists = config.get("value_if_exists", 1)
            value_if_not_exists = config.get("value_if_not_exists", 0)
            keywords = config.get("keywords")

            raw_str = str(raw_value).strip()

            if keywords:
                # キーワードのいずれかが含まれていれば存在と見なす
                exists = any(k in raw_str for k in keywords)
                value = value_if_exists if exists else value_if_not_exists
            else:
                value = value_if_exists if raw_str else value_if_not_exists

            result.append((config["target_col"], value, source_col))

        elif conv_type == 'decimal_round':
            decimal_places = config.get('decimal_places', 0)
            value = process_decimal_round(raw_value, decimal_places)
            if not value in (None, '', ' '):
                result.append((config["target_col"], value, source_col))

        elif conv_type == "pass_through":
            ## 特別対応
            #if ("保存液-その他（テキスト）(D)[保存（ドナー）]" == source_col):
                #print(f"保存液-その他（テキスト）(D)[保存（ドナー）] {raw_value}, {source_col}")

            if ("腫瘍の個数-それ以上（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]" == source_col):
                if raw_value == "4":
                    continue
                if raw_value == "5":
                    continue
            raw_value = raw_value.strip(" \t\u3000") if raw_value is not None else None
            if not raw_value in (None, '', ' '):
                result.append((config["target_col"], raw_value, source_col))

        elif conv_type == "split_dates_backfill":
            target_cols = config["target_cols"]
            max_split = config.get("max_split", len(target_cols))
            
            # 日付候補リスト（all_rowの値）
            candidate_dates = []

            # ① all_row から値を取得            
            if ("合併症による再手術-手術日(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]" == source_col):
                candidate_comments = []
                if raw_value:
                    parts = [d.strip() for d in str(raw_value).split(",") if d.strip()]
                    candidate_dates.extend(parts)


                target_raw_comments = all_row["合併症による再手術-術式(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]"]
                if target_raw_comments:
                    parts = [d.strip() for d in str(target_raw_comments).split(",") if d.strip()]
                    candidate_comments.extend(parts)

                # レシピエントの追跡データ
                for _idx, recipient_row in recipient_rows:
                    target_col1 = "合併症による再手術-手術日[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]"
                    if target_col1 in recipient_row:
                        v = recipient_row[target_col1]
                        parts = [d.strip() for d in str(v).split(",") if d.strip()]
                        candidate_dates.extend(parts)

                    target_col2 = "合併症による再手術-術式[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]"
                    if target_col2 in recipient_row:
                        v = recipient_row[target_col2]
                        parts = [d.strip() for d in str(v).split(",") if d.strip()]
                        candidate_comments.extend(parts)


                    # 日付とコメントをペアにする
                    pairs = list(zip(candidate_dates, candidate_comments))
                    print(f"ここが問題！！！！！！{candidate_dates} -> dates: {candidate_comments}, comments: {pairs}")

                    # pandasで日付に変換してソート（時刻付き/なし両対応）
                    pairs_sorted = sorted(pairs, key=lambda x: pd.to_datetime(x[0], errors="coerce"))

                    # 最新5件を取り出す
                    latest_5 = pairs_sorted[-5:]

                    # 再び分解
                    date_target_cols = [
                        "GAPPEI_SAISYUJUTU_DATE",
                        "GAPPEI_SAISYUJUTU_DATE2",
                        "GAPPEI_SAISYUJUTU_DATE3",
                        "GAPPEI_SAISYUJUTU_DATE4",
                        "GAPPEI_SAISYUJUTU_DATE5",
                    ]
                    text_target_cols = [
                        "GAPPEI_SAISYUJUTU_JUTUSHIKI",
                        "GAPPEI_SAISYUJUTU_JUTUSHIKI2",
                        "GAPPEI_SAISYUJUTU_JUTUSHIKI3",
                        "GAPPEI_SAISYUJUTU_JUTUSHIKI4",
                        "GAPPEI_SAISYUJUTU_JUTUSHIKI5",
                    ]
                    sorted_dates = [d for d, c in latest_5]
                    sorted_comments = [c for d, c in latest_5]
                    print(f"ここが問題！！！！！！{source_col} -> dates: {sorted_dates}, comments: {sorted_comments}")
 
                    for tcol, tval in zip(date_target_cols, sorted_dates):
                        print(f"@@@@@@@STEP1@@@@@@@@@@@@@@@@@@@@@{tcol} {tval.replace("-", "")}")
                        try:
                            dt = datetime.strptime(tval, "%Y-%m-%d %H:%M:%S")
                        except ValueError:
                            dt = datetime.strptime(tval, "%Y-%m-%d")
                        result.append((tcol, dt.strftime("%Y%m%d"), "合併症による再手術-手術日(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]"))
                    for tcol, tval in zip(text_target_cols, sorted_comments):
                        print(f"@@@@@@@STEP2@@@@@@@@@@@@@@@@@@@@@{tcol} {tval}")
                        result.append((tcol, tval, "合併症による再手術-術式(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]"))

                    for a, b,c in result:
                        print(f"@@@@ DATA @@@@@@@@@@@@@@@@@{a} {b} {c}")
            else:
                if raw_value:
                    parts = [d.strip() for d in str(raw_value).split(",") if d.strip()]
                    candidate_dates.extend(parts)

                    if len(candidate_dates) == 0:
                        # ② recipient_rows と donor_rows も確認
                        for row_set in [recipient_rows, donor_rows]:
                            for row in row_set:
                                df = row[1] if isinstance(row, tuple) else row
                                if isinstance(df, pd.Series):
                                    df = df.to_frame().T
                                if source_col in df.columns:
                                    val = str(df[source_col].values[0]).strip()
                                    if val:
                                        parts = [d.strip() for d in val.split(",") if d.strip()]
                                        candidate_dates.extend(parts)
                    
                    # 日付を datetime 型に変換して、nullは除外
                    parsed_dates = []
                    for d in candidate_dates:
                        try:
                            dt = pd.to_datetime(d, errors='coerce')
                            if not pd.isna(dt):
                                parsed_dates.append(dt)
                        except Exception:
                            continue  # 変換エラーは無視

                    # 最大5件取得
                    sorted_dates = sorted(parsed_dates, reverse=True)[:max_split]
                    use_text_pairing = False
                    text_items = []
                    if source_col == "再手術-手術日(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]":
                        text_raw_value = first_nonempty_value_from_rows(
                            recipient_rows,
                            "再手術-術式(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]"
                        )
                        if text_raw_value is not None and str(text_raw_value).strip():
                            text_items = [s.strip() for s in str(text_raw_value).split(",") if s.strip()]
                            use_text_pairing = True
                        text_target_cols = [
                            "GAPPEI_SAISYUJUTU_JUTUSHIKI",
                            "GAPPEI_SAISYUJUTU_JUTUSHIKI2",
                            "GAPPEI_SAISYUJUTU_JUTUSHIKI3",
                            "GAPPEI_SAISYUJUTU_JUTUSHIKI4",
                            "GAPPEI_SAISYUJUTU_JUTUSHIKI5",
                        ][:max_split]


                    if use_text_pairing:

                        # ---- 日付と術式をペアにしてソート ----
                        date_items = [d.strip() for d in candidate_dates if d and str(d).strip()]
                        if len(date_items) != len(text_items):
                            print(f"[warn] length mismatch: dates={len(date_items)} texts={len(text_items)}")
                        pairs = []
                        for d, t in zip(date_items, text_items):  # ずれがあれば短い方に自動で合わせる
                            dt = pd.to_datetime(d, errors="coerce")
                            if pd.isna(dt):
                                continue
                            pairs.append((dt, t))

                        # 日付の降順で最新5件
                        top_pairs = sorted(pairs, key=lambda x: x[0], reverse=True)[:max_split]
                        # 出力（yyyyMMdd）
                        formatted_dates = [dt.strftime("%Y%m%d") for dt, _ in top_pairs]
                        sorted_texts    = [t for _, t in top_pairs]  # 必要なら後続で格納

                    else:
                        # ---- 日付だけでソート ----
                        parsed_dates = []
                        for d in candidate_dates:
                            dt = pd.to_datetime(d, errors="coerce")
                            if not pd.isna(dt):
                                parsed_dates.append(dt)

                        sorted_dates = sorted(parsed_dates, reverse=True)[:max_split]
                        formatted_dates = [d.strftime("%Y%m%d") for d in sorted_dates]
                        # テキストは扱わない

                    # パディング＆result 追加（従来どおり）
                    padded_dates = formatted_dates + [None] * (len(target_cols) - len(formatted_dates))
                    for col, val in zip(target_cols, padded_dates):
                        result.append((col, val, source_col))
                    if use_text_pairing:
                        padded_texts = sorted_texts + [None] * (len(text_target_cols) - len(sorted_texts))
                        for tcol, tval in zip(text_target_cols, padded_texts):
                            result.append((tcol, tval, source_col))

                    """            
                    # yyyyMMdd 形式に変換
                    formatted_dates = [d.strftime("%Y%m%d") for d in sorted_dates]
                    print(f"max2{formatted_dates}")            
                    # 必要な数まで None を補完
                    padded_dates = formatted_dates + [None] * (len(target_cols) - len(formatted_dates))
                    print(f"max3{padded_dates}")            
                    # 出力リストに追加
                    for col, val in zip(target_cols, padded_dates):
                        result.append((col, val, source_col))
                    """

        elif conv_type == "split_text_backfill":
            # 最終的に収集するテキストリスト
            collected_parts = []

            # ① all_row の値を取得
            if raw_value:
                parts = [d.strip() for d in re.split(r"[、,]", str(raw_value)) if d.strip()]
                collected_parts.extend(parts)

            # ② recipient_rows と donor_rows を後ろ（最新）から確認
            if len(collected_parts) == 0:
                for row_set in [recipient_rows, donor_rows]:
                    # 最新データを優先するため reversed でループ
                    for row in reversed(row_set):
                        df = row[1] if isinstance(row, tuple) else row
                        if isinstance(df, pd.Series):
                            df = df.to_frame().T

                        if source_col in df.columns:
                            val = str(df[source_col].values[0]).strip()
                            if val:
                                parts = [d.strip() for d in re.split(r"[、,]", val) if d.strip()]
                                collected_parts.extend(parts)

            # 最大件数を設定
            max_split = config.get("max_split", len(config["target_cols"]))
            # 取得した全データから最新の max_split 件を取得（リストの後ろを優先）
            collected_parts = collected_parts[-max_split:] if collected_parts else []
            # 足りない分は None で埋める
            padded_parts = collected_parts + [None] * (len(config["target_cols"]) - len(collected_parts))

            # 結果を出力リストに追加
            result.extend([(col, val, source_col) for col, val in zip(config["target_cols"], padded_parts)])


        elif conv_type == "multi_output_map":
            value_str = str(raw_value).strip()
            is_null = value_str == "" or pd.isna(raw_value)

            for target_col, rule_map in config["rules"].items():
                result_value = None

                if is_null:
                    result_value = rule_map.get("default", None)
                elif value_str.isdigit():
                    num = int(value_str)
                    if str(num) in rule_map:
                        result_value = rule_map[str(num)]
                    elif num >= 1 and "1+" in rule_map:
                        result_value = rule_map["1+"]
                    else:
                        result_value = rule_map.get("default", None)
                else:
                    result_value = rule_map.get("default", None)

                if result_value == "value":
                    result_value = raw_value  # 元の値をそのまま使う

                result.append((target_col, result_value, source_col))
        elif conv_type == "aggregate_sum_flagged":
            value_col = config["value_col"]
            sum_target_col = config["sum_target_col"]
            flag_target_col = config["flag_target_col"]
            rules = config.get("rules", {})

            total_sum = 0

            # 処理対象セット（例: recipient_rows, donor_rows）
            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    # rowがtupleの場合（(key, DataFrame)）
                    if isinstance(row, tuple):
                        df = row[1]
                    else:
                        df = row  # rowがそのままDataFrameまたはSeries

                    # SeriesをDataFrameに変換
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T

                    if value_col in df.columns:
                        col_values = pd.to_numeric(df[value_col], errors='coerce').fillna(0)
                        total_sum += col_values.sum()

            result.append((sum_target_col, int(total_sum), source_col))

            if total_sum == 0:
                flag_value = rules.get("if_sum_equals_0", None)
            else:
                flag_value = rules.get("if_sum_greater_than_0", None)

            result.append((flag_target_col, flag_value, source_col))

        elif conv_type == "paired_split_backfill":
            paired_date_col = source_col.strip()
            paired_text_col = config.get("paired_text_col", "").strip()
            max_split = config.get("max_split", 5)

            combined_pairs = []

            # recipient_rows, donor_rows 両方からデータを取得
            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    # rowがtupleならDataFrameを取り出す
                    df = row[1] if isinstance(row, tuple) else row

                    # Series を DataFrame に変換
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    
                    if paired_date_col in df.columns:
                        date_values = str(df[paired_date_col].values[0])
                        text_values = str(df[paired_text_col].values[0]) if paired_text_col in df.columns else ""

                        date_list = [d.strip() for d in date_values.split(',') if d.strip()]
                        text_list = [t.strip() for t in text_values.split(',') if t.strip()]

                        date_objs = [pd.to_datetime(d, errors='coerce') for d in date_list]
                        valid_pairs = [(d, t) for d, t in zip(date_objs, text_list) if not pd.isna(d)]

                        combined_pairs.extend(valid_pairs)

            # 最新順に並べ替えて、max_split件だけ取得
            sorted_pairs = sorted(combined_pairs, key=lambda x: x[0], reverse=True)[:max_split]

            # 日付とテキストを分解
            sorted_dates = [d.strftime("%Y%m%d") for d, _ in sorted_pairs]
            sorted_texts = [t for _, t in sorted_pairs]

            # 必要数だけNone埋め
            filled_dates = sorted_dates + [None] * (len(config["target_cols_date"]) - len(sorted_dates))
            filled_texts = sorted_texts + [None] * (len(config["target_cols_text"]) - len(sorted_texts))

            # 結果に追加（DATE）
            for col, val in zip(config["target_cols_date"], filled_dates):
                result.append((col, val, paired_date_col))

            # 結果に追加（TEXT）
            for col, val in zip(config["target_cols_text"], filled_texts):
                result.append((col, val, paired_text_col))

        elif conv_type == "latest_exist_and_detail_flag":
            flag_col = config["flag_target_col"]
            detail_col = config["detail_target_col"]
            latest_value = None

            # ① all_row を優先的に確認
            if source_col in all_row and str(all_row[source_col]).strip():
                latest_value = str(all_row[source_col]).strip()

            # ② all_row になければ recipient_rows / donor_rows を順に確認
            if not latest_value:
                for row_set in [recipient_rows, donor_rows]:
                    for row in row_set:
                        df = row[1] if isinstance(row, tuple) else row
                        if isinstance(df, pd.Series):
                            df = df.to_frame().T

                        if source_col in df.columns:
                            val = str(df[source_col].values[0]).strip()
                            if val:
                                latest_value = val  # 上書きしていくので最後にある値が採用される

            # ③ 結果格納
            if latest_value:
                result.append((flag_col, 1, source_col))
                result.append((detail_col, latest_value, source_col))
            else:
                result.append((flag_col, 0, source_col))
                result.append((detail_col, None, source_col))

        elif conv_type == "disease_code_mapping_regular":
            # DiseaseCodeProcessorを使用した新しい処理
            processor = DiseaseCodeProcessor()
            
            # 1行のSeriesをDataFrameに変換
            df_single_row = pd.DataFrame([all_row])
            
            # 疾患コード変換処理を実行
            processed_df = processor.process_disease_codes(df_single_row)
            processed_row = processed_df.iloc[0]
            
            # 結果をresultリストに追加
            for i in range(1, 6):
                h_value = processed_row.get(f"GENSIKKAN{i}_H", '')
                l_value = processed_row.get(f"GENSIKKAN{i}_L", '')
                cmnt_value = processed_row.get(f"GENSIKKAN{i}_CMNT", '')
                
                if h_value:
                    result.append((f"GENSIKKAN{i}_H", h_value, "disease_code_mapping_regular"))
                if l_value:
                    result.append((f"GENSIKKAN{i}_L", l_value, "disease_code_mapping_regular"))
                if cmnt_value:
                    result.append((f"GENSIKKAN{i}_CMNT", cmnt_value, "disease_code_mapping_regular"))

        elif conv_type == "disease_code_mapping_admin":
            admin_mappings = [
                # パターン①: Primary non-function
                {
                    "field": "Primary non-function(R)[初回調査用紙（レシピエント／ドナー情報）]",
                    "h_value": "08",
                    "l_value": "0801",
                    "cmnt_field": None
                },
                # パターン②: 慢性拒絶反応
                {
                    "field": "慢性拒絶反応(R)[初回調査用紙（レシピエント／ドナー情報）]",
                    "h_value": "08",
                    "l_value": "0802",
                    "cmnt_field": None
                },
                # パターン③: 原疾患の再発
                {
                    "field": "原疾患の再発(R)[初回調査用紙（レシピエント／ドナー情報）]",
                    "h_value": "08",
                    "l_value": "0803",
                    "cmnt_field": "原疾患の再発（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]"
                },
                # パターン④: 血管系合併症
                {
                    "field": "血管系合併症(R)[初回調査用紙（レシピエント／ドナー情報）]",
                    "h_value": "08",
                    "l_value": "0804",
                    "cmnt_field": "血管系合併症（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]"
                },
                # パターン⑤: その他
                {
                    "field": "その他(R)[初回調査用紙（レシピエント／ドナー情報）]",
                    "h_value": "08",
                    "l_value": "0805",
                    "cmnt_field": "その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]"
                },
            ]
            filed_count = 1
            # 各疾患タイプを1回だけ処理するよう反復処理
            for mapping in admin_mappings:
                if filed_count > 3:
                    continue
                field = mapping["field"]
                cmnt_field = mapping["cmnt_field"]
                value = all_row[field]
                cmnt_value = all_row.get(cmnt_field) if cmnt_field in all_row else None
                if value == 'True':
                    h_value = mapping["h_value"]
                    l_value = mapping["l_value"]
                    l_value = mapping["l_value"]
                    result.append((f"GENSIKKAN_KANRISHA{filed_count}_H", h_value, field))
                    result.append((f"GENSIKKAN_KANRISHA{filed_count}_L", l_value, field))
                    if cmnt_value:
                        result.append((f"GENSIKKAN_KANRISHA{filed_count}_CMNT", cmnt_value, cmnt_field))
                    filed_count = filed_count + 1
                else:
                    ## 群コードが入っていなかったら次のカラムを参照する
                    continue
        elif conv_type == "multi_flag_split_contains":
            # 改行・カンマ・全角カンマ・読点を考慮して分割
            parts = re.split(r"[,\n、，]", str(raw_value)) if raw_value else []
            parts = [p.strip() for p in parts if p.strip()]
            
            for keyword, target_col in config["rules"].items():
                value = 1 if any(keyword in p for p in parts) else 0
                result.append((target_col, value, source_col))

        elif conv_type == "conditional_flag_with_detail":
            main_flag_col = config["main_flag_col"]
            value_if_true = config["value_if_true"]
            value_if_false = config["value_if_false"]
            details_cfg = config["details"]
            details_col = details_cfg["source_col"]
            
            # まず main_flag を判断（all_row, recipient_rows, donor_rows）
            def get_value(col):
                if col in all_row and str(all_row[col]).strip():
                    return str(all_row[col]).strip()
                for row_set in [recipient_rows, donor_rows]:
                    for row in row_set:
                        df = row[1] if isinstance(row, tuple) else row
                        if isinstance(df, pd.Series):
                            df = df.to_frame().T
                        if col in df.columns:
                            val = str(df[col].values[0]).strip()
                            if val:
                                return val
                return ""

            main_value = get_value(source_col)
            detail_value = get_value(details_col)

            # フラグの判定
            if main_value == value_if_true:
                result.append((main_flag_col, 1, source_col))
                
                for keyword, mapping in details_cfg["mappings"].items():
                    if keyword in detail_value:
                        result.append((mapping["target_col"], 1, details_col))
                        # コメントが必要な場合
                        if "comment_col" in mapping and "comment_source_col" in mapping:
                            comment_val = get_value(mapping["comment_source_col"])
                            result.append((mapping["comment_col"], comment_val, mapping["comment_source_col"]))
                    else:
                        result.append((mapping["target_col"], 0, details_col))
                        if "comment_col" in mapping:
                            result.append((mapping["comment_col"], None, mapping["comment_source_col"]))
            elif main_value == value_if_false:
                result.append((main_flag_col, 0, source_col))
            else:
                result.append((main_flag_col, None, source_col))  # 値が不明な場合

        elif conv_type == "latest_date_from_split":
            candidate_values = [str(raw_value).strip()] if raw_value else []

            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    df = row[1] if isinstance(row, tuple) else row
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    if source_col in df.columns:
                        val = str(df[source_col].values[0]).strip()
                        if val:
                            candidate_values.append(val)

            # 最後に見つかった非空値
            final_value = next((v for v in reversed(candidate_values) if v), "")

            # カンマ区切り → 日付に変換 → 最新値を抽出
            date_parts = [pd.to_datetime(d.strip(), errors="coerce") for d in final_value.split(",")]
            valid_dates = [d for d in date_parts if not pd.isna(d)]

            if valid_dates:
                latest_date = max(valid_dates)
                formatted = latest_date.strftime(config.get("format", "%Y%m%d"))
            else:
                formatted = None
            result.append((config.get("target_col", source_col), formatted, source_col))

        elif conv_type == "minutes_to_hour_minute":
            candidate_values = [str(raw_value).strip()] if raw_value else []

            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    df = row[1] if isinstance(row, tuple) else row
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    if source_col in df.columns:
                        val = str(df[source_col].values[0]).strip()
                        if val:
                            candidate_values.append(val)

            final_value = next((v for v in reversed(candidate_values) if v), "")
            try:
                total_minutes = int(final_value)
                hours = total_minutes // 60
                minutes = total_minutes % 60
            except Exception as e:
                #print(f"❌ minutes_to_hour_minute parse error: {source_col} → {e}")
                hours = None
                minutes = None

            result.append((config["target_cols"]["hour"], hours, source_col))
            result.append((config["target_cols"]["minute"], minutes, source_col))

        elif conv_type == "donor_jutsumae_fukki":
            status_value = str(all_row.get(source_col, "")).strip()

            fields = config.get("fields", {})
            is_kenzon = status_value == "健存"
            is_byotai = status_value in ["死亡", "病悩", "病脳"]

            if is_kenzon:
                fukki_field = fields.get("復帰_健存_可否")
                katsudo_field = fields.get("復帰_健存_活動")
                riyu_field = fields.get("復帰_健存_理由区分")
                cmnt_field = fields.get("復帰_健存_理由詳細")

                fukki_value = all_row.get(fukki_field, "").strip()
                result.append(("JUTSUMAE_FUKKI", 1 if fukki_value == "可" else 0, fukki_field))

                katsudo_value = all_row.get(katsudo_field, "").strip()
                result.append(("KATSUDO_JOKYO", katsudo_value, katsudo_field))

                riyu_value = all_row.get(riyu_field, "").strip()
                riyu_mapped = 1 if riyu_value == "医学的" else 2 if riyu_value == "社会的" else None
                result.append(("JUTSUMAE_FUKKI_NOT", riyu_mapped, riyu_field))

                cmnt_value = all_row.get(cmnt_field, "").strip()
                result.append(("JUTSUMAE_FUKKI_NOT_CMNT", cmnt_value, cmnt_field))

            elif is_byotai:
                fukki_field = fields.get("復帰_病態_可否")
                katsudo_field = fields.get("復帰_病態_活動")
                riyu_field = fields.get("復帰_健存_理由区分")  # 同じ項目名
                period_field = fields.get("復帰_病態_期間")
                byonou_riyuu_field = fields.get("復帰_病態_理由詳細")

                fukki_value = all_row.get(fukki_field, "").strip()
                result.append(("JUTSUMAE_FUKKI", 1 if fukki_value == "可" else 0, fukki_field))

                katsudo_value = all_row.get(katsudo_field, "").strip()
                result.append(("KATSUDO_JOKYO", katsudo_value, katsudo_field))

                riyu_value = all_row.get(riyu_field, "").strip()
                riyu_mapped = 1 if riyu_value == "医学的" else 2 if riyu_value == "社会的" else None
                result.append(("JUTSUMAE_FUKKI_NOT", riyu_mapped, riyu_field))

                period = all_row.get(period_field, "").strip()
                reason = all_row.get(byonou_riyuu_field, "").strip()
                cmnt_value = f"{period} {reason}".strip() if reason else period
                result.append(("JUTSUMAE_FUKKI_NOT_CMNT", cmnt_value, byonou_riyuu_field))


        elif conv_type == "keyword_map_with_default":
            candidate_values = [str(raw_value).strip()] if raw_value else []

            # recipient_rows → donor_rows からも追加で値を取得
            for row_set in [recipient_rows, donor_rows]:
                for row in row_set:
                    df = row[1] if isinstance(row, tuple) else row
                    if isinstance(df, pd.Series):
                        df = df.to_frame().T
                    if source_col in df.columns:
                        val = str(df[source_col].values[0]).strip()
                        if val:
                            candidate_values.append(val)

            # 最後に出現した非空値を使用
            final_value = next((v for v in reversed(candidate_values) if v), "")

            matched = False
            rules = config.get("rules", {})
            for keyword, rule in rules.items():
                if keyword != "default" and keyword in final_value:
                    result.append((rule["target_col"], rule["value"], source_col))
                    matched = True
                    break

            if not matched and final_value:
                default_rule = rules.get("default", {})
                if "target_col" in default_rule:
                    result.append((default_rule["target_col"], default_rule.get("value"), source_col))
                if "comment_col" in default_rule:
                    result.append((default_rule["comment_col"], final_value, source_col))


        elif conv_type == "split_minutes_to_hour_minute":
            try:
                minutes = int(float(raw_value)) if raw_value else 0
                hours = minutes // 60
                remainder = minutes % 60
                result.append((config["hour_target_col"], hours, source_col))
                result.append((config["minute_target_col"], remainder, source_col))
            except (ValueError, TypeError):
                result.append((config["hour_target_col"], None, source_col))
                result.append((config["minute_target_col"], None, source_col))

        elif conv_type == "multi_flag_map_with_etc":
            raw_items = [s.strip() for s in str(raw_value).split(config.get("delimiter", ",")) if s.strip()]
            matched_keys = set(config["map"].keys())

            used = set()
            for item in raw_items:
                if item in config["map"]:
                    target_col = config["map"][item]
                    result.append((target_col, 1, source_col))
                    used.add(item)

            # 未マッチがあったら etc フラグとコメント
            unmatched = [item for item in raw_items if item not in used]
            if unmatched:
                result.append((config["etc_flag_field"], 1, source_col))
                result.append((config["etc_comment_field"], raw_value, source_col))
            else:
                result.append((config["etc_flag_field"], 0, source_col))
                result.append((config["etc_comment_field"], None, source_col))

        elif conv_type == "conditional_multi_flag_map":
            value_str = str(raw_value).strip()
            rules = config.get("rules", {})
            
            # 条件に一致するルールを探す
            if value_str in rules:
                update_rule = rules[value_str]
            else:
                update_rule = rules.get("default", {})

            for target_col, target_value in update_rule.items():
                result.append((target_col, target_value, source_col))

        elif conv_type == "keyword_flag_map":

            text = str(raw_value)
            used = set()
            for keyword, target_col in config["keywords"].items():
                if keyword in text:
                    result.append((target_col, 1, source_col))
                    used.add(target_col)

            # 対象の全てのフィールドに対して未使用なら0
            for target_col in set(config["keywords"].values()):
                if target_col not in used:
                    result.append((target_col, 0, source_col))

        elif conv_type == "multi_flag_split_contains_with_exist_flag":
            config_rules = config["config"]["rules"]
            exist_flag_col = config["config"]["exist_flag_col"]

            # 値が存在する場合は exist_flag_col を 1、存在しない場合は 0
            if raw_value and str(raw_value).strip():
                result.append((exist_flag_col, 1, source_col))
            else:
                result.append((exist_flag_col, 0, source_col))
                continue  # 空ならこれ以上処理しない

            # データ分割（カンマ、改行、全角カンマ、読点）
            parts = re.split(r"[,\n、，]", str(raw_value))
            parts = [p.strip() for p in parts if p.strip()]

            matched_keywords = set()

            # 既存：キーワードごとにフラグ判定（そのまま）
            for keyword, target_col in config_rules.items():
                if any(keyword in p for p in parts):
                    result.append((target_col, 1, source_col))
                    matched_keywords.add(target_col)
                else:
                    result.append((target_col, 0, source_col))

            # ★追加：ルール外（未マッチ）文字列を抽出
            unmatched_parts = []
            rule_keywords = list(config_rules.keys())
            for p in parts:
                if not any((kw in p) for kw in rule_keywords):
                    unmatched_parts.append(p)

            # ★追加：汎用のETC/コメント出力（設定されている場合のみ）
            etc_target_col = config["config"].get("etc_target_col")
            etc_comment_col = config["config"].get("etc_comment_col")

            if etc_target_col:
                if unmatched_parts:
                    # ルール外がある場合は 1 を立てる
                    result.append((etc_target_col, 1, source_col))
                    if etc_comment_col:
                        # 複数あれば全角読点で結合（お好みで変更可）
                        result.append((etc_comment_col, "、".join(unmatched_parts), source_col))
                else:
                    # ルール外が無ければ 0、およびコメントは空（出力不要ならこの2行は削除可）
                    result.append((etc_target_col, 0, source_col))
                    if etc_comment_col:
                        result.append((etc_comment_col, "", source_col))

            # 既存のコメント出力（オプション）機能はそのまま
            comment_col = config["config"].get("comment_target_col")
            comment_targets = config["config"].get("comment_rules", [])
            if comment_col and any(target in matched_keywords for target in comment_targets):
                result.append((comment_col, raw_value, source_col))

        elif conv_type == "conditional_text_map":
            """
            条件付きテキストマッピング
            特定の値が選択された場合に、対応するテキストフィールドの値を取得する
            """
            target_col = config.get("target_col")
            rules = config.get("rules", {})
            
            # データ取得の優先順位: all.csv -> donor.csv -> recipient.csv
            value = None
            
            # all.csvから値を取得
            if source_col in self.all_df.columns:
                value = self.all_df.at[row_idx, source_col]
            
            # allにない場合、donor/recipientを確認（最新データを取得）
            if pd.isna(value) or value == "":
                # (D)がついている場合はdonor優先
                if "(D)" in source_col:
                    value = self.get_latest_data_from_followup(source_col, row_idx, self.donor_df)
                # (R)がついている場合はrecipient優先  
                elif "(R)" in source_col:
                    value = self.get_latest_data_from_followup(source_col, row_idx, self.recipient_df)
                # 識別子がない場合は両方確認
                else:
                    value = self.get_latest_data_from_followup(source_col, row_idx, self.donor_df)
                    if pd.isna(value) or value == "":
                        value = self.get_latest_data_from_followup(source_col, row_idx, self.recipient_df)
            
            if pd.isna(value) or value == "":
                continue
                
            # 値の変換処理
            if str(value) in rules:
                rule = rules[str(value)]
                
                # rulesの値が辞書の場合（テキスト参照が必要）
                if isinstance(rule, dict) and "text_source_col" in rule:
                    mapped_value = rule.get("value", value)
                    text_col_name = rule["text_source_col"]
                    
                    # テキストフィールドからデータを取得（同じ優先順位）
                    text_value = None
                    
                    # all.csvから値を取得
                    if text_col_name in self.all_df.columns:
                        text_value = self.all_df.at[row_idx, text_col_name]
                    
                    # allにない場合、donor/recipientを確認（最新データを取得）
                    if pd.isna(text_value) or text_value == "":
                        if "(D)" in text_col_name:
                            text_value = self.get_latest_data_from_followup(text_col_name, row_idx, self.donor_df)
                        elif "(R)" in text_col_name:
                            text_value = self.get_latest_data_from_followup(text_col_name, row_idx, self.recipient_df)
                        else:
                            text_value = self.get_latest_data_from_followup(text_col_name, row_idx, self.donor_df)
                            if pd.isna(text_value) or text_value == "":
                                text_value = self.get_latest_data_from_followup(text_col_name, row_idx, self.recipient_df)
                    
                    # テキスト値がある場合は、その値を使用
                    if not pd.isna(text_value) and str(text_value).strip() != "":
                        final_value = str(text_value).strip()
                    else:
                        final_value = mapped_value
                        
                else:
                    # 通常のマッピング
                    final_value = rule
            else:
                # デフォルト値処理
                final_value = rules.get("default", value)
            
            # 結果をresultリストに追加
            if final_value is not None:
                result.append((target_col, final_value, source_col))

        # kan.py の apply_rules メソッドに追加する新しい変換タイプ
        elif conv_type == "multi_flag_with_unknown_handling":
            """
            複数フラグ分割（未知文字列処理付き）
            指定されたキーワードにマッチするものは対応フィールドに1を設定
            マッチしない文字列があれば、指定されたフィールドに1を設定し、コメントフィールドに格納

            追加要件:
            - 「リツキシマブ」を含む場合、special_flags["rituximab_flag"] を 1（なければ 0）
            - 「バシリキシマブ」または「ATG」または「ALG」を含む場合、special_flags["antibody_flag"] を 1（なければ 0）
            """
            config_rules = config["config"]["rules"]
            exist_flag_col = config["config"].get("exist_flag_col")
            unknown_flag_col = config["config"].get("unknown_flag_col", "KOTAI_ETC")
            unknown_comment_col = config["config"].get("unknown_comment_col", "KOTAI_ETC_CMNT")
            special_flags = config["config"].get("special_flags", {})

            rituximab_flag_col = special_flags.get("rituximab_flag")   # 例: "KOTAISHORI_FLG"
            antibody_flag_col  = special_flags.get("antibody_flag")    # 例: "DONYUKOTAI_FLG"

            # 値が存在する場合は exist_flag_col を 1、存在しない場合は 0
            if raw_value and str(raw_value).strip():
                if exist_flag_col:
                    result.append((exist_flag_col, 1, source_col))
            else:
                if exist_flag_col:
                    result.append((exist_flag_col, 0, source_col))
                # ルール対象フィールドは全て0
                for target_col in config_rules.values():
                    result.append((target_col, 0, source_col))
                # special flags も 0
                if rituximab_flag_col:
                    result.append((rituximab_flag_col, 0, source_col))
                if antibody_flag_col:
                    result.append((antibody_flag_col, 0, source_col))
                # unknownフラグは0
                result.append((unknown_flag_col, 0, source_col))
                continue

            # データ分割（カンマのみ。必要なら読点/全角カンマを足してください）
            parts = re.split(r"[,]", str(raw_value))
            parts = [p.strip() for p in parts if p.strip()]

            matched_targets = set()     # マッチした出力カラム名
            matched_keywords = set()    # マッチしたキーワード（文字列）
            unknown_parts = []

            # 各部分をチェック
            for part in parts:
                matched_keywords_in_part = set()

                # 既知のキーワードをすべてチェック（breakしない）
                for keyword, target_col in config_rules.items():
                    if keyword in part:
                        # 同じターゲットに重複して 1 を積み上げないようにする
                        if target_col not in matched_targets:
                            result.append((target_col, 1, source_col))
                            matched_targets.add(target_col)
                        matched_keywords.add(keyword)
                        matched_keywords_in_part.add(keyword)

                # この部分で何もマッチしなかった場合のみ unknown_parts に追加
                if not matched_keywords_in_part:
                    unknown_parts.append(part)

            # マッチしなかったフィールドを0に設定
            for target_col in config_rules.values():
                if target_col not in matched_targets:
                    result.append((target_col, 0, source_col))

            # --- 追加: special flags の設定 ---
            # リツキシマブ検出 -> rituximab_flag を 1、それ以外は 0
            if rituximab_flag_col:
                if "リツキシマブ" in matched_keywords:
                    result.append((rituximab_flag_col, 1, source_col))
                else:
                    result.append((rituximab_flag_col, 0, source_col))

            # バシリキシマブ or ATG or ALG 検出 -> antibody_flag を 1、それ以外は 0
            if antibody_flag_col:
                if any(k in matched_keywords for k in ("バシリキシマブ", "ATG")):
                    result.append((antibody_flag_col, 1, source_col))
                elif "ALG" in str(raw_value):  # ALGを含むかをチェック
                    result.append((antibody_flag_col, 1, source_col))
                else:
                    result.append((antibody_flag_col, 0, source_col))
            # --- 追加ここまで ---

            # 未知の文字列がある場合の処理
            if unknown_parts:
                result.append((unknown_flag_col, 1, source_col))
                result.append((unknown_comment_col, ",".join(unknown_parts), source_col))
            else:
                result.append((unknown_flag_col, 0, source_col))

        else:
            print(f"DEBUG: conv_type = {repr(conv_type)}")
            print(f"⚠️ 未対応の変換タイプ: {conv_type} ({source_col})")

    return result

def update_table(cursor, connection, table_name, column_series, data_row, id_columns, id_values, ishoku_toroku_id, sisetsu_name, recipient_rows=None, donor_rows=None):
    if not isinstance(id_columns, (list, tuple)):
        id_columns = [id_columns]
    if not isinstance(id_values, (list, tuple)):
        id_values = [id_values]

    where_clause = " AND ".join([f"`{col}` = %s" for col in id_columns])
    # 1. conversion_map による更新
    conversion_results = apply_conversion_rules_to_row(table_name, column_series, data_row, recipient_rows, donor_rows, conversion_map, toshima_excluded_columns)
    for db_field_name, value, source_col in conversion_results:
        if value is None or value == "":
            continue
        try:
            sql = f"UPDATE {table_name} SET `{db_field_name}` = %s WHERE {where_clause}"
            ## デバック用 Start　片岡
            #if db_field_name in ["ISYOKUGO_NINSINREKI","ISYOKUGO_NINSIN_CNT"]:
            #if any(substr in db_field_name for substr in ["AKUSEISIKKAN_ETC"]):
            #if any(substr in db_field_name for substr in ["GAPPEI", "TEKISHUTSU_LI_TYPE"]):
            #    params = [value] + list(id_values)
                # 生SQL風に再現
            formatted_sql = sql
            params = [value] + list(id_values)
            for p in params:
                val = f"'{p}'" if isinstance(p, str) else str(p)
            formatted_sql = formatted_sql.replace("%s", val, 1)
            print(f"▶️ 実行SQL: {formatted_sql}")
            ## デバック用 End
            cursor.execute(sql, [value] + list(id_values))
        except Exception as e:
            #if source_col in [col.strip() for col in schema_mismatch_columns]:
            #    pass
            #else:
            error_logs.append({
                "テーブル名": table_name,
                "移植登録ID": ishoku_toroku_id,
                "施設名": sisetsu_name, 
                "カラム名": db_field_name,
                "データ": value,
                "エラー内容": str(e)
            })

    # 2. 通常カラムの更新（conversion_mapに含まれないもの）
    for _, row_update in column_series.iterrows():
        key = row_update['アルトマーク　項目名']
        db_field_name = row_update['物理名']
        target_column = row_update['項目']

        key_normalized = key.strip()
        #print(f"aaaa{key_normalized}")

        # 変換マップで処理済ならスキップ
        if key_normalized in conversion_map:
            continue

        # 移行しないデータはスキップする
        excluded_columns_strip = [col.strip() for col in excluded_columns]
        if key_normalized in excluded_columns_strip:
            continue

        value = None

        #if key_normalized in toshima_excluded_columns:
        #    continue
        try:
            if target_column == '項目名（移植時）':
                matching_column = next((col for col in data_row.index if col.strip() == key_normalized), None)
                if matching_column:
                    value = data_row.get(matching_column)

            elif target_column == '項目名（レシピエントフォロー）' and recipient_rows:
                for recipient_row in recipient_rows:
                    if recipient_row[1][key_normalized]:
                        value = recipient_row[1][key_normalized]

            elif target_column == '項目名（ドナーフォロー）' and donor_rows:
                for donor_row in donor_rows:
                    if donor_row[1][key_normalized]:
                        value = donor_row[1][key_normalized]

            if value is not None and value != "":
                sql = f"UPDATE {table_name} SET `{db_field_name}` = %s WHERE {where_clause}"
                cursor.execute(sql, [value] + list(id_values))

        except Exception as e:
            #if key_normalized in [col.strip() for col in schema_mismatch_columns]:
            #    pass
                #print(f"❌ 通常項目UPDATE失敗: {db_field_name}, 移植ID: {ishoku_toroku_id}, データ: {value} CSVカラム:{key_normalized}, エラー: {e}")
            #else:
            error_logs.append({
                "テーブル名": table_name,
                "移植登録ID": ishoku_toroku_id,
                "施設名": sisetsu_name, 
                "カラム名": db_field_name,
                "データ": value,
                "エラー内容": str(e)
            })

    def get_latest_data_from_followup(self, col_name: str, row_idx: int, df: pd.DataFrame):
        """
        フォローアップデータから最新のデータを取得（51年目→50年目→...→3ヶ月後の順でチェック）
        
        Args:
            col_name: カラム名
            row_idx: 行インデックス
            df: 検索対象のDataFrame
            
        Returns:
            Any: 見つかった最新の値、見つからない場合はNone
        """
        # フォローアップ順序（逆順：最新から古い順）
        followup_order_reverse = [f"{i}年後" for i in range(51, 0, -1)] + ["3ヶ月後"]
        
        # 基本カラム名（フォローアップ識別子を除去）
        base_col_name = col_name
        
        # フォローアップ順序で最新データを検索
        for period in followup_order_reverse:
            # カラム名に期間が含まれているかチェック
            if period in col_name:
                # 既に期間が含まれている場合はそのまま使用
                search_col_name = col_name
            else:
                # 期間を追加してカラム名を生成
                # 例: "血液浄化法-詳細(R)[移植直前の状態...]" → "血液浄化法-詳細(R)[移植直前の状態...] 3ヶ月後"
                search_col_name = f"{base_col_name} {period}"
            
            # DataFrameに該当カラムが存在するかチェック
            if search_col_name in df.columns:
                value = df.at[row_idx, search_col_name]
                # 有効な値が見つかった場合は返す
                if not pd.isna(value) and str(value).strip() != "":
                    return value
        
        # フォローアップデータが見つからない場合、基本カラムをチェック
        if base_col_name in df.columns:
            value = df.at[row_idx, base_col_name]
            if not pd.isna(value) and str(value).strip() != "":
                return value
        
        return None

# DB接続情報
#db_config = {
#    "host": "mysql-db",
#    "user": "root",
#    "password": "admin",
#    "database": "tracer",
#    "charset": "utf8mb4",
#    "cursorclass": pymysql.cursors.DictCursor
#}

### 片岡
db_config = {
    "host": "db",  # コンテナ名
    "user": "root",
    "password": "123456",
    "database": "dev_TRACERDB7",
    "charset": "utf8mb4",
        "cursorclass": pymysql.cursors.DictCursor
}


"""該当テーブル
T_ISHOKU_KIHON_LIV
T_ISHOKU_KIHON_LIVER_LIV
T_DONOR_LIV
T_DONOR_LIVER_LIV
T_LIVING_R_LIV
T_GAPPEI_R_LIV
T_REJECTION_R_LIV
T_IJI_MENEKI_YOKUSEI_R_LIV
T_KENSA_R_LIV
T_KENSA_D_LIV
T_GAPPEI_D_LIV
"""


# 空データ挿入対象テーブルと設定値
table_names = [
    "T_ISHOKU_KIHON_LIV",         ## 移植基本情報(共通-生体)             / １レコード　　　　※問題あり
    "T_TRACER_IKO",               ## TRACER移行紐づけ                  /  ※複数行だけど、１レコード
    "T_ISHOKU_KIHON_LIVER_LIV",   ## 移植基本情報(肝-生体)              /  1レコード　　　　※問題あり
    "T_DONOR_LIV",                ## ドナー情報(共通-生体)              /  1レコード　　　　※問題あり
    "T_DONOR_LIVER_LIV",          ## ドナー情報(肝-生体)                /  1レコード　　　　※問題あり
    "T_GAPPEI_R_LIV",             ## 合併症R(生体-レシピエント)          /  複数レコード　　　※問題あり
    "T_GAPPEI_D_LIV",              ## 合併症D(生体-ドナー)               /  複数レコード　　　※問題あり
    "T_LIVING_R_LIV",             ## 生活状況R(生体-レシピエント)        /  複数レコード　　※一旦完了　戸嶋さんに確認中
    "T_LIVING_D_LIV",
    "T_REJECTION_R_LIV",          ## 拒絶反応R(生体-レシピエント)        /  複数レコード   ※一旦完了　戸嶋さんに確認中
    "T_IJI_MENEKI_YOKUSEI_R_LIV", ## 維持期免疫抑制薬R(生体-レシピエント) /  複数レコード   ※一旦完了　戸嶋さんに確認中   問題あり
    "T_KENSA_R_LIV",              ## 検査項目R(生体-レシピエント)        /  複数レコード    ※完了
    "T_KENSA_D_LIV",              ## 検査項目D(生体-ドナー)             /  複数レコード    ※完了
]


institution_df = pd.read_csv("/csv/liver/shisetsu.csv", encoding="cp932", dtype=str).fillna("")

# TRACERデータ（仮にCSVなどで読み込み済とする）
# 実際には元データや整備済DataFrameを使ってください
#tracer_df = pd.read_csv("/csv/liver/LITREJ_肝臓_移植時_250217.csv", encoding='cp932', dtype=str).fillna("")
#tracer_df = pd.read_csv("/csv/liver/all.csv", encoding='cp932', dtype=str).fillna("")
tracer_df = pd.read_excel("/csv/liver/R移植時.xlsx", sheet_name="LITREJ_20250630184614_all", dtype=str).fillna("")
tracer_df = tracer_df[tracer_df['生体肝／脳死肝の別'].str.strip() == '生体肝']
# 数値的にIDが189以上の行を抽出（astypeでintに変換）　ID変更片岡
#tracer_df = tracer_df[tracer_df['移植登録ID'].astype(int) == 10743]
#tracer_df = tracer_df[tracer_df['移植登録ID'].astype(int).between(1, 200)]
# 除外リスト（肝臓移植ID）
#exclude_ids = [196, 362, 365, 433, 501, 502, 504, 506, 507, 508, 509, 513, 514, 515, 518, 519]
exclude_ids=[]
# デバック用 IDが"189"の行を抽出（文字列で一致） 
#tracer_df = tracer_df[tracer_df['移植登録ID'] == '196']
# 件数を出力して確認
#print(f"抽出された件数: {len(filtered_df)} 件")
# 先頭1件だけ表示
#print(filtered_df.head(1))

#donor_df = pd.read_csv("/csv/liver/LITREJ_肝臓_移植後経過_ドナー_250217.csv", encoding='cp932', dtype=str).fillna("")
#recipient_df = pd.read_csv("/csv/liver/LITREJ_肝臓_移植後経過_レシピエント_250217.csv", encoding='cp932', dtype=str).fillna("")
#institution_df = pd.read_csv("/csv/liver/施設.csv", sep="\t", dtype=str).fillna("")
#mapping_dict = pd.read_excel("/csv/liver/移行データ対応表.xlsx", sheet_name=None)
# 手動でソート順を定義
followup_order = ["3ヶ月後"] + [f"{i}年後" for i in range(1, 51)]

#donor_df = pd.read_csv("/csv/liver/R移植時.xlsx", encoding='cp932', dtype=str).fillna("")
donor_df = pd.read_excel("/csv/liver/D移植後.xlsx", sheet_name="LITREJ_20250630185101_donor_fol", dtype=str).fillna("")
# カテゴリ型として順序を指定
donor_df["追跡調査の種類[ドナー追跡調査]"] = pd.Categorical(
    donor_df["追跡調査の種類[ドナー追跡調査]"],
    categories=followup_order,
    ordered=True
)

# ソート実行
donor_df.sort_values(
    by=["肝臓移植ID[ドナー追跡調査]", "追跡調査の種類[ドナー追跡調査]"],
    ascending=True,
    inplace=True
)

#recipient_df = pd.read_csv("/csv/liver/recipient.csv", encoding='cp932', dtype=str).fillna("")
recipient_df = pd.read_excel("/csv/liver/R移植後.xlsx", sheet_name="LITREJ_20250630185917_recipient", dtype=str).fillna("")
# カテゴリ型として順序を指定
recipient_df["追跡調査の種類[レシピエント追跡調査]"] = pd.Categorical(
    recipient_df["追跡調査の種類[レシピエント追跡調査]"],
    categories=followup_order,
    ordered=True
)

# R監視者用原疾患
recipient_genshikan_df = pd.read_excel("/csv/liver/R管理者原疾患.xlsx", sheet_name="登録元データ2001.12.5", dtype=str).fillna("")

# ソート実行
recipient_df.sort_values(
    by=["肝臓移植ID[レシピエント追跡調査]", "追跡調査の種類[レシピエント追跡調査]"],
    ascending=True,
    inplace=True
)

mapping_dict = pd.read_excel("/csv/liver/map.xlsx", sheet_name=None, keep_default_na=False)

change_list_df = pd.read_csv("/csv/liver/map_table_list.csv", sep="\t", dtype=str).fillna("")
error_logs = []  # エラー情報をここにためるリスト

df_mapping = mapping_dict['肝臓_生体']

"""
カラムの項目を表示する
tracer_df_columns = tracer_df.columns.tolist()
print(tracer_df_columns)
['移植登録ID', '作成日', '生体肝／脳死肝の別', '施設名', '診療科', '電話番号', 'FAX番号', '担当医名', '移植肝亜区域', '移植肝亜区域-中肝静脈', '移植肝重量', '移植日', '移植回数', '移植回数（テキスト）', '姓(R)[初回調査用紙（レシピエント／ドナー情報）]', '名(R)[初回調査用紙（レシピエント／ドナー情報）]', '性別(R)[初回調査用紙（レシピエント／ドナー情報）]', '生年月日(R)[初回調査用紙（レシピエント／ドナー情報）]', '年齢(R)[初回調査用紙（レシピエント／ドナー情報）]', '月齢(R)[初回調査用紙（レシピエント／ドナー情報）]', 'カルテ番号／ID(R)[初回調査用紙（レシピエント／ドナー情報）]', '胆汁鬱滞性疾患(R)[初回調査用紙（レシピエント／ドナー情報）]', '胆汁鬱滞性疾患-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '胆汁鬱滞性疾患-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '胆汁鬱滞性疾患-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '胆汁鬱滞性疾患-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患-他の原発性（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患-転移性（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '腫瘍性疾患-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '肝細胞性疾患（肝硬変）(R)[初回調査用紙（レシピエント／ドナー情報）]', '肝細胞性疾患（肝硬変）-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '肝細胞性疾患（肝硬変）-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '肝細胞性疾患（肝硬変）-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '肝細胞性疾患（肝硬変）-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-他のviral（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-薬剤性（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '急性肝不全-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患-Glycogen Storage Disease（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '代謝性疾患-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管性疾患(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管性疾患-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管性疾患-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管性疾患-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管性疾患-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他の疾患(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他の疾患-詳細(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他の疾患-その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他の疾患-群コード(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他の疾患-疾患コード(R)[初回調査用紙（レシピエント／ドナー情報）]', 'Primary non-function(R)[初回調査用紙（レシピエント／ドナー情報）]', '慢性拒絶反応(R)[初回調査用紙（レシピエント／ドナー情報）]', '原疾患の再発(R)[初回調査用紙（レシピエント／ドナー情報）]', '原疾患の再発（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管系合併症(R)[初回調査用紙（レシピエント／ドナー情報）]', '血管系合併症（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他(R)[初回調査用紙（レシピエント／ドナー情報）]', 'その他（テキスト）(R)[初回調査用紙（レシピエント／ドナー情報）]', '姓(D)[初回調査用紙（レシピエント／ドナー情報）]', '名(D)[初回調査用紙（レシピエント／ドナー情報）]', '性別(D)[初回調査用紙（レシピエント／ドナー情報）]', '生年月日(D)[初回調査用紙（レシピエント／ドナー情報）]', '年齢(D)[初回調査用紙（レシピエント／ドナー情報）]', '月齢(D)[初回調査用紙（レシピエント／ドナー情報）]', '人種(R)[レシピエント情報／ドナー情報]', '人種-その他（テキスト）(R)[レシピエント情報／ドナー情報]', '身長(R)[レシピエント情報／ドナー情報]', '体重(R)[レシピエント情報／ドナー情報]', 'ABO血液型(R)[レシピエント情報／ドナー情報]', 'Rh血液型(R)[レシピエント情報／ドナー情報]', 'HLA Type:A1(R)[レシピエント情報／ドナー情報]', 'HLA Type:A2(R)[レシピエント情報／ドナー情報]', 'HLA Type:B1(R)[レシピエント情報／ドナー情報]', 'HLA Type:B2(R)[レシピエント情報／ドナー情報]', 'HLA Type:C1(R)[レシピエント情報／ドナー情報]', 'HLA Type:C2(R)[レシピエント情報／ドナー情報]', 'HLA Type:DR1(R)[レシピエント情報／ドナー情報]', 'HLA Type:DR2(R)[レシピエント情報／ドナー情報]', '年齢(D)[レシピエント情報／ドナー情報]', '人種(D)[レシピエント情報／ドナー情報]', '人種-その他（テキスト）(D)[レシピエント情報／ドナー情報]', '身長(D)[レシピエント情報／ドナー情報]', '体重(D)[レシピエント情報／ドナー情報]', 'ABO血液型(D)[レシピエント情報／ドナー情報]', 'Rh血液型(D)[レシピエント情報／ドナー情報]', 'HLA Type:A1(D)[レシピエント情報／ドナー情報]', 'HLA Type:A2(D)[レシピエント情報／ドナー情報]', 'HLA Type:B1(D)[レシピエント情報／ドナー情報]', 'HLA Type:B2(D)[レシピエント情報／ドナー情報]', 'HLA Type:C1(D)[レシピエント情報／ドナー情報]', 'HLA Type:C2(D)[レシピエント情報／ドナー情報]', 'HLA Type:DR1(D)[レシピエント情報／ドナー情報]', 'HLA Type:DR2(D)[レシピエント情報／ドナー情報]', '腹水(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '脳症(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '血液浄化法(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '血液浄化法-詳細(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '血液浄化法-詳細-その他（テキスト）(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '肝細胞癌歴(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '透析治療(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '肝肺症候群(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '肺高血圧(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Quality of Life(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'PS(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Platelet(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'T.Bil(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'D.Bil(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Alb(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Creatinine(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'PT(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'ワーファリンの使用(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'INR(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Na(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBsAg(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBsAb(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBeAg(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBeAb(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBcAb(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBV-DNA(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HCVAb(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HCV-RNA(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HIV(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'ATLA(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'CMV(IgG)(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'MELDスコア(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'PELDスコア(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'CTPスコア(R)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '死因(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '死因-頭部外傷（その他）（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '死因-脳血管障害（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '死因-脳腫瘍（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '死因-その他（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '経過中の心停止(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '昇圧剤の使用(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '種類(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '使用量(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '種類(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_1', '使用量(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_2', '既往歴(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '既往歴-有（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'アルコール歴(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '種類と一日量(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '年数(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '併存症(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '併存症-詳細(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '併存症-詳細-その他（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '脂肪肝(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '脂肪肝-有（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'AST(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'ALT(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'T.Bil(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Na(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBsAg(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBsAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBeAg(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBeAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBcAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HCVAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HIV(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'ATLA(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'CMV(IgG)(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '血液(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '血液-菌種（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'レシピエントとの関係(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'レシピエントとの関係-その他血縁者（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'レシピエントとの関係-その他血縁者以外（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '既往歴(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_3', '既往歴-有（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_4', '併存症(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_5', '併存症-詳細(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_6', '併存症-詳細-その他（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_7', '脂肪肝(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_8', '脂肪肝-有（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_9', '倫理的問題(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '倫理的問題-詳細(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '倫理的問題-合併症（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '倫理的問題-血縁関係（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', '倫理的問題-その他（テキスト）(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'AST(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_10', 'ALT(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_11', 'T.Bil(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_12', 'D.Bil(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Alb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'Creatinine(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'PT(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'ワーファリンの使用(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'INR(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'APTT(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]', 'HBsAg(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_13', 'HBsAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_14', 'HBeAg(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_15', 'HBeAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_16', 'HBcAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_17', 'HCVAb(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_18', 'ATLA(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_19', 'CMV(IgG)(D)[移植直前の状態（レシピエント）／提供直前の状態（ドナー）]_20', '他臓器（組織）の移植の既往(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '他臓器（組織）の移植の既往-有（テキスト）(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '他臓器（組織）の同時移植(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '他臓器（組織）の同時移植-その他（テキスト）(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '手術時間(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '無肝期(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '手術法(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '分割（split）移植(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '分割（split）移植-Yes：他施設（テキスト）(R)[移植手術（レシピエント）／摘出手術（ドナー）]', 'ドミノ移植(R)[移植手術（レシピエント）／摘出手術（ドナー）]', 'ドミノ移植-Yes：他施設（テキスト）(R)[移植手術（レシピエント）／摘出手術（ドナー）]', 'GV/SLV (%)(R)[移植手術（レシピエント）／摘出手術（ドナー）]', 'GRWR(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '門脈血栓(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '特殊な門脈再建(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '特殊な門脈再建-有（テキスト）(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '胆道再建(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '出血量(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '輸血量(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '赤血球液(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '新鮮凍結血漿(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '血小板(R)[移植手術（レシピエント）／摘出手術（ドナー）]', 'その他(R)[移植手術（レシピエント）／摘出手術（ドナー）]', '摘出手術日(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器-詳細(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器-詳細-有（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '肝摘出チーム(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '肝摘出チーム-他施設（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '提供施設(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '手術時間(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '摘出肝(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '摘出肝-その他（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '摘出肝重量(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '残肝率(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器・組織(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器・組織-詳細(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器・組織-血管（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '他の摘出臓器・組織-その他（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '出血量(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '輸血(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '輸血-詳細(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '自己血（術前採血）（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '同種血-詳細(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '同種血-赤血球液（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '同種血-新鮮凍結血漿（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '同種血-血小板（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', '同種血-その他（テキスト）(D)[移植手術（レシピエント）／摘出手術（ドナー）]', 'ステロイド(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'カルシニュリン・ インヒビター(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'カルシニュリン・ インヒビター-詳細(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'mTOR阻害剤(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'mTOR阻害剤-詳細(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '核酸合成阻害(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '核酸合成阻害-詳細(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体製剤(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体製剤-詳細(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'その他(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', 'その他（テキスト）(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体価(IgG) 抗A抗体処置前(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体価(IgG) 抗A抗体処置後(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体価(IgG) 抗B抗体処置前(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体価(IgG) 抗B抗体処置後(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '抗体処理法(R)[免疫抑制：導入期（移植後1カ月以内）、ABO不適合移植（レシピエント）]', '合併症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '出血(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '出血-部位（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'Primary non-function(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '免疫学的合併症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '免疫学的合併症-詳細(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血管系合併症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血管系合併症-詳細(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血管系合併症-その他（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血栓症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血栓症-その他（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '狭窄(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '狭窄-その他（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道合併症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道合併症-詳細(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道合併症-その他（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '感染症(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '感染症（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他（テキスト）(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '合併症による再手術(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '合併症による再手術-手術日(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '合併症による再手術-術式(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清T. Bil最高値(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清T. Bil最高値-D.Bil(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清T. Bil最高値-術後＿日目(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'T.BillND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'D.BillND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'T. Bil 5.0以上の持続期間(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'T. Bil 5.0以上の持続期間 ＿日間(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'T. Bil 2.0以上の継続期間(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'T. Bil 2.0以上の継続期間 ＿日間(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清AST最高値(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清AST最高値-術後＿日目(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'AST ND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清ALT最高値(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血清ALT最高値-術後＿日目(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'ALT ND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'PT最低値(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'PT最低値-術後＿日目(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'PT ND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血中アンモニア最高値(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '血中アンモニア最高値-術後＿日目(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'アンモニアND(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '合併症(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '出血(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '出血-部位（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '心血管系(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '心血管系（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '呼吸器系(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '呼吸器系-詳細(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '呼吸器系-その他（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道系(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道系-詳細(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '胆道系-その他（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他の消化器系(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他の消化器系-詳細(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他の消化器系-その他（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '手術部位感染（SSI）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'それ以外の感染症(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'それ以外の感染症-部位（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他-詳細(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', 'その他-その他（テキスト）(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '再手術(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '再手術-手術日(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '再手術-術式(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '退院日西暦(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '術後入院日数(D)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]', '治療歴(R)[肝細胞癌歴ありの場合（レシピエント）]', 'viableな肝細胞癌(R)[肝細胞癌歴ありの場合（レシピエント）]', '腫瘍の個数(R)[肝細胞癌歴ありの場合（レシピエント）]', '腫瘍の個数-それ以上（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]', '腫瘍の最大径(R)[肝細胞癌歴ありの場合（レシピエント）]', '腫瘍の最大径-5cm以上（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]', 'AFP(R)[肝細胞癌歴ありの場合（レシピエント）]', 'AFP-15ng/ml以上99999ng/ml以下（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]', 'PIVKA-II(R)[肝細胞癌歴ありの場合（レシピエント）]', 'PIVKA-II-40mAU/ml以上99999mAU/ml以下（テキスト）(R)[肝細胞癌歴ありの場合（レシピエント）]', '初期潅流(D)[保存（ドナー）]', '初期潅流-その他（テキスト）(D)[保存（ドナー）]', '保存液(D)[保存（ドナー）]', '保存液-その他（テキスト）(D)[保存（ドナー）]', 'リンス液(D)[保存（ドナー）]', 'リンス液-詳細(D)[保存（ドナー）]', 'リンス液-その他（テキスト）(D)[保存（ドナー）]', '冷虚血時間(D)[保存（ドナー）]', '温虚血時間(D)[保存（ドナー）]', '全虚血時間(D)[保存（ドナー）]', '登録年[追加情報(管理者用)]', '登録No[追加情報(管理者用)]', 'RecNo[追加情報(管理者用)]', 'CDNo[追加情報(管理者用)]', 'LDNo(D)[追加情報(管理者用)]', '原疾患[追加情報(管理者用)]', '郡コード[追加情報(管理者用)]', '疾患コード1[追加情報(管理者用)]', '疾患コード2[追加情報(管理者用)]', 'ABO適合[追加情報(管理者用)]', '最終確認日(R)[追加情報(管理者用)]', '最終確認日(D)[追加情報(管理者用)]', 'G予後[追加情報(管理者用)]', 'G最終確認日[追加情報(管理者用)]', '死因(R)[追加情報(管理者用)]', 'ドミノ[追加情報(管理者用)]', 'ドミノ相方[追加情報(管理者用)]', 'Split[追加情報(管理者用)]', 'Split相方[追加情報(管理者用)]', 'APOLT[追加情報(管理者用)]', '備考[追加情報(管理者用)]', 'ドナー死因(D)[追加情報(管理者用)]', 'レシピエント最終予後調査日', 'ドナー最終予後調査日']
donor_columns = donor_df.columns.tolist()
recipient_columns = recipient_df.columns.tolist()
print(donor_columns)
['肝臓移植ID[ドナー追跡調査]', '調査日[ドナー追跡調査]', '追跡調査の種類[ドナー追跡調査]', '現状[現状]', '最終生存確認日[現状]', '術前状態への完全復帰[現状]', '否の場合、その理由[現状]', '具体的理由（テキスト）[現状]', '活動状況；術前に比べて（テキスト）[現状]', 'Quality of Life[現状]', 'PS[現状]', '向精神薬（睡眠導入剤を含む）の継続的使用[現状]', '向精神薬（睡眠導入剤を含む）の継続的使用-詳細[現状]', '向精神薬（睡眠導入剤を含む）の継続的使用-その他（テキスト）[現状]', '最終生存確認日[現状].1', '病脳の場合：その理由[現状]', '病脳期間[現状]', '通常生活への復帰[現状]', '活動状況；術前に比べて[現状]', 'Quality of Life[現状].1', 'PS[現状].1', '向精神薬（睡眠導入剤を含む）の継続的使用[現状].1', '向精神薬（睡眠導入剤を含む）の継続的使用-詳細[現状].1', '向精神薬（睡眠導入剤を含む）の継続的使用-その他（テキスト）[現状].1', '死亡：死亡日[現状]', '死因[現状]', '死因-消化器疾患（その他）（テキスト）[現状]', '死因-悪性新生物（テキスト）[現状]', '死因-その他（テキスト）[現状]', 'T.Bil[検査値と合併症・再手術・再入院]', 'D.Bil[検査値と合併症・再手術・再入院]', 'AST[検査値と合併症・再手術・再入院]', 'ALT[検査値と合併症・再手術・再入院]', 'PT[検査値と合併症・再手術・再入院]', 'ワーファリンの使用[検査値と合併症・再手術・再入院]', 'INR[検査値と合併症・再手術・再入院]', '疾患の有無[検査値と合併症・再手術・再入院]', '心血管系[検査値と合併症・再手術・再入院]', '心血管系（テキスト）[検査値と合併症・再手術・再入院]', '呼吸器系[検査値と合併症・再手術・再入院]', '呼吸器系（テキスト）[検査値と合併症・再手術・再入院]', '胆道系[検査値と合併症・再手術・再入院]', '胆道系-詳細[検査値と合併症・再手術・再入院]', '胆道系-その他（テキスト）[検査値と合併症・再手術・再入院]', 'その他の消化器系[検査値と合併症・再手術・再入院]', 'その他の消化器系-詳細[検査値と合併症・再手術・再入院]', 'その他の消化器系-その他（テキスト）[検査値と合併症・再手術・再入院]', '手術部位感染（SSI）[検査値と合併症・再手術・再入院]', 'それ以外の感染症[検査値と合併症・再手術・再入院]', 'それ以外の感染症-部位（テキスト）[検査値と合併症・再手術・再入院]', 'その他[検査値と合併症・再手術・再入院]', 'その他（テキスト）[検査値と合併症・再手術・再入院]', '再手術（前回報告以降のもののみ）[検査値と合併症・再手術・再入院]', '再入院（前回報告以降のもののみ）[検査値と合併症・再手術・再入院]', '再手術（前回報告以降のもののみ）-手術日[検査値と合併症・再手術・再入院]', '再手術（前回報告以降のもののみ）-術式[検査値と合併症・再手術・再入院]', '再入院（前回報告以降のもののみ）-入院年月日[検査値と合併症・再手術・再入院]', '再入院（前回報告以降のもののみ）-理由[検査値と合併症・再手術・再入院]']
print(recipient_columns)
['肝臓移植ID[レシピエント追跡調査]', '調査日[レシピエント追跡調査]', '追跡調査の種類[レシピエント追跡調査]', 'レシピエントの状態[レシピエントの状態]', '最終生存確認日[レシピエントの状態]', 'Quality of Life[レシピエントの状態]', 'PS[レシピエントの状態]', '向精神薬（睡眠導入剤を含む）の継続的使用[レシピエントの状態]', '向精神薬（睡眠導入剤を含む）の継続的使用-詳細[レシピエントの状態]', '向精神薬（睡眠導入剤を含む）の継続的使用-その他（テキスト）[レシピエントの状態]', '禁酒の継続について[レシピエントの状態]', '専門医療機関(精神科)によるフォローアップの有無[レシピエントの状態]', '自助グループへの参加の有無[レシピエントの状態]', '死亡日[レシピエントの状態]', '出血[レシピエントの状態]', '出血-部位（テキスト）[レシピエントの状態]', 'Primary non-function[レシピエントの状態]', '拒絶反応[レシピエントの状態]', '血管系合併症[レシピエントの状態]', '血管系合併症-詳細[レシピエントの状態]', '血管系合併症-その他（テキスト）[レシピエントの状態]', '血栓症[レシピエントの状態]', '血栓症-その他（テキスト）[レシピエントの状態]', '狭窄[レシピエントの状態]', '狭窄-その他（テキスト）[レシピエントの状態]', '胆道合併症[レシピエントの状態]', '胆道合併症-詳細[レシピエントの状態]', '胆道合併症-その他（テキスト）[レシピエントの状態]', '感染症[レシピエントの状態]', '感染症（テキスト）[レシピエントの状態]', '原疾患の再発[レシピエントの状態]', '原疾患の再発（テキスト）[レシピエントの状態]', 'PTLD[レシピエントの状態]', '悪性腫瘍[レシピエントの状態]', '悪性腫瘍（テキスト）[レシピエントの状態]', 'その他[レシピエントの状態]', 'その他（テキスト）[レシピエントの状態]', '最終生存確認日[レシピエントの状態].1', '施設名[レシピエントの状態]', '主治医名[レシピエントの状態]', '郵便番号[レシピエントの状態]', '住所[レシピエントの状態]', 'TEL[レシピエントの状態]', 'FAX[レシピエントの状態]', 'ステロイド[免疫抑制：維持期（現在）]', 'カルシニュリン・ インヒビター[免疫抑制：維持期（現在）]', 'カルシニュリン・ インヒビター-詳細[免疫抑制：維持期（現在）]', 'mTOR阻害剤[免疫抑制：維持期（現在）]', 'mTOR阻害剤 -詳細[免疫抑制：維持期（現在）]', '核酸合成阻害[免疫抑制：維持期（現在）]', '核酸合成阻害-詳細[免疫抑制：維持期（現在）]', 'その他[免疫抑制：維持期（現在）]', 'その他（テキスト）[免疫抑制：維持期（現在）]', '出血[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '出血-部位（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', 'Primary non-function[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '免疫学的合併症[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '血管系合併症[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '血管系合併症-詳細[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '血管系合併症-その他（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '血栓症[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '血栓症-その他（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '狭窄[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '狭窄-その他（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '胆道合併症[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '胆道合併症-詳細[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '胆道合併症-その他（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '感染症[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '感染症（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '原疾患の再発（HCC再発を含む）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '原疾患の再発（HCC再発を含む）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '原疾患の再発（HCC再発を含む）（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '原疾患の再発（診断年月日）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '原疾患の再発（診断年月日）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', 'PTLD[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '悪性腫瘍（HCC再発を除く）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '悪性腫瘍（HCC再発を除く）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '悪性腫瘍（HCC再発を除く）（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '悪性腫瘍（診断年月日）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '悪性腫瘍（診断年月日）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', 'その他[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', 'その他（テキスト）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再手術[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再手術-手術日[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再手術-術式[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再入院[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再入院-入院年月日[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '合併症による再入院-理由[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '妊娠回数[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', '出産回数[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]', 'AST[追跡時の検査値]', 'ALT[追跡時の検査値]', 'Platelet[追跡時の検査値]', 'T.Bil[追跡時の検査値]', 'D.Bil[追跡時の検査値]', 'Alb[追跡時の検査値]', 'Creatinine[追跡時の検査値]', 'PT[追跡時の検査値]', 'ワーファリンの使用[追跡時の検査値]', 'INR[追跡時の検査値]', 'HBsAg[追跡時の検査値]', 'HBsAb[追跡時の検査値]', 'HBeAg[追跡時の検査値]', 'HBeAb[追跡時の検査値]', 'HBcAb[追跡時の検査値]', 'HBV-DNA[追跡時の検査値]', 'HCVAb[追跡時の検査値]', 'HCV-RNA[追跡時の検査値]', 'HIV[追跡時の検査値]', 'ATLA[追跡時の検査値]', 'CMV(IgG)[追跡時の検査値]', 'AFP[追跡時の検査値]', 'AFP-15ng/ml以上99999ng/ml以下（テキスト）[追跡時の検査値]', 'PIVKA-II[追跡時の検査値]', 'PIVKA-II-40mAU/ml以上99999mAU/ml以下（テキスト）[追跡時の検査値]']
"""

institution_df = pd.read_csv("/csv/liver/shisetsu.csv", dtype=str, encoding="cp932").fillna("")
# CSVファイルにはないデフォルト値
tracer_df["DEL_FLG"] = 0
tracer_df["INS_USER_ID"] = 1
tracer_df["INS_PROGRAM_ID"] = 1
# 施設名を施設コードに変換する
tracer_df["ISYOKU_ISYOKUSISETU_CD"] = tracer_df["施設名"].apply(get_sisetu_cd)
tracer_df["RECNO"] = tracer_df["RecNo[追加情報(管理者用)]"]

################################################

# tracer_dfを「生体肝」に限定

# 出力ファイルを準備
"""
with open("output44444.txt", "w", encoding="utf-8") as f:
    for _, row in tracer_df.iterrows():
        # 既存条件で抽出
        matched_recipient_df = recipient_df[
            (recipient_df['肝臓移植ID[レシピエント追跡調査]'] == row['移植登録ID']) &
            (recipient_df['追跡調査の種類[レシピエント追跡調査]'] != '3ヶ月後')
        ]
        
        if matched_recipient_df.empty:
            continue

        # 施設名が空の行だけを対象
        matched_recipient_df = matched_recipient_df[matched_recipient_df["施設名[レシピエントの状態]"].fillna("") == ""]
        if matched_recipient_df.empty:
            continue

        # 追加条件: check_columns のいずれかに値が入っているか
        for i, rec_row in matched_recipient_df.iterrows():
            base_val = row['合併症による再手術-手術日(R)[手術合併症・再手術・再入院（レシピエント）／術後検査値と手術合併症・再手術（ドナー）]']
            recipient_val = rec_row.get("合併症による再手術-手術日[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]", "")

            # それぞれをカンマで分割してリスト化
            base_list = [v.strip() for v in base_val.split(",") if v.strip()]
            recipient_list = [v.strip() for v in recipient_val.split(",") if v.strip()]

            # 連結（重複削除する場合は下を有効化）
            merged_list = base_list + recipient_list
            # merged_list = list(dict.fromkeys(base_list + recipient_list))

            if merged_list:
                merged_val = ",".join(merged_list)
                line = f"{row['移植登録ID']},{len(base_list)},{len(recipient_list)},{len(merged_list)},'{merged_val}'\n"
                f.write(line)

print("完了！！！")
exit()
"""
#####################################################



ISYOKU_ISYOKUSISETU_CD = ""
#try:
start=True
if start:
    connection = pymysql.connect(**db_config)

    with connection.cursor() as cursor:
        print("✅ DB接続成功")
        # 初期化処理を一括で呼び出し
        # 片岡
        print(f"✅ DB初期化開始")
        # 腎臓のデータもあるのでテーブルの削除はしない
        reset_tables(connection, cursor)
        print(f"✅ DB初期化完了")
        for index, row in tracer_df.iterrows():
            connection.begin()
            #print("カラム一覧:", list(row.index))
            ## ３ヶ月後のデータは除外する
            matched_recipient_df = recipient_df[
                (recipient_df['肝臓移植ID[レシピエント追跡調査]'] == row['移植登録ID']) &
                (recipient_df['追跡調査の種類[レシピエント追跡調査]'] != '3ヶ月後')
            ]
            recipient_df_sorted = sort_df_by_followup_desc(matched_recipient_df, '追跡調査の種類[レシピエント追跡調査]')

            matched_donor_df = donor_df[
                (donor_df['肝臓移植ID[ドナー追跡調査]'] == row['移植登録ID']) &
                (donor_df['追跡調査の種類[ドナー追跡調査]'] != '3ヶ月後')
            ]

            # 外側の処理で持っている現在の行(row)の「移植登録ID」と一致するレコードだけに絞り込み
            matched_recipient_genshikan_df = recipient_genshikan_df[
                recipient_genshikan_df['移植登録ID'] == str(row['移植登録ID'])
            ]

            donor_df_sorted = sort_df_by_followup_desc(matched_donor_df,'追跡調査の種類[ドナー追跡調査]')
            recipient_rows = list(recipient_df_sorted.iterrows())
            donor_rows = list(donor_df_sorted.iterrows())

            """
            for i, (idx, r) in enumerate(recipient_rows[:20], 1):
                print(f"{i:02d}. idx={idx}, 追跡={r['追跡調査の種類[レシピエント追跡調査]']}")
            for i, (idx, r) in enumerate(donor_rows[:20], 1):
                print(f"{i:02d}. idx={idx}, 追跡={r['追跡調査の種類[ドナー追跡調査]']}")

            recipient_latest_seizon = first_nonempty_value_from_rows(recipient_rows, '最終生存確認日[レシピエントの状態]')
            donor_latest_alt        = first_nonempty_value_from_rows(donor_rows,     'ALT[検査値と合併症・再手術・再入院]')


            print('最新の最終生存確認日（Recipient）:', recipient_latest_seizon)
            print('最新の ALT（Donor）:', donor_latest_alt)
            exit()
            """
            recipient_latest_seizon = first_nonempty_value_from_rows(recipient_rows, '最終生存確認日[レシピエントの状態]')

            ishoku_toroku_id = row["移植登録ID"] if "移植登録ID" in row else "UNKNOWN"
            sisetsu_name = row["施設名"]
            #print(f"✅{table_names}")
            #print(f"#### 移植ID: {ishoku_toroku_id} #####################################################################")
            #print("▶️ ######## 登録処理スタート ############")  
            for table_name in table_names:
                column_series = df_mapping.query(f'テーブル物理名 == "{table_name}"')
                print(f"🚗{table_name}")
                if table_name == 'T_ISHOKU_KIHON_LIV':                    

                    try:
                        ISYOKU_ISYOKUSISETU_CD = row["ISYOKU_ISYOKUSISETU_CD"]
                        # INSERT対象カラムと値（NOT NULL のみ）
                        insert_columns = ["ISYOKU_ISYOKUSISETU_CD", "ZOKI_CODE", "DEL_FLG", "INS_USER_ID", "INS_PROGRAM_ID"]
                        insert_values = [
                            row["ISYOKU_ISYOKUSISETU_CD"],  # 例：施設コード（上で変換済）
                            3,                              # 臓器コード
                            0,                              # DEL_FLG（削除フラグ）
                            1,                              # INS_USER_ID（登録ユーザ）
                            1                               # INS_PROGRAM_ID（登録プログラム）
                        ]
                        
                        sql = f"""
                            INSERT INTO T_ISHOKU_KIHON_LIV ({','.join(insert_columns)})
                            VALUES ({','.join(['%s'] * len(insert_columns))})
                        """
                        cursor.execute(sql, insert_values)
                        #connection.commit()
                        
                        # 自動採番されたIDを取得
                        seitai_ishoku_id = cursor.lastrowid
                        RECIPIENT_ID = str(seitai_ishoku_id)[-7:].zfill(7)
                        #print(f"✅ 登録完了：SEITAI_ISYOKU_ID = {seitai_ishoku_id}")
                        # TRACER_ID生成
                        organ_code = "3"  # 肝臓
                        transplant_type_code = "1"  # 生体間移植

                        # 移植日 → 年取得（8桁: YYYYMMDD → YYYY）
                        year = row["移植日"][:4] if "移植日" in row and row["移植日"] else "2024"

                        # 同年・同TRACERプレフィックスの最大連番取得
                        prefix = f"{organ_code}{transplant_type_code}{year}"
                        cursor.execute(
                            "SELECT MAX(TRACER_ID) AS max_id FROM T_ISHOKU_KIHON_LIV WHERE TRACER_ID LIKE %s",
                            (f"{prefix}____",)
                        )
                        result = cursor.fetchone()
                        if result["max_id"]:
                            last_seq = int(result["max_id"][-4:])
                            next_seq = last_seq + 1
                        else:
                            next_seq = 1

                        tracer_id = f"{prefix}{next_seq:04d}"

                        # TRACER_ID更新
                        cursor.execute(
                            "UPDATE T_ISHOKU_KIHON_LIV SET TRACER_ID = %s, RECIPIENT_ID=%s WHERE SEITAI_ISYOKU_ID = %s",
                            (tracer_id, RECIPIENT_ID, seitai_ishoku_id)
                        )
                        connection.commit()

                            # connection.commit()

                        #print(f"✅ TRACER_ID 生成＆更新済: {tracer_id}")
                    except Exception as e:
                        print(f"❌ INSERT失敗: T_ISHOKU_KIHON_LIV, エラー: {e}")
                        error_logs.append({
                            "テーブル名": "T_ISHOKU_KIHON_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": sql,
                            "入力値": insert_values,
                            "エラー内容": str(e)
                        })
                        #connection.rollback()
                        continue  # 処理を次に進める（任意）

                    ## 更新処理
                    # ✅ ここで一気にupdate_tableで更新処理をまとめる！！
                    update_table(
                        cursor=cursor,
                        connection=connection,
                        table_name="T_ISHOKU_KIHON_LIV",
                        column_series=column_series,
                        data_row=row,
                        id_columns="SEITAI_ISYOKU_ID",
                        id_values=seitai_ishoku_id,
                        ishoku_toroku_id=ishoku_toroku_id,
                        sisetsu_name=sisetsu_name,
                        recipient_rows=recipient_rows,
                        donor_rows=donor_rows
                    )

                elif table_name == 'T_TRACER_IKO':

                    # INSERT対象カラムと値（NOT NULL のみ）
                    ishoku_toroku_id = row["移植登録ID"] if "移植登録ID" in row else "UNKNOWN"
                    recno = row["RECNO"] if "RECNO" in row else "0000"

                    cursor.execute("SELECT 1 FROM T_TRACER_IKO WHERE TRACER_ID=%s LIMIT 1", (tracer_id,))
                    exists = cursor.fetchone() is not None
                    if exists:
                        print(f"超問題！！！T_TRACER_IKO Error ### {tracer_id} / {seitai_ishoku_id} / {ishoku_toroku_id}")
                    else:
                        cursor.execute("""
                            INSERT INTO T_TRACER_IKO (TRACER_ID, ZOKI_CODE, SEITAI_ISYOKU_ID, ISHOKU_TOROKU_ID, RECNO)
                            VALUES (%s, %s, %s, %s, %s)
                        """, (tracer_id, organ_code, seitai_ishoku_id, ishoku_toroku_id, recno))
                    #tracer_iko_sql =
                    #    INSERT INTO T_TRACER_IKO 
                    #    (TRACER_ID, ZOKI_CODE, SEITAI_ISYOKU_ID, ISHOKU_TOROKU_ID, RECNO) 
                    #    VALUES (%s, %s, %s, %s, %s)
                    #
                    #cursor.execute(tracer_iko_sql, (tracer_id, organ_code, seitai_ishoku_id, ishoku_toroku_id, recno))

                    #print(f"✅ T_TRACER_IKO にも登録完了: TRACER_ID={tracer_id}, 登録ID={ishoku_toroku_id}, RECNO={recno}")
                elif table_name == 'T_ISHOKU_KIHON_LIVER_LIV':

                    # 必須カラムだけでINSERT
                    required_columns = ["SEITAI_ISYOKU_ID", "INS_USER_ID", "INS_PROGRAM_ID"]
                    insert_values = [seitai_ishoku_id, 1, 1]

                    try:
                        sql = f"""
                            INSERT INTO T_ISHOKU_KIHON_LIVER_LIV ({','.join(required_columns)})
                            VALUES ({','.join(['%s'] * len(required_columns))})
                        """
                        cursor.execute(sql, insert_values)
                        #print(f"✅ T_ISHOKU_KIHON_LIVER_LIV に登録完了: SEITAI_ISYOKU_ID={seitai_ishoku_id}")

                    except Exception as e:
                        print(f"❌ INSERT失敗: T_ISHOKU_KIHON_LIVER_LIV, エラー: {e}")
                        error_logs.append({
                            "テーブル名": "T_ISHOKU_KIHON_LIVER_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": sql,
                            "入力値": insert_values,
                            "エラー内容": str(e)
                        })
                        connection.rollback()
                        continue  # 処理を次に進める（任意）


                    update_table(
                        cursor=cursor,
                        connection=connection,
                        table_name="T_ISHOKU_KIHON_LIVER_LIV",
                        column_series=column_series,
                        data_row=row,
                        id_columns="SEITAI_ISYOKU_ID",
                        id_values=seitai_ishoku_id,
                        ishoku_toroku_id=ishoku_toroku_id,
                        sisetsu_name=sisetsu_name,
                        recipient_rows=recipient_rows,
                        donor_rows=donor_rows
                    )

                    # DataFrameに対して直接 iterrows() で回す。
                    for kanri_gen_index, kanri_gen_row in matched_recipient_genshikan_df.iterrows():
                        # f文字列の内側キーはシングルクォートに変更（ダブルだと字句が壊れる）
                        # 出力ラベルも不適切語をやめて "DEBUG" に変更
                        print(f"[DEBUG] transplant_id={kanri_gen_row.get('移植登録ID')} 原疾患={kanri_gen_row.get('原疾患')}")


                elif table_name == 'T_DONOR_LIV':

                    # 仮登録せずに、先に ID を生成する
                    cursor.execute("SELECT MAX(DONOR_ID) AS max_id FROM T_DONOR_LIV")
                    result = cursor.fetchone()
                    next_donor_id = int(result['max_id']) + 1 if result['max_id'] else 1
                    donor_id_str = str(next_donor_id).zfill(7)

                    try:
                        insert_columns = ["DONOR_ID", "SEITAI_ISYOKU_ID", "INS_USER_ID", "INS_PROGRAM_ID"]
                        insert_values = [donor_id_str, seitai_ishoku_id, 1, 1]

                        sql = f"""
                            INSERT INTO T_DONOR_LIV ({','.join(insert_columns)})
                            VALUES ({','.join(['%s'] * len(insert_columns))})
                        """
                        cursor.execute(sql, insert_values)
                        connection.commit()

                        donor_a_id = cursor.lastrowid
                        DONOR_ID = str(donor_a_id)[-7:].zfill(7)
                        cursor.execute(
                            "UPDATE T_DONOR_LIV SET DONOR_ID = %s WHERE DONOR_A_ID = %s",
                            (DONOR_ID, donor_a_id)
                        )
                        connection.commit()
                        
                        #print(f"✅ T_DONOR_LIV 登録完了: DONOR_ID={donor_id_str}, DONOR_A_ID={donor_a_id}")
                    except Exception as e:
                        print(f"❌ INSERT失敗: T_DONOR_LIV, エラー: {e}")
                        error_logs.append({
                            "テーブル名": "T_DONOR_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": sql,
                            "入力値": insert_values,
                            "エラー内容": str(e)
                        })
                        connection.rollback()
                        continue  # 処理を次に進める（任意）
                    
                    update_table(
                        cursor=cursor,
                        connection=connection,
                        table_name="T_DONOR_LIV",
                        column_series=column_series,
                        data_row=row,
                        id_columns="DONOR_A_ID",
                        id_values=donor_a_id,
                        ishoku_toroku_id=ishoku_toroku_id,
                        sisetsu_name=sisetsu_name,
                        recipient_rows=recipient_rows,
                        donor_rows=donor_rows
                    )

                elif table_name == 'T_DONOR_LIVER_LIV':
                    
                    try:
                        insert_columns = ["SEITAI_ISYOKU_ID", "DONOR_A_ID", "INS_USER_ID", "INS_PROGRAM_ID"]
                        insert_values = [seitai_ishoku_id, donor_a_id, 1, 1]

                        sql = f"""
                            INSERT INTO T_DONOR_LIVER_LIV ({','.join(insert_columns)})
                            VALUES ({','.join(['%s'] * len(insert_columns))})
                        """

                        #print(sql)
                        cursor.execute(sql, insert_values)
                        connection.commit()
                        #print(f"✅ T_DONOR_LIVER_LIV 登録完了: SEITAI_ISYOKU_ID={seitai_ishoku_id}, DONOR_A_ID={donor_a_id}")

                    except Exception as e:
                        print(f"❌ INSERT失敗: T_DONOR_LIVER_LIV, エラー: {e}")
                        error_logs.append({
                            "テーブル名": "T_DONOR_LIVER_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": sql,
                            "入力値": insert_values,
                            "エラー内容": str(e)
                        })
                        connection.rollback()
                        continue  # 処理を次に進める（任意）
                    
                    # 更新処理も続けて
                    update_table(
                        cursor=cursor,
                        connection=connection,
                        table_name="T_DONOR_LIVER_LIV",
                        column_series=column_series,
                        data_row=row,
                        id_columns=["SEITAI_ISYOKU_ID", "DONOR_A_ID"],
                        id_values=[seitai_ishoku_id, donor_a_id],
                        ishoku_toroku_id=ishoku_toroku_id,
                        sisetsu_name=sisetsu_name,
                        recipient_rows=recipient_rows,
                        donor_rows=donor_rows
                    )                    

                    nyuryoku_rows = [
                        {
                            "KANJA_KBN": "0",  # 1:recipient
                            "KANJA_ID": seitai_ishoku_id,  # 生体移植ID
                            "KIROKU_TIMING": "0",  # 0:新規
                            "NYURYOKUJOKYO": "0",  # 0:未入力
                        },
                        {
                            "KANJA_KBN": "1",  # ドナー:ドナーID.A
                            "KANJA_ID": donor_a_id,
                            "KIROKU_TIMING": "0",  # 1:移植
                            "NYURYOKUJOKYO": "0",  # 1:完了
                        }
                    ]
#
                    
                    for nyuryoku in nyuryoku_rows:
                        insert_sql = """
                            INSERT INTO T_NYURYOKUJOKYO_LIV (
                                SEITAI_ISYOKU_ID,
                                KANJA_KBN,
                                KANJA_ID,
                                KIROKU_TIMING,
                                NYURYOKUJOKYO,
                                INS_USER_ID,
                                INS_PROGRAM_ID,
                                INS_DATE,
                                UPD_USER_ID,
                                UPD_PROGRAM_ID,
                                UPD_DATE
                            ) VALUES (%s, %s, %s, %s, %s, %s, %s, NOW(), %s, %s, NOW())
                        """
                        insert_params = (
                            seitai_ishoku_id,
                            nyuryoku["KANJA_KBN"],
                            nyuryoku["KANJA_ID"],
                            nyuryoku["KIROKU_TIMING"],
                            nyuryoku["NYURYOKUJOKYO"],
                            "1",   # INS_USER_ID
                            "1",   # INS_PROGRAM_ID
                            "1",   # UPD_USER_ID
                            "1"    # UPD_PROGRAM_ID
                        )

                        # 既存行の一意特定に使うキー列は環境に合わせて調整してください
                        # ここでは (SEITAI_ISYOKU_ID, KANJA_KBN, KANJA_ID, KIROKU_TIMING) を想定
                        update_sql_for_duplicate = """
                            UPDATE T_NYURYOKUJOKYO_LIV
                            SET NYURYOKUJOKYO = %s,
                                UPD_USER_ID   = %s,
                                UPD_PROGRAM_ID= %s,
                                UPD_DATE      = NOW()
                            WHERE SEITAI_ISYOKU_ID = %s
                            AND KANJA_KBN        = %s
                            AND KANJA_ID         = %s
                            AND KIROKU_TIMING    = %s
                        """
                        update_params_for_duplicate = (
                            nyuryoku["NYURYOKUJOKYO"],
                            "1", "1",
                            seitai_ishoku_id, nyuryoku["KANJA_KBN"], nyuryoku["KANJA_ID"], nyuryoku["KIROKU_TIMING"]
                        )

                        try:
                            cursor.execute(insert_sql, insert_params)
                            connection.commit()
                        except IntegrityError as e:
                            # 1062 = Duplicate entry（主キー/ユニークキー重複）
                            if e.args and e.args[0] == 1062:
                                try:
                                    cursor.execute(update_sql_for_duplicate, update_params_for_duplicate)
                                    connection.commit()
                                    print("ℹ️ 既存行があったため UPDATE に切替えて完了: T_NYURYOKUJOKYO_LIV")
                                except Exception as e2:
                                    connection.rollback()
                                    print(f"❌ UPSERT(UPDATE)失敗: T_NYURYOKUJOKYO_LIV, エラー: {e2}")
                                    error_logs.append({
                                        "テーブル名": "T_NYURYOKUJOKYO_LIV",
                                        "移植登録ID": ishoku_toroku_id,
                                        "施設名": sisetsu_name,
                                        "SQL": update_sql_for_duplicate,
                                        "入力値": update_params_for_duplicate,
                                        "エラー内容": str(e2)
                                    })
                            else:
                                connection.rollback()
                                print(f"❌ INSERT失敗: T_NYURYOKUJOKYO_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_NYURYOKUJOKYO_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name,
                                    "SQL": insert_sql,
                                    "入力値": insert_params,
                                    "エラー内容": str(e)
                                })
                        except Exception as e:
                            connection.rollback()
                            print(f"❌ INSERT失敗(その他): T_NYURYOKUJOKYO_LIV, エラー: {e}")
                            error_logs.append({
                                "テーブル名": "T_NYURYOKUJOKYO_LIV",
                                "移植登録ID": ishoku_toroku_id,
                                "施設名": sisetsu_name,
                                "SQL": insert_sql,
                                "入力値": insert_params,
                                "エラー内容": str(e)
                            })


                    # 先頭の非空を取得
                    current_statu_value = first_nonempty_value_from_rows(donor_rows, "現状[現状]") or ""
                    is_alive = any(k in str(current_statu_value) for k in ("健存", "生存"))
                    qol_map = {
                        "入院中（ICU）": 5,
                        "入院中（一般病棟）": 4,
                        "自宅療養、就労・就学不能<br/>（学齢期以前では成長停止）": 3,
                        "パートタイムの就労、常時の就学不能（学齢期以前では成長低下）": 2,
                        "パートタイムの就労、常時の就学不能<br/>（学齢期以前では成長低下）": 2,
                        "常時の就労・就学（学齢期以前では正常な成長）": 1,
                        "常時の就労・就学<br/>（学齢期以前では正常な成長）": 1,
                        "不明": 6,
                        "default": None
                    }

                    if is_alive:
                        # ── 生存 ──────────────────────────────────────────────
                        jutsumae_fukki = 1 if str(first_nonempty_value_from_rows(donor_rows, '術前状態への完全復帰[現状]') or '').strip() == '可' else 0
                        jutsumae_fukki_not      = first_nonempty_value_from_rows(donor_rows, '否の場合、その理由[現状]')
                        jutsumae_fukki_not = (
                            1 if "医学的" in (jutsumae_fukki_not or "")
                            else 2 if "社会的" in (jutsumae_fukki_not or "")
                            else None
                        )

                        jutsumae_fukki_not_cmnt = first_nonempty_value_from_rows(donor_rows, '具体的理由（テキスト）[現状]')
                        katsudo_jokyo           = first_nonempty_value_from_rows(donor_rows, '活動状況；術前に比べて（テキスト）[現状]')
                        qol                     = first_nonempty_value_from_rows(donor_rows, 'Quality of Life[現状]')
                        qol = qol_map.get(qol, None)

                    else:
                        # ── 生存以外 ───────────────────────────────────────────
                        # 理由や期間は適宜コメントへ併合する運用に合わせてください
                        reason = first_nonempty_value_from_rows(donor_rows, '病脳の場合：その理由[現状]')
                        period = first_nonempty_value_from_rows(donor_rows, '病脳期間[現状]')

                        # 余計な空白を除去して文字列化
                        r = str(reason or '').strip()
                        p = str(period or '').strip()

                        # 両方あれば " / " で連結、どちらか一方ならそのまま、両方空なら空文字
                        jutsumae_fukki_not = (r + " / " + p) if (r and p) else (r or p)

                        jutsumae_fukki = 1 if str(first_nonempty_value_from_rows(donor_rows, '通常生活への復帰[現状]') or '').strip() == '可' else 0
                        katsudo_jokyo           = first_nonempty_value_from_rows(donor_rows, '活動状況；術前に比べて[現状]')
                        qol                     = first_nonempty_value_from_rows(donor_rows, 'Quality of Life[現状]')
                        qol = qol_map.get(qol, None)

                        # 死因テキストの連結（空はスキップ・重複除去・順序保持）
                        cols = [
                            '死因-消化器疾患（その他）（テキスト）[現状]',
                            '死因-悪性新生物（テキスト）[現状]',
                            '死因-その他（テキスト）[現状]',
                        ]

                        _vals = [first_nonempty_value_from_rows(donor_rows, c) for c in cols]

                        _parts = []
                        for v in _vals:
                            # None 対策 & 前後空白除去
                            s = str(v if v is not None else "").strip()
                            # NaN/None 文字列化対策
                            if s == "" or s.lower() in ("nan", "none"):
                                continue
                            # 全角→半角などの揺れ対策（使える環境なら）
                            try:
                                import unicodedata
                                s = unicodedata.normalize("NFKC", s)
                            except Exception:
                                pass
                            _parts.append(s)

                        # 重複除去（順序保持）
                        _seen, _uniq = set(), []
                        for x in _parts:
                            if x not in _seen:
                                _seen.add(x)
                                _uniq.append(x)

                        donor_tenki_siin_cmnt = " / ".join(_uniq) if _uniq else None
                        # 生存以外では具体的理由コメントを死因コメントに寄せる例
                        jutsumae_fukki_not_cmnt = donor_tenki_siin_cmnt

                    # 空文字→None（NULL）にそろえる
                    jutsumae_fukki          = (str(jutsumae_fukki).strip() or None)            if jutsumae_fukki is not None else None
                    jutsumae_fukki_not      = (str(jutsumae_fukki_not).strip() or None)        if jutsumae_fukki_not is not None else None
                    jutsumae_fukki_not_cmnt = (str(jutsumae_fukki_not_cmnt).strip() or None)   if jutsumae_fukki_not_cmnt is not None else None
                    katsudo_jokyo           = (str(katsudo_jokyo).strip() or None)             if katsudo_jokyo is not None else None
                    qol                     = (str(qol).strip() or None)                        if qol is not None else None

                    # 2テーブル更新（同一トランザクション推奨）
                    try:
                        # T_DONOR_LIVER_LIV
                        update_sql = """
                            UPDATE T_DONOR_LIVER_LIV
                            SET JUTSUMAE_FUKKI = %s,
                                JUTSUMAE_FUKKI_NOT = %s,
                                JUTSUMAE_FUKKI_NOT_CMNT = %s,
                                KATSUDO_JOKYO = %s,
                                UPD_USER_ID = %s,
                                UPD_PROGRAM_ID = %s,
                                UPD_DATE = NOW()
                            WHERE SEITAI_ISYOKU_ID = %s AND DONOR_A_ID = %s
                        """
                        update_params = (
                            jutsumae_fukki,
                            jutsumae_fukki_not,
                            jutsumae_fukki_not_cmnt,
                            katsudo_jokyo,
                            "1",
                            "1",
                            seitai_ishoku_id,
                            donor_a_id,
                        )
                        cursor.execute(update_sql, update_params)

                        # T_DONOR_LIV
                        update_sql = """
                            UPDATE T_DONOR_LIV
                            SET QOL = %s,
                                UPD_USER_ID = %s,
                                UPD_PROGRAM_ID = %s,
                                UPD_DATE = NOW()
                            WHERE SEITAI_ISYOKU_ID = %s AND DONOR_A_ID = %s
                        """
                        update_params = (
                            qol,
                            "1",
                            "1",
                            seitai_ishoku_id,
                            donor_a_id,
                        )
                        cursor.execute(update_sql, update_params)

                        connection.commit()

                    except Exception as e:
                        print(f"❌ UPDATE失敗: T_DONOR_LIVER_LIV（JUTSUMAE+QOL関連）, DONOR_A_ID={donor_a_id}, エラー: {e}")
                        error_logs.append({
                            "テーブル名": "T_DONOR_LIVER_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": update_sql,
                            "入力値": update_params,
                            "エラー内容": str(e)
                        })
                        connection.rollback()

                    ## 更新処理だがupdate_table関数では補えないので個別で行う
                    """
                    qol_map = {
                        "入院中（ICU）": 1,
                        "入院中（一般病棟）": 1,
                        "自宅療養、就労・就学不能<br/>（学齢期以前では成長停止）": 2,
                        "パートタイムの就労、常時の就学不能（学齢期以前では成長低下）": 4,
                        "パートタイムの就労、常時の就学不能<br/>（学齢期以前では成長低下）": 4,
                        "常時の就労・就学（学齢期以前では正常な成長）": 5,
                        "常時の就労・就学<br/>（学齢期以前では正常な成長）": 5,
                        "不明": 99
                    }

                    for donor_row in donor_rows:
                        _, row = donor_row

                        genjo = str(row.get("現状[現状]", "")).strip()
                        jutzen_fukki = 0
                        jutzen_fukki_not = None
                        jutzen_fukki_not_cmnt = None
                        katsudo_jokyo = None
                        qol_raw = str(row.get("Quality of Life[現状]", "")).strip()
                        qol = qol_map.get(qol_raw, None)

                        if genjo == "健存":
                            if str(row.get("術前状態への完全復帰[現状]", "")).strip() == "可":
                                jutzen_fukki = 1
                            else:
                                reason = str(row.get("否の場合、その理由[現状]", "")).strip()
                                if reason == "医学的":
                                    jutzen_fukki_not = 1
                                elif reason == "社会的":
                                    jutzen_fukki_not = 2
                                # 健存かつ「否」の場合、コメントも設定
                                jutzen_fukki_not_cmnt = str(row.get("具体的理由（テキスト）[現状]", "")).strip()

                            raw_value = row.get("活動状況；術前に比べて（テキスト）[現状]", "")
                            katsudo_jokyo = None if str(raw_value).strip() == "" else str(raw_value).strip()

                        else:
                            if str(row.get("通常生活への復帰[現状]", "")).strip() == "可":
                                jutzen_fukki = 1
                            else:
                                byonou_reason = str(row.get("病脳の場合：その理由[現状]", "")).strip()
                                byonou_period = str(row.get("病脳期間[現状]", "")).strip()
                                if byonou_reason or byonou_period:
                                    jutzen_fukki_not = 1
                                if genjo == "脳":
                                    jutzen_fukki_not_cmnt = f"{byonou_reason} {byonou_period}".strip()

                            raw_value = row.get("活動状況；術前に比べて[現状]", "")
                            katsudo_jokyo = None if str(raw_value).strip() == "" else str(raw_value).strip()

                        try:
                            update_sql = 
                                UPDATE T_DONOR_LIVER_LIV
                                SET JUTSUMAE_FUKKI = %s,
                                    JUTSUMAE_FUKKI_NOT = %s,
                                    JUTSUMAE_FUKKI_NOT_CMNT = %s,
                                    KATSUDO_JOKYO = %s,
                                    UPD_USER_ID = %s,
                                    UPD_PROGRAM_ID = %s,
                                    UPD_DATE = NOW()
                                WHERE SEITAI_ISYOKU_ID = %s AND DONOR_A_ID = %s
                            
                            update_params = (
                                jutzen_fukki,
                                jutzen_fukki_not,
                                jutzen_fukki_not_cmnt,
                                katsudo_jokyo,
                                "1",
                                "1",
                                seitai_ishoku_id,
                                donor_a_id
                            )
                            cursor.execute(update_sql, update_params)


                            update_sql = 
                                UPDATE T_DONOR_LIV
                                SET QOL = %s
                                WHERE SEITAI_ISYOKU_ID = %s AND DONOR_A_ID = %s
                            

                            update_params = (
                                qol,
                                seitai_ishoku_id,
                                donor_a_id
                            )
                            cursor.execute(update_sql, update_params)

                            connection.commit()

                        except Exception as e:
                            print(f"❌ UPDATE失敗: T_DONOR_LIVER_LIV（JUTSUMAE+QOL関連）, DONOR_A_ID={donor_a_id}, エラー: {e}")
                            error_logs.append({
                                "テーブル名": "T_DONOR_LIVER_LIV",
                                "移植登録ID": ishoku_toroku_id,
                                "施設名": sisetsu_name, 
                                "SQL": update_sql,
                                "入力値": update_params,
                                "エラー内容": str(e)
                            })
                            connection.rollback()
                    """

                elif table_name == 'T_GAPPEI_D_LIV':

                    for index__, row in donor_rows:
                        row_data = row.to_dict() if isinstance(row, pd.Series) else row
                        original_date = row_data.get("調査日[ドナー追跡調査]")
                        try:
                            parsed_date = datetime.strptime(original_date, "%Y-%m-%d %H:%M:%S")
                            nyuin_date = parsed_date.strftime("%Y%m%d")
                        except Exception:
                            nyuin_date = ""
                        ## 入院年月日がない
                        nyuin_date = ""
                        def insert_gappei(gappei, gappei_l, nyuin_date="", cmnt=None):
                            try:
                                insert_columns = ["DONOR_A_ID", "GAPPEI", "GAPPEI_L", "NYUIN_DATE", "INS_USER_ID", "INS_PROGRAM_ID", "DEL_FLG"]
                                insert_values = [donor_a_id, gappei, gappei_l, nyuin_date or "", 1, 1, 0]
                                if cmnt:
                                    insert_columns.append("CMNT")
                                    insert_values.append(cmnt)

                                sql = f"""
                                    INSERT INTO T_GAPPEI_D_LIV ({','.join(insert_columns)})
                                    VALUES ({','.join(['%s'] * len(insert_columns))})
                                """
                                cursor.execute(sql, insert_values)
                                connection.commit()
                            except Exception as e:
                                print(f"❌ INSERT失敗: T_GAPPEI_D_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_GAPPEI_D_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name, 
                                    "SQL": sql,
                                    "入力値": insert_values,
                                    "エラー内容": str(e)
                                })
                                connection.rollback()

                        # 心血管系
                        if row_data.get("心血管系[検査値と合併症・再手術・再入院]") == "True":
                            insert_gappei("08", "0801", nyuin_date=nyuin_date, cmnt=row_data.get("心血管系（テキスト）[検査値と合併症・再手術・再入院]"))

                        # 呼吸器系
                        if row_data.get("呼吸器系[検査値と合併症・再手術・再入院]") == "True":
                            details = str(row_data.get("胆道系-詳細[検査値と合併症・再手術・再入院]", "")).split(',')
                            for d in details:
                                d = d.strip()
                                gappei_l = {"縫合不全": "0601", "狭窄": "0602", "その他": "0603"}.get(d, "0603")
                                insert_gappei("06", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("呼吸器系（テキスト）[検査値と合併症・再手術・再入院]"))

                        # 胆道系
                        if row_data.get("胆道系[検査値と合併症・再手術・再入院]") == "True":
                            details = str(row_data.get("胆道系-詳細[検査値と合併症・再手術・再入院]", "")).split(',')
                            for d in details:
                                d = d.strip()
                                gappei_l = {"縫合不全": "0601", "狭窄": "0602", "その他": "0603"}.get(d, "0603")
                                insert_gappei("06", gappei_l, nyuin_date=nyuin_date, cmnt=row_data.get("胆道系-その他（テキスト）[検査値と合併症・再手術・再入院]"))

                        # その他の消化器系
                        if row_data.get("その他の消化器系[検査値と合併症・再手術・再入院]") == "True":
                            details = str(row_data.get("その他の消化器系-その他（テキスト）[検査値と合併症・再手術・再入院]", "")).split(',')
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("その他の消化器系-その他（テキスト）[検査値と合併症・再手術・再入院]"))

                        if row_data.get("それ以外の感染症[検査値と合併症・再手術・再入院]") == "True":
                            details = str(row_data.get("その他の消化器系-その他（テキスト）[検査値と合併症・再手術・再入院]", "")).split(',')
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("それ以外の感染症-部位（テキスト）[検査値と合併症・再手術・再入院]"))

                        if row_data.get("その他[検査値と合併症・再手術・再入院]") == "True":
                            details = str(row_data.get("その他（テキスト）[検査値と合併症・再手術・再入院]", "")).split(',')
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("その他（テキスト）[検査値と合併症・再手術・再入院]"))


                        # 文字列をその場で分割（, と 全角・読点「、」の両方に対応）
                        date_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("再入院（前回報告以降のもののみ）-入院年月日[検査値と合併症・再手術・再入院]", "")) if s.strip()]
                        proc_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("再手術（前回報告以降のもののみ）-術式[検査値と合併症・再手術・再入院]", "")) if s.strip()]


                        # 2) "2023-2-27 00:00:00" / "2023/2/27T00:00" などの「時間付き」を無視して日付だけ取り出す
                        #    "/"→"-" に寄せてから、"T" または空白でsplitして先頭だけ使用
                        dates = [re.split(r'[ T]', s.replace('/', '-'))[0] for s in date_items]

                        # 3) ペアでinsert（長さ不一致は短い方に合わせる）
                        for d, p in zip(dates, proc_items):
                            if re.fullmatch(r"\d{4}-\d{1,2}-\d{1,2}", d or ""):
                                dt = datetime.strptime(d, "%Y-%m-%d")  # フォーマットは YYYY-M-D 想定
                                nyuin_date = dt.strftime("%Y%m%d")
                                insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=p)

                        # 4) 参考までに不一致を通知（必要なければ削ってOK）
                        if len(dates) != len(proc_items):
                            print(f"Warning: counts differ. dates={len(dates)}, procs={len(proc_items)}")


                        date_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("再手術（前回報告以降のもののみ）-手術日[検査値と合併症・再手術・再入院]", "")) if s.strip()]
                        proc_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("再手術（前回報告以降のもののみ）-術式[検査値と合併症・再手術・再入院]", "")) if s.strip()]

                        # 2) "2023-2-27 00:00:00" / "2023/2/27T00:00" などの「時間付き」を無視して日付だけ取り出す
                        #    "/"→"-" に寄せてから、"T" または空白でsplitして先頭だけ使用
                        dates = [re.split(r'[ T]', s.replace('/', '-'))[0] for s in date_items]

                        # 3) ペアでinsert（長さ不一致は短い方に合わせる）
                        for d, p in zip(dates, proc_items):
                            if re.fullmatch(r"\d{4}-\d{1,2}-\d{1,2}", d or ""):
                                dt = datetime.strptime(d, "%Y-%m-%d")  # フォーマットは YYYY-M-D 想定
                                nyuin_date = dt.strftime("%Y%m%d")
                                insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=p)

                        # 4) 参考までに不一致を通知（必要なければ削ってOK）
                        if len(dates) != len(proc_items):
                            print(f"Warning: counts differ. dates={len(dates)}, procs={len(proc_items)}")


                elif table_name == 'T_GAPPEI_R_LIV':

                    gappei_rules = []

                    # データソースとしてrecipient_rowsの最新1件（または複数）を対象
                    for index__, row in recipient_rows:
                        row_data = row.to_dict() if isinstance(row, pd.Series) else row
                        original_date = row_data.get("調査日[レシピエント追跡調査]")
                        # 変換処理
                        try:
                            parsed_date = datetime.strptime(original_date, "%Y-%m-%d %H:%M:%S")
                            nyuin_date = parsed_date.strftime("%Y%m%d")
                        except (TypeError, ValueError):
                            nyuin_date = None  # または空文字など適宜処理
                        ## 入院年月日がない
                        #nyuin_date = ""

                        def insert_gappei(gappei, gappei_l, nyuin_date="", cmnt=None):
                            try:
                                insert_columns = [
                                    "SEITAI_ISYOKU_ID", "GAPPEI", "GAPPEI_L", "NYUIN_DATE",
                                    "INS_USER_ID", "INS_PROGRAM_ID", "DEL_FLG"
                                ]
                                insert_values = [
                                    seitai_ishoku_id, gappei, gappei_l, nyuin_date or "", 1, 1, 0
                                ]
                                if cmnt:
                                    insert_columns.append("CMNT")
                                    insert_values.append(cmnt)

                                sql = f"""
                                    INSERT INTO T_GAPPEI_R_LIV ({','.join(insert_columns)})
                                    VALUES ({','.join(['%s'] * len(insert_columns))})
                                """
                                cursor.execute(sql, insert_values)
                                connection.commit()

                            except Exception as e:
                                print(f"❌ INSERT失敗: T_GAPPEI_R_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_GAPPEI_R_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name, 
                                    "SQL": sql,
                                    "入力値": insert_values,
                                    "エラー内容": str(e)
                                })
                                connection.rollback()

                        # 出血
                        if row_data.get("出血[レシピエントの状態]") == "True":
                            insert_gappei("01", "0101", nyuin_date=nyuin_date, cmnt=row_data.get("出血-部位（テキスト）[レシピエントの状態]"))

                        # Primary non-function
                        if row_data.get("Primary non-function[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]") == True:
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt="Primary non-function")

                        # 血栓症
                        if pd.notna(row_data.get("血栓症[レシピエントの状態]")):
                            thrombo_parts = str(row_data.get("血栓症[レシピエントの状態]"))
                            for part in thrombo_parts.split(','):
                                part = part.strip()
                                gappei_l = {
                                    "門脈": "0402", "動脈": "0403", "肝静脈": "0401", "その他": "0404"
                                }.get(part, "0404")
                                insert_gappei("04", gappei_l, nyuin_date=nyuin_date, cmnt=row_data.get("血栓症-その他（テキスト）[レシピエントの状態]"))

                        # 狭窄
                        if pd.notna(row_data.get("狭窄[レシピエントの状態]")):
                            stenosis_parts = str(row_data.get("狭窄[レシピエントの状態]"))
                            for part in stenosis_parts.split(','):
                                part = part.strip()
                                gappei_l = {
                                    "肝動脈": "0501", "門脈": "0502", "肝静脈": "0503", "その他": "0504"
                                }.get(part, "0504")
                                insert_gappei("05", gappei_l, nyuin_date=nyuin_date, cmnt=row_data.get("狭窄-その他（テキスト）[レシピエントの状態]"))

                        # 胆道合併症
                        if row_data.get("胆道合併症[レシピエントの状態]") == "True":
                            detail_parts = str(row_data.get("胆道合併症-詳細[レシピエントの状態]", "")).split(',')
                            for detail in detail_parts:
                                detail = detail.strip()
                                gappei_l = {
                                    "縫合不全": "0601", "狭窄": "0602", "その他": "0603"
                                }.get(detail, "0603")
                                insert_gappei("06", gappei_l, nyuin_date=nyuin_date, cmnt=row_data.get("胆道合併症-その他（テキスト）[レシピエントの状態]"))

                        # 感染症
                        if row_data.get("感染症[レシピエントの状態]") == "True":
                            insert_gappei("03", "0304", nyuin_date=nyuin_date, cmnt=row_data.get("感染症（テキスト）[レシピエントの状態]"))

                        # 血管系合併症-詳細
                        if pd.notna(row_data.get("血管系合併症-詳細[レシピエントの状態]")):
                            parts = str(row_data.get("血管系合併症-詳細[レシピエントの状態]", "")).split(',')
                            cmnt_val = row_data.get("血管系合併症-その他（テキスト）[レシピエントの状態]", "")
                            for p in parts:
                                p = p.strip()
                                gappei = {
                                    "血栓症": "04", "狭窄": "05", "その他": "99"
                                }.get(p, "99")
                                gappei_l = {
                                    "門脈盗血": "0402", "outflow obstruction": "0403",
                                    "肝静脈うっ滞": "0403", "脾動脈瘤": "0404", "DIC": "0101",
                                    "肝動脈解離": "0401"
                                }.get(cmnt_val, "9901")
                                insert_gappei(gappei, gappei_l, nyuin_date, cmnt=cmnt_val)

                        # 合併症による再入院-理由[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]
                        if row_data.get("合併症による再入院-理由[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]") == "True":
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("合併症による再入院-理由[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]"))

                        # その他
                        if row_data.get("その他[レシピエントの状態]") == "True":
                            insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=row_data.get("その他（テキスト）[レシピエントの状態]"))


                        # 文字列をその場で分割（, と 全角・読点「、」の両方に対応）
                        date_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("合併症による再入院-入院年月日[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]", "")) if s.strip()]
                        proc_items = [s.strip() for s in re.split(r'[,\u3001]\s*', row_data.get("合併症による再入院-理由[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]", "")) if s.strip()]


                        # 2) "2023-2-27 00:00:00" / "2023/2/27T00:00" などの「時間付き」を無視して日付だけ取り出す
                        #    "/"→"-" に寄せてから、"T" または空白でsplitして先頭だけ使用
                        dates = [re.split(r'[ T]', s.replace('/', '-'))[0] for s in date_items]

                        # 3) ペアでinsert（長さ不一致は短い方に合わせる）
                        for d, p in zip(dates, proc_items):
                            if re.fullmatch(r"\d{4}-\d{1,2}-\d{1,2}", d or ""):
                                dt = datetime.strptime(d, "%Y-%m-%d")  # フォーマットは YYYY-M-D 想定
                                nyuin_date = dt.strftime("%Y%m%d")
                                insert_gappei("99", "9901", nyuin_date=nyuin_date, cmnt=p)

                        # 4) 参考までに不一致を通知（必要なければ削ってOK）
                        if len(dates) != len(proc_items):
                            print(f"Warning: counts differ. dates={len(dates)}, procs={len(proc_items)}")

                elif table_name == 'T_LIVING_R_LIV':

                    for index_t_living_r_liv, row_t_living_r_liv in recipient_rows:

                        #if int(row_t_living_r_liv['肝臓移植ID[レシピエント追跡調査]']) in exclude_ids:
                        #    continue

                        original_date = row_t_living_r_liv['調査日[レシピエント追跡調査]']
                        if pd.isna(original_date) or original_date == '':
                            original_date = row_t_living_r_liv['最終生存確認日[レシピエントの状態]']
                        if not original_date:
                            continue
                        #input_date = datetime.strptime(original_date, '%Y/%m/%d').strftime('%Y%m%d')
                        if original_date:
                            # 肝臓移植ID[レシピエント追跡調査] = 11109のデータのように「2024/1/30」が複数あるので、分と秒数で乱数を生成してユニークにする
                            date8 = pd.to_datetime(original_date, errors='coerce').strftime('%Y%m%d')
                            input_date = f"{date8}00{random_mmss()}"  # ← HH=00 を固定、MMSSは乱数

                        followup_cycle = row_t_living_r_liv.get("追跡調査の種類[レシピエント追跡調査]","")
                        #if not followup_cycle or followup_cycle.strip() == "":
                        #    continue
                        cycle_str = followup_cycle.replace("年後", "").zfill(2)

                        # recipientの施設情報を取得する
                        facility = row_t_living_r_liv['施設名[レシピエントの状態]']
                        isyoku_isyokusisetu_cd = get_sisetu_cd(facility)
                        ## defaultならskipする  
                        if isyoku_isyokusisetu_cd == "709999":
                            continue
                        # 移植時の方を使う
                        #isyoku_isyokusisetu_cd = ISYOKU_ISYOKUSISETU_CD
                        try:
                            insert_columns = ["SEITAI_ISYOKU_ID", "INPUT_DATE", "SISETU_CD", "CYCLE", "INS_USER_ID", "INS_PROGRAM_ID"]
                            insert_values = [seitai_ishoku_id, input_date, isyoku_isyokusisetu_cd, cycle_str, 1, 1]

                            sql = f"""
                                INSERT INTO T_LIVING_R_LIV ({','.join(insert_columns)})
                                VALUES ({','.join(['%s'] * len(insert_columns))})
                            """
                            cursor.execute(sql, insert_values)

                            # --- 各種フィールドの変換 ---
                            labels = [
                                ('入院中（ICU）', 1),
                                ('入院中（一般病棟）', 1),
                                ('自宅療養、就労・就学不能（学齢期以前では成長停止）', 2),
                                ('自宅療養、就労・就学不能<br/>（学齢期以前では成長停止）', 2),
                                ('パートタイムの就労、常時の就学不能（学齢期以前では成長低下）', 4),
                                ('パートタイムの就労、常時の就学不能<br/>（学齢期以前では成長低下）', 4),
                                ('常時の就労・就学（学齢期以前では正常な成長）', 5),
                                ('常時の就労・就学<br/>（学齢期以前では正常な成長）', 5),
                                ('不明', 99),
                            ]

                            # 正規化したキーで辞書化（<br>の有無や空白違いも同じキーに収束）
                            qol_map = {canon(label): code for label, code in labels}
                            # 単一値の変換
                            qol_raw = row_t_living_r_liv.get('Quality of Life[レシピエントの状態]')
                            qol_code = None if qol_raw is None else qol_map.get(canon(qol_raw), None)

                            ps_map = {'0': 1, '1': 2, '2': 3, '3': 4, '4': 5, '不明': 6}
                            ps = ps_map.get(str(row_t_living_r_liv.get('PS[レシピエントの状態]')).strip(), None)

                            kousei_flg_raw = row_t_living_r_liv.get('向精神薬（睡眠導入剤を含む）の継続的使用[レシピエントの状態]')
                            kousei_flg = 1 if kousei_flg_raw == '有' else 0 if kousei_flg_raw == '無' else None

                            detail = row_t_living_r_liv.get('向精神薬（睡眠導入剤を含む）の継続的使用-詳細[レシピエントの状態]', '')
                            detail_list = [d.strip() for d in detail.split(',') if d.strip()]
                            koufuan = 1 if '抗不安薬（睡眠導入剤を含む）' in detail_list else 0
                            kouutsu = 1 if '抗うつ薬' in detail_list else 0
                            kousei = 1 if '向精神薬' in detail_list else 0
                            etc = 1 if 'その他' in detail_list else 0
                            etc_cmnt = row_t_living_r_liv.get('向精神薬（睡眠導入剤を含む）の継続的使用-その他（テキスト）[レシピエントの状態]')

                            abst = row_t_living_r_liv.get('禁酒の継続について[レシピエントの状態]', '')
                            kankohen_flg = 1 if abst else 0
                            kankohen_kinsyu_flg = 1 if abst else 0 if abst else None

                            followup_raw = row_t_living_r_liv.get('専門医療機関(精神科)によるフォローアップの有無[レシピエントの状態]')
                            followup = 1 if followup_raw == '有' else 0 if followup_raw == '無' else None

                            selfhelp = row_t_living_r_liv.get('自助グループへの参加の有無[レシピエントの状態]')
                            selfhelp_flg = 1 if selfhelp == '有' else 0 if selfhelp == '無' else None

                            update_sql = """
                                UPDATE T_LIVING_R_LIV SET
                                    SHAKAIFUKKI_JOKYO = %s,
                                    PS = %s,
                                    KOUSEISINYAKU_FLG = %s,
                                    KOUSEISINYAKU_KOUFUANYAKU = %s,
                                    KOUSEISINYAKU_KOUUTUYAKU = %s,
                                    KOUSEISINYAKU_KOUSEISINYAKU = %s,
                                    KOUSEISINYAKU_ETC = %s,
                                    KOUSEISINYAKU_ETC_CMNT = %s,
                                    KANKOHEN_FLG = %s,
                                    KANKOHEN_KINSYU_FLG = %s,
                                    KANKOHEN_FOLLOWUP_FLG = %s,
                                    KANKOHEN_JIJYO_GRP_FLG = %s,
                                    UPD_USER_ID = 1,
                                    UPD_PROGRAM_ID = 1
                                WHERE SEITAI_ISYOKU_ID = %s AND INPUT_DATE = %s
                            """
                            print(update_sql)
                            update_values = [
                                qol_code,ps, kousei_flg, koufuan, kouutsu, kousei, etc, etc_cmnt,
                                kankohen_flg, kankohen_kinsyu_flg, followup, selfhelp_flg,
                                seitai_ishoku_id, input_date
                            ]
                            cursor.execute(update_sql, update_values)
                            connection.commit()

                        except Exception as e:
                            connection.rollback()
                            error_logs.append({
                                "テーブル名": "T_LIVING_R_LIV",
                                "移植登録ID": ishoku_toroku_id,
                                "施設名": sisetsu_name, 
                                "SQL": sql,
                                "入力値": insert_values,
                                "エラー内容": str(e)
                            })


                elif table_name == 'T_REJECTION_R_LIV':

                    ## 下記の診断日でよいのでしょうか。
                    ## 原疾患の再発（診断年月日）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]の値がTRUE
                    ## 原疾患の再発（診断年月日）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]
                    ## 悪性腫瘍（診断年月日）（詳細）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]	
                    ## 悪性腫瘍（診断年月日）[合併症・再手術・再入院・妊娠・出産：前回調査以降のもののみ]
                    for index_t_rejection_r_liv, row_t_rejection_r_liv in recipient_rows:
                        id = row_t_living_r_liv['肝臓移植ID[レシピエント追跡調査]']
                        #### 報告: 調査日及び最終生存確認日が不正なデータはスキップする
                        ####  ここで指定しているIDはCSVファイルの肝臓移植ID[レシピエント追跡調査]を指定する
                        if int(id) in exclude_ids:
                            continue  # このIDの場合はスキップ（何もしない）                        
                        ## 梅先生のデータが来たら参照する指定されている最新の日付までのデータを登録している。
                        ## 現在は調査日が設定されていれば全て登録している
                        original_date = row_t_rejection_r_liv['調査日[レシピエント追跡調査]']
                        # 変換処理
                        if original_date:
                            #input_date = datetime.strptime(original_date, '%Y/%m/%d').strftime('%Y%m%d')
                            input_date = pd.to_datetime(original_date, errors='coerce').strftime('%Y%m%d')
                        else:
                            continue
                        rejection_val = row_t_rejection_r_liv.get('拒絶反応[レシピエントの状態]')
                        if rejection_val == "true":
                            rejection_code = 1
                        elif rejection_val == "false":
                            rejection_code = 0
                        elif rejection_val in [None, ""]:
                            rejection_code = None
                        else:
                            rejection_code = None  # その他の予期しない値もNULL扱い


                        try:
                            ### REJECTION_TYPEは「1:急性拒絶反応」を固定
                            insert_columns = ["SEITAI_ISYOKU_ID", "REJECTION_TYPE", "SINDAN_DATE", "REJECTION_TIRYOU_FLG", "INS_USER_ID", "INS_PROGRAM_ID"]
                            insert_values = [seitai_ishoku_id, 1, input_date, rejection_code, 1, 1]

                            sql = f"""
                                INSERT INTO T_REJECTION_R_LIV ({','.join(insert_columns)})
                                VALUES ({','.join(['%s'] * len(insert_columns))})
                            """
                            cursor.execute(sql, insert_values)
                            connection.commit()
                            #print(f"✅ T_REJECTION_R_LIV 登録完了: SEITAI_ISYOKU_ID={seitai_ishoku_id} SINDAN_DATEs={input_date} CSVのID={row_t_rejection_r_liv['肝臓移植ID[レシピエント追跡調査]']}")

                        except Exception as e:
                                print(f"❌ INSERT失敗: T_REJECTION_R_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_REJECTION_R_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name, 
                                    "SQL": sql,
                                    "入力値": insert_values,
                                    "エラー内容": str(e)
                                })
                                connection.rollback()

                elif table_name == 'T_KENSA_R_LIV':

                    target_csv_columns = [
                        'AST[追跡時の検査値]',
                        'ALT[追跡時の検査値]',
                        'T.Bil[追跡時の検査値]',
                        'D.Bil[追跡時の検査値]',
                        'ワーファリンの使用[追跡時の検査値]',
                        'INR[追跡時の検査値]',
                    ]

                    for target_csv_column in target_csv_columns:
                        loop_cnt = 1
                        for index__t_kensa_r_liv, row__t_kensa_r_liv in recipient_rows:
                            try:
                                kensa_value = row__t_kensa_r_liv.get(target_csv_column,None)
                                ## もし空白やNaNならスキップ
                                if pd.isna(kensa_value) or kensa_value == "":
                                    continue  # 空白やNaNはスキップ
                                cycle_str = f"{loop_cnt:02}"

                                kensa_name, kensa_unit, dtype, threshold = get_kensa_metadata(target_csv_column)
                                kensa_code = evaluate_kensa_code(kensa_value, dtype, threshold)

                                insert_columns = [
                                    "SEITAI_ISYOKU_ID", "KENSA_NAME", "KENSA_VALUE_CODE",
                                    "KENSA_VALUE", "KENSA_UNIT", "CYCLE", "INS_USER_ID", "INS_PROGRAM_ID"
                                ]
                                insert_values = [
                                    seitai_ishoku_id, kensa_name, kensa_code,
                                    kensa_value, kensa_unit, cycle_str, 1, 1
                                ]

                                sql = f"""
                                    INSERT INTO T_KENSA_R_LIV ({','.join(insert_columns)})
                                    VALUES ({','.join(['%s'] * len(insert_columns))})
                                """
                                cursor.execute(sql, insert_values)
                                connection.commit()

                            except Exception as e:
                                print(f"❌ INSERT失敗: T_KENSA_R_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_KENSA_R_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name, 
                                    "SQL": sql,
                                    "入力値": insert_values,
                                    "エラー内容": str(e)
                                })
                                connection.rollback()

                            loop_cnt += 1

                elif table_name == 'T_KENSA_D_LIV':

                    # このテーブルは更新処理は行わず、そのままデータを登録するだけでおわり。
                    target_csv_columns = [
                        'T.Bil[検査値と合併症・再手術・再入院]',
                        'D.Bil[検査値と合併症・再手術・再入院]',
                        'AST[検査値と合併症・再手術・再入院]',
                        'ALT[検査値と合併症・再手術・再入院]',
                        'PT[検査値と合併症・再手術・再入院]',
                        'ワーファリンの使用[検査値と合併症・再手術・再入院]',
                        'INR[検査値と合併症・再手術・再入院]',
                        '疾患の有無[検査値と合併症・再手術・再入院]',
                    ]

                    for index__t_kensa_d_liv, row__t_kensa_d_liv in donor_rows:  # ✅ donor_rows が外側で正しい
                        for target_csv_column in target_csv_columns:

                            try:
                                kensa_value = row__t_kensa_d_liv.get(target_csv_column)
                                cycle_str = to_cycle_str(row__t_kensa_d_liv.get("追跡調査の種類[ドナー追跡調査]"))
                                kensa_name, kensa_unit, dtype, threshold = get_kensa_metadata(target_csv_column)
                                kensa_code = evaluate_kensa_code(kensa_value, dtype, threshold)

                                # 重複チェック SQL
                                check_sql = """
                                    SELECT COUNT(*) as cnt FROM T_KENSA_D_LIV
                                    WHERE DONOR_A_ID = %s AND KENSA_NAME = %s AND CYCLE = %s
                                """
                                cursor.execute(check_sql, (donor_a_id, kensa_name, cycle_str))
                                count = cursor.fetchone()['cnt']

                                if count > 0:
                                    print(f"⚠️ 重複データ検出: {donor_a_id} - {kensa_name} - {cycle_str} → スキップ")
                                    continue  # 登録スキップ

                                if pd.notna(kensa_value) and kensa_value != "":
                                    insert_columns = [
                                        "DONOR_A_ID", "KENSA_NAME", "KENSA_VALUE_CODE",
                                        "KENSA_VALUE", "KENSA_UNIT", "CYCLE", "INS_USER_ID", "INS_PROGRAM_ID"
                                    ]
                                    insert_values = [
                                        donor_a_id, kensa_name, kensa_code,
                                        kensa_value, kensa_unit, cycle_str, 1, 1
                                    ]

                                    sql = f"""
                                        INSERT INTO T_KENSA_D_LIV ({','.join(insert_columns)})
                                        VALUES ({','.join(['%s'] * len(insert_columns))})
                                    """

                                    cursor.execute(sql, insert_values)
                                    connection.commit()

                            except Exception as e:
                                print(f"❌ INSERT失敗: T_KENSA_D_LIV, エラー: {e}")
                                error_logs.append({
                                    "テーブル名": "T_KENSA_D_LIV",
                                    "移植登録ID": ishoku_toroku_id,
                                    "施設名": sisetsu_name,
                                    "SQL": sql,
                                    "入力値": insert_values,
                                    "エラー内容": str(e)
                                })
                                connection.rollback()


                elif table_name == 'T_IJI_MENEKI_YOKUSEI_R_LIV':

                    try:
                        
                        for _, rec in recipient_rows:

                            test_id = rec.get("肝臓移植ID[レシピエント追跡調査]")
                            followup_cycle = rec.get("追跡調査の種類[レシピエント追跡調査]")
                            
                            if not followup_cycle or followup_cycle.strip() == "":
                                continue
                            cycle_str = followup_cycle.replace("年後", "").zfill(2)

                            # 値の取得と変換
                            steroid_val = rec.get('ステロイド[免疫抑制：維持期（現在）]')
                            cs = 1 if steroid_val == 'True' else 2 if steroid_val == 'False' else None

                            cni_detail = rec.get('カルシニュリン・ インヒビター-詳細[免疫抑制：維持期（現在）]', '') or ''
                            tac = 1 if 'FK506' in cni_detail else 0
                            csa = 1 if 'CyA' in cni_detail else 0

                            mtor_detail = rec.get('mTOR阻害剤 -詳細[免疫抑制：維持期（現在）]')
                            evl = 1 if 'Rapamycin' in mtor_detail else 0
                            rap = 1 if 'RAD' in mtor_detail else 0

                            nukleic_detail = rec.get('核酸合成阻害-詳細[免疫抑制：維持期（現在）]', '') or ''
                            mmf = 1 if 'MMF' in nukleic_detail else 0
                            az = 1 if 'AZP' in nukleic_detail else 0
                            mz = 1 if 'MZR' in nukleic_detail else 0
                            cp = 1 if 'CP' in nukleic_detail else 0

                            meneki_etc= 1 if rec.get('その他（テキスト）[免疫抑制：維持期（現在）]') else 0
                            meneki_etc_cmt = rec.get('その他（テキスト）[免疫抑制：維持期（現在）]')
                            exists_sql = """
                                SELECT 1
                                FROM T_IJI_MENEKI_YOKUSEI_R_LIV
                                WHERE SEITAI_ISYOKU_ID = %s AND CYCLE = %s
                                LIMIT 1
                            """
                            cursor.execute(exists_sql, (seitai_ishoku_id, cycle_str))
                            if cursor.fetchone():
                                # 既にあるのでスキップ
                                # ここでログだけ出すなど
                                print(f"skip: ({seitai_ishoku_id}, {cycle_str}) は既存")
                                continue

                            # 登録処理（初回 insert）
                            insert_columns = [
                                "SEITAI_ISYOKU_ID", "CYCLE", "INS_USER_ID", "INS_PROGRAM_ID"
                            ]
                            insert_values = [
                                seitai_ishoku_id, cycle_str, 1, 1
                            ]

                            sql = f"""
                                INSERT INTO T_IJI_MENEKI_YOKUSEI_R_LIV ({','.join(insert_columns)})
                                VALUES ({','.join(['%s'] * len(insert_columns))})
                            """
                            cursor.execute(sql, insert_values)

                            # 追加項目を update（キー：SEITAI_ISYOKU_ID + CYCLE）
                            update_sql = """
                                UPDATE T_IJI_MENEKI_YOKUSEI_R_LIV
                                SET CS = %s, TAC = %s, CSA = %s, EVL = %s, RAP = %s,
                                    MMF = %s, AZ = %s, MZ = %s, MENEKI_ETC = %s, MENEKI_ETC_CMNT = %s,
                                    CP = %s,
                                    UPD_USER_ID = %s, UPD_PROGRAM_ID = %s
                                WHERE SEITAI_ISYOKU_ID = %s AND CYCLE = %s
                            """
                            update_values = [cs, tac, csa, evl, rap, mmf, az, mz, meneki_etc, meneki_etc_cmt, cp, 1, 1, seitai_ishoku_id, cycle_str]
                            cursor.execute(update_sql, update_values)

                            connection.commit()

                    except Exception as e:
                        print("エラーになっている？")
                        error_logs.append({
                            "テーブル名": "T_IJI_MENEKI_YOKUSEI_R_LIV",
                            "移植登録ID": ishoku_toroku_id,
                            "施設名": sisetsu_name, 
                            "SQL": sql,
                            "入力値": insert_values,
                            "エラー内容": str(e)
                        })
                        connection.rollback()


                elif table_name == 'T_LIVING_D_LIV':

                    original_date = None
                    input_date = None

                    for index_living_d, row_living_d in donor_rows:
                        try:

                            # 必須項目処理
                            sisetu_cd = ISYOKU_ISYOKUSISETU_CD  # 要: 施設名列が存在していること
                            followup_cycle = row_living_d.get("追跡調査の種類[ドナー追跡調査]","")
                            #if not followup_cycle or followup_cycle.strip() == "":
                            #    continue
                            cycle_str = followup_cycle.replace("年後", "").zfill(2)

                            original_date = row_living_d.get('調査日[ドナー追跡調査]')
                            if pd.isna(original_date) or original_date == '':
                                original_date = row_living_d.get('最終生存確認日[現状]')

                            if not original_date:
                                continue
                            #input_date = datetime.strptime(original_date, '%Y/%m/%d').strftime('%Y%m%d')
                            if original_date:
                                date8 = pd.to_datetime(original_date, errors='coerce').strftime('%Y%m%d')
                                input_date = f"{date8}00{random_mmss()}"  # ← HH=00 を固定、MMSSは乱数


                            current_statu_value = row_living_d.get('現状[現状]')

                            is_alive = any(k in str(current_statu_value) for k in ("健存", "生存"))

                            ### 共通項目
                            # PS変換
                            ps_raw = str(row_living_d.get("PS[現状]", "")).strip()
                            ps_map = {"0": 1, "1": 2, "2": 3, "3": 4, "4": 5, "不明": 6}
                            ps_value = ps_map.get(ps_raw, None)

                            # 向精神薬有無
                            med_raw = str(row_living_d.get("向精神薬（睡眠導入剤を含む）の継続的使用[現状]", "")).strip()
                            med_flag = 1 if med_raw == "有" else 0 if med_raw == "無" else None

                            # 詳細（フラグ）
                            med_detail_raw = str(row_living_d.get("向精神薬（睡眠導入剤を含む）の継続的使用-詳細[現状]", "")).strip()
                            kouseisinyaku_koufuanyaku_flg = 1 if "抗不安薬" in med_detail_raw else 0
                            kouseisinyaku_kouutuyaku_flg = 1 if "抗うつ薬" in med_detail_raw else 0
                            kouseisinyaku_kouseisinyaku_flg = 1 if "向精神薬" in med_detail_raw else 0
                            kouseisinyaku_etc_flg     = 1 if "その他" in med_detail_raw else 0

                            # コメント
                            etc_comment = str(row_living_d.get("向精神薬（睡眠導入剤を含む）の継続的使用-その他（テキスト）[現状]", "")).strip()

                            if is_alive:
                                jutsumae_fukki = 1 if str(row_living_d.get('術前状態への完全復帰[現状]') or '').strip() == '可' else 0
                                jutsumae_fukki_not      = row_living_d.get('否の場合、その理由[現状]')
                                jutsumae_fukki_not = (
                                    1 if "医学的" in (jutsumae_fukki_not or "")
                                    else 2 if "社会的" in (jutsumae_fukki_not or "")
                                    else None
                                )
                                jutsumae_fukki_not_cmnt = row_living_d.get('具体的理由（テキスト）[現状]')
                                katsudo_jokyo           = row_living_d.get('活動状況；術前に比べて（テキスト）[現状]', None)
                                if katsudo_jokyo =="":
                                    katsudo_jokyo = None
                            else:
                                reason = row_living_d.get('病脳の場合：その理由[現状]')
                                period = row_living_d.get('病脳期間[現状]')

                                # 余計な空白を除去して文字列化
                                r = str(reason or '').strip()
                                p = str(period or '').strip()

                                # 両方あれば " / " で連結、どちらか一方ならそのまま、両方空なら空文字
                                jutsumae_fukki_not_cmnt = (r + " / " + p) if (r and p) else (r or p)
                                jutsumae_fukki_not = 0
                                jutsumae_fukki = 1 if str(row_living_d.get('通常生活への復帰[現状]') or '').strip() == '可' else 0
                                katsudo_jokyo           = row_living_d.get('活動状況；術前に比べて[現状]', None)
                                if katsudo_jokyo =="":
                                    katsudo_jokyo = None
                            insert_columns = [
                                "DONOR_A_ID", "INPUT_DATE", "SISETU_CD", "CYCLE",
                                "PS", "KOUSEISINYAKU_FLG",
                                "KOUSEISINYAKU_KOUFUANYAKU", "KOUSEISINYAKU_KOUUTUYAKU",
                                "KOUSEISINYAKU_KOUSEISINYAKU", "KOUSEISINYAKU_ETC",
                                "JUTSUMAE_FUKKI", "JUTSUMAE_FUKKI_NOT", "JUTSUMAE_FUKKI_NOT_CMNT", "KATSUDO_JOKYO",
                                "KOUSEISINYAKU_ETC_CMNT", "INS_USER_ID", "INS_PROGRAM_ID", "DEL_FLG"
                            ]
                            insert_values = [
                                donor_a_id, input_date, sisetu_cd, cycle_str,
                                ps_value, med_flag,
                                kouseisinyaku_koufuanyaku_flg, kouseisinyaku_kouutuyaku_flg,
                                kouseisinyaku_kouseisinyaku_flg, kouseisinyaku_etc_flg,
                                jutsumae_fukki, jutsumae_fukki_not, jutsumae_fukki_not_cmnt, katsudo_jokyo,
                                etc_comment, 1, 1, 0
                            ]

                            sql = f"""
                                INSERT INTO T_LIVING_D_LIV ({','.join(insert_columns)})
                                VALUES ({','.join(['%s'] * len(insert_columns))})
                            """
                            cursor.execute(sql, insert_values)
                            connection.commit()

                        except Exception as e:
                            print(f"❌ INSERT失敗: T_LIVING_D_LIV, エラー: {e}")
                            error_logs.append({
                                "テーブル名": "T_LIVING_D_LIV",
                                "移植登録ID": ishoku_toroku_id,
                                "施設名": sisetsu_name, 
                                "SQL": sql,
                                "入力値": insert_values,
                                "エラー内容": str(e)
                            })
                            connection.rollback()

                else:
                    connection.commit()
                    pass
                    #print("@@@@@@@@@@@@@@@@@@@ ")
                    #print(table_name)
                #print(tracer_df["移植登録ID"])
    connection.commit()
#except Exception as e:
#    print(f"❌ エラー: {e}")
#    connection.rollback()
#finally:
#    connection.close()
#    print("✅ DB接続を終了しました。")

if error_logs:  # もしエラーが1件以上あれば
    df_error = pd.DataFrame(error_logs)
    df_error.to_csv('error_logs_kanzou_20250930_last.csv', index=False, encoding='utf-8-sig')  # 日本語対応のためutf-8-sigで
    print("✅ エラーログを error_logs.csv に出力しました！")
else:
    print("✅ エラーはありませんでした！")