🍊

Pleasanter で日報を作って、AWS Comprehend で感情分析をする

2022/12/05に公開

2022 個人アドベントカレンダー の記事です。

これは何

  • 日報の提案
    • 自分が使いやすいと思うかたち
  • AWS 連携として、AWS Comprehend でコメントの感情分析をする
    • Web システムである Pleasanter には HTTP で連携して機能拡張するところに将来性を感じてます。
      • スクリプトを駆使して、テーブル内に機能を実装すると、秘伝の Excel/VBA みたいなことになると思ってます。

デモサイトでも動作するサイトパッケージはこちらから。
サーバスクリプトが予期せず発動しないよう条件を外しています。
また Lambda はご自身でご用意ください。

日報

デモサイトにも「営業日報」のサンプルがあり、新規作成のテンプレートに「作業日報」はあるが、こういう日常的なものこそ細かく使い勝手を改善していけるのが、ノーコードツールの良さな気がします。

  • 今日の日報があれば、そのテーブルの編集画面に移動する
    • サーバスクリプトで対応。これがやりたかった。
  • 最大 4 作業入力する
    • あまり細かくしない。計算式で作業時間の和を求める
  • 説明項目には [md] を既定で入れる
    • 実際には ・良かった点 ・改善点 などガイド付けをしたほうがよさそう。
  • 数値 A は 1 以上にする。1 作業はする想定。また細かくしすぎないよう、整数に切り上げ。

といったところが自分にとっての使いやすさと考えました。

サーバスクリプト:今日の日報に移動する

  • 処理としては、サイトの一覧が表示されそうなアクションのときに、割り込みをして今日の日報を下がしてリダイレクトをかける流れ
    • 振り返りなどで一覧を利用する場合のために、編集画面などのガイドとして、クエリパラメータを付けたリンクを記載。
  • 自分用サイトなのでユーザを考慮していないので、チーム利用する場合は、既存レコードを検索する際にユーザを考慮する必要がある
try {
  const host = 'http://127.0.0.1:5000';
  const siteId = context.SiteId;
  const now = new Date();
  now.setHours(now.getHours() + 9);
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
  // サイトID とレコード ID が一致するということは、レコードではなくサイトを見ようとしている
  if (siteId === context.Id) {
    if (context.Action !== 'new' && !context.QueryStrings.Bool('allowList')) {
      const param = {
        View: {
          ColumnFilterHash: {
            DateA: `["${today.toLocaleDateString(
              'ja-JP',
            )} 00:00:00,${today.toLocaleDateString('ja-JP')} 23:59:59"]`,
          },
        },
      };
      const rec = items.Get(siteId, JSON.stringify(param));
      if (rec.Length < 1) {
        context.Redirect(`${host}/${context.Controller}/${siteId}/new`);
      }
      const recId = rec[0].ResultId || rec[0].IssueId;
      context.Redirect(`${host}/${context.Controller}/${recId}/edit`);
    }
  }
  context.Log(context.Id);
} catch (e) {
  context.Log(e.stack);
}

感情分析

上長側にとって、日報にあると便利な機能を考えて、テキストの感情分析があると良いのではないかと考えました。

実装は次のとおりです。

  • AWS Lambda に HTTP エンドポイントを作成して、Lambda 上で HTTP リクエストに対して、Comprehend の結果を応答させます。
    • Pleasanter 上で AWS SDK を実行するのは難しいし、そもそもそんなことをすべきでないと考えています。
    • Lambda でなくとも SDK を導入できて HTTP リクエストを受け付けられるサーバを立ててもよいと思います。
    • 今回は、実装可能性の確認のため Lambda の HTTP エンドポイントは認証を付けませんでした。セキュリティには十分ご留意ください。
  • サーバスクリプトでは httpClient でデータを post して、応答された評価値をデータ記入しています。

サーバスクリプト:Post と応答内容の記述

  • 条件は、作成前、更新前
    • 「作成後」「更新後」で、自レコードを Update すると、このサーバスクリプトによるアップデートがトリガーになって、再度処理が走るので基本的に自レコードに入れる処理は作成前・更新前が望ましいです。
    • 本サンプルでは、HTTP リクエストをする都合上、何もしない場合に比べてわずかながら待ちが生じたことが感じられます。
try {
  httpClient.RequestUri =
    'https://${randomString}.lambda-url.ap-northeast-1.on.aws/';
  const content = { sentence: model.DescriptionE };
  httpClient.Content = JSON.stringify(content);
  const result = JSON.parse(httpClient.Post());
  model.ClassA = result.Sentiment;
  model.NumF = result.Positive;
  model.NumG = result.Negative;
  model.NumH = result.Mixed;
  model.NumI = result.Neutral;
} catch (e) {
  context.Log(e.stack);
}

Lambda:Comprehend での分析

  • ランタイムは Node 18 です。
  • Post されたパラメータがどのように event に渡されるかはドキュメントに記述があります。
import {
  ComprehendClient,
  DetectSentimentCommand,
} from '@aws-sdk/client-comprehend';
const detectParameterBuilderJa = (statement) => ({
  Text: statement,
  LanguageCode: 'ja',
});
export async function handler(event, ctx, callback) {
  try {
    const text = JSON.parse(event?.body) ?? 'こんにちは';
    const clientConfig = { region: 'ap-northeast-1' };
    const client = new ComprehendClient(clientConfig);
    const detectParameter = detectParameterBuilderJa(text);
    const detection = new DetectSentimentCommand(detectParameter);
    const { Sentiment, SentimentScore } = await client.send(detection);
    const responseBody = {
      Sentiment: Sentiment ?? '',
      Postiive: SentimentScore?.Positive ?? 0,
      Negative: SentimentScore?.Negative ?? 0,
      Mixed: SentimentScore?.Mixed ?? 0,
      Neutral: SentimentScore?.Neutral ?? 0,
    };
    return {
      statusCode: 200,
      body: JSON.stringify(responseBody),
    };
  } catch (e) {
    console.error(e);
    return {
      statusCode: 400,
      body: JSON.stringify(e),
    };
  }
}

発展

  • 通知やリマインダーと、ビューを使って、ネガティブな評価の高い内容を通知するとよいかもしれないです
    • 通知だと変更の都度になるので、レビュー日を設けてそれをリマインドの対象期限にして、条件を付けてリマインドが妥当でしょうか
  • テキストの評価項目は、項目のアクセス制御によって、特定のグループや組織のみに参照可能とすることもできそう
    • この場合に、サーバスクリプトで書き込みが適切に行えるかは未検証です
    • 本人に見えて気付きにする、というのもあり得るので、この辺はチーム・メンバーに依存しそう
  • 作成・更新の都度評価するので、コスト観点ではあまり良い振舞いではないです
    • ただ、なんでもかんでもバッチ処理、は個人的に好きではないです
    • レビュワーがレビューして完了するタイミングで評価する、といった方法はありかも。

さいごに

  • データ指向アプリケーションとして、いかに利用者を誘導して欲しいデータを作れるか、という観点で、その場で改善できるのがプリザンターの強みだと思ってます
  • HTTP リクエストベースのルーブ・ゴールドバーグ・マシン的な繋ぎ込みで他に何かできないかまた考えてみたいです
GitHubで編集を提案

Discussion