🍟

2023幎に䜜成した瀟内業務支揎システムをたずめおみた

に公開

今幎の IVRy Advent Calendar は玅癜察抗戊を行っおいたす

https://x.com/IVRy_jp/status/1730521537294520802?s=20

https://adventar.org/calendars/9247

https://adventar.org/calendars/9453


株匏䌚瀟 IVRy アむブリヌ 瀟員番号 7 番 ゚ンゞニアのボルドヌです。
私は普段 IVRy ではフロント゚ンドずモバむルアプリの開発を担圓しおいたす。

今回は私が 2023幎に䜜成した瀟内業務支揎システムをたずめお玹介したす。

2023幎に䜜成した業務支揎システム

䞀郚合宿で開発したものを含みたすが、基本的には業務時間倖に私が有志ずしお取り組んだものです。
時系列に沿っお玹介しおいきたす。

時期 システム 利甚頻床 補足
1,2月 Slack Reaction 集蚈 月1,2回 月初めの衚地時に利甚の他、任意のリアクションが気になった際に利甚
3月 コラム蚘事の Textlint 週1回以䞊 毎週公開しおいるコラム蚘事の Pull Request 提出時に利甚
5月 VoIP Push 通知の自動着信テスト 毎日 毎日昌倜問わず皌働䞭
6月 メンタルヘルスチェック 週1回 アンケヌト送信が週1回、回答受付は垞時皌働䞭
9月 特定の Slack チャンネル矀に䞀括参加 䞍明 Workspace 内の党ナヌザヌに公開しおいるため頻床は䞍明
9月 動きがない Slack チャンネルの自動アヌカむブ 月1回 月末に予告、月初にアヌカむブ実行
10月 OCR 名刺自動曞き起こし 䞍定期 展瀺䌚開催期間䞭は数分単䜍で実行

Slack Reaction 集蚈

昚幎のアドベントカレンダヌにお、匊瀟の定めおいる Value を根付かせる目的で 3぀の Value それぞれの Slack カスタム絵文字 を䜜成し、誰が䜓珟できおいるか集蚈しお競い合える仕組みを䜜りたした。

https://zenn.dev/ivry/articles/60a97aae9166f0

その埌 1,2月に改良を加えたしお、リアクションを 貰った / あげた人それぞれを集蚈できるようにするず共に、スプレッドシヌトに曞き出すようにしたした。
ずいうのも、玹介した ナヌザヌ毎の reactions.list を取埗しお集蚈する凊理では党おのリアクションが取埗できおいるため実行時に匕数に指定しおいないものも難なく集蚈できる利点があるので掻甚したした。


コマンドで指定したリアクションの䞭で巊から倚い順、続いお指定されおいないものが倚い順に䞊びたす[1]

結果ずしお 1幎経った今でも様々な甚途で集蚈結果を楜しんでもらえおいるず自負しおいたす。

スプレッドシヌトに曞き出す際にシステムの絵文字ずカスタム絵文字それぞれを衚瀺する郚分に結構苊戊したした。
システム絵文字は察応マップを地道に䜜成し、カスタム絵文字は emoji.list で取埗した 画像URL をスプレッドシヌトの image関数 を甚いお衚瀺するようにしおいたす。
その他 行ず列の固定 や䞀芧性を考慮した幅調敎などを API で行うこずもやや苊劎したした。

コラム蚘事の Textlint

3月にコラム蚘事の運甚改善に぀いお玹介する蚘事を曞きたした。

https://zenn.dev/ivry/articles/e4a13bd03b91ae

こちらはすぐに需芁がなくなるかず思いきや最近行った倧量のリラむトで倧掻躍しおおり Textlint の仕組みがあっお助かりたした。


先日倧量の lint error が出た PR 䞊のキャプチャ

3月に運甚が蟛いけど

入皿ツヌルの開発は優先順䜍が䞊がらず実珟できおいたせん。

ず蚀っおいたのは決しお嘘ではないのですが、開発メンバヌも増えお今では䞀郚ですが CMS に移行が完了しおプレビュヌも可胜な状態ずなっおいたす。
本圓にあっずいう間の 1幎でした。近々開発を担圓したメンバヌのテックブログずしお玹介できるかず思いたす。

VoIP Push 通知の自動着信テスト

5月に着信通知に関する障害を発生させおしたったこずがきっかけで VoIP Push 通知の着信を定期的に確認する仕組みを䜜りたした。[2]

https://zenn.dev/ivry/articles/882a969e0f3bb6

今も毎日 瀟内に垞蚭しおある実機が通知を受けおおり、先日 人員増加に䌎うネットワヌク増匷察応で瀟内ネットワヌクが数時間停止しおいた際には無事にアラヌトを䞊げおいたした。
このシステムのログがせっせず Slack に吐かれおいるこずによっおお客様からの「通知が来ない」ずいう問い合わせを受けおも党䜓圱響ではないず確信を持っお心穏やかにいられるので助かっおいたす。

メンタルヘルスチェック

瀟員のメンタル面のアラヌトを機械的に怜知する仕組みずしお毎週 珟圚の調子を5択で問う Slack BOT を代衚 奥西の芁望を受けお開発し、詊隓運甚しおいたす。[3]


毎週 5択の質問が DM で届いお回答しおいる様子

このシステムは 6月に開催した 第1回IVRy開発合宿 にお䜜成したした。実質開発時間は 4,5時間ずタむトだったためハマらないように事前調査等行った䞊で臚み無事時間内に仕䞊げるこずができたした。

https://zenn.dev/ivry/articles/0937b755bb2a86

この時初めお Slack の Block Kit Builder を䜿っおみたのですが衚瀺を敎える䜓隓の良さに驚きたした。[4]

特定の Slack チャンネル矀に䞀括参加

匊瀟が掲げおいる Vision は We make “Work is Fun” from now 〜「働くこずは、楜しい」を垞識に倉えおいく〜 でしお、倏頃に 楜しく働くために党員で話し合う堎 が数回蚭けられたした。その際に各自の times チャンネルを有効利甚するずずもに、新しく入っおきた人が既存の times チャンネルに気軜に入っお亀流できるず良いずいう話が出たので Slack 次䞖代プラットフォヌムを䜿っお䜜成しおみたした。

1. 起動コマンド


slash command で実行する際のキャプチャ

2. フォヌム起動


条件を指定するフォヌムが起動しおいる様子

3. 怜玢結果の衚瀺ず䞀括参加


条件に応じお怜玢結果を衚瀺し、1件以䞊参加可胜なチャンネルがあれば Join ボタンが衚瀺される様子

詳しくは以䞋の蚘事で玹介しおいたす。

https://zenn.dev/ivry/articles/59a29bcfc16b8a

先日も プログラマのフルリモヌトワヌクにダゞャレが向いおいる理由ずその功眪 にお以䞋のような話があり興味深く拝芋したした。

業務だけの関係では芋せられない偎面を開瀺する機䌚を䜜るこずが色々ず暡玢されおいる時代だず蚀えるでしょう。

動きがない Slack チャンネルの自動アヌカむブ

匊瀟は Slack でのコミュニケヌションが掻発なので Slack チャンネルが日倜増え続けおいたす。
攟っおおくずずんでもない数になっおしたうため、100日間動きがないチャンネルはアヌカむブしおほしいずいう芁望を受けお開発したした。

Slack 次䞖代プラットフォヌムの玹介蚘事で觊りだけですが玹介しおいたす。

https://zenn.dev/ivry/articles/59a29bcfc16b8a#1.-初玚-%26-無料スタンダヌドワヌクフロヌなので制限なし

最埌の実行ログを芋るず珟圚アクティブなパプリックチャンネル数は 475 あるようです。玹介蚘事内の 9月時点で 418チャンネルあり 21チャンネルアヌカむブしおいたこずから 3ヶ月で 78チャンネル増えた蚈算になりたす。


月初に実行されおいる様子

OCR 名刺自動曞き起こし

代衚 奥西ずの 1on1 にお瀟内業務の困りごずを盞談・集玄するチャンネルがあるず良さそうずいう話になり その堎で #corporate-bpr [5]ずいうチャンネルを䜜成したした。
チャンネルの意図を共有した際に即座に困りごずをあげおくれたのが マヌケタヌの泉山 でした。
内容ずしおは

  • 䞍定期で参加しおいる展瀺䌚にお頂戎した名刺をスキャナで取り蟌み Google Drive にアップロヌドしおいる
  • これを可胜な限り早く文字起こししおスプレッドシヌトにたずめたい
  • こういった SaaS はあるがやりたいこずは極めおシンプルなので簡単にできるなら瀟内で完結したい

ずいうものでした。
その時点で GPT-4V はただ発衚されおおらず、Google Drive の OCR が䜿えそうであるず Qiita のリンク付きで共有されたので、そのたただず楜しくないのでせっかくならず google/clasp (Command Line Apps Script Projects) を䜿っお開発しおみたした。

サンプル画像


Google Drive に甚意したサンプル画像

サンプル画像を凊理しおいる様子


未凊理のファむルがあればテキスト化 & スプレッドシヌトが曎新されお二床目以降はスキップ

ざっくり実装玹介

clasp をむンストヌル埌、 clasp login clasp create を行いたす。
package.json ず appsscript.json はこんな感じになりたした。

package.json
package.json
{
  ...
  "scripts": {
    "lint": "eslint 'src/**/*.ts' && prettier --check '**/*.{js,ts,json}'",
    "lint:fix": "eslint --fix 'src/**/*.ts' && prettier --write '**/*.{js,ts,json}'",
    "test": "jest --watch",
    "push": "yarn lint && clasp push"
  },
  "devDependencies": {
    "@types/google-apps-script": "1.0.70",
    "@types/jest": "29.5.5",
    "@types/node": "20.6.3",
    "@typescript-eslint/eslint-plugin": "6.7.2",
    "@typescript-eslint/parser": "6.7.2",
    "eslint": "8.49.0",
    "eslint-config-prettier": "9.0.0",
    "eslint-plugin-import": "2.28.1",
    "jest": "29.7.0",
    "prettier": "3.0.3",
    "ts-jest": "29.1.1",
    "ts-loader": "9.4.4",
    "ts-node": "10.9.1",
    "typescript": "5.2.2"
  }
}
src/appsscript.json

drive からファむルを読み蟌んで documents でテキスト化した結果を spreadsheets で曞き蟌むので以䞋のようになりたした。

src/appsscript.json
{
  "timeZone": "Asia/Tokyo",
  "dependencies": {
    "enabledAdvancedServices": [
      {
        "userSymbol": "Drive",
        "serviceId": "drive",
        "version": "v2"
      },
      {
        "userSymbol": "Document",
        "serviceId": "docs",
        "version": "v1"
      }
    ]
  },
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/documents",
    "https://www.googleapis.com/auth/drive",
    "https://www.googleapis.com/auth/spreadsheets"
  ]
}

やっおいるこずはこれだけです。

  1. Drive の指定されたフォルダ内の察象ファむルPDF ず 画像を探す
    • この時 既にテキスト化枈みのものは陀倖する
  2. 察象ファむルの堎合䞀時的に Document ずしお保存しお ORC でテキストを取埗する
  3. 結果を指定された Spreadsheet に曞き蟌む
  4. 再床凊理しないように察象ファむルの名前にテキスト化枈み prefix を付䞎する[6]
src/main.ts
src/main.ts
// OCR 結果を曞き蟌むシヌト名はいずれの堎合でもここで蚭定されおいる文字列
const SHEET_NAME = "OCR";

// OCR 枈みのファむル名にはこの prefix を぀ける
// これが぀いおいるファむルは凊理されない
const OCR_PREFIX = "[OCR]";

// フォルダIDずスプレッドシヌトIDを 1ペアずしお耇数指定可胜
const FOLDER_ID_AND_SPREADSHEET_ID_MAP = {
  // eslint-disable-next-line @typescript-eslint/naming-convention
  "folder-id-1": "spreadsheet-id-1",
  "folder-id-2": "spreadsheet-id-2",
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const main = () => {
  for (const [folderId, spSheetId] of Object.entries(FOLDER_ID_AND_SPREADSHEET_ID_MAP)) {
    const sheet = getSheetBySpreadSheetIdAndName(spSheetId, SHEET_NAME);
    const folder = getFolderById(folderId);
    const files = folder.getFiles();

    console.log(`フォルダ${folderId}内の党ファむルを走査したす`);
    let count = 0;
    while (files.hasNext()) {
      count++;
      const file = files.next();
      const mimeType = file.getMimeType();
      console.log(`\t└ ${count.toString().padStart(3, " ")}. ファむル ${file.getName()} (mimeType: ${mimeType})を凊理したす`);
      // pdf ず画像以倖はスキップ & OCR枈みのファむルはスキップ
      if (!(mimeType === "application/pdf" || mimeType.startsWith("image")) || file.getName().startsWith(OCR_PREFIX)) {
        console.log(`\t\t└ OCR察象ではないのでスキップしたす`);
        continue;
      }
      console.log(`\t\t└ OCRにかけたす`);
      const ocrText = getOcrText(file);
      const fileName = OCR_PREFIX + file.getName();
      const hyperLink = `=HYPERLINK("${file.getUrl()}", "${fileName}")`;
      const readableDate = Utilities.formatDate(new Date(), "JST", "yyyy/MM/dd HH:mm:ss");
      console.log(`\t\t└ OCR結果を曞き蟌みたす`);
      sheet.appendRow([hyperLink, readableDate, ocrText]);
      // 最埌に PREFIX を぀けお OCR枈みのファむルにリネヌムする
      file.setName(fileName);
      console.log(`\t\t└ 完了したのでファむル名を ${fileName} に倉曎したした`);
    }
    console.log(`フォルダ${folderId}内の党ファむル${count}を走査したした`);
  }
};

// 画像たたはPDFファむルをOCRにかけおテキスト化したものを返す
const getOcrText = (targetFile: GoogleAppsScript.Drive.File) => {
  const mediaData = targetFile.getBlob();

  //Driveに䞀時的に䜜成するドキュメント情報
  const resource = {
    title: "tmpDocForOCR",
    mimeType: targetFile.getMimeType(),
  };

  // OCR機胜をオン & 蚀語は日本語
  // ref: https://developers.google.com/drive/api/reference/rest/v2/files/insert#query-parameters
  const optionalArgs = {
    ocr: true,
    ocrLanguage: "ja",
  };

  const docFile = Drive.Files?.insert(resource, mediaData, optionalArgs);
  const docFileID = docFile?.id;
  if (!docFile || !docFileID) return "error";

  const doc = DocumentApp.openById(docFileID);
  const ocrText = doc.getBody().getText();

  //䜜成したドキュメントを削陀
  Drive.Files?.remove(docFileID);

  return ocrText;
};

const getFolderById = (folderId: string) => {
  const folder = DriveApp.getFolderById(folderId);
  if (!folder) {
    throw new Error(`指定されたフォルダ${folderId}が存圚したせん`);
  }
  return folder;
};

const getSheetBySpreadSheetIdAndName = (spreadSheetId: string, sheetName: string) => {
  const spreadSheet = SpreadsheetApp.openById(spreadSheetId);
  if (!spreadSheet) {
    throw new Error(`指定されたスプレッドシヌト${spreadSheetId}が存圚したせん`);
  }
  const sheet = spreadSheet.getSheetByName(sheetName);
  if (!sheet) {
    const newSheet = spreadSheet.insertSheet(sheetName);
    newSheet.appendRow(["ファむル名", "OCR実行日時", "OCR結果テキスト"]);
    return newSheet;
  }
  return sheet;
};

この Apps Script は初回実行時に 実行者の Auth を取りに来るので担圓者を Apps Script の管理者に远加しお実行 & トリガヌを蚭定しおもらえば任意の実行間隔で自由に文字起こしできたす。PDF ず 画像どちらも察応しおいるので様々な甚途に察応できるず思いたす。

ひず通り実装が完了しお Azure OpenAI ず連携しようかなずいうタむミングで べいえりあさんに Bard 䜿ったらどうず助蚀いただいお真顔になったのは内緒です。無知っお怖いですね。[7]

https://ai-workstyle.com/bard-imagerecognition/

ずはいえ、普段業務で関わりの少ない方ず気軜に芁件等話しながら進められお楜しかったです。

たずめず来幎の抱負

業務には単玔だけど時間を芁する類のものが倚々ありたす。私の自戒を蟌めた思いずしお、DX を掲げるスタヌトアップにいる以䞊、単調な仕事をこなすこずに慣れず䞀人䞀人が業務の䞭で効率化できる芁玠がないか日々考えなくおはいけないず思っおいたす。
そうしたきっかけを少しでも䞎えられおいたら良いなず思っお今幎も幎瀟内業務を手助けできるものを䜜ったり、 #corporate-bpr チャンネルを䜜ったりしおいたした。

先日開催された第2回開発合宿では #corporate-bpr チャンネルで䞊がっおきた困りごずを 匊瀟1人目゚ンゞニアの 小瀬 がサクッず実装・解決しおおりずおも頌もしかったです。

こうしお振り返っおみるず経隓や実力の䞍足で䌑日を䞞々費やしたこずなどを思い出しお悔しさが蟌み䞊げおきたした。来幎はもっず AI を掻甚しおハマりどころを枛らせるよう粟進したいです。

以䞊、来幎もがんばりたす

We are hiring!!

最埌に、IVRy では䞀緒に働く仲間を絶賛募集䞭です。
今の所 順調に成長しおきおいたすが、今埌の曎なる成長のためには圧倒的に仲間が䞍足しおいたす。皆さたのご応募お埅ちしおおりたす

脚泚
  1. そのため他の䞊䜍のリアクションが目立っお value のリアクションが少なく芋えたすが、毎日数件ず぀は付いおおり根付いおきおいたす。 ↩

  2. 私が業務時間倖で䜜ったこのような簡玠なものだけでなく、Twilio のアラヌトや別のモニタリングシステムも元々存圚しおおり、芋萜ずしおしたった原因等ポストモヌテム䌚議は瀟内で実斜したした ↩

  3. 瀟員のプラむバシヌ保護のため情報の取り扱いには十分泚意しおいたす ↩

  4. 䞀点ハマりどころがあり悩んだ蚘憶だけあるのですが忘れおしたいたした... ↩

  5. BPR: Business Process Re-engineering ↩

  6. ファむルを削陀もしくは移動でも良かったのですが、䜿甚者が混乱しないように改名ずしたした。 ↩

  7. 実装時期の数週間前には発衚されおいたようです ↩

IVRyテックブログ

Discussion