【個人開発】ライブのタイムスケジュールを自動作成するサービスを作りました


概要

こんにちは。
この度、ライブイベントのタイムテーブルを自動で作成するサービスを開発しました。

↓作ったサービス
Stagio(ステージオ)

自己紹介

普段は製造業に従事し、データサイエンティストもどきとして活動しています。

  • 製造現場で起こる不適合に関わるデータを分析し、不良品が発生する条件を明らかにする
  • 分析結果を元に機械学習モデルを作成し、現場改善に活用する

などが主な業務です。

週末はサークル活動(アカペラ)を嗜んでいましたが、コロナの影響で活動もままならず。
自己研鑽も兼ねて、空いた時間にWebサービスを作り始めました。

解決したかった課題

大学や社会人のアカペラサークルは、

  • 100-300人規模
  • 4~7人くらいでバンドを組む
  • バンドは50~100個くらいある

という特徴があります。必然的に、複数のバンドを掛け持ちしているメンバーが存在します。

これがなかなか厄介で、学祭などのライブイベントでは、メンバーが被っているバンド同士は出演時間を離さなければなりません。連続出演は負担になるからです。
また、当日に予定が入っているメンバーもいるので、バンドによっては出演できない時間もあります

これらを考慮して手作業でタイムテーブルを組むのが、運営者にとっては大きな負担となっていました。「自動で組めるアプリがあればいいのに」という声も以前からちらほらと。

ないなら作ってしまおう!

開発言語とフレームワーク

<サーバーサイド>
言語 : Python(3.8)
Web Framework : Django(3.1)
サーバー : Nginx + gunicorn

<フロントエンド>
CSS Framework : Bootstrap4
Admin Template : CoreUI
Landing Page Template : AppLand
広告 : Google Adsense
その他プラグイン
- FullCalendar
- Select2

使い方

某予定調整サービスよろしく、

  1. イベントを作成
  2. 各バンドに出演可能時間と所属メンバーを申告してもらう
  3. カレンダー上に手動でバンドを配置するか、後述の自動計算機能を使う

というステップで使います。
バンドはドラッグ&ドロップで配置できます。この辺りはFullCalendarで実装しました。
ドラッグを開始すると、そのバンドの出演可能時間が青でハイライトされます。

出演できない時間にバンドを配置したり、メンバーが被っているバンド同士を接近させたりすると、警告が表示されます。

自動計算機能

概要

本サービスの目玉機能で、いい感じのタイムスケジュールを自動生成してくれる機能です。
メンバー被りと出演可能時間を考慮した最適なタイムスケジュールをワンクリックで計算でき、大幅な労力削減になります。

手法

最適化手法には遺伝的アルゴリズム(GA)を使っています。Pythonには有用な数理最適化ライブラリがたくさんあるのですが、やりたいことがちょっぴり複雑だったので自分で実装しました。

最初にバンドをランダムに並べた個体をたくさん作って、各個体の性能評価を行い、評価が高かった個体同士を掛け合わせることでより良い個体を生み出す…というステップを繰り返し、最適(に近い)解を生み出します。

バックグラウンドでの計算

計算には数分かかるので、バックグラウンドで計算してくれるようにしました。
具体的には、計算ボタンを押すと計算キューが登録され、計算待ちの状態になります。

models.py

#計算キュー
#実際のコードとは異なります
class Que(models.Model):
    id=models.UUIDField(default=uuid.uuid4, primary_key=True,editable=False)
    status_choices=(
        ("0","計算前"),
        ("1","計算中"),
        ("2","正常終了"),
        ("3","異常終了")
        )
    status=models.CharField(max_length=16,choices=status_choices,verbose_name="ステータス",default="0")

    created_at=models.DateTimeField(default=timezone.now,verbose_name="登録日時")
    started_at=models.DateTimeField(blank=True,null=True,verbose_name="計算開始日時")
    finished_at=models.DateTimeField(blank=True,null=True,verbose_name="計算完了日時")

実際の計算はdjangoのカスタムコマンドで実装します。登録されているキューを見に行って、一番昔に登録されたものを見つけて、計算を実行します。

myapp/management/commands/calc.py
#毎分発動
#実際のコードとは異なります
class Command(BaseCommand):
    def handle(self, *args, **options):
        #計算中のキューがあればスキップ
        if Que.objects.filter(status="1").exists():
            return

        #計算中のものがない場合、計算待ちを探す
        ques=Que.objects.filter(status="0").order_by('created_at')

        #1個でもあったら発動
        if ques.count()>0:
            que=ques.first()

            #ステータス変更
            que.status="1"
            que.started_at=timezone.now()
            que.save()

            #計算を実行

            #ステータス変更
            que.status="2"
            que.finished_at=timezone.now()
            que.save()

あとはこのコマンドをcronで毎分実行すれば、1分ごとにキューを確認して、計算リクエストがある場合はそれを処理する、という動きが実現できます。

/etc/crontab
* * * * * root python /path/to/myapp/manage.py calc

celeryや、djangoユーザー待望の非同期処理であるasgiを使う手もあったのですが、同時刻に重い計算が集中するのを防ぎたかったのでこのような形式にしました。ベストではないけど、シンプルでメンテナンスもしやすいスタイルだとは思います。

教訓

Full Kitという考え方

製造業に身を置く中で、工場の改善というテーマをTOC(Theory Of Constraints:「制約理論」または「制約条件の理論」)という理論に当てはめるシーンが多くありました。
平たく言うと、これは「全てのシステムには効率化を阻むボトルネックがある。そのボトルネックを集中的に改善すれば、システム全体を効率化できる」というものです。

TOC(Theory of Constraints:「制約条件の理論」)は、「現在から将来にわたって繁栄し続ける」 という企業の目的を達成するため、それを妨げている”制約条件(Constraints)”に集中して改善することで、企業全体の業績改善/向上が期待できる経営改善/マネジメントの手法です。

簡単に表現すると、全ての課題に対策を打つのではなく、その課題の根本原因となっているごく少数の制約条件に対策を打つことで、最小の手間/時間で最大の改善効果を得ることができるマネジメント手法となります。

また、『どんなに複雑なシステムでも、常に、ごく少数の要素に支配されている』という仮定から出発した包括的な経営哲学であり、「ごく少数の要素」つまりそれは「制約条件」がシステム(組織)のパフォーマンスの鍵を握っていることを指します。

Being Consulting - TOCとは

TOCの中に、Full Kit(万全の準備)という考え方があります。これは、製品やシステムの開発段階で、ボトルネックになるであろう要素を洗い出しておき、事前にその問題を解決しておくことで、開発をスムーズに行うというものです。

Web開発を行う中で、どんな要素がボトルネックになるだろう?と考えたとき、「コアの機能の開発に手間取ったらヤバいな」と思い至りました。

そこで開発前に、本サービスのコアとなり得る下記の機能を先に開発し、検証しておくことにしました。

  • FullCalendarを活用したUI
  • タイムスケジュールの性能評価手法
  • 自動計算機能

具体的には、FullCalendarのドキュメントを隅まで読み込み、本当に自分の達成したい機能をカバーできるのか、各機能はどのメソッドを使って達成するのかをリストアップしました。
その上でUIのプロトタイプを作成し、問題なく動くことを確認しています。

タイムスケジュールの性能評価や自動計算機能についても、jupyter notebook上でテストデータを作成し、アルゴリズムや計算式を先に固めておきました。

このように開発前に不安要素を取り除いておくことで、「たぶんできる」という見込みを持った状態で開発に進むことができました

言われてみれば当たり前の話ではあるのですが、経験の少ない身としてはこういった取り組みをしておいて非常によかったなと思います。

設定ファイルは分けよう

djangoは、デフォルトだとsettings.pyという単一のファイルでDB設定や環境変数を管理するのですが、今回はローカル開発用/動作確認用/本番用の3つに設定ファイルを分割しました。

どの設定ファイルを読み込むかはdotenvで制御します。これで随分管理が楽になりました。

詳しい実装方法は下記の記事をご参照ください。とっても助かりました。ありがとうございます。

諦めない

結局ここに尽きるかと思いました。

マネージドクラウドなど、お膳立てされたサーバーに載せたことはありますが、VPSを設定してデプロイしたのは初めてで、色々と挫けそうになりました。

システム開発の段階でも、JSプラグインのドキュメントとにらめっこして、先人の知恵を借りつつ一個一個クリアしていったお陰で、リリースまで漕ぎ着けました。

諦めずに調べ続ければ突破口も開けるし、自分の実装や設定のどこに誤りがあったのかもわかり、結果的に勉強にもなります。
何が何でも解決してやるという強い気持ちが、技術力よりも大切なものなんだと痛感しました。

楽しむ!

すみません、こっちの方が大事でしたね!笑

Qiita内の個人開発の記事を読ませて頂くと、多くの方が「モチベーションの維持が大切」と仰っていました。
完成まで数か月はかかるものですから、どんな方法でもモチベーションを保つようにしないといけません。

自分にとっては、半ば趣味として「楽しむ」という心持ちが一番よく当てはまりました。コロナ禍で外出もできず、今までの趣味も制限される中、実益も兼ねたこうした趣味に出会えたことをとても嬉しく思います。

今後の予定

実は、もともとはもっと大規模なサービスを開発していて、そのうちタイムスケジュール作成部分だけを抜き出して先行リリースしたのがこのサービスなのです。
本来リリースしようとしていたのは、

  • サークルメンバーの管理
  • 出演バンドの募集
  • タイムスケジュール作成
  • 広報
  • 出演者間の情報共有
  • アンケート集計

などを一貫して行うことができる、ライブ運営統合管理サービスです。
こちらも開発は継続していますので、5月頃にお披露目できればと思います。


今後もアルゴリズムの改良は続けていきます。
最新情報はTwitterでも発信しておりますので、ご興味ありましたらぜひフォロー頂ければと思います!

Twitter@Stagio_info