指摘ありがとうございます。たしかに重要な点は:
family[].relationshipとmainOwner.relationshipは どちらもログインユーザーから見た続柄 であり、 そのまま比較しても「被保険者(mainOwner)から見た」関係にならないことです。- しかし今回の判定(本人 / 配偶者 / 同居の家族)が目的なので、続柄を厳密に「被保険者基準」に変換するのではなく、ログインユーザー基準の
relationshipとlivingStatusを組み合わせて「被保険者にとって本人か・配偶者か・同居家族か」になるかを判断するのが実用的かつシンプルです。
以下はその考えを実装した修正版 isCoveredDriver と、指摘の例(被保険者が「同居する実親」で運転者が「本人」)を含む Vitest の網羅的テストケース です。
修正版 isCoveredDriver 実装(TypeScript)
export const isCoveredDriver = (
driverScope:
| "本人"
| "本人+配偶者"
| "本人+配偶者+同居の家族"
| "なし"
| undefined,
family: {
personalId: string;
livingStatus: "同居" | "別居" | undefined;
relationship:
| "配偶者"
| "兄弟"
| "実親"
| "義親"
| "本人"
| "その他"
| undefined;
}[],
mainOwner: {
personalId: string;
livingStatus: "同居" | "別居" | undefined;
relationship:
| "配偶者"
| "兄弟"
| "実親"
| "義親"
| "本人"
| "その他"
| undefined;
}
): boolean => {
// driverScope が undefined / "なし" は誰も対象外
if (!driverScope || driverScope === "なし") return false;
const isMainOwner = (pId: string) => pId === mainOwner.personalId;
/**
* mainOwner と driver が「同居している」と判定するルール
* - 両者ともログインユーザー視点で "同居" であれば同一世帯(=同居)とみなす
* - また、driver がログインユーザー本人 (relationship === "本人")
* で mainOwner.livingStatus === "同居" なら mainOwner と同居している
* - 同様に mainOwner が "本人" で driver.livingStatus === "同居" なら同居
*/
const isCohabitingWithMainOwner = (driver: typeof family[number]) => {
if (mainOwner.livingStatus === "同居" && driver.livingStatus === "同居")
return true;
if (driver.relationship === "本人" && mainOwner.livingStatus === "同居")
return true;
if (mainOwner.relationship === "本人" && driver.livingStatus === "同居")
return true;
return false;
};
/**
* mainOwner の配偶者かどうかを簡易判定するルール
* (ログインユーザー視点の続柄を組み合わせて推論する)
*
* ケース例(ログインユーザーを U、被保険者を M、運転者を D):
* - M.relationship === "本人" && D.relationship === "配偶者" => D は M の配偶者
* - M.relationship === "配偶者" && D.relationship === "本人" => D は M の配偶者
*
* 上記以外は保守的に false とする(曖昧なケースで誤判定を防ぐ)。
*/
const isSpouseOfMainOwner = (driver: typeof family[number]) => {
if (driver.relationship === "配偶者" && mainOwner.relationship === "本人")
return true;
if (driver.relationship === "本人" && mainOwner.relationship === "配偶者")
return true;
return false;
};
const isCoveredOne = (driver: typeof family[number]) => {
if (isMainOwner(driver.personalId)) return true;
switch (driverScope) {
case "本人":
return isMainOwner(driver.personalId);
case "本人+配偶者":
return isMainOwner(driver.personalId) || isSpouseOfMainOwner(driver);
case "本人+配偶者+同居の家族":
return (
isMainOwner(driver.personalId) ||
isSpouseOfMainOwner(driver) ||
isCohabitingWithMainOwner(driver)
);
default:
return false;
}
};
// family に含まれる全員が covered であることが必要(1人でも範囲外なら false)
return family.every(isCoveredOne);
};
Vitest テスト(網羅的・指摘の例を含む)
import { describe, it, expect } from "vitest";
import { isCoveredDriver } from "./isCoveredDriver"; // 実際のパスに合わせて修正
const mk = (
id: string,
living: "同居" | "別居" | undefined,
rel:
| "配偶者"
| "兄弟"
| "実親"
| "義親"
| "本人"
| "その他"
| undefined
) => ({ personalId: id, livingStatus: living, relationship: rel });
describe("isCoveredDriver(修正版)", () => {
const OWNER = mk("OWNER", "同居", "本人");
describe("driverScope = なし / undefined", () => {
it("なし -> false", () => {
expect(isCoveredDriver("なし", [mk("OWNER", "同居", "本人")], OWNER)).toBe(false);
});
it("undefined -> false", () => {
expect(isCoveredDriver(undefined, [mk("OWNER", "同居", "本人")], OWNER)).toBe(false);
});
});
describe("driverScope = 本人", () => {
it("被保険者本人のみ OK", () => {
expect(isCoveredDriver("本人", [mk("OWNER", "同居", "本人")], OWNER)).toBe(true);
});
it("ログインユーザー(本人)だが被保険者が同居実親の場合 -> NOT OK(本人限定)", () => {
// mainOwner はログインユーザー視点で実親(=被保険者は user の親)
const mainOwner = mk("PARENT", "同居", "実親");
const driverUser = mk("ME", undefined, "本人");
// driver は mainOwner にとって「子」だが driverScope が "本人" のため NG
expect(isCoveredDriver("本人", [driverUser], mainOwner)).toBe(false);
});
});
describe("driverScope = 本人+配偶者", () => {
it("被保険者本人 OR 配偶者 を許可", () => {
const mainOwner = mk("ME", "同居", "本人");
expect(isCoveredDriver("本人+配偶者", [mk("ME", "同居", "本人")], mainOwner)).toBe(true);
expect(isCoveredDriver("本人+配偶者", [mk("SPOUSE", "同居", "配偶者")], mainOwner)).toBe(true);
});
it("ログインユーザーが被保険者の配偶者のケース(mainOwner.relationship === '配偶者')", () => {
// 例: ログインユーザー = D (本人), 被保険者 M はログインユーザーから見て '配偶者'
// => D は M の配偶者になるので許可されるべき
const mainOwner = mk("M", "同居", "配偶者"); // M は login user から見て配偶者
const driver = mk("D", undefined, "本人"); // driver は login user(=D)
expect(isCoveredDriver("本人+配偶者", [driver], mainOwner)).toBe(true);
});
it("曖昧ケースは厳格に NG(誤判定を避ける)", () => {
// 両方とも配偶者という値などで判定できない場合は false とする
const mainOwner = mk("X", "別居", "配偶者");
const driver = mk("Y", "別居", "配偶者"); // driver は login user から見て配偶者(別の人)
expect(isCoveredDriver("本人+配偶者", [driver], mainOwner)).toBe(false);
});
});
describe("driverScope = 本人+配偶者+同居の家族", () => {
it("被保険者本人 OK", () => {
const mainOwner = mk("ME", "同居", "本人");
expect(isCoveredDriver("本人+配偶者+同居の家族", [mk("ME", "同居", "本人")], mainOwner)).toBe(true);
});
it("被保険者が同居する実親で、運転者がログインユーザー(本人) -> OK (ご指摘の例)", () => {
// mainOwner: login user から見て '実親' かつ mainOwner.livingStatus === '同居'
const mainOwner = mk("PARENT", "同居", "実親");
// driver: login user(本人)。login user は mainOwner と同居している想定(mainOwner.livingStatus === '同居')
const driver = mk("ME", undefined, "本人");
expect(isCoveredDriver("本人+配偶者+同居の家族", [driver], mainOwner)).toBe(true);
});
it("同居フラグで同居家族と判定されるケース", () => {
const mainOwner = mk("M", "同居", "本人");
const sibling = mk("SIB", "同居", "兄弟");
expect(isCoveredDriver("本人+配偶者+同居の家族", [sibling], mainOwner)).toBe(true);
});
it("別居家族は NG", () => {
const mainOwner = mk("M", "同居", "本人");
const distant = mk("D", "別居", "兄弟");
expect(isCoveredDriver("本人+配偶者+同居の家族", [distant], mainOwner)).toBe(false);
});
it("配偶者(別居でも) -> 配偶者判定は許可", () => {
const mainOwner = mk("ME", "別居", "本人");
const spouse = mk("SP", "別居", "配偶者");
expect(isCoveredDriver("本人+配偶者+同居の家族", [spouse], mainOwner)).toBe(true);
});
it("複数運転者で1人でも NG なら false", () => {
const mainOwner = mk("ME", "同居", "本人");
const ok = mk("A", "同居", "兄弟");
const ng = mk("B", "別居", "兄弟");
expect(isCoveredDriver("本人+配偶者+同居の家族", [ok, ng], mainOwner)).toBe(false);
});
});
});
実装上の補足(なぜこのやり方か)
- 続柄を完全に「被保険者視点」に変換してラベルを当て直すと、家系のあらゆる組み合わせ(親→子→配偶者など)を網羅する膨大なロジックが必要になり、実務では過学習かつ誤判定の原因になります。
そこで「ログインユーザー視点の
relationshipとlivingStatusの組み合わせから、被保険者にとって 本人/配偶者/同居家族 になるかを保守的に推論」する実装にしました。- 重要なのは過大判定(誤って OK にする)を避けることなので、曖昧なケースは
falseにする方針を取っています。
- 重要なのは過大判定(誤って OK にする)を避けることなので、曖昧なケースは
- ご要望があれば、より攻めた(=積極的に OK にする)ルールや、逆に家系変換表を作って正確に「被保険者基準の続柄」に変換する厳密版も作れます。
必要なら次を用意します:
- 上の実装での Jest / Vitest 実行スナップショット(実行結果)
- 続柄を完全に変換する「家系トランスレータ」版(より複雑)
- 既存システムの具体データ(実際の
relationshipの使い方)に合わせてルール微調整
どれを優先しますか?