/**
* 施設別パフォーマンスの「取得 → 絞り込み → 並べ替え → 連続日数の上限処理」(D-163)
*
* ★★一覧(`GET /reports/facility-performance`)と CSV(`GET /reports/facility-performance/export`)の
* 両方がこの1本を呼ぶ★★ — ★片方だけ直すと、画面の表と CSV の中身が食い違います★
* (例外もテスト失敗も出ない壊れ方。`/clean-logs` と `/clean-logs/export` が
* 同じ引数を渡すことにしているのと同じ理由。指示書AC 段階2 D-6)。
*
* ★`limit` はここでは適用しない★ — 一覧は戻り値を `slice(0, limit)` し、
* CSV は全件を出す(画面の「全施設表示」の200件上限に縛られない。D-163)。
*
* ★DB の例外はそのまま投げる★ — 500 にするか(とログを出すか)はルートが決める。
*/
import { CONSECUTIVE_DAYS_DISPLAY } from "@/lib/config";
import { db } from "@/lib/db";
import { intersectFacilityIds } from "@/lib/facilities/intersect-ids";
import { resolveFacilityIdsByMasters } from "@/lib/facilities/resolve-by-master";
import { resolveFacilityIdsByName } from "@/lib/facilities/resolve-by-name";
import {
capFacilityPerformance,
filterFacilityPerformanceByName,
filterFacilityPerformanceBySerial,
filterFacilityPerformanceByStatus,
selectFacilityPerformanceRows,
sortFacilityPerformance,
toReportDbQuery,
type ReportQuery,
} from "@/lib/reports/query";
import { loadConsecutiveDaysMax } from "@/lib/settings/display-limits";
import type { FacilityPerformance } from "@/lib/types";
/**
* 絞り込み・並べ替え・連続日数の上限処理まで済ませた ★全件★ を返す(`limit` は見ない)。
*
* 並び順は稼働率の昇順(ワースト順)・同値は facility_id の昇順・null は最後
* (`sortFacilityPerformance()`。`d-watch-api.yaml` の /reports/facility-performance)。
*/
export async function loadFacilityPerformance(
query: ReportQuery
): Promise<FacilityPerformance[]> {
// ★表示上限(指示書AF 段階6)★ — 設定が読めないだけで一覧を 500 にしない。
// ★型注釈 `: number` が要る★(`CONSECUTIVE_DAYS_DISPLAY` は `as const` でリテラル型)
let maxDays: number = CONSECUTIVE_DAYS_DISPLAY.defaultMax;
try {
maxDays = await loadConsecutiveDaysMax();
} catch {
// 既定値のまま続行する
}
// ★★施設名の照合はアプリ側で施設IDに解決してから SQL に渡す★★(指示書AC C-1 / 段階5。
// ★段階2(`/clean-logs`)で確定した作法をそのまま真似る。新しい書き方を発明しない★)—
// ★記号(中点等)を無視する照合は LIKE では書けない★(照合順序でも無理。実測済み)。
// ★★戻り値の `null` と `[]` は別物★★: `null` = 施設名の条件なし(全施設)/
// `[]` = ★照合したが1件も当たらなかった(=0件)★。
// ★★施設名の絞り込みを持つレポートAPIはここだけ★★(`d-watch-api.yaml` の
// `/reports/facility-performance` と、その CSV の `/export` にだけ `facility_name` がある)。
// ★`serial` は据え置き=メモリ絞り込みのまま★(索引が効かず利得が薄い。C-1)。
// ★★分類(`group_id` / `customer_id`)とは**積集合(AND)**を取る★★
// (指示書AK §6-2 / 段階4b)— ★どちらか片方を捨てないこと★。
// ★積が空なら `[]` = 0件★(★`null` にすると条件ごと消えて全件表示に化ける★。
// 落とし穴2。例外もテスト失敗も出ない)。
const [byName, byMasters] = await Promise.all([
resolveFacilityIdsByName(query.facilityName),
resolveFacilityIdsByMasters(query),
]);
const rows = await db.facilityPerformance(
toReportDbQuery(query, intersectFacilityIds(byName, byMasters))
);
// ここから下は純粋な絞り込み・並べ替え。
const inPeriod: FacilityPerformance[] = selectFacilityPerformanceRows(rows, query);
const filtered = filterFacilityPerformanceBySerial(
filterFacilityPerformanceByName(
filterFacilityPerformanceByStatus(inPeriod, query.status),
query.facilityName
),
query.serial
);
const sorted = sortFacilityPerformance(filtered);
// ★表示上限は絞り込み・並べ替えの**後**にかける★(指示書AF 段階6-2)
// 件数(`meta.total`)も `status` の判定も上限とは無関係(上限は表示のためだけ)。
// ★1行ずつの写像なので、`limit` で切る前にかけても後にかけても結果は同じ★(D-163)。
return capFacilityPerformance(sorted, maxDays);
}