// ShogaishaFukushiSystem — 固有ロジック（共通coreへ委譲される per-system handle）
import { join } from "node:path";

// ── 障害福祉サービス 利用者負担上限月額の算定（標準仕様書: 所得区分により月額上限を決定）──
//   生活保護・低所得(非課税)=0円、一般1(課税・所得割16万円未満／障害児通所等は28万円未満)、一般2=37,200円。
//   一般1の月額上限: 障害者9,300円／障害児(通所・施設入所等)4,600円。金額は国基準（令和6年）。
function computeShogaiBurden({ isWelfare = false, taxNonTaxable = false, taxIncomeLevy = 0, isChild = false, homeOrChildService = false }) {
  if (isWelfare) return { category: "生活保護", monthlyCap: 0 };
  if (taxNonTaxable) return { category: "低所得（市町村民税非課税）", monthlyCap: 0 };
  const levy = Number(taxIncomeLevy) || 0;
  // 一般1の判定: 障害者=所得割16万円未満、障害児通所・居宅等=28万円未満
  const ippan1Limit = (isChild || homeOrChildService) ? 280000 : 160000;
  if (levy < ippan1Limit) {
    return { category: "一般1", monthlyCap: isChild ? 4600 : 9300 };
  }
  return { category: "一般2", monthlyCap: 37200 };
}

// ── 補装具費の自己負担計算（標準仕様書 0221100/0221110）─────────────────────────
// 見積額・基準額・所得区分の月額負担上限から、法定内自己負担(基準額の1割を月額上限でcap)・
// 超過分自己負担(見積額−基準額)・利用者負担額・公費負担額を算定する。
function computeHosoguFutan({ estimate = 0, standard = 0, monthlyCap = 37200, alreadyPaidThisMonth = 0 }) {
  const est = Math.max(0, Number(estimate) || 0);
  const std = Math.max(0, Number(standard) || 0);
  const cap = Math.max(0, Number(monthlyCap) || 0);
  const capRemaining = Math.max(0, cap - (Number(alreadyPaidThisMonth) || 0));
  // 法定内自己負担: 基準額の1割。ただし月額負担上限額(残)を超えない。
  const houteinaiJiko = Math.min(Math.floor(std * 0.1), capRemaining);
  // 基準額を超える見積分は全額自己負担（超過分）
  const choka = Math.max(0, est - std);
  const userBurden = houteinaiJiko + choka;
  const publicBurden = std - houteinaiJiko; // 基準額のうち公費が持つ分
  return {
    estimate: est, standard: std, monthlyCap: cap,
    法定内自己負担額: houteinaiJiko, 超過分自己負担額: choka,
    利用者負担額: userBurden, 公費負担額: publicBurden, 支給額: publicBurden,
    note: "補装具費: 法定内自己負担(基準額1割×月額上限)+超過分(見積−基準)。公費=基準額−法定内自己負担。",
  };
}
// ── 高額障害福祉サービス費の支給額計算（標準仕様書 0220777）────────────────────
// 合算対象(障害福祉/介護保険/障害児通所等)の利用者負担額合計が世帯の負担上限額を超える分を高額支給。
// 超過分は各制度の負担額で按分する。
function computeKogakuShogai({ burdens = [], householdCap = 37200 }) {
  const items = (Array.isArray(burdens) ? burdens : [])
    .map((b) => ({ type: b.type ?? "障害福祉", amount: Math.max(0, Number(b.amount) || 0) }));
  const total = items.reduce((s, b) => s + b.amount, 0);
  const cap = Math.max(0, Number(householdCap) || 0);
  const kogaku = Math.max(0, total - cap);
  // 高額支給額を負担額で按分（端数は最大負担へ寄せる）
  const allocations = items.map((b) => ({ type: b.type, amount: b.amount,
    shikyu: total > 0 ? Math.floor(kogaku * b.amount / total) : 0 }));
  const allocated = allocations.reduce((s, a) => s + a.shikyu, 0);
  if (kogaku - allocated > 0 && allocations.length) {
    const maxIdx = allocations.reduce((mi, a, i, arr) => a.amount > arr[mi].amount ? i : mi, 0);
    allocations[maxIdx].shikyu += kogaku - allocated;
  }
  return { 合算利用者負担額: total, 負担上限額: cap, 高額支給額: kogaku, allocations,
    note: "合算利用者負担額が世帯の負担上限額を超える分を高額障害福祉サービス費として支給(制度別按分)。" };
}

// ── 総合等級の指数合算による自動設定（標準仕様書 0221265）──
//   身体障害者障害程度等級の併合: 障害部位毎の等級コードを指数に換算して合算し、合計指数から総合等級を判定する。
//   指数表・合計指数→総合等級の閾値は身体障害認定基準準拠（パラメタで上書き可）。
const TOUKYU_SHISU = { 1: 18, 2: 11, 3: 7, 4: 4, 5: 2, 6: 1, 7: 0.5 };
const SHISU_TO_TOUKYU = [[18, 1], [11, 2], [7, 3], [4, 4], [2, 5], [1, 6], [0.5, 7]]; // [合計指数の下限, 総合等級]
function computeSougouToukyuu({ grades = [], shisuTable = null, thresholds = null } = {}) {
  const table = shisuTable ?? TOUKYU_SHISU;
  const th = thresholds ?? SHISU_TO_TOUKYU;
  const list = (Array.isArray(grades) ? grades : []).map((g) => Number(g)).filter((g) => g >= 1);
  if (!list.length) return { error: "grades(障害部位ごとの等級コード)が必要です" };
  const parts = list.map((g) => ({ grade: g, shisu: Number(table[g]) || 0 }));
  const totalShisu = parts.reduce((s, p) => s + p.shisu, 0);
  let sougou = null;
  for (const [low, tk] of th) { if (totalShisu >= low) { sougou = tk; break; } }
  return { 総合等級: sougou, 合計指数: totalShisu, parts,
    note: "障害部位ごとの等級を指数換算し合算、合計指数から総合等級を判定（身体障害認定基準準拠）。" };
}

// ── 手帳有効期限の自動計算（標準仕様書 0220392=当月末日 / 0220873等=応当日）──
//   kind="末日": 基準有効期限からN年後の当月末日（0220392）。kind="応当日": 有効期間開始日からN年後の応当日（0220873/0220951/0221025）。
//   いずれもうるう年対応（2/29起点は翌年2/28へクランプ）。転入時はスキップ。
function computeTechouYukoKigen({ baseDate, years = 2, isTennyuu = false, enabled = true, kind = "末日" } = {}) {
  const m = String(baseDate ?? "").match(/^(\d{4})-(\d{1,2})-(\d{1,2})/);
  if (!m) return { error: "baseDate(基準となる有効期限/有効期間開始日)はYYYY-MM-DDで指定してください" };
  if (!enabled || isTennyuu) return { autoCalculated: false, reason: isTennyuu ? "転入による申請は自動計算対象外" : "自動計算が無効", yukoKigen: null };
  const y = Number(m[1]) + Number(years), mo = Number(m[2]), d = Number(m[3]);
  const lastDay = new Date(Date.UTC(y, mo, 0)).getUTCDate(); // 当月末日（うるう年自動対応）
  const day = kind === "応当日" ? Math.min(d, lastDay) : lastDay; // 応当日はうるう年クランプ、末日は当月末日
  const yukoKigen = `${y}-${String(mo).padStart(2, "0")}-${String(day).padStart(2, "0")}`;
  return { autoCalculated: true, yukoKigen, years: Number(years), kind,
    note: kind === "応当日" ? "有効期間開始日から指定年数後の応当日（うるう年対応）" : "有効期限から指定年数後の当月末日（うるう年対応）" };
}

// ── 旧市町村民税所得割額の算出（標準仕様書 0220617/0220853/0220932/0221005/0221085）──
//   障害福祉の所得区分判定に用いる旧（ただし書き）市町村民税所得割額を、課税標準額×旧税率で算出（調整控除前）。
//   あわせて調整控除を考慮した額（調整控除後）も算出する。旧税率は標準で市町村民税6%（パラメタで上書き可）。
function computeKyuShotokuwari({ 課税標準額 = 0, 税率 = 0.06, 調整控除 = 0 } = {}) {
  const base = Math.max(0, Number(課税標準額) || 0);
  const rate = Number(税率) || 0;
  const adj = Math.max(0, Number(調整控除) || 0);
  const 調整控除前 = Math.floor(base * rate); // 旧市町村民税所得割額（調整控除前）
  const 調整控除後 = Math.max(0, 調整控除前 - adj); // 調整控除を考慮した算出
  return { 課税標準額: base, 税率: rate, 調整控除: adj, 旧所得割額_調整控除前: 調整控除前, 旧所得割額_調整控除後: 調整控除後,
    note: "旧市町村民税所得割額＝課税標準額×旧税率（既定6%）。調整控除前と、調整控除を考慮した後の両方を算出。" };
}

// ── 旅客運賃割引コードの障害等級からの自動設定（標準仕様書 0221385）──
//   身体障害者手帳の旅客運賃割引: 1〜3級＝第1種（本人・介護者が割引対象）、4〜6級＝第2種（本人のみ）。パラメタで上書き可。
function unrinWaribikiCode(grade, mapping = null) {
  const g = Number(grade);
  if (!(g >= 1)) return { error: "grade(障害等級コード)が必要です" };
  if (mapping && mapping[g] != null) return { 等級: g, 旅客運賃割引コード: String(mapping[g]) };
  return { 等級: g, 旅客運賃割引コード: g <= 3 ? "第1種" : "第2種",
    note: "1〜3級=第1種(本人・介護者)、4〜6級=第2種(本人のみ)" };
}

// ── 補足給付（グループホーム家賃助成）月額の自動判定（標準仕様書 0220620/0220621）──
//   対象（所得区分が生保/低所得等）の入居者に、家賃のうち月額上限まで助成する。
function computeKachinJosei({ rent = 0, incomeKubun = "", monthlyCap = 10000, targetKubun = ["生活保護", "低所得", "低所得1", "低所得2"] } = {}) {
  const r = Math.max(0, Number(rent) || 0);
  const cap = Math.max(0, Number(monthlyCap) || 0);
  const eligible = Array.isArray(targetKubun) ? targetKubun.includes(String(incomeKubun)) : true;
  const josei = eligible ? Math.min(r, cap) : 0;
  return { eligible, 家賃: r, 補足給付月額: josei, 月額上限: cap, incomeKubun: String(incomeKubun),
    note: "対象者(所得区分が生保/低所得等)に家賃のうち月額上限まで助成。" };
}

export async function handle(ctx) {
  const { req, res, pathname, searchParams, actor, send, sendRaw, parseBody, readJson, writeJson, audit,
    maskRecord, validateRecord, requireRole, hasRole, dataRoot, restrictionsFile, crypto,
    dbSelect, dbSelectOne, dbInsert, dbUpdate, dbDelete } = ctx;
  const reply = (...a) => { send(...a); return true; };
  const disabilityPersonsFile = join(dataRoot, "disability-persons.json");
  const disabilityServicesFile = join(dataRoot, "disability-services.json");

  // ── 他システムへの情報提供データセット（個人住民税/審査会/障害者総合支援/生活保護/子ども子育て 等）──
  //   汎用提供API: dataset種別ごとに台帳から必要項目だけを抽出して返し、提供先と件数を監査記録する。
  const PROVIDE_DATASETS = {
    "techo-shintai": { name: "身体障害者手帳情報", fields: ["atenaId", "fullName", "techoShintai"] },
    "techo-ryoiku": { name: "療育手帳情報", fields: ["atenaId", "fullName", "techoRyoiku"] },
    "techo-seishin": { name: "精神障害者保健福祉手帳情報", fields: ["atenaId", "fullName", "techoSeishin"] },
    "jiritsu-kosei": { name: "自立支援医療（更生医療）情報", fields: ["atenaId", "fullName", "jiritsuKosei"] },
    "jiritsu-seishin": { name: "自立支援医療（精神通院医療）情報", fields: ["atenaId", "fullName", "jiritsuSeishin"] },
    "jiritsu-ikusei": { name: "自立支援医療（育成医療）情報", fields: ["atenaId", "fullName", "jiritsuIkusei"] },
    "hosougu": { name: "補装具情報", fields: ["atenaId", "fullName", "hosougu"] },
    "teate": { name: "国制度手当・特別児童扶養手当情報", fields: ["atenaId", "fullName", "teate"] },
    "jukyu": { name: "サービス受給情報", fields: ["atenaId", "fullName", "services"] },
    "shinsakai-address": { name: "審査会向け 送付先・連絡先・メモ情報", fields: ["atenaId", "fullName", "address", "phone", "memo"] },
  };


    // ── 障害者 (Disability Persons) ───────────────────────────────────────

    const DISABILITY_TYPES = ["physical", "intellectual", "mental", "developmental"];

    if (pathname === "/api/disability-persons" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const persons = (await dbSelect("shogaisha.disability_person")) ?? await readJson(disabilityPersonsFile, []);
      const restrictions = await readJson(restrictionsFile, []);
      return reply(res, 200, persons.map((p) => maskRecord(actor, restrictions, p)));
    }

    if (pathname === "/api/disability-persons" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.fullName || !body.birthDate || !body.disabilityType) return reply(res, 400, { error: "fullName, birthDate, disabilityType required" });
      if (!DISABILITY_TYPES.includes(body.disabilityType)) return reply(res, 400, { error: `disabilityType must be one of: ${DISABILITY_TYPES.join(", ")}` });
      const persons = (await dbSelect("shogaisha.disability_person")) ?? await readJson(disabilityPersonsFile, []);
      const certificateNumber = "SH-" + Date.now().toString(36).toUpperCase();
      const person = { id: crypto.randomUUID(), certificateNumber, fullName: body.fullName, birthDate: body.birthDate, disabilityType: body.disabilityType, disabilityGrade: body.disabilityGrade ?? "", address: body.address ?? "", status: "certified", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("shogaisha.disability_person", person); if (dbInserted === null) { persons.push(person); await writeJson(disabilityPersonsFile, persons); }
      await audit(req, "disability-person.certify", { id: person.id, disabilityType: body.disabilityType });
      return reply(res, 201, person);
    }

    const disabilityPersonMatch = pathname.match(/^\/api\/disability-persons\/([^/]+)$/);
    if (disabilityPersonMatch) {
      requireRole(actor, "CLERK");
      const person = (await dbSelectOne("shogaisha.disability_person", disabilityPersonMatch[1]))
        ?? (await readJson(disabilityPersonsFile, [])).find((p) => p.id === disabilityPersonMatch[1]);
      if (!person) return reply(res, 404, { error: "person not found" });
      if (req.method === "GET") return reply(res, 200, person);
      if (req.method === "PUT") {
        const body = await parseBody(req);
        Object.assign(person, body, { id: person.id, certificateNumber: person.certificateNumber, updatedAt: new Date().toISOString() });
        const upd = await dbUpdate("shogaisha.disability_person", person.id, person); if (upd === null) { const wl = await readJson(disabilityPersonsFile, []); const wi = wl.findIndex((x) => x.id === person.id); if (wi !== -1) { wl[wi] = person; } await writeJson(disabilityPersonsFile, wl); }
        await audit(req, "disability-person.update", { id: person.id });
        return reply(res, 200, person);
      }
    }

    // ── 障害福祉サービス (Disability Services) ────────────────────────────

    if (pathname === "/api/disability-services" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const services = (await dbSelect("shogaisha.disability_service")) ?? await readJson(disabilityServicesFile, []);
      const { personId } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      return reply(res, 200, personId ? services.filter((s) => s.personId === personId) : services);
    }

    if (pathname === "/api/disability-services" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.personId || !body.serviceType || !body.startDate) return reply(res, 400, { error: "personId, serviceType, startDate required" });
      const persons = (await dbSelect("shogaisha.disability_person")) ?? await readJson(disabilityPersonsFile, []);
      if (!persons.find((p) => p.id === body.personId)) return reply(res, 404, { error: "person not found" });
      const services = (await dbSelect("shogaisha.disability_service")) ?? await readJson(disabilityServicesFile, []);
      const service = { id: crypto.randomUUID(), personId: body.personId, serviceType: body.serviceType, provider: body.provider ?? "", startDate: body.startDate, endDate: body.endDate ?? null, status: "active", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("shogaisha.disability_service", service); if (dbInserted === null) { services.push(service); await writeJson(disabilityServicesFile, services); }
      await audit(req, "disability-service.register", { id: service.id, serviceType: body.serviceType });
      return reply(res, 201, service);
    }


    const disabilityServiceMatch = pathname.match(/^\/api\/disability-services\/([^/]+)$/);
    if (disabilityServiceMatch && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, "CLERK");
      const id = disabilityServiceMatch[1];
      const item = (await dbSelectOne("shogaisha.disability_service", id))
        ?? (await readJson(disabilityServicesFile, [])).find((s) => s.id === id);
      if (!item) return reply(res, 404, { error: "disability service not found" });
      if (req.method === "GET") return reply(res, 200, item);

      if (item.status !== "active") return reply(res, 409, { error: "can only edit active disability services" });
      const body = await parseBody(req);
      const editable = ["serviceType", "provider", "startDate", "endDate"];
      const patch = {};
      for (const k of editable) if (body[k] !== undefined) patch[k] = body[k];
      patch.updatedAt = new Date().toISOString();
      patch.updatedBy = actor.id;
      const updated = await dbUpdate("shogaisha.disability_service", id, patch);
      let result = updated;
      if (updated === null) {
        const items = await readJson(disabilityServicesFile, []);
        const idx = items.findIndex((x) => x.id === id);
        items[idx] = { ...items[idx], ...patch };
        await writeJson(disabilityServicesFile, items);
        result = items[idx];
      }
      await audit(req, "disability-service.update", { id, fields: Object.keys(patch).filter((k) => k !== "updatedAt" && k !== "updatedBy") });
      return reply(res, 200, result);
    }

    const disabilityDeleteMatch = pathname.match(/^\/api\/disability-persons\/([^\/]+)$/);
    if (disabilityDeleteMatch && req.method === "DELETE") {
      requireRole(actor, "ADMIN");
      const delId = disabilityDeleteMatch[1];
      const deleted = await dbDelete("shogaisha.disability_person", delId);
      if (deleted === null) {
        const _items = await readJson(disabilityPersonsFile, []);
        const _idx = _items.findIndex((x) => x.id === delId);
        if (_idx === -1) return reply(res, 404, { error: "disability person not found" });
        _items.splice(_idx, 1);
        await writeJson(disabilityPersonsFile, _items);
      }
      await audit(req, "disability-person.delete", { id: delId });
      return reply(res, 204, {});
    }


    // ── 障害福祉サービス 利用者負担上限月額の算定（標準仕様書準拠）──
    if (pathname === "/api/shogai/burden" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      const result = computeShogaiBurden(body || {});
      await audit(req, "shogai.burden-calc", { category: result.category, monthlyCap: result.monthlyCap });
      return reply(res, 200, result);
    }

    // ── 総合等級の指数合算自動設定（0221265）──
    if (pathname === "/api/shogai/sougou-toukyuu" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeSougouToukyuu((await parseBody(req)) || {});
      if (result.error) return reply(res, 400, result);
      await audit(req, "shogai.sougou-toukyuu", { 総合等級: result.総合等級, 合計指数: result.合計指数 });
      return reply(res, 200, result);
    }
    // ── 手帳有効期限の自動計算（0220392）──
    if (pathname === "/api/shogai/techou-kigen" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeTechouYukoKigen((await parseBody(req)) || {});
      if (result.error) return reply(res, 400, result);
      return reply(res, 200, result);
    }
    // ── 旅客運賃割引コードの等級自動設定（0221385）──
    if (pathname === "/api/shogai/unrin-waribiki" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = (await parseBody(req)) || {};
      const result = unrinWaribikiCode(b.grade, b.mapping);
      if (result.error) return reply(res, 400, result);
      return reply(res, 200, result);
    }
    // ── 補足給付（家賃助成）月額の自動判定（0220620/0220621）──
    if (pathname === "/api/shogai/kachin-josei" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeKachinJosei((await parseBody(req)) || {});
      await audit(req, "shogai.kachin-josei", { eligible: result.eligible, 補足給付月額: result.補足給付月額 });
      return reply(res, 200, result);
    }
    // ── 旧市町村民税所得割額の算出（0220617/0220853/0220932/0221005/0221085・調整控除考慮）──
    if (pathname === "/api/shogai/kyu-shotokuwari" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeKyuShotokuwari((await parseBody(req)) || {});
      await audit(req, "shogai.kyu-shotokuwari", { 調整控除後: result.旧所得割額_調整控除後 });
      return reply(res, 200, result);
    }

    // ════ 他システム連携・審査会・独自施策判定・支援区分一次判定（標準仕様書）════

    // ── 情報提供（個人住民税・審査会・障害者総合支援・生活保護・子ども子育て等の他システム向け）──
    if (pathname === "/api/shogai/provide" && req.method === "GET") {
      requireRole(actor, "ADMIN"); // 外部提供は管理者権限＋監査必須
      const dataset = searchParams.get("dataset") ?? "";
      const targetSystem = searchParams.get("targetSystem") ?? "未指定";
      const def = PROVIDE_DATASETS[dataset];
      if (!def) return reply(res, 400, { error: `dataset は ${Object.keys(PROVIDE_DATASETS).join("/")} のいずれか` });
      const persons = await readJson(disabilityPersonsFile, []);
      const rows = persons.map((p) => Object.fromEntries(def.fields.map((f) => [f, p[f] ?? p.payload?.[f] ?? ""])));
      await audit(req, "shogai.provide", { dataset, name: def.name, targetSystem, count: rows.length });
      return reply(res, 200, { dataset, name: def.name, targetSystem, count: rows.length, rows });
    }
    if (pathname === "/api/shogai/provide" && req.method === "OPTIONS") {
      return reply(res, 200, { datasets: Object.entries(PROVIDE_DATASETS).map(([id, d]) => ({ id, name: d.name })) });
    }

    // ── 手帳情報の突合表示（台帳画面で3手帳の保有を明示的に気づける仕組み）──
    {
      const techoMatch = pathname.match(/^\/api\/shogai\/techo\/([^/]+)$/);
      if (techoMatch && req.method === "GET") {
        requireRole(actor, "CLERK");
        const persons = await readJson(disabilityPersonsFile, []);
        const person = persons.find((p) => p.id === techoMatch[1] || p.atenaId === techoMatch[1]);
        if (!person) return reply(res, 404, { error: "person not found" });
        const summary = {
          身体障害者手帳: !!(person.techoShintai ?? person.payload?.techoShintai),
          療育手帳: !!(person.techoRyoiku ?? person.payload?.techoRyoiku),
          精神障害者保健福祉手帳: !!(person.techoSeishin ?? person.payload?.techoSeishin),
        };
        return reply(res, 200, { atenaId: person.atenaId ?? person.id, fullName: person.fullName ?? "", techo: summary,
          highlight: Object.values(summary).some(Boolean) }); // true → 画面で強調表示
      }
    }

    // ── 審査会資料の個人情報マスキング（塗りつぶし=■置換）──
    if (pathname === "/api/shogai/shinsakai/material" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const text = String(b.text ?? "");
      const maskTargets = [b.fullName, b.address, b.phone, ...(Array.isArray(b.maskWords) ? b.maskWords : [])]
        .filter((w) => w && String(w).length >= 2).map(String);
      let masked = text;
      for (const w of maskTargets) masked = masked.replaceAll(w, "■".repeat(Math.min(w.length, 8)));
      await audit(req, "shogai.shinsakai-mask", { maskedWords: maskTargets.length });
      return reply(res, 200, { masked, maskedWords: maskTargets.length });
    }

    // ── 独自施策（上乗せ）の所得区分・負担上限月額の自動判定（自立支援医療・補装具）──
    //   標準の所得区分: 生活保護/低所得1/低所得2/中間所得1/中間所得2/一定所得以上（市町村民税所得割額で判定）
    if (pathname === "/api/shogai/jouge/judge" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const taxAmount = Number(b.municipalTaxAmount); // 市町村民税所得割額（年額）
      if (Number.isNaN(taxAmount)) return reply(res, 400, { error: "municipalTaxAmount required" });
      const onSeikatsuHogo = b.onSeikatsuHogo === true;
      const lowIncome1 = b.lowIncome1 === true; // 市町村民税非課税×本人収入80万円以下
      let bracket, cap;
      if (onSeikatsuHogo) { bracket = "生活保護"; cap = 0; }
      else if (taxAmount <= 0) { bracket = lowIncome1 ? "低所得1" : "低所得2"; cap = lowIncome1 ? 2500 : 5000; }
      else if (taxAmount < 33000) { bracket = "中間所得1"; cap = 5000; }
      else if (taxAmount < 235000) { bracket = "中間所得2"; cap = 10000; }
      else { bracket = "一定所得以上"; cap = b.juudoKeizoku === true ? 20000 : null; } // 重度かつ継続のみ対象、他は対象外
      const scheme = ["更生医療", "育成医療", "精神通院", "補装具"].includes(b.scheme) ? b.scheme : "更生医療";
      await audit(req, "shogai.jouge-judge", { scheme, bracket });
      return reply(res, 200, { scheme, bracket, monthlyCap: cap, eligible: cap !== null,
        note: cap === null ? "一定所得以上（重度かつ継続に非該当）のため対象外" : "" });
    }

    // ── 障害支援区分の一次判定（組込型の参照実装: 調査項目スコアの合算で区分1〜6）──
    if (pathname === "/api/shogai/kubun/judge" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.scores) || !b.scores.length) return reply(res, 400, { error: "scores[] required（認定調査項目の評点）" });
      const total = b.scores.reduce((sum, v) => sum + (Number(v) || 0), 0);
      const max = b.scores.length * 2; // 各項目0-2点想定
      const ratio = max ? total / max : 0;
      const kubun = ratio < 0.1 ? "非該当" : `区分${Math.min(6, Math.max(1, Math.ceil(ratio * 6)))}`;
      await audit(req, "shogai.kubun-judge", { kubun, total });
      return reply(res, 200, { kubun, total, items: b.scores.length,
        note: "一次判定（参照実装）。二次判定は市町村審査会で行う" });
    }

    // ════ EUC帳票・税情報履歴・所得判定/特定親族控除・手当支払・国保連一括・二次審査・特定疾病ほか（標準仕様書）════

    const shogaiTaxInfoFile = join(dataRoot, "shogai-tax-info.json");
    const shogaiConfigFile = join(dataRoot, "shogai-config.json");
    const shogaiPayFile = join(dataRoot, "shogai-payments.json");

    // ── EUC帳票（進達情報ファイル・受理簿・交付台帳）──
    if (pathname === "/api/shogai/euc/documents" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const DOC_TYPES = ["進達情報ファイル", "身体障害者手帳交付申請受理簿", "身体障害者手帳交付台帳",
        "療育手帳交付申請受理簿", "療育手帳交付台帳", "障害者手帳交付申請受理簿"];
      const rows = Array.isArray(b.rows) ? b.rows : [];
      if (!DOC_TYPES.includes(b.docType) || !rows.length) return reply(res, 400, { error: "docType, rows[] required", docTypes: DOC_TYPES });
      await audit(req, "shogai-euc.documents", { docType: b.docType, rows: rows.length });
      return reply(res, 200, { docType: b.docType, via: "EUC機能", count: rows.length, rows,
        ...(b.docType === "進達情報ファイル" ? { target: "都道府県" } : {}),
        ...(b.docType === "身体障害者手帳交付台帳" ? { basis: "身体障害者福祉法施行令 第9条" } : {}),
        ...(b.docType === "療育手帳交付台帳" ? { basis: "療育手帳制度の実施について 第6" } : {}) });
    }

    // ── EUC（共通機能標準仕様書準拠の演算子・抽出・分析・加工・出力）：指定条件で抽出→一覧確認→加工 ──
    if (pathname === "/api/shogai/euc/query" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const rows = Array.isArray(b.rows) ? b.rows : [];
      const conditions = Array.isArray(b.conditions) ? b.conditions : [];
      if (!rows.length) return reply(res, 400, { error: "rows[] required" });
      const OPS = {
        "=": (a, v) => String(a) === String(v), "≠": (a, v) => String(a) !== String(v),
        ">": (a, v) => Number(a) > Number(v), "<": (a, v) => Number(a) < Number(v),
        "≧": (a, v) => Number(a) >= Number(v), "≦": (a, v) => Number(a) <= Number(v),
        "部分一致": (a, v) => String(a).includes(String(v)),
        "前方一致": (a, v) => String(a).startsWith(String(v)),
        "後方一致": (a, v) => String(a).endsWith(String(v)),
      };
      const logic = b.logic === "or" ? "or" : "and";
      const test = (row) => conditions.map((c) => (OPS[c.op] ?? (() => false))(row[c.field], c.value));
      let hits = conditions.length ? rows.filter((r) => (logic === "and" ? test(r).every(Boolean) : test(r).some(Boolean))) : rows;
      if (Array.isArray(b.fields) && b.fields.length) hits = hits.map((r) => Object.fromEntries(b.fields.map((f) => [f, r[f]])));
      let aggregate = null;
      if (b.groupBy) {
        const groups = {};
        for (const r of hits) groups[String(r[b.groupBy])] = (groups[String(r[b.groupBy])] ?? 0) + 1;
        aggregate = Object.entries(groups).map(([key, count]) => ({ [b.groupBy]: key, count }));
      }
      await audit(req, "shogai-euc.query", { hits: hits.length });
      return reply(res, 200, { count: hits.length, results: hits, aggregate });
    }

    // ── 取込税情報の履歴・更正履歴の確認（マイナンバー利用目的の範囲内）──
    if (pathname === "/api/shogai/tax-info" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const allowedPurposes = ["障害者手帳事務", "自立支援給付の支給", "手当の支給"];
      if (!b.atenaId || !allowedPurposes.includes(b.purpose)) {
        return reply(res, 403, { error: "マイナンバー制度の利用目的の範囲外です", allowedPurposes });
      }
      const store = await readJson(shogaiTaxInfoFile, {});
      const entry = store[String(b.atenaId)] ?? { history: [], corrections: [] };
      if (b.income !== undefined) {
        if (entry.history.length && b.correction === true) {
          entry.corrections.push({ before: entry.history[0].income, after: Number(b.income), at: new Date().toISOString() });
        }
        entry.history.unshift({ income: Number(b.income), fiscalYear: b.fiscalYear ?? null, at: new Date().toISOString() });
        store[String(b.atenaId)] = entry;
        await writeJson(shogaiTaxInfoFile, store);
      }
      await audit(req, "shogai-tax-info", { atenaId: b.atenaId, purpose: b.purpose });
      return reply(res, 200, { atenaId: String(b.atenaId), purpose: b.purpose,
        history: entry.history, corrections: entry.corrections,
        note: "取込税情報は利用目的の範囲内で履歴・更正履歴とも確認可能" });
    }

    // ── 保存期間の任意指定（移行データ含む）──
    if (pathname === "/api/shogai/config/retention" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const b = await parseBody(req);
      if (!b.docKind || !Number(b.years)) return reply(res, 400, { error: "docKind, years required" });
      const config = await readJson(shogaiConfigFile, {});
      config.retention = { ...(config.retention ?? {}), [String(b.docKind)]: Number(b.years) };
      await writeJson(shogaiConfigFile, config);
      await audit(req, "shogai-config.retention", { docKind: b.docKind });
      return reply(res, 200, { ...config.retention, note: "移行データ含め団体が定める期間利用可能" });
    }

    // ── 自由記載欄の文字強調（フォント・サイズ・太字・斜体・下線・二重下線）──
    if (pathname === "/api/shogai/documents/free-text" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.text) return reply(res, 400, { error: "text required" });
      const styles = { font: b.font ?? "明朝", size: Number(b.size ?? 10.5),
        bold: b.bold === true, italic: b.italic === true,
        underline: b.underline === true, doubleUnderline: b.doubleUnderline === true };
      await audit(req, "shogai-documents.free-text", {});
      return reply(res, 200, { text: String(b.text), styles, supported: ["文字フォント", "文字サイズ", "太字", "斜体", "下線", "二重下線"] });
    }

    // ── 所得判定（本人・配偶者・扶養義務者・扶養義務者候補の支給/不支給＋自動該非＋特定親族特別控除）──
    if (pathname === "/api/shogai/income-judge" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const persons = Array.isArray(b.persons) ? b.persons : [];
      if (!persons.length) return reply(res, 400, { error: "persons[]{role,income,limit,tokuteiShinzokuDeduction?} required" });
      const results = persons.map((p) => {
        // 令和7年度税制改正: 特定親族特別控除額（最大63万円）を所得から控除して判定
        const deduction = Math.min(630000, Number(p.tokuteiShinzokuDeduction ?? 0));
        const judged = Number(p.income ?? 0) - deduction <= Number(p.limit ?? Infinity);
        return { role: p.role ?? "本人", income: Number(p.income ?? 0),
          tokuteiShinzokuDeduction: deduction, judgement: judged ? "支給" : "不支給" };
      });
      const overall = results.every((r) => r.judgement === "支給") ? "支給該当" : "支給非該当";
      await audit(req, "shogai-income.judge", { persons: results.length, overall });
      return reply(res, 200, { results, autoJudgement: overall,
        note: "所得判定後に自動で支給該非を判定（特定親族特別控除=令和7年度税制改正対応）" });
    }

    // ── 20歳到達者の一括資格喪失 ──
    if (pathname === "/api/shogai/teate/age20-loss" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const members = Array.isArray(b.members) ? b.members : [];
      if (!b.asOf || !members.length) return reply(res, 400, { error: "asOf, members[]{personId,birthDate} required" });
      const lost = members.filter((m) => {
        const age20 = m.birthDate?.replace(/^(\d{4})/, (y) => String(Number(y) + 20));
        return age20 && age20 <= b.asOf;
      }).map((m) => ({ personId: m.personId, status: "資格喪失（20歳到達）" }));
      await audit(req, "shogai-teate.age20-loss", { lost: lost.length });
      return reply(res, 200, { asOf: b.asOf, bulk: true, lost });
    }

    // ── 手当の支払（定例払い・随時払い×全銀協ファイル＝種別一括/分割×並び順×再作成）──
    if (pathname === "/api/shogai/teate/pay" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const modes = ["定例", "随時"];
      if (!modes.includes(b.mode)) return reply(res, 400, { error: `mode(${modes.join("/")}) required` });
      // 定例払い月（特別障害者手当等=2,5,8,11 / 特別児童扶養手当=4,8,11or12）。ファイル作成月は前月。
      const SCHEDULES = { "障害手当": { payMonths: [2, 5, 8, 11], fileMonths: [1, 4, 7, 10] },
        "特児手当": { payMonths: [4, 8, 11, 12], fileMonths: [3, 7, 10, 11] } };
      const sched = SCHEDULES[b.schedule ?? "障害手当"];
      if (b.mode === "定例" && b.fileMonth && !sched.fileMonths.includes(Number(b.fileMonth))) {
        return reply(res, 400, { error: `定例払いの支払用ファイル作成月は${sched.fileMonths.join("・")}月のみ（指定月は禁止）` });
      }
      const rows = Array.isArray(b.rows) ? b.rows : [];
      const kinds = ["特別障害者手当", "障害児福祉手当", "経過的福祉手当"];
      const sortKey = ["認定番号", "カナ氏名", "金融機関コード"].includes(b.sortBy) ? b.sortBy : "認定番号";
      const keyMap = { "認定番号": "ninteiNo", "カナ氏名": "kanaName", "金融機関コード": "bankCode" };
      const sorted = [...rows].sort((a, x) => String(a[keyMap[sortKey]] ?? "").localeCompare(String(x[keyMap[sortKey]] ?? ""), "ja"));
      let files;
      if (b.splitByKind === true) {
        files = kinds.map((k) => ({ file: `zengin_${k}.txt`, kind: k, records: sorted.filter((r) => r.teateKind === k).length }))
          .filter((f) => f.records > 0);
      } else {
        files = [{ file: "zengin_all.txt", kind: "全種別一括", records: sorted.length }];
      }
      const store = await readJson(shogaiPayFile, { lastRun: null });
      store.lastRun = { mode: b.mode, files, sortBy: sortKey, at: new Date().toISOString() };
      await writeJson(shogaiPayFile, store);
      await audit(req, "shogai-teate.pay", { mode: b.mode, files: files.length });
      return reply(res, 200, { mode: b.mode, trigger: b.mode === "随時" ? (b.trigger ?? "新規認定") : "定例",
        format: "全銀協フォーマット", sortBy: sortKey, files });
    }
    if (pathname === "/api/shogai/teate/pay/recreate" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const store = await readJson(shogaiPayFile, { lastRun: null });
      if (!store.lastRun) return reply(res, 404, { error: "直近の支払ファイルがありません" });
      await audit(req, "shogai-teate.pay-recreate", {});
      return reply(res, 200, { recreated: true, basedOn: store.lastRun,
        note: "直近の支払について全銀協ファイルをいつでも再作成可能" });
    }

    // ── 高額支払処理の再処理・取消＋全銀協再作成（既存高額・新高額）──
    if (pathname === "/api/shogai/kogaku/pay" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const actions = ["再処理", "取消", "全銀協再作成"];
      const kinds = ["既存高額", "新高額"];
      if (!actions.includes(b.action) || !kinds.includes(b.kind)) return reply(res, 400, { error: `action(${actions.join("/")}), kind(${kinds.join("/")}) required` });
      await audit(req, "shogai-kogaku.pay", { action: b.action, kind: b.kind });
      return reply(res, 200, { kind: b.kind, action: b.action, done: true });
    }

    // ── 対象者の同時抽出（障害福祉サービス＋障害児通所支援）──
    if (pathname === "/api/shogai/taisho/extract" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const subjects = Array.isArray(b.subjects) ? b.subjects : [];
      if (!subjects.length) return reply(res, 400, { error: "subjects[]{atenaId,category} required" });
      const fukushi = subjects.filter((s) => s.category === "障害福祉サービス");
      const tsusho = subjects.filter((s) => s.category === "障害児通所支援");
      await audit(req, "shogai-taisho.extract", { fukushi: fukushi.length, tsusho: tsusho.length });
      return reply(res, 200, { simultaneous: true,
        障害福祉サービス: fukushi.length, 障害児通所支援: tsusho.length,
        targets: [...fukushi, ...tsusho] });
    }

    // ── 国保連合会への送信情報の一括抽出・作成（7データセット）──
    if (pathname === "/api/shogai/kokuhoren/extract" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const b = await parseBody(req);
      const DATASETS = {
        "jukyusha-teisei": "受給者訂正連絡票情報・障害児支援受給者訂正連絡票情報",
        "totsugou": "受給者情報突合情報・障害児支援受給者情報突合情報",
        "kogaku-setai-teisei": "高額障害福祉サービス費世帯等訂正連絡票情報・高額障害児給付費世帯等訂正連絡票情報",
        "kojinbango-teisei": "個人番号訂正連絡票情報・障害児支援個人番号訂正連絡票情報",
        "kago-moshitate": "過誤申立書情報",
        "kogaku-hantei-kizon": "高額障害福祉サービス費給付判定結果情報・高額障害児給付費給付判定結果情報（既存高額）",
        "kogaku-hantei-shin": "高額障害福祉サービス等給付費給付判定結果情報（新高額・施行令第47条第6項）",
      };
      const name = DATASETS[b.dataset ?? ""];
      if (!name) return reply(res, 400, { error: `dataset は ${Object.keys(DATASETS).join("/")} のいずれか` });
      const rows = Array.isArray(b.rows) ? b.rows : [];
      await audit(req, "shogai-kokuhoren.extract", { dataset: b.dataset, rows: rows.length });
      return reply(res, 200, { dataset: b.dataset, name, target: "国保連合会", bulk: true,
        count: rows.length, rows });
    }

    // ── 市町村二次審査（一次審査結果＝警告（重度）/警告→返戻確認・正常も審査可）──
    if (pathname === "/api/shogai/shinsa/secondary" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const results = Array.isArray(b.results) ? b.results : [];
      if (!results.length) return reply(res, 400, { error: "results[]{claimId,primary(警告（重度）/警告/正常)} required" });
      const reviewed = results.map((r) => ({ claimId: String(r.claimId), primary: r.primary,
        secondary: ["警告（重度）", "警告"].includes(r.primary) ? "返戻候補（要確認）" : "二次審査対象（通過可）" }));
      await audit(req, "shogai-shinsa.secondary", { reviewed: reviewed.length });
      return reply(res, 200, { kind: "市町村二次審査", reviewed });
    }

    // ── 特定疾病療養受療のチェック（認定の有無×適用有無の相違を医療機関×月単位で検出）──
    if (pathname === "/api/shogai/tokutei-shippei/check" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const records = Array.isArray(b.records) ? b.records : [];
      if (!records.length) return reply(res, 400, { error: "records[]{hospitalId,month,ninteiUmu,tekiyoUmu} required" });
      const mismatches = records.filter((r) => Boolean(r.ninteiUmu) !== Boolean(r.tekiyoUmu))
        .map((r) => ({ hospitalId: r.hospitalId, month: r.month,
          issue: "特定疾病療養受療認定の有無と特定疾病療養費の適用有無に相違" }));
      await audit(req, "shogai-tokutei-shippei.check", { checked: records.length, mismatches: mismatches.length });
      return reply(res, 200, { checked: records.length, unit: "1医療機関×1か月", ok: mismatches.length === 0, mismatches });
    }

    // ── 判定依頼書＋調査書のセット出力 ──
    if (pathname === "/api/shogai/documents/hantei-set" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.personId) return reply(res, 400, { error: "personId required" });
      const set = b.set !== false;
      await audit(req, "shogai-documents.hantei-set", { personId: b.personId, set });
      return reply(res, 200, { personId: String(b.personId),
        documents: set ? ["判定依頼書", "調査書"] : ["判定依頼書"], setOutput: set });
    }

    // ── 現況届のバーコード対象者確定 ──
    if (pathname === "/api/shogai/genkyo/barcode" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const m = String(b.barcode ?? "").match(/^SHOG-(\d{4})-(.+)$/);
      if (!m) return reply(res, 400, { error: "barcode形式エラー（SHOG-YYYY-personId）" });
      await audit(req, "shogai-genkyo.barcode", { personId: m[2] });
      return reply(res, 200, { personId: m[2], fiscalYear: Number(m[1]), confirmed: true,
        note: "現況時にバーコード読込で対象者を確定" });
    }

    // ── 補装具費の自己負担計算（見積額・基準額・所得区分の月額負担上限→法定内/超過/利用者/公費）──
    if (pathname === "/api/shogai/futan/hosogu" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (b.standard === undefined || b.estimate === undefined) {
        return reply(res, 400, { error: "estimate(見積額), standard(基準額) が必須です" });
      }
      // 月額上限が未指定なら所得区分から算定
      const monthlyCap = b.monthlyCap !== undefined ? Number(b.monthlyCap)
        : computeShogaiBurden(b.burden || {}).monthlyCap;
      const result = computeHosoguFutan({ ...b, monthlyCap });
      await audit(req, "shogai-futan.hosogu", { userBurden: result.利用者負担額, public: result.公費負担額 });
      return reply(res, 200, result);
    }

    // ── 高額障害福祉サービス費の支給額計算（合算利用者負担額−世帯負担上限額を制度別按分）──
    if (pathname === "/api/shogai/futan/kogaku" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.burdens) || b.burdens.length === 0) {
        return reply(res, 400, { error: "burdens[]{type,amount}（合算対象の利用者負担額）が必須です" });
      }
      const result = computeKogakuShogai(b);
      await audit(req, "shogai-futan.kogaku", { total: result.合算利用者負担額, kogaku: result.高額支給額 });
      return reply(res, 200, result);
    }

    // ── 利用者負担上限月額の認定（所得区分判定: 生活保護/低所得/一般1/一般2）──
    if (pathname === "/api/shogai/futan/jogen" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const result = computeShogaiBurden(b);
      await audit(req, "shogai-futan.jogen", { category: result.category, cap: result.monthlyCap });
      return reply(res, 200, result);
    }

    // ── 給付実績統計（指定集計期間で 障害区分別・所得区分別・障害支援区分別 × サービス種類毎に
    //   利用者数・利用量・サービス単位数を集計）標準仕様書 0220744 ──
    if (pathname === "/api/shogai/jisseki/shukei" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const records = Array.isArray(b.records) ? b.records : [];
      if (records.length === 0) return reply(res, 400, { error: "records[]（給付実績）が必須です" });
      const inRange = (ym) => (!b.from || ym >= b.from) && (!b.to || ym <= b.to);
      const axes = { 障害区分別: "shogaiKubun", 所得区分別: "shotokuKubun", 障害支援区分別: "shienKubun" };
      const result = {};
      for (const [label, key] of Object.entries(axes)) {
        const byKey = {};
        for (const r of records) {
          const ym = String(r.serviceMonth ?? r.shinsaMonth ?? "");
          if (!inRange(ym)) continue;
          const k = String(r[key] ?? "未設定");
          const svc = String(r.serviceType ?? "計");
          ((byKey[k] ??= {})[svc] ??= { 利用者数: new Set(), 利用量: 0, サービス単位数: 0 });
          byKey[k][svc].利用者数.add(r.atenaId ?? r.id ?? Math.random());
          byKey[k][svc].利用量 += Number(r.amount) || 0;
          byKey[k][svc].サービス単位数 += Number(r.units) || 0;
        }
        // Set→件数へ変換
        for (const k of Object.keys(byKey)) for (const svc of Object.keys(byKey[k])) {
          byKey[k][svc].利用者数 = byKey[k][svc].利用者数.size;
        }
        result[label] = byKey;
      }
      await audit(req, "shogai-jisseki.shukei", { from: b.from, to: b.to, records: records.length });
      return reply(res, 200, { period: { from: b.from ?? null, to: b.to ?? null }, 集計: result,
        note: "給付実績を障害区分別・所得区分別・障害支援区分別×サービス種類毎に集計" });
    }

    // ── 身体障害者手帳等の手帳情報の管理（登録・修正・削除・照会）─────────────────────────
    //   管理項目(初回交付日/手帳番号/障害部位・等級コード/再判定/旅客運賃割引コード等)を保持しCRUDする。
    //   ※等級指数合算・旅客運賃割引コードの自動設定・手帳有効期限の算定は別エンドポイント(R87)で実装済。
    const techoFile = join(dataRoot, "shogai-techo.json");
    if (pathname === "/api/shogai/techo-info" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 手帳情報の照会（宛名番号別フィルタ可）
      const atena = searchParams.get("宛名番号") ?? searchParams.get("atenaId");
      const all = await readJson(techoFile, []);
      const list = atena ? all.filter((x) => String(x.宛名番号 ?? "") === String(atena)) : all;
      return reply(res, 200, { 件数: list.length, 手帳一覧: list });
    }
    if (pathname === "/api/shogai/techo-info" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 手帳情報の登録
      const b = await parseBody(req);
      if (!b.手帳番号 && !b.宛名番号) return reply(res, 400, { error: "手帳番号 または 宛名番号 が必要です" });
      const all = await readJson(techoFile, []);
      const { id: _ignore, createdAt: _c, ...fields } = b || {};
      const item = { id: crypto.randomUUID(), ...fields, createdAt: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(techoFile, all);
      await audit(req, "shogai-techo.create", { id: item.id, 手帳番号: item.手帳番号 ?? null });
      return reply(res, 201, item);
    }
    const techoItemMatch = pathname.match(/^\/api\/shogai\/techo-info\/([^/]+)$/);
    if (techoItemMatch) {
      const all = await readJson(techoFile, []);
      const idx = all.findIndex((x) => x.id === techoItemMatch[1]);
      if (req.method === "GET") { // 照会
        requireRole(actor, "CLERK");
        if (idx === -1) return reply(res, 404, { error: "手帳情報が見つかりません" });
        return reply(res, 200, all[idx]);
      }
      if (req.method === "PUT") { // 修正（任意の管理項目を更新）
        requireRole(actor, "CLERK");
        if (idx === -1) return reply(res, 404, { error: "手帳情報が見つかりません" });
        const b = await parseBody(req);
        const { id: _i, createdAt: _c, ...patch } = b || {};
        all[idx] = { ...all[idx], ...patch, updatedAt: new Date().toISOString() };
        await writeJson(techoFile, all);
        await audit(req, "shogai-techo.update", { id: all[idx].id });
        return reply(res, 200, all[idx]);
      }
      if (req.method === "DELETE") { // 削除
        requireRole(actor, "CLERK");
        if (idx === -1) return reply(res, 404, { error: "手帳情報が見つかりません" });
        const [removed] = all.splice(idx, 1);
        await writeJson(techoFile, all);
        await audit(req, "shogai-techo.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

    // ── 自立支援医療等の指定医療機関情報のマスタ管理（登録・修正・削除・照会・一覧）─────────
    const shiteiIryoFile = join(dataRoot, "shogai-shitei-iryo.json");
    if (pathname === "/api/shogai/shitei-iryo" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 一覧確認（医療機関番号/事業フィルタ可）
      const num = searchParams.get("医療機関番号") ?? searchParams.get("医療機関コード");
      const all = await readJson(shiteiIryoFile, []);
      const list = num ? all.filter((x) => [x.医療機関番号, x.医療機関コード].map((v) => String(v ?? "")).includes(String(num))) : all;
      return reply(res, 200, { 件数: list.length, 指定医療機関一覧: list });
    }
    if (pathname === "/api/shogai/shitei-iryo" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 登録
      const b = await parseBody(req);
      if (!b.医療機関番号 && !b.医療機関コード && !b.医療機関名称) return reply(res, 400, { error: "医療機関番号 または 医療機関コード または 医療機関名称 が必要です" });
      const all = await readJson(shiteiIryoFile, []);
      const { id: _i, createdAt: _c, ...fields } = b || {};
      const item = { id: crypto.randomUUID(), ...fields, createdAt: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(shiteiIryoFile, all);
      await audit(req, "shogai-shitei-iryo.create", { id: item.id, 医療機関番号: item.医療機関番号 ?? null });
      return reply(res, 201, item);
    }
    const shiteiIryoItemMatch = pathname.match(/^\/api\/shogai\/shitei-iryo\/([^/]+)$/);
    if (shiteiIryoItemMatch) {
      const all = await readJson(shiteiIryoFile, []);
      const idx = all.findIndex((x) => x.id === shiteiIryoItemMatch[1]);
      if (req.method === "GET") { requireRole(actor, "CLERK"); return idx === -1 ? reply(res, 404, { error: "指定医療機関情報が見つかりません" }) : reply(res, 200, all[idx]); }
      if (req.method === "PUT") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "指定医療機関情報が見つかりません" });
        const b = await parseBody(req); const { id: _i, createdAt: _c, ...patch } = b || {};
        all[idx] = { ...all[idx], ...patch, updatedAt: new Date().toISOString() };
        await writeJson(shiteiIryoFile, all); await audit(req, "shogai-shitei-iryo.update", { id: all[idx].id });
        return reply(res, 200, all[idx]);
      }
      if (req.method === "DELETE") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "指定医療機関情報が見つかりません" });
        const [removed] = all.splice(idx, 1);
        await writeJson(shiteiIryoFile, all); await audit(req, "shogai-shitei-iryo.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

    // ── 保険者情報の管理（登録・修正・削除・一覧確認）: 医療保険者等の保険者情報を保持し一覧で確認 ──
    const hokenshaFile = join(dataRoot, "shogai-hokensha.json");
    if (pathname === "/api/shogai/hokensha" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 保険者情報を一覧で確認
      const num = searchParams.get("保険者番号");
      const all = await readJson(hokenshaFile, []);
      const list = num ? all.filter((x) => String(x.保険者番号 ?? "") === String(num)) : all;
      return reply(res, 200, { 件数: list.length, 保険者一覧: list });
    }
    if (pathname === "/api/shogai/hokensha" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 保険者情報の登録
      const b = await parseBody(req);
      if (!b.保険者番号 && !b.保険者名) return reply(res, 400, { error: "保険者番号 または 保険者名 が必要です" });
      const all = await readJson(hokenshaFile, []);
      const { id: _i, createdAt: _c, ...fields } = b || {};
      const item = { id: crypto.randomUUID(), ...fields, createdAt: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(hokenshaFile, all);
      await audit(req, "shogai-hokensha.create", { id: item.id, 保険者番号: item.保険者番号 ?? null });
      return reply(res, 201, item);
    }
    const hokenshaItemMatch = pathname.match(/^\/api\/shogai\/hokensha\/([^/]+)$/);
    if (hokenshaItemMatch && (req.method === "PUT" || req.method === "DELETE" || req.method === "GET")) {
      const all = await readJson(hokenshaFile, []);
      const idx = all.findIndex((x) => x.id === hokenshaItemMatch[1]);
      if (idx === -1) return reply(res, 404, { error: "保険者情報が見つかりません" });
      requireRole(actor, "CLERK");
      if (req.method === "GET") return reply(res, 200, all[idx]);
      if (req.method === "PUT") {
        const b = await parseBody(req); const { id: _i, createdAt: _c, ...patch } = b || {};
        all[idx] = { ...all[idx], ...patch, updatedAt: new Date().toISOString() };
        await writeJson(hokenshaFile, all); await audit(req, "shogai-hokensha.update", { id: all[idx].id });
        return reply(res, 200, all[idx]);
      }
      const [removed] = all.splice(idx, 1);
      await writeJson(hokenshaFile, all); await audit(req, "shogai-hokensha.delete", { id: removed.id });
      return reply(res, 200, { deleted: true, id: removed.id });
    }

    // ── 障害福祉の各種情報の管理（登録・修正・削除・照会）: 住登外/手帳・医療の申請届出/所得限度額マスタ/事業所/合議体委員/審査会日程/過誤申立/補装具台帳等を種別別CRUD保持 ──
    const shogaiRecordsFile = join(dataRoot, "shogai-records.json");
    if (pathname === "/api/shogai/records" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 照会（種別/宛名番号フィルタ可）
      const 種別 = searchParams.get("種別"), atena = searchParams.get("宛名番号") ?? searchParams.get("atenaId");
      const all = await readJson(shogaiRecordsFile, []);
      const list = all.filter((x) => (!種別 || x.種別 === 種別) && (!atena || String(x.宛名番号 ?? x.atenaId ?? "") === String(atena)));
      return reply(res, 200, { 件数: list.length, records: list });
    }
    if (pathname === "/api/shogai/records" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 登録
      const b = await parseBody(req);
      if (!b.種別) return reply(res, 400, { error: "種別(住登外/手帳申請届出/所得限度額/事業所/合議体委員/審査会日程/過誤申立/補装具台帳 等) が必要です" });
      const all = await readJson(shogaiRecordsFile, []);
      const { id: _i, createdAt: _c, ...fields } = b || {};
      const item = { id: crypto.randomUUID(), ...fields, createdAt: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(shogaiRecordsFile, all);
      await audit(req, "shogai-record.create", { id: item.id, 種別: item.種別 });
      return reply(res, 201, item);
    }
    const shogaiRecordItemMatch = pathname.match(/^\/api\/shogai\/records\/([^/]+)$/);
    if (shogaiRecordItemMatch) {
      const all = await readJson(shogaiRecordsFile, []);
      const idx = all.findIndex((x) => x.id === shogaiRecordItemMatch[1]);
      if (req.method === "GET") { requireRole(actor, "CLERK"); return idx === -1 ? reply(res, 404, { error: "記録が見つかりません" }) : reply(res, 200, all[idx]); }
      if (req.method === "PUT") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "記録が見つかりません" });
        const b = await parseBody(req); const { id: _i, createdAt: _c, ...patch } = b || {};
        all[idx] = { ...all[idx], ...patch, updatedAt: new Date().toISOString() };
        await writeJson(shogaiRecordsFile, all); await audit(req, "shogai-record.update", { id: all[idx].id });
        return reply(res, 200, all[idx]);
      }
      if (req.method === "DELETE") {
        requireRole(actor, "CLERK"); if (idx === -1) return reply(res, 404, { error: "記録が見つかりません" });
        const [removed] = all.splice(idx, 1);
        await writeJson(shogaiRecordsFile, all); await audit(req, "shogai-record.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

  return false;
}
