Newer
Older
client_dwatch / lib / facilities / intersect-ids.ts
/**
 * 施設IDの絞り込み結果どうしの積集合(指示書AK 段階3 §6-2)。
 *
 * ★★「施設名の絞り込み」と「分類の絞り込み」は同時に指定されうる★★ので、
 * ★両方の結果を AND で畳んでから `where: { facility_id: { in: [...] } }` に渡します★
 * (★どちらか片方を捨てないこと★。指示書AK §2-2)。
 *
 * ★★`null` と `[]` の意味は `resolveFacilityIdsByName()` / `resolveFacilityIdsByMasters()`
 *   と同じ★★:
 *   - `null` … ★その条件は指定されていない(絞らない)★
 *   - `[]`   … ★指定されたが0件ヒット★
 *
 * 期待値表(★この6行がこの関数の契約★。テストで固定してある):
 *
 * | 施設名の解決結果 | 分類の解決結果 | 戻り値 |
 * |---|---|---|
 * | `null`    | `null`    | ★`null`★ |
 * | `[1,2,3]` | `null`    | `[1,2,3]` |
 * | `null`    | `[2,3,4]` | `[2,3,4]` |
 * | `[1,2,3]` | `[2,3,4]` | ★`[2,3]`★ |
 * | ★`[1]`★  | ★`[2]`★  | ★★`[]`★★(★`null` にしないこと★) |
 * | `[]`      | `[1,2]`   | ★`[]`★ |
 *
 * ★★最後の2行が要です★★(★落とし穴2★)— ★積集合が空になったら `[]` を返すこと★。
 *   ★`null` にすると条件ごと消えて全件表示に化けます。例外もテスト失敗も出ません。★
 *
 * ★純粋関数にすること★(`CLAUDE.md` §9.4)— DBアクセスやログ出力を混ぜない。
 */

/**
 * 2つの施設ID絞り込み結果の積集合を取る。
 *
 * ★★`a ?? b` や `[...(a ?? []), ...(b ?? [])]` で済ませないこと★★ —
 *   前者は片方を捨て、後者は和集合(OR)になります。★契約は AND★。
 *
 * ★並び順は `a` の順序を保つ★(★並び替えない★)—
 *   `facility_id: { in: [...] }` は順序を見ないので意味は変わりませんが、
 *   ★入力の順序を勝手に変えるとテストの期待値が理由なく揺れます★。
 *
 * ★重複は取り除かない★ — 解決側(`facilityIdsMatchingName()` /
 *   `facilityIdsMatchingMasters()`)が施設マスタを1行ずつ写した配列を返すので、
 *   ★そもそも重複が入らない★。ここで `Set` に通して「直した」ことにすると、
 *   ★重複が出る不具合が上流で起きても気づけなくなります★。
 */
export function intersectFacilityIds(
  a: number[] | null,
  b: number[] | null
): number[] | null {
  // ★★`!== null` で分岐すること★★ — `a?.length` で書くと `[]`(0件ヒット)が
  //   「未指定」に化け、★0件が全件表示になります★(罠1 / 落とし穴1・2)
  if (a === null) return b;
  if (b === null) return a;
  const inB = new Set(b);
  return a.filter((id) => inB.has(id));
}