在席フロアとフロアごとの混雑率を表示してみた話

**この投稿は、2023年JINSのアドベントカレンダー3日目の記事です。**

## 自己紹介
JINSのITデジタル部所属の内田(@j-uchi)と申します。
メインはアプリケーションを(TypeScriptなWebアプリが多い)書く人ですが、色々やってます。

## オフィス移転
2023年5月末に株式会社ジンズは神田の新オフィスへ移転しました！

↓以前の移転関連記事
[SASEで新オフィスのネットワークを構築してみた[1日目]](https://qiita.com/knt-okada/items/f57e4320fe4347578c6b)
[無かったので造った話[2日目]](https://qiita.com/r-kanzaki/items/ea4cf1dfafe7322ef4b1)

↓JINS PARKにも引っ越し記事があるのでお時間ある方は読んでいただけると嬉しいです！
[たった2キロの長い旅。JINS本社引っ越し奮闘記](https://park.jins.com/series/hikkoshi/)

## オフィスが狭くなって多層に！
JINSの執務エリアは今までは飯田橋のオフィスビルの30Fにありました(画像左)
新オフィスは一棟借りで5F～8Fに執務スペースが設置されています(画像右)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632669/7d00f861-cf9c-8f99-fae7-cd2f5c26a95b.png)

### 問題点
- 執務エリアフロアが分かれたことで誰がどこにいるかわからなくなる
- 新オフィスの席数は本部社員の数の6割程度
  - 特定のフロアに行っても座れない可能性がある

### どうしたい？
- 誰がどこにいるかわかるようにしたい
- どのフロアが混んでいるか事前にわかるようにしたい

### そこで、、、
在席管理のシステムを導入することを検討！
→**既製品は導入に数千万円かかるとのこと**

しょうがないにゃあ🐱わたしに任せなさい！
内製で作って差し上げましょう🦾

## システム概要

今回作成したものは下記2つに大きく別れています。

### 1. フロアの在席率を表示するサイネージ

各フロアのエレベーターホールに設置(在席率は左側の円グラフ)
右側の部署一覧も定期的に変化します(部署ガチャ)
- クライアント(`1F`, `5F-8F`の5台)
  - 27インチモニタ
  - Intel Compute Stick
    - Ubuntu 22.04 LTS
    - Chrome(キオスクモード)
- サーバー
  - Next.js(SSG + API Routes)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632669/71036ba6-a86a-ad0a-4263-0df939ea8ead.png)


### 2. どの社員がどのフロアに在席しているかを表示するGoogleスプレッドシート

- 定期実行のスクリプトで5分置きに社内共有スプレッドシートに出力

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3632669/05482cd1-0698-d0b8-7cd1-76278089570e.png)

## どうやって？
神田本社では保守省力化のためCisco Merakiを導入しており、API経由で以下のような情報が取得できます。
```json:https://api.meraki.com/api/v1/networks/${networkId}/clients
[
    {
        "id": "******",
        "mac": "a6:92:b3:xx:xx:xx",
        "description": "xxxxx-iPhone-13-Pro",
        "ip": "172.24.xxx.xxx",
        "ip6": null,
        "ip6Local": "fe80:0:0:0:xxxx:xxxx:xxxx:xxxx",
        "user": null,
        "firstSeen": "2023-06-24T05:41:44Z",
        "lastSeen": "2023-12-02T16:44:41Z",
        "manufacturer": null,
        "os": "Apple iPhone",
        "deviceTypePrediction": "Apple iPhone 13 Pro, iOS17.1.1",
        "recentDeviceSerial": "xxxx-xxxx-xxxx",
        "recentDeviceName": "9F_South",
        "recentDeviceMac": "a8:46:9d:xx:xx:xx",
        "recentDeviceConnection": "Wireless",
        "ssid": "HOGEHOGE",
        "vlan": "xx",
        "switchport": null,
        "usage":{
        "sent": 52354,
        "recv": 427774,
        "total": 480128
        },
        "status": "Online",
        "notes": null,
        "groupPolicy8021x": null,
        "adaptivePolicyGroup": null,
        "smInstalled": false,
        "namedVlan": null,
        "pskGroup": null
    }
]
```

- `recentDeviceName`が接続先の無線AP(有線の場合はSwitch)
- `description`がDHCPクライアントが名乗るホスト名

`1.`の在席率サイネージは社員に貸与されているメインPCが各フロアに何台あるかで、フロアの在席率を算出しています。
この情報を機器管理表と社員の所属情報と突合し`2.`の在席情報スプレッドシートで必要とされる情報を出力することが可能です。

```typescript:APIを叩くところ
import axios from 'axios';

const MERAKI_API_KEY = "MerakiのAPI Key";

const merakiClient = axios.create({
  baseURL: 'https://api.meraki.com/api/v1/',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-Cisco-Meraki-API-Key': MERAKI_API_KEY,
  },
});

interface NetworkClient {
  // 省略
}

const getNetworkClients = async (networkId: string) => {
  const result = await merakiClient.get<NetworkClient[]>(
    `/networks/${networkId}/clients`,
    {
      params: {
        perPage: 1000,
        statuses: ['Online'],
      },
    }
  );

  return result.data;
};
```

## 最後に
既存システム導入だと数千万円かかったものが、私1人で12時間程度でできました。
既製品と比べたら簡素なものですが、内部向けなので必要十分です。

明日は @tskoma さんの re:Invent行ってきた です！ラスベガス出張レポート！お楽しみに！