10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

LINE Botで医薬品を瞬時に識別! 禁忌情報や出荷状況も一目でわかる新システムのプロトタイプ

Last updated at Posted at 2024-09-24

皆さんこんにちは!おもぷーです。

今回の記事は、LINE Botに医薬品の写真を送信すると、自動で薬の名前や詳細情報を返信してくれて、さらに禁忌情報や限定出荷の有無なども一緒に確認できるという、痒いところに手が届くプロトタイプになってます。

Teachable MachineLINE BotGoogle Apps Script (GAS) を連携させたプロトタイプになります。

実際の動作動画

なぜこのプロトタイプを作ったのか

現在の薬局業界では医薬品供給不足が大きな課題となっています。特に、入荷できない薬が増えており、どの薬が通常通り入荷でき、どの薬が入荷困難なのかを常に確認しなければなりません。

この確認作業がとにかくめんどくさい

例えば、A薬とB薬が通常通り入荷していたとしても、ある日突然B薬だけが欠品し、その代替薬も入手できないケースが頻発しています。
このような状況では、薬局側は卸会社に連絡して毎回在庫状況を確認し、患者さんへの対応を再検討しなければならず、業務負担は増える一方です。業務効率が低下するだけでなく、患者さんをお待たせしてしまい、信頼の損失にもつながりかねません。

調剤事務(受付)の負担も爆増しています

自分の職場の調剤事務も、1日に何回も卸に確認をしており、この無駄な時間を何とかしないといけません。今回考えた解決策は、LINEで薬の写真を送信し、出荷情報を瞬時に確認できるシステムとなります。

(薬局に1錠もない場合は基本的に他薬局へ紹介をしています)

使用ツール

  1. Teachable Machine

    • 画像認識モデルを作成し、薬の画像を判定するために使用
    • URL: Teachable Machine
  2. LINE Messaging API

    • LINE Botを作成し、ユーザーが薬の写真を送信することで判定結果を返信する機能を提供
    • URL: LINE Developers
  3. Google Apps Script (GAS)

    • 薬の詳細情報、禁忌情報、出荷情報をGoogleスプレッドシートから取得し、LINEで返信するために使用
    • URL: Google Apps Script
  4. Node.js

    • サーバーサイドの処理を実行し、LINE BotとTeachable Machine、GASを連携させるために使用
    • URL: Node.js
  5. TensorFlow.js

    • Teachable Machineのモデルを利用して、画像の判定を行うために使用
    • URL: TensorFlow.js

手順

Teachable Machineで薬分類モデルを作成

  1. Teachable Machineにアクセスし、画像プロジェクトを作成
    スクリーンショット 2024-09-23 220324.png

  2. モデルを学習させ、精度を確認スクリーンショット 2024-09-23 220337.png

  3. TensorFlow.js形式でエクスポートし、ダウンロード
    スクリーンショット 2024-09-23 220444.png

  4. ダウンロードしたモデルを保存
    スクリーンショット 2024-09-24 161126.png

LINE Messaging APIの設定

  1. LINE Developersにアクセスし、新規チャネルを作成します

  2. 新規作成をしたら、
    チャンネルの設定画面で「Messaging API」を有効にし、チャネルシークレットとチャネルアクセストークンを取得します
    スクリーンショット 2024-09-24 162301.png

  3. WebhookのURLを設定します
    スクリーンショット 2024-09-24 162805.png

Google Apps Script (GAS) でスプレッドシート連携

写真で医薬品を判別した後、その医薬品の情報をLINEに送るためGASと連携をしました

  1. Googleスプレッドシートを作成し、薬の名前、詳細情報、禁忌情報、出荷情報などを記載し、拡張機能からApps Scriptを選択しますスクリーンショット 2024-09-24 163933.png

  2. GASスクリプトを作成

🐽クリックしてコードを表示🐽
      function doGet(e) {
        const sheet = SpreadsheetApp.openById('スプレッドシートID').getActiveSheet();
        const query = e.parameter.query;
        const data = sheet.getRange(2, 1, sheet.getLastRow(), sheet.getLastColumn()).getValues();
        for (const row of data) {
          if (row[0] === query) {
            return ContentService.createTextOutput(JSON.stringify({
              "薬名": row[0],
              "詳細": row[1],
              "禁忌情報": row[2],
              "出荷情報": row[3]
            })).setMimeType(ContentService.MimeType.JSON);
          }
        }
        return ContentService.createTextOutput(JSON.stringify({ "error": "該当する薬が見つかりません" })).setMimeType(ContentService.MimeType.JSON);
      }

スプレッドシートIDの設定を忘れずに行ってください

3 . デプロイして「ウェブアプリ」として公開します。
スクリーンショット 2024-09-24 165505.png
内容の設定をします
スクリーンショット 2024-09-24 165646.png
作成されたURLは後で使用します

Node.jsでサーバーをセットアップ

  1. Node.jsをインストールしていない場合、Node.js公式サイトからインストールします。
  2. プロジェクトプロンプトを作成し、以下のコマンドで必要なパッケージをインストールします
    npm init -y
    npm install express body-parser @line/bot-sdk node-fetch @tensorflow/tfjs-node
    
  3. index.jsファイルを作成し、以下のコードを貼り付けます
    ファイルの作成はメモ帳を使用します
🐽クリックしてコードを表示🐽
     const express = require('express');
const bodyParser = require('body-parser');
const line = require('@line/bot-sdk');
const fetch = require('node-fetch');
const tf = require('@tensorflow/tfjs-node');

const app = express();
const port = process.env.PORT || 3000;

// LINE APIの設定
const config = {
  channelAccessToken: 'チャンネルアクセストークン',  // あなたのチャンネルアクセストークン
  channelSecret: 'シークレットトークン'  // あなたのシークレットトークン
};

// LINE SDKのクライアントオブジェクトを定義
const client = new line.Client(config);

app.post('/webhook', line.middleware(config), (req, res) => {
  console.log('Webhook received');
  Promise.all(req.body.events.map(handleEvent))
    .then((result) => {
      console.log('Event handled successfully');
      res.json(result);
    })
    .catch((error) => {
      console.error('Error in webhook handling:', error);
      res.status(500).end();
    });
});

// 薬名に基づきGASから薬の詳細情報を取得する
async function getDrugInfo(drugName) {
  const url = `https://script.google.com/macros/s/GASで取得したIDを入力/exec?query=${encodeURIComponent(drugName)}`;
  const response = await fetch(url);
  const data = await response.json();
  return data;
}

// メイン処理: メッセージのイベントを処理
async function handleEvent(event) {
  try {
    console.log('Handling event:', event);

    if (event.type !== 'message' || event.message.type !== 'image') {
      console.log('Non-image message received');
      return Promise.resolve(null);
    }

    const imageURL = await getImageFromLine(event.message.id);
    console.log('Image URL fetched:', imageURL);

    const prediction = await classifyImage(imageURL);
    console.log('Prediction result:', prediction);

    // 予測結果を処理して、最も確率の高いクラスを取得
    const predictionData = await prediction.array();
    console.log('Prediction array:', predictionData);  // 配列形式の予測結果をログ出力

    const classIndex = predictionData[0].indexOf(Math.max(...predictionData[0]));  // 最も高い確率のクラスを取得
    const classLabels = ['ロキソプロフェンEMEC 60mg', 'レバミピド100mg EMEC'];  // Teachable Machineのクラスラベルに応じて修正
    const predictedClass = classLabels[classIndex];
    console.log('Predicted class:', predictedClass);  // 選ばれたクラスラベルをログ出力

    // GASから薬の詳細情報を取得
    const drugInfo = await getDrugInfo(predictedClass);
    console.log('Drug Info fetched:', drugInfo);

    // 詳細情報を返信メッセージに整形
    const replyMessage = `薬名: ${drugInfo.薬名}\n詳細: ${drugInfo.詳細}\n禁忌情報: ${drugInfo.禁忌情報 || 'なし'}\n出荷情報: ${drugInfo.出荷情報 || '通常出荷'}`;

    // 判定結果をLINEに返信
    return client.replyMessage(event.replyToken, {
      type: 'text',
      text: replyMessage
    });
  } catch (error) {
    console.error('Error in handleEvent:', error);
    throw new Error('Event handling failed');
  }
}

// LINEから画像を取得
async function getImageFromLine(messageId) {
  const url = `https://api-data.line.me/v2/bot/message/${messageId}/content`;
  const headers = {
    Authorization: `Bearer ${config.channelAccessToken}`,
  };
  const response = await fetch(url, { headers });

  if (!response.ok) {
    throw new Error(`Failed to fetch image: ${response.statusText}`);
  }

  const buffer = await response.buffer();
  return buffer;
}

// 画像を判定(Teachable Machineモデルを使用)
async function classifyImage(imageBuffer) {
  const imageTensor = tf.node.decodeImage(imageBuffer);
  const resizedImageTensor = tf.image.resizeBilinear(imageTensor, [224, 224]);
  const model = await tf.loadLayersModel('file://model/model.json');
  const batchedImage = resizedImageTensor.expandDims(0);
  const prediction = model.predict(batchedImage);

  return prediction;
}

app.listen(port, () => {
  console.log(`Listening on port ${port}`);
});

注意点

  • LINE Developersで獲得したチャンネルアクセストークン、チャネルシークレットは個別入力
  • GASでデプロイした時に獲得したURLを個別入力
  • Teachable Machineでダウンロードしたモデルの保存先を個別入力

Webhook URLを設定してLINE Botをテスト

  1. ngrok をインストールしていない場合、ngrokをインストールし、ngrok http 3000 でローカルサーバーを公開します
  2. ngrokで生成されたURLをLINE DeveloperのWebhook設定に追加します
  3. LINEでテストメッセージを送信し、画像判定が正しく動作することを確認します

使ってもらった感想

自店の調剤事務に試しに使ってもらいました!

  • 難しいかもしれませんが、全種類データに入れてほしい
  • 毎回卸に電話をしなくていいのは本当に助かる!
  • 写真を撮ると薬の効能がすぐに分かる機能は、医療従事者というよりは一般向け。家の中に眠ってる薬の効能が分かるのは便利

色んな意見を頂きました。
1000022668.jpg

技術面での課題

  • データベースの拡大:膨大な薬の情報をどうするか・・・!

  • カード形式のメッセージ表示:テキスト表示だと見ずらいため、分かりやすい表示に変更する

  • サーバーの問題:現在、ngrokをつけっぱなしじゃないと使えない

  • 相互作用チェック機能:複数の薬が処方された際に、自動で判別
    などなど

実用させるにはまだまだ沢山の課題があります

今後の展望:薬の監査システムへの発展

このプロトタイプは、薬の供給状況を効率的に確認するためのツールとして開発されましたが、将来的にはさらに薬の監査システムとして発展させたいと考えています。

薬局の現場では、薬剤の監査作業に非常に時間と労力がかかります。現在の監査システムは、JANコードや重さで薬を判定する機器が一般的ですが、これに対し画像認識技術を応用して、よりスマートに監査を行うシステムを構築することが目標です。

具体的には

  • 複数の薬剤を同時に認識し、正確な監査結果を提供
  • 薬剤の詳細情報や禁忌情報の自動提供による監査の効率化

今回のシステムと合わせて利用することで、薬剤師や事務員の負担を大幅に軽減し、調剤ミスの防止や時間短縮に寄与することが出来たらと思っています。

10
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?