プログラミング初学者の現役医師が、firebase使って診断名を管理できるLINEBOTを実装してみた。

# 皆さん、ご自身の病名（医学的には診断名）を覚えていますか？

僕の本職は医師（開業医）です。
日常診療の課題感から、以下のような、『診断名を保管でき、かつ、必要に応じて引き出せる』アプリを実装しました。
動作は以下です。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">診察券番号を伝えると、診断名を返してくれるBot完成！<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E3%83%98%E3%83%AB%E3%82%B9%E5%AD%A6%E4%BC%9A?src=hash&amp;ref_src=twsrc%5Etfw">#デジタルヘルス学会</a> <a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%88%9D%E5%AD%A6%E8%80%85?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング初学者</a> <a href="https://twitter.com/hashtag/%E8%A9%95%E8%AB%96%E5%AE%B6%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E5%89%B5%E9%80%A0%E8%80%85%E3%81%AB%E3%81%AA%E3%82%8D%E3%81%86?src=hash&amp;ref_src=twsrc%5Etfw">#評論家ではなく創造者になろう</a> <a href="https://twitter.com/hashtag/linebot?src=hash&amp;ref_src=twsrc%5Etfw">#linebot</a> <a href="https://t.co/IJuusFHLkF">pic.twitter.com/IJuusFHLkF</a></p>&mdash; 北城雅照|ゼロからプログラミング勉強を始めた整形外科医 (@teru3_kitashiro) <a href="https://twitter.com/teru3_kitashiro/status/1341365962142957568?ref_src=twsrc%5Etfw">December 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

医師として、月曜から土曜まで毎週700人以上の患者さまを診察させていただいております。
外来診療を行う中で、「多くの方がご自身の診断名を覚えていらっしゃらない」ことに課題感を持っていました。
診断名がわからないと、次の処置を行っても良いものか、非常に悩む瞬間があるからです。
でも例えば、「好酸球性多発血管炎性肉芽腫症」と診断されても、「飛影邪王炎殺黒龍波」（分かる人いるかな？）みたいに難しく、なかなか覚えられるものではありません。
そこで、上述の『診断名を保管でき、かつ、必要に応じて引き出せる』アプリを実装しました。
システム概要は以下です。
![protooutstudio.001.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/e14714c7-1634-2cbe-8e30-16d3cbf27811.jpeg)
1-1　webappか診察券番号でIDを設定し病名の登録
1-2　webappで診察券番号で病名の確認
2-1　LINEBOTから診察券番号でIDを設定し病名の登録
2-2　LINEBOTで診察券番号で病名の確認
それぞれについて確認します。

# 下準備
必要なした準備は以下の2つ
①　VScordのインストール
②　firebaseの設定
上記はgoogle先生に譲ります。

# Webappの実装
今回のwebappはvue.jsで実装します。

## webappから病名の登録
UIは以下のようになります。
![スクリーンショット 2020-12-23 7.36.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/e0c75a3e-a67c-672a-1fb8-7e77e323103a.png)


```html:addMydg.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>リアルタイムにデータ取得</title>
</head>

<body>
  <div id="app">
    <p>
      診察券番号：<input v-model="cardId" placeholder="00000000"><br>
      名前：<input v-model="name" placeholder="名前"><br>
      誕生日：<input type="date" v-model="birthday" placeholder="1900/1/1"><br>
      性別：<input v-model="gender" placeholder="男or女"><br>
      診断名：<input v-model="dgname" placeholder="診断名"><br>
      診断日：<input type="date" v-model="dgday" placeholder="1900/1/1"><br>
      <button v-on:click='post'>送信</button><br>
      <a href="https://unruffled-curran-e76f78.netlify.app/searchmydg">登録確認のために診断名検索ページに移動する</a>
    </p>
  </div>

  <script src="https://unpkg.com/vue"></script>
  <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-firestore.js"></script>
  <script>
    // firebaseの設定から下記を記入
    const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
    const db = firebase.firestore();
    let patient = '';
    let patientId = '';

    const app = new Vue({
      el: '#app',
      data: {
        allData: [],
        cardId:'',
        name: '',
        birthday: '',
        gender: '',
        dgname: '',
        dgday: '',
      },
      methods: {
        //データ追加
        post: async function () {
          patient = this.cardId
          const res = await db.collection("medicalRecord1").doc(patient).set({
            name: this.name,
            birthday: this.birthday,
            gender: this.gender,
            diagnosis: {dgday:this.dgday, dgname:this.dgname},
            cardId:this.cardId
          });
          console.log(patient);
      },
      }
    })
  </script>

</body>

</html>
```

## webappで病名の確認
UIは以下のようになります。
![スクリーンショット 2020-12-23 7.41.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/581193/00c2fa3a-7349-8367-10b1-3d2ab7fedfae.png)


```html:search.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>診断名検索</title>
</head>

<body>
  <div id="app">
    <h1>診断名検索</h1>
    <input v-model:value="cardID" placeholder="診察券番号"><br>
    診察券番号：{{ cardID }}
    <button v-on:click="searchDg">検索</button><br>
    <p>
    氏名：{{ name }}<br>
    診断名：{{ diagnosis }}<br>
    診断日：{{ diagnosisday }}
  </p>
    <button v-on:click="clearAll">全てをクリア</button><br>
    <a href="https://mystifying-tesla-bb64e7.netlify.app/addmydg">診断名登録ページに移動する</a>
  </div>

  <script src="https://unpkg.com/vue"></script>
  <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/7.2.3/firebase-firestore.js"></script>

  <script>
    // firebaseの設定から下記を記入
    const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
    };
    // Initialize Firebase
    firebase.initializeApp(firebaseConfig);
    const db = firebase.firestore();

    const app = new Vue({
    el: '#app', // Vueが管理する一番外側のDOM要素
    data: {
      // Vue内部で使いたい変数は全てこの中に定義する
      cardID:'',
      name:'',
      diagnosis: '',
      diagnosisday:'',
    },
    methods: {
      // 関数はここに記入
      searchDg: async function() {
        console.log('次の診察券番号の患者さまが検索されました：', this.cardID);
        const patientData = await db.collection("medicalRecord1").doc(this.cardID).get();
        console.log(patientData.date);
          //データの格納
        const pData = patientData.data();//繰り返し各箇所なので一回定数に
 　　　　 this.name = pData.name;
 　　　　 this.diagnosis = pData.diagnosis.dgname;
         this.diagnosisday = pData.diagnosis.dgday;
      },
      clearAll: function() {
       this.cardID = '';
       this.name = '';
       this.diagnosis = '';
       this.diagnosisday = '';
      console.log('全てのToDoが消去されました');
    },
    },
});
  </script>
</body>
</html>
```

LINEBotから病名の登録
リッチメニューを設定し、病名登録のwebappがLINEの中で立ち上がるように設定しました。
同様に、病名確認のwebappも立ち上がるように設定しました。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">診断名の新規登録はリッチメニューから、登録用のサイトに飛ぶ設定になってます。<a href="https://twitter.com/hashtag/protoout?src=hash&amp;ref_src=twsrc%5Etfw">#protoout</a> <a href="https://twitter.com/hashtag/%E3%83%87%E3%82%B8%E3%82%BF%E3%83%AB%E3%83%98%E3%83%AB%E3%82%B9%E5%AD%A6%E4%BC%9A?src=hash&amp;ref_src=twsrc%5Etfw">#デジタルヘルス学会</a> <a href="https://twitter.com/hashtag/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E5%88%9D%E5%AD%A6%E8%80%85?src=hash&amp;ref_src=twsrc%5Etfw">#プログラミング初学者</a> <a href="https://twitter.com/hashtag/%E8%A9%95%E8%AB%96%E5%AE%B6%E3%81%A7%E3%81%AF%E3%81%AA%E3%81%8F%E5%89%B5%E9%80%A0%E8%80%85%E3%81%AB%E3%81%AA%E3%82%8D%E3%81%86?src=hash&amp;ref_src=twsrc%5Etfw">#評論家ではなく創造者になろう</a> <a href="https://t.co/iYpa1BMKI4">pic.twitter.com/iYpa1BMKI4</a></p>&mdash; 北城雅照|ゼロからプログラミング勉強を始めた整形外科医 (@teru3_kitashiro) <a href="https://twitter.com/teru3_kitashiro/status/1341367118147686400?ref_src=twsrc%5Etfw">December 22, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

LINEBotで病名の確認
診断名を確認する場合、“診断名確認”とLINEBotに送ると、“診察券番号を教えてください”と返信させ、診察券番号を送ると名前・診断名・診断日を返すように設定しました。

まずは、必要なパッケージの導入
以下のコードを順番にターミナルに入力

```:ターミナルコマンド
$ npm init -y
```

```:ターミナルコマンド
$ npm i express
```

```:ターミナルコマンド
$ npm i @line/bot-sdk
```

```:ターミナルコマンド
$ npm i firebase
```

今回作成したコードは以下の通り。


```javascript:searchMydg.js
'use strict'; 

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const firebase = require("firebase/app");
require("firebase/firestore");

// firebaseの設定から下記を記入
const firebaseConfig = {
    apiKey: "",
    authDomain: "",
    projectId: "",
    storageBucket: "",
    messagingSenderId: "",
    appId: "",
    measurementId: ""
    };
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const db = firebase.firestore();

// ローカル（自分のPC）でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIの設定から記入
const config = {
    channelSecret: '',
    channelAccessToken: ''
};

// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    //診断名検索というテキストを受け取ったら診察券番号を確認するリプライを出す
    if (event.message.text == '診断名確認') {
        return client.replyMessage(event.replyToken, {
        type: 'text',
        text: '診察番号を入力してください'
    });
    }
    //cardIDの中に審査券番号が格納
    const cardID = event.message.text;
    console.log(cardID);
    
    //診察券番号からfirebase上の診断名を検索
    const patientData = await db.collection('medicalRecord1').doc(cardID).get();
    console.log(patientData.data());
    const pData = patientData.data();
    if (pData　!= undefined) {
        const name = pData.name;
        const diagnosis = pData.diagnosis.dgname;
        const diagnosisDay = pData.diagnosis.dgday;
        let msg = '';
        msg = 'お名前：'+ name +  '\n' +'診断名：' + diagnosis +  '\n' +'診断日：' + diagnosisDay
        console.log(msg);
        const message = {
            type: 'text',
            text: msg
        };
        client.replyMessage(event.replyToken, message);
    }else{
        const message = {
            type: 'text',
            text: '再度診察券番号を記入してください。'
        };
        client.replyMessage(event.replyToken, message);
    };
}


// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP GETによって '/' のパスにアクセスがあったときに 'Hello LINE BOT! (HTTP GET)' と返事します
// これはMessaging APIとは関係のない確認用のものです
app.get('/', (req, res) => res.send('Hello LINE BOT! (HTTP GET)'));

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
    // Webhookの中身を確認用にターミナルに表示します
    console.log(req.body.events);

    // 空っぽの場合、検証ボタンをクリックしたときに飛んできた"接続確認"用
    // 削除しても問題ありません
    if (req.body.events.length == 0) {
        res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
        console.log('検証イベントを受信しました！'); // ターミナルに表示します
        return; // これより下は実行されません
    }

    // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
    // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// （環境によってはローカルネットワーク内にも公開されます）
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);
```

# 今後の課題
診断名を一つではないので、診断名をリスト化し、LINEBotに返せる設定を実装したいと思いました。
もう少し深めて行く予定です。

# その他の記事
[近すぎると小池都知事が『密です。』と連呼するデバイスを作ったら腹筋が崩壊したので、皆さんにも試して欲しい。](https://qiita.com/Teru_3/items/4570bd76d8feeaa37db4)

[誰が使うかわからないけど、膝のレントゲン写真を送ったら、その膝がどの程度痛んでいるのか教えてくれるラインbotを作ってみた。](https://qiita.com/Teru_3/items/80cecd138860fbd0c924)

[猪木の名言で元気をくれるbotを作ったら、想定外の応答で笑いが止まらなくなったから、ぜひ試して欲しい。](https://qiita.com/Teru_3/items/4d0c32fbbc609695e4b8)

[twitterが使えないというIntegromatの弱点を克服し、最強化する方法を書くので試して欲しい。](https://qiita.com/Teru_3/items/9530447a181bf7ca7cc2)