接客スケジュールを複数設定する

このページについて

  • 接客に複数のスケジュールを設定する方法をチュートリアル形式で説明します。
  • Craft Functionsを定期実行して、API経由で接客の公開・非公開を切り替えます。

概要

このチュートリアルでできること・できないこと、全体の構成と作業の概要を説明します。

何を作るか

2023年3月現在、KARTEの接客では開始終了日、曜日単位、そして時間単位の公開スケジュールが設定できます。しかし、曜日・時間が一定ではない場合や開始終了のタイミングが複数ある場合、その都度公開スケジュールを変更する必要があります。
このハンズオンでは接客に複数のスケジュールを設定する機能をCraftを使って実装します。具体的には、接客の公開・停止スケジュールを複数設定できるようにし、なおかつ祝日の場合の公開・停止を設定する機能を実装します。

このチュートリアルで実装した機能でできることとできないことは以下のとおりです。

  • できること
    • 接客のスケジュールを複数設定できる
      • 対象の接客について「開始時刻」、「終了時刻」の組を複数設定し、これらの期間内のみ接客を公開することができます。
    • 接客ごとに「祝日の場合は公開・非公開」が設定できる。
      • 以下の設定が可能です。
        • 「祝日の場合は公開」スケジュール期間中かつ祝日の場合は公開する
        • 「祝日の場合は非公開」スケジュール期間中であっても、祝日の場合は非公開にする
        • 「祝日を考慮しない」祝日による公開・非公開を設定しない
  • できないこと
    • 秒単位の公開・非公開設定
      • 接客の公開・非公開を行う処理の頻度が最短で1分となるためです。
    • KARTE接客のスケジュールとの同時設定
      • これらのスケジュール設定は全て空にする必要があります。
2000

接客のスケジュール

構成図

作成する機能の全体像は以下の通りです。

構成要素は以下の通りです。

1406

構成図

  • Craft Functions
    • コード実行環境です。Functionとして記述したコードで以下の処理を行います。
    • コード内に記述したスケジュール(開始時刻・終了時刻の組)に基づき、対象接客を公開・非公開します。
  • Craft Scheduler
    • Craft Functionsを定期的に実行する機能です。
  • KARTE Action
    • KARTEの接客機能です。
  • 内閣府ウェブサイト
  • 管理者
    • 接客スケジュールを設定する人です。KARTEアカウントが必要です。

ハンズオン

以下の手順で接客スケジュール機能を実装していきます。

前提

KARTEプロジェクトで以下の機能を有効化する必要があります。

作業概要

初回の設定作業では、Functionsを作成し、初回のスケジュール設定を行います。

  • API v2設定
  • Functions の作成
    • コードのデプロイ
    • Craft Schedulerの設定
  • 接客スケジュールの設定
    • コード内の CAMPAIGN_SCHEDULES を設定します。
    • Functionsを更新します。

運用作業としては接客スケジュールの更新があります。更新の手順は上記の接客スケジュールの設定と同じです。
移行、各作業について説明します。

Step1. API v2設定

  • 以下のリンクを参考に、API v2 のAppを作成し、アクセストークンを控えてください。

Step2 Functionの作成

  • 前の手順で設定したAppのアクセストークンをシークレットに登録します。
    • KARTE管理画面で [すべてのプロダクト] > [Craft] > [シークレット] を選択し、シークレット一覧画面を開きます。
    • [作成] を選択し、シークレット情報を記入します。
      • シークレット名は英語大文字とアンダースコアで任意の名前(ex: KARTE_API_TOKEN )を指定します。
    • データには前の手順で控えたアクセストークンを指定します。
  • Functionを作成します。
    • KARTE管理画面で [すべてのプロダクト] > [Craft] > [ファンクション] を選択し、ファンクション一覧画面を開きます。
    • ファンクション一覧画面で「コードから作成」を選択し、必要項目を入力します。
      • ファンクション名には任意の名前を指定してください。ここでは campaign-scheduler とします。
      • code は後述の「Functionのコード」を貼り付けてください。
      • 「変数」タブを開き、次の値を指定します
        • 変数名: KARTE_API_SECRET_NAME
          • 値:先ほど作成したシークレット名
          • 型:文字列
        • 変数名: LOG_LEVEL
          • 値:WARN (他にも DEBUG, INFO, ERROR が有効)
          • 型:文字列
  • 入力が完了したら「保存」を選択します。
  • Functionの実行スケジュールを設定します。
    • campaign-scheduler Function の詳細画面から [設定] タブを開きます。
    • スケジュール欄で [作成] を選択します。
    • 設定をcron式で記述し、[保存] を選択します
        • * * * * 毎分実行
        • /5 * * * * 5分ごとに実行
        • 10 * * * * 毎時10分に実行

🚧

実行間隔を短くするほどより細かく開始終了時刻を指定できますが、Public APIの呼出頻度が増えるため、API v2のLimitを超えないように頻度を設定してください。

Functionのコード

Step2. でデプロイするFunctionのコードは以下のとおりです。

import api from "api";

/**********************************************
 * 管理者向け設定
 **********************************************/

// 接客スケジュール
// デプロイ時に適宜設定する
const CAMPAIGN_SCHEDULES = [
  // 祝日判定なしの例
  {
    campaign_id: "aaaaaaaaaaaaaaaaaaaaaaaa",
    schedules: [
      { start: "2023-02-01T16:00:00+09:00", end: "2023-02-03T16:30:00+09:00" },
    ],
  },

  // 祝日のみ無効の例
  {
    campaign_id: "bbbbbbbbbbbbbbbbbbbbbbbb",
    on_holiday: "disable",
    schedules: [
      { start: "2023-02-01T16:00:00+09:00", end: "2023-02-01T16:30:00+09:00" },
    ],
  },

  // スケジュール期間内かつ祝日のみ有効にする例
  {
    campaign_id: "cccccccccccccccccccccccc",
    on_holiday: "enable",
    schedules: [
      { start: "2023-02-01T16:00:00+09:00", end: "2023-02-03T16:30:00+09:00" },
      { start: "2023-02-02T16:00:00+09:00", end: "2023-02-13T16:00:00+09:00" },
    ],
  },
];

/**********************************************
 * 各種処理
 **********************************************/

const KARTE_API_SECRET_NAME = "<% KARTE_API_SECRET_NAME %>"; // KARTE APIトークンを保存するシークレット名
const LOG_LEVEL = "<% LOG_LEVEL %>"; // 'DEBUG', 'INFO', 'WARN', 'ERROR' のいずれか

// 各種定数定義

// UTCからの時差
const HOUR_DELTA = 9;

// 時差分を加えた時刻を返す
function getOffsetDate(date, hourDelta) {
  const delta = 1000 * 60 * 60 * hourDelta; // msec
  return new Date(date.getTime() + delta);
}

function getDateString(date) {
  return `${date.getUTCFullYear()}/${
    date.getUTCMonth() + 1
  }/${date.getUTCDate()}`;
}

/**
 * 祝日一覧CSVから祝日情報を文字列として取得する
 * @returns 祝日('YYYY-MM-DD')の配列
 */
async function getHolidays(logger) {
  const HOLIDAYS_LIST_URL =
    "https://www8.cao.go.jp/chosei/shukujitsu/syukujitsu.csv";
  const res = await fetch(HOLIDAYS_LIST_URL, {
    method: "GET",
    headers: {
      Accept: "text/csv",
    },
  });

  if (res.status !== 200) {
    logger.error(res);
    throw new Error("cannot get holidays data.");
  }

  // 祝日一覧のCSVから日時情報のみの一覧を取得する
  return (await res.text())
    .split("\n")
    .slice(1)
    .map((line) => line.split(",")[0]);
}

/**
 * 接客の公開・非公開を判定する
 * @param {*} logger logger モジュール
 * @param {Object} param0 現在日時(date: Date)、祝日一覧(holidays: string[])、接客情報(campaign: object) からなるオブジェクト
 * @returns 接客の公開(true), 非公開(false) を返す
 */
const isEnable = (logger, { date, holidays, campaign }) => {
  // 現在時刻がスケジュール内かどうか
  const onSchedule =
    campaign.schedules
      .map((schedule) => {
        const start = getOffsetDate(new Date(schedule.start), HOUR_DELTA);
        const end = getOffsetDate(new Date(schedule.end), HOUR_DELTA);
        if (date.getTime() < start.getTime()) return false;
        if (date.getTime() >= end.getTime()) return false;
        return true;
      })
      .filter((v) => v).length > 0;

  // 現在が祝日かどうか
  const dateString = getDateString(date);
  const onHoliday = holidays.includes(dateString);

  if (campaign.on_holiday == null) {
    return onSchedule;
  }

  if (campaign.on_holiday === "enable") {
    return onSchedule && onHoliday;
  } else if (campaign.on_holiday === "disable") {
    return onSchedule && !onHoliday;
  }

  logger.warn(
    '"on_holiday" parameter is valid only with "enable" and "disable"'
  );
  return onSchedule;
};

export default async function (data, { MODULES }) {
  const { secret, initLogger } = MODULES;
  const logger = initLogger({ logLevel: LOG_LEVEL });

  // 現在時刻と祝日一覧を取得する
  logger.log("get date & holidays");
  const date = getOffsetDate(new Date(), HOUR_DELTA);
  const holidays = await getHolidays(logger);

  // 接客APIクライアント
  logger.log("get KARTE API Client");
  let action;
  try {
    const { [KARTE_API_SECRET_NAME]: token } = await secret.get({
      keys: [KARTE_API_SECRET_NAME],
    });
    action = api("@dev-karte/v1.0#16x5jb2alnwwkn1v"); // 補足を参照
    action.auth(token);
  } catch (err) {
    logger.error(err);
    throw err;
  }

  logger.log("start check schedule");
  for (const campaign of CAMPAIGN_SCHEDULES) {
    logger.log(JSON.stringify(campaign));
    const enabled = await isEnable(logger, { date, holidays, campaign });
    const res = await action.postV2betaActionCampaignToggleenabled({
      id: campaign.campaign_id,
      enabled,
    });
    logger.log(JSON.stringify(res));
    logger.log(
      `dateString: ${getDateString(
        date
      )}, time: ${date.toISOString()}, campaign: ${
        campaign.campaign_id
      }, enabled: ${enabled}`
    );
  }
}

packagesには次の値を指定します。

{
    "api": "5.0.8"
}

コードの補足

  • 祝日一覧は getHolidays() で取得します。内閣府のCSVファイルに合わせて祝日一覧を取得しているので、別の情報元から祝日一覧を取得する場合はこの部分を書き換えればOKです。
  • 祝日判定のため、世界標準時からの時差を HOUR_DELTA で定義しています。日本時間は標準時より9時間進んでいるため、(+) 9 を設定しています。ないとは思いますが、別のタイムゾーンで祝日を判定する場合はこの値を変更してください。
  • 42, 43行目でFunctionの変数を展開しています。詳細は 変数の利用 を参照してください。
  • api() のパラメータは以下のAPIリファレンスのNode.jsサンプルに記載のSpec URIを指定してください。
APIリファレンス

APIリファレンス

Step3. 接客スケジュールの設定・更新

以下の手順で接客スケジュールを設定・更新します。

  • campaign-schedulerファンクションの詳細画面を開き、[基本設定] タブの [編集] を選択します。
  • [code] の編集欄で CAMPAIGN_SCHEDULES 変数の定義を変更します。
    • 詳細は後述の「接客スケジュールの設定方法」を参照してください。
  • [保存] を選択し、Functionを更新します。

接客スケジュールの設定方法

接客スケジュールはコード内の変数 CAMPAIGN_SCHEDULES で定義します。

const CAMPAIGN_SCHEDULES = [
  {
    campaign_id: 'xxxxxxxxxxxxxxxxxx',
    on_holiday: '(enable|disable)'
    schedules: [
      { start: 'start_date', end: 'end_date' },
      ...
    ],
  },
  ...
];

CAMPAIGN_SCHEDULES は以下の要素からなるオブジェクトの配列として定義しています。

要素説明
campaign_id接客サービス詳細画面のURLの末尾24桁を指定してください。
ex) URLが https://admin.karte.io/p/0123456789abcdef01234567/service/1234567890abcdef12345678 の場合、 service/ 以降の24桁の文字列 1234567890abcdef12345678 を指定します。
on_holiday祝日の場合の挙動を指定します。
enable の場合は祝日の場合のみ有効となります。
disableの場合は祝日の場合のみ無効となります。
この要素は省略可能です。省略した場合、祝日の挙動は平日と変わりません。
schedulesstart end を要素に持つオブジェクトの配列としてスケジュールの開始終了日時を指定します。日時はRFC3309の表記に従って指定してください。

ex)
2023-02-01T16:00:00+09:00 = 2023/02/01 16:00(日本時間)
2023-03-01T12:00:00Z = 2023/03/01 12:00(世界標準時) = 2023/03/01 21:00(日本時間)

設定例は以下の通りです。

  • 設定例

    // ダブルスラッシュで始まる行はコメントです。
    // Craft Functionはこの行を無視してプログラムを実行するので、設定の際に適宜ご活用ください。
    
    const CAMPAIGN_SCHEDULES = [
      // 2/1 16:00 - 16:30 (日本時間) の間接客を公開する
      {
        campaign_id: '1234567890abcdef11111111',
        schedules: [{ start: '2023-02-01T16:00:00+09:00', end: '2023-02-01T16:30:00+09:00' }],
      },
    
      // 2/1 16:00 - 2/15 16:00 の間接客を公開する
      // 祝日(この場合は建国記念日)のみ無効となる
      {
        campaign_id: '1234567890abcdef22222222',
        on_holiday: 'disable',
        schedules: [{ start: '2023-02-01T16:00:00+09:00', end: '2023-02-15T16:00:00+09:00' }],
      },
    
      // スケジュール期間内かつ祝日のみ有効
      // この設定では日本時間の 2023-02-11 のみ接客を公開する
      {
        campaign_id: '1234567890abcdef33333333',
        on_holiday: 'enable',
        schedules: [
          { start: '2023-02-01T16:00:00+09:00', end: '2023-02-01T16:30:00+09:00' },
          { start: '2023-02-02T16:00:00+09:00', end: '2023-02-15T16:00:00+09:00' },
        ],
      },
    ];
    
    

停止/削除方法

この機能の停止方法、削除方法は以下のとおりです。

  • 停止方法
    • Craft Functionsのスケジュール設定を削除します。
      • スケジュールの削除により、Craft Functionsの定期実行が終了します。
  • 完全に削除する方法
    • (初回のみ) の手順で作成した各コンポーネントを削除します。
      • Function
      • シークレット設定
      • API v2 App

📘

停止時の注意

機能を停止、削除する際、各接客の公開設定は変更されません。変更する場合は管理画面経由で各接客の選定を変更してください。

まとめ

このハンズオンでは、Craftを活用して「複数の接客スケジュール設定」機能を実装しました。具体的には、現在時刻と接客スケジュールを判定して接客の公開・非公開を行うFunctionを作成し、そのFunctionを定期実行することで、標準機能では設定できないスケジュール設定を実現しました。また、内閣府Webサイトの休日情報に基づく休祝日判定も実装しました。