かなり柔らかい話題の雑記です。

本記事のサマリ by AI

この記事は、Google SheetとApps Scriptを使用してデータを可視化し、Slackに投稿する方法について説明しています。また、ChatGPTを使用してプログラムの作成やAPIの仕様の理解を支援する方法についても言及しています。Apps ScriptのプログラムとChatGPTを使用してSQLを書く方法も含まれています。

前置き

昔こんな記事を書きました。もう7年も前のことになります。

これは当時かなり画期的で、当時所属していたメルカリのBIチームからも重宝してもらった記憶があります。このときに「詳しく話を聞かせてくれ」といった熊のような男はPodcastの相方となり、もうひとりの見るからに実直で真摯な方は10Xの社員となりました。7年という月日はエグい。

image

で、今は2023年なのですが、流石にこれだけ月日もたったし、BIツールは無限に存在するので「さっとデータを可視化してSlackにいい感じに流す」という問題はもうほとんど存在しないだろうと思っていました。わざわざGoogle Sheetで可視化をして提示で送りつける、なんて面倒なことを欲する機会はないだろう、と思っていたんです。

現に10X創業直後(2017−2020年くらいまで)はRedashというツールを使っていて、これはRedash上でクエリ作成も、データ可視化も、Slackへのチャート定時投稿もすべて完結するので、何の不便もなかったんです。Redash がセルフホスト不要な時代は。

(Redash Web版が2021年に終了し、すべてをセルフホスト前提となりこの前提も瓦解)

その後、10XでもMetabaseやLookerなどのBIを渡り歩き、今ではTableauに行き着きました。SlackといいTableauといい、Salesforece経済圏にガッツリ飲み込まれています。Notionも買収して統合してくれ(心の声)。

しかし、Redashほどの市井感はどのツールにも有りません。どのツールも大量のデータを、複数の人でも正確に扱い、ビジュアライズの多様性みたいなところにはかなり気合が入っています(大変よろしい)。他方で「みやすさ」「コミュニケーションのしやすさ」みたいなところは結構弱いと感じます。チャート一枚の見やすさにこだわったツールは今の所ありません。

10X内部ではデータエンジニアリングもめざましく進化し、データウェアハウスは複層化され、権限管理やデータセキュリティも整備が進んでいます。分析についてもガバナンス設計が進んで、私のような野良クエリをできるだけ避け、いろんな分析をアナリストが担当してくれる体制になりました。「重要なデータ資産を適切に保存し、運用する」という点で理想的な進化です。

しかし事業に対して「常に分析リソースがカツカツ」問題と「事業経営に必要な分析は100%の精度より10%精度を捨ててでも速度」の原則があります。Stailerはまさにコンシューマー・フォーカスのフェーズですので、日々の施策や活動に対し、細かな検証結果をグリグリ見たいフェーズです。私も気になることが多すぎ、あのデータが見たいというのが分単位で発生します。ネットスーパーはデータポイントが本当にたくさんある素敵な事業です。

そんなときに「全員の頭をきれいなチャートで揃えに行く手段」がぽっかり空いていることに気づき、それであればと7年前のプラクティスをややアップデートして活用しようと思い立ったのが前置きになります。長いな。

🕺🏻Apps Scriptプログラム

Slack APIにはUser Token と Bot Tokenがありますが、Channelへの画像投稿は2023年現在でもUser Tokenしか許可されていません。そのためUser Tokenにchat:writeの権限をつけて使います。

2016年の記事からのアップデート部分は以下です。

  • 複数のファイルを同時に操作できる
  • 複数のGoogle Sheetのシートを指定できる
  • シート内にある、複数のファイルをまとめて取得して、投稿できる
  • どのチャートを取得するかは指定する必要もない
  • 配列変数ごとに投稿チャンネルを指定できる
  • プログラムがシュッとしている

それではプログラムをどうぞ。spreadsheetsInfo配列の中のspreadsheetIdsheetNamestokenchannelIdを設定してデプロイすればすぐ動かせると思います。私はpostChartsToSlackに対して日次トリガーを引いて使っています。

function postChartsToSlack() {
  var token = "xoxp-"; // SlackのAPIトークン
  
  // スプレッドシートID、シート名、SlackチャンネルIDの情報をまとめたオブジェクトの配列
  var spreadsheetsInfo = [
    {
      spreadsheetId: "任意のID",
      sheetNames: ["任意のシート1","任意のシート2"],
      channelId: "hogehoge"
    }
    ,{
      spreadsheetId: "任意のID",
      sheetNames: ["任意のシート1","任意のシート2"],
      channelId: "hogehoge"
    }
    ,{
      spreadsheetId: "任意のID",
      sheetNames: ["任意のシート1","任意のシート2"],
      channelId: "hogehoge"
    }
    ,{
      spreadsheetId: "任意のID",
      sheetNames: ["任意のシート1","任意のシート2"],
      channelId: "hogehoge"
    }
  ];
  
  for (var k = 0; k < spreadsheetsInfo.length; k++) {
    var spreadsheetId = spreadsheetsInfo[k].spreadsheetId;
    var sheetNames = spreadsheetsInfo[k].sheetNames;
    var channelId = spreadsheetsInfo[k].channelId;
    
    try {
      var spreadsheet = SpreadsheetApp.openById(spreadsheetId);
      var spreadsheetUrl = spreadsheet.getUrl(); // スプレッドシートのURLを取得
      
      for (var i = 0; i < sheetNames.length; i++) {
        var sheetName = sheetNames[i];
        var sheet = spreadsheet.getSheetByName(sheetName);
        var charts = sheet.getCharts();
        
        for (var j = 0; j < charts.length; j++) {
          try {
            var chart = charts[j];
            var imageData = chart.getBlob();
            var fileName = chart.getOptions().get('title') + ".png";
            
            var payload = {
              "token": token,
              "channels": channelId,
              "initial_comment": fileName.replace('.png','') + ": " + "<" + spreadsheetUrl + "|" + "URL" + ">",
              "filename": fileName,
              "filetype": "png",
              "file": imageData
            };
            
            var options = {
              "method": "post",
              "payload": payload
            };
            
            var response = UrlFetchApp.fetch("https://slack.com/api/files.upload", options);
            Logger.log(response.getContentText()); // ログにレスポンスを出力する
          } catch(err) {
            // ここにエラー時の処理を書く
            Logger.log('Error uploading file: ' + err.message);
          }
        }
      }
    } catch(err) {
      // ここにエラー時の処理を書く
      Logger.log('Error opening spreadsheet: ' + err.message);
    }
  }
}

User Tokenを使っているので、自分自身が身を切ってBotのように振る舞います。

image

若干嫌なんだけど、このがチャートきっかけでリアクションやスレッドが生まれるとその通知も自分に返ってくるのでそこは良しです。ぜひいい感じにご活用ください。

🕺🏻ChatGPTに書かせた話

直近ではPythonからNotionAPIを叩いて複数のテーブルを自動でRelationつける自動処理を書かせたり、Zapierではできない細かなSaaS間の連携をApps Scriptで書いたりして、自分の経営に必要なデータを整えたり、処理したり、または面倒なSQLも自分で書いてデータを可視化したりする機会が増えました。

ただこういうのは普段プログラムを書く仕事から「完全に」離れている自分みたいな人だと、環境構築で半日、プログラムのお作法を思い出すのに2日、書き出して大量のデバッグでN日溶かす、みたいなのが常なんですよね。結果、タイムパフォーマンスが悪すぎるので、週末の日曜大工か、社内の誰かにお願いするのが日常になりがちです。

そんな折に現れたのがChatGPTです。ChatGPTは「自然言語で入力して、自然言語を出力してもらう」という使い方だと、対してポテンシャルは感じなかったのですが、「自然言語で入力して、人工言語を吐き出させる」という使いみちを取るとものすごいパフォーマンスを発揮します

この記事に載せた簡単なプログラムでも実は自分で考えると大量の落とし穴があります。Slack APIの仕様の把握とか。Notion APIを動かすときもどのversionを使えばいいか、とか。メソッドはなにか、とか。あとそもそも文法など。このあたりはChatGPTがかなり正確にフォローしてくれます。たたき台となるドラフトの

例えばSQLを書くときも、BigQueryからスキーマだけをコピって教え込み、日本語でほしいデータを指示するだけでSQLをかなりの精度で書いてくれたりします。これがプロンプトエンジニアリング。

こういった使い捨てのプログラムであれば、かなりの部分をAIに預けられるよね、という話でタイミーCTOのkameikeさんと盛り上がったPodcastも張っておきます。