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

// ── 介護保険料（第1号被保険者）算定（標準仕様書: 基準額×所得段階別係数。
//   基準額・段階係数は市町村の条例パラメータ。既定は国の標準13段階（令和6〜8年度）の係数）──
const KAIGO_COEFFICIENTS = [0.285, 0.485, 0.685, 0.90, 1.00, 1.20, 1.30, 1.50, 1.70, 1.90, 2.10, 2.30, 2.40];
// 所得段階の判定（標準13段階）。criteria: 生活保護等/世帯課税状況/本人課税/判定所得・合計所得
function determineKaigoStage({ isWelfareOrOldAgePension = false, householdAllNonTaxable = false, selfTaxable = false, judgeIncome = 0, totalIncome = 0 }) {
  const ji = Number(judgeIncome) || 0;   // 課税年金収入額＋その他の合計所得金額（第1〜5段階の判定）
  const ti = Number(totalIncome) || 0;   // 合計所得金額（第6段階以降の判定）
  if (!selfTaxable) {
    if (householdAllNonTaxable) {
      // 世帯全員が住民税非課税
      if (isWelfareOrOldAgePension || ji <= 800000) return 1;
      if (ji <= 1200000) return 2;
      return 3;
    }
    // 本人非課税・世帯に課税者あり
    return ji <= 800000 ? 4 : 5;
  }
  // 本人が住民税課税（合計所得金額で区分）
  if (ti < 1200000) return 6;
  if (ti < 2100000) return 7;
  if (ti < 3200000) return 8;
  if (ti < 4200000) return 9;
  if (ti < 5200000) return 10;
  if (ti < 6200000) return 11;
  if (ti < 7200000) return 12;
  return 13;
}
function computeKaigoPremium({ baseAnnual, stage, ...criteria }, coefficients = KAIGO_COEFFICIENTS) {
  const base = Number(baseAnnual) || 0;
  const st = stage != null ? Number(stage) : determineKaigoStage(criteria);
  const idx = Math.min(coefficients.length, Math.max(1, st)) - 1;
  const coefficient = coefficients[idx];
  const annual = Math.floor(base * coefficient);
  return { stage: idx + 1, coefficient, annual, monthly: Math.floor(annual / 12) };
}

// ── 月割賦課（標準仕様書 0230388/0230389）。資格取得月〜喪失前月の月数で年額を按分 ──
const _kaigoMonthIdx = (m) => { // 4月=0 … 3月=11
  if (m == null || m === "") return null;
  const mm = typeof m === "string" && m.includes("-") ? Number(m.split("-")[1]) : Number(m);
  return ((mm - 4) % 12 + 12) % 12;
};
function computeKaigoTsukiwari({ annual = 0, acquireMonth = null, loseMonth = null }) {
  const acq = _kaigoMonthIdx(acquireMonth);
  const lose = _kaigoMonthIdx(loseMonth);
  const startIdx = acq == null ? 0 : acq;          // 取得月から賦課
  const endIdx = lose == null ? 11 : lose - 1;     // 喪失月の前月まで賦課
  const months = Math.max(0, endIdx - startIdx + 1);
  const prorated = Math.floor((Number(annual) || 0) * months / 12);
  return { annual: Number(annual) || 0, months, prorated, note: "資格取得月〜喪失前月の月数で月割賦課" };
}
// ── 期割（普通徴収）。均等／資格喪失の直近納期まとめ／賦課更正増額の直近〜最終納期按分 ──
function computeKaigoKiwari({ amount = 0, periods = 8, mode = "均等", alreadyCollected = 0, currentPeriod = 1 }) {
  const amt = Math.max(0, (Number(amount) || 0) - (Number(alreadyCollected) || 0));
  const total = Math.max(1, Number(periods) || 1);
  if (mode === "喪失まとめ") {
    return { mode, installments: [{ period: Number(currentPeriod) || total, amount: amt }],
      note: "資格喪失: 普通徴収の残額を直近納期でまとめて徴収" };
  }
  const from = mode === "増額按分" ? (Number(currentPeriod) || 1) : 1;
  const n = total - from + 1;
  const per = Math.floor(amt / n / 100) * 100;
  const rem = amt - per * n;
  const installments = [];
  for (let p = from; p <= total; p++) installments.push({ period: p, amount: per + (p === from ? rem : 0) });
  return { mode, perPeriod: per, installments, note: "期割: 直近納期〜最終納期で按分（端数は初回期）" };
}
// ── 仮算定（標準仕様書 0230360-0230364）。特別徴収=前年度最終期別を4/6/8月へ、普通徴収=前年度確定額で暫定賦課 ──
function computeKaigoKariSantei({ type = "特別徴収", prevFinalPeriodAmount = 0, prevAnnual = 0 }) {
  if (type === "普通徴収") {
    return { type, 暫定賦課年額: Number(prevAnnual) || 0, note: "普通徴収: 前年度確定保険料額をもとに暫定賦課" };
  }
  const per = Math.floor(Number(prevFinalPeriodAmount) || 0);
  return { type, 仮徴収: [{ month: 4, amount: per }, { month: 6, amount: per }, { month: 8, amount: per }],
    total: per * 3, note: "特別徴収: 前年度最終徴収期別保険料を4/6/8月の仮徴収額に設定" };
}
// ── 仮徴収額の平準化（標準仕様書 0230367/0230370）。年6期(4/6/8/10/12/2月)で均等化 ──
function computeKaigoHeijunka({ annualEstimate = 0, aprilProvisional = 0, mode = "6月8月" }) {
  const annual = Math.max(0, Number(annualEstimate) || 0);
  const ideal = Math.floor(annual / 6 / 100) * 100;
  const april = Number(aprilProvisional) || 0;
  const jun = ideal;
  const aug = ideal;
  const hon = Math.max(0, annual - (april + jun + aug)); // 本徴収(10/12/2)合計
  const honPer = Math.floor(hon / 3 / 100) * 100;
  return { mode, annualEstimate: annual, ideal,
    仮徴収: { "4月": april, "6月": jun, "8月": aug },
    本徴収: { "10月": honPer, "12月": honPer, "2月": hon - honPer * 2 },
    note: "仮徴収額の平準化: 年6期で均等化（4月は確定、6月・8月を平準化対象として調整）" };
}

// ── 利用者負担割合の判定（標準仕様書 0230584-0230586「介護保険負担割合証」）──
//   第1号被保険者(65歳以上)の合計所得金額と年金収入＋その他合計所得で1割/2割/3割を判定。
function computeKaigoFutanWariai({ 合計所得金額 = 0, 年金収入その他 = 0, 世帯65歳以上人数 = 1, 第2号 = false }) {
  if (第2号) return { 割合: 1, 区分: "第2号被保険者", note: "40〜64歳の第2号被保険者は一律1割" };
  const inc = Math.max(0, Number(合計所得金額) || 0);
  const yos = Math.max(0, Number(年金収入その他) || 0);
  const single = (Number(世帯65歳以上人数) || 1) <= 1;
  // 3割: 合計所得220万以上 かつ 年金収入+その他合計所得が単身340万/2人以上463万以上
  if (inc >= 2200000 && yos >= (single ? 3400000 : 4630000)) return { 割合: 3, 区分: "3割負担", 合計所得金額: inc };
  // 2割: 合計所得160万以上 かつ 年金収入+その他が単身280万/2人以上346万以上
  if (inc >= 1600000 && yos >= (single ? 2800000 : 3460000)) return { 割合: 2, 区分: "2割負担", 合計所得金額: inc };
  return { 割合: 1, 区分: "1割負担", 合計所得金額: inc };
}
// ── 特定入所者介護サービス費（補足給付）の算定（標準仕様書 0231444/0231478）──
//   施設の食費・居住費について、基準費用額と利用者負担段階別の負担限度額の差額を日数分給付する。
function computeHosokuKyufu({ 食費基準費用額 = 0, 食費負担限度額 = 0, 居住費基準費用額 = 0, 居住費負担限度額 = 0, 日数 = 30 }) {
  const days = Math.max(0, Number(日数) || 0);
  const 食費差額 = Math.max(0, (Number(食費基準費用額) || 0) - (Number(食費負担限度額) || 0));
  const 居住費差額 = Math.max(0, (Number(居住費基準費用額) || 0) - (Number(居住費負担限度額) || 0));
  return {
    日数: days, 食費補足: 食費差額 * days, 居住費補足: 居住費差額 * days,
    補足給付額: (食費差額 + 居住費差額) * days,
    note: "補足給付＝(食費基準費用額−食費負担限度額＋居住費基準費用額−居住費負担限度額)×日数",
  };
}

// ── 認定調査委託料／意見書作成料の計算（標準仕様書 0231396/0231397）──
//   予定／実績の在宅施設区分×新規継続区分から単価を引き、消費税・診断検査費用・その他費用を加えて合計額を求める。
//   単価は rates[]（{ zaitakuShisetsu?, shinkiKeizoku?, tanka }）から区分一致で解決。直接 tanka 指定も可。
function computeItakuryo({ tanka, rates = null, zaitakuShisetsu, shinkiKeizoku, shohizeiRate = 0.1, taxable = true, otherCosts = [] } = {}) {
  let unit = Number(tanka);
  let applied = null;
  if (!Number.isFinite(unit) && Array.isArray(rates)) {
    const r = rates.find((x) => (x.zaitakuShisetsu == null || String(x.zaitakuShisetsu) === String(zaitakuShisetsu))
      && (x.shinkiKeizoku == null || String(x.shinkiKeizoku) === String(shinkiKeizoku)));
    if (r) { unit = Number(r.tanka); applied = r; }
  }
  if (!Number.isFinite(unit)) return { error: "tanka(単価) もしくは区分一致する rates[] が必要です" };
  unit = Math.max(0, unit);
  const 消費税 = taxable ? Math.floor(unit * (Number(shohizeiRate) || 0)) : 0;
  const others = (Array.isArray(otherCosts) ? otherCosts : []).map((v) => Math.max(0, Number(v) || 0));
  const otherTotal = others.reduce((s, v) => s + v, 0);
  const 合計額 = unit + 消費税 + otherTotal;
  return { 単価: unit, 消費税, その他費用合計: otherTotal, 合計額, ...(applied ? { appliedRate: applied } : {}),
    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 ltcInsuredFile = join(dataRoot, "ltc-insured.json");
  const careAssessmentsFile = join(dataRoot, "care-assessments.json");
  const careServicesFile = join(dataRoot, "care-services.json");
  const kaigoShokanFile = join(dataRoot, "kaigo-shokan.json");
  const kaigoKogakuAccountsFile = join(dataRoot, "kaigo-kogaku-accounts.json");
  const kaigoReentryFile = join(dataRoot, "kaigo-reentry.json");
  const kaigoPayMethodFile = join(dataRoot, "kaigo-pay-methods.json");
  const kaigoShunoFile = join(dataRoot, "kaigo-shuno.json");
  const kaigoConsentFile = join(dataRoot, "kaigo-consents.json");
  const kaigoSaishinsaFile = join(dataRoot, "kaigo-saishinsa.json");

  // 他システムへの情報提供データセット（認定審査会・国保連合会・地域包括・障害福祉・金融機関）
  const KAIGO_DATASETS = {
    "nintei-shinsei": { name: "要介護認定情報（認定申請情報等）", target: "認定審査会システム" },
    "nintei-chosa": { name: "認定調査情報・認定調査詳細情報", target: "認定審査会システム" },
    "ikensho": { name: "意見書情報・意見書詳細情報", target: "認定審査会システム" },
    "shinsakai-taisho": { name: "認定審査会対象者情報", target: "認定審査会システム" },
    "chosa-yotei": { name: "認定調査予定情報", target: "認定審査会システム" },
    "shakin-hoshu": { name: "謝金・報酬支払情報（調査委託料・意見書作成料・審査員報酬）", target: "認定審査会システム" },
    "jigyosho": { name: "事業所情報（種別・口座・送付先・所属者・職種）", target: "認定審査会システム" },
    "master": { name: "マスタ情報（保険者・他自治体・金融機関・店舗・医療保険者）", target: "認定審査会システム" },
    "jukyusha-teisei": { name: "受給者訂正連絡票情報", target: "国保連合会" },
    "kojinbango-teisei": { name: "個人番号訂正連絡票情報", target: "国保連合会" },
    "jukyusha-totsugou": { name: "受給者情報突合情報", target: "国保連合会" },
    "kago-tsujo": { name: "介護給付費過誤申立書情報（通常）", target: "国保連合会" },
    "kago-dogetsu": { name: "介護給付費過誤申立書情報（同月）", target: "国保連合会" },
    "chiiki-houkatsu": { name: "要介護認定情報等", target: "地域包括支援センター" },
    "kogaku-shogai": { name: "介護保険資格・高額給付情報", target: "障害者福祉システム" },
    "zengin-furikomi": { name: "口座振込情報（全銀協フォーマット・償還払/高額/委託料）", target: "金融機関" },
    "gogitai-shinsakai": { name: "合議体情報・認定審査会情報", target: "認定審査会システム（認定ソフト以外）" },
    "dokuji-sesaku": { name: "認定情報等（認定調査・意見書・対象者・要介護認定）", target: "独自施策システム" },
    "saishinsa-moshitate": { name: "介護給付費再審査申立書情報（保険者分）", target: "国保連合会" },
  };


    // ── 介護保険 被保険者 (LTC Insured) ──────────────────────────────────

    if (pathname === "/api/ltc-insured" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const insured = (await dbSelect("kaigo.ltc_insured")) ?? await readJson(ltcInsuredFile, []);
      const restrictions = await readJson(restrictionsFile, []);
      return reply(res, 200, insured.map((p) => maskRecord(actor, restrictions, p)));
    }

    if (pathname === "/api/ltc-insured" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.fullName || !body.birthDate || !body.address) return reply(res, 400, { error: "fullName, birthDate, address required" });
      const insured = (await dbSelect("kaigo.ltc_insured")) ?? await readJson(ltcInsuredFile, []);
      const insuredNumber = "KI-" + Date.now().toString(36).toUpperCase();
      const myNumberMasked = body.myNumberLast4 ? "*******" + body.myNumberLast4 : null;
      const category = body.birthDate < String(new Date().getFullYear() - 65) + "-01-01" ? "1" : "2";
      const person = { id: crypto.randomUUID(), insuredNumber, fullName: body.fullName, birthDate: body.birthDate, address: body.address, myNumberMasked, category, status: "enrolled", enrolledAt: new Date().toISOString(), createdBy: actor.id };
      const dbInserted = await dbInsert("kaigo.ltc_insured", person); if (dbInserted === null) { insured.push(person); await writeJson(ltcInsuredFile, insured); }
      await audit(req, "ltc-insured.create", { id: person.id, insuredNumber });
      return reply(res, 201, person);
    }

    const ltcInsuredMatch = pathname.match(/^\/api\/ltc-insured\/([^/]+)$/);
    if (ltcInsuredMatch) {
      requireRole(actor, "CLERK");
      const person = (await dbSelectOne("kaigo.ltc_insured", ltcInsuredMatch[1]))
        ?? (await readJson(ltcInsuredFile, [])).find((p) => p.id === ltcInsuredMatch[1]);
      if (!person) return reply(res, 404, { error: "insured 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, insuredNumber: person.insuredNumber, updatedAt: new Date().toISOString() });
        const upd = await dbUpdate("kaigo.ltc_insured", person.id, person); if (upd === null) { const wl = await readJson(ltcInsuredFile, []); const wi = wl.findIndex((x) => x.id === person.id); if (wi !== -1) { wl[wi] = person; } await writeJson(ltcInsuredFile, wl); }
        await audit(req, "ltc-insured.update", { id: person.id });
        return reply(res, 200, person);
      }
    }

    // ── 要介護認定 (Care Assessments) ────────────────────────────────────

    const CARE_LEVELS = ["support1", "support2", "care1", "care2", "care3", "care4", "care5", "non_applicable"];

    if (pathname === "/api/care-assessments" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const assessments = (await dbSelect("kaigo.care_assessment")) ?? await readJson(careAssessmentsFile, []);
      const { insuredId } = Object.fromEntries(new URL(req.url, "http://x").searchParams);
      return reply(res, 200, insuredId ? assessments.filter((a) => a.insuredId === insuredId) : assessments);
    }

    if (pathname === "/api/care-assessments" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.insuredId || !body.applicationDate) return reply(res, 400, { error: "insuredId, applicationDate required" });
      const insured = (await dbSelect("kaigo.ltc_insured")) ?? await readJson(ltcInsuredFile, []);
      if (!insured.find((p) => p.id === body.insuredId)) return reply(res, 404, { error: "insured not found" });
      const assessments = (await dbSelect("kaigo.care_assessment")) ?? await readJson(careAssessmentsFile, []);
      const assessment = { id: crypto.randomUUID(), insuredId: body.insuredId, applicationDate: body.applicationDate, certifiedLevel: null, validFrom: null, validUntil: null, status: "applied", createdBy: actor.id, createdAt: new Date().toISOString() };
      const dbInserted = await dbInsert("kaigo.care_assessment", assessment); if (dbInserted === null) { assessments.push(assessment); await writeJson(careAssessmentsFile, assessments); }
      await audit(req, "care-assessment.apply", { id: assessment.id });
      return reply(res, 201, assessment);
    }

    const assessmentMatch = pathname.match(/^\/api\/care-assessments\/([^/]+)$/);
    if (assessmentMatch && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, "CLERK");
      const id = assessmentMatch[1];
      const item = (await dbSelectOne("kaigo.care_assessment", id))
        ?? (await readJson(careAssessmentsFile, [])).find((a) => a.id === id);
      if (!item) return reply(res, 404, { error: "assessment not found" });
      if (req.method === "GET") return reply(res, 200, item);

      if (item.status !== "applied") return reply(res, 409, { error: "can only edit assessments in applied status" });
      const body = await parseBody(req);
      const editable = ["applicationDate"];
      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("kaigo.care_assessment", id, patch);
      let result = updated;
      if (updated === null) {
        const items = await readJson(careAssessmentsFile, []);
        const idx = items.findIndex((x) => x.id === id);
        items[idx] = { ...items[idx], ...patch };
        await writeJson(careAssessmentsFile, items);
        result = items[idx];
      }
      await audit(req, "care-assessment.update", { id, fields: Object.keys(patch).filter((k) => k !== "updatedAt" && k !== "updatedBy") });
      return reply(res, 200, result);
    }

    const assessmentCertifyMatch = pathname.match(/^\/api\/care-assessments\/([^/]+)\/certify$/);
    if (assessmentCertifyMatch && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const assessment = (await dbSelectOne("kaigo.care_assessment", assessmentCertifyMatch[1]))
        ?? (await readJson(careAssessmentsFile, [])).find((a) => a.id === assessmentCertifyMatch[1]);
      if (!assessment) return reply(res, 404, { error: "assessment not found" });
      if (assessment.status !== "applied") return reply(res, 409, { error: "assessment is not in applied status" });
      const body = await parseBody(req);
      if (!body.certifiedLevel || !CARE_LEVELS.includes(body.certifiedLevel)) return reply(res, 400, { error: "valid certifiedLevel required" });
      if (!body.validFrom || !body.validUntil) return reply(res, 400, { error: "validFrom, validUntil required" });
      Object.assign(assessment, { certifiedLevel: body.certifiedLevel, validFrom: body.validFrom, validUntil: body.validUntil, status: "certified", certifiedAt: new Date().toISOString(), certifiedBy: actor.id });
      const upd = await dbUpdate("kaigo.care_assessment", assessment.id, assessment); if (upd === null) { const wl = await readJson(careAssessmentsFile, []); const wi = wl.findIndex((x) => x.id === assessment.id); if (wi !== -1) { wl[wi] = assessment; } await writeJson(careAssessmentsFile, wl); }
      await audit(req, "care-assessment.certify", { id: assessment.id, certifiedLevel: body.certifiedLevel });
      return reply(res, 200, assessment);
    }

    // ── 介護サービス利用 (Care Services) ─────────────────────────────────

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

    if (pathname === "/api/care-services" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (!body.insuredId || !body.serviceType || !body.startDate) return reply(res, 400, { error: "insuredId, serviceType, startDate required" });
      const services = (await dbSelect("kaigo.care_service")) ?? await readJson(careServicesFile, []);
      const service = { id: crypto.randomUUID(), insuredId: body.insuredId, 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("kaigo.care_service", service); if (dbInserted === null) { services.push(service); await writeJson(careServicesFile, services); }
      await audit(req, "care-service.register", { id: service.id });
      return reply(res, 201, service);
    }


    const serviceMatch = pathname.match(/^\/api\/care-services\/([^/]+)$/);
    if (serviceMatch && (req.method === "GET" || req.method === "PUT")) {
      requireRole(actor, "CLERK");
      const id = serviceMatch[1];
      const item = (await dbSelectOne("kaigo.care_service", id))
        ?? (await readJson(careServicesFile, [])).find((s) => s.id === id);
      if (!item) return reply(res, 404, { error: "care 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 care 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("kaigo.care_service", id, patch);
      let result = updated;
      if (updated === null) {
        const items = await readJson(careServicesFile, []);
        const idx = items.findIndex((x) => x.id === id);
        items[idx] = { ...items[idx], ...patch };
        await writeJson(careServicesFile, items);
        result = items[idx];
      }
      await audit(req, "care-service.update", { id, fields: Object.keys(patch).filter((k) => k !== "updatedAt" && k !== "updatedBy") });
      return reply(res, 200, result);
    }

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


    // ── 介護保険料（第1号）の算定（標準仕様書準拠：基準額×所得段階別係数）──
    if (pathname === "/api/kaigo/premium" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const body = await parseBody(req);
      if (body.baseAnnual === undefined || body.baseAnnual === null) return reply(res, 400, { error: "baseAnnual is required" });
      const result = computeKaigoPremium(body);
      await audit(req, "kaigo.premium-calc", { stage: result.stage, annual: result.annual });
      return reply(res, 200, result);
    }

    // ── 月割賦課（資格取得月〜喪失前月の月数で年額を按分）──
    if (pathname === "/api/kaigo/fuka/tsukiwari" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (b.annual === undefined) return reply(res, 400, { error: "annual（年額保険料）が必須です" });
      const result = computeKaigoTsukiwari(b);
      await audit(req, "kaigo-fuka.tsukiwari", { months: result.months, prorated: result.prorated });
      return reply(res, 200, result);
    }

    // ── 期割（普通徴収の期別按分・資格喪失まとめ・賦課更正増額の直近〜最終納期按分）──
    if (pathname === "/api/kaigo/fuka/kiwari" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (b.amount === undefined) return reply(res, 400, { error: "amount（賦課額）が必須です" });
      if (b.mode && !["均等", "喪失まとめ", "増額按分"].includes(b.mode)) {
        return reply(res, 400, { error: "mode は 均等/喪失まとめ/増額按分 のいずれかです" });
      }
      const result = computeKaigoKiwari(b);
      await audit(req, "kaigo-fuka.kiwari", { mode: result.mode, periods: result.installments.length });
      return reply(res, 200, result);
    }

    // ── 仮算定（特別徴収=前年度最終期別を4/6/8月へ・普通徴収=前年度確定額で暫定賦課）──
    if (pathname === "/api/kaigo/fuka/karisantei" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (b.type && !["特別徴収", "普通徴収"].includes(b.type)) {
        return reply(res, 400, { error: "type は 特別徴収/普通徴収 のいずれかです" });
      }
      const result = computeKaigoKariSantei(b);
      await audit(req, "kaigo-fuka.karisantei", { type: result.type });
      return reply(res, 200, result);
    }

    // ── 仮徴収額の平準化（年6期で均等化・6月/8月を調整）──
    if (pathname === "/api/kaigo/fuka/heijunka" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (b.annualEstimate === undefined) return reply(res, 400, { error: "annualEstimate（年額見込）が必須です" });
      const result = computeKaigoHeijunka(b);
      await audit(req, "kaigo-fuka.heijunka", { ideal: result.ideal });
      return reply(res, 200, result);
    }

    // ── 利用者負担割合の判定（1割/2割/3割。介護保険負担割合証）──
    if (pathname === "/api/kaigo/futan-wariai" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeKaigoFutanWariai(await parseBody(req));
      await audit(req, "kaigo.futan-wariai", { wariai: result.割合 });
      return reply(res, 200, result);
    }

    // ── 特定入所者介護サービス費（補足給付）の算定 ──
    if (pathname === "/api/kaigo/hosoku-kyufu" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeHosokuKyufu(await parseBody(req));
      await audit(req, "kaigo.hosoku-kyufu", { kyufu: result.補足給付額 });
      return reply(res, 200, result);
    }
    // ── 認定調査委託料／意見書作成料の計算（0231396/0231397: 区分別単価＋消費税＋諸費用＝合計額）──
    if ((pathname === "/api/kaigo/chosa-itakuryo" || pathname === "/api/kaigo/ikensho-sakuseiryo") && req.method === "POST") {
      requireRole(actor, "CLERK");
      const result = computeItakuryo((await parseBody(req)) || {});
      if (result.error) return reply(res, 400, result);
      await audit(req, "kaigo.itakuryo", { kind: pathname.endsWith("ikensho-sakuseiryo") ? "意見書作成料" : "認定調査委託料", 合計額: result.合計額 });
      return reply(res, 200, result);
    }

    // ════ 情報提供・高額介護サービス費・償還払いチェック・審査会割当（標準仕様書）════

    // ── 情報提供（随時抽出可・提供先つき監査。全銀協向けは振込レコード形式で返す）──
    if (pathname === "/api/kaigo/provide" && req.method === "GET") {
      requireRole(actor, "ADMIN");
      const dataset = searchParams.get("dataset") ?? "";
      const def = KAIGO_DATASETS[dataset];
      if (!def) return reply(res, 400, { error: `dataset は ${Object.keys(KAIGO_DATASETS).join("/")} のいずれか` });
      const insured = await readJson(ltcInsuredFile, []);
      let rows;
      if (dataset === "zengin-furikomi") {
        // 全銀協規定フォーマット相当（参照実装: 銀行/支店/種目/口座番号/カナ名義/金額）
        const accounts = await readJson(kaigoKogakuAccountsFile, []);
        rows = accounts.map((a) => ({
          bankCode: a.bankCode ?? "0000", branchCode: a.branchCode ?? "000",
          accountType: a.accountType === "checking" ? "2" : "1",
          accountNumber: a.accountNumber ?? "", kanaName: a.kanaName ?? "", amount: a.pendingAmount ?? 0,
        }));
      } else {
        rows = insured.map((p) => ({ atenaId: p.atenaId ?? p.id, fullName: p.fullName ?? "", dataset }));
      }
      await audit(req, "kaigo.provide", { dataset, name: def.name, target: def.target, count: rows.length });
      return reply(res, 200, { dataset, name: def.name, target: def.target, count: rows.length, rows });
    }

    // ── 高額介護（予防）サービス費: 支給対象者の作成＋チェック（給付額減額の確認）──
    if (pathname === "/api/kaigo/kogaku/run" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.claims) || !b.claims.length) return reply(res, 400, { error: "claims[]（{atenaId,monthlyBurden,cap,reduced}）required" });
      const accounts = await readJson(kaigoKogakuAccountsFile, []);
      const registered = new Set(accounts.map((a) => a.atenaId));
      const targets = [];
      const checks = [];
      for (const c of b.claims) {
        const cap = Number(c.cap ?? 44400);
        const burden = Number(c.monthlyBurden) || 0;
        if (c.reduced === true) checks.push({ atenaId: c.atenaId, check: "給付額減額の対象者（要確認）" });
        if (burden > cap) {
          targets.push({ atenaId: c.atenaId, refund: burden - cap, cap,
            applicationRequired: !registered.has(c.atenaId) }); // 口座払い初回登録済みなら2回目以降は申請不要
        }
      }
      await audit(req, "kaigo.kogaku-run", { targets: targets.length, checks: checks.length });
      return reply(res, 200, { targets, checks, count: targets.length });
    }
    // 高額の口座払い申請（初回登録。以降は申請手続不要になる）
    if (pathname === "/api/kaigo/kogaku/account" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId || !b.accountNumber) return reply(res, 400, { error: "atenaId, accountNumber required" });
      const accounts = await readJson(kaigoKogakuAccountsFile, []);
      accounts.unshift({ atenaId: String(b.atenaId), bankCode: b.bankCode ?? "0000", branchCode: b.branchCode ?? "000",
        accountType: b.accountType ?? "savings", accountNumber: String(b.accountNumber), kanaName: b.kanaName ?? "",
        registeredAt: new Date().toISOString(), by: actor.id });
      await writeJson(kaigoKogakuAccountsFile, accounts);
      await audit(req, "kaigo.kogaku-account", { atenaId: b.atenaId });
      return reply(res, 201, { atenaId: b.atenaId, applicationRequiredNextTime: false });
    }

    // ── 償還払い（住宅改修費・福祉用具購入費）の申請チェック ──
    //   同一箇所の工事重複／同一年度の同一種目購入／支給限度額（住宅改修20万・福祉用具年10万）／受領委任契約業者
    if (pathname === "/api/kaigo/shokan/apply" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const kinds = ["住宅改修", "福祉用具"];
      if (!b.atenaId || !kinds.includes(b.kind) || b.amount === undefined) {
        return reply(res, 400, { error: `atenaId, kind(${kinds.join("/")}), amount required` });
      }
      const all = await readJson(kaigoShokanFile, []);
      const warnings = [];
      const past = all.filter((x) => x.atenaId === b.atenaId && x.kind === b.kind);
      if (b.kind === "住宅改修" && b.location && past.some((x) => x.location === b.location)) {
        warnings.push("同一箇所の工事が過去に申請されています");
      }
      if (b.kind === "福祉用具" && b.itemType && past.some((x) => x.itemType === b.itemType && x.fiscalYear === Number(b.fiscalYear))) {
        warnings.push("同一年度に同一種目の福祉用具購入があります");
      }
      const limit = b.kind === "住宅改修" ? 200000 : 100000;
      const used = past.filter((x) => b.kind === "住宅改修" || x.fiscalYear === Number(b.fiscalYear)).reduce((s, x) => s + (x.amount ?? 0), 0);
      const remaining = Math.max(0, limit - used);
      if (Number(b.amount) > remaining) warnings.push(`支給限度額超過（残高${remaining}円）`);
      if (b.payMethod === "受領委任" && b.vendorContracted !== true) warnings.push("受領委任払いの契約業者であることが確認できません");
      if (warnings.length && b.confirm !== true) {
        return reply(res, 409, { error: "CHECK_FAILED", warnings, remaining });
      }
      const item = { id: crypto.randomUUID(), atenaId: String(b.atenaId), kind: b.kind, amount: Number(b.amount),
        fiscalYear: Number(b.fiscalYear) || new Date().getFullYear(), location: b.location ?? "", itemType: b.itemType ?? "",
        payMethod: b.payMethod ?? "償還払い", at: new Date().toISOString(), by: actor.id };
      all.unshift(item);
      await writeJson(kaigoShokanFile, all);
      await audit(req, "kaigo.shokan-apply", { kind: item.kind, amount: item.amount, warnings: warnings.length });
      return reply(res, 201, { ...item, remaining: remaining - item.amount, warnings });
    }

    // ── 認定審査会の割当チェック（調査員・意見書作成医が審査員と重複しない）──
    if (pathname === "/api/kaigo/shinsakai/assign-check" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const investigators = new Set((b.investigators ?? []).map(String));
      const physicians = new Set((b.physicians ?? []).map(String));
      const board = (b.boardMembers ?? []).map(String);
      const conflicts = board.filter((m) => investigators.has(m) || physicians.has(m));
      await audit(req, "kaigo.assign-check", { conflicts: conflicts.length });
      return reply(res, 200, { ok: conflicts.length === 0, conflicts,
        note: conflicts.length ? "調査員・意見書作成医と審査員が重複しています（別合議体へ割当してください）" : "" });
    }

    // ════ 再転入引継・EUC電子ファイル・特徴→普徴調整・代理納付・二重消込チェック（標準仕様書）════

    // ── 再転入者の情報引継 ──
    if (pathname === "/api/kaigo/reentry" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId || !b.snapshot) return reply(res, 400, { error: "atenaId, snapshot required" });
      const list = await readJson(kaigoReentryFile, []);
      list.unshift({ id: crypto.randomUUID(), atenaId: String(b.atenaId), snapshot: b.snapshot,
        movedOutAt: b.movedOutAt ?? new Date().toISOString().slice(0, 10), at: new Date().toISOString(), by: actor.id });
      await writeJson(kaigoReentryFile, list);
      await audit(req, "kaigo-reentry.save", { atenaId: b.atenaId });
      return reply(res, 201, { saved: true });
    }
    {
      const reMatch = pathname.match(/^\/api\/kaigo\/reentry\/([^/]+)$/);
      if (reMatch && req.method === "GET") {
        requireRole(actor, "CLERK");
        const list = await readJson(kaigoReentryFile, []);
        const hits = list.filter((r) => r.atenaId === reMatch[1]);
        if (!hits.length) return reply(res, 404, { error: "転出時点の情報がありません" });
        return reply(res, 200, { atenaId: reMatch[1], reusable: true, snapshots: hits });
      }
    }

    // ── 対象者の関連情報のEUC出力（結び付く電子ファイルも併せて出力）──
    if (pathname === "/api/kaigo/euc/related" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId) return reply(res, 400, { error: "atenaId required" });
      const insured = await readJson(ltcInsuredFile, []);
      const person = insured.find((p) => p.atenaId === b.atenaId || p.id === b.atenaId);
      const shokan = (await readJson(kaigoShokanFile, [])).filter((s) => s.atenaId === b.atenaId);
      const linkedFiles = Array.isArray(b.linkedFiles) ? b.linkedFiles : []; // 結び付く電子ファイル（メタ）
      await audit(req, "kaigo-euc.related", { atenaId: b.atenaId, files: linkedFiles.length });
      return reply(res, 200, { atenaId: String(b.atenaId), person: person ?? null,
        related: { shokanApplications: shokan }, linkedFiles,
        note: "対象者の関連情報＋結び付く電子ファイルを一括出力" });
    }

    // ── 特別徴収対象とならなかった者の普通徴収への調整 ──
    if (pathname === "/api/kaigo/tokucho/adjust" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.candidates)) return reply(res, 400, { error: "candidates[]{atenaId,tokuchoEligible} required" });
      const adjusted = b.candidates.filter((c) => c.tokuchoEligible !== true)
        .map((c) => ({ atenaId: c.atenaId, method: "普通徴収", reason: "特別徴収対象外のため調整" }));
      await audit(req, "kaigo-tokucho.adjust", { adjusted: adjusted.length });
      return reply(res, 200, { adjusted, count: adjusted.length });
    }

    // ── 納付方法: 代理納付（生活保護受給者・中国残留邦人）──
    if (pathname === "/api/kaigo/payment-method" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const methods = ["普通徴収", "特別徴収", "代理納付"];
      if (!b.atenaId || !methods.includes(b.method)) return reply(res, 400, { error: `atenaId, method(${methods.join("/")}) required` });
      if (b.method === "代理納付" && !["生活保護", "中国残留邦人"].includes(b.reason)) {
        return reply(res, 400, { error: "代理納付は reason(生活保護|中国残留邦人) が必須です" });
      }
      const list = await readJson(kaigoPayMethodFile, []);
      const item = { id: crypto.randomUUID(), atenaId: String(b.atenaId), method: b.method, reason: b.reason ?? "", at: new Date().toISOString(), by: actor.id };
      list.unshift(item);
      await writeJson(kaigoPayMethodFile, list);
      await audit(req, "kaigo-pay-method.set", { atenaId: item.atenaId, method: item.method });
      return reply(res, 201, item);
    }

    // ── 収納消込データの二重消込等エラーチェック ──
    if (pathname === "/api/kaigo/shuno/check" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!Array.isArray(b.rows)) return reply(res, 400, { error: "rows[]{atenaId,period,amount} required" });
      const seen = new Set();
      const errors = [];
      for (const r of b.rows) {
        const key = `${r.atenaId}|${r.period}`;
        if (seen.has(key)) errors.push({ atenaId: r.atenaId, period: r.period, issue: "二重消込" });
        seen.add(key);
        if (!(Number(r.amount) > 0)) errors.push({ atenaId: r.atenaId, period: r.period, issue: "金額不正" });
      }
      await audit(req, "kaigo-shuno.check", { rows: b.rows.length, errors: errors.length });
      return reply(res, 200, { checked: b.rows.length, ok: errors.length === 0, errors });
    }

    // ════ 収納決算・延滞金試算・分納計画・同意確認・住宅改修履歴・再審査（標準仕様書）════

    // ── 時効到来分の還付不能処理（一括）──
    if (pathname === "/api/kaigo/shuno/refund-impossible" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const b = await parseBody(req);
      const refunds = Array.isArray(b.refunds) ? b.refunds : [];
      if (!refunds.length) return reply(res, 400, { error: "refunds[]{id,amount,expiresAt} required" });
      const asOf = b.asOf ?? new Date().toISOString().slice(0, 10);
      const expired = refunds.filter((r) => r.expiresAt && r.expiresAt <= asOf)
        .map((r) => ({ id: String(r.id), amount: Number(r.amount ?? 0), status: "還付不能（時効到来）" }));
      const shuno = await readJson(kaigoShunoFile, {});
      shuno.refundImpossible = [...(shuno.refundImpossible ?? []), ...expired.map((e) => ({ ...e, asOf, by: actor.id }))];
      await writeJson(kaigoShunoFile, shuno);
      await audit(req, "kaigo-shuno.refund-impossible", { asOf, processed: expired.length });
      return reply(res, 200, { asOf, bulk: true, processed: expired.length, items: expired });
    }

    // ── 現年度会計の決算処理（還付金繰越＋滞納分繰越）──
    if (pathname === "/api/kaigo/shuno/kessan" && req.method === "POST") {
      requireRole(actor, "ADMIN");
      const b = await parseBody(req);
      if (!b.fiscalYear) return reply(res, 400, { error: "fiscalYear required" });
      const refunds = (Array.isArray(b.refundsPending) ? b.refundsPending : [])
        .map((r) => ({ id: String(r.id), amount: Number(r.amount ?? 0), toYear: Number(b.fiscalYear) + 1, kubun: "還付金繰越" }));
      const arrears = (Array.isArray(b.arrears) ? b.arrears : [])
        .filter((a) => Number(a.unpaid ?? 0) > 0)
        .map((a) => ({ levyId: String(a.levyId), unpaid: Number(a.unpaid), toYear: Number(b.fiscalYear) + 1, kubun: "滞納繰越分" }));
      await audit(req, "kaigo-shuno.kessan", { fiscalYear: b.fiscalYear, refunds: refunds.length, arrears: arrears.length });
      return reply(res, 200, { fiscalYear: Number(b.fiscalYear), settled: true,
        refundCarryover: refunds, arrearsCarryover: arrears });
    }

    // ── 指定日・滞納期別指定の延滞金試算 ──
    if (pathname === "/api/kaigo/shuno/entaikin/trial" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const installments = Array.isArray(b.installments) ? b.installments : [];
      if (!b.asOf || !installments.length) return reply(res, 400, { error: "asOf, installments[]{period,amount,dueDate} required" });
      const RATE_FIRST = 0.024, RATE_AFTER = 0.087; // 特例基準割合ベースの参照値（団体設定で上書き可）
      const results = installments.map((i) => {
        const due = new Date(i.dueDate + "T00:00:00Z");
        const asOf = new Date(b.asOf + "T00:00:00Z");
        const days = Math.max(0, Math.floor((asOf - due) / 86400000));
        const firstDays = Math.min(days, 30);
        const afterDays = Math.max(0, days - 30);
        const amount = Number(i.amount ?? 0);
        const charge = Math.floor((amount * RATE_FIRST * firstDays) / 365 + (amount * RATE_AFTER * afterDays) / 365);
        return { period: i.period, amount, overdueDays: days, entaikin: charge < 1000 ? 0 : charge };
      });
      await audit(req, "kaigo-shuno.entaikin-trial", { asOf: b.asOf, periods: results.length });
      return reply(res, 200, { asOf: b.asOf, results, total: results.reduce((a, r) => a + r.entaikin, 0) });
    }

    // ── 分納相談（現状の滞納情報から分納計画を試算）──
    if (pathname === "/api/kaigo/shuno/bunno/plan" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      const total = Number(b.totalArrears);
      const monthly = Number(b.monthlyAmount);
      if (!total || !monthly) return reply(res, 400, { error: "totalArrears, monthlyAmount required" });
      const months = Math.ceil(total / monthly);
      const schedule = Array.from({ length: months }, (_, i) => ({
        month: i + 1, amount: i === months - 1 ? total - monthly * (months - 1) : monthly,
      }));
      await audit(req, "kaigo-shuno.bunno-plan", { total, months });
      return reply(res, 200, { totalArrears: total, monthlyAmount: monthly, months, schedule });
    }

    // ── 情報開示同意の登録とケアマネ等への提供可否判断 ──
    if (pathname === "/api/kaigo/consent" && req.method === "PUT") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId || typeof b.disclosureConsent !== "boolean") return reply(res, 400, { error: "atenaId, disclosureConsent(boolean) required" });
      const consents = await readJson(kaigoConsentFile, {});
      consents[String(b.atenaId)] = { disclosureConsent: b.disclosureConsent, at: new Date().toISOString(), by: actor.id };
      await writeJson(kaigoConsentFile, consents);
      await audit(req, "kaigo-consent.set", { atenaId: b.atenaId, consent: b.disclosureConsent });
      return reply(res, 200, { atenaId: String(b.atenaId), disclosureConsent: b.disclosureConsent });
    }
    if (pathname === "/api/kaigo/provide/care-manager" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.atenaId) return reply(res, 400, { error: "atenaId required" });
      const consents = await readJson(kaigoConsentFile, {});
      const consent = consents[String(b.atenaId)];
      const allowed = consent?.disclosureConsent === true;
      await audit(req, "kaigo-provide.care-manager", { atenaId: b.atenaId, allowed });
      if (!allowed) return reply(res, 403, { atenaId: String(b.atenaId), allowed: false,
        reason: consent ? "本人が情報開示に同意していません" : "同意記録がありません（提供前に同意確認が必要）" });
      return reply(res, 200, { atenaId: String(b.atenaId), allowed: true, target: "居宅介護支援事業者（ケアマネジャー等）",
        note: "本人の開示同意を確認のうえ提供" });
    }

    // ── 住宅改修: 過去の申請情報の確認（同一箇所の工事チェック・自庁/委託運用）──
    if (pathname === "/api/kaigo/shokan/history" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const atenaId = searchParams.get("atenaId") ?? "";
      if (!atenaId) return reply(res, 400, { error: "atenaId required" });
      const location = searchParams.get("location") ?? "";
      const history = (await readJson(kaigoShokanFile, [])).filter((s) => s.atenaId === atenaId);
      const sameLocation = location ? history.filter((s) => (s.location ?? "") === location) : [];
      await audit(req, "kaigo-shokan.history", { atenaId, hits: history.length });
      return reply(res, 200, { atenaId, applications: history,
        sameLocationCheck: location ? { location, duplicated: sameLocation.length > 0, matches: sameLocation } : null });
    }

    // ── 認定審査会資料の個人情報マスキング（氏名・住所・生年月日等を■置換）──
    if (pathname === "/api/kaigo/shinsakai/material" && 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[]{name,address,birthDate,...} required" });
      const MASK_FIELDS = ["name", "address", "birthDate", "phone", "insuredNumber"];
      const masked = subjects.map((s) => {
        const out = { ...s };
        for (const f of MASK_FIELDS) if (out[f] !== undefined) out[f] = "■■■■";
        return out;
      });
      await audit(req, "kaigo-shinsakai.material", { subjects: subjects.length });
      return reply(res, 200, { kind: "認定審査会資料（マスキング済）", maskedFields: MASK_FIELDS, materials: masked });
    }

    // ── 再審査申立（保険者分）の登録・結果記録と過去履歴の確認 ──
    if (pathname === "/api/kaigo/saishinsa" && req.method === "POST") {
      requireRole(actor, "CLERK");
      const b = await parseBody(req);
      if (!b.claimId) return reply(res, 400, { error: "claimId required" });
      const list = await readJson(kaigoSaishinsaFile, []);
      const item = { id: crypto.randomUUID(), claimId: String(b.claimId), reason: b.reason ?? "",
        result: b.result ?? "申立中", resultAt: b.result ? new Date().toISOString() : null,
        at: new Date().toISOString(), by: actor.id };
      list.unshift(item);
      await writeJson(kaigoSaishinsaFile, list);
      await audit(req, "kaigo-saishinsa.file", { claimId: item.claimId, result: item.result });
      return reply(res, 201, item);
    }
    if (pathname === "/api/kaigo/saishinsa/history" && req.method === "GET") {
      requireRole(actor, "CLERK");
      const claimId = searchParams.get("claimId") ?? "";
      const list = await readJson(kaigoSaishinsaFile, []);
      const hits = claimId ? list.filter((s) => s.claimId === claimId) : list;
      await audit(req, "kaigo-saishinsa.history", { claimId, hits: hits.length });
      return reply(res, 200, { claimId: claimId || "（全件）", count: hits.length, history: hits });
    }

    // ── 介護保険の各種情報の管理（登録・修正・削除・照会）: 保険者/医療保険者/個人住民税情報等を種別別にCRUD保持 ──
    const kaigoRecordsFile = join(dataRoot, "kaigo-records.json");
    if (pathname === "/api/kaigo/records" && req.method === "GET") {
      requireRole(actor, "CLERK"); // 照会（種別/宛名番号フィルタ可）
      const 種別 = searchParams.get("種別"), atena = searchParams.get("宛名番号") ?? searchParams.get("被保険者番号");
      const all = await readJson(kaigoRecordsFile, []);
      const list = all.filter((x) => (!種別 || x.種別 === 種別) && (!atena || String(x.宛名番号 ?? x.被保険者番号 ?? "") === String(atena)));
      return reply(res, 200, { 件数: list.length, records: list });
    }
    if (pathname === "/api/kaigo/records" && req.method === "POST") {
      requireRole(actor, "CLERK"); // 登録
      const b = await parseBody(req);
      if (!b.種別) return reply(res, 400, { error: "種別(保険者/医療保険者/個人住民税 等) が必要です" });
      const all = await readJson(kaigoRecordsFile, []);
      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(kaigoRecordsFile, all);
      await audit(req, "kaigo-record.create", { id: item.id, 種別: item.種別 });
      return reply(res, 201, item);
    }
    const kaigoRecordItemMatch = pathname.match(/^\/api\/kaigo\/records\/([^/]+)$/);
    if (kaigoRecordItemMatch) {
      const all = await readJson(kaigoRecordsFile, []);
      const idx = all.findIndex((x) => x.id === kaigoRecordItemMatch[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(kaigoRecordsFile, all); await audit(req, "kaigo-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(kaigoRecordsFile, all); await audit(req, "kaigo-record.delete", { id: removed.id });
        return reply(res, 200, { deleted: true, id: removed.id });
      }
    }

  return false;
}
