ライフワークバランスを確認するツールを作った話 - 2

:::note info
この記事は[LITALICO Engineers Advent Calendar 2024](https://qiita.com/advent-calendar/2024/litalico) カレンダー3 の 12日目の記事です
:::

# はじめに

[前日の記事](https://qiita.com/nonikeno/private/8b271ee327f421db2976)の続きとなります

さて、家事の予定を組み合わせ最適化によって、自動的に登録できるようになりました
ただし、それだけでは、業務の予定を考慮できていないので、今回は、仕事のカレンダーからのデータの取り込みと、仕事のカレンダーの更新に、対応する話を書きたいと思います

# 仕事のカレンダーのデータの読み込み

googleは多くのAPIを公開してくれております、Google カレンダーもAPIが多く、とても便利です
業務の予定を、Googleカレンダーで管理されている場合は、google カレンダーAPIで、予定を一括取得できます

- [予定の取得API（Events: List）](https://developers.google.com/calendar/api/v3/reference/events/list?hl=ja)

今回の場合は、1ヶ月毎に、家事の予定を登録する作りにしておりますので、timeMinとtimeMaxの[リクエストパラメータ](https://developers.google.com/calendar/api/v3/reference/events/list?hl=ja#parameters)を活用して、関連のある仕事の予定を取得します

また、改めて、解説の必要がないくらい、セットアップ手順についても詳細にドキュメントが用意されています
今回は、Lambdaのnodeで連携するので、[Node.jsでのセットアップ手順](https://developers.google.com/calendar/api/quickstart/nodejs?hl=ja)を参考にしました

ほぼ、全てのドキュメントが多言語対応されているのも嬉しいですよね

### 業務の予定をAPIから取得する際に

仕事の予定に関しては、業務上の機密が含まれていますので、同じようにGoogle APIからデータを取得し活用する場合は、所属する会社へ確認するようにしてください

LITALICOの場合は、セキュリティの対策を取っているため、直接取得はできません
会社のカレンダーの予定から、マスクした予定を別のプライベートカレンダーに登録して、
そちらに対して、APIで抽出しております

この辺りの会社のコンプライアンスや、セキュリティを意識した上で、便利なものを作っていくのって面白いですよね。制約があるからこそ、工夫が生まれると思います

### 業務の予定をカレンダーからAPIで取得するのが難しい場合

多くのカレンダーでは、ファイルでの出力が用意されているので、そちらを取り込むことを検討する形でしょうか
ただし、その場合、この記事の以降で検討するような、リアルタイムでの連携は難しくなります

今回は、前述の通り、元の仕事のカレンダーから転記しておりますので、API連携にこだわる必要もなかったのですが、ゆくゆくの展開を考え、リアルタイム連携の方法に関して、模索した形です

## 業務の予定の保持に関して

家事の予定は、組み合わせ最適の計算によって、決定変数内の配置を変更します
仕事の予定の場合は、組み合わせ最適の計算で勝手に変更されたら困るので、カレンダーから呼び出した予定を、別の配列で保持しておき、家事予定の計算で決定変数を初期化する際に、仕事の予定を保持する配列から、仕事の予定をコピーします

```python
# 仕事の予定を保持、後で、googleカレンダーから取得した仕事の予定を整形して格納
work_schedules = []

...

# 家事予定の初期化
def init_housework_schedules():
    # 一旦、空に
    housework_schedules = [None] * days * 24 * 4

    # 仕事の予定からコピー、この予定は変更させない
    housework_schedules = copy.deepcopy(work_schedules)
    
    ...
```

# 仕事のカレンダーのデータが更新されたときの対応

Google カレンダー APIの[PUSH通知](https://developers.google.com/calendar/api/guides/push?hl=ja)の機能を利用します

上記、確認いただくと分かる通り、受信URL（Webhook）を設定できるので、そこで、仕事のカレンダーが更新された際に、家事の予定をリセットし、再度、家事予定の自動登録を行います

## 更新差分を取得する

上記のPUSH通知は、更新があったことはわかるものの具体的にどの予定が更新されたかまではわかりません。通知を受け取ったら、これも前述の予定の取得APIで、予定の詳細を取得し、通知前の予定と比較して、差分を計算する必要があります

ちなみに、予定の削除も更新としての検知になりますので、更新の通知があった際に、元々設定されていた予定が存在しない場合は、削除されたと考える必要があります

## 差分更新が必要か？

さて、更新されたことはわかるのだから、更新のPUSH通知の度にリセット&自動登録を行なっても良いのではないかと考える方もいますよね、ケースバイケースですが、それでも良い場合もあるでしょう

ただ、今回の場合、予定の自動登録はかなりの計算を伴う処理です、諸々頑張ってみましたが、それでも、長いときで、1分〜10分程度の時間がかかります

仕事の予定の更新の度に、1分〜10分程度処理が実行されるのは、UX的にもランニングコスト的にもいただけないですよね

ケースバイケースというのは、元々やりたかったこと、つまり、要求の実現のために、そのコストが必要になることもあるからです。今回の場合はどうでしょうか

業務の影響によって、家事予定を調整する必要があるか、知りたいのはここだけですよね。そのため、想定していた業務時間の開始と終了が変更になり、家事予定と重なってしまう場合のみ、予定のリセット＆自動登録を実行するようにしております

![名称未設定ファイル.drawio (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2277180/81db2f0f-3383-3494-b125-bf236415c4d8.png)


## 家事予定を大幅に変更したくない

家事の予定をリセット＆自動登録すると、場合によっては、予定が大幅に変更することがあります

もちろん、それでも良いのですが、計画の変更が大変な場合もありますよね、例えば、土曜日に妻をデートに誘っているとか

1つのアイディアとしては、仕事の予定と同じように、大事な予定も移動できないものとして登録する方法もあるのですが、そういった大事な予定がなくても、予定の大幅な変更が直前だと困ってしまうこともあります

そのため、なるべく大きな予定の変更をせず、最小限の予定の変更になるよう目的関数を追加することにしました

```
目的関数: 
- 家事の予定の登録された日の分散
- 各家事の設定周期と予定間隔の差分の最小化
- 特定家事（掃除や洗濯など、音が大きい家事）の配置曜日、時間の評価
    - 土日など、ゆっくりしたい日や時間帯をなるべく避けるように
- 設定済みの予定の更新が少ない [ New ] 
```

目的関数が増えてくると、各目的関数の重みづけが重要になってきますが、この辺りは3日目の記事でまとめていきます

# 終わりに

ご覧いただきありがとうございます

業務の考慮もできるようになり、大分使い勝手が向上しました
妻にこの話をした際に、「自分も使ってみたい」と言ってくれたのがとても嬉しかったです
ただ、そこで、人によって、どういった配置がベストか異なるため、目的関数の重みのチューニングが必要と気づきまして、明日はその辺りを中心に記事を書きます
よろしければ、ご覧ください

※ 実を言うと、妻に使ってもらう前はCLIツールでしたが、Amplifyを用いてGUI化しました