「心理的安全性」とソフトウエア開発

はじめに

こんにちは、株式会社エニグモの開発を担当しているグループでエンジニアリングマネージャーをしている後藤です。

この記事は Enigmo Advent Calendar 2023の15日目の記事です。

私は、開発グループでエンジニアリングマネージャーという役割をしています。この記事では、チームメンバーとの関わりの中で大切にしている「心理的安全性」についてポエムを書かせていただきます。

この記事の対象者

この記事の対象は、「心理的安全性」という言葉は聞いたことあるけどモヤッとしかわからないが大切そうだ、みんなが「心理的安全性」といっているから上っ面だけでも理解してドヤ顔をしたいという人を対象にしています。

この記事を通して、読者の方に「心理的安全性」がどうソフトウエア開発に影響を与えていると感じているのか、どうしたらソフトウエア開発チームにとって良い「心理的安全性」のある環境を作れるのかを自分で解釈する手助けになればと思っております。

あくまで、私の解釈ですのでこの記事を参考に自分の考え、理解を勧めていく手助けになれば幸いです。

因みに、私が思っている「心理的安全」なチームは以下のイラストのイメージです。

安心できるチーム

心理的安全性の定義とソフトウエア開発現場における私の理解

まずはじめに、「心理的安全性」という言葉ですが、Googleが発表した「成果を出すチーム」の条件に出てきて有名になったと理解しています。私もこの記事で知りました。それからずっとこの「心理的安全性」が引っかかっていました。

なぜならマネージャーと名のつく役割を担っている以上、「成果」を出すことを期待されています。それに、最も影響を与えるとなれば理解しないわけにはいきません。

ところがです、「心理的安全性(psychological safety)」について調べると、組織の中で自分の考えや気持ちを誰に対してでも安心して発言できる状態のこと、と出てきます。また、そのような環境を作るために、自分の病気のことを話したり、プライベートなことも話せる関係を作っていくと良いと結ばれていました。

そうなのかー、私には無理じゃん、というのが正直な感想でした。エンジニア上がりでエンジニア仕事大好きなどちらかというとコミュ障寄りの私にとって、プライベートな話をしたり雑談をしながらチームを作って行くなんてできっこないと思ったわけです。

それでも、この「心理的安全性」を放置しておくわけにもいかないですし、別にプライベートな事が話せる事が必須条件ではないだろうという思いもあり私なりに噛み砕いてみました。

心理的安全性」が低いとどんな問題があるのか

心理的安全性」を理解するにあたり、「心理的安全性」が低いとどんな問題があるのかを考えてみました。

いろいろあるのですが、私が考えている一番の弊害は、開発をしていて「この部分少し不安だ」、「あの部分、実は問題がある気がする」といった「もやっと」した気持ちを他のメンバーに伝えることができない、伝えることに抵抗がある、ということではないかと考えました。

なぜなら、「この部分少し不安だ」、「あの部分、実は問題がある気がする」という部分は、なぜだか本番にリリースするとかなりの確率でエラーが起きたりします。そして、分かっていた本人は心のなかで「やっぱりか!」と思いながら修正に追われるわけです。

この「もやっと」を事前に伝えられていれば、トラブル対応に追われるという状況を回避できたはずです。

この、少し不安だが確信が持てない、間違っているかもしれないけど、みたいな事を伝えられるチームである事が生産性に影響を与えるのではないかと考えたのです。

「もやっと」を伝えられるチームにするために何をしているのか

私達のチームでは、毎週金曜日にKPTフレームワークをつかって振り返りをしています。この場でこの「もやっと」を伝えられる雰囲気を作る事を心がけました。

そうはいっても、「もやっと」を伝えてくださいね、と言ってみたところで意見が出てくるわけではありません。

そのため、1on1で出てきた、困ったことや解決した事を、些細なことでも良いので週次の振り返りに上げてもらうよう伝えることを根気よく繰り返しています。また、私自身が見つけた問題や、解決した事も話すようにしています。

このような事を繰り返していくことで、小さな事でも伝えて良いんだ、という空気ができてくる。また他の人が伝えてくれた小さな事が役に立った!という経験の積み重で、「もやっと」を伝えていこうという空気が出てくるのではないかと思っています。

まとめ

ソフトウエア開発の現場で「心理的安全性」は、小さな「もやっと」を伝えられるか、に影響すると考えています。

この小さな「もやっと」を意識的に引き出していくことで、ソフトウエア開発で発生する問題の芽を早いうちに摘む事ができ、開発生産性を改善できるのではないかと考えています。

そのために、小さな発見を伝えられる場作りを大切にしています。

おまけ

週末に BIBLIOTHECA というラジオ番組を聞いていたら、この「もやっと」が伝えられないチームの状況を適切に表す言葉に出会いました。この状況は、「集団浅慮(しゅうだんせんりょ)」と呼ばれる状況と一致しているのではないかと思います。

「集団浅慮(しゅうだんせんりょ)」とは、優秀な人達が集まって議論した結果、とても残念な結論を出して大失敗するという状況です。

そのラジオ番組によると、過去にケネディ大統領がこの失敗に陥り、そこからどう改善していったのかという事が以下の本に書いてある、と紹介されていました。この本の中に、マネージャーとして気をつけるべきことが潜んでいるのではないかと思っています。もっとも、集団浅慮に陥りやすいのは強いカリスマ的なリーダーがいるとき、というのがあったのでその部分は当てはまらない気がしますが、今度読んでみたいと思っています。

一緒に働く仲間を募集しています!

株式会社エニグモでは一緒に働く仲間を募集しています。興味のある方は以下の求人をご参照ください。

株式会社エニグモ すべての求人一覧

hrmos.co

【コンバージョン数3倍増!!】HTMLメルマガの改修事例をご紹介します

こんにちは、デザイナーの細田です。
この記事はEnigmo Advent Calendar 2023 の 15日目の記事です。

日頃の業務ではBUYMAの様々なコンテンツのデザインを担当しています。
中でもHTMLメルマガのデザインに関しては、数年前から改修に携わるようになり、ABテストによるデザイン検証に取り組んできました。

今回はその中でも、改修によって、お問い合わせフォームへの流入数が前月比で140%増加し、本施策のコンバージョンとなるお問い合わせ数も約3倍に増えた成功事例をご紹介します。

改修前のデザイン

まずは、今回改修依頼を受けたメルマガの概要をご紹介します。

対象者

高額な商品を複数回閲覧しているユーザー

内容

BUYMAコンシェルジュ*1というサービスを紹介し、お問合せフォームへ誘導する

デザイン

このメールを見て、皆さんはどう感じましたか?

私は、「なんだか唐突だな」と感じました。

例えるなら、お店で洋服を見ていたら突然声をかけられ、 「なにかお探しですか?コンシェルジュへご相談ください」と名刺を渡され去っていく・・
そのような唐突さを感じました。この内容では、お問合せしてみようと思う人は少ないかもしれません。

それでは、どうすれば唐突な印象を解消し、BUYMAコンシェルジュというサービスに興味をもってもらえるのでしょうか。

改修後のデザイン

改修後のデザインは以下のようになりました。順番に解説していきたいと思います。

【💡POINT 1 】ヘッダーに送信理由を記載する

私が、メルマガにおいて1番重要だと感じていることは、受信者に“自分宛” と思わせることです。

メルマガは “チラシ” と ”手紙” の両方の役割を担っています。
サッと見てゴミ箱行きのチラシとなるか、じっくり読んで返事を書きたくなる手紙になるか。
それにはそのメルマガが無作為に送られたものではなく、“自分に宛てて送られたものである” と感じてもらえるかどうかが重要となります。

そのために有効な手段として、上記のようにヘッダーに “どういう理由でこのメールを送信したのか” を記載する、という方法があります。
今回であれば、「高額な商品をご検討中の方へ特別なご案内です。」と記載することで、受信者がなぜこのメルマガが自分へ届いたかを理解し、「自分宛だから見てみよう」と感じる可能性を高めることができます。

【💡POINT 2 】タイトルは1番伝えたいことを簡潔に

メルマガ内で1番目を引くタイトルには、そのメルマガで1番伝えたいことを簡潔に記載します。

改修前のタイトルでは、「お探しの商品は見つかりましたか?」とありますが、このメルマガで1番に伝えたい事はBUYMAコンシェルジュの紹介です。
そこで、改修後のデザインでは「BUYMAコンシェルジュがバイマ太郎様のお買い物をサポートいたします。」と変更しました。
そうすることで、「BUYMAコンシェルジュって何だろう?」と興味を抱いてもらうことができます。

また、タイトルに受信者の名前を挿入し強調することで(例ではバイマ太郎となっています)
【 POINT 1 】で取り上げた“自分宛である” ことの演出を、より一層強めることができます。

【💡POINT 3 】分かりやすい見出しで離脱を防ぐ

改修前のメルマガにはBUYMAコンシェルジュについての説明がなく、「そもそもBUYMAコンシェルジュってなに?」と受信者を戸惑わせてしまう可能性がありました。

そこで改修後のデザインでは、BUYMAコンシェルジュについての説明エリアを追加したのですが、重要となるのが見出しです。
今回は、「BUYMAコンシェルジュとは?」という見出しをかなり目立つ形で挿入しています。
こうすることで、BUYMAコンシェルジュの説明エリアであることを明示し、タイトルを見て「BUYMAコンシェルジュって何だろう?」と興味を示したユーザーの目線をそのまま説明へと誘導する役割を担っています。

これが見出しのない、ただの文章としてそこに配置されていた場合、
特にスマートフォンの小さい画面で見た時に、反射的に読み飛ばされてしまったり、「読むのが面倒だ」とメルマガ自体から離脱されてしまう恐れもあります。

しっかり読んでもらいたい文章を配置する時こそ、目立つ見出しで分かりやすく区切ったり、文章自体も極力簡潔にまとめるなどの、細かい気配りが必要となります。

【💡POINT 4 】ボタンはベタ塗りで視認性を高める

改修前はボーダーであったボタンを、改修後はベタ塗りに変更しました。

その理由は、BUYMA全体のデザインルールに合わせるという目的もあったのですが、
スマートフォンの小さな画面で見た時の視認性の高さが、やはりベタ塗りの方が圧倒的に高いからです。

また、最近ではメルマガをダークモードで閲覧しているケースも増えており、
ダークモードで見ると特に、ベタ塗りボタンの視認性の高さが顕著になります。

まとめ

以上のような改善を行った結果、BUYMAコンシェルジュのお問い合わせフォームへの流入数が前月比で140%増加し、本施策のコンバージョンとなるお問い合わせ数も約3倍に増えました。

弊社ではデザイン改修に加え、MAツールの導入により、コンテンツ内容のパーソナライズ化や、配信対象の最適化など、よりユーザー1人1人に寄り添ったメルマガ作成を行なっており、確実に成果を生み出しています。

今後も地道なABテストを積み重ねながら、このような成功例を蓄積し、発信していければと考えています。


株式会社エニグモ すべての求人一覧

hrmos.co

*1:BUYMAコンシェルジュとは、特別なお客様限定の招待型プライベートサービスです。ご希望商品のお探しからお取引の相談まで、BUYMAのご利用に関するあらゆるサポートを専任のコンシェルジュが行なっています。

自社サービスで必ず達成しておきたいウェブアクセシビリティ対応についてまとめました

こんにちは。株式会社エニグモにてフロントエンドエンジニアを務めています新井です。

こちらは Enigmo Advent Calendar 2023 および アクセシビリティ Advent Calendar 2023 の 14 日目の記事です。

はじめに:ウェブアクセシビリティとは?

X(旧 Twitter)や各種テック記事などを見ていると、多くの人がウェブアクセシビリティを、障がいを持つ人々、特に視覚障がいのある人々への配慮として認識しています。

これは正しい認識の一部ではありますが、ウェブアクセシビリティの範囲はもっと広いです。実際には、全ての年齢層や様々な身体的、精神的条件を持つ人々が、多種多様な環境下でウェブサイトやアプリケーションを利用可能にすることを目指しています。

障がいがない人も、健康問題、マウスやトラックパッドの故障、音声再生の不可能な環境など、様々な状況に直面することがあります。ウェブアクセシビリティとは、これら多様なユーザーがウェブサイトやアプリケーションを容易に利用できるように設計することを意味します。

この記事では、ウェブアクセシビリティを向上させるための重要な10の項目を紹介します。私たちが運営するショッピングサイト「BUYMA」も今年からアクセシビリティへの取り組みを進めており、これらの項目を今後達成することを目指しています。

1. ページ内の見出しの最適化

具体的にはページ内で使用されている h1〜h6 タグを、階層構造の深さに応じて最適化します。

多くの方が見出しの最適化と聞いて SEO の改善を連想するのではないでしょうか?実際に、見出しの整理が SEO に好影響を及ぼすことはあるかもしれません。しかし、もっと注目すべきなのは、この最適化が SEO のみならずアクセシビリティにも大きく貢献するということです。

スクリーンリーダーなどの支援技術を利用するユーザーは、見出しを通してページ全体の構造を理解します。また、見出しジャンプ機能を使って必要な情報に素早くアクセスすることもあります。WebAIM のアンケートによると、約 7 割のユーザーが見出しジャンプ機能を使用しているとのことです。

webaim.org

見出しを用いてウェブサイトの情報構造を適切に整理することで、ユーザーが情報を迅速に理解できるようになります。

Google Chrome拡張機能Web Developer」を使用することでページの見出しのアウトラインを可視化することができます。見出しレベルを視覚化して検証することで、ページの構造が明確になり、改善点を見つけやすくなります。見出しの正確さや一貫性を確認するために、見出しレベルは可視化して検証することをおすすめします。

参考:達成基準 2.4.6: 見出し及びラベルを理解する

2. リンクや状態の判別を色の変化だけで行わないようにする

リンクや状態(フォーカスやホバーなど)の判別を色のみでデザインしてしまうと、色覚異常を持つユーザーにとって判別が困難になる可能性があります。 この問題に対処するためには、色だけでなく形状やその他のビジュアル変化を用いて区別ができるようにすると良いでしょう。

具体的な対策としては次のようなものがあります。

  • テキストリンクは色の変更に加えて、下線を表示する
  • 状態が変化する際に、アイコンを変更する
  • ボタンがホバーされた時は、背景色を変えるだけでなく枠線も表示する

特にホバーの状態は、透明度の変更のみでデザインされることが多いので、デザイナーと協力して状態の変化が明確に分かるデザインを作成することが望ましいです。

また、以下の CSS コードをデベロッパーツールで適用することで、ウェブサイトをグレースケールに変換し、色に頼った UI がないかどうかを検証するのに役立ちます。

body {
  filter: grayscale(1);
}

BUYMAのトップページをデベロッパーツールでグレースケールにしたサンプルです

ただし、濃い赤のような色はグレースケールへの変換では確認しにくい場合があるため、検証のコストとのバランスを考慮しながら、色覚特性のタイプごとに見え方をシミュレーションすることも有効かもしれません。

参考: 達成基準 1.4.11: 非テキストのコントラストを理解する

3. キーボード操作時のフォーカスインジケーターは非表示にしない

リンクやボタンがクリックされた際に表示されるフォーカスインジケーターは、しばしばデザイン上の理由で非表示にされがちです。しかし、フォーカスインジケーターを非表示にしてしまうと、キーボードでウェブサイトを操作するユーザーはコンテンツの利用が困難になってしまいます。

クリック時のフォーカスインジケーターを非表示にしつつ、キーボード操作時のフォーカスインジケーターを維持するには、以下のような CSS をベースで指定しておくと良いでしょう。

:focus:not(:focus-visible) {
  outline: none;
}

:focus-visible 擬似クラスはキーボード操作によりフォーカスされた場合に適用され、マウス操作によりフォーカスした場合には適用されません。

もしサイト内に複数の CSS が読み込まれ、各要素に outline:none が適用されているような場合、以下のような CSS を指定することでキーボード操作時のフォーカスインジケーターを復活させることができます。

@layer focus {
  :focus-visible {
    outline: revert;
  }
}

この方法では、Cascade Layers という機能を用いて優先度を高めています。 このスタイルは ID セレクタで定義されたスタイルよりも優先されますが、インラインスタイル( style 属性)や !important 指定されたスタイルよりは優先度が低いため、 !important を使用するよりも安全です。 ただし、 outline プロパティを上書きする場合は新たに @layer を設けて上書きする手間があるので、あくまで応急処置として扱うのが賢明です。 Cascade Layers は、現在すべてのモダンブラウザで使用可能です。

さらに、フォーカス時の状態をより分かりやすくするために、インジケーターに加えてホバー時のスタイルも適用しておくとフォーカスの判別がよりわかりやすくなります。

.button:focus-visible {
  /* ホバー時のスタイル */
}

@media (hover: hover) and (pointer: fine) {
  .button:where(:any-link, :enabled, summary):hover {
    /* ホバー時のスタイル */
  }
}

タッチデバイスでのホバーは動作に支障が出る場合があるため、hover メディアクエリを利用してタッチデバイスでのホバーは無効化しておくと良いでしょう。

参考:達成基準 2.4.7: フォーカスの可視化を理解する

4. クリッカブルな要素の実装には a タグまたは button タグを使用する

クリッカブルな要素を div タグや span タグを用いて実装すると、支援技術を使用するユーザーやキーボード操作に依存するユーザーがその要素を選択できなくなることがあり、結果としてコンテンツのアクセシビリティが損なわれる可能性があります。

リンクは a タグを、ボタンは button タグを使用して、適切にマークアップしましょう。支援技術は a 要素を「リンク」、button 要素を「ボタン」と読み上げます。

既存のコードで a タグや button タグに変更するのが難しい場合、tabindex 属性と keydown イベントを使ってクリッカブルな振る舞いを実装することができます。以下は、BUYMA で使用されている React のコードの一例です。

<tr
  key={item.id}
  className="catalogs-table__row"
  onClick={() => handleSelectItem(item)}
  onKeyDown={(e) => {
    if (e.key === "Enter" || e.key === " ") {
      // Enter or Space で実行
      handleSelectItem(item);
    }
  }}
  tabIndex={0}
>
  ...
</tr>

このコードでは、tr要素に onClick イベントと onKeyDown イベントが設定されており、tabIndex={0}によってキーボード操作で選択可能になっています。

5. 画像の代替テキストの指定と装飾的な画像の取り扱い

原則的にコンテンツ上意味のある画像には必ず代替テキストを指定し、装飾的な画像は読み上げしないようにします。

img 要素の代替テキストは alt 属性で指定します。

<img alt="BUYMA" src="logo.png" width="130" height="25" decoding="async" />

アイコンフォントや SVG 要素に代替テキストを指定する際には、role="img" と aria-label 属性を併用して指定します。span や div、svgaria-label を指定すると、支援技術によっては適切に読み上げられない場合があります。また、 span や div の暗黙のロールが WAI-ARIA 1.2 より generic となったことで aria-label を付与するのは仕様違反となっています。安定した読み上げを確保するためにも、必ず role="img" を指定しましょう。

<span role="img" class="fab-icon fab-icon-facebook" aria-label="Facebook"></span>
<svg role="img" aria-label="X" viewBox="0 0 20 20"></svg>

装飾的な画像として img 要素を使用する場合は、alt 属性を空にします。alt 属性を省略すると、支援技術は URL を読み上げる可能性があります。

<img alt="" src="crown.svg" width="20" height="20" decoding="async" loading="lazy" />

装飾的なアイコンフォントや SVG 要素は、読み上げから除外するために aria-hidden="true" を指定します。

<span class="fab-icon fab-icon-information" aria-hidden="true"></span>
<svg aria-hidden="true" viewBox="0 0 20 20"></svg>

代替テキストの作成時には、画像が伝える情報を正確かつ適切にユーザーに届けることを心掛けるようにします。また、画像に関連しない情報をSEOのために加える行為はNGです。代替テキストはコンテンツの重要部分であり、他のテキストと同様、コンテンツ設計の初期段階で検討すると良いでしょう。

さらに、意味を持つ画像は CSS の background-image を使って背景として設定しないようにします。背景画像として設定してしまうと、支援技術を使用するユーザーが画像を認識できなくなります。また、background-image は loading 属性や decoding 属性を指定できないため、ウェブサイトのパフォーマンスに影響を及ぼす可能性があります。

参考:達成基準 1.1.1: 非テキストコンテンツを理解する

6. フォームコントロールには必ずラベルを付与する

フォーム内の入力欄やテキストエリア、セレクトボックス、チェックボックスラジオボタンなどのフォームコントロールには、label 要素を使って明確にラベルを付けるようにします。

label 要素の使用にはアクセシビリティの観点から大きな利点があります。フォームコントロールにフォーカスが合わされた際に要素の名前を読み上げることが可能になり、またクリックやタップの範囲が広がり、利用しやすくなります。

フォームコントロールとラベルを関連付ける方法としては、ラベルとフォームコントロールを label 要素で包むか、フォームコントロールの id 属性を label 要素の for 属性に紐付ける方法があります。

<label>
  ニックネーム
  <input type="text" name="nickname" />
</label>
<label for="nickname">ニックネーム</label>
<input id="nickname" type="text" name="nickname" />

デザイン上、フォームコントロールのラベルが UI 上に表示されていない場合でも、支援技術利用者のために aria-labelledby 属性や aria-label 属性を使用してラベル付けを行うことが望ましいです。

aria-labelledby 属性を使用した例

<form>
  <p id="keyword_label" style="display: none">検索キーワード</p>
  <input type="search" name="keyword" aria-labelledby="keyword_label" />
  <button>検索</button>
</form>

aria-label 属性を使用した例

<form>
  <input type="search" name="keyword" aria-label="検索キーワード" />
  <button>検索</button>
</form>

また、ラジオボタンチェックボックスなど、複数のフォームコントロールが意味的に一つのグループを形成する場合、fieldset 要素を使用してこれらをグループ化するようにします。この方法を採用すると、支援技術を使用してフォームコントロールを操作する際に、そのコントロールが属するグループの名前も読み上げられるようになります。

以下は、fieldset 要素を使用してラジオボタンをグループ化した例です。性別のコードは ISO 5218 で定められたものに従っています。

<fieldset>
  <legend>性別</legend>
  <ul>
    <li><label><input type="radio" name="gender" value="1" /></label></li>
    <li><label><input type="radio" name="gender" value="2" /></label></li>
    <li><label><input type="radio" name="gender" value="9" />その他</label></li>
    <li><label><input type="radio" name="gender" value="0" />不明・回答しない</label></li>
  </ul>
</fieldset>

また、ラベルをプレースホルダーで代用するUIも時折見かけますが、以下の観点から避けたほうがいいでしょう。

  • 記入時にラベルが消えてしまうためユーザーの短期記憶に負荷をかけることとなる
  • フォームの送信前にユーザーがどの項目に何を書いたのか判別できなくなる可能性がある
  • キーボード操作でフォーカスが当たるとラベルが消えることによる利便性の低下

参考:達成基準 2.4.6: 見出し及びラベルを理解する

7. フォームの入力欄のオートコンプリートを有効にする

オートコンプリートを有効にすることにより、ブラウザに保存された補完機能を利用できるようになり、結果としてユーザーは素早くフォームの入力を完了することができます。

<label>
  電話番号
  <input type="tel" name="tel" placeholder="(例) 090-0000-0000" autocomplete="tel" pattern="\d{2,4}-?\d{2,4}-?\d{3,4}" title="電話番号は正しく記入してください" />
</label>

サービス内でよく使用する autocomplete 属性の値は以下の通りです。

補完する要素
on autocompleteを許可する
off autocompleteを許可しない
name 氏名
given-name ファーストネーム(名前)
additional-name ミドルネーム
family-name ラストネーム(名字)
tel 電話番号
email メールアドレス
username ユーザー名orアカウント名
postal-code 郵便番号
address-level1 都道府県
address-level2 市区町村
address-level3 町域
address-level4 番地など
organization 企業・団体・組織名
cc-name クレジットカード登録名
cc-number カード番号
cc-exp カードの有効期限

多くのサービスでは、電話番号やクレジットカード番号などでオートコンプリート属性を活用します。一部の UI デザインでは、電話番号やクレジット番号の各部分を別々の入力欄で要求することがありますが、これは単に使いにくいだけでなく、オートコンプリートの利用を妨げる可能性があるため、デザイナーはこれらの情報を一つの入力欄で収めるよう配慮すると良いでしょう。

8. ラジオボタンチェックボックスを装飾する際は元の input の隠し方に気をつける

display:nonevisibility:hiddenされている input はキーボード操作でのフォーカスが不可能になってしまうため、ラジオボタンチェックボックスを装飾する際は別のアプローチで隠すようにしましょう。

<label class="checkbox">
  <input class="checkbox__input" type="checkbox" />
  <span class="checkbox__icon" aria-hidden="true"></span>
  <span class="checkbox__text">規約に同意する</span>
</label>
.checkbox__input {
  height: 1px;
  opacity: 0;
  position: absolute;
  width: 1px;
  z-index: -1;
}

さらに、装飾されたチェックボックスラジオボタンがフォーカスされた際には、その状態がユーザーに明確に認識できるようにしましょう。 以下の CSS は、フォーカスされた際にチェックボックスの周りにアウトラインを表示し、関連するテキストを下線で強調する方法の例です。

.checkbox__input {
  height: 1px;
  opacity: 0;
  position: absolute;
  width: 1px;
  z-index: -1;
}

.checkbox:has(.checkbox__input:focus-visible) {
  outline: 1px solid blue;
}

.checkbox__input:focus-visible ~ .checkbox__text {
  text-decoration: underline;
}

9. アコーディオン UI は可能な限り details 要素を利用する

アコーディオン UI を details 要素で実装すると、非アクティブな要素内のテキストもページ内検索で見つけることができ、キーボード操作や支援技術によるナビゲーションも可能になります。さらに、JavaScript を使わないためパフォーマンスが向上し、JavaScript が動作しない場合でもアコーディオンが利用可能になります。

<details>
  <summary>見出し</summary>
  <p>コンテンツ</p>
</details>

例えば、BUYMA ではスマートフォン表示のフッターメニューに details 要素が使用されています。

BUYMAでdetails要素が取り入れられているサンプルです

ただし、jQuery の slideToggle のようなアニメーション効果を追加したい場合、閉じる際に CSS の transition が効かないため別途 JavaScript が必要になります。

余談ですが、jQuery の slideToggle のようなアニメーションを実装する際、現代では折りたたむ要素に display: gridtransition: grid-template-rows .3s ease-out (所要時間とイージングはお好みで調整してください) を指定し、 開閉時に grid-template-rows の値を 0fr ↔ 1fr に変更するだけで対応が可能です。

参考:detailsとsummaryタグで作るアコーディオンUI - アニメーションのより良い実装方法 - ICS MEDIA

10. モーダル表示時は背面のコンテンツを読み上げ・選択不可能にする

モーダルコンテンツ(ダイアログやドロワーメニューなど)が表示されている際には、背後にあるコンテンツがキーボードや支援技術からアクセスされないよう設定します。

背面コンテンツに inert 属性を付与することで実現できます。inert 属性があると、ブラウザはその要素に対するユーザーの入力イベント(フォーカスイベントや支援技術からのイベントを含む)を無視します。

<body>
  <div id="wrapper" inert>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
  <div role="dialog" aria-modal="true" aria-labelledby="modal_1_title" tabindex="-1">
    <h2 id="modal_1_title">モーダルタイトル</h2>
    <p>モーダルコンテンツ</p>
  </div>
</body>

inert 属性は多くの現代のブラウザで利用可能ですが、未対応の環境での使用を検討している場合は、Polyfill が利用可能です。

www.npmjs.com

参考:HTMLElement: inert プロパティ

おわりに:BUYMA でウェブアクセシビリティを促進する理由

ウェブアクセシビリティを考慮することにより、サービスをより広範なユーザーに提供することが可能になります。これにより、アクセシビリティの問題からサービスの利用を諦めていた人々を取り戻し、機会損失を防ぐことができます。

さらに、使いやすいサービスを提供することで、全体的なユーザー体験を向上させることにもつながります。

弊社では、アクセシビリティチャンネルを Slack で設立し、アクセシビリティに関する Redmine チケットを作成するなど、取り組みを進めています。まだ完全に対応しきれていない部分は多いですが、近い将来、これらの「必ず達成しておきたいアクセシビリティ対応」を実現する目標に向けて努力を続けています。

明日の記事の担当は SELL チームの後藤さんです。お楽しみに!


株式会社エニグモ すべての求人一覧

hrmos.co

Emacs Orgmode を使ったタスク管理で生産性を爆上げしよう!

こんにちは、サービスエンジニアリング本部の寺田です!

この記事は Enigmo Advent Calendar 2023 の13日目の記事になります。

私はエンジニアとして BUYMA の決済システム・配送システムの保守/運用や、またこれらのシステム領域を中心としたサービスの新規開発などを主に担当しています。

この記事では Emacs Orgmode を使ったタスク管理の方法について紹介していきたいと思います。

みなさんはタスク管理に何を利用しているでしょうか??

紙とペン、OS 標準の Reminder アプリ、Google Calendar など、人それぞれ普段お使いのものがあるかと思います。

私は実際に普段の業務で Emacs Orgmode を利用したタスク管理を行っています。

これを使っていく中で私は以下のようなメリットを感じています。

  • 仕事の抜け・漏れを起こさなくなる。
  • 自分のキャパを最大限活かせる。(キャパオーバーになるのも避けられる。)
  • 日々の仕事に達成感を得られる。

今現在のタスク管理の方法がしっくりこない...と感じている方は、 ぜひこの記事をご覧いただき、一つ選択肢に加えてみてはいかがでしょうか?

Emacs Orgmode とは

Orgmode は Emacs拡張機能の一つです。独自のマークアップ言語を利用できるシンプルなテキストエディタですが、 非常に高機能でタスク管理などに便利な数多くの機能をサポートしています。

https://orgmode.org/ja/

当然 Emacs を必要とするので、Emacser ではない方には関係ない...とお思いかもしれません。 しかし私も普段の開発では VSCode + Vim キーバインディングを使っており、 Emacs は何もかもが素人中の素人です。

そういった方におすすめしたいのが Spacemacs です。

https://www.spacemacs.org/

Emacs ベースのエディタですが、デフォルトで Vimキーバインドが利用できます。 私も Spacemacs を利用していますが、ほぼほぼ Vim と同じ操作感で扱うことができています。

Orgmode を利用したタスク管理の流れ

ここからは私が実際に行っているタスク管理の流れを、Orgmode の機能の紹介とともに説明していきます。

ファイル構成

私はタスク管理のために3種類のファイルを用意しています。

.
├── TODO.org
└── weekly
    └── YYYYMMDD.org
└── daily
    └── YYYYMMDD.org

TODO.org

自分が担当することになったタスクはひとまず全て TODO.org に追加します。 まさしく TODO リストの役割を果たしており、こちらにタスク全量がリスト化されることになります。

weekly/YYYYMMDD.org

1週間分のタスクリストを表すファイルです。 TODO.org からタスクをピックアップして作成します。

daily/YYYYMMDD.org

1日分のタスクリストを作成する場所です。 weekly/YYYYMMDD.org からさらにタスクをピックアップして作成します。

(随時)TODO リストを作成する

何かタスクにアサインされたらまずは TODO.org に追加していきます。 合わせてここで作業時間の見積もりを行います。

Effort Estimates を利用すると見積もった時間をタスクに対して簡単に設定できます。

https://orgmode.org/manual/Effort-Estimates.html

また、TODO リストの未済管理には Multi-state Workflow を利用してステータス管理を行うと良いでしょう。

https://orgmode.org/guide/Multi_002dstate-Workflow.html

あらかじめ TODO, DONE ステータスを設定しておき、コマンド一発で遷移できるようにしておきます。

見積もり、ステータス設定を行った後の TODO.org は以下のようになります。

#+SEQ_TODO: TODO(t/!) | DONE(d) // Multi-state Workflow で遷移可能なステータスの定義。タスクに対して C-c C-t でステータスを変更できる。
#+PROPERTY: Effort_ALL 1:00 2:00 3:00 5:00 8:00 13:00  // Effort Estimates で設定可能な見積もり時間。タスクに対して C-c C-x e でここで定義した時間を設定できる。
  
*** 新規配送システム導入 
  **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:
  **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる
    :PROPERTIES:
    :Effort:   8:00
    :END:
  **** TODO 出品者は新規配送方法を利用して配送を実施することができる
    :PROPERTIES:
    :Effort:   13:00
    :END:

(毎週月曜日)1週間のやることリストを作成する

週初めの業務開始のタイミングで1週間のやることリストとして weekly/YYYYMMDD.org を作成します。

まずは自分が1週間で作業に充てられる時間を計算していきます。

ただし1週間のうち全ての時間を作業時間に使えるわけではないのでこれを考慮しましょう。

私の場合、勤務時間のトータルは 40 時間です。(週5日、1日8時間勤務で 5 * 8 = 40 時間)しかし定例や打ち合わせの時間を除くと大体 20~30 時間くらいが作業に充てられる時間となることがほとんどです。

当然休みの日や早退の日があればこれも考慮します。

計算が終わったらファイルにこの時間をメモしましょう。

次に TODO.org から計算した作業時間内でこなせるだけのタスクを選んで weekly/YYYYMMDD.org に追加します。

ここでのポイントですが、 週はじめの段階では作業時間の 80 % 程度でできるタスク量にしておいた方が良いです。

エンジニアは不具合の修正対応など突発的なタスクが発生しがちです。 こういった事態も吸収できるようにバッファを持たせておくことが重要です。

Work time: 20 hrs (80 %: 16 hrs) // 純粋に作業に充てられる時間だけを見積もる。
                                 // また、突発的なタスクに対応できるよう、80%程度の時間でできることに収めた方が良い。

*** 新規配送システム導入 
  **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:
  **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる
    :PROPERTIES:
    :Effort:   8:00
    :END:
  **** TODO 出品者は新規配送方法を利用して配送を実施することができる
    :PROPERTIES:
    :Effort:   3:00
    :END:

ここに追加したタスクの見積もりの合計が、作業時間内で収まっているかどうかを確認するには Column View を活用すると良いでしょう。

https://orgmode.org/manual/Column-View.html

親タスクに対して C-c C-x C-c とすると簡単に子タスクの見積もり時間の合計時間を表示することができます。

#+COLUMNS: %ITEM %Effort{:} // ITEM: タスク名, Effort: 見積もり時間 を表示

| *** 新規配送システム導入     | 16:00 | # 子タスクの合計時間が表示されている
| **** 購入者は新規配送方法... | 5:00 |
| **** 出品者は新規配送方法... | 8:00 |
| **** 出品者は新規配送方法... | 3:00 |

1週間分のやることを週初めの段階で決めることで次のようなメリットがあります。

まずキャパオーバーになることを避けられます。 既に予定しているタスクで今週分の作業時間が埋まっているならば、 これ以上の仕事を受けて、早急に対応することは難しいとすぐに判断できます。

逆にこの段階で作業時間が余るようであれば、 新しいタスクを拾ったり、忙しい時にはできないようなカイゼン系のタスクなどに取り組もうと、 週初めの段階で決めてしまえば良いのです。

後者は暇になってから考えても同じじゃないの?とも思いますが、 無計画に暇を迎えるとただただ時間を潰すだけになりがち。人間とは弱い生き物なのです...

こうして無理のない範囲に収めながらも、 自分の時間を最大限活用することが可能になっていきます。

(毎日朝)今日やることのリストを作成する

毎日業務を開始するタイミングで今日一日でやることのリストを daily/YYYYMMDD.org に作成します。

週初めの weekly/YYYMMDD.org の作成と同じように一日の作業時間を計算して、 その日に行えるだけのタスクを weekly/YYYYMMDD.org からこちらにコピーして追加します。

1週間分のやることリストの作成と同じように、 ミーティングの時間などは除いて、純粋に作業に充てられる時間に対してタスクを当てこみましょう。

Work time: 5 hrs

*** 新規配送システム導入 
  **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:

(業務時間中)作業時間を記録する

作業した時間は原則タスクに対して記録していきます。 Orgmode には Clocking commands というものが用意されています。

https://orgmode.org/manual/Clocking-commands.html

作業開始時には C-c C-x i、終了時には C-c C-x o を入力することで、各タスクに対して作業時間の記録が行えます。

*** 新規配送システム導入 
  **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] => 0:30 // 作業時間が記録される
    :END:

またポモドーロテクニックを使いながら作業時間を記録できる org-pomodoro というサードパーティのライブラリもあります。

https://github.com/marcinkoziej/org-pomodoro

こちらは作業開始時に C-c C-x p を実行すると、 あらかじめ指定した時間が経過したら自動的に作業終了となり時間が記録されます。

作業終了時にはチャイムが鳴ってお知らせもしてくれます。

作業時間中はタスクに全集中で取り組みましょう。X のタイムラインを見ながらダラダラと...なんてのはダメです(笑)。休憩する時は Clocking commands を停止してメリハリをつけましょう!

予定していた1日の作業時間を消化できたら、業務は一旦おしまいにします。おつかれさまでした!

(業務終了時)1日の作業を振り返る

予定していた作業時間に対してどのくらい自分がコミットできたかは、前述した Column View で簡単に確認することができます。

#+COLUMNS: %ITEM %Effort{:} %CLOCKSUM // CLOCKSUM: 記録した時間の合計 を表示

| *** 新規配送システム導入     | 5:00 | 5:12 |
| **** 購入者は新規配送方法... | 5:00 | 5:12 |

次に daily/YYYYMMDD.org に記録した作業時間を、weekly/YYYYMNDD.org に転記していきます。 こうすることで1日を経る毎に1週間分の作業時間の記録が溜まっていきます。

*** 新規配送システム導入 
  **** TODO 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-04 月 17:00]--[2023-12-04 月 18:12] =>  1:12
    CLOCK: [2023-12-04 月 13:00]--[2023-12-04 月 15:00] =>  2:00
    CLOCK: [2023-12-04 月 10:30]--[2023-12-04 月 12:00] =>  1:30
    CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] =>  0:30
    :END:
  **** TODO 出品者は新規配送方法に利用できる配送用ラベルを発行できる
    :PROPERTIES:
    :Effort:   8:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-05 火 15:30]--[2023-12-05 火 18:00] =>  2:30
    CLOCK: [2023-12-05 火 10:00]--[2023-12-05 火 12:00] =>  2:00
    :END:

このように日々、予定に対してどのくらい実際にコミットできたかを時間で確認できると、自分の仕事にかなりの達成感が出るのではないでしょうか??

モチベーションを維持することは、業務に対するパフォーマンスを最大化するためにはとても重要です。

(毎週金曜日)1週間の作業を振り返る

毎日の振り返りのタイミングで weekly/YYYYMMDD.org に作業時間の記録をしています。 なので週末の業務終了時点では1週間分の作業時間の合計が記録されているはずです。

大元の TODO リストとなっている TODO.org に1週間の作業結果を反映していきましょう。 作業時間の記録を転記して、もし完了になったタスクがあればステータスを変更します。

#+SEQ_TODO: TODO(t/!) | DONE(d)
#+PROPERTY: Effort_ALL 1:00 2:00 3:00 5:00 8:00 13:00
  
*** 新規配送システム導入 
  **** DONE 購入者は新規配送方法を指定して商品購入を行うことができる
    :PROPERTIES:
    :Effort:   5:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-04 月 17:00]--[2023-12-04 月 18:12] =>  1:12
    CLOCK: [2023-12-04 月 13:00]--[2023-12-04 月 15:00] =>  2:00
    CLOCK: [2023-12-04 月 10:30]--[2023-12-04 月 12:00] =>  1:30
    CLOCK: [2023-12-04 月 09:30]--[2023-12-04 月 10:00] =>  0:30
    :END:
  **** DONE 出品者は新規配送方法に利用できる配送用ラベルを発行できる
    :PROPERTIES:
    :Effort:   8:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-07 木 09:00]--[2023-12-07 木 10:00] =>  0:30
    CLOCK: [2023-12-06 水 16:00]--[2023-12-06 水 18:00] =>  2:00
    CLOCK: [2023-12-06 水 11:00]--[2023-12-06 水 12:00] =>  1:00
    CLOCK: [2023-12-05 火 15:30]--[2023-12-05 火 18:00] =>  2:30
    CLOCK: [2023-12-05 火 10:00]--[2023-12-05 火 12:00] =>  2:00
    :END:
  **** TODO 出品者は新規配送方法を利用して配送を実施することができる
    :PROPERTIES:
    :Effort:   13:00
    :END:
    :CLOCKING:
    CLOCK: [2023-12-08 金 17:00]--[2023-12-08 金 18:00] =>  1:00
    CLOCK: [2023-12-08 金 11:00]--[2023-12-08 金 12:00] =>  0:30
    CLOCK: [2023-12-07 木 13:00]--[2023-12-07 木 14:30] =>  1:30
    :END:

DONE になったタスクをそのまま TODO.org においておくと、 ファイルがどんどん巨大になってしまいます。Orgmode にはこれを Archive として他ファイルに移動してくれる機能がありますのでこちらを利用しましょう。

https://orgmode.org/guide/Archiving.html

タスクに対して C-c C-x s を実行することで、あらかじめ指定したファイルにタスクを移動させることができます。

#+ARCHIVE: ~/dev/orgmode/archives/myArchive.org:: # Archive 先の指定。

*** 新規配送システム導入 
  **** DONE 購入者は新規配送方法を指定して商品購入を行うことができる

まとめ

以上が Emacs Orgmode を利用したタスク管理の紹介になります。

冒頭にも話したことになりますがこのタスク管理を行うことで、

  • 仕事の抜け・漏れを起こさなくなる。
  • 自分のキャパを最大限活かせる。(キャパオーバーになるのも避けられる。)
  • 日々の仕事に達成感を得られる。

こういった恩恵を受けることができると私は感じています。

本記事を読んで気になった方は一度試してみてはいかかでしょうか??

明日の記事の担当は UXD(UI, UX の改善に主に取り組んでいるチーム)の新井さんです。お楽しみに!


株式会社エニグモ すべての求人一覧

hrmos.co

BUYMAのUXリサーチ『ユーザ理解を深めるためにアンケートとインタビューを進めている話』

こんにちは!UIUXデザイナーの和田です。

こちらはEnigmo Advent Calendar 2023 の12日目の記事です。

この記事では、BUYMAのサービスやアプリ・WebサイトのUIUXをより良くすることを目的に進めているUXリサーチ(ユーザ調査)についてご紹介します。

1.なぜUXリサーチ(ユーザ調査)をするのか?

より多くの方にBUYMAのファンになっていただけるように【真にユーザの体験価値にマッチしたお買い物体験を実現する】ためには、より深い顧客理解に基づいたPDCAが必要だと考えています。

そのために、現在BUYMAをご利用いただいているお客さまの利用文脈・背景(ユーザコンテキスト)を深掘りして、ユーザインサイトについての理解を深められるような調査を心がけています。

「どのようなお客さまなのか?」
→ ユーザ属性に加えて、家族構成や生活スタイルなど

「お客さまがどのような価値観・嗜好をお持ちなのか?」
→ ファッションアイテム全般についての価値観・潜在ニーズや行動特性

「どのようなことをBUYMAに期待してくださっているのか?」
→ BUYMAに対するニーズや期待値

BUYMAについて期待と現状のギャップはどのようなところにあるか?」
→ BUYMA利用における AS-IS(現状)とTO-BE(理想)のギャップ

上記のような点について、定量的・定性的それぞれの側面から現状把握と改善のヒントを得られるように、 ユーザアンケートとユーザインタビューを組み合わせて調査を実施しています。

2.BUYMAのUXリサーチ体制

弊社では、UIUXデザイナーとデータアナリストの調査チームでUXリサーチを企画・進行しています。 またテーマによって、他部署のメンバーと連携・協働することで調査に幅広い視点が盛り込めるように工夫を進めています。

複数の職種からなるチームで調査に取り組むことで、UXリサーチの企画→実施→分析→施策化→デザイン・実装→効果検証・・・のように調査メンバーが主体的にPDCAを回しやすい体制ができていると感じています。(UXリサーチ専任ではないため、各自がその他の業務を調整しながらUXリサーチを進行するため、調査期間中はどうしても忙しくなってしまうこともあります。)

3.調査テーマに沿ったUXリサーチ企画・進行のながれ

弊社では、設定した調査テーマに沿ってユーザ調査を実施しています。 1回のユーザ調査につき、約3〜4ヶ月で企画から分析までが完結するようなスケジュール感で予定を立てて進めています。 以下、調査の流れをご紹介します。

1.企画・準備(1.5ヶ月)

調査テーマは、社内で相談して関心の高いテーマを設定するようにしています。 テーマに沿って対象者の選定や、ヒアリングポイントを整理して、 アンケート設問やインタビューシナリオについて、レビューを繰り返して準備を進めています。

2.アンケート・インタビュー実施(1ヶ月)

アンケートはテーマや対象者に合わせて、メールのほか、LINEやアプリからも回答いただけるようにしています。 アンケート設問の中でインタビューにご協力いただける方を募る形で、インタビューにつなげられるようにしています。
アンケート回答期間が約1週間、インタビューセッティング期間が1週間、インタビュー実施期間が約2週間と、とにかく忙しい1ヶ月間です。

3.分析・まとめ(1.5ヶ月)

インタビューの実施が完了した後に、分析とまとめ作業をしています。 アンケートとインタビューそれぞれについて、分析結果・ヒアリング結果を記事にまとめて社内で共有するようにしています。 最後にチームでKPTの形式で調査のふりかえり会を実施して、調査の1サイクルが完了となります。

4.UXリサーチに利用しているユーザセグメントの分類軸

UXリサーチでは、ユーザや利用傾向を把握・グルーピングをしやすくするために、独自のユーザセグメント分類軸を定義して利用しています。
アンケート配信対象者を選定する際にも、 調査テーマに沿って「直近利用(R)・利用頻度(F)・価格志向(M)・年代(U)」の分類軸を選定条件に利用しています。

  • 直近利用(R)
    最終購入日時(直近購入)または会員登録日時を基準にした直近利用に関する軸

  • 利用頻度(F)
    最終購入から遡った1年間における購入頻度の軸

  • 価格志向(M)
    生涯購入における最高購入金額の軸
    ※どんな価格帯のアイテムに関心の高いユーザなのか分類するために生涯購入金額ではなく最高購入金額で分類しています

  • 年代(U)
    ライフスタイル変化に合わせた年代グループ

※ご興味のある方は、ぜひ昨年のAdvent Calendar 2022の記事もご覧ください!

tech.enigmo.co.jp

5.傾向・改善ヒントを掴むためのユーザアンケート

前述した通り、RFMUのユーザセグメント分類軸を利用して、調査テーマに沿ったアンケート配信対象者を選定してアンケート配信をしています。
アンケートの設問は以下のような形式の設問を利用しながら、定量的な傾向、定性的なご意見をキャッチアップできるような設問体系にしています。 アンケートツールは、Googleフォームを利用しています。

  • 単一選択形式/複数選択形式の設問
    仮説から選択肢を用意できるような場合は選択形式を利用して定量的に傾向を掴めるようにしています。

  • フリーアンサー(自由記述)設問
    選択肢に挙げられていなかったユーザインサイトを掴むために、積極的にフリーアンサーを用いてユーザのリアルな声をいただけるようにしています。 仮説として全く挙げられていなかったご意見を多数いただくこともあり、フリーアンサー回答の集計も非常に重要だと考えています。

  • 段階評価の設問
    尺度を測りたい場合に段階評価を用いています。

アンケート回答は、回答全体の分析に加えて、セグメント別の分析や回答を掛け合わせたクロス集計による傾向分析も実施しています。

6.ユーザインサイトを深掘るためのユーザインタビュー

アンケート集計後は、回答者のユーザセグメント傾向を分析して、インタビューしたいユーザグループをいくつかに分類するようにしています。 それぞれのユーザグループについて数名ずつインタビューを実施できるように、アンケート回答やBUYMAのご利用傾向をもとに候補者の方にお声がけをさせていただくようにしています。

弊社ではZoomを利用した60分のオンラインインタビューを実施しています。 オンラインでご参加いただけるため、地方にお住まいの方やテレワークで休憩中の方、お仕事先からご参加いただける方など・・・、本当にさまざまなご職業・ライフスタイルの方にご参加いただけています!

インタビューの内容は主に以下のセクションごとに進行しています。 また、後述するユーザプロフィールシートに基づいて注力したいヒアリングポイントを中心にお話をお聞きするようにしています。

ユーザーインタビューの内容

  • ユーザ自身について
    日中の過ごし方やファッション情報のチェック先などをお聞きしています。

  • ファッション全般の嗜好やお買い物について
    好きなブランドや普段のお買い物の傾向についてお聞きしています。

  • BUYMA利用について
    BUYMA認知・利用のきっかけやご利用の体験談などをお聞きしています。

  • 調査テーマヒアリング
    調査テーマに沿った内容について、価値観やニーズ、普段の行動、BUYMAにおけるペイン・ゲインなどをお聞きしています。

  • コンセプトヒアリング
    Figmaのプロトタイプを利用して新機能やUIのコンセプトについてのヒアリングを実施しています。

7.ユーザープロフィールシートの作成と活用

インタビュー対象者ごとにユーザの情報がまとまったユーザプロフィールシートを作成するようにしています。(個人が特定されるような情報は含まれていません。)
また、ユーザプロフィールシートに対象者のRFMUユーザセグメント分類軸も付与しておくことで、近しい傾向を持つユーザを探しやすくする工夫をしています。

ユーザプロフィールシートの内容

  • ユーザ基本情報:ユーザ属性やRFMU分類軸

  • BUYMA利用情報:BUYMAにおける利用・購入に関する情報

  • アンケート回答:アンケートのご回答まとめ

  • インタビューレポート:インタビュー議事録・記録など

ユーザインタビューの前に、あらかじめユーザプロフィールシートを作成することで、 ヒアリングポイントを精査して対象者の方に合わせたインタビューができるようにしています。

また、ユーザインサイトについて深掘りしたくなった際に、対象となるユーザプロフィールシートを探して活用できるように、 UXリサーチ資産としての整備を進行しています。

8.UXリサーチから得るもの

調査が完了した後、チームメンバーで分析結果・まとめをもとに施策ブレストを実施しています。
そこから、サービス・UIUX改善につながる施策化を進めるかたちで調査を役立てるようにしています。

実際に、UXリサーチで得られたインサイトから生まれた機能やサービスもBUYMAにどんどん盛り込まれています! さらなるBUYMAのUX向上に期待していただけますと幸いです!

明日の記事の担当は・・・
BUYMAのサーバーサイドアプリケーション開発をされている寺田さん】です!
お楽しみに!


株式会社エニグモ すべての求人一覧

hrmos.co

データ基盤のTerraformディレクトリ構成

こんにちは、今年の4月に新卒で入社したデータエンジニアの中村です。

この記事はEnigmo Advent Calendar 2023 の11日目の記事です。

入社してからは、社内のデータを利用者がより使いやすくなるように、データ基盤の整備・データ連携を進めております。

最近弊社のデータ基盤をTerraformによる管理に移行するタスクに取り組んでいるので、どのようなディレクトリ構成を採用したか説明していこうと思います。

データ基盤をこれからTerraform化しようとしている方に有益な情報となれば幸いです。

目次

背景

弊社のデータ基盤はGCP上にあり、BigQueryをはじめとした様々なサービスを活用してデータを収集しています。

GCPのサービスを利用する際に、これまではコンソール画面からボタンをポチポチしてリソースを作成していたのですが、それだと以下のような課題があります。

  • 作業ミスによって誤ったリソースが作成されてしまう。

  • GUIでの設定だと再現性が低くなってしまう。

  • リソースの管理が大変。(このリソース消しちゃって大丈夫だっけ?いつ誰が作ったんだ??みたいな状態が起こる。)

  • 同じような設定のリソースを繰り返し作成するのに効率が悪い。

これらの課題を解決するため、IaCサービスであるTerraformを活用し、GCP上のサービスをコードベースでの管理に移行することに決めました。

ディレクトリ構成

結論から行くと、色々試行錯誤した末に決定した弊社データ基盤のディレクトリ構成のベストプラクティスはこちらになります。

terraform
    ├── root (ルートモジュール)
    │    ├── bigquery
    │    │     ├── environments
    │    │     │    ├── development
    │    │     │    │    ├── terraform.tfbackend (開発環境のstateファイルのパス)
    │    │     │    │    ├── terraform.tfvars (開発環境の変数)
    │    │     │    │    └── dataset
    │    │     │    │           ├── <dataset>.yaml (yamlでデータセット毎に管理)
    │    │     │    │           ・・・
    │    │     │    └── production
    │    │     │         ├── terraform.tfbackend (本番環境のstateファイルのパス)
    │    │     │         ├── terraform.tfvars (本番環境の変数)
    │    │     │         └── dataset
    │    │     │                ├── <dataset>.yaml
    │    │     │               ・・・
    │    │     ├── main.tf
    │    │     ├── variables.tf
    │    │     └── versions.tf
    │    ├── datastream
    │    ├── <service_name>
    │    ・・・
    └── modules(子モジュール)
         ├── bigquery
         ├── datastream
         ├── cloud_monitoring
         ├── secret_manager
         ├── <service_name>
         ・・・

構成の説明

ディレクトリの役割

今回作成した構成は、大別するとrootディレクトリとmodulesディレクトリに分かれており、各役割は以下の通りです。

  • root: modulesディレクトリで定義したモジュールを呼び出す
  • modules: 再利用性の高いモジュールを配置

datastreamの例だと、以下のように、modulesで定義した複数のモジュールをrootから呼び出すことでリソースを定義しています。

module "secret_manager" {
  source = "../../../modules/secret_manager"
  ・・・
}

module "monitoring" {
  source                = "../../../modules/monitoring"
  ・・・
}

module "data_stream" {
  source           = "../../../modules/datastream/postgresql"
  ・・・
}

stateファイルの分割粒度

この構成では、stateファイルはリソース毎に分割しています。

stateファイルの粒度が大きすぎると、その分リリース毎の影響範囲が広くなってしまいます。

現状そこまで管理対象が多くないので、1環境1stateファイルのようなモノリシックな構成でも良かったですが、今後スケールすることを想定して、管理が煩雑にならないようにリソース粒度で分割しました。

工夫したポイント

ここまで聞くと、意外とスタンダードな構成だと感じた方も多いのではないでしょうか。

しかし、いくつか工夫した点があるのでそちらも説明していきたいと思います。

1. 環境差分の切り出し

リソース毎に分割する構成の場合、よくあるのが以下の構成です。

├── root
│    ├── bigquery
│    │     ├── development
│    │     │     ├── main.tf
│    │     │    ・・・
│    │     └── production
│    │           ├── main.tf
│    │          ・・・
│    ├── <service>
│    │     ├── development
│    │     └── production

この構成だと、「developmentをコピペしてproductionを作成して、差分だけ修正して・・」と二度手間になってしまいます。

その無駄な作業を省くために、環境毎で中身の変わらないファイルは共有する構成にしました。

├── root
│    ├── bigquery
│    │     ├── environments
│    │     │    ├── development
│    │     │    └── production
│    │     ├── main.tf
│    │     ├── variables.tf
│    │     └── versions.tf

この構成のメリットは、環境毎の設定値差分だけenvironmentsディレクトリに記載すればその他共通のファイルを繰り返し作成する必要がないので、コーディング量が減らせるところです。

また、開発環境だけあるリソースを作成したいといった場合は、tfvarsとモジュール側でうまく吸収することで環境差分にも対応できます。

Workspacesの利用も考えましたが、公式では開発・本番環境毎に分けるような利用方法は非推奨だったので、今回は利用を見送りました。

2. BigQueryのリソースはyamlで管理

データ基盤の中でも中核をなすBigQueryの管理についてですが、一つのtfvarsで管理したいとなるとファイルが膨大になってしまします。

そこで、データセット毎にyamlファイルを作成することで見通しをよくしました。

本来tfvarsで全てのデータセットを管理すると数千行は当たり前に行くところを、以下のようにデータセット毎に設定ファイルを分割することで管理が非常に楽になりました。

dataset_id: "hoge"
description: "hogehoge"
location: "US"
access_roles_group_by_email:
    "hoge@hoge.co.jp": ["READER"]
access_roles_user_by_email:
    "hogehoge@hogehoge.co.jp": ["READER"]

Terraformでは、yamldecode関数が用意されているので、main.tf側で以下のように記述することでTerraformのmapとして扱うことができます。

locals {
  yaml_files = fileset("./environments/${var.env}/dataset", "*.yaml")

  datasets = {
    for file in local.yaml_files :
    element(split(".", basename(file)), 0) => yamldecode(file("./environments/${var.env}/dataset/${file}"))
  }
}

今後の展望

まだTerraform化できていないサービスがあるので、引き続き完全Terraform化を目指していきたいと思います。

また、リリース作業は現在手動で行なっているので、CI/CD環境も整えて作業効率を高めていきたいところです。

最後に

データ基盤のTerraformディレクトリ構成に関する記事はネット上を探しても事例が少なく、ゼロから作るのは大変苦労しました。少しでも参考になった部分があれば幸いです。

最後までご覧いただきありがとうございました!

明日の記事の担当は、UIUXデザイナーの和田さんです。お楽しみに!


株式会社エニグモ すべての求人一覧

hrmos.co

ChatGPTを活用したAIで商品をさがす機能実現のための工夫

こんにちは、データサイエンティストの髙橋です。業務では企画/分析/機械学習モデル作成/プロダクション向けの実装/効果検証を一貫して行っています。

この記事では 2023/11/01 に公開した AIでさがす機能の実現にあたり工夫した点について紹介します。本機能では OpenAI の Chat Completions API (Web 版の ChatGPTAPI 版のようなもの)を活用しているため、同 API 関連での工夫が中心となります。

この記事は Enigmo Advent Calendar 2023 の 9 日目の記事です。

AIでさがす機能の紹介

AIでさがす機能は BUYMA の Web サイト・アプリの検索機能の 1 つとしてボタンを追加しており、Web サイトの場合は以下のボタンを押下すると AIでさがす画面に遷移します。

ユーザーは事例から質問するか、アイテムのカテゴリを選択した上でフリーワードで質問するかのどちらかからチャットを始めることができます。事例は、アイテムのカテゴリとフリーワードの文章をあらかじめ弊社で作成したものです。
例えば、レディースアイテムカテゴリを選択した上で「30代におすすめのハイブランドレディースバッグ教えてください」と入力してメッセージを送信すると、以下のようにいくつかの商品が表示されます。

上記画面において、商品画像を押下すると該当の商品詳細画面が開き、「もっと見る」を押下すると表示されているおすすめ(シャネル チェーンバッグ など)を検索キーワードとした検索画面が開きます。

さらに、表示された商品群の末尾に以下のように「選んだ理由を聞く」ボタンが表示されており、押下すると選んだ理由が表示されます。

理由も表示できるようにすることでユーザーに納得感を得てもらったり、理由からこういう商品も良いかもと思い検索してもらったりすることを狙っています、また、弊社では FISS Project というファッションのプロが一緒に服を選んでくれるサービスも運営しており、より要望に特化した商品を選んでほしいと思われた方々の同サービスへの流入増加も狙いの1つです。そのため、以下のように理由表示後に同サービスの紹介も表示されます(カテゴリとしてメンズアイテムを選択した場合のみ表示されます)。

素早い機能公開とコスト削減の工夫にフォーカスした理由

目次を見ていただくと分かるように素早い機能公開とコスト削減に関する工夫が中心です。この2つにフォーカスした理由は以下のとおりです。

  • AIでさがす機能は他社の類似事例が少なく、どの程度の効果が見込めるか予測が難しかった
  • このような機能はどういったものであれば BUYMA の会員にとって有益であるのか予測が難しかった

そこで、可能な限り低コストで素早く公開し 分析→改善 のサイクルを回すことにより、どのようなものが有益か、どの程度効果が出そうなのかを予測しやすくしようと考えました。

結果として、本格的に開発を開始してから約6ヶ月で公開することが出来ました。なお、この期間にはソフトウェアエンジニアの方によるチャット画面周りのフロント/バッグエンド開発も含んでいます。 また、具体的な数値は出せませんがコストもかなり抑えた状態で公開できました。

素早い機能公開のため工夫

まずは素早い機能公開のための工夫を3つ紹介します。

おすすめ商品以外の質問には回答できなくする

企画の初期段階では商品のおすすめ以外の質問にもある程度対応できるようなチャットボットにすることを考えていました。例えば、BUYMA 利用時の QA でよくある「関税がかかるのか?」という質問に回答できるようにするなどです。しかし、それを実現しようとすると誤った情報を回答させないために BUYMA 公式の QA ガイドを embedding 化し、ユーザーの質問と類似した文書を抽出、その文書をプロンプトに組み込み回答させる、などが必要だろうと考えました。しかし、この作業自体に時間がかかる上に様々な質問に対して誤った情報を回答しないかを検証する時間もかかるため、思い切っておすすめの商品以外の質問には回答できないようにしました。

これを実現するために、ユーザーからのメッセージに対して Chat Completions API により回答を生成させる前に、メッセージがおすすめの商品に関するものかどうかを分類しそれ以外の質問の場合は以下のように固定したメッセージを返すようにしました。

なお、分類自体も Chat Completions API により行うこととしました。これは分類モデルを作成する手間を削減できると考え検証したところ、Chat Completions API による分類でも十分な精度が得られたためです。そのため、ユーザーのメッセージ 1 つに対して最大で 2 回 Chat Completions API を呼び出しています。

RAG は行わずシンプルに検索結果を返す仕組みとする

RAG (Retrieval-augmented Generation) とは、外部データを ChatGPT などの大規模言語モデルに与えることでそのデータに基づいた回答を生成させる手法です。

記事執筆時点(2023/12/09)で採用している方法はシンプルで、 ChatGPT におすすめ商品をいくつか生成してもらい、それぞれを検索キーワードとした検索結果内の複数個の商品 URL と検索結果の URL を表示するというものです。

企画初期段階では RAG のようなやり方(例えば商品情報を embedding 化し、回答の embedding と類似する商品を取得して画面に表示するなど)を検証していたのですが、以下の課題から RAG の方向性を突き詰めると時間がかかりそうなことを実感し、シンプルな方法を採用することとしました。embedding 化には OpenAI の text-embedding-ada-002 を利用しました。

  • 商品のメタデータ(商品名やカテゴリ、ブランド名など)を embedding 化したものと、ChatGPT の回答を embedding 化したものの類似度を取って上位の商品を見ると、回答とずれた商品がそれなりに出現する。
    • 例えば、ChatGPT の回答では「オックスフォードシャツ」と記載があるが、類似度上位にはTシャツが出現することがありました。
  • 上記のような問題があった場合に原因を特定することが難しい。
    • 商品名やカテゴリ、ブランド名などの複数の商品メタデータを embedding 化しているため、どのメタデータが原因なのか、特定のメタデータの組み合わせが原因なのかなどを特定することが難しく、また特定できたとしても今度は別のパターンで問題が発生することがありました。これを対応していると、この部分の開発のみでそれなりの時間を要してしまうと考えました。

また、仮に embedding 化がうまくいったとしても、それをシステム化することにも時間がかかるだろうと考えました。具体的には、 BUYMA には 600 万品以上の商品があり、この数の embedding と類似度を高速に計算するには Vertex Matching Engine などの利用が必要であり、弊社ではまだ利用実績もなかったため検証・構築に時間を要すると考えました。その上、コストもそれなりにかかりそうであったためシンプルな方法を採用するに至りました。

なお、記事執筆時点(2023/12/09)では RAG を利用した回答生成を高速に実現できそうな機能が OpenAI の Assistants APIGoogle Cloud の Grounding for PaLM API としてアナウンスされており、今後はユーザーの利用状況を見ながらこれらの活用も検討予定です。

API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする

これは弊社のソフトウェアエンジニアとの連携にかかる時間を削減するための取り組みです。Chat Completions API では過去のユーザーと AI のメッセージのやり取りを API の呼び出し側で組み立てる仕様になっています。具体的には、Open AI のドキュメントに記載のように API 呼び出し時の messages パラメータに過去のユーザーと AI のやり取りを渡す必要があります。

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-3.5-turbo",
    "messages": [
      {
        "role": "system",
        "content": "You are a helpful assistant."
      },
      {
        "role": "user",
        "content": "Who won the world series in 2020?"
      },
      {
        "role": "assistant",
        "content": "The Los Angeles Dodgers won the World Series in 2020."
      },
      {
        "role": "user",
        "content": "Where was it played?"
      }
    ]
  }'

これを実現するための単純な方法は、アプリ側で画面に表示しているユーザーと AI のメッセージを取得し、 API 呼び出し時に渡すようソフトウェアエンジニアの方に依頼することです。しかし、そうすると過去のメッセージ群の一部を意図的に変更したい場合に、こちらの意図しないメッセージ群が渡されるるなどしてソフトウェアエンジニアの方との連携に時間がかかってしまうだろうと想定しました。
そこで、Chat Completions API をラップする独自 API を作成し、その API のレスポンスとして次の呼び出し時に渡すべきメッセージ群を返す仕様としました。具体的には、先程の Open AI のドキュメントの例を用いると Who won the world series in 2020? というメッセージで呼び出したときのレスポンスに、以下のように次の呼び出し時に渡すべきメッセージ群を含め、

{
    "past_messages": [
        {
            "role": "user",
            "content": "Who won the world series in 2020?"
        },
        {
            "role": "assistant",
            "content": "The Los Angeles Dodgers won the World Series in 2020."
        }
    ]
}

次の呼び出し時は以下のようにユーザーのメッセージと過去のメッセージ群をパラメータとして渡す仕様の独自 API を作成しました。

{
    "message": "Where was it played?",
    "past_messages": [
        {
            "role": "user",
            "content": "Who won the world series in 2020?"
        },
        {
            "role": "assistant",
            "content": "The Los Angeles Dodgers won the World Series in 2020."
        }
    ]
}

この独自 API を作成したことでこちらの意図しないメッセージ群が渡されてしまうことを防ぐことが出来ました。また、後述しますが過去のメッセージ群の一部を意図的に変更することを行っており、それを独自 API のレイヤーで行うことでソフトウェアエンジニアの方に意図的な変更の実装を依頼しこちらで確認するなどの連携が不要となり、開発期間の短縮に繋がりました。

コスト削減のための工夫

次にコスト削減のための工夫を 2 つ紹介します。ここで紹介するものは 1 回の Chat Completions API 呼び出し単位で見れば小さいものですが、BUYMA の会員数は 1000 万人以上であり 1 回の API 呼び出しが全体のコストに与える影響は大きいものとなります。どの程度効果が出るのか予測が難しく、また企画当初から全会員への公開を目指していたため少しのコスト削減でも検討することとしました。

一部のメッセージ群を API 呼び出し時は除外する

Chat Completions API の料金は input tokens による従量課金性であり、選んだ理由の回答が長文になりがちであるためそれに関するメッセージ群を API 呼び出し時は除外するようにしました。

例えば、ユーザーが「30代におすすめのハイブランドレディースバッグ教えてください」というメッセージを送り、「シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ」というメッセージが返された後に、「選んだ理由を教えて」とメッセージを送ると以下のようなメッセージが返されます。


シャネル チェーンバッグ
シャネルのチェーンバッグは、エレガントで上品なデザインが特徴です。高級感溢れる素材とクラシックなチェーンストラップが、大人の女性にふさわしい印象を与えます。また、シャネルのバッグはブランドの人気アイテムであり、長く愛用することができるため、コストパフォーマンスも高いです。

グッチ トートバッグ
グッチのトートバッグは、洗練されたデザインと高品質な素材が魅力です。ブランドのアイコンであるGG柄やロゴプリントが施されたデザインは、一目でグッチと分かる存在感を持っています。また、トートバッグは容量も大きく使い勝手が良いため、日常使いから旅行まで幅広く活躍します。

プラダ ショルダーバッグ
プラダのショルダーバッグは、モダンで洗練されたデザインが特徴です。シンプルながらも上品さを兼ね備えたデザインは、幅広いスタイルに合わせやすく、長く愛用することができます。また、プラダのバッグは高品質な素材と丁寧な作りが評価されており、耐久性も抜群です。


このメッセージの token 数は約 448 であり、以降の Chat Completions API 呼び出し毎に約 $0.00067 のコスト *1 がかかることとなります。先程述べたようにBUYMAの会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00067 のコスト増加でも大きな影響となります。そこで、過去の選んだ理由に関するメッセージ群を API 呼び出し時に除外することとしました。

具体例として、ユーザーが以下の順番でメッセージを送ったとし

  • 30代におすすめのハイブランドレディースバッグ教えてください
  • 選んだ理由を教えて
  • 小旅行で使えるおすすめレディースバッグは?

この時のメッセージ群が以下のようになったとします。

{
    "messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
        {
            "role": "user",
            "content": "選んだ理由を教えて"
        },
        {
            "role": "assistant",
            "content": "シャネルのチェーンバッグは、... / グッチのトートバッグは、... / プラダのショルダーバッグは、..."
        },
        {
            "role": "user",
            "content": "小旅行で使えるおすすめレディースバッグは?"
        },
        {
            "role": "assistant",
            "content": "トートバッグ / ショルダーバッグ / リュックサック"
        }
    ]
}'

この時、次の API 呼び出し時に渡す過去のメッセージ群を以下のように理由に関するやり取りを除外したものとしました。

{
    "messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
        {
            "role": "user",
            "content": "小旅行で使えるおすすめレディースバッグは?"
        },
        {
            "role": "assistant",
            "content": "トートバッグ / ショルダーバッグ / リュックサック"
        }
    ]
}

なお、上記の除外処理はアプリ側でも可能ですが、「API に渡す過去のメッセージの組み立てをアプリ側でやらないこととする」にも記載したようにデータサイエンティストとソフトウェアエンジニア間の連携にかかる時間を削減するために、独自 API のレイヤーで行う仕様としました。具体的には、「選んだ理由を教えて」というメッセージで独自 API を呼び出した時のレスポンスは、以下のように理由に関するメッセージ群を除外したものになる仕様としました。

{
    "past_messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
    ]
}

そして、次に「小旅行で使えるおすすめレディースバッグは?」というメッセージで独自 API を呼び出す時は、上記の理由に関するメッセージ群が除外された過去のメッセージ群をそのまま渡す仕様としました。

{
    "message": "小旅行で使えるおすすめレディースバッグは?",
    "past_messages": [
        {
            "role": "user",
            "content": "30代におすすめのハイブランドレディースバッグ教えてください"
        },
        {
            "role": "assistant",
            "content": "シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ"
        },
    ]
}

プロンプトに質問と回答の例を含めないこととする

この機能ではユーザーからのメッセージに対して弊社システム側でプロンプトを追加し、Chat Completions API にリクエストを送信しています。そのプロンプトについて、開発初期は以下のようにユーザからのメッセージと AI からのメッセージの例をいくつか含めることでこちらの意図したフォーマットで回答を生成させ、複数のおすすめを分割していました(以下のプロンプトは実際に利用しているものとは異なります)。

「おすすめ1 / おすすめ2 / おすすめ3」というフォーマットで回答してください。
以下が質問と回答の例です。
質問:30代におすすめのハイブランドレディースバッグ教えてください
回答:シャネル チェーンバッグ / グッチ トートバッグ / プラダ ショルダーバッグ

質問:小旅行で使えるおすすめレディースバッグは?
回答:トートバッグ / ショルダーバッグ / リュックサック

しかし、ある程度開発が進んだ時点でコストを見積もったところ、予想以上にコストがかかる結果となりました。そこで、上記のプロンプトの 以下が質問と回答の例です。 以降を除外することを検討しました。

以下が質問と回答の例です。 以降の token 数は約 140 であり、 Chat Completions API 呼び出し毎に約 $0.00021 のコストがかかることとなります。先程述べた通り BUYMA の会員数は 1000 万人以上のため 1 呼び出しあたり約 $0.00021 のコスト削減でも見積もり上のコスト削減は大きなものでした。

一方で、実際に上記のプロンプトの 以下が質問と回答の例です。 以降を除外し、様々なユーザーのメッセージパターンで検証してみたところ、おすすめされるものにはほとんど変化がないもののフォーマットにブレが生じ、おすすめがうまく分割出来ないことがありました(半角の / が全角の になるなど)。 そこで、うまく抽出できなかったパターンに対し正規表現などの後処理を利用して対応しました。

結果として、公開から1ヶ月程度が経過した時点でフォーマットのブレによりおすすめが抽出出来ない問題は1件も発生しておらず、コストを削減しつつ安定して回答を生成することが出来ています。

まとめ

先日公開した AIでさがす機能実現のための工夫を紹介しました。特に素早い機能公開とコスト削減にフォーカスすることで安価に早く公開することが出来ました。今後は、利用状況を分析し RAG などにより BUYMA データとの連携を強化したり、Fine-tuning などによりさらにユーザーにとって有益な回答を生成させたりなどを検討予定です。


株式会社エニグモ すべての求人一覧

hrmos.co

*1:当機能の作成期間(2023/05 ~ 2023/11)では gpt-3.5-turbo の 1K tokens あたりの料金は $0.0015 であったためその数値を用いて計算しました。