/**
* 清掃結果(★`clean_task_reports.status`★)の表示整形(指示書AQ §1-2)。
*
* ★★取得元が変わった(指示書AQ 決定2・決定3。2026-09-30 ユーザー確定)★★ —
* 清掃実績の参照元が `clean_status_histories` → ★`clean_task_reports`★ になり、
* ★新しいテーブルに `clean_error` 列が存在しない★(`prisma/schema.prisma:517-558`)。
* 旧 `formatCleanResult(clean_error)` は旧 `CLEAN_ERROR_LABELS`(定義書の原文。2026-10-19 削除)で
* 日本語ラベルを引いていたが、★その対応表は `clean_status_histories.clean_error` のもの★
* であり、★`clean_task_reports.status` に流用できる根拠は無い★。
*
* ★★`status` は表示名にする★★(2026-10-02。D-155 項2)—
* TeamWorks 第10回 N-2 で値の意味を受領した(`CLEAN_TASK_STATUS_LABELS`。原文ママ):
* 0 清掃開始前 / 1 清掃中 / 2 清掃中断 / 3 清掃キャンセル / 4 清掃正常終了 / 5 清掃異常終了。
* ★6 は 3 と同じ「清掃キャンセル」★(N-2 の表に無い値。ユーザー指示 2026-10-30)。
* ★表に無い値(7 以上など)は「不明(7)」★(数字を残す。ユーザー指示 2026-10-30)。
* ★「その他」「不明」に潰さないこと★(D-089 項3 / D-104 項2)。
* ★判定には使わない★(表示だけ。D-155 項2)。★英語は画面側で `t()` に通す★(`lib/i18n.ts`。仮訳)。
* ★CSV は日本語の表示名のまま★(見出しも日本語固定。指示書AS D11)。
* ★旧: 定義書に値の説明が無かった(`prisma/schema.prisma:528-529`)ので生値をそのまま返していた★
* (D-077 / `CLAUDE.md` §4.8。指示書AQ §1-2)。
* ★★`CLEAN_ERROR` に対応づけないこと★★ — 別のテーブルの別のコード表(`clean_status_histories.clean_error`)。
*
* ★★関数名を `formatCleanResult()` から改名した理由★★ —
* 引数の意味が `clean_error` → `status` に変わったのに、★どちらも `number | null` なので
* 旧名のままだと呼び出し側が `r.clean_error` を渡し続けても tsc が通ってしまう★
* (fail-silent。画面には日本語ラベルの代わりに数字が出るだけで、誰も気づけない)。
* ★名前を変えて呼び出し側をコンパイルエラーで止める★。
* ★旧名の再export は作らないこと★(指示書AQ 担当C 指示)。
*
* ★★`null` を「出発失敗」と表示する根拠★★(★従来から変わっていない★):
* - ★指示書AQ §1-1★: 「未実施 = 本清掃の `clean_timetables` 行に対応する
* `clean_task_reports` の行が無い」。★「出発失敗」と「未実施」は同一扱いのまま★
* (ユーザー確認 2026-09-30)
* - ★ユーザー確定(2026-09-16)★: 「`null` は『出発失敗』と表示する」
* - `lib/db/clean-log-mapping.ts` の `notExecutedFromReport()` — ★レポート行が無ければ
* `departed === false` / `completion_rate === null`★。★本関数の `null` と同じ事実を指す★
*
* ★`formatRawLabel()` の `EMPTY_VALUE_LABEL`(`(値なし)`)を使わない理由★:
* あちらは「DBのカラムが空である」という事実だけを述べる文言だが、ここは
* 「レポート行が無い=出発していない」と★意味が確定している★場面である(文言はユーザー指定)。
*
* ★DB アクセスなし・例外を投げない純粋関数。lib/judge/ には置かない★
* (あちらは◎○△×等の評価ロジックの置き場所。こちらは表示整形。CLAUDE.md §9.1)。
*/
import { cleanTaskStatusLabel, isKnownCleanTaskStatus } from "@/lib/config";
import type { CleanLogItem } from "@/lib/types";
/** レポート行が無い(=出発失敗/未実施)ときの表示。★ユーザー確定 2026-09-16★ */
export const CLEAN_RESULT_NOT_DEPARTED_LABEL = "出発失敗";
/**
* `clean_task_reports.status` → 詳細ログ「清掃結果」列 / CSV の表示文字列。
*
* @param status `clean_task_reports.status` の生値。
* ★`null` は「対応するレポート行が無い」ことを表す★(値が NULL なのではない。
* `status` 列自体は `NOT NULL DEFAULT 0`)。
* @returns `null` なら `CLEAN_RESULT_NOT_DEPARTED_LABEL`、それ以外は ★`cleanTaskStatusLabel(status)`★
* (日本語の表示名。★表に無い値は「不明(7)」★)。★日本語のまま返す★ — 画面は `t()` に通し、
* CSV はそのまま出す(指示書AS D11)
*/
export function formatCleanTaskStatus(status: number | null): string {
// ★`??` を `||` にしないこと★ — `0`(清掃開始前)が「出発失敗」に化ける
if (status === null) {
return CLEAN_RESULT_NOT_DEPARTED_LABEL;
}
return cleanTaskStatusLabel(status);
}
/**
* ★結果待ち(`CleanLogItem.result_pending === true`)の行の表示★(ユーザー判断 2026-10-07。D-173)。
* 画面(`t()` で "Pending")と CSV(日本語のまま。出発列にも入れる)で使う。
*/
export const CLEAN_RESULT_PENDING_LABEL = "結果待ち";
/**
* ★予定外タスクの行(`CleanLogItem.unscheduled === true`)の「予定」列★(指示書BG §2-1。ユーザー指示 2026-10-29)。
* 時刻の代わりに出す。画面は `t()` で "No schedule"、CSV は日本語のまま(「予定時刻」列)。
* ★文言は「予定なし」★(ユーザー指示 2026-11-04「文言を予定なし、予定ありに統一して」。旧: 「予定外」/ EN "Unscheduled")。
* 概念名(予定外タスク)・`unscheduled` などのコード上の名前は変えない。
*/
export const CLEAN_LOG_UNSCHEDULED_LABEL = "予定なし";
/** 詳細ログの1行の見え方(`cleanLogRowView()` の戻り) */
export interface CleanLogRowView {
/** 出発列。ok = ✓ / ng = ✗ / ★pending = …(結果待ち。✓✗ の色を使わない)★ */
departed: "ok" | "ng" | "pending";
/** 清掃結果列(★日本語★。画面は `t()` に通し、CSV はそのまま出す) */
resultLabel: string;
/**
* ★表に無い `status`(7 以上など)のときだけ、その生値★。それ以外は null。
* 画面は `t(CLEAN_TASK_STATUS_UNKNOWN_TEMPLATE)` の `{id}` をこれで置き換える(英語で "Unknown (7)")。
* `resultLabel`(「不明(7)」)をそのまま `t()` に通しても訳が引けないため。CSV は `resultLabel` のまま
*/
unknownStatus: number | null;
/** 完了率列に出す値(★結果待ちは null★。画面は `displayRate()` で「-」、CSV は `csvRate()` で空) */
completionRate: number | null;
/** 判定バッジを出すか(★結果待ちは出さない★。画面は「-」、CSV は空) */
gradeShown: boolean;
/**
* 行を開けるか(△× のアドバイス)。★結果待ちは開けない★。
* 予定あり・予定なしとも △× なら true(予定なしは `/api/clean-logs/unscheduled/{report_id}/advice`。指示書BI)
*/
expandable: boolean;
/**
* 「エリア別詳細を見る →」を出すか。★予定のある行だけ true★(`/areas` は予定の id が要る。指示書BI)
*/
areaDetail: boolean;
}
/**
* 清掃詳細ログの1行の見え方を決める(表2 C2・C4。D-173)。★純粋関数★。
*
* ★★結果待ちは `departed: false` / `task_status: null` / `completion_rate: null` / `grade: "×"` のまま★★
* (不変条件を壊さない)なので、★旗(`result_pending`)を見ないと出発失敗と区別できない★。
* 表(`CleanLogTable.tsx`。ダッシュボードの縮小表示も)と CSV(`lib/clean-logs/csv.ts`)が
* ★ここを共有する★ — 片方だけ直すと、同じ行が画面では結果待ち・CSV では出発失敗になる(例外もテスト失敗も出ない)。
* ★判定(`grade`)は書き換えない★(API から受け取ったまま。§9.6)。出すか出さないかだけを決める。
*/
export function cleanLogRowView(
row: Pick<
CleanLogItem,
"departed" | "task_status" | "completion_rate" | "grade" | "result_pending" | "unscheduled"
>
): CleanLogRowView {
if (row.result_pending) {
return {
departed: "pending",
resultLabel: CLEAN_RESULT_PENDING_LABEL,
unknownStatus: null,
completionRate: null,
gradeShown: false,
expandable: false,
areaDetail: false,
};
}
const expandable = row.grade === "△" || row.grade === "×";
return {
departed: row.departed ? "ok" : "ng",
resultLabel: formatCleanTaskStatus(row.task_status),
unknownStatus:
row.task_status !== null && !isKnownCleanTaskStatus(row.task_status) ? row.task_status : null,
completionRate: row.completion_rate,
gradeShown: true,
expandable,
areaDetail: expandable && !row.unscheduled,
};
}