/**
* fixtures 版レポート4メソッドの絞り込み(指示書AC ★段階5★。段階2 D-5 の作法をそのまま真似る)。
*
* ★★`USE_FIXTURES` の true/false で件数が変わってはいけない★★(指示書AC 禁止事項6)。
* Prisma 版が SQL に降ろした条件(期間キー・施設ID)と ★同じ意味★ をここで再現します。
*
* ★絞り込み規則の「正」は `lib/reports/query.ts` の `selectXxxRows()` / `selectKpiRow()`★ —
* ここも Prisma 側もその ★写し★ です(指示書AC C-5)。★★あちらを外さないこと★★。
*
* ★DBアクセスを混ぜない純粋関数★(テストから直接呼べるようにするため。
* `lib/db/fixture-clean-logs.ts` / `lib/db/fixture-prechecks.ts` と同じ流儀)。
*/
import type {
FacilityPerformanceDbQuery,
FacilityPerformanceRow,
ReportDbQuery,
ReportKpiDbQuery,
ReportPeriodKey,
} from "@/lib/types";
/**
* 行の期間キーが要求された期間と一致するか。
*
* ★`matchesPeriod()`(`lib/reports/query.ts`)と同じ4項目の単純比較★ —
* ★monthly も `week` を 0 で持つ(D-072)ので period による場合分けは要らない★。
* ★★あちらは private なので写してある★★(`lib/types` を経由しない import で
* ルート層とデータ層を結ぶより、同じ規則を2行書くほうが依存が素直)。
* ★規則が変わったら両方直すこと★ — ★どちらかだけ変えると、SQL 版だけ / fixtures 版だけが
* ずれ、`USE_FIXTURES` の切替で件数が変わります★(禁止事項6)。
*/
function matchesPeriod(row: ReportPeriodKey, query: ReportDbQuery): boolean {
return (
row.period === query.period &&
row.year === query.year &&
row.month === query.month &&
row.week === query.week
);
}
/**
* 期間キーだけで絞る(`reportKpis()` / `continuousProblems()` / `errorBreakdown()` 用)。
*
* ★★前期(`shiftPeriod()` の1つ前)の行は返さない★★ — Prisma 版は「前期比」のために
* サマリを2行ぶん引きますが、★返すのは要求された期間の行だけ★(前期の値は
* `KpiValue.prev` の中に入って出る)。★fixtures も同じにすること★。
*/
export function filterFixtureReportRows<TRow extends ReportPeriodKey>(
rows: readonly TRow[],
query: ReportDbQuery
): TRow[] {
return rows.filter((row) => matchesPeriod(row, query));
}
/**
* 期間キー + ★対象顧客★ で絞る(`reportKpis()` 用。指示書AK ★段階6★)。
*
* ★★Prisma 版が `customer_summaries` の `where` に `customer_id` を足したのと**同じ意味**★★ —
* ★`USE_FIXTURES` の true/false で数字が変わってはいけない★(禁止事項6)。
*
* ★★「絞らない」という選択肢は無い★★ — `ReportKpiDbQuery.customer_id` は必須で、
* ★顧客が決まらないときはデータ層を呼ばずに 422★(`resolveTargetCustomer()`。§2-1)。
* ★`facility_ids` の `null` / `[]` のような二択ではない★ので、ここに `!== null` の分岐は無い。
*
* ★該当が無ければ空配列★ — ★存在しない `customer_id` でも 404 にしない★
* (期待値表 6-9。ルート側が空のKPIを組み立てる)。
*
* ★★`query.facility_ids` はここでは使わない★★(指示書AL ★段階1★)—
* Prisma 版は ★当期(進行中の週・月)だけ★ この値で `clean_timetables` を絞るが、
* ★fixtures の KPI 行は当期ぶんも「顧客単位の集計値」として置いてある★
* (`lib/fixtures/reports.ts` の `reportKpis`。施設の次元を持たない)ので、
* ★同じ顧客から解決した施設IDで絞っても結果は変わらない★。
* ★★つまり `customer_id` だけで Prisma 版と同じ母集団になっている★★
* (= 存在しない顧客なら両方とも空。指示書AL 期待値表 1-3)。
*/
export function filterFixtureReportKpis<
TRow extends ReportPeriodKey & { customer_id: number },
>(rows: readonly TRow[], query: ReportKpiDbQuery): TRow[] {
return filterFixtureReportRows(rows, query).filter(
(row) => row.customer_id === query.customer_id
);
}
/**
* 期間キー + ★施設ID★ で絞る(`facilityPerformance()` / `continuousProblems()` 用)。
*
* ★★`facility_ids` の `null` と `[]` を Prisma 版と**同じ意味**で扱うこと★★(★罠9★):
* - ★`null` → 施設で絞らない(全件)★
* - ★★`[]` → 0件を返す★★(★★`[]` を「条件なし」と読んで全件返す実装にしないこと★★ —
* `USE_FIXTURES` の true/false で件数が変わり、禁止事項6 に違反します)
*
* ★★`?.length` で分岐しないこと★★ — `[]` が「条件なし」に化けます(罠1と同じ形)。
*
* ★`item.facility_id` を持つ行型ならどれでも使える★(指示書AK 段階4b で
* `ContinuousProblemRow` にも要るようになったため一般化した)。
* ★`ErrorBreakdownRow` には使えない★ — ★あちらの `item` は施設をまたいで
* 足し上げたあとの集計値で、施設の情報を持たない★(`errorBreakdown()` の注記)。
*/
export function filterFixtureRowsByFacility<
TRow extends ReportPeriodKey & { item: { facility_id: number } },
>(rows: readonly TRow[], query: ReportDbQuery): TRow[] {
const inPeriod = filterFixtureReportRows(rows, query);
// ★★`!== null` で分岐すること★★(罠1 / 罠9)
if (query.facility_ids === null) return inPeriod;
const ids = new Set(query.facility_ids);
return inPeriod.filter((row) => ids.has(row.item.facility_id));
}
/**
* `facilityPerformance()` 用の別名(★既存の呼び出し側とテストのために残してある★)。
* 中身は `filterFixtureRowsByFacility()` そのもの。
*/
export function filterFixtureFacilityPerformance(
rows: readonly FacilityPerformanceRow[],
query: FacilityPerformanceDbQuery
): FacilityPerformanceRow[] {
return filterFixtureRowsByFacility(rows, query);
}