Newer
Older
client_dwatch / lib / format / robot-model.ts
/**
 * `devices.maker_id` / `model_name` の組み合わせが、対応機種レジストリ(`ROBOT_MODELS`)に
 * 登録済みかどうかを判定する(指示書N / D-087)。
 *
 * ★設定値は `lib/config`、それを読む純粋関数は `lib/format`★ — `lib/format/contract.ts` と
 * 同じ分け方。機種の組み合わせをこの関数の中に直書きしないこと。
 *
 * ★`lib/judge/` には置かない★ — あそこは ◎○△× / L1〜L3 / KPI / 丸めの置き場所(CLAUDE.md §9.1)。
 * こちらは「静的な対応表を引くだけ」で、DB由来の実測値に対する評価ではない。
 *
 * 実データの表記ゆれに備え、比較前に前後の空白を除去し大文字小文字を揃えてから
 * 完全一致で判定する(指示書K の B2 判定と同じ考え方 — 実データが何を返すか
 * 分からないので、正規化してから完全一致で判定する)。部分一致はしない
 * ("CC10" が "CC1" にヒットしてはいけない)。
 */
import { ROBOT_MODELS } from "@/lib/config";

function normalize(value: string): string {
  return value.trim().toUpperCase();
}

/**
 * DB アクセスなし・例外を投げない純粋関数。
 * レジストリに無い組み合わせ(完全に未知の機種を含む)は `false` を返す。
 */
export function isSupportedModel(maker: string, model: string): boolean {
  const normalizedMaker = normalize(maker);
  const normalizedModel = normalize(model);
  return ROBOT_MODELS.some(
    (entry) =>
      entry.supported &&
      normalize(entry.maker) === normalizedMaker &&
      normalize(entry.model) === normalizedModel
  );
}

/**
 * `devices.model_name` の先頭にメーカー名が入っていたら外す(例 メーカー `"PUDU"`・機種 `"PUDU CC1"` → `"CC1"`)。
 *
 * ★ステージングの `devices.model_name` は `"PUDU CC1"`(メーカー名込み)★(2026-10-29 実測・先方投入)。
 *   そのまま `"<メーカー名> <機種名>"` に連結すると「PUDU PUDU CC1」になり、`isSupportedModel()` も
 *   機種 `"PUDU CC1"` を `ROBOT_MODELS` の `"CC1"` と照合できない(ユーザー指示 2026-10-29「アプリ側でメーカー名を重ねないようにして」)。
 * - 大文字小文字・前後の空白を無視して「メーカー名+空白」で始まるときだけ外す(`"PUDUX1"` は外さない)
 * - 外した残りが空なら元の値のまま返す。メーカー名で始まらなければ元の値のまま返す
 */
export function stripMakerPrefix(maker: string, model: string): string {
  const trimmedModel = model.trim();
  const prefix = `${normalize(maker)} `;
  if (normalize(maker) === "" || !trimmedModel.toUpperCase().startsWith(prefix)) return model;
  const rest = trimmedModel.slice(prefix.length).trim();
  return rest === "" ? model : rest;
}